BokehServer: a Bokeh server in Julia

Goals

It seemed Julia was missing a reactive plot library, meaning one which would react to changes in the data when used with Pluto or IJulia. This package tries to bring these features by relying on the excellent (Bokeh)[https://docs.bokeh.org/en/latest/index.html] library. The latter is basically a python web server with a javascript client. This package reproduces the python server and reuses the javascript part as is. This package's API is loosely similar to its python counterpart.

Getting Started

A number of examples are provided in the examples directory. These usually involve starting a server:

BokehServer.serve() do
    BokehServer.line(rand(Float64, 100))
end

One could also create a stand-alone html page:

BokehServer.html() do
    BokehServer.line(rand(Float64, 100))
end

We try to keep a similar API to the one provided by Bokeh. The main differences are due to Julia's aversion to instance or class methods. A python call fig.line([1, 2, 3]) becomes line!(fig; y = [1, 2, 3]). In other words:

  1. Julia relies on free functions. Thus the call requires adding the current plot as first argument.

  2. Julia suggests using a ! when a function mutates its first argument. Thus our free functions:

    • without a !, will create a plot, feed it to the !-ended free function, finally returning said plot.
    • with a !, will require a plot as first argument. The function usually adds one or more renderers to the plot.
  3. Julia makes a clear distinction between positional and keyword arguments, whereas python doesn't. We recreated something similar to python's: one will obtain the same plot when writing line!(fig, 1:10, (1:10).^2) or line!(fig; x = 1:10, y = (1:10).^2).

Checkout Bokeh's gallery or try the files in the examples directory.

Using the Plots package

A Plots backend is provided. It relies on a bokeh function being called.

begin
    using Plots
    using BokehServer
    bokeh()
end

Using Jupyter or Pluto

Example notebooks are examples/jupyter_notebook.ipynb and examples/pluto_notebook.jl.

Using the library requires two initial lines:

using BokehServer # import our library
# provide javascript headers to your browser, default port is 5006
BokehServer.Embeddings.notebook(; port = 4321)
Initializing a notebook environment

We need the notebook call to be the last in the cell! This is so the bokehjs library is added to the browser page.

Any layout or plot returned by a cell is then displayed in the notebook. Changes occurring in julia in other cells will affect this display.

The following will display a time series, with default indices on the x axis.

plot = BokehServer.line(; y = randn(Float64, 100) .+ (1:100))

We can update the previous cell from another:

push!(
    plot.renderers[1].data_source,
    Dict("y" => randn(Float64, 100) .+ (100:-1:1), "x" => 101:200)
);

In the background, a websocket server is created which will synchronize your BokehServer objects in Julia with their typescript counterparts. It will also manage the event mechanism provided by BokehServer.

Starting a server or creating a stand-alone html page

Examples abound in the examples directory. They all involve using:

BokehServer.serve() do
    plot = figure() # create a plot
    ...
    plot # return the plot
end
Stand-alone HTML pages

To create a stand-alone page, simply replace serve by html.

The do ... end scope

BokehServer objects should be created strictly within the do ... end scope. This is because of the event mechanism is initialized only within this scope.

Available plots

One can create a plot using:

BokehServer.Plotting.figureFunction
figure(;
    # tool keywords
    active_drag    :: Union{Nothing, iDrag, String, Model.EnumType{(:auto,)}}                              = :auto
    active_inspect :: Union{Nothing, iInspectTool, String, Model.EnumType{(:auto,)}, Vector{iInspectTool}} = :auto
    active_multi   :: Union{Nothing, iGestureTool, String, Model.EnumType{(:auto,)}}                       = :auto
    active_scroll  :: Union{Nothing, iScroll, String, Model.EnumType{(:auto,)}}                            = :auto
    active_tap     :: Union{Nothing, iTap, String, Model.EnumType{(:auto,)}}                               = :auto
    tools          :: Union{String, Vector{Union{iTool, String}}}                                          = "pan,wheel_zoom,box_zoom,save,reset,help"
    tooltips       :: Union{Nothing, iTemplate, String, Vector{Tuple{String, String}}}                     = nothing

    # x-axis keywords
    x_axis_label    :: Union{Nothing, iBaseText, String}                                            = ""
    x_axis_location :: Union{Nothing, Model.EnumType{(:above, :below)}}                             = :below
    x_axis_type     :: Union{Nothing, Model.EnumType{(:auto, :linear, :log, :datetime, :mercator)}} = :auto
    x_minor_ticks   :: Union{Int64, Model.EnumType{(:auto,)}}                                       = :auto
    x_range         :: Any                                                                          = nothing

    # y-axis keywords
    y_axis_label    :: Union{Nothing, iBaseText, String}                                            = ""
    y_axis_location :: Union{Nothing, Model.EnumType{(:left, :right)}}                              = :left
    y_axis_type     :: Union{Nothing, Model.EnumType{(:auto, :linear, :log, :datetime, :mercator)}} = :auto
    y_minor_ticks   :: Union{Int64, Model.EnumType{(:auto,)}}                                       = :auto
    y_range         :: Any                                                                          = nothing
)

Create a Plot object.

source

The following types of plots are available, with and without the !:

BokehServer.Plotting.boxplot!Function
boxplot!(
    plot::Models.Plot,
    labels,
    values;
    direction   :: Symbol          = :vertical,
    sortby      :: Function        = count,
    skipmissing :: Bool            = true,
    ranges      :: Vector{Float64} = [.25, .5, .75],
    iqr         :: Float64         = 1.5,
    bar      = (; line_color = :black, fill_color = :lightskyblue),
    segments = (; line_color = :black),
    borders  = (; marker = :dash, color = :black),
    aberrant = (; marker = :circle, color = :lightskyblue),
)

boxplot!(
    plot::Models.Plot,
    boxes::AbstractVector{<:BoxPlotItem};
    direction   :: Symbol          = :vertical,
    sortby      :: Function        = count,
    bar      = (; line_color = :black, fill_color = :lightskyblue),
    segments = (; line_color = :black),
    borders  = (; marker = :dash, color = :black),
    aberrant = (; marker = :circle, color = :lightskyblue),
)

Create a boxplot for every label.

The labels can be sorted by (use argument sortby with a function):

  • count: the number of values per box
  • median: the median of values per box
  • keys: the box key value
BokehServer.html() do
    labels = [j == 1 ? "x" : "y" for j = 1:2 for _ = 1:(j*10)]
    vals   = [(randn(Float64, 10) .+ 10.)..., (randn(Float64, 20) .- 10.)...]
    BokehServer.boxplot(labels, vals)
end
source

BokehServer.Plotting.GlyphPlotting.annularwedge!Function
annularwedge!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    inner_radius :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "inner_radius",
    outer_radius :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "outer_radius",
    start_angle :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = "start_angle",
    finish_angle :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = "end_angle",
    direction  :: Union{BokehServer.Model.EnumType{(:clock, :anticlock)}, AbstractArray} = :anticlock;
    kwa...
)

