2017-09-23 16 views
3

Je travaille actuellement sur un projet où je voudrais sortir un nuage de points interactif 3D en utilisant Bokeh. Je voudrais colorier les points basés sur 2 ou 3 catégories, et je voudrais montrer le gène correspondant au point après l'avoir plané. Je suis conscient du fait que Bokeh n'implémente pas complètement les tracés 3D, et j'ai trouvé le script suivant, qui permet de produire un tel tracé 3D avec python (original code).Implémentation d'une diffusion 3D complète avec bokeh en python

Bien que le code original produise une surface 3D, avec quelques lectures de documentation j'ai réussi à produire un tracé 3D. J'ai également réussi à colorier les points en fonction des catégories. Cependant, lorsque j'essaie de produire des info-bulles, dont les informations seront encodées dans la variable 'extra' dans python (ou dans tout autre), je ne peux pas produire cette information. Je n'ai aucune connaissance de JS, alors j'essaie juste de modifier les variables pour voir ce qui se passe.

Le code que produit est celui-ci:

from __future__ import division 
from bokeh.core.properties import Instance, String 
from bokeh.models import ColumnDataSource, LayoutDOM 
from bokeh.io import show 
import numpy as np 


JS_CODE = """ 
# This file contains the JavaScript (CoffeeScript) implementation 
# for a Bokeh custom extension. The "surface3d.py" contains the 
# python counterpart. 
# 
# This custom model wraps one part of the third-party vis.js library: 
# 
#  http://visjs.org/index.html 
# 
# Making it easy to hook up python data analytics tools (NumPy, SciPy, 
# Pandas, etc.) to web presentations using the Bokeh server. 

# These "require" lines are similar to python "import" statements 
import * as p from "core/properties" 
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom" 

# This defines some default options for the Graph3d feature of vis.js 
# See: http://visjs.org/graph3d_examples.html for more details. 
OPTIONS = 
    width: '700px' 
    height: '700px' 
    style: 'dot-color' 
    showPerspective: true 
    showGrid: true 
    keepAspectRatio: true 
    verticalRatio: 1.0 
    showLegend: false 
    cameraPosition: 
    horizontal: -0.35 
    vertical: 0.22 
    distance: 1.8 

    dotSizeRatio: 0.01 

    tooltip: true #(point) -> return 'value: <b>' + point.z + '</b><br>' + point.data.extra 




# To create custom model extensions that will render on to the HTML canvas 
# or into the DOM, we must create a View subclass for the model. Currently 
# Bokeh models and views are based on BackBone. More information about 
# using Backbone can be found here: 
# 
#  http://backbonejs.org/ 
# 
# In this case we will subclass from the existing BokehJS ``LayoutDOMView``, 
# corresponding to our 
export class Surface3dView extends LayoutDOMView 

    initialize: (options) -> 
    super(options) 

    url = "https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js" 

    script = document.createElement('script') 
    script.src = url 
    script.async = false 
    script.onreadystatechange = script.onload =() => @_init() 
    document.querySelector("head").appendChild(script) 

    _init:() -> 
    # Create a new Graph3s using the vis.js API. This assumes the vis.js has 
    # already been loaded (e.g. in a custom app template). In the future Bokeh 
    # models will be able to specify and load external scripts automatically. 
    # 
    # Backbone Views create <div> elements by default, accessible as @el. Many 
    # Bokeh views ignore this default <div>, and instead do things like draw 
    # to the HTML canvas. In this case though, we use the <div> to attach a 
    # Graph3d to the DOM. 
    @_graph = new vis.Graph3d(@el, @get_data(), OPTIONS) 

    # Set Backbone listener so that when the Bokeh data source has a change 
    # event, we can process the new data 
    @connect(@model.data_source.change,() => 
     @_graph.setData(@get_data()) 
    ) 

    # This is the callback executed when the Bokeh data has an change. Its basic 
    # function is to adapt the Bokeh data source to the vis.js DataSet format. 
    get_data:() -> 
    data = new vis.DataSet() 
    source = @model.data_source 
    for i in [0...source.get_length()] 
     data.add({ 
     x:  source.get_column(@model.x)[i] 
     y:  source.get_column(@model.y)[i] 
     z:  source.get_column(@model.z)[i] 
     extra: source.get_column(@model.extra)[i] 
     style: source.get_column(@model.color)[i] 
     }) 
    return data 

# We must also create a corresponding JavaScript Backbone model sublcass to 
# correspond to the python Bokeh model subclass. In this case, since we want 
# an element that can position itself in the DOM according to a Bokeh layout, 
# we subclass from ``LayoutDOM`` 
export class Surface3d extends LayoutDOM 

    # This is usually boilerplate. In some cases there may not be a view. 
    default_view: Surface3dView 

    # The ``type`` class attribute should generally match exactly the name 
    # of the corresponding Python class. 
    type: "Surface3d" 

    # The @define block adds corresponding "properties" to the JS model. These 
    # should basically line up 1-1 with the Python model class. Most property 
    # types have counterparts, e.g. ``bokeh.core.properties.String`` will be 
    # ``p.String`` in the JS implementatin. Where the JS type system is not yet 
    # as rich, you can use ``p.Any`` as a "wildcard" property type. 
    @define { 
    x:   [ p.String   ] 
    y:   [ p.String   ] 
    z:   [ p.String   ] 
    color:  [ p.String   ] 
    extra:  [ p.String   ] 
    data_source: [ p.Instance   ] 
    } 
""" 

