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:
Julia relies on free functions. Thus the call requires adding the current plot as first argument.
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.
- without a
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)
orline!(fig; x = 1:10, y = (1:10).^2)
.
Gallery
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)
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
To create a stand-alone page, simply replace serve
by html
.
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.figure
— Functionfigure(;
# 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.
The following types of plots are available, with and without the !
:
BokehServer.Plotting.boxplot!
— Functionboxplot!(
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
BokehServer.Plotting.GlyphPlotting.annularwedge!
— Functionannularwedge!(
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
BokehServer.Plotting.GlyphPlotting.annulus!
— Functionannulus!(
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
BokehServer.Plotting.GlyphPlotting.arc!
— Functionarc!(
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
BokehServer.Plotting.Stacks.areastack!
— FunctionGenerate 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}
BokehServer.Plotting.Stacks.barstack!
— FunctionGenerate 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}
BokehServer.Plotting.GlyphPlotting.bezier!
— Functionbezier!(
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
BokehServer.Plotting.GlyphPlotting.circle!
— Functioncircle!(
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
BokehServer.Plotting.GlyphPlotting.ellipse!
— Functionellipse!(
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
BokehServer.Plotting.graph!
— FunctionCreates 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
.
BokehServer.Plotting.GlyphPlotting.harea!
— Functionharea!(
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
BokehServer.Plotting.GlyphPlotting.hbar!
— Functionhbar!(
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
BokehServer.Plotting.GlyphPlotting.hextile!
— Functionhextile!(
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
BokehServer.Plotting.GlyphPlotting.image!
— Functionimage!(
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
BokehServer.Plotting.GlyphPlotting.imagergba!
— Functionimagergba!(
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
BokehServer.Plotting.GlyphPlotting.imageurl!
— Functionimageurl!(
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
BokehServer.Plotting.GlyphPlotting.line!
— Functionline!(
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
BokehServer.Plotting.Stacks.linestack!
— FunctionGenerate 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}
BokehServer.Plotting.GlyphPlotting.multiline!
— Functionmultiline!(
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
BokehServer.Plotting.GlyphPlotting.multipolygons!
— Functionmultipolygons!(
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
BokehServer.Plotting.GlyphPlotting.patches!
— Functionpatches!(
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
BokehServer.Plotting.GlyphPlotting.quad!
— Functionquad!(
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
BokehServer.Plotting.GlyphPlotting.quadratic!
— Functionquadratic!(
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
BokehServer.Plotting.GlyphPlotting.ray!
— Functionray!(
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
BokehServer.Plotting.GlyphPlotting.rect!
— Functionrect!(
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
BokehServer.Plotting.GlyphPlotting.scatter!
— Functionscatter!(
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
BokehServer.Plotting.GlyphPlotting.segment!
— Functionsegment!(
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
BokehServer.Plotting.GlyphPlotting.text!
— Functiontext!(
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
BokehServer.Plotting.GlyphPlotting.varea!
— Functionvarea!(
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
BokehServer.Plotting.GlyphPlotting.vbar!
— Functionvbar!(
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
BokehServer.Plotting.GlyphPlotting.wedge!
— Functionwedge!(
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
Layouts
Multiple plots can be displayed together using:
BokehServer.Plotting.Layouts.layout
— Functionlayout(
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:
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])]
.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})
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
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,
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.Source
— FunctionSource(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
BokehServer.Model.stream!
— Functionstream!(
γ :: 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
BokehServer.Model.update!
— Functionupdate!(
γ::Union{ColumnDataSource, DataDictContainer},
𝑑s::Vararg{Dict{String, Vector}}
)
Adds or replaces columns.
BokehServer.Model.patch!
— Functionpatch!(
γ::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]
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.onchange
— Functiononchange(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
onchange(func::Function, model::iModel)
Adds a Julia callback to a model-level event.
The function can have two different signatures:
callback(evt [::X])
where specifyingX
allows triggering on a specific event type.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`
As can be seen in the examples:
- Events are triggered on
Document
orModel
instances. - One can use event types in the signature to filter which events
should trigger a given callback.
Document event types are:
BokehServer.Events.RootAddedEvent
— TypeEvent triggered on a BokehServer.Document
when a root is added to it.
Fields:
doc::iDocument
: the mutated documentroot::iModel
: the root added to the documentindex::Int
: the root index in the list of roots.
Supertypes: RootAddedEvent <: iDocRootEvent <: iDocEvent <: iEvent
BokehServer.Events.RootRemovedEvent
— TypeEvent triggered on a BokehServer.Document
when a root is removed to it.
Fields:
doc::iDocument
: the mutated documentroot::iModel
: the root removed to the documentindex::Int
: the root index in the list of roots.
Supertypes: RootRemovedEvent <: iDocRootEvent <: iDocEvent <: iEvent
BokehServer.Events.TitleChangedEvent
— TypeEvent triggered on a BokehServer.Document
when the HTML document title is changed.
Fields:
doc::iDocument
: the mutated documenttitle::String
: the new title
Supertypes: TitleChangedEvent <: iDocEvent <: iEvent
Model event types are:
BokehServer.Events.ModelChangedEvent
— TypeEvent triggered by a mutated field in an iHasProps
instance.
Fields:
model::iModel
: the mutated instanceattr::Symbol
: the mutated field nameold::Any
: the previous field valuenew::Any
: the currnet field value
Supertypes: ModelChangedEvent <: iDocModelEvent <: iEvent
BokehServer.Events.ColumnsPatchedEvent
— TypeEvent 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 instanceattr::Symbol
: the mutated field name (always:data
)patches::Dict{String, Vector{Pair}}
: the patches applied
Supertypes: ColumnsPatchedEvent <: iDataSourceEvent <: iDocModelEvent <: iEvent
BokehServer.Events.ColumnsStreamedEvent
— TypeEvent triggered by a calling BokehServer.stream!
on a BokehServer.Models.ColumnDataSource
, i.e, by adding rows to the ColumnDataSource
.
Fields:
model::iModel
: the mutated instanceattr::Symbol
: the mutated field name (always:data
)data::DataDict
: columns added to theColumnDataSource
rollover::Union{Nothing, Int}
: the rollover which was applied
Supertypes: ColumnsStreamedEvent <: iDataSourceEvent <: iDocModelEvent <: iEvent
BokehServer.Events.ColumnDataChangedEvent
— TypeEvent triggered by a calling BokehServer.update!
on a BokehServer.Models.ColumnDataSource
, i.e, by adding columns to the ColumnDataSource
.
Fields:
model::iModel
: the mutated instanceattr::Symbol
: the mutated field name (always:data
)data::DataDict
: columns added to theColumnDataSource
Supertypes: ColumnDataChangedEvent <: iDataSourceEvent <: iDocModelEvent <: iEvent
UI event types are:
BokehServer.Models.UIEvents.DocumentReady
— TypeAnnounce when a Document is fully idle.
BokehServer.Models.UIEvents.ButtonClick
— TypeAnnounce a button click event on a BokehServer button widget.
BokehServer.Models.UIEvents.MenuItemClick
— TypeAnnounce a button click event on a BokehServer menu item.
BokehServer.Models.UIEvents.LODStart
— TypeAnnounce 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
BokehServer.Models.UIEvents.LODEnd
— TypeAnnounce 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
BokehServer.Models.UIEvents.RangesUpdate
— TypeAnnounce combined range updates in a single event.
Fields:
model::iPlot
: the plot affected by the eventx0::Float64
: start x-coordinate for the default x-rangex1::Float64
: end x-coordinate for the default x-rangey0::Float64
: start x-coordinate for the default y-rangey1::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.
BokehServer.Models.UIEvents.SelectionGeometry
— TypeAnnounce the coordinates of a selection event on a plot.
Fields:
model::iModel
: the model affected by the eventgeometry::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.
BokehServer.Models.UIEvents.Reset
— TypeAnnounce a button click event on a plot $ResetTool$.
BokehServer.Models.UIEvents.Tap
— TypeAnnounce a Tap
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.DoubleTap
— TypeAnnounce a DoubleTap
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.Press
— TypeAnnounce a Press
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.PressUp
— TypeAnnounce a PressUp
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.MouseEnter
— TypeAnnounce a MouseEnter
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.MouseLeave
— TypeAnnounce a MouseLeave
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.MouseMove
— TypeAnnounce a MouseMove
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.PanEnd
— TypeAnnounce a PanEnd
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.PanStart
— TypeAnnounce a PanStart
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.PinchStart
— TypeAnnounce a PinchStart
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.Rotate
— TypeAnnounce a Rotate
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.RotateStart
— TypeAnnounce a RotateStart
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.RotateEnd
— TypeAnnounce a RotateEnd
event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.MouseWheel
— TypeAnnounce a mouse wheel event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventdelta::Float64
: the (signed) scroll speedsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::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.
BokehServer.Models.UIEvents.Pan
— TypeAnnounce a pan event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventdelta_x::Float64
: the amount of scroll in the x directiondelta_y::Float64
: the amount of scroll in the y directiondirection::Float64
: the direction of scroll (1 or -1)sx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
BokehServer.Models.UIEvents.Pinch
— TypeAnnounce a pinch event on a BokehServer plot.
Fields:
model::iPlot
: the plot affected by the eventscale::Float64
: the (signed) amount of scalingsx::Float64
: x-coordinate of the event in screen spacesy::Float64
: y-coordinate of the event in screen spacex::Float64
: x-coordinate of the event in data spacey::Float64
: y-coordinate of the event in data space
Note This event is only applicable for touch-enabled devices.
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
orJupyter
environment, for cells coming after a call toBokehServer.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.
A new document always inherits a copy of the current global theme.
BokehServer.Themes.THEME
— ConstantTHEME :: Theme: the default theme
BokehServer.Themes.Theme
— TypeA 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.
BokehServer.Themes.changetheme!
— Methodchangetheme!(obj::iHasProps, dic::Theme)
Updates obj
such that it now conforms to theme dic
BokehServer.Themes.read!
— Methodread!(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
BokehServer.Themes.setvalue!
— Methodsetvalue!([dic::Theme = THEME,] cls::Symbol, attr::Symbol, val)
Adds a default value function val
to theme dic
for type cls
and field attr
BokehServer.Themes.setvalues!
— Methodsetvalues!([theme::Theme = THEME,] name::Union{AbstractString, Symbol, AbstractDict, Nothing})
Sets the generic theme to new values
BokehServer.Themes.setvalues!
— Methodsetvalues!(𝐹::Function, [theme::Theme = THEME,] name)
Sets the theme to new values for the duration of 𝐹
.
Note This is not thread-safe.
BokehServer.Themes.theme
— Methodtheme([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
BokehServer.Themes.theme
— Methodtheme([dic::Theme,] T::Type)
Creates a default object given the (current) theme
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 thecore/model
directory. - some type extensions and conversions mechanisms for seemlessly converting typescript values to Julia, namely
bokehconvert
, see thecore/model/properties
directory. - an event mechanism for reacting to changes in objects wrapped with
@model
, seecore/events
- a server, see
core/server
and its protocol, seecore/protocol
for dealing with synchronisation.
- macros for mirroring javascript objects, namely
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.