Adds a AnnularWedge glyph to the Plot

source

BokehServer.Plotting.GlyphPlotting.annulus!Function
annulus!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    inner_radius :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "inner_radius",
    outer_radius :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "outer_radius";
    kwa...
)

Adds a Annulus glyph to the Plot

source

BokehServer.Plotting.GlyphPlotting.arc!Function
arc!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    radius     :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "radius",
    start_angle :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = "start_angle",
    finish_angle :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = "end_angle",
    direction  :: Union{BokehServer.Model.EnumType{(:clock, :anticlock)}, AbstractArray} = :anticlock;
    kwa...
)

Adds a Arc glyph to the Plot

source

BokehServer.Plotting.Stacks.areastack!Function

Generate multiple area renderers for levels stacked either left to right or bottom to top.

The user must provide values for both x and y. Only one of these may be a list of fields.

Example

plot = BokehServer.figure()
renderers = areastack!(plot; x = ["a", "b", "c"], y = 0, source = Dict("a"=>[1,2], "b" => [3,4], "c"=>[5, 6]))
@assert renderers isa Vector{BokehServer.Models.GlyphRenderer}
source

BokehServer.Plotting.Stacks.barstack!Function

Generate multiple bar renderers for levels stacked either left to right or bottom to top.

The user must provide values for both x and y. Only one of these may be a list of fields.

Example

plot = BokehServer.figure()
renderers = barstack!(plot; x = ["a", "b", "c"], y = 0, source = Dict("a"=>[1,2], "b" => [3,4], "c"=>[5, 6]))
@assert renderers isa Vector{BokehServer.Models.GlyphRenderer}
source

BokehServer.Plotting.GlyphPlotting.bezier!Function
bezier!(
    plot       :: Models.Plot,
    x0         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x0",
    y0         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y0",
    x1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x1",
    y1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y1",
    cx0        :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "cx0",
    cy0        :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "cy0",
    cx1        :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "cx1",
    cy1        :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "cy1";
    kwa...
)

Adds a Bezier glyph to the Plot

