Skip to content

Commit

Permalink
Add ECharts pane [doc-build] (#1484)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Sep 17, 2020
1 parent 51474e5 commit 043a25b
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 0 deletions.
184 changes: 184 additions & 0 deletions examples/reference/panes/ECharts.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import panel as pn\n",
"pn.extension('echarts')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ``ECharts`` pane renders [Apache ECharts](https://echarts.apache.org/en/index.html) plots inside Panel. Note that to use the ``ECharts`` pane in the notebook the Panel extension has to be loaded with 'echarts' as an argument to ensure that echarts.js is initialized. \n",
"\n",
"#### Parameters:\n",
"\n",
"For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n",
"\n",
"* **``object``** (dict): A ECharts plot specification expressed as a Python dictionary, which is then converted to JSON\n",
"* **``renderer``** (str): Whether to render with HTML 'canvas' (default) or 'svg'\n",
"* **``theme``** (str): Theme to apply to plots (one of 'default', 'dark', 'light')\n",
"___"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ``ECharts`` pane supports ECharts specs which may be provided in a raw form (i.e. a dictionary), e.g. here we declare a bar plot:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"echart = {\n",
" 'title': {\n",
" 'text': 'ECharts entry example'\n",
" },\n",
" 'tooltip': {},\n",
" 'legend': {\n",
" 'data':['Sales']\n",
" },\n",
" 'xAxis': {\n",
" 'data': [\"shirt\",\"cardign\",\"chiffon shirt\",\"pants\",\"heels\",\"socks\"]\n",
" },\n",
" 'yAxis': {},\n",
" 'series': [{\n",
" 'name': 'Sales',\n",
" 'type': 'bar',\n",
" 'data': [5, 20, 36, 10, 10, 20]\n",
" }],\n",
"};\n",
"echart_pane = pn.pane.ECharts(echart, height=480, width=640)\n",
"echart_pane"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Like all other panes, the ``ECharts`` pane ``object`` can be updated, either in place and triggering an update:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"echart['series'] = [dict(echart['series'][0], type= 'line')]\n",
"echart_pane.param.trigger('object')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vega specification can also be responsively sized by declaring the width or height to match the container:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"responsive_spec = dict(echart, responsive=True)\n",
"\n",
"pn.pane.ECharts(responsive_spec, height=400)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ECharts library supports a wide range of chart types and since the plots are expressed using JSON datastructures we can easily update the data and then emit change events to update the charts:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"gauge = {\n",
" 'tooltip': {\n",
" 'formatter': '{a} <br/>{b} : {c}%'\n",
" },\n",
" 'series': [\n",
" {\n",
" 'name': 'Gauge',\n",
" 'type': 'gauge',\n",
" 'detail': {'formatter': '{value}%'},\n",
" 'data': [{'value': 50, 'name': 'Value'}]\n",
" }\n",
" ]\n",
"};\n",
"gauge_pane = pn.pane.ECharts(gauge, width=400, height=400)\n",
"\n",
"slider = pn.widgets.IntSlider(start=0, end=100)\n",
"\n",
"slider.jscallback(args={'gauge': gauge_pane}, value=\"\"\"\n",
"gauge.data.series[0].data[0].value = cb_obj.value\n",
"gauge.properties.data.change.emit()\n",
"\"\"\")\n",
"\n",
"pn.Column(slider, gauge_pane)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Controls\n",
"\n",
"The `EChart` pane exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pn.Row(gauge_pane.controls(jslink=True), gauge_pane)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.7.5 64-bit ('gv_dev': conda)",
"language": "python",
"name": "python37564bitgvdevcondacc2b7e051cc74b569c7fcfe099557b10"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}
1 change: 1 addition & 0 deletions panel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ class panel_extension(_pyviz_extension):
'vega': 'panel.models.vega',
'vtk': 'panel.models.vtk',
'ace': 'panel.models.ace',
'echarts': 'panel.models.echarts',
'ipywidgets': 'ipywidgets_bokeh.widget'}

