diff --git a/.travis.yml b/.travis.yml index 4663f986..275f1d99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,8 @@ script: - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - julia -e 'Pkg.clone(pwd()); Pkg.build("Plots")' # - julia -e 'Pkg.clone("https://github.com/tbreloff/Images.jl.git"); Pkg.checkout("Images","tom_imgcompare");' - - julia -e 'Pkg.clone("Images"); Pkg.build("Images")' - - julia -e 'Pkg.clone("ImageMagick"); Pkg.build("ImageMagick")' + # - julia -e 'Pkg.clone("Images"); Pkg.build("Images")' + # - julia -e 'Pkg.clone("ImageMagick"); Pkg.build("ImageMagick")' - julia -e 'Pkg.clone("https://github.com/tbreloff/VisualRegressionTests.jl.git");' - julia -e 'Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");' - julia -e 'Pkg.clone("Cairo"); Pkg.build("Cairo")' diff --git a/README.md b/README.md index 22740644..69f76c99 100644 --- a/README.md +++ b/README.md @@ -18,399 +18,4 @@ Plots is a plotting API and toolset. My goals with the package are: Use the preprocessing pipeline in Plots to fully describe your visualization before it calls the backend code. This maintains modularity and allows for efficient separation of front end code, algorithms, and backend graphics. New graphical backends can be added with minimal effort. -Check out the [summary graphs](img/supported/supported.md) for the features that each backend supports. - -Please add wishlist items, bugs, or any other comments/questions to the issues list. - -## Examples for each implemented backend: - -- [Gadfly.jl/Immerse.jl](https://github.com/tbreloff/ExamplePlots.jl/tree/master/docs/gadfly_examples.md) -- [PyPlot.jl](https://github.com/tbreloff/ExamplePlots.jl/tree/master/docs/pyplot_examples.md) -- [UnicodePlots.jl](https://github.com/tbreloff/ExamplePlots.jl/tree/master/docs/unicodeplots_examples.md) -- [Qwt.jl](https://github.com/tbreloff/ExamplePlots.jl/tree/master/docs/qwt_examples.md) - -Also check out the many [IJulia notebooks](http://nbviewer.ipython.org/github/tbreloff/ExamplePlots.jl/tree/master/examples/) with many examples. - -## Installation - -First, add the package - -```julia -Pkg.add("Plots") - -# if you want the latest features: -Pkg.checkout("Plots") - -# or for the bleeding edge: -Pkg.checkout("Plots", "dev") -``` - -then get any plotting packages you need (obviously, you should get at least one backend). - -```julia -Pkg.add("Gadfly") -Pkg.add("Immerse") -Pkg.add("PyPlot") -Pkg.add("UnicodePlots") -Pkg.add("Qwt") -Pkg.add("Bokeh") -``` - -## Use - -Load it in. The underlying plotting backends are not imported until `backend()` is called (which happens -on your first call to `plot` or `subplot`). This means that you don't need any backends to be installed when you call `using Plots`. - -Plots will try to figure out a good default backend for you automatically based on what backends are installed. - -```julia -using Plots -``` - -Do a plot in Gadfly (inspired by [this example](http://gadflyjl.org/geom_point.html)), then save a png: - -```julia -gadfly() # switch to Gadfly as a backend -dataframes() # turn on support for DataFrames inputs - -# load some data -using RDatasets -iris = dataset("datasets", "iris"); - -# This will bring up a browser window with the plot. Add a semicolon at the end to skip display. -scatter(iris, :SepalLength, :SepalWidth, group=:Species, m=([:+ :d :s], 12), smooth=0.99, bg=:black) - -# save a png (equivalent to png("gadfly1.png") and savefig("gadfly1.png")) -png("gadfly1") -``` - -![gadfly_plt](img/gadfly1.png) - -## API - -Call `backend(backend::Symbol)` or the shorthands (`gadfly()`, `qwt()`, `unicodeplots()`, etc) to set the current plotting backend. -Subsequent commands are converted into the relevant plotting commands for that package: - -```julia -gadfly() -plot(1:10) # this effectively calls `y = 1:10; Gadfly.plot(x=1:length(y), y=y)` -qwt() -plot(1:10) # this effectively calls `Qwt.plot(1:10)` -``` - -Use `plot` to create a new plot object, and `plot!` to add to an existing one: - -```julia -plot(args...; kw...) # creates a new plot window, and sets it to be the `current` -plot!(args...; kw...) # adds to the `current` -plot!(plotobj, args...; kw...) # adds to the plot `plotobj` -``` - -Now that you know which plot object you're updating (new, current, or other), I'll leave it off for simplicity. -There are many ways to pass in data to the plot functions... some examples: - -- Vector-like (subtypes of AbstractArray{T,1}) -- Matrix-like (subtypes of AbstractArray{T,2}) -- Vectors of Vectors -- Functions -- Vectors of Functions -- DataFrames with column symbols (initialize with `dataframes()`) - -In general, you can pass in a `y` only, or an `x` and `y`, both of whatever type(s) you want, and Plots will slice up the data as needed. -For matrices, data is split by columns. For functions, data is mapped. For DataFrames, a Symbol/Symbols in place of x/y will map to -the relevant column(s). - -Here are some example usages... remember you can always use `plot!` to update an existing plot, and that, unless specified, you will update the `current()`. - -```julia -plot() # empty plot object -plot(4) # initialize with 4 empty series -plot(rand(10)) # plot 1 series... x = 1:10 -plot(rand(10,5)) # plot 5 series... x = 1:10 -plot(rand(10), rand(10)) # plot 1 series -plot(rand(10,5), rand(10)) # plot 5 series... y is the same for all -plot(sin, rand(10)) # y = sin(x) -plot(rand(10), sin) # same... y = sin(x) -plot([sin,cos], 0:0.1:π) # plot 2 series, sin(x) and cos(x) -plot([sin,cos], 0, π) # plot sin and cos on the range [0, π] -plot(1:10, Any[rand(10), sin]) # plot 2 series, y = rand(10) for the first, y = sin(x) for the second... x = 1:10 for both -plot(dataset("Ecdat", "Airline"), :Cost) # plot from a DataFrame (call `dataframes()` first to import DataFrames and initialize) -``` - -All plot methods accept a number of keyword arguments (see the tables below), which follow some rules: -- Many arguments have aliases which are replaced during preprocessing. `c` is the same as `color`, `m` is the same as `marker`, etc. You can choose how verbose you'd like to be. (see the tables below) -- There are some special arguments (`xaxis`, `yaxis`, `line`, `marker`, `fill` and the aliases `l`, `m`, `f`) which magically set many related things at once. (see the __Tip__ below) -- If the argument is a "matrix-type", then each column will map to a series, cycling through columns if there are fewer columns than series. Anything else will apply the argument value to every series. -- Many arguments accept many different types... for example the `color` (also `markercolor`, `fillcolor`, etc) argument will accept strings or symbols with a color name, or any `Colors.Colorant`, or a `ColorScheme`, or a symbol representing a `ColorGradient`, or an AbstractVector of colors/symbols/etc... - -You can update certain plot settings after plot creation (not supported on all backends): - -```julia -plot!(title = "New Title", xlabel = "New xlabel", ylabel = "New ylabel") -plot!(xlims = (0, 5.5), ylims = (-2.2, 6), xticks = 0:0.5:10, yticks = [0,1,5,10]) - -# using shorthands: -xaxis!("mylabel", :log10, :flip) -``` - -With `subplot`, create multiple plots at once, with flexible layout options: - -```julia -y = rand(100,3) -subplot(y; n = 3) # create an automatic grid, and let it figure out the shape -subplot(y; n = 3, nr = 1) # create an automatic grid, but fix the number of rows -subplot(y; n = 3, nc = 1) # create an automatic grid, but fix the number of columns -subplot(y; layout = [1, 2]) # explicit layout. Lists the number of plots in each row -``` - -__Tip__: You can call `subplot!(args...; kw...)` to add to an existing subplot. - -__Tip__: Calling `subplot!` on a `Plot` object, or `plot!` on a `Subplot` object will throw an error. - -Shorthands: - -```julia -scatter(args...; kw...) = plot(args...; kw..., linetype = :scatter) -scatter!(args...; kw...) = plot!(args...; kw..., linetype = :scatter) -bar(args...; kw...) = plot(args...; kw..., linetype = :bar) -bar!(args...; kw...) = plot!(args...; kw..., linetype = :bar) -histogram(args...; kw...) = plot(args...; kw..., linetype = :hist) -histogram!(args...; kw...) = plot!(args...; kw..., linetype = :hist) -heatmap(args...; kw...) = plot(args...; kw..., linetype = :heatmap) -heatmap!(args...; kw...) = plot!(args...; kw..., linetype = :heatmap) -sticks(args...; kw...) = plot(args...; kw..., linetype = :sticks, marker = :ellipse) -sticks!(args...; kw...) = plot!(args...; kw..., linetype = :sticks, marker = :ellipse) -hline(args...; kw...) = plot(args...; kw..., linetype = :hline) -hline!(args...; kw...) = plot!(args...; kw..., linetype = :hline) -vline(args...; kw...) = plot(args...; kw..., linetype = :vline) -vline!(args...; kw...) = plot!(args...; kw..., linetype = :vline) -ohlc(args...; kw...) = plot(args...; kw..., linetype = :ohlc) -ohlc!(args...; kw...) = plot!(args...; kw..., linetype = :ohlc) - -title!(s::AbstractString) = plot!(title = s) -xlabel!(s::AbstractString) = plot!(xlabel = s) -ylabel!(s::AbstractString) = plot!(ylabel = s) -xlims!{T<:Real,S<:Real}(lims::Tuple{T,S}) = plot!(xlims = lims) -ylims!{T<:Real,S<:Real}(lims::Tuple{T,S}) = plot!(ylims = lims) -xticks!{T<:Real}(v::AVec{T}) = plot!(xticks = v) -yticks!{T<:Real}(v::AVec{T}) = plot!(yticks = v) -xflip!(flip::Bool = true) = plot!(xflip = flip) -yflip!(flip::Bool = true) = plot!(yflip = flip) -xaxis!(args...) = plot!(xaxis = args) -yaxis!(args...) = plot!(yaxis = args) -annotate!(anns) = plot!(annotation = anns) -``` - -### Keyword arguments: - -Keyword | Default | Type | Aliases ----- | ---- | ---- | ---- -`:annotation` | `nothing` | Series | `:ann`, `:annotate`, `:annotations`, `:anns` -`:axis` | `left` | Series | `:axiss` -`:background_color` | `RGB{U8}(1.0,1.0,1.0)` | Plot | `:background`, `:background_colour`, `:bg`, `:bg_color`, `:bgcolor` -`:color_palette` | `auto` | Plot | `:palette` -`:fill` | `nothing` | Series | `:area`, `:f` -`:fillalpha` | `nothing` | Series | `:fa`, `:fillalphas`, `:fillopacity` -`:fillcolor` | `match` | Series | `:fc`, `:fcolor`, `:fillcolors`, `:fillcolour` -`:fillrange` | `nothing` | Series | `:fillranges`, `:fillrng` -`:foreground_color` | `auto` | Plot | `:fg`, `:fg_color`, `:fgcolor`, `:foreground`, `:foreground_colour` -`:grid` | `true` | Plot | -`:group` | `nothing` | Series | `:g`, `:groups` -`:guidefont` | `Plots.Font("Helvetica",11,:hcenter,:vcenter,0.0,RGB{U8}(0.0,0.0,0.0))` | Plot | -`:label` | `AUTO` | Series | `:lab`, `:labels` -`:layout` | `nothing` | Plot | -`:legend` | `true` | Plot | `:leg` -`:legendfont` | `Plots.Font("Helvetica",8,:hcenter,:vcenter,0.0,RGB{U8}(0.0,0.0,0.0))` | Plot | -`:line` | `nothing` | Series | `:l` -`:linealpha` | `nothing` | Series | `:la`, `:linealphas`, `:lineopacity` -`:linecolor` | `auto` | Series | `:c`, `:color`, `:colour`, `:linecolors` -`:linestyle` | `solid` | Series | `:linestyles`, `:ls`, `:s`, `:style` -`:linetype` | `path` | Series | `:linetypes`, `:lt`, `:t`, `:type` -`:linewidth` | `1` | Series | `:linewidths`, `:lw`, `:w`, `:width` -`:link` | `false` | Plot | -`:linkfunc` | `nothing` | Plot | -`:linkx` | `false` | Plot | `:xlink` -`:linky` | `false` | Plot | `:ylink` -`:marker` | `nothing` | Series | `:m`, `:mark` -`:markeralpha` | `nothing` | Series | `:alpha`, `:ma`, `:markeralphas`, `:markeropacity`, `:opacity` -`:markercolor` | `match` | Series | `:markercolors`, `:markercolour`, `:mc`, `:mcolor` -`:markershape` | `none` | Series | `:markershapes`, `:shape` -`:markersize` | `6` | Series | `:markersizes`, `:ms`, `:msize` -`:markerstrokealpha` | `nothing` | Series | `:markerstrokealphas` -`:markerstrokecolor` | `match` | Series | `:markerstrokecolors` -`:markerstrokestyle` | `solid` | Series | `:markerstrokestyles` -`:markerstrokewidth` | `1` | Series | `:markerstrokewidths` -`:n` | `-1` | Plot | -`:nbins` | `30` | Series | `:nb`, `:nbin`, `:nbinss` -`:nc` | `-1` | Plot | -`:nlevels` | `15` | Series | `:nlevelss` -`:nr` | `-1` | Plot | -`:pos` | `(0,0)` | Plot | -`:show` | `false` | Plot | `:display`, `:gui` -`:size` | `(500,300)` | Plot | `:windowsize`, `:wsize` -`:smooth` | `false` | Series | `:reg`, `:regression`, `:smooths` -`:surface` | `nothing` | Series | `:surfaces` -`:tickfont` | `Plots.Font("Helvetica",8,:hcenter,:vcenter,0.0,RGB{U8}(0.0,0.0,0.0))` | Plot | -`:title` | `` | Plot | -`:windowtitle` | `Plots.jl` | Plot | `:wtitle` -`:xaxis` | `nothing` | Plot | -`:xflip` | `false` | Plot | -`:xlabel` | `` | Plot | `:xlab` -`:xlims` | `auto` | Plot | `:xlim`, `:xlimit`, `:xlimits` -`:xscale` | `identity` | Plot | -`:xticks` | `auto` | Plot | `:xtick` -`:yaxis` | `nothing` | Plot | -`:yflip` | `false` | Plot | -`:ylabel` | `` | Plot | `:ylab` -`:ylims` | `auto` | Plot | `:ylim`, `:ylimit`, `:ylimits` -`:yrightlabel` | `` | Plot | `:y2lab`, `:y2label`, `:ylab2`, `:ylabel2`, `:ylabelright`, `:ylabr`, `:yrlab` -`:yscale` | `identity` | Plot | -`:yticks` | `auto` | Plot | `:ytick` -`:z` | `nothing` | Series | `:zs` - - -### Plot types: - -Type | Desc | Aliases ----- | ---- | ---- -`:none` | No line | `:n`, `:no` -`:line` | Lines with sorted x-axis | `:l` -`:path` | Lines | `:p` -`:steppre` | Step plot (vertical then horizontal) | `:stepinv`, `:stepinverted`, `:stepsinv`, `:stepsinverted` -`:steppost` | Step plot (horizontal then vertical) | `:stair`, `:stairs`, `:step`, `:steps` -`:sticks` | Vertical lines | `:stem`, `:stems` -`:scatter` | Points, no lines | `:dots` -`:heatmap` | Colored regions by density | -`:hexbin` | Similar to heatmap | -`:hist` | Histogram (doesn't use x) | `:histogram` -`:bar` | Bar plot (centered on x values) | -`:hline` | Horizontal line (doesn't use x) | -`:vline` | Vertical line (doesn't use x) | -`:ohlc` | Open/High/Low/Close chart (expects y is AbstractVector{Plots.OHLC}) | -`:contour` | Contour lines (uses z) | -`:path3d` | 3D path (uses z) | `:line3d` -`:scatter3d` | 3D scatter plot (uses z) | - - -### Line styles: - -Type | Aliases ----- | ---- -`:auto` | `:a` -`:solid` | `:s` -`:dash` | `:d` -`:dot` | -`:dashdot` | `:dd` -`:dashdotdot` | `:ddd` - - -### Markers: - -Type | Aliases ----- | ---- -`:none` | `:n`, `:no` -`:auto` | `:a` -`:cross` | `:+`, `:plus` -`:diamond` | `:d` -`:dtriangle` | `:V`, `:downtri`, `:downtriangle`, `:dt`, `:dtri`, `:v` -`:ellipse` | `:c`, `:circle` -`:heptagon` | `:hep` -`:hexagon` | `:h`, `:hex` -`:octagon` | `:o`, `:oct` -`:pentagon` | `:p`, `:pent` -`:rect` | `:r`, `:sq`, `:square` -`:star4` | -`:star5` | `:s`, `:star`, `:star1` -`:star6` | -`:star7` | -`:star8` | `:s2`, `:star2` -`:utriangle` | `:^`, `:uptri`, `:uptriangle`, `:ut`, `:utri` -`:xcross` | `:X`, `:x` - - -__Tip__: With supported backends, you can pass a `Plots.Shape` object for the `marker`/`markershape` arguments. `Shape` takes a vector of 2-tuples in the constructor, defining the points of the polygon's shape in a unit-scaled coordinate space. To make a square, for example, you could do `Shape([(1,1),(1,-1),(-1,-1),(-1,1)])` - -__Tip__: You can see the default value for a given argument with `default(arg::Symbol)`, and set the default value with `default(arg::Symbol, value)` or `default(; kw...)`. For example set the default window size and whether we should show a legend with `default(size=(600,400), leg=false)`. - -__Tip__: There are some helper arguments you can set: `xaxis`, `yaxis`, `line`, `marker`, `fill`. These go through special preprocessing to extract values into individual arguments. The order doesn't matter, and if you pass a single value it's equivalent to wrapping it in a Tuple. Examples: - -``` -plot(y, xaxis = ("mylabel", :log, :flip, (-1,1))) # this sets the `xlabel`, `xscale`, `xflip`, and `xlims` arguments automatically -plot(y, line = (:bar, :blue, :dot, 10)) # this sets the `linetype`, `color`, `linestyle`, and `linewidth` arguments automatically -plot(y, marker = (:rect, :red, 10)) # this sets the `markershape`, `markercolor`, and `markersize` arguments automatically -plot(y, fill = (:green, 10)) # this sets the `fillcolor` and `fillrange` arguments automatically - # Note: `fillrange` can be: - a number (fill to horizontal line) - a vector of numbers (different for each data point) - a tuple of vectors (fill a band) -``` - -__Tip__: When plotting multiple lines, you can set all series to use the same value, or pass in a matrix to cycle through values. Example: - -```julia -plot(rand(100,4); color = [:red RGB(0,0,1)], # (Matrix) lines 1 and 3 are red, lines 2 and 4 are blue - axis = :auto, # lines 1 and 3 are on the left axis, lines 2 and 4 are on the right - markershape = [:rect, :star] # (Vector) ALL lines are passed the vector [:rect, :star1] - width = 5) # all lines have a width of 5 -``` - -__Tip__: Not all features are supported for each backend, but you can see what's supported by calling the functions: `supportedArgs()`, `supportedAxes()`, `supportedTypes()`, `supportedStyles()`, `supportedMarkers()`, `subplotSupported()` - -__Tip__: Call `gui()` to display the plot in a window. Interactivity depends on backend. Plotting at the REPL (without semicolon) implicitly calls `gui()`. - -### Animations - -Animations are created in 3 steps (see example #2): -- Initialize an `Animation` object. -- Save each frame of the animation with `frame(anim)`. -- Convert the frames to an animated gif with `gif(anim, filename, fps=15)` - - -## TODO features: - -- [x] Plot vectors/matrices/functions -- [x] Plot DataFrames -- [x] Histograms -- [x] Grouping -- [x] Annotations -- [x] Scales -- [x] Categorical Inputs (strings, etc... for hist, bar? or can split one series into multiple?) -- [x] Custom markers -- [x] Animations -- [x] Subplots -- [ ] Contours -- [ ] Boxplots -- [ ] 3D plotting -- [ ] Scenes/Drawing -- [ ] Graphs -- [ ] Interactivity (GUIs) - -## TODO backends: - -- [x] Gadfly.jl -- [x] Immerse.jl -- [x] PyPlot.jl -- [x] UnicodePlots.jl -- [x] Qwt.jl -- [x] Winston.jl (deprecated) -- [ ] GLPlot.jl -- [ ] Bokeh.jl -- [ ] Vega.jl -- [ ] Gaston.jl -- [ ] Plotly.jl -- [ ] GoogleCharts.jl -- [ ] PLplot.jl -- [ ] TextPlots.jl -- [ ] ASCIIPlots.jl -- [ ] Sparklines.jl -- [ ] Hinton.jl -- [ ] ImageTerm.jl -- [ ] GraphViz.jl -- [ ] TikzGraphs.jl -- [ ] GraphLayout.jl - -## More information on backends (both supported and unsupported) - -See the wiki at: https://github.com/JuliaPlot/juliaplot_docs/wiki - - +View the [full documentation](http://plots.readthedocs.org). diff --git a/REQUIRE b/REQUIRE index 04b7f31e..126413bb 100644 --- a/REQUIRE +++ b/REQUIRE @@ -3,3 +3,5 @@ julia 0.4 Colors Reexport Compat +Requires +FixedSizeArrays diff --git a/docs/example_generation.jl b/deprecated/docs/example_generation.jl similarity index 100% rename from docs/example_generation.jl rename to deprecated/docs/example_generation.jl diff --git a/docs/readme_template.md b/deprecated/docs/readme_template.md similarity index 100% rename from docs/readme_template.md rename to deprecated/docs/readme_template.md diff --git a/img/gadfly1.png b/deprecated/img/gadfly1.png similarity index 100% rename from img/gadfly1.png rename to deprecated/img/gadfly1.png diff --git a/img/supported/Plots.supportGraphArgs.png b/deprecated/img/supported/Plots.supportGraphArgs.png similarity index 100% rename from img/supported/Plots.supportGraphArgs.png rename to deprecated/img/supported/Plots.supportGraphArgs.png diff --git a/img/supported/Plots.supportGraphAxes.png b/deprecated/img/supported/Plots.supportGraphAxes.png similarity index 100% rename from img/supported/Plots.supportGraphAxes.png rename to deprecated/img/supported/Plots.supportGraphAxes.png diff --git a/img/supported/Plots.supportGraphMarkers.png b/deprecated/img/supported/Plots.supportGraphMarkers.png similarity index 100% rename from img/supported/Plots.supportGraphMarkers.png rename to deprecated/img/supported/Plots.supportGraphMarkers.png diff --git a/img/supported/Plots.supportGraphScales.png b/deprecated/img/supported/Plots.supportGraphScales.png similarity index 100% rename from img/supported/Plots.supportGraphScales.png rename to deprecated/img/supported/Plots.supportGraphScales.png diff --git a/img/supported/Plots.supportGraphStyles.png b/deprecated/img/supported/Plots.supportGraphStyles.png similarity index 100% rename from img/supported/Plots.supportGraphStyles.png rename to deprecated/img/supported/Plots.supportGraphStyles.png diff --git a/img/supported/Plots.supportGraphTypes.png b/deprecated/img/supported/Plots.supportGraphTypes.png similarity index 100% rename from img/supported/Plots.supportGraphTypes.png rename to deprecated/img/supported/Plots.supportGraphTypes.png diff --git a/img/supported/supported.md b/deprecated/img/supported/supported.md similarity index 100% rename from img/supported/supported.md rename to deprecated/img/supported/supported.md diff --git a/src/plotter.jl b/deprecated/plotter.jl similarity index 84% rename from src/plotter.jl rename to deprecated/plotter.jl index 3ca93d15..ee62107b 100644 --- a/src/plotter.jl +++ b/deprecated/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 GLVisualizePackage <: PlottingPackage end immutable NoPackage <: PlottingPackage end @@ -21,18 +22,20 @@ export unicodeplots, bokeh, plotly, + gr, glvisualize # winston -gadfly() = backend(:gadfly) -immerse() = backend(:immerse) -pyplot() = backend(:pyplot) -qwt() = backend(:qwt) -unicodeplots() = backend(:unicodeplots) -bokeh() = backend(:bokeh) -plotly() = backend(:plotly) -glvisualize() = backend(:glvisualize) -# winston() = backend(:winston) +gadfly(; kw...) = (default(; kw...); backend(:gadfly)) +immerse(; kw...) = (default(; kw...); backend(:immerse)) +pyplot(; kw...) = (default(; kw...); backend(:pyplot)) +qwt(; kw...) = (default(; kw...); backend(:qwt)) +unicodeplots(; kw...) = (default(; kw...); backend(:unicodeplots)) +bokeh(; kw...) = (default(; kw...); backend(:bokeh)) +plotly(; kw...) = (default(; kw...); backend(:plotly)) +gr(; kw...) = (default(; kw...); backend(:gr)) +glvisualize(; kw...) = (default(; kw...); backend(:glvisualize)) +# winston(; kw...) = (default(; kw...); backend(:winston)) backend_name(::GadflyPackage) = :gadfly backend_name(::ImmersePackage) = :immerse @@ -41,6 +44,7 @@ backend_name(::UnicodePlotsPackage) = :unicodeplots backend_name(::QwtPackage) = :qwt backend_name(::BokehPackage) = :bokeh backend_name(::PlotlyPackage) = :plotly +backend_name(::GRPackage) = :gr backend_name(::GLVisualizePackage) = :glvisualize backend_name(::NoPackage) = :none @@ -56,6 +60,7 @@ include("backends/winston.jl") include("backends/web.jl") include("backends/bokeh.jl") include("backends/plotly.jl") +include("backends/gr.jl") include("backends/glvisualize.jl") @@ -77,12 +82,12 @@ subplot!(pkg::PlottingPackage, subplt::Subplot; kw...) = error("subplot!($pkg, s # --------------------------------------------------------- -const BACKENDS = [:qwt, :gadfly, :unicodeplots, :pyplot, :immerse, :bokeh, :plotly] -const INITIALIZED_BACKENDS = Set{Symbol}() +const BACKENDS = [:qwt, :gadfly, :unicodeplots, :pyplot, :immerse, :bokeh, :plotly, :gr] +const _initialized_backends = Set{Symbol}() backends() = BACKENDS -function backendInstance(sym::Symbol) +function _backend_instance(sym::Symbol) sym == :qwt && return QwtPackage() sym == :gadfly && return GadflyPackage() sym == :unicodeplots && return UnicodePlotsPackage() @@ -91,6 +96,7 @@ function backendInstance(sym::Symbol) sym == :winston && return WinstonPackage() sym == :bokeh && return BokehPackage() sym == :plotly && return PlotlyPackage() + sym == :gr && return GRPackage() sym == :glvisualize && return GLVisualizePackage() sym == :none && return NoPackage() error("Unsupported backend $sym") @@ -101,12 +107,12 @@ type CurrentBackend sym::Symbol pkg::PlottingPackage end -CurrentBackend(sym::Symbol) = CurrentBackend(sym, backendInstance(sym)) +CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym)) # --------------------------------------------------------- function pickDefaultBackend() - for pkgstr in ("PyPlot", "Immerse", "Qwt", "Gadfly", "UnicodePlots", "Bokeh", "GLVisualize") + for pkgstr in ("PyPlot", "Immerse", "Qwt", "Gadfly", "GR", "UnicodePlots", "Bokeh", "GLVisualize") if Pkg.installed(pkgstr) != nothing return backend(symbol(lowercase(pkgstr))) end @@ -128,7 +134,7 @@ function backend() end currentBackendSymbol = CURRENT_BACKEND.sym - if !(currentBackendSymbol in INITIALIZED_BACKENDS) + if !(currentBackendSymbol in _initialized_backends) # initialize println("[Plots.jl] Initializing backend: ", CURRENT_BACKEND.sym) @@ -235,13 +241,26 @@ function backend() # end borrowing (thanks :) ########################### + try + include(joinpath(Pkg.dir("Plots"), "src", "backends", "plotly_blink.jl")) + catch err + warn("Error including PlotlyJS: $err\n Note: Will fall back to built-in display.") + end + end catch err warn("Couldn't setup Plotly") rethrow(err) end - elseif currentBackendSymbol == :glvisualize + elseif currentBackendSymbol == :gr + try + @eval import GR + catch err + warn("Couldn't import GR. Install it with: Pkg.add(\"GR\").") + end + + elseif currentBackendSymbol == :glvisualize try @eval import GLVisualize @eval export GLVisualize @@ -264,7 +283,7 @@ function backend() else error("Unknown backend $currentBackendSymbol. Choose from: $BACKENDS") end - push!(INITIALIZED_BACKENDS, currentBackendSymbol) + push!(_initialized_backends, currentBackendSymbol) end CURRENT_BACKEND.pkg @@ -297,7 +316,9 @@ function backend(modname) CURRENT_BACKEND.pkg = BokehPackage() elseif modname == :plotly CURRENT_BACKEND.pkg = PlotlyPackage() -elseif modname == :glvisualize + elseif modname == :gr + CURRENT_BACKEND.pkg = GRPackage() + elseif modname == :glvisualize CURRENT_BACKEND.pkg = GLVisualizePackage() else error("Unknown backend $modname. Choose from: $BACKENDS") diff --git a/test/refimg/gadfly/ref1.png b/deprecated/test/refimg/gadfly/ref1.png similarity index 100% rename from test/refimg/gadfly/ref1.png rename to deprecated/test/refimg/gadfly/ref1.png diff --git a/test/refimg/gadfly/ref10.png b/deprecated/test/refimg/gadfly/ref10.png similarity index 100% rename from test/refimg/gadfly/ref10.png rename to deprecated/test/refimg/gadfly/ref10.png diff --git a/test/refimg/gadfly/ref11.png b/deprecated/test/refimg/gadfly/ref11.png similarity index 100% rename from test/refimg/gadfly/ref11.png rename to deprecated/test/refimg/gadfly/ref11.png diff --git a/test/refimg/gadfly/ref12.png b/deprecated/test/refimg/gadfly/ref12.png similarity index 100% rename from test/refimg/gadfly/ref12.png rename to deprecated/test/refimg/gadfly/ref12.png diff --git a/deprecated/test/refimg/gadfly/ref13.png b/deprecated/test/refimg/gadfly/ref13.png new file mode 100644 index 00000000..e533d428 Binary files /dev/null and b/deprecated/test/refimg/gadfly/ref13.png differ diff --git a/test/refimg/gadfly/ref14.png b/deprecated/test/refimg/gadfly/ref14.png similarity index 100% rename from test/refimg/gadfly/ref14.png rename to deprecated/test/refimg/gadfly/ref14.png diff --git a/deprecated/test/refimg/gadfly/ref15.png b/deprecated/test/refimg/gadfly/ref15.png new file mode 100644 index 00000000..a7847442 Binary files /dev/null and b/deprecated/test/refimg/gadfly/ref15.png differ diff --git a/test/refimg/gadfly/ref16.png b/deprecated/test/refimg/gadfly/ref16.png similarity index 100% rename from test/refimg/gadfly/ref16.png rename to deprecated/test/refimg/gadfly/ref16.png diff --git a/test/refimg/gadfly/ref17.png b/deprecated/test/refimg/gadfly/ref17.png similarity index 100% rename from test/refimg/gadfly/ref17.png rename to deprecated/test/refimg/gadfly/ref17.png diff --git a/test/refimg/gadfly/ref18.png b/deprecated/test/refimg/gadfly/ref18.png similarity index 100% rename from test/refimg/gadfly/ref18.png rename to deprecated/test/refimg/gadfly/ref18.png diff --git a/test/refimg/gadfly/ref2.png b/deprecated/test/refimg/gadfly/ref2.png similarity index 100% rename from test/refimg/gadfly/ref2.png rename to deprecated/test/refimg/gadfly/ref2.png diff --git a/test/refimg/gadfly/ref20.png b/deprecated/test/refimg/gadfly/ref20.png similarity index 100% rename from test/refimg/gadfly/ref20.png rename to deprecated/test/refimg/gadfly/ref20.png diff --git a/test/refimg/gadfly/ref21.png b/deprecated/test/refimg/gadfly/ref21.png similarity index 100% rename from test/refimg/gadfly/ref21.png rename to deprecated/test/refimg/gadfly/ref21.png diff --git a/test/refimg/gadfly/ref22.png b/deprecated/test/refimg/gadfly/ref22.png similarity index 100% rename from test/refimg/gadfly/ref22.png rename to deprecated/test/refimg/gadfly/ref22.png diff --git a/test/refimg/gadfly/ref3.png b/deprecated/test/refimg/gadfly/ref3.png similarity index 100% rename from test/refimg/gadfly/ref3.png rename to deprecated/test/refimg/gadfly/ref3.png diff --git a/test/refimg/gadfly/ref4.png b/deprecated/test/refimg/gadfly/ref4.png similarity index 100% rename from test/refimg/gadfly/ref4.png rename to deprecated/test/refimg/gadfly/ref4.png diff --git a/test/refimg/gadfly/ref5.png b/deprecated/test/refimg/gadfly/ref5.png similarity index 100% rename from test/refimg/gadfly/ref5.png rename to deprecated/test/refimg/gadfly/ref5.png diff --git a/test/refimg/gadfly/ref6.png b/deprecated/test/refimg/gadfly/ref6.png similarity index 100% rename from test/refimg/gadfly/ref6.png rename to deprecated/test/refimg/gadfly/ref6.png diff --git a/test/refimg/gadfly/ref7.png b/deprecated/test/refimg/gadfly/ref7.png similarity index 100% rename from test/refimg/gadfly/ref7.png rename to deprecated/test/refimg/gadfly/ref7.png diff --git a/test/refimg/gadfly/ref8.png b/deprecated/test/refimg/gadfly/ref8.png similarity index 100% rename from test/refimg/gadfly/ref8.png rename to deprecated/test/refimg/gadfly/ref8.png diff --git a/test/refimg/gadfly/ref9.png b/deprecated/test/refimg/gadfly/ref9.png similarity index 100% rename from test/refimg/gadfly/ref9.png rename to deprecated/test/refimg/gadfly/ref9.png diff --git a/test/refimg/pyplot/ref1.png b/deprecated/test/refimg/pyplot/ref1.png similarity index 100% rename from test/refimg/pyplot/ref1.png rename to deprecated/test/refimg/pyplot/ref1.png diff --git a/test/refimg/pyplot/ref10.png b/deprecated/test/refimg/pyplot/ref10.png similarity index 100% rename from test/refimg/pyplot/ref10.png rename to deprecated/test/refimg/pyplot/ref10.png diff --git a/deprecated/test/refimg/pyplot/ref11.png b/deprecated/test/refimg/pyplot/ref11.png new file mode 100644 index 00000000..c7ca4373 Binary files /dev/null and b/deprecated/test/refimg/pyplot/ref11.png differ diff --git a/test/refimg/pyplot/ref12.png b/deprecated/test/refimg/pyplot/ref12.png similarity index 100% rename from test/refimg/pyplot/ref12.png rename to deprecated/test/refimg/pyplot/ref12.png diff --git a/deprecated/test/refimg/pyplot/ref13.png b/deprecated/test/refimg/pyplot/ref13.png new file mode 100644 index 00000000..bc7a7265 Binary files /dev/null and b/deprecated/test/refimg/pyplot/ref13.png differ diff --git a/test/refimg/pyplot/ref14.png b/deprecated/test/refimg/pyplot/ref14.png similarity index 100% rename from test/refimg/pyplot/ref14.png rename to deprecated/test/refimg/pyplot/ref14.png diff --git a/test/refimg/pyplot/ref15.png b/deprecated/test/refimg/pyplot/ref15.png similarity index 100% rename from test/refimg/pyplot/ref15.png rename to deprecated/test/refimg/pyplot/ref15.png diff --git a/test/refimg/pyplot/ref16.png b/deprecated/test/refimg/pyplot/ref16.png similarity index 100% rename from test/refimg/pyplot/ref16.png rename to deprecated/test/refimg/pyplot/ref16.png diff --git a/test/refimg/pyplot/ref17.png b/deprecated/test/refimg/pyplot/ref17.png similarity index 100% rename from test/refimg/pyplot/ref17.png rename to deprecated/test/refimg/pyplot/ref17.png diff --git a/test/refimg/pyplot/ref18.png b/deprecated/test/refimg/pyplot/ref18.png similarity index 100% rename from test/refimg/pyplot/ref18.png rename to deprecated/test/refimg/pyplot/ref18.png diff --git a/test/refimg/pyplot/ref2.png b/deprecated/test/refimg/pyplot/ref2.png similarity index 100% rename from test/refimg/pyplot/ref2.png rename to deprecated/test/refimg/pyplot/ref2.png diff --git a/test/refimg/pyplot/ref20.png b/deprecated/test/refimg/pyplot/ref20.png similarity index 100% rename from test/refimg/pyplot/ref20.png rename to deprecated/test/refimg/pyplot/ref20.png diff --git a/test/refimg/pyplot/ref21.png b/deprecated/test/refimg/pyplot/ref21.png similarity index 100% rename from test/refimg/pyplot/ref21.png rename to deprecated/test/refimg/pyplot/ref21.png diff --git a/test/refimg/pyplot/ref22.png b/deprecated/test/refimg/pyplot/ref22.png similarity index 100% rename from test/refimg/pyplot/ref22.png rename to deprecated/test/refimg/pyplot/ref22.png diff --git a/test/refimg/pyplot/ref24.png b/deprecated/test/refimg/pyplot/ref24.png similarity index 100% rename from test/refimg/pyplot/ref24.png rename to deprecated/test/refimg/pyplot/ref24.png diff --git a/test/refimg/pyplot/ref3.png b/deprecated/test/refimg/pyplot/ref3.png similarity index 100% rename from test/refimg/pyplot/ref3.png rename to deprecated/test/refimg/pyplot/ref3.png diff --git a/test/refimg/pyplot/ref4.png b/deprecated/test/refimg/pyplot/ref4.png similarity index 100% rename from test/refimg/pyplot/ref4.png rename to deprecated/test/refimg/pyplot/ref4.png diff --git a/test/refimg/pyplot/ref5.png b/deprecated/test/refimg/pyplot/ref5.png similarity index 100% rename from test/refimg/pyplot/ref5.png rename to deprecated/test/refimg/pyplot/ref5.png diff --git a/test/refimg/pyplot/ref6.png b/deprecated/test/refimg/pyplot/ref6.png similarity index 100% rename from test/refimg/pyplot/ref6.png rename to deprecated/test/refimg/pyplot/ref6.png diff --git a/test/refimg/pyplot/ref7.png b/deprecated/test/refimg/pyplot/ref7.png similarity index 100% rename from test/refimg/pyplot/ref7.png rename to deprecated/test/refimg/pyplot/ref7.png diff --git a/test/refimg/pyplot/ref8.png b/deprecated/test/refimg/pyplot/ref8.png similarity index 100% rename from test/refimg/pyplot/ref8.png rename to deprecated/test/refimg/pyplot/ref8.png diff --git a/test/refimg/pyplot/ref9.png b/deprecated/test/refimg/pyplot/ref9.png similarity index 100% rename from test/refimg/pyplot/ref9.png rename to deprecated/test/refimg/pyplot/ref9.png diff --git a/test/test_plottypes.jl b/deprecated/test/test_plottypes.jl similarity index 100% rename from test/test_plottypes.jl rename to deprecated/test/test_plottypes.jl diff --git a/src/Plots.jl b/src/Plots.jl index feca5ce9..352d8aae 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -6,6 +6,8 @@ module Plots using Compat using Reexport @reexport using Colors +using Requires +using FixedSizeArrays export Plot, @@ -18,8 +20,6 @@ export plot, plot!, - # plot_display, - # plot_display!, subplot, subplot!, @@ -33,6 +33,8 @@ export bar!, histogram, histogram!, + histogram2d, + histogram2d!, density, density!, heatmap, @@ -57,8 +59,11 @@ export wireframe!, path3d, path3d!, + plot3d, + plot3d!, scatter3d, scatter3d!, + abline!, title!, xlabel!, @@ -79,6 +84,7 @@ export backend, backends, + backend_name, aliases, dataframes, @@ -112,17 +118,14 @@ export Animation, frame, gif, + @animate, + @gif, # recipes PlotRecipe, # EllipseRecipe, - # spy, - corrplot - -# --------------------------------------------------------- - - -const IMG_DIR = Pkg.dir("Plots") * "/img/" + spy + # corrplot # --------------------------------------------------------- @@ -131,7 +134,7 @@ include("types.jl") include("utils.jl") include("colors.jl") include("components.jl") -include("plotter.jl") +include("plotter2.jl") include("args.jl") include("plot.jl") include("subplot.jl") @@ -148,6 +151,8 @@ bar(args...; kw...) = plot(args...; kw..., linetype = :bar) bar!(args...; kw...) = plot!(args...; kw..., linetype = :bar) histogram(args...; kw...) = plot(args...; kw..., linetype = :hist) histogram!(args...; kw...) = plot!(args...; kw..., linetype = :hist) +histogram2d(args...; kw...) = plot(args...; kw..., linetype = :hist2d) +histogram2d!(args...; kw...) = plot!(args...; kw..., linetype = :hist2d) density(args...; kw...) = plot(args...; kw..., linetype = :density) density!(args...; kw...) = plot!(args...; kw..., linetype = :density) heatmap(args...; kw...) = plot(args...; kw..., linetype = :heatmap) @@ -172,6 +177,8 @@ wireframe(args...; kw...) = plot(args...; kw..., linetype = :wireframe) wireframe!(args...; kw...) = plot!(args...; kw..., linetype = :wireframe) path3d(args...; kw...) = plot(args...; kw..., linetype = :path3d) path3d!(args...; kw...) = plot!(args...; kw..., linetype = :path3d) +plot3d(args...; kw...) = plot(args...; kw..., linetype = :path3d) +plot3d!(args...; kw...) = plot!(args...; kw..., linetype = :path3d) scatter3d(args...; kw...) = plot(args...; kw..., linetype = :scatter3d) scatter3d!(args...; kw...) = plot!(args...; kw..., linetype = :scatter3d) @@ -220,38 +227,16 @@ yaxis!(plt::Plot, args...; kw...) = plot!(pl # --------------------------------------------------------- - -# try -# import DataFrames -# dataframes() -# end - -# const CURRENT_BACKEND = pickDefaultBackend() - -# for be in backends() -# try -# backend(be) -# backend() -# catch err -# @show err -# end -# end - const CURRENT_BACKEND = CurrentBackend(:none) -# function __init__() -# # global const CURRENT_BACKEND = pickDefaultBackend() -# # global const CURRENT_BACKEND = CurrentBackend(:none) +function __init__() -# # global CURRENT_BACKEND -# # println("[Plots.jl] Default backend: ", CURRENT_BACKEND.sym) - -# # # auto init dataframes if the import statement doesn't error out -# # try -# # @eval import DataFrames -# # dataframes() -# # end -# end + # override IJulia inline display + if isijulia() + @eval import IJulia + IJulia.display_dict(plt::PlottingObject) = Dict{ASCIIString, ByteString}("text/html" => sprint(writemime, "text/html", plt)) + end +end # --------------------------------------------------------- diff --git a/src/animation.jl b/src/animation.jl index b923cf38..a11e48db 100644 --- a/src/animation.jl +++ b/src/animation.jl @@ -1,19 +1,18 @@ -immutable Animation{P<:PlottingObject} - plt::P +immutable Animation dir::ASCIIString frames::Vector{ASCIIString} end -function Animation(plt::PlottingObject) - Animation(plt, mktempdir(), ASCIIString[]) +function Animation() + tmpdir = convert(ASCIIString, mktempdir()) + Animation(tmpdir, ASCIIString[]) end -Animation() = Animation(current()) -function frame(anim::Animation) +function frame{P<:PlottingObject}(anim::Animation, plt::P=current()) i = length(anim.frames) + 1 filename = @sprintf("%06d.png", i) - png(anim.plt, joinpath(anim.dir, filename)) + png(plt, joinpath(anim.dir, filename)) push!(anim.frames, filename) end @@ -29,11 +28,11 @@ function gif(anim::Animation, fn::@compat(AbstractString) = "tmp.gif"; fps::Inte fn = abspath(fn) try - + # high quality speed = round(Int, 100 / fps) - run(`convert -delay $speed -loop 0 $(anim.dir)/*.png $fn`) - + run(`convert -delay $speed -loop 0 $(anim.dir)/*.png -alpha off $fn`) + catch err warn("Tried to create gif using convert (ImageMagick), but got error: $err\nWill try ffmpeg, but it's lower quality...)") @@ -52,3 +51,86 @@ end function Base.writemime(io::IO, ::MIME"text/html", agif::AnimatedGif) write(io, "\" />") end + + +# ----------------------------------------------- + +function _animate(forloop::Expr, args...; callgif = false) + if forloop.head != :for + error("@animate macro expects a for-block. got: $(forloop.head)") + end + + # add the call to frame to the end of each iteration + animsym = gensym("anim") + countersym = gensym("counter") + block = forloop.args[2] + + # create filter + n = length(args) + filterexpr = if n == 0 + # no filter... every iteration gets a frame + true + + elseif args[1] == :every + # filter every `freq` frames (starting with the first frame) + @assert n == 2 + freq = args[2] + @assert isa(freq, Integer) && freq > 0 + :(mod1($countersym, $freq) == 1) + + elseif args[1] == :when + # filter on custom expression + @assert n == 2 + args[2] + + else + error("Unsupported animate filter: $args") + end + + push!(block.args, :(if $filterexpr; frame($animsym); end)) + push!(block.args, :($countersym += 1)) + + # add a final call to `gif(anim)`? + retval = callgif ? :(gif($animsym)) : animsym + + # full expression: + esc(quote + $animsym = Animation() # init animation object + $countersym = 1 # init iteration counter + $forloop # for loop, saving a frame after each iteration + $retval # return the animation object, or the gif + end) +end + +""" +Builds an `Animation` using one frame per loop iteration, then create an animated GIF. + +Example: + +``` + p = plot(1) + @gif for x=0:0.1:5 + push!(p, 1, sin(x)) + end +``` +""" +macro gif(forloop::Expr, args...) + _animate(forloop, args...; callgif = true) +end + +""" +Collect one frame per for-block iteration and return an `Animation` object. + +Example: + +``` + p = plot(1) + anim = @animate for x=0:0.1:5 + push!(p, 1, sin(x)) + end + gif(anim) +``` +""" +macro animate(forloop::Expr, args...) + _animate(forloop, args...) +end diff --git a/src/args.jl b/src/args.jl index 53337b87..41139354 100644 --- a/src/args.jl +++ b/src/args.jl @@ -2,15 +2,15 @@ const _allAxes = [:auto, :left, :right] @compat const _axesAliases = Dict( - :a => :auto, - :l => :left, + :a => :auto, + :l => :left, :r => :right ) const _3dTypes = [:path3d, :scatter3d, :surface, :wireframe] const _allTypes = vcat([ :none, :line, :path, :steppre, :steppost, :sticks, :scatter, - :heatmap, :hexbin, :hist, :density, :bar, :hline, :vline, :ohlc, + :heatmap, :hexbin, :hist, :hist2d, :hist3d, :density, :bar, :hline, :vline, :ohlc, :contour, :pie ], _3dTypes) @compat const _typeAliases = Dict( @@ -39,7 +39,6 @@ const _allTypes = vcat([ ishistlike(lt::Symbol) = lt in (:hist, :density) islinelike(lt::Symbol) = lt in (:line, :path, :steppre, :steppost) -isheatmaplike(lt::Symbol) = lt in (:heatmap, :hexbin) const _allStyles = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] @@ -129,7 +128,7 @@ _seriesDefaults[:markerstrokecolor] = :match _seriesDefaults[:markerstrokealpha] = nothing # _seriesDefaults[:ribbon] = nothing # _seriesDefaults[:ribboncolor] = :match -_seriesDefaults[:nbins] = 30 # number of bins for heatmaps and hists +_seriesDefaults[:nbins] = 30 # number of bins for hists _seriesDefaults[:smooth] = false # regression line? _seriesDefaults[:group] = nothing # groupby vector # _seriesDefaults[:annotation] = nothing # annotation tuple(s)... (x,y,annotation) @@ -138,7 +137,8 @@ _seriesDefaults[:y] = nothing _seriesDefaults[:z] = nothing # depth for contour, surface, etc _seriesDefaults[:zcolor] = nothing # value for color scale # _seriesDefaults[:surface] = nothing -_seriesDefaults[:nlevels] = 15 +# _seriesDefaults[:nlevels] = 15 +_seriesDefaults[:levels] = 15 _seriesDefaults[:orientation] = :vertical @@ -149,7 +149,8 @@ _plotDefaults[:title] = "" _plotDefaults[:xlabel] = "" _plotDefaults[:ylabel] = "" _plotDefaults[:yrightlabel] = "" -_plotDefaults[:legend] = true +_plotDefaults[:legend] = :best +_plotDefaults[:colorbar] = :legend _plotDefaults[:background_color] = colorant"white" _plotDefaults[:foreground_color] = :auto _plotDefaults[:xlims] = :auto @@ -177,14 +178,14 @@ _plotDefaults[:tickfont] = font(8) _plotDefaults[:guidefont] = font(11) _plotDefaults[:legendfont] = font(8) _plotDefaults[:grid] = true -_plotDefaults[:annotation] = nothing # annotation tuple(s)... (x,y,annotation) - +_plotDefaults[:annotation] = nothing # annotation tuple(s)... (x,y,annotation) +_plotDefaults[:overwrite_figure] = false # TODO: x/y scales const _allArgs = sort(collect(union(keys(_seriesDefaults), keys(_plotDefaults)))) -supportedArgs(::PlottingPackage) = _allArgs +supportedArgs(::PlottingPackage) = error("supportedArgs not defined") #_allArgs supportedArgs() = supportedArgs(backend()) @@ -230,6 +231,7 @@ end :type => :linetype, :lt => :linetype, :t => :linetype, + :seriestype => :linetype, :style => :linestyle, :s => :linestyle, :ls => :linestyle, @@ -271,6 +273,9 @@ end :ylabel2 => :yrightlabel, :y2label => :yrightlabel, :leg => :legend, + :key => :legend, + :cbar => :colorbar, + :cb => :colorbar, :bg => :background_color, :bgcolor => :background_color, :bg_color => :background_color, @@ -283,6 +288,9 @@ end :foreground_colour => :foreground_color, :regression => :smooth, :reg => :smooth, + :nlevels => :levels, + :nlev => :levels, + :levs => :levels, :xlim => :xlims, :xlimit => :xlims, :xlimits => :xlims, @@ -299,6 +307,14 @@ end :palette => :color_palette, :xlink => :linkx, :ylink => :linky, + :nrow => :nr, + :nrows => :nr, + :ncol => :nc, + :ncols => :nc, + :clf => :overwrite_figure, + :clearfig => :overwrite_figure, + :overwrite => :overwrite_figure, + :reuse => :overwrite_figure, ) # add all pluralized forms to the _keyAliases dict @@ -348,12 +364,6 @@ end # ----------------------------------------------------------------------------- -wraptuple(x::@compat(Tuple)) = x -wraptuple(x) = (x,) - -trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x) -trueOrAllTrue(f::Function, x) = f(x) - function handleColors!(d::Dict, arg, csym::Symbol) try if arg == :auto @@ -405,11 +415,13 @@ end function processLineArg(d::Dict, arg) # linetype - if trueOrAllTrue(a -> get(_typeAliases, a, a) in _allTypes, arg) + # if trueOrAllTrue(a -> get(_typeAliases, a, a) in _allTypes, arg) + if allLineTypes(arg) d[:linetype] = arg - + # linestyle - elseif trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg) + # elseif trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg) + elseif allStyles(arg) d[:linestyle] = arg elseif typeof(arg) <: Stroke @@ -424,11 +436,13 @@ function processLineArg(d::Dict, arg) arg.alpha == nothing || (d[:fillalpha] = arg.alpha) # linealpha - elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg) + # elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg) + elseif allAlphas(arg) d[:linealpha] = arg # linewidth - elseif trueOrAllTrue(a -> typeof(a) <: Real, arg) + # elseif trueOrAllTrue(a -> typeof(a) <: Real, arg) + elseif allReals(arg) d[:linewidth] = arg # color @@ -442,13 +456,16 @@ end function processMarkerArg(d::Dict, arg) # markershape - if trueOrAllTrue(a -> get(_markerAliases, a, a) in _allMarkers, arg) + # if trueOrAllTrue(a -> get(_markerAliases, a, a) in _allMarkers, arg) + # d[:markershape] = arg + + # elseif trueOrAllTrue(a -> isa(a, Shape), arg) + if allShapes(arg) d[:markershape] = arg - elseif trueOrAllTrue(a -> isa(a, Shape), arg) - d[:markershape] = arg - + # stroke style - elseif trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg) + # elseif trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg) + elseif allStyles(arg) d[:markerstrokestyle] = arg elseif typeof(arg) <: Stroke @@ -463,17 +480,19 @@ function processMarkerArg(d::Dict, arg) arg.alpha == nothing || (d[:markeralpha] = arg.alpha) # linealpha - elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg) + # elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg) + elseif allAlphas(arg) d[:markeralpha] = arg # markersize - elseif trueOrAllTrue(a -> typeof(a) <: Real, arg) + # elseif trueOrAllTrue(a -> typeof(a) <: Real, arg) + elseif allReals(arg) d[:markersize] = arg # markercolor elseif !handleColors!(d, arg, :markercolor) warn("Skipped marker arg $arg.") - + end end @@ -485,15 +504,31 @@ function processFillArg(d::Dict, arg) arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : colorscheme(arg.color)) arg.alpha == nothing || (d[:fillalpha] = arg.alpha) + # fillrange function + # elseif trueOrAllTrue(a -> isa(a, Function), arg) + elseif allFunctions(arg) + d[:fillrange] = arg + + # fillalpha + # elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg) + elseif allAlphas(arg) + d[:fillalpha] = arg + elseif !handleColors!(d, arg, :fillcolor) + d[:fillrange] = arg end end +_replace_markershape(shape::Symbol) = get(_markerAliases, shape, shape) +_replace_markershape(shapes::AVec) = map(_replace_markershape, shapes) +_replace_markershape(shape) = shape + + "Handle all preprocessing of args... break out colors/sizes/etc and replace aliases." function preprocessArgs!(d::Dict) replaceAliases!(d, _keyAliases) - + # handle axis args for axisletter in ("x", "y") asym = symbol(axisletter * "axis") @@ -501,6 +536,12 @@ function preprocessArgs!(d::Dict) processAxisArg(d, axisletter, arg) end delete!(d, asym) + + # turn :labels into :ticks_and_labels + tsym = symbol(axisletter * "ticks") + if haskey(d, tsym) && ticksType(d[tsym]) == :labels + d[tsym] = (1:length(d[tsym]), d[tsym]) + end end # handle line args @@ -516,7 +557,12 @@ function preprocessArgs!(d::Dict) anymarker = true end delete!(d, :marker) - if anymarker && !haskey(d, :markershape) + # if anymarker && !haskey(d, :markershape) + # d[:markershape] = :ellipse + # end + if haskey(d, :markershape) + d[:markershape] = _replace_markershape(d[:markershape]) + elseif anymarker d[:markershape] = :ellipse end @@ -528,6 +574,14 @@ function preprocessArgs!(d::Dict) # convert into strokes and brushes + # legends + if haskey(d, :legend) + d[:legend] = convertLegendValue(d[:legend]) + end + if haskey(d, :colorbar) + d[:colorbar] = convertLegendValue(d[:colorbar]) + end + # handle subplot links if haskey(d, :link) l = d[:link] @@ -588,9 +642,12 @@ function warnOnUnsupportedArgs(pkg::PlottingPackage, d::Dict) end end +_markershape_supported(pkg::PlottingPackage, shape::Symbol) = shape in supportedMarkers(pkg) +_markershape_supported(pkg::PlottingPackage, shape::Shape) = Shape in supportedMarkers(pkg) +_markershape_supported(pkg::PlottingPackage, shapes::AVec) = all([_markershape_supported(pkg, shape) for shape in shapes]) function warnOnUnsupported(pkg::PlottingPackage, d::Dict) - (d[:axis] in supportedAxes(pkg) + (d[:axis] in supportedAxes(pkg) || warn("axis $(d[:axis]) is unsupported with $pkg. Choose from: $(supportedAxes(pkg))")) (d[:linetype] == :none || d[:linetype] in supportedTypes(pkg) @@ -598,8 +655,9 @@ function warnOnUnsupported(pkg::PlottingPackage, d::Dict) (d[:linestyle] in supportedStyles(pkg) || warn("linestyle $(d[:linestyle]) is unsupported with $pkg. Choose from: $(supportedStyles(pkg))")) (d[:markershape] == :none - || d[:markershape] in supportedMarkers(pkg) - || (Shape in supportedMarkers(pkg) && typeof(d[:markershape]) <: Shape) + || _markershape_supported(pkg, d[:markershape]) + # || d[:markershape] in supportedMarkers(pkg) + # || (Shape in supportedMarkers(pkg) && typeof(d[:markershape]) <: Shape) || warn("markershape $(d[:markershape]) is unsupported with $pkg. Choose from: $(supportedMarkers(pkg))")) end @@ -635,6 +693,19 @@ function setDictValue(d_in::Dict, d_out::Dict, k::Symbol, idx::Int, defaults::Di end end +function convertLegendValue(val::Symbol) + if val in (:both, :all, :yes) + :best + elseif val in (:no, :none) + :none + elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend) + val + else + error("Invalid symbol for legend: $val") + end +end +convertLegendValue(val::Bool) = val ? :best : :none + # ----------------------------------------------------------------------------- # build the argument dictionary for the plot @@ -655,6 +726,13 @@ function getPlotArgs(pkg::PlottingPackage, kw, idx::Int; set_defaults = true) end end + # handle legend/colorbar + d[:legend] = convertLegendValue(d[:legend]) + d[:colorbar] = convertLegendValue(d[:colorbar]) + if d[:colorbar] == :legend + d[:colorbar] = d[:legend] + end + # convert color handlePlotColors(pkg, d) @@ -683,7 +761,7 @@ function getSeriesArgs(pkg::PlottingPackage, plotargs::Dict, kw, commandIndex::I d[k] = kwdict[k] end end - + if haskey(_typeAliases, d[:linetype]) d[:linetype] = _typeAliases[d[:linetype]] end @@ -710,8 +788,6 @@ function getSeriesArgs(pkg::PlottingPackage, plotargs::Dict, kw, commandIndex::I c = (c == :match ? d[:linecolor] : getSeriesRGBColor(c, plotargs, plotIndex)) d[:fillcolor] = c - # TODO: rebuild - # set label label = d[:label] label = (label == "AUTO" ? "y$globalIndex" : label) diff --git a/src/backends/bokeh.jl b/src/backends/bokeh.jl index c6b678df..09fa303d 100644 --- a/src/backends/bokeh.jl +++ b/src/backends/bokeh.jl @@ -1,6 +1,15 @@ # https://github.com/bokeh/Bokeh.jl + +function _initialize_backend(::BokehPackage; kw...) + @eval begin + warn("Bokeh is no longer supported... many features will likely be broken.") + import Bokeh + export Bokeh + end +end + # make255(x) = round(Int, 255 * x) # function bokehcolor(c::Colorant) diff --git a/src/backends/gadfly.jl b/src/backends/gadfly.jl index 441c1b7e..963af95d 100644 --- a/src/backends/gadfly.jl +++ b/src/backends/gadfly.jl @@ -1,6 +1,15 @@ # https://github.com/dcjones/Gadfly.jl + +function _initialize_backend(::GadflyPackage; kw...) + @eval begin + import Gadfly, Compose + export Gadfly, Compose + include(joinpath(Pkg.dir("Plots"), "src", "backends", "gadfly_shapes.jl")) + end +end + # --------------------------------------------------------------------------- # immutable MissingVec <: AbstractVector{Float64} end @@ -27,7 +36,7 @@ function getLineGeom(d::Dict) xbins, ybins = maketuple(d[:nbins]) if lt == :hexbin Gadfly.Geom.hexbin(xbincount = xbins, ybincount = ybins) - elseif lt == :heatmap + elseif lt == :hist2d Gadfly.Geom.histogram2d(xbincount = xbins, ybincount = ybins) elseif lt == :hist Gadfly.Geom.histogram(bincount = xbins) @@ -44,7 +53,7 @@ function getLineGeom(d::Dict) elseif lt == :vline Gadfly.Geom.vline elseif lt == :contour - Gadfly.Geom.contour(levels = d[:nlevels]) + Gadfly.Geom.contour(levels = d[:levels]) else nothing end @@ -159,11 +168,16 @@ end # --------------------------------------------------------------------------- +# extract the underlying ShapeGeometry object(s) +getMarkerGeom(shape::Shape) = gadflyshape(shape) +getMarkerGeom(shape::Symbol) = gadflyshape(_shapes[shape]) +getMarkerGeom(shapes::AVec) = map(getMarkerGeom, shapes) +getMarkerGeom(d::Dict) = getMarkerGeom(d[:markershape]) -function getMarkerGeom(d::Dict) - shape = d[:markershape] - gadflyshape(isa(shape, Shape) ? shape : _shapes[shape]) -end +# function getMarkerGeom(d::Dict) +# shape = d[:markershape] +# gadflyshape(isa(shape, Shape) ? shape : _shapes[shape]) +# end function getGadflyMarkerTheme(d::Dict, plotargs::Dict) @@ -192,6 +206,7 @@ function getGadflyMarkerTheme(d::Dict, plotargs::Dict) end function addGadflyContColorScale(plt::Plot{GadflyPackage}, c) + plt.plotargs[:colorbar] == :none && return if !isa(c, ColorGradient) c = colorscheme(:bluesreds) end @@ -224,7 +239,7 @@ end function addToGadflyLegend(plt::Plot, d::Dict) # add the legend? - if plt.plotargs[:legend] + if plt.plotargs[:legend] != :none && d[:label] != "" gplt = getGadflyContext(plt) # add the legend if needed @@ -288,7 +303,7 @@ function addGadflySeries!(plt::Plot, d::Dict) lt = d[:linetype] if lt == :ohlc error("Haven't re-implemented after refactoring") - elseif lt in (:heatmap, :hexbin) && (isa(d[:linecolor], ColorGradient) || isa(d[:linecolor], ColorFunction)) + elseif lt in (:hist2d, :hexbin) && (isa(d[:linecolor], ColorGradient) || isa(d[:linecolor], ColorFunction)) push!(gplt.scales, Gadfly.Scale.ContinuousColorScale(p -> RGB(getColorZ(d[:linecolor], p)))) elseif lt == :scatter && d[:markershape] == :none d[:markershape] = :ellipse @@ -299,7 +314,7 @@ function addGadflySeries!(plt::Plot, d::Dict) prepend!(layers, addGadflyMarker!(plt, length(gplt.layers), d, plt.plotargs, smooth...)) end - lt in (:hist, :heatmap, :hexbin, :contour) || addToGadflyLegend(plt, d) + lt in (:hist2d, :hexbin, :contour) || addToGadflyLegend(plt, d) # now save the layers that apply to this series d[:gadflylayers] = layers @@ -359,7 +374,7 @@ function addGadflyTicksGuide(gplt, ticks, isx::Bool) gfunc = isx ? Gadfly.Scale.x_discrete : Gadfly.Scale.y_discrete labelmap = Dict(zip(ticks...)) labelfunc = val -> labelmap[val] - push!(gplt.scales, gfunc(levels = ticks[1], labels = labelfunc)) + push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc)) else error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks) @@ -482,9 +497,13 @@ function updateGadflyPlotTheme(plt::Plot, d::Dict) # # TODO: should this be part of the main `plot` command in plot.jl??? # d = merge!(plt.plotargs, d) - # hide the legend? - if !get(d, :legend, true) - kwargs[:key_position] = :none + # # hide the legend? + # if !get(d, :legend, true) + # kwargs[:key_position] = :none + # end + leg = d[d[:legend] == :none ? :colorbar : :legend] + if leg != :best + kwargs[:key_position] = leg == :inside ? :right : leg end if !get(d, :grid, true) @@ -656,7 +675,7 @@ setGadflyDisplaySize(subplt::Subplot) = setGadflyDisplaySize(getplotargs(subplt, # ------------------------------------------------------------------------- -function dowritemime{P<:GadflyOrImmerse}(io::IO, func, plt::PlottingObject{P}) +function dowritemime{P<:Union{GadflyPackage,ImmersePackage}}(io::IO, func, plt::PlottingObject{P}) gplt = getGadflyContext(plt) setGadflyDisplaySize(plt) Gadfly.draw(func(io, Compose.default_graphic_width, Compose.default_graphic_height), gplt) @@ -667,10 +686,11 @@ getGadflyWriteFunc(::MIME"image/svg+xml") = Gadfly.SVG # getGadflyWriteFunc(::MIME"text/html") = Gadfly.SVGJS getGadflyWriteFunc(::MIME"application/pdf") = Gadfly.PDF getGadflyWriteFunc(::MIME"application/postscript") = Gadfly.PS +getGadflyWriteFunc(::MIME"application/x-tex") = Gadfly.PGF getGadflyWriteFunc(m::MIME) = error("Unsupported in Gadfly/Immerse: ", m) -for mime in (MIME"image/png", MIME"image/svg+xml", MIME"application/pdf", MIME"application/postscript") - @eval function Base.writemime{P<:GadflyOrImmerse}(io::IO, ::$mime, plt::PlottingObject{P}) +for mime in (MIME"image/png", MIME"image/svg+xml", MIME"application/pdf", MIME"application/postscript", MIME"application/x-tex") + @eval function Base.writemime{P<:Union{GadflyPackage,ImmersePackage}}(io::IO, ::$mime, plt::PlottingObject{P}) func = getGadflyWriteFunc($mime()) dowritemime(io, func, plt) end diff --git a/src/backends/gadfly_shapes.jl b/src/backends/gadfly_shapes.jl index 73ccd4ec..25672598 100644 --- a/src/backends/gadfly_shapes.jl +++ b/src/backends/gadfly_shapes.jl @@ -66,7 +66,7 @@ function Gadfly.render(geom::ShapeGeometry, theme::Gadfly.Theme, aes::Gadfly.Aes end function gadflyshape(sv::Shape) - ShapeGeometry([(x,-y) for (x,y) in sv.vertices]) + ShapeGeometry(sv.vertices) end diff --git a/src/backends/glvisualize.jl b/src/backends/glvisualize.jl index 591172a0..82d60fcf 100644 --- a/src/backends/glvisualize.jl +++ b/src/backends/glvisualize.jl @@ -2,6 +2,13 @@ # [WEBSITE] +function _initialize_backend(::GLVisualizePackage; kw...) + @eval begin + import GLVisualize + export GLVisualize + end +end + # --------------------------------------------------------------------------- immutable GLScreenWrapper @@ -12,6 +19,8 @@ function _create_plot(pkg::GLVisualizePackage; kw...) d = Dict(kw) # TODO: create the window/canvas/context that is the plot within the backend (call it `o`) # TODO: initialize the plot... title, xlabel, bgcolor, etc + + # TODO: this should be moved to the display method? w=GLVisualize.glscreen() @async GLVisualize.renderloop(w) Plot(GLScreenWrapper(w), pkg, 0, d, Dict[]) @@ -22,6 +31,7 @@ function _add_series(::GLVisualizePackage, plt::Plot; kw...) d = Dict(kw) # TODO: add one series to the underlying package push!(plt.seriesargs, d) + # TODO: this should be moved to the display method? x,y,z=map(Float32,d[:x]), map(Float32,d[:y]), map(Float32,d[:z].surf) GLVisualize.view(GLVisualize.visualize((x*ones(y)', ones(x)*y', z), :surface),plt.o.window) plt @@ -50,13 +60,16 @@ end # accessors for x/y data function Base.getindex(plt::Plot{GLVisualizePackage}, i::Int) - series = plt.o.lines[i] - series.x, series.y + # TODO: + # series = plt.o.lines[i] + # series.x, series.y + nothing, nothing end function Base.setindex!(plt::Plot{GLVisualizePackage}, xy::Tuple, i::Integer) - series = plt.o.lines[i] - series.x, series.y = xy + # TODO: + # series = plt.o.lines[i] + # series.x, series.y = xy plt end @@ -82,6 +95,10 @@ end function Base.display(::PlotsDisplay, plt::Plot{GLVisualizePackage}) # TODO: display/show the plot + + # NOTE: I think maybe this should be empty? We can start with the assumption that creating + # and adding to a plot will automatically open a window and draw to it, then the display + # wouldn't actually need to do anything end function Base.display(::PlotsDisplay, plt::Subplot{GLVisualizePackage}) diff --git a/src/backends/gr.jl b/src/backends/gr.jl new file mode 100644 index 00000000..5f43a8c0 --- /dev/null +++ b/src/backends/gr.jl @@ -0,0 +1,790 @@ + +# https://github.com/jheinen/GR.jl + +function _initialize_backend(::GRPackage; kw...) + @eval begin + import GR + export GR + end +end + +const gr_linetype = Dict( + :auto => 1, :solid => 1, :dash => 2, :dot => 3, :dashdot => 4, + :dashdotdot => -1 ) + +const gr_markertype = Dict( + :auto => 1, :none => -1, :ellipse => -1, :rect => -7, :diamond => -13, + :utriangle => -3, :dtriangle => -5, :pentagon => -21, :hexagon => -22, + :heptagon => -23, :octagon => -24, :cross => 2, :xcross => 5, + :star4 => -25, :star5 => -26, :star6 => -27, :star7 => -28, :star8 => -29, + :vline => -30, :hline => -31 ) + +const gr_halign = Dict(:left => 1, :hcenter => 2, :right => 3) +const gr_valign = Dict(:top => 1, :vcenter => 3, :bottom => 5) + +const gr_font_family = Dict( + "times" => 1, "helvetica" => 5, "courier" => 9, "bookman" => 14, + "newcenturyschlbk" => 18, "avantgarde" => 22, "palatino" => 26) + +function gr_getcolorind(v) + c = getColor(v) + return convert(Int, GR.inqcolorfromrgb(c.r, c.g, c.b)) +end + +function gr_getaxisind(p) + axis = get(p, :axis, :none) + if axis in [:none, :left] + return 1 + else + return 2 + end +end + +function gr_setmarkershape(p) + if haskey(p, :markershape) + shape = p[:markershape] + if isa(shape, Shape) + p[:vertices] = shape.vertices + else + GR.setmarkertype(gr_markertype[shape]) + p[:vertices] = :none + end + end +end + +function gr_polymarker(p, x, y) + if p[:vertices] != :none + vertices= p[:vertices] + dx = Float64[el[1] for el in vertices] * 0.01 + dy = Float64[el[2] for el in vertices] * 0.01 + GR.selntran(0) + GR.setfillcolorind(gr_getcolorind(p[:markercolor])) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + for i = 1:length(x) + xn, yn = GR.wctondc(x[i], y[i]) + GR.fillarea(xn + dx, yn + dy) + end + GR.selntran(1) + else + GR.polymarker(x, y) + end +end + +function gr_polyline(x, y) + if NaN in x || NaN in y + i = 1 + j = 1 + n = length(x) + while i < n + while j < n && x[j] != Nan && y[j] != NaN + j += 1 + end + if i < j + GR.polyline(x[i:j], y[i:j]) + end + i = j + 1 + end + else + GR.polyline(x, y) + end +end + +function gr_display(plt::Plot{GRPackage}, clear=true, update=true, + subplot=[0, 1, 0, 1]) + d = plt.plotargs + + clear && GR.clearws() + + mwidth, mheight, width, height = GR.inqdspsize() + w, h = d[:size] + viewport = zeros(4) + if w > h + ratio = float(h) / w + msize = mwidth * w / width + GR.setwsviewport(0, msize, 0, msize * ratio) + GR.setwswindow(0, 1, 0, ratio) + viewport[1] = subplot[1] + 0.125 * (subplot[2] - subplot[1]) + viewport[2] = subplot[1] + 0.95 * (subplot[2] - subplot[1]) + viewport[3] = ratio * (subplot[3] + 0.125 * (subplot[4] - subplot[3])) + viewport[4] = ratio * (subplot[3] + 0.95 * (subplot[4] - subplot[3])) + else + ratio = float(w) / h + msize = mheight * h / height + GR.setwsviewport(0, msize * ratio, 0, msize) + GR.setwswindow(0, ratio, 0, 1) + viewport[1] = ratio * (subplot[1] + 0.125 * (subplot[2] - subplot[1])) + viewport[2] = ratio * (subplot[1] + 0.95 * (subplot[2] - subplot[1])) + viewport[3] = subplot[3] + 0.125 * (subplot[4] - subplot[3]) + viewport[4] = subplot[3] + 0.95 * (subplot[4] - subplot[3]) + end + + if haskey(d, :background_color) + GR.savestate() + GR.selntran(0) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + GR.setfillcolorind(gr_getcolorind(d[:background_color])) + if w > h + GR.fillrect(subplot[1], subplot[2], ratio*subplot[3], ratio*subplot[4]) + else + GR.fillrect(ratio*subplot[1], ratio*subplot[2], subplot[3], subplot[4]) + end + GR.selntran(1) + GR.restorestate() + c = getColor(d[:background_color]) + if 0.21 * c.r + 0.72 * c.g + 0.07 * c.b < 0.9 + fg = convert(Int, GR.inqcolorfromrgb(1-c.r, 1-c.g, 1-c.b)) + else + fg = 1 + end + else + fg = 1 + end + + extrema = zeros(2, 4) + num_axes = 1 + cmap = false + axes_2d = true + grid_flag = get(d, :grid, true) + + for axis = 1:2 + xmin = ymin = typemax(Float64) + xmax = ymax = typemin(Float64) + for p in plt.seriesargs + if axis == gr_getaxisind(p) + if axis == 2 + num_axes = 2 + end + if p[:linetype] == :bar + x, y = 1:length(p[:y]), p[:y] + elseif p[:linetype] == :ohlc + x, y = 1:size(p[:y], 1), p[:y] + elseif p[:linetype] in [:hist, :density] + x, y = Base.hist(p[:y]) + elseif p[:linetype] in [:hist2d, :hexbin] + E = zeros(length(p[:x]),2) + E[:,1] = p[:x] + E[:,2] = p[:y] + if isa(p[:nbins], Tuple) + xbins, ybins = p[:nbins] + else + xbins = ybins = p[:nbins] + end + cmap = true + x, y, H = Base.hist2d(E, xbins, ybins) + elseif p[:linetype] == :pie + axes_2d = false + xmin, xmax, ymin, ymax = 0, 1, 0, 1 + x, y = p[:x], p[:y] + else + if p[:linetype] in [:contour, :surface] + cmap = true + end + if p[:linetype] in [:surface, :wireframe, :path3d, :scatter3d] + axes_2d = false + end + x, y = p[:x], p[:y] + end + if p[:linetype] != :pie + xmin = min(minimum(x), xmin) + xmax = max(maximum(x), xmax) + if p[:linetype] == :ohlc + for val in y + ymin = min(val.open, val.high, val.low, val.close, ymin) + ymax = max(val.open, val.high, val.low, val.close, ymax) + end + else + ymin = min(minimum(y), ymin) + ymax = max(maximum(y), ymax) + end + end + end + end + if d[:xlims] != :auto + xmin, xmax = d[:xlims] + end + if d[:ylims] != :auto + ymin, ymax = d[:ylims] + end + if xmax <= xmin + xmax = xmin + 1 + end + if ymax <= ymin + ymax = ymin + 1 + end + extrema[axis,:] = [xmin, xmax, ymin, ymax] + end + + if num_axes == 2 || !axes_2d + viewport[2] -= 0.0525 + end + if cmap + viewport[2] -= 0.1 + end + GR.setviewport(viewport[1], viewport[2], viewport[3], viewport[4]) + + 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) + + for axis = 1:num_axes + xmin, xmax, ymin, ymax = extrema[axis,:] + 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.setwindow(xmin, xmax, ymin, ymax) + GR.setscale(scale) + + diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2) + charheight = max(0.018 * diag, 0.01) + GR.setcharheight(charheight) + GR.settextcolorind(fg) + + if axes_2d + GR.setlinewidth(1) + GR.setlinecolorind(fg) + ticksize = 0.0075 * diag + if grid_flag && fg == 1 + GR.grid(xtick, ytick, 0, 0, majorx, majory) + end + if num_axes == 1 + GR.axes(xtick, ytick, xorg[1], yorg[1], majorx, majory, ticksize) + GR.axes(xtick, ytick, xorg[2], yorg[2], -majorx, -majory, -ticksize) + elseif axis == 1 + GR.axes(xtick, ytick, xorg[1], yorg[1], majorx, majory, ticksize) + else + GR.axes(xtick, ytick, xorg[2], yorg[2], -majorx, majory, -ticksize) + end + end + end + + if get(d, :title, "") != "" + GR.savestate() + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) + GR.settextcolorind(fg) + GR.text(0.5 * (viewport[1] + viewport[2]), min(ratio, 1), d[:title]) + GR.restorestate() + end + if get(d, :xlabel, "") != "" + GR.savestate() + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) + GR.settextcolorind(fg) + GR.text(0.5 * (viewport[1] + viewport[2]), 0, d[:xlabel]) + GR.restorestate() + end + if get(d, :ylabel, "") != "" + GR.savestate() + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) + GR.setcharup(-1, 0) + GR.settextcolorind(fg) + GR.text(0, 0.5 * (viewport[3] + viewport[4]), d[:ylabel]) + GR.restorestate() + end + if get(d, :yrightlabel, "") != "" + GR.savestate() + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) + GR.setcharup(1, 0) + GR.settextcolorind(fg) + GR.text(1, 0.5 * (viewport[3] + viewport[4]), d[:yrightlabel]) + GR.restorestate() + end + + legend = false + + for p in plt.seriesargs + GR.savestate() + xmin, xmax, ymin, ymax = extrema[gr_getaxisind(p),:] + GR.setwindow(xmin, xmax, ymin, ymax) + if p[:linetype] in [:path, :line, :steppre, :steppost, :sticks, :hline, :vline, :ohlc] + haskey(p, :linestyle) && GR.setlinetype(gr_linetype[p[:linestyle]]) + haskey(p, :linewidth) && GR.setlinewidth(p[:linewidth]) + haskey(p, :linecolor) && GR.setlinecolorind(gr_getcolorind(p[:linecolor])) + end + if p[:linetype] == :path + if haskey(p, :fillcolor) + GR.setfillcolorind(gr_getcolorind(p[:fillcolor])) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + end + if length(p[:x]) > 1 + if p[:fillrange] != nothing + GR.fillarea([p[:x][1]; p[:x]; p[:x][length(p[:x])]], [p[:fillrange]; p[:y]; p[:fillrange]]) + end + GR.polyline(p[:x], p[:y]) + end + legend = true + end + if p[:linetype] == :line + if length(p[:x]) > 1 + gr_polyline(p[:x], p[:y]) + end + legend = true + elseif p[:linetype] in [:steppre, :steppost] + n = length(p[:x]) + x = zeros(2*n + 1) + y = zeros(2*n + 1) + x[1], y[1] = p[:x][1], p[:y][1] + j = 2 + for i = 2:n + if p[:linetype] == :steppre + x[j], x[j+1] = p[:x][i-1], p[:x][i] + y[j], y[j+1] = p[:y][i], p[:y][i] + else + x[j], x[j+1] = p[:x][i], p[:x][i] + y[j], y[j+1] = p[:y][i-1], p[:y][i] + end + j += 2 + end + if n > 1 + GR.polyline(x, y) + end + legend = true + elseif p[:linetype] == :sticks + x, y = p[:x], p[:y] + for i = 1:length(y) + GR.polyline([x[i], x[i]], [ymin, y[i]]) + end + legend = true + elseif p[:linetype] == :scatter || (p[:markershape] != :none && axes_2d) + haskey(p, :markercolor) && GR.setmarkercolorind(gr_getcolorind(p[:markercolor])) + gr_setmarkershape(p) + if haskey(d, :markersize) + if typeof(d[:markersize]) <: Number + GR.setmarkersize(d[:markersize] / 4.0) + if length(p[:x]) > 0 + gr_polymarker(p, p[:x], p[:y]) + end + else + c = p[:markercolor] + GR.setcolormap(-GR.COLORMAP_GLOWING) + for i = 1:length(p[:x]) + if isa(c, ColorGradient) && p[:zcolor] != nothing + ci = round(Int, 1000 + p[:zcolor][i] * 255) + GR.setmarkercolorind(ci) + end + GR.setmarkersize(d[:markersize][i] / 4.0) + gr_polymarker(p, [p[:x][i]], [p[:y][i]]) + end + end + else + if length(p[:x]) > 0 + gr_polymarker(p, p[:x], p[:y]) + end + end + legend = true + elseif p[:linetype] == :bar + y = p[:y] + for i = 1:length(y) + GR.setfillcolorind(gr_getcolorind(p[:fillcolor])) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + GR.fillrect(i-0.4, i+0.4, max(0, ymin), y[i]) + GR.setfillcolorind(1) + GR.setfillintstyle(GR.INTSTYLE_HOLLOW) + GR.fillrect(i-0.4, i+0.4, max(0, ymin), y[i]) + end + elseif p[:linetype] in [:hist, :density] + h = Base.hist(p[:y]) + x, y = float(collect(h[1])), float(h[2]) + for i = 2:length(y) + GR.setfillcolorind(gr_getcolorind(p[:fillcolor])) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + GR.fillrect(x[i-1], x[i], ymin, y[i]) + GR.setfillcolorind(1) + GR.setfillintstyle(GR.INTSTYLE_HOLLOW) + GR.fillrect(x[i-1], x[i], ymin, y[i]) + end + elseif p[:linetype] in [:hline, :vline] + for xy in p[:y] + if p[:linetype] == :hline + GR.polyline([xmin, xmax], [xy, xy]) + else + GR.polyline([xy, xy], [ymin, ymax]) + end + end + elseif p[:linetype] in [:hist2d, :hexbin] + E = zeros(length(p[:x]),2) + E[:,1] = p[:x] + E[:,2] = p[:y] + if isa(p[:nbins], Tuple) + xbins, ybins = p[:nbins] + else + xbins = ybins = p[:nbins] + end + x, y, H = Base.hist2d(E, xbins, ybins) + counts = round(Int32, 1000 + 255 * H / maximum(H)) + n, m = size(counts) + GR.setcolormap(GR.COLORMAP_COOLWARM) + GR.cellarray(xmin, xmax, ymin, ymax, n, m, counts) + GR.setviewport(viewport[2] + 0.02, viewport[2] + 0.05, + viewport[3], viewport[4]) + GR.setspace(0, maximum(counts), 0, 90) + diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2) + charheight = max(0.016 * diag, 0.01) + GR.setcharheight(charheight) + GR.colormap() + elseif p[:linetype] == :contour + x, y, z = p[:x], p[:y], p[:z].surf + zmin, zmax = minimum(z), maximum(z) + if typeof(p[:levels]) <: Array + h = p[:levels] + else + h = linspace(zmin, zmax, p[:levels]) + end + GR.setspace(zmin, zmax, 0, 90) + GR.setcolormap(GR.COLORMAP_COOLWARM) + GR.contour(x, y, h, reshape(z, length(x) * length(y)), 1000) + GR.setviewport(viewport[2] + 0.02, viewport[2] + 0.05, + viewport[3], viewport[4]) + l = round(Int32, 1000 + (h - minimum(h)) / (maximum(h) - minimum(h)) * 255) + GR.setwindow(xmin, xmax, zmin, zmax) + GR.cellarray(xmin, xmax, zmax, zmin, 1, length(l), l) + ztick = 0.5 * GR.tick(zmin, zmax) + diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2) + charheight = max(0.016 * diag, 0.01) + GR.setcharheight(charheight) + GR.axes(0, ztick, xmax, zmin, 0, 1, 0.005) + elseif p[:linetype] in [:surface, :wireframe] + x, y, z = p[:x], p[:y], p[:z].surf + zmin, zmax = GR.adjustrange(minimum(z), maximum(z)) + GR.setspace(zmin, zmax, 40, 70) + xtick = GR.tick(xmin, xmax) / 2 + ytick = GR.tick(ymin, ymax) / 2 + ztick = GR.tick(zmin, zmax) / 2 + diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2) + charheight = max(0.018 * diag, 0.01) + ticksize = 0.01 * (viewport[2] - viewport[1]) + GR.setlinewidth(1) + if grid_flag + GR.grid3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2) + GR.grid3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0) + end + z = reshape(z, length(x) * length(y)) + if p[:linetype] == :surface + GR.setcolormap(GR.COLORMAP_COOLWARM) + GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH) + else + GR.setfillcolorind(0) + GR.surface(x, y, z, GR.OPTION_FILLED_MESH) + end + GR.setlinewidth(1) + GR.setcharheight(charheight) + GR.axes3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2, -ticksize) + GR.axes3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0, ticksize) + if cmap + GR.setviewport(viewport[2] + 0.07, viewport[2] + 0.1, + viewport[3], viewport[4]) + GR.colormap() + end + elseif p[:linetype] in [:path3d, :scatter3d] + x, y, z = p[:x], p[:y], p[:z] + zmin, zmax = GR.adjustrange(minimum(z), maximum(z)) + GR.setspace(zmin, zmax, 40, 70) + xtick = GR.tick(xmin, xmax) / 2 + ytick = GR.tick(ymin, ymax) / 2 + ztick = GR.tick(zmin, zmax) / 2 + diag = sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2) + charheight = max(0.018 * diag, 0.01) + ticksize = 0.01 * (viewport[2] - viewport[1]) + GR.setlinewidth(1) + if grid_flag && p[:linetype] == :path3d + GR.grid3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2) + GR.grid3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0) + end + if p[:linetype] == :scatter3d + haskey(p, :markercolor) && GR.setmarkercolorind(gr_getcolorind(p[:markercolor])) + gr_setmarkershape(p) + for i = 1:length(z) + px, py = GR.wc3towc(x[i], y[i], z[i]) + gr_polymarker(p, [px], [py]) + end + else + haskey(p, :linewidth) && GR.setlinewidth(p[:linewidth]) + if length(x) > 0 + GR.polyline3d(x, y, z) + end + end + GR.setlinewidth(1) + GR.setcharheight(charheight) + GR.axes3d(xtick, 0, ztick, xmin, ymin, zmin, 2, 0, 2, -ticksize) + GR.axes3d(0, ytick, 0, xmax, ymin, zmin, 0, 2, 0, ticksize) + elseif p[:linetype] == :ohlc + y = p[:y] + n = size(y, 1) + ticksize = 0.5 * (xmax - xmin) / n + for i in 1:n + GR.polyline([i-ticksize, i], [y[i].open, y[i].open]) + GR.polyline([i, i], [y[i].low, y[i].high]) + GR.polyline([i, i+ticksize], [y[i].close, y[i].close]) + end + elseif p[:linetype] == :pie + GR.selntran(0) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + xmin, xmax, ymin, ymax = viewport + ymax -= 0.05 * (xmax - xmin) + xcenter = 0.5 * (xmin + xmax) + ycenter = 0.5 * (ymin + ymax) + if xmax - xmin > ymax - ymin + r = 0.5 * (ymax - ymin) + xmin, xmax = xcenter - r, xcenter + r + else + r = 0.5 * (xmax - xmin) + ymin, ymax = ycenter - r, ycenter + r + end + labels, slices = p[:x], p[:y] + numslices = length(slices) + total = sum(slices) + a1 = 0 + x = zeros(3) + y = zeros(3) + for i in 1:numslices + a2 = round(Int, a1 + (slices[i] / total) * 360.0) + GR.setfillcolorind(980 + (i-1) % 20) + GR.fillarc(xmin, xmax, ymin, ymax, a1, a2) + alpha = 0.5 * (a1 + a2) + cosf = r * cos(alpha * pi / 180) + sinf = r * sin(alpha * pi / 180) + x[1] = xcenter + cosf + y[1] = ycenter + sinf + x[2] = x[1] + 0.1 * cosf + y[2] = y[1] + 0.1 * sinf + y[3] = y[2] + if 90 <= alpha < 270 + x[3] = x[2] - 0.05 + GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF) + GR.text(x[3] - 0.01, y[3], string(labels[i])) + else + x[3] = x[2] + 0.05 + GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) + GR.text(x[3] + 0.01, y[3], string(labels[i])) + end + GR.polyline(x, y) + a1 = a2 + end + GR.selntran(1) + end + GR.restorestate() + end + + if d[:legend] != :none && legend + GR.savestate() + GR.selntran(0) + GR.setscale(0) + w = 0 + i = 0 + for p in plt.seriesargs + if typeof(p[:label]) <: Array + i += 1 + lab = p[:label][i] + else + lab = p[:label] + end + tbx, tby = GR.inqtext(0, 0, lab) + w = max(w, tbx[3]) + end + px = viewport[2] - 0.05 - w + py = viewport[4] - 0.06 + dy = 0.03 * sqrt((viewport[2] - viewport[1])^2 + (viewport[4] - viewport[3])^2) + GR.setfillintstyle(GR.INTSTYLE_SOLID) + GR.setfillcolorind(0) + GR.fillrect(px - 0.08, px + w + 0.02, py + dy, py - dy * length(plt.seriesargs)) + GR.setlinetype(1) + GR.setlinewidth(1) + GR.drawrect(px - 0.08, px + w + 0.02, py + dy, py - dy * length(plt.seriesargs)) + haskey(d, :linewidth) && GR.setlinewidth(d[:linewidth]) + i = 0 + for p in plt.seriesargs + if p[:linetype] in [:path, :line, :steppre, :steppost, :sticks] + haskey(p, :linecolor) && GR.setlinecolorind(gr_getcolorind(p[:linecolor])) + haskey(p, :linestyle) && GR.setlinetype(gr_linetype[p[:linestyle]]) + GR.polyline([px - 0.07, px - 0.01], [py, py]) + end + if p[:linetype] == :scatter || p[:markershape] != :none + haskey(p, :markercolor) && GR.setmarkercolorind(gr_getcolorind(p[:markercolor])) + gr_setmarkershape(p) + if p[:linetype] in [:path, :line, :steppre, :steppost, :sticks] + gr_polymarker(p, [px - 0.06, px - 0.02], [py, py]) + else + gr_polymarker(p, [px - 0.06, px - 0.04, px - 0.02], [py, py, py]) + end + end + if typeof(p[:label]) <: Array + i += 1 + lab = p[:label][i] + else + lab = p[:label] + end + GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) + GR.settextcolorind(1) + GR.text(px, py, lab) + py -= dy + end + GR.selntran(1) + GR.restorestate() + end + + if haskey(d, :anns) + GR.savestate() + for ann in d[:anns] + x, y, val = ann + x, y = GR.wctondc(x, y) + alpha = val.font.rotation + family = lowercase(val.font.family) + GR.setcharheight(0.7 * val.font.pointsize / d[:size][2]) + GR.setcharup(sin(val.font.rotation), cos(val.font.rotation)) + if haskey(gr_font_family, family) + GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING) + end + GR.settextcolorind(gr_getcolorind(val.font.color)) + GR.settextalign(gr_halign[val.font.halign], gr_valign[val.font.valign]) + GR.text(x, y, val.str) + end + GR.restorestate() + end + + update && GR.updatews() +end + +function gr_display(subplt::Subplot{GRPackage}) + clear = true + update = false + l = enumerate(subplt.layout) + nr = nrows(subplt.layout) + for (i, (r, c)) in l + nc = ncols(subplt.layout, r) + if i == length(l) + update = true + end + subplot = [(c-1)/nc, c/nc, 1-r/nr, 1-(r-1)/nr] + gr_display(subplt.plts[i], clear, update, subplot) + clear = false + end +end + +function _create_plot(pkg::GRPackage; kw...) + d = Dict(kw) + Plot(nothing, pkg, 0, d, Dict[]) +end + +function _add_series(::GRPackage, plt::Plot; kw...) + d = Dict(kw) + push!(plt.seriesargs, d) + plt +end + +function _add_annotations{X,Y,V}(plt::Plot{GRPackage}, anns::AVec{@compat(Tuple{X,Y,V})}) + if haskey(plt.plotargs, :anns) + append!(plt.plotargs[:anns], anns) + else + plt.plotargs[:anns] = anns + end +end + +# ---------------------------------------------------------------- + +function _before_update_plot(plt::Plot{GRPackage}) +end + +function _update_plot(plt::Plot{GRPackage}, d::Dict) + for k in (:title, :xlabel, :ylabel) + haskey(d, k) && (plt.plotargs[k] = d[k]) + end +end + +function _update_plot_pos_size(plt::PlottingObject{GRPackage}, d::Dict) +end + +# ---------------------------------------------------------------- + +function Base.getindex(plt::Plot{GRPackage}, i::Int) + d = plt.seriesargs[i] + d[:x], d[:y] +end + +function Base.setindex!(plt::Plot{GRPackage}, xy::Tuple, i::Integer) + d = plt.seriesargs[i] + d[:x], d[:y] = xy + plt +end + +# ---------------------------------------------------------------- + +function _create_subplot(subplt::Subplot{GRPackage}, isbefore::Bool) + true +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, m::MIME"image/png", plt::PlottingObject{GRPackage}) + GR.emergencyclosegks() + ENV["GKS_WSTYPE"] = "png" + gr_display(plt) + GR.emergencyclosegks() + write(io, readall("gks.png")) +end + +function Base.writemime(io::IO, m::MIME"image/svg+xml", plt::PlottingObject{GRPackage}) + GR.emergencyclosegks() + ENV["GKS_WSTYPE"] = "svg" + gr_display(plt) + GR.emergencyclosegks() + write(io, readall("gks.svg")) +end + +function Base.writemime(io::IO, m::MIME"text/html", plt::PlottingObject{GRPackage}) + writemime(io, MIME("image/svg+xml"), plt) +end + +function Base.writemime(io::IO, m::MIME"application/pdf", plt::PlottingObject{GRPackage}) + GR.emergencyclosegks() + ENV["GKS_WSTYPE"] = "pdf" + gr_display(plt) + GR.emergencyclosegks() + write(io, readall("gks.pdf")) +end + +function Base.writemime(io::IO, m::MIME"application/postscript", plt::PlottingObject{GRPackage}) + GR.emergencyclosegks() + ENV["GKS_WSTYPE"] = "ps" + gr_display(plt) + GR.emergencyclosegks() + write(io, readall("gks.ps")) +end + +function Base.display(::PlotsDisplay, plt::Plot{GRPackage}) + gr_display(plt) +end + +function Base.display(::PlotsDisplay, plt::Subplot{GRPackage}) + gr_display(plt) + true +end diff --git a/src/backends/immerse.jl b/src/backends/immerse.jl index ee199f8e..3eb6cfba 100644 --- a/src/backends/immerse.jl +++ b/src/backends/immerse.jl @@ -1,6 +1,13 @@ # https://github.com/JuliaGraphics/Immerse.jl +function _initialize_backend(::ImmersePackage; kw...) + @eval begin + import Immerse, Gadfly, Compose, Gtk + export Immerse, Gadfly, Compose, Gtk + include(joinpath(Pkg.dir("Plots"), "src", "backends", "gadfly_shapes.jl")) + end +end function createImmerseFigure(d::Dict) w,h = d[:size] diff --git a/src/backends/pgfplots.jl b/src/backends/pgfplots.jl new file mode 100644 index 00000000..f76cb33b --- /dev/null +++ b/src/backends/pgfplots.jl @@ -0,0 +1,107 @@ + +# https://github.com/sisl/PGFPlots.jl + +function _initialize_backend(::PGFPlotsPackage; kw...) + @eval begin + import PGFPlots + export PGFPlots + # TODO: other initialization that needs to be eval-ed + end + # TODO: other initialization +end + +# --------------------------------------------------------------------------- + +function _create_plot(pkg::PGFPlotsPackage; kw...) + d = Dict{Symbol,Any}(kw) + # TODO: create the window/canvas/context that is the plot within the backend (call it `o`) + # TODO: initialize the plot... title, xlabel, bgcolor, etc + Plot(nothing, pkg, 0, d, Dict[]) +end + + +function _add_series(::PGFPlotsPackage, plt::Plot; kw...) + d = Dict{Symbol,Any}(kw) + # TODO: add one series to the underlying package + push!(plt.seriesargs, d) + plt +end + +function _add_annotations{X,Y,V}(plt::Plot{PGFPlotsPackage}, anns::AVec{@compat(Tuple{X,Y,V})}) + # set or add to the annotation_list + if haskey(plt.plotargs, :annotation_list) + append!(plt.plotargs[:annotation_list], anns) + else + plt.plotargs[:annotation_list] = anns + end +end + +# ---------------------------------------------------------------- + +function _before_update_plot(plt::Plot{PGFPlotsPackage}) +end + +# TODO: override this to update plot items (title, xlabel, etc) after creation +function _update_plot(plt::Plot{PGFPlotsPackage}, d::Dict) +end + +function _update_plot_pos_size(plt::PlottingObject{PGFPlotsPackage}, d::Dict) +end + +# ---------------------------------------------------------------- + +# accessors for x/y data + +function Base.getindex(plt::Plot{PGFPlotsPackage}, i::Int) + d = plt.seriesargs[i] + d[:x], d[:y] +end + +function Base.setindex!(plt::Plot{PGFPlotsPackage}, xy::Tuple, i::Integer) + d = plt.seriesargs[i] + d[:x], d[:y] = xy + plt +end + +# ---------------------------------------------------------------- + +function _create_subplot(subplt::Subplot{PGFPlotsPackage}, isbefore::Bool) + # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example + true +end + +function _expand_limits(lims, plt::Plot{PGFPlotsPackage}, isx::Bool) + # TODO: call expand limits for each plot data +end + +function _remove_axis(plt::Plot{PGFPlotsPackage}, isx::Bool) + # TODO: if plot is inner subplot, might need to remove ticks or axis labels +end + +# ---------------------------------------------------------------- + + +# ---------------------------------------------------------------- + +################# This is the important method to implement!!! ################# +function _make_pgf_plot(plt::Plot{PGFPlotsPackage}) + # TODO: convert plt.plotargs and plt.seriesargs into PGFPlots calls + # TODO: return the PGFPlots object +end + +function Base.writemime(io::IO, mime::MIME"image/png", plt::PlottingObject{PGFPlotsPackage}) + plt.o = _make_pgf_plot(plt) + writemime(io, mime, plt.o) +end + +# function Base.writemime(io::IO, ::MIME"text/html", plt::PlottingObject{PGFPlotsPackage}) +# end + +function Base.display(::PlotsDisplay, plt::PlottingObject{PGFPlotsPackage}) + plt.o = _make_pgf_plot(plt) + display(plt.o) +end + +# function Base.display(::PlotsDisplay, plt::Subplot{PGFPlotsPackage}) +# # TODO: display/show the subplot +# end diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index a417d3fb..bf8614bc 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -1,10 +1,40 @@ # https://plot.ly/javascript/getting-started +function _initialize_backend(::PlotlyPackage; kw...) + @eval begin + import JSON + JSON._print(io::IO, state::JSON.State, dt::Union{Date,DateTime}) = print(io, '"', dt, '"') + + ############################ + # borrowed from https://github.com/spencerlyon2/Plotlyjs.jl/blob/master/src/display.jl + _js_path = joinpath(Pkg.dir("Plots"), "deps", "plotly-latest.min.js") + + # if we're in IJulia call setupnotebook to load js and css + if isijulia() + # the first script is some hack I needed to do in order for the notebook + # to not complain about Plotly being undefined + display("text/html", """ + + + """) + # display("text/html", "