source

BokehServer.Plotting.GlyphPlotting.circle!Function
circle!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y";
    kwa...
)

Adds a Circle glyph to the Plot

source

BokehServer.Plotting.GlyphPlotting.ellipse!Function
ellipse!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    width      :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "width",
    height     :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "height",
    angle      :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = 0.0;
    kwa...
)

Adds a Ellipse glyph to the Plot

source
BokehServer.Plotting.graph!Function

Creates a network graph using the given node, edge and layout provider.

Nodes are created using all node_ keywords. These are passed on to a call to BokehServer.Plotting.glyph(BokehServer.Models.Scatter; x...) (as also occurs in BokehServer.scatter!). One can also provide the glyph directly using keyword node.

Edge are created using all edge_ keywords. These are passed on to a call to BokehServer.Plotting.glyph(BokehServer.Models.Multiline; x...) (as also occurs in BokehServer.multiline!). One can also provide the glyph directly using keyword edge.

source
BokehServer.Plotting.GlyphPlotting.harea!Function
harea!(
    plot       :: Models.Plot,
    x1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x1",
    x2         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x2",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y";
    kwa...
)

Adds a HArea glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.hbar!Function
hbar!(
    plot       :: Models.Plot,
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    height     :: Union{BokehServer.Model.NumberSpec, AbstractArray} = 1.0,
    right      :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "right",
    left       :: Union{BokehServer.Model.NumberSpec, AbstractArray} = 0.0;
    kwa...
)

Adds a HBar glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.hextile!Function
hextile!(
    plot       :: Models.Plot,
    q          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "q",
    r          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "r";
    kwa...
)

Adds a HexTile glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.image!Function
image!(
    plot       :: Models.Plot,
    image      :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "image",
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    dw         :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "dw",
    dh         :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "dh",
    dilate     :: Union{Bool, AbstractArray}                         = false;
    kwa...
)

Adds a Image glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.imagergba!Function
imagergba!(
    plot       :: Models.Plot,
    image      :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "image",
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    dw         :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "dw",
    dh         :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "dh",
    dilate     :: Union{Bool, AbstractArray}                         = false;
    kwa...
)

Adds a ImageRGBA glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.imageurl!Function
imageurl!(
    plot       :: Models.Plot,
    url        :: Union{BokehServer.Model.StringSpec, AbstractArray} = (field = "url",),
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    w          :: Union{Nothing, BokehServer.Model.DistanceSpec, AbstractArray} = nothing,
    h          :: Union{Nothing, BokehServer.Model.DistanceSpec, AbstractArray} = nothing,
    angle      :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = 0.0,
    dilate     :: Union{Bool, AbstractArray}                         = false;
    kwa...
)

Adds a ImageURL glyph to the Plot

source

BokehServer.Plotting.GlyphPlotting.line!Function
line!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y";
    kwa...
)

Adds a Line glyph to the Plot

source
BokehServer.Plotting.Stacks.linestack!Function

Generate multiple line renderers for levels stacked either left to right or bottom to top.

The user must provide values for both x and y. Only one of these may be a list of fields.

Example

plot = BokehServer.figure()
renderers = linestack!(plot; x = ["a", "b", "c"], y = 0, source = Dict("a"=>[1,2], "b" => [3,4], "c"=>[5, 6]))
@assert renderers isa Vector{BokehServer.Models.GlyphRenderer}
source

BokehServer.Plotting.GlyphPlotting.multiline!Function
multiline!(
    plot       :: Models.Plot,
    xs         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "xs",
    ys         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "ys";
    kwa...
)

Adds a MultiLine glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.multipolygons!Function
multipolygons!(
    plot       :: Models.Plot,
    xs         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "xs",
    ys         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "ys";
    kwa...
)

Adds a MultiPolygons glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.patches!Function
patches!(
    plot       :: Models.Plot,
    xs         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "xs",
    ys         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "ys";
    kwa...
)

Adds a Patches glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.quad!Function
quad!(
    plot       :: Models.Plot,
    left       :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "left",
    right      :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "right",
    top        :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "top",
    bottom     :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "bottom";
    kwa...
)

Adds a Quad glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.quadratic!Function
quadratic!(
    plot       :: Models.Plot,
    x0         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x0",
    y0         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y0",
    x1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x1",
    y1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y1",
    cx         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "cx",
    cy         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "cy";
    kwa...
)

Adds a Quadratic glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.ray!Function
ray!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    length     :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = 0.0,
    angle      :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = 0.0;
    kwa...
)

