Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ECharts pane [doc-build] #1484

Merged
merged 4 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)