plotly subplot layouts

This commit is contained in:
Thomas Breloff 2016-05-24 14:25:03 -04:00
parent 851fe9f72c
commit c1baca181c
5 changed files with 131 additions and 64 deletions

View File

@ -214,6 +214,7 @@ const _subplot_defaults = KW(
:top_margin => :match,
:right_margin => :match,
:bottom_margin => :match,
:subplot_index => -1,
)
const _axis_defaults = KW(
@ -236,6 +237,7 @@ const _suppress_warnings = KW(
:y_discrete_indices => nothing,
:z_discrete_indices => nothing,
:subplot => nothing,
:subplot_index => nothing,
)
# add defaults for the letter versions

View File

@ -211,6 +211,27 @@ end
use_axis_field(ticks) = !(ticks in (nothing, :none))
# this method gets the start/end in percentage of the canvas for this axis direction
function plotly_domain(sp::Subplot, letter)
figw, figh = sp.plt.attr[:size]
# @show letter,figw,figh sp.plotarea
pcts = bbox_to_pcts(sp.plotarea, figw*px, figh*px)
# @show pcts
i1,i2 = (letter == :x ? (1,3) : (2,4))
# @show i1,i2
[pcts[i1], pcts[i1]+pcts[i2]]
end
# TODO: this should actually take into account labels, font sizes, etc
# sets (left, top, right, bottom)
function plotly_minpad(sp::Subplot)
(12mm, 2mm, 2mm, 8mm)
end
function _update_min_padding!(sp::Subplot{PlotlyBackend})
sp.minpad = plotly_minpad(sp)
end
# tickssym(letter) = symbol(letter * "ticks")
# limssym(letter) = symbol(letter * "lims")
# flipsym(letter) = symbol(letter * "flip")
@ -230,6 +251,14 @@ function plotly_axis(axis::Axis, sp::Subplot)
# fgcolor = webcolor(d[:foreground_color])
# tsym = tickssym(letter)
# spidx = sp.attr[:subplot_index]
# d_out[:xaxis] = "x$spidx"
# d_out[:yaxis] = "y$spidx"
if letter in (:x,:y)
ax[:domain] = plotly_domain(sp, letter)
ax[:anchor] = "$(letter==:x ? :y : :x)$(plotly_subplot_index(sp))"
end
rot = d[:rotation]
if rot != 0
ax[:tickangle] = rot
@ -279,76 +308,78 @@ end
function plotly_layout(plt::Plot)
d_out = KW()
# for now, we only support 1 subplot
if length(plt.subplots) > 1
warn("Subplots not supported yet")
end
sp = plt.subplots[1]
# # for now, we only support 1 subplot
# if length(plt.subplots) > 1
# warn("Subplots not supported yet")
# end
# sp = plt.subplots[1]
d_out[:width], d_out[:height] = plt.attr[:size]
# set the fields for the plot
d_out[:title] = sp.attr[:title]
d_out[:titlefont] = plotlyfont(sp.attr[:titlefont], webcolor(sp.attr[:foreground_color_title]))
# TODO: use subplot positioning logic
d_out[:margin] = KW(:l=>35, :b=>30, :r=>8, :t=>20)
d_out[:plot_bgcolor] = webcolor(sp.attr[:background_color_inside])
d_out[:paper_bgcolor] = webcolor(plt.attr[:background_color_outside])
# TODO: x/y axis tick values/labels
for sp in plt.subplots
sp_out = KW()
spidx = plotly_subplot_index(sp)
# if any(is3d, seriesargs)
if is3d(sp)
d_out[:scene] = KW(
:xaxis => plotly_axis(sp.attr[:xaxis], sp),
:yaxis => plotly_axis(sp.attr[:yaxis], sp),
:xzxis => plotly_axis(sp.attr[:zaxis], sp),
)
else
d_out[:xaxis] = plotly_axis(sp.attr[:xaxis], sp)
d_out[:yaxis] = plotly_axis(sp.attr[:yaxis], sp)
end
# set the fields for the plot
d_out[:title] = sp.attr[:title]
d_out[:titlefont] = plotlyfont(sp.attr[:titlefont], webcolor(sp.attr[:foreground_color_title]))
# legend
d_out[:showlegend] = sp.attr[:legend] != :none
if sp.attr[:legend] != :none
d_out[:legend] = KW(
:bgcolor => webcolor(sp.attr[:background_color_legend]),
:bordercolor => webcolor(sp.attr[:foreground_color_legend]),
:font => plotlyfont(sp.attr[:legendfont]),
)
end
# # TODO: use subplot positioning logic
# d_out[:margin] = KW(:l=>35, :b=>30, :r=>8, :t=>20)
d_out[:margin] = KW(:l=>0, :b=>0, :r=>0, :t=>30)
# if haskey(plt.attr, :annotation_list)
# append!(plt.attr[:annotation_list], anns)
# else
# plt.attr[:annotation_list] = anns
# end
d_out[:plot_bgcolor] = webcolor(sp.attr[:background_color_inside])
# annotations
anns = get(sp.attr, :annotations, [])
d_out[:annotations] = if isempty(anns)
KW[]
else
KW[get_annotation_dict(ann...) for ann in anns]
end
# TODO: x/y axis tick values/labels
# # arrows
# for sargs in seriesargs
# a = sargs[:arrow]
# if sargs[:seriestype] in (:path, :line) && typeof(a) <: Arrow
# add_arrows(sargs[:x], sargs[:y]) do xyprev, xy
# push!(d_out[:annotations], get_annotation_dict_for_arrow(sargs, xyprev, xy, a))
# end
# end
# end
# dumpdict(d_out,"",true)
# @show d_out[:annotations]
# if any(is3d, seriesargs)
if is3d(sp)
d_out[:scene] = KW(
symbol("xaxis$spidx") => plotly_axis(sp.attr[:xaxis], sp),
symbol("yaxis$spidx") => plotly_axis(sp.attr[:yaxis], sp),
symbol("zaxis$spidx") => plotly_axis(sp.attr[:zaxis], sp),
)
else
d_out[symbol("xaxis$spidx")] = plotly_axis(sp.attr[:xaxis], sp)
d_out[symbol("yaxis$spidx")] = plotly_axis(sp.attr[:yaxis], sp)
end
if ispolar(sp)
d_out[:direction] = "counterclockwise"
# legend
d_out[:showlegend] = sp.attr[:legend] != :none
if sp.attr[:legend] != :none
d_out[:legend] = KW(
:bgcolor => webcolor(sp.attr[:background_color_legend]),
:bordercolor => webcolor(sp.attr[:foreground_color_legend]),
:font => plotlyfont(sp.attr[:legendfont]),
)
end
# annotations
anns = get(sp.attr, :annotations, [])
d_out[:annotations] = if isempty(anns)
KW[]
else
KW[get_annotation_dict(ann...) for ann in anns]
end
# # arrows
# for sargs in seriesargs
# a = sargs[:arrow]
# if sargs[:seriestype] in (:path, :line) && typeof(a) <: Arrow
# add_arrows(sargs[:x], sargs[:y]) do xyprev, xy
# push!(d_out[:annotations], get_annotation_dict_for_arrow(sargs, xyprev, xy, a))
# end
# end
# end
# dumpdict(d_out,"",true)
# @show d_out[:annotations]
if ispolar(sp)
d_out[:direction] = "counterclockwise"
end
d_out
end
d_out
@ -374,14 +405,25 @@ const _plotly_markers = KW(
:hline => "line-ew",
)
function plotly_subplot_index(sp::Subplot)
spidx = sp.attr[:subplot_index]
spidx == 1 ? "" : spidx
end
# get a dictionary representing the series params (d is the Plots-dict, d_out is the Plotly-dict)
function plotly_series(plt::Plot, series::Series)
d = series.d
sp = d[:subplot]
d_out = KW()
# these are the axes that the series should be mapped to
spidx = plotly_subplot_index(sp)
d_out[:xaxis] = "x$spidx"
d_out[:yaxis] = "y$spidx"
x, y = collect(d[:x]), collect(d[:y])
d_out[:name] = d[:label]
st = d[:seriestype]
isscatter = st in (:scatter, :scatter3d)
hasmarker = isscatter || d[:markershape] != :none
@ -450,7 +492,12 @@ function plotly_series(plt::Plot, series::Series)
elseif st == :pie
d_out[:type] = "pie"
d_out[:labels] = x
d_out[:labels] = if haskey(d,:x_discrete_indices)
dvals = sp.attr[:xaxis].d[:discrete_values]
[dvals[idx] for idx in d[:x_discrete_indices]]
else
d[:x]
end
d_out[:values] = y
d_out[:hoverinfo] = "label+percent+name"
@ -556,14 +603,24 @@ end
# ----------------------------------------------------------------
# compute layout bboxes
function plotly_finalize(plt::Plot)
w, h = plt.attr[:size]
plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px)
update_child_bboxes!(plt.layout)
end
function Base.writemime(io::IO, ::MIME"image/png", plt::AbstractPlot{PlotlyBackend})
plotly_finalize(plt)
writemime_png_from_html(io, plt)
end
function Base.writemime(io::IO, ::MIME"text/html", plt::AbstractPlot{PlotlyBackend})
plotly_finalize(plt)
write(io, html_head(plt) * html_body(plt))
end
function Base.display(::PlotsDisplay, plt::AbstractPlot{PlotlyBackend})
standalone_html_window(plt)
plotly_finalize(plt)
standalone_html_window(plt)
end

View File

@ -211,7 +211,13 @@ end
# writemime_png_from_html(io, plt)
# end
function _update_min_padding!(sp::Subplot{PlotlyBackend})
sp.minpad = plotly_minpad(sp)
end
function Base.display(::PlotsDisplay, plt::Plot{PlotlyJSBackend})
plotly_finalize(plt)
display(plt.o)
end

View File

@ -67,7 +67,8 @@ function crop(parent::BoundingBox, child::BoundingBox)
BoundingBox(l, t, w, h)
end
# convert a bounding box from absolute coords to percentages... returns an array of percentages of figure size: [left, bottom, width, height]
# convert a bounding box from absolute coords to percentages...
# returns an array of percentages of figure size: [left, bottom, width, height]
function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true)
mms = Float64[f(bb).value for f in (left,bottom,width,height)]
if flipy

View File

@ -57,6 +57,7 @@ function plot(args...; kw...)
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
for (idx,sp) in enumerate(plt.subplots)
sp.plt = plt
sp.attr[:subplot_index] = idx
_update_subplot_args(plt, sp, copy(d), idx)
end