Adds a Ray glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.rect!Function
rect!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    width      :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "width",
    height     :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "height",
    angle      :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = 0.0,
    dilate     :: Union{Bool, AbstractArray}                         = false;
    kwa...
)

Adds a Rect glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.scatter!Function
scatter!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    size       :: Union{BokehServer.Model.SizeSpec, AbstractArray}   = 4.0,
    angle      :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = 0.0,
    marker     :: Union{BokehServer.Model.MarkerSpec, AbstractArray} = :circle;
    kwa...
)

Adds a Scatter glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.segment!Function
segment!(
    plot       :: Models.Plot,
    x0         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x0",
    y0         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y0",
    x1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x1",
    y1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y1";
    kwa...
)

Adds a Segment glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.text!Function
text!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    text       :: Union{BokehServer.Model.StringSpec, AbstractArray} = (field = "text",),
    angle      :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = 0.0,
    x_offset   :: Union{BokehServer.Model.NumberSpec, AbstractArray} = 0.0,
    y_offset   :: Union{BokehServer.Model.NumberSpec, AbstractArray} = 0.0;
    kwa...
)

Adds a Text glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.varea!Function
varea!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y1         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y1",
    y2         :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y2";
    kwa...
)

Adds a VArea glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.vbar!Function
vbar!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    width      :: Union{BokehServer.Model.NumberSpec, AbstractArray} = 1.0,
    top        :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "top",
    bottom     :: Union{BokehServer.Model.NumberSpec, AbstractArray} = 0.0;
    kwa...
)

Adds a VBar glyph to the Plot

source
BokehServer.Plotting.GlyphPlotting.wedge!Function
wedge!(
    plot       :: Models.Plot,
    x          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "x",
    y          :: Union{BokehServer.Model.NumberSpec, AbstractArray} = "y",
    radius     :: Union{BokehServer.Model.DistanceSpec, AbstractArray} = "radius",
    start_angle :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = "start_angle",
    finish_angle :: Union{BokehServer.Model.AngleSpec, AbstractArray}  = "end_angle",
    direction  :: Union{BokehServer.Model.EnumType{(:clock, :anticlock)}, AbstractArray} = :anticlock;
    kwa...
)

Adds a Wedge glyph to the Plot

source

Layouts

Multiple plots can be displayed together using:

BokehServer.Plotting.Layouts.layoutFunction
layout(
    children::AbstractVector{<:Models.iLayoutDOM};
    nrow  :: Union{Int, Nothing} = nothing,
    ncols :: Union{Int, Nothing} = nothing,
    kwa...
)
layout(children::AbstractArray; kwa...)

Conveniently create a grid of layoutable objects.

Grids are created by using $GridBox$ model. This gives the most control over the layout of a grid, but is also tedious and may result in unreadable code in practical applications. $grid()$ function remedies this by reducing the level of control, but in turn providing a more convenient API.

