diff --git a/NEWS.md b/NEWS.md index 14f5bad3..197ce33d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,13 +3,42 @@ #### notes on release changes, ongoing development, and future planned work -- All new development should target 0.15! +- Minor version 0.17 is the last one to support Julia 0.6!! - Minor version 0.11 is the last one to support Julia 0.5!! - Critical bugfixes only - `backports` branch is for Julia 0.5 --- ## (current master) +- All new development should target Julia 0.7! + +## 0.17.3 +- Log-scale heatmap edge computation +- Fix size and dpi for GR and PyPlot +- Fix fillrange with line segments on PyPlot and Plotly +- fix flip for heatmap and image on GR +- New attributes for PGFPlots +- Widen axes for most series types and log scales +- Plotly: fix log scale with no ticks +- Fix axis flip on Plotly +- Fix hover and zcolor interaction in Plotly +- WebIO integration for PlotlyJS backend + +## 0.17.2 +- fix single subplot in plotly +- implement `(xyz)lims = :round` +- PyPlot: fix bg_legend = invisible() +- set fallback tick specification for axes with discrete values +- restructure of show methods + +## 0.17.1 +- Fix contour for PGFPlots +- 32Bit fix: Int64 -> Int +- Make series of shapes and segments toggle together in Plotly(JS) +- Fix marker arguments +- Fix processing order of series recipes +- Fix Plotly(JS) ribbon +- Contour plots with x,y in grid form on PyPlot ## 0.17.0 - Add GR dependency to make it the default backend diff --git a/src/arg_desc.jl b/src/arg_desc.jl index 199190db..56e3719d 100644 --- a/src/arg_desc.jl +++ b/src/arg_desc.jl @@ -65,6 +65,7 @@ const _arg_desc = KW( :html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.", :inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots", :dpi => "Number. Dots Per Inch of output figures", +:thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.", :display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.", :extra_kwargs => "KW (Dict{Symbol,Any}). Pass a map of extra keyword args which may be specific to a backend.", :fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides", @@ -110,7 +111,7 @@ const _arg_desc = KW( # axis args :guide => "String. Axis guide (label).", -:lims => "NTuple{2,Number}. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example).", +:lims => "NTuple{2,Number} or Symbol. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example). `:round` widens the limit to the nearest round number ie. [0.1,3.6]=>[0.0,4.0]", :ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`", :scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`", :rotation => "Number. Degrees rotation of tick labels.", @@ -139,5 +140,6 @@ const _arg_desc = KW( :gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)", :gridlinewidth => "Number. Width of the grid lines (in pixels)", :tick_direction => "Symbol. Direction of the ticks. `:in` or `:out`", -:showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`" +:showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`", +:widen => "Bool. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders. Defaults to `true`.", ) diff --git a/src/args.jl b/src/args.jl index 9bfed7cb..671485d0 100644 --- a/src/args.jl +++ b/src/args.jl @@ -301,6 +301,7 @@ const _plot_defaults = KW( :inset_subplots => nothing, # optionally pass a vector of (parent,bbox) tuples which are # the parent layout and the relative bounding box of inset subplots :dpi => DPI, # dots per inch for images, etc + :thickness_scaling => 1, :display_type => :auto, :extra_kwargs => KW(), ) @@ -381,6 +382,7 @@ const _axis_defaults = KW( :gridlinewidth => 0.5, :tick_direction => :in, :showaxis => true, + :widen => true, ) const _suppress_warnings = Set{Symbol}([ @@ -1473,26 +1475,19 @@ function _replace_linewidth(d::KW) end function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int) - pkg = plt.backend - globalIndex = d[:series_plotindex] - # add default values to our dictionary, being careful not to delete what we just added! for (k,v) in _series_defaults slice_arg!(d, d, k, v, commandIndex, false) end - # this is how many series belong to this subplot - # plotIndex = count(series -> series.d[:subplot] === sp && series.d[:primary], plt.series_list) - plotIndex = 0 - for series in sp.series_list - if series[:primary] - plotIndex += 1 - end - end - # plotIndex = count(series -> series[:primary], sp.series_list) - if get(d, :primary, true) - plotIndex += 1 - end + return d +end + + +function _update_series_attributes!(d::KW, plt::Plot, sp::Subplot) + pkg = plt.backend + globalIndex = d[:series_plotindex] + plotIndex = _series_index(d, sp) aliasesAndAutopick(d, :linestyle, _styleAliases, supported_styles(pkg), plotIndex) aliasesAndAutopick(d, :markershape, _markerAliases, supported_markers(pkg), plotIndex) @@ -1528,11 +1523,11 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int) # update markerstrokecolor d[:markerstrokecolor] = if d[:markerstrokecolor] == :match - plot_color(sp[:foreground_color_subplot], d[:markerstrokealpha]) + plot_color(sp[:foreground_color_subplot]) elseif d[:markerstrokecolor] == :auto - getSeriesRGBColor(plot_color(d[:markercolor], d[:markeralpha]), sp, plotIndex) + getSeriesRGBColor.(d[:markercolor], sp, plotIndex) else - getSeriesRGBColor(plot_color(d[:markerstrokecolor], d[:markerstrokealpha]), sp, plotIndex) + getSeriesRGBColor.(d[:markerstrokecolor], sp, plotIndex) end # if marker_z, fill_z or line_z are set, ensure we have a gradient @@ -1562,3 +1557,19 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int) _replace_linewidth(d) d end + +function _series_index(d, sp) + idx = 0 + for series in series_list(sp) + if series[:primary] + idx += 1 + end + if series == d + return idx + end + end + if get(d, :primary, true) + idx += 1 + end + return idx +end diff --git a/src/axes.jl b/src/axes.jl index db4137e3..1410b28b 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -246,19 +246,17 @@ function get_ticks(axis::Axis) ticks = ticks == :native ? :auto : ticks dvals = axis[:discrete_values] - cv, dv = if !isempty(dvals) - # discrete ticks... - n = length(dvals) - rng = if ticks == :auto - Int[round(Int,i) for i in linspace(1, n, 15)] - elseif ticks == :all - 1:n - elseif typeof(ticks) <: Int - Int[round(Int,i) for i in linspace(1, n, ticks)] - end - axis[:continuous_values][rng], dvals[rng] - elseif typeof(ticks) <: Symbol - if ispolar(axis.sps[1]) && axis[:letter] == :x + cv, dv = if typeof(ticks) <: Symbol + if !isempty(dvals) + # discrete ticks... + n = length(dvals) + rng = if ticks == :auto + Int[round(Int,i) for i in linspace(1, n, 15)] + else # if ticks == :all + 1:n + end + axis[:continuous_values][rng], dvals[rng] + elseif ispolar(axis.sps[1]) && axis[:letter] == :x #force theta axis to be full circle (collect(0:pi/4:7pi/4), string.(0:45:315)) else @@ -266,8 +264,13 @@ function get_ticks(axis::Axis) optimal_ticks_and_labels(axis) end elseif typeof(ticks) <: Union{AVec, Int} - # override ticks, but get the labels - optimal_ticks_and_labels(axis, ticks) + if !isempty(dvals) && typeof(ticks) <: Int + rng = Int[round(Int,i) for i in linspace(1, length(dvals), ticks)] + axis[:continuous_values][rng], dvals[rng] + else + # override ticks, but get the labels + optimal_ticks_and_labels(axis, ticks) + end elseif typeof(ticks) <: NTuple{2, Any} # assuming we're passed (ticks, labels) ticks @@ -415,21 +418,23 @@ end # ------------------------------------------------------------------------- # push the limits out slightly -function widen(lmin, lmax) - span = lmax - lmin +function widen(lmin, lmax, scale = :identity) + f, invf = scalefunc(scale), invscalefunc(scale) + span = f(lmax) - f(lmin) # eps = NaNMath.max(1e-16, min(1e-2span, 1e-10)) eps = NaNMath.max(1e-16, 0.03span) - lmin-eps, lmax+eps + invf(f(lmin)-eps), invf(f(lmax)+eps) end -# figure out if widening is a good idea. if there's a scale set it's too tricky, -# so lazy out and don't widen +# figure out if widening is a good idea. +const _widen_seriestypes = (:line, :path, :steppre, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d) + function default_should_widen(axis::Axis) should_widen = false - if axis[:scale] == :identity && !is_2tuple(axis[:lims]) + if !is_2tuple(axis[:lims]) for sp in axis.sps for series in series_list(sp) - if series.d[:seriestype] in (:scatter,) || series.d[:markershape] != :none + if series.d[:seriestype] in _widen_seriestypes should_widen = true end end @@ -438,6 +443,13 @@ function default_should_widen(axis::Axis) should_widen end +function round_limits(amin,amax) + scale = 10^(1-round(log10(amax - amin))) + amin = floor(amin*scale)/scale + amax = ceil(amax*scale)/scale + amin, amax +end + # using the axis extrema and limit overrides, return the min/max value for this axis function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis)) ex = axis[:extrema] @@ -466,8 +478,10 @@ function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis) else amin, amax end - elseif should_widen - widen(amin, amax) + elseif should_widen && axis[:widen] + widen(amin, amax, axis[:scale]) + elseif lims == :round + round_limits(amin,amax) else amin, amax end diff --git a/src/backends/gr.jl b/src/backends/gr.jl index b742eb15..483e717f 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -353,25 +353,18 @@ function gr_draw_markers(series::Series, x, y, msize, mz) msi = _cycle(msize, i) shape = _cycle(shapes, i) cfunc = isa(shape, Shape) ? gr_set_fillcolor : gr_set_markercolor - cfuncind = isa(shape, Shape) ? GR.setfillcolorind : GR.setmarkercolorind # draw a filled in shape, slightly bigger, to estimate a stroke if series[:markerstrokewidth] > 0 - cfunc(_cycle(series[:markerstrokecolor], i)) #, series[:markerstrokealpha]) + cfunc(get_markerstrokecolor(series, i)) + gr_set_transparency(get_markerstrokealpha(series, i)) gr_draw_marker(x[i], y[i], msi + series[:markerstrokewidth], shape) end - # draw the shape - if mz == nothing - cfunc(_cycle(series[:markercolor], i)) #, series[:markeralpha]) - else - # pick a color from the pre-loaded gradient - ci = round(Int, 1000 + _cycle(mz, i) * 255) - cfuncind(ci) - gr_set_transparency(_gr_gradient_alpha[ci-999]) - end - # don't draw filled area if marker shape is 1D + # draw the shape - don't draw filled area if marker shape is 1D if !(shape in (:hline, :vline, :+, :x)) + cfunc(get_markercolor(series, i)) + gr_set_transparency(get_markeralpha(series, i)) gr_draw_marker(x[i], y[i], msi, shape) end end @@ -390,7 +383,7 @@ end function gr_set_line(lw, style, c) #, a) GR.setlinetype(gr_linetype[style]) w, h = gr_plot_size - GR.setlinewidth(max(0, lw / ((w + h) * 0.001))) + GR.setlinewidth(_gr_thickness_scaling[1] * max(0, lw / ((w + h) * 0.001))) gr_set_linecolor(c) #, a) end @@ -403,6 +396,7 @@ end # this stores the conversion from a font pointsize to "percentage of window height" (which is what GR uses) const _gr_point_mult = 0.0018 * ones(1) +const _gr_thickness_scaling = ones(1) # set the font attributes... assumes _gr_point_mult has been populated already function gr_set_font(f::Font; halign = f.halign, valign = f.valign, @@ -549,6 +543,9 @@ end function gr_display(plt::Plot, fmt="") GR.clearws() + _gr_thickness_scaling[1] = plt[:thickness_scaling] + dpi_factor = plt[:dpi] / Plots.DPI + # collect some monitor/display sizes in meters and pixels display_width_meters, display_height_meters, display_width_px, display_height_px = GR.inqdspsize() display_width_ratio = display_width_meters / display_width_px @@ -557,14 +554,6 @@ function gr_display(plt::Plot, fmt="") # compute the viewport_canvas, normalized to the larger dimension viewport_canvas = Float64[0,1,0,1] w, h = plt[:size] - if !haskey(ENV, "PLOTS_TEST") - dpi_factor = plt[:dpi] / DPI - if fmt == "png" - dpi_factor *= 6 - end - else - dpi_factor = 1 - end gr_plot_size[:] = [w, h] if w > h ratio = float(h) / w @@ -587,7 +576,7 @@ function gr_display(plt::Plot, fmt="") # update point mult px_per_pt = px / pt - _gr_point_mult[1] = 1.5 * px_per_pt / max(h,w) + _gr_point_mult[1] = 1.5 * _gr_thickness_scaling[1] * px_per_pt / max(h,w) # subplots: for sp in plt.subplots @@ -633,13 +622,14 @@ function gr_get_ticks_size(ticks, i) end function _update_min_padding!(sp::Subplot{GRBackend}) + dpi = sp.plt[:thickness_scaling] if !haskey(ENV, "GKSwstype") if isijulia() || (isdefined(Main, :Juno) && Juno.isactive()) ENV["GKSwstype"] = "svg" end end # Add margin given by the user - leftpad = 2mm + sp[:left_margin] + leftpad = 4mm + sp[:left_margin] toppad = 2mm + sp[:top_margin] rightpad = 4mm + sp[:right_margin] bottompad = 2mm + sp[:bottom_margin] @@ -675,7 +665,7 @@ function _update_min_padding!(sp::Subplot{GRBackend}) if sp[:yaxis][:guide] != "" leftpad += 4mm end - sp.minpad = (leftpad, toppad, rightpad, bottompad) + sp.minpad = Tuple(dpi * [leftpad, toppad, rightpad, bottompad]) end function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) @@ -725,6 +715,12 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end if st == :heatmap outside_ticks = true + for ax in (sp[:xaxis], sp[:yaxis]) + v = series[ax[:letter]] + if diff(collect(extrema(diff(v))))[1] > 1e-6*std(v) + warn("GR: heatmap only supported with equally spaced data.") + end + end x, y = heatmap_edges(series[:x], sp[:xaxis][:scale]), heatmap_edges(series[:y], sp[:yaxis][:scale]) xy_lims = x[1], x[end], y[1], y[end] expand_extrema!(sp[:xaxis], x) @@ -776,7 +772,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # draw the axes gr_set_font(tickfont(xaxis)) - GR.setlinewidth(1) + GR.setlinewidth(sp.plt[:thickness_scaling]) if is3d(sp) zmin, zmax = gr_lims(zaxis, true) @@ -1046,10 +1042,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end if series[:markershape] != :none - if series[:marker_z] != nothing - zmin, zmax = extrema(series[:marker_z]) - GR.setspace(zmin, zmax, 0, 90) - end gr_draw_markers(series, x, y, clims) end @@ -1100,6 +1092,10 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) elseif st == :heatmap xmin, xmax, ymin, ymax = xy_lims zmin, zmax = clims + m, n = length(x), length(y) + xinds = sort(1:m, rev = xaxis[:flip]) + yinds = sort(1:n, rev = yaxis[:flip]) + z = reshape(reshape(z, m, n)[xinds, yinds], m*n) GR.setspace(zmin, zmax, 0, 90) grad = isa(series[:fillcolor], ColorGradient) ? series[:fillcolor] : cgrad() colors = [plot_color(grad[clamp((zi-zmin) / (zmax-zmin), 0, 1)], series[:fillalpha]) for zi=z] @@ -1203,7 +1199,10 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) elseif st == :image z = transpose_z(series, series[:z].surf, true)' - w, h = size(z) + w, h = length(x), length(y) + xinds = sort(1:w, rev = xaxis[:flip]) + yinds = sort(1:h, rev = yaxis[:flip]) + z = z[xinds, yinds] xmin, xmax = ignorenan_extrema(series[:x]); ymin, ymax = ignorenan_extrema(series[:y]) if eltype(z) <: Colors.AbstractGray grey = round.(UInt8, float(z) * 255) diff --git a/src/backends/hdf5.jl b/src/backends/hdf5.jl index 44a72d59..9889dadc 100644 --- a/src/backends/hdf5.jl +++ b/src/backends/hdf5.jl @@ -240,10 +240,6 @@ end # ---------------------------------------------------------------- -_show(io::IO, mime::MIME"text/plain", plt::Plot{HDF5Backend}) = nothing #Don't show - -# ---------------------------------------------------------------- - # Display/show the plot (open a GUI window, or browser page, for example). function _display(plt::Plot{HDF5Backend}) msg = "HDF5 interface does not support `display()` function." diff --git a/src/backends/inspectdr.jl b/src/backends/inspectdr.jl index d0d3e285..8cc2a22c 100644 --- a/src/backends/inspectdr.jl +++ b/src/backends/inspectdr.jl @@ -523,7 +523,6 @@ for (mime, fmt) in _inspectdr_mimeformats_nodpi _inspectdr_show(io, mime, _inspectdr_getmplot(plt.o), plt[:size]...) end end -_show(io::IO, mime::MIME"text/plain", plt::Plot{InspectDRBackend}) = nothing #Don't show # ---------------------------------------------------------------- diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index 5493cfd3..aae1a356 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -8,11 +8,12 @@ end const _pgfplots_attr = merge_with_base_supported([ :annotations, - # :background_color_legend, + :background_color_legend, :background_color_inside, # :background_color_outside, - # :foreground_color_legend, :foreground_color_grid, :foreground_color_axis, - # :foreground_color_text, :foreground_color_border, + # :foreground_color_legend, + :foreground_color_grid, :foreground_color_axis, + :foreground_color_text, :foreground_color_border, :label, :seriescolor, :seriesalpha, :linecolor, :linestyle, :linewidth, :linealpha, @@ -149,6 +150,10 @@ function pgf_colormap(grad::ColorGradient) end,", ") end +pgf_thickness_scaling(plt::Plot) = plt[:thickness_scaling] +pgf_thickness_scaling(sp::Subplot) = pgf_thickness_scaling(sp.plt) +pgf_thickness_scaling(series) = pgf_thickness_scaling(series[:subplot]) + function pgf_fillstyle(d, i = 1) cstr,a = pgf_color(get_fillcolor(d, i)) fa = get_fillalpha(d, i) @@ -158,52 +163,56 @@ function pgf_fillstyle(d, i = 1) "fill = $cstr, fill opacity=$a" end -function pgf_linestyle(d, i = 1) - cstr,a = pgf_color(get_linecolor(d, i)) - la = get_linealpha(d, i) - if la != nothing - a = la - end +function pgf_linestyle(linewidth::Real, color, α = 1, linestyle = "solid") + cstr, a = pgf_color(plot_color(color, α)) """ color = $cstr, - draw opacity=$a, - line width=$(get_linewidth(d, i)), - $(get(_pgfplots_linestyles, get_linestyle(d, i), "solid"))""" + draw opacity = $a, + line width = $linewidth, + $(get(_pgfplots_linestyles, linestyle, "solid"))""" +end + +function pgf_linestyle(d, i = 1) + lw = pgf_thickness_scaling(d) * get_linewidth(d, i) + lc = get_linecolor(d, i) + la = get_linealpha(d, i) + ls = get_linestyle(d, i) + return pgf_linestyle(lw, lc, la, ls) +end + +function pgf_font(fontsize, thickness_scaling = 1, font = "\\selectfont") + fs = fontsize * thickness_scaling + return string("{\\fontsize{", fs, " pt}{", 1.3fs, " pt}", font, "}") end function pgf_marker(d, i = 1) shape = _cycle(d[:markershape], i) - cstr, a = pgf_color(_cycle(d[:markercolor], i)) - if d[:markeralpha] != nothing - a = _cycle(d[:markeralpha], i) - end - cstr_stroke, a_stroke = pgf_color(_cycle(d[:markerstrokecolor], i)) - if d[:markerstrokealpha] != nothing - a_stroke = _cycle(d[:markerstrokealpha], i) - end + cstr, a = pgf_color(plot_color(get_markercolor(d, i), get_markeralpha(d, i))) + cstr_stroke, a_stroke = pgf_color(plot_color(get_markerstrokecolor(d, i), get_markerstrokealpha(d, i))) """ mark = $(get(_pgfplots_markers, shape, "*")), - mark size = $(0.5 * _cycle(d[:markersize], i)), + mark size = $(pgf_thickness_scaling(d) * 0.5 * _cycle(d[:markersize], i)), mark options = { color = $cstr_stroke, draw opacity = $a_stroke, fill = $cstr, fill opacity = $a, - line width = $(_cycle(d[:markerstrokewidth], i)), + line width = $(pgf_thickness_scaling(d) * _cycle(d[:markerstrokewidth], i)), rotate = $(shape == :dtriangle ? 180 : 0), $(get(_pgfplots_linestyles, _cycle(d[:markerstrokestyle], i), "solid")) }""" end -function pgf_add_annotation!(o,x,y,val) +function pgf_add_annotation!(o, x, y, val, thickness_scaling = 1) # Construct the style string. # Currently supports color and orientation cstr,a = pgf_color(val.font.color) push!(o, PGFPlots.Plots.Node(val.str, # Annotation Text - x, y, - style=""" - $(get(_pgf_annotation_halign,val.font.halign,"")), - color=$cstr, draw opacity=$(convert(Float16,a)), - rotate=$(val.font.rotation) - """)) + x, y, + style=""" + $(get(_pgf_annotation_halign,val.font.halign,"")), + color=$cstr, draw opacity=$(convert(Float16,a)), + rotate=$(val.font.rotation), + font=$(pgf_font(val.font.pointsize, thickness_scaling)) + """)) end # -------------------------------------------------------------------------------------- @@ -222,10 +231,6 @@ function pgf_series(sp::Subplot, series::Series) straightline_data(series) elseif st == :shape shape_data(series) - elseif d[:marker_z] != nothing - # If a marker_z is used pass it as third coordinate to a 2D plot. - # See "Scatter Plots" in PGFPlots documentation - d[:x], d[:y], d[:marker_z] elseif ispolar(sp) theta, r = filter_radial_data(d[:x], d[:y], axis_limits(sp[:yaxis])) rad2deg.(theta), r @@ -380,8 +385,9 @@ function pgf_axis(sp::Subplot, letter) # axis guide kw[Symbol(letter,:label)] = axis[:guide] - # Add ticklabel rotations - push!(style, "$(letter)ticklabel style={rotate = $(axis[:rotation])}") + # Add label font + cstr, α = pgf_color(plot_color(axis[:guidefontcolor])) + push!(style, string(letter, "label style = {font = ", pgf_font(axis[:guidefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:guidefontrotation], "}")) # flip/reverse? axis[:flip] && push!(style, "$letter dir=reverse") @@ -437,6 +443,9 @@ function pgf_axis(sp::Subplot, letter) push!(style, string(letter, "ticklabels = {}")) end push!(style, string(letter, "tick align = ", (axis[:tick_direction] == :out ? "outside" : "inside"))) + cstr, α = pgf_color(plot_color(axis[:tickfontcolor])) + push!(style, string(letter, "ticklabel style = {font = ", pgf_font(axis[:tickfontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:tickfontrotation], "}")) + push!(style, string(letter, " grid style = {", pgf_linestyle(pgf_thickness_scaling(sp) * axis[:gridlinewidth], axis[:foreground_color_grid], axis[:gridalpha], axis[:gridstyle]), "}")) end # framestyle @@ -449,7 +458,7 @@ function pgf_axis(sp::Subplot, letter) if framestyle == :zerolines push!(style, string("extra ", letter, " ticks = 0")) push!(style, string("extra ", letter, " tick labels = ")) - push!(style, string("extra ", letter, " tick style = {grid = major, major grid style = {color = black, draw opacity=1.0, line width=0.5), solid}}")) + push!(style, string("extra ", letter, " tick style = {grid = major, major grid style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_axis], 1.0), "}}")) end if !axis[:showaxis] @@ -457,6 +466,8 @@ function pgf_axis(sp::Subplot, letter) end if !axis[:showaxis] || framestyle in (:zerolines, :grid, :none) push!(style, string(letter, " axis line style = {draw opacity = 0}")) + else + push!(style, string(letter, " axis line style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_axis], 1.0), "}")) end # return the style list and KW args @@ -501,6 +512,8 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) if sp[:title] != "" kw[:title] = "$(sp[:title])" + cstr, α = pgf_color(plot_color(sp[:titlefontcolor])) + push!(style, string("title style = {font = ", pgf_font(sp[:titlefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", sp[:titlefontrotation], "}")) end if sp[:aspect_ratio] in (1, :equal) @@ -511,6 +524,8 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) if haskey(_pgfplots_legend_pos, legpos) kw[:legendPos] = _pgfplots_legend_pos[legpos] end + cstr, a = pgf_color(plot_color(sp[:background_color_legend])) + push!(style, string("legend style = {", pgf_linestyle(pgf_thickness_scaling(sp), sp[:foreground_color_legend], 1.0, "solid"), ",", "fill = $cstr,", "font = ", pgf_font(sp[:legendfontsize], pgf_thickness_scaling(sp)), "}")) if any(s[:seriestype] == :contour for s in series_list(sp)) kw[:view] = "{0}{90}" @@ -564,13 +579,13 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend}) # add series annotations anns = series[:series_annotations] for (xi,yi,str,fnt) in EachAnn(anns, series[:x], series[:y]) - pgf_add_annotation!(o, xi, yi, PlotText(str, fnt)) + pgf_add_annotation!(o, xi, yi, PlotText(str, fnt), pgf_thickness_scaling(series)) end end # add the annotations for ann in sp[:annotations] - pgf_add_annotation!(o, locate_annotation(sp, ann...)...) + pgf_add_annotation!(o, locate_annotation(sp, ann...)..., pgf_thickness_scaling(sp)) end diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index f1daf3d7..333a4276 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -263,22 +263,22 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot) end ax[:tickangle] = -axis[:rotation] + ax[:type] = plotly_scale(axis[:scale]) lims = axis_limits(axis) - if axis[:ticks] != :native || axis[:lims] != :auto + if axis[:ticks] != :native || axis[:lims] != :auto ax[:range] = map(scalefunc(axis[:scale]), lims) end if !(axis[:ticks] in (nothing, :none, false)) ax[:titlefont] = plotly_font(guidefont(axis)) - ax[:type] = plotly_scale(axis[:scale]) ax[:tickfont] = plotly_font(tickfont(axis)) ax[:tickcolor] = framestyle in (:zerolines, :grid) || !axis[:showaxis] ? rgba_string(invisible()) : rgb_string(axis[:foreground_color_axis]) ax[:linecolor] = rgba_string(axis[:foreground_color_axis]) # flip if axis[:flip] - ax[:autorange] = "reversed" + ax[:range] = reverse(ax[:range]) end # ticks @@ -328,9 +328,11 @@ function plotly_layout(plt::Plot) d_out[:annotations] = KW[] + multiple_subplots = length(plt.subplots) > 1 + for sp in plt.subplots - spidx = sp[:subplot_index] - x_idx, y_idx = plotly_link_indicies(plt, sp) + spidx = multiple_subplots ? sp[:subplot_index] : "" + x_idx, y_idx = multiple_subplots ? plotly_link_indicies(plt, sp) : ("", "") # add an annotation for the title... positioned horizontally relative to plotarea, # but vertically just below the top of the subplot bounding box if sp[:title] != "" @@ -388,6 +390,7 @@ function plotly_layout(plt::Plot) :bgcolor => rgba_string(sp[:background_color_legend]), :bordercolor => rgba_string(sp[:foreground_color_legend]), :font => plotly_font(legendfont(sp)), + :tracegroupgap => 0, :x => xpos, :y => ypos ) @@ -490,7 +493,7 @@ end function plotly_data(series::Series, letter::Symbol, data) axis = series[:subplot][Symbol(letter, :axis)] - + data = if axis[:ticks] == :native && data != nothing plotly_native_data(axis, data) else @@ -516,7 +519,7 @@ function plotly_native_data(axis::Axis, data::AbstractArray) construct_categorical_data(data, axis) elseif axis[:formatter] in (datetimeformatter, dateformatter, timeformatter) plotly_convert_to_datetime(data, axis[:formatter]) - else + else data end end @@ -584,6 +587,8 @@ function plotly_series(plt::Plot, series::Series) return plotly_series_segments(series, d_out, x, y, z) elseif st == :heatmap + x = heatmap_edges(x, sp[:xaxis][:scale]) + y = heatmap_edges(y, sp[:yaxis][:scale]) d_out[:type] = "heatmap" d_out[:x], d_out[:y], d_out[:z] = x, y, z d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) @@ -632,31 +637,17 @@ function plotly_series(plt::Plot, series::Series) # add "marker" if hasmarker + inds = eachindex(x) d_out[:marker] = KW( :symbol => get(_plotly_markers, series[:markershape], string(series[:markershape])), # :opacity => series[:markeralpha], - :size => 2 * series[:markersize], - # :color => rgba_string(series[:markercolor]), + :size => 2 * _cycle(series[:markersize], inds), + :color => rgba_string.(plot_color.(get_markercolor.(series, inds), get_markeralpha.(series, inds))), :line => KW( - :color => _cycle(rgba_string.(series[:markerstrokecolor]),eachindex(series[:x])), - :width => series[:markerstrokewidth], + :color => rgba_string.(plot_color.(get_markerstrokecolor.(series, inds), get_markerstrokealpha.(series, inds))), + :width => _cycle(series[:markerstrokewidth], inds), ), ) - - # gotta hack this (for now?) since plotly can't handle rgba values inside the gradient - if series[:marker_z] == nothing - d_out[:marker][:color] = _cycle(rgba_string.(series[:markercolor]),eachindex(series[:x])) - else - # grad = ColorGradient(series[:markercolor], alpha=series[:markeralpha]) - # grad = as_gradient(series[:markercolor], series[:markeralpha]) - cmin, cmax = get_clims(sp) - # zrange = zmax == zmin ? 1 : zmax - zmin # if all marker_z values are the same, plot all markers same color (avoids division by zero in next line) - d_out[:marker][:color] = [clamp(zi, cmin, cmax) for zi in series[:marker_z]] - d_out[:marker][:cmin] = cmin - d_out[:marker][:cmax] = cmax - d_out[:marker][:colorscale] = plotly_colorscale(series[:markercolor], series[:markeralpha]) - d_out[:marker][:showscale] = hascolorbar(sp) - end end plotly_polar!(d_out, series) @@ -674,13 +665,14 @@ function plotly_series_shapes(plt::Plot, series::Series) # these are the axes that the series should be mapped to x_idx, y_idx = plotly_link_indicies(plt, series[:subplot]) - base_d = KW() - base_d[:xaxis] = "x$(x_idx)" - base_d[:yaxis] = "y$(y_idx)" - base_d[:name] = series[:label] - # base_d[:legendgroup] = series[:label] + d_base = KW( + :xaxis => "x$(x_idx)", + :yaxis => "y$(y_idx)", + :name => series[:label], + :legendgroup => series[:label], + ) - x, y = (plotly_data(series, letter, data) + x, y = (plotly_data(series, letter, data) for (letter, data) in zip((:x, :y), shape_data(series)) ) @@ -688,7 +680,7 @@ function plotly_series_shapes(plt::Plot, series::Series) length(rng) < 2 && continue # to draw polygons, we actually draw lines with fill - d_out = merge(base_d, KW( + d_out = merge(d_base, KW( :type => "scatter", :mode => "lines", :x => vcat(x[rng], x[rng[1]]), @@ -709,9 +701,11 @@ function plotly_series_shapes(plt::Plot, series::Series) d_outs[i] = d_out end if series[:fill_z] != nothing - push!(d_outs, plotly_colorbar_hack(series, base_d, :fill)) + push!(d_outs, plotly_colorbar_hack(series, d_base, :fill)) elseif series[:line_z] != nothing - push!(d_outs, plotly_colorbar_hack(series, base_d, :line)) + push!(d_outs, plotly_colorbar_hack(series, d_base, :line)) + elseif series[:marker_z] != nothing + push!(d_outs, plotly_colorbar_hack(series, d_base, :marker)) end d_outs end @@ -729,10 +723,11 @@ function plotly_series_segments(series::Series, d_base::KW, x, y, z) d_outs = Vector{KW}((hasfillrange ? 2 : 1 ) * length(segments)) for (i,rng) in enumerate(segments) - length(rng) < 2 && continue + !isscatter && length(rng) < 2 && continue d_out = deepcopy(d_base) d_out[:showlegend] = i==1 ? should_add_to_legend(series) : false + d_out[:legendgroup] = series[:label] # set the type if st in (:path, :scatter, :scattergl, :straightline) @@ -766,30 +761,15 @@ function plotly_series_segments(series::Series, d_base::KW, x, y, z) # add "marker" if hasmarker d_out[:marker] = KW( - :symbol => get(_plotly_markers, series[:markershape], string(series[:markershape])), + :symbol => get(_plotly_markers, _cycle(series[:markershape], i), string(_cycle(series[:markershape], i))), # :opacity => series[:markeralpha], - :size => 2 * series[:markersize], - # :color => rgba_string(series[:markercolor]), + :size => 2 * _cycle(series[:markersize], i), + :color => rgba_string(plot_color(get_markercolor(series, i), get_markeralpha(series, i))), :line => KW( - :color => _cycle(rgba_string.(series[:markerstrokecolor]), eachindex(rng)), - :width => series[:markerstrokewidth], + :color => rgba_string(plot_color(get_markerstrokecolor(series, i), get_markerstrokealpha(series, i))), + :width => _cycle(series[:markerstrokewidth], i), ), ) - - # gotta hack this (for now?) since plotly can't handle rgba values inside the gradient - if series[:marker_z] == nothing - d_out[:marker][:color] = _cycle(rgba_string.(series[:markercolor]), eachindex(rng)) - else - # grad = ColorGradient(series[:markercolor], alpha=series[:markeralpha]) - # grad = as_gradient(series[:markercolor], series[:markeralpha]) - cmin, cmax = get_clims(sp) - # zrange = zmax == zmin ? 1 : zmax - zmin # if all marker_z values are the same, plot all markers same color (avoids division by zero in next line) - d_out[:marker][:color] = [clamp(zi, cmin, cmax) for zi in _cycle(series[:marker_z], rng)] - d_out[:marker][:cmin] = cmin - d_out[:marker][:cmax] = cmax - d_out[:marker][:colorscale] = plotly_colorscale(series[:markercolor], series[:markeralpha]) - d_out[:marker][:showscale] = hascolorbar(sp) - end end # add "line" @@ -809,7 +789,7 @@ function plotly_series_segments(series::Series, d_base::KW, x, y, z) end plotly_polar!(d_out, series) - plotly_hover!(d_out, series[:hover]) + plotly_hover!(d_out, _cycle(series[:hover], rng)) if hasfillrange # if hasfillrange is true, return two dictionaries (one for original @@ -825,14 +805,14 @@ function plotly_series_segments(series::Series, d_base::KW, x, y, z) series[:fillrange] = (f1, f2) end if isa(series[:fillrange], AbstractVector) - d_out_fillrange[:y] = series[:fillrange] + d_out_fillrange[:y] = series[:fillrange][rng] delete!(d_out_fillrange, :fill) delete!(d_out_fillrange, :fillcolor) else # if fillrange is a tuple with upper and lower limit, d_out_fillrange # is the series that will do the filling - d_out_fillrange[:x], d_out_fillrange[:y] = - concatenate_fillrange(x[rng], series[:fillrange][rng]) + fillrng = Tuple(series[:fillrange][i][rng] for i in 1:2) + d_out_fillrange[:x], d_out_fillrange[:y] = concatenate_fillrange(x[rng], fillrng) d_out_fillrange[:line][:width] = 0 delete!(d_out, :fill) delete!(d_out, :fillcolor) @@ -848,6 +828,8 @@ function plotly_series_segments(series::Series, d_base::KW, x, y, z) push!(d_outs, plotly_colorbar_hack(series, d_base, :line)) elseif series[:fill_z] != nothing push!(d_outs, plotly_colorbar_hack(series, d_base, :fill)) + elseif series[:marker_z] != nothing + push!(d_outs, plotly_colorbar_hack(series, d_base, :marker)) end d_outs @@ -858,6 +840,7 @@ function plotly_colorbar_hack(series::Series, d_base::KW, sym::Symbol) cmin, cmax = get_clims(series[:subplot]) d_out[:showlegend] = false d_out[:type] = is3d(series) ? :scatter3d : :scatter + d_out[:hoverinfo] = :none d_out[:mode] = :markers d_out[:x], d_out[:y] = [series[:x][1]], [series[:y][1]] if is3d(series) @@ -866,6 +849,7 @@ function plotly_colorbar_hack(series::Series, d_base::KW, sym::Symbol) # zrange = zmax == zmin ? 1 : zmax - zmin # if all marker_z values are the same, plot all markers same color (avoids division by zero in next line) d_out[:marker] = KW( :size => 0, + :opacity => 0, :color => [0.5], :cmin => cmin, :cmax => cmax, @@ -944,17 +928,7 @@ end # ---------------------------------------------------------------- -function _show(io::IO, ::MIME"image/png", plt::Plot{PlotlyBackend}) - # show_png_from_html(io, plt) - error("png output from the plotly backend is not supported. Please use plotlyjs instead.") -end - -function _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyBackend}) - error("svg output from the plotly backend is not supported. Please use plotlyjs instead.") -end - -function Base.show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend}) - prepare_output(plt) +function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend}) write(io, html_head(plt) * html_body(plt)) end diff --git a/src/backends/plotlyjs.jl b/src/backends/plotlyjs.jl index 1bcc3845..e2883220 100644 --- a/src/backends/plotlyjs.jl +++ b/src/backends/plotlyjs.jl @@ -1,5 +1,5 @@ @require Revise begin - Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "plotlyjs.jl")) + Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "plotlyjs.jl")) end # https://github.com/spencerlyon2/PlotlyJS.jl @@ -88,8 +88,7 @@ end # ---------------------------------------------------------------- -function Base.show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) - prepare_output(plt) +function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) if isijulia() && !_use_remote[] write(io, PlotlyJS.html_body(PlotlyJS.JupyterPlot(plt.o))) else @@ -121,6 +120,12 @@ function _display(plt::Plot{PlotlyJSBackend}) end end +@require WebIO begin + function WebIO.render(plt::Plot{PlotlyJSBackend}) + prepare_output(plt) + WebIO.render(plt.o) + end +end function closeall(::PlotlyJSBackend) if !isplotnull() && isa(current().o, PlotlyJS.SyncPlot) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 38d9f15f..fa86acc3 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -395,14 +395,14 @@ function py_bbox_title(ax) end function py_dpi_scale(plt::Plot{PyPlotBackend}, ptsz) - ptsz * plt[:dpi] / DPI + ptsz end # --------------------------------------------------------------------------- # Create the window/figure for this backend. function _create_backend_figure(plt::Plot{PyPlotBackend}) - w,h = map(px2inch, plt[:size]) + w,h = map(px2inch, Tuple(s * plt[:dpi] / Plots.DPI for s in plt[:size])) # # reuse the current figure? fig = if plt[:overwrite_figure] @@ -570,12 +570,12 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) if series[:markershape] != :none && st in (:path, :scatter, :path3d, :scatter3d, :steppre, :steppost, :bar) - if series[:marker_z] == nothing - extrakw[:c] = series[:markershape] in (:+, :x, :hline, :vline) ? py_markerstrokecolor(series) : py_color_fix(py_markercolor(series), x) + markercolor = if any(typeof(series[arg]) <: AVec for arg in (:markercolor, :markeralpha)) || series[:marker_z] != nothing + py_color(plot_color.(get_markercolor.(series, eachindex(x)), get_markeralpha.(series, eachindex(x)))) else - extrakw[:c] = convert(Vector{Float64}, series[:marker_z]) - extrakw[:cmap] = py_markercolormap(series) + py_color(plot_color(series[:markercolor], series[:markeralpha])) end + extrakw[:c] = py_color_fix(markercolor, x) xyargs = if st == :bar && !isvertical(series) (y, x) else @@ -591,11 +591,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) msc = py_markerstrokecolor(series) lw = py_dpi_scale(plt, series[:markerstrokewidth]) for i=1:length(y) - extrakw[:c] = if series[:marker_z] == nothing - py_color_fix(py_color(_cycle(series[:markercolor],i)), x) - else - extrakw[:c] - end + extrakw[:c] = _cycle(markercolor, i) push!(handle, ax[:scatter](_cycle(x,i), _cycle(y,i); label = series[:label], @@ -638,6 +634,12 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) if st in (:contour, :contour3d) z = transpose_z(series, z.surf) + if typeof(x)<:Plots.Surface + x = Plots.transpose_z(series, x.surf) + end + if typeof(y)<:Plots.Surface + y = Plots.transpose_z(series, y.surf) + end if st == :contour3d extrakw[:extend3d] = true @@ -835,9 +837,9 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) end n = length(dim1) args = if typeof(fillrange) <: Union{Real, AVec} - dim1, expand_data(fillrange, n), dim2 + dim1, _cycle(fillrange, rng), dim2 elseif is_2tuple(fillrange) - dim1, expand_data(fillrange[1], n), expand_data(fillrange[2], n) + dim1, _cycle(fillrange[1], rng), _cycle(fillrange[2], rng) end handle = ax[f](args..., trues(n), false, py_fillstepstyle(st); @@ -953,10 +955,10 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) w, h = plt[:size] fig = plt.o fig[:clear]() - dpi = plt[:dpi] - fig[:set_size_inches](w/dpi, h/dpi, forward = true) + dpi = plt[:thickness_scaling] * plt[:dpi] + fig[:set_size_inches](w/DPI/plt[:thickness_scaling], h/DPI/plt[:thickness_scaling], forward = true) fig[set_facecolor_sym](py_color(plt[:background_color_outside])) - fig[:set_dpi](dpi) + fig[:set_dpi](plt[:dpi]) # resize the window PyPlot.plt[:get_current_fig_manager]()[:resize](w, h) @@ -1014,10 +1016,16 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) kw[:ticks] = locator kw[:format] = formatter kw[:boundaries] = vcat(0, kw[:values] + 0.5) - elseif any(colorbar_series[attr] != nothing for attr in (:line_z, :fill_z)) + elseif any(colorbar_series[attr] != nothing for attr in (:line_z, :fill_z, :marker_z)) cmin, cmax = get_clims(sp) norm = pycolors[:Normalize](vmin = cmin, vmax = cmax) - f = colorbar_series[:line_z] != nothing ? py_linecolormap : py_fillcolormap + f = if colorbar_series[:line_z] != nothing + py_linecolormap + elseif colorbar_series[:fill_z] != nothing + py_fillcolormap + else + py_markercolormap + end cmap = pycmap[:ScalarMappable](norm = norm, cmap = f(colorbar_series)) cmap[:set_array]([]) handle = cmap @@ -1077,7 +1085,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend}) pyaxis[Symbol(:tick_, pos)]() # the tick labels end py_set_scale(ax, axis) - axis[:ticks] != :native || axis[:lims] != :auto ? py_set_lims(ax, axis) : nothing + axis[:ticks] != :native ? py_set_lims(ax, axis) : nothing if ispolar(sp) && letter == :y ax[:set_rlabel_position](90) end @@ -1201,7 +1209,9 @@ function _update_min_padding!(sp::Subplot{PyPlotBackend}) rightpad += sp[:right_margin] bottompad += sp[:bottom_margin] - sp.minpad = (leftpad, toppad, rightpad, bottompad) + dpi_factor = sp.plt[:thickness_scaling] * Plots.DPI / sp.plt[:dpi] + + sp.minpad = Tuple(dpi_factor .* [leftpad, toppad, rightpad, bottompad]) end @@ -1262,8 +1272,8 @@ function py_add_legend(plt::Plot, sp::Subplot, ax) linewidth = py_dpi_scale(plt, clamp(get_linewidth(series), 0, 5)), linestyle = py_linestyle(:path, get_linestyle(series)), marker = py_marker(series[:markershape]), - markeredgecolor = py_markerstrokecolor(series), - markerfacecolor = series[:marker_z] == nothing ? py_markercolor(series) : py_color(series[:markercolor][0.5]) + markeredgecolor = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)), + markerfacecolor = series[:marker_z] == nothing ? py_color(get_markercolor(series), get_markeralpha(series)) : py_color(series[:markercolor][0.5]) ) else series[:serieshandle][1] @@ -1278,23 +1288,17 @@ function py_add_legend(plt::Plot, sp::Subplot, ax) labels, loc = get(_pyplot_legend_pos, leg, "best"), scatterpoints = 1, - fontsize = py_dpi_scale(plt, sp[:legendfontsize]) - # family = sp[:legendfont].family - # framealpha = 0.6 + fontsize = py_dpi_scale(plt, sp[:legendfontsize]), + facecolor = py_color(sp[:background_color_legend]), + edgecolor = py_color(sp[:foreground_color_legend]), + framealpha = alpha(plot_color(sp[:background_color_legend])), ) leg[:set_zorder](1000) sp[:legendtitle] != nothing && leg[:set_title](sp[:legendtitle]) - fgcolor = py_color(sp[:foreground_color_legend]) - lfcolor = py_color(sp[:legendfontcolor]) for txt in leg[:get_texts]() - PyPlot.plt[:setp](txt, color = lfcolor, family = sp[:legendfontfamily]) + PyPlot.plt[:setp](txt, color = py_color(sp[:legendfontcolor]), family = sp[:legendfontfamily]) end - - # set some legend properties - frame = leg[:get_frame]() - frame[set_facecolor_sym](py_color(sp[:background_color_legend])) - frame[:set_edgecolor](fgcolor) end end end @@ -1356,7 +1360,7 @@ for (mime, fmt) in _pyplot_mimeformats # figsize = map(px2inch, plt[:size]), facecolor = fig[:get_facecolor](), edgecolor = "none", - dpi = plt[:dpi] + dpi = plt[:dpi] * plt[:thickness_scaling] ) end end diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl index 6b4e124d..f644966d 100644 --- a/src/backends/unicodeplots.jl +++ b/src/backends/unicodeplots.jl @@ -212,7 +212,7 @@ end function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend}) unicodeplots_rebuild(plt) - map(show, plt.o) + foreach(x -> show(io, x), plt.o) nothing end diff --git a/src/output.jl b/src/output.jl index 3932d94b..1f9da687 100644 --- a/src/output.jl +++ b/src/output.jl @@ -157,17 +157,6 @@ end # --------------------------------------------------------- -const _mimeformats = Dict( - "application/eps" => "eps", - "image/eps" => "eps", - "application/pdf" => "pdf", - "image/png" => "png", - "application/postscript" => "ps", - "image/svg+xml" => "svg", - "text/plain" => "txt", - "application/x-tex" => "tex", -) - const _best_html_output_type = KW( :pyplot => :png, :unicodeplots => :txt, @@ -177,7 +166,7 @@ const _best_html_output_type = KW( ) # a backup for html... passes to svg or png depending on the html_output_format arg -function Base.show(io::IO, ::MIME"text/html", plt::Plot) +function _show(io::IO, ::MIME"text/html", plt::Plot) output_type = Symbol(plt.attr[:html_output_format]) if output_type == :auto output_type = get(_best_html_output_type, backend_name(plt.backend), :svg) @@ -191,26 +180,32 @@ function Base.show(io::IO, ::MIME"text/html", plt::Plot) elseif output_type == :txt show(io, MIME("text/plain"), plt) else - error("only png or svg allowed. got: $output_type") + error("only png or svg allowed. got: $(repr(output_type))") end end -function _show(io::IO, m, plt::Plot{B}) where B - # Base.show_backtrace(STDOUT, backtrace()) - warn("_show is not defined for this backend. m=", string(m)) +# delegate mimewritable (showable on julia 0.7) to _show instead +function Base.mimewritable(m::M, plt::P) where {M<:MIME, P<:Plot} + return method_exists(_show, Tuple{IO, M, P}) end + function _display(plt::Plot) warn("_display is not defined for this backend.") end # for writing to io streams... first prepare, then callback -for mime in keys(_mimeformats) - @eval function Base.show(io::IO, m::MIME{Symbol($mime)}, plt::Plot{B}) where B +for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml", + "application/eps", "application/pdf", "application/postscript", + "application/x-tex") + @eval function Base.show(io::IO, m::MIME{Symbol($mime)}, plt::Plot) prepare_output(plt) _show(io, m, plt) end end +# default text/plain for all backends +_show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt) + "Close all open gui windows of the current backend" closeall() = closeall(backend()) @@ -218,9 +213,10 @@ closeall() = closeall(backend()) # --------------------------------------------------------- # A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert +const PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend} if is_installed("FileIO") @eval import FileIO - function _show(io::IO, ::MIME"image/png", plt::Plot) + function _show(io::IO, ::MIME"image/png", plt::Plot{<:PDFBackends}) fn = tempname() # first save a pdf file @@ -288,7 +284,10 @@ end output_type = get(_best_html_output_type, backend_name(plt.backend), :svg) end out = Dict() - if output_type == :png + if output_type == :txt + mime = "text/plain" + out[mime] = sprint(show, MIME(mime), plt) + elseif output_type == :png mime = "image/png" out[mime] = base64encode(show, MIME(mime), plt) elseif output_type == :svg @@ -304,11 +303,6 @@ end out end - # default text/plain passes to html... handles Interact issues - function Base.show(io::IO, m::MIME"text/plain", plt::Plot) - show(io, MIME("text/html"), plt) - end - ENV["MPLBACKEND"] = "Agg" end end @@ -322,9 +316,6 @@ end if Juno.isactive() Media.media(Plot, Media.Plot) - - _show(io::IO, m::MIME"text/plain", plt::Plot{B}) where {B} = print(io, "Plot{$B}()") - function Juno.render(e::Juno.Editor, plt::Plot) Juno.render(e, nothing) end @@ -333,13 +324,20 @@ end function Juno.render(pane::Juno.PlotPane, plt::Plot) # temporarily overwrite size to be Atom.plotsize sz = plt[:size] + dpi = plt[:dpi] + thickness_scaling = plt[:thickness_scaling] jsize = Juno.plotsize() jsize[1] == 0 && (jsize[1] = 400) jsize[2] == 0 && (jsize[2] = 500) - plt[:size] = jsize + scale = minimum(jsize[i] / sz[i] for i in 1:2) + plt[:size] = (s * scale for s in sz) + plt[:dpi] = Plots.DPI + plt[:thickness_scaling] *= scale Juno.render(pane, HTML(stringmime(MIME("text/html"), plt))) plt[:size] = sz + plt[:dpi] = dpi + plt[:thickness_scaling] = thickness_scaling end # special handling for PlotlyJS function Juno.render(pane::Juno.PlotPane, plt::Plot{PlotlyJSBackend}) diff --git a/src/pipeline.jl b/src/pipeline.jl index 931aa76e..d1af402d 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -327,7 +327,7 @@ end function _override_seriestype_check(d::KW, st::Symbol) # do we want to override the series type? - if !is3d(st) + if !is3d(st) && !(st in (:contour,:contour3d)) z = d[:z] if !isa(z, Void) && (size(d[:x]) == size(d[:y]) == size(z)) st = (st == :scatter ? :scatter3d : :path3d) @@ -398,6 +398,7 @@ function _process_seriesrecipe(plt::Plot, d::KW) sp = _prepare_subplot(plt, d) _prepare_annotations(sp, d) _expand_subplot_extrema(sp, d, st) + _update_series_attributes!(d, plt, sp) _add_the_series(plt, sp, d) else diff --git a/src/recipes.jl b/src/recipes.jl index 2393f6a8..dfb1fd54 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -413,6 +413,7 @@ end z := nothing seriestype := :shape label := "" + widen --> false () end @deps plots_heatmap shape diff --git a/src/series.jl b/src/series.jl index 8b3f19bb..9af76774 100644 --- a/src/series.jl +++ b/src/series.jl @@ -627,7 +627,7 @@ group_as_matrix(t) = false else g = args[1] if length(g.args) == 1 - x = zeros(Int64, lengthGroup) + x = zeros(Int, lengthGroup) for indexes in groupby.groupIds x[indexes] = 1:length(indexes) end diff --git a/src/utils.jl b/src/utils.jl index 6153f4b4..2334d96a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -195,9 +195,13 @@ end function iter_segments(series::Series) x, y, z = series[:x], series[:y], series[:z] if has_attribute_segments(series) - return [i:(i + 1) for i in 1:(length(y) - 1)] + if series[:seriestype] in (:scatter, :scatter3d) + return [[i] for i in 1:length(y)] + else + return [i:(i + 1) for i in 1:(length(y) - 1)] + end else - segs = UnitRange{Int64}[] + segs = UnitRange{Int}[] args = is3d(series) ? (x, y, z) : (x, y) for seg in iter_segments(args...) push!(segs, seg) @@ -354,19 +358,18 @@ const _scale_base = Dict{Symbol, Real}( :ln => e, ) -"create an (n+1) list of the outsides of heatmap rectangles" -function heatmap_edges(v::AVec, scale::Symbol = :identity) +function _heatmap_edges(v::AVec) vmin, vmax = ignorenan_extrema(v) - extra_min = extra_max = 0.5 * (vmax-vmin) / (length(v)-1) - if scale in _logScales - vmin > 0 || error("The axis values must be positive for a $scale scale") - while vmin - extra_min <= 0 - extra_min /= _scale_base[scale] - end - end + extra_min = (v[2] - v[1]) / 2 + extra_max = (v[end] - v[end - 1]) / 2 vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max) end +"create an (n+1) list of the outsides of heatmap rectangles" +function heatmap_edges(v::AVec, scale::Symbol = :identity) + f, invf = scalefunc(scale), invscalefunc(scale) + map(invf, _heatmap_edges(map(f,v))) +end function calc_r_extrema(x, y) xmin, xmax = ignorenan_extrema(x) @@ -620,7 +623,7 @@ function get_linecolor(series, i::Int = 1) lc = series[:linecolor] lz = series[:line_z] if lz == nothing - isa(lc, ColorGradient) ? lc : _cycle(lc, i) + isa(lc, ColorGradient) ? lc : plot_color(_cycle(lc, i)) else cmin, cmax = get_clims(series[:subplot]) grad = isa(lc, ColorGradient) ? lc : cgrad() @@ -644,7 +647,7 @@ function get_fillcolor(series, i::Int = 1) fc = series[:fillcolor] fz = series[:fill_z] if fz == nothing - isa(fc, ColorGradient) ? fc : _cycle(fc, i) + isa(fc, ColorGradient) ? fc : plot_color(_cycle(fc, i)) else cmin, cmax = get_clims(series[:subplot]) grad = isa(fc, ColorGradient) ? fc : cgrad() @@ -656,6 +659,31 @@ function get_fillalpha(series, i::Int = 1) _cycle(series[:fillalpha], i) end +function get_markercolor(series, i::Int = 1) + mc = series[:markercolor] + mz = series[:marker_z] + if mz == nothing + isa(mc, ColorGradient) ? mc : plot_color(_cycle(mc, i)) + else + cmin, cmax = get_clims(series[:subplot]) + grad = isa(mc, ColorGradient) ? mc : cgrad() + grad[clamp((_cycle(mz, i) - cmin) / (cmax - cmin), 0, 1)] + end +end + +function get_markeralpha(series, i::Int = 1) + _cycle(series[:markeralpha], i) +end + +function get_markerstrokecolor(series, i::Int = 1) + msc = series[:markerstrokecolor] + isa(msc, ColorGradient) ? msc : _cycle(msc, i) +end + +function get_markerstrokealpha(series, i::Int = 1) + _cycle(series[:markerstrokealpha], i) +end + function has_attribute_segments(series::Series) # we want to check if a series needs to be split into segments just because # of its attributes @@ -666,7 +694,7 @@ function has_attribute_segments(series::Series) end series[:seriestype] == :shape && return false # ... else we check relevant attributes if they have multiple inputs - return any((typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) for attr in [:seriescolor, :seriesalpha, :linecolor, :linealpha, :linewidth, :fillcolor, :fillalpha]) || any(typeof(series[attr]) <: AbstractArray{<:Real} for attr in (:line_z, :fill_z)) + return any((typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) for attr in [:seriescolor, :seriesalpha, :linecolor, :linealpha, :linewidth, :fillcolor, :fillalpha, :markercolor, :markeralpha, :markerstrokecolor, :markerstrokealpha]) || any(typeof(series[attr]) <: AbstractArray{<:Real} for attr in (:line_z, :fill_z, :marker_z)) end # --------------------------------------------------------------- diff --git a/test/imgcomp.jl b/test/imgcomp.jl index 1fa2aef9..625d6d77 100644 --- a/test/imgcomp.jl +++ b/test/imgcomp.jl @@ -23,7 +23,7 @@ default(size=(500,300)) # TODO: use julia's Condition type and the wait() and notify() functions to initialize a Window, then wait() on a condition that # is referenced in a button press callback (the button clicked callback will call notify() on that condition) -const _current_plots_version = v"0.17.0" +const _current_plots_version = v"0.17.3" function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2) diff --git a/test/travis_commands.jl b/test/travis_commands.jl index 0ef1f732..a283d780 100644 --- a/test/travis_commands.jl +++ b/test/travis_commands.jl @@ -9,8 +9,7 @@ Pkg.clone("https://github.com/JuliaPlots/PlotReferenceImages.jl.git") # Pkg.clone("https://github.com/JuliaStats/KernelDensity.jl.git") Pkg.clone("StatPlots") -Pkg.checkout("PlotUtils", "julia0.7") -Pkg.checkout("RecipesBase", "julia0.7") +# Pkg.checkout("PlotUtils") # Pkg.clone("Blink") # Pkg.build("Blink")