diff --git a/src/Plots.jl b/src/Plots.jl index 74c5bf57..a7b6af20 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -56,6 +56,7 @@ const IMG_DIR = Pkg.dir("Plots") * "/img/" include("types.jl") include("utils.jl") +include("colors.jl") include("plotter.jl") include("args.jl") include("plot.jl") diff --git a/src/args.jl b/src/args.jl index 181de769..c0222eb5 100644 --- a/src/args.jl +++ b/src/args.jl @@ -134,6 +134,10 @@ const keyAliases = Dict( :bgcolor => :background_color, :bg_color => :background_color, :background => :background_color, + :fg => :foreground_color, + :fgcolor => :foreground_color, + :fg_color => :foreground_color, + :foreground => :foreground_color, :windowsize => :size, :wsize => :size, :wtitle => :windowtitle, @@ -182,65 +186,6 @@ end # ----------------------------------------------------------------------------- -convertColor(c::Union{AbstractString, Symbol}) = parse(Colorant, string(c)) -convertColor(c::Colorant) = c -convertColor(cvec::AbstractVector) = map(convertColor, cvec) - -isbackgrounddark(bgcolor::Color) = Lab(bgcolor).l < 0.5 - -# move closer to lighter/darker depending on background value -function adjustAway(val, bgval, vmin=0., vmax=100.) - if bgval < 0.5 * (vmax+vmin) - tmp = max(val, bgval) - return 0.5 * (tmp + max(tmp, vmax)) - else - tmp = min(val, bgval) - return 0.5 * (tmp + min(tmp, vmin)) - end -end - -function getBackgroundRGBColor(c, d::Dict) - bgcolor = convertColor(d[:background_color]) - d[:background_color] = bgcolor - palette = distinguishable_colors(20, bgcolor)[2:end] - - # # try to adjust lightness away from background color - # bg_lab = Lab(bgcolor) - # palette = RGB{Float64}[begin - # lab = Lab(rgb) - # Lab( - # adjustAway(lab.l, bg_lab.l, 25, 75), - # lab.a, - # lab.b - # ) - # end for rgb in palette] - d[:color_palette] = palette - - # set the foreground color (text, ticks, gridlines) to be white or black depending - # on how dark the background is. borrowed from http://stackoverflow.com/a/1855903 - a = 0.299 * red(bgcolor) + 0.587 * green(bgcolor) + 0.114 * blue(bgcolor) - d[:foreground_color] = a < 0.5 ? colorant"white" : colorant"black" - - bgcolor -end - -# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically -function getSeriesRGBColor(c, d::Dict, n::Int) - - if c == :auto - c = autopick(d[:color_palette], n) - else - c = convertColor(c) - end - - # should be a RGB now... either it was passed in, generated automatically, or created from a string - # @assert isa(c, RGB) - @assert isa(c, Colorant) - - # return the RGB - c -end - function warnOnUnsupported(pkg::PlottingPackage, d::Dict) @@ -280,7 +225,7 @@ end # note: idx is the index of this series within this call, n is the index of the series from all calls to plot/subplot -function getSeriesArgs(pkg::PlottingPackage, initargs::Dict, kw, idx::Int, n::Int) # TODO, pass in initargs, not plt +function getSeriesArgs(pkg::PlottingPackage, initargs::Dict, kw, commandIndex::Int, plotIndex::Int, globalIndex::Int) # TODO, pass in initargs, not plt d = Dict(kw) # add defaults? @@ -289,7 +234,7 @@ function getSeriesArgs(pkg::PlottingPackage, initargs::Dict, kw, idx::Int, n::In v = d[k] if isa(v, AbstractVector) && !isempty(v) # we got a vector, cycling through - d[k] = autopick(v, idx) + d[k] = autopick(v, commandIndex) end else d[k] = _seriesDefaults[k] @@ -298,26 +243,26 @@ function getSeriesArgs(pkg::PlottingPackage, initargs::Dict, kw, idx::Int, n::In # auto-pick if d[:axis] == :auto - d[:axis] = autopick_ignore_none_auto(supportedAxes(pkg), n) + d[:axis] = autopick_ignore_none_auto(supportedAxes(pkg), plotIndex) end if d[:linestyle] == :auto - d[:linestyle] = autopick_ignore_none_auto(supportedStyles(pkg), n) + d[:linestyle] = autopick_ignore_none_auto(supportedStyles(pkg), plotIndex) end if d[:marker] == :auto - d[:marker] = autopick_ignore_none_auto(supportedMarkers(pkg), n) + d[:marker] = autopick_ignore_none_auto(supportedMarkers(pkg), plotIndex) end # update color - d[:color] = getSeriesRGBColor(d[:color], initargs, n) + d[:color] = getSeriesRGBColor(d[:color], initargs, plotIndex) # update markercolor mc = d[:markercolor] - mc = (mc == :match ? d[:color] : getSeriesRGBColor(mc, initargs, n)) + mc = (mc == :match ? d[:color] : getSeriesRGBColor(mc, initargs, plotIndex)) d[:markercolor] = mc # set label label = d[:label] - label = (label == "AUTO" ? "y$n" : label) + label = (label == "AUTO" ? "y$globalIndex" : label) if d[:axis] == :right && length(label) >= 4 && label[end-3:end] != " (R)" label = string(label, " (R)") end diff --git a/src/colors.jl b/src/colors.jl new file mode 100644 index 00000000..15e22d7c --- /dev/null +++ b/src/colors.jl @@ -0,0 +1,173 @@ + +# note: I found this list of hex values in a comment by Tatarize here: http://stackoverflow.com/a/12224359 +const _masterColorList = [ + 0xFFFFFF, + 0x000000, + 0x0000FF, + 0x00FF00, + 0xFF0000, + 0x01FFFE, + 0xFFA6FE, + 0xFFDB66, + 0x006401, + 0x010067, + 0x95003A, + 0x007DB5, + 0xFF00F6, + 0xFFEEE8, + 0x774D00, + 0x90FB92, + 0x0076FF, + 0xD5FF00, + 0xFF937E, + 0x6A826C, + 0xFF029D, + 0xFE8900, + 0x7A4782, + 0x7E2DD2, + 0x85A900, + 0xFF0056, + 0xA42400, + 0x00AE7E, + 0x683D3B, + 0xBDC6FF, + 0x263400, + 0xBDD393, + 0x00B917, + 0x9E008E, + 0x001544, + 0xC28C9F, + 0xFF74A3, + 0x01D0FF, + 0x004754, + 0xE56FFE, + 0x788231, + 0x0E4CA1, + 0x91D0CB, + 0xBE9970, + 0x968AE8, + 0xBB8800, + 0x43002C, + 0xDEFF74, + 0x00FFC6, + 0xFFE502, + 0x620E00, + 0x008F9C, + 0x98FF52, + 0x7544B1, + 0xB500FF, + 0x00FF78, + 0xFF6E41, + 0x005F39, + 0x6B6882, + 0x5FAD4E, + 0xA75740, + 0xA5FFD2, + 0xFFB167, + 0x009BFF, + 0xE85EBE, +] + + + + +convertColor(c::Union{AbstractString, Symbol}) = parse(Colorant, string(c)) +convertColor(c::Colorant) = c +convertColor(cvec::AbstractVector) = map(convertColor, cvec) + +isbackgrounddark(bgcolor::Color) = Lab(bgcolor).l < 0.5 + +# move closer to lighter/darker depending on background value +function adjustAway(val, bgval, vmin=0., vmax=100.) + if bgval < 0.5 * (vmax+vmin) + tmp = max(val, bgval) + return 0.5 * (tmp + max(tmp, vmax)) + else + tmp = min(val, bgval) + return 0.5 * (tmp + min(tmp, vmin)) + end +end + +lightnessLevel(c::Colorant) = 0.299 * red(c) + 0.587 * green(c) + 0.114 * blue(c) +isdark(c::Colorant) = lightnessLevel(c) < 0.5 +islight(c::Colorant) = !isdark(c) + +function convertHexToRGB(h::Unsigned) + mask = 0x0000FF + RGB([(x & mask) / 0xFF for x in (h >> 16, h >> 8, h)]...) +end + +const _allColors = map(convertHexToRGB, _masterColorList) +const _darkColors = filter(isdark, _allColors) +const _lightColors = filter(islight, _allColors) +const _sortedColorsForDarkBackground = vcat(_lightColors, reverse(_darkColors[2:end])) +const _sortedColorsForLightBackground = vcat(_darkColors, reverse(_lightColors[2:end])) + +const _defaultNumColors = 20 + +function getPaletteUsingDistinguishableColors(bgcolor::Colorant, numcolors::Int = _defaultNumColors) + palette = distinguishable_colors(numcolors, bgcolor)[2:end] + + # try to adjust lightness away from background color + bg_lab = Lab(bgcolor) + palette = RGB{Float64}[begin + lab = Lab(rgb) + Lab( + adjustAway(lab.l, bg_lab.l, 25, 75), + lab.a, + lab.b + ) + end for rgb in palette] +end + +function getPaletteUsingFixedColorList(bgcolor::Colorant, numcolors::Int = _defaultNumColors) + palette = isdark(bgcolor) ? _sortedColorsForDarkBackground : _sortedColorsForLightBackground + palette[1:min(numcolors,length(palette))] +end + +function getPaletteUsingColorDiffFromBackground(bgcolor::Colorant, numcolors::Int = _defaultNumColors) + colordiffs = [colordiff(c, bgcolor) for c in _allColors] + mindiff = colordiffs[reverse(sortperm(colordiffs))[numcolors]] + @show bgcolor numcolors mindiff colordiffs + # filter(c -> colordiff(c, bgcolor) > 0.5, _allColors) + filter(c -> colordiff(c, bgcolor) >= mindiff, _allColors) +end + +# TODO: try to use the algorithms from https://github.com/timothyrenner/ColorBrewer.jl +# TODO: allow the setting of the algorithm, either by passing a symbol (:colordiff, :fixed, etc) or a function? + +function getBackgroundRGBColor(c, d::Dict) + bgcolor = convertColor(d[:background_color]) + d[:background_color] = bgcolor + + # d[:color_palette] = getPaletteUsingDistinguishableColors(bgcolor) + # d[:color_palette] = getPaletteUsingFixedColorList(bgcolor) + d[:color_palette] = getPaletteUsingColorDiffFromBackground(bgcolor) + @show d[:color_palette] + + # set the foreground color (text, ticks, gridlines) to be white or black depending + # on how dark the background is. borrowed from http://stackoverflow.com/a/1855903 + # a = 0.299 * red(bgcolor) + 0.587 * green(bgcolor) + 0.114 * blue(bgcolor) + if !haskey(d, :foreground_color) || d[:foreground_color] == :auto + d[:foreground_color] = isdark(bgcolor) ? colorant"white" : colorant"black" + end + + bgcolor +end + +# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically +function getSeriesRGBColor(c, d::Dict, n::Int) + + if c == :auto + c = autopick(d[:color_palette], n) + else + c = convertColor(c) + end + + # should be a RGB now... either it was passed in, generated automatically, or created from a string + # @assert isa(c, RGB) + @assert isa(c, Colorant) + + # return the RGB + c +end diff --git a/src/plot.jl b/src/plot.jl index add2ad43..99b109ec 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -23,6 +23,7 @@ Base.show(io::IO, plt::Plot) = print(io, string(plt)) getplot(plt::Plot) = plt getinitargs(plt::Plot, idx::Int = 1) = plt.initargs +convertSeriesIndex(plt::Plot, n::Int) = n doc""" @@ -132,7 +133,7 @@ function plot!(plt::Plot, args...; kw...) # Ideally we don't change the insides ot createKWargsList too much to # save from code repetition. We could consider adding a throw - kwList = createKWargsList2(plt, args...; d...) + kwList = createKWargsList(plt, args...; d...) for (i,d) in enumerate(kwList) plt.n += 1 plot!(plt.plotter, plt; d...) @@ -213,14 +214,15 @@ end # create n=max(mx,my) series arguments. the shorter list is cycled through # note: everything should flow through this -function createKWargsList2(plt::PlottingObject, x, y; kw...) +function createKWargsList(plt::PlottingObject, x, y; kw...) xs = convertToAnyVector(x) ys = convertToAnyVector(y) mx = length(xs) my = length(ys) ret = [] for i in 1:max(mx, my) - d = getSeriesArgs(plt.plotter, getinitargs(plt, plt.n+i), kw, i, plt.n+i) + n = plt.n + i + d = getSeriesArgs(plt.plotter, getinitargs(plt, n), kw, i, convertSeriesIndex(plt, n), n) d[:x], d[:y] = computeXandY(xs[mod1(i,mx)], ys[mod1(i,my)]) push!(ret, d) end @@ -228,46 +230,46 @@ function createKWargsList2(plt::PlottingObject, x, y; kw...) end # pass it off to the x/y version -function createKWargsList2(plt::PlottingObject, y; kw...) - createKWargsList2(plt, nothing, y; kw...) +function createKWargsList(plt::PlottingObject, y; kw...) + createKWargsList(plt, nothing, y; kw...) end -function createKWargsList2(plt::PlottingObject, f::FuncOrFuncs; kw...) +function createKWargsList(plt::PlottingObject, f::FuncOrFuncs; kw...) error("Can't pass a Function or Vector{Function} for y without also passing x") end -function createKWargsList2(plt::PlottingObject, f::FuncOrFuncs, x; kw...) +function createKWargsList(plt::PlottingObject, f::FuncOrFuncs, x; kw...) @assert !(x <: FuncOrFuncs) # otherwise we'd hit infinite recursion here - createKWargsList2(plt, x, f; kw...) + createKWargsList(plt, x, f; kw...) end # special handling... xmin/xmax with function(s) -function createKWargsList2(plt::PlottingObject, f::FuncOrFuncs, xmin::Real, xmax::Real; kw...) +function createKWargsList(plt::PlottingObject, f::FuncOrFuncs, xmin::Real, xmax::Real; kw...) width = plt.initargs[:size][1] x = collect(linspace(xmin, xmax, width)) # we don't need more than the width - createKWargsList2(plt, x, f; kw...) + createKWargsList(plt, x, f; kw...) end mapFuncOrFuncs(f::Function, u::AVec) = map(f, u) mapFuncOrFuncs(fs::AVec{Function}, u::AVec) = [map(f, u) for f in fs] # special handling... xmin/xmax with parametric function(s) -function createKWargsList2(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Real, umax::Real, numPoints::Int = 1000000; kw...) +function createKWargsList(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Real, umax::Real, numPoints::Int = 1000000; kw...) u = collect(linspace(umin, umax, numPoints)) - createKWargsList2(plt, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u); kw...) + createKWargsList(plt, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u); kw...) end # special handling... no args... 1 series -function createKWargsList2(plt::PlottingObject; kw...) +function createKWargsList(plt::PlottingObject; kw...) d = Dict(kw) if !haskey(d, :y) error("Called plot/subplot without args... must set y in the keyword args. Example: plot(; y=rand(10))") end if haskey(d, :x) - return createKWargsList2(plt, d[:x], d[:y]; kw...) + return createKWargsList(plt, d[:x], d[:y]; kw...) else - return createKWargsList2(plt, d[:y]; kw...) + return createKWargsList(plt, d[:y]; kw...) end end diff --git a/src/subplot.jl b/src/subplot.jl index 79794fee..9ca179a3 100644 --- a/src/subplot.jl +++ b/src/subplot.jl @@ -43,6 +43,7 @@ Base.show(io::IO, subplt::Subplot) = print(io, string(subplt)) getplot(subplt::Subplot, idx::Int = subplt.n) = subplt.plts[mod1(idx, subplt.p)] getinitargs(subplt::Subplot, idx::Int) = getplot(subplt, idx).initargs +convertSeriesIndex(subplt::Subplot, n::Int) = ceil(Int, n / subplt.p) # ------------------------------------------------------------