Supported patterns:

  1. Nested arrays of layoutable objects. Assumes vectors are for rows, 1xN matrixes, are for columns. NxM matrixes are viewed as a vector of rows.

    julia> grid([p1, [[p2, p3]', p4]']) GridBox(children=[ (p1, 0, 0, 1, 2), (p2, 1, 0, 1, 1), (p3, 2, 0, 1, 1), (p4, 1, 1, 2, 1), ])

    ** warning ** The adjoint operator ' is recursive. A non-recursive code would be [p1, permutedims([[p2, p3], p4])].

  2. Flat list of layoutable objects. This requires $nrows$ and/or $ncols$ to be set. The input list will be rearranged into a 2D array accordingly. One can use $None$ for padding purpose.

    julia> grid([p1, p2, p3, p4], ncols=2) GridBox(children=[ (p1, 0, 0, 1, 1), (p2, 0, 1, 1, 1), (p3, 1, 0, 1, 1), (p4, 1, 1, 1, 1), ])

Keywords are the same as for layout(children::AbstractVector{<:LayoutEntry})

source
layout(
    children         :: AbstractVector{<:LayoutPlotEntry};
    sizing_mode      :: Union{Nothing, SizingModeType} = :fixed,
    toolbar_location :: Union{Nothing, LocationType}   = :above,
    width            :: Union{Nothing, Int}            = nothing,
    height           :: Union{Nothing, Int}            = nothing,
    toolbar_options  :: Any                            = (;)
)

Create a layout of plots. Toolbars will be merged by default.

Keywords:

  • sizing_mode: the sizing mode for each child, which is left untouched if nothing is given.
  • toolbar_location: the merged toolbar position. Use nothing to remove it
  • width: each plot width,
  • height: each plot height,
  • toolbar_options: toolbar options to use for creating the merged toolbar
source
layout(
    children    :: AbstractVector{<:LayoutDOMEntry};
    sizing_mode :: Union{Nothing, SizingModeType} = nothing,
    width       :: Union{Nothing, Int}            = nothing,
    height      :: Union{Nothing, Int}            = nothing,
)

Create a layout of any layoutable object (plots, widgets...).

Keywords:

  • sizing_mode: the sizing mode for each child, which is left untouched if nothing is given.
  • width: each item width,
  • height: each item height,
source

Document roots

As in Bokeh, users can add and remove roots from a Document. This can be done using functions push!(::iDocument, ::iModel) and pop!(::iDocument, ::iModel):

doc = BokehServer.Document()
fig = BokehServer.figure()
push!(doc, fig)
@assert length(doc) == 1
pop!(doc, fig)
@assert length(doc) == 0

Linking axes

Their axes can be linked, either using the event mechanisms or by sharing a Range1d object.

plot1 = BokehServer.scatter(;
    x       = randn(Float64, 100),
    y       = randn(Float64, 100),
)
plot2 = BokehServer.scatter(;
    x       = randn(Float64, 100),
    y       = randn(Float64, 100),
)

# make sure plot2 reacts to plot1 mutations
BokehServer.onchange(plot1.x_range) do evt::BokehServer.ModelChangedEvent
    setproperty!(plot2.x_range, evt.attr, evt.new)
end

# make sure plot1 reacts to plot2 mutations
BokehServer.onchange(plot2.x_range) do evt::BokehServer.ModelChangedEvent
    setproperty!(plot1.x_range, evt.attr, evt.new)
end

BokehServer.layout([plot1, plot2])
plot1 = BokehServer.scatter(;
    x       = randn(Float64, 100),
    y       = randn(Float64, 100),
    x_range = BokehServer.Range1d(; start = -10, finish = 10)
)
plot2 = BokehServer.scatter(;
    x       = randn(Float64, 100),
    y       = randn(Float64, 100),
    x_range = plot1.x_range
)
BokehServer.layout([plot1, plot2])

The ColumnDataSource structure

As in Bokeh, the ColumnDataSource structure is central to updating plots. The same methods are available for dealing with its mutations:

BokehServer.Models.SourceFunction
Source(args::Vararg{Pair{<:AbstractString, <:AbstractVector}}; kwa...)

Create a ColumnDataSource.

Columns can be specified using either or both positional and keyword arguments. Keyword arguments which are not a ColumnDataSource field name are considered to be a

CDS = Source("x" => 1:5; y = 1:5, selection_policy = IntersectRenderers())
@assert "x" ∈ keys(CDS.data)
@assert "y" ∈ keys(CDS.data)
@assert CDS.selection_policy isa IntersectRenderers
source
BokehServer.Model.stream!Function
stream!(
        γ         :: Union{ColumnDataSource, DataDictContainer},
        𝑑s        :: Vararg{DataDictArg};
        rollover  :: Union{Int, Nothing} = nothing,
)

Add rows to the ColumnDataSource.

  • 𝑑s can be of dictionnaries or pairs (column name, new data).
  • rollover indicates maximum number of rows after the rows are added. Oldest

rows are deleted as required.

DS = BokehServer.ColumnDataSource(; data = Dict("x" => [1, 2], "y" => [3, 4]))

BokehServer.stream!(DS, "x" => [3], "y" => [4])
@assert length(DS.data["x"]) == 3

BokehServer.stream!(DS, Dict("x" => [4], "y" => [5]))
@assert length(DS.data["x"]) == 4
source
BokehServer.Model.update!Function
update!(
    γ::Union{ColumnDataSource, DataDictContainer},
    𝑑s::Vararg{Dict{String, Vector}}
)

Adds or replaces columns.

source
BokehServer.Model.patch!Function
patch!(
    γ::Union{ColumnDataSource, DataDictContainer},
    patches::Vararg{Pair{String, Pair}}
)
patch!(
    γ::Union{ColumnDataSource, DataDictContainer},
    patches::Vararg{Dict{String, Vector{Pair}}}
)

Updates values within existing columns.

x = DataDictContainer(Dict("a" => [1, 2, 3]))

BokehServer.patch!(x, "a" => 2 => 10)
@assert x["a"] == [1, 10, 3] 

BokehServer.patch!(x, Dict("a" => [1 => 5, 2:3 => 10]))
@assert x["a"] == [5, 10, 10] 
source
Note

One can also use Base.push! instead of Base.stream!.

One can also use Base.merge! instead of Base.update! or Base.patch!.

The event mechanism

As with Bokeh events can be both triggered from and dealt with both in typescript and Julia.

Creating callbacks in Julia

Julia event callbacks are created using BokehServer.onchange:

BokehServer.Events.onchangeFunction
onchange(func::Function, model::iDocument)

Adds a Julia callback to a document-level event.

The function must have a single positional argument. Specifying an event type allows triggering the callback on that specific event type only.

Examples

doc = BokehServer.Document

# Add a callback for every type of event.
onchange(doc) do evt
    @assert evt isa BokehServer.Events.iDocEvent
    println("callback 1: any doc events")
end

# Add a callback for `RootAddedEvent` only
onchange(doc) do evt::BokehServer.RootAddedEvent
    @assert evt isa BokehServer.RootAddedEvent
    println("callback 2: only RootAddedEvent")
end

# now trigger the events
BokehServer.Events.eventlist!() do
    push!(doc, Model1())
    delete!(doc, Model2())
end

will outputs

callback 1: any doc events
callback 2: only RootAddedEvent
callback 1: any doc events
source
onchange(func::Function, model::iModel)

Adds a Julia callback to a model-level event.

The function can have two different signatures:

  1. callback(evt [::X]) where specifying X allows triggering on a specific event type.
  2. callback(model [::iModel], attribute [::Symbol], old [::A], new [::B]) where

specifying A or B allows triggering on a specific field type.

** Warning ** Using an incorrect type in the signature can result in callback being silently ignored.

Examples

obj = Model()

# Add a callback triggered by every type of event
onchange(obj) do evt
    @assert evt isa BokehServer.Events.iDocModelEvent
    println("callback 1: receive events")
end

# Add a callback triggered by `BokehServer.ModelChangedEvent` only.
onchange(obj) do model, attr, old, new
    println("callback 2: just sugar")
end

# Add a callback triggered by `BokehServer.ModelChangedEvent` only, where a
# `Float64` is the new value.
onchange(obj) do model, attr, old, new::Float64
    @assert new isa Float64
    println("callback 3: a specific type for `new`")
end

# now trigger the events
BokehServer.Events.eventlist!() do
    obj.a = 1
    obj.a = 10.
end

will outputs

callback 1: receive events
callback 2: just sugar
callback 1: receive events
callback 2: just sugar
callback 3: a specific type for `new`
source

As can be seen in the examples:

  1. Events are triggered on Document or Model instances.
  2. One can use event types in the signature to filter which events

should trigger a given callback.

Document event types are:

BokehServer.Events.RootAddedEventType

Event triggered on a BokehServer.Document when a root is added to it.

Fields:

  • doc::iDocument: the mutated document
  • root::iModel: the root added to the document
  • index::Int: the root index in the list of roots.

Supertypes: RootAddedEvent <: iDocRootEvent <: iDocEvent <: iEvent

source
BokehServer.Events.RootRemovedEventType

Event triggered on a BokehServer.Document when a root is removed to it.

Fields:

  • doc::iDocument: the mutated document
  • root::iModel: the root removed to the document
  • index::Int: the root index in the list of roots.

Supertypes: RootRemovedEvent <: iDocRootEvent <: iDocEvent <: iEvent

source
BokehServer.Events.TitleChangedEventType

Event triggered on a BokehServer.Document when the HTML document title is changed.

Fields:

  • doc::iDocument: the mutated document
  • title::String: the new title

Supertypes: TitleChangedEvent <: iDocEvent <: iEvent

source

Model event types are:

BokehServer.Events.ModelChangedEventType

Event triggered by a mutated field in an iHasProps instance.

Fields:

  • model::iModel: the mutated instance
  • attr::Symbol: the mutated field name
  • old::Any: the previous field value
  • new::Any: the currnet field value

Supertypes: ModelChangedEvent <: iDocModelEvent <: iEvent

source
BokehServer.Events.ColumnsPatchedEventType

Event triggered by a calling BokehServer.patch! on a BokehServer.Models.ColumnDataSource, i.e, by mutating parts of the data in a the ColumnDataSource.

Fields:

  • model::iModel: the mutated instance
  • attr::Symbol: the mutated field name (always :data)
  • patches::Dict{String, Vector{Pair}}: the patches applied

Supertypes: ColumnsPatchedEvent <: iDataSourceEvent <: iDocModelEvent <: iEvent

source
BokehServer.Events.ColumnsStreamedEventType

Event triggered by a calling BokehServer.stream! on a BokehServer.Models.ColumnDataSource, i.e, by adding rows to the ColumnDataSource.

Fields:

  • model::iModel: the mutated instance
  • attr::Symbol: the mutated field name (always :data)
  • data::DataDict: columns added to the ColumnDataSource
  • rollover::Union{Nothing, Int}: the rollover which was applied

Supertypes: ColumnsStreamedEvent <: iDataSourceEvent <: iDocModelEvent <: iEvent

source
BokehServer.Events.ColumnDataChangedEventType

Event triggered by a calling BokehServer.update! on a BokehServer.Models.ColumnDataSource, i.e, by adding columns to the ColumnDataSource.

Fields:

  • model::iModel: the mutated instance
  • attr::Symbol: the mutated field name (always :data)
  • data::DataDict: columns added to the ColumnDataSource

Supertypes: ColumnDataChangedEvent <: iDataSourceEvent <: iDocModelEvent <: iEvent

source

UI event types are:

BokehServer.Models.UIEvents.LODStartType

Announce the start of "interactive level-of-detail" mode on a plot.

During interactive actions such as panning or zooming, BokehServer can optionally, temporarily draw a reduced set of the data, in order to maintain high interactive rates. This is referred to as interactive Level-of-Detail (LOD) mode. This event fires whenever a LOD mode has just begun.

Fields:

  • model::iPlot: the plot affected by the event
source
BokehServer.Models.UIEvents.LODEndType

Announce the end of "interactive level-of-detail" mode on a plot.

During interactive actions such as panning or zooming, BokehServer can optionally, temporarily draw a reduced set of the data, in order to maintain high interactive rates. This is referred to as interactive Level-of-Detail (LOD) mode. This event fires whenever a LOD mode has just ended.

Fields:

  • model::iPlot: the plot affected by the event
source
BokehServer.Models.UIEvents.RangesUpdateType

Announce combined range updates in a single event.

Fields:

  • model::iPlot: the plot affected by the event
  • x0::Float64: start x-coordinate for the default x-range
  • x1::Float64: end x-coordinate for the default x-range
  • y0::Float64: start x-coordinate for the default y-range
  • y1::Float64: end y-coordinate for the default x-range

Callbacks may be added to range $start$ and $end$ properties to respond to range changes, but this can result in multiple callbacks being invoked for a single logical operation (e.g. a pan or zoom). This event is emitted by supported tools when the entire range update is complete, in order to afford a single event that can be responded to.

source
BokehServer.Models.UIEvents.SelectionGeometryType

Announce the coordinates of a selection event on a plot.

Fields:

  • model::iModel: the model affected by the event
  • geometry::DICT: a dictionary containing the coordinates of the selection

event.

  • final::BOOL: whether the selection event is the last selection event in the

case of selections on every mousemove.

source
BokehServer.Models.UIEvents.TapType

Announce a Tap event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.DoubleTapType

Announce a DoubleTap event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.PressType

Announce a Press event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.PressUpType

Announce a PressUp event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.MouseEnterType

Announce a MouseEnter event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.MouseLeaveType

Announce a MouseLeave event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.MouseMoveType

Announce a MouseMove event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.PanEndType

Announce a PanEnd event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.PanStartType

Announce a PanStart event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.PinchStartType

Announce a PinchStart event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.RotateType

Announce a Rotate event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.RotateStartType

Announce a RotateStart event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.RotateEndType

Announce a RotateEnd event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.MouseWheelType

Announce a mouse wheel event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • delta::Float64: the (signed) scroll speed
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space

Note By default, BokehServer plots do not prevent default scroll events unless a $WheelZoomTool$ or $WheelPanTool$ is active. This may change in future releases.

source
BokehServer.Models.UIEvents.PanType

Announce a pan event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • delta_x::Float64: the amount of scroll in the x direction
  • delta_y::Float64: the amount of scroll in the y direction
  • direction::Float64: the direction of scroll (1 or -1)
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space
source
BokehServer.Models.UIEvents.PinchType

Announce a pinch event on a BokehServer plot.

Fields:

  • model::iPlot: the plot affected by the event
  • scale::Float64: the (signed) amount of scaling
  • sx::Float64: x-coordinate of the event in screen space
  • sy::Float64: y-coordinate of the event in screen space
  • x::Float64: x-coordinate of the event in data space
  • y::Float64: y-coordinate of the event in data space

Note This event is only applicable for touch-enabled devices.

source

Details

Events can only be triggered if an event manager has been provided. This is normally done automatically, although, as in Bokeh, only is specific cases:

  • when initializing a new document
  • when responding to a typescript message
  • when in a Pluto or Jupyter environment, for cells coming after a call to BokehServer.Embeddings.notebook().

As opposed to Bokeh, event managers collect all events before triggering callback and finally synchronizing with typescript. Some events might disappear at any point during collection or callbacks, say if a document root is mutated then simply removed from the document.

The collection is done thanks to a task-specific manager, hidden inside the task_local_storage() dictionnary.

Advanced users could change the manager behavior by creating custom BokehServer.Server.Application types, overloading Server.eventlist(::iApplication), and providing instances of these applications to the server. An example is the BokehServer.Embeddings.Notebooks.NotebookApp which deals with the specifics of working in Pluto or Jupyter environment.

Javascript callbacks

Users can provide javascript code, to be executed client-side. This is done using a CustomJS object. The latter is then provided to the model triggering the change. One or both these actions can be done using:

js_onchange
js_onevent
@js_link

Themes

Themes provided by Bokeh are also available here.

One can set either a global theme or one specific to a document:

# set the global theme
BokehServer.Themes.setvalues!(:caliber) # or :dark_minimal, :light_minimal, :contrast, :night_sky or :default

# set the doc theme
BokehServer.Themes.setvalues!(mydoc.theme, :caliber)

In most cases where user creates new BokehServer objects, a default document has been added to the task_local_storage dictionnary, and its theme is the one applied to those objects.

Note

A new document always inherits a copy of the current global theme.

BokehServer.Themes.ThemeType

A structure containing theme defaults.

One can create a theme by providing a path to a JSON file or the name of a python bokeh theme.

source
BokehServer.Themes.read!Method
read!(dic::Theme, io::IO; empty::Bool = true)
read!(dic::Theme, path::AbstractString; empty::Bool = true)
read!(dic::Theme, name::Symbol)

Applies a JSON or python theme file to the dic.

Symbols can be :caliber, :contrast, :darkminimal, :lightminimal, :night_sky and correspond to python bokeh options

source
BokehServer.Themes.setvalue!Method
setvalue!([dic::Theme = THEME,] cls::Symbol, attr::Symbol, val)

Adds a default value function val to theme dic for type cls and field attr

source
BokehServer.Themes.setvalues!Method
setvalues!([theme::Theme = THEME,] name::Union{AbstractString, Symbol, AbstractDict, Nothing})

Sets the generic theme to new values

source
BokehServer.Themes.setvalues!Method
setvalues!(𝐹::Function, [theme::Theme = THEME,] name)

Sets the theme to new values for the duration of 𝐹.

Note This is not thread-safe.

source
BokehServer.Themes.themeMethod
theme([dic::Theme,] T::Type, attr::Symbol)

Retrieves a default value from theme dic for type cls and field attr. This is done by looking through all supertypes for cls

source

Custom models

Custom models can be created and used. Examples are in the examples/extensions directory.

These models should follow the following pattern

@BokehServer.model mutable struct MyModel <: BokehServer.iModel
    __implementation__ = "path to typescript, relative to current code directory"

    a :: Int = 10
    b :: Vector{Float64} =  [1., 2., 3.]
end

Typescript code is required as well. The user is invited to refer to python Bokeh. Other fields __javascript__, __css__, __dependencies__ can also be used. Again, better documentation is available in the python library.

Creating a template of a derived type

The julia hierarchy system is very different from python's. In particular, one needs to redeclare all fields for every new structure. A template can be created as follows:

julia> path = joinpath(dirname(pathof(BokehServer)), "template.jl")
julia> run(`$path Slider`)  # Slider or any python Bokeh model class name

The package architecture

BokehServer provides the same services Bokeh:

  • Means for mirroring the typescript library bokehjs.
  • An event mechanism for reacting to changes to the Julia objects.
  • A websocket server for synchronizing objects between Julia and typescript.

The code is divided in:

  • a core directory which provides:

    • macros for mirroring javascript objects, namely @model, see the core/model directory.
    • some type extensions and conversions mechanisms for seemlessly converting typescript values to Julia, namely bokehconvert, see the core/model/properties directory.
    • an event mechanism for reacting to changes in objects wrapped with @model, see core/events
    • a server, see core/server and its protocol, see core/protocol for dealing with synchronisation.
  • a models directory, created by automatically parsing the python code. It contains all structures corresponding to typescript classes.

  • a plotting directory, providing a plotting interface.