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

Correctly load plotly and vega libraries #309

Merged
merged 12 commits into from
Mar 16, 2019
10 changes: 10 additions & 0 deletions panel/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,18 @@ class panel_extension(_pyviz_extension):

_loaded = False

_imports = {'plotly': 'panel.models.plotly',
'vega': 'panel.models.vega'}

def __call__(self, *args, **params):
# Abort if IPython not found
for arg in args:
if arg not in self._imports:
self.param.warning('%s extension not recognized and '
'will be skipped.' % arg)
else:
__import__(self._imports[arg])

try:
ip = params.pop('ip', None) or get_ipython() # noqa (get_ipython)
except:
Expand Down
1 change: 0 additions & 1 deletion panel/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
files.
"""

from .plots import PlotlyPlot, VegaPlot # noqa
from .state import State # noqa
from .widgets import Audio, FileInput, Player # noqa
27 changes: 27 additions & 0 deletions panel/models/plotly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Defines a custom PlotlyPlot bokeh model to render Plotly plots.
"""
import os

from bokeh.core.properties import Dict, String, List, Any, Instance
from bokeh.models import LayoutDOM, ColumnDataSource

from ..util import CUSTOM_MODELS


class PlotlyPlot(LayoutDOM):
"""
A bokeh model that wraps around a plotly plot and renders it inside
a bokeh plot.
"""

__javascript__ = ['https://cdn.plot.ly/plotly-latest.min.js']

__implementation__ = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'plotly.ts')

data = Dict(String, Any)

data_sources = List(Instance(ColumnDataSource))


CUSTOM_MODELS['panel.models.plotly.PlotlyPlot'] = PlotlyPlot
30 changes: 1 addition & 29 deletions panel/models/plotly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,16 @@ import {HTMLBox, HTMLBoxView} from "models/layouts/html_box"

