diff --git a/docs/readme_template.md b/docs/readme_template.md index e69de29b..ce1dd6b9 100644 --- a/docs/readme_template.md +++ b/docs/readme_template.md @@ -0,0 +1,273 @@ +# Plots + +[![Build Status](https://travis-ci.org/tbreloff/Plots.jl.svg?branch=master)](https://travis-ci.org/tbreloff/Plots.jl) + +#### Author: Thomas Breloff (@tbreloff) + +Plots is a plotting interface and wrapper for several plotting packages. My goals with the package are: + +- Simple. The interface should be intuitive enough that someone coming from Matlab, Python, etc can immediately start generating complex plots without reading volumes of documentation. +- Automatic (if you want). There should be smart defaults for the most common functionality, and simple, high-level ways to override complex functionality. +- Flexible. You should be able to produce your favorite plot in your favorite package, but quicker and simpler. +- Consistent. Don't commit to one graphics package. One command will switch your backend, and the exact same plotting commands will work with a very different underlying backend. + +Please add wishlist items, bugs, or any other comments/questions to the issues list. + +## Examples for each implemented backend: + +- [Qwt.jl](docs/qwt_examples.md) +- [Gadfly.jl](docs/gadfly_examples.md) +- [UnicodePlots.jl](docs/unicodeplots_examples.md) +- [PyPlot.jl](docs/pyplot_examples.md) +- [Immerse.jl](docs/immerse_examples.md) +- [Winston.jl](docs/winston_examples.md) + +## Installation + +First, add the package + +```julia +Pkg.add("Plots") +``` + +then get any plotting packages you need (obviously, you should get at least one backend): + +```julia +Pkg.add("Gadfly") +Pkg.add("Immerse") +Pkg.add("UnicodePlots") +Pkg.add("PyPlot") # requires python and matplotlib +Pkg.clone("https://github.com/tbreloff/Qwt.jl.git") # requires pyqt and pyqwt +Pkg.add("Winston") +``` + +## Use + +Load it in. The underlying plotting backends are not imported until `plotter()` 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, then save a png: + +```julia +# switch to Gadfly as a backend +gadfly!() + +# This will bring up a browser window with the plot. Add a semicolon at the end to skip display. +plot(rand(10,2); marker = :rect, markersize = [10,30], style = :auto) + +# save it as a PNG +savepng(Plots.IMG_DIR * "gadfly1.png") +``` + +which saves: + +![gadfly_plt](img/gadfly1.png) + +See the examples pages for lots of examples of plots, and what those commands produce for each supported backend. + +## API + +Call `plotter!(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 `currentPlot` +plot!(args...; kw...) # adds to the `currentPlot` +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 +- (TODO) DataFrames with column symbols + +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 (TODO), a Symbol/Symbols in place of x/y will map to +the relevant column(s) and also automatically set the associated legend label. + +Here are some example usages... remember you can always use `plot!` to update an existing plot, and that, unless specified, you will update the `currentPlot()`. + +```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, [rand(10), sin]) # plot 2 series, y = rand(10) for the first, y = sin(x) for the second... x = 1:10 for both +``` + +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) +``` + +Some keyword arguments you can set: + +[[KEYWORD_ARGS_TABLE]] + +[[REPLACE_START]] +``` +axis # :left or :right +color # can be a string ("red") or a symbol (:red) or a ColorsTypes.jl + # Colorant (RGB(1,0,0)) or :auto (which lets the package pick) +label # string or symbol, applies to that line, may go in a legend +width # width of a line +linetype # :line, :step, :stepinverted, :sticks, :scatter, :none, :heatmap, :hexbin, :hist, :bar +linestyle # :solid, :dash, :dot, :dashdot, :dashdotdot +marker # :none, :ellipse, :rect, :diamond, :utriangle, :dtriangle, + # :cross, :xcross, :star1, :star2, :hexagon +markercolor # same choices as `color`, or :match will set the color to be the same as `color` +markersize # size of the marker +nbins # number of bins for heatmap/hexbin and histograms +heatmap_c # color cutoffs for Qwt heatmaps +fillto # fillto value for area plots +title # string or symbol, title of the plot +xlabel # string or symbol, label on the bottom (x) axis +ylabel # string or symbol, label on the left (y) axis +yrightlabel # string or symbol, label on the right (y) axis +reg # true or false, add a regression line for each line +size # (Int,Int), resize the enclosing window +pos # (Int,Int), move the enclosing window to this position +windowtitle # string or symbol, set the title of the enclosing windowtitle +screen # Integer, move enclosing window to this screen number (for multiscreen desktops) +``` + +Note that not every backend supports all options. + +If you don't include a keyword argument, these are the defaults: + +``` +axis = :left +color = :auto +label = automatically generated (y1, y2, ...., or y1 (R), y2 (R) for the right axis) +width = 1 +linetype = :line +linestype = :solid +marker = :none +markercolor = :match +markersize = 3 +nbins = 100 +heatmap_c = (0.15, 0.5) +fillto = nothing +title = "" +xlabel = "" +ylabel = "" +yrightlabel = "" +reg = false +size = (600,400) +pos = (0,0) +windowtitle = "Plots.jl" +screen = 1 +``` +[[REPLACE_END]] + +__Tip__: You can see the default value for a given argument with `plotDefault(arg::Symbol)`, and set the default value with `plotDefault!(arg::Symbol, value)` + +__Tip__: When plotting multiple lines, you can set all series to use the same value, or pass in an array to cycle through values. Example: + +```julia +plot(rand(100,4); color = [:red, RGB(0,0,1)], # 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 + 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: `supportedAxes()`, `supportedTypes()`, `supportedStyles()`, `supportedMarkers()`, `subplotSupported()` + +## TODO features: + +- [x] Plot vectors/matrices/functions +- [ ] Plot DataFrames +- [ ] Scales +- [ ] Categorical Inputs (strings, etc... for hist, bar? or can split one series into multiple?) +- [ ] Custom markers +- [ ] Special plots (boxplot, ohlc?) +- [x] Subplots +- [x] Histograms +- [ ] 3D plotting +- [ ] 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 +- [ ] 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 + + diff --git a/src/backends/gadfly.jl b/src/backends/gadfly.jl index 7e59b99e..4f52ad86 100644 --- a/src/backends/gadfly.jl +++ b/src/backends/gadfly.jl @@ -9,7 +9,7 @@ gadfly!() = plotter!(:gadfly) supportedArgs(::GadflyPackage) = setdiff(_allArgs, [:heatmap_c, :pos]) supportedAxes(::GadflyPackage) = setdiff(_allAxes, [:right]) -supportedTypes(::GadflyPackage) = [:none, :line, :step, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline, :ohlc] +supportedTypes(::GadflyPackage) = [:none, :line, :path, :step, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline, :ohlc] supportedStyles(::GadflyPackage) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] supportedMarkers(::GadflyPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star1, :star2, :hexagon, :octagon] @@ -46,7 +46,7 @@ function getLineGeoms(d::Dict) lt in (:heatmap,:hexbin) && return [Gadfly.Geom.hexbin(xbincount = d[:nbins], ybincount = d[:nbins])] lt == :hist && return [Gadfly.Geom.histogram(bincount = d[:nbins])] # lt == :none && return [Gadfly.Geom.path] - lt == :line && return [Gadfly.Geom.path] + lt == :path && return [Gadfly.Geom.path] lt == :scatter && return [Gadfly.Geom.point] lt == :bar && return [Gadfly.Geom.bar] lt == :step && return [Gadfly.Geom.step] @@ -109,12 +109,19 @@ function addGadflySeries!(gplt, d::Dict) gfargs = [] # if my PR isn't present, don't set the line_style - extra_theme_args = Gadfly.isdefined(:getStrokeVector) ? [(:line_style, getGadflyStrokeVector(d[:linestyle]))] : [] + local extra_theme_args + try + Gadfly.getStrokeVector(:solid) + extra_theme_args = [(:line_style, getGadflyStrokeVector(d[:linestyle]))] + catch + extra_theme_args = [] + end + # extra_theme_args = Gadfly.isdefined(:getStrokeVector) ? [(:line_style, getGadflyStrokeVector(d[:linestyle]))] : [] # line_style = getGadflyStrokeVector(d[:linestyle]) # set theme: color, line width, and point size line_width = d[:width] * (d[:linetype] == :none ? 0 : 1) * Gadfly.px # 0 width when we don't show a line - theme = Gadfly.Theme(default_color = d[:color], + theme = Gadfly.Theme(; default_color = d[:color], line_width = line_width, default_point_size = 0.5 * d[:markersize] * Gadfly.px, extra_theme_args...) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 120ee921..6150a407 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -10,7 +10,7 @@ pyplot!() = plotter!(:pyplot) supportedArgs(::PyPlotPackage) = setdiff(_allArgs, [:reg, :heatmap_c, :fillto, :pos]) supportedAxes(::PyPlotPackage) = _allAxes -supportedTypes(::PyPlotPackage) = [:none, :line, :step, :stepinverted, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar] +supportedTypes(::PyPlotPackage) = [:none, :line, :path, :step, :stepinverted, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar] supportedStyles(::PyPlotPackage) = [:auto, :solid, :dash, :dot, :dashdot] supportedMarkers(::PyPlotPackage) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star1, :hexagon] subplotSupported(::PyPlotPackage) = false diff --git a/src/backends/qwt.jl b/src/backends/qwt.jl index bd1f608f..3a025eb1 100644 --- a/src/backends/qwt.jl +++ b/src/backends/qwt.jl @@ -6,7 +6,7 @@ immutable QwtPackage <: PlottingPackage end export qwt! qwt!() = plotter!(:qwt) -supportedTypes(::QwtPackage) = [:none, :path, :step, :stepinverted, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar] +supportedTypes(::QwtPackage) = [:none, :line, :path, :step, :stepinverted, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar] # ------------------------------- @@ -41,6 +41,7 @@ end function plot(pkg::QwtPackage; kw...) d = Dict(kw) + replaceAliases!(d, _qwtAliases) # d = adjustQwtKeywords(true; kw...) o = Qwt.plot(zeros(0,0); d..., show=false) plt = Plot(o, pkg, 0, d, Dict[]) @@ -49,6 +50,7 @@ end function plot!(::QwtPackage, plt::Plot; kw...) d = adjustQwtKeywords(false; kw...) + @show d Qwt.oplot(plt.o; d...) push!(plt.seriesargs, d) plt diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl index 48ce4f5f..58810897 100644 --- a/src/backends/unicodeplots.jl +++ b/src/backends/unicodeplots.jl @@ -10,7 +10,7 @@ unicodeplots!() = plotter!(:unicodeplots) supportedArgs(::UnicodePlotsPackage) = setdiff(_allArgs, [:reg, :heatmap_c, :fillto, :pos]) supportedAxes(::UnicodePlotsPackage) = [:auto, :left] -supportedTypes(::UnicodePlotsPackage) = [:none, :line, :step, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline] +supportedTypes(::UnicodePlotsPackage) = [:none, :line, :path, :step, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline] supportedStyles(::UnicodePlotsPackage) = [:auto, :solid] supportedMarkers(::UnicodePlotsPackage) = [:none, :auto, :ellipse] @@ -67,7 +67,7 @@ function addUnicodeSeries!(o, d::Dict, addlegend::Bool) # get the function, or special handling for step/bar/hist lt = d[:linetype] stepstyle = :post - if lt == :line + if lt == :path func = UnicodePlots.lineplot! elseif lt == :scatter || d[:marker] != :none func = UnicodePlots.scatterplot! diff --git a/src/backends/winston.jl b/src/backends/winston.jl index 3aab9ae9..a19ff600 100644 --- a/src/backends/winston.jl +++ b/src/backends/winston.jl @@ -32,7 +32,7 @@ const winston_marker = Dict(:none=>".", supportedArgs(::WinstonPackage) = setdiff(_allArgs, [:heatmap_c, :fillto, :pos, :markercolor, :background_color]) supportedAxes(::WinstonPackage) = [:auto, :left] -supportedTypes(::WinstonPackage) = [:none, :line, :sticks, :scatter, :hist, :bar] +supportedTypes(::WinstonPackage) = [:none, :line, :path, :sticks, :scatter, :hist, :bar] supportedStyles(::WinstonPackage) = intersect(_allStyles, collect(keys(winston_linestyle))) # [:auto, :solid, :dash, :dot, :dashdot] supportedMarkers(::WinstonPackage) = intersect(_allMarkers, collect(keys(winston_marker))) # [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star1] subplotSupported(::WinstonPackage) = false @@ -135,11 +135,11 @@ function plot!(::WinstonPackage, plt::Plot; kw...) - ## lintype :line, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar + ## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :heatmap, :hexbin, :hist, :bar if d[:linetype] == :none Winston.add(wplt, Winston.Points(d[:x], d[:y]; copy_remove(e, :kind)...)) - elseif d[:linetype] == :line + elseif d[:linetype] == :path x, y = d[:x], d[:y] Winston.add(wplt, Winston.Curve(x, y; e...)) diff --git a/src/plot.jl b/src/plot.jl index 47e60014..65616793 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -224,6 +224,15 @@ function createKWargsList(plt::PlottingObject, x, y; kw...) n = plt.n + i d = getSeriesArgs(plt.plotter, getinitargs(plt, n), kw, i, convertSeriesIndex(plt, n), n) d[:x], d[:y] = computeXandY(xs[mod1(i,mx)], ys[mod1(i,my)]) + + if d[:linetype] == :line + # order by x + indices = sortperm(d[:x]) + d[:x] = d[:x][indices] + d[:y] = d[:y][indices] + d[:linetype] = :path + end + push!(ret, d) end ret @@ -254,7 +263,7 @@ mapFuncOrFuncs(f::Function, u::AVec) = map(f, u) mapFuncOrFuncs(fs::AVec{Function}, u::AVec) = [map(f, u) for f in fs] # special handling... xmin/xmax with parametric function(s) -function createKWargsList(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Real, umax::Real, numPoints::Int = 1000000; kw...) +function createKWargsList(plt::PlottingObject, fx::FuncOrFuncs, fy::FuncOrFuncs, umin::Real, umax::Real, numPoints::Int = 1000; kw...) u = collect(linspace(umin, umax, numPoints)) createKWargsList(plt, mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u); kw...) end diff --git a/src/utils.jl b/src/utils.jl index e7c65250..4a5d4cc7 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -64,7 +64,7 @@ function barHack(; kw...) d[:x] = x d[:y] = y - d[:linetype] = :line + d[:linetype] = :path d[:fillto] = fillto d end @@ -94,7 +94,7 @@ function sticksHack(; kw...) # change the line args dLine[:x] = x dLine[:y] = y - dLine[:linetype] = :line + dLine[:linetype] = :path dLine[:marker] = :none dLine[:fillto] = nothing