diff --git a/src/args.jl b/src/args.jl index 9bfed7cb..be105f70 100644 --- a/src/args.jl +++ b/src/args.jl @@ -1528,11 +1528,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 diff --git a/src/backends/gr.jl b/src/backends/gr.jl index b742eb15..ad235940 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 @@ -1046,10 +1039,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 diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl index 5493cfd3..19155c92 100644 --- a/src/backends/pgfplots.jl +++ b/src/backends/pgfplots.jl @@ -173,14 +173,8 @@ 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)), @@ -222,10 +216,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 diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index f1daf3d7..0e69bb17 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -265,7 +265,7 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot) ax[:tickangle] = -axis[:rotation] 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 @@ -490,7 +490,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 +516,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 @@ -632,31 +632,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 +660,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], + ) + # d_base[: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 +675,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 +696,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,7 +718,7 @@ 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 @@ -766,30 +755,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" @@ -848,6 +822,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 diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 38d9f15f..04290da1 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -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], @@ -1014,10 +1010,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 +1079,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 diff --git a/src/utils.jl b/src/utils.jl index 6153f4b4..8ee4a656 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -195,7 +195,11 @@ 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}[] args = is3d(series) ? (x, y, z) : (x, y) @@ -656,6 +660,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 : _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 +695,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 # ---------------------------------------------------------------