From 7750660ac4bfe9e785e04228ef8b3d237ee2abf1 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 5 Jun 2018 11:32:42 -0700 Subject: [PATCH 1/6] add a cumsum transform to cumulatively sum a single column --- bokeh/models/expressions.py | 16 ++++++- bokeh/transform.py | 9 +++- bokehjs/src/lib/models/expressions/cumsum.ts | 49 ++++++++++++++++++++ bokehjs/src/lib/models/expressions/index.ts | 1 + 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 bokehjs/src/lib/models/expressions/cumsum.ts diff --git a/bokeh/models/expressions.py b/bokeh/models/expressions.py index e96123e22e3..bbd7ccb16b2 100644 --- a/bokeh/models/expressions.py +++ b/bokeh/models/expressions.py @@ -24,7 +24,7 @@ from __future__ import absolute_import from ..core.has_props import abstract -from ..core.properties import Seq, String +from ..core.properties import Bool, Seq, String from ..model import Model @abstract @@ -46,6 +46,20 @@ class Expression(Model): ''' pass +class CumSum(Expression): + ''' An expression for generating arrays by cumulatively summing a single + column from a ``ColumnDataSource``. + + ''' + + field = String(help=""" + + """) + + include_zero = Bool(default=False, help=""" + + """) + class Stack(Expression): ''' An expression for generating arrays by summing different columns from a ``ColumnDataSource``. diff --git a/bokeh/transform.py b/bokeh/transform.py index ecd53e39bd8..cf55848d9bb 100644 --- a/bokeh/transform.py +++ b/bokeh/transform.py @@ -28,7 +28,7 @@ # Bokeh imports from .core.properties import expr, field -from .models.expressions import Stack +from .models.expressions import CumSum, Stack from .models.mappers import CategoricalColorMapper, LinearColorMapper, LogColorMapper from .models.transforms import Dodge, Jitter @@ -50,6 +50,12 @@ # General API #----------------------------------------------------------------------------- +def cumsum(field, include_zero=False): + ''' + + ''' + return expr(CumSum(field=field, include_zero=include_zero)) + def dodge(field_name, value, range=None): ''' Create a ``DataSpec`` dict to apply a client-side ``Jitter`` transformation to a ``ColumnDataSource`` column. @@ -211,6 +217,7 @@ def stack(*fields): coordinate for a ``VBar``. ''' + return expr(Stack(fields=fields)) def transform(field_name, transform): diff --git a/bokehjs/src/lib/models/expressions/cumsum.ts b/bokehjs/src/lib/models/expressions/cumsum.ts new file mode 100644 index 00000000000..d32206f3399 --- /dev/null +++ b/bokehjs/src/lib/models/expressions/cumsum.ts @@ -0,0 +1,49 @@ +import {ColumnarDataSource} from "../sources/columnar_data_source" +import {Expression} from "./expression" +import {Arrayable} from "core/types" +import * as p from "core/properties" + +export namespace CumSum { + export interface Attrs extends Expression.Attrs { + field: string + include_zero: boolean + } + + export interface Props extends Expression.Props {} +} + +export interface CumSum extends CumSum.Attrs {} + +export class CumSum extends Expression { + + properties: CumSum.Props + + constructor(attrs?: Partial) { + super(attrs) + } + + static initClass(): void { + this.prototype.type = "CumSum" + + this.define({ + field: [ p.String ], + include_zero: [ p.Boolean, false ], + }) + } + + _v_compute(source: ColumnarDataSource): Arrayable { + const result = new Float64Array(source.get_length() || 0) + const col = source.data[this.field] + const offset = this.include_zero ? 1 : 0 + if (this.include_zero) + result[0] = 0 + else + result[0] = col[0] + debugger + for (let i = 1; i < result.length; i++) { + result[i] = result[i-1] + col[i-offset] + } + return result + } +} +CumSum.initClass() diff --git a/bokehjs/src/lib/models/expressions/index.ts b/bokehjs/src/lib/models/expressions/index.ts index 5732828ebe7..dbe2dbcf212 100644 --- a/bokehjs/src/lib/models/expressions/index.ts +++ b/bokehjs/src/lib/models/expressions/index.ts @@ -1,2 +1,3 @@ export {Expression} from "./expression" export {Stack} from "./stack" +export {CumSum} from "./cumsum" From 7d2d55fd8ac1bd4226986c15c9515e4290b45be0 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 5 Jun 2018 12:22:43 -0700 Subject: [PATCH 2/6] add tests and docs --- bokeh/models/expressions.py | 16 ++++- bokeh/tests/test_transform.py | 22 +++++- bokeh/transform.py | 18 ++++- bokehjs/test/models/expressions/cumsum.coffee | 68 ++++++++++++++++++ bokehjs/test/models/expressions/index.ts | 1 + examples/plotting/file/pie.py | 43 +++++++++++ sphinx/source/_images/gallery/pie_chart.png | Bin 0 -> 35394 bytes sphinx/source/docs/gallery.json | 1 + sphinx/source/docs/releases/0.13.0.rst | 2 + 9 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 bokehjs/test/models/expressions/cumsum.coffee create mode 100644 examples/plotting/file/pie.py create mode 100644 sphinx/source/_images/gallery/pie_chart.png diff --git a/bokeh/models/expressions.py b/bokeh/models/expressions.py index bbd7ccb16b2..270a6dd9084 100644 --- a/bokeh/models/expressions.py +++ b/bokeh/models/expressions.py @@ -53,10 +53,24 @@ class CumSum(Expression): ''' field = String(help=""" - + The name of a ColumnDataSource column to cumulatively sum for new values. """) include_zero = Bool(default=False, help=""" + Whether to include zero at the start of the result. Note that the length + of the result is always the same as the input column. Therefore if this + property is True, then the last value of the column will not be included + in the sum. + + .. code-block:: python + + source = ColumnDataSource(data=dict(foo=[1, 2, 3, 4])) + + CumSum('foo') + # -> [1, 3, 6, 10] + + CumSum('foo', True) + # -> [0, 1, 3, 6] """) diff --git a/bokeh/tests/test_transform.py b/bokeh/tests/test_transform.py index e145c667ac4..bd2ab4aabc5 100644 --- a/bokeh/tests/test_transform.py +++ b/bokeh/tests/test_transform.py @@ -22,7 +22,7 @@ # External imports # Bokeh imports -from bokeh.models import CategoricalColorMapper, Dodge, FactorRange, Jitter, LinearColorMapper, LogColorMapper, Stack +from bokeh.models import CategoricalColorMapper, CumSum, Dodge, FactorRange, Jitter, LinearColorMapper, LogColorMapper, Stack from bokeh.util.testing import verify_all # Module under test @@ -33,6 +33,7 @@ #----------------------------------------------------------------------------- ALL = ( + 'cumsum', 'dodge', 'factor_cmap', 'jitter', @@ -48,6 +49,25 @@ Test___all__ = verify_all(bt, ALL) +class Test_cumsum(object): + + def test_basic(object): + s = bt.cumsum("foo") + assert isinstance(s, dict) + assert list(s.keys()) == ["expr"] + assert isinstance(s['expr'], CumSum) + assert s['expr'].field == 'foo' + assert s['expr'].include_zero == False + + def test_include_zero(object): + s = bt.cumsum("foo", include_zero=True) + assert isinstance(s, dict) + assert list(s.keys()) == ["expr"] + assert isinstance(s['expr'], CumSum) + assert s['expr'].field == 'foo' + assert s['expr'].include_zero == True + + class Test_dodge(object): def test_basic(self): diff --git a/bokeh/transform.py b/bokeh/transform.py index cf55848d9bb..9e46535b811 100644 --- a/bokeh/transform.py +++ b/bokeh/transform.py @@ -37,6 +37,7 @@ #----------------------------------------------------------------------------- __all__ = ( + 'cumsum', 'dodge', 'factor_cmap', 'jitter', @@ -51,7 +52,22 @@ #----------------------------------------------------------------------------- def cumsum(field, include_zero=False): - ''' + ''' Create a Create a ``DataSpec`` dict to generate a ``CumSum`` expression + for a ``ColumnDataSource``. + + Examples: + + .. code-block:: python + + p.wedge(start_angle=cumsum('angle', include_zero=True), + end_angle=cumsum('angle'), + ...) + + will generate a ``CumSum`` expressions that sum the ``"angle"`` column + of a data source. For the ``start_angle`` value, the cumulative sums + will start with a zero value. For ``start_angle``, no initial zero will + be added (i.e. the sums will start with the first angle value, and + include the last). ''' return expr(CumSum(field=field, include_zero=include_zero)) diff --git a/bokehjs/test/models/expressions/cumsum.coffee b/bokehjs/test/models/expressions/cumsum.coffee new file mode 100644 index 00000000000..b318e0a3e1d --- /dev/null +++ b/bokehjs/test/models/expressions/cumsum.coffee @@ -0,0 +1,68 @@ +{expect} = require "chai" + +{ColumnDataSource} = require("models/sources/column_data_source") +{CumSum} = require("models/expressions/cumsum") + +describe "CumSum", -> + + it "should should compute for a source", -> + source = new ColumnDataSource({data: {foo: [1, 2, 3, 4]}}) + s = new CumSum({field: 'foo'}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([1, 3, 6, 10]) + + s = new CumSum({field: 'foo', include_zero: true}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([0, 1, 3, 6]) + + it "should should compute for different sources", -> + source1 = new ColumnDataSource({data: {foo: [1, 2, 3, 4]}}) + source2 = new ColumnDataSource({data: {foo: [10, 20, 30, 40]}}) + s = new CumSum({field: 'foo'}) + ret = s.v_compute(source1) + expect(ret).to.deep.equal new Float64Array([1, 3, 6, 10]) + + s = new CumSum({field: 'foo', include_zero: true}) + ret = s.v_compute(source1) + expect(ret).to.deep.equal new Float64Array([0, 1, 3, 6]) + s = new CumSum({field: 'foo'}) + ret = s.v_compute(source2) + expect(ret).to.deep.equal new Float64Array([10, 30, 60, 100]) + + s = new CumSum({field: 'foo', include_zero: true}) + ret = s.v_compute(source2) + expect(ret).to.deep.equal new Float64Array([0, 10, 30, 60]) + + it "should should re-compute if a source changes", -> + source = new ColumnDataSource({data: {foo: [1, 2, 3, 4]}}) + s = new CumSum({field: 'foo'}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([1, 3, 6, 10]) + + source.data = {foo: [10, 20, 30, 40]} + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([10, 30, 60, 100]) + + it "should should re-compute if a source patches", -> + source = new ColumnDataSource({data: {foo: [1, 2, 3, 4]}}) + s = new CumSum({field: 'foo'}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([1, 3, 6, 10]) + + source.patch({"foo": [[1, 12]]}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([1, 13, 16, 20]) + + source.patch({"foo": [[0, 1.1]]}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([1.1, 13.1, 16.1, 20.1]) + + it "should should re-compute if a source streams", -> + source = new ColumnDataSource({data: {foo: [1, 2, 3, 4]}}) + s = new CumSum({field: 'foo'}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([1, 3, 6, 10]) + + source.stream({foo: [5]}) + ret = s.v_compute(source) + expect(ret).to.deep.equal new Float64Array([1, 3, 6, 10, 15]) diff --git a/bokehjs/test/models/expressions/index.ts b/bokehjs/test/models/expressions/index.ts index 3c531f1ab6a..2cd02f7555c 100644 --- a/bokehjs/test/models/expressions/index.ts +++ b/bokehjs/test/models/expressions/index.ts @@ -1 +1,2 @@ +import "./cumsum" import "./stack" diff --git a/examples/plotting/file/pie.py b/examples/plotting/file/pie.py new file mode 100644 index 00000000000..c7740b4762b --- /dev/null +++ b/examples/plotting/file/pie.py @@ -0,0 +1,43 @@ +from collections import Counter +from math import pi + +import pandas as pd + +from bokeh.io import output_file, show +from bokeh.palettes import Category20c +from bokeh.plotting import figure +from bokeh.transform import cumsum + +output_file("pie.py") + +x = Counter({ + 'United States': 157, + 'United Kingdom': 93, + 'Japan': 89, + 'China': 63, + 'Germany': 44, + 'India': 42, + 'Italy': 40, + 'Australia': 35, + 'Brazil': 32, + 'France': 31, + 'Taiwan': 31, + 'Spain': 29 +}) + +data = pd.DataFrame.from_dict(dict(x), orient='index').reset_index().rename(index=str, columns={0:'value', 'index':'country'}) +data['angle'] = data['value']/sum(x.values()) * 2*pi +data['color'] = Category20c[len(x)] + +p = figure(plot_height=350, title="Pie Chart", toolbar_location=None, + tools="hover", tooltips=[("Country", "@country"),("Value", "@value")]) + +p.wedge(x=0, y=1, radius=0.4, + start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), + line_color="white", fill_color='color', legend='country', source=data) + +p.axis.axis_label=None +p.axis.visible=False +p.grid.grid_line_color = None + +show(p) diff --git a/sphinx/source/_images/gallery/pie_chart.png b/sphinx/source/_images/gallery/pie_chart.png new file mode 100644 index 0000000000000000000000000000000000000000..2f11acb9ff4ecaa39ead924bbb6ea6076249f369 GIT binary patch literal 35394 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 Z9|`E+3C5KsGeE)?D+@bfm6_+Ye*^RCDAND{ literal 0 HcmV?d00001 diff --git a/sphinx/source/docs/gallery.json b/sphinx/source/docs/gallery.json index 95acae09aa8..8588a5d2a52 100644 --- a/sphinx/source/docs/gallery.json +++ b/sphinx/source/docs/gallery.json @@ -6,6 +6,7 @@ { "path": "examples/plotting/file/hex_tile.py", "name": "hex_tile" }, { "path": "examples/plotting/file/bar_colormapped.py", "name": "bar_colormapped" }, { "path": "examples/plotting/file/bar_intervals.py", "name": "bar_intervals" }, + { "path": "examples/plotting/file/pie.py", "name": "pie_chart" }, { "path": "examples/plotting/file/bar_mixed.py", "name": "bar_mixed" }, { "path": "examples/plotting/file/bar_nested_colormapped.py", "name": "bar_nested_colormapped" }, { "path": "examples/plotting/file/categorical.py", "name": "categorical" }, diff --git a/sphinx/source/docs/releases/0.13.0.rst b/sphinx/source/docs/releases/0.13.0.rst index 18f1a015ab0..2f5b47fb90a 100644 --- a/sphinx/source/docs/releases/0.13.0.rst +++ b/sphinx/source/docs/releases/0.13.0.rst @@ -5,6 +5,8 @@ Bokeh Version ``0.13.0`` is an incremental update that adds a few new features and fixes several bugs. Some of the highlights include: * Improved hover tool fields for common stacked bar plot cases +* New ``CumSum`` transform to generate values from cumulative sums + of CDS columns on the client side. And several other bug fixes and docs additions. For full details see the :bokeh-tree:`CHANGELOG`. From c59175147ff6c61c95692f1ac13223b61a7161b6 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 5 Jun 2018 13:10:44 -0700 Subject: [PATCH 3/6] remove errant debugger statement --- bokehjs/src/lib/models/expressions/cumsum.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/bokehjs/src/lib/models/expressions/cumsum.ts b/bokehjs/src/lib/models/expressions/cumsum.ts index d32206f3399..a78643ccf87 100644 --- a/bokehjs/src/lib/models/expressions/cumsum.ts +++ b/bokehjs/src/lib/models/expressions/cumsum.ts @@ -39,7 +39,6 @@ export class CumSum extends Expression { result[0] = 0 else result[0] = col[0] - debugger for (let i = 1; i < result.length; i++) { result[i] = result[i-1] + col[i-offset] } From 7f2a0dc151138aec7add74bebf5249a1d40740bb Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 5 Jun 2018 13:11:55 -0700 Subject: [PATCH 4/6] fix docs mistake --- bokeh/models/expressions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bokeh/models/expressions.py b/bokeh/models/expressions.py index 270a6dd9084..0f3d47fa64c 100644 --- a/bokeh/models/expressions.py +++ b/bokeh/models/expressions.py @@ -66,10 +66,10 @@ class CumSum(Expression): source = ColumnDataSource(data=dict(foo=[1, 2, 3, 4])) - CumSum('foo') + CumSum(field='foo') # -> [1, 3, 6, 10] - CumSum('foo', True) + CumSum(field='foo', include_zero=True) # -> [0, 1, 3, 6] """) From 0c3eaa9beabbab60dbd1d7d99571f2c7b7cbb1fb Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 5 Jun 2018 13:14:41 -0700 Subject: [PATCH 5/6] simplify result initialization expression --- bokehjs/src/lib/models/expressions/cumsum.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bokehjs/src/lib/models/expressions/cumsum.ts b/bokehjs/src/lib/models/expressions/cumsum.ts index a78643ccf87..704b2981892 100644 --- a/bokehjs/src/lib/models/expressions/cumsum.ts +++ b/bokehjs/src/lib/models/expressions/cumsum.ts @@ -35,10 +35,7 @@ export class CumSum extends Expression { const result = new Float64Array(source.get_length() || 0) const col = source.data[this.field] const offset = this.include_zero ? 1 : 0 - if (this.include_zero) - result[0] = 0 - else - result[0] = col[0] + result[0] = this.include_zero ? 0 : col[0] for (let i = 1; i < result.length; i++) { result[i] = result[i-1] + col[i-offset] } From 992b8c23cfe4a19052c04c802511b1522ddb46ab Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 5 Jun 2018 13:38:46 -0700 Subject: [PATCH 6/6] add debugger check to linting --- bokehjs/tslint.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bokehjs/tslint.json b/bokehjs/tslint.json index 718e70a2b53..ec1f44abc95 100644 --- a/bokehjs/tslint.json +++ b/bokehjs/tslint.json @@ -27,6 +27,7 @@ "no-var-keyword": true, "no-string-throw": true, "no-invalid-template-strings": true, - "return-type": true + "return-type": true, + "no-debugger": true } }