Here’s an example using Bokeh.jl
. It’s quite long, because you need to be explicit about a lot of things, but I’ve been thinking about an AoG-style wrapper for it.
using DataFrames, Random, Bokeh
# OP's data
Random.seed!(123)
d = DataFrame(name = repeat(["A","B","C","D","E","F"], inner=4),
time=repeat([0,1,3,6], outer=6), value = rand(24));
# (optional) displays plots in the browser - omit if you are in a notebook
Bokeh.settings!(display=:browser)
# make the figure
fig = figure()
# set the axis labels
fig.x_axis.axis_label = "time"
fig.y_axis.axis_label = "value"
# (optional) by default the legend is placed inside the plot
plot!(fig, Legend; location="right")
# defines the mapping from names to colors
color = factor_cmap("name", "Set2_8", sort(unique(d.name)))
# defines the data source
source = ColumnDataSource(data=d)
# add scatter plot
plot!(fig, Scatter; x="time", y="value", source, color, size=10, legend_field="name")
# add text
plot!(fig, Bokeh.Text; x="time", y="value", text="name", source, color, text_baseline="middle", x_offset=10)
# add lines - note bokeh will not group lines for you, so you must
# group the data yourself and either make multiple `Line` plots or
# one `MultiLine` plot
w = combine(groupby(d, :name), d->(time=[d.time], value=[d.value]))
plot!(fig, MultiLine; xs="time", ys="value", source=w, color)
# show the result
display(fig)
Edit: here is the result.