# This custom extension model will have a DOM view that should layout-able in 
# Bokeh layouts, so use ``LayoutDOM`` as the base class. If you wanted to create 
# a custom tool, you could inherit from ``Tool``, or from ``Glyph`` if you 
# wanted to create a custom glyph, etc. 
class Surface3d(LayoutDOM): 

    # The special class attribute ``__implementation__`` should contain a string 
    # of JavaScript (or CoffeeScript) code that implements the JavaScript side 
    # of the custom extension model. 
    __implementation__ = JS_CODE 

    # Below are all the "properties" for this model. Bokeh properties are 
    # class attributes that define the fields (and their types) that can be 
    # communicated automatically between Python and the browser. Properties 
    # also support type validation. More information about properties in 
    # can be found here: 
    # 
    # https://bokeh.pydata.org/en/latest/docs/reference/core.html#bokeh-core-properties 

    # This is a Bokeh ColumnDataSource that can be updated in the Bokeh 
    # server by Python code 
    data_source = Instance(ColumnDataSource) 

    # The vis.js library that we are wrapping expects data for x, y, z, and 
    # color. The data will actually be stored in the ColumnDataSource, but 
    # these properties let us specify the *name* of the column that should 
    # be used for each field. 
    x = String 
    y = String 
    z = String 
    extra = String 
    color = String 




X_data = np.random.normal(0,10,100) 
Y_data = np.random.normal(0,5,100) 
Z_data = np.random.normal(0,3,100) 
color = np.asarray([0 for x in range(50)]+[1 for x in range(50)]) 
extra = np.asarray(['a' for x in range(50)]+['b' for x in range(50)]) 


source = ColumnDataSource(data=dict(x=X_data, y=Y_data, z=Z_data, color = color, extra=extra)) 

surface = Surface3d(x="x", y="y", z="z", extra="extra", color="color", data_source=source) 

show(surface) 

Compte tenu de cela, ma sortie idéalisée du projet devrait être:

  1. Produire les info-bulles correct, avec le gène correspondant à la valeur.
  2. Complémentairement, ajoutez dans l'info-bulle la catégorie à laquelle appartient le point (si 1. peut être fait, je n'aurais aucun problème à faire celui-ci).
  3. En quelque sorte, supprimez la barre de couleurs (légende), dont je n'aurai pas besoin. Il ne disparaît pas lorsque la valeur showLegend est définie sur false.

Merci d'avance.

Répondre

3

Il y a donc deux petites modifications à faire pour obtenir ce que vous voulez. Je pense que le plus important est la version de visjs utilisée.

Modifier l'URL pour url = "http://visjs.org/dist/vis.js"

En second lieu, la déclaration de fonction pour infobulle devrait être modifiée pour:

tooltip: (point) -> return 'value: <b>' + point.z + '</b><br>' + 'extra: <b>' + point.data.extra 

Pas un utilisateur coffeescript, mais qui semble corriger la syntaxe pour utiliser l'info-bulle personnalisé html .

Voici l'exemple mis à jour, si nécessaire.

from __future__ import division 
from bokeh.core.properties import Instance, String 
from bokeh.models import ColumnDataSource, LayoutDOM 
from bokeh.io import show 
import numpy as np 