_loaded_extensions = []
Expand Down
31 changes: 31 additions & 0 deletions panel/models/echarts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Defines custom ECharts bokeh model to render Vega json plots.
"""
from bokeh.core.properties import Any, Dict, Enum, String
from bokeh.models import LayoutDOM


class ECharts(LayoutDOM):
"""
A Bokeh model that wraps around an ECharts plot and renders it
inside a Bokeh.
"""

__javascript__ = ["https://cdn.jsdelivr.net/npm/echarts@4.8.0/dist/echarts.min.js"]

__js_skip__ = {
'echarts': __javascript__[:1]}

__js_require__ = {
'baseUrl': 'https://cdn.jsdelivr.net/npm/',
'paths': {
"echarts": "echarts@4.8.0/dist/echarts.min"
},
'exports': {}
}

data = Dict(String, Any)

renderer = Enum("canvas", "svg")

theme = Enum("default", "light", "dark")
73 changes: 73 additions & 0 deletions panel/models/echarts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as p from "@bokehjs/core/properties"
import {HTMLBox, HTMLBoxView} from "@bokehjs/models/layouts/html_box"

export class EChartsView extends HTMLBoxView {
model: ECharts
_chart: any

connect_signals(): void {
super.connect_signals()
this.connect(this.model.properties.data.change, () => this._plot())
const {width, height, renderer, theme} = this.model.properties
this.on_change([width, height], () => this._resize())
this.on_change([theme, renderer], () => this._rerender())
}

render(): void {
super.render()
const config = {width: this.model.width, height: this.model.height, renderer: this.model.renderer}
this._chart = (window as any).echarts.init(this.el, this.model.theme, config);
this._plot()
}

after_layout(): void {
super.after_layout()
this._chart.resize()
}

_plot(): void {
if ((window as any).echarts == null)
return
this._chart.setOption(this.model.data);
}

_rerender(): void {
(window as any).echarts.dispose(this._chart);
this.render();
}

_resize(): void {
this._chart.resize({width: this.model.width, height: this.model.height});
}
}

export namespace ECharts {
export type Attrs = p.AttrsOf<Props>
export type Props = HTMLBox.Props & {
data: p.Property<any>
renderer: p.Property<string>
theme: p.Property<string>
}
}

export interface ECharts extends ECharts.Attrs {}

export class ECharts extends HTMLBox {
properties: ECharts.Props

constructor(attrs?: Partial<ECharts.Attrs>) {
super(attrs)
}

static __module__ = "panel.models.echarts"

static init_ECharts(): void {
this.prototype.default_view = EChartsView

this.define<ECharts.Props>({
data: [ p.Any ],
theme: [ p.String, "default" ],
renderer: [ p.String, "canvas"]
})
}
}
1 change: 1 addition & 0 deletions panel/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export {Audio} from "./audio"
export {Card} from "./card"
export {CommManager} from "./comm_manager"
export {DeckGLPlot} from "./deckgl"
export {ECharts} from "./echarts"
export {HTML} from "./html"
export {IPyWidget} from "./ipywidget"
export {JSON} from "./json"
Expand Down
1 change: 1 addition & 0 deletions panel/pane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .base import PaneBase, Pane, panel # noqa
from .equation import LaTeX # noqa
from .deckgl import DeckGL # noqa
from .echarts import ECharts # noqa
from .holoviews import HoloViews # noqa
from .ipywidget import IPyWidget # noqa
from .image import GIF, JPG, PNG, SVG # noqa
Expand Down
72 changes: 72 additions & 0 deletions panel/pane/echarts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from __future__ import absolute_import, division, unicode_literals

import sys

import param

from pyviz_comms import JupyterComm

from ..viewable import Layoutable
from .base import PaneBase


class ECharts(PaneBase):
"""
ECharts panes allow rendering echarts.js plots.
"""

renderer = param.ObjectSelector(default="canvas", objects=["canvas", "svg"], doc="""
Whether to render as HTML canvas or SVG""")

theme = param.ObjectSelector(default="default", objects=["default", "light", "dark"], doc="""
Theme to apply to plots.""")

priority = 0

_rename = {"object": "data"}

_rerender_params = []

_updates = True

@classmethod
def applies(cls, obj):
return isinstance(obj, dict)

@classmethod
def _get_dimensions(cls, json, props):
if json is None:
return
responsive = json.get('responsive')
if responsive:
props['sizing_mode'] = 'stretch_both'
else:
props['sizing_mode'] = 'fixed'

def _get_model(self, doc, root=None, parent=None, comm=None):
if 'panel.models.echarts' not in sys.modules:
if isinstance(comm, JupyterComm):
self.param.warning('EChart was not imported on instantiation '
'and may not render in a notebook. Restart '
'the notebook kernel and ensure you load '
'it as part of the extension using:'
'\n\npn.extension(\'echart\')\n')
from ..models.echarts import ECharts
else:
ECharts = getattr(sys.modules['panel.models.echarts'], 'ECharts')

props = self._process_param_change(self._init_properties())
self._get_dimensions(self.object, props)
data = {} if self.object is None else dict(self.object)
model = ECharts(data=data, **props)
if root is None:
root = model
self._models[root.ref['id']] = (model, parent)
return model

def _update(self, ref=None, model=None):
props = {p : getattr(self, p) for p in list(Layoutable.param)
if getattr(self, p) is not None}
self._get_dimensions(self.object, props)
props['data'] = {} if self.object else dict(self.object)
model.update(**props)

0 comments on commit 043a25b

Please sign in to comment.