Skip to content

Commit 5d1ece2

Browse files
committed
Implement int and float cell types
1 parent 65b7f28 commit 5d1ece2

File tree

11 files changed

+467
-420
lines changed

11 files changed

+467
-420
lines changed

docs/source/index.ipynb

+367-368
Large diffs are not rendered by default.

examples/format.ipynb

+10-3
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616
"outputs": [],
1717
"source": [
1818
"sheet = ipysheet.sheet()\n",
19-
"cell0 = ipysheet.cell(0, 0, 0, numeric_format='0.0', type='numeric')\n",
19+
"cell0 = ipysheet.cell(0, 0, 0, numeric_format='.2', type='float')\n",
2020
"cell1 = ipysheet.cell(1, 0, \"Hello\", type='text')\n",
21-
"cell2 = ipysheet.cell(0, 1, 0.1, numeric_format='0.000', type='numeric')\n",
22-
"cell3 = ipysheet.cell(1, 1, 15.9, numeric_format='0.00', type='numeric')\n",
21+
"cell2 = ipysheet.cell(0, 1, 10000000000, numeric_format='e', type='int')\n",
22+
"cell3 = ipysheet.cell(1, 1, 5.91234314, numeric_format='.4', type='float')\n",
2323
"cell4 = ipysheet.cell(2, 2, \"02/14/2019\", date_format='MM/DD/YYYY', type='date')\n",
2424
"\n",
2525
"sheet"
2626
]
27+
},
28+
{
29+
"cell_type": "code",
30+
"execution_count": null,
31+
"metadata": {},
32+
"outputs": [],
33+
"source": []
2734
}
2835
],
2936
"metadata": {

ipysheet/easy.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"""
66
__all__ = ['sheet', 'current', 'cell', 'calculation', 'row', 'column', 'cell_range', 'hold_cells', 'renderer']
77

8-
import numbers
98
import six
109
from contextlib import contextmanager
1110

@@ -23,15 +22,15 @@
2322

2423
_common_doc = {
2524
'args': """
26-
type (string): Type of cell, options are: text, numeric, checkbox, dropdown, numeric, date, widget.
25+
type (string): Type of cell, options are: text, int, float, checkbox, dropdown, date, widget.
2726
If type is None, the type is inferred from the type of the value being passed,
28-
numeric (float or int type), boolean (bool type), widget (any widget object), or else text.
27+
float, int, boolean (bool type), widget (any widget object), or else text.
2928
When choice is given the type will be assumed to be dropdown.
3029
The types refer (currently) to the handsontable types: https://handsontable.com/docs/6.2.2/demo-custom-renderers.html
3130
color (string): The text color in the cell
3231
background_color (string): The background color in the cell
3332
read_only (bool): Whether the cell is editable or not
34-
numeric_format (string): Numbers format
33+
numeric_format (ipywidgets.widgets.trait_types.NumberFormat): Numbers format, default is 'd' for int cells, '.2f' for float cells
3534
date_format (string): Dates format
3635
time_format (string): Time format
3736
renderer (string): Renderer name to use for the cell
@@ -95,7 +94,7 @@ def current():
9594
@doc_subst(_common_doc)
9695
def cell(row, column, value=0., type=None, color=None, background_color=None,
9796
font_style=None, font_weight=None, style=None, label_left=None, choice=None,
98-
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
97+
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
9998
"""Adds a new ``Cell`` widget to the current ``Sheet``
10099
101100
Args:
@@ -111,7 +110,8 @@ def cell(row, column, value=0., type=None, color=None, background_color=None,
111110
>>> from ipysheet import sheet, cell
112111
>>>
113112
>>> s1 = sheet()
114-
>>> cell(0, 0, 36.) # The Cell type will be 'numeric'
113+
>>> cell(0, 0, 36) # The Cell type will be 'int'
114+
>>> cell(0, 0, 36.3) # The Cell type will be 'float'
115115
>>> cell(1, 0, True) # The Cell type will be 'checkbox'
116116
>>> cell(0, 1, 'Hello World!') # The Cell type will be 'text'
117117
>>> c = cell(1, 1, True)
@@ -121,8 +121,10 @@ def cell(row, column, value=0., type=None, color=None, background_color=None,
121121
if type is None:
122122
if isinstance(value, bool):
123123
type = 'checkbox'
124-
elif isinstance(value, numbers.Number):
125-
type = 'numeric'
124+
elif isinstance(value, int):
125+
type = 'int'
126+
elif isinstance(value, float):
127+
type = 'float'
126128
elif isinstance(value, widgets.Widget):
127129
type = 'widget'
128130
else:
@@ -157,7 +159,7 @@ def cell(row, column, value=0., type=None, color=None, background_color=None,
157159
@doc_subst(_common_doc)
158160
def row(row, value, column_start=0, column_end=None, type=None, color=None, background_color=None,
159161
font_style=None, font_weight=None, style=None, choice=None,
160-
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
162+
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
161163
"""Create a ``Cell`` widget, representing multiple cells in a sheet, in a horizontal row
162164
163165
Args:
@@ -187,7 +189,7 @@ def row(row, value, column_start=0, column_end=None, type=None, color=None, back
187189
@doc_subst(_common_doc)
188190
def column(column, value, row_start=0, row_end=None, type=None, color=None, background_color=None,
189191
font_style=None, font_weight=None, style=None, choice=None,
190-
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
192+
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
191193
"""Create a ``Cell`` widget, representing multiple cells in a sheet, in a vertical column
192194
193195
Args:
@@ -219,7 +221,7 @@ def cell_range(value,
219221
row_start=0, column_start=0, row_end=None, column_end=None, transpose=False,
220222
squeeze_row=False, squeeze_column=False, type=None, color=None, background_color=None,
221223
font_style=None, font_weight=None, style=None, choice=None,
222-
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
224+
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
223225
"""Create a ``Cell`` widget, representing multiple cells in a sheet
224226
225227
Args:
@@ -278,7 +280,8 @@ def cell_range(value,
278280
# see if we an infer a type from the data, otherwise leave it None
279281
if type is None:
280282
type_check_map = [('checkbox', lambda x: isinstance(x, bool)),
281-
('numeric', lambda x: isinstance(x, numbers.Number)),
283+
('int', lambda x: isinstance(x, int)),
284+
('float', lambda x: isinstance(x, float) or isinstance(x, int)),
282285
('text', lambda x: isinstance(x, six.string_types)),
283286
('widget', lambda x: isinstance(x, widgets.Widget)),
284287
]

ipysheet/numpy_loader.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .easy import sheet, column, cell_range
2-
from .utils import extract_data, get_cell_type, get_cell_numeric_format
2+
from .utils import extract_data, get_cell_type
33

44

55
def from_array(array):
@@ -27,7 +27,6 @@ def from_array(array):
2727
columns = 1 if len(array.shape) == 1 else array.shape[1]
2828

2929
kwargs = {
30-
'numeric_format': get_cell_numeric_format(array.dtype),
3130
'type': get_cell_type(array.dtype)
3231
}
3332

ipysheet/pandas_loader.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .sheet import Cell, Sheet
2-
from .utils import extract_data, get_cell_numeric_format, get_cell_type
2+
from .utils import extract_data, get_cell_type
33

44

55
def _format_date(date):
@@ -56,7 +56,6 @@ def from_dataframe(dataframe):
5656
column_start=idx,
5757
column_end=idx,
5858
type=get_cell_type(arr.dtype),
59-
numeric_format=get_cell_numeric_format(arr.dtype),
6059
squeeze_row=False,
6160
squeeze_column=True
6261
))

ipysheet/sheet.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ipywidgets as widgets
22
from ipywidgets.widgets.widget_layout import LayoutTraitType
3+
from ipywidgets.widgets.trait_types import NumberFormat
34

45
import traitlets
56
from traitlets import Unicode, CInt, List, Tuple, Instance, Union, Dict, Bool, Any
@@ -33,7 +34,7 @@ class Cell(widgets.Widget):
3334
squeeze_column = Bool(True).tag(sync=True)
3435
transpose = Bool(False).tag(sync=True)
3536
choice = List(Unicode(), allow_none=True, default_value=None).tag(sync=True)
36-
numeric_format = Unicode('0.000', allow_none=True).tag(sync=True)
37+
numeric_format = NumberFormat(None, allow_none=True).tag(sync=True)
3738
date_format = Unicode('YYYY/MM/DD', allow_none=True).tag(sync=True)
3839
time_format = Unicode('h:mm:ss a', allow_none=True).tag(sync=True)
3940

ipysheet/test_all.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,10 @@ def test_cell_values():
276276

277277
cell = ipysheet.cell(0, 0, value=1.2)
278278
assert cell.value == 1.2
279-
assert cell.type == 'numeric'
279+
assert cell.type == 'float'
280280
cell = ipysheet.cell(0, 0, value=1)
281281
assert cell.value == 1
282-
assert cell.type == 'numeric'
282+
assert cell.type == 'int'
283283

284284
cell = ipysheet.Cell(value='1.2')
285285
assert cell.value == '1.2'
@@ -291,18 +291,18 @@ def test_cell_values():
291291

292292
cell = ipysheet.row(0, [0, 1.2])
293293
assert cell.value == [0, 1.2]
294-
assert cell.type == 'numeric'
294+
assert cell.type == 'float'
295295

296296
cell = ipysheet.row(0, [0, 1])
297297
assert cell.value == [0, 1]
298-
assert cell.type == 'numeric'
298+
assert cell.type == 'int'
299299

300300
cell = ipysheet.row(0, ['a', 'b'])
301301
assert cell.value == ['a', 'b']
302302
assert cell.type == 'text'
303303

304304
cell = ipysheet.row(0, [True, 0])
305-
assert cell.type == 'numeric'
305+
assert cell.type == 'int'
306306

307307
cell = ipysheet.row(0, [True, 'bla'])
308308
assert cell.type is None
@@ -442,21 +442,21 @@ def test_from_dataframe():
442442
sheet = ipysheet.from_dataframe(df)
443443
assert len(sheet.cells) == 7
444444
assert sheet.cells[0].value == [1., 1., 1., 1.]
445-
assert sheet.cells[0].type == 'numeric'
445+
assert sheet.cells[0].type == 'float'
446446
assert sheet.cells[1].value == [None, '2013/01/02', None, '2013/01/02']
447447
assert sheet.cells[1].type == 'date'
448448
assert sheet.cells[2].value == [1., 1., 1., 1.]
449-
assert sheet.cells[2].type == 'numeric'
450-
assert sheet.cells[2].numeric_format == '0.000'
449+
assert sheet.cells[2].type == 'float'
450+
assert sheet.cells[2].numeric_format is None
451451
assert sheet.cells[3].value == [False, True, False, False]
452452
assert sheet.cells[3].type == 'checkbox'
453453
assert sheet.cells[4].value == ['test', 'train', 'test', 'train']
454454
assert sheet.cells[4].type == 'text'
455455
assert sheet.cells[5].value == ['foo', 'foo', 'foo', 'foo']
456456
assert sheet.cells[5].type == 'text'
457457
assert sheet.cells[6].value == [0, 3, 9, 18]
458-
assert sheet.cells[6].type == 'numeric'
459-
assert sheet.cells[6].numeric_format == '0[.]0'
458+
assert sheet.cells[6].type == 'int'
459+
assert sheet.cells[6].numeric_format is None
460460

461461

462462
def test_from_to_dataframe():
@@ -503,7 +503,7 @@ def test_from_array():
503503
arr = np.random.randn(6, 10)
504504
sheet = ipysheet.from_array(arr)
505505
assert len(sheet.cells) == 1
506-
assert sheet.cells[0].type == 'numeric'
506+
assert sheet.cells[0].type == 'float'
507507
assert sheet.cells[0].value is arr
508508
assert sheet.rows == 6
509509
assert sheet.columns == 10

ipysheet/utils.py

+4-12
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,16 @@ def extract_data(sheet):
5050

5151

5252
def get_cell_type(dt):
53-
# TODO Differentiate integer and float? Using custom renderers and
54-
# validators for integers?
5553
# Add support for void type from NumPy?
5654
# See https://handsontable.com/docs/6.2.2/tutorial-cell-types.html
5755
return {
5856
'b': 'checkbox',
59-
'i': 'numeric',
60-
'u': 'numeric',
61-
'f': 'numeric',
62-
'm': 'numeric',
57+
'i': 'int',
58+
'u': 'int',
59+
'f': 'float',
60+
'm': 'float',
6361
'M': 'date',
6462
'S': 'text',
6563
'U': 'text'
6664
}.get(dt.kind, 'text')
6765

68-
69-
def get_cell_numeric_format(dt):
70-
return {
71-
'i': '0[.]0',
72-
'f': '0.000',
73-
}.get(dt.kind)

js/src/sheet.ts

+52-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import * as widgets from '@jupyter-widgets/base';
1+
import * as widgets from '@jupyter-widgets/base';
2+
import * as d3 from 'd3-format';
23
import {cloneDeep, extend, includes as contains, each, debounce, times, map, unzip as transpose} from 'lodash';
34
import {semver_range} from './version';
45
import {RendererModel} from './renderer';
@@ -35,7 +36,7 @@ let CellRangeModel = widgets.WidgetModel.extend({
3536
squeeze_row: true,
3637
squeeze_column: true,
3738
transpose: false,
38-
numeric_format: '0.000',
39+
numeric_format: null,
3940
date_format: 'YYYY/MM/DD',
4041
time_format: 'h:mm:ss a'
4142
});
@@ -143,8 +144,8 @@ let SheetModel = widgets.DOMWidgetModel.extend({
143144
cell_data.options['readOnly'] = cell.get('read_only');
144145
if (cell.get('choice') != null)
145146
cell_data.options['source'] = cell.get('choice')
146-
if (cell.get('numeric_format') && cell.get('type') == 'numeric')
147-
cell_data.options['numericFormat'] = {'pattern': cell.get('numeric_format')};
147+
if (cell.get('numeric_format') && (cell.get('type') == 'int' || cell.get('type') == 'float'))
148+
cell_data.options['numericFormat'] = cell.get('numeric_format');
148149
if (cell.get('date_format') && cell.get('type') == 'date') {
149150
cell_data.options['correctFormat'] = true;
150151
cell_data.options['dateFormat'] = cell.get('date_format') || cell_data.options['dateFormat'];
@@ -262,7 +263,7 @@ let put_values2d = function(grid, values) {
262263
}
263264
};
264265

265-
// calls the original renderer and then applies custom styling
266+
// Custom styled renderer that applies the default renderer then apply the given style on the cell
266267
(Handsontable.renderers as any).registerRenderer('styled', function customRenderer(hotInstance, td, row, column, prop, value, cellProperties) {
267268
let name = cellProperties.original_renderer || cellProperties.type || 'text';
268269
let original_renderer = (Handsontable.renderers as any).getRenderer(name);
@@ -272,6 +273,52 @@ let put_values2d = function(grid, values) {
272273
});
273274
});
274275

276+
277+
// Register `int` and `float` cell types
278+
class IntEditor extends Handsontable.editors.TextEditor {
279+
getValue() {
280+
return parseInt(this.TEXTAREA.value);
281+
}
282+
};
283+
284+
function int_renderer(hotInstance, td, row, column, prop, value, cellProperties) {
285+
const numeric_format = cellProperties.numericFormat || 'd';
286+
td.innerHTML = d3.format(numeric_format)(value);
287+
}
288+
289+
function int_validator(query, callback) {
290+
callback(Number.isInteger(query));
291+
}
292+
293+
(Handsontable.cellTypes as any).registerCellType('int', {
294+
editor: IntEditor,
295+
renderer: int_renderer,
296+
validator: int_validator,
297+
allowInvalid: false
298+
});
299+
300+
class FloatEditor extends Handsontable.editors.TextEditor {
301+
getValue() {
302+
return parseFloat(this.TEXTAREA.value);
303+
}
304+
};
305+
306+
function float_renderer(hotInstance, td, row, column, prop, value, cellProperties) {
307+
const numeric_format = cellProperties.numericFormat || '.2f';
308+
td.innerHTML = d3.format(numeric_format)(value);
309+
}
310+
311+
function float_validator(query, callback) {
312+
callback(typeof query == 'number');
313+
}
314+
315+
(Handsontable.cellTypes as any).registerCellType('float', {
316+
editor: FloatEditor,
317+
renderer: float_renderer,
318+
validator: float_validator,
319+
allowInvalid: false
320+
});
321+
275322
let SheetView = widgets.DOMWidgetView.extend({
276323
render: function() {
277324
// this.widget_view_promises = {}

js/src/test/test_sheet.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,12 @@ describe('sheet', function() {
147147
expect(data[1][2].value, 'when cell.value is change').to.equal(999);
148148
})
149149
it('numeric cell with value zero should indeed have value zero', async function() {
150-
await make_cell.apply(this, [{value: 0.00, type:'numeric'}]);
150+
await make_cell.apply(this, [{value: 0.00, type:'float'}]);
151151
var data = this.sheet.data;
152-
expect(data[1][2].value, 'for initial value').to.equal(0);
152+
expect(data[1][2].value, 'for initial value').to.equal(0.0);
153153
})
154154
it('none cell with should be set', async function() {
155-
var cell = await make_cell.apply(this, [{value: 0.00, type:'numeric'}]);
155+
var cell = await make_cell.apply(this, [{value: 0.00, type:'float'}]);
156156
var data = this.sheet.data;
157157
expect(data[1][2].value, 'for initial value').to.equal(0);
158158
cell.set('value', null);

js/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"noImplicitAny": false,
4-
"lib": ["dom", "es5", "es2015.promise", "es2015.iterable"],
4+
"lib": ["dom", "es5", "es2015"],
55
"strictNullChecks": true,
66
"module": "commonjs",
77
"moduleResolution": "node",

0 commit comments

Comments
 (0)