Rust Bindings for Bokeh

Hi,
I have started working on a Rust bindings for Bokeh, I have been able to import Bokeh models into Rust by parsing the json outputs from spec.py, but I need help to understand how to serialize the models into a json output that is compatible with BokehJS
Thanks

[Topic moved to Development category]

Hi @Rodolphe_Conan that’s an exciting endeavor, I personally would love to see Rust bindings for Bokeh. However, the question above is a bit to broad and vague to really offer any concrete answers. Can you narrow down to a couple of specific questions related to specific steps you want to accomplish, that could help get you started?

Hi, thanks for your response, ok I am going to try to be more specific, now that I do have all Bokeh models in Rust, I wonder how to put them together and produce an html file like in the following example
from bokeh.plotting import figure, show, output_file
output_file("/tmp/out.html")
p = figure()
p.circle([1, 2, 3], [4, 5, 6])
show§

@Rodolphe_Conan I am afraid I still consider the question way too broad (it’s really just a re-statement of your first post). I don’t know what you already know, and what you don’t already know, so trying to answer the question as-is risks me waste a huge amount of effort only to explaining things that you might already know. We need to proceed step by step from small, atomic, narrow questions about specific technical points with well-defined answers.

  • “What are the different BokehJS APIs for rendering Bokeh content?”
  • “How do those APIs differ?”
  • “What is the smallest unit of serialization?”
  • “Should default values be included in serialized JSON?”
  • “What is the top-level key structure of a Document”

Questions like that are things I can answer.

Ok I see, so lets start with these 2 questions:

  • “What is the smallest unit of serialization?”
  • “What is the top-level key structure of a Document”
    Thank you
What is the smallest unit of serialization

A Document is the smallest meaningful unit of serialization. Although individual Bokeh models have some JSON representation, that representation may also contain references to other models, so it is not in general meaningful to “serialize” a single Bokeh model in isolation. When you add a model as a “root” of a Document you also need to traverse any and all other Bokeh models reachable from that root, and include them in the Document references. Here’s an example of adding a bare plot as a root to a Document:

In [16]: p = figure()

In [17]: doc = Document()

In [18]: doc.add_root(p)

In [19]: doc.to_json()

The resulting output is

{
  "roots": {
    "references": [
      {
        "attributes": {
          "axis": {
            "id": "1013"
          },
          "ticker": null
        },
        "id": "1016",
        "type": "Grid"
      },
      {
        "attributes": {
          "renderers": []
        },
        "id": "1005",
        "type": "DataRange1d"
      },
      {
        "attributes": {},
        "id": "1014",
        "type": "BasicTicker"
      },
      {
        "attributes": {
          "renderers": []
        },
        "id": "1007",
        "type": "DataRange1d"
      },
      {
        "attributes": {
          "formatter": {
            "id": "1040"
          },
          "ticker": {
            "id": "1018"
          }
        },
        "id": "1017",
        "type": "LinearAxis"
      },
      {
        "attributes": {},
        "id": "1040",
        "type": "BasicTickFormatter"
      },
      {
        "attributes": {
          "active_drag": "auto",
          "active_inspect": "auto",
          "active_multi": null,
          "active_scroll": "auto",
          "active_tap": "auto",
          "tools": [
            {
              "id": "1021"
            },
            {
              "id": "1022"
            },
            {
              "id": "1023"
            },
            {
              "id": "1024"
            },
            {
              "id": "1025"
            },
            {
              "id": "1026"
            }
          ]
        },
        "id": "1028",
        "type": "Toolbar"
      },
      {
        "attributes": {},
        "id": "1022",
        "type": "WheelZoomTool"
      },
      {
        "attributes": {
          "bottom_units": "screen",
          "fill_alpha": 0.5,
          "fill_color": "lightgrey",
          "left_units": "screen",
          "level": "overlay",
          "line_alpha": 1,
          "line_color": "black",
          "line_dash": [
            4,
            4
          ],
          "line_width": 2,
          "right_units": "screen",
          "top_units": "screen"
        },
        "id": "1027",
        "type": "BoxAnnotation"
      },
      {
        "attributes": {
          "below": [
            {
              "id": "1013"
            }
          ],
          "center": [
            {
              "id": "1016"
            },
            {
              "id": "1020"
            }
          ],
          "left": [
            {
              "id": "1017"
            }
          ],
          "title": {
            "id": "1035"
          },
          "toolbar": {
            "id": "1028"
          },
          "x_range": {
            "id": "1005"
          },
          "x_scale": {
            "id": "1009"
          },
          "y_range": {
            "id": "1007"
          },
          "y_scale": {
            "id": "1011"
          }
        },
        "id": "1004",
        "subtype": "Figure",
        "type": "Plot"
      },
      {
        "attributes": {
          "overlay": {
            "id": "1027"
          }
        },
        "id": "1023",
        "type": "BoxZoomTool"
      },
      {
        "attributes": {},
        "id": "1009",
        "type": "LinearScale"
      },
      {
        "attributes": {},
        "id": "1025",
        "type": "ResetTool"
      },
      {
        "attributes": {
          "formatter": {
            "id": "1038"
          },
          "ticker": {
            "id": "1014"
          }
        },
        "id": "1013",
        "type": "LinearAxis"
      },
      {
        "attributes": {},
        "id": "1024",
        "type": "SaveTool"
      },
      {
        "attributes": {},
        "id": "1011",
        "type": "LinearScale"
      },
      {
        "attributes": {
          "text": ""
        },
        "id": "1035",
        "type": "Title"
      },
      {
        "attributes": {},
        "id": "1021",
        "type": "PanTool"
      },
      {
        "attributes": {},
        "id": "1026",
        "type": "HelpTool"
      },
      {
        "attributes": {},
        "id": "1018",
        "type": "BasicTicker"
      },
      {
        "attributes": {},
        "id": "1038",
        "type": "BasicTickFormatter"
      },
      {
        "attributes": {
          "axis": {
            "id": "1017"
          },
          "dimension": 1,
          "ticker": null
        },
        "id": "1020",
        "type": "Grid"
      }
    ],
    "root_ids": [
      "1004"
    ]
  },
  "title": "Bokeh Application",
  "version": "2.3.0dev5-6-g8c193aa5b"
}
What is the top-level key structure of a Document

