Skip to content

Commit

Permalink
Implement int and float cell types
Browse files Browse the repository at this point in the history
  • Loading branch information
martinRenou committed May 21, 2019
1 parent 65b7f28 commit b88eacd
Show file tree
Hide file tree
Showing 11 changed files with 468 additions and 421 deletions.
735 changes: 367 additions & 368 deletions docs/source/index.ipynb

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions examples/format.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@
"outputs": [],
"source": [
"sheet = ipysheet.sheet()\n",
"cell0 = ipysheet.cell(0, 0, 0, numeric_format='0.0', type='numeric')\n",
"cell0 = ipysheet.cell(0, 0, 0, numeric_format='.2', type='float')\n",
"cell1 = ipysheet.cell(1, 0, \"Hello\", type='text')\n",
"cell2 = ipysheet.cell(0, 1, 0.1, numeric_format='0.000', type='numeric')\n",
"cell3 = ipysheet.cell(1, 1, 15.9, numeric_format='0.00', type='numeric')\n",
"cell2 = ipysheet.cell(0, 1, 10000000000, numeric_format='e', type='int')\n",
"cell3 = ipysheet.cell(1, 1, 5.91234314, numeric_format='.4', type='float')\n",
"cell4 = ipysheet.cell(2, 2, \"02/14/2019\", date_format='MM/DD/YYYY', type='date')\n",
"\n",
"sheet"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand Down
28 changes: 16 additions & 12 deletions ipysheet/easy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"""
__all__ = ['sheet', 'current', 'cell', 'calculation', 'row', 'column', 'cell_range', 'hold_cells', 'renderer']

import numbers
import six
from contextlib import contextmanager

Expand All @@ -23,15 +22,16 @@

_common_doc = {
'args': """
type (string): Type of cell, options are: text, numeric, checkbox, dropdown, numeric, date, widget.
type (string): Type of cell, options are: text, int, float, checkbox, dropdown, date, widget.
If type is None, the type is inferred from the type of the value being passed,
numeric (float or int type), boolean (bool type), widget (any widget object), or else text.
float, int, boolean (bool type), widget (any widget object), or else text.
When choice is given the type will be assumed to be dropdown.
The types refer (currently) to the handsontable types: https://handsontable.com/docs/6.2.2/demo-custom-renderers.html
color (string): The text color in the cell
background_color (string): The background color in the cell
read_only (bool): Whether the cell is editable or not
numeric_format (string): Numbers format
numeric_format (ipywidgets.widgets.trait_types.NumberFormat): Numbers format, default is 'd' for int cells, '.2f' for
float cells
date_format (string): Dates format
time_format (string): Time format
renderer (string): Renderer name to use for the cell
Expand Down Expand Up @@ -95,7 +95,7 @@ def current():
@doc_subst(_common_doc)
def cell(row, column, value=0., type=None, color=None, background_color=None,
font_style=None, font_weight=None, style=None, label_left=None, choice=None,
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
"""Adds a new ``Cell`` widget to the current ``Sheet``
Args:
Expand All @@ -111,7 +111,8 @@ def cell(row, column, value=0., type=None, color=None, background_color=None,
>>> from ipysheet import sheet, cell
>>>
>>> s1 = sheet()
>>> cell(0, 0, 36.) # The Cell type will be 'numeric'
>>> cell(0, 0, 36) # The Cell type will be 'int'
>>> cell(0, 0, 36.3) # The Cell type will be 'float'
>>> cell(1, 0, True) # The Cell type will be 'checkbox'
>>> cell(0, 1, 'Hello World!') # The Cell type will be 'text'
>>> c = cell(1, 1, True)
Expand All @@ -121,8 +122,10 @@ def cell(row, column, value=0., type=None, color=None, background_color=None,
if type is None:
if isinstance(value, bool):
type = 'checkbox'
elif isinstance(value, numbers.Number):
type = 'numeric'
elif isinstance(value, int):
type = 'int'
elif isinstance(value, float):
type = 'float'
elif isinstance(value, widgets.Widget):
type = 'widget'
else:
Expand Down Expand Up @@ -157,7 +160,7 @@ def cell(row, column, value=0., type=None, color=None, background_color=None,
@doc_subst(_common_doc)
def row(row, value, column_start=0, column_end=None, type=None, color=None, background_color=None,
font_style=None, font_weight=None, style=None, choice=None,
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
"""Create a ``Cell`` widget, representing multiple cells in a sheet, in a horizontal row
Args:
Expand Down Expand Up @@ -187,7 +190,7 @@ def row(row, value, column_start=0, column_end=None, type=None, color=None, back
@doc_subst(_common_doc)
def column(column, value, row_start=0, row_end=None, type=None, color=None, background_color=None,
font_style=None, font_weight=None, style=None, choice=None,
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
"""Create a ``Cell`` widget, representing multiple cells in a sheet, in a vertical column
Args:
Expand Down Expand Up @@ -219,7 +222,7 @@ def cell_range(value,
row_start=0, column_start=0, row_end=None, column_end=None, transpose=False,
squeeze_row=False, squeeze_column=False, type=None, color=None, background_color=None,
font_style=None, font_weight=None, style=None, choice=None,
read_only=False, numeric_format='0.000', date_format='YYYY/MM/DD', renderer=None, **kwargs):
read_only=False, numeric_format=None, date_format='YYYY/MM/DD', renderer=None, **kwargs):
"""Create a ``Cell`` widget, representing multiple cells in a sheet
Args:
Expand Down Expand Up @@ -278,7 +281,8 @@ def cell_range(value,
# see if we an infer a type from the data, otherwise leave it None
if type is None:
type_check_map = [('checkbox', lambda x: isinstance(x, bool)),
('numeric', lambda x: isinstance(x, numbers.Number)),
('int', lambda x: isinstance(x, int)),
('float', lambda x: isinstance(x, float) or isinstance(x, int)),
('text', lambda x: isinstance(x, six.string_types)),
('widget', lambda x: isinstance(x, widgets.Widget)),
]
Expand Down
3 changes: 1 addition & 2 deletions ipysheet/numpy_loader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .easy import sheet, column, cell_range
from .utils import extract_data, get_cell_type, get_cell_numeric_format
from .utils import extract_data, get_cell_type


def from_array(array):
Expand Down Expand Up @@ -27,7 +27,6 @@ def from_array(array):
columns = 1 if len(array.shape) == 1 else array.shape[1]

kwargs = {
'numeric_format': get_cell_numeric_format(array.dtype),
'type': get_cell_type(array.dtype)
}

Expand Down
3 changes: 1 addition & 2 deletions ipysheet/pandas_loader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .sheet import Cell, Sheet
from .utils import extract_data, get_cell_numeric_format, get_cell_type
from .utils import extract_data, get_cell_type


def _format_date(date):
Expand Down Expand Up @@ -56,7 +56,6 @@ def from_dataframe(dataframe):
column_start=idx,
column_end=idx,
type=get_cell_type(arr.dtype),
numeric_format=get_cell_numeric_format(arr.dtype),
squeeze_row=False,
squeeze_column=True
))
Expand Down
3 changes: 2 additions & 1 deletion ipysheet/sheet.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ipywidgets as widgets
from ipywidgets.widgets.widget_layout import LayoutTraitType
from ipywidgets.widgets.trait_types import NumberFormat

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

Expand Down
22 changes: 11 additions & 11 deletions ipysheet/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,10 @@ def test_cell_values():

cell = ipysheet.cell(0, 0, value=1.2)
assert cell.value == 1.2
assert cell.type == 'numeric'
assert cell.type == 'float'
cell = ipysheet.cell(0, 0, value=1)
assert cell.value == 1
assert cell.type == 'numeric'
assert cell.type == 'int'

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

cell = ipysheet.row(0, [0, 1.2])
assert cell.value == [0, 1.2]
assert cell.type == 'numeric'
assert cell.type == 'float'

cell = ipysheet.row(0, [0, 1])
assert cell.value == [0, 1]
assert cell.type == 'numeric'
assert cell.type == 'int'

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

cell = ipysheet.row(0, [True, 0])
assert cell.type == 'numeric'
assert cell.type == 'int'

cell = ipysheet.row(0, [True, 'bla'])
assert cell.type is None
Expand Down Expand Up @@ -442,21 +442,21 @@ def test_from_dataframe():
sheet = ipysheet.from_dataframe(df)
assert len(sheet.cells) == 7
assert sheet.cells[0].value == [1., 1., 1., 1.]
assert sheet.cells[0].type == 'numeric'
assert sheet.cells[0].type == 'float'
assert sheet.cells[1].value == [None, '2013/01/02', None, '2013/01/02']
assert sheet.cells[1].type == 'date'
assert sheet.cells[2].value == [1., 1., 1., 1.]
assert sheet.cells[2].type == 'numeric'
assert sheet.cells[2].numeric_format == '0.000'
assert sheet.cells[2].type == 'float'
assert sheet.cells[2].numeric_format is None
assert sheet.cells[3].value == [False, True, False, False]
assert sheet.cells[3].type == 'checkbox'
assert sheet.cells[4].value == ['test', 'train', 'test', 'train']
assert sheet.cells[4].type == 'text'
assert sheet.cells[5].value == ['foo', 'foo', 'foo', 'foo']
assert sheet.cells[5].type == 'text'
assert sheet.cells[6].value == [0, 3, 9, 18]
assert sheet.cells[6].type == 'numeric'
assert sheet.cells[6].numeric_format == '0[.]0'
assert sheet.cells[6].type == 'int'
assert sheet.cells[6].numeric_format is None


def test_from_to_dataframe():
Expand Down Expand Up @@ -503,7 +503,7 @@ def test_from_array():
arr = np.random.randn(6, 10)
sheet = ipysheet.from_array(arr)
assert len(sheet.cells) == 1
assert sheet.cells[0].type == 'numeric'
assert sheet.cells[0].type == 'float'
assert sheet.cells[0].value is arr
assert sheet.rows == 6
assert sheet.columns == 10
Expand Down
17 changes: 4 additions & 13 deletions ipysheet/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,15 @@ def extract_data(sheet):


def get_cell_type(dt):
# TODO Differentiate integer and float? Using custom renderers and
# validators for integers?
# Add support for void type from NumPy?
# See https://handsontable.com/docs/6.2.2/tutorial-cell-types.html
return {
'b': 'checkbox',
'i': 'numeric',
'u': 'numeric',
'f': 'numeric',
'm': 'numeric',
'i': 'int',
'u': 'int',
'f': 'float',
'm': 'float',
'M': 'date',
'S': 'text',
'U': 'text'
}.get(dt.kind, 'text')


def get_cell_numeric_format(dt):
return {
'i': '0[.]0',
'f': '0.000',
}.get(dt.kind)
57 changes: 52 additions & 5 deletions js/src/sheet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as widgets from '@jupyter-widgets/base';
import * as widgets from '@jupyter-widgets/base';
import * as d3 from 'd3-format';
import {cloneDeep, extend, includes as contains, each, debounce, times, map, unzip as transpose} from 'lodash';
import {semver_range} from './version';
import {RendererModel} from './renderer';
Expand Down Expand Up @@ -35,7 +36,7 @@ let CellRangeModel = widgets.WidgetModel.extend({
squeeze_row: true,
squeeze_column: true,
transpose: false,
numeric_format: '0.000',
numeric_format: null,
date_format: 'YYYY/MM/DD',
time_format: 'h:mm:ss a'
});
Expand Down Expand Up @@ -143,8 +144,8 @@ let SheetModel = widgets.DOMWidgetModel.extend({
cell_data.options['readOnly'] = cell.get('read_only');
if (cell.get('choice') != null)
cell_data.options['source'] = cell.get('choice')
if (cell.get('numeric_format') && cell.get('type') == 'numeric')
cell_data.options['numericFormat'] = {'pattern': cell.get('numeric_format')};
if (cell.get('numeric_format') && (cell.get('type') == 'int' || cell.get('type') == 'float'))
cell_data.options['numericFormat'] = cell.get('numeric_format');
if (cell.get('date_format') && cell.get('type') == 'date') {
cell_data.options['correctFormat'] = true;
cell_data.options['dateFormat'] = cell.get('date_format') || cell_data.options['dateFormat'];
Expand Down Expand Up @@ -262,7 +263,7 @@ let put_values2d = function(grid, values) {
}
};

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


// Register `int` and `float` cell types
class IntEditor extends Handsontable.editors.TextEditor {
getValue() {
return parseInt(this.TEXTAREA.value);
}
};

function int_renderer(hotInstance, td, row, column, prop, value, cellProperties) {
const numeric_format = cellProperties.numericFormat || 'd';
td.innerHTML = d3.format(numeric_format)(value);
}

function int_validator(query, callback) {
callback(Number.isInteger(query));
}

(Handsontable.cellTypes as any).registerCellType('int', {
editor: IntEditor,
renderer: int_renderer,
validator: int_validator,
allowInvalid: false
});

class FloatEditor extends Handsontable.editors.TextEditor {
getValue() {
return parseFloat(this.TEXTAREA.value);
}
};

function float_renderer(hotInstance, td, row, column, prop, value, cellProperties) {
const numeric_format = cellProperties.numericFormat || '.2f';
td.innerHTML = d3.format(numeric_format)(value);
}

function float_validator(query, callback) {
callback(typeof query == 'number');
}

(Handsontable.cellTypes as any).registerCellType('float', {
editor: FloatEditor,
renderer: float_renderer,
validator: float_validator,
allowInvalid: false
});

let SheetView = widgets.DOMWidgetView.extend({
render: function() {
// this.widget_view_promises = {}
Expand Down
6 changes: 3 additions & 3 deletions js/src/test/test_sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,12 @@ describe('sheet', function() {
expect(data[1][2].value, 'when cell.value is change').to.equal(999);
})
it('numeric cell with value zero should indeed have value zero', async function() {
await make_cell.apply(this, [{value: 0.00, type:'numeric'}]);
await make_cell.apply(this, [{value: 0.00, type:'float'}]);
var data = this.sheet.data;
expect(data[1][2].value, 'for initial value').to.equal(0);
expect(data[1][2].value, 'for initial value').to.equal(0.0);
})
it('none cell with should be set', async function() {
var cell = await make_cell.apply(this, [{value: 0.00, type:'numeric'}]);
var cell = await make_cell.apply(this, [{value: 0.00, type:'float'}]);
var data = this.sheet.data;
expect(data[1][2].value, 'for initial value').to.equal(0);
cell.set('value', null);
Expand Down
2 changes: 1 addition & 1 deletion js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"noImplicitAny": false,
"lib": ["dom", "es5", "es2015.promise", "es2015.iterable"],
"lib": ["dom", "es5", "es2015"],
"strictNullChecks": true,
"module": "commonjs",
"moduleResolution": "node",
Expand Down

0 comments on commit b88eacd

Please sign in to comment.