Putting it together

We will now look at how to combine traces and a layout to create a plot.

We'll also discuss how to integrate with various frontends

Plot

Recall that the definition of the Plot object is

mutable struct Plot{TT<:AbstractTrace}
    data::Vector{TT}
    layout::AbstractLayout
    divid::Base.Random.UUID
end

Given one or more AbstractTraces and optionally a layout, we construct a Plot object with any of the following constructors

# A blank canvas no traces or a layout
Plot()

# A vector of traces and a layout
Plot{T<:AbstractTrace}(data::AbstractVector{T}, layout::AbstractLayout)

# A vector of traces -- default layout supplied
Plot{T<:AbstractTrace}(data::AbstractVector{T})

# a single trace: will be put into a vector -- default layout supplied
Plot(data::AbstractTrace)

# a single trace and a layout (trace put into a vector)
Plot(data::AbstractTrace, layout::AbstractLayout)

Notice that none of the recommended constructors have you pass the divid field manually. This is an internal field used to allow the display and unique identification of multiple plots in a single web page.

Convenience methods

There are also a number of convenience methods to the Plot function that will attempt to construct the traces for you. They have the following signatures

# Build a trace of type `kind` from x and y -- apply all kwargs to this trace
Plot(x::AbstractVector, y::AbstractVector, l::Layout=Layout(); kind="scatter", kwargs...)

# Build one trace of type `kind` for each column of the matrix `y`. Repeat the
# `x` argument for all traces. Apply `kwargs` identically to all traces
Plot(x::AbstractVector, y::AbstractMatrix, l::Layout=Layout(); kind="scatter", kwargs...)

# For `x` and `y` with the same number of columns, build one trace of type
# `kind` for each column pair, applying all `kwargs` identically do all traces
Plot(x::AbstractMatrix, y::AbstractMatrix, l::Layout=Layout(); kind="scatter", kwargs...)

# For any array of eltype `_Scalar` (really data, see PlotlyJS._Scalar), call
# the method `Plot(1:size(y, 1), y, l; kwargs...)` described above
Plot{T<:_Scalar}(y::AbstractArray{T}, l::Layout=Layout(); kwargs...)

# Create one trace by applying `f` at 50 evenly spaced points from `x0` to `x1`
Plot(f::Function, x0::Number, x1::Number, l::Layout=Layout(); kwargs...)

# Create one trace for each function `f ∈ fs` by applying `f` at 50 evenly
# spaced points from `x0` to `x1`
Plot(fs::AbstractVector{Function}, x0::Number, x1::Number, l::Layout=Layout(); kwargs...)

# Construct one or more traces using data from the DataFrame `df` when possible
# If `group` is a symbol and that symbol matches one of the column names of
# `df`, make one trace for each unique value in the column `df[group]` (see
# example below)
Plot(df::AbstractDataFrame, l::Layout=Layout(); group=nothing, kwargs...)

# Use the column `df[x]` as the x value on each trace. Similar for `y`
Plot(df::AbstractDataFrame, x::Symbol, y::Symbol, l::Layout=Layout(); group=nothing, kwargs...)

# Use the column `df[y]` as the y value on each trace
Plot(df::AbstractDataFrame, y::Symbol, l::Layout=Layout(); group=nothing, kwargs...)

For more information on how these methods work please see the docstring for the Plot function by calling ?Plot from the REPL.

Especially convenient is the group keyword argument when calling Plot(::AbstractDataFrame, ... ; ...). Here is an example below:

julia> using RDatasets

julia> iris = dataset("datasets", "iris");

julia> p = Plot(iris, x=:SepalLength, y=:SepalWidth, mode="markers", marker_size=8, group=:Species)
data: [
  "scatter with fields marker, mode, name, type, x, and y",
  "scatter with fields marker, mode, name, type, x, and y",
  "scatter with fields marker, mode, name, type, x, and y"
]

layout: "layout with field margin"

which would result in the following plot

SyncPlots

A Plot is a pure Julia object and doesn't interact with plotly.js by itself. This means that we can't view the actual plotly figure the data represents.

To do that we need to link the Plot to one or more display frontends.

To actually connect to the display frontends we use the WebIO.jl package. Our interaction with WebIO is wrapped up in a type called SyncPlot that is defined as follows:

mutable struct SyncPlot
    plot::PlotlyBase.Plot
    scope::Scope
    events::Dict
    options::Dict
end

As its name suggests, a SyncPlot will keep the Julia representation of the a plot (the Plot instance) in sync with a plot with a frontend.

Note

The Plot function will create a new Plot object and the plot function will create a new SyncPlot. The plot function passes all arguments (except the options keyword argument -- see below) to construct a Plot and then sets up the display. All Plot methods are also defined for plot

By leveraging WebIO.jl we can render our figures anywhere WebIO can render. At time of writing this includes Jupyter notebooks, Jupyterlab, Mux.jl web apps, the Juno Julia environment inside the Atom text editor, and Electron windows from Blink.jl. Please see the WebIO.jl readme for additional (and up to date!) information.

When using PlotlyJS.jl at the Julia REPL a plot will automatically be displayed in an Electron window. This is a dedicated browser window we have full control over. To see a plot p, just type p by itself at the REPL and execute the line. Alternatively you can call display(p).

In addition to being able to see our charts in many front-end environments, WebIO also provides a 2-way communication bridge between javascript and Julia. In fact, when a SyncPlot is constructed, we automatically get listeners for all plotly.js javascript events. What's more is that we can hook up Julia functions as callbacks when those events are triggered. In the very contrived example below we have Julia print out details regarding points on a plot whenever a user hovers over them on the display:

using WebIO
p = plot(rand(10, 4));
display(p)  # usually optional

on(p["hover"]) do data
    println("\nYou hovered over", data)
end

In this next example, whenever we click on a point we change its marker symbol to a star and marker color to gold:

using WebIO
colors = (fill("red", 10), fill("blue", 10))
symbols = (fill("circle", 10), fill("circle", 10))
ys = (rand(10), rand(10))
p = plot(
    [scatter(y=y, marker=attr(color=c, symbol=s, size=15), line_color=c[1])
    for (y, c, s) in zip(ys, colors, symbols)]
)
display(p)  # usually optional

on(p["click"]) do data
    colors = (fill("red", 10), fill("blue", 10))
    symbols = (fill("circle", 10), fill("circle", 10))
    for point in data["points"]
        colors[point["curveNumber"] + 1][point["pointIndex"] + 1] = "gold"
        symbols[point["curveNumber"] + 1][point["pointIndex"] + 1] = "star"
    end
    restyle!(p, marker_color=colors, marker_symbol=symbols)
end

While completely nonsensical, hopefully these examples show you that it is possible to build rich, interactive, web-based data visualization applications with business logic implemented entirely in Julia!.

Display configuration

When calling plot the options keyword argument is given special treatment. It should be an instance of AbstractDict and its contents are passed as display options to the plotly.js library. For details on which options are supported, see the plotly.js documentation on the subject.

As an example, if we were to execute the following code, we would see a static chart (no hover information or ability to zoom/pan) with 4 lines instead of an interactive one:

plot(rand(10, 4), options=Dict(:staticPlot => true))