Plotly javascript loaded.

") + end + # end borrowing (thanks :) + ########################### + + end + # TODO: other initialization +end + # --------------------------------------------------------------------------- function _create_plot(pkg::PlotlyPackage; kw...) - d = Dict(kw) + d = Dict{Symbol,Any}(kw) # TODO: create the window/canvas/context that is the plot within the backend (call it `o`) # TODO: initialize the plot... title, xlabel, bgcolor, etc Plot(nothing, pkg, 0, d, Dict[]) @@ -12,7 +42,7 @@ end function _add_series(::PlotlyPackage, plt::Plot; kw...) - d = Dict(kw) + d = Dict{Symbol,Any}(kw) # TODO: add one series to the underlying package push!(plt.seriesargs, d) plt @@ -83,7 +113,7 @@ end # _plotDefaults[:yflip] = false function plotlyfont(font::Font, color = font.color) - Dict( + Dict{Symbol,Any}( :family => font.family, :size => round(Int, font.pointsize*1.4), :color => webcolor(color), @@ -91,7 +121,7 @@ function plotlyfont(font::Font, color = font.color) end function get_annotation_dict(x, y, val::Union{AbstractString,Symbol}) - Dict( + Dict{Symbol,Any}( :text => val, :xref => "x", :x => x, @@ -102,7 +132,7 @@ function get_annotation_dict(x, y, val::Union{AbstractString,Symbol}) end function get_annotation_dict(x, y, ptxt::PlotText) - merge(get_annotation_dict(x, y, ptxt.str), Dict( + merge(get_annotation_dict(x, y, ptxt.str), Dict{Symbol,Any}( :font => plotlyfont(ptxt.font), :xanchor => ptxt.font.halign == :hcenter ? :center : ptxt.font.halign, :yanchor => ptxt.font.valign == :vcenter ? :middle : ptxt.font.valign, @@ -127,7 +157,7 @@ scalesym(isx::Bool) = symbol((isx ? "x" : "y") * "scale") labelsym(isx::Bool) = symbol((isx ? "x" : "y") * "label") function plotlyaxis(d::Dict, isx::Bool) - ax = Dict( + ax = Dict{Symbol,Any}( :title => d[labelsym(isx)], :showgrid => d[:grid], :zeroline => false, @@ -176,9 +206,10 @@ function plotlyaxis(d::Dict, isx::Bool) ax end -function get_plot_json(plt::Plot{PlotlyPackage}) - d = plt.plotargs - d_out = Dict() +# function get_plot_json(plt::Plot{PlotlyPackage}) +# d = plt.plotargs +function plotly_layout(d::Dict) + d_out = Dict{Symbol,Any}() bgcolor = webcolor(d[:background_color]) fgcolor = webcolor(d[:foreground_color]) @@ -186,96 +217,18 @@ function get_plot_json(plt::Plot{PlotlyPackage}) # set the fields for the plot d_out[:title] = d[:title] d_out[:titlefont] = plotlyfont(d[:guidefont], fgcolor) - d_out[:margin] = Dict(:l=>35, :b=>30, :r=>8, :t=>20) + d_out[:margin] = Dict{Symbol,Any}(:l=>35, :b=>30, :r=>8, :t=>20) d_out[:plot_bgcolor] = bgcolor d_out[:paper_bgcolor] = bgcolor - + # TODO: x/y axis tick values/labels d_out[:xaxis] = plotlyaxis(d, true) d_out[:yaxis] = plotlyaxis(d, false) - # # x-axis - # d_out[:xaxis] = Dict( - # :title => d[:xlabel], - # :showgrid => d[:grid], - # :zeroline => false, - # ) - # merge!(d_out[:xaxis], if use_axis_field(d[:xticks]) - # ax = Dict( - # :titlefont => plotlyfont(d[:guidefont]), - # :type => plotlyscale(d[:xscale]), - # :tickfont => plotlyfont(d[:tickfont]), - # :tickcolor => fgcolor, - # :linecolor => fgcolor, - # ) - - # # xlims - # lims = d[:xlims] - # if lims != :auto && limsType(lims) == :limits - # ax[:range] = lims - # end - - # # xflip - # if d[:xflip] - # ax[:autorange] = "reversed" - # end - - # # xticks - # ticks = d[:xticks] - # if ticks != :auto - # ttype = ticksType(ticks) - # if ttype == :ticks - # ax[:tickmode] = "array" - # ax[:tickvals] = ticks - # elseif ttype == :ticks_and_labels - # ax[:tickmode] = "array" - # ax[:tickvals], ax[:ticktext] = ticks - # end - # end - - # ax - # else - # Dict( - # :showticklabels => false, - # :showgrid => false, - # ) - # end) - - - # # y-axis - # d_out[:yaxis] = Dict( - # :title => d[:ylabel], - # :showgrid => d[:grid], - # :zeroline => false, - # ) - # merge!(d_out[:yaxis], if use_axis_field(d[:yticks]) - # Dict( - # :titlefont => plotlyfont(d[:guidefont]), - # :type => plotlyscale(d[:yscale]), - # :tickfont => plotlyfont(d[:tickfont]), - # :tickcolor => fgcolor, - # :linecolor => fgcolor, - # ) - # else - # Dict( - # :showticklabels => false, - # :showgrid => false, - # ) - # end) - - # lims = d[:ylims] - # if lims != :auto && limsType(lims) == :limits - # d_out[:yaxis][:range] = lims - # end - - # if d[:yflip] - # d_out[:yaxis][:autorange] = "reversed" - # end - # legend - d_out[:showlegend] = d[:legend] - if d[:legend] - d_out[:legend] = Dict( + d_out[:showlegend] = d[:legend] != :none + if d[:legend] != :none + d_out[:legend] = Dict{Symbol,Any}( :bgcolor => bgcolor, :bordercolor => fgcolor, :font => plotlyfont(d[:legendfont]), @@ -288,17 +241,20 @@ function get_plot_json(plt::Plot{PlotlyPackage}) d_out[:annotations] = [get_annotation_dict(ann...) for ann in anns] end - # finally build and return the json - JSON.json(d_out) + d_out +end + +function get_plot_json(plt::Plot{PlotlyPackage}) + JSON.json(plotly_layout(plt.plotargs)) end -function plotly_colorscale(grad::ColorGradient) - [[grad.values[i], webcolor(grad.colors[i])] for i in 1:length(grad.colors)] +function plotly_colorscale(grad::ColorGradient, alpha = nothing) + [[grad.values[i], webcolor(grad.colors[i], alpha)] for i in 1:length(grad.colors)] end -plotly_colorscale(c) = plotly_colorscale(ColorGradient(:bluesreds)) +plotly_colorscale(c, alpha = nothing) = plotly_colorscale(ColorGradient(:bluesreds), alpha) -const _plotly_markers = Dict( +const _plotly_markers = Dict{Symbol,Any}( :rect => "square", :xcross => "x", :utriangle => "triangle-up", @@ -309,8 +265,8 @@ const _plotly_markers = Dict( ) # get a dictionary representing the series params (d is the Plots-dict, d_out is the Plotly-dict) -function get_series_json(d::Dict; plot_index = nothing) - d_out = Dict() +function plotly_series(d::Dict; plot_index = nothing) + d_out = Dict{Symbol,Any}() x, y = collect(d[:x]), collect(d[:y]) d_out[:name] = d[:label] @@ -340,7 +296,7 @@ function get_series_json(d::Dict; plot_index = nothing) d_out[:type] = "bar" d_out[:x], d_out[:y] = x, y - elseif lt == :heatmap + elseif lt == :hist2d d_out[:type] = "histogram2d" d_out[:x], d_out[:y] = x, y if isa(d[:nbins], Tuple) @@ -360,16 +316,26 @@ function get_series_json(d::Dict; plot_index = nothing) d_out[:histnorm] = "probability density" end - elseif lt in (:contour, :surface, :wireframe) - d_out[:type] = lt == :wireframe ? :surface : string(lt) + elseif lt == :heatmap + d_out[:type] = "heatmap" d_out[:x], d_out[:y] = x, y d_out[:z] = d[:z].surf - # d_out[:showscale] = d[:legend] - if lt == :contour - d_out[:ncontours] = d[:nlevels] - d_out[:contours] = Dict(:coloring => d[:fillrange] != nothing ? "fill" : "lines") - end - d_out[:colorscale] = plotly_colorscale(d[lt == :contour ? :linecolor : :fillcolor]) + d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha]) + + elseif lt == :contour + d_out[:type] = "contour" + d_out[:x], d_out[:y] = x, y + d_out[:z] = d[:z].surf + # d_out[:showscale] = d[:colorbar] != :none + d_out[:ncontours] = d[:levels] + d_out[:contours] = Dict{Symbol,Any}(:coloring => d[:fillrange] != nothing ? "fill" : "lines") + d_out[:colorscale] = plotly_colorscale(d[:linecolor], d[:linealpha]) + + elseif lt in (:surface, :wireframe) + d_out[:type] = "surface" + d_out[:x], d_out[:y] = x, y + d_out[:z] = d[:z].surf + d_out[:colorscale] = plotly_colorscale(d[:fillcolor], d[:fillalpha]) elseif lt == :pie d_out[:type] = "pie" @@ -389,30 +355,37 @@ function get_series_json(d::Dict; plot_index = nothing) else warn("Plotly: linetype $lt isn't supported.") - return Dict() + return Dict{Symbol,Any}() end # add "marker" if hasmarker - d_out[:marker] = Dict( + d_out[:marker] = Dict{Symbol,Any}( :symbol => get(_plotly_markers, d[:markershape], string(d[:markershape])), :opacity => d[:markeralpha], :size => 2 * d[:markersize], :color => webcolor(d[:markercolor], d[:markeralpha]), - :line => Dict( + :line => Dict{Symbol,Any}( :color => webcolor(d[:markerstrokecolor], d[:markerstrokealpha]), :width => d[:markerstrokewidth], ), ) + + # gotta hack this (for now?) since plotly can't handle rgba values inside the gradient if d[:zcolor] != nothing - d_out[:marker][:color] = d[:zcolor] - d_out[:marker][:colorscale] = plotly_colorscale(d[:markercolor]) + # d_out[:marker][:color] = d[:zcolor] + # d_out[:marker][:colorscale] = plotly_colorscale(d[:markercolor], d[:markeralpha]) + # d_out[:showscale] = true + grad = ColorGradient(d[:markercolor], alpha=d[:markeralpha]) + zmin, zmax = extrema(d[:zcolor]) + d_out[:marker][:color] = [webcolor(getColorZ(grad, (zi - zmin) / (zmax - zmin))) for zi in d[:zcolor]] end + end # add "line" if hasline - d_out[:line] = Dict( + d_out[:line] = Dict{Symbol,Any}( :color => webcolor(d[:linecolor], d[:linealpha]), :width => d[:linewidth], :shape => if lt == :steppre @@ -438,14 +411,14 @@ end # get a list of dictionaries, each representing the series params function get_series_json(plt::Plot{PlotlyPackage}) - JSON.json(map(get_series_json, plt.seriesargs)) + JSON.json(map(plotly_series, plt.seriesargs)) end function get_series_json(subplt::Subplot{PlotlyPackage}) ds = Dict[] for (i,plt) in enumerate(subplt.plts) for d in plt.seriesargs - push!(ds, get_series_json(d, plot_index = i)) + push!(ds, plotly_series(d, plot_index = i)) end end JSON.json(ds) @@ -479,10 +452,10 @@ function html_body(subplt::Subplot{PlotlyPackage}) html = ["
"] nr = nrows(subplt.layout) ph = h / nr - + for r in 1:nr push!(html, "
") - + nc = ncols(subplt.layout, r) pw = w / nc @@ -490,7 +463,7 @@ function html_body(subplt::Subplot{PlotlyPackage}) plt = subplt[r,c] push!(html, html_body(plt, "float:left; width:$(pw)px; height:$(ph)px;")) end - + push!(html, "
") end push!(html, "
") @@ -501,11 +474,8 @@ end # ---------------------------------------------------------------- - function Base.writemime(io::IO, ::MIME"image/png", plt::PlottingObject{PlotlyPackage}) - isijulia() && return - # TODO: write a png to io - println("todo: png") + warn("todo: png") end function Base.writemime(io::IO, ::MIME"text/html", plt::PlottingObject{PlotlyPackage}) diff --git a/src/backends/plotlyjs.jl b/src/backends/plotlyjs.jl new file mode 100644 index 00000000..ab40ec80 --- /dev/null +++ b/src/backends/plotlyjs.jl @@ -0,0 +1,128 @@ + +# https://github.com/spencerlyon2/PlotlyJS.jl + +function _initialize_backend(::PlotlyJSPackage; kw...) + @eval begin + import PlotlyJS + export PlotlyJS + end + + for (mime, fmt) in PlotlyJS._mimeformats + @eval Base.writemime(io::IO, m::MIME{symbol($mime)}, p::Plot{PlotlyJSPackage}) = writemime(io, m, p.o) + end + + # override IJulia inline display + if isijulia() + IJulia.display_dict(plt::PlottingObject{PlotlyJSPackage}) = IJulia.display_dict(plt.o) + end +end + +# --------------------------------------------------------------------------- + +function _create_plot(pkg::PlotlyJSPackage; kw...) + d = Dict(kw) + # TODO: create the window/canvas/context that is the plot within the backend (call it `o`) + # TODO: initialize the plot... title, xlabel, bgcolor, etc + # o = PlotlyJS.Plot(PlotlyJS.GenericTrace[], PlotlyJS.Layout(), + # Base.Random.uuid4(), PlotlyJS.ElectronDisplay()) + # T = isijulia() ? PlotlyJS.JupyterPlot : PlotlyJS.ElectronPlot + # o = T(PlotlyJS.Plot()) + o = PlotlyJS.plot() + + Plot(o, pkg, 0, d, Dict[]) +end + + +function _add_series(::PlotlyJSPackage, plt::Plot; kw...) + d = Dict(kw) + syncplot = plt.o + + # dumpdict(d, "addseries", true) + + # add to the data array + pdict = plotly_series(d) + typ = pop!(pdict, :type) + gt = PlotlyJS.GenericTrace(typ; pdict...) + PlotlyJS.addtraces!(syncplot, gt) + + push!(plt.seriesargs, d) + plt +end + + +# --------------------------------------------------------------------------- + + +function _add_annotations{X,Y,V}(plt::Plot{PlotlyJSPackage}, anns::AVec{@compat(Tuple{X,Y,V})}) + # set or add to the annotation_list + if !haskey(plt.plotargs, :annotation_list) + plt.plotargs[:annotation_list] = Any[] + end + append!(plt.plotargs[:annotation_list], anns) +end + +# ---------------------------------------------------------------- + +function _before_update_plot(plt::Plot{PlotlyJSPackage}) +end + +# TODO: override this to update plot items (title, xlabel, etc) after creation +function _update_plot(plt::Plot{PlotlyJSPackage}, d::Dict) + pdict = plotly_layout(d) + # dumpdict(pdict, "pdict updateplot", true) + syncplot = plt.o + w,h = d[:size] + PlotlyJS.relayout!(syncplot, pdict, width = w, height = h) +end + + +function _update_plot_pos_size(plt::PlottingObject{PlotlyJSPackage}, d::Dict) +end + +# ---------------------------------------------------------------- + +# accessors for x/y data + +function Base.getindex(plt::Plot{PlotlyJSPackage}, i::Int) + d = plt.seriesargs[i] + d[:x], d[:y] +end + +function Base.setindex!(plt::Plot{PlotlyJSPackage}, xy::Tuple, i::Integer) + d = plt.seriesargs[i] + d[:x], d[:y] = xy + # TODO: this is likely ineffecient... we should make a call that ONLY changes the plot data + # PlotlyJS.restyle!(plt.o, i, plotly_series(d)) + PlotlyJS.restyle!(plt.o, i, Dict(:x=>(d[:x],), :y=>(d[:y],))) + plt +end + +# ---------------------------------------------------------------- + +function _create_subplot(subplt::Subplot{PlotlyJSPackage}, isbefore::Bool) + # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example + true +end + +function _expand_limits(lims, plt::Plot{PlotlyJSPackage}, isx::Bool) + # TODO: call expand limits for each plot data +end + +function _remove_axis(plt::Plot{PlotlyJSPackage}, isx::Bool) + # TODO: if plot is inner subplot, might need to remove ticks or axis labels +end + +# ---------------------------------------------------------------- + +function Base.writemime(io::IO, m::MIME"text/html", plt::PlottingObject{PlotlyJSPackage}) + Base.writemime(io, m, plt.o) +end + +function Base.display(::PlotsDisplay, plt::Plot{PlotlyJSPackage}) + display(plt.o) +end + +function Base.display(::PlotsDisplay, plt::Subplot{PlotlyJSPackage}) + error() +end + diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index d433eb49..cb19ec86 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -1,10 +1,37 @@ # https://github.com/stevengj/PyPlot.jl +function _initialize_backend(::PyPlotPackage) + @eval begin + import PyPlot + export PyPlot + const pycolors = PyPlot.pywrap(PyPlot.pyimport("matplotlib.colors")) + const pypath = PyPlot.pywrap(PyPlot.pyimport("matplotlib.path")) + const mplot3d = PyPlot.pywrap(PyPlot.pyimport("mpl_toolkits.mplot3d")) + # const pycolorbar = PyPlot.pywrap(PyPlot.pyimport("matplotlib.colorbar")) + end + + if !isa(Base.Multimedia.displays[end], Base.REPL.REPLDisplay) + PyPlot.ioff() # stops wierd behavior of displaying incomplete graphs in IJulia + + # # TODO: how the hell can I use PyQt4?? + # "pyqt4"=>:qt_pyqt4 + # PyPlot.backend[1] = "pyqt4" + # PyPlot.gui[1] = :qt_pyqt4 + # PyPlot.switch_backend("Qt4Agg") + + # only turn on the gui if we want it + if PyPlot.gui != :none + PyPlot.pygui(true) + end + end +end + # ------------------------------- # convert colorant to 4-tuple RGBA getPyPlotColor(c::Colorant, α=nothing) = map(f->float(f(convertColor(c,α))), (red, green, blue, alpha)) +getPyPlotColor(cvec::ColorVector, α=nothing) = map(getPyPlotColor, convertColor(cvec, α).v) getPyPlotColor(scheme::ColorScheme, α=nothing) = getPyPlotColor(convertColor(getColor(scheme), α)) getPyPlotColor(c, α=nothing) = getPyPlotColor(convertColor(c, α)) # getPyPlotColor(c, alpha) = getPyPlotColor(colorscheme(c, alpha)) @@ -12,12 +39,14 @@ getPyPlotColor(c, α=nothing) = getPyPlotColor(convertColor(c, α)) function getPyPlotColorMap(c::ColorGradient, α=nothing) # c = ColorGradient(c.colors, c.values, alpha=α) # pycolors.pymember("LinearSegmentedColormap")[:from_list]("tmp", map(getPyPlotColor, getColorVector(c))) - pyvals = [(c.values[i], getPyPlotColor(c.colors[i], α)) for i in 1:length(c.colors)] + # pyvals = [(c.values[i], getPyPlotColor(c.colors[i], α)) for i in 1:length(c.colors)] + pyvals = [(v, getPyPlotColor(getColorZ(c, v), α)) for v in c.values] + # @show c α pyvals pycolors.pymember("LinearSegmentedColormap")[:from_list]("tmp", pyvals) end -# anything else just gets a redsblue gradient -getPyPlotColorMap(c, α=nothing) = getPyPlotColorMap(ColorGradient(:redsblues), α) +# anything else just gets a bluesred gradient +getPyPlotColorMap(c, α=nothing) = getPyPlotColorMap(ColorGradient(:bluesreds), α) # get the style (solid, dashed, etc) function getPyPlotLineStyle(linetype::Symbol, linestyle::Symbol) @@ -62,8 +91,14 @@ function getPyPlotMarker(marker::Symbol) return "o" end +# getPyPlotMarker(markers::AVec) = map(getPyPlotMarker, markers) +function getPyPlotMarker(markers::AVec) + warn("Vectors of markers are currently unsupported in PyPlot: $markers") + getPyPlotMarker(markers[1]) +end + # pass through -function getPyPlotMarker(marker::@compat(AbstractString)) +function getPyPlotMarker(marker::AbstractString) @assert length(marker) == 1 marker end @@ -134,13 +169,14 @@ function getPyPlotFunction(plt::Plot, axis::Symbol, linetype::Symbol) :density => :hist, :sticks => :bar, :bar => :bar, - :heatmap => :hexbin, + :hist2d => :hexbin, :hexbin => :hexbin, :scatter => :scatter, :contour => :contour, :scatter3d => :scatter, :surface => :plot_surface, :wireframe => :plot_wireframe, + :heatmap => :pcolor, # :surface => pycolors.pymember("LinearSegmentedColormap")[:from_list] ) return ax[get(fmap, linetype, :plot)] @@ -188,6 +224,41 @@ end # ------------------------------------------------------------------ +function pyplot_figure(plotargs::Dict) + w,h = map(px2inch, plotargs[:size]) + bgcolor = getPyPlotColor(plotargs[:background_color]) + + # reuse the current figure? + fig = if plotargs[:overwrite_figure] + PyPlot.gcf() + else + PyPlot.figure() + end + + # update the specs + # fig[:set_size_inches](w,h, (isijulia() ? [] : [true])...) + fig[:set_size_inches](w, h, forward = true) + fig[:set_facecolor](bgcolor) + fig[:set_dpi](DPI) + fig[:set_tight_layout](true) + + # clear the figure + PyPlot.clf() + + # resize the window + PyPlot.plt[:get_current_fig_manager]()[:resize](plotargs[:size]...) + fig +end + +function pyplot_3d_setup!(wrap, d) + # 3D? + # if haskey(d, :linetype) && first(d[:linetype]) in _3dTypes # && isa(plt.o, PyPlotFigWrapper) + if trueOrAllTrue(lt -> lt in _3dTypes, get(d, :linetype, :none)) + push!(wrap.kwargs, (:projection, "3d")) + end +end + + # TODO: # fillto # might have to use barHack/histogramHack?? # reg # true or false, add a regression line for each line @@ -204,9 +275,13 @@ function _create_plot(pkg::PyPlotPackage; kw...) if haskey(d, :subplot) wrap = nothing else - w,h = map(px2inch, d[:size]) - bgcolor = getPyPlotColor(d[:background_color]) - wrap = PyPlotAxisWrapper(nothing, nothing, PyPlot.figure(; figsize = (w,h), facecolor = bgcolor, dpi = DPI, tight_layout = true), []) + wrap = PyPlotAxisWrapper(nothing, nothing, pyplot_figure(d), []) + # wrap = PyPlotAxisWrapper(nothing, nothing, PyPlot.figure(; figsize = (w,h), facecolor = bgcolor, dpi = DPI, tight_layout = true), []) + + # if haskey(d, :linetype) && first(d[:linetype]) in _3dTypes # && isa(plt.o, PyPlotFigWrapper) + # push!(wrap.kwargs, (:projection, "3d")) + # end + pyplot_3d_setup!(wrap, d) end plt = Plot(wrap, pkg, 0, d, Dict[]) @@ -217,11 +292,21 @@ end function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) d = Dict(kw) + # 3D plots have a different underlying Axes object in PyPlot lt = d[:linetype] - if lt in _3dTypes # && isa(plt.o, PyPlotFigWrapper) + if lt in _3dTypes && isempty(plt.o.kwargs) push!(plt.o.kwargs, (:projection, "3d")) end + # handle mismatched x/y sizes, as PyPlot doesn't like that + x, y = d[:x], d[:y] + nx, ny = map(length, (x,y)) + if nx < ny + d[:x] = Float64[x[mod1(i,nx)] for i=1:ny] + else + d[:y] = Float64[y[mod1(i,ny)] for i=1:nx] + end + ax = getAxis(plt, d[:axis]) if !(lt in supportedTypes(pkg)) error("linetype $(lt) is unsupported in PyPlot. Choose from: $(supportedTypes(pkg))") @@ -232,7 +317,7 @@ function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) if lt == :sticks d,_ = sticksHack(;d...) - + elseif lt in (:scatter, :scatter3d) if d[:markershape] == :none d[:markershape] = :ellipse @@ -249,7 +334,7 @@ function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) end - lt = d[:linetype] + # lt = d[:linetype] extra_kwargs = Dict() plotfunc = getPyPlotFunction(plt, d[:axis], lt) @@ -267,7 +352,7 @@ function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) extra_kwargs[:linewidth] = (lt == :sticks ? 0.1 : 0.9) end - elseif lt in (:heatmap, :hexbin) + elseif lt in (:hist2d, :hexbin) extra_kwargs[:gridsize] = d[:nbins] extra_kwargs[:cmap] = getPyPlotColorMap(d[:linecolor]) @@ -286,6 +371,9 @@ function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) extra_kwargs[:linewidth] = d[:linewidth] extra_kwargs[:edgecolor] = getPyPlotColor(d[:linecolor], d[:linealpha]) + elseif lt == :heatmap + extra_kwargs[:cmap] = getPyPlotColorMap(d[:fillcolor], d[:fillalpha]) + else extra_kwargs[:linestyle] = getPyPlotLineStyle(lt, d[:linestyle]) @@ -294,15 +382,28 @@ function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) if lt in (:scatter, :scatter3d) extra_kwargs[:s] = d[:markersize].^2 c = d[:markercolor] - if isa(c, ColorGradient) && d[:zcolor] != nothing + if d[:zcolor] != nothing + if !isa(c, ColorGradient) + c = colorscheme(:bluesreds) + end extra_kwargs[:c] = convert(Vector{Float64}, d[:zcolor]) extra_kwargs[:cmap] = getPyPlotColorMap(c, d[:markeralpha]) else - extra_kwargs[:c] = getPyPlotColor(c, d[:markeralpha]) - end - if d[:markeralpha] != nothing - extra_kwargs[:alpha] = d[:markeralpha] + # extra_kwargs[:c] = getPyPlotColor(c, d[:markeralpha]) + ppc = getPyPlotColor(c, d[:markeralpha]) + + # total hack due to PyPlot bug (see issue #145). + # hack: duplicate the color vector when the total rgba fields is the same as the series length + if (typeof(ppc) <: AbstractArray && length(ppc)*4 == length(x)) || + (typeof(ppc) <: Tuple && length(x) == 4) + ppc = vcat(ppc, ppc) + end + extra_kwargs[:c] = ppc + end + # if d[:markeralpha] != nothing + # extra_kwargs[:alpha] = d[:markeralpha] + # end extra_kwargs[:edgecolors] = getPyPlotColor(d[:markerstrokecolor], d[:markerstrokealpha]) extra_kwargs[:linewidths] = d[:markerstrokewidth] else @@ -321,7 +422,7 @@ function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) # end # set these for all types - if !(lt in (:contour,:surface,:wireframe)) + if !(lt in (:contour,:surface,:wireframe,:heatmap)) if !(lt in (:scatter, :scatter3d)) extra_kwargs[:color] = color extra_kwargs[:linewidth] = d[:linewidth] @@ -333,31 +434,56 @@ function _add_series(pkg::PyPlotPackage, plt::Plot; kw...) # do the plot d[:serieshandle] = if ishistlike(lt) plotfunc(d[:y]; extra_kwargs...)[1] + elseif lt == :contour - # NOTE: x/y are backwards in pyplot, so we switch the x and y args (also y is reversed), - # and take the transpose of the surface matrix x, y = d[:x], d[:y] surf = d[:z].surf' - handle = plotfunc(x, y, surf, d[:nlevels]; extra_kwargs...) + levels = d[:levels] + if isscalar(levels) + extra_args = (levels) + elseif isvector(levels) + extra_args = () + extra_kwargs[:levels] = levels + else + error("Only numbers and vectors are supported with levels keyword") + end + handle = plotfunc(x, y, surf, extra_args...; extra_kwargs...) if d[:fillrange] != nothing - handle = ax[:contourf](x, y, surf, d[:nlevels]; cmap = getPyPlotColorMap(d[:fillcolor], d[:fillalpha])) + extra_kwargs[:cmap] = getPyPlotColorMap(d[:fillcolor], d[:fillalpha]) + delete!(extra_kwargs, :linewidths) + handle = ax[:contourf](x, y, surf, extra_args...; extra_kwargs...) end handle + elseif lt in (:surface,:wireframe) - plotfunc(repmat(d[:x]',length(d[:y]),1), repmat(d[:y],1,length(d[:x])), d[:z].surf'; extra_kwargs...) + x, y, z = d[:x], d[:y], Array(d[:z]) + if !ismatrix(x) || !ismatrix(y) + x = repmat(x', length(y), 1) + y = repmat(y, 1, length(d[:x])) + z = z' + end + plotfunc(x, y, z; extra_kwargs...) + elseif lt in _3dTypes plotfunc(d[:x], d[:y], d[:z]; extra_kwargs...) - elseif lt in (:scatter, :heatmap, :hexbin) + + elseif lt in (:scatter, :hist2d, :hexbin) plotfunc(d[:x], d[:y]; extra_kwargs...) - else + + elseif lt == :heatmap + x, y, z = d[:x], d[:y], d[:z].surf' + plotfunc(heatmap_edges(x), heatmap_edges(y), z; extra_kwargs...) + + else # plot plotfunc(d[:x], d[:y]; extra_kwargs...)[1] end + # smoothing handleSmooth(plt, ax, d, d[:smooth]) # add the colorbar legend - if plt.plotargs[:legend] && haskey(extra_kwargs, :cmap) - PyPlot.colorbar(d[:serieshandle]) + if plt.plotargs[:colorbar] != :none && haskey(extra_kwargs, :cmap) + PyPlot.colorbar(d[:serieshandle], ax=ax) end # @show extra_kwargs @@ -390,30 +516,52 @@ function Base.getindex(plt::Plot{PyPlotPackage}, i::Integer) xy = series[:get_offsets]() return vec(xy[:,1]), vec(xy[:,2]) end - # series[:relim]() - # mapping = getGadflyMappings(plt, i)[1] - # mapping[:x], mapping[:y] +end + +function minmaxseries(ds, vec, axis) + lo, hi = Inf, -Inf + for d in ds + d[:axis] == axis || continue + v = d[vec] + if length(v) > 0 + vlo, vhi = extrema(v) + lo = min(lo, vlo) + hi = max(hi, vhi) + end + end + if lo == hi + hi = if lo == 0 + 1e-6 + else + hi + min(abs(1e-2hi), 1e-6) + end + end + lo, hi +end + +# TODO: this needs to handle one-sided fixed limits +function set_lims!(plt::Plot{PyPlotPackage}, axis::Symbol) + ax = getAxis(plt, axis) + if plt.plotargs[:xlims] == :auto + ax[:set_xlim](minmaxseries(plt.seriesargs, :x, axis)...) + end + if plt.plotargs[:ylims] == :auto + ax[:set_ylim](minmaxseries(plt.seriesargs, :y, axis)...) + end end function Base.setindex!{X,Y}(plt::Plot{PyPlotPackage}, xy::Tuple{X,Y}, i::Integer) - series = plt.seriesargs[i][:serieshandle] + d = plt.seriesargs[i] + series = d[:serieshandle] x, y = xy + d[:x], d[:y] = x, y try series[:set_data](x, y) catch series[:set_offsets](hcat(x, y)) end - ax = series[:axes] - if plt.plotargs[:xlims] == :auto - xmin, xmax = ax[:get_xlim]() - ax[:set_xlim](min(xmin, minimum(x)), max(xmax, maximum(x))) - end - if plt.plotargs[:ylims] == :auto - ymin, ymax = ax[:get_ylim]() - ax[:set_ylim](min(ymin, minimum(y)), max(ymax, maximum(y))) - end - + set_lims!(plt, d[:axis]) plt end @@ -428,7 +576,13 @@ function addPyPlotLims(ax, lims, isx::Bool) lims == :auto && return ltype = limsType(lims) if ltype == :limits - ax[isx ? :set_xlim : :set_ylim](lims...) + if isx + isfinite(lims[1]) && ax[:set_xlim](left = lims[1]) + isfinite(lims[2]) && ax[:set_xlim](right = lims[2]) + else + isfinite(lims[1]) && ax[:set_ylim](bottom = lims[1]) + isfinite(lims[2]) && ax[:set_ylim](top = lims[2]) + end else error("Invalid input for $(isx ? "xlims" : "ylims"): ", lims) end @@ -444,7 +598,8 @@ function addPyPlotTicks(ax, ticks, isx::Bool) if ttype == :ticks ax[isx ? :set_xticks : :set_yticks](ticks) elseif ttype == :ticks_and_labels - ax[isx ? :set_xticks : :set_yticks](ticks...) + ax[isx ? :set_xticks : :set_yticks](ticks[1]) + ax[isx ? :set_xticklabels : :set_yticklabels](ticks[2]) else error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks) end @@ -465,7 +620,7 @@ function _update_plot(plt::Plot{PyPlotPackage}, d::Dict) ax[:set_ylabel](d[:ylabel]) end if usingRightAxis(plt) && get(d, :yrightlabel, "") != "" - rightax = getRightAxis(figorax) + rightax = getRightAxis(figorax) rightax[:set_ylabel](d[:yrightlabel]) end @@ -494,7 +649,7 @@ function _update_plot(plt::Plot{PyPlotPackage}, d::Dict) # font sizes for ax in axes # haskey(d, :yrightlabel) || continue - + # guides sz = get(d, :guidefont, plt.plotargs[:guidefont]).pointsize @@ -509,7 +664,7 @@ function _update_plot(plt::Plot{PyPlotPackage}, d::Dict) lab[:set_fontsize](sz) end end - + # grid if get(d, :grid, false) ax[:xaxis][:grid](true) @@ -562,9 +717,11 @@ end function _create_subplot(subplt::Subplot{PyPlotPackage}, isbefore::Bool) l = subplt.layout - w,h = map(px2inch, getplotargs(subplt,1)[:size]) - bgcolor = getPyPlotColor(getplotargs(subplt,1)[:background_color]) - fig = PyPlot.figure(; figsize = (w,h), facecolor = bgcolor, dpi = DPI, tight_layout = true) + # w,h = map(px2inch, getplotargs(subplt,1)[:size]) + # bgcolor = getPyPlotColor(getplotargs(subplt,1)[:background_color]) + # fig = PyPlot.figure(; figsize = (w,h), facecolor = bgcolor, dpi = DPI, tight_layout = true) + plotargs = getplotargs(subplt, 1) + fig = pyplot_figure(plotargs) nr = nrows(l) for (i,(r,c)) in enumerate(l) @@ -575,10 +732,12 @@ function _create_subplot(subplt::Subplot{PyPlotPackage}, isbefore::Bool) ax = fig[:add_subplot](nr, nc, fakeidx) subplt.plts[i].o = PyPlotAxisWrapper(ax, nothing, fig, []) + pyplot_3d_setup!(subplt.plts[i].o, plotargs) end # subplt.o = PyPlotFigWrapper(fig, []) subplt.o = PyPlotAxisWrapper(nothing, nothing, fig, []) + pyplot_3d_setup!(subplt.o, plotargs) true end @@ -625,15 +784,24 @@ end # ----------------------------------------------------------------- +const _pyplot_legend_pos = Dict( + :right => "right", + :left => "center left", + :top => "upper center", + :bottom => "lower center" + ) + # function addPyPlotLegend(plt::Plot) function addPyPlotLegend(plt::Plot, ax) - if plt.plotargs[:legend] + leg = plt.plotargs[:legend] + if leg != :none # gotta do this to ensure both axes are included - args = filter(x -> !(x[:linetype] in (:hist,:density,:hexbin,:heatmap,:hline,:vline,:contour, :path3d, :scatter3d)), plt.seriesargs) + args = filter(x -> !(x[:linetype] in (:hist,:density,:hexbin,:hist2d,:hline,:vline,:contour,:surface,:wireframe,:heatmap,:path3d,:scatter3d)), plt.seriesargs) + args = filter(x -> x[:label] != "", args) if length(args) > 0 leg = ax[:legend]([d[:serieshandle] for d in args], [d[:label] for d in args], - loc="best", + loc = get(_pyplot_legend_pos, leg, "best"), fontsize = plt.plotargs[:legendfont].pointsize # framealpha = 0.6 ) @@ -713,7 +881,7 @@ const _pyplot_mimeformats = @compat Dict( "application/pdf" => "pdf", "image/png" => "png", "application/postscript" => "ps", - # "image/svg+xml" => "svg" + "image/svg+xml" => "svg" ) @@ -732,6 +900,7 @@ for (mime, fmt) in _pyplot_mimeformats end end + # function Base.writemime(io::IO, m::MIME"image/png", subplt::Subplot{PyPlotPackage}) # finalizePlot(subplt) # writemime(io, m, getfig(subplt.o)) diff --git a/src/backends/qwt.jl b/src/backends/qwt.jl index 922f691d..a6b09088 100644 --- a/src/backends/qwt.jl +++ b/src/backends/qwt.jl @@ -1,6 +1,13 @@ # https://github.com/tbreloff/Qwt.jl +function _initialize_backend(::QwtPackage; kw...) + @eval begin + warn("Qwt is no longer supported... many features will likely be broken.") + import Qwt + export Qwt + end +end # ------------------------------- diff --git a/src/backends/supported.jl b/src/backends/supported.jl index a23615de..0ba5ff49 100644 --- a/src/backends/supported.jl +++ b/src/backends/supported.jl @@ -33,6 +33,7 @@ supportedArgs(::GadflyPackage) = [ :label, :layout, :legend, + :colorbar, :linestyle, :linetype, :linewidth, @@ -43,6 +44,7 @@ supportedArgs(::GadflyPackage) = [ :markeralpha, :markerstrokewidth, :markerstrokecolor, + :markerstrokealpha, # :markerstrokestyle, :n, :nbins, @@ -74,11 +76,11 @@ supportedArgs(::GadflyPackage) = [ :legendfont, :grid, # :surface, - :nlevels, + :levels, ] supportedAxes(::GadflyPackage) = [:auto, :left] supportedTypes(::GadflyPackage) = [:none, :line, :path, :steppre, :steppost, :sticks, - :scatter, :heatmap, :hexbin, :hist, :bar, + :scatter, :hist2d, :hexbin, :hist, :bar, :hline, :vline, :contour] supportedStyles(::GadflyPackage) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] supportedMarkers(::GadflyPackage) = vcat(_allMarkers, Shape) @@ -114,6 +116,83 @@ supportedArgs(::PyPlotPackage) = [ :label, :layout, :legend, + :colorbar, + :linestyle, + :linetype, + :linewidth, + :markershape, + :markercolor, + :markersize, + :markerstrokewidth, + :markerstrokecolor, + :markerstrokealpha, + # :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, + :levels, + :fillalpha, + :linealpha, + :markeralpha, + :overwrite_figure, + ] +supportedAxes(::PyPlotPackage) = _allAxes +supportedTypes(::PyPlotPackage) = [:none, :line, :path, :steppre, :steppost, #:sticks, + :scatter, :hist2d, :hexbin, :hist, :density, :bar, + :hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe, :heatmap] +supportedStyles(::PyPlotPackage) = [:auto, :solid, :dash, :dot, :dashdot] +# supportedMarkers(::PyPlotPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon] +supportedMarkers(::PyPlotPackage) = vcat(_allMarkers, Shape) +supportedScales(::PyPlotPackage) = [:identity, :ln, :log2, :log10] +subplotSupported(::PyPlotPackage) = true + + +# -------------------------------------------------------------------------------------- + + + +supportedArgs(::GRPackage) = [ + :annotation, + :axis, + :background_color, + :linecolor, + :color_palette, + :fillrange, + :fillcolor, + :foreground_color, + :group, + :label, + :layout, + :legend, + :colorbar, :linestyle, :linetype, :linewidth, @@ -159,15 +238,15 @@ supportedArgs(::PyPlotPackage) = [ :linealpha, :markeralpha, ] -supportedAxes(::PyPlotPackage) = _allAxes -supportedTypes(::PyPlotPackage) = [:none, :line, :path, :steppre, :steppost, :sticks, - :scatter, :heatmap, :hexbin, :hist, :density, :bar, - :hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe] -supportedStyles(::PyPlotPackage) = [:auto, :solid, :dash, :dot, :dashdot] -# supportedMarkers(::PyPlotPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon] -supportedMarkers(::PyPlotPackage) = vcat(_allMarkers, Shape) -supportedScales(::PyPlotPackage) = [:identity, :ln, :log2, :log10] -subplotSupported(::PyPlotPackage) = true +supportedAxes(::GRPackage) = _allAxes +supportedTypes(::GRPackage) = [:none, :line, :path, :steppre, :steppost, :sticks, + :scatter, :hist2d, :hexbin, :hist, :density, :bar, + :hline, :vline, :contour, :path3d, :scatter3d, :surface, + :wireframe, :ohlc, :pie] +supportedStyles(::GRPackage) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] +supportedMarkers(::GRPackage) = vcat(_allMarkers, Shape) +supportedScales(::GRPackage) = [:identity, :log10] +subplotSupported(::GRPackage) = true # -------------------------------------------------------------------------------------- @@ -225,7 +304,7 @@ supportedArgs(::QwtPackage) = [ # :yflip, # :z, ] -supportedTypes(::QwtPackage) = [:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline] +supportedTypes(::QwtPackage) = [:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :hist2d, :hexbin, :hist, :bar, :hline, :vline] supportedMarkers(::QwtPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :star8, :hexagon] supportedScales(::QwtPackage) = [:identity, :log10] subplotSupported(::QwtPackage) = true @@ -284,7 +363,7 @@ supportedArgs(::UnicodePlotsPackage) = [ # :z, ] supportedAxes(::UnicodePlotsPackage) = [:auto, :left] -supportedTypes(::UnicodePlotsPackage) = [:none, :line, :path, :steppost, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline] +supportedTypes(::UnicodePlotsPackage) = [:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :hist2d, :hexbin, :hist, :bar, :hline, :vline] supportedStyles(::UnicodePlotsPackage) = [:auto, :solid] supportedMarkers(::UnicodePlotsPackage) = [:none, :auto, :ellipse] supportedScales(::UnicodePlotsPackage) = [:identity] @@ -413,10 +492,10 @@ supportedArgs(::BokehPackage) = [ # :legendfont, # :grid, # :surface, - # :nlevels, + # :levels, ] supportedAxes(::BokehPackage) = [:auto, :left] -supportedTypes(::BokehPackage) = [:none, :path, :scatter] #,:steppre, :steppost, :sticks, :heatmap, :hexbin, :hist, :bar, :hline, :vline, :contour] +supportedTypes(::BokehPackage) = [:none, :path, :scatter] #,:steppre, :steppost, :sticks, :hist2d, :hexbin, :hist, :bar, :hline, :vline, :contour] supportedStyles(::BokehPackage) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] supportedMarkers(::BokehPackage) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape) supportedScales(::BokehPackage) = [:identity, :ln] #, :ln, :log2, :log10, :asinh, :sqrt] @@ -479,12 +558,12 @@ supportedArgs(::PlotlyPackage) = [ :guidefont, :legendfont, :grid, - :nlevels, + :levels, ] supportedAxes(::PlotlyPackage) = [:auto, :left] supportedTypes(::PlotlyPackage) = [:none, :line, :path, :scatter, :steppre, :steppost, - :heatmap, :hist, :density, :bar, :contour, :surface, :path3d, :scatter3d, - :pie] #,, :sticks, :hexbin, :hline, :vline] + :hist2d, :hist, :density, :bar, :contour, :surface, :path3d, :scatter3d, + :pie, :heatmap] #,, :sticks, :hexbin, :hline, :vline] supportedStyles(::PlotlyPackage) = [:auto, :solid, :dash, :dot, :dashdot] supportedMarkers(::PlotlyPackage) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :pentagon, :hexagon, :octagon, :vline, :hline] #vcat(_allMarkers, Shape) @@ -493,6 +572,75 @@ subplotSupported(::PlotlyPackage) = true stringsSupported(::PlotlyPackage) = true +# -------------------------------------------------------------------------------------- + +supportedArgs(::PlotlyJSPackage) = [ + :annotation, + # :axis, + :background_color, + :color_palette, + :fillrange, + :fillcolor, + :fillalpha, + :foreground_color, + :group, + :label, + :layout, + :legend, + :linecolor, + :linestyle, + :linetype, + :linewidth, + :linealpha, + :markershape, + :markercolor, + :markersize, + :markeralpha, + :markerstrokewidth, + :markerstrokecolor, + :markerstrokestyle, + :n, + :nbins, + :nc, + :nr, + # :pos, + # :smooth, + :show, + :size, + :title, + :windowtitle, + :x, + :xlabel, + :xlims, + :xticks, + :y, + :ylabel, + :ylims, + # :yrightlabel, + :yticks, + :xscale, + :yscale, + :xflip, + :yflip, + :z, + :zcolor, + :tickfont, + :guidefont, + :legendfont, + :grid, + :levels, + ] +supportedAxes(::PlotlyJSPackage) = [:auto, :left] +supportedTypes(::PlotlyJSPackage) = [:none, :line, :path, :scatter, :steppre, :steppost, + :hist2d, :hist, :density, :bar, :contour, :surface, :path3d, :scatter3d, + :pie, :heatmap] #,, :sticks, :hexbin, :hline, :vline] +supportedStyles(::PlotlyJSPackage) = [:auto, :solid, :dash, :dot, :dashdot] +supportedMarkers(::PlotlyJSPackage) = [:none, :auto, :ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, + :pentagon, :hexagon, :octagon, :vline, :hline] #vcat(_allMarkers, Shape) +supportedScales(::PlotlyJSPackage) = [:identity, :log10] #, :ln, :log2, :log10, :asinh, :sqrt] +subplotSupported(::PlotlyJSPackage) = true +stringsSupported(::PlotlyJSPackage) = true + # -------------------------------------------------------------------------------------- supportedArgs(::GLVisualizePackage) = [ @@ -549,7 +697,7 @@ supportedArgs(::GLVisualizePackage) = [ # :legendfont, # :grid, # :surface - # :nlevels, + # :levels, ] supportedAxes(::GLVisualizePackage) = [:auto, :left] supportedTypes(::GLVisualizePackage) = [:surface] #, :path, :scatter ,:steppre, :steppost, :sticks, :heatmap, :hexbin, :hist, :bar, :hline, :vline, :contour] @@ -557,3 +705,69 @@ supportedStyles(::GLVisualizePackage) = [:auto, :solid] #, :dash, :dot, :dashdot supportedMarkers(::GLVisualizePackage) = [:none, :auto, :ellipse] #, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape) supportedScales(::GLVisualizePackage) = [:identity] #, :log, :log2, :log10, :asinh, :sqrt] subplotSupported(::GLVisualizePackage) = false + +# -------------------------------------------------------------------------------------- + +supportedArgs(::PGFPlotsPackage) = [ + # :annotation, + # :axis, + # :background_color, + # :color_palette, + # :fillrange, + # :fillcolor, + # :fillalpha, + # :foreground_color, + # :group, + # :label, + # :layout, + # :legend, + # :linecolor, + # :linestyle, + # :linetype, + # :linewidth, + # :linealpha, + # :markershape, + # :markercolor, + # :markersize, + # :markeralpha, + # :markerstrokewidth, + # :markerstrokecolor, + # :markerstrokestyle, + # :n, + # :nbins, + # :nc, + # :nr, + # :pos, + # :smooth, + # :show, + # :size, + # :title, + # :windowtitle, + # :x, + # :xlabel, + # :xlims, + # :xticks, + # :y, + # :ylabel, + # :ylims, + # :yrightlabel, + # :yticks, + # :xscale, + # :yscale, + # :xflip, + # :yflip, + # :z, + # :tickfont, + # :guidefont, + # :legendfont, + # :grid, + # :surface + # :levels, + ] +supportedAxes(::PGFPlotsPackage) = [:auto, :left] +supportedTypes(::PGFPlotsPackage) = [:contour] #, :path, :scatter ,:steppre, :steppost, :sticks, :hist2d, :hexbin, :hist, :bar, :hline, :vline, :contour] +supportedStyles(::PGFPlotsPackage) = [:auto, :solid] #, :dash, :dot, :dashdot, :dashdotdot] +supportedMarkers(::PGFPlotsPackage) = [:none, :auto, :ellipse] #, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5] #vcat(_allMarkers, Shape) +supportedScales(::PGFPlotsPackage) = [:identity] #, :log, :log2, :log10, :asinh, :sqrt] +subplotSupported(::PGFPlotsPackage) = false + diff --git a/src/backends/template.jl b/src/backends/template.jl index 450f264f..b8148da0 100644 --- a/src/backends/template.jl +++ b/src/backends/template.jl @@ -3,6 +3,15 @@ # [WEBSITE] +function _initialize_backend(::[PkgName]Package; kw...) + @eval begin + import [PkgName] + export [PkgName] + # TODO: other initialization that needs to be eval-ed + end + # TODO: other initialization +end + # --------------------------------------------------------------------------- function _create_plot(pkg::[PkgName]Package; kw...) @@ -43,13 +52,11 @@ end # accessors for x/y data function Base.getindex(plt::Plot{[PkgName]Package}, i::Int) - series = plt.o.lines[i] - series.x, series.y + # TODO: return a tuple of (x, y) vectors end function Base.setindex!(plt::Plot{[PkgName]Package}, xy::Tuple, i::Integer) - series = plt.o.lines[i] - series.x, series.y = xy + # TODO: set the plot data from the (x,y) tuple plt end diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl index a85b4ff5..b2da0c11 100644 --- a/src/backends/unicodeplots.jl +++ b/src/backends/unicodeplots.jl @@ -1,6 +1,13 @@ # https://github.com/Evizero/UnicodePlots.jl +function _initialize_backend(::UnicodePlotsPackage; kw...) + @eval begin + import UnicodePlots + export UnicodePlots + end +end + # ------------------------------- @@ -52,7 +59,7 @@ function rebuildUnicodePlot!(plt::Plot) # now use the ! functions to add to the plot for d in sargs - addUnicodeSeries!(o, d, iargs[:legend], xlim, ylim) + addUnicodeSeries!(o, d, iargs[:legend] != :none, xlim, ylim) end # save the object @@ -91,13 +98,13 @@ function addUnicodeSeries!(o, d::Dict, addlegend::Bool, xlim, ylim) else error("Linestyle $lt not supported by UnicodePlots") end - + # get the series data and label x, y = [collect(float(d[s])) for s in (:x, :y)] label = addlegend ? d[:label] : "" # if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide - color = d[:linecolor] in UnicodePlots.autoColors ? d[:linecolor] : :auto + color = d[:linecolor] in UnicodePlots.color_cycle ? d[:linecolor] : :auto # add the series func(o, x, y; color = color, name = label, style = stepstyle) diff --git a/src/backends/winston.jl b/src/backends/winston.jl index b9b71a98..f836c6b8 100644 --- a/src/backends/winston.jl +++ b/src/backends/winston.jl @@ -3,6 +3,14 @@ # credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation +function _initialize_backend(::WinstonPackage; kw...) + @eval begin + # ENV["WINSTON_OUTPUT"] = "gtk" + warn("Winston is no longer supported... many features will likely be broken.") + import Winston, Gtk + export Winston, Gtk + end +end # --------------------------------------------------------------------------- @@ -80,7 +88,7 @@ function _add_series(::WinstonPackage, plt::Plot; kw...) - ## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar + ## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :hist2d, :hexbin, :hist, :bar if d[:linetype] == :none Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)..., color=getColor(d[:markercolor]))) @@ -115,7 +123,7 @@ function _add_series(::WinstonPackage, plt::Plot; kw...) # elseif d[:linetype] == :dots # fn = Winston.XXX - # elseif d[:linetype] == :heatmap + # elseif d[:linetype] == :hist2d # fn = Winston.XXX # elseif d[:linetype] == :hexbin @@ -198,7 +206,7 @@ end # ---------------------------------------------------------------- function addWinstonLegend(plt::Plot, wplt) - if plt.plotargs[:legend] + if plt.plotargs[:legend] != :none Winston.legend(wplt, [sd[:label] for sd in plt.seriesargs]) end end diff --git a/src/colors.jl b/src/colors.jl index 4e5a9195..2028ebfb 100644 --- a/src/colors.jl +++ b/src/colors.jl @@ -72,33 +72,44 @@ const _testColors = [colorant"darkblue", colorant"blueviolet", colorant"darkcya "Continuous gradient between values. Wraps a list of bounding colors and the values they represent." immutable ColorGradient <: ColorScheme - colors::Vector{Colorant} - values::Vector{Float64} + colors::Vector + values::Vector - function ColorGradient{T<:Colorant,S<:Real}(cs::AVec{T}, vals::AVec{S} = 0:1; alpha = nothing) + function ColorGradient{S<:Real}(cs::AVec, vals::AVec{S} = linspace(0, 1, length(cs)); alpha = nothing) if length(cs) == length(vals) return new(convertColor(cs,alpha), collect(vals)) end - # otherwise interpolate evenly between the minval and maxval - minval, maxval = minimum(vals), maximum(vals) - vs = Float64[interpolate(minval, maxval, w) for w in linspace(0, 1, length(cs))] - new(convertColor(cs,alpha), vs) + # # otherwise interpolate evenly between the minval and maxval + # minval, maxval = minimum(vals), maximum(vals) + # vs = Float64[interpolate(minval, maxval, w) for w in linspace(0, 1, length(cs))] + # new(convertColor(cs,alpha), vs) + + # interpolate the colors for each value + vals = merge(linspace(0, 1, length(cs)), vals) + grad = ColorGradient(cs) + cs = [getColorZ(grad, z) for z in linspace(0, 1, length(vals))] + new(convertColor(cs, alpha), vals) end end # create a gradient from a symbol (blues, reds, etc) and vector of boundary values -function ColorGradient{T<:Real}(s::Symbol, vals::AVec{T} = 0:1; kw...) +function ColorGradient{T<:Real}(s::Symbol, vals::AVec{T} = 0:0; kw...) haskey(_gradients, s) || error("Invalid gradient symbol. Choose from: ", sort(collect(keys(_gradients)))) - - # if we passed in the right number of values, create the gradient directly cs = _gradients[s] + if vals == 0:0 + vals = linspace(0, 1, length(cs)) + end ColorGradient(cs, vals; kw...) end -function ColorGradient{T<:Real}(cs::AVec{Symbol}, vals::AVec{T} = 0:1; kw...) - ColorGradient(map(convertColor, cs), vals; kw...) -end +# function ColorGradient{T<:Real}(cs::AVec, vals::AVec{T} = linspace(0, 1, length(cs)); kw...) +# ColorGradient(map(convertColor, cs), vals; kw...) +# end + +# function ColorGradient(grad::ColorGradient; alpha = nothing) +# ColorGradient(convertColor(grad.colors, alpha), grad.values) +# end getColor(gradient::ColorGradient, idx::Int) = gradient.colors[mod1(idx, length(gradient.colors))] diff --git a/src/components.jl b/src/components.jl index f1b7114b..a7ffa742 100644 --- a/src/components.jl +++ b/src/components.jl @@ -131,6 +131,7 @@ immutable PlotText str::@compat(AbstractString) font::Font end +PlotText(str) = PlotText(string(str), font()) function text(str, args...) PlotText(string(str), font(args...)) @@ -157,7 +158,8 @@ function stroke(args...; alpha = nothing) for arg in args T = typeof(arg) - if arg in _allStyles + # if arg in _allStyles + if allStyles(arg) style = arg elseif T <: Colorant color = arg @@ -165,7 +167,11 @@ function stroke(args...; alpha = nothing) try color = parse(Colorant, string(arg)) end - elseif typeof(arg) <: Real + # elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg) + elseif allAlphas(arg) + alpha = arg + # elseif typeof(arg) <: Real + elseif allReals(arg) width = arg else warn("Unused stroke arg: $arg ($(typeof(arg)))") @@ -198,7 +204,11 @@ function brush(args...; alpha = nothing) try color = parse(Colorant, string(arg)) end - elseif typeof(arg) <: Real + # elseif trueOrAllTrue(a -> typeof(a) <: Real && a > 0 && a < 1, arg) + elseif allAlphas(arg) + alpha = arg + # elseif typeof(arg) <: Real + elseif allReals(arg) size = arg else warn("Unused brush arg: $arg ($(typeof(arg)))") @@ -231,6 +241,13 @@ end Surface(f::Function, x, y) = Surface(Float64[f(xi,yi) for xi in x, yi in y]) +Base.Array(surf::Surface) = surf.surf + +for f in (:length, :size) + @eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...) +end +Base.copy(surf::Surface) = Surface(copy(surf.surf)) + # ----------------------------------------------------------------------- type OHLC{T<:Real} @@ -239,3 +256,71 @@ type OHLC{T<:Real} low::T close::T end + + +# @require FixedSizeArrays begin + + export + P2, + P3, + BezierCurve, + curve_points, + directed_curve + + typealias P2 FixedSizeArrays.Vec{2,Float64} + typealias P3 FixedSizeArrays.Vec{3,Float64} + + type BezierCurve{T <: FixedSizeArrays.Vec} + control_points::Vector{T} + end + + function Base.call(bc::BezierCurve, t::Real) + p = zero(P2) + n = length(bc.control_points)-1 + for i in 0:n + p += bc.control_points[i+1] * binomial(n, i) * (1-t)^(n-i) * t^i + end + p + end + + Base.mean(x::Real, y::Real) = 0.5*(x+y) + Base.mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps) + + curve_points(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, linspace(range..., n)) + + # build a BezierCurve which leaves point p vertically upwards and arrives point q vertically upwards. + # may create a loop if necessary. Assumes the view is [0,1] + function directed_curve(p::P2, q::P2; xview = 0:1, yview = 0:1) + mn = mean(p, q) + diff = q - p + + minx, maxx = minimum(xview), maximum(xview) + miny, maxy = minimum(yview), maximum(yview) + diffpct = P2(diff[1] / (maxx - minx), + diff[2] / (maxy - miny)) + + # these points give the initial/final "rise" + # vertical_offset = P2(0, (maxy - miny) * max(0.03, min(abs(0.5diffpct[2]), 1.0))) + vertical_offset = P2(0, max(0.15, 0.5norm(diff))) + upper_control = p + vertical_offset + lower_control = q - vertical_offset + + # try to figure out when to loop around vs just connecting straight + # TODO: choose loop direction based on sign of p[1]?? + # x_close_together = abs(diffpct[1]) <= 0.05 + p_is_higher = diff[2] <= 0 + inside_control_points = if p_is_higher + # add curve points which will create a loop + sgn = mn[1] < 0.5 * (maxx + minx) ? -1 : 1 + inside_offset = P2(0.3 * (maxx - minx), 0) + additional_offset = P2(sgn * diff[1], 0) # make it even loopier + [upper_control + sgn * (inside_offset + max(0, additional_offset)), + lower_control + sgn * (inside_offset + max(0, -additional_offset))] + else + [] + end + + BezierCurve([p, upper_control, inside_control_points..., lower_control, q]) + end + +# end diff --git a/src/output.jl b/src/output.jl index afc98c25..ad8bb9f3 100644 --- a/src/output.jl +++ b/src/output.jl @@ -37,6 +37,15 @@ end ps(fn::@compat(AbstractString)) = ps(current(), fn) +function tex(plt::PlottingObject, fn::@compat(AbstractString)) + fn = addExtension(fn, "tex") + io = open(fn, "w") + writemime(io, MIME("application/x-tex"), plt) + close(io) +end +tex(fn::@compat(AbstractString)) = tex(current(), fn) + + # ---------------------------------------------------------------- @@ -45,6 +54,7 @@ ps(fn::@compat(AbstractString)) = ps(current(), fn) "svg" => svg, "pdf" => pdf, "ps" => ps, + "tex" => tex, ) function getExtension(fn::@compat(AbstractString)) @@ -102,3 +112,8 @@ gui(plt::PlottingObject = current()) = display(PlotsDisplay(), plt) # override the REPL display to open a gui window Base.display(::Base.REPL.REPLDisplay, ::MIME"text/plain", plt::PlottingObject) = gui(plt) + +# a backup for html... passes to svg +function Base.writemime(io::IO, ::MIME"text/html", plt::PlottingObject) + writemime(io, MIME("image/svg+xml"), plt) +end diff --git a/src/plot.jl b/src/plot.jl index 47b11a43..ef8d5da8 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -48,7 +48,7 @@ function plot(args...; kw...) preprocessArgs!(d) dumpdict(d, "After plot preprocessing") - plotargs = getPlotArgs(pkg, d, 1) + plotargs = merge(d, getPlotArgs(pkg, d, 1)) dumpdict(plotargs, "Plot args") plt = _create_plot(pkg; plotargs...) # create a new, blank plot @@ -186,12 +186,21 @@ updateDictWithMeta(d::Dict, plotargs::Dict, meta, isx::Bool) = nothing annotations(::@compat(Void)) = [] annotations{X,Y,V}(v::AVec{@compat(Tuple{X,Y,V})}) = v annotations{X,Y,V}(t::@compat(Tuple{X,Y,V})) = [t] +annotations(v::AVec{PlotText}) = v +annotations(v::AVec) = map(PlotText, v) annotations(anns) = error("Expecting a tuple (or vector of tuples) for annotations: ", "(x, y, annotation)\n got: $(typeof(anns))") function _add_annotations(plt::Plot, d::Dict) anns = annotations(get(d, :annotation, nothing)) if !isempty(anns) + + # if we just have a list of PlotText objects, then create (x,y,text) tuples + if typeof(anns) <: AVec{PlotText} + x, y = plt[plt.n] + anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] + end + _add_annotations(plt, anns) end end @@ -219,41 +228,50 @@ end typealias FuncOrFuncs @compat(Union{Function, AVec{Function}}) +all3D(d::Dict) = trueOrAllTrue(lt -> lt in (:contour, :heatmap, :surface, :wireframe), get(d, :linetype, :none)) + # missing -convertToAnyVector(v::@compat(Void); kw...) = Any[nothing], nothing +convertToAnyVector(v::@compat(Void), d::Dict) = Any[nothing], nothing # fixed number of blank series -convertToAnyVector(n::Integer; kw...) = Any[zeros(0) for i in 1:n], nothing +convertToAnyVector(n::Integer, d::Dict) = Any[zeros(0) for i in 1:n], nothing # numeric vector -convertToAnyVector{T<:Real}(v::AVec{T}; kw...) = Any[v], nothing +convertToAnyVector{T<:Real}(v::AVec{T}, d::Dict) = Any[v], nothing # string vector -convertToAnyVector{T<:@compat(AbstractString)}(v::AVec{T}; kw...) = Any[v], nothing +convertToAnyVector{T<:@compat(AbstractString)}(v::AVec{T}, d::Dict) = Any[v], nothing # numeric matrix -convertToAnyVector{T<:Real}(v::AMat{T}; kw...) = Any[v[:,i] for i in 1:size(v,2)], nothing +function convertToAnyVector{T<:Real}(v::AMat{T}, d::Dict) + if all3D(d) + Any[Surface(v)] + else + Any[v[:,i] for i in 1:size(v,2)] + end, nothing +end # function -convertToAnyVector(f::Function; kw...) = Any[f], nothing +convertToAnyVector(f::Function, d::Dict) = Any[f], nothing # surface -convertToAnyVector(s::Surface; kw...) = Any[s], nothing +convertToAnyVector(s::Surface, d::Dict) = Any[s], nothing # vector of OHLC -convertToAnyVector(v::AVec{OHLC}; kw...) = Any[v], nothing +convertToAnyVector(v::AVec{OHLC}, d::Dict) = Any[v], nothing # dates -convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}; kw...) = Any[dts], nothing +convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::Dict) = Any[dts], nothing # list of things (maybe other vectors, functions, or something else) -function convertToAnyVector(v::AVec; kw...) +function convertToAnyVector(v::AVec, d::Dict) if all(x -> typeof(x) <: Real, v) # all real numbers wrap the whole vector as one item Any[convert(Vector{Float64}, v)], nothing else # something else... treat each element as an item - Any[vi for vi in v], nothing + vcat(Any[convertToAnyVector(vi, d)[1] for vi in v]...), nothing + # Any[vi for vi in v], nothing end end @@ -262,7 +280,7 @@ end # in computeXandY, we take in any of the possible items, convert into proper x/y vectors, then return. # this is also where all the "set x to 1:length(y)" happens, and also where we assert on lengths. -computeX(x::@compat(Void), y) = 1:length(y) +computeX(x::@compat(Void), y) = 1:size(y,1) computeX(x, y) = copy(x) computeY(x, y::Function) = map(y, x) computeY(x, y) = copy(y) @@ -281,8 +299,9 @@ end # create n=max(mx,my) series arguments. the shorter list is cycled through # note: everything should flow through this function createKWargsList(plt::PlottingObject, x, y; kw...) - xs, xmeta = convertToAnyVector(x; kw...) - ys, ymeta = convertToAnyVector(y; kw...) + kwdict = Dict(kw) + xs, xmeta = convertToAnyVector(x, kwdict) + ys, ymeta = convertToAnyVector(y, kwdict) mx = length(xs) my = length(ys) @@ -290,7 +309,7 @@ function createKWargsList(plt::PlottingObject, x, y; kw...) for i in 1:max(mx, my) # try to set labels using ymeta - d = Dict(kw) + d = copy(kwdict) if !haskey(d, :label) && ymeta != nothing if isa(ymeta, Symbol) d[:label] = string(ymeta) @@ -307,13 +326,22 @@ function createKWargsList(plt::PlottingObject, x, y; kw...) dumpdict(d, "after getSeriesArgs") d[:x], d[:y] = computeXandY(xs[mod1(i,mx)], ys[mod1(i,my)]) + lt = d[:linetype] + if isa(d[:y], Surface) + if lt in (:contour, :heatmap, :surface, :wireframe) + z = d[:y] + d[:y] = 1:size(z,2) + d[:z] = z + end + end + if haskey(d, :idxfilter) d[:x] = d[:x][d[:idxfilter]] d[:y] = d[:y][d[:idxfilter]] end # for linetype `line`, need to sort by x values - if d[:linetype] == :line + if lt == :line # order by x indices = sortperm(d[:x]) d[:x] = d[:x][indices] @@ -321,6 +349,14 @@ function createKWargsList(plt::PlottingObject, x, y; kw...) d[:linetype] = :path end + # map functions to vectors + if isa(d[:zcolor], Function) + d[:zcolor] = map(d[:zcolor], d[:x]) + end + if isa(d[:fillrange], Function) + d[:fillrange] = map(d[:fillrange], d[:x]) + end + # cleanup those fields that were used only for generating kw args for k in (:idxfilter, :numUncounted, :dataframe) delete!(d, k) @@ -361,6 +397,16 @@ function createKWargsList(plt::PlottingObject, x::AVec, y::AVec, zvec::AVec; kw. createKWargsList(plt, x, y; z=zvec, d...) end +function createKWargsList{T<:Real}(plt::PlottingObject, z::AMat{T}; kw...) + d = Dict(kw) + if all3D(d) + n,m = size(z) + createKWargsList(plt, 1:n, 1:m, z; kw...) + else + createKWargsList(plt, nothing, z; kw...) + end +end + # contours or surfaces... function grid function createKWargsList(plt::PlottingObject, x::AVec, y::AVec, zf::Function; kw...) # only allow sorted x/y for now @@ -378,14 +424,15 @@ function createKWargsList{T<:Real}(plt::PlottingObject, x::AVec, y::AVec, zmat:: @assert x == sort(x) @assert y == sort(y) @assert size(zmat) == (length(x), length(y)) - surf = Surface(convert(Matrix{Float64}, zmat)) + # surf = Surface(convert(Matrix{Float64}, zmat)) # surf = Array(Any,1,1) # surf[1,1] = convert(Matrix{Float64}, zmat) d = Dict(kw) - if !(get(d, :linetype, :none) in (:contour, :surface, :wireframe)) + d[:z] = Surface(convert(Matrix{Float64}, zmat)) + if !(get(d, :linetype, :none) in (:contour, :heatmap, :surface, :wireframe)) d[:linetype] = :contour end - createKWargsList(plt, x, y; d..., z = surf) + createKWargsList(plt, x, y; d...) #, z = surf) end # contours or surfaces... general x, y grid @@ -394,7 +441,12 @@ function createKWargsList{T<:Real}(plt::PlottingObject, x::AMat{T}, y::AMat{T}, surf = Surface(convert(Matrix{Float64}, zmat)) # surf = Array(Any,1,1) # surf[1,1] = convert(Matrix{Float64}, zmat) - createKWargsList(plt, x, y; kw..., surface = surf, linetype = :contour) + d = Dict(kw) + d[:z] = Surface(convert(Matrix{Float64}, zmat)) + if !(get(d, :linetype, :none) in (:contour, :heatmap, :surface, :wireframe)) + d[:linetype] = :contour + end + createKWargsList(plt, Any[x], Any[y]; d...) #kw..., z = surf, linetype = :contour) end @@ -407,7 +459,7 @@ function createKWargsList(plt::PlottingObject, x::AVec, y::AVec, surf::Surface; end function createKWargsList(plt::PlottingObject, f::FuncOrFuncs; kw...) - error("Can't pass a Function or Vector{Function} for y without also passing x") + createKWargsList(plt, f, xmin(plt), xmax(plt); kw...) end # list of functions @@ -431,6 +483,19 @@ createKWargsList{T<:Real}(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, createKWargsList{T<:Real}(plt::PlottingObject, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs; kw...) = createKWargsList(plt, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u); kw...) createKWargsList(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Real, umax::Real, numPoints::Int = 1000; kw...) = createKWargsList(plt, fx, fy, linspace(umin, umax, numPoints); kw...) +# special handling... 3D parametric function(s) +createKWargsList{T<:Real}(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, u::AVec{T}; kw...) = createKWargsList(plt, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u); kw...) +createKWargsList{T<:Real}(plt::PlottingObject, u::AVec{T}, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs; kw...) = createKWargsList(plt, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u); kw...) +createKWargsList(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, fz::FuncOrFuncs, umin::Real, umax::Real, numPoints::Int = 1000; kw...) = createKWargsList(plt, fx, fy, fz, linspace(umin, umax, numPoints); kw...) + +# (x,y) tuples +function createKWargsList{R1<:Real,R2<:Real}(plt::PlottingObject, xy::AVec{Tuple{R1,R2}}; kw...) + createKWargsList(plt, unzip(xy)...; kw...) +end +function createKWargsList{R1<:Real,R2<:Real}(plt::PlottingObject, xy::Tuple{R1,R2}; kw...) + createKWargsList(plt, [xy[1]], [xy[2]]; kw...) +end + # special handling... no args... 1 series @@ -451,16 +516,34 @@ end # -------------------------------------------------------------------- -"For DataFrame support. Imports DataFrames and defines the necessary methods which support them." -function dataframes() - @eval import DataFrames - @eval function createKWargsList(plt::PlottingObject, df::DataFrames.DataFrame, args...; kw...) +# @require FixedSizeArrays begin + + unzip{T}(x::AVec{FixedSizeArrays.Vec{2,T}}) = T[xi[1] for xi in x], T[xi[2] for xi in x] + unzip{T}(x::FixedSizeArrays.Vec{2,T}) = T[x[1]], T[x[2]] + + function createKWargsList{T<:Real}(plt::PlottingObject, xy::AVec{FixedSizeArrays.Vec{2,T}}; kw...) + createKWargsList(plt, unzip(xy)...; kw...) + end + + function createKWargsList{T<:Real}(plt::PlottingObject, xy::FixedSizeArrays.Vec{2,T}; kw...) + createKWargsList(plt, [xy[1]], [xy[2]]; kw...) + end + +# end + +# -------------------------------------------------------------------- + +# For DataFrame support. Imports DataFrames and defines the necessary methods which support them. + +@require DataFrames begin + + function createKWargsList(plt::PlottingObject, df::DataFrames.AbstractDataFrame, args...; kw...) createKWargsList(plt, args...; kw..., dataframe = df) end # expecting the column name of a dataframe that was passed in... anything else should error - @eval function extractGroupArgs(s::Symbol, df::DataFrames.DataFrame, args...) + function extractGroupArgs(s::Symbol, df::DataFrames.AbstractDataFrame, args...) if haskey(df, s) return extractGroupArgs(df[s]) else @@ -468,18 +551,23 @@ function dataframes() end end - @eval function getDataFrameFromKW(; kw...) - for (k,v) in kw - if k == :dataframe - return v - end + function getDataFrameFromKW(d::Dict) + # for (k,v) in kw + # if k == :dataframe + # return v + # end + # end + get(d, :dataframe) do + error("Missing dataframe argument!") end - error("Missing dataframe argument in arguments!") end # the conversion functions for when we pass symbols or vectors of symbols to reference dataframes - @eval convertToAnyVector(s::Symbol; kw...) = Any[getDataFrameFromKW(;kw...)[s]], s - @eval convertToAnyVector(v::AVec{Symbol}; kw...) = (df = getDataFrameFromKW(;kw...); Any[df[s] for s in v]), v + # convertToAnyVector(s::Symbol; kw...) = Any[getDataFrameFromKW(;kw...)[s]], s + # convertToAnyVector(v::AVec{Symbol}; kw...) = (df = getDataFrameFromKW(;kw...); Any[df[s] for s in v]), v + convertToAnyVector(s::Symbol, d::Dict) = Any[getDataFrameFromKW(d)[s]], s + convertToAnyVector(v::AVec{Symbol}, d::Dict) = (df = getDataFrameFromKW(d); Any[df[s] for s in v]), v + end diff --git a/src/plotter2.jl b/src/plotter2.jl new file mode 100644 index 00000000..ac142ba6 --- /dev/null +++ b/src/plotter2.jl @@ -0,0 +1,121 @@ + +immutable NoPackage <: PlottingPackage end + +const _backendType = Dict{Symbol, DataType}(:none => NoPackage) +const _backendSymbol = Dict{DataType, Symbol}(NoPackage => :none) +const _backends = Symbol[] +const _initialized_backends = Set{Symbol}() + +backends() = _backends +backend_name() = CURRENT_BACKEND.sym +_backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym") + +macro init_plotting_pkg(s) + str = lowercase(string(s)) + sym = symbol(str) + T = symbol(string(s) * "Package") + esc(quote + immutable $T <: PlottingPackage end + export $sym + $sym(; kw...) = (default(; kw...); backend(symbol($str))) + backend_name(::$T) = symbol($str) + push!(_backends, symbol($str)) + _backendType[symbol($str)] = $T + _backendSymbol[$T] = symbol($str) + include("backends/" * $str * ".jl") + end) +end + +@init_plotting_pkg Immerse +@init_plotting_pkg Gadfly +@init_plotting_pkg PyPlot +@init_plotting_pkg Qwt +@init_plotting_pkg UnicodePlots +@init_plotting_pkg Winston +@init_plotting_pkg Bokeh +@init_plotting_pkg Plotly +@init_plotting_pkg PlotlyJS +@init_plotting_pkg GR +@init_plotting_pkg GLVisualize +@init_plotting_pkg PGFPlots + +include("backends/web.jl") +include("backends/supported.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") +_update_plot(pkg::PlottingPackage, plt::Plot, d::Dict) = error("_update_plot($pkg, plt, d) is not implemented") +_update_plot_pos_size{P<:PlottingPackage}(plt::PlottingObject{P}, d::Dict) = nothing +subplot(pkg::PlottingPackage; kw...) = error("subplot($pkg; kw...) is not implemented") +subplot!(pkg::PlottingPackage, subplt::Subplot; kw...) = error("subplot!($pkg, subplt; kw...) is not implemented") + +# --------------------------------------------------------- + + +type CurrentBackend + sym::Symbol + pkg::PlottingPackage +end +CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym)) + +# --------------------------------------------------------- + +function pickDefaultBackend() + for pkgstr in ("PyPlot", "Immerse", "Qwt", "Gadfly", "GR", "UnicodePlots", "Bokeh", "GLVisualize") + if Pkg.installed(pkgstr) != nothing + return backend(symbol(lowercase(pkgstr))) + end + end + + # the default if nothing else is installed + backend(:plotly) +end + + +# --------------------------------------------------------- + +""" +Returns the current plotting package name. Initializes package on first call. +""" +function backend() + + global CURRENT_BACKEND + if CURRENT_BACKEND.sym == :none + pickDefaultBackend() + end + + sym = CURRENT_BACKEND.sym + if !(sym in _initialized_backends) + + # initialize + println("[Plots.jl] Initializing backend: ", sym) + + inst = _backend_instance(sym) + try + _initialize_backend(inst) + catch err + warn("Couldn't initialize $sym. (might need to install it?)") + rethrow(err) + end + + push!(_initialized_backends, sym) + + end + CURRENT_BACKEND.pkg +end + +""" +Set the plot backend. +""" +function backend(pkg::PlottingPackage) + CURRENT_BACKEND.sym = backend_name(pkg) + CURRENT_BACKEND.pkg = pkg +end + +function backend(modname::Symbol) + CURRENT_BACKEND.sym = modname + CURRENT_BACKEND.pkg = _backend_instance(modname) +end diff --git a/src/recipes.jl b/src/recipes.jl index 44ba5d85..c133e813 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -125,8 +125,15 @@ end "Sparsity plot... heatmap of non-zero values of a matrix" -function spy{T<:Real}(y::AMat{T}; kw...) - I,J,V = findnz(y) - heatmap(J, I; leg=false, yflip=true, nbins=size(y), kw...) +function spy{T<:Real}(z::AMat{T}; kw...) + # I,J,V = findnz(z) + # heatmap(J, I; leg=false, yflip=true, kw...) + heatmap(map(zi->float(zi!=0), z); leg=false, yflip=true, kw...) end +"Adds a+bx... straight line over the current plot" +function abline!(plt::Plot, a, b; kw...) + plot!(plt, [extrema(plt)...], x -> b + a*x; kw...) +end + +abline!(args...; kw...) = abline!(current(), args...; kw...) \ No newline at end of file diff --git a/src/subplot.jl b/src/subplot.jl index dee0a7cd..07b56500 100644 --- a/src/subplot.jl +++ b/src/subplot.jl @@ -135,7 +135,7 @@ convertSeriesIndex(subplt::Subplot, n::Int) = ceil(Int, n / subplt.p) function validateSubplotSupported() if !subplotSupported() - error(CURRENT_BACKEND.sym, " does not support the subplot/subplot! commands at this time. Try one of: ", join(filter(pkg->subplotSupported(backendInstance(pkg)), backends()),", ")) + error(CURRENT_BACKEND.sym, " does not support the subplot/subplot! commands at this time. Try one of: ", join(filter(pkg->subplotSupported(_backend_instance(pkg)), backends()),", ")) end end diff --git a/src/utils.jl b/src/utils.jl index 167910b4..3bdd2c2f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -174,11 +174,20 @@ function replaceAliases!(d::Dict, aliases::Dict) end createSegments(z) = collect(repmat(z',2,1))[2:end] + Base.first(c::Colorant) = c +Base.first(x::Symbol) = x sortedkeys(d::Dict) = sort(collect(keys(d))) +"create an (n+1) list of the outsides of heatmap rectangles" +function heatmap_edges(v::AVec) + vmin, vmax = extrema(v) + extra = 0.5 * (vmax-vmin) / (length(v)-1) + vcat(vmin-extra, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra) +end + function fakedata(sz...) y = zeros(sz...) @@ -190,9 +199,21 @@ end isijulia() = isdefined(Main, :IJulia) && Main.IJulia.inited +istuple(::Tuple) = true +istuple(::Any) = false +isvector(::AVec) = true +isvector(::Any) = false +ismatrix(::AMat) = true +ismatrix(::Any) = false +isscalar(::Real) = true +isscalar(::Any) = false + + + # ticksType{T<:Real,S<:Real}(ticks::@compat(Tuple{T,S})) = :limits ticksType{T<:Real}(ticks::AVec{T}) = :ticks +ticksType{T<:AbstractString}(ticks::AVec{T}) = :labels ticksType{T<:AVec,S<:AVec}(ticks::@compat(Tuple{T,S})) = :ticks_and_labels ticksType(ticks) = :invalid @@ -204,9 +225,28 @@ limsType(lims) = :invalid Base.convert{T<:Real}(::Type{Vector{T}}, rng::Range{T}) = T[x for x in rng] Base.convert{T<:Real,S<:Real}(::Type{Vector{T}}, rng::Range{S}) = T[x for x in rng] +Base.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b))) # --------------------------------------------------------------- +wraptuple(x::@compat(Tuple)) = x +wraptuple(x) = (x,) + +trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x) +trueOrAllTrue(f::Function, x) = f(x) + +allLineTypes(arg) = trueOrAllTrue(a -> get(_typeAliases, a, a) in _allTypes, arg) +allStyles(arg) = trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg) +allShapes(arg) = trueOrAllTrue(a -> get(_markerAliases, a, a) in _allMarkers, arg) || + trueOrAllTrue(a -> isa(a, Shape), arg) +allAlphas(arg) = trueOrAllTrue(a -> (typeof(a) <: Real && a > 0 && a < 1) || + (typeof(a) <: AbstractFloat && (a == zero(typeof(a)) || a == one(typeof(a)))), arg) +allReals(arg) = trueOrAllTrue(a -> typeof(a) <: Real, arg) +allFunctions(arg) = trueOrAllTrue(a -> isa(a, Function), arg) + +# --------------------------------------------------------------- + + """ Allows temporary setting of backend and defaults for Plots. Settings apply only for the `do` block. Example: ``` @@ -226,10 +266,13 @@ function with(f::Function, args...; kw...) end # save the backend + if CURRENT_BACKEND.sym == :none + pickDefaultBackend() + end oldbackend = CURRENT_BACKEND.sym for arg in args - + # change backend? if arg in backends() backend(arg) @@ -399,15 +442,15 @@ function supportGraph(allvals, func) y = ASCIIString[] for val in vals for b in bs - supported = func(Plots.backendInstance(b)) + supported = func(Plots._backend_instance(b)) if val in supported push!(x, string(b)) push!(y, string(val)) end - end + end end n = length(vals) - + scatter(x,y, m=:rect, ms=10, @@ -428,7 +471,7 @@ function dumpSupportGraphs() for func in (supportGraphArgs, supportGraphTypes, supportGraphStyles, supportGraphMarkers, supportGraphScales, supportGraphAxes) plt = func() - png(IMG_DIR * "/supported/$(string(func))") + png(joinpath(Pkg.dir("ExamplePlots"), "docs", "examples", "img", "supported", "$(string(func))")) end end @@ -448,3 +491,11 @@ mm2inch(mm::Real) = float(mm / MM_PER_INCH) px2mm(px::Real) = float(px * MM_PER_PX) mm2px(mm::Real) = float(px / MM_PER_PX) + +"Smallest x in plot" +xmin(plt::Plot) = minimum([minimum(d[:x]) for d in plt.seriesargs]) +"Largest x in plot" +xmax(plt::Plot) = maximum([maximum(d[:x]) for d in plt.seriesargs]) + +"Extrema of x-values in plot" +Base.extrema(plt::Plot) = (xmin(plt), xmax(plt)) \ No newline at end of file diff --git a/test/REQUIRE b/test/REQUIRE index 9ad44a64..a2997f73 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -2,9 +2,11 @@ julia 0.4 Colors Reexport +Requires FactCheck Gadfly Images ImageMagick PyPlot @osx QuartzImageIO +GR diff --git a/test/imgcomp.jl b/test/imgcomp.jl index e61e7523..e898e174 100644 --- a/test/imgcomp.jl +++ b/test/imgcomp.jl @@ -1,11 +1,4 @@ -# # include this first to help with crashing?? -# try -# @eval using Gtk -# catch err -# warn("Gtk not loaded. err: $err") -# end - using VisualRegressionTests using ExamplePlots @@ -17,53 +10,10 @@ try info("Matplotlib version: $(PyPlot.matplotlib[:__version__])") end -# include("../docs/example_generation.jl") - using Plots, FactCheck -# import Images, ImageMagick -# if !isdefined(ImageMagick, :init_deps) -# function ImageMagick.init_deps() -# ccall((:MagickWandGenesis,libwand), Void, ()) -# end -# end - -# function makeImageWidget(fn) -# img = Gtk.GtkImageLeaf(fn) -# vbox = Gtk.GtkBoxLeaf(:v) -# push!(vbox, Gtk.GtkLabelLeaf(fn)) -# push!(vbox, img) -# show(img) -# vbox -# end - -# function replaceReferenceImage(tmpfn, reffn) -# cmd = `cp $tmpfn $reffn` -# run(cmd) -# info("Replaced reference image with: $cmd") -# end - -# "Show a Gtk popup with both images and a confirmation whether we should replace the new image with the old one" -# function compareToReferenceImage(tmpfn, reffn) - -# # add the images -# imgbox = Gtk.GtkBoxLeaf(:h) -# push!(imgbox, makeImageWidget(tmpfn)) -# push!(imgbox, makeImageWidget(reffn)) - -# win = Gtk.GtkWindowLeaf("Should we make this the new reference image?") -# push!(win, Gtk.GtkFrameLeaf(imgbox)) - -# showall(win) - -# # now ask the question -# if Gtk.ask_dialog("Should we make this the new reference image?", "No", "Yes") -# replaceReferenceImage(tmpfn, reffn) -# end - -# destroy(win) -# end +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 @@ -73,7 +23,7 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is # first Plots._debugMode.on = debug - example = ExamplePlots.examples[idx] + example = ExamplePlots._examples[idx] info("Testing plot: $pkg:$idx:$(example.header)") backend(pkg) backend() @@ -81,24 +31,15 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is # ensure consistent results srand(1234) + # reference image directory setup + refdir = joinpath(Pkg.dir("ExamplePlots"), "test", "refimg", string(pkg)) + # test function func = (fn, idx) -> begin map(eval, example.exprs) png(fn) end - # run the example - # map(eval, PlotExamples.examples[idx].exprs) - - # # save the png - # tmpfn = tempname() * ".png" - # png(tmpfn) - - # # load the saved png - # tmpimg = Images.load(tmpfn) - - # reference image directory setup - refdir = joinpath(Pkg.dir("Plots"), "test", "refimg", string(pkg)) try run(`mkdir -p $refdir`) catch err @@ -109,47 +50,18 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is # the test vtest = VisualTest(func, reffn, idx) test_images(vtest, popup=popup, sigma=sigma, eps=eps) - - # try - - # # info("Comparing $tmpfn to reference $reffn") - - # # load the reference image - # refimg = Images.load(reffn) - - # # run the comparison test... a difference will throw an error - # # NOTE: sigma is a 2-length vector with x/y values for the number of pixels - # # to blur together when comparing images - # diffpct = Images.test_approx_eq_sigma_eps(tmpimg, refimg, sigma, eps) - - # # we passed! - # info("Reference image $reffn matches. Difference: $diffpct") - # return true - - # catch err - # warn("Image did not match reference image $reffn. err: $err") - # # showerror(Base.STDERR, err) - - # if isinteractive() - - # # if we're in interactive mode, open a popup and give us a chance to examine the images - # warn("Should we make this the new reference image?") - # compareToReferenceImage(tmpfn, reffn) - # # println("exited") - # return - - # else - - # # if we rejected the image, or if we're in automated tests, throw the error - # rethrow(err) - # end - - # end end -function image_comparison_facts(pkg::Symbol; skip = [], debug = false, sigma = [1,1], eps = 1e-2) - for i in 1:length(ExamplePlots.examples) +function image_comparison_facts(pkg::Symbol; + skip = [], # skip these examples (int index) + only = nothing, # limit to these examples (int index) + debug = false, # print debug information? + sigma = [1,1], # number of pixels to "blur" + eps = 1e-2) # acceptable error (percent) + for i in 1:length(ExamplePlots._examples) i in skip && continue - @fact image_comparison_tests(pkg, i, debug=debug, sigma=sigma, eps=eps) |> success --> true + if only == nothing || i in only + @fact image_comparison_tests(pkg, i, debug=debug, sigma=sigma, eps=eps) |> success --> true + end end end diff --git a/test/refimg/gadfly/ref13.png b/test/refimg/gadfly/ref13.png deleted file mode 100644 index a3d1aecc..00000000 Binary files a/test/refimg/gadfly/ref13.png and /dev/null differ diff --git a/test/refimg/gadfly/ref15.png b/test/refimg/gadfly/ref15.png deleted file mode 100644 index 785bea7a..00000000 Binary files a/test/refimg/gadfly/ref15.png and /dev/null differ diff --git a/test/refimg/pyplot/ref11.png b/test/refimg/pyplot/ref11.png deleted file mode 100644 index b6027f37..00000000 Binary files a/test/refimg/pyplot/ref11.png and /dev/null differ diff --git a/test/refimg/pyplot/ref13.png b/test/refimg/pyplot/ref13.png deleted file mode 100644 index 4b80aa7f..00000000 Binary files a/test/refimg/pyplot/ref13.png and /dev/null differ diff --git a/test/runtests.jl b/test/runtests.jl index 14e879c0..3c582d6c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,152 +1,37 @@ module PlotsTests - -# # don't let pyplot use a gui... it'll crash -# # note: Agg will set gui -> :none in PyPlot -# ENV["MPLBACKEND"] = "Agg" -# try -# @eval import PyPlot -# catch err -# warn("Couldn't import PyPlot: $err") -# end - - -# using Plots -# using FactCheck - -# # note: wrap first include in a try block because of the ImageMagick init_deps bug -# try -# include("imgcomp.jl") -# end include("imgcomp.jl") # don't actually show the plots srand(1234) default(show=false) - -# note: we wrap in a try block so that the tests only run if we have the backend installed -# try - # Pkg.installed("Gadfly") - # gadfly() - # backend() - -img_eps = 2e-2 +img_eps = 5e-2 facts("Gadfly") do @fact gadfly() --> Plots.GadflyPackage() @fact backend() --> Plots.GadflyPackage() @fact typeof(plot(1:10)) --> Plots.Plot{Plots.GadflyPackage} - - # plot(x::AVec, y::AVec; kw...) # one line (will assert length(x) == length(y)) @fact plot(Int[1,2,3], rand(3)) --> not(nothing) - # @fact_throws plot(1:5, 1:4) - - # plot(x::AVec, y::AMat; kw...) # multiple lines (one per column of x), all sharing x (will assert length(x) == size(y,1)) @fact plot(sort(rand(10)), rand(Int, 10, 3)) --> not(nothing) - # @fact_throws(plot!(rand(10), rand(9,2))) - - # plot(x::AMat, y::AMat; kw...) # multiple lines (one per column of x/y... will assert size(x) == size(y)) @fact plot!(rand(10,3), rand(10,3)) --> not(nothing) - image_comparison_facts(:gadfly, skip=[4,19,23,24], eps=img_eps) + image_comparison_facts(:gadfly, skip=[4,6,19,23,24], eps=img_eps) end - facts("PyPlot") do @fact pyplot() --> Plots.PyPlotPackage() @fact backend() --> Plots.PyPlotPackage() - image_comparison_facts(:pyplot, skip=[19,21,23], eps=img_eps) + + image_comparison_facts(:pyplot, skip=[4,10,13,19,21,23], eps=img_eps) end +facts("GR") do + @fact gr() --> Plots.GRPackage() + @fact backend() --> Plots.GRPackage() -# catch err -# warn("Skipped Gadfly due to: ", string(err)) -# end - -# # note: we wrap in a try block so that the tests only run if we have the backend installed -# try -# Pkg.installed("Qwt") -# qwt() -# backend() -# facts("Qwt") do -# @fact backend(:qwt) --> Plots.QwtPackage() -# @fact backend() --> Plots.QwtPackage() -# @fact typeof(plot(1:10)) --> Plots.Plot{Plots.QwtPackage} - -# # plot(y::AVec; kw...) # one line... x = 1:length(y) -# @fact plot(1:10) --> not(nothing) -# @fact length(current().o.lines) --> 1 - -# # plot(x::AVec, f::Function; kw...) # one line, y = f(x) -# @fact plot(1:10, sin) --> not(nothing) -# @fact current().o.lines[1].y --> sin(collect(1:10)) - -# # plot(x::AMat, f::Function; kw...) # multiple lines, yᵢⱼ = f(xᵢⱼ) -# @fact plot(rand(10,2), sin) --> not(nothing) -# @fact length(current().o.lines) --> 2 - -# # plot(y::AMat; kw...) # multiple lines (one per column of x), all sharing x = 1:size(y,1) -# @fact plot!(rand(10,2)) --> not(nothing) -# @fact length(current().o.lines) --> 4 - -# # plot(x::AVec, fs::AVec{Function}; kw...) # multiple lines, yᵢⱼ = fⱼ(xᵢ) -# @fact plot(1:10, Function[sin,cos]) --> not(nothing) -# @fact current().o.lines[1].y --> sin(collect(1:10)) -# @fact current().o.lines[2].y --> cos(collect(1:10)) - -# # plot(y::AVec{AVec}; kw...) # multiple lines, each with x = 1:length(y[i]) -# @fact plot([11:20 ; rand(10)]) --> not(nothing) -# @fact current().o.lines[1].x[4] --> 4 -# @fact current().o.lines[1].y[4] --> 14 -# end -# catch err -# warn("Skipped Qwt due to: ", string(err)) -# end - -# try - # Pkg.installed("PyPlot") - # pyplot() - # backend() - # facts("PyPlot") do - # @fact backend(:pyplot) --> Plots.PyPlotPackage() - # @fact backend() --> Plots.PyPlotPackage() - # @fact typeof(plot(1:10)) --> Plots.Plot{Plots.PyPlotPackage} - - # # image_comparison_facts(:pyplot, skip=[19]) - # end -# catch err -# warn("Skipped PyPlot due to: ", string(err)) -# end - - -# try -# Pkg.installed("UnicodePlots") -# unicodeplots() -# backend() -# facts("UnicodePlots") do -# @fact backend(:unicodeplots) --> Plots.UnicodePlotsPackage() -# @fact backend() --> Plots.UnicodePlotsPackage() -# @fact typeof(plot(1:10)) --> Plots.Plot{Plots.UnicodePlotsPackage} -# end -# catch err -# warn("Skipped UnicodePlots due to: ", string(err)) -# end - - -# try -# Pkg.installed("Winston") -# winston() -# backend() -# facts("Winston") do -# @fact backend(:winston) --> Plots.WinstonPackage() -# @fact backend() --> Plots.WinstonPackage() -# @fact typeof(plot(1:10)) --> Plots.Plot{Plots.WinstonPackage} -# end -# catch err -# warn("Skipped Winston due to: ", string(err)) -# end - + # image_comparison_facts(:gr, only=[1], eps=img_eps) +end FactCheck.exitstatus() end # module