JS_CODE = """ 
# This file contains the JavaScript (CoffeeScript) implementation 
# for a Bokeh custom extension. The "surface3d.py" contains the 
# python counterpart. 
# 
# This custom model wraps one part of the third-party vis.js library: 
# 
#  http://visjs.org/index.html 
# 
# Making it easy to hook up python data analytics tools (NumPy, SciPy, 
# Pandas, etc.) to web presentations using the Bokeh server. 

# These "require" lines are similar to python "import" statements 
import * as p from "core/properties" 
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom" 

# This defines some default options for the Graph3d feature of vis.js 
# See: http://visjs.org/graph3d_examples.html for more details. 
OPTIONS = 
    width: '700px' 
    height: '700px' 
    style: 'dot-color' 
    showPerspective: true 
    showGrid: true 
    keepAspectRatio: true 
    verticalRatio: 1.0 
    showLegend: false 
    cameraPosition: 
    horizontal: -0.35 
    vertical: 0.22 
    distance: 1.8 
    dotSizeRatio: 0.01 
    tooltip: (point) -> return 'value: <b>' + point.z + '</b><br>' + 'extra: <b>' + point.data.extra 




# To create custom model extensions that will render on to the HTML canvas 
# or into the DOM, we must create a View subclass for the model. Currently 
# Bokeh models and views are based on BackBone. More information about 
# using Backbone can be found here: 
# 
#  http://backbonejs.org/ 
# 
# In this case we will subclass from the existing BokehJS ``LayoutDOMView``, 
# corresponding to our 
export class Surface3dView extends LayoutDOMView 

    initialize: (options) -> 
    super(options) 

    url = "http://visjs.org/dist/vis.js" 

    script = document.createElement('script') 
    script.src = url 
    script.async = false 
    script.onreadystatechange = script.onload =() => @_init() 
    document.querySelector("head").appendChild(script) 

    _init:() -> 
    # Create a new Graph3s using the vis.js API. This assumes the vis.js has 
    # already been loaded (e.g. in a custom app template). In the future Bokeh 
    # models will be able to specify and load external scripts automatically. 
    # 
    # Backbone Views create <div> elements by default, accessible as @el. Many 
    # Bokeh views ignore this default <div>, and instead do things like draw 
    # to the HTML canvas. In this case though, we use the <div> to attach a 
    # Graph3d to the DOM. 
    @_graph = new vis.Graph3d(@el, @get_data(), OPTIONS) 

    # Set Backbone listener so that when the Bokeh data source has a change 
    # event, we can process the new data 
    @connect(@model.data_source.change,() => 
     @_graph.setData(@get_data()) 
    ) 

    # This is the callback executed when the Bokeh data has an change. Its basic 
    # function is to adapt the Bokeh data source to the vis.js DataSet format. 
    get_data:() -> 
    data = new vis.DataSet() 
    source = @model.data_source 
    for i in [0...source.get_length()] 
     data.add({ 
     x:  source.get_column(@model.x)[i] 
     y:  source.get_column(@model.y)[i] 
     z:  source.get_column(@model.z)[i] 
     extra: source.get_column(@model.extra)[i] 
     style: source.get_column(@model.color)[i] 
     }) 
    return data 

# We must also create a corresponding JavaScript Backbone model sublcass to 
# correspond to the python Bokeh model subclass. In this case, since we want 
# an element that can position itself in the DOM according to a Bokeh layout, 
# we subclass from ``LayoutDOM`` 
export class Surface3d extends LayoutDOM 

    # This is usually boilerplate. In some cases there may not be a view. 
    default_view: Surface3dView 

    # The ``type`` class attribute should generally match exactly the name 
    # of the corresponding Python class. 
    type: "Surface3d" 

    # The @define block adds corresponding "properties" to the JS model. These 
    # should basically line up 1-1 with the Python model class. Most property 
    # types have counterparts, e.g. ``bokeh.core.properties.String`` will be 
    # ``p.String`` in the JS implementatin. Where the JS type system is not yet 
    # as rich, you can use ``p.Any`` as a "wildcard" property type. 
    @define { 
    x:   [ p.String   ] 
    y:   [ p.String   ] 
    z:   [ p.String   ] 
    color:  [ p.String   ] 
    extra:  [ p.String   ] 
    data_source: [ p.Instance   ] 
    } 
""" 

# This custom extension model will have a DOM view that should layout-able in 
# Bokeh layouts, so use ``LayoutDOM`` as the base class. If you wanted to create 
# a custom tool, you could inherit from ``Tool``, or from ``Glyph`` if you 
# wanted to create a custom glyph, etc. 
class Surface3d(LayoutDOM): 

    # The special class attribute ``__implementation__`` should contain a string 
    # of JavaScript (or CoffeeScript) code that implements the JavaScript side 
    # of the custom extension model. 
    __implementation__ = JS_CODE 

    # Below are all the "properties" for this model. Bokeh properties are 
    # class attributes that define the fields (and their types) that can be 
    # communicated automatically between Python and the browser. Properties 
    # also support type validation. More information about properties in 
    # can be found here: 
    # 
    # https://bokeh.pydata.org/en/latest/docs/reference/core.html#bokeh-core-properties 

    # This is a Bokeh ColumnDataSource that can be updated in the Bokeh 
    # server by Python code 
    data_source = Instance(ColumnDataSource) 

    # The vis.js library that we are wrapping expects data for x, y, z, and 
    # color. The data will actually be stored in the ColumnDataSource, but 
    # these properties let us specify the *name* of the column that should 
    # be used for each field. 
    x = String 
    y = String 
    z = String 
    extra = String 
    color = String 




X_data = np.random.normal(0,10,100) 
Y_data = np.random.normal(0,5,100) 
Z_data = np.random.normal(0,3,100) 
color = np.asarray([0 for x in range(50)]+[1 for x in range(50)]) 
extra = np.asarray(['a' for x in range(50)]+['b' for x in range(50)]) 


source = ColumnDataSource(data=dict(x=X_data, y=Y_data, z=Z_data, color = color, extra=extra)) 

surface = Surface3d(x="x", y="y", z="z", extra="extra", color="color", data_source=source) 

show(surface) 
+0

Fonctionne parfaitement! Merci beaucoup! –

+0

pas un problème, j'ai eu le même problème avec la barre de couleurs il y a un certain temps - devinez que cela se résume à un bogue dans la version utilisée dans l'exemple, ou la fonctionnalité n'a pas encore été implémentée. – Anthonydouc