Referring to the above:

 "roots": {
    "references": [<all the serialized models>],
    "root_ids": ["<some model id>"]
  },
  "title": "Some title for the document",
  "version": "<bokeh version that created>"
}

The references contains a JSON representation of every Bokeh model reachable from the root, where each looks like:

      {
        "attributes": {
          "axis": {
            "id": "1017"
          },
          "dimension": 1,
          "ticker": null
        },
        "id": "1020",
        "type": "Grid"
      }

The above is a JSON repr for an object of type Grid, with "1020". Every models needs an id value that is unique within the document. Bokeh increments a simple counter by default but it can be anything (e.g. UUIDs if you want). The attributes contains all property values that are different from the default value for the property. I.e. all values that have been explicitly set. In this case it has a dimension value of 1, a ticker value of null (None, from Python) and for the axis, it has a reference to another model with id “1017”. These values were modified by the figure function when it assembled all the pieces of the figure.

Thanks, that’s extremely helpful, another question: what is the template for the html output file?

That template is here:

https://github.com/bokeh/bokeh/blob/b56543346bad8cdabe99960e63133619feaafbb7/bokeh/core/_templates/file.html

But I am not sure how useful it will be in isolation (also: ignore the comments at the top that appear badly out of date). I think you’ll want to trace code paths starting here at the file_html function, which is the basic function to take Bokeh models and embed them as standalone content in that template:

https://github.com/bokeh/bokeh/blob/branch-2.3/bokeh/embed/standalone.py#L243

Thanks, I am generating the bindings from the output of the spec.py script, but it seems some attributes are missing like the plot attributes in the models Grid or LinearAxis for example, can you confirm?

I’m not sure I understand the question. If you are asking if Grid and LinearAxis should have a .plot property that is a back reference to the plot they are on, the answer is no:

In [5]: p.axis[0].plot
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-26506ad36d50> in <module>
----> 1 p.axis[0].plot

AttributeError: 'LinearAxis' object has no attribute 'plot'

Alternatively if you are asking if plots should have properties for grids and axes, the answer is also no. There is a list of renderers that may (or may not) contain axis renderers or grid renderers. The plot.axis is a python convenience that just returns the list of renderers that happen to be Axis (if any), it is not a Bokeh model property.