|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +""" |
| 4 | +Created on Mon May 21 13:45:34 2018 |
| 5 | +
|
| 6 | +@author: gkanarek |
| 7 | +""" |
| 8 | + |
| 9 | +from bokeh.core.properties import Instance, String, Any, Dict |
| 10 | +from bokeh.models import ColumnDataSource, LayoutDOM |
| 11 | + |
| 12 | +DEFAULTS = { |
| 13 | + 'width': '600px', |
| 14 | + 'height': '600px', |
| 15 | + 'style': 'surface', |
| 16 | + 'showPerspective': True, |
| 17 | + 'showGrid': True, |
| 18 | + 'keepAspectRatio': True, |
| 19 | + 'verticalRatio': 1.0, |
| 20 | + 'legendLabel': 'stuff', |
| 21 | + 'cameraPosition': { |
| 22 | + 'horizontal': -0.35, |
| 23 | + 'vertical': 0.22, |
| 24 | + 'distance': 1.8, |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +JS_CODE = """ |
| 29 | +# This file contains the JavaScript (CoffeeScript) implementation |
| 30 | +# for a Bokeh custom extension. The "surface3d.py" contains the |
| 31 | +# python counterpart. |
| 32 | +# |
| 33 | +# This custom model wraps one part of the third-party vis.js library: |
| 34 | +# |
| 35 | +# http://visjs.org/index.html |
| 36 | +# |
| 37 | +# Making it easy to hook up python data analytics tools (NumPy, SciPy, |
| 38 | +# Pandas, etc.) to web presentations using the Bokeh server. |
| 39 | +
|
| 40 | +# These "require" lines are similar to python "import" statements |
| 41 | +import * as p from "core/properties" |
| 42 | +import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom" |
| 43 | +
|
| 44 | +# This defines some default options for the Graph3d feature of vis.js |
| 45 | +# See: http://visjs.org/graph3d_examples.html for more details. |
| 46 | +OPTIONS = |
| 47 | + width: '600px' |
| 48 | + height: '600px' |
| 49 | + style: 'surface' |
| 50 | + showPerspective: true |
| 51 | + showGrid: true |
| 52 | + keepAspectRatio: true |
| 53 | + verticalRatio: 1.0 |
| 54 | + legendLabel: 'stuff' |
| 55 | + cameraPosition: |
| 56 | + horizontal: -0.35 |
| 57 | + vertical: 0.22 |
| 58 | + distance: 1.8 |
| 59 | +
|
| 60 | +# To create custom model extensions that will render on to the HTML canvas |
| 61 | +# or into the DOM, we must create a View subclass for the model. Currently |
| 62 | +# Bokeh models and views are based on BackBone. More information about |
| 63 | +# using Backbone can be found here: |
| 64 | +# |
| 65 | +# http://backbonejs.org/ |
| 66 | +# |
| 67 | +# In this case we will subclass from the existing BokehJS ``LayoutDOMView``, |
| 68 | +# corresponding to our |
| 69 | +export class Surface3dView extends LayoutDOMView |
| 70 | +
|
| 71 | + initialize: (options) -> |
| 72 | + super(options) |
| 73 | +
|
| 74 | + url = "https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js" |
| 75 | +
|
| 76 | + script = document.createElement('script') |
| 77 | + script.src = url |
| 78 | + script.async = false |
| 79 | + script.onreadystatechange = script.onload = () => @_init() |
| 80 | + document.querySelector("head").appendChild(script) |
| 81 | +
|
| 82 | + _init: () -> |
| 83 | + # Create a new Graph3s using the vis.js API. This assumes the vis.js has |
| 84 | + # already been loaded (e.g. in a custom app template). In the future Bokeh |
| 85 | + # models will be able to specify and load external scripts automatically. |
| 86 | + # |
| 87 | + # Backbone Views create <div> elements by default, accessible as @el. Many |
| 88 | + # Bokeh views ignore this default <div>, and instead do things like draw |
| 89 | + # to the HTML canvas. In this case though, we use the <div> to attach a |
| 90 | + # Graph3d to the DOM. |
| 91 | + @_graph = new vis.Graph3d(@el, @get_data(), @model.options) |
| 92 | +
|
| 93 | + # Set Backbone listener so that when the Bokeh data source has a change |
| 94 | + # event, we can process the new data |
| 95 | + @listenTo(@model.data_source, 'change', () => |
| 96 | + @_graph.setData(@get_data()) |
| 97 | + ) |
| 98 | +
|
| 99 | + # This is the callback executed when the Bokeh data has an change. Its basic |
| 100 | + # function is to adapt the Bokeh data source to the vis.js DataSet format. |
| 101 | + get_data: () -> |
| 102 | + data = new vis.DataSet() |
| 103 | + source = @model.data_source |
| 104 | + for i in [0...source.get_length()] |
| 105 | + data.add({ |
| 106 | + x: source.get_column(@model.x)[i] |
| 107 | + y: source.get_column(@model.y)[i] |
| 108 | + z: source.get_column(@model.z)[i] |
| 109 | + }) |
| 110 | + return data |
| 111 | +
|
| 112 | +# We must also create a corresponding JavaScript Backbone model sublcass to |
| 113 | +# correspond to the python Bokeh model subclass. In this case, since we want |
| 114 | +# an element that can position itself in the DOM according to a Bokeh layout, |
| 115 | +# we subclass from ``LayoutDOM`` |
| 116 | +export class Surface3d extends LayoutDOM |
| 117 | +
|
| 118 | + # This is usually boilerplate. In some cases there may not be a view. |
| 119 | + default_view: Surface3dView |
| 120 | +
|
| 121 | + # The ``type`` class attribute should generally match exactly the name |
| 122 | + # of the corresponding Python class. |
| 123 | + type: "Surface3d" |
| 124 | +
|
| 125 | + # The @define block adds corresponding "properties" to the JS model. These |
| 126 | + # should basically line up 1-1 with the Python model class. Most property |
| 127 | + # types have counterparts, e.g. ``bokeh.core.properties.String`` will be |
| 128 | + # ``p.String`` in the JS implementatin. Where the JS type system is not yet |
| 129 | + # as rich, you can use ``p.Any`` as a "wildcard" property type. |
| 130 | + @define { |
| 131 | + x: [ p.String ] |
| 132 | + y: [ p.String ] |
| 133 | + z: [ p.String ] |
| 134 | + data_source: [ p.Instance ] |
| 135 | + options: [ p.Any, OPTIONS ] |
| 136 | + } |
| 137 | +""" |
| 138 | + |
| 139 | + |
| 140 | +# This custom extension model will have a DOM view that should layout-able in |
| 141 | +# Bokeh layouts, so use ``LayoutDOM`` as the base class. If you wanted to create |
| 142 | +# a custom tool, you could inherit from ``Tool``, or from ``Glyph`` if you |
| 143 | +# wanted to create a custom glyph, etc. |
| 144 | +class Surface3d(LayoutDOM): |
| 145 | + |
| 146 | + # The special class attribute ``__implementation__`` should contain a string |
| 147 | + # of JavaScript (or CoffeeScript) code that implements the JavaScript side |
| 148 | + # of the custom extension model. |
| 149 | + __implementation__ = JS_CODE |
| 150 | + |
| 151 | + # Below are all the "properties" for this model. Bokeh properties are |
| 152 | + # class attributes that define the fields (and their types) that can be |
| 153 | + # communicated automatically between Python and the browser. Properties |
| 154 | + # also support type validation. More information about properties in |
| 155 | + # can be found here: |
| 156 | + # |
| 157 | + # http://bokeh.pydata.org/en/latest/docs/reference/core.html#bokeh-core-properties |
| 158 | + |
| 159 | + # This is a Bokeh ColumnDataSource that can be updated in the Bokeh |
| 160 | + # server by Python code |
| 161 | + data_source = Instance(ColumnDataSource) |
| 162 | + |
| 163 | + # The vis.js library that we are wrapping expects data for x, y, z, and |
| 164 | + # color. The data will actually be stored in the ColumnDataSource, but |
| 165 | + # these properties let us specify the *name* of the column that should |
| 166 | + # be used for each field. |
| 167 | + x = String |
| 168 | + y = String |
| 169 | + z = String |
| 170 | + color = String |
| 171 | + |
| 172 | + options = Dict(String, Any, default=DEFAULTS) |
0 commit comments