From 7566b0159c0c08a4886f55767d84b19fdb4857fa Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Fri, 25 Dec 2015 20:11:05 +0100 Subject: [PATCH 1/6] Start experimenting with GR.jl backend --- src/backends/gr.jl | 80 +++++++++++++++++++++++++++++++++++++++ src/backends/supported.jl | 73 +++++++++++++++++++++++++++++++++++ src/plotter.jl | 22 +++++++++-- 3 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 src/backends/gr.jl diff --git a/src/backends/gr.jl b/src/backends/gr.jl new file mode 100644 index 000000000..5888a61fe --- /dev/null +++ b/src/backends/gr.jl @@ -0,0 +1,80 @@ + +# https://github.com/jheinen/GR.jl + +function _create_plot(pkg::GRPackage; kw...) + d = Dict(kw) + GR.title(d[:title]) + GR.xlabel(d[:xlabel]) + GR.ylabel(d[:ylabel]) + Plot(nothing, pkg, 0, d, Dict[]) +end + + +function _add_series(::GRPackage, plt::Plot; kw...) + d = Dict(kw) + # TODO: add one series to the underlying package + GR.plot(d[:x], d[:y]) + push!(plt.seriesargs, d) + plt +end + +function _add_annotations{X,Y,V}(plt::Plot{GRPackage}, anns::AVec{@compat(Tuple{X,Y,V})}) + for ann in anns + # TODO: add the annotation to the plot + end +end + +# ---------------------------------------------------------------- + +function _before_update_plot(plt::Plot{GRPackage}) +end + +# TODO: override this to update plot items (title, xlabel, etc) after creation +function _update_plot(plt::Plot{GRPackage}, d::Dict) +end + +function _update_plot_pos_size(plt::PlottingObject{GRPackage}, d::Dict) +end + +# ---------------------------------------------------------------- + +# accessors for x/y data + +function Base.getindex(plt::Plot{GRPackage}, i::Int) + series = plt.o.lines[i] + series.x, series.y +end + +function Base.setindex!(plt::Plot{GRPackage}, xy::Tuple, i::Integer) + series = plt.o.lines[i] + series.x, series.y = xy + plt +end + +# ---------------------------------------------------------------- + +function _create_subplot(subplt::Subplot{GRPackage}, isbefore::Bool) + # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example +end + +function _expand_limits(lims, plt::Plot{GRPackage}, isx::Bool) + # TODO: call expand limits for each plot data +end + +function _remove_axis(plt::Plot{GRPackage}, isx::Bool) + # TODO: if plot is inner subplot, might need to remove ticks or axis labels +end + +# ---------------------------------------------------------------- + +function Base.writemime(io::IO, ::MIME"image/png", plt::PlottingObject{GRPackage}) + # TODO: write a png to io +end + +function Base.display(::PlotsDisplay, plt::Plot{GRPackage}) + # TODO: display/show the plot +end + +function Base.display(::PlotsDisplay, plt::Subplot{GRPackage}) + # TODO: display/show the subplot +end diff --git a/src/backends/supported.jl b/src/backends/supported.jl index 70253e10b..2b9709077 100644 --- a/src/backends/supported.jl +++ b/src/backends/supported.jl @@ -174,6 +174,79 @@ subplotSupported(::PyPlotPackage) = true +supportedArgs(::GRPackage) = [ + :annotation, + :axis, + :background_color, + :linecolor, + :color_palette, + :fillrange, + :fillcolor, + :foreground_color, + :group, + :label, + :layout, + :legend, + :linestyle, + :linetype, + :linewidth, + :markershape, + :markercolor, + :markersize, + :markerstrokewidth, + :markerstrokecolor, + # :markerstrokestyle, + :n, + :nbins, + :nc, + :nr, + # :pos, + :smooth, + # :ribbon, + :show, + :size, + :title, + :windowtitle, + :x, + :xlabel, + :xlims, + :xticks, + :y, + :ylabel, + :ylims, + :yrightlabel, + :yticks, + :xscale, + :yscale, + :xflip, + :yflip, + :z, + :zcolor, # only supported for scatter/scatter3d + :tickfont, + :guidefont, + :legendfont, + :grid, + # :surface, + :nlevels, + :fillalpha, + :linealpha, + :markeralpha, + ] +supportedAxes(::GRPackage) = _allAxes +supportedTypes(::GRPackage) = [:none, :line, :path, :steppre, :steppost, :sticks, + :scatter, :heatmap, :hexbin, :hist, :density, :bar, + :hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe] +supportedStyles(::GRPackage) = [:auto, :solid, :dash, :dot, :dashdot] +# supportedMarkers(::GRPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon] +supportedMarkers(::GRPackage) = vcat(_allMarkers, Shape) +supportedScales(::GRPackage) = [:identity, :ln, :log2, :log10] +subplotSupported(::GRPackage) = true + + +# -------------------------------------------------------------------------------------- + + + supportedArgs(::QwtPackage) = [ :annotation, # :args, diff --git a/src/plotter.jl b/src/plotter.jl index 20176fdb7..26a925ca4 100644 --- a/src/plotter.jl +++ b/src/plotter.jl @@ -8,6 +8,7 @@ immutable UnicodePlotsPackage <: PlottingPackage end immutable WinstonPackage <: PlottingPackage end immutable BokehPackage <: PlottingPackage end immutable PlotlyPackage <: PlottingPackage end +immutable GRPackage <: PlottingPackage end immutable NoPackage <: PlottingPackage end typealias GadflyOrImmerse @compat(Union{GadflyPackage, ImmersePackage}) @@ -19,7 +20,8 @@ export qwt, unicodeplots, bokeh, - plotly + plotly, + gr # winston gadfly() = backend(:gadfly) @@ -29,6 +31,7 @@ qwt() = backend(:qwt) unicodeplots() = backend(:unicodeplots) bokeh() = backend(:bokeh) plotly() = backend(:plotly) +gr() = backend(:gr) # winston() = backend(:winston) backend_name(::GadflyPackage) = :gadfly @@ -38,6 +41,7 @@ backend_name(::UnicodePlotsPackage) = :unicodeplots backend_name(::QwtPackage) = :qwt backend_name(::BokehPackage) = :bokeh backend_name(::PlotlyPackage) = :plotly +backend_name(::GRPackage) = :gr backend_name(::NoPackage) = :none include("backends/supported.jl") @@ -52,6 +56,7 @@ include("backends/winston.jl") include("backends/web.jl") include("backends/bokeh.jl") include("backends/plotly.jl") +include("backends/gr.jl") # --------------------------------------------------------- @@ -71,7 +76,7 @@ subplot!(pkg::PlottingPackage, subplt::Subplot; kw...) = error("subplot!($pkg, s # --------------------------------------------------------- -const BACKENDS = [:qwt, :gadfly, :unicodeplots, :pyplot, :immerse, :bokeh, :plotly] +const BACKENDS = [:qwt, :gadfly, :unicodeplots, :pyplot, :immerse, :bokeh, :plotly, :gr] const INITIALIZED_BACKENDS = Set{Symbol}() backends() = BACKENDS @@ -85,6 +90,7 @@ function backendInstance(sym::Symbol) sym == :winston && return WinstonPackage() sym == :bokeh && return BokehPackage() sym == :plotly && return PlotlyPackage() + sym == :gr && return GRPackage() sym == :none && return NoPackage() error("Unsupported backend $sym") end @@ -99,7 +105,7 @@ CurrentBackend(sym::Symbol) = CurrentBackend(sym, backendInstance(sym)) # --------------------------------------------------------- function pickDefaultBackend() - for pkgstr in ("PyPlot", "Immerse", "Qwt", "Gadfly", "UnicodePlots", "Bokeh") + for pkgstr in ("Gr", "PyPlot", "Immerse", "Qwt", "Gadfly", "UnicodePlots", "Bokeh") if Pkg.installed(pkgstr) != nothing return backend(symbol(lowercase(pkgstr))) end @@ -234,6 +240,14 @@ function backend() rethrow(err) end + elseif currentBackendSymbol == :gr + try + @eval import GR + catch err + warn("Couldn't import GR. Install it with: Pkg.add(\"GR\").") + rethrow(err) + end + elseif currentBackendSymbol == :winston warn("Winston support is deprecated and broken. Try another backend: $BACKENDS") try @@ -281,6 +295,8 @@ function backend(modname) CURRENT_BACKEND.pkg = BokehPackage() elseif modname == :plotly CURRENT_BACKEND.pkg = PlotlyPackage() + elseif modname == :gr + CURRENT_BACKEND.pkg = GRPackage() else error("Unknown backend $modname. Choose from: $BACKENDS") end From c3808c0c5b4ecfbf06ff42dd353e1d4edb2a0364 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Tue, 29 Dec 2015 20:07:44 +0100 Subject: [PATCH 2/6] gr.jl: implemented line/scatter plots --- src/backends/gr.jl | 130 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 8 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 5888a61fe..960bc8e87 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -1,19 +1,29 @@ # https://github.com/jheinen/GR.jl +fig = Dict() + +fig[:size] = [500, 500] + +@compat const gr_linetype = Dict( + :auto => 0, :solid => 1, :dash => 2, :dot => 3, :dashdot => 4, + :dashdotdot => -1 ) + +@compat const gr_markertype = Dict( + :none => 1, :ellipse => -1, :rect => -7, :diamond => -13, + :utriangle => -3, :dtriangle => -5, :pentagon => -14, + :cross => 2, :xcross => 5, :star5 => 3 ) + function _create_plot(pkg::GRPackage; kw...) + global fig d = Dict(kw) - GR.title(d[:title]) - GR.xlabel(d[:xlabel]) - GR.ylabel(d[:ylabel]) + fig[:size] = d[:size] Plot(nothing, pkg, 0, d, Dict[]) end - function _add_series(::GRPackage, plt::Plot; kw...) + global fig d = Dict(kw) - # TODO: add one series to the underlying package - GR.plot(d[:x], d[:y]) push!(plt.seriesargs, d) plt end @@ -29,11 +39,115 @@ end function _before_update_plot(plt::Plot{GRPackage}) end -# TODO: override this to update plot items (title, xlabel, etc) after creation function _update_plot(plt::Plot{GRPackage}, d::Dict) + global fig + + GR.clearws() + + mwidth, mheight, width, height = GR.inqdspsize() + w, h = fig[:size] + if w > h + ratio = float(h) / w + size = mwidth * w / width + GR.setwsviewport(0, size, 0, size * ratio) + GR.setwswindow(0, 1, 0, ratio) + viewport = [0.1, 0.95, 0.1 * ratio, 0.95 * ratio] + else + ratio = float(w) / h + size = mheight * h / height + GR.setwsviewport(0, size * ratio, 0, size) + GR.setwswindow(0, ratio, 0, 1) + viewport = [0.1 * ratio, 0.95 * ratio, 0.1, 0.95] + end + + xmin = ymin = typemax(Float64) + xmax = ymax = typemin(Float64) + for p in plt.seriesargs + x, y = p[:x], p[:y] + xmin = min(minimum(x), xmin) + xmax = max(maximum(x), xmax) + ymin = min(minimum(y), ymin) + ymax = max(maximum(y), ymax) + end + + xmin, xmax = GR.adjustlimits(xmin, xmax) + ymin, ymax = GR.adjustlimits(ymin, ymax) + majorx = 5 + xtick = GR.tick(xmin, xmax) / majorx + majory = 5 + ytick = GR.tick(ymin, ymax) / majory + + GR.setviewport(viewport[1], viewport[2], viewport[3], viewport[4]) + GR.setwindow(xmin, xmax, ymin, ymax) + + charheight = 0.03 * (viewport[4] - viewport[3]) + GR.setcharheight(charheight) + GR.grid(xtick, ytick, 0, 0, majorx, majory) + ticksize = 0.0125 * (viewport[2] - viewport[1]) + GR.axes(xtick, ytick, xmin, ymin, majorx, majory, ticksize) + GR.axes(xtick, ytick, xmax, ymax, -majorx, -majory, -ticksize) + + if haskey(d, :title) + GR.savestate() + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) + GR.text(0.5, min(ratio, 1), d[:title]) + GR.restorestate() + end + if haskey(d, :xlabel) + GR.savestate() + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) + GR.text(0.5, 0, d[:xlabel]) + GR.restorestate() + end + if haskey(d, :ylabel) + GR.savestate() + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) + GR.setcharup(-1, 0) + GR.text(0, 0.5 * (viewport[3] + viewport[4]), d[:ylabel]) + GR.restorestate() + end + + GR.savestate() + haskey(d, :linewidth) && GR.setlinewidth(d[:linewidth]) + haskey(d, :linestyle) && GR.setlinetype(gr_linetype[d[:linestyle]]) + haskey(d, :markersize) && GR.setmarkersize(d[:markersize]) + haskey(d, :markershape) && GR.setmarkertype(gr_markertype[d[:markershape]]) + GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) + + for p in plt.seriesargs + GR.uselinespec("") + if p[:linetype] == :path + GR.polyline(p[:x], p[:y]) + elseif p[:linetype] == :scatter + GR.polymarker(p[:x], p[:y]) + end + end + + px = viewport[2] - 0.15 + py = viewport[4] - 0.15 + GR.selntran(0) + for p in plt.seriesargs + GR.uselinespec("") + if p[:linetype] == :path + GR.polyline([px, px + 0.04], [py, py]) + elseif p[:linetype] == :scatter + GR.polymarker([px, px + 0.02], [py, py]) + end + GR.text(px + 0.05, py, p[:label]) + py -= 0.03 + end + GR.selntran(1) + + GR.restorestate() + + GR.updatews() end function _update_plot_pos_size(plt::PlottingObject{GRPackage}, d::Dict) + global fig + if haskey(d, :size) + fig[:size] = d[:size] + end end # ---------------------------------------------------------------- @@ -44,7 +158,7 @@ function Base.getindex(plt::Plot{GRPackage}, i::Int) series = plt.o.lines[i] series.x, series.y end - + function Base.setindex!(plt::Plot{GRPackage}, xy::Tuple, i::Integer) series = plt.o.lines[i] series.x, series.y = xy From b0b2e695e171f90936e420c62091df537c63f5ed Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Wed, 30 Dec 2015 11:52:19 +0100 Subject: [PATCH 3/6] Added support for logarithmic / flipped axes --- src/backends/gr.jl | 43 +++++++++++++++++++++++++++++++-------- src/backends/supported.jl | 5 ++--- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 960bc8e87..0b24d23b2 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -22,7 +22,6 @@ function _create_plot(pkg::GRPackage; kw...) end function _add_series(::GRPackage, plt::Plot; kw...) - global fig d = Dict(kw) push!(plt.seriesargs, d) plt @@ -70,22 +69,47 @@ function _update_plot(plt::Plot{GRPackage}, d::Dict) ymax = max(maximum(y), ymax) end - xmin, xmax = GR.adjustlimits(xmin, xmax) - ymin, ymax = GR.adjustlimits(ymin, ymax) - majorx = 5 - xtick = GR.tick(xmin, xmax) / majorx - majory = 5 - ytick = GR.tick(ymin, ymax) / majory + scale = 0 + d[:xscale] == :log10 && (scale |= GR.OPTION_X_LOG) + d[:yscale] == :log10 && (scale |= GR.OPTION_Y_LOG) + get(d, :xflip, false) && (scale |= GR.OPTION_FLIP_X) + get(d, :yflip, false) && (scale |= GR.OPTION_FLIP_Y) + + if scale & GR.OPTION_X_LOG == 0 + xmin, xmax = GR.adjustlimits(xmin, xmax) + majorx = 5 + xtick = GR.tick(xmin, xmax) / majorx + else + xtick = majorx = 1 + end + if scale & GR.OPTION_Y_LOG == 0 + ymin, ymax = GR.adjustlimits(ymin, ymax) + majory = 5 + ytick = GR.tick(ymin, ymax) / majory + else + ytick = majory = 1 + end + if scale & GR.OPTION_FLIP_X == 0 + xorg = (xmin, xmax) + else + xorg = (xmax, xmin) + end + if scale & GR.OPTION_FLIP_Y == 0 + yorg = (ymin, ymax) + else + yorg = (ymax, ymin) + end GR.setviewport(viewport[1], viewport[2], viewport[3], viewport[4]) GR.setwindow(xmin, xmax, ymin, ymax) + GR.setscale(scale) charheight = 0.03 * (viewport[4] - viewport[3]) GR.setcharheight(charheight) GR.grid(xtick, ytick, 0, 0, majorx, majory) ticksize = 0.0125 * (viewport[2] - viewport[1]) - GR.axes(xtick, ytick, xmin, ymin, majorx, majory, ticksize) - GR.axes(xtick, ytick, xmax, ymax, -majorx, -majory, -ticksize) + GR.axes(xtick, ytick, xorg[1], yorg[1], majorx, majory, ticksize) + GR.axes(xtick, ytick, xorg[2], yorg[2], -majorx, -majory, -ticksize) if haskey(d, :title) GR.savestate() @@ -126,6 +150,7 @@ function _update_plot(plt::Plot{GRPackage}, d::Dict) px = viewport[2] - 0.15 py = viewport[4] - 0.15 GR.selntran(0) + GR.setscale(0) for p in plt.seriesargs GR.uselinespec("") if p[:linetype] == :path diff --git a/src/backends/supported.jl b/src/backends/supported.jl index 2b9709077..256f33ca0 100644 --- a/src/backends/supported.jl +++ b/src/backends/supported.jl @@ -237,9 +237,8 @@ supportedTypes(::GRPackage) = [:none, :line, :path, :steppre, :steppost, :sticks :scatter, :heatmap, :hexbin, :hist, :density, :bar, :hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe] supportedStyles(::GRPackage) = [:auto, :solid, :dash, :dot, :dashdot] -# supportedMarkers(::GRPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon] -supportedMarkers(::GRPackage) = vcat(_allMarkers, Shape) -supportedScales(::GRPackage) = [:identity, :ln, :log2, :log10] +supportedMarkers(::GRPackage) = [:none, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :pentagon, :cross, :xcross, :star5] +supportedScales(::GRPackage) = [:identity, :log10] subplotSupported(::GRPackage) = true From fd889723809ef323be46a82670b6a407e4987c13 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Fri, 1 Jan 2016 19:33:35 +0100 Subject: [PATCH 4/6] Added support for legends --- src/backends/gr.jl | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 0b24d23b2..22e97a6ee 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -147,24 +147,37 @@ function _update_plot(plt::Plot{GRPackage}, d::Dict) end end - px = viewport[2] - 0.15 - py = viewport[4] - 0.15 - GR.selntran(0) - GR.setscale(0) - for p in plt.seriesargs - GR.uselinespec("") - if p[:linetype] == :path - GR.polyline([px, px + 0.04], [py, py]) - elseif p[:linetype] == :scatter - GR.polymarker([px, px + 0.02], [py, py]) + if plt.plotargs[:legend] + GR.selntran(0) + GR.setscale(0) + w = 0 + for p in plt.seriesargs + tbx, tby = GR.inqtext(0, 0, p[:label]) + w = max(w, tbx[3]) + end + px = viewport[2] - 0.05 - w + py = viewport[4] - 0.06 + GR.setfillintstyle(GR.INTSTYLE_SOLID) + GR.setfillcolorind(0) + GR.fillrect(px - 0.06, px + w + 0.02, py + 0.03, py - 0.03 * length(plt.seriesargs)) + GR.setlinecolorind(1) + GR.setlinewidth(1) + GR.drawrect(px - 0.06, px + w + 0.02, py + 0.03, py - 0.03 * length(plt.seriesargs)) + haskey(d, :linewidth) && GR.setlinewidth(d[:linewidth]) + for p in plt.seriesargs + GR.uselinespec("") + if p[:linetype] == :path + GR.polyline([px - 0.05, px - 0.01], [py, py]) + elseif p[:linetype] == :scatter + GR.polymarker([px - 0.05, px - 0.01], [py, py]) + end + GR.text(px, py, p[:label]) + py -= 0.03 end - GR.text(px + 0.05, py, p[:label]) - py -= 0.03 + GR.selntran(1) end - GR.selntran(1) GR.restorestate() - GR.updatews() end From 7acca183137ae230fbc8251fce5f322a9c68eeb2 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Sat, 2 Jan 2016 10:40:45 +0100 Subject: [PATCH 5/6] remove superfluous compat macros --- src/backends/gr.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 22e97a6ee..6f1916d12 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -5,11 +5,11 @@ fig = Dict() fig[:size] = [500, 500] -@compat const gr_linetype = Dict( +const gr_linetype = Dict( :auto => 0, :solid => 1, :dash => 2, :dot => 3, :dashdot => 4, :dashdotdot => -1 ) -@compat const gr_markertype = Dict( +const gr_markertype = Dict( :none => 1, :ellipse => -1, :rect => -7, :diamond => -13, :utriangle => -3, :dtriangle => -5, :pentagon => -14, :cross => 2, :xcross => 5, :star5 => 3 ) From a33161aa9c43fa0a472038b5020acacac64b472d Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Sat, 2 Jan 2016 12:15:07 +0100 Subject: [PATCH 6/6] Reset default backend to PyPlot GR should NOT be the default backend until it is finished :-) --- src/plotter.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plotter.jl b/src/plotter.jl index 032e41528..6cd8bb32d 100644 --- a/src/plotter.jl +++ b/src/plotter.jl @@ -112,7 +112,7 @@ CurrentBackend(sym::Symbol) = CurrentBackend(sym, backendInstance(sym)) # --------------------------------------------------------- function pickDefaultBackend() - for pkgstr in ("Gr", "PyPlot", "Immerse", "Qwt", "Gadfly", "UnicodePlots", "Bokeh", "GLVisualize") + for pkgstr in ("PyPlot", "Gr", "Immerse", "Qwt", "Gadfly", "UnicodePlots", "Bokeh", "GLVisualize") if Pkg.installed(pkgstr) != nothing return backend(symbol(lowercase(pkgstr))) end