diff --git a/README.md b/README.md index 4aa27ee6..d933f3d2 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@ Plotting interface and wrapper for several plotting packages. Please add wishlist items, bugs, or any other comments/questions to the issues list. -## Examples +## Examples for each implemented backend: - [Qwt.jl](docs/qwt_examples.md) - [Gadfly.jl](docs/gadfly_examples.md) +- [UnicodePlots.jl](docs/gadfly_examples.md) ## Installation @@ -187,6 +188,10 @@ When plotting multiple lines, you can give every line the same trait by using th - [x] Plot vectors/matrices/functions - [ ] Plot DataFrames +- [ ] Scales +- [ ] Categorical Inputs (strings, etc... for hist, bar? or can split one series into multiple?) +- [ ] Custom markers +- [ ] Special plots (boxplot, ohlc?) - [x] Subplots - [x] Histograms - [ ] 3D plotting diff --git a/src/Plots.jl b/src/Plots.jl index 55572c31..0b0722b3 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -39,15 +39,7 @@ const IMG_DIR = Pkg.dir("Plots") * "/img/" include("types.jl") include("utils.jl") - -# --------------------------------------------------------- - -include("qwt.jl") -include("gadfly.jl") include("plotter.jl") - -# --------------------------------------------------------- - include("args.jl") include("plot.jl") include("subplot.jl") diff --git a/src/args.jl b/src/args.jl index 72648449..76e3ae3e 100644 --- a/src/args.jl +++ b/src/args.jl @@ -125,7 +125,11 @@ function getPlotKeywordArgs(kw, idx::Int, n::Int) # set label label = d[:label] - d[:label] = string(label == "AUTO" ? "y_$n" : label, d[:axis] == :left ? "" : " (R)") + label = (label == "AUTO" ? "y_$n" : label) + if d[:axis] == :right && label[end-3:end] != " (R)" + label = string(label, " (R)") + end + d[:label] = label end d diff --git a/src/gadfly.jl b/src/backends/gadfly.jl similarity index 95% rename from src/gadfly.jl rename to src/backends/gadfly.jl index 81f93f14..46e4738b 100644 --- a/src/gadfly.jl +++ b/src/backends/gadfly.jl @@ -27,7 +27,7 @@ function plot(pkg::GadflyPackage; kw...) plt.theme = Gadfly.Theme(background_color = (haskey(d, :background_color) ? d[:background_color] : colorant"white")) - Plot(plt, pkg, 0) + Plot(plt, pkg, 0, kw, Dict[]) end function getGeomFromLineType(linetype::Symbol, nbins::Int) @@ -95,6 +95,9 @@ function plot!(::GadflyPackage, plt::Plot; kw...) warn("Gadly only supports one y axis") end + # save the kw args + plt.push!(plt.seriesargs, d) + # add the layer to the Gadfly.Plot prepend!(plt.o.layers, Gadfly.layer(unique(gfargs)...; x = x, y = d[:y])) plt @@ -115,9 +118,7 @@ end # ------------------------------- -# # create the underlying object (each backend will do this differently) -# o = buildSubplotObject(plts, pkg, layout) - +# create the underlying object (each backend will do this differently) function buildSubplotObject!(::GadflyPackage, subplt::Subplot) i = 0 rows = [] diff --git a/src/qwt.jl b/src/backends/qwt.jl similarity index 88% rename from src/qwt.jl rename to src/backends/qwt.jl index 097580d5..273b110d 100644 --- a/src/qwt.jl +++ b/src/backends/qwt.jl @@ -24,13 +24,16 @@ end function plot(pkg::QwtPackage; kw...) kw = adjustQwtKeywords(true; kw...) - plt = Plot(Qwt.plot(zeros(0,0); kw..., show=false), pkg, 0) + o = Qwt.plot(zeros(0,0); kw..., show=false) + plt = Plot(o, pkg, 0, kw, Dict[]) plt end function plot!(::QwtPackage, plt::Plot; kw...) kw = adjustQwtKeywords(false; kw...) Qwt.oplot(plt.o; kw...) + plt.push!(plt.seriesargs, kw) + plt end function Base.display(::QwtPackage, plt::Plot) @@ -44,9 +47,7 @@ savepng(::QwtPackage, plt::PlottingObject, fn::String, args...) = Qwt.savepng(pl # ------------------------------- -# # create the underlying object (each backend will do this differently) -# o = buildSubplotObject(plts, pkg, layout) - +# create the underlying object (each backend will do this differently) function buildSubplotObject!(::QwtPackage, subplt::Subplot) i = 0 rows = [] diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl new file mode 100644 index 00000000..c2ac6868 --- /dev/null +++ b/src/backends/unicodeplots.jl @@ -0,0 +1,124 @@ + +# https://github.com/Evizero/UnicodePlots.jl + +immutable UnicodePlotsPackage <: PlottingPackage end + +# ------------------------------- + +function expandLimits!(lims, x) + e1, e2 = extrema(x) + lims[1] = min(lims[1], e1) + lims[2] = max(lims[2], e2) + nothing +end + + +# do all the magic here... build it all at once, since we need to know about all the series at the very beginning +function rebuildUnicodePlot!(plt::Plot) + + # figure out the plotting area xlim = [xmin, xmax] and ylim = [ymin, ymax] + sargs = plt.seriesargs + xlim = [Inf, -Inf] + ylim = [Inf, -Inf] + for d in sargs + @show xlim ylim d[:x] d[:y] + expandLimits!(xlim, d[:x]) + expandLimits!(ylim, d[:y]) + @show xlim ylim d[:x] d[:y] + end + x = Float64[xlim[1]] + y = Float64[ylim[1]] + + # create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds + iargs = plt.initargs + width, height = iargs[:size] + o = UnicodePlots.createPlotWindow(x, y; width = width, + height = height, + title = iargs[:title], + # labels = iargs[:legend], + xlim = xlim, + ylim = ylim) + + # set the axis labels + UnicodePlots.xlabel!(o, iargs[:xlabel]) + UnicodePlots.ylabel!(o, iargs[:ylabel]) + + # now use the ! functions to add to the plot + for d in sargs + addUnicodeSeries!(o, d, iargs[:legend]) + end + + # save the object + plt.o = o +end + + +# add a single series +function addUnicodeSeries!(o, d::Dict, addlegend::Bool) + + lt = d[:linetype] + x, y = d[:x], d[:y] + label = addlegend ? d[:label] : "" + stepstyle = :post + + # if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide + color = d[:color] in UnicodePlots.autoColors ? d[:color] : :auto + + if lt == :line + func = UnicodePlots.lineplot! + elseif lt == :dots || d[:marker] != :none + func = UnicodePlots.scatterplot! + elseif lt == :step + func = UnicodePlots.stairs! + elseif lt == :stepinverted + func = UnicodePlots.stairs! + stepstyle = :pre + else + error("Linestyle $lt not supported by UnicodePlots") + end + + func(o, x, y; color = color, name = label, style = stepstyle) +end + + +# ------------------------------- + + +function plot(pkg::UnicodePlotsPackage; kw...) + plt = Plot(nothing, pkg, 0, Dict(kw), Dict[]) + + # do we want to give a new default size? + if !haskey(plt.initargs, :size) || plt.initargs[:size] == PLOT_DEFAULTS[:size] + plt.initargs[:size] = (60,20) + end + # w,h = plt.initargs[:size] + # plt.initargs[:size] = (min(200,w), min(200,h)) + plt +end + +function plot!(::UnicodePlotsPackage, plt::Plot; kw...) + push!(plt.seriesargs, Dict(kw)) + plt +end + +function Base.display(::UnicodePlotsPackage, plt::Plot) + rebuildUnicodePlot!(plt) + show(plt.o) +end + +# ------------------------------- + +savepng(::UnicodePlotsPackage, plt::PlottingObject, fn::String, args...) = error("currently unsupported") + +# ------------------------------- + +# create the underlying object (each backend will do this differently) +function buildSubplotObject!(::UnicodePlotsPackage, subplt::Subplot) + error("UnicodePlots doesn't support subplots") +end + + +function Base.display(::UnicodePlotsPackage, subplt::Subplot) + error("UnicodePlots doesn't support subplots") +end + diff --git a/src/plot.jl b/src/plot.jl index 769b0db1..e3d67650 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -104,14 +104,6 @@ end # this adds to a specific plot... most plot commands will flow through here function plot!(plt::Plot, args...; kw...) - # # increment n if we're going directly to the package's plot method - # if length(args) == 0 - # plt.n += 1 - # end - - # plot!(plt.plotter, plt, args...; kw...) - - kwList = createKWargsList(plt, args...; kw...) for (i,d) in enumerate(kwList) plt.n += 1 @@ -292,123 +284,3 @@ end # ------------------------- -# # most calls should flow through here now... we create a Dict with the keyword args for each series, and plot them -# function plot!(pkg::PlottingPackage, plt::Plot, args...; kw...) -# kwList = createKWargsList(plt, args...; kw...) -# for (i,d) in enumerate(kwList) -# plt.n += 1 -# plot!(pkg, plt; d...) -# end -# plt -# end - -# ------------------------- - -# # These methods are various ways to add to an existing plot - -# function plot!{T<:Real}(pkg::PlottingPackage, plt::Plot, y::AVec{T}; kw...) -# plt.n += 1 -# # plot!(pkg, plt; x = 1:length(y), y = y, getPlotKeywordArgs(kw, 1, plt)...) -# end - -# function plot!{T<:Real,S<:Real}(pkg::PlottingPackage, plt::Plot, x::AVec{T}, y::AVec{S}; kw...) # one line (will assert length(x) == length(y)) -# @assert length(x) == length(y) -# plt.n += 1 -# plot!(pkg, plt; x=x, y=y, getPlotKeywordArgs(kw, 1, plt)...) -# end - -# function plot!(pkg::PlottingPackage, plt::Plot, y::AMat; kw...) # multiple lines (one per column of x), all sharing x = 1:size(y,1) -# n,m = size(y) -# for i in 1:m -# plt.n += 1 -# plot!(pkg, plt; x = 1:n, y = y[:,i], getPlotKeywordArgs(kw, i, plt)...) -# end -# plt -# end - -# function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, y::AMat; kw...) # multiple lines (one per column of x), all sharing x (will assert length(x) == size(y,1)) -# n,m = size(y) -# for i in 1:m -# @assert length(x) == n -# plt.n += 1 -# plot!(pkg, plt; x = x, y = y[:,i], getPlotKeywordArgs(kw, i, plt)...) -# end -# plt -# end - -# function plot!(pkg::PlottingPackage, plt::Plot, x::AMat, y::AMat; kw...) # multiple lines (one per column of x/y... will assert size(x) == size(y)) -# @assert size(x) == size(y) -# for i in 1:size(x,2) -# plt.n += 1 -# plot!(pkg, plt; x = x[:,i], y = y[:,i], getPlotKeywordArgs(kw, i, plt)...) -# end -# plt -# end - -# function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, f::Function; kw...) # one line, y = f(x) -# plt.n += 1 -# plot!(pkg, plt; x = x, y = map(f,x), getPlotKeywordArgs(kw, 1, plt)...) -# end - -# function plot!(pkg::PlottingPackage, plt::Plot, x::AMat, f::Function; kw...) # multiple lines, yᵢⱼ = f(xᵢⱼ) -# for i in 1:size(x,2) -# xi = x[:,i] -# plt.n += 1 -# plot!(pkg, plt; x = xi, y = map(f, xi), getPlotKeywordArgs(kw, i, plt)...) -# end -# plt -# end - -# # function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, fs::AVec{Function}; kw...) # multiple lines, yᵢⱼ = fⱼ(xᵢ) -# # for i in 1:length(fs) -# # plt.n += 1 -# # plot!(pkg, plt; x = x, y = map(fs[i], x), getPlotKeywordArgs(kw, i, plt)...) -# # end -# # plt -# # end - -# function plot!(pkg::PlottingPackage, plt::Plot, y::AVec; kw...) # multiple lines, each with x = 1:length(y[i]) -# for i in 1:length(y) -# plt.n += 1 -# plot!(pkg, plt; x = 1:length(y[i]), y = y[i], getPlotKeywordArgs(kw, i, plt)...) -# end -# plt -# end - -# function plot!{T<:Real}(pkg::PlottingPackage, plt::Plot, x::AVec{T}, y::AVec; kw...) # multiple lines, will assert length(x) == length(y[i]) -# for i in 1:length(y) -# if typeof(y[i]) <: AbstractVector -# @assert length(x) == length(y[i]) -# plt.n += 1 -# plot!(pkg, plt; x = x, y = y[i], getPlotKeywordArgs(kw, i, plt)...) -# elseif typeof(y[i]) == Function -# plt.n += 1 -# plot!(pkg, plt; x = x, y = map(y[i], x), getPlotKeywordArgs(kw, 1, plt)...) -# end -# end -# plt -# end - -# function plot!(pkg::PlottingPackage, plt::Plot, x::AVec, y::AVec; kw...) # multiple lines, will assert length(x[i]) == length(y[i]) -# @assert length(x) == length(y) -# for i in 1:length(x) -# @assert length(x[i]) == length(y[i]) -# plt.n += 1 -# plot!(pkg, plt; x = x[i], y = y[i], getPlotKeywordArgs(kw, i, plt)...) -# end -# plt -# end - -# function plot!(pkg::PlottingPackage, plt::Plot, n::Integer; kw...) # n lines, all empty (for updating plots) -# for i in 1:n -# plt.n += 1 -# plot(pkg, plt, x = zeros(0), y = zeros(0), getPlotKeywordArgs(kw, i, plt)...) -# end -# end - -# ------------------------- - -# # this is the core method... add to a plot object using kwargs, with args already converted into kwargs -# function plot!(pkg::PlottingPackage, plt::Plot; kw...) -# plot!(pl, plt; kw...) -# end diff --git a/src/plotter.jl b/src/plotter.jl index a5748626..edacdbfc 100644 --- a/src/plotter.jl +++ b/src/plotter.jl @@ -1,5 +1,13 @@ +include("backends/qwt.jl") +include("backends/gadfly.jl") +include("backends/unicodeplots.jl") + + +# --------------------------------------------------------- + + plot(pkg::PlottingPackage; kw...) = error("plot($pkg; kw...) is not implemented") plot!(pkg::PlottingPackage, plt::Plot; kw...) = error("plot!($pkg, plt; kw...) is not implemented") Base.display(pkg::PlottingPackage, plt::Plot) = error("display($pkg, plt) is not implemented") @@ -7,14 +15,13 @@ Base.display(pkg::PlottingPackage, plt::Plot) = error("display($pkg, plt) is not # --------------------------------------------------------- -const AVAILABLE_PACKAGES = [:qwt, :gadfly] +const AVAILABLE_PACKAGES = [:qwt, :gadfly, :unicodeplots] const INITIALIZED_PACKAGES = Set{Symbol}() type CurrentPackage sym::Symbol pkg::PlottingPackage end -# const CURRENT_PACKAGE = CurrentPackage(:qwt, QwtPackage()) const CURRENT_PACKAGE = CurrentPackage(:gadfly, GadflyPackage()) @@ -32,6 +39,8 @@ function plotter() @eval import Qwt elseif currentPackageSymbol == :gadfly @eval import Gadfly + elseif currentPackageSymbol == :unicodeplots + @eval import UnicodePlots else error("Unknown plotter $currentPackageSymbol. Choose from: $AVAILABLE_PACKAGES") end @@ -43,7 +52,7 @@ function plotter() end doc""" -Set the plot backend. Choose from: :qwt, :gadfly +Set the plot backend. Choose from: :qwt, :gadfly, :unicodeplots """ function plotter!(modname) @@ -52,6 +61,8 @@ function plotter!(modname) CURRENT_PACKAGE.pkg = QwtPackage() elseif modname == :gadfly CURRENT_PACKAGE.pkg = GadflyPackage() + elseif modname == :unicodeplots + CURRENT_PACKAGE.pkg = UnicodePlotsPackage() else error("Unknown plotter $modname. Choose from: $AVAILABLE_PACKAGES") end diff --git a/src/types.jl b/src/types.jl index 248f485d..a5b31b9f 100644 --- a/src/types.jl +++ b/src/types.jl @@ -8,7 +8,11 @@ abstract PlottingPackage type Plot <: PlottingObject o # the underlying object plotter::PlottingPackage - n::Int # number of series + n::Int # number of series + + # store these just in case + initargs::Dict + seriesargs::Vector{Dict} # args for each series end