export class PlotlyPlotView extends HTMLBoxView {
model: PlotlyPlot
protected _initialized: boolean

initialize(options: any): void {
super.initialize(options)
const url = "https://cdn.plot.ly/plotly-latest.min.js"

this._initialized = false;
if ((window as any).Plotly) {
this._init()
} else if (((window as any).Jupyter !== undefined) && ((window as any).Jupyter.notebook !== undefined)) {
(window as any).require.config({
paths: {
Plotly: url.slice(0, -3)
}
});
(window as any).require(["Plotly"], (Plotly: any) => {
(window as any).Plotly = Plotly
this._init()
})
} else {
const script: any = document.createElement('script')
script.src = url
script.async = false
script.onreadystatechange = script.onload = () => { this._init() }
(document.querySelector("head") as any).appendChild(script)
}
}

_init(): void {
this._plot()
this._initialized = true
this.connect(this.model.properties.data.change, this._plot)
}

render(): void {
super.render()
if (this._initialized)
this._plot()
this._plot()
}

_plot(): void {
Expand Down
28 changes: 28 additions & 0 deletions panel/models/vega.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Defines custom VegaPlot bokeh model to render Vega json plots.
"""
import os

from bokeh.core.properties import Dict, String, Any, Instance
from bokeh.models import LayoutDOM, ColumnDataSource

from ..util import CUSTOM_MODELS

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

__javascript__ = ["https://cdnjs.cloudflare.com/ajax/libs/vega/5.2.0/vega.min.js",
'https://cdnjs.cloudflare.com/ajax/libs/vega-lite/2.6.0/vega-lite.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/vega-embed/3.30.0/vega-embed.min.js']

__implementation__ = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'vega.ts')

data = Dict(String, Any)

data_sources = Dict(String, Instance(ColumnDataSource))


CUSTOM_MODELS['panel.models.vega.VegaPlot'] = VegaPlot
44 changes: 1 addition & 43 deletions panel/models/vega.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,10 @@ import {HTMLBox, HTMLBoxView} from "models/layouts/html_box"

export class VegaPlotView extends HTMLBoxView {
model: VegaPlot
protected _initialized: boolean

initialize(options: any): void {
super.initialize(options)
const vega_url = "https://cdn.jsdelivr.net/npm/vega@4.2.0?noext"
const vega_lite_url = "https://cdn.jsdelivr.net/npm/vega-lite@3.0.0-rc4?noext"
const vega_embed_url = "https://cdn.jsdelivr.net/npm/vega-embed@3.18.2?noext"

this._initialized = false;
if ((window as any).vega) {
this._init()
} else if (((window as any).Jupyter !== undefined) && ((window as any).Jupyter.notebook !== undefined)) {
(window as any).requirejs.config({
paths: {
"vega-embed": vega_embed_url,
"vega-lib": "https://cdn.jsdelivr.net/npm/vega-lib?noext",
"vega-lite": vega_lite_url,
"vega": vega_url
}
});
(window as any).require(["vega-embed", "vega", "vega-lite"], (vegaEmbed: any, vega: any, vegaLite: any) => {
(window as any).vega = vega
(window as any).vl = vegaLite
(window as any).vegaEmbed = vegaEmbed
this._init()
})
} else {
const init = () => { this._init() }
const load_vega_embed = () => { this._add_script(vega_embed_url, init) }
const load_vega_lite = () => { this._add_script(vega_lite_url, load_vega_embed) }
this._add_script(vega_url, load_vega_lite)
}
}

_add_script(url: string, callback: any): void {
const script: any = document.createElement('script')
script.src = url
script.async = false
script.onreadystatechange = script.onload = callback;
(document.querySelector("head") as any).appendChild(script)
}

_init(): void {
this._plot()
this._initialized = true
this.connect(this.model.properties.data.change, this._plot)
}

Expand All @@ -71,8 +30,7 @@ export class VegaPlotView extends HTMLBoxView {

render(): void {
super.render()
if (this._initialized)
this._plot()
this._plot()
}

_plot(): void {
Expand Down
4 changes: 4 additions & 0 deletions panel/pane/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ def _synced_params(self):
ignored_params = ['name', 'default_layout']+self._rerender_params
return [p for p in self.param if p not in ignored_params]

def _init_properties(self):
return {k: v for k, v in self.param.get_param_values()
if v is not None and k not in ['default_layout', 'object']}

def _update_object(self, old_model, doc, root, parent, comm):
if self._updates:
self._update(old_model)
Expand Down
15 changes: 14 additions & 1 deletion panel/pane/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"""
from __future__ import absolute_import, division, unicode_literals

import sys

import numpy as np

from bokeh.models import ColumnDataSource
from pyviz_comms import JupyterComm

from ..models import PlotlyPlot
from .base import PaneBase


Expand Down Expand Up @@ -59,6 +61,17 @@ def _get_model(self, doc, root=None, parent=None, comm=None):
"""
Should return the bokeh model to be rendered.
"""
if 'panel.models.plotly' not in sys.modules:
if isinstance(comm, JupyterComm):
self.param.warning('PlotlyPlot 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(\'plotly\')\n')
from ..models.plotly import PlotlyPlot
else:
PlotlyPlot = getattr(sys.modules['panel.models.plotly'], 'PlotlyPlot')

if self.object is None:
json, sources = None, []
else:
Expand Down
23 changes: 20 additions & 3 deletions panel/pane/vega.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import sys

import param
import numpy as np

from bokeh.models import ColumnDataSource
from pyviz_comms import JupyterComm

from ..models import VegaPlot
from .base import PaneBase


Expand All @@ -33,6 +34,11 @@ class Vega(PaneBase):
the figure on bokeh server and via Comms.
"""

margin = param.Parameter(default=(5, 5, 30, 5), doc="""
Allows to create additional space around the component. May
be specified as a two-tuple of the form (vertical, horizontal)
or a four-tuple (top, right, bottom, left).""")

priority = 0.8

_updates = True
Expand Down Expand Up @@ -78,14 +84,26 @@ def _get_sources(self, json, sources):
sources['data'] = ColumnDataSource(data=ds_as_cds(data))

def _get_model(self, doc, root=None, parent=None, comm=None):
if 'panel.models.vega' not in sys.modules:
if isinstance(comm, JupyterComm):
self.param.warning('VegaPlot 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(\'vega\')\n')
from ..models.plots import VegaPlot
else:
VegaPlot = getattr(sys.modules['panel.models.vega'], 'VegaPlot')

sources = {}
if self.object is None:
json = None
else:
json = self._to_json(self.object)
json['data'] = dict(json['data'])
self._get_sources(json, sources)
model = VegaPlot(data=json, data_sources=sources)
props = self._process_param_change(self._init_properties())
model = VegaPlot(data=json, data_sources=sources, **props)
if root is None:
root = model
self._models[root.ref['id']] = (model, parent)
Expand All @@ -98,4 +116,3 @@ def _update(self, model):
json = self._to_json(self.object)
self._get_sources(json, model.data_sources)
model.data = json

2 changes: 1 addition & 1 deletion panel/tests/test_plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import numpy as np

from panel.models import PlotlyPlot
from panel.models.plotly import PlotlyPlot
from panel.pane import Pane, PaneBase, Plotly


Expand Down
2 changes: 1 addition & 1 deletion panel/tests/test_vega.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import numpy as np

from panel.models import VegaPlot
from panel.models.vega import VegaPlot
from panel.pane import Pane, PaneBase, Vega

vega_example = {
Expand Down
18 changes: 6 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from setuptools import setup, find_packages
from setuptools.command.develop import develop
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info

import pyct.build

Expand All @@ -34,8 +33,14 @@ def build_custom_models():
Compiles custom bokeh models and stores the compiled JSON alongside
the original code.
"""
from panel.io import panel_extension
from panel.util import CUSTOM_MODELS
from bokeh.util.compiler import _get_custom_models, _compile_models

# Ensure that all optional models are loaded
for imp in panel_extension._imports.values():
__import__(imp)

custom_models = _get_custom_models(list(CUSTOM_MODELS.values()))
compiled_models = _compile_models(custom_models)
for name, model in custom_models.items():
Expand Down Expand Up @@ -70,16 +75,6 @@ def run(self):
print("Custom model compilation failed with: %s" % e)
install.run(self)

class CustomEggInfoCommand(egg_info):
"""Custom installation for egg_info mode."""
def run(self):
try:
print("Building custom models:")
build_custom_models()
except ImportError as e:
print("Custom model compilation failed with: %s" % e)
egg_info.run(self)

########## dependencies ##########

install_requires = [
Expand Down Expand Up @@ -155,7 +150,6 @@ def run(self):
cmdclass={
'develop': CustomDevelopCommand,
'install': CustomInstallCommand,
'egg_info': CustomEggInfoCommand
},
packages=find_packages(),
include_package_data=True,
Expand Down