Merge pull request #1611 from daschw/pyplot-thickness

Fix pyplot thickness and 0.6 backports release
This commit is contained in:
Daniel Schwabeneder 2018-07-26 17:03:48 +02:00 committed by GitHub
commit c2e8b5a1fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 5822 additions and 1940 deletions

View File

@ -4,7 +4,7 @@ os:
- linux - linux
# - osx # - osx
julia: julia:
- 0.5 - 0.6
matrix: matrix:
allow_failures: allow_failures:
- julia: nightly - julia: nightly

257
NEWS.md
View File

@ -3,14 +3,261 @@
#### notes on release changes, ongoing development, and future planned work #### notes on release changes, ongoing development, and future planned work
- All new development should target 0.9! - Minor version 0.17 is the last one to support Julia 0.6!!
- Minor version 0.8 is the last one to support Julia 0.4!! - Minor version 0.11 is the last one to support Julia 0.5!!
- Critical bugfixes only - Critical bugfixes only
- `backports` branch is for Julia 0.4 - `backports` branch is for Julia 0.5
--- ---
## (current master)
- All new development should target Julia 0.7!
## 0.17.4
- fix thickness_scaling for pyplot
## 0.17.3
- Log-scale heatmap edge computation
- Fix size and dpi for GR and PyPlot
- Fix fillrange with line segments on PyPlot and Plotly
- fix flip for heatmap and image on GR
- New attributes for PGFPlots
- Widen axes for most series types and log scales
- Plotly: fix log scale with no ticks
- Fix axis flip on Plotly
- Fix hover and zcolor interaction in Plotly
- WebIO integration for PlotlyJS backend
## 0.17.2
- fix single subplot in plotly
- implement `(xyz)lims = :round`
- PyPlot: fix bg_legend = invisible()
- set fallback tick specification for axes with discrete values
- restructure of show methods
## 0.17.1
- Fix contour for PGFPlots
- 32Bit fix: Int64 -> Int
- Make series of shapes and segments toggle together in Plotly(JS)
- Fix marker arguments
- Fix processing order of series recipes
- Fix Plotly(JS) ribbon
- Contour plots with x,y in grid form on PyPlot
## 0.17.0
- Add GR dependency to make it the default backend
- Improve histogram2d bin estimation
- Allow vector arguments for certain series attributes and support line_z and fill_z on GR, PyPlot, Plotly(JS) and PGFPlots
- Automatic scientific notation for tick labels
- Allow to set the theme in PLOTS_DEFAULTS
- Implement plots_heatmap seriestype providing a Plots recipe for heatmaps
## 0.16.0
- fix 3D plotting in PyPlot
- Infinite objects
## 0.15.1
- fix scientific notation for labels in GR
- fix labels with logscale
- fix image cropping with GR
- fix grouping of annotations
- fix annotations in Plotly
- allow saving notebook with plots as pdf from IJulia
- fix fillrange and ribbon for step recipes
- implement native ticks that respond to zoom
- fix bar plot with one bar
- contour labels and colorbar fixes
- interactive linked axis for PyPlot
- add `NamedTuple` syntax to group with named legend
- use bar recipe in Plotly
- implement categorical ticks
## 0.15.0
- improve resolution of png output of GR with savefig()
- add check for ticks=nothing
- allow transparency in heatmaps
- fix line_z for GR
- fix legendcolor for pyplot
- fix pyplot ignoring alpha values of images
- don't let `abline!` change subplot limits
- update showtheme recipe
## 0.14.2
- fix plotly bar lines bug
- allow passing multiple series to `ribbon`
- add a new example for `line_z`
## 0.14.1
- Add linestyle argument to the legend
- Plotly: bar_width and stroke_width support for bar plots
- abline! does not change axis limits
- Fix default log scale ticks in GR backend
- Use the :fontsize keys so the scalefontsizes command works
- Prepare support for new PlotTheme type in PlotThemes
## 0.14.0
- remove use of imagemagick; saving gifs now requires ffmpeg
- improvements to ffmpeg gif quality and speed
- overhaul of fonts, allows setting fonts in recipes and with magic arguments
- added `camera` attribute to control camera position for 3d plots
- added `showaxis` attribute to control which axes to display
- improvements of polar plots axes, and better backend consistency
- changed the 'spy' recipe back to using heatmap
- added `scatterpath` seriestype
- allow plotlyjs to save svg
- add `reset_defaults()` function to reset plot defaults
- update syntax to 0.6
- make `fill = true` fill to 0 rather than to 1
- use new `@df` syntax in StatPlots examples
- allow changing the color of legend box
- implement `title_location` for gr
- add `hline` marker to pgfplots - fixes errorbars
- pyplot legends now show marker types
- pyplot colorbars take font style from y axis
- pyplot tickmarks color the same as axis color
- allow setting linewidth for contour in gr
- allow legend to be outside plot area for pgfplots
- expand axis extrema for heatmap
- extendg grid lines to axis limits
- fix `line_z` for pyplot and gr
- fixed colorbar problem for flipped axes with gr
- fix marker_z for 3d plots in gr
- fix `weights` functionality for histograms
- fix gr annotations with colorbar
- fix aspect ratio in gr
- fix "hidden window" problem after savefig in gr
- fix pgfplots logscale ticks error
- fix pgfplots legends symbols
- fix axis linking for plotlyjs
- fix plotting of grayscale images
## 0.13.1
- fix a bug when passing a vector of functions with no bounds (e.g. `plot([sin, cos])`)
- export `pct` and `px` from Plots.PlotMeasures
## 0.13.0
- support `plotattributes` rather than `d` in recipes
- no longer export `w`, `h` and names from Measures.jl; use `using Plots.PlotMeasures` to get these names back
- `bar_width` now depends on the minimum distance between bars, not the mean
- better automatic x axis limits for plotting Functions
- `tick_direction` attribute now allows ticks to be on the inside of the plot border
- removed a bug where `p1 = plot(randn(10)); plot(p1, p2)` made `display(p1)` impossible
- allow `plot([])` to generate an empty plot
- add `origin` framestyle
- ensure finite bin number on histograms with only one unique value
- better automatic histogram bins for 2d histograms
- more informative error message on passing unsupported seriestype in a recipe
- allow grouping in user recipes
- GR now has `line_z` and `fill_z` attributes for determining the color of shapes and lines
- change GR default view angle for 3D plots to match that of PyPlot
- fix `clims` on GR
- fix `marker_z` for plotly backend
- implement `framestyle` for plotly
- fix logscale bug error for values < 1e-16 on pyplot
- fix an issue on pyplot where >1 colorbar would be shown if there was >1 series
- fix `writemime` for eps
## 0.12.4
- added a new `framestyle` argument with choices: :box, :semi, :axes, :grid and :none
- changed the default bar width to 0.8
- added working ribbon to plotly backend
- ensure that automatic ticks always generate 4 to 8 ticks
- group now groups keyword arguments of the same length as the input
- allow passing DateTime objects as ticks
- allow specifying the number of ticks as an integre
- fix bug on errorbars in gr
- fixed some but not all world age issues
- better margin with room for text
- added a `match` option for linecolor
- better error message un unsupported series types
- add a 'stride' keyword for the pyplot backend
## 0.12.3
- new grid line style defaults
- `grid` is now an axis attribute and a magic argument: it is now possible to modify the grid line style, alpha and line width
- Enforce plot order in user recipes
- import `plot!` from RecipesBase
- GR no longer automatically handles _ and ^ in texts
- fix GR colorbar for scatter plots
#### 0.12.2
- fix an issue with Juno/PlotlyJS compatibility on new installations
- fix markers not showing up in seriesrecipes using :scatter
- don't use pywrap in the pyplot backend
- improve the bottom margin for the gr backend
#### 0.12.1
- fix deprecation warnings
- switch from FixedSizeArrays to StaticArrays.FixedSizeArrays
- drop FactCheck in tests
- remove julia 0.5 compliant uses of transpose operator
- fix GR heatmap bugs
- fix GR guide padding
- improve legend markers in GR
- add surface alpha for Plotly(JS)
- add fillrange to Plotly(JS)
- allow usage of Matplotlib 1.5 with PyPlot
- fix GLVisualize for julia 0.6
- conform to changes in InspectDR
#### 0.12.0
- 0.6 only
#### 0.11.3
- add HDF5 backend
- GR replaces PyPlot as first-choice backend
- support for legend position in GR
- smaller markers in GR
- better viewport size in GR
- fix glvisualize support
- remove bug with three-argument method of `text`
- `legendtitle` attribute added
- add test for `spy`
#### 0.11.0
- julia 0.6 compatibility
- matplotlib 2.0 compatibility
- add inspectdr backend
- improved histogram functionality:
- added a `:stephist` and `:scatterhist` series type as well as ``:barhist` (the default)
- support for log scale axes with histograms
- support for plotting `StatsBase.Histogram`
- allowing bins to be specified as `:sturges`, `:rice`, `:scott` or :fd
- allow `normalization` to be specified as :density (for unequal bins) or :pdf (sum to 1)
- add a `plotattr` function to access documentation for Plots attribute
- add `fill_z` attribute for pyplot
- add colorbar_title to plotlyjs
- enable standalone window for plotlyjs
- improved support for pgfplots, ticks rotation, clims, series_annotations
- restore colorbars for GR
- better axis labels for heatmap in GR
- better marker sizes in GR
- fix color representation in GR
- update GR legend
- fix image bug on GR
- fix glvisualize dependencies
- set dotted grid lines for pyplot
- several improvements to inspectdr
- improved tick positions for TimeType x axes
- support for improved color gradient capability in PlotUtils
- add a showlibrary recipe to display color libraries
- add a showgradient recipe to display color gradients
- add `vectorfield` as an alias for `quiver`
- use `PlotUtils.adaptedgrid` for functions
## 0.9 (current master/dev)
#### 0.9.5 #### 0.9.5
@ -331,7 +578,7 @@
- z-axis keywords - z-axis keywords
- 3D indexing overhaul: `push!`, `append!` support - 3D indexing overhaul: `push!`, `append!` support
- matplotlib colormap constants (`:inferno` is the new default colormap for Plots) - matplotlib colormap constants (`:inferno` is the new default colormap for Plots)
- `typealias KW Dict{Symbol,Any}` used in place of splatting in many places - `const KW = Dict{Symbol,Any}` used in place of splatting in many places
- png generation for plotly backend using wkhtmltoimage - png generation for plotly backend using wkhtmltoimage
- `normalize` and `weights` keywords - `normalize` and `weights` keywords
- background/foreground subcategories for fine-tuning of looks - background/foreground subcategories for fine-tuning of looks

View File

@ -1,13 +1,16 @@
# Plots # Plots
[![Build Status](https://travis-ci.org/tbreloff/Plots.jl.svg?branch=master)](https://travis-ci.org/tbreloff/Plots.jl) [![Build Status](https://travis-ci.org/JuliaPlots/Plots.jl.svg?branch=master)](https://travis-ci.org/JuliaPlots/Plots.jl)
[![Build status](https://ci.appveyor.com/api/projects/status/github/juliaplots/plots.jl?branch=master&svg=true)](https://ci.appveyor.com/project/mkborregaard/plots-jl)
[![Join the chat at https://gitter.im/tbreloff/Plots.jl](https://badges.gitter.im/tbreloff/Plots.jl.svg)](https://gitter.im/tbreloff/Plots.jl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/tbreloff/Plots.jl](https://badges.gitter.im/tbreloff/Plots.jl.svg)](https://gitter.im/tbreloff/Plots.jl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- [![Plots](http://pkg.julialang.org/badges/Plots_0.3.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.3) --> <!-- [![Plots](http://pkg.julialang.org/badges/Plots_0.3.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.3) -->
<!-- [![Plots](http://pkg.julialang.org/badges/Plots_0.4.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.4) --> <!-- [![Plots](http://pkg.julialang.org/badges/Plots_0.4.svg)](http://pkg.julialang.org/?pkg=Plots&ver=0.4) -->
<!-- [![Coverage Status](https://coveralls.io/repos/tbreloff/Plots.jl/badge.svg?branch=master)](https://coveralls.io/r/tbreloff/Plots.jl?branch=master) --> <!-- [![Coverage Status](https://coveralls.io/repos/tbreloff/Plots.jl/badge.svg?branch=master)](https://coveralls.io/r/tbreloff/Plots.jl?branch=master) -->
<!-- [![codecov.io](http://codecov.io/github/tbreloff/Plots.jl/coverage.svg?branch=master)](http://codecov.io/github/tbreloff/Plots.jl?branch=master) --> <!-- [![codecov.io](http://codecov.io/github/tbreloff/Plots.jl/coverage.svg?branch=master)](http://codecov.io/github/tbreloff/Plots.jl?branch=master) -->
#### Author: Thomas Breloff (@tbreloff) #### Created by Tom Breloff (@tbreloff)
#### Maintained by the [JuliaPlot members](https://github.com/orgs/JuliaPlots/people)
Plots is a plotting API and toolset. My goals with the package are: Plots is a plotting API and toolset. My goals with the package are:
@ -19,4 +22,4 @@ Plots is a plotting API and toolset. My goals with the package are:
- **Lightweight**. Very few dependencies. - **Lightweight**. Very few dependencies.
- **Smart**. Attempts to figure out what you **want** it to do... not just what you **tell** it. - **Smart**. Attempts to figure out what you **want** it to do... not just what you **tell** it.
View the [full documentation](http://juliaplots.github.io). View the [full documentation](http://docs.juliaplots.org/latest).

17
REQUIRE
View File

@ -1,9 +1,16 @@
julia 0.5 julia 0.6
RecipesBase RecipesBase 0.2.3
PlotUtils PlotUtils 0.4.1
PlotThemes PlotThemes 0.1.3
Reexport Reexport
FixedSizeArrays StaticArrays 0.5
FixedPointNumbers 0.3
Measures Measures
Showoff Showoff
StatsBase 0.14.0
JSON
NaNMath
Requires
Contour
GR 0.31.0

View File

@ -1,9 +1,15 @@
environment: environment:
matrix: matrix:
- JULIAVERSION: "julialang/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe"
- JULIAVERSION: "julialang/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
- JULIAVERSION: "julianightlies/bin/winnt/x86/julia-latest-win32.exe" - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
- JULIAVERSION: "julianightlies/bin/winnt/x64/julia-latest-win64.exe" - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
matrix:
allow_failures:
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" #check and address
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
notifications: notifications:
- provider: Email - provider: Email
@ -12,13 +18,14 @@ notifications:
on_build_status_changed: false on_build_status_changed: false
install: install:
- ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12"
# If there's a newer build queued for the same PR, cancel this one # If there's a newer build queued for the same PR, cancel this one
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
throw "There are newer queued builds for this pull request, failing early." } throw "There are newer queued builds for this pull request, failing early." }
# Download most recent Julia Windows binary # Download most recent Julia Windows binary
- ps: (new-object net.webclient).DownloadFile($("http://s3.amazonaws.com/"+$env:JULIAVERSION), "C:\projects\julia-binary.exe") - ps: (new-object net.webclient).DownloadFile($env:JULIA_URL, "C:\projects\julia-binary.exe")
# Run installer silently, output to C:\projects\julia # Run installer silently, output to C:\projects\julia
- C:\projects\julia-binary.exe /S /D=C:\projects\julia - C:\projects\julia-binary.exe /S /D=C:\projects\julia

View File

@ -1,14 +1,22 @@
__precompile__(false) __precompile__(true)
module Plots module Plots
using Reexport using Reexport
using FixedSizeArrays
import StaticArrays
using StaticArrays.FixedSizeArrays
@reexport using RecipesBase @reexport using RecipesBase
import RecipesBase: plot, plot!, animate
using Base.Meta using Base.Meta
@reexport using PlotUtils @reexport using PlotUtils
@reexport using PlotThemes @reexport using PlotThemes
import Showoff import Showoff
import StatsBase
import JSON
using Requires
export export
grid, grid,
@ -29,9 +37,6 @@ export
with, with,
twinx, twinx,
@userplot,
@shorthands,
pie, pie,
pie!, pie!,
plot3d, plot3d,
@ -50,6 +55,8 @@ export
yflip!, yflip!,
xaxis!, xaxis!,
yaxis!, yaxis!,
xgrid!,
ygrid!,
xlims, xlims,
ylims, ylims,
@ -99,15 +106,50 @@ export
center, center,
P2, P2,
P3, P3,
BezierCurve BezierCurve,
plotattr
# ---------------------------------------------------------
import NaNMath # define functions that ignores NaNs. To overcome the destructive effects of https://github.com/JuliaLang/julia/pull/12563
ignorenan_minimum(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.minimum(x)
ignorenan_minimum(x) = Base.minimum(x)
ignorenan_maximum(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.maximum(x)
ignorenan_maximum(x) = Base.maximum(x)
ignorenan_mean(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.mean(x)
ignorenan_mean(x) = Base.mean(x)
ignorenan_extrema(x::AbstractArray{F}) where {F<:AbstractFloat} = NaNMath.extrema(x)
ignorenan_extrema(x) = Base.extrema(x)
# ---------------------------------------------------------
# to cater for block matrices, Base.transpose is recursive.
# This makes it impossible to create row vectors of String and Symbol with the transpose operator.
# This solves this issue, internally in Plots at least.
# commented out on the insistence of the METADATA maintainers
#Base.transpose(x::Symbol) = x
#Base.transpose(x::String) = x
# --------------------------------------------------------- # ---------------------------------------------------------
import Measures
module PlotMeasures
import Measures import Measures
import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height, w, h import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height, w, h
typealias BBox Measures.Absolute2DBox const BBox = Measures.Absolute2DBox
export BBox, BoundingBox, mm, cm, inch, pt, px, pct, w, h
# allow pixels and percentages
const px = AbsoluteLength(0.254)
const pct = Length{:pct, Float64}(1.0)
export BBox, BoundingBox, mm, cm, inch, px, pct, pt, w, h
end
using .PlotMeasures
import .PlotMeasures: Length, AbsoluteLength, Measure, width, height
# --------------------------------------------------------- # ---------------------------------------------------------
include("types.jl") include("types.jl")
@ -115,7 +157,6 @@ include("utils.jl")
include("components.jl") include("components.jl")
include("axes.jl") include("axes.jl")
include("args.jl") include("args.jl")
include("backends.jl")
include("themes.jl") include("themes.jl")
include("plot.jl") include("plot.jl")
include("pipeline.jl") include("pipeline.jl")
@ -124,34 +165,31 @@ include("layouts.jl")
include("subplots.jl") include("subplots.jl")
include("recipes.jl") include("recipes.jl")
include("animation.jl") include("animation.jl")
include("output.jl")
include("examples.jl") include("examples.jl")
include("arg_desc.jl") include("arg_desc.jl")
include("plotattr.jl")
include("backends.jl")
include("output.jl")
# --------------------------------------------------------- # ---------------------------------------------------------
# define and export shorthand plotting method definitions
macro shorthands(funcname::Symbol)
funcname2 = Symbol(funcname, "!")
esc(quote
export $funcname, $funcname2
$funcname(args...; kw...) = plot(args...; kw..., seriestype = $(quot(funcname)))
$funcname2(args...; kw...) = plot!(args...; kw..., seriestype = $(quot(funcname)))
end)
end
@shorthands scatter @shorthands scatter
@shorthands bar @shorthands bar
@shorthands barh @shorthands barh
@shorthands histogram @shorthands histogram
@shorthands barhist
@shorthands stephist
@shorthands scatterhist
@shorthands histogram2d @shorthands histogram2d
@shorthands density @shorthands density
@shorthands heatmap @shorthands heatmap
@shorthands plots_heatmap
@shorthands hexbin @shorthands hexbin
@shorthands sticks @shorthands sticks
@shorthands hline @shorthands hline
@shorthands vline @shorthands vline
@shorthands hspan
@shorthands vspan
@shorthands ohlc @shorthands ohlc
@shorthands contour @shorthands contour
@shorthands contourf @shorthands contourf
@ -165,52 +203,86 @@ end
@shorthands quiver @shorthands quiver
@shorthands curves @shorthands curves
"Plot a pie diagram"
pie(args...; kw...) = plot(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing) pie(args...; kw...) = plot(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
pie!(args...; kw...) = plot!(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing) pie!(args...; kw...) = plot!(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing)
"Plot with seriestype :path3d"
plot3d(args...; kw...) = plot(args...; kw..., seriestype = :path3d) plot3d(args...; kw...) = plot(args...; kw..., seriestype = :path3d)
plot3d!(args...; kw...) = plot!(args...; kw..., seriestype = :path3d) plot3d!(args...; kw...) = plot!(args...; kw..., seriestype = :path3d)
"Add title to an existing plot"
title!(s::AbstractString; kw...) = plot!(; title = s, kw...) title!(s::AbstractString; kw...) = plot!(; title = s, kw...)
"Add xlabel to an existing plot"
xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...) xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...)
"Add ylabel to an existing plot"
ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...) ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...)
xlims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; xlims = lims, kw...)
ylims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; ylims = lims, kw...) "Set xlims for an existing plot"
zlims!{T<:Real,S<:Real}(lims::Tuple{T,S}; kw...) = plot!(; zlims = lims, kw...) xlims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; xlims = lims, kw...)
"Set ylims for an existing plot"
ylims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; ylims = lims, kw...)
"Set zlims for an existing plot"
zlims!(lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(; zlims = lims, kw...)
xlims!(xmin::Real, xmax::Real; kw...) = plot!(; xlims = (xmin,xmax), kw...) xlims!(xmin::Real, xmax::Real; kw...) = plot!(; xlims = (xmin,xmax), kw...)
ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...) ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...)
zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin,zmax), kw...) zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin,zmax), kw...)
xticks!{T<:Real}(v::AVec{T}; kw...) = plot!(; xticks = v, kw...)
yticks!{T<:Real}(v::AVec{T}; kw...) = plot!(; yticks = v, kw...)
xticks!{T<:Real,S<:AbstractString}( "Set xticks for an existing plot"
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(; xticks = (ticks,labels), kw...) xticks!(v::AVec{T}; kw...) where {T<:Real} = plot!(; xticks = v, kw...)
yticks!{T<:Real,S<:AbstractString}(
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(; yticks = (ticks,labels), kw...) "Set yticks for an existing plot"
yticks!(v::AVec{T}; kw...) where {T<:Real} = plot!(; yticks = v, kw...)
xticks!(
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(; xticks = (ticks,labels), kw...)
yticks!(
ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(; yticks = (ticks,labels), kw...)
"Add annotations to an existing plot"
annotate!(anns...; kw...) = plot!(; annotation = anns, kw...) annotate!(anns...; kw...) = plot!(; annotation = anns, kw...)
annotate!{T<:Tuple}(anns::AVec{T}; kw...) = plot!(; annotation = anns, kw...) annotate!(anns::AVec{T}; kw...) where {T<:Tuple} = plot!(; annotation = anns, kw...)
"Flip the current plots' x axis"
xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...) xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...)
"Flip the current plots' y axis"
yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...) yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...)
"Specify x axis attributes for an existing plot"
xaxis!(args...; kw...) = plot!(; xaxis = args, kw...) xaxis!(args...; kw...) = plot!(; xaxis = args, kw...)
"Specify x axis attributes for an existing plot"
yaxis!(args...; kw...) = plot!(; yaxis = args, kw...) yaxis!(args...; kw...) = plot!(; yaxis = args, kw...)
xgrid!(args...; kw...) = plot!(; xgrid = args, kw...)
ygrid!(args...; kw...) = plot!(; ygrid = args, kw...)
let PlotOrSubplot = Union{Plot, Subplot} let PlotOrSubplot = Union{Plot, Subplot}
title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...) title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...)
xlabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...) xlabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; xlabel = s, kw...)
ylabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...) ylabel!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; ylabel = s, kw...)
xlims!{T<:Real,S<:Real}(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) = plot!(plt; xlims = lims, kw...) xlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; xlims = lims, kw...)
ylims!{T<:Real,S<:Real}(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) = plot!(plt; ylims = lims, kw...) ylims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; ylims = lims, kw...)
zlims!{T<:Real,S<:Real}(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) = plot!(plt; zlims = lims, kw...) zlims!(plt::PlotOrSubplot, lims::Tuple{T,S}; kw...) where {T<:Real,S<:Real} = plot!(plt; zlims = lims, kw...)
xlims!(plt::PlotOrSubplot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...) xlims!(plt::PlotOrSubplot, xmin::Real, xmax::Real; kw...) = plot!(plt; xlims = (xmin,xmax), kw...)
ylims!(plt::PlotOrSubplot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...) ylims!(plt::PlotOrSubplot, ymin::Real, ymax::Real; kw...) = plot!(plt; ylims = (ymin,ymax), kw...)
zlims!(plt::PlotOrSubplot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...) zlims!(plt::PlotOrSubplot, zmin::Real, zmax::Real; kw...) = plot!(plt; zlims = (zmin,zmax), kw...)
xticks!{T<:Real}(plt::PlotOrSubplot, ticks::AVec{T}; kw...) = plot!(plt; xticks = ticks, kw...) xticks!(plt::PlotOrSubplot, ticks::AVec{T}; kw...) where {T<:Real} = plot!(plt; xticks = ticks, kw...)
yticks!{T<:Real}(plt::PlotOrSubplot, ticks::AVec{T}; kw...) = plot!(plt; yticks = ticks, kw...) yticks!(plt::PlotOrSubplot, ticks::AVec{T}; kw...) where {T<:Real} = plot!(plt; yticks = ticks, kw...)
xticks!{T<:Real,S<:AbstractString}(plt::PlotOrSubplot, xticks!(plt::PlotOrSubplot,
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; xticks = (ticks,labels), kw...) ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; xticks = (ticks,labels), kw...)
yticks!{T<:Real,S<:AbstractString}(plt::PlotOrSubplot, yticks!(plt::PlotOrSubplot,
ticks::AVec{T}, labels::AVec{S}; kw...) = plot!(plt; yticks = (ticks,labels), kw...) ticks::AVec{T}, labels::AVec{S}; kw...) where {T<:Real,S<:AbstractString} = plot!(plt; yticks = (ticks,labels), kw...)
xgrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xgrid = args, kw...)
ygrid!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; ygrid = args, kw...)
annotate!(plt::PlotOrSubplot, anns...; kw...) = plot!(plt; annotation = anns, kw...) annotate!(plt::PlotOrSubplot, anns...; kw...) = plot!(plt; annotation = anns, kw...)
annotate!{T<:Tuple}(plt::PlotOrSubplot, anns::AVec{T}; kw...) = plot!(plt; annotation = anns, kw...) annotate!(plt::PlotOrSubplot, anns::AVec{T}; kw...) where {T<:Tuple} = plot!(plt; annotation = anns, kw...)
xflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...) xflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; xflip = flip, kw...)
yflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...) yflip!(plt::PlotOrSubplot, flip::Bool = true; kw...) = plot!(plt; yflip = flip, kw...)
xaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xaxis = args, kw...) xaxis!(plt::PlotOrSubplot, args...; kw...) = plot!(plt; xaxis = args, kw...)
@ -222,13 +294,14 @@ end
const CURRENT_BACKEND = CurrentBackend(:none) const CURRENT_BACKEND = CurrentBackend(:none)
function __init__() # for compatibility with Requires.jl:
setup_ijulia() @init begin
setup_atom()
if isdefined(Main, :PLOTS_DEFAULTS) if isdefined(Main, :PLOTS_DEFAULTS)
if haskey(Main.PLOTS_DEFAULTS, :theme)
theme(Main.PLOTS_DEFAULTS[:theme])
end
for (k,v) in Main.PLOTS_DEFAULTS for (k,v) in Main.PLOTS_DEFAULTS
default(k, v) k == :theme || default(k, v)
end end
end end
end end

View File

@ -1,5 +1,5 @@
"Represents an animation object"
immutable Animation struct Animation
dir::String dir::String
frames::Vector{String} frames::Vector{String}
end end
@ -9,7 +9,12 @@ function Animation()
Animation(tmpdir, String[]) Animation(tmpdir, String[])
end end
function frame{P<:AbstractPlot}(anim::Animation, plt::P=current()) """
frame(animation[, plot])
Add a plot (the current plot if not specified) to an existing animation
"""
function frame(anim::Animation, plt::P=current()) where P<:AbstractPlot
i = length(anim.frames) + 1 i = length(anim.frames) + 1
filename = @sprintf("%06d.png", i) filename = @sprintf("%06d.png", i)
png(plt, joinpath(anim.dir, filename)) png(plt, joinpath(anim.dir, filename))
@ -20,7 +25,7 @@ giffn() = (isijulia() ? "tmp.gif" : tempname()*".gif")
movfn() = (isijulia() ? "tmp.mov" : tempname()*".mov") movfn() = (isijulia() ? "tmp.mov" : tempname()*".mov")
mp4fn() = (isijulia() ? "tmp.mp4" : tempname()*".mp4") mp4fn() = (isijulia() ? "tmp.mp4" : tempname()*".mp4")
type FrameIterator mutable struct FrameIterator
itr itr
every::Int every::Int
kw kw
@ -49,46 +54,40 @@ end
# ----------------------------------------------- # -----------------------------------------------
"Wraps the location of an animated gif so that it can be displayed" "Wraps the location of an animated gif so that it can be displayed"
immutable AnimatedGif struct AnimatedGif
filename::String filename::String
end end
file_extension(fn) = Base.Filesystem.splitext(fn)[2][2:end] file_extension(fn) = Base.Filesystem.splitext(fn)[2][2:end]
gif(anim::Animation, fn = giffn(); kw...) = buildanimation(anim.dir, fn; kw...) gif(anim::Animation, fn = giffn(); kw...) = buildanimation(anim.dir, fn; kw...)
mov(anim::Animation, fn = movfn(); kw...) = buildanimation(anim.dir, fn; kw...) mov(anim::Animation, fn = movfn(); kw...) = buildanimation(anim.dir, fn, false; kw...)
mp4(anim::Animation, fn = mp4fn(); kw...) = buildanimation(anim.dir, fn; kw...) mp4(anim::Animation, fn = mp4fn(); kw...) = buildanimation(anim.dir, fn, false; kw...)
const _imagemagick_initialized = Ref(false)
function buildanimation(animdir::AbstractString, fn::AbstractString; function buildanimation(animdir::AbstractString, fn::AbstractString,
fps::Integer = 20, loop::Integer = 0) is_animated_gif::Bool=true;
fps::Integer = 20, loop::Integer = 0,
variable_palette::Bool=false,
show_msg::Bool=true)
fn = abspath(fn) fn = abspath(fn)
try
if !_imagemagick_initialized[] if is_animated_gif
file = joinpath(Pkg.dir("ImageMagick"), "deps","deps.jl") if variable_palette
if isfile(file) && !haskey(ENV, "MAGICK_CONFIGURE_PATH") # generate a colorpalette for each frame for highest quality, but larger filesize
include(file) palette="palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1"
end run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`)
_imagemagick_initialized[] = true else
# generate a colorpalette first so ffmpeg does not have to guess it
run(`ffmpeg -v 0 -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`)
# then apply the palette to get better results
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -y $fn`)
end end
else
# prefix = get(ENV, "MAGICK_CONFIGURE_PATH", "") run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)
# high quality
speed = round(Int, 100 / fps)
run(`convert -delay $speed -loop $loop $(joinpath(animdir, "*.png")) -alpha off $fn`)
catch err
warn("""Tried to create gif using convert (ImageMagick), but got error: $err
ImageMagick can be installed by executing `Pkg.add("ImageMagick")`
Will try ffmpeg, but it's lower quality...)""")
# low quality
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -y $fn`)
# run(`ffmpeg -v warning -i "fps=$fps,scale=320:-1:flags=lanczos"`)
end end
info("Saved animation to ", fn) show_msg && info("Saved animation to ", fn)
AnimatedGif(fn) AnimatedGif(fn)
end end
@ -117,6 +116,7 @@ function _animate(forloop::Expr, args...; callgif = false)
# add the call to frame to the end of each iteration # add the call to frame to the end of each iteration
animsym = gensym("anim") animsym = gensym("anim")
countersym = gensym("counter") countersym = gensym("counter")
freqassert = :()
block = forloop.args[2] block = forloop.args[2]
# create filter # create filter
@ -129,7 +129,7 @@ function _animate(forloop::Expr, args...; callgif = false)
# filter every `freq` frames (starting with the first frame) # filter every `freq` frames (starting with the first frame)
@assert n == 2 @assert n == 2
freq = args[2] freq = args[2]
@assert isa(freq, Integer) && freq > 0 freqassert = :(@assert isa($freq, Integer) && $freq > 0)
:(mod1($countersym, $freq) == 1) :(mod1($countersym, $freq) == 1)
elseif args[1] == :when elseif args[1] == :when
@ -149,6 +149,7 @@ function _animate(forloop::Expr, args...; callgif = false)
# full expression: # full expression:
esc(quote esc(quote
$freqassert # if filtering, check frequency is an Integer > 0
$animsym = Animation() # init animation object $animsym = Animation() # init animation object
$countersym = 1 # init iteration counter $countersym = 1 # init iteration counter
$forloop # for loop, saving a frame after each iteration $forloop # for loop, saving a frame after each iteration

View File

@ -21,7 +21,7 @@ const _arg_desc = KW(
:markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)", :markerstrokewidth => "Number. Width of the marker stroke (border. in pixels)",
:markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.", :markerstrokecolor => "Color Type. Color of the marker stroke (border). `:match` will take the value from `:foreground_color_subplot`.",
:markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.", :markerstrokealpha => "Number in [0,1]. The alpha/opacity override for the marker stroke (border). `nothing` (the default) means it will take the alpha value of markerstrokecolor.",
:bins => "Integer, NTuple{2,Integer}, AbstractVector. For histogram-types, defines the number of bins, or the edges, of the histogram.", :bins => "Integer, NTuple{2,Integer}, AbstractVector or Symbol. Default is :auto (the Freedman-Diaconis rule). For histogram-types, defines the approximate number of bins to aim for, or the auto-binning algorithm to use (:sturges, :sqrt, :rice, :scott or :fd). For fine-grained control pass a Vector of break values, e.g. `linspace(extrema(x)..., 25)`",
:smooth => "Bool. Add a regression line?", :smooth => "Bool. Add a regression line?",
:group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`.", :group => "AbstractVector. Data is split into a separate series, one for each unique value in `group`.",
:x => "Various. Input data. First Dimension", :x => "Various. Input data. First Dimension",
@ -40,9 +40,10 @@ const _arg_desc = KW(
:ribbon => "Number or AbstractVector. Creates a fillrange around the data points.", :ribbon => "Number or AbstractVector. Creates a fillrange around the data points.",
:quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.", :quiver => "AbstractVector or 2-Tuple of vectors. The directional vectors U,V which specify velocity/gradient vectors for a quiver plot.",
:arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.", :arrow => "nothing (no arrows), Bool (if true, default arrows), Arrow object, or arg(s) that could be style or head length/widths. Defines arrowheads that should be displayed at the end of path line segments (just before a NaN and the last non-NaN point). Used in quiverplot, streamplot, or similar.",
:normalize => "Bool. Should normalize histogram types? Trying for area == 1.", :normalize => "Bool or Symbol. Histogram normalization mode. Possible values are: false/:none (no normalization, default), true/:pdf (normalize to a discrete Probability Density Function, where the total area of the bins is 1), :probability (bin heights sum to 1) and :density (the area of each bin, rather than the height, is equal to the counts - useful for uneven bin sizes).",
:weights => "AbstractVector. Used in histogram types for weighted counts.", :weights => "AbstractVector. Used in histogram types for weighted counts.",
:contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.", :contours => "Bool. Add contours to the side-grids of 3D plots? Used in surface/wireframe.",
:contour_labels => "Bool. Show labels at the contour lines?",
:match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.", :match_dimensions => "Bool. For heatmap types... should the first dimension of a matrix (rows) correspond to the first dimension of the plot (x-axis)? The default is false, which matches the behavior of Matplotlib, Plotly, and others. Note: when passing a function for z, the function should still map `(x,y) -> z`.",
:subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.", :subplot => "Integer (subplot index) or Subplot object. The subplot that this series belongs to.",
:series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.", :series_annotations => "AbstractVector of String or PlotText. These are annotations which are mapped to data points/positions.",
@ -64,26 +65,37 @@ const _arg_desc = KW(
:html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.", :html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.",
:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots", :inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots",
:dpi => "Number. Dots Per Inch of output figures", :dpi => "Number. Dots Per Inch of output figures",
:thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.",
:display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.", :display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.",
:extra_kwargs => "KW (Dict{Symbol,Any}). Pass a map of extra keyword args which may be specific to a backend.", :extra_kwargs => "KW (Dict{Symbol,Any}). Pass a map of extra keyword args which may be specific to a backend.",
:fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides",
# subplot args # subplot args
:title => "String. Subplot title.", :title => "String. Subplot title.",
:title_location => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`", :title_location => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`",
:titlefont => "Font. Font of subplot title.", :titlefontfamily => "String or Symbol. Font family of subplot title.",
:titlefontsize => "Integer. Font pointsize of subplot title.",
:titlefonthalign => "Symbol. Font horizontal alignment of subplot title: :hcenter, :left, :right or :center",
:titlefontvalign => "Symbol. Font vertical alignment of subplot title: :vcenter, :top, :bottom or :center",
:titlefontrotation => "Real. Font rotation of subplot title",
:titlefontcolor => "Color Type. Font color of subplot title",
:background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.", :background_color_subplot => "Color Type or `:match` (matches `:background_color`). Base background color of the subplot.",
:background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.", :background_color_legend => "Color Type or `:match` (matches `:background_color_subplot`). Background color of the legend.",
:background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).", :background_color_inside => "Color Type or `:match` (matches `:background_color_subplot`). Background color inside the plot area (under the grid).",
:foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.", :foreground_color_subplot => "Color Type or `:match` (matches `:foreground_color`). Base foreground color of the subplot.",
:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.", :foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.",
:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.",
:foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.", :foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.",
:color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.", :color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.",
:legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` (note: only some may be supported in each backend)", :legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` (note: only some may be supported in each backend)",
:legendfontfamily => "String or Symbol. Font family of legend entries.",
:legendfontsize => "Integer. Font pointsize of legend entries.",
:legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center",
:legendfontvalign => "Symbol. Font vertical alignment of legend entries: :vcenter, :top, :bottom or :center",
:legendfontrotation => "Real. Font rotation of legend entries",
:legendfontcolor => "Color Type. Font color of legend entries",
:colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)", :colorbar => "Bool (show the colorbar?) or Symbol (colorbar position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:legend` (matches legend value) (note: only some may be supported in each backend)",
:clims => "`:auto` or NTuple{2,Number}. Fixes the limits of the colorbar.", :clims => "`:auto` or NTuple{2,Number}. Fixes the limits of the colorbar.",
:legendfont => "Font. Font of legend items.", :legendfont => "Font. Font of legend items.",
:grid => "Bool. Show the grid lines?",
:annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.", :annotations => "(x,y,text) tuple(s). Can be a single tuple or a list of them. Text can be String or PlotText (created with `text(args...)`) Add one-off text annotations at the x,y coordinates.",
:projection => "Symbol or String. '3d' or 'polar'", :projection => "Symbol or String. '3d' or 'polar'",
:aspect_ratio => "Symbol (:equal) or Number. Plot area is resized so that 1 y-unit is the same size as `apect_ratio` x-units.", :aspect_ratio => "Symbol (:equal) or Number. Plot area is resized so that 1 y-unit is the same size as `apect_ratio` x-units.",
@ -94,21 +106,40 @@ const _arg_desc = KW(
:bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.", :bottom_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the bottom of the subplot.",
:subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.", :subplot_index => "Integer. Internal (not set by user). Specifies the index of this subplot in the Plot's `plt.subplot` list.",
:colorbar_title => "String. Title of colorbar.", :colorbar_title => "String. Title of colorbar.",
:framestyle => "Symbol. Style of the axes frame. Choose from $(_allFramestyles)",
:camera => "NTuple{2, Real}. Sets the view angle (azimuthal, elevation) for 3D plots",
# axis args # axis args
:guide => "String. Axis guide (label).", :guide => "String. Axis guide (label).",
:lims => "NTuple{2,Number}. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example).", :lims => "NTuple{2,Number} or Symbol. Force axis limits. Only finite values are used (you can set only the right limit with `xlims = (-Inf, 2)` for example). `:round` widens the limit to the nearest round number ie. [0.1,3.6]=>[0.0,4.0]",
:ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`", :ticks => "Vector of numbers (set the tick values), Tuple of (tickvalues, ticklabels), or `:auto`",
:scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`", :scale => "Symbol. Scale of the axis: `:none`, `:ln`, `:log2`, `:log10`",
:rotation => "Number. Degrees rotation of tick labels.", :rotation => "Number. Degrees rotation of tick labels.",
:flip => "Bool. Should we flip (reverse) the axis?", :flip => "Bool. Should we flip (reverse) the axis?",
:formatter => "Function, :scientific, or :auto. A method which converts a number to a string for tick labeling.", :formatter => "Function, :scientific, or :auto. A method which converts a number to a string for tick labeling.",
:tickfont => "Font. Font of axis tick labels.", :tickfontfamily => "String or Symbol. Font family of tick labels.",
:guidefont => "Font. Font of axis guide (label).", :tickfontsize => "Integer. Font pointsize of tick labels.",
:tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center",
:tickfontvalign => "Symbol. Font vertical alignment of tick labels: :vcenter, :top, :bottom or :center",
:tickfontrotation => "Real. Font rotation of tick labels",
:tickfontcolor => "Color Type. Font color of tick labels",
:guidefontfamily => "String or Symbol. Font family of axes guides.",
:guidefontsize => "Integer. Font pointsize of axes guides.",
:guidefonthalign => "Symbol. Font horizontal alignment of axes guides: :hcenter, :left, :right or :center",
:guidefontvalign => "Symbol. Font vertical alignment of axes guides: :vcenter, :top, :bottom or :center",
:guidefontrotation => "Real. Font rotation of axes guides",
:guidefontcolor => "Color Type. Font color of axes guides",
:foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.", :foreground_color_axis => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis ticks.",
:foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).", :foreground_color_border => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of plot area border (spines).",
:foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.", :foreground_color_text => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of tick labels.",
:foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).", :foreground_color_guide => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of axis guides (axis labels).",
:mirror => "Bool. Switch the side of the tick labels (right or top).", :mirror => "Bool. Switch the side of the tick labels (right or top).",
:grid => "Bool, Symbol, String or `nothing`. Show the grid lines? `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:none`, `:off`",
:foreground_color_grid => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of grid lines.",
:gridalpha => "Number in [0,1]. The alpha/opacity override for the grid lines.",
:gridstyle => "Symbol. Style of the grid lines. Choose from $(_allStyles)",
:gridlinewidth => "Number. Width of the grid lines (in pixels)",
:tick_direction => "Symbol. Direction of the ticks. `:in` or `:out`",
:showaxis => "Bool, Symbol or String. Show the axis. `true`, `false`, `:show`, `:hide`, `:yes`, `:no`, `:x`, `:y`, `:z`, `:xy`, ..., `:all`, `:off`",
:widen => "Bool. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders. Defaults to `true`.",
) )

View File

@ -35,7 +35,9 @@ const _3dTypes = [
] ]
const _allTypes = vcat([ const _allTypes = vcat([
:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :none, :line, :path, :steppre, :steppost, :sticks, :scatter,
:heatmap, :hexbin, :histogram, :histogram2d, :histogram3d, :density, :bar, :hline, :vline, :heatmap, :hexbin, :barbins, :barhist, :histogram, :scatterbins,
:scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :histogram3d,
:density, :bar, :hline, :vline,
:contour, :pie, :shape, :image :contour, :pie, :shape, :image
], _3dTypes) ], _3dTypes)
@ -65,6 +67,7 @@ const _typeAliases = Dict{Symbol,Symbol}(
:polygon => :shape, :polygon => :shape,
:box => :boxplot, :box => :boxplot,
:velocity => :quiver, :velocity => :quiver,
:vectorfield => :quiver,
:gradient => :quiver, :gradient => :quiver,
:img => :image, :img => :image,
:imshow => :image, :imshow => :image,
@ -77,9 +80,13 @@ const _typeAliases = Dict{Symbol,Symbol}(
add_non_underscore_aliases!(_typeAliases) add_non_underscore_aliases!(_typeAliases)
like_histogram(seriestype::Symbol) = seriestype in (:histogram, :density) const _histogram_like = [:histogram, :barhist, :barbins]
like_line(seriestype::Symbol) = seriestype in (:line, :path, :steppre, :steppost) const _line_like = [:line, :path, :steppre, :steppost]
like_surface(seriestype::Symbol) = seriestype in (:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image) const _surface_like = [:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image]
like_histogram(seriestype::Symbol) = seriestype in _histogram_like
like_line(seriestype::Symbol) = seriestype in _line_like
like_surface(seriestype::Symbol) = seriestype in _surface_like
is3d(seriestype::Symbol) = seriestype in _3dTypes is3d(seriestype::Symbol) = seriestype in _3dTypes
is3d(series::Series) = is3d(series.d) is3d(series::Series) = is3d(series.d)
@ -152,12 +159,75 @@ const _markerAliases = Dict{Symbol,Symbol}(
:spike => :vline, :spike => :vline,
) )
const _positionAliases = Dict{Symbol,Symbol}(
:top_left => :topleft,
:tl => :topleft,
:top_center => :topcenter,
:tc => :topcenter,
:top_right => :topright,
:tr => :topright,
:bottom_left => :bottomleft,
:bl => :bottomleft,
:bottom_center => :bottomcenter,
:bc => :bottomcenter,
:bottom_right => :bottomright,
:br => :bottomright,
)
const _allScales = [:identity, :ln, :log2, :log10, :asinh, :sqrt] const _allScales = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
const _logScales = [:ln, :log2, :log10]
const _logScaleBases = Dict(:ln => e, :log2 => 2.0, :log10 => 10.0)
const _scaleAliases = Dict{Symbol,Symbol}( const _scaleAliases = Dict{Symbol,Symbol}(
:none => :identity, :none => :identity,
:log => :log10, :log => :log10,
) )
const _allGridSyms = [:x, :y, :z,
:xy, :xz, :yx, :yz, :zx, :zy,
:xyz, :xzy, :yxz, :yzx, :zxy, :zyx,
:all, :both, :on, :yes, :show,
:none, :off, :no, :hide]
const _allGridArgs = [_allGridSyms; string.(_allGridSyms); nothing]
hasgrid(arg::Void, letter) = false
hasgrid(arg::Bool, letter) = arg
function hasgrid(arg::Symbol, letter)
if arg in _allGridSyms
arg in (:all, :both, :on) || contains(string(arg), string(letter))
else
warn("Unknown grid argument $arg; $(Symbol(letter, :grid)) was set to `true` instead.")
true
end
end
hasgrid(arg::AbstractString, letter) = hasgrid(Symbol(arg), letter)
const _allShowaxisSyms = [:x, :y, :z,
:xy, :xz, :yx, :yz, :zx, :zy,
:xyz, :xzy, :yxz, :yzx, :zxy, :zyx,
:all, :both, :on, :yes, :show,
:off, :no, :hide]
const _allShowaxisArgs = [_allGridSyms; string.(_allGridSyms)]
showaxis(arg::Void, letter) = false
showaxis(arg::Bool, letter) = arg
function showaxis(arg::Symbol, letter)
if arg in _allGridSyms
arg in (:all, :both, :on, :yes) || contains(string(arg), string(letter))
else
warn("Unknown showaxis argument $arg; $(Symbol(letter, :showaxis)) was set to `true` instead.")
true
end
end
showaxis(arg::AbstractString, letter) = hasgrid(Symbol(arg), letter)
const _allFramestyles = [:box, :semi, :axes, :origin, :zerolines, :grid, :none]
const _framestyleAliases = Dict{Symbol, Symbol}(
:frame => :box,
:border => :box,
:on => :box,
:transparent => :semi,
:semitransparent => :semi,
)
const _bar_width = 0.8
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
const _series_defaults = KW( const _series_defaults = KW(
@ -167,7 +237,7 @@ const _series_defaults = KW(
:seriestype => :path, :seriestype => :path,
:linestyle => :solid, :linestyle => :solid,
:linewidth => :auto, :linewidth => :auto,
:linecolor => :match, :linecolor => :auto,
:linealpha => nothing, :linealpha => nothing,
:fillrange => nothing, # ribbons, areas, etc :fillrange => nothing, # ribbons, areas, etc
:fillcolor => :match, :fillcolor => :match,
@ -180,7 +250,7 @@ const _series_defaults = KW(
:markerstrokewidth => 1, :markerstrokewidth => 1,
:markerstrokecolor => :match, :markerstrokecolor => :match,
:markerstrokealpha => nothing, :markerstrokealpha => nothing,
:bins => 30, # number of bins for hists :bins => :auto, # number of bins for hists
:smooth => false, # regression line? :smooth => false, # regression line?
:group => nothing, # groupby vector :group => nothing, # groupby vector
:x => nothing, :x => nothing,
@ -202,6 +272,7 @@ const _series_defaults = KW(
:normalize => false, # do we want a normalized histogram? :normalize => false, # do we want a normalized histogram?
:weights => nothing, # optional weights for histograms (1D and 2D) :weights => nothing, # optional weights for histograms (1D and 2D)
:contours => false, # add contours to 3d surface and wireframe plots :contours => false, # add contours to 3d surface and wireframe plots
:contour_labels => false,
:match_dimensions => false, # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196 :match_dimensions => false, # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196
# this ONLY effects whether or not the z-matrix is transposed for a heatmap display! # this ONLY effects whether or not the z-matrix is transposed for a heatmap display!
:subplot => :auto, # which subplot(s) does this series belong to? :subplot => :auto, # which subplot(s) does this series belong to?
@ -209,6 +280,7 @@ const _series_defaults = KW(
:primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow :primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow
# one logical series to be broken up (path and markers, for example) # one logical series to be broken up (path and markers, for example)
:hover => nothing, # text to display when hovering over the data points :hover => nothing, # text to display when hovering over the data points
:stride => (1,1), # array stride for wireframe/surface, the first element is the row stride and the second is the column stride.
) )
@ -217,6 +289,7 @@ const _plot_defaults = KW(
:background_color => colorant"white", # default for all backgrounds, :background_color => colorant"white", # default for all backgrounds,
:background_color_outside => :match, # background outside grid, :background_color_outside => :match, # background outside grid,
:foreground_color => :auto, # default for all foregrounds, and title color, :foreground_color => :auto, # default for all foregrounds, and title color,
:fontfamily => "sans-serif",
:size => (600,400), :size => (600,400),
:pos => (0,0), :pos => (0,0),
:window_title => "Plots.jl", :window_title => "Plots.jl",
@ -228,6 +301,7 @@ const _plot_defaults = KW(
:inset_subplots => nothing, # optionally pass a vector of (parent,bbox) tuples which are :inset_subplots => nothing, # optionally pass a vector of (parent,bbox) tuples which are
# the parent layout and the relative bounding box of inset subplots # the parent layout and the relative bounding box of inset subplots
:dpi => DPI, # dots per inch for images, etc :dpi => DPI, # dots per inch for images, etc
:thickness_scaling => 1,
:display_type => :auto, :display_type => :auto,
:extra_kwargs => KW(), :extra_kwargs => KW(),
) )
@ -236,20 +310,30 @@ const _plot_defaults = KW(
const _subplot_defaults = KW( const _subplot_defaults = KW(
:title => "", :title => "",
:title_location => :center, # also :left or :right :title_location => :center, # also :left or :right
:titlefont => font(14), :fontfamily_subplot => :match,
:titlefontfamily => :match,
:titlefontsize => 14,
:titlefonthalign => :hcenter,
:titlefontvalign => :vcenter,
:titlefontrotation => 0.0,
:titlefontcolor => :match,
:background_color_subplot => :match, # default for other bg colors... match takes plot default :background_color_subplot => :match, # default for other bg colors... match takes plot default
:background_color_legend => :match, # background of legend :background_color_legend => :match, # background of legend
:background_color_inside => :match, # background inside grid :background_color_inside => :match, # background inside grid
:foreground_color_subplot => :match, # default for other fg colors... match takes plot default :foreground_color_subplot => :match, # default for other fg colors... match takes plot default
:foreground_color_legend => :match, # foreground of legend :foreground_color_legend => :match, # foreground of legend
:foreground_color_grid => :match, # grid color
:foreground_color_title => :match, # title color :foreground_color_title => :match, # title color
:color_palette => :auto, :color_palette => :auto,
:legend => :best, :legend => :best,
:legendtitle => nothing,
:colorbar => :legend, :colorbar => :legend,
:clims => :auto, :clims => :auto,
:legendfont => font(8), :legendfontfamily => :match,
:grid => true, :legendfontsize => 8,
:legendfonthalign => :hcenter,
:legendfontvalign => :vcenter,
:legendfontrotation => 0.0,
:legendfontcolor => :match,
:annotations => [], # annotation tuples... list of (x,y,annotation) :annotations => [], # annotation tuples... list of (x,y,annotation)
:projection => :none, # can also be :polar or :3d :projection => :none, # can also be :polar or :3d
:aspect_ratio => :none, # choose from :none or :equal :aspect_ratio => :none, # choose from :none or :equal
@ -260,6 +344,8 @@ const _subplot_defaults = KW(
:bottom_margin => :match, :bottom_margin => :match,
:subplot_index => -1, :subplot_index => -1,
:colorbar_title => "", :colorbar_title => "",
:framestyle => :axes,
:camera => (30,30),
) )
const _axis_defaults = KW( const _axis_defaults = KW(
@ -270,8 +356,18 @@ const _axis_defaults = KW(
:rotation => 0, :rotation => 0,
:flip => false, :flip => false,
:link => [], :link => [],
:tickfont => font(8), :tickfontfamily => :match,
:guidefont => font(11), :tickfontsize => 8,
:tickfonthalign => :hcenter,
:tickfontvalign => :vcenter,
:tickfontrotation => 0.0,
:tickfontcolor => :match,
:guidefontfamily => :match,
:guidefontsize => 11,
:guidefonthalign => :hcenter,
:guidefontvalign => :vcenter,
:guidefontrotation => 0.0,
:guidefontcolor => :match,
:foreground_color_axis => :match, # axis border/tick colors, :foreground_color_axis => :match, # axis border/tick colors,
:foreground_color_border => :match, # plot area border/spines, :foreground_color_border => :match, # plot area border/spines,
:foreground_color_text => :match, # tick text color, :foreground_color_text => :match, # tick text color,
@ -279,6 +375,14 @@ const _axis_defaults = KW(
:discrete_values => [], :discrete_values => [],
:formatter => :auto, :formatter => :auto,
:mirror => false, :mirror => false,
:grid => true,
:foreground_color_grid => :match, # grid color
:gridalpha => 0.1,
:gridstyle => :solid,
:gridlinewidth => 0.5,
:tick_direction => :in,
:showaxis => true,
:widen => true,
) )
const _suppress_warnings = Set{Symbol}([ const _suppress_warnings = Set{Symbol}([
@ -330,6 +434,15 @@ const _all_defaults = KW[
_axis_defaults_byletter _axis_defaults_byletter
] ]
const _initial_defaults = deepcopy(_all_defaults)
const _initial_axis_defaults = deepcopy(_axis_defaults)
# to be able to reset font sizes to initial values
const _initial_fontsizes = Dict(:titlefontsize => _subplot_defaults[:titlefontsize],
:legendfontsize => _subplot_defaults[:legendfontsize],
:tickfontsize => _axis_defaults[:tickfontsize],
:guidefontsize => _axis_defaults[:guidefontsize])
const _all_args = sort(collect(union(map(keys, _all_defaults)...))) const _all_args = sort(collect(union(map(keys, _all_defaults)...)))
RecipesBase.is_key_supported(k::Symbol) = is_attr_supported(k) RecipesBase.is_key_supported(k::Symbol) = is_attr_supported(k)
@ -390,7 +503,7 @@ add_aliases(:foreground_color_title, :fg_title, :fgtitle, :fgcolor_title, :fg_co
add_aliases(:foreground_color_axis, :fg_axis, :fgaxis, :fgcolor_axis, :fg_color_axis, :foreground_axis, add_aliases(:foreground_color_axis, :fg_axis, :fgaxis, :fgcolor_axis, :fg_color_axis, :foreground_axis,
:foreground_colour_axis, :fgcolour_axis, :fg_colour_axis, :axiscolor) :foreground_colour_axis, :fgcolour_axis, :fg_colour_axis, :axiscolor)
add_aliases(:foreground_color_border, :fg_border, :fgborder, :fgcolor_border, :fg_color_border, :foreground_border, add_aliases(:foreground_color_border, :fg_border, :fgborder, :fgcolor_border, :fg_color_border, :foreground_border,
:foreground_colour_border, :fgcolour_border, :fg_colour_border, :bordercolor, :border) :foreground_colour_border, :fgcolour_border, :fg_colour_border, :bordercolor)
add_aliases(:foreground_color_text, :fg_text, :fgtext, :fgcolor_text, :fg_color_text, :foreground_text, add_aliases(:foreground_color_text, :fg_text, :fgtext, :fgcolor_text, :fg_color_text, :foreground_text,
:foreground_colour_text, :fgcolour_text, :fg_colour_text, :textcolor) :foreground_colour_text, :fgcolour_text, :fg_colour_text, :textcolor)
add_aliases(:foreground_color_guide, :fg_guide, :fgguide, :fgcolor_guide, :fg_color_guide, :foreground_guide, add_aliases(:foreground_color_guide, :fg_guide, :fgguide, :fgcolor_guide, :fg_color_guide, :foreground_guide,
@ -402,6 +515,7 @@ add_aliases(:linealpha, :la, :lalpha, :lα, :lineopacity, :lopacity)
add_aliases(:markeralpha, :ma, :malpha, :mα, :markeropacity, :mopacity) add_aliases(:markeralpha, :ma, :malpha, :mα, :markeropacity, :mopacity)
add_aliases(:markerstrokealpha, :msa, :msalpha, :msα, :markerstrokeopacity, :msopacity) add_aliases(:markerstrokealpha, :msa, :msalpha, :msα, :markerstrokeopacity, :msopacity)
add_aliases(:fillalpha, :fa, :falpha, :fα, :fillopacity, :fopacity) add_aliases(:fillalpha, :fa, :falpha, :fα, :fillopacity, :fopacity)
add_aliases(:gridalpha, :ga, :galpha, :gα, :gridopacity, :gopacity)
# series attributes # series attributes
add_aliases(:seriestype, :st, :t, :typ, :linetype, :lt) add_aliases(:seriestype, :st, :t, :typ, :linetype, :lt)
@ -434,6 +548,7 @@ add_aliases(:zticks, :ztick)
add_aliases(:zrotation, :zrot, :zr) add_aliases(:zrotation, :zrot, :zr)
add_aliases(:fill_z, :fillz, :fz, :surfacecolor, :surfacecolour, :sc, :surfcolor, :surfcolour) add_aliases(:fill_z, :fillz, :fz, :surfacecolor, :surfacecolour, :sc, :surfcolor, :surfcolour)
add_aliases(:legend, :leg, :key) add_aliases(:legend, :leg, :key)
add_aliases(:legendtitle, :legend_title, :labeltitle, :label_title, :leg_title, :key_title)
add_aliases(:colorbar, :cb, :cbar, :colorkey) add_aliases(:colorbar, :cb, :cbar, :colorkey)
add_aliases(:clims, :clim, :cbarlims, :cbar_lims, :climits, :color_limits) add_aliases(:clims, :clim, :cbarlims, :cbar_lims, :climits, :color_limits)
add_aliases(:smooth, :regression, :reg) add_aliases(:smooth, :regression, :reg)
@ -445,7 +560,7 @@ add_aliases(:color_palette, :palette)
add_aliases(:overwrite_figure, :clf, :clearfig, :overwrite, :reuse) add_aliases(:overwrite_figure, :clf, :clearfig, :overwrite, :reuse)
add_aliases(:xerror, :xerr, :xerrorbar) add_aliases(:xerror, :xerr, :xerrorbar)
add_aliases(:yerror, :yerr, :yerrorbar, :err, :errorbar) add_aliases(:yerror, :yerr, :yerrorbar, :err, :errorbar)
add_aliases(:quiver, :velocity, :quiver2d, :gradient) add_aliases(:quiver, :velocity, :quiver2d, :gradient, :vectorfield)
add_aliases(:normalize, :norm, :normed, :normalized) add_aliases(:normalize, :norm, :normed, :normalized)
add_aliases(:aspect_ratio, :aspectratio, :axis_ratio, :axisratio, :ratio) add_aliases(:aspect_ratio, :aspectratio, :axis_ratio, :axisratio, :ratio)
add_aliases(:match_dimensions, :transpose, :transpose_z) add_aliases(:match_dimensions, :transpose, :transpose_z)
@ -456,7 +571,13 @@ add_aliases(:series_annotations, :series_ann, :seriesann, :series_anns, :seriesa
add_aliases(:html_output_format, :format, :fmt, :html_format) add_aliases(:html_output_format, :format, :fmt, :html_format)
add_aliases(:orientation, :direction, :dir) add_aliases(:orientation, :direction, :dir)
add_aliases(:inset_subplots, :inset, :floating) add_aliases(:inset_subplots, :inset, :floating)
add_aliases(:stride, :wirefame_stride, :surface_stride, :surf_str, :str)
add_aliases(:gridlinewidth, :gridwidth, :grid_linewidth, :grid_width, :gridlw, :grid_lw)
add_aliases(:gridstyle, :grid_style, :gridlinestyle, :grid_linestyle, :grid_ls, :gridls)
add_aliases(:framestyle, :frame_style, :frame, :axesstyle, :axes_style, :boxstyle, :box_style, :box, :borderstyle, :border_style, :border)
add_aliases(:tick_direction, :tickdirection, :tick_dir, :tickdir, :tick_orientation, :tickorientation, :tick_or, :tickor)
add_aliases(:camera, :cam, :viewangle, :view_angle)
add_aliases(:contour_labels, :contourlabels, :clabels, :clabs)
# add all pluralized forms to the _keyAliases dict # add all pluralized forms to the _keyAliases dict
for arg in keys(_series_defaults) for arg in keys(_series_defaults)
@ -475,7 +596,6 @@ end
`default(; kw...)` will set the current default value for each key/value pair `default(; kw...)` will set the current default value for each key/value pair
`default(d, key)` returns the key from d if it exists, otherwise `default(key)` `default(d, key)` returns the key from d if it exists, otherwise `default(key)`
""" """
function default(k::Symbol) function default(k::Symbol)
k = get(_keyAliases, k, k) k = get(_keyAliases, k, k)
for defaults in _all_defaults for defaults in _all_defaults
@ -505,6 +625,8 @@ function default(k::Symbol, v)
end end
function default(; kw...) function default(; kw...)
kw = KW(kw)
preprocessArgs!(kw)
for (k,v) in kw for (k,v) in kw
default(k, v) default(k, v)
end end
@ -514,7 +636,10 @@ function default(d::KW, k::Symbol)
get(d, k, default(k)) get(d, k, default(k))
end end
function reset_defaults()
foreach(merge!, _all_defaults, _initial_defaults)
merge!(_axis_defaults, _initial_axis_defaults)
end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -617,6 +742,9 @@ function processFillArg(d::KW, arg)
arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : plot_color(arg.color)) arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : plot_color(arg.color))
arg.alpha == nothing || (d[:fillalpha] = arg.alpha) arg.alpha == nothing || (d[:fillalpha] = arg.alpha)
elseif typeof(arg) <: Bool
d[:fillrange] = arg ? 0 : nothing
# fillrange function # fillrange function
elseif allFunctions(arg) elseif allFunctions(arg)
d[:fillrange] = arg d[:fillrange] = arg
@ -625,6 +753,10 @@ function processFillArg(d::KW, arg)
elseif allAlphas(arg) elseif allAlphas(arg)
d[:fillalpha] = arg d[:fillalpha] = arg
# fillrange provided as vector or number
elseif typeof(arg) <: Union{AbstractArray{<:Real}, Real}
d[:fillrange] = arg
elseif !handleColors!(d, arg, :fillcolor) elseif !handleColors!(d, arg, :fillcolor)
d[:fillrange] = arg d[:fillrange] = arg
@ -633,6 +765,68 @@ function processFillArg(d::KW, arg)
return return
end end
function processGridArg!(d::KW, arg, letter)
if arg in _allGridArgs || isa(arg, Bool)
d[Symbol(letter, :grid)] = hasgrid(arg, letter)
elseif allStyles(arg)
d[Symbol(letter, :gridstyle)] = arg
elseif typeof(arg) <: Stroke
arg.width == nothing || (d[Symbol(letter, :gridlinewidth)] = arg.width)
arg.color == nothing || (d[Symbol(letter, :foreground_color_grid)] = arg.color in (:auto, :match) ? :match : plot_color(arg.color))
arg.alpha == nothing || (d[Symbol(letter, :gridalpha)] = arg.alpha)
arg.style == nothing || (d[Symbol(letter, :gridstyle)] = arg.style)
# linealpha
elseif allAlphas(arg)
d[Symbol(letter, :gridalpha)] = arg
# linewidth
elseif allReals(arg)
d[Symbol(letter, :gridlinewidth)] = arg
# color
elseif !handleColors!(d, arg, Symbol(letter, :foreground_color_grid))
warn("Skipped grid arg $arg.")
end
end
function processFontArg!(d::KW, fontname::Symbol, arg)
T = typeof(arg)
if T <: Font
d[Symbol(fontname, :family)] = arg.family
d[Symbol(fontname, :size)] = arg.pointsize
d[Symbol(fontname, :halign)] = arg.halign
d[Symbol(fontname, :valign)] = arg.valign
d[Symbol(fontname, :rotation)] = arg.rotation
d[Symbol(fontname, :color)] = arg.color
elseif arg == :center
d[Symbol(fontname, :halign)] = :hcenter
d[Symbol(fontname, :valign)] = :vcenter
elseif arg in (:hcenter, :left, :right)
d[Symbol(fontname, :halign)] = arg
elseif arg in (:vcenter, :top, :bottom)
d[Symbol(fontname, :valign)] = arg
elseif T <: Colorant
d[Symbol(fontname, :color)] = arg
elseif T <: Symbol || T <: AbstractString
try
d[Symbol(fontname, :color)] = parse(Colorant, string(arg))
catch
d[Symbol(fontname, :family)] = string(arg)
end
elseif typeof(arg) <: Integer
d[Symbol(fontname, :size)] = arg
elseif typeof(arg) <: Real
d[Symbol(fontname, :rotation)] = convert(Float64, arg)
else
warn("Skipped font arg: $arg ($(typeof(arg)))")
end
end
_replace_markershape(shape::Symbol) = get(_markerAliases, shape, shape) _replace_markershape(shape::Symbol) = get(_markerAliases, shape, shape)
_replace_markershape(shapes::AVec) = map(_replace_markershape, shapes) _replace_markershape(shapes::AVec) = map(_replace_markershape, shapes)
_replace_markershape(shape) = shape _replace_markershape(shape) = shape
@ -651,12 +845,13 @@ function preprocessArgs!(d::KW)
replaceAliases!(d, _keyAliases) replaceAliases!(d, _keyAliases)
# clear all axis stuff # clear all axis stuff
if haskey(d, :axis) && d[:axis] in (:none, nothing, false) # if haskey(d, :axis) && d[:axis] in (:none, nothing, false)
d[:ticks] = nothing # d[:ticks] = nothing
d[:foreground_color_border] = RGBA(0,0,0,0) # d[:foreground_color_border] = RGBA(0,0,0,0)
d[:grid] = false # d[:foreground_color_axis] = RGBA(0,0,0,0)
delete!(d, :axis) # d[:grid] = false
end # delete!(d, :axis)
# end
# for letter in (:x, :y, :z) # for letter in (:x, :y, :z)
# asym = Symbol(letter, :axis) # asym = Symbol(letter, :axis)
# if haskey(d, asym) || d[asym] in (:none, nothing, false) # if haskey(d, asym) || d[asym] in (:none, nothing, false)
@ -665,6 +860,13 @@ function preprocessArgs!(d::KW)
# end # end
# end # end
# handle axis args common to all axis
args = pop!(d, :axis, ())
for arg in wraptuple(args)
for letter in (:x, :y, :z)
process_axis_arg!(d, arg, letter)
end
end
# handle axis args # handle axis args
for letter in (:x, :y, :z) for letter in (:x, :y, :z)
asym = Symbol(letter, :axis) asym = Symbol(letter, :axis)
@ -676,6 +878,48 @@ function preprocessArgs!(d::KW)
end end
end end
# handle grid args common to all axes
args = pop!(d, :grid, ())
for arg in wraptuple(args)
for letter in (:x, :y, :z)
processGridArg!(d, arg, letter)
end
end
# handle individual axes grid args
for letter in (:x, :y, :z)
gridsym = Symbol(letter, :grid)
args = pop!(d, gridsym, ())
for arg in wraptuple(args)
processGridArg!(d, arg, letter)
end
end
# fonts
for fontname in (:titlefont, :legendfont)
args = pop!(d, fontname, ())
for arg in wraptuple(args)
processFontArg!(d, fontname, arg)
end
end
# handle font args common to all axes
for fontname in (:tickfont, :guidefont)
args = pop!(d, fontname, ())
for arg in wraptuple(args)
for letter in (:x, :y, :z)
processFontArg!(d, Symbol(letter, fontname), arg)
end
end
end
# handle individual axes font args
for letter in (:x, :y, :z)
for fontname in (:tickfont, :guidefont)
args = pop!(d, Symbol(letter, fontname), ())
for arg in wraptuple(args)
processFontArg!(d, Symbol(letter, fontname), arg)
end
end
end
# handle line args # handle line args
for arg in wraptuple(pop!(d, :line, ())) for arg in wraptuple(pop!(d, :line, ()))
processLineArg(d, arg) processLineArg(d, arg)
@ -694,6 +938,9 @@ function preprocessArgs!(d::KW)
delete!(d, :marker) delete!(d, :marker)
if haskey(d, :markershape) if haskey(d, :markershape)
d[:markershape] = _replace_markershape(d[:markershape]) d[:markershape] = _replace_markershape(d[:markershape])
if d[:markershape] == :none && d[:seriestype] in (:scatter, :scatterbins, :scatterhist, :scatter3d) #the default should be :auto, not :none, so that :none can be set explicitly and would be respected
d[:markershape] = :circle
end
elseif anymarker elseif anymarker
d[:markershape_to_add] = :circle # add it after _apply_recipe d[:markershape_to_add] = :circle # add it after _apply_recipe
end end
@ -737,6 +984,11 @@ function preprocessArgs!(d::KW)
d[:colorbar] = convertLegendValue(d[:colorbar]) d[:colorbar] = convertLegendValue(d[:colorbar])
end end
# framestyle
if haskey(d, :framestyle) && haskey(_framestyleAliases, d[:framestyle])
d[:framestyle] = _framestyleAliases[d[:framestyle]]
end
# warnings for moved recipes # warnings for moved recipes
st = get(d, :seriestype, :path) st = get(d, :seriestype, :path)
if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatPlots) if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatPlots)
@ -749,28 +1001,49 @@ end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
"A special type that will break up incoming data into groups, and allow for easier creation of grouped plots" "A special type that will break up incoming data into groups, and allow for easier creation of grouped plots"
type GroupBy mutable struct GroupBy
groupLabels::Vector # length == numGroups groupLabels::Vector # length == numGroups
groupIds::Vector{Vector{Int}} # list of indices for each group groupIds::Vector{Vector{Int}} # list of indices for each group
end end
# this is when given a vector-type of values to group by # this is when given a vector-type of values to group by
function extractGroupArgs(v::AVec, args...) function extractGroupArgs(v::AVec, args...; legendEntry = string)
groupLabels = sort(collect(unique(v))) groupLabels = sort(collect(unique(v)))
n = length(groupLabels) n = length(groupLabels)
if n > 100 if n > 100
warn("You created n=$n groups... Is that intended?") warn("You created n=$n groups... Is that intended?")
end end
groupIds = Vector{Int}[filter(i -> v[i] == glab, 1:length(v)) for glab in groupLabels] groupIds = Vector{Int}[filter(i -> v[i] == glab, 1:length(v)) for glab in groupLabels]
GroupBy(map(string, groupLabels), groupIds) GroupBy(map(legendEntry, groupLabels), groupIds)
end end
legendEntryFromTuple(ns::Tuple) = join(ns, ' ')
# this is when given a tuple of vectors of values to group by
function extractGroupArgs(vs::Tuple, args...)
isempty(vs) && return GroupBy([""], [1:size(args[1],1)])
v = map(tuple, vs...)
extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple)
end
# allow passing NamedTuples for a named legend entry
@require NamedTuples begin
legendEntryFromTuple(ns::NamedTuples.NamedTuple) =
join(["$k = $v" for (k, v) in zip(keys(ns), values(ns))], ", ")
function extractGroupArgs(vs::NamedTuples.NamedTuple, args...)
isempty(vs) && return GroupBy([""], [1:size(args[1],1)])
NT = eval(:(NamedTuples.@NT($(keys(vs)...)))){map(eltype, vs)...}
v = map(NT, vs...)
extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple)
end
end
# expecting a mapping of "group label" to "group indices" # expecting a mapping of "group label" to "group indices"
function extractGroupArgs{T, V<:AVec{Int}}(idxmap::Dict{T,V}, args...) function extractGroupArgs(idxmap::Dict{T,V}, args...) where {T, V<:AVec{Int}}
groupLabels = sortedkeys(idxmap) groupLabels = sortedkeys(idxmap)
groupIds = VecI[collect(idxmap[k]) for k in groupLabels] groupIds = Vector{Int}[collect(idxmap[k]) for k in groupLabels]
GroupBy(groupLabels, groupIds) GroupBy(groupLabels, groupIds)
end end
@ -851,7 +1124,7 @@ function convertLegendValue(val::Symbol)
:best :best
elseif val in (:no, :none) elseif val in (:no, :none)
:none :none
elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend, :topright, :topleft, :bottomleft, :bottomright) elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend, :topright, :topleft, :bottomleft, :bottomright, :outertopright)
val val
else else
error("Invalid symbol for legend: $val") error("Invalid symbol for legend: $val")
@ -859,7 +1132,7 @@ function convertLegendValue(val::Symbol)
end end
convertLegendValue(val::Bool) = val ? :best : :none convertLegendValue(val::Bool) = val ? :best : :none
convertLegendValue(val::Void) = :none convertLegendValue(val::Void) = :none
convertLegendValue{S<:Real, T<:Real}(v::Tuple{S,T}) = v convertLegendValue(v::Tuple{S,T}) where {S<:Real, T<:Real} = v
convertLegendValue(v::AbstractArray) = map(convertLegendValue, v) convertLegendValue(v::AbstractArray) = map(convertLegendValue, v)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -928,12 +1201,17 @@ const _match_map = KW(
:background_color_legend => :background_color_subplot, :background_color_legend => :background_color_subplot,
:background_color_inside => :background_color_subplot, :background_color_inside => :background_color_subplot,
:foreground_color_legend => :foreground_color_subplot, :foreground_color_legend => :foreground_color_subplot,
:foreground_color_grid => :foreground_color_subplot,
:foreground_color_title => :foreground_color_subplot, :foreground_color_title => :foreground_color_subplot,
:left_margin => :margin, :left_margin => :margin,
:top_margin => :margin, :top_margin => :margin,
:right_margin => :margin, :right_margin => :margin,
:bottom_margin => :margin, :bottom_margin => :margin,
:titlefontfamily => :fontfamily_subplot,
:legendfontfamily => :fontfamily_subplot,
:titlefontcolor => :foreground_color_subplot,
:legendfontcolor => :foreground_color_subplot,
:tickfontcolor => :foreground_color_text,
:guidefontcolor => :foreground_color_guide,
) )
# these can match values from the parent container (axis --> subplot --> plot) # these can match values from the parent container (axis --> subplot --> plot)
@ -942,8 +1220,12 @@ const _match_map2 = KW(
:foreground_color_subplot => :foreground_color, :foreground_color_subplot => :foreground_color,
:foreground_color_axis => :foreground_color_subplot, :foreground_color_axis => :foreground_color_subplot,
:foreground_color_border => :foreground_color_subplot, :foreground_color_border => :foreground_color_subplot,
:foreground_color_grid => :foreground_color_subplot,
:foreground_color_guide => :foreground_color_subplot, :foreground_color_guide => :foreground_color_subplot,
:foreground_color_text => :foreground_color_subplot, :foreground_color_text => :foreground_color_subplot,
:fontfamily_subplot => :fontfamily,
:tickfontfamily => :fontfamily_subplot,
:guidefontfamily => :fontfamily_subplot,
) )
# properly retrieve from plt.attr, passing `:match` to the correct key # properly retrieve from plt.attr, passing `:match` to the correct key
@ -1048,11 +1330,9 @@ end
function _update_subplot_periphery(sp::Subplot, anns::AVec) function _update_subplot_periphery(sp::Subplot, anns::AVec)
# extend annotations, and ensure we always have a (x,y,PlotText) tuple # extend annotations, and ensure we always have a (x,y,PlotText) tuple
newanns = vcat(anns, sp[:annotations]) newanns = []
for (i,ann) in enumerate(newanns) for ann in vcat(anns, sp[:annotations])
x,y,tmp = ann append!(newanns, process_annotation(sp, ann...))
ptxt = isa(tmp, PlotText) ? tmp : text(tmp)
newanns[i] = (x,y,ptxt)
end end
sp.attr[:annotations] = newanns sp.attr[:annotations] = newanns
@ -1076,7 +1356,6 @@ function _update_subplot_colors(sp::Subplot)
# foreground colors # foreground colors
color_or_nothing!(sp.attr, :foreground_color_subplot) color_or_nothing!(sp.attr, :foreground_color_subplot)
color_or_nothing!(sp.attr, :foreground_color_legend) color_or_nothing!(sp.attr, :foreground_color_legend)
color_or_nothing!(sp.attr, :foreground_color_grid)
color_or_nothing!(sp.attr, :foreground_color_title) color_or_nothing!(sp.attr, :foreground_color_title)
return return
end end
@ -1128,6 +1407,7 @@ function _update_axis_colors(axis::Axis)
color_or_nothing!(axis.d, :foreground_color_border) color_or_nothing!(axis.d, :foreground_color_border)
color_or_nothing!(axis.d, :foreground_color_guide) color_or_nothing!(axis.d, :foreground_color_guide)
color_or_nothing!(axis.d, :foreground_color_text) color_or_nothing!(axis.d, :foreground_color_text)
color_or_nothing!(axis.d, :foreground_color_grid)
return return
end end
@ -1164,24 +1444,26 @@ end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
has_black_border_for_default(st) = error("The seriestype attribute only accepts Symbols, you passed the $(typeof(st)) $st.")
has_black_border_for_default(st::Function) = error("The seriestype attribute only accepts Symbols, you passed the function $st.")
function has_black_border_for_default(st::Symbol) function has_black_border_for_default(st::Symbol)
like_histogram(st) || st in (:hexbin, :bar, :shape) like_histogram(st) || st in (:hexbin, :bar, :shape)
end end
# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically # converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically
function getSeriesRGBColor(c, α, sp::Subplot, n::Int) function getSeriesRGBColor(c, sp::Subplot, n::Int)
if c == :auto if c == :auto
c = autopick(sp[:color_palette], n) c = autopick(sp[:color_palette], n)
elseif isa(c, Int) elseif isa(c, Int)
c = autopick(sp[:color_palette], c) c = autopick(sp[:color_palette], c)
end end
plot_color(c, α) plot_color(c)
end end
function ensure_gradient!(d::KW, csym::Symbol, asym::Symbol) function ensure_gradient!(d::KW, csym::Symbol, asym::Symbol)
if !isa(d[csym], ColorGradient) if !isa(d[csym], ColorGradient)
d[csym] = cgrad(alpha = d[asym]) d[csym] = typeof(d[asym]) <: AbstractVector ? cgrad() : cgrad(alpha = d[asym])
end end
end end
@ -1193,26 +1475,19 @@ function _replace_linewidth(d::KW)
end end
function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int) function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
pkg = plt.backend
globalIndex = d[:series_plotindex]
# add default values to our dictionary, being careful not to delete what we just added! # add default values to our dictionary, being careful not to delete what we just added!
for (k,v) in _series_defaults for (k,v) in _series_defaults
slice_arg!(d, d, k, v, commandIndex, false) slice_arg!(d, d, k, v, commandIndex, false)
end end
# this is how many series belong to this subplot return d
# plotIndex = count(series -> series.d[:subplot] === sp && series.d[:primary], plt.series_list) end
plotIndex = 0
for series in sp.series_list
if series[:primary] function _update_series_attributes!(d::KW, plt::Plot, sp::Subplot)
plotIndex += 1 pkg = plt.backend
end globalIndex = d[:series_plotindex]
end plotIndex = _series_index(d, sp)
# plotIndex = count(series -> series[:primary], sp.series_list)
if get(d, :primary, true)
plotIndex += 1
end
aliasesAndAutopick(d, :linestyle, _styleAliases, supported_styles(pkg), plotIndex) aliasesAndAutopick(d, :linestyle, _styleAliases, supported_styles(pkg), plotIndex)
aliasesAndAutopick(d, :markershape, _markerAliases, supported_markers(pkg), plotIndex) aliasesAndAutopick(d, :markershape, _markerAliases, supported_markers(pkg), plotIndex)
@ -1228,39 +1503,46 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
end end
# update series color # update series color
d[:seriescolor] = getSeriesRGBColor(d[:seriescolor], d[:seriesalpha], sp, plotIndex) d[:seriescolor] = getSeriesRGBColor.(d[:seriescolor], sp, plotIndex)
# update other colors # update other colors
for s in (:line, :marker, :fill) for s in (:line, :marker, :fill)
csym, asym = Symbol(s,:color), Symbol(s,:alpha) csym, asym = Symbol(s,:color), Symbol(s,:alpha)
d[csym] = if d[csym] == :match d[csym] = if d[csym] == :auto
plot_color(if has_black_border_for_default(d[:seriestype]) && s == :line plot_color.(if has_black_border_for_default(d[:seriestype]) && s == :line
sp[:foreground_color_subplot] sp[:foreground_color_subplot]
else else
d[:seriescolor] d[:seriescolor]
end, d[asym]) end)
elseif d[csym] == :match
plot_color.(d[:seriescolor])
else else
getSeriesRGBColor(d[csym], d[asym], sp, plotIndex) getSeriesRGBColor.(d[csym], sp, plotIndex)
end end
end end
# update markerstrokecolor # update markerstrokecolor
d[:markerstrokecolor] = if d[:markerstrokecolor] == :match d[:markerstrokecolor] = if d[:markerstrokecolor] == :match
plot_color(sp[:foreground_color_subplot], d[:markerstrokealpha]) plot_color(sp[:foreground_color_subplot])
elseif d[:markerstrokecolor] == :auto
getSeriesRGBColor.(d[:markercolor], sp, plotIndex)
else else
getSeriesRGBColor(d[:markerstrokecolor], d[:markerstrokealpha], sp, plotIndex) getSeriesRGBColor.(d[:markerstrokecolor], sp, plotIndex)
end end
# if marker_z or line_z are set, ensure we have a gradient # if marker_z, fill_z or line_z are set, ensure we have a gradient
if d[:marker_z] != nothing if d[:marker_z] != nothing
ensure_gradient!(d, :markercolor, :markeralpha) ensure_gradient!(d, :markercolor, :markeralpha)
end end
if d[:line_z] != nothing if d[:line_z] != nothing
ensure_gradient!(d, :linecolor, :linealpha) ensure_gradient!(d, :linecolor, :linealpha)
end end
if d[:fill_z] != nothing
ensure_gradient!(d, :fillcolor, :fillalpha)
end
# scatter plots don't have a line, but must have a shape # scatter plots don't have a line, but must have a shape
if d[:seriestype] in (:scatter, :scatter3d) if d[:seriestype] in (:scatter, :scatterbins, :scatterhist, :scatter3d)
d[:linewidth] = 0 d[:linewidth] = 0
if d[:markershape] == :none if d[:markershape] == :none
d[:markershape] = :circle d[:markershape] = :circle
@ -1275,3 +1557,19 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int)
_replace_linewidth(d) _replace_linewidth(d)
d d
end end
function _series_index(d, sp)
idx = 0
for series in series_list(sp)
if series[:primary]
idx += 1
end
if series == d
return idx
end
end
if get(d, :primary, true)
idx += 1
end
return idx
end

View File

@ -70,13 +70,16 @@ function process_axis_arg!(d::KW, arg, letter = "")
elseif arg == nothing elseif arg == nothing
d[Symbol(letter,:ticks)] = [] d[Symbol(letter,:ticks)] = []
elseif T <: Bool || arg in _allShowaxisArgs
d[Symbol(letter,:showaxis)] = showaxis(arg, letter)
elseif typeof(arg) <: Number elseif typeof(arg) <: Number
d[Symbol(letter,:rotation)] = arg d[Symbol(letter,:rotation)] = arg
elseif typeof(arg) <: Function elseif typeof(arg) <: Function
d[Symbol(letter,:formatter)] = arg d[Symbol(letter,:formatter)] = arg
else elseif !handleColors!(d, arg, Symbol(letter, :foreground_color_axis))
warn("Skipped $(letter)axis arg $arg") warn("Skipped $(letter)axis arg $arg")
end end
@ -118,7 +121,7 @@ Base.show(io::IO, axis::Axis) = dumpdict(axis.d, "Axis", true)
# Base.getindex(axis::Axis, k::Symbol) = getindex(axis.d, k) # Base.getindex(axis::Axis, k::Symbol) = getindex(axis.d, k)
Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...) Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...)
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k) Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k)
Base.extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax)) ignorenan_extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
const _scale_funcs = Dict{Symbol,Function}( const _scale_funcs = Dict{Symbol,Function}(
@ -156,16 +159,52 @@ function optimal_ticks_and_labels(axis::Axis, ticks = nothing)
scale = axis[:scale] scale = axis[:scale]
sf = scalefunc(scale) sf = scalefunc(scale)
# If the axis input was a Date or DateTime use a special logic to find
# "round" Date(Time)s as ticks
# This bypasses the rest of optimal_ticks_and_labels, because
# optimize_datetime_ticks returns ticks AND labels: the label format (Date
# or DateTime) is chosen based on the time span between amin and amax
# rather than on the input format
# TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime
if ticks == nothing && scale == :identity
if axis[:formatter] == dateformatter
# optimize_datetime_ticks returns ticks and labels(!) based on
# integers/floats corresponding to the DateTime type. Thus, the axes
# limits, which resulted from converting the Date type to integers,
# are converted to 'DateTime integers' (actually floats) before
# being passed to optimize_datetime_ticks.
# (convert(Int, convert(DateTime, convert(Date, i))) == 87600000*i)
ticks, labels = optimize_datetime_ticks(864e5 * amin, 864e5 * amax;
k_min = 2, k_max = 4)
# Now the ticks are converted back to floats corresponding to Dates.
return ticks / 864e5, labels
elseif axis[:formatter] == datetimeformatter
return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4)
end
end
# get a list of well-laid-out ticks # get a list of well-laid-out ticks
scaled_ticks = if ticks == nothing if ticks == nothing
optimize_ticks( scaled_ticks = optimize_ticks(
sf(amin), sf(amin),
sf(amax); sf(amax);
k_min = 5, # minimum number of ticks k_min = 4, # minimum number of ticks
k_max = 8, # maximum number of ticks k_max = 8, # maximum number of ticks
)[1] )[1]
elseif typeof(ticks) <: Int
scaled_ticks, viewmin, viewmax = optimize_ticks(
sf(amin),
sf(amax);
k_min = ticks, # minimum number of ticks
k_max = ticks, # maximum number of ticks
k_ideal = ticks,
# `strict_span = false` rewards cases where the span of the
# chosen ticks is not too much bigger than amin - amax:
strict_span = false,
)
axis[:lims] = map(invscalefunc(scale), (viewmin, viewmax))
else else
map(sf, filter(t -> amin <= t <= amax, ticks)) scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks)))
end end
unscaled_ticks = map(invscalefunc(scale), scaled_ticks) unscaled_ticks = map(invscalefunc(scale), scaled_ticks)
@ -173,12 +212,20 @@ function optimal_ticks_and_labels(axis::Axis, ticks = nothing)
formatter = axis[:formatter] formatter = axis[:formatter]
if formatter == :auto if formatter == :auto
# the default behavior is to make strings of the scaled values and then apply the labelfunc # the default behavior is to make strings of the scaled values and then apply the labelfunc
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :auto))
elseif formatter == :plain
# Leave the numbers in plain format
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :plain)) map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, :plain))
elseif formatter == :scientific elseif formatter == :scientific
Showoff.showoff(unscaled_ticks, :scientific) Showoff.showoff(unscaled_ticks, :scientific)
else else
# there was an override for the formatter... use that on the unscaled ticks # there was an override for the formatter... use that on the unscaled ticks
map(formatter, unscaled_ticks) map(formatter, unscaled_ticks)
# if the formatter left us with numbers, still apply the default formatter
# However it leave us with the problem of unicode number decoding by the backend
# if eltype(unscaled_ticks) <: Number
# Showoff.showoff(unscaled_ticks, :auto)
# end
end end
else else
# no finite ticks to show... # no finite ticks to show...
@ -192,20 +239,39 @@ end
# return (continuous_values, discrete_values) for the ticks on this axis # return (continuous_values, discrete_values) for the ticks on this axis
function get_ticks(axis::Axis) function get_ticks(axis::Axis)
ticks = axis[:ticks] ticks = _transform_ticks(axis[:ticks])
ticks in (nothing, false) && return nothing ticks in (nothing, false) && return nothing
# treat :native ticks as :auto
ticks = ticks == :native ? :auto : ticks
dvals = axis[:discrete_values] dvals = axis[:discrete_values]
cv, dv = if !isempty(dvals) && ticks == :auto cv, dv = if typeof(ticks) <: Symbol
# discrete ticks... if !isempty(dvals)
axis[:continuous_values], dvals # discrete ticks...
elseif ticks == :auto n = length(dvals)
# compute optimal ticks and labels rng = if ticks == :auto
optimal_ticks_and_labels(axis) Int[round(Int,i) for i in linspace(1, n, 15)]
elseif typeof(ticks) <: AVec else # if ticks == :all
# override ticks, but get the labels 1:n
optimal_ticks_and_labels(axis, ticks) end
elseif typeof(ticks) <: NTuple{2} axis[:continuous_values][rng], dvals[rng]
elseif ispolar(axis.sps[1]) && axis[:letter] == :x
#force theta axis to be full circle
(collect(0:pi/4:7pi/4), string.(0:45:315))
else
# compute optimal ticks and labels
optimal_ticks_and_labels(axis)
end
elseif typeof(ticks) <: Union{AVec, Int}
if !isempty(dvals) && typeof(ticks) <: Int
rng = Int[round(Int,i) for i in linspace(1, length(dvals), ticks)]
axis[:continuous_values][rng], dvals[rng]
else
# override ticks, but get the labels
optimal_ticks_and_labels(axis, ticks)
end
elseif typeof(ticks) <: NTuple{2, Any}
# assuming we're passed (ticks, labels) # assuming we're passed (ticks, labels)
ticks ticks
else else
@ -213,15 +279,13 @@ function get_ticks(axis::Axis)
end end
# @show ticks dvals cv dv # @show ticks dvals cv dv
# TODO: better/smarter cutoff values for sampling ticks return cv, dv
if length(cv) > 30
rng = Int[round(Int,i) for i in linspace(1, length(cv), 15)]
cv[rng], dv[rng]
else
cv, dv
end
end end
_transform_ticks(ticks) = ticks
_transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(ticks)
_transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2])
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@ -236,8 +300,8 @@ end
function expand_extrema!(ex::Extrema, v::Number) function expand_extrema!(ex::Extrema, v::Number)
ex.emin = min(v, ex.emin) ex.emin = isfinite(v) ? min(v, ex.emin) : ex.emin
ex.emax = max(v, ex.emax) ex.emax = isfinite(v) ? max(v, ex.emax) : ex.emax
ex ex
end end
@ -250,13 +314,13 @@ expand_extrema!(axis::Axis, ::Void) = axis[:extrema]
expand_extrema!(axis::Axis, ::Bool) = axis[:extrema] expand_extrema!(axis::Axis, ::Bool) = axis[:extrema]
function expand_extrema!{MIN<:Number,MAX<:Number}(axis::Axis, v::Tuple{MIN,MAX}) function expand_extrema!(axis::Axis, v::Tuple{MIN,MAX}) where {MIN<:Number,MAX<:Number}
ex = axis[:extrema] ex = axis[:extrema]
ex.emin = min(v[1], ex.emin) ex.emin = isfinite(v[1]) ? min(v[1], ex.emin) : ex.emin
ex.emax = max(v[2], ex.emax) ex.emax = isfinite(v[2]) ? max(v[2], ex.emax) : ex.emax
ex ex
end end
function expand_extrema!{N<:Number}(axis::Axis, v::AVec{N}) function expand_extrema!(axis::Axis, v::AVec{N}) where N<:Number
ex = axis[:extrema] ex = axis[:extrema]
for vi in v for vi in v
expand_extrema!(ex, vi) expand_extrema!(ex, vi)
@ -275,6 +339,9 @@ function expand_extrema!(sp::Subplot, d::KW)
else else
letter == :x ? :y : letter == :y ? :x : :z letter == :x ? :y : letter == :y ? :x : :z
end] end]
if letter != :z && d[:seriestype] == :straightline && any(series[:seriestype] != :straightline for series in series_list(sp)) && data[1] != data[2]
data = [NaN]
end
axis = sp[Symbol(letter, "axis")] axis = sp[Symbol(letter, "axis")]
if isa(data, Volume) if isa(data, Volume)
@ -307,7 +374,7 @@ function expand_extrema!(sp::Subplot, d::KW)
if fr == nothing && d[:seriestype] == :bar if fr == nothing && d[:seriestype] == :bar
fr = 0.0 fr = 0.0
end end
if fr != nothing if fr != nothing && !all3D(d)
axis = sp.attr[vert ? :yaxis : :xaxis] axis = sp.attr[vert ? :yaxis : :xaxis]
if typeof(fr) <: Tuple if typeof(fr) <: Tuple
for fri in fr for fri in fr
@ -325,13 +392,22 @@ function expand_extrema!(sp::Subplot, d::KW)
bw = d[:bar_width] bw = d[:bar_width]
if bw == nothing if bw == nothing
bw = d[:bar_width] = mean(diff(data)) bw = d[:bar_width] = _bar_width * ignorenan_minimum(filter(x->x>0,diff(sort(data))))
end end
axis = sp.attr[Symbol(dsym, :axis)] axis = sp.attr[Symbol(dsym, :axis)]
expand_extrema!(axis, maximum(data) + 0.5maximum(bw)) expand_extrema!(axis, ignorenan_maximum(data) + 0.5maximum(bw))
expand_extrema!(axis, minimum(data) - 0.5minimum(bw)) expand_extrema!(axis, ignorenan_minimum(data) - 0.5minimum(bw))
end end
# expand for heatmaps
if d[:seriestype] == :heatmap
for letter in (:x, :y)
data = d[letter]
axis = sp[Symbol(letter, "axis")]
scale = get(d, Symbol(letter, "scale"), :identity)
expand_extrema!(axis, heatmap_edges(data, scale))
end
end
end end
function expand_extrema!(sp::Subplot, xmin, xmax, ymin, ymax) function expand_extrema!(sp::Subplot, xmin, xmax, ymin, ymax)
@ -342,21 +418,23 @@ end
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# push the limits out slightly # push the limits out slightly
function widen(lmin, lmax) function widen(lmin, lmax, scale = :identity)
span = lmax - lmin f, invf = scalefunc(scale), invscalefunc(scale)
# eps = max(1e-16, min(1e-2span, 1e-10)) span = f(lmax) - f(lmin)
eps = max(1e-16, 0.03span) # eps = NaNMath.max(1e-16, min(1e-2span, 1e-10))
lmin-eps, lmax+eps eps = NaNMath.max(1e-16, 0.03span)
invf(f(lmin)-eps), invf(f(lmax)+eps)
end end
# figure out if widening is a good idea. if there's a scale set it's too tricky, # figure out if widening is a good idea.
# so lazy out and don't widen const _widen_seriestypes = (:line, :path, :steppre, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d)
function default_should_widen(axis::Axis) function default_should_widen(axis::Axis)
should_widen = false should_widen = false
if axis[:scale] == :identity && !is_2tuple(axis[:lims]) if !is_2tuple(axis[:lims])
for sp in axis.sps for sp in axis.sps
for series in series_list(sp) for series in series_list(sp)
if series.d[:seriestype] in (:scatter,) || series.d[:markershape] != :none if series.d[:seriestype] in _widen_seriestypes
should_widen = true should_widen = true
end end
end end
@ -365,6 +443,13 @@ function default_should_widen(axis::Axis)
should_widen should_widen
end end
function round_limits(amin,amax)
scale = 10^(1-round(log10(amax - amin)))
amin = floor(amin*scale)/scale
amax = ceil(amax*scale)/scale
amin, amax
end
# using the axis extrema and limit overrides, return the min/max value for this axis # using the axis extrema and limit overrides, return the min/max value for this axis
function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis)) function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis))
ex = axis[:extrema] ex = axis[:extrema]
@ -384,8 +469,19 @@ function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis)
if !isfinite(amin) && !isfinite(amax) if !isfinite(amin) && !isfinite(amax)
amin, amax = 0.0, 1.0 amin, amax = 0.0, 1.0
end end
if should_widen if ispolar(axis.sps[1])
widen(amin, amax) if axis[:letter] == :x
amin, amax = 0, 2pi
elseif lims == :auto
#widen max radius so ticks dont overlap with theta axis
amin, amax + 0.1 * abs(amax - amin)
else
amin, amax
end
elseif should_widen && axis[:widen]
widen(amin, amax, axis[:scale])
elseif lims == :round
round_limits(amin,amax)
else else
amin, amax amin, amax
end end
@ -401,7 +497,7 @@ function discrete_value!(axis::Axis, dv)
# @show axis[:discrete_map], axis[:discrete_values], dv # @show axis[:discrete_map], axis[:discrete_values], dv
if cv_idx == -1 if cv_idx == -1
ex = axis[:extrema] ex = axis[:extrema]
cv = max(0.5, ex.emax + 1.0) cv = NaNMath.max(0.5, ex.emax + 1.0)
expand_extrema!(axis, cv) expand_extrema!(axis, cv)
push!(axis[:discrete_values], dv) push!(axis[:discrete_values], dv)
push!(axis[:continuous_values], cv) push!(axis[:continuous_values], cv)
@ -466,38 +562,94 @@ function axis_drawing_info(sp::Subplot)
ymin, ymax = axis_limits(yaxis) ymin, ymax = axis_limits(yaxis)
xticks = get_ticks(xaxis) xticks = get_ticks(xaxis)
yticks = get_ticks(yaxis) yticks = get_ticks(yaxis)
spine_segs = Segments(2) xaxis_segs = Segments(2)
grid_segs = Segments(2) yaxis_segs = Segments(2)
xtick_segs = Segments(2)
ytick_segs = Segments(2)
xgrid_segs = Segments(2)
ygrid_segs = Segments(2)
xborder_segs = Segments(2)
yborder_segs = Segments(2)
if !(xaxis[:ticks] in (nothing, false)) if sp[:framestyle] != :none
f = scalefunc(yaxis[:scale]) # xaxis
invf = invscalefunc(yaxis[:scale]) if xaxis[:showaxis]
t1 = invf(f(ymin) + 0.015*(f(ymax)-f(ymin))) if sp[:framestyle] != :grid
t2 = invf(f(ymax) - 0.015*(f(ymax)-f(ymin))) y1, y2 = if sp[:framestyle] in (:origin, :zerolines)
0.0, 0.0
else
xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax)
end
push!(xaxis_segs, (xmin, y1), (xmax, y1))
# don't show the 0 tick label for the origin framestyle
if sp[:framestyle] == :origin && !(xticks in (nothing,false)) && length(xticks) > 1
showticks = xticks[1] .!= 0
xticks = (xticks[1][showticks], xticks[2][showticks])
end
end
sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine
end
if !(xaxis[:ticks] in (nothing, false))
f = scalefunc(yaxis[:scale])
invf = invscalefunc(yaxis[:scale])
ticks_in = xaxis[:tick_direction] == :out ? -1 : 1
t1 = invf(f(ymin) + 0.015 * (f(ymax) - f(ymin)) * ticks_in)
t2 = invf(f(ymax) - 0.015 * (f(ymax) - f(ymin)) * ticks_in)
t3 = invf(f(0) + 0.015 * (f(ymax) - f(ymin)) * ticks_in)
push!(spine_segs, (xmin,ymin), (xmax,ymin)) # bottom spine for xtick in xticks[1]
# push!(spine_segs, (xmin,ymax), (xmax,ymax)) # top spine if xaxis[:showaxis]
for xtick in xticks[1] tick_start, tick_stop = if sp[:framestyle] == :origin
push!(spine_segs, (xtick, ymin), (xtick, t1)) # bottom tick (0, t3)
push!(grid_segs, (xtick, t1), (xtick, t2)) # vertical grid else
# push!(spine_segs, (xtick, ymax), (xtick, t2)) # top tick xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, t2) : (ymin, t1)
end
push!(xtick_segs, (xtick, tick_start), (xtick, tick_stop)) # bottom tick
end
# sp[:draw_axes_border] && push!(xaxis_segs, (xtick, ymax), (xtick, t2)) # top tick
xaxis[:grid] && push!(xgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid
end
end
# yaxis
if yaxis[:showaxis]
if sp[:framestyle] != :grid
x1, x2 = if sp[:framestyle] in (:origin, :zerolines)
0.0, 0.0
else
xor(yaxis[:mirror], xaxis[:flip]) ? (xmax, xmin) : (xmin, xmax)
end
push!(yaxis_segs, (x1, ymin), (x1, ymax))
# don't show the 0 tick label for the origin framestyle
if sp[:framestyle] == :origin && !(yticks in (nothing,false)) && length(yticks) > 1
showticks = yticks[1] .!= 0
yticks = (yticks[1][showticks], yticks[2][showticks])
end
end
sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine
end
if !(yaxis[:ticks] in (nothing, false))
f = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale])
ticks_in = yaxis[:tick_direction] == :out ? -1 : 1
t1 = invf(f(xmin) + 0.015 * (f(xmax) - f(xmin)) * ticks_in)
t2 = invf(f(xmax) - 0.015 * (f(xmax) - f(xmin)) * ticks_in)
t3 = invf(f(0) + 0.015 * (f(xmax) - f(xmin)) * ticks_in)
for ytick in yticks[1]
if yaxis[:showaxis]
tick_start, tick_stop = if sp[:framestyle] == :origin
(0, t3)
else
xor(yaxis[:mirror], xaxis[:flip]) ? (xmax, t2) : (xmin, t1)
end
push!(ytick_segs, (tick_start, ytick), (tick_stop, ytick)) # left tick
end
# sp[:draw_axes_border] && push!(yaxis_segs, (xmax, ytick), (t2, ytick)) # right tick
yaxis[:grid] && push!(ygrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid
end
end end
end end
if !(yaxis[:ticks] in (nothing, false)) xticks, yticks, xaxis_segs, yaxis_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xborder_segs, yborder_segs
f = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale])
t1 = invf(f(xmin) + 0.015*(f(xmax)-f(xmin)))
t2 = invf(f(xmax) - 0.015*(f(xmax)-f(xmin)))
push!(spine_segs, (xmin,ymin), (xmin,ymax)) # left spine
# push!(spine_segs, (xmax,ymin), (xmax,ymax)) # right spine
for ytick in yticks[1]
push!(spine_segs, (xmin, ytick), (t1, ytick)) # left tick
push!(grid_segs, (t1, ytick), (t2, ytick)) # horizontal grid
# push!(spine_segs, (xmax, ytick), (t2, ytick)) # right tick
end
end
xticks, yticks, spine_segs, grid_segs
end end

View File

@ -1,12 +1,15 @@
immutable NoBackend <: AbstractBackend end struct NoBackend <: AbstractBackend end
const _backendType = Dict{Symbol, DataType}(:none => NoBackend) const _backendType = Dict{Symbol, DataType}(:none => NoBackend)
const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none) const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none)
const _backends = Symbol[] const _backends = Symbol[]
const _initialized_backends = Set{Symbol}() const _initialized_backends = Set{Symbol}()
"Returns a list of supported backends"
backends() = _backends backends() = _backends
"Returns the name of the current backend"
backend_name() = CURRENT_BACKEND.sym backend_name() = CURRENT_BACKEND.sym
_backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym") _backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym")
@ -15,7 +18,7 @@ macro init_backend(s)
sym = Symbol(str) sym = Symbol(str)
T = Symbol(string(s) * "Backend") T = Symbol(string(s) * "Backend")
esc(quote esc(quote
immutable $T <: AbstractBackend end struct $T <: AbstractBackend end
export $sym export $sym
$sym(; kw...) = (default(; kw...); backend(Symbol($str))) $sym(; kw...) = (default(; kw...); backend(Symbol($str)))
backend_name(::$T) = Symbol($str) backend_name(::$T) = Symbol($str)
@ -48,8 +51,8 @@ _series_updated(plt::Plot, series::Series) = nothing
_before_layout_calcs(plt::Plot) = nothing _before_layout_calcs(plt::Plot) = nothing
title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt
guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt
"Returns the (width,height) of a text label." "Returns the (width,height) of a text label."
function text_size(lablen::Int, sz::Number, rot::Number = 0) function text_size(lablen::Int, sz::Number, rot::Number = 0)
@ -90,7 +93,7 @@ function tick_padding(axis::Axis)
# hgt # hgt
# get the height of the rotated label # get the height of the rotated label
text_size(longest_label, axis[:tickfont].pointsize, rot)[2] text_size(longest_label, axis[:tickfontsize], rot)[2]
end end
end end
@ -120,7 +123,7 @@ _update_plot_object(plt::Plot) = nothing
# --------------------------------------------------------- # ---------------------------------------------------------
type CurrentBackend mutable struct CurrentBackend
sym::Symbol sym::Symbol
pkg::AbstractBackend pkg::AbstractBackend
end end
@ -148,7 +151,7 @@ function pickDefaultBackend()
# the ordering/inclusion of this package list is my semi-arbitrary guess at # the ordering/inclusion of this package list is my semi-arbitrary guess at
# which one someone will want to use if they have the package installed...accounting for # which one someone will want to use if they have the package installed...accounting for
# features, speed, and robustness # features, speed, and robustness
for pkgstr in ("PyPlot", "GR", "PlotlyJS", "Immerse", "Gadfly", "UnicodePlots") for pkgstr in ("GR", "PyPlot", "PlotlyJS", "PGFPlots", "UnicodePlots", "InspectDR", "GLVisualize")
if Pkg.installed(pkgstr) != nothing if Pkg.installed(pkgstr) != nothing
return backend(Symbol(lowercase(pkgstr))) return backend(Symbol(lowercase(pkgstr)))
end end
@ -277,6 +280,7 @@ end
@init_backend GLVisualize @init_backend GLVisualize
@init_backend PGFPlots @init_backend PGFPlots
@init_backend InspectDR @init_backend InspectDR
@init_backend HDF5
# --------------------------------------------------------- # ---------------------------------------------------------

View File

@ -1,4 +1,4 @@
``#= #=
TODO TODO
* move all gl_ methods to GLPlot * move all gl_ methods to GLPlot
* integrate GLPlot UI * integrate GLPlot UI
@ -7,9 +7,12 @@ TODO
* polar plots * polar plots
* labes and axis * labes and axis
* fix units in all visuals (e.g dotted lines, marker scale, surfaces) * fix units in all visuals (e.g dotted lines, marker scale, surfaces)
* why is there so little unicode supported in the font!??!?
=# =#
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "glvisualize.jl"))
end
const _glvisualize_attr = merge_with_base_supported([ const _glvisualize_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend, :background_color_inside, :background_color_outside,
@ -21,11 +24,15 @@ const _glvisualize_attr = merge_with_base_supported([
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:fillrange, :fillcolor, :fillalpha, :fillrange, :fillcolor, :fillalpha,
:bins, :bar_width, :bar_edges, :bar_position, :bins, :bar_width, :bar_edges, :bar_position,
:title, :title_location, :titlefont, :title, :title_location,
:window_title, :window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation, :guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont, :titlefontsize, :titlefontcolor,
:grid, :legend, :colorbar, :legendfontsize, :legendfontcolor,
:tickfontsize,
:guidefontsize, :guidefontcolor,
:grid, :gridalpha, :gridstyle, :gridlinewidth,
:legend, :colorbar,
:marker_z, :marker_z,
:line_z, :line_z,
:levels, :levels,
@ -39,10 +46,12 @@ const _glvisualize_attr = merge_with_base_supported([
:clims, :clims,
:inset_subplots, :inset_subplots,
:dpi, :dpi,
:hover :hover,
:framestyle,
:tick_direction,
]) ])
const _glvisualize_seriestype = [ const _glvisualize_seriestype = [
:path, :shape, :path, :shape, :straightline,
:scatter, :hexbin, :scatter, :hexbin,
:bar, :boxplot, :bar, :boxplot,
:heatmap, :image, :volume, :heatmap, :image, :volume,
@ -59,7 +68,7 @@ const _glvisualize_scale = [:identity, :ln, :log2, :log10]
function _initialize_backend(::GLVisualizeBackend; kw...) function _initialize_backend(::GLVisualizeBackend; kw...)
@eval begin @eval begin
import GLVisualize, GeometryTypes, Reactive, GLAbstraction, GLWindow, Contour import GLVisualize, GeometryTypes, Reactive, GLAbstraction, GLWindow, Contour
import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0, GLNormalMesh, SimpleRectangle import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0, GLNormalMesh, SimpleRectangle, Point, Vec
import FileIO, Images import FileIO, Images
export GLVisualize export GLVisualize
import Reactive: Signal import Reactive: Signal
@ -67,10 +76,9 @@ function _initialize_backend(::GLVisualizeBackend; kw...)
import GLVisualize: visualize import GLVisualize: visualize
import Plots.GL import Plots.GL
import UnicodeFun import UnicodeFun
Plots.slice_arg(img::Images.AbstractImage, idx::Int) = img Plots.slice_arg(img::Matrix{C}, idx::Int) where {C<:Colorant} = img
is_marker_supported(::GLVisualizeBackend, shape::GLVisualize.AllPrimitives) = true is_marker_supported(::GLVisualizeBackend, shape::GLVisualize.AllPrimitives) = true
is_marker_supported{Img<:Images.AbstractImage}(::GLVisualizeBackend, shape::Union{Vector{Img}, Img}) = true is_marker_supported(::GLVisualizeBackend, shape::Union{Vector{Matrix{C}}, Matrix{C}}) where {C<:Colorant} = true
is_marker_supported{C<:Colorant}(::GLVisualizeBackend, shape::Union{Vector{Matrix{C}}, Matrix{C}}) = true
is_marker_supported(::GLVisualizeBackend, shape::Shape) = true is_marker_supported(::GLVisualizeBackend, shape::Shape) = true
const GL = Plots const GL = Plots
end end
@ -78,14 +86,9 @@ end
function add_backend_string(b::GLVisualizeBackend) function add_backend_string(b::GLVisualizeBackend)
""" """
For those incredibly brave souls who assume full responsibility for what happens next... if !Plots.is_installed("GLVisualize")
There's an easy way to get what you need for the GLVisualize backend to work (until Pkg3 is usable): Pkg.add("GLVisualize")
end
Pkg.clone("https://github.com/tbreloff/MetaPkg.jl")
using MetaPkg
meta_checkout("MetaGL")
See the MetaPkg readme for details...
""" """
end end
@ -99,46 +102,6 @@ end
# end # end
const _glplot_deletes = [] const _glplot_deletes = []
function close_child_signals!(screen)
for child in screen.children
for (k, s) in child.inputs
empty!(s.actions)
end
for (k, cam) in child.cameras
for f in fieldnames(cam)
s = getfield(cam, f)
if isa(s, Signal)
close(s, false)
end
end
end
empty!(child.cameras)
close_child_signals!(child)
end
return
end
function empty_screen!(screen)
if isempty(_glplot_deletes)
close_child_signals!(screen)
empty!(screen)
empty!(screen.cameras)
for (k, s) in screen.inputs
empty!(s.actions)
end
empty!(screen)
else
for del_signal in _glplot_deletes
push!(del_signal, true) # trigger delete
end
empty!(_glplot_deletes)
end
nothing
end
function poll_reactive()
# run_till_now blocks when message queue is empty!
Base.n_avail(Reactive._messages) > 0 && Reactive.run_till_now()
end
function get_plot_screen(list::Vector, name, result = []) function get_plot_screen(list::Vector, name, result = [])
for elem in list for elem in list
@ -155,38 +118,36 @@ function get_plot_screen(screen, name, result = [])
end end
function create_window(plt::Plot{GLVisualizeBackend}, visible) function create_window(plt::Plot{GLVisualizeBackend}, visible)
name = Symbol("Plots.jl") name = Symbol("__Plots.jl")
# make sure we have any screen open # make sure we have any screen open
if isempty(GLVisualize.get_screens()) if isempty(GLVisualize.get_screens())
# create a fresh, new screen # create a fresh, new screen
parent_screen = GLVisualize.glscreen( parent_screen = GLVisualize.glscreen(
"Plot", "Plots",
resolution = plt[:size], resolution = plt[:size],
visible = visible visible = visible
) )
@async GLWindow.waiting_renderloop(parent_screen) @async GLWindow.renderloop(parent_screen)
GLVisualize.add_screen(parent_screen)
end end
# now lets get ourselves a permanent Plotting screen # now lets get ourselves a permanent Plotting screen
plot_screens = get_plot_screen(GLVisualize.get_screens(), name) plot_screens = get_plot_screen(GLVisualize.current_screen(), name)
screen = if isempty(plot_screens) # no screen with `name` screen = if isempty(plot_screens) # no screen with `name`
parent = GLVisualize.current_screen() parent = GLVisualize.current_screen()
screen = GLWindow.Screen( screen = GLWindow.Screen(
parent, area = map(GLWindow.zeroposition, parent.area), parent, area = map(GLWindow.zeroposition, parent.area),
name = name name = name
) )
for (k, s) in screen.inputs # copy signals, so we can clean them up better
screen.inputs[k] = map(identity, s)
end
screen screen
elseif length(plot_screens) == 1 elseif length(plot_screens) == 1
plot_screens[1] plot_screens[1]
else else
# okay this is silly! Lets see if we can. There is an ID we could use # okay this is silly! Lets see if we can. There is an ID we could use
# will not be fine for more than 255 screens though -.-. # will not be fine for more than 255 screens though -.-.
error("multiple Plot screens. Please don't use any screen with the name Plots.jl") error("multiple Plot screens. Please don't use any screen with the name $name")
end end
# Since we own this window, we can do deep cleansing # Since we own this window, we can do deep cleansing
empty_screen!(screen) empty!(screen)
plt.o = screen plt.o = screen
GLWindow.set_visibility!(screen, visible) GLWindow.set_visibility!(screen, visible)
resize!(screen, plt[:size]...) resize!(screen, plt[:size]...)
@ -221,12 +182,12 @@ function gl_marker(shape)
shape shape
end end
function gl_marker(shape::Shape) function gl_marker(shape::Shape)
points = Point2f0[Vec{2,Float32}(p) for p in zip(shape.x, shape.y)] points = Point2f0[GeometryTypes.Vec{2, Float32}(p) for p in zip(shape.x, shape.y)]
bb = GeometryTypes.AABB(points) bb = GeometryTypes.AABB(points)
mini, maxi = minimum(bb), maximum(bb) mini, maxi = minimum(bb), maximum(bb)
w3 = maxi-mini w3 = maxi-mini
origin, width = Point2f0(mini[1], mini[2]), Point2f0(w3[1], w3[2]) origin, width = Point2f0(mini[1], mini[2]), Point2f0(w3[1], w3[2])
map!(p -> ((p - origin) ./ width) - 0.5f0, points) # normalize and center map!(p -> ((p - origin) ./ width) - 0.5f0, points, points) # normalize and center
GeometryTypes.GLNormalMesh(points) GeometryTypes.GLNormalMesh(points)
end end
# create a marker/shape type # create a marker/shape type
@ -260,13 +221,13 @@ function extract_limits(sp, d, kw_args)
nothing nothing
end end
to_vec{T <: FixedVector}(::Type{T}, vec::T) = vec to_vec(::Type{T}, vec::T) where {T <: StaticArrays.StaticVector} = vec
to_vec{T <: FixedVector}(::Type{T}, s::Number) = T(s) to_vec(::Type{T}, s::Number) where {T <: StaticArrays.StaticVector} = T(s)
to_vec{T <: FixedVector{2}}(::Type{T}, vec::FixedVector{3}) = T(vec[1], vec[2]) to_vec(::Type{T}, vec::StaticArrays.StaticVector{3}) where {T <: StaticArrays.StaticVector{2}} = T(vec[1], vec[2])
to_vec{T <: FixedVector{3}}(::Type{T}, vec::FixedVector{2}) = T(vec[1], vec[2], 0) to_vec(::Type{T}, vec::StaticArrays.StaticVector{2}) where {T <: StaticArrays.StaticVector{3}} = T(vec[1], vec[2], 0)
to_vec{T <: FixedVector}(::Type{T}, vecs::AbstractVector) = map(x-> to_vec(T, x), vecs) to_vec(::Type{T}, vecs::AbstractVector) where {T <: StaticArrays.StaticVector} = map(x-> to_vec(T, x), vecs)
function extract_marker(d, kw_args) function extract_marker(d, kw_args)
dim = Plots.is3d(d) ? 3 : 2 dim = Plots.is3d(d) ? 3 : 2
@ -321,15 +282,21 @@ end
function extract_surface(d) function extract_surface(d)
map(_extract_surface, (d[:x], d[:y], d[:z])) map(_extract_surface, (d[:x], d[:y], d[:z]))
end end
function topoints{P}(::Type{P}, array) function topoints(::Type{P}, array) where P
P[x for x in zip(array...)] [P(x) for x in zip(array...)]
end end
function extract_points(d) function extract_points(d)
dim = is3d(d) ? 3 : 2 dim = is3d(d) ? 3 : 2
array = (d[:x], d[:y], d[:z])[1:dim] array = if d[:seriestype] == :straightline
straightline_data(d)
elseif d[:seriestype] == :shape
shape_data(d)
else
(d[:x], d[:y], d[:z])[1:dim]
end
topoints(Point{dim, Float32}, array) topoints(Point{dim, Float32}, array)
end end
function make_gradient{C <: Colorant}(grad::Vector{C}) function make_gradient(grad::Vector{C}) where C <: Colorant
grad grad
end end
function make_gradient(grad::ColorGradient) function make_gradient(grad::ColorGradient)
@ -352,7 +319,7 @@ function extract_any_color(d, kw_args)
kw_args[:color_norm] = Vec2f0(clims) kw_args[:color_norm] = Vec2f0(clims)
end end
elseif clims == :auto elseif clims == :auto
kw_args[:color_norm] = Vec2f0(extrema(d[:y])) kw_args[:color_norm] = Vec2f0(ignorenan_extrema(d[:y]))
end end
end end
else else
@ -363,7 +330,7 @@ function extract_any_color(d, kw_args)
kw_args[:color_norm] = Vec2f0(clims) kw_args[:color_norm] = Vec2f0(clims)
end end
elseif clims == :auto elseif clims == :auto
kw_args[:color_norm] = Vec2f0(extrema(d[:y])) kw_args[:color_norm] = Vec2f0(ignorenan_extrema(d[:y]))
else else
error("Unsupported limits: $clims") error("Unsupported limits: $clims")
end end
@ -375,7 +342,7 @@ end
function extract_stroke(d, kw_args) function extract_stroke(d, kw_args)
extract_c(d, kw_args, :line) extract_c(d, kw_args, :line)
if haskey(d, :linewidth) if haskey(d, :linewidth)
kw_args[:thickness] = d[:linewidth] * 3 kw_args[:thickness] = Float32(d[:linewidth] * 3)
end end
end end
@ -384,7 +351,7 @@ function extract_color(d, sym)
end end
gl_color(c::PlotUtils.ColorGradient) = c.colors gl_color(c::PlotUtils.ColorGradient) = c.colors
gl_color{T<:Colorant}(c::Vector{T}) = c gl_color(c::Vector{T}) where {T<:Colorant} = c
gl_color(c::RGBA{Float32}) = c gl_color(c::RGBA{Float32}) = c
gl_color(c::Colorant) = RGBA{Float32}(c) gl_color(c::Colorant) = RGBA{Float32}(c)
@ -415,14 +382,14 @@ end
dist(a, b) = abs(a-b) dist(a, b) = abs(a-b)
mindist(x, a, b) = min(dist(a, x), dist(b, x)) mindist(x, a, b) = NaNMath.min(dist(a, x), dist(b, x))
function gappy(x, ps) function gappy(x, ps)
n = length(ps) n = length(ps)
x <= first(ps) && return first(ps) - x x <= first(ps) && return first(ps) - x
for j=1:(n-1) for j=1:(n-1)
p0 = ps[j] p0 = ps[j]
p1 = ps[min(j+1, n)] p1 = ps[NaNMath.min(j+1, n)]
if p0 <= x && p1 >= x if p0 <= x && p1 >= x
return mindist(x, p0, p1) * (isodd(j) ? 1 : -1) return mindist(x, p0, p1) * (isodd(j) ? 1 : -1)
end end
@ -443,7 +410,7 @@ function extract_linestyle(d, kw_args)
haskey(d, :linestyle) || return haskey(d, :linestyle) || return
ls = d[:linestyle] ls = d[:linestyle]
lw = d[:linewidth] lw = d[:linewidth]
kw_args[:thickness] = lw kw_args[:thickness] = Float32(lw)
if ls == :dash if ls == :dash
points = [0.0, lw, 2lw, 3lw, 4lw] points = [0.0, lw, 2lw, 3lw, 4lw]
insert_pattern!(points, kw_args) insert_pattern!(points, kw_args)
@ -530,7 +497,7 @@ function hover(to_hover, to_display, window)
end end
function extract_extrema(d, kw_args) function extract_extrema(d, kw_args)
xmin, xmax = extrema(d[:x]); ymin, ymax = extrema(d[:y]) xmin, xmax = ignorenan_extrema(d[:x]); ymin, ymax = ignorenan_extrema(d[:y])
kw_args[:primitive] = GeometryTypes.SimpleRectangle{Float32}(xmin, ymin, xmax-xmin, ymax-ymin) kw_args[:primitive] = GeometryTypes.SimpleRectangle{Float32}(xmin, ymin, xmax-xmin, ymax-ymin)
nothing nothing
end end
@ -557,7 +524,7 @@ function extract_colornorm(d, kw_args)
else else
d[:y] d[:y]
end end
kw_args[:color_norm] = Vec2f0(extrema(z)) kw_args[:color_norm] = Vec2f0(ignorenan_extrema(z))
kw_args[:intensity] = map(Float32, collect(z)) kw_args[:intensity] = map(Float32, collect(z))
end end
end end
@ -615,7 +582,7 @@ function draw_grid_lines(sp, grid_segs, thickness, style, model, color)
) )
d = Dict( d = Dict(
:linestyle => style, :linestyle => style,
:linewidth => thickness, :linewidth => Float32(thickness),
:linecolor => color :linecolor => color
) )
Plots.extract_linestyle(d, kw_args) Plots.extract_linestyle(d, kw_args)
@ -624,8 +591,10 @@ end
function align_offset(startpos, lastpos, atlas, rscale, font, align) function align_offset(startpos, lastpos, atlas, rscale, font, align)
xscale, yscale = GLVisualize.glyph_scale!('X', rscale) xscale, yscale = GLVisualize.glyph_scale!('X', rscale)
xmove = (lastpos-startpos)[1]+xscale xmove = (lastpos-startpos)[1] + xscale
if align == :top if isa(align, GeometryTypes.Vec)
return -Vec2f0(xmove, yscale) .* align
elseif align == :top
return -Vec2f0(xmove/2f0, yscale) return -Vec2f0(xmove/2f0, yscale)
elseif align == :right elseif align == :right
return -Vec2f0(xmove, yscale/2f0) return -Vec2f0(xmove, yscale/2f0)
@ -634,11 +603,6 @@ function align_offset(startpos, lastpos, atlas, rscale, font, align)
end end
end end
function align_offset(startpos, lastpos, atlas, rscale, font, align::Vec)
xscale, yscale = GLVisualize.glyph_scale!('X', rscale)
xmove = (lastpos-startpos)[1] + xscale
return -Vec2f0(xmove, yscale) .* align
end
function alignment2num(x::Symbol) function alignment2num(x::Symbol)
(x in (:hcenter, :vcenter)) && return 0.5 (x in (:hcenter, :vcenter)) && return 0.5
@ -654,10 +618,10 @@ end
pointsize(font) = font.pointsize * 2 pointsize(font) = font.pointsize * 2
function draw_ticks( function draw_ticks(
axis, ticks, isx, lims, m, text = "", axis, ticks, isx, isorigin, lims, m, text = "",
positions = Point2f0[], offsets=Vec2f0[] positions = Point2f0[], offsets=Vec2f0[]
) )
sz = pointsize(axis[:tickfont]) sz = pointsize(tickfont(axis))
atlas = GLVisualize.get_texture_atlas() atlas = GLVisualize.get_texture_atlas()
font = GLVisualize.defaultfont() font = GLVisualize.defaultfont()
@ -672,7 +636,11 @@ function draw_ticks(
for (cv, dv) in zip(ticks...) for (cv, dv) in zip(ticks...)
x, y = cv, lims[1] x, y = cv, lims[1]
xy = isx ? (x, y) : (y, x) xy = if isorigin
isx ? (x, 0) : (0, x)
else
isx ? (x, y) : (y, x)
end
_pos = m * GeometryTypes.Vec4f0(xy[1], xy[2], 0, 1) _pos = m * GeometryTypes.Vec4f0(xy[1], xy[2], 0, 1)
startpos = Point2f0(_pos[1], _pos[2]) - axis_gap startpos = Point2f0(_pos[1], _pos[2]) - axis_gap
str = string(dv) str = string(dv)
@ -686,7 +654,7 @@ function draw_ticks(
position = GLVisualize.calc_position(str, startpos, sz, font, atlas) position = GLVisualize.calc_position(str, startpos, sz, font, atlas)
offset = GLVisualize.calc_offset(str, sz, font, atlas) offset = GLVisualize.calc_offset(str, sz, font, atlas)
alignoff = align_offset(startpos, last(position), atlas, sz, font, align) alignoff = align_offset(startpos, last(position), atlas, sz, font, align)
map!(position) do pos map!(position, position) do pos
pos .+ alignoff pos .+ alignoff
end end
append!(positions, position) append!(positions, position)
@ -697,7 +665,7 @@ function draw_ticks(
text, positions, offsets text, positions, offsets
end end
function text(position, text, kw_args) function glvisualize_text(position, text, kw_args)
text_align = alignment2num(text.font) text_align = alignment2num(text.font)
startpos = Vec2f0(position) startpos = Vec2f0(position)
atlas = GLVisualize.get_texture_atlas() atlas = GLVisualize.get_texture_atlas()
@ -708,7 +676,7 @@ function text(position, text, kw_args)
offset = GLVisualize.calc_offset(text.str, rscale, font, atlas) offset = GLVisualize.calc_offset(text.str, rscale, font, atlas)
alignoff = align_offset(startpos, last(position), atlas, rscale, font, text_align) alignoff = align_offset(startpos, last(position), atlas, rscale, font, text_align)
map!(position) do pos map!(position, position) do pos
pos .+ alignoff pos .+ alignoff
end end
kw_args[:position] = position kw_args[:position] = position
@ -728,72 +696,122 @@ function text_model(font, pivot)
end end
end end
function gl_draw_axes_2d(sp::Plots.Subplot{Plots.GLVisualizeBackend}, model, area) function gl_draw_axes_2d(sp::Plots.Subplot{Plots.GLVisualizeBackend}, model, area)
xticks, yticks, spine_segs, grid_segs = Plots.axis_drawing_info(sp) xticks, yticks, xspine_segs, yspine_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xborder_segs, yborder_segs = Plots.axis_drawing_info(sp)
xaxis = sp[:xaxis]; yaxis = sp[:yaxis] xaxis = sp[:xaxis]; yaxis = sp[:yaxis]
c = Colors.color(Plots.gl_color(sp[:foreground_color_grid])) xgc = Colors.color(Plots.gl_color(xaxis[:foreground_color_grid]))
ygc = Colors.color(Plots.gl_color(yaxis[:foreground_color_grid]))
axis_vis = [] axis_vis = []
if sp[:grid] if xaxis[:grid]
grid = draw_grid_lines(sp, grid_segs, 1f0, :dot, model, RGBA(c, 0.3f0)) grid = draw_grid_lines(sp, xgrid_segs, xaxis[:gridlinewidth], xaxis[:gridstyle], model, RGBA(xgc, xaxis[:gridalpha]))
push!(axis_vis, grid) push!(axis_vis, grid)
end end
if alpha(xaxis[:foreground_color_border]) > 0 if yaxis[:grid]
spine = draw_grid_lines(sp, spine_segs, 1f0, :solid, model, RGBA(c, 1.0f0)) grid = draw_grid_lines(sp, ygrid_segs, yaxis[:gridlinewidth], yaxis[:gridstyle], model, RGBA(ygc, yaxis[:gridalpha]))
push!(axis_vis, grid)
end
xac = Colors.color(Plots.gl_color(xaxis[:foreground_color_axis]))
yac = Colors.color(Plots.gl_color(yaxis[:foreground_color_axis]))
if alpha(xaxis[:foreground_color_axis]) > 0
spine = draw_grid_lines(sp, xspine_segs, 1f0, :solid, model, RGBA(xac, 1.0f0))
push!(axis_vis, spine) push!(axis_vis, spine)
end end
if alpha(yaxis[:foreground_color_axis]) > 0
spine = draw_grid_lines(sp, yspine_segs, 1f0, :solid, model, RGBA(yac, 1.0f0))
push!(axis_vis, spine)
end
if sp[:framestyle] in (:zerolines, :grid)
if alpha(xaxis[:foreground_color_grid]) > 0
spine = draw_grid_lines(sp, xtick_segs, 1f0, :solid, model, RGBA(xgc, xaxis[:gridalpha]))
push!(axis_vis, spine)
end
if alpha(yaxis[:foreground_color_grid]) > 0
spine = draw_grid_lines(sp, ytick_segs, 1f0, :solid, model, RGBA(ygc, yaxis[:gridalpha]))
push!(axis_vis, spine)
end
else
if alpha(xaxis[:foreground_color_axis]) > 0
spine = draw_grid_lines(sp, xtick_segs, 1f0, :solid, model, RGBA(xac, 1.0f0))
push!(axis_vis, spine)
end
if alpha(yaxis[:foreground_color_axis]) > 0
spine = draw_grid_lines(sp, ytick_segs, 1f0, :solid, model, RGBA(yac, 1.0f0))
push!(axis_vis, spine)
end
end
fcolor = Plots.gl_color(xaxis[:foreground_color_axis]) fcolor = Plots.gl_color(xaxis[:foreground_color_axis])
xlim = Plots.axis_limits(xaxis) xlim = Plots.axis_limits(xaxis)
ylim = Plots.axis_limits(yaxis) ylim = Plots.axis_limits(yaxis)
if !(xaxis[:ticks] in (nothing, false, :none)) if !(xaxis[:ticks] in (nothing, false, :none)) && !(sp[:framestyle] == :none) && xaxis[:showaxis]
ticklabels = map(model) do m ticklabels = map(model) do m
mirror = xaxis[:mirror] mirror = xaxis[:mirror]
t, positions, offsets = draw_ticks(xaxis, xticks, true, ylim, m) t, positions, offsets = draw_ticks(xaxis, xticks, true, sp[:framestyle] == :origin, ylim, m)
mirror = xaxis[:mirror]
t, positions, offsets = draw_ticks(
yaxis, yticks, false, xlim, m,
t, positions, offsets
)
end end
kw_args = Dict{Symbol, Any}( kw_args = Dict{Symbol, Any}(
:position => map(x-> x[2], ticklabels), :position => map(x-> x[2], ticklabels),
:offset => map(last, ticklabels), :offset => map(last, ticklabels),
:color => fcolor, :color => fcolor,
:relative_scale => pointsize(xaxis[:tickfont]), :relative_scale => pointsize(tickfont(xaxis)),
:scale_primitive => false :scale_primitive => false
) )
push!(axis_vis, visualize(map(first, ticklabels), Style(:default), kw_args)) push!(axis_vis, visualize(map(first, ticklabels), Style(:default), kw_args))
end end
if !(yaxis[:ticks] in (nothing, false, :none)) && !(sp[:framestyle] == :none) && yaxis[:showaxis]
ticklabels = map(model) do m
mirror = yaxis[:mirror]
t, positions, offsets = draw_ticks(yaxis, yticks, false, sp[:framestyle] == :origin, xlim, m)
end
kw_args = Dict{Symbol, Any}(
:position => map(x-> x[2], ticklabels),
:offset => map(last, ticklabels),
:color => fcolor,
:relative_scale => pointsize(tickfont(xaxis)),
:scale_primitive => false
)
push!(axis_vis, visualize(map(first, ticklabels), Style(:default), kw_args))
end
xbc = Colors.color(Plots.gl_color(xaxis[:foreground_color_border]))
ybc = Colors.color(Plots.gl_color(yaxis[:foreground_color_border]))
intensity = sp[:framestyle] == :semi ? 0.5f0 : 1.0f0
if sp[:framestyle] in (:box, :semi)
xborder = draw_grid_lines(sp, xborder_segs, intensity, :solid, model, RGBA(xbc, intensity))
yborder = draw_grid_lines(sp, yborder_segs, intensity, :solid, model, RGBA(ybc, intensity))
push!(axis_vis, xborder, yborder)
end
area_w = GeometryTypes.widths(area) area_w = GeometryTypes.widths(area)
if sp[:title] != "" if sp[:title] != ""
tf = sp[:titlefont]; color = gl_color(sp[:foreground_color_title]) tf = titlefont(sp)
font = Plots.Font(tf.family, tf.pointsize, :hcenter, :top, tf.rotation, color) font = Plots.Font(tf.family, tf.pointsize, :hcenter, :top, tf.rotation, tf.color)
xy = Point2f0(area.w/2, area_w[2] + pointsize(tf)/2) xy = Point2f0(area.w/2, area_w[2] + pointsize(tf)/2)
kw = Dict(:model => text_model(font, xy), :scale_primitive => true) kw = Dict(:model => text_model(font, xy), :scale_primitive => true)
extract_font(font, kw) extract_font(font, kw)
t = PlotText(sp[:title], font) t = PlotText(sp[:title], font)
push!(axis_vis, text(xy, t, kw)) push!(axis_vis, glvisualize_text(xy, t, kw))
end end
if xaxis[:guide] != "" if xaxis[:guide] != ""
tf = xaxis[:guidefont]; color = gl_color(xaxis[:foreground_color_guide]) tf = guidefont(xaxis)
xy = Point2f0(area.w/2, - pointsize(tf)/2) xy = Point2f0(area.w/2, - pointsize(tf)/2)
font = Plots.Font(tf.family, tf.pointsize, :hcenter, :bottom, tf.rotation, color) font = Plots.Font(tf.family, tf.pointsize, :hcenter, :bottom, tf.rotation, tf.color)
kw = Dict(:model => text_model(font, xy), :scale_primitive => true) kw = Dict(:model => text_model(font, xy), :scale_primitive => true)
t = PlotText(xaxis[:guide], font) t = PlotText(xaxis[:guide], font)
extract_font(font, kw) extract_font(font, kw)
push!(axis_vis, text(xy, t, kw)) push!(axis_vis, glvisualize_text(xy, t, kw))
end end
if yaxis[:guide] != "" if yaxis[:guide] != ""
tf = yaxis[:guidefont]; color = gl_color(yaxis[:foreground_color_guide]) tf = guidefont(yaxis)
font = Plots.Font(tf.family, tf.pointsize, :hcenter, :top, 90f0, color) font = Plots.Font(tf.family, tf.pointsize, :hcenter, :top, 90f0, tf.color)
xy = Point2f0(-pointsize(tf)/2, area.h/2) xy = Point2f0(-pointsize(tf)/2, area.h/2)
kw = Dict(:model => text_model(font, xy), :scale_primitive=>true) kw = Dict(:model => text_model(font, xy), :scale_primitive=>true)
t = PlotText(yaxis[:guide], font) t = PlotText(yaxis[:guide], font)
extract_font(font, kw) extract_font(font, kw)
push!(axis_vis, text(xy, t, kw)) push!(axis_vis, glvisualize_text(xy, t, kw))
end end
axis_vis axis_vis
@ -829,9 +847,9 @@ function gl_bar(d, kw_args)
# compute half-width of bars # compute half-width of bars
bw = nothing bw = nothing
hw = if bw == nothing hw = if bw == nothing
mean(diff(x)) ignorenan_mean(diff(x))
else else
Float64[cycle(bw,i)*0.5 for i=1:length(x)] Float64[_cycle(bw,i)*0.5 for i=1:length(x)]
end end
# make fillto a vector... default fills to 0 # make fillto a vector... default fills to 0
@ -840,12 +858,12 @@ function gl_bar(d, kw_args)
fillto = 0 fillto = 0
end end
# create the bar shapes by adding x/y segments # create the bar shapes by adding x/y segments
positions, scales = Array(Point2f0, ny), Array(Vec2f0, ny) positions, scales = Array{Point2f0}(ny), Array{Vec2f0}(ny)
m = Reactive.value(kw_args[:model]) m = Reactive.value(kw_args[:model])
sx, sy = m[1,1], m[2,2] sx, sy = m[1,1], m[2,2]
for i=1:ny for i=1:ny
center = x[i] center = x[i]
hwi = abs(cycle(hw,i)); yi = y[i]; fi = cycle(fillto,i) hwi = abs(_cycle(hw,i)); yi = y[i]; fi = _cycle(fillto,i)
if Plots.isvertical(d) if Plots.isvertical(d)
sz = (hwi*sx, yi*sy) sz = (hwi*sx, yi*sy)
else else
@ -881,7 +899,7 @@ function gl_boxplot(d, kw_args)
sx, sy = m[1,1], m[2,2] sx, sy = m[1,1], m[2,2]
for (i,glabel) in enumerate(glabels) for (i,glabel) in enumerate(glabels)
# filter y # filter y
values = y[filter(i -> cycle(x,i) == glabel, 1:length(y))] values = y[filter(i -> _cycle(x,i) == glabel, 1:length(y))]
# compute quantiles # compute quantiles
q1,q2,q3,q4,q5 = quantile(values, linspace(0,1,5)) q1,q2,q3,q4,q5 = quantile(values, linspace(0,1,5))
# notch # notch
@ -894,7 +912,7 @@ function gl_boxplot(d, kw_args)
# make the shape # make the shape
center = Plots.discrete_value!(d[:subplot][:xaxis], glabel)[1] center = Plots.discrete_value!(d[:subplot][:xaxis], glabel)[1]
hw = d[:bar_width] == nothing ? Plots._box_halfwidth*2 : cycle(d[:bar_width], i) hw = d[:bar_width] == nothing ? Plots._box_halfwidth*2 : _cycle(d[:bar_width], i)
l, m, r = center - hw/2, center, center + hw/2 l, m, r = center - hw/2, center, center + hw/2
# internal nodes for notches # internal nodes for notches
@ -912,7 +930,7 @@ function gl_boxplot(d, kw_args)
end end
# change q1 and q5 to show outliers # change q1 and q5 to show outliers
# using maximum and minimum values inside the limits # using maximum and minimum values inside the limits
q1, q5 = extrema(inside) q1, q5 = ignorenan_extrema(inside)
end end
# Box # Box
if notch if notch
@ -991,9 +1009,9 @@ function scale_for_annotations!(series::Series, scaletype::Symbol = :pixels)
# we use baseshape to overwrite the markershape attribute # we use baseshape to overwrite the markershape attribute
# with a list of custom shapes for each # with a list of custom shapes for each
msw, msh = anns.scalefactor msw, msh = anns.scalefactor
offsets = Array(Vec2f0, length(anns.strs)) offsets = Array{Vec2f0}(length(anns.strs))
series[:markersize] = map(1:length(anns.strs)) do i series[:markersize] = map(1:length(anns.strs)) do i
str = cycle(anns.strs, i) str = _cycle(anns.strs, i)
# get the width and height of the string (in mm) # get the width and height of the string (in mm)
sw, sh = text_size(str, anns.font.pointsize) sw, sh = text_size(str, anns.font.pointsize)
@ -1090,7 +1108,7 @@ function _display(plt::Plot{GLVisualizeBackend}, visible = true)
kw_args[:stroke_width] = Float32(d[:linewidth]/100f0) kw_args[:stroke_width] = Float32(d[:linewidth]/100f0)
end end
vis = GL.gl_surface(x, y, z, kw_args) vis = GL.gl_surface(x, y, z, kw_args)
elseif (st in (:path, :path3d)) && d[:linewidth] > 0 elseif (st in (:path, :path3d, :straightline)) && d[:linewidth] > 0
kw = copy(kw_args) kw = copy(kw_args)
points = Plots.extract_points(d) points = Plots.extract_points(d)
extract_linestyle(d, kw) extract_linestyle(d, kw)
@ -1106,7 +1124,7 @@ function _display(plt::Plot{GLVisualizeBackend}, visible = true)
kw = copy(kw_args) kw = copy(kw_args)
fr = d[:fillrange] fr = d[:fillrange]
ps = if all(x-> x >= 0, diff(d[:x])) # if is monotonic ps = if all(x-> x >= 0, diff(d[:x])) # if is monotonic
vcat(points, Point2f0[(points[i][1], cycle(fr, i)) for i=length(points):-1:1]) vcat(points, Point2f0[(points[i][1], _cycle(fr, i)) for i=length(points):-1:1])
else else
points points
end end
@ -1141,8 +1159,7 @@ function _display(plt::Plot{GLVisualizeBackend}, visible = true)
vis = gl_bar(d, kw_args) vis = gl_bar(d, kw_args)
elseif st == :image elseif st == :image
extract_extrema(d, kw_args) extract_extrema(d, kw_args)
z = transpose_z(series, d[:z].surf, false) vis = GL.gl_image(d[:z].surf, kw_args)
vis = GL.gl_image(z, kw_args)
elseif st == :boxplot elseif st == :boxplot
extract_c(d, kw_args, :fill) extract_c(d, kw_args, :fill)
vis = gl_boxplot(d, kw_args) vis = gl_boxplot(d, kw_args)
@ -1171,9 +1188,9 @@ function _display(plt::Plot{GLVisualizeBackend}, visible = true)
anns = series[:series_annotations] anns = series[:series_annotations]
for (x, y, str, font) in EachAnn(anns, d[:x], d[:y]) for (x, y, str, font) in EachAnn(anns, d[:x], d[:y])
txt_args = Dict{Symbol, Any}(:model => eye(GLAbstraction.Mat4f0)) txt_args = Dict{Symbol, Any}(:model => eye(GLAbstraction.Mat4f0))
x, y = Reactive.value(model_m) * Vec{4, Float32}(x, y, 0, 1) x, y = Reactive.value(model_m) * GeometryTypes.Vec{4, Float32}(x, y, 0, 1)
extract_font(font, txt_args) extract_font(font, txt_args)
t = text(Point2f0(x, y), PlotText(str, font), txt_args) t = glvisualize_text(Point2f0(x, y), PlotText(str, font), txt_args)
GLVisualize._view(t, sp_screen, camera = :perspective) GLVisualize._view(t, sp_screen, camera = :perspective)
end end
@ -1182,7 +1199,7 @@ function _display(plt::Plot{GLVisualizeBackend}, visible = true)
if _3d if _3d
GLAbstraction.center!(sp_screen) GLAbstraction.center!(sp_screen)
end end
Reactive.post_empty() GLAbstraction.post_empty()
yield() yield()
end end
end end
@ -1197,21 +1214,18 @@ function _show(io::IO, ::MIME"image/png", plt::Plot{GLVisualizeBackend})
GLWindow.render_frame(GLWindow.rootscreen(plt.o)) GLWindow.render_frame(GLWindow.rootscreen(plt.o))
GLWindow.swapbuffers(plt.o) GLWindow.swapbuffers(plt.o)
buff = GLWindow.screenbuffer(plt.o) buff = GLWindow.screenbuffer(plt.o)
png = Images.Image(map(RGB{U8}, buff), png = map(RGB{U8}, buff)
colorspace = "sRGB",
spatialorder = ["y", "x"]
)
FileIO.save(FileIO.Stream(FileIO.DataFormat{:PNG}, io), png) FileIO.save(FileIO.Stream(FileIO.DataFormat{:PNG}, io), png)
end end
function gl_image(img, kw_args) function gl_image(img, kw_args)
rect = kw_args[:primitive] rect = kw_args[:primitive]
kw_args[:primitive] = GeometryTypes.SimpleRectangle{Float32}(rect.x, rect.y, rect.h, rect.w) # seems to be flipped kw_args[:primitive] = GeometryTypes.SimpleRectangle{Float32}(rect.x, rect.y, rect.w, rect.h)
visualize(img, Style(:default), kw_args) visualize(img, Style(:default), kw_args)
end end
function handle_segment{P}(lines, line_segments, points::Vector{P}, segment) function handle_segment(lines, line_segments, points::Vector{P}, segment) where P
(isempty(segment) || length(segment) < 2) && return (isempty(segment) || length(segment) < 2) && return
if length(segment) == 2 if length(segment) == 2
append!(line_segments, view(points, segment)) append!(line_segments, view(points, segment))
@ -1280,7 +1294,7 @@ function gl_scatter(points, kw_args)
if haskey(kw_args, :stroke_width) if haskey(kw_args, :stroke_width)
s = Reactive.value(kw_args[:scale]) s = Reactive.value(kw_args[:scale])
sw = kw_args[:stroke_width] sw = kw_args[:stroke_width]
if sw*5 > cycle(Reactive.value(s), 1)[1] # restrict marker stroke to 1/10th of scale (and handle arrays of scales) if sw*5 > _cycle(Reactive.value(s), 1)[1] # restrict marker stroke to 1/10th of scale (and handle arrays of scales)
kw_args[:stroke_width] = s[1] / 5f0 kw_args[:stroke_width] = s[1] / 5f0
end end
end end
@ -1342,7 +1356,7 @@ function gl_surface(x,y,z, kw_args)
end end
color = get(kw_args, :stroke_color, RGBA{Float32}(0,0,0,1)) color = get(kw_args, :stroke_color, RGBA{Float32}(0,0,0,1))
kw_args[:color] = color kw_args[:color] = color
kw_args[:thickness] = get(kw_args, :stroke_width, 1f0) kw_args[:thickness] = Float32(get(kw_args, :stroke_width, 1f0))
kw_args[:indices] = faces kw_args[:indices] = faces
delete!(kw_args, :stroke_color) delete!(kw_args, :stroke_color)
delete!(kw_args, :stroke_width) delete!(kw_args, :stroke_width)
@ -1358,8 +1372,8 @@ function gl_contour(x, y, z, kw_args)
if kw_args[:fillrange] != nothing if kw_args[:fillrange] != nothing
delete!(kw_args, :intensity) delete!(kw_args, :intensity)
I = GLVisualize.Intensity{1, Float32} I = GLVisualize.Intensity{Float32}
main = I[z[j,i] for i=1:size(z, 2), j=1:size(z, 1)] main = [I(z[j,i]) for i=1:size(z, 2), j=1:size(z, 1)]
return visualize(main, Style(:default), kw_args) return visualize(main, Style(:default), kw_args)
else else
@ -1367,7 +1381,7 @@ function gl_contour(x, y, z, kw_args)
T = eltype(z) T = eltype(z)
levels = Contour.contours(map(T, x), map(T, y), z, h) levels = Contour.contours(map(T, x), map(T, y), z, h)
result = Point2f0[] result = Point2f0[]
zmin, zmax = get(kw_args, :limits, Vec2f0(extrema(z))) zmin, zmax = get(kw_args, :limits, Vec2f0(ignorenan_extrema(z)))
cmap = get(kw_args, :color_map, get(kw_args, :color, RGBA{Float32}(0,0,0,1))) cmap = get(kw_args, :color_map, get(kw_args, :color, RGBA{Float32}(0,0,0,1)))
colors = RGBA{Float32}[] colors = RGBA{Float32}[]
for c in levels.contours for c in levels.contours
@ -1388,10 +1402,10 @@ end
function gl_heatmap(x,y,z, kw_args) function gl_heatmap(x,y,z, kw_args)
get!(kw_args, :color_norm, Vec2f0(extrema(z))) get!(kw_args, :color_norm, Vec2f0(ignorenan_extrema(z)))
get!(kw_args, :color_map, Plots.make_gradient(cgrad())) get!(kw_args, :color_map, Plots.make_gradient(cgrad()))
delete!(kw_args, :intensity) delete!(kw_args, :intensity)
I = GLVisualize.Intensity{1, Float32} I = GLVisualize.Intensity{Float32}
heatmap = I[z[j,i] for i=1:size(z, 2), j=1:size(z, 1)] heatmap = I[z[j,i] for i=1:size(z, 2), j=1:size(z, 1)]
tex = GLAbstraction.Texture(heatmap, minfilter=:nearest) tex = GLAbstraction.Texture(heatmap, minfilter=:nearest)
kw_args[:stroke_width] = 0f0 kw_args[:stroke_width] = 0f0
@ -1422,6 +1436,8 @@ function label_scatter(d, w, ho)
color = get(kw, :color, nothing) color = get(kw, :color, nothing)
kw[:color] = isa(color, Array) ? first(color) : color kw[:color] = isa(color, Array) ? first(color) : color
end end
strcolor = get(kw, :stroke_color, RGBA{Float32}(0,0,0,0))
kw[:stroke_color] = isa(strcolor, Array) ? first(strcolor) : strcolor
p = get(kw, :primitive, GeometryTypes.Circle) p = get(kw, :primitive, GeometryTypes.Circle)
if isa(p, GLNormalMesh) if isa(p, GLNormalMesh)
bb = GeometryTypes.AABB{Float32}(GeometryTypes.vertices(p)) bb = GeometryTypes.AABB{Float32}(GeometryTypes.vertices(p))
@ -1436,6 +1452,9 @@ function label_scatter(d, w, ho)
kw[:scale] = Vec3f0(w/2) kw[:scale] = Vec3f0(w/2)
delete!(kw, :offset) delete!(kw, :offset)
end end
if isa(p, Array)
kw[:primitive] = GeometryTypes.Circle
end
GL.gl_scatter(Point2f0[(w/2, ho)], kw) GL.gl_scatter(Point2f0[(w/2, ho)], kw)
end end
@ -1447,7 +1466,7 @@ function make_label(sp, series, i)
d = series.d d = series.d
st = d[:seriestype] st = d[:seriestype]
kw_args = KW() kw_args = KW()
if (st in (:path, :path3d)) && d[:linewidth] > 0 if (st in (:path, :path3d, :straightline)) && d[:linewidth] > 0
points = Point2f0[(0, ho), (w, ho)] points = Point2f0[(0, ho), (w, ho)]
kw = KW() kw = KW()
extract_linestyle(d, kw) extract_linestyle(d, kw)
@ -1473,14 +1492,13 @@ function make_label(sp, series, i)
else else
series[:label] series[:label]
end end
color = sp[:foreground_color_legend] ft = legendfont(sp)
ft = sp[:legendfont] font = Plots.Font(ft.family, ft.pointsize, :left, :bottom, 0.0, ft.color)
font = Plots.Font(ft.family, ft.pointsize, :left, :bottom, 0.0, color)
xy = Point2f0(w+gap, 0.0) xy = Point2f0(w+gap, 0.0)
kw = Dict(:model => text_model(font, xy), :scale_primitive=>false) kw = Dict(:model => text_model(font, xy), :scale_primitive=>false)
extract_font(font, kw) extract_font(font, kw)
t = PlotText(labeltext, font) t = PlotText(labeltext, font)
push!(result, text(xy, t, kw)) push!(result, glvisualize_text(xy, t, kw))
GLAbstraction.Context(result...), i GLAbstraction.Context(result...), i
end end

File diff suppressed because it is too large Load Diff

665
src/backends/hdf5.jl Normal file
View File

@ -0,0 +1,665 @@
#HDF5 Plots: Save/replay plots to/from HDF5
#-------------------------------------------------------------------------------
#==Usage
===============================================================================
Write to .hdf5 file using:
p = plot(...)
Plots.hdf5plot_write(p, "plotsave.hdf5")
Read from .hdf5 file using:
pyplot() #Must first select backend
pread = Plots.hdf5plot_read("plotsave.hdf5")
display(pread)
==#
#==TODO
===============================================================================
1. Support more features
- SeriesAnnotations & GridLayout known to be missing.
3. Improve error handling.
- Will likely crash if file format is off.
2. Save data in a folder parallel to "plot".
- Will make it easier for users to locate data.
- Use HDF5 reference to link data?
3. Develop an actual versioned file format.
- Should have some form of backward compatibility.
- Should be reliable for archival purposes.
==#
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "hdf5.jl"))
end
import FixedPointNumbers: N0f8 #In core Julia
#Dispatch types:
struct HDF5PlotNative; end #Indentifies a data element that can natively be handled by HDF5
struct HDF5CTuple; end #Identifies a "complex" tuple structure
mutable struct HDF5Plot_PlotRef
ref::Union{Plot, Void}
end
#==Useful constants
===============================================================================#
const _hdf5_plotroot = "plot"
const _hdf5_dataroot = "data" #TODO: Eventually move data to different root (easier to locate)?
const _hdf5plot_datatypeid = "TYPE" #Attribute identifying type
const _hdf5plot_countid = "COUNT" #Attribute for storing count
#Dict has problems using "Types" as keys. Initialize in "_initialize_backend":
const HDF5PLOT_MAP_STR2TELEM = Dict{String, Type}()
const HDF5PLOT_MAP_TELEM2STR = Dict{Type, String}()
#Don't really like this global variable... Very hacky
const HDF5PLOT_PLOTREF = HDF5Plot_PlotRef(nothing)
#Simple sub-structures that can just be written out using _hdf5plot_gwritefields:
const HDF5PLOT_SIMPLESUBSTRUCT = Union{Font, BoundingBox,
GridLayout, RootLayout, ColorGradient, SeriesAnnotations, PlotText
}
#==
===============================================================================#
const _hdf5_attr = merge_with_base_supported([
:annotations,
:background_color_legend, :background_color_inside, :background_color_outside,
:foreground_color_grid, :foreground_color_legend, :foreground_color_title,
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text,
:label,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:fillrange, :fillcolor, :fillalpha,
:bins, :bar_width, :bar_edges, :bar_position,
:title, :title_location, :titlefont,
:window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar,
:marker_z, :line_z, :fill_z,
:levels,
:ribbon, :quiver, :arrow,
:orientation,
:overwrite_figure,
:polar,
:normalize, :weights,
:contours, :aspect_ratio,
:match_dimensions,
:clims,
:inset_subplots,
:dpi,
:colorbar_title,
])
const _hdf5_seriestype = [
:path, :steppre, :steppost, :shape, :straightline,
:scatter, :hexbin, #:histogram2d, :histogram,
# :bar,
:heatmap, :pie, :image,
:contour, :contour3d, :path3d, :scatter3d, :surface, :wireframe
]
const _hdf5_style = [:auto, :solid, :dash, :dot, :dashdot]
const _hdf5_marker = vcat(_allMarkers, :pixel)
const _hdf5_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::HDF5Backend, shape::Shape) = true
function add_backend_string(::HDF5Backend)
"""
if !Plots.is_installed("HDF5")
Pkg.add("HDF5")
end
"""
end
#==Helper functions
===============================================================================#
_hdf5_plotelempath(subpath::String) = "$_hdf5_plotroot/$subpath"
_hdf5_datapath(subpath::String) = "$_hdf5_dataroot/$subpath"
_hdf5_map_str2telem(k::String) = HDF5PLOT_MAP_STR2TELEM[k]
_hdf5_map_str2telem(v::Vector) = HDF5PLOT_MAP_STR2TELEM[v[1]]
function _hdf5_merge!(dest::Dict, src::Dict)
for (k, v) in src
if isa(v, Axis)
_hdf5_merge!(dest[k].d, v.d)
else
dest[k] = v
end
end
return
end
#==
===============================================================================#
function _initialize_backend(::HDF5Backend)
@eval begin
import HDF5
export HDF5
if length(HDF5PLOT_MAP_TELEM2STR) < 1
#Possible element types of high-level data types:
const telem2str = Dict{String, Type}(
"NATIVE" => HDF5PlotNative,
"VOID" => Void,
"BOOL" => Bool,
"SYMBOL" => Symbol,
"TUPLE" => Tuple,
"CTUPLE" => HDF5CTuple, #Tuple of complex structures
"RGBA" => ARGB{N0f8},
"EXTREMA" => Extrema,
"LENGTH" => Length,
"ARRAY" => Array, #Dict won't allow Array to be key in HDF5PLOT_MAP_TELEM2STR
#Sub-structure types:
"FONT" => Font,
"BOUNDINGBOX" => BoundingBox,
"GRIDLAYOUT" => GridLayout,
"ROOTLAYOUT" => RootLayout,
"SERIESANNOTATIONS" => SeriesAnnotations,
# "PLOTTEXT" => PlotText,
"COLORGRADIENT" => ColorGradient,
"AXIS" => Axis,
"SURFACE" => Surface,
"SUBPLOT" => Subplot,
"NULLABLE" => Nullable,
)
merge!(HDF5PLOT_MAP_STR2TELEM, telem2str)
merge!(HDF5PLOT_MAP_TELEM2STR, Dict{Type, String}(v=>k for (k,v) in HDF5PLOT_MAP_STR2TELEM))
end
end
end
# ---------------------------------------------------------------------------
# Create the window/figure for this backend.
function _create_backend_figure(plt::Plot{HDF5Backend})
#Do nothing
end
# ---------------------------------------------------------------------------
# # this is called early in the pipeline, use it to make the plot current or something
# function _prepare_plot_object(plt::Plot{HDF5Backend})
# end
# ---------------------------------------------------------------------------
# Set up the subplot within the backend object.
function _initialize_subplot(plt::Plot{HDF5Backend}, sp::Subplot{HDF5Backend})
#Do nothing
end
# ---------------------------------------------------------------------------
# Add one series to the underlying backend object.
# Called once per series
# NOTE: Seems to be called when user calls plot()... even if backend
# plot, sp.o has not yet been constructed...
function _series_added(plt::Plot{HDF5Backend}, series::Series)
#Do nothing
end
# ---------------------------------------------------------------------------
# When series data is added/changed, this callback can do dynamic updates to the backend object.
# note: if the backend rebuilds the plot from scratch on display, then you might not do anything here.
function _series_updated(plt::Plot{HDF5Backend}, series::Series)
#Do nothing
end
# ---------------------------------------------------------------------------
# called just before updating layout bounding boxes... in case you need to prep
# for the calcs
function _before_layout_calcs(plt::Plot{HDF5Backend})
#Do nothing
end
# ----------------------------------------------------------------
# Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot{HDF5Backend})
#Do nothing
end
# ----------------------------------------------------------------
# Override this to update plot items (title, xlabel, etc), and add annotations (d[:annotations])
function _update_plot_object(plt::Plot{HDF5Backend})
#Do nothing
end
# ----------------------------------------------------------------
# Display/show the plot (open a GUI window, or browser page, for example).
function _display(plt::Plot{HDF5Backend})
msg = "HDF5 interface does not support `display()` function."
msg *= "\nUse `Plots.hdf5plot_write(::String)` method to write to .HDF5 \"plot\" file instead."
warn(msg)
return
end
#==HDF5 write functions
===============================================================================#
function _hdf5plot_writetype(grp, k::String, tstr::Array{String})
d = HDF5.d_open(grp, k)
HDF5.a_write(d, _hdf5plot_datatypeid, tstr)
end
function _hdf5plot_writetype(grp, k::String, T::Type)
tstr = HDF5PLOT_MAP_TELEM2STR[T]
d = HDF5.d_open(grp, k)
HDF5.a_write(d, _hdf5plot_datatypeid, tstr)
end
function _hdf5plot_overwritetype(grp, k::String, T::Type)
tstr = HDF5PLOT_MAP_TELEM2STR[T]
d = HDF5.d_open(grp, k)
HDF5.a_delete(d, _hdf5plot_datatypeid)
HDF5.a_write(d, _hdf5plot_datatypeid, tstr)
end
function _hdf5plot_writetype(grp, T::Type) #Write directly to group
tstr = HDF5PLOT_MAP_TELEM2STR[T]
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
end
function _hdf5plot_overwritetype(grp, T::Type) #Write directly to group
tstr = HDF5PLOT_MAP_TELEM2STR[T]
HDF5.a_delete(grp, _hdf5plot_datatypeid)
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
end
function _hdf5plot_writetype(grp, ::Type{Array{T}}) where T<:Any
tstr = HDF5PLOT_MAP_TELEM2STR[Array] #ANY
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
end
function _hdf5plot_writetype(grp, ::Type{T}) where T<:BoundingBox
tstr = HDF5PLOT_MAP_TELEM2STR[BoundingBox]
HDF5.a_write(grp, _hdf5plot_datatypeid, tstr)
end
function _hdf5plot_writecount(grp, n::Int) #Write directly to group
HDF5.a_write(grp, _hdf5plot_countid, n)
end
function _hdf5plot_gwritefields(grp, k::String, v)
grp = HDF5.g_create(grp, k)
for _k in fieldnames(v)
_v = getfield(v, _k)
kstr = string(_k)
_hdf5plot_gwrite(grp, kstr, _v)
end
_hdf5plot_writetype(grp, typeof(v))
return
end
# Write data
# ----------------------------------------------------------------
function _hdf5plot_gwrite(grp, k::String, v) #Default
grp[k] = v
_hdf5plot_writetype(grp, k, HDF5PlotNative)
end
function _hdf5plot_gwrite(grp, k::String, v::Array{T}) where T<:Number #Default for arrays
grp[k] = v
_hdf5plot_writetype(grp, k, HDF5PlotNative)
end
#=
function _hdf5plot_gwrite(grp, k::String, v::Array{Any})
# @show grp, k
warn("Cannot write Array: $k=$v")
end
=#
function _hdf5plot_gwrite(grp, k::String, v::Void)
grp[k] = 0
_hdf5plot_writetype(grp, k, Void)
end
function _hdf5plot_gwrite(grp, k::String, v::Bool)
grp[k] = Int(v)
_hdf5plot_writetype(grp, k, Bool)
end
function _hdf5plot_gwrite(grp, k::String, v::Symbol)
grp[k] = string(v)
_hdf5plot_writetype(grp, k, Symbol)
end
function _hdf5plot_gwrite(grp, k::String, v::Tuple)
varr = [v...]
elt = eltype(varr)
# if isleaftype(elt)
_hdf5plot_gwrite(grp, k, varr)
if elt <: Number
#We just wrote a simple dataset
_hdf5plot_overwritetype(grp, k, Tuple)
else #Used a more complex scheme (using subgroups):
_hdf5plot_overwritetype(grp[k], HDF5CTuple)
end
#NOTE: _hdf5plot_overwritetype overwrites "Array" type with "Tuple".
end
function _hdf5plot_gwrite(grp, k::String, d::Dict)
# warn("Cannot write dict: $k=$d")
end
function _hdf5plot_gwrite(grp, k::String, v::Range)
_hdf5plot_gwrite(grp, k, collect(v)) #For now
end
function _hdf5plot_gwrite(grp, k::String, v::ARGB{N0f8})
grp[k] = [v.r.i, v.g.i, v.b.i, v.alpha.i]
_hdf5plot_writetype(grp, k, ARGB{N0f8})
end
function _hdf5plot_gwrite(grp, k::String, v::Colorant)
_hdf5plot_gwrite(grp, k, ARGB{N0f8}(v))
end
#Custom vector (when not using simple numeric type):
function _hdf5plot_gwritearray(grp, k::String, v::Array{T}) where T
if "annotations" == k;
return #Hack. Does not yet support annotations.
end
vgrp = HDF5.g_create(grp, k)
_hdf5plot_writetype(vgrp, Array) #ANY
sz = size(v)
for iter in eachindex(v)
coord = ind2sub(sz, iter)
elem = v[iter]
idxstr = join(coord, "_")
_hdf5plot_gwrite(vgrp, "v$idxstr", v[iter])
end
_hdf5plot_gwrite(vgrp, "dim", [sz...])
return
end
_hdf5plot_gwrite(grp, k::String, v::Array) =
_hdf5plot_gwritearray(grp, k, v)
function _hdf5plot_gwrite(grp, k::String, v::Extrema)
grp[k] = [v.emin, v.emax]
_hdf5plot_writetype(grp, k, Extrema)
end
function _hdf5plot_gwrite(grp, k::String, v::Length{T}) where T
grp[k] = v.value
_hdf5plot_writetype(grp, k, [HDF5PLOT_MAP_TELEM2STR[Length], string(T)])
end
# Write more complex structures:
# ----------------------------------------------------------------
function _hdf5plot_gwrite(grp, k::String, v::Plot)
#Don't write plot references
end
function _hdf5plot_gwrite(grp, k::String, v::HDF5PLOT_SIMPLESUBSTRUCT)
_hdf5plot_gwritefields(grp, k, v)
return
end
function _hdf5plot_gwrite(grp, k::String, v::Axis)
grp = HDF5.g_create(grp, k)
for (_k, _v) in v.d
kstr = string(_k)
_hdf5plot_gwrite(grp, kstr, _v)
end
_hdf5plot_writetype(grp, Axis)
return
end
function _hdf5plot_gwrite(grp, k::String, v::Surface)
grp = HDF5.g_create(grp, k)
_hdf5plot_gwrite(grp, "data2d", v.surf)
_hdf5plot_writetype(grp, Surface)
end
#TODO: "Properly" support Nullable using _hdf5plot_writetype?
function _hdf5plot_gwrite(grp, k::String, v::Nullable)
if isnull(v)
_hdf5plot_gwrite(grp, k, nothing)
else
_hdf5plot_gwrite(grp, k, v.value)
end
return
end
function _hdf5plot_gwrite(grp, k::String, v::SeriesAnnotations)
#Currently no support for SeriesAnnotations
return
end
function _hdf5plot_gwrite(grp, k::String, v::Subplot)
grp = HDF5.g_create(grp, k)
_hdf5plot_gwrite(grp, "index", v[:subplot_index])
_hdf5plot_writetype(grp, Subplot)
return
end
function _hdf5plot_write(grp, d::Dict)
for (k, v) in d
kstr = string(k)
_hdf5plot_gwrite(grp, kstr, v)
end
return
end
# Write main plot structures:
# ----------------------------------------------------------------
function _hdf5plot_write(sp::Subplot{HDF5Backend}, subpath::String, f)
f = f::HDF5.HDF5File #Assert
grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/attr"))
_hdf5plot_write(grp, sp.attr)
grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/series_list"))
_hdf5plot_writecount(grp, length(sp.series_list))
for (i, series) in enumerate(sp.series_list)
grp = HDF5.g_create(f, _hdf5_plotelempath("$subpath/series_list/series$i"))
_hdf5plot_write(grp, series.d)
end
return
end
function _hdf5plot_write(plt::Plot{HDF5Backend}, f)
f = f::HDF5.HDF5File #Assert
grp = HDF5.g_create(f, _hdf5_plotelempath("attr"))
_hdf5plot_write(grp, plt.attr)
grp = HDF5.g_create(f, _hdf5_plotelempath("subplots"))
_hdf5plot_writecount(grp, length(plt.subplots))
for (i, sp) in enumerate(plt.subplots)
_hdf5plot_write(sp, "subplots/subplot$i", f)
end
return
end
function hdf5plot_write(plt::Plot{HDF5Backend}, path::AbstractString)
HDF5.h5open(path, "w") do file
_hdf5plot_write(plt, file)
end
end
hdf5plot_write(path::AbstractString) = hdf5plot_write(current(), path)
#==HDF5 playback (read) functions
===============================================================================#
function _hdf5plot_readcount(grp) #Read directly from group
return HDF5.a_read(grp, _hdf5plot_countid)
end
_hdf5plot_convert(T::Type{HDF5PlotNative}, v) = v
_hdf5plot_convert(T::Type{Void}, v) = nothing
_hdf5plot_convert(T::Type{Bool}, v) = (v!=0)
_hdf5plot_convert(T::Type{Symbol}, v) = Symbol(v)
_hdf5plot_convert(T::Type{Tuple}, v) = tuple(v...) #With Vector{T<:Number}
function _hdf5plot_convert(T::Type{ARGB{N0f8}}, v)
r, g, b, a = reinterpret(N0f8, v)
return Colors.ARGB{N0f8}(r, g, b, a)
end
_hdf5plot_convert(T::Type{Extrema}, v) = Extrema(v[1], v[2])
# Read data structures:
# ----------------------------------------------------------------
function _hdf5plot_read(grp, k::String, T::Type, dtid)
v = HDF5.d_read(grp, k)
return _hdf5plot_convert(T, v)
end
function _hdf5plot_read(grp, k::String, T::Type{Length}, dtid::Vector)
v = HDF5.d_read(grp, k)
TU = Symbol(dtid[2])
T = typeof(v)
return Length{TU,T}(v)
end
# Read more complex data structures:
# ----------------------------------------------------------------
function _hdf5plot_read(grp, k::String, T::Type{Font}, dtid)
grp = HDF5.g_open(grp, k)
family = _hdf5plot_read(grp, "family")
pointsize = _hdf5plot_read(grp, "pointsize")
halign = _hdf5plot_read(grp, "halign")
valign = _hdf5plot_read(grp, "valign")
rotation = _hdf5plot_read(grp, "rotation")
color = _hdf5plot_read(grp, "color")
return Font(family, pointsize, halign, valign, rotation, color)
end
function _hdf5plot_read(grp, k::String, T::Type{Array}, dtid) #ANY
grp = HDF5.g_open(grp, k)
sz = _hdf5plot_read(grp, "dim")
if [0] == sz; return []; end
sz = tuple(sz...)
result = Array{Any}(sz)
for iter in eachindex(result)
coord = ind2sub(sz, iter)
idxstr = join(coord, "_")
result[iter] = _hdf5plot_read(grp, "v$idxstr")
end
#Hack: Implicitly make Julia detect element type.
# (Should probably write it explicitly to file)
result = [result[iter] for iter in eachindex(result)] #Potentially make more specific
return reshape(result, sz)
end
function _hdf5plot_read(grp, k::String, T::Type{HDF5CTuple}, dtid)
v = _hdf5plot_read(grp, k, Array, dtid)
return tuple(v...)
end
function _hdf5plot_read(grp, k::String, T::Type{ColorGradient}, dtid)
grp = HDF5.g_open(grp, k)
colors = _hdf5plot_read(grp, "colors")
values = _hdf5plot_read(grp, "values")
return ColorGradient(colors, values)
end
function _hdf5plot_read(grp, k::String, T::Type{BoundingBox}, dtid)
grp = HDF5.g_open(grp, k)
x0 = _hdf5plot_read(grp, "x0")
a = _hdf5plot_read(grp, "a")
return BoundingBox(x0, a)
end
_hdf5plot_read(grp, k::String, T::Type{RootLayout}, dtid) = RootLayout()
function _hdf5plot_read(grp, k::String, T::Type{GridLayout}, dtid)
grp = HDF5.g_open(grp, k)
# parent = _hdf5plot_read(grp, "parent")
parent = RootLayout()
minpad = _hdf5plot_read(grp, "minpad")
bbox = _hdf5plot_read(grp, "bbox")
grid = _hdf5plot_read(grp, "grid")
widths = _hdf5plot_read(grp, "widths")
heights = _hdf5plot_read(grp, "heights")
attr = KW() #TODO support attr: _hdf5plot_read(grp, "attr")
return GridLayout(parent, minpad, bbox, grid, widths, heights, attr)
end
function _hdf5plot_read(grp, k::String, T::Type{Axis}, dtid)
grp = HDF5.g_open(grp, k)
kwlist = KW()
_hdf5plot_read(grp, kwlist)
return Axis([], kwlist)
end
function _hdf5plot_read(grp, k::String, T::Type{Surface}, dtid)
grp = HDF5.g_open(grp, k)
data2d = _hdf5plot_read(grp, "data2d")
return Surface(data2d)
end
function _hdf5plot_read(grp, k::String, T::Type{Subplot}, dtid)
grp = HDF5.g_open(grp, k)
idx = _hdf5plot_read(grp, "index")
return HDF5PLOT_PLOTREF.ref.subplots[idx]
end
function _hdf5plot_read(grp, k::String)
dtid = HDF5.a_read(grp[k], _hdf5plot_datatypeid)
T = _hdf5_map_str2telem(dtid) #expect exception
return _hdf5plot_read(grp, k, T, dtid)
end
#Read in values in group to populate d:
function _hdf5plot_read(grp, d::Dict)
gnames = names(grp)
for k in gnames
try
v = _hdf5plot_read(grp, k)
d[Symbol(k)] = v
catch e
@show e
@show grp
warn("Could not read field $k")
end
end
return
end
# Read main plot structures:
# ----------------------------------------------------------------
function _hdf5plot_read(sp::Subplot, subpath::String, f)
f = f::HDF5.HDF5File #Assert
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/attr"))
kwlist = KW()
_hdf5plot_read(grp, kwlist)
_hdf5_merge!(sp.attr, kwlist)
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list"))
nseries = _hdf5plot_readcount(grp)
for i in 1:nseries
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list/series$i"))
kwlist = KW()
_hdf5plot_read(grp, kwlist)
plot!(sp, kwlist[:x], kwlist[:y]) #Add data & create data structures
_hdf5_merge!(sp.series_list[end].d, kwlist)
end
return
end
function _hdf5plot_read(plt::Plot, f)
f = f::HDF5.HDF5File #Assert
#Assumpltion: subplots are already allocated (plt.subplots)
HDF5PLOT_PLOTREF.ref = plt #Used when reading "layout"
grp = HDF5.g_open(f, _hdf5_plotelempath("attr"))
_hdf5plot_read(grp, plt.attr)
for (i, sp) in enumerate(plt.subplots)
_hdf5plot_read(sp, "subplots/subplot$i", f)
end
return
end
function hdf5plot_read(path::AbstractString)
plt = nothing
HDF5.h5open(path, "r") do file
grp = HDF5.g_open(file, _hdf5_plotelempath("subplots"))
n = _hdf5plot_readcount(grp)
plt = plot(layout=n) #Get reference to a new plot
_hdf5plot_read(plt, file)
end
return plt
end
#Last line

View File

@ -13,12 +13,17 @@ Add in functionality to Plots.jl:
:aspect_ratio, :aspect_ratio,
=# =#
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "inspectdr.jl"))
end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
#TODO: remove features #TODO: remove features
const _inspectdr_attr = merge_with_base_supported([ const _inspectdr_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend, :background_color_inside, :background_color_outside,
:foreground_color_grid, :foreground_color_legend, :foreground_color_title, # :foreground_color_grid,
:foreground_color_legend, :foreground_color_title,
:foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text, :foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text,
:label, :label,
:linecolor, :linestyle, :linewidth, :linealpha, :linecolor, :linestyle, :linewidth, :linealpha,
@ -27,10 +32,13 @@ const _inspectdr_attr = merge_with_base_supported([
:markerstrokestyle, #Causes warning not to have it... what is this? :markerstrokestyle, #Causes warning not to have it... what is this?
:fillcolor, :fillalpha, #:fillrange, :fillcolor, :fillalpha, #:fillrange,
# :bins, :bar_width, :bar_edges, :bar_position, # :bins, :bar_width, :bar_edges, :bar_position,
:title, :title_location, :titlefont, :title, :title_location,
:window_title, :window_title,
:guide, :lims, :scale, #:ticks, :flip, :rotation, :guide, :lims, :scale, #:ticks, :flip, :rotation,
:tickfont, :guidefont, :legendfont, :titlefontfamily, :titlefontsize, :titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefontcolor,
:grid, :legend, #:colorbar, :grid, :legend, #:colorbar,
# :marker_z, # :marker_z,
# :line_z, # :line_z,
@ -38,7 +46,7 @@ const _inspectdr_attr = merge_with_base_supported([
# :ribbon, :quiver, :arrow, # :ribbon, :quiver, :arrow,
# :orientation, # :orientation,
:overwrite_figure, :overwrite_figure,
# :polar, :polar,
# :normalize, :weights, # :normalize, :weights,
# :contours, :aspect_ratio, # :contours, :aspect_ratio,
:match_dimensions, :match_dimensions,
@ -49,7 +57,7 @@ const _inspectdr_attr = merge_with_base_supported([
]) ])
const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot] const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot]
const _inspectdr_seriestype = [ const _inspectdr_seriestype = [
:path, :scatter, :shape #, :steppre, :steppost :path, :scatter, :shape, :straightline, #, :steppre, :steppost
] ]
#see: _allMarkers, _shape_keys #see: _allMarkers, _shape_keys
const _inspectdr_marker = Symbol[ const _inspectdr_marker = Symbol[
@ -66,6 +74,9 @@ const _inspectdr_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::InspectDRBackend, shape::Shape) = true is_marker_supported(::InspectDRBackend, shape::Shape) = true
_inspectdr_to_pixels(bb::BoundingBox) =
InspectDR.BoundingBox(to_pixels(left(bb)), to_pixels(right(bb)), to_pixels(top(bb)), to_pixels(bottom(bb)))
#Do we avoid Map to avoid possible pre-comile issues? #Do we avoid Map to avoid possible pre-comile issues?
function _inspectdr_mapglyph(s::Symbol) function _inspectdr_mapglyph(s::Symbol)
s == :rect && return :square s == :rect && return :square
@ -125,16 +136,17 @@ end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
function _inspectdr_getscale(s::Symbol) function _inspectdr_getscale(s::Symbol, yaxis::Bool)
#TODO: Support :asinh, :sqrt #TODO: Support :asinh, :sqrt
kwargs = yaxis ? (:tgtmajor=>8, :tgtminor=>2) : () #More grid lines on y-axis
if :log2 == s if :log2 == s
return InspectDR.AxisScale(:log2) return InspectDR.AxisScale(:log2; kwargs...)
elseif :log10 == s elseif :log10 == s
return InspectDR.AxisScale(:log10) return InspectDR.AxisScale(:log10; kwargs...)
elseif :ln == s elseif :ln == s
return InspectDR.AxisScale(:ln) return InspectDR.AxisScale(:ln; kwargs...)
else #identity else #identity
return InspectDR.AxisScale(:lin) return InspectDR.AxisScale(:lin; kwargs...)
end end
end end
@ -158,7 +170,7 @@ function _initialize_backend(::InspectDRBackend; kw...)
2*InspectDR.GLYPH_SQUARE.x, InspectDR.GLYPH_SQUARE.y 2*InspectDR.GLYPH_SQUARE.x, InspectDR.GLYPH_SQUARE.y
) )
type InspecDRPlotRef mutable struct InspecDRPlotRef
mplot::Union{Void, InspectDR.Multiplot} mplot::Union{Void, InspectDR.Multiplot}
gui::Union{Void, InspectDR.GtkPlot} gui::Union{Void, InspectDR.GtkPlot}
end end
@ -167,7 +179,7 @@ function _initialize_backend(::InspectDRBackend; kw...)
_inspectdr_getmplot(r::InspecDRPlotRef) = r.mplot _inspectdr_getmplot(r::InspecDRPlotRef) = r.mplot
_inspectdr_getgui(::Any) = nothing _inspectdr_getgui(::Any) = nothing
_inspectdr_getgui(gplot::InspectDR.GtkPlot) = (gplot.destroyed? nothing: gplot) _inspectdr_getgui(gplot::InspectDR.GtkPlot) = (gplot.destroyed ? nothing : gplot)
_inspectdr_getgui(r::InspecDRPlotRef) = _inspectdr_getgui(r.gui) _inspectdr_getgui(r::InspecDRPlotRef) = _inspectdr_getgui(r.gui)
end end
end end
@ -209,14 +221,10 @@ end
# Set up the subplot within the backend object. # Set up the subplot within the backend object.
function _initialize_subplot(plt::Plot{InspectDRBackend}, sp::Subplot{InspectDRBackend}) function _initialize_subplot(plt::Plot{InspectDRBackend}, sp::Subplot{InspectDRBackend})
plot = sp.o plot = sp.o
#Don't do anything without a "subplot" object: Will process later. #Don't do anything without a "subplot" object: Will process later.
if nothing == plot; return; end if nothing == plot; return; end
plot.data = [] plot.data = []
plot.markers = [] #Clear old markers plot.userannot = [] #Clear old markers/text annotation/polyline "annotation"
plot.atext = [] #Clear old annotation
plot.apline = [] #Clear old poly lines
return plot return plot
end end
@ -234,8 +242,18 @@ function _series_added(plt::Plot{InspectDRBackend}, series::Series)
#Don't do anything without a "subplot" object: Will process later. #Don't do anything without a "subplot" object: Will process later.
if nothing == plot; return; end if nothing == plot; return; end
_vectorize(v) = isa(v, Vector)? v: collect(v) #InspectDR only supports vectors _vectorize(v) = isa(v, Vector) ? v : collect(v) #InspectDR only supports vectors
x = _vectorize(series[:x]); y = _vectorize(series[:y]) x, y = if st == :straightline
straightline_data(series)
else
_vectorize(series[:x]), _vectorize(series[:y])
end
#No support for polar grid... but can still perform polar transformation:
if ispolar(sp)
Θ = x; r = y
x = r.*cos.(Θ); y = r.*sin.(Θ)
end
# doesn't handle mismatched x/y - wrap data (pyplot behaviour): # doesn't handle mismatched x/y - wrap data (pyplot behaviour):
nx = length(x); ny = length(y) nx = length(x); ny = length(y)
@ -254,28 +272,29 @@ For st in :shape:
=# =#
if st in (:shape,) if st in (:shape,)
x, y = shape_data(series)
nmax = 0 nmax = 0
for (i,rng) in enumerate(iter_segments(x, y)) for (i,rng) in enumerate(iter_segments(x, y))
nmax = i nmax = i
if length(rng) > 1 if length(rng) > 1
linewidth = series[:linewidth] linewidth = series[:linewidth]
linecolor = _inspectdr_mapcolor(cycle(series[:linecolor], i)) linecolor = _inspectdr_mapcolor(_cycle(series[:linecolor], i))
fillcolor = _inspectdr_mapcolor(cycle(series[:fillcolor], i)) fillcolor = _inspectdr_mapcolor(_cycle(series[:fillcolor], i))
line = InspectDR.line( line = InspectDR.line(
style=:solid, width=linewidth, color=linecolor style=:solid, width=linewidth, color=linecolor
) )
apline = InspectDR.PolylineAnnotation( apline = InspectDR.PolylineAnnotation(
x[rng], y[rng], line=line, fillcolor=fillcolor x[rng], y[rng], line=line, fillcolor=fillcolor
) )
push!(plot.apline, apline) InspectDR.add(plot, apline)
end end
end end
i = (nmax >= 2? div(nmax, 2): nmax) #Must pick one set of colors for legend i = (nmax >= 2 ? div(nmax, 2) : nmax) #Must pick one set of colors for legend
if i > 1 #Add dummy waveform for legend entry: if i > 1 #Add dummy waveform for legend entry:
linewidth = series[:linewidth] linewidth = series[:linewidth]
linecolor = _inspectdr_mapcolor(cycle(series[:linecolor], i)) linecolor = _inspectdr_mapcolor(_cycle(series[:linecolor], i))
fillcolor = _inspectdr_mapcolor(cycle(series[:fillcolor], i)) fillcolor = _inspectdr_mapcolor(_cycle(series[:fillcolor], i))
wfrm = InspectDR.add(plot, Float64[], Float64[], id=series[:label]) wfrm = InspectDR.add(plot, Float64[], Float64[], id=series[:label])
wfrm.line = InspectDR.line( wfrm.line = InspectDR.line(
style=:none, width=linewidth, #linewidth affects glyph style=:none, width=linewidth, #linewidth affects glyph
@ -285,11 +304,11 @@ For st in :shape:
color = linecolor, fillcolor = fillcolor color = linecolor, fillcolor = fillcolor
) )
end end
elseif st in (:path, :scatter) #, :steppre, :steppost) elseif st in (:path, :scatter, :straightline) #, :steppre, :steppost)
#NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think). #NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think).
linewidth = series[:linewidth] linewidth = series[:linewidth]
#More efficient & allows some support for markerstrokewidth: #More efficient & allows some support for markerstrokewidth:
_style = (0==linewidth? :none: series[:linestyle]) _style = (0==linewidth ? :none : series[:linestyle])
wfrm = InspectDR.add(plot, x, y, id=series[:label]) wfrm = InspectDR.add(plot, x, y, id=series[:label])
wfrm.line = InspectDR.line( wfrm.line = InspectDR.line(
style = _style, style = _style,
@ -328,48 +347,60 @@ end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend}) function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend})
const gridon = InspectDR.grid(vmajor=true, hmajor=true)
const gridoff = InspectDR.grid()
const plot = sp.o const plot = sp.o
const strip = plot.strips[1] #Only 1 strip supported with Plots.jl
xaxis = sp[:xaxis]; yaxis = sp[:yaxis] xaxis = sp[:xaxis]; yaxis = sp[:yaxis]
xscale = _inspectdr_getscale(xaxis[:scale]) xgrid_show = xaxis[:grid]
yscale = _inspectdr_getscale(yaxis[:scale]) ygrid_show = yaxis[:grid]
plot.axes = InspectDR.AxesRect(xscale, yscale)
strip.grid = InspectDR.GridRect(
vmajor=xgrid_show, # vminor=xgrid_show,
hmajor=ygrid_show, # hminor=ygrid_show,
)
plot.xscale = _inspectdr_getscale(xaxis[:scale], false)
strip.yscale = _inspectdr_getscale(yaxis[:scale], true)
xmin, xmax = axis_limits(xaxis) xmin, xmax = axis_limits(xaxis)
ymin, ymax = axis_limits(yaxis) ymin, ymax = axis_limits(yaxis)
plot.ext = InspectDR.PExtents2D() #reset if ispolar(sp)
plot.ext_full = InspectDR.PExtents2D(xmin, xmax, ymin, ymax) #Plots.jl appears to give (xmin,xmax) ≜ (Θmin,Θmax) & (ymin,ymax) ≜ (rmin,rmax)
rmax = NaNMath.max(abs(ymin), abs(ymax))
xmin, xmax = -rmax, rmax
ymin, ymax = -rmax, rmax
end
plot.xext = InspectDR.PExtents1D() #reset
strip.yext = InspectDR.PExtents1D() #reset
plot.xext_full = InspectDR.PExtents1D(xmin, xmax)
strip.yext_full = InspectDR.PExtents1D(ymin, ymax)
a = plot.annotation a = plot.annotation
a.title = sp[:title] a.title = sp[:title]
a.xlabel = xaxis[:guide]; a.ylabel = yaxis[:guide] a.xlabel = xaxis[:guide]; a.ylabels = [yaxis[:guide]]
l = plot.layout l = plot.layout
l.framedata.fillcolor = _inspectdr_mapcolor(sp[:background_color_inside]) l[:frame_canvas].fillcolor = _inspectdr_mapcolor(sp[:background_color_subplot])
l.framedata.line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis]) l[:frame_data].fillcolor = _inspectdr_mapcolor(sp[:background_color_inside])
l.fnttitle = InspectDR.Font(sp[:titlefont].family, l[:frame_data].line.color = _inspectdr_mapcolor(xaxis[:foreground_color_axis])
_inspectdr_mapptsize(sp[:titlefont].pointsize), l[:font_title] = InspectDR.Font(sp[:titlefontfamily],
color = _inspectdr_mapcolor(sp[:foreground_color_title]) _inspectdr_mapptsize(sp[:titlefontsize]),
color = _inspectdr_mapcolor(sp[:titlefontcolor])
) )
#Cannot independently control fonts of axes with InspectDR: #Cannot independently control fonts of axes with InspectDR:
l.fntaxlabel = InspectDR.Font(xaxis[:guidefont].family, l[:font_axislabel] = InspectDR.Font(xaxis[:guidefontfamily],
_inspectdr_mapptsize(xaxis[:guidefont].pointsize), _inspectdr_mapptsize(xaxis[:guidefontsize]),
color = _inspectdr_mapcolor(xaxis[:foreground_color_guide]) color = _inspectdr_mapcolor(xaxis[:guidefontcolor])
) )
l.fntticklabel = InspectDR.Font(xaxis[:tickfont].family, l[:font_ticklabel] = InspectDR.Font(xaxis[:tickfontfamily],
_inspectdr_mapptsize(xaxis[:tickfont].pointsize), _inspectdr_mapptsize(xaxis[:tickfontsize]),
color = _inspectdr_mapcolor(xaxis[:foreground_color_text]) color = _inspectdr_mapcolor(xaxis[:tickfontcolor])
) )
#No independent control of grid??? l[:enable_legend] = (sp[:legend] != :none)
l.grid = sp[:grid]? gridon: gridoff #l[:halloc_legend] = 150 #TODO: compute???
leg = l.legend l[:font_legend] = InspectDR.Font(sp[:legendfontfamily],
leg.enabled = (sp[:legend] != :none) _inspectdr_mapptsize(sp[:legendfontsize]),
#leg.width = 150 #TODO: compute??? color = _inspectdr_mapcolor(sp[:legendfontcolor])
leg.font = InspectDR.Font(sp[:legendfont].family,
_inspectdr_mapptsize(sp[:legendfont].pointsize),
color = _inspectdr_mapcolor(sp[:foreground_color_legend])
) )
leg.frame.fillcolor = _inspectdr_mapcolor(sp[:background_color_legend]) l[:frame_legend].fillcolor = _inspectdr_mapcolor(sp[:background_color_legend])
end end
# called just before updating layout bounding boxes... in case you need to prep # called just before updating layout bounding boxes... in case you need to prep
@ -378,6 +409,13 @@ function _before_layout_calcs(plt::Plot{InspectDRBackend})
const mplot = _inspectdr_getmplot(plt.o) const mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end if nothing == mplot; return; end
mplot.title = plt[:plot_title]
if "" == mplot.title
#Don't use window_title... probably not what you want.
#mplot.title = plt[:window_title]
end
mplot.layout[:frame].fillcolor = _inspectdr_mapcolor(plt[:background_color_outside])
resize!(mplot.subplots, length(plt.subplots)) resize!(mplot.subplots, length(plt.subplots))
nsubplots = length(plt.subplots) nsubplots = length(plt.subplots)
for (i, sp) in enumerate(plt.subplots) for (i, sp) in enumerate(plt.subplots)
@ -385,30 +423,28 @@ function _before_layout_calcs(plt::Plot{InspectDRBackend})
mplot.subplots[i] = InspectDR.Plot2D() mplot.subplots[i] = InspectDR.Plot2D()
end end
sp.o = mplot.subplots[i] sp.o = mplot.subplots[i]
plot = sp.o
_initialize_subplot(plt, sp) _initialize_subplot(plt, sp)
_inspectdr_setupsubplot(sp) _inspectdr_setupsubplot(sp)
sp.o.layout.frame.fillcolor =
_inspectdr_mapcolor(plt[:background_color_outside])
# add the annotations # add the annotations
for ann in sp[:annotations] for ann in sp[:annotations]
_inspectdr_add_annotations(mplot.subplots[i], ann...) _inspectdr_add_annotations(plot, ann...)
end end
end end
#Do not yet support absolute plot positionning. #Do not yet support absolute plot positionning.
#Just try to make things look more-or less ok: #Just try to make things look more-or less ok:
if nsubplots <= 1 if nsubplots <= 1
mplot.ncolumns = 1 mplot.layout[:ncolumns] = 1
elseif nsubplots <= 4 elseif nsubplots <= 4
mplot.ncolumns = 2 mplot.layout[:ncolumns] = 2
elseif nsubplots <= 6 elseif nsubplots <= 6
mplot.ncolumns = 3 mplot.layout[:ncolumns] = 3
elseif nsubplots <= 12 elseif nsubplots <= 12
mplot.ncolumns = 4 mplot.layout[:ncolumns] = 4
else else
mplot.ncolumns = 5 mplot.layout[:ncolumns] = 5
end end
for series in plt.series_list for series in plt.series_list
@ -422,8 +458,19 @@ end
# Set the (left, top, right, bottom) minimum padding around the plot area # Set the (left, top, right, bottom) minimum padding around the plot area
# to fit ticks, tick labels, guides, colorbars, etc. # to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot{InspectDRBackend}) function _update_min_padding!(sp::Subplot{InspectDRBackend})
sp.minpad = (20mm, 5mm, 2mm, 10mm) plot = sp.o
#TODO: Add support for padding. if !isa(plot, InspectDR.Plot2D); return sp.minpad; end
#Computing plotbounds with 0-BoundingBox returns required padding:
bb = InspectDR.plotbounds(plot.layout.values, InspectDR.BoundingBox(0,0,0,0))
#NOTE: plotbounds always pads for titles, legends, etc. even if not in use.
#TODO: possibly zero-out items not in use??
# add in the user-specified margin to InspectDR padding:
leftpad = abs(bb.xmin)*px + sp[:left_margin]
toppad = abs(bb.ymin)*px + sp[:top_margin]
rightpad = abs(bb.xmax)*px + sp[:right_margin]
bottompad = abs(bb.ymax)*px + sp[:bottom_margin]
sp.minpad = (leftpad, toppad, rightpad, bottompad)
end end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
@ -432,6 +479,13 @@ end
function _update_plot_object(plt::Plot{InspectDRBackend}) function _update_plot_object(plt::Plot{InspectDRBackend})
mplot = _inspectdr_getmplot(plt.o) mplot = _inspectdr_getmplot(plt.o)
if nothing == mplot; return; end if nothing == mplot; return; end
for (i, sp) in enumerate(plt.subplots)
graphbb = _inspectdr_to_pixels(plotarea(sp))
plot = mplot.subplots[i]
plot.plotbb = InspectDR.plotbounds(plot.layout.values, graphbb)
end
gplot = _inspectdr_getgui(plt.o) gplot = _inspectdr_getgui(plt.o)
if nothing == gplot; return; end if nothing == gplot; return; end
@ -452,22 +506,23 @@ const _inspectdr_mimeformats_nodpi = Dict(
# "application/postscript" => "ps", #TODO: support once Cairo supports PSSurface # "application/postscript" => "ps", #TODO: support once Cairo supports PSSurface
"application/pdf" => "pdf" "application/pdf" => "pdf"
) )
_inspectdr_show(io::IO, mime::MIME, ::Void) = _inspectdr_show(io::IO, mime::MIME, ::Void, w, h) =
throw(ErrorException("Cannot show(::IO, ...) plot - not yet generated")) throw(ErrorException("Cannot show(::IO, ...) plot - not yet generated"))
_inspectdr_show(io::IO, mime::MIME, mplot) = show(io, mime, mplot) function _inspectdr_show(io::IO, mime::MIME, mplot, w, h)
InspectDR._show(io, mime, mplot, Float64(w), Float64(h))
end
for (mime, fmt) in _inspectdr_mimeformats_dpi for (mime, fmt) in _inspectdr_mimeformats_dpi
@eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend}) @eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend})
dpi = plt[:dpi]#TODO: support dpi = plt[:dpi]#TODO: support
_inspectdr_show(io, mime, _inspectdr_getmplot(plt.o)) _inspectdr_show(io, mime, _inspectdr_getmplot(plt.o), plt[:size]...)
end end
end end
for (mime, fmt) in _inspectdr_mimeformats_nodpi for (mime, fmt) in _inspectdr_mimeformats_nodpi
@eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend}) @eval function _show(io::IO, mime::MIME{Symbol($mime)}, plt::Plot{InspectDRBackend})
_inspectdr_show(io, mime, _inspectdr_getmplot(plt.o)) _inspectdr_show(io, mime, _inspectdr_getmplot(plt.o), plt[:size]...)
end end
end end
_show(io::IO, mime::MIME"text/plain", plt::Plot{InspectDRBackend}) = nothing #Don't show
# ---------------------------------------------------------------- # ----------------------------------------------------------------

View File

@ -2,13 +2,18 @@
# significant contributions by: @pkofod # significant contributions by: @pkofod
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "pgfplots.jl"))
end
const _pgfplots_attr = merge_with_base_supported([ const _pgfplots_attr = merge_with_base_supported([
# :annotations, :annotations,
# :background_color_legend, :background_color_legend,
:background_color_inside, :background_color_inside,
# :background_color_outside, # :background_color_outside,
# :foreground_color_legend, :foreground_color_grid, :foreground_color_axis, # :foreground_color_legend,
# :foreground_color_text, :foreground_color_border, :foreground_color_grid, :foreground_color_axis,
:foreground_color_text, :foreground_color_border,
:label, :label,
:seriescolor, :seriesalpha, :seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha, :linecolor, :linestyle, :linewidth, :linealpha,
@ -22,19 +27,23 @@ const _pgfplots_attr = merge_with_base_supported([
:guide, :lims, :ticks, :scale, :flip, :rotation, :guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont, :tickfont, :guidefont, :legendfont,
:grid, :legend, :grid, :legend,
# :colorbar, :colorbar,
# :marker_z, :levels, :fill_z, :line_z, :marker_z, :levels,
# :ribbon, :quiver, :arrow, # :ribbon, :quiver, :arrow,
# :orientation, # :orientation,
# :overwrite_figure, # :overwrite_figure,
# :polar, :polar,
# :normalize, :weights, :contours, # :normalize, :weights, :contours,
:aspect_ratio, :aspect_ratio,
# :match_dimensions, # :match_dimensions,
:tick_direction,
:framestyle,
:camera,
:contour_labels,
]) ])
const _pgfplots_seriestype = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour] const _pgfplots_seriestype = [:path, :path3d, :scatter, :steppre, :stepmid, :steppost, :histogram2d, :ysticks, :xsticks, :contour, :shape, :straightline,]
const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] const _pgfplots_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _pgfplots_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon] #vcat(_allMarkers, Shape) const _pgfplots_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon, :hline] #vcat(_allMarkers, Shape)
const _pgfplots_scale = [:identity, :ln, :log2, :log10] const _pgfplots_scale = [:identity, :ln, :log2, :log10]
@ -79,6 +88,7 @@ const _pgfplots_markers = KW(
:star6 => "asterisk", :star6 => "asterisk",
:diamond => "diamond*", :diamond => "diamond*",
:pentagon => "pentagon*", :pentagon => "pentagon*",
:hline => "-"
) )
const _pgfplots_legend_pos = KW( const _pgfplots_legend_pos = KW(
@ -86,6 +96,7 @@ const _pgfplots_legend_pos = KW(
:bottomright => "south east", :bottomright => "south east",
:topright => "north east", :topright => "north east",
:topleft => "north west", :topleft => "north west",
:outertopright => "outer north east",
) )
@ -98,71 +109,131 @@ const _pgf_series_extrastyle = KW(
:xsticks => "xcomb", :xsticks => "xcomb",
) )
# PGFPlots uses the anchors to define orientations for example to align left
# one needs to use the right edge as anchor
const _pgf_annotation_halign = KW(
:center => "",
:left => "right",
:right => "left"
)
const _pgf_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none]
const _pgf_framestyle_defaults = Dict(:semi => :box)
function pgf_framestyle(style::Symbol)
if style in _pgf_framestyles
return style
else
default_style = get(_pgf_framestyle_defaults, style, :axes)
warn("Framestyle :$style is not (yet) supported by the PGFPlots backend. :$default_style was cosen instead.")
default_style
end
end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
# takes in color,alpha, and returns color and alpha appropriate for pgf style # takes in color,alpha, and returns color and alpha appropriate for pgf style
function pgf_color(c) function pgf_color(c::Colorant)
cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", red(c), green(c), blue(c)) cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", red(c), green(c), blue(c))
cstr, alpha(c) cstr, alpha(c)
end end
function pgf_fillstyle(d::KW) function pgf_color(grad::ColorGradient)
cstr,a = pgf_color(d[:fillcolor]) # Can't handle ColorGradient here, fallback to defaults.
cstr = @sprintf("{rgb,1:red,%.8f;green,%.8f;blue,%.8f}", 0.0, 0.60560316,0.97868012)
cstr, 1
end
# Generates a colormap for pgfplots based on a ColorGradient
function pgf_colormap(grad::ColorGradient)
join(map(grad.colors) do c
@sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c),blue(c))
end,", ")
end
pgf_thickness_scaling(plt::Plot) = plt[:thickness_scaling]
pgf_thickness_scaling(sp::Subplot) = pgf_thickness_scaling(sp.plt)
pgf_thickness_scaling(series) = pgf_thickness_scaling(series[:subplot])
function pgf_fillstyle(d, i = 1)
cstr,a = pgf_color(get_fillcolor(d, i))
fa = get_fillalpha(d, i)
if fa != nothing
a = fa
end
"fill = $cstr, fill opacity=$a" "fill = $cstr, fill opacity=$a"
end end
function pgf_linestyle(d::KW) function pgf_linestyle(linewidth::Real, color, α = 1, linestyle = "solid")
cstr,a = pgf_color(d[:linecolor]) cstr, a = pgf_color(plot_color(color, α))
""" """
color = $cstr, color = $cstr,
draw opacity=$a, draw opacity = $a,
line width=$(d[:linewidth]), line width = $linewidth,
$(get(_pgfplots_linestyles, d[:linestyle], "solid"))""" $(get(_pgfplots_linestyles, linestyle, "solid"))"""
end end
function pgf_marker(d::KW) function pgf_linestyle(d, i = 1)
shape = d[:markershape] lw = pgf_thickness_scaling(d) * get_linewidth(d, i)
cstr, a = pgf_color(d[:markercolor]) lc = get_linecolor(d, i)
cstr_stroke, a_stroke = pgf_color(d[:markerstrokecolor]) la = get_linealpha(d, i)
ls = get_linestyle(d, i)
return pgf_linestyle(lw, lc, la, ls)
end
function pgf_font(fontsize, thickness_scaling = 1, font = "\\selectfont")
fs = fontsize * thickness_scaling
return string("{\\fontsize{", fs, " pt}{", 1.3fs, " pt}", font, "}")
end
function pgf_marker(d, i = 1)
shape = _cycle(d[:markershape], i)
cstr, a = pgf_color(plot_color(get_markercolor(d, i), get_markeralpha(d, i)))
cstr_stroke, a_stroke = pgf_color(plot_color(get_markerstrokecolor(d, i), get_markerstrokealpha(d, i)))
""" """
mark = $(get(_pgfplots_markers, shape, "*")), mark = $(get(_pgfplots_markers, shape, "*")),
mark size = $(0.5 * d[:markersize]), mark size = $(pgf_thickness_scaling(d) * 0.5 * _cycle(d[:markersize], i)),
mark options = { mark options = {
color = $cstr_stroke, draw opacity = $a_stroke, color = $cstr_stroke, draw opacity = $a_stroke,
fill = $cstr, fill opacity = $a, fill = $cstr, fill opacity = $a,
line width = $(d[:markerstrokewidth]), line width = $(pgf_thickness_scaling(d) * _cycle(d[:markerstrokewidth], i)),
rotate = $(shape == :dtriangle ? 180 : 0), rotate = $(shape == :dtriangle ? 180 : 0),
$(get(_pgfplots_linestyles, d[:markerstrokestyle], "solid")) $(get(_pgfplots_linestyles, _cycle(d[:markerstrokestyle], i), "solid"))
}""" }"""
end end
function pgf_add_annotation!(o, x, y, val, thickness_scaling = 1)
# Construct the style string.
# Currently supports color and orientation
cstr,a = pgf_color(val.font.color)
push!(o, PGFPlots.Plots.Node(val.str, # Annotation Text
x, y,
style="""
$(get(_pgf_annotation_halign,val.font.halign,"")),
color=$cstr, draw opacity=$(convert(Float16,a)),
rotate=$(val.font.rotation),
font=$(pgf_font(val.font.pointsize, thickness_scaling))
"""))
end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
function pgf_series(sp::Subplot, series::Series) function pgf_series(sp::Subplot, series::Series)
d = series.d d = series.d
st = d[:seriestype] st = d[:seriestype]
style = [] series_collection = PGFPlots.Plot[]
kw = KW()
push!(style, pgf_linestyle(d))
push!(style, pgf_marker(d))
if d[:fillrange] != nothing
push!(style, pgf_fillstyle(d))
end
# add to legend?
if sp[:legend] != :none && should_add_to_legend(series)
kw[:legendentry] = d[:label]
else
push!(style, "forget plot")
end
# function args # function args
args = if st == :contour args = if st == :contour
d[:z].surf, d[:x], d[:y] d[:z].surf, d[:x], d[:y]
elseif is3d(st) elseif is3d(st)
d[:x], d[:y], d[:z] d[:x], d[:y], d[:z]
elseif st == :straightline
straightline_data(series)
elseif st == :shape
shape_data(series)
elseif ispolar(sp)
theta, r = filter_radial_data(d[:x], d[:y], axis_limits(sp[:yaxis]))
rad2deg.(theta), r
else else
d[:x], d[:y] d[:x], d[:y]
end end
@ -173,34 +244,131 @@ function pgf_series(sp::Subplot, series::Series)
else else
a a
end, args) end, args)
# for (i,a) in enumerate(args)
# if typeof(a) <: AbstractVector && typeof(a) != Vector
# args[i] = collect(a)
# end
# end
# include additional style, then add to the kw if st in (:contour, :histogram2d)
style = []
kw = KW()
push!(style, pgf_linestyle(d))
push!(style, pgf_marker(d))
push!(style, "forget plot")
kw[:style] = join(style, ',')
func = if st == :histogram2d
PGFPlots.Histogram2
else
kw[:labels] = series[:contour_labels]
kw[:levels] = series[:levels]
PGFPlots.Contour
end
push!(series_collection, func(args...; kw...))
else
# series segments
segments = iter_segments(series)
for (i, rng) in enumerate(segments)
style = []
kw = KW()
push!(style, pgf_linestyle(d, i))
push!(style, pgf_marker(d, i))
if st == :shape
push!(style, pgf_fillstyle(d, i))
end
# add to legend?
if i == 1 && sp[:legend] != :none && should_add_to_legend(series)
if d[:fillrange] != nothing
push!(style, "forget plot")
push!(series_collection, pgf_fill_legend_hack(d, args))
else
kw[:legendentry] = d[:label]
if st == :shape # || d[:fillrange] != nothing
push!(style, "area legend")
end
end
else
push!(style, "forget plot")
end
seg_args = (arg[rng] for arg in args)
# include additional style, then add to the kw
if haskey(_pgf_series_extrastyle, st)
push!(style, _pgf_series_extrastyle[st])
end
kw[:style] = join(style, ',')
# add fillrange
if series[:fillrange] != nothing && st != :shape
push!(series_collection, pgf_fillrange_series(series, i, _cycle(series[:fillrange], rng), seg_args...))
end
# build/return the series object
func = if st == :path3d
PGFPlots.Linear3
elseif st == :scatter
PGFPlots.Scatter
else
PGFPlots.Linear
end
push!(series_collection, func(seg_args...; kw...))
end
end
series_collection
end
function pgf_fillrange_series(series, i, fillrange, args...)
st = series[:seriestype]
style = []
kw = KW()
push!(style, "line width = 0")
push!(style, "draw opacity = 0")
push!(style, pgf_fillstyle(series, i))
push!(style, pgf_marker(series, i))
push!(style, "forget plot")
if haskey(_pgf_series_extrastyle, st) if haskey(_pgf_series_extrastyle, st)
push!(style, _pgf_series_extrastyle[st]) push!(style, _pgf_series_extrastyle[st])
end end
kw[:style] = join(style, ',') kw[:style] = join(style, ',')
func = is3d(series) ? PGFPlots.Linear3 : PGFPlots.Linear
return func(pgf_fillrange_args(fillrange, args...)...; kw...)
end
# build/return the series object function pgf_fillrange_args(fillrange, x, y)
n = length(x)
x_fill = [x; x[n:-1:1]; x[1]]
y_fill = [y; _cycle(fillrange, n:-1:1); y[1]]
return x_fill, y_fill
end
function pgf_fillrange_args(fillrange, x, y, z)
n = length(x)
x_fill = [x; x[n:-1:1]; x[1]]
y_fill = [y; y[n:-1:1]; x[1]]
z_fill = [z; _cycle(fillrange, n:-1:1); z[1]]
return x_fill, y_fill, z_fill
end
function pgf_fill_legend_hack(d, args)
style = []
kw = KW()
push!(style, pgf_linestyle(d, 1))
push!(style, pgf_marker(d, 1))
push!(style, pgf_fillstyle(d, 1))
push!(style, "area legend")
kw[:legendentry] = d[:label]
kw[:style] = join(style, ',')
st = d[:seriestype]
func = if st == :path3d func = if st == :path3d
PGFPlots.Linear3 PGFPlots.Linear3
elseif st == :scatter elseif st == :scatter
PGFPlots.Scatter PGFPlots.Scatter
elseif st == :histogram2d
PGFPlots.Histogram2
elseif st == :contour
PGFPlots.Contour
else else
PGFPlots.Linear PGFPlots.Linear
end end
func(args...; kw...) return func(([arg[1]] for arg in args)...; kw...)
end end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function pgf_axis(sp::Subplot, letter) function pgf_axis(sp::Subplot, letter)
@ -208,9 +376,19 @@ function pgf_axis(sp::Subplot, letter)
style = [] style = []
kw = KW() kw = KW()
# turn off scaled ticks
push!(style, "scaled $(letter) ticks = false")
# set to supported framestyle
framestyle = pgf_framestyle(sp[:framestyle])
# axis guide # axis guide
kw[Symbol(letter,:label)] = axis[:guide] kw[Symbol(letter,:label)] = axis[:guide]
# Add label font
cstr, α = pgf_color(plot_color(axis[:guidefontcolor]))
push!(style, string(letter, "label style = {font = ", pgf_font(axis[:guidefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:guidefontrotation], "}"))
# flip/reverse? # flip/reverse?
axis[:flip] && push!(style, "$letter dir=reverse") axis[:flip] && push!(style, "$letter dir=reverse")
@ -222,22 +400,74 @@ function pgf_axis(sp::Subplot, letter)
end end
# ticks on or off # ticks on or off
if axis[:ticks] in (nothing, false) if axis[:ticks] in (nothing, false, :none) || framestyle == :none
push!(style, "$(letter)majorticks=false") push!(style, "$(letter)majorticks=false")
end end
# grid on or off
if axis[:grid] && framestyle != :none
push!(style, "$(letter)majorgrids = true")
else
push!(style, "$(letter)majorgrids = false")
end
# limits # limits
# TODO: support zlims # TODO: support zlims
if letter != :z if letter != :z
lims = axis_limits(axis) lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(axis)) : axis_limits(axis)
kw[Symbol(letter,:min)] = lims[1] kw[Symbol(letter,:min)] = lims[1]
kw[Symbol(letter,:max)] = lims[2] kw[Symbol(letter,:max)] = lims[2]
end end
if !(axis[:ticks] in (nothing, false, :none, :auto)) if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none
ticks = get_ticks(axis) ticks = get_ticks(axis)
push!(style, string(letter, "tick = {", join(ticks[1],","), "}")) #pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values
push!(style, string(letter, "ticklabels = {", join(ticks[2],","), "}")) tick_values = ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] : ticks[1]
push!(style, string(letter, "tick = {", join(tick_values,","), "}"))
if axis[:showaxis] && axis[:scale] in (:ln, :log2, :log10) && axis[:ticks] == :auto
# wrap the power part of label with }
tick_labels = String[begin
base, power = split(label, "^")
power = string("{", power, "}")
string(base, "^", power)
end for label in ticks[2]]
push!(style, string(letter, "ticklabels = {\$", join(tick_labels,"\$,\$"), "\$}"))
elseif axis[:showaxis]
tick_labels = ispolar(sp) && letter == :x ? [ticks[2][3:end]..., "0", "45"] : ticks[2]
if axis[:formatter] in (:scientific, :auto)
tick_labels = string.("\$", convert_sci_unicode.(tick_labels), "\$")
tick_labels = replace.(tick_labels, "×", "\\times")
end
push!(style, string(letter, "ticklabels = {", join(tick_labels,","), "}"))
else
push!(style, string(letter, "ticklabels = {}"))
end
push!(style, string(letter, "tick align = ", (axis[:tick_direction] == :out ? "outside" : "inside")))
cstr, α = pgf_color(plot_color(axis[:tickfontcolor]))
push!(style, string(letter, "ticklabel style = {font = ", pgf_font(axis[:tickfontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", axis[:tickfontrotation], "}"))
push!(style, string(letter, " grid style = {", pgf_linestyle(pgf_thickness_scaling(sp) * axis[:gridlinewidth], axis[:foreground_color_grid], axis[:gridalpha], axis[:gridstyle]), "}"))
end
# framestyle
if framestyle in (:axes, :origin)
axispos = framestyle == :axes ? "left" : "middle"
# the * after lines disables the arrows at the axes
push!(style, string("axis lines* = ", axispos))
end
if framestyle == :zerolines
push!(style, string("extra ", letter, " ticks = 0"))
push!(style, string("extra ", letter, " tick labels = "))
push!(style, string("extra ", letter, " tick style = {grid = major, major grid style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_axis], 1.0), "}}"))
end
if !axis[:showaxis]
push!(style, "separate axis lines")
end
if !axis[:showaxis] || framestyle in (:zerolines, :grid, :none)
push!(style, string(letter, " axis line style = {draw opacity = 0}"))
else
push!(style, string(letter, " axis line style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_axis], 1.0), "}"))
end end
# return the style list and KW args # return the style list and KW args
@ -249,8 +479,12 @@ end
function _update_plot_object(plt::Plot{PGFPlotsBackend}) function _update_plot_object(plt::Plot{PGFPlotsBackend})
plt.o = PGFPlots.Axis[] plt.o = PGFPlots.Axis[]
# Obtain the total height of the plot by extracting the maximal bottom
# coordinate from the bounding box.
total_height = bottom(bbox(plt.layout))
for sp in plt.subplots for sp in plt.subplots
# first build the PGFPlots.Axis object # first build the PGFPlots.Axis object
style = ["unbounded coords=jump"] style = ["unbounded coords=jump"]
kw = KW() kw = KW()
@ -265,10 +499,12 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
# bounding box values are in mm # bounding box values are in mm
# note: bb origin is top-left, pgf is bottom-left # note: bb origin is top-left, pgf is bottom-left
# A round on 2 decimal places should be enough precision for 300 dpi
# plots.
bb = bbox(sp) bb = bbox(sp)
push!(style, """ push!(style, """
xshift = $(left(bb).value)mm, xshift = $(left(bb).value)mm,
yshift = $((height(bb) - (bottom(bb))).value)mm, yshift = $(round((total_height - (bottom(bb))).value,2))mm,
axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])} axis background/.style={fill=$(pgf_color(sp[:background_color_inside])[1])}
""") """)
kw[:width] = "$(width(bb).value)mm" kw[:width] = "$(width(bb).value)mm"
@ -276,9 +512,10 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
if sp[:title] != "" if sp[:title] != ""
kw[:title] = "$(sp[:title])" kw[:title] = "$(sp[:title])"
cstr, α = pgf_color(plot_color(sp[:titlefontcolor]))
push!(style, string("title style = {font = ", pgf_font(sp[:titlefontsize], pgf_thickness_scaling(sp)), ", color = ", cstr, ", draw opacity = ", α, ", rotate = ", sp[:titlefontrotation], "}"))
end end
sp[:grid] && push!(style, "grid = major")
if sp[:aspect_ratio] in (1, :equal) if sp[:aspect_ratio] in (1, :equal)
kw[:axisEqual] = "true" kw[:axisEqual] = "true"
end end
@ -287,20 +524,76 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
if haskey(_pgfplots_legend_pos, legpos) if haskey(_pgfplots_legend_pos, legpos)
kw[:legendPos] = _pgfplots_legend_pos[legpos] kw[:legendPos] = _pgfplots_legend_pos[legpos]
end end
cstr, a = pgf_color(plot_color(sp[:background_color_legend]))
push!(style, string("legend style = {", pgf_linestyle(pgf_thickness_scaling(sp), sp[:foreground_color_legend], 1.0, "solid"), ",", "fill = $cstr,", "font = ", pgf_font(sp[:legendfontsize], pgf_thickness_scaling(sp)), "}"))
o = PGFPlots.Axis(; style = style, kw...) if any(s[:seriestype] == :contour for s in series_list(sp))
kw[:view] = "{0}{90}"
kw[:colorbar] = !(sp[:colorbar] in (:none, :off, :hide, false))
elseif is3d(sp)
azim, elev = sp[:camera]
kw[:view] = "{$(azim)}{$(elev)}"
end
axisf = PGFPlots.Axis
if sp[:projection] == :polar
axisf = PGFPlots.PolarAxis
#make radial axis vertical
kw[:xmin] = 90
kw[:xmax] = 450
end
# Search series for any gradient. In case one series uses a gradient set
# the colorbar and colomap.
# The reasoning behind doing this on the axis level is that pgfplots
# colorbar seems to only works on axis level and needs the proper colormap for
# correctly displaying it.
# It's also possible to assign the colormap to the series itself but
# then the colormap needs to be added twice, once for the axis and once for the
# series.
# As it is likely that all series within the same axis use the same
# colormap this should not cause any problem.
for series in series_list(sp)
for col in (:markercolor, :fillcolor, :linecolor)
if typeof(series.d[col]) == ColorGradient
push!(style,"colormap={plots}{$(pgf_colormap(series.d[col]))}")
if sp[:colorbar] == :none
kw[:colorbar] = "false"
else
kw[:colorbar] = "true"
end
# goto is needed to break out of col and series for
@goto colorbar_end
end
end
end
@label colorbar_end
o = axisf(; style = join(style, ","), kw...)
# add the series object to the PGFPlots.Axis # add the series object to the PGFPlots.Axis
for series in series_list(sp) for series in series_list(sp)
push!(o, pgf_series(sp, series)) push!.(o, pgf_series(sp, series))
# add series annotations
anns = series[:series_annotations]
for (xi,yi,str,fnt) in EachAnn(anns, series[:x], series[:y])
pgf_add_annotation!(o, xi, yi, PlotText(str, fnt), pgf_thickness_scaling(series))
end
end end
# add the annotations
for ann in sp[:annotations]
pgf_add_annotation!(o, locate_annotation(sp, ann...)..., pgf_thickness_scaling(sp))
end
# add the PGFPlots.Axis to the list # add the PGFPlots.Axis to the list
push!(plt.o, o) push!(plt.o, o)
end end
end end
function _show(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend}) function _show(io::IO, mime::MIME"image/svg+xml", plt::Plot{PGFPlotsBackend})
show(io, mime, plt.o) show(io, mime, plt.o)
end end

View File

@ -1,11 +1,15 @@
# https://plot.ly/javascript/getting-started # https://plot.ly/javascript/getting-started
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "plotly.jl"))
end
const _plotly_attr = merge_with_base_supported([ const _plotly_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend, :background_color_inside, :background_color_outside,
:foreground_color_legend, :foreground_color_guide, :foreground_color_legend, :foreground_color_guide,
# :foreground_color_grid, :foreground_color_axis, :foreground_color_grid, :foreground_color_axis,
:foreground_color_text, :foreground_color_border, :foreground_color_text, :foreground_color_border,
:foreground_color_title, :foreground_color_title,
:label, :label,
@ -15,12 +19,18 @@ const _plotly_attr = merge_with_base_supported([
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle, :markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
:fillrange, :fillcolor, :fillalpha, :fillrange, :fillcolor, :fillalpha,
:bins, :bins,
:title, :title_location, :titlefont, :title, :title_location,
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign,
:titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefontcolor,
:window_title, :window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation, :guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont, :tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar, :grid, :gridalpha, :gridlinewidth,
:marker_z, :fill_z, :levels, :legend, :colorbar, :colorbar_title,
:marker_z, :fill_z, :line_z, :levels,
:ribbon, :quiver, :ribbon, :quiver,
:orientation, :orientation,
# :overwrite_figure, # :overwrite_figure,
@ -31,11 +41,17 @@ const _plotly_attr = merge_with_base_supported([
:hover, :hover,
:inset_subplots, :inset_subplots,
:bar_width, :bar_width,
:clims,
:framestyle,
:tick_direction,
:camera,
:contour_labels,
]) ])
const _plotly_seriestype = [ const _plotly_seriestype = [
:path, :scatter, :bar, :pie, :heatmap, :path, :scatter, :pie, :heatmap,
:contour, :surface, :wireframe, :path3d, :scatter3d, :shape, :scattergl, :contour, :surface, :wireframe, :path3d, :scatter3d, :shape, :scattergl,
:straightline
] ]
const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot] const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot]
const _plotly_marker = [ const _plotly_marker = [
@ -45,6 +61,17 @@ const _plotly_marker = [
const _plotly_scale = [:identity, :log10] const _plotly_scale = [:identity, :log10]
is_subplot_supported(::PlotlyBackend) = true is_subplot_supported(::PlotlyBackend) = true
# is_string_supported(::PlotlyBackend) = true # is_string_supported(::PlotlyBackend) = true
const _plotly_framestyles = [:box, :axes, :zerolines, :grid, :none]
const _plotly_framestyle_defaults = Dict(:semi => :box, :origin => :zerolines)
function _plotly_framestyle(style::Symbol)
if style in _plotly_framestyles
return style
else
default_style = get(_plotly_framestyle_defaults, style, :axes)
warn("Framestyle :$style is not supported by Plotly and PlotlyJS. :$default_style was cosen instead.")
default_style
end
end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
@ -61,8 +88,6 @@ const _plotly_js_path_remote = "https://cdn.plot.ly/plotly-latest.min.js"
function _initialize_backend(::PlotlyBackend; kw...) function _initialize_backend(::PlotlyBackend; kw...)
@eval begin @eval begin
import JSON
_js_code = open(readstring, _plotly_js_path, "r") _js_code = open(readstring, _plotly_js_path, "r")
# borrowed from https://github.com/plotly/plotly.py/blob/2594076e29584ede2d09f2aa40a8a195b3f3fc66/plotly/offline/offline.py#L64-L71 c/o @spencerlyon2 # borrowed from https://github.com/plotly/plotly.py/blob/2594076e29584ede2d09f2aa40a8a195b3f3fc66/plotly/offline/offline.py#L64-L71 c/o @spencerlyon2
@ -106,7 +131,7 @@ const _plotly_legend_pos = KW(
) )
plotly_legend_pos(pos::Symbol) = get(_plotly_legend_pos, pos, [1.,1.]) plotly_legend_pos(pos::Symbol) = get(_plotly_legend_pos, pos, [1.,1.])
plotly_legend_pos{S<:Real, T<:Real}(v::Tuple{S,T}) = v plotly_legend_pos(v::Tuple{S,T}) where {S<:Real, T<:Real} = v
function plotly_font(font::Font, color = font.color) function plotly_font(font::Font, color = font.color)
KW( KW(
@ -122,7 +147,7 @@ function plotly_annotation_dict(x, y, val; xref="paper", yref="paper")
:text => val, :text => val,
:xref => xref, :xref => xref,
:x => x, :x => x,
:yref => xref, :yref => yref,
:y => y, :y => y,
:showarrow => false, :showarrow => false,
) )
@ -208,43 +233,57 @@ function plotly_domain(sp::Subplot, letter)
end end
function plotly_axis(axis::Axis, sp::Subplot) function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
letter = axis[:letter] letter = axis[:letter]
framestyle = sp[:framestyle]
ax = KW( ax = KW(
:visible => framestyle != :none,
:title => axis[:guide], :title => axis[:guide],
:showgrid => sp[:grid], :showgrid => axis[:grid],
:zeroline => false, :gridcolor => rgba_string(plot_color(axis[:foreground_color_grid], axis[:gridalpha])),
:ticks => "inside", :gridwidth => axis[:gridlinewidth],
:zeroline => framestyle == :zerolines,
:zerolinecolor => rgba_string(axis[:foreground_color_axis]),
:showline => framestyle in (:box, :axes) && axis[:showaxis],
:linecolor => rgba_string(plot_color(axis[:foreground_color_axis])),
:ticks => axis[:tick_direction] == :out ? "outside" : "inside",
:mirror => framestyle == :box,
:showticklabels => axis[:showaxis],
) )
if letter in (:x,:y) if letter in (:x,:y)
ax[:domain] = plotly_domain(sp, letter) ax[:domain] = plotly_domain(sp, letter)
ax[:anchor] = "$(letter==:x ? :y : :x)$(plotly_subplot_index(sp))" if is3d(sp)
# don't link 3d axes for synchronized interactivity
x_idx = y_idx = sp[:subplot_index]
else
x_idx, y_idx = plotly_link_indicies(plt, sp)
end
ax[:anchor] = "$(letter==:x ? "y$(y_idx)" : "x$(x_idx)")"
end end
ax[:tickangle] = -axis[:rotation] ax[:tickangle] = -axis[:rotation]
ax[:type] = plotly_scale(axis[:scale])
lims = axis_limits(axis)
if !(axis[:ticks] in (nothing, :none)) if axis[:ticks] != :native || axis[:lims] != :auto
ax[:titlefont] = plotly_font(axis[:guidefont], axis[:foreground_color_guide]) ax[:range] = map(scalefunc(axis[:scale]), lims)
ax[:type] = plotly_scale(axis[:scale]) end
ax[:tickfont] = plotly_font(axis[:tickfont], axis[:foreground_color_text])
ax[:tickcolor] = rgba_string(axis[:foreground_color_border])
ax[:linecolor] = rgba_string(axis[:foreground_color_border])
# lims if !(axis[:ticks] in (nothing, :none, false))
lims = axis[:lims] ax[:titlefont] = plotly_font(guidefont(axis))
if lims != :auto && limsType(lims) == :limits ax[:tickfont] = plotly_font(tickfont(axis))
ax[:range] = map(scalefunc(axis[:scale]), lims) ax[:tickcolor] = framestyle in (:zerolines, :grid) || !axis[:showaxis] ? rgba_string(invisible()) : rgb_string(axis[:foreground_color_axis])
end ax[:linecolor] = rgba_string(axis[:foreground_color_axis])
# flip # flip
if axis[:flip] if axis[:flip]
ax[:autorange] = "reversed" ax[:range] = reverse(ax[:range])
end end
# ticks # ticks
ticks = get_ticks(axis) if axis[:ticks] != :native
if ticks != :auto ticks = get_ticks(axis)
ttype = ticksType(ticks) ttype = ticksType(ticks)
if ttype == :ticks if ttype == :ticks
ax[:tickmode] = "array" ax[:tickmode] = "array"
@ -259,6 +298,23 @@ function plotly_axis(axis::Axis, sp::Subplot)
ax[:showgrid] = false ax[:showgrid] = false
end end
ax
end
function plotly_polaraxis(axis::Axis)
ax = KW(
:visible => axis[:showaxis],
:showline => axis[:grid],
)
if axis[:letter] == :x
ax[:range] = rad2deg.(axis_limits(axis))
else
ax[:range] = axis_limits(axis)
ax[:orientation] = -90
end
ax ax
end end
@ -268,14 +324,15 @@ function plotly_layout(plt::Plot)
w, h = plt[:size] w, h = plt[:size]
d_out[:width], d_out[:height] = w, h d_out[:width], d_out[:height] = w, h
d_out[:paper_bgcolor] = rgba_string(plt[:background_color_outside]) d_out[:paper_bgcolor] = rgba_string(plt[:background_color_outside])
d_out[:margin] = KW(:l=>0, :b=>0, :r=>0, :t=>20) d_out[:margin] = KW(:l=>0, :b=>20, :r=>0, :t=>20)
d_out[:annotations] = KW[] d_out[:annotations] = KW[]
multiple_subplots = length(plt.subplots) > 1
for sp in plt.subplots for sp in plt.subplots
spidx = plotly_subplot_index(sp) spidx = multiple_subplots ? sp[:subplot_index] : ""
x_idx, y_idx = multiple_subplots ? plotly_link_indicies(plt, sp) : ("", "")
# add an annotation for the title... positioned horizontally relative to plotarea, # add an annotation for the title... positioned horizontally relative to plotarea,
# but vertically just below the top of the subplot bounding box # but vertically just below the top of the subplot bounding box
if sp[:title] != "" if sp[:title] != ""
@ -289,22 +346,40 @@ function plotly_layout(plt::Plot)
0.5 * (left(bb) + right(bb)) 0.5 * (left(bb) + right(bb))
end end
titlex, titley = xy_mm_to_pcts(xmm, top(bbox(sp)), w*px, h*px) titlex, titley = xy_mm_to_pcts(xmm, top(bbox(sp)), w*px, h*px)
titlefont = font(sp[:titlefont], :top, sp[:foreground_color_title]) title_font = font(titlefont(sp), :top)
push!(d_out[:annotations], plotly_annotation_dict(titlex, titley, text(sp[:title], titlefont))) push!(d_out[:annotations], plotly_annotation_dict(titlex, titley, text(sp[:title], title_font)))
end end
d_out[:plot_bgcolor] = rgba_string(sp[:background_color_inside]) d_out[:plot_bgcolor] = rgba_string(sp[:background_color_inside])
# set to supported framestyle
sp[:framestyle] = _plotly_framestyle(sp[:framestyle])
# if any(is3d, seriesargs) # if any(is3d, seriesargs)
if is3d(sp) if is3d(sp)
azim = sp[:camera][1] - 90 #convert azimuthal to match GR behaviour
theta = 90 - sp[:camera][2] #spherical coordinate angle from z axis
d_out[:scene] = KW( d_out[:scene] = KW(
Symbol("xaxis$spidx") => plotly_axis(sp[:xaxis], sp), Symbol("xaxis$(spidx)") => plotly_axis(plt, sp[:xaxis], sp),
Symbol("yaxis$spidx") => plotly_axis(sp[:yaxis], sp), Symbol("yaxis$(spidx)") => plotly_axis(plt, sp[:yaxis], sp),
Symbol("zaxis$spidx") => plotly_axis(sp[:zaxis], sp), Symbol("zaxis$(spidx)") => plotly_axis(plt, sp[:zaxis], sp),
#2.6 multiplier set camera eye such that whole plot can be seen
:camera => KW(
:eye => KW(
:x => cosd(azim)*sind(theta)*2.6,
:y => sind(azim)*sind(theta)*2.6,
:z => cosd(theta)*2.6,
),
),
) )
elseif ispolar(sp)
d_out[Symbol("angularaxis$(spidx)")] = plotly_polaraxis(sp[:xaxis])
d_out[Symbol("radialaxis$(spidx)")] = plotly_polaraxis(sp[:yaxis])
else else
d_out[Symbol("xaxis$spidx")] = plotly_axis(sp[:xaxis], sp) d_out[Symbol("xaxis$(x_idx)")] = plotly_axis(plt, sp[:xaxis], sp)
d_out[Symbol("yaxis$spidx")] = plotly_axis(sp[:yaxis], sp) # don't allow yaxis to be reupdated/reanchored in a linked subplot
spidx == y_idx ? d_out[Symbol("yaxis$(y_idx)")] = plotly_axis(plt, sp[:yaxis], sp) : nothing
end end
# legend # legend
@ -314,15 +389,17 @@ function plotly_layout(plt::Plot)
d_out[:legend] = KW( d_out[:legend] = KW(
:bgcolor => rgba_string(sp[:background_color_legend]), :bgcolor => rgba_string(sp[:background_color_legend]),
:bordercolor => rgba_string(sp[:foreground_color_legend]), :bordercolor => rgba_string(sp[:foreground_color_legend]),
:font => plotly_font(sp[:legendfont], sp[:foreground_color_legend]), :font => plotly_font(legendfont(sp)),
:tracegroupgap => 0,
:x => xpos, :x => xpos,
:y => ypos :y => ypos
) )
end end
# annotations # annotations
append!(d_out[:annotations], KW[plotly_annotation_dict(ann...; xref = "x$spidx", yref = "y$spidx") for ann in sp[:annotations]]) for ann in sp[:annotations]
append!(d_out[:annotations], KW[plotly_annotation_dict(locate_annotation(sp, ann...)...; xref = "x$(x_idx)", yref = "y$(y_idx)")])
end
# series_annotations # series_annotations
for series in series_list(sp) for series in series_list(sp)
anns = series[:series_annotations] anns = series[:series_annotations]
@ -330,7 +407,7 @@ function plotly_layout(plt::Plot)
push!(d_out[:annotations], plotly_annotation_dict( push!(d_out[:annotations], plotly_annotation_dict(
xi, xi,
yi, yi,
PlotText(str,fnt); xref = "x$spidx", yref = "y$spidx") PlotText(str,fnt); xref = "x$(x_idx)", yref = "y$(y_idx)")
) )
end end
end end
@ -366,9 +443,17 @@ end
function plotly_colorscale(grad::ColorGradient, α) function plotly_colorscale(grad::ColorGradient, α)
[[grad.values[i], rgb_string(grad.colors[i])] for i in 1:length(grad.colors)] [[grad.values[i], rgba_string(plot_color(grad.colors[i], α))] for i in 1:length(grad.colors)]
end end
plotly_colorscale(c, α) = plotly_colorscale(cgrad(alpha=α), α) plotly_colorscale(c, α) = plotly_colorscale(cgrad(alpha=α), α)
function plotly_colorscale(c::AbstractVector{<:RGBA}, α)
if length(c) == 1
return [[0.0, rgba_string(plot_color(c[1], α))], [1.0, rgba_string(plot_color(c[1], α))]]
else
vals = linspace(0.0, 1.0, length(c))
return [[vals[i], rgba_string(plot_color(c[i], α))] for i in eachindex(c)]
end
end
# plotly_colorscale(c, alpha = nothing) = plotly_colorscale(cgrad(), alpha) # plotly_colorscale(c, alpha = nothing) = plotly_colorscale(cgrad(), alpha)
@ -383,9 +468,15 @@ const _plotly_markers = KW(
:hline => "line-ew", :hline => "line-ew",
) )
function plotly_subplot_index(sp::Subplot) # find indicies of axes to which the supblot links to
spidx = sp[:subplot_index] function plotly_link_indicies(plt::Plot, sp::Subplot)
spidx == 1 ? "" : spidx if plt[:link] in (:x, :y, :both)
x_idx = sp[:xaxis].sps[1][:subplot_index]
y_idx = sp[:yaxis].sps[1][:subplot_index]
else
x_idx = y_idx = sp[:subplot_index]
end
x_idx, y_idx
end end
@ -400,14 +491,55 @@ function plotly_close_shapes(x, y)
nanvcat(xs), nanvcat(ys) nanvcat(xs), nanvcat(ys)
end end
plotly_data(v) = collect(v) function plotly_data(series::Series, letter::Symbol, data)
axis = series[:subplot][Symbol(letter, :axis)]
data = if axis[:ticks] == :native && data != nothing
plotly_native_data(axis, data)
else
data
end
if series[:seriestype] in (:heatmap, :contour, :surface, :wireframe)
plotly_surface_data(series, data)
else
plotly_data(data)
end
end
plotly_data(v) = v != nothing ? collect(v) : v
plotly_data(surf::Surface) = surf.surf plotly_data(surf::Surface) = surf.surf
plotly_data{R<:Rational}(v::AbstractArray{R}) = float(v) plotly_data(v::AbstractArray{R}) where {R<:Rational} = float(v)
plotly_surface_data(series::Series, a::AbstractVector) = a plotly_surface_data(series::Series, a::AbstractVector) = a
plotly_surface_data(series::Series, a::AbstractMatrix) = transpose_z(series, a, false) plotly_surface_data(series::Series, a::AbstractMatrix) = transpose_z(series, a, false)
plotly_surface_data(series::Series, a::Surface) = plotly_surface_data(series, a.surf) plotly_surface_data(series::Series, a::Surface) = plotly_surface_data(series, a.surf)
function plotly_native_data(axis::Axis, data::AbstractArray)
if !isempty(axis[:discrete_values])
construct_categorical_data(data, axis)
elseif axis[:formatter] in (datetimeformatter, dateformatter, timeformatter)
plotly_convert_to_datetime(data, axis[:formatter])
else
data
end
end
plotly_native_data(axis::Axis, a::Surface) = Surface(plotly_native_data(axis, a.surf))
function plotly_convert_to_datetime(x::AbstractArray, formatter::Function)
if formatter == datetimeformatter
map(xi -> replace(formatter(xi), "T", " "), x)
elseif formatter == dateformatter
map(xi -> string(formatter(xi), " 00:00:00"), x)
elseif formatter == timeformatter
map(xi -> string(Dates.Date(Dates.now()), " ", formatter(xi)), x)
else
error("Invalid DateTime formatter. Expected Plots.datetime/date/time formatter but got $formatter")
end
end
#ensures that a gradient is called if a single color is supplied where a gradient is needed (e.g. if a series recipe defines marker_z)
as_gradient(grad::ColorGradient, α) = grad
as_gradient(grad, α) = cgrad(alpha = α)
# get a dictionary representing the series params (d is the Plots-dict, d_out is the Plotly-dict) # get a dictionary representing the series params (d is the Plots-dict, d_out is the Plotly-dict)
function plotly_series(plt::Plot, series::Series) function plotly_series(plt::Plot, series::Series)
st = series[:seriestype] st = series[:seriestype]
@ -419,79 +551,77 @@ function plotly_series(plt::Plot, series::Series)
d_out = KW() d_out = KW()
# these are the axes that the series should be mapped to # these are the axes that the series should be mapped to
spidx = plotly_subplot_index(sp) x_idx, y_idx = plotly_link_indicies(plt, sp)
d_out[:xaxis] = "x$spidx" d_out[:xaxis] = "x$(x_idx)"
d_out[:yaxis] = "y$spidx" d_out[:yaxis] = "y$(y_idx)"
d_out[:showlegend] = should_add_to_legend(series) d_out[:showlegend] = should_add_to_legend(series)
x, y = plotly_data(series[:x]), plotly_data(series[:y]) if st == :straightline
x, y = straightline_data(series)
z = series[:z]
else
x, y, z = series[:x], series[:y], series[:z]
end
x, y, z = (plotly_data(series, letter, data)
for (letter, data) in zip((:x, :y, :z), (x, y, z))
)
d_out[:name] = series[:label] d_out[:name] = series[:label]
isscatter = st in (:scatter, :scatter3d, :scattergl) isscatter = st in (:scatter, :scatter3d, :scattergl)
hasmarker = isscatter || series[:markershape] != :none hasmarker = isscatter || series[:markershape] != :none
hasline = st in (:path, :path3d) hasline = st in (:path, :path3d, :straightline)
hasfillrange = st in (:path, :scatter, :scattergl, :straightline) &&
(isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple))
# for surface types, set the data d_out[:colorbar] = KW(:title => sp[:colorbar_title])
if st in (:heatmap, :contour, :surface, :wireframe)
for letter in [:x,:y,:z] clims = sp[:clims]
d_out[letter] = plotly_surface_data(series, series[letter]) if is_2tuple(clims)
end d_out[:zmin], d_out[:zmax] = clims
end end
# set the "type" # set the "type"
if st in (:path, :scatter, :scattergl) if st in (:path, :scatter, :scattergl, :straightline, :path3d, :scatter3d)
d_out[:type] = st==:scattergl ? "scattergl" : "scatter" return plotly_series_segments(series, d_out, x, y, z)
d_out[:mode] = if hasmarker
hasline ? "lines+markers" : "markers"
else
hasline ? "lines" : "none"
end
if series[:fillrange] == true || series[:fillrange] == 0
d_out[:fill] = "tozeroy"
d_out[:fillcolor] = rgba_string(series[:fillcolor])
elseif !(series[:fillrange] in (false, nothing))
warn("fillrange ignored... plotly only supports filling to zero. fillrange: $(series[:fillrange])")
end
d_out[:x], d_out[:y] = x, y
elseif st == :bar
d_out[:type] = "bar"
d_out[:x], d_out[:y], d_out[:orientation] = if isvertical(series)
x, y, "v"
else
y, x, "h"
end
d_out[:marker] = KW(:color => rgba_string(series[:fillcolor]))
elseif st == :heatmap elseif st == :heatmap
x = heatmap_edges(x, sp[:xaxis][:scale])
y = heatmap_edges(y, sp[:yaxis][:scale])
d_out[:type] = "heatmap" d_out[:type] = "heatmap"
# d_out[:x], d_out[:y], d_out[:z] = series[:x], series[:y], transpose_z(series, series[:z].surf, false) d_out[:x], d_out[:y], d_out[:z] = x, y, z
d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha])
d_out[:showscale] = hascolorbar(sp)
elseif st == :contour elseif st == :contour
d_out[:type] = "contour" d_out[:type] = "contour"
# d_out[:x], d_out[:y], d_out[:z] = series[:x], series[:y], transpose_z(series, series[:z].surf, false) d_out[:x], d_out[:y], d_out[:z] = x, y, z
# d_out[:showscale] = series[:colorbar] != :none # d_out[:showscale] = series[:colorbar] != :none
d_out[:ncontours] = series[:levels] d_out[:ncontours] = series[:levels]
d_out[:contours] = KW(:coloring => series[:fillrange] != nothing ? "fill" : "lines") d_out[:contours] = KW(:coloring => series[:fillrange] != nothing ? "fill" : "lines", :showlabels => series[:contour_labels] == true)
d_out[:colorscale] = plotly_colorscale(series[:linecolor], series[:linealpha]) d_out[:colorscale] = plotly_colorscale(series[:linecolor], series[:linealpha])
d_out[:showscale] = hascolorbar(sp)
elseif st in (:surface, :wireframe) elseif st in (:surface, :wireframe)
d_out[:type] = "surface" d_out[:type] = "surface"
# d_out[:x], d_out[:y], d_out[:z] = series[:x], series[:y], transpose_z(series, series[:z].surf, false) d_out[:x], d_out[:y], d_out[:z] = x, y, z
if st == :wireframe if st == :wireframe
d_out[:hidesurface] = true d_out[:hidesurface] = true
wirelines = KW( wirelines = KW(
:show => true, :show => true,
:color => rgba_string(series[:linecolor]), :color => rgba_string(plot_color(series[:linecolor], series[:linealpha])),
:highlightwidth => series[:linewidth], :highlightwidth => series[:linewidth],
) )
d_out[:contours] = KW(:x => wirelines, :y => wirelines, :z => wirelines) d_out[:contours] = KW(:x => wirelines, :y => wirelines, :z => wirelines)
d_out[:showscale] = false
else else
d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) d_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha])
d_out[:opacity] = series[:fillalpha]
if series[:fill_z] != nothing if series[:fill_z] != nothing
d_out[:surfacecolor] = plotly_surface_data(series, series[:fill_z]) d_out[:surfacecolor] = plotly_surface_data(series, series[:fill_z])
end end
d_out[:showscale] = hascolorbar(sp)
end end
elseif st == :pie elseif st == :pie
@ -500,16 +630,6 @@ function plotly_series(plt::Plot, series::Series)
d_out[:values] = y d_out[:values] = y
d_out[:hoverinfo] = "label+percent+name" d_out[:hoverinfo] = "label+percent+name"
elseif st in (:path3d, :scatter3d)
d_out[:type] = "scatter3d"
d_out[:mode] = if hasmarker
hasline ? "lines+markers" : "markers"
else
hasline ? "lines" : "none"
end
d_out[:x], d_out[:y] = x, y
d_out[:z] = plotly_data(series[:z])
else else
warn("Plotly: seriestype $st isn't supported.") warn("Plotly: seriestype $st isn't supported.")
return KW() return KW()
@ -517,98 +637,235 @@ function plotly_series(plt::Plot, series::Series)
# add "marker" # add "marker"
if hasmarker if hasmarker
inds = eachindex(x)
d_out[:marker] = KW( d_out[:marker] = KW(
:symbol => get(_plotly_markers, series[:markershape], string(series[:markershape])), :symbol => get(_plotly_markers, series[:markershape], string(series[:markershape])),
# :opacity => series[:markeralpha], # :opacity => series[:markeralpha],
:size => 2 * series[:markersize], :size => 2 * _cycle(series[:markersize], inds),
# :color => rgba_string(series[:markercolor]), :color => rgba_string.(plot_color.(get_markercolor.(series, inds), get_markeralpha.(series, inds))),
:line => KW( :line => KW(
:color => rgba_string(series[:markerstrokecolor]), :color => rgba_string.(plot_color.(get_markerstrokecolor.(series, inds), get_markerstrokealpha.(series, inds))),
:width => series[:markerstrokewidth], :width => _cycle(series[:markerstrokewidth], inds),
), ),
) )
# gotta hack this (for now?) since plotly can't handle rgba values inside the gradient
d_out[:marker][:color] = if series[:marker_z] == nothing
rgba_string(series[:markercolor])
else
# grad = ColorGradient(series[:markercolor], alpha=series[:markeralpha])
grad = series[:markercolor]
zmin, zmax = extrema(series[:marker_z])
[rgba_string(grad[(zi - zmin) / (zmax - zmin)]) for zi in series[:marker_z]]
end
end
# add "line"
if hasline
d_out[:line] = KW(
:color => rgba_string(series[:linecolor]),
:width => series[:linewidth],
:shape => if st == :steppre
"vh"
elseif st == :steppost
"hv"
else
"linear"
end,
:dash => string(series[:linestyle]),
# :dash => "solid",
)
end end
plotly_polar!(d_out, series) plotly_polar!(d_out, series)
plotly_hover!(d_out, series[:hover]) plotly_hover!(d_out, series[:hover])
[d_out] return [d_out]
end end
function plotly_series_shapes(plt::Plot, series::Series) function plotly_series_shapes(plt::Plot, series::Series)
d_outs = [] segments = iter_segments(series)
d_outs = Vector{KW}(length(segments))
# TODO: create a d_out for each polygon # TODO: create a d_out for each polygon
# x, y = series[:x], series[:y] # x, y = series[:x], series[:y]
# these are the axes that the series should be mapped to # these are the axes that the series should be mapped to
spidx = plotly_subplot_index(series[:subplot]) x_idx, y_idx = plotly_link_indicies(plt, series[:subplot])
base_d = KW() d_base = KW(
base_d[:xaxis] = "x$spidx" :xaxis => "x$(x_idx)",
base_d[:yaxis] = "y$spidx" :yaxis => "y$(y_idx)",
base_d[:name] = series[:label] :name => series[:label],
# base_d[:legendgroup] = series[:label] :legendgroup => series[:label],
)
x, y = plotly_data(series[:x]), plotly_data(series[:y]) x, y = (plotly_data(series, letter, data)
for (i,rng) in enumerate(iter_segments(x,y)) for (letter, data) in zip((:x, :y), shape_data(series))
)
for (i,rng) in enumerate(segments)
length(rng) < 2 && continue length(rng) < 2 && continue
# to draw polygons, we actually draw lines with fill # to draw polygons, we actually draw lines with fill
d_out = merge(base_d, KW( d_out = merge(d_base, KW(
:type => "scatter", :type => "scatter",
:mode => "lines", :mode => "lines",
:x => vcat(x[rng], x[rng[1]]), :x => vcat(x[rng], x[rng[1]]),
:y => vcat(y[rng], y[rng[1]]), :y => vcat(y[rng], y[rng[1]]),
:fill => "tozeroy", :fill => "tozeroy",
:fillcolor => rgba_string(cycle(series[:fillcolor], i)), :fillcolor => rgba_string(plot_color(get_fillcolor(series, i), get_fillalpha(series, i))),
)) ))
if series[:markerstrokewidth] > 0 if series[:markerstrokewidth] > 0
d_out[:line] = KW( d_out[:line] = KW(
:color => rgba_string(cycle(series[:linecolor], i)), :color => rgba_string(plot_color(get_linecolor(series, i), get_linealpha(series, i))),
:width => series[:linewidth], :width => get_linewidth(series, i),
:dash => string(series[:linestyle]), :dash => string(get_linestyle(series, i)),
) )
end end
d_out[:showlegend] = i==1 ? should_add_to_legend(series) : false d_out[:showlegend] = i==1 ? should_add_to_legend(series) : false
plotly_polar!(d_out, series) plotly_polar!(d_out, series)
plotly_hover!(d_out, cycle(series[:hover], i)) plotly_hover!(d_out, _cycle(series[:hover], i))
push!(d_outs, d_out) d_outs[i] = d_out
end
if series[:fill_z] != nothing
push!(d_outs, plotly_colorbar_hack(series, d_base, :fill))
elseif series[:line_z] != nothing
push!(d_outs, plotly_colorbar_hack(series, d_base, :line))
elseif series[:marker_z] != nothing
push!(d_outs, plotly_colorbar_hack(series, d_base, :marker))
end end
d_outs d_outs
end end
function plotly_series_segments(series::Series, d_base::KW, x, y, z)
st = series[:seriestype]
sp = series[:subplot]
isscatter = st in (:scatter, :scatter3d, :scattergl)
hasmarker = isscatter || series[:markershape] != :none
hasline = st in (:path, :path3d, :straightline)
hasfillrange = st in (:path, :scatter, :scattergl, :straightline) &&
(isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple))
segments = iter_segments(series)
d_outs = Vector{KW}((hasfillrange ? 2 : 1 ) * length(segments))
for (i,rng) in enumerate(segments)
!isscatter && length(rng) < 2 && continue
d_out = deepcopy(d_base)
d_out[:showlegend] = i==1 ? should_add_to_legend(series) : false
d_out[:legendgroup] = series[:label]
# set the type
if st in (:path, :scatter, :scattergl, :straightline)
d_out[:type] = st==:scattergl ? "scattergl" : "scatter"
d_out[:mode] = if hasmarker
hasline ? "lines+markers" : "markers"
else
hasline ? "lines" : "none"
end
if series[:fillrange] == true || series[:fillrange] == 0 || isa(series[:fillrange], Tuple)
d_out[:fill] = "tozeroy"
d_out[:fillcolor] = rgba_string(plot_color(get_fillcolor(series, i), get_fillalpha(series, i)))
elseif typeof(series[:fillrange]) <: Union{AbstractVector{<:Real}, Real}
d_out[:fill] = "tonexty"
d_out[:fillcolor] = rgba_string(plot_color(get_fillcolor(series, i), get_fillalpha(series, i)))
elseif !(series[:fillrange] in (false, nothing))
warn("fillrange ignored... plotly only supports filling to zero and to a vector of values. fillrange: $(series[:fillrange])")
end
d_out[:x], d_out[:y] = x[rng], y[rng]
elseif st in (:path3d, :scatter3d)
d_out[:type] = "scatter3d"
d_out[:mode] = if hasmarker
hasline ? "lines+markers" : "markers"
else
hasline ? "lines" : "none"
end
d_out[:x], d_out[:y], d_out[:z] = x[rng], y[rng], z[rng]
end
# add "marker"
if hasmarker
d_out[:marker] = KW(
:symbol => get(_plotly_markers, _cycle(series[:markershape], i), string(_cycle(series[:markershape], i))),
# :opacity => series[:markeralpha],
:size => 2 * _cycle(series[:markersize], i),
:color => rgba_string(plot_color(get_markercolor(series, i), get_markeralpha(series, i))),
:line => KW(
:color => rgba_string(plot_color(get_markerstrokecolor(series, i), get_markerstrokealpha(series, i))),
:width => _cycle(series[:markerstrokewidth], i),
),
)
end
# add "line"
if hasline
d_out[:line] = KW(
:color => rgba_string(plot_color(get_linecolor(series, i), get_linealpha(series, i))),
:width => get_linewidth(series, i),
:shape => if st == :steppre
"vh"
elseif st == :steppost
"hv"
else
"linear"
end,
:dash => string(get_linestyle(series, i)),
)
end
plotly_polar!(d_out, series)
plotly_hover!(d_out, _cycle(series[:hover], rng))
if hasfillrange
# if hasfillrange is true, return two dictionaries (one for original
# series, one for series being filled to) instead of one
d_out_fillrange = deepcopy(d_out)
d_out_fillrange[:showlegend] = false
# if fillrange is provided as real or tuple of real, expand to array
if typeof(series[:fillrange]) <: Real
series[:fillrange] = fill(series[:fillrange], length(rng))
elseif typeof(series[:fillrange]) <: Tuple
f1 = typeof(series[:fillrange][1]) <: Real ? fill(series[:fillrange][1], length(rng)) : series[:fillrange][1][rng]
f2 = typeof(series[:fillrange][2]) <: Real ? fill(series[:fillrange][2], length(rng)) : series[:fillrange][2][rng]
series[:fillrange] = (f1, f2)
end
if isa(series[:fillrange], AbstractVector)
d_out_fillrange[:y] = series[:fillrange][rng]
delete!(d_out_fillrange, :fill)
delete!(d_out_fillrange, :fillcolor)
else
# if fillrange is a tuple with upper and lower limit, d_out_fillrange
# is the series that will do the filling
fillrng = Tuple(series[:fillrange][i][rng] for i in 1:2)
d_out_fillrange[:x], d_out_fillrange[:y] = concatenate_fillrange(x[rng], fillrng)
d_out_fillrange[:line][:width] = 0
delete!(d_out, :fill)
delete!(d_out, :fillcolor)
end
d_outs[(2 * i - 1):(2 * i)] = [d_out_fillrange, d_out]
else
d_outs[i] = d_out
end
end
if series[:line_z] != nothing
push!(d_outs, plotly_colorbar_hack(series, d_base, :line))
elseif series[:fill_z] != nothing
push!(d_outs, plotly_colorbar_hack(series, d_base, :fill))
elseif series[:marker_z] != nothing
push!(d_outs, plotly_colorbar_hack(series, d_base, :marker))
end
d_outs
end
function plotly_colorbar_hack(series::Series, d_base::KW, sym::Symbol)
d_out = deepcopy(d_base)
cmin, cmax = get_clims(series[:subplot])
d_out[:showlegend] = false
d_out[:type] = is3d(series) ? :scatter3d : :scatter
d_out[:hoverinfo] = :none
d_out[:mode] = :markers
d_out[:x], d_out[:y] = [series[:x][1]], [series[:y][1]]
if is3d(series)
d_out[:z] = [series[:z][1]]
end
# zrange = zmax == zmin ? 1 : zmax - zmin # if all marker_z values are the same, plot all markers same color (avoids division by zero in next line)
d_out[:marker] = KW(
:size => 0,
:opacity => 0,
:color => [0.5],
:cmin => cmin,
:cmax => cmax,
:colorscale => plotly_colorscale(series[Symbol("$(sym)color")], 1),
:showscale => hascolorbar(series[:subplot]),
)
return d_out
end
function plotly_polar!(d_out::KW, series::Series) function plotly_polar!(d_out::KW, series::Series)
# convert polar plots x/y to theta/radius # convert polar plots x/y to theta/radius
if ispolar(series[:subplot]) if ispolar(series[:subplot])
d_out[:t] = rad2deg(pop!(d_out, :x)) theta, r = filter_radial_data(pop!(d_out, :x), pop!(d_out, :y), axis_limits(series[:subplot][:yaxis]))
d_out[:r] = pop!(d_out, :y) d_out[:t] = rad2deg.(theta)
d_out[:r] = r
end end
end end
@ -623,21 +880,23 @@ function plotly_hover!(d_out::KW, hover)
end end
# get a list of dictionaries, each representing the series params # get a list of dictionaries, each representing the series params
function plotly_series_json(plt::Plot) function plotly_series(plt::Plot)
slist = [] slist = []
for series in plt.series_list for series in plt.series_list
append!(slist, plotly_series(plt, series)) append!(slist, plotly_series(plt, series))
end end
JSON.json(slist) slist
# JSON.json(map(series -> plotly_series(plt, series), plt.series_list))
end end
# get json string for a list of dictionaries, each representing the series params
plotly_series_json(plt::Plot) = JSON.json(plotly_series(plt))
# ---------------------------------------------------------------- # ----------------------------------------------------------------
const _use_remote = Ref(false) const _use_remote = Ref(false)
function html_head(plt::Plot{PlotlyBackend}) function html_head(plt::Plot{PlotlyBackend})
jsfilename = _use_remote[] ? _plotly_js_path_remote : _plotly_js_path jsfilename = _use_remote[] ? _plotly_js_path_remote : ("file://" * _plotly_js_path)
# "<script src=\"$(joinpath(dirname(@__FILE__),"..","..","deps","plotly-latest.min.js"))\"></script>" # "<script src=\"$(joinpath(dirname(@__FILE__),"..","..","deps","plotly-latest.min.js"))\"></script>"
"<script src=\"$jsfilename\"></script>" "<script src=\"$jsfilename\"></script>"
end end
@ -669,12 +928,7 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _show(io::IO, ::MIME"image/png", plt::Plot{PlotlyBackend}) function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend})
# show_png_from_html(io, plt)
error("png output from the plotly backend is not supported. Please use plotlyjs instead.")
end
function _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyBackend})
write(io, html_head(plt) * html_body(plt)) write(io, html_head(plt) * html_body(plt))
end end

View File

@ -1,3 +1,6 @@
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "plotlyjs.jl"))
end
# https://github.com/spencerlyon2/PlotlyJS.jl # https://github.com/spencerlyon2/PlotlyJS.jl
@ -85,7 +88,7 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend}) function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend})
if isijulia() && !_use_remote[] if isijulia() && !_use_remote[]
write(io, PlotlyJS.html_body(PlotlyJS.JupyterPlot(plt.o))) write(io, PlotlyJS.html_body(PlotlyJS.JupyterPlot(plt.o)))
else else
@ -98,14 +101,31 @@ function plotlyjs_save_hack(io::IO, plt::Plot{PlotlyJSBackend}, ext::String)
PlotlyJS.savefig(plt.o, tmpfn) PlotlyJS.savefig(plt.o, tmpfn)
write(io, read(open(tmpfn))) write(io, read(open(tmpfn)))
end end
_show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "svg")
_show(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "png") _show(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "png")
_show(io::IO, ::MIME"application/pdf", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "pdf") _show(io::IO, ::MIME"application/pdf", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "pdf")
_show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "eps") _show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = plotlyjs_save_hack(io, plt, "eps")
function _display(plt::Plot{PlotlyJSBackend}) function write_temp_html(plt::Plot{PlotlyJSBackend})
display(plt.o) filename = string(tempname(), ".html")
savefig(plt, filename)
filename
end end
function _display(plt::Plot{PlotlyJSBackend})
if get(ENV, "PLOTS_USE_ATOM_PLOTPANE", true) in (true, 1, "1", "true", "yes")
display(plt.o)
else
standalone_html_window(plt)
end
end
@require WebIO begin
function WebIO.render(plt::Plot{PlotlyJSBackend})
prepare_output(plt)
WebIO.render(plt.o)
end
end
function closeall(::PlotlyJSBackend) function closeall(::PlotlyJSBackend)
if !isplotnull() && isa(current().o, PlotlyJS.SyncPlot) if !isplotnull() && isa(current().o, PlotlyJS.SyncPlot)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,10 @@
# https://github.com/Evizero/UnicodePlots.jl # https://github.com/Evizero/UnicodePlots.jl
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "unicodeplots.jl"))
end
const _unicodeplots_attr = merge_with_base_supported([ const _unicodeplots_attr = merge_with_base_supported([
:label, :label,
:legend, :legend,
@ -13,7 +17,7 @@ const _unicodeplots_attr = merge_with_base_supported([
:guide, :lims, :guide, :lims,
]) ])
const _unicodeplots_seriestype = [ const _unicodeplots_seriestype = [
:path, :scatter, :path, :scatter, :straightline,
# :bar, # :bar,
:shape, :shape,
:histogram2d, :histogram2d,
@ -138,7 +142,7 @@ function addUnicodeSeries!(o, d::KW, addlegend::Bool, xlim, ylim)
return return
end end
if st == :path if st in (:path, :straightline)
func = UnicodePlots.lineplot! func = UnicodePlots.lineplot!
elseif st == :scatter || d[:markershape] != :none elseif st == :scatter || d[:markershape] != :none
func = UnicodePlots.scatterplot! func = UnicodePlots.scatterplot!
@ -151,14 +155,20 @@ function addUnicodeSeries!(o, d::KW, addlegend::Bool, xlim, ylim)
end end
# get the series data and label # get the series data and label
x, y = [collect(float(d[s])) for s in (:x, :y)] x, y = if st == :straightline
straightline_data(d)
elseif st == :shape
shape_data(series)
else
[collect(float(d[s])) for s in (:x, :y)]
end
label = addlegend ? d[:label] : "" label = addlegend ? d[:label] : ""
# if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide # if we happen to pass in allowed color symbols, great... otherwise let UnicodePlots decide
color = d[:linecolor] in UnicodePlots.color_cycle ? d[:linecolor] : :auto color = d[:linecolor] in UnicodePlots.color_cycle ? d[:linecolor] : :auto
# add the series # add the series
x, y = Plots.unzip(collect(filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y)))) x, y = Plots.unzip(collect(Base.Iterators.filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y))))
func(o, x, y; color = color, name = label) func(o, x, y; color = color, name = label)
end end
@ -202,7 +212,7 @@ end
function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend}) function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
unicodeplots_rebuild(plt) unicodeplots_rebuild(plt)
map(show, plt.o) foreach(x -> show(io, x), plt.o)
nothing nothing
end end

View File

@ -2,7 +2,9 @@
# NOTE: backend should implement `html_body` and `html_head` # NOTE: backend should implement `html_body` and `html_head`
# CREDIT: parts of this implementation were inspired by @joshday's PlotlyLocal.jl # CREDIT: parts of this implementation were inspired by @joshday's PlotlyLocal.jl
@require Revise begin
Revise.track(Plots, joinpath(Pkg.dir("Plots"), "src", "backends", "web.jl"))
end
function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr, :window_title, "Plots.jl")) function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr, :window_title, "Plots.jl"))
""" """
@ -10,6 +12,7 @@ function standalone_html(plt::AbstractPlot; title::AbstractString = get(plt.attr
<html> <html>
<head> <head>
<title>$title</title> <title>$title</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
$(html_head(plt)) $(html_head(plt))
</head> </head>
<body> <body>
@ -23,11 +26,11 @@ function open_browser_window(filename::AbstractString)
@static if is_apple() @static if is_apple()
return run(`open $(filename)`) return run(`open $(filename)`)
end end
@static if is_linux() @static if is_linux() || is_bsd() # is_bsd() addition is as yet untested, but based on suggestion in https://github.com/JuliaPlots/Plots.jl/issues/681
return run(`xdg-open $(filename)`) return run(`xdg-open $(filename)`)
end end
@static if is_windows() @static if is_windows()
return run(`$(ENV["COMSPEC"]) /c start $(filename)`) return run(`$(ENV["COMSPEC"]) /c start "" "$(filename)"`)
end end
warn("Unknown OS... cannot open browser window.") warn("Unknown OS... cannot open browser window.")
end end

View File

@ -1,7 +1,7 @@
typealias P2 FixedSizeArrays.Vec{2,Float64} const P2 = FixedSizeArrays.Vec{2,Float64}
typealias P3 FixedSizeArrays.Vec{3,Float64} const P3 = FixedSizeArrays.Vec{3,Float64}
nanpush!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); push!(a, b)) nanpush!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); push!(a, b))
nanappend!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); append!(a, b)) nanappend!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); append!(a, b))
@ -11,7 +11,7 @@ compute_angle(v::P2) = (angle = atan2(v[2], v[1]); angle < 0 ? 2π - angle : ang
# ------------------------------------------------------------- # -------------------------------------------------------------
immutable Shape struct Shape
x::Vector{Float64} x::Vector{Float64}
y::Vector{Float64} y::Vector{Float64}
# function Shape(x::AVec, y::AVec) # function Shape(x::AVec, y::AVec)
@ -22,6 +22,13 @@ immutable Shape
# end # end
# end # end
end end
"""
Shape(x, y)
Shape(vertices)
Construct a polygon to be plotted
"""
Shape(verts::AVec) = Shape(unzip(verts)...) Shape(verts::AVec) = Shape(unzip(verts)...)
Shape(s::Shape) = deepcopy(s) Shape(s::Shape) = deepcopy(s)
@ -32,6 +39,7 @@ vertices(shape::Shape) = collect(zip(shape.x, shape.y))
#deprecated #deprecated
@deprecate shape_coords coords @deprecate shape_coords coords
"return the vertex points from a Shape or Segments object"
function coords(shape::Shape) function coords(shape::Shape)
shape.x, shape.y shape.x, shape.y
end end
@ -156,6 +164,7 @@ Shape(k::Symbol) = deepcopy(_shapes[k])
# uses the centroid calculation from https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon # uses the centroid calculation from https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
"return the centroid of a Shape"
function center(shape::Shape) function center(shape::Shape)
x, y = coords(shape) x, y = coords(shape)
n = length(x) n = length(x)
@ -174,7 +183,7 @@ function center(shape::Shape)
Cx / 6A, Cy / 6A Cx / 6A, Cy / 6A
end end
function Base.scale!(shape::Shape, x::Real, y::Real = x, c = center(shape)) function scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
sx, sy = coords(shape) sx, sy = coords(shape)
cx, cy = c cx, cy = c
for i=1:length(sx) for i=1:length(sx)
@ -184,11 +193,12 @@ function Base.scale!(shape::Shape, x::Real, y::Real = x, c = center(shape))
shape shape
end end
function Base.scale(shape::Shape, x::Real, y::Real = x, c = center(shape)) function scale(shape::Shape, x::Real, y::Real = x, c = center(shape))
shapecopy = deepcopy(shape) shapecopy = deepcopy(shape)
scale!(shapecopy, x, y, c) scale!(shapecopy, x, y, c)
end end
"translate a Shape in space"
function translate!(shape::Shape, x::Real, y::Real = x) function translate!(shape::Shape, x::Real, y::Real = x)
sx, sy = coords(shape) sx, sy = coords(shape)
for i=1:length(sx) for i=1:length(sx)
@ -227,6 +237,7 @@ function rotate!(shape::Shape, Θ::Real, c = center(shape))
shape shape
end end
"rotate an object in space"
function rotate(shape::Shape, Θ::Real, c = center(shape)) function rotate(shape::Shape, Θ::Real, c = center(shape))
shapecopy = deepcopy(shape) shapecopy = deepcopy(shape)
rotate!(shapecopy, Θ, c) rotate!(shapecopy, Θ, c)
@ -235,7 +246,7 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
type Font mutable struct Font
family::AbstractString family::AbstractString
pointsize::Int pointsize::Int
halign::Symbol halign::Symbol
@ -294,23 +305,50 @@ end
function scalefontsize(k::Symbol, factor::Number) function scalefontsize(k::Symbol, factor::Number)
f = default(k) f = default(k)
f.pointsize = round(Int, factor * f.pointsize) f = round(Int, factor * f)
default(k, f) default(k, f)
end end
"""
scalefontsizes(factor::Number)
Scales all **current** font sizes by `factor`. For example `scalefontsizes(1.1)` increases all current font sizes by 10%. To reset to initial sizes, use `scalefontsizes()`
"""
function scalefontsizes(factor::Number) function scalefontsizes(factor::Number)
for k in (:titlefont, :guidefont, :tickfont, :legendfont) for k in (:titlefontsize, :guidefontsize, :tickfontsize, :legendfontsize)
scalefontsize(k, factor) scalefontsize(k, factor)
end end
end end
"""
scalefontsizes()
Resets font sizes to initial default values.
"""
function scalefontsizes()
for k in (:titlefontsize, :guidefontsize, :tickfontsize, :legendfontsize)
f = default(k)
if k in keys(_initial_fontsizes)
factor = f / _initial_fontsizes[k]
scalefontsize(k, 1.0/factor)
end
end
end
"Wrap a string with font info" "Wrap a string with font info"
immutable PlotText struct PlotText
str::AbstractString str::AbstractString
font::Font font::Font
end end
PlotText(str) = PlotText(string(str), font()) PlotText(str) = PlotText(string(str), font())
"""
text(string, args...)
Create a PlotText object wrapping a string with font info, for plot annotations
"""
text(t::PlotText) = t text(t::PlotText) = t
text(t::PlotText, font::Font) = PlotText(t.str, font)
text(str::AbstractString, f::Font) = PlotText(str, f) text(str::AbstractString, f::Font) = PlotText(str, f)
function text(str, args...) function text(str, args...)
PlotText(string(str), font(args...)) PlotText(string(str), font(args...))
@ -322,13 +360,18 @@ Base.length(t::PlotText) = length(t.str)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
immutable Stroke struct Stroke
width width
color color
alpha alpha
style style
end end
"""
stroke(args...; alpha = nothing)
Define the properties of the stroke used in plotting lines
"""
function stroke(args...; alpha = nothing) function stroke(args...; alpha = nothing)
width = 1 width = 1
color = :black color = :black
@ -359,7 +402,7 @@ function stroke(args...; alpha = nothing)
end end
immutable Brush struct Brush
size # fillrange, markersize, or any other sizey attribute size # fillrange, markersize, or any other sizey attribute
color color
alpha alpha
@ -392,7 +435,7 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
type SeriesAnnotations mutable struct SeriesAnnotations
strs::AbstractVector # the labels/names strs::AbstractVector # the labels/names
font::Font font::Font
baseshape::Nullable baseshape::Nullable
@ -446,7 +489,7 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
msw,msh = anns.scalefactor msw,msh = anns.scalefactor
msize = Float64[] msize = Float64[]
shapes = Shape[begin shapes = Shape[begin
str = cycle(anns.strs,i) str = _cycle(anns.strs,i)
# get the width and height of the string (in mm) # get the width and height of the string (in mm)
sw, sh = text_size(str, anns.font.pointsize) sw, sh = text_size(str, anns.font.pointsize)
@ -462,7 +505,7 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
# and then re-scale a copy of baseshape to match the w/h ratio # and then re-scale a copy of baseshape to match the w/h ratio
maxscale = max(xscale, yscale) maxscale = max(xscale, yscale)
push!(msize, maxscale) push!(msize, maxscale)
baseshape = cycle(get(anns.baseshape),i) baseshape = _cycle(get(anns.baseshape),i)
shape = scale(baseshape, msw*xscale/maxscale, msh*yscale/maxscale, (0,0)) shape = scale(baseshape, msw*xscale/maxscale, msh*yscale/maxscale, (0,0))
end for i=1:length(anns.strs)] end for i=1:length(anns.strs)]
series[:markershape] = shapes series[:markershape] = shapes
@ -471,7 +514,7 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
return return
end end
type EachAnn mutable struct EachAnn
anns anns
x x
y y
@ -479,13 +522,13 @@ end
Base.start(ea::EachAnn) = 1 Base.start(ea::EachAnn) = 1
Base.done(ea::EachAnn, i) = ea.anns == nothing || isempty(ea.anns.strs) || i > length(ea.y) Base.done(ea::EachAnn, i) = ea.anns == nothing || isempty(ea.anns.strs) || i > length(ea.y)
function Base.next(ea::EachAnn, i) function Base.next(ea::EachAnn, i)
tmp = cycle(ea.anns.strs,i) tmp = _cycle(ea.anns.strs,i)
str,fnt = if isa(tmp, PlotText) str,fnt = if isa(tmp, PlotText)
tmp.str, tmp.font tmp.str, tmp.font
else else
tmp, ea.anns.font tmp, ea.anns.font
end end
((cycle(ea.x,i), cycle(ea.y,i), str, fnt), i+1) ((_cycle(ea.x,i), _cycle(ea.y,i), str, fnt), i+1)
end end
annotations(::Void) = [] annotations(::Void) = []
@ -493,24 +536,72 @@ annotations(anns::AVec) = anns
annotations(anns) = Any[anns] annotations(anns) = Any[anns]
annotations(sa::SeriesAnnotations) = sa annotations(sa::SeriesAnnotations) = sa
# Expand arrays of coordinates, positions and labels into induvidual annotations
# and make sure labels are of type PlotText
function process_annotation(sp::Subplot, xs, ys, labs, font = font())
anns = []
labs = makevec(labs)
for i in 1:max(length(xs), length(ys), length(labs))
x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i)
if lab == :auto
alphabet = "abcdefghijklmnopqrstuvwxyz"
push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
else
push!(anns, (x, y, isa(lab, PlotText) ? lab : text(lab, font)))
end
end
anns
end
function process_annotation(sp::Subplot, positions::Union{AVec{Symbol},Symbol}, labs, font = font())
anns = []
positions, labs = makevec(positions), makevec(labs)
for i in 1:max(length(positions), length(labs))
pos, lab = _cycle(positions, i), _cycle(labs, i)
pos = get(_positionAliases, pos, pos)
if lab == :auto
alphabet = "abcdefghijklmnopqrstuvwxyz"
push!(anns, (pos, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
else
push!(anns, (pos, isa(lab, PlotText) ? lab : text(lab, font)))
end
end
anns
end
# Give each annotation coordinates based on specified position
function locate_annotation(sp::Subplot, pos::Symbol, lab::PlotText)
position_multiplier = Dict{Symbol, Tuple{Float64,Float64}}(
:topleft => (0.1, 0.9),
:topcenter => (0.5, 0.9),
:topright => (0.9, 0.9),
:bottomleft => (0.1, 0.1),
:bottomcenter => (0.5, 0.1),
:bottomright => (0.9, 0.1),
)
xmin, xmax = ignorenan_extrema(sp[:xaxis])
ymin, ymax = ignorenan_extrema(sp[:yaxis])
x, y = (xmin, ymin).+ position_multiplier[pos].* (xmax - xmin, ymax - ymin)
(x, y, lab)
end
locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
"type which represents z-values for colors and sizes (and anything else that might come up)" "type which represents z-values for colors and sizes (and anything else that might come up)"
immutable ZValues struct ZValues
values::Vector{Float64} values::Vector{Float64}
zrange::Tuple{Float64,Float64} zrange::Tuple{Float64,Float64}
end end
function zvalues{T<:Real}(values::AVec{T}, zrange::Tuple{T,T} = (minimum(values), maximum(values))) function zvalues(values::AVec{T}, zrange::Tuple{T,T} = (ignorenan_minimum(values), ignorenan_maximum(values))) where T<:Real
ZValues(collect(float(values)), map(Float64, zrange)) ZValues(collect(float(values)), map(Float64, zrange))
end end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
abstract AbstractSurface abstract type AbstractSurface end
"represents a contour or surface mesh" "represents a contour or surface mesh"
immutable Surface{M<:AMat} <: AbstractSurface struct Surface{M<:AMat} <: AbstractSurface
surf::M surf::M
end end
@ -521,8 +612,8 @@ Base.Array(surf::Surface) = surf.surf
for f in (:length, :size) for f in (:length, :size)
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...) @eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
end end
Base.copy(surf::Surface) = Surface{typeof(surf.surf)}(copy(surf.surf)) Base.copy(surf::Surface) = Surface(copy(surf.surf))
Base.eltype{T}(surf::Surface{T}) = eltype(T) Base.eltype(surf::Surface{T}) where {T} = eltype(T)
function expand_extrema!(a::Axis, surf::Surface) function expand_extrema!(a::Axis, surf::Surface)
ex = a[:extrema] ex = a[:extrema]
@ -533,7 +624,7 @@ function expand_extrema!(a::Axis, surf::Surface)
end end
"For the case of representing a surface as a function of x/y... can possibly avoid allocations." "For the case of representing a surface as a function of x/y... can possibly avoid allocations."
immutable SurfaceFunction <: AbstractSurface struct SurfaceFunction <: AbstractSurface
f::Function f::Function
end end
@ -543,19 +634,19 @@ end
# # I don't want to clash with ValidatedNumerics, but this would be nice: # # I don't want to clash with ValidatedNumerics, but this would be nice:
# ..(a::T, b::T) = (a,b) # ..(a::T, b::T) = (a,b)
immutable Volume{T} struct Volume{T}
v::Array{T,3} v::Array{T,3}
x_extents::Tuple{T,T} x_extents::Tuple{T,T}
y_extents::Tuple{T,T} y_extents::Tuple{T,T}
z_extents::Tuple{T,T} z_extents::Tuple{T,T}
end end
default_extents{T}(::Type{T}) = (zero(T), one(T)) default_extents(::Type{T}) where {T} = (zero(T), one(T))
function Volume{T}(v::Array{T,3}, function Volume(v::Array{T,3},
x_extents = default_extents(T), x_extents = default_extents(T),
y_extents = default_extents(T), y_extents = default_extents(T),
z_extents = default_extents(T)) z_extents = default_extents(T)) where T
Volume(v, x_extents, y_extents, z_extents) Volume(v, x_extents, y_extents, z_extents)
end end
@ -563,19 +654,25 @@ Base.Array(vol::Volume) = vol.v
for f in (:length, :size) for f in (:length, :size)
@eval Base.$f(vol::Volume, args...) = $f(vol.v, args...) @eval Base.$f(vol::Volume, args...) = $f(vol.v, args...)
end end
Base.copy{T}(vol::Volume{T}) = Volume{T}(copy(vol.v), vol.x_extents, vol.y_extents, vol.z_extents) Base.copy(vol::Volume{T}) where {T} = Volume{T}(copy(vol.v), vol.x_extents, vol.y_extents, vol.z_extents)
Base.eltype{T}(vol::Volume{T}) = T Base.eltype(vol::Volume{T}) where {T} = T
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# style is :open or :closed (for now) # style is :open or :closed (for now)
immutable Arrow struct Arrow
style::Symbol style::Symbol
side::Symbol # :head (default), :tail, or :both side::Symbol # :head (default), :tail, or :both
headlength::Float64 headlength::Float64
headwidth::Float64 headwidth::Float64
end end
"""
arrow(args...)
Define arrowheads to apply to lines - args are `style` (`:open` or `:closed`),
`side` (`:head`, `:tail` or `:both`), `headlength` and `headwidth`
"""
function arrow(args...) function arrow(args...)
style = :simple style = :simple
side = :head side = :head
@ -625,14 +722,14 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
"Represents data values with formatting that should apply to the tick labels." "Represents data values with formatting that should apply to the tick labels."
immutable Formatted{T} struct Formatted{T}
data::T data::T
formatter::Function formatter::Function
end end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
"create a BezierCurve for plotting"
type BezierCurve{T <: FixedSizeArrays.Vec} mutable struct BezierCurve{T <: FixedSizeArrays.Vec}
control_points::Vector{T} control_points::Vector{T}
end end
@ -645,8 +742,8 @@ function (bc::BezierCurve)(t::Real)
p p
end end
Base.mean(x::Real, y::Real) = 0.5*(x+y) # mean(x::Real, y::Real) = 0.5*(x+y) #commented out as I cannot see this used anywhere and it overwrites a Base method with different functionality
Base.mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps) # mean{N,T<:Real}(ps::FixedSizeArrays.Vec{N,T}...) = sum(ps) / length(ps) # I also could not see this used anywhere, and it's type piracy - implementing a NaNMath version for this would just involve converting to a standard array
@deprecate curve_points coords @deprecate curve_points coords
@ -659,7 +756,7 @@ function directed_curve(args...; kw...)
end end
function extrema_plus_buffer(v, buffmult = 0.2) function extrema_plus_buffer(v, buffmult = 0.2)
vmin,vmax = extrema(v) vmin,vmax = ignorenan_extrema(v)
vdiff = vmax-vmin vdiff = vmax-vmin
buffer = vdiff * buffmult buffer = vdiff * buffmult
vmin - buffer, vmax + buffer vmin - buffer, vmax + buffer

View File

@ -558,7 +558,7 @@ function createGadflyAnnotationObject(x, y, txt::PlotText)
)) ))
end end
function _add_annotations{X,Y,V}(plt::Plot{GadflyBackend}, anns::AVec{Tuple{X,Y,V}}) function _add_annotations(plt::Plot{GadflyBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns for ann in anns
push!(plt.o.guides, createGadflyAnnotationObject(ann...)) push!(plt.o.guides, createGadflyAnnotationObject(ann...))
end end
@ -614,7 +614,7 @@ function getxy(plt::Plot{GadflyBackend}, i::Integer)
mapping[:x], mapping[:y] mapping[:x], mapping[:y]
end end
function setxy!{X,Y}(plt::Plot{GadflyBackend}, xy::Tuple{X,Y}, i::Integer) function setxy!(plt::Plot{GadflyBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
for mapping in getGadflyMappings(plt, i) for mapping in getGadflyMappings(plt, i)
mapping[:x], mapping[:y] = xy mapping[:x], mapping[:y] = xy
end end
@ -677,7 +677,7 @@ setGadflyDisplaySize(plt::Plot) = setGadflyDisplaySize(plt.attr[:size]...)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
function doshow{P<:Union{GadflyBackend,ImmerseBackend}}(io::IO, func, plt::AbstractPlot{P}) function doshow(io::IO, func, plt::AbstractPlot{P}) where P<:Union{GadflyBackend,ImmerseBackend}
gplt = getGadflyContext(plt) gplt = getGadflyContext(plt)
setGadflyDisplaySize(plt) setGadflyDisplaySize(plt)
Gadfly.draw(func(io, Compose.default_graphic_width, Compose.default_graphic_height), gplt) Gadfly.draw(func(io, Compose.default_graphic_width, Compose.default_graphic_height), gplt)
@ -692,7 +692,7 @@ getGadflyWriteFunc(::MIME"application/x-tex") = Gadfly.PGF
getGadflyWriteFunc(m::MIME) = error("Unsupported in Gadfly/Immerse: ", m) 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", MIME"application/x-tex") for mime in (MIME"image/png", MIME"image/svg+xml", MIME"application/pdf", MIME"application/postscript", MIME"application/x-tex")
@eval function Base.show{P<:Union{GadflyBackend,ImmerseBackend}}(io::IO, ::$mime, plt::AbstractPlot{P}) @eval function Base.show(io::IO, ::$mime, plt::AbstractPlot{P}) where P<:Union{GadflyBackend,ImmerseBackend}
func = getGadflyWriteFunc($mime()) func = getGadflyWriteFunc($mime())
doshow(io, func, plt) doshow(io, func, plt)
end end

View File

@ -2,7 +2,7 @@
# Geometry which displays arbitrary shapes at given (x, y) positions. # Geometry which displays arbitrary shapes at given (x, y) positions.
# note: vertices is a list of shapes # note: vertices is a list of shapes
immutable ShapeGeometry <: Gadfly.GeometryElement struct ShapeGeometry <: Gadfly.GeometryElement
vertices::AbstractVector #{Tuple{Float64,Float64}} vertices::AbstractVector #{Tuple{Float64,Float64}}
tag::Symbol tag::Symbol
@ -84,7 +84,7 @@ function make_polygon(geom::ShapeGeometry, xs::AbstractArray, ys::AbstractArray,
x = Compose.x_measure(xs[mod1(i, length(xs))]) x = Compose.x_measure(xs[mod1(i, length(xs))])
y = Compose.y_measure(ys[mod1(i, length(ys))]) y = Compose.y_measure(ys[mod1(i, length(ys))])
r = rs[mod1(i, length(rs))] r = rs[mod1(i, length(rs))]
polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in cycle(geom.vertices, i)] polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in _cycle(geom.vertices, i)]
end end
Gadfly.polygon(polys, geom.tag) Gadfly.polygon(polys, geom.tag)
end end

View File

@ -61,7 +61,7 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _add_annotations{X,Y,V}(plt::Plot{ImmerseBackend}, anns::AVec{Tuple{X,Y,V}}) function _add_annotations(plt::Plot{ImmerseBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns for ann in anns
push!(getGadflyContext(plt).guides, createGadflyAnnotationObject(ann...)) push!(getGadflyContext(plt).guides, createGadflyAnnotationObject(ann...))
end end
@ -76,7 +76,7 @@ function getxy(plt::Plot{ImmerseBackend}, i::Integer)
mapping[:x], mapping[:y] mapping[:x], mapping[:y]
end end
function setxy!{X,Y}(plt::Plot{ImmerseBackend}, xy::Tuple{X,Y}, i::Integer) function setxy!(plt::Plot{ImmerseBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
for mapping in getGadflyMappings(plt, i) for mapping in getGadflyMappings(plt, i)
mapping[:x], mapping[:y] = xy mapping[:x], mapping[:y] = xy
end end

View File

@ -218,7 +218,7 @@ function createQwtAnnotation(plt::Plot, x, y, val::AbstractString)
marker[:attach](plt.o.widget) marker[:attach](plt.o.widget)
end end
function _add_annotations{X,Y,V}(plt::Plot{QwtBackend}, anns::AVec{Tuple{X,Y,V}}) function _add_annotations(plt::Plot{QwtBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns for ann in anns
createQwtAnnotation(plt, ann...) createQwtAnnotation(plt, ann...)
end end
@ -233,7 +233,7 @@ function getxy(plt::Plot{QwtBackend}, i::Int)
series.x, series.y series.x, series.y
end end
function setxy!{X,Y}(plt::Plot{QwtBackend}, xy::Tuple{X,Y}, i::Integer) function setxy!(plt::Plot{QwtBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
series = plt.o.lines[i] series = plt.o.lines[i]
series.x, series.y = xy series.x, series.y = xy
plt plt

View File

@ -217,7 +217,7 @@ function createWinstonAnnotationObject(plt::Plot{WinstonBackend}, x, y, val::Abs
Winston.text(x, y, val) Winston.text(x, y, val)
end end
function _add_annotations{X,Y,V}(plt::Plot{WinstonBackend}, anns::AVec{Tuple{X,Y,V}}) function _add_annotations(plt::Plot{WinstonBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns for ann in anns
createWinstonAnnotationObject(plt, ann...) createWinstonAnnotationObject(plt, ann...)
end end

View File

@ -1,5 +1,5 @@
abstract ColorScheme abstract type ColorScheme end
Base.getindex(scheme::ColorScheme, i::Integer) = getColor(scheme, i) Base.getindex(scheme::ColorScheme, i::Integer) = getColor(scheme, i)
@ -34,9 +34,9 @@ getColorVector(scheme::ColorScheme) = [getColor(scheme)]
colorscheme(scheme::ColorScheme) = scheme colorscheme(scheme::ColorScheme) = scheme
colorscheme(s::AbstractString; kw...) = colorscheme(Symbol(s); kw...) colorscheme(s::AbstractString; kw...) = colorscheme(Symbol(s); kw...)
colorscheme(s::Symbol; kw...) = haskey(_gradients, s) ? ColorGradient(s; kw...) : ColorWrapper(convertColor(s); kw...) colorscheme(s::Symbol; kw...) = haskey(_gradients, s) ? ColorGradient(s; kw...) : ColorWrapper(convertColor(s); kw...)
colorscheme{T<:Real}(s::Symbol, vals::AVec{T}; kw...) = ColorGradient(s, vals; kw...) colorscheme(s::Symbol, vals::AVec{T}; kw...) where {T<:Real} = ColorGradient(s, vals; kw...)
colorscheme(cs::AVec, vs::AVec; kw...) = ColorGradient(cs, vs; kw...) colorscheme(cs::AVec, vs::AVec; kw...) = ColorGradient(cs, vs; kw...)
colorscheme{T<:Colorant}(cs::AVec{T}; kw...) = ColorGradient(cs; kw...) colorscheme(cs::AVec{T}; kw...) where {T<:Colorant} = ColorGradient(cs; kw...)
colorscheme(f::Function; kw...) = ColorFunction(f; kw...) colorscheme(f::Function; kw...) = ColorFunction(f; kw...)
colorscheme(v::AVec; kw...) = ColorVector(v; kw...) colorscheme(v::AVec; kw...) = ColorVector(v; kw...)
colorscheme(m::AMat; kw...) = size(m,1) == 1 ? map(c->colorscheme(c; kw...), m) : [colorscheme(m[:,i]; kw...) for i in 1:size(m,2)]' colorscheme(m::AMat; kw...) = size(m,1) == 1 ? map(c->colorscheme(c; kw...), m) : [colorscheme(m[:,i]; kw...) for i in 1:size(m,2)]'
@ -98,7 +98,7 @@ const _gradients = KW(
:lighttest => map(c -> lighten(c, 0.3), _testColors), :lighttest => map(c -> lighten(c, 0.3), _testColors),
) )
function register_gradient_colors{C<:Colorant}(name::Symbol, colors::AVec{C}) function register_gradient_colors(name::Symbol, colors::AVec{C}) where C<:Colorant
_gradients[name] = colors _gradients[name] = colors
end end
@ -109,11 +109,11 @@ default_gradient() = ColorGradient(:inferno)
# -------------------------------------------------------------- # --------------------------------------------------------------
"Continuous gradient between values. Wraps a list of bounding colors and the values they represent." "Continuous gradient between values. Wraps a list of bounding colors and the values they represent."
immutable ColorGradient <: ColorScheme struct ColorGradient <: ColorScheme
colors::Vector colors::Vector
values::Vector values::Vector
function ColorGradient{S<:Real}(cs::AVec, vals::AVec{S} = linspace(0, 1, length(cs)); alpha = nothing) function ColorGradient(cs::AVec, vals::AVec{S} = linspace(0, 1, length(cs)); alpha = nothing) where S<:Real
if length(cs) == length(vals) if length(cs) == length(vals)
return new(convertColor(cs,alpha), collect(vals)) return new(convertColor(cs,alpha), collect(vals))
end end
@ -138,7 +138,7 @@ Base.getindex(cs::ColorGradient, z::Number) = getColorZ(cs, z)
# create a gradient from a symbol (blues, reds, etc) and vector of boundary values # create a gradient from a symbol (blues, reds, etc) and vector of boundary values
function ColorGradient{T<:Real}(s::Symbol, vals::AVec{T} = 0:0; kw...) function ColorGradient(s::Symbol, vals::AVec{T} = 0:0; kw...) where T<:Real
haskey(_gradients, s) || error("Invalid gradient symbol. Choose from: ", sort(collect(keys(_gradients)))) haskey(_gradients, s) || error("Invalid gradient symbol. Choose from: ", sort(collect(keys(_gradients))))
cs = _gradients[s] cs = _gradients[s]
if vals == 0:0 if vals == 0:0
@ -208,7 +208,7 @@ end
# -------------------------------------------------------------- # --------------------------------------------------------------
"Wraps a function, taking an index and returning a Colorant" "Wraps a function, taking an index and returning a Colorant"
immutable ColorFunction <: ColorScheme struct ColorFunction <: ColorScheme
f::Function f::Function
end end
@ -217,7 +217,7 @@ getColor(scheme::ColorFunction, idx::Int) = scheme.f(idx)
# -------------------------------------------------------------- # --------------------------------------------------------------
"Wraps a function, taking an z-value and returning a Colorant" "Wraps a function, taking an z-value and returning a Colorant"
immutable ColorZFunction <: ColorScheme struct ColorZFunction <: ColorScheme
f::Function f::Function
end end
@ -226,7 +226,7 @@ getColorZ(scheme::ColorZFunction, z::Real) = scheme.f(z)
# -------------------------------------------------------------- # --------------------------------------------------------------
"Wraps a vector of colors... may be vector of Symbol/String/Colorant" "Wraps a vector of colors... may be vector of Symbol/String/Colorant"
immutable ColorVector <: ColorScheme struct ColorVector <: ColorScheme
v::Vector{Colorant} v::Vector{Colorant}
ColorVector(v::AVec; alpha = nothing) = new(convertColor(v,alpha)) ColorVector(v::AVec; alpha = nothing) = new(convertColor(v,alpha))
end end
@ -238,7 +238,7 @@ getColorVector(scheme::ColorVector) = scheme.v
# -------------------------------------------------------------- # --------------------------------------------------------------
"Wraps a single color" "Wraps a single color"
immutable ColorWrapper <: ColorScheme struct ColorWrapper <: ColorScheme
c::RGBA c::RGBA
ColorWrapper(c::Colorant; alpha = nothing) = new(convertColor(c, alpha)) ColorWrapper(c::Colorant; alpha = nothing) = new(convertColor(c, alpha))
end end
@ -347,8 +347,8 @@ function get_color_palette(palette, bgcolor::Union{Colorant,ColorWrapper}, numco
RGBA[getColorZ(grad, z) for z in zrng] RGBA[getColorZ(grad, z) for z in zrng]
end end
function get_color_palette{C<:Colorant}(palette::Vector{C}, function get_color_palette(palette::Vector{C},
bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer) bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer) where C<:Colorant
palette palette
end end

View File

@ -5,21 +5,21 @@
# This should cut down on boilerplate code and allow more focused dispatch on type # This should cut down on boilerplate code and allow more focused dispatch on type
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now # note: returns meta information... mainly for use with automatic labeling from DataFrames for now
typealias FuncOrFuncs @compat(Union{Function, AVec{Function}}) const FuncOrFuncs = Union{Function, AVec{Function}}
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image), get(d, :seriestype, :none)) all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image), get(d, :seriestype, :none))
# missing # missing
convertToAnyVector(v::@compat(Void), d::KW) = Any[nothing], nothing convertToAnyVector(v::Void, d::KW) = Any[nothing], nothing
# fixed number of blank series # fixed number of blank series
convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing
# numeric vector # numeric vector
convertToAnyVector{T<:Number}(v::AVec{T}, d::KW) = Any[v], nothing convertToAnyVector(v::AVec{T}, d::KW) where {T<:Number} = Any[v], nothing
# string vector # string vector
convertToAnyVector{T<:@compat(AbstractString)}(v::AVec{T}, d::KW) = Any[v], nothing convertToAnyVector(v::AVec{T}, d::KW) where {T<:AbstractString} = Any[v], nothing
function convertToAnyVector(v::AMat, d::KW) function convertToAnyVector(v::AMat, d::KW)
if all3D(d) if all3D(d)
@ -39,7 +39,7 @@ convertToAnyVector(s::Surface, d::KW) = Any[s], nothing
# convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing # convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing
# dates # dates
convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::KW) = Any[dts], nothing convertToAnyVector(dts::AVec{D}, d::KW) where {D<:Union{Date,DateTime}} = Any[dts], nothing
# list of things (maybe other vectors, functions, or something else) # list of things (maybe other vectors, functions, or something else)
function convertToAnyVector(v::AVec, d::KW) function convertToAnyVector(v::AVec, d::KW)

View File

@ -1,7 +1,7 @@
""" """
Holds all data needed for a documentation example... header, description, and plotting expression (Expr) Holds all data needed for a documentation example... header, description, and plotting expression (Expr)
""" """
type PlotExample mutable struct PlotExample
header::AbstractString header::AbstractString
desc::AbstractString desc::AbstractString
exprs::Vector{Expr} exprs::Vector{Expr}
@ -18,7 +18,14 @@ PlotExample("Lines",
), ),
PlotExample("Functions, adding data, and animations", PlotExample("Functions, adding data, and animations",
"Plot multiple functions. You can also put the function first, or use the form `plot(f, xmin, xmax)` where f is a Function or AbstractVector{Function}.\n\nGet series data: `x, y = plt[i]`. Set series data: `plt[i] = (x,y)`. Add to the series with `push!`/`append!`.\n\nEasily build animations. (`convert` or `ffmpeg` must be available to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the animation.", """
Plot multiple functions. You can also put the function first, or use the form `plot(f,
xmin, xmax)` where f is a Function or AbstractVector{Function}.\n\nGet series data:
`x, y = plt[i]`. Set series data: `plt[i] = (x,y)`. Add to the series with
`push!`/`append!`.\n\nEasily build animations. (`convert` or `ffmpeg` must be available
to generate the animation.) Use command `gif(anim, filename, fps=15)` to save the
animation.
""",
[:(begin [:(begin
p = plot([sin,cos], zeros(0), leg=false) p = plot([sin,cos], zeros(0), leg=false)
anim = Animation() anim = Animation()
@ -37,23 +44,35 @@ PlotExample("Parametric plots",
), ),
PlotExample("Colors", PlotExample("Colors",
"Access predefined palettes (or build your own with the `colorscheme` method). Line/marker colors are auto-generated from the plot's palette, unless overridden. Set the `z` argument to turn on series gradients.", """
Access predefined palettes (or build your own with the `colorscheme` method).
Line/marker colors are auto-generated from the plot's palette, unless overridden. Set
the `z` argument to turn on series gradients.
""",
[:(begin [:(begin
y = rand(100) y = rand(100)
plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=0, α=0.6) plot(0:10:100,rand(11,4),lab="lines",w=3,palette=:grays,fill=0, α=0.6)
scatter!(y, zcolor=abs(y-.5), m=(:heat,0.8,stroke(1,:green)), ms=10*abs(y-0.5)+4, lab="grad") scatter!(y, zcolor=abs.(y-.5), m=(:heat,0.8,stroke(1,:green)), ms=10*abs.(y-0.5)+4,
lab="grad")
end)] end)]
), ),
PlotExample("Global", PlotExample("Global",
"Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow you to pass a tuple or value which will be mapped to the relevant args automatically. The `xaxis` below will be replaced with `xlabel` and `xlims` args automatically during the preprocessing step. You can also use shorthand functions: `title!`, `xaxis!`, `yaxis!`, `xlabel!`, `ylabel!`, `xlims!`, `ylims!`, `xticks!`, `yticks!`", """
Change the guides/background/limits/ticks. Convenience args `xaxis` and `yaxis` allow
you to pass a tuple or value which will be mapped to the relevant args automatically.
The `xaxis` below will be replaced with `xlabel` and `xlims` args automatically during
the preprocessing step. You can also use shorthand functions: `title!`, `xaxis!`,
`yaxis!`, `xlabel!`, `ylabel!`, `xlims!`, `ylims!`, `xticks!`, `yticks!`
""",
[:(begin [:(begin
y = rand(20,3) y = rand(20,3)
plot(y, xaxis=("XLABEL",(-5,30),0:2:20,:flip), background_color = RGB(0.2,0.2,0.2), leg=false) plot(y, xaxis=("XLABEL",(-5,30),0:2:20,:flip), background_color = RGB(0.2,0.2,0.2),
hline!(mean(y,1)+rand(1,3), line=(4,:dash,0.6,[:lightgreen :green :darkgreen])) leg=false)
vline!([5,10]) hline!(mean(y,1)+rand(1,3), line=(4,:dash,0.6,[:lightgreen :green :darkgreen]))
title!("TITLE") vline!([5,10])
yaxis!("YLABEL", :log10) title!("TITLE")
yaxis!("YLABEL", :log10)
end)] end)]
), ),
@ -66,14 +85,21 @@ PlotExample("Global",
PlotExample("Images", PlotExample("Images",
"Plot an image. y-axis is set to flipped", "Plot an image. y-axis is set to flipped",
[:(begin [:(begin
import Images import FileIO
img = Images.load(Pkg.dir("PlotReferenceImages","Plots","pyplot","0.7.0","ref1.png")) img = FileIO.load(Pkg.dir("PlotReferenceImages","Plots","pyplot","0.7.0","ref1.png"))
plot(img) plot(img)
end)] end)]
), ),
PlotExample("Arguments", PlotExample("Arguments",
"Plot multiple series with different numbers of points. Mix arguments that apply to all series (marker/markersize) with arguments unique to each series (colors). Special arguments `line`, `marker`, and `fill` will automatically figure out what arguments to set (for example, we are setting the `linestyle`, `linewidth`, and `color` arguments with `line`.) Note that we pass a matrix of colors, and this applies the colors to each series.", """
Plot multiple series with different numbers of points. Mix arguments that apply to all
series (marker/markersize) with arguments unique to each series (colors). Special
arguments `line`, `marker`, and `fill` will automatically figure out what arguments to
set (for example, we are setting the `linestyle`, `linewidth`, and `color` arguments with
`line`.) Note that we pass a matrix of colors, and this applies the colors to each
series.
""",
[:(begin [:(begin
ys = Vector[rand(10), rand(20)] ys = Vector[rand(10), rand(20)]
plot(ys, color=[:black :orange], line=(:dot,4), marker=([:hex :d],12,0.8,stroke(3,:gray))) plot(ys, color=[:black :orange], line=(:dot,4), marker=([:hex :d],12,0.8,stroke(3,:gray)))
@ -115,20 +141,23 @@ PlotExample("Line types",
PlotExample("Line styles", PlotExample("Line styles",
"", "",
[:(begin [:(begin
styles = filter(s -> s in Plots.supported_styles(), [:solid, :dash, :dot, :dashdot, :dashdotdot])' styles = filter(s -> s in Plots.supported_styles(),
n = length(styles) [:solid, :dash, :dot, :dashdot, :dashdotdot])
y = cumsum(randn(20,n),1) styles = reshape(styles, 1, length(styles)) # Julia 0.6 unfortunately gives an error when transposing symbol vectors
plot(y, line = (5, styles), label = map(string,styles)) n = length(styles)
end)] y = cumsum(randn(20,n),1)
plot(y, line = (5, styles), label = map(string,styles), legendtitle = "linestyle")
end)]
), ),
PlotExample("Marker types", PlotExample("Marker types",
"", "",
[:(begin [:(begin
markers = filter(m -> m in Plots.supported_markers(), Plots._shape_keys)' markers = filter(m -> m in Plots.supported_markers(), Plots._shape_keys)
markers = reshape(markers, 1, length(markers))
n = length(markers) n = length(markers)
x = linspace(0,10,n+2)[2:end-1] x = linspace(0,10,n+2)[2:end-1]
y = repmat(reverse(x)', n, 1) y = repmat(reshape(reverse(x),1,:), n, 1)
scatter(x, y, m=(8,:auto), lab=map(string,markers), bg=:linen, xlim=(0,10), ylim=(0,10)) scatter(x, y, m=(8,:auto), lab=map(string,markers), bg=:linen, xlim=(0,10), ylim=(0,10))
end)] end)]
), ),
@ -143,24 +172,30 @@ PlotExample("Bar",
PlotExample("Histogram", PlotExample("Histogram",
"", "",
[:(begin [:(begin
histogram(randn(1000), nbins=20) histogram(randn(1000), bins = :scott, weights = repeat(1:5, outer = 200))
end)] end)]
), ),
PlotExample("Subplots", PlotExample("Subplots",
""" """
Use the `layout` keyword, and optionally the convenient `@layout` macro to generate arbitrarily complex subplot layouts. Use the `layout` keyword, and optionally the convenient `@layout` macro to generate
""", arbitrarily complex subplot layouts.
""",
[:(begin [:(begin
l = @layout([a{0.1h}; b [c;d e]]) l = @layout([a{0.1h}; b [c;d e]])
plot(randn(100,5), layout=l, t=[:line :histogram :scatter :steppre :bar], leg=false, ticks=nothing, border=false) plot(randn(100,5), layout=l, t=[:line :histogram :scatter :steppre :bar], leg=false,
ticks=nothing, border=:none)
end)] end)]
), ),
PlotExample("Adding to subplots", PlotExample("Adding to subplots",
"Note here the automatic grid layout, as well as the order in which new series are added to the plots.", """
Note here the automatic grid layout, as well as the order in which new series are added
to the plots.
""",
[:(begin [:(begin
plot(Plots.fakedata(100,10), layout=4, palette=[:grays :blues :heat :lightrainbow], bg_inside=[:orange :pink :darkblue :black]) plot(Plots.fakedata(100,10), layout=4, palette=[:grays :blues :heat :lightrainbow],
bg_inside=[:orange :pink :darkblue :black])
end)] end)]
), ),
@ -173,48 +208,68 @@ PlotExample("",
), ),
PlotExample("Open/High/Low/Close", PlotExample("Open/High/Low/Close",
"Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y` argument. This uses recipes to first convert the tuples to OHLC objects, and subsequently create a :path series with the appropriate line segments.", """
Create an OHLC chart. Pass in a list of (open,high,low,close) tuples as your `y`
argument. This uses recipes to first convert the tuples to OHLC objects, and
subsequently create a :path series with the appropriate line segments.
""",
[:(begin [:(begin
n=20 n=20
hgt=rand(n)+1 hgt=rand(n)+1
bot=randn(n) bot=randn(n)
openpct=rand(n) openpct=rand(n)
closepct=rand(n) closepct=rand(n)
y = OHLC[(openpct[i]*hgt[i]+bot[i], bot[i]+hgt[i], bot[i], closepct[i]*hgt[i]+bot[i]) for i in 1:n] y = OHLC[(openpct[i]*hgt[i]+bot[i], bot[i]+hgt[i], bot[i],
ohlc(y) closepct[i]*hgt[i]+bot[i]) for i in 1:n]
ohlc(y)
end)] end)]
), ),
PlotExample("Annotations", PlotExample("Annotations",
"The `annotations` keyword is used for text annotations in data-coordinates. Pass in a tuple (x,y,text) or a vector of annotations. `annotate!(ann)` is shorthand for `plot!(; annotation=ann)`. Series annotations are used for annotating individual data points. They require only the annotation... x/y values are computed. A `PlotText` object can be build with the method `text(string, attr...)`, which wraps font and color attributes.", """
The `annotations` keyword is used for text annotations in data-coordinates. Pass in a
tuple (x,y,text) or a vector of annotations. `annotate!(ann)` is shorthand for `plot!(;
annotation=ann)`. Series annotations are used for annotating individual data points.
They require only the annotation... x/y values are computed. A `PlotText` object can be
build with the method `text(string, attr...)`, which wraps font and color attributes.
""",
[:(begin [:(begin
y = rand(10) y = rand(10)
plot(y, annotations = (3,y[3],text("this is #3",:left)), leg=false) plot(y, annotations = (3,y[3],text("this is #3",:left)), leg=false)
annotate!([(5, y[5], text("this is #5",16,:red,:center)), (10, y[10], text("this is #10",:right,20,"courier"))]) annotate!([(5, y[5], text("this is #5",16,:red,:center)),
scatter!(linspace(2,8,6), rand(6), marker=(50,0.2,:orange), series_annotations = ["series","annotations","map","to","series",text("data",:green)]) (10, y[10], text("this is #10",:right,20,"courier"))])
scatter!(linspace(2,8,6), rand(6), marker=(50,0.2,:orange),
series_annotations = ["series","annotations","map","to","series",
text("data",:green)])
end)] end)]
), ),
PlotExample("Custom Markers", PlotExample("Custom Markers",
"A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and the size is expected to be rougly the area of the unit circle.", """A `Plots.Shape` is a light wrapper around vertices of a polygon. For supported
backends, pass arbitrary polygons as the marker shapes. Note: The center is (0,0) and
the size is expected to be rougly the area of the unit circle.
""",
[:(begin [:(begin
verts = [(-1.0,1.0),(-1.28,0.6),(-0.2,-1.4),(0.2,-1.4),(1.28,0.6),(1.0,1.0), verts = [(-1.0,1.0),(-1.28,0.6),(-0.2,-1.4),(0.2,-1.4),(1.28,0.6),(1.0,1.0),
(-1.0,1.0),(-0.2,-0.6),(0.0,-0.2),(-0.4,0.6),(1.28,0.6),(0.2,-1.4), (-1.0,1.0),(-0.2,-0.6),(0.0,-0.2),(-0.4,0.6),(1.28,0.6),(0.2,-1.4),
(-0.2,-1.4),(0.6,0.2),(-0.2,0.2),(0.0,-0.2),(0.2,0.2),(-0.2,-0.6)] (-0.2,-1.4),(0.6,0.2),(-0.2,0.2),(0.0,-0.2),(0.2,0.2),(-0.2,-0.6)]
x = 0.1:0.2:0.9 x = 0.1:0.2:0.9
y = 0.7rand(5)+0.15 y = 0.7rand(5)+0.15
plot(x, y, line = (3,:dash,:lightblue), marker = (Shape(verts),30,RGBA(0,0,0,0.2)), plot(x, y, line = (3,:dash,:lightblue), marker = (Shape(verts),30,RGBA(0,0,0,0.2)),
bg=:pink, fg=:darkblue, xlim = (0,1), ylim=(0,1), leg=false) bg=:pink, fg=:darkblue, xlim = (0,1), ylim=(0,1), leg=false)
end)] end)]
), ),
PlotExample("Contours", PlotExample("Contours",
"Any value for fill works here. We first build a filled contour from a function, then an unfilled contour from a matrix.", """
Any value for fill works here. We first build a filled contour from a function, then an
unfilled contour from a matrix.
""",
[:(begin [:(begin
x = 1:0.5:20 x = 1:0.5:20
y = 1:0.5:10 y = 1:0.5:10
f(x,y) = (3x+y^2)*abs(sin(x)+cos(y)) f(x,y) = (3x+y^2)*abs(sin(x)+cos(y))
X = repmat(x', length(y), 1) X = repmat(reshape(x,1,:), length(y), 1)
Y = repmat(y, 1, length(x)) Y = repmat(y, 1, length(x))
Z = map(f, X, Y) Z = map(f, X, Y)
p1 = contour(x, y, f, fill=true) p1 = contour(x, y, f, fill=true)
@ -250,7 +305,7 @@ PlotExample("DataFrames",
[:(begin [:(begin
import RDatasets import RDatasets
iris = RDatasets.dataset("datasets", "iris") iris = RDatasets.dataset("datasets", "iris")
scatter(iris, :SepalLength, :SepalWidth, group=:Species, @df iris scatter(:SepalLength, :SepalWidth, group=:Species,
title = "My awesome plot", xlabel = "Length", ylabel = "Width", title = "My awesome plot", xlabel = "Length", ylabel = "Width",
marker = (0.5, [:cross :hex :star7], 12), bg=RGB(.2,.2,.2)) marker = (0.5, [:cross :hex :star7], 12), bg=RGB(.2,.2,.2))
end)] end)]
@ -260,7 +315,8 @@ PlotExample("Groups and Subplots",
"", "",
[:(begin [:(begin
group = rand(map(i->"group $i",1:4),100) group = rand(map(i->"group $i",1:4),100)
plot(rand(100), layout=@layout([a b;c]), group=group, linetype=[:bar :scatter :steppre]) plot(rand(100), layout=@layout([a b;c]), group=group,
linetype=[:bar :scatter :steppre], linecolor = :match)
end)] end)]
), ),
@ -268,7 +324,7 @@ PlotExample("Polar Plots",
"", "",
[:(begin [:(begin
Θ = linspace(0,1.5π,100) Θ = linspace(0,1.5π,100)
r = abs(0.1randn(100)+sin(3Θ)) r = abs.(0.1randn(100)+sin.(3Θ))
plot(Θ, r, proj=:polar, m=2) plot(Θ, r, proj=:polar, m=2)
end)] end)]
), ),
@ -278,7 +334,7 @@ PlotExample("Heatmap, categorical axes, and aspect_ratio",
[:(begin [:(begin
xs = [string("x",i) for i=1:10] xs = [string("x",i) for i=1:10]
ys = [string("y",i) for i=1:4] ys = [string("y",i) for i=1:4]
z = float((1:4)*(1:10)') z = float((1:4)*reshape(1:10,1,:))
heatmap(xs, ys, z, aspect_ratio=1) heatmap(xs, ys, z, aspect_ratio=1)
end)] end)]
), ),
@ -286,9 +342,10 @@ PlotExample("Heatmap, categorical axes, and aspect_ratio",
PlotExample("Layouts, margins, label rotation, title location", PlotExample("Layouts, margins, label rotation, title location",
"", "",
[:(begin [:(begin
using Plots.PlotMeasures # for Measures, e.g. mm and px
plot(rand(100,6),layout=@layout([a b; c]),title=["A" "B" "C"], plot(rand(100,6),layout=@layout([a b; c]),title=["A" "B" "C"],
title_location=:left, left_margin=[20mm 0mm], title_location=:left, left_margin=[20mm 0mm],
bottom_margin=50px, xrotation=60) bottom_margin=10px, xrotation=60)
end)] end)]
), ),
@ -297,10 +354,83 @@ PlotExample("Boxplot and Violin series recipes",
[:(begin [:(begin
import RDatasets import RDatasets
singers = RDatasets.dataset("lattice", "singer") singers = RDatasets.dataset("lattice", "singer")
violin(singers, :VoicePart, :Height, line = 0, fill = (0.2, :blue)) @df singers violin(:VoicePart, :Height, line = 0, fill = (0.2, :blue))
boxplot!(singers, :VoicePart, :Height, line = (2,:black), fill = (0.3, :orange)) @df singers boxplot!(:VoicePart, :Height, line = (2,:black), fill = (0.3, :orange))
end)] end)]
) ),
PlotExample("Animation with subplots",
"The `layout` macro can be used to create an animation with subplots.",
[:(begin
l = @layout([[a; b] c])
p = plot(plot([sin,cos],1,leg=false),
scatter([atan,cos],1,leg=false),
plot(log,1,xlims=(1,10π),ylims=(0,5),leg=false),layout=l)
anim = Animation()
for x = linspace(1,10π,100)
plot(push!(p,x,Float64[sin(x),cos(x),atan(x),cos(x),log(x)]))
frame(anim)
end
end)]
),
PlotExample("Spy",
"""
For a matrix `mat` with unique nonzeros `spy(mat)` returns a colorless plot. If `mat` has
various different nonzero values, a colorbar is added. The colorbar can be disabled with
`legend = nothing`.
""",
[:(begin
a = spdiagm((ones(50), ones(49), ones(49), ones(40), ones(40)),(0, 1, -1, 10, -10))
b = spdiagm((1:50, 1:49, 1:49, 1:40, 1:40),(0, 1, -1, 10, -10))
plot(spy(a), spy(b), title = ["Unique nonzeros" "Different nonzeros"])
end)]
),
PlotExample("Magic grid argument",
"""
The grid lines can be modified individually for each axis with the magic `grid` argument.
""",
[:(begin
x = rand(10)
p1 = plot(x, title = "Default looks")
p2 = plot(x, grid = (:y, :olivedrab, :dot, 1, 0.9), title = "Modified y grid")
p3 = plot(deepcopy(p2), title = "Add x grid")
xgrid!(p3, :on, :cadetblue, 2, :dashdot, 0.4)
plot(p1, p2, p3, layout = (1, 3), label = "", fillrange = 0, fillalpha = 0.3)
end)]
),
PlotExample("Framestyle",
"""
The style of the frame/axes of a (sub)plot can be changed with the `framestyle`
attribute. The default framestyle is `:axes`.
""",
[:(begin
scatter(fill(randn(10), 6), fill(randn(10), 6),
framestyle = [:box :semi :origin :zerolines :grid :none],
title = [":box" ":semi" ":origin" ":zerolines" ":grid" ":none"],
color = RowVector(1:6), layout = 6, label = "", markerstrokewidth = 0,
ticks = -2:2)
end)]
),
PlotExample("Lines and markers with varying colors",
"""
You can use the `line_z` and `marker_z` properties to associate a color with
each line segment or marker in the plot.
""",
[:(begin
t = linspace(0, 1, 100)
θ = 6π .* t
x = t .* cos.(θ)
y = t .* sin.(θ)
p1 = plot(x, y, line_z=t, linewidth=3, legend=false)
p2 = scatter(x, y, marker_z=t, color=:bluesreds, legend=false)
plot(p1, p2)
end)]
),
] ]
@ -321,6 +451,13 @@ function test_examples(pkgname::Symbol, idx::Int; debug = false, disp = true)
end end
# generate all plots and create a dict mapping idx --> plt # generate all plots and create a dict mapping idx --> plt
"""
test_examples(pkgname[, idx]; debug = false, disp = true, sleep = nothing,
skip = [], only = nothing
Run the `idx` test example for a given backend, or all examples if `idx`
is not specified.
"""
function test_examples(pkgname::Symbol; debug = false, disp = true, sleep = nothing, function test_examples(pkgname::Symbol; debug = false, disp = true, sleep = nothing,
skip = [], only = nothing) skip = [], only = nothing)
Plots._debugMode.on = debug Plots._debugMode.on = debug

View File

@ -1,23 +1,19 @@
# NOTE: (0,0) is the top-left !!! # NOTE: (0,0) is the top-left !!!
# allow pixels and percentages
const px = AbsoluteLength(0.254)
const pct = Length{:pct, Float64}(1.0)
to_pixels(m::AbsoluteLength) = m.value / 0.254 to_pixels(m::AbsoluteLength) = m.value / 0.254
const _cbar_width = 5mm const _cbar_width = 5mm
Base.:.*(m::Measure, n::Number) = m * n Base.broadcast(::typeof(Base.:.*), m::Measure, n::Number) = m * n
Base.:.*(n::Number, m::Measure) = m * n Base.broadcast(::typeof(Base.:.*), m::Number, n::Measure) = m * n
Base.:-(m::Measure, a::AbstractArray) = map(ai -> m - ai, a) Base.:-(m::Measure, a::AbstractArray) = map(ai -> m - ai, a)
Base.:-(a::AbstractArray, m::Measure) = map(ai -> ai - m, a) Base.:-(a::AbstractArray, m::Measure) = map(ai -> ai - m, a)
Base.zero(::Type{typeof(mm)}) = 0mm Base.zero(::Type{typeof(mm)}) = 0mm
Base.one(::Type{typeof(mm)}) = 1mm Base.one(::Type{typeof(mm)}) = 1mm
Base.typemin(::typeof(mm)) = -Inf*mm Base.typemin(::typeof(mm)) = -Inf*mm
Base.typemax(::typeof(mm)) = Inf*mm Base.typemax(::typeof(mm)) = Inf*mm
Base.convert{F<:AbstractFloat}(::Type{F}, l::AbsoluteLength) = convert(F, l.value) Base.convert(::Type{F}, l::AbsoluteLength) where {F<:AbstractFloat} = convert(F, l.value)
# TODO: these are unintuitive and may cause tricky bugs # TODO: these are unintuitive and may cause tricky bugs
# Base.:+(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value)) # Base.:+(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value))
@ -99,7 +95,7 @@ end
# ----------------------------------------------------------- # -----------------------------------------------------------
# points combined by x/y, pct, and length # points combined by x/y, pct, and length
type MixedMeasures mutable struct MixedMeasures
xy::Float64 xy::Float64
pct::Float64 pct::Float64
len::AbsoluteLength len::AbsoluteLength
@ -133,7 +129,12 @@ make_measure_hor(m::Measure) = m
make_measure_vert(n::Number) = n * h make_measure_vert(n::Number) = n * h
make_measure_vert(m::Measure) = m make_measure_vert(m::Measure) = m
"""
bbox(x, y, w, h [,originargs...])
bbox(layout)
Create a bounding box for plotting
"""
function bbox(x, y, w, h, oarg1::Symbol, originargs::Symbol...) function bbox(x, y, w, h, oarg1::Symbol, originargs::Symbol...)
oargs = vcat(oarg1, originargs...) oargs = vcat(oarg1, originargs...)
orighor = :left orighor = :left
@ -215,7 +216,7 @@ bottompad(layout::AbstractLayout) = 0mm
# RootLayout # RootLayout
# this is the parent of the top-level layout # this is the parent of the top-level layout
immutable RootLayout <: AbstractLayout end struct RootLayout <: AbstractLayout end
Base.parent(::RootLayout) = nothing Base.parent(::RootLayout) = nothing
parent_bbox(::RootLayout) = defaultbox parent_bbox(::RootLayout) = defaultbox
@ -225,7 +226,7 @@ bbox(::RootLayout) = defaultbox
# EmptyLayout # EmptyLayout
# contains blank space # contains blank space
type EmptyLayout <: AbstractLayout mutable struct EmptyLayout <: AbstractLayout
parent::AbstractLayout parent::AbstractLayout
bbox::BoundingBox bbox::BoundingBox
attr::KW # store label, width, and height for initialization attr::KW # store label, width, and height for initialization
@ -243,7 +244,7 @@ _update_min_padding!(layout::EmptyLayout) = nothing
# GridLayout # GridLayout
# nested, gridded layout with optional size percentages # nested, gridded layout with optional size percentages
type GridLayout <: AbstractLayout mutable struct GridLayout <: AbstractLayout
parent::AbstractLayout parent::AbstractLayout
minpad::Tuple # leftpad, toppad, rightpad, bottompad minpad::Tuple # leftpad, toppad, rightpad, bottompad
bbox::BoundingBox bbox::BoundingBox
@ -253,6 +254,13 @@ type GridLayout <: AbstractLayout
attr::KW attr::KW
end end
"""
grid(args...; kw...)
Create a grid layout for subplots. `args` specify the dimensions, e.g.
`grid(3,2, widths = (0.6,04))` creates a grid with three rows and two
columns of different width.
"""
grid(args...; kw...) = GridLayout(args...; kw...) grid(args...; kw...) = GridLayout(args...; kw...)
function GridLayout(dims...; function GridLayout(dims...;
@ -473,12 +481,12 @@ function layout_args(n::Integer)
GridLayout(nr, nc), n GridLayout(nr, nc), n
end end
function layout_args{I<:Integer}(sztup::NTuple{2,I}) function layout_args(sztup::NTuple{2,I}) where I<:Integer
nr, nc = sztup nr, nc = sztup
GridLayout(nr, nc), nr*nc GridLayout(nr, nc), nr*nc
end end
function layout_args{I<:Integer}(sztup::NTuple{3,I}) function layout_args(sztup::NTuple{3,I}) where I<:Integer
n, nr, nc = sztup n, nr, nc = sztup
nr, nc = compute_gridsize(n, nr, nc) nr, nc = compute_gridsize(n, nr, nc)
GridLayout(nr, nc), n GridLayout(nr, nc), n
@ -704,7 +712,7 @@ function link_axes!(axes::Axis...)
a1 = axes[1] a1 = axes[1]
for i=2:length(axes) for i=2:length(axes)
a2 = axes[i] a2 = axes[i]
expand_extrema!(a1, extrema(a2)) expand_extrema!(a1, ignorenan_extrema(a2))
for k in (:extrema, :discrete_values, :continuous_values, :discrete_map) for k in (:extrema, :discrete_values, :continuous_values, :discrete_map)
a2[k] = a1[k] a2[k] = a1[k]
end end

View File

@ -39,7 +39,7 @@ ps(fn::AbstractString) = ps(current(), fn)
function eps(plt::Plot, fn::AbstractString) function eps(plt::Plot, fn::AbstractString)
fn = addExtension(fn, "eps") fn = addExtension(fn, "eps")
io = open(fn, "w") io = open(fn, "w")
writemime(io, MIME("image/eps"), plt) show(io, MIME("image/eps"), plt)
close(io) close(io)
end end
eps(fn::AbstractString) = eps(current(), fn) eps(fn::AbstractString) = eps(current(), fn)
@ -97,6 +97,13 @@ function addExtension(fn::AbstractString, ext::AbstractString)
end end
end end
"""
savefig([plot,] filename)
Save a Plot (the current plot if `plot` is not passed) to file. The file
type is inferred from the file extension. All backends support png and pdf
file types, some also support svg, ps, eps, html and tex.
"""
function savefig(plt::Plot, fn::AbstractString) function savefig(plt::Plot, fn::AbstractString)
# get the extension # get the extension
@ -119,7 +126,11 @@ savefig(fn::AbstractString) = savefig(current(), fn)
# --------------------------------------------------------- # ---------------------------------------------------------
"""
gui([plot])
Display a plot using the backends' gui window
"""
gui(plt::Plot = current()) = display(PlotsDisplay(), plt) gui(plt::Plot = current()) = display(PlotsDisplay(), plt)
# IJulia only... inline display # IJulia only... inline display
@ -146,25 +157,16 @@ end
# --------------------------------------------------------- # ---------------------------------------------------------
const _mimeformats = Dict(
"application/eps" => "eps",
"image/eps" => "eps",
"application/pdf" => "pdf",
"image/png" => "png",
"application/postscript" => "ps",
"image/svg+xml" => "svg",
"text/plain" => "txt",
"application/x-tex" => "tex",
)
const _best_html_output_type = KW( const _best_html_output_type = KW(
:pyplot => :png, :pyplot => :png,
:unicodeplots => :txt, :unicodeplots => :txt,
:glvisualize => :png :glvisualize => :png,
:plotlyjs => :html,
:plotly => :html
) )
# a backup for html... passes to svg or png depending on the html_output_format arg # a backup for html... passes to svg or png depending on the html_output_format arg
function Base.show(io::IO, ::MIME"text/html", plt::Plot) function _show(io::IO, ::MIME"text/html", plt::Plot)
output_type = Symbol(plt.attr[:html_output_format]) output_type = Symbol(plt.attr[:html_output_format])
if output_type == :auto if output_type == :auto
output_type = get(_best_html_output_type, backend_name(plt.backend), :svg) output_type = get(_best_html_output_type, backend_name(plt.backend), :svg)
@ -178,35 +180,43 @@ function Base.show(io::IO, ::MIME"text/html", plt::Plot)
elseif output_type == :txt elseif output_type == :txt
show(io, MIME("text/plain"), plt) show(io, MIME("text/plain"), plt)
else else
error("only png or svg allowed. got: $output_type") error("only png or svg allowed. got: $(repr(output_type))")
end end
end end
function _show{B}(io::IO, m, plt::Plot{B}) # delegate mimewritable (showable on julia 0.7) to _show instead
# Base.show_backtrace(STDOUT, backtrace()) function Base.mimewritable(m::M, plt::P) where {M<:MIME, P<:Plot}
warn("_show is not defined for this backend. m=", string(m)) return method_exists(_show, Tuple{IO, M, P})
end end
function _display(plt::Plot) function _display(plt::Plot)
warn("_display is not defined for this backend.") warn("_display is not defined for this backend.")
end end
# for writing to io streams... first prepare, then callback # for writing to io streams... first prepare, then callback
for mime in keys(_mimeformats) for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml",
@eval function Base.show{B}(io::IO, m::MIME{Symbol($mime)}, plt::Plot{B}) "application/eps", "application/pdf", "application/postscript",
"application/x-tex")
@eval function Base.show(io::IO, m::MIME{Symbol($mime)}, plt::Plot)
prepare_output(plt) prepare_output(plt)
_show(io, m, plt) _show(io, m, plt)
end end
end end
# default text/plain for all backends
_show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt)
"Close all open gui windows of the current backend"
closeall() = closeall(backend()) closeall() = closeall(backend())
# --------------------------------------------------------- # ---------------------------------------------------------
# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert # A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert
const PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend}
if is_installed("FileIO") if is_installed("FileIO")
@eval import FileIO @eval import FileIO
function _show(io::IO, ::MIME"image/png", plt::Plot) function _show(io::IO, ::MIME"image/png", plt::Plot{<:PDFBackends})
fn = tempname() fn = tempname()
# first save a pdf file # first save a pdf file
@ -220,14 +230,10 @@ if is_installed("FileIO")
FileIO.save(pngfn, s) FileIO.save(pngfn, s)
# now write from the file # now write from the file
write(io, readall(open(pngfn))) write(io, readstring(open(pngfn)))
end end
end end
# function html_output_format(fmt) # function html_output_format(fmt)
# if fmt == "png" # if fmt == "png"
# @eval function Base.show(io::IO, ::MIME"text/html", plt::Plot) # @eval function Base.show(io::IO, ::MIME"text/html", plt::Plot)
@ -248,80 +254,108 @@ end
# IJulia # IJulia
# --------------------------------------------------------- # ---------------------------------------------------------
const _ijulia_output = String["text/html"] @require IJulia begin
if IJulia.inited
function setup_ijulia() """
# override IJulia inline display Add extra jupyter mimetypes to display_dict based on the plot backed.
if isijulia()
@eval begin
import IJulia
export set_ijulia_output
function set_ijulia_output(mimestr::AbstractString)
# info("Setting IJulia output format to $mimestr")
global _ijulia_output
_ijulia_output[1] = mimestr
end
function IJulia.display_dict(plt::Plot)
global _ijulia_output
Dict{String, String}(_ijulia_output[1] => sprint(show, _ijulia_output[1], plt))
end
# default text/plain passes to html... handles Interact issues The default is nothing, except for plotly based backends, where it
function Base.show(io::IO, m::MIME"text/plain", plt::Plot) adds data for `application/vnd.plotly.v1+json` that is used in
show(io, MIME("text/html"), plt) frontends like jupyterlab and nteract.
end """
_extra_mime_info!(plt::Plot, out::Dict) = out
function _extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict)
out["application/vnd.plotly.v1+json"] = JSON.lower(plt.o)
out
end end
set_ijulia_output("text/html")
function _extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict)
out["application/vnd.plotly.v1+json"] = Dict(
:data => plotly_series(plt),
:layout => plotly_layout(plt)
)
out
end
function IJulia.display_dict(plt::Plot)
output_type = Symbol(plt.attr[:html_output_format])
if output_type == :auto
output_type = get(_best_html_output_type, backend_name(plt.backend), :svg)
end
out = Dict()
if output_type == :txt
mime = "text/plain"
out[mime] = sprint(show, MIME(mime), plt)
elseif output_type == :png
mime = "image/png"
out[mime] = base64encode(show, MIME(mime), plt)
elseif output_type == :svg
mime = "image/svg+xml"
out[mime] = sprint(show, MIME(mime), plt)
elseif output_type == :html
mime = "text/html"
out[mime] = sprint(show, MIME(mime), plt)
else
error("Unsupported output type $output_type")
end
_extra_mime_info!(plt, out)
out
end
ENV["MPLBACKEND"] = "Agg"
end end
end end
# --------------------------------------------------------- # ---------------------------------------------------------
# Atom PlotPane # Atom PlotPane
# --------------------------------------------------------- # ---------------------------------------------------------
@require Juno begin
import Hiccup, Media
function setup_atom() if Juno.isactive()
if isatom()
@eval import Atom, Media
Media.media(Plot, Media.Plot) Media.media(Plot, Media.Plot)
# default text/plain so it doesn't complain function Juno.render(e::Juno.Editor, plt::Plot)
function Base.show{B}(io::IO, ::MIME"text/plain", plt::Plot{B}) Juno.render(e, nothing)
print(io, "Plot{$B}()")
end
function Media.render(e::Atom.Editor, plt::Plot)
Media.render(e, nothing)
end end
if get(ENV, "PLOTS_USE_ATOM_PLOTPANE", true) in (true, 1, "1", "true", "yes") if get(ENV, "PLOTS_USE_ATOM_PLOTPANE", true) in (true, 1, "1", "true", "yes")
# this is like "display"... sends an html div with the plot to the PlotPane function Juno.render(pane::Juno.PlotPane, plt::Plot)
function Media.render(pane::Atom.PlotPane, plt::Plot)
# temporarily overwrite size to be Atom.plotsize # temporarily overwrite size to be Atom.plotsize
sz = plt[:size] sz = plt[:size]
plt[:size] = Juno.plotsize() dpi = plt[:dpi]
Media.render(pane, Atom.div(".fill", Atom.HTML(stringmime(MIME("text/html"), plt)))) thickness_scaling = plt[:thickness_scaling]
jsize = Juno.plotsize()
jsize[1] == 0 && (jsize[1] = 400)
jsize[2] == 0 && (jsize[2] = 500)
scale = minimum(jsize[i] / sz[i] for i in 1:2)
plt[:size] = (s * scale for s in sz)
plt[:dpi] = Plots.DPI
plt[:thickness_scaling] *= scale
Juno.render(pane, HTML(stringmime(MIME("text/html"), plt)))
plt[:size] = sz plt[:size] = sz
plt[:dpi] = dpi
plt[:thickness_scaling] = thickness_scaling
end
# special handling for PlotlyJS
function Juno.render(pane::Juno.PlotPane, plt::Plot{PlotlyJSBackend})
display(Plots.PlotsDisplay(), plt)
end end
else else
# function Juno.render(pane::Juno.PlotPane, plt::Plot)
function Media.render(pane::Atom.PlotPane, plt::Plot)
display(Plots.PlotsDisplay(), plt) display(Plots.PlotsDisplay(), plt)
s = "PlotPane turned off. Unset ENV[\"PLOTS_USE_ATOM_PLOTPANE\"] and restart Julia to enable it." s = "PlotPane turned off. Unset ENV[\"PLOTS_USE_ATOM_PLOTPANE\"] and restart Julia to enable it."
Media.render(pane, Atom.div(Atom.HTML(s))) Juno.render(pane, HTML(s))
end end
end end
# special handling for plotly... use PlotsDisplay # special handling for plotly... use PlotsDisplay
function Media.render(pane::Atom.PlotPane, plt::Plot{PlotlyBackend}) function Juno.render(pane::Juno.PlotPane, plt::Plot{PlotlyBackend})
display(Plots.PlotsDisplay(), plt) display(Plots.PlotsDisplay(), plt)
s = "PlotPane turned off. The plotly and plotlyjs backends cannot render in the PlotPane due to javascript issues." s = "PlotPane turned off. The plotly backend cannot render in the PlotPane due to javascript issues. Plotlyjs is similar to plotly and is compatible with the plot pane."
Media.render(pane, Atom.div(Atom.HTML(s))) Juno.render(pane, HTML(s))
end
# special handling for PlotlyJS to pass through to that render method
function Media.render(pane::Atom.PlotPane, plt::Plot{PlotlyJSBackend})
Plots.prepare_output(plt)
Media.render(pane, plt.o)
end end
end end
end end

View File

@ -60,29 +60,26 @@ function _process_userrecipes(plt::Plot, d::KW, args)
args = _preprocess_args(d, args, still_to_process) args = _preprocess_args(d, args, still_to_process)
# for plotting recipes, swap out the args and update the parameter dictionary # for plotting recipes, swap out the args and update the parameter dictionary
# we are keeping a queue of series that still need to be processed. # we are keeping a stack of series that still need to be processed.
# each pass through the loop, we pop one off and apply the recipe. # each pass through the loop, we pop one off and apply the recipe.
# the recipe will return a list a Series objects... the ones that are # the recipe will return a list a Series objects... the ones that are
# finished (no more args) get added to the kw_list, and the rest go into the queue # finished (no more args) get added to the kw_list, the ones that are not
# for processing. # are placed on top of the stack and are then processed further.
kw_list = KW[] kw_list = KW[]
while !isempty(still_to_process) while !isempty(still_to_process)
# grab the first in line to be processed and pass it through apply_recipe # grab the first in line to be processed and either add it to the kw_list or
# to generate a list of RecipeData objects (data + attributes) # pass it through apply_recipe to generate a list of RecipeData objects (data + attributes)
# for further processing.
next_series = shift!(still_to_process) next_series = shift!(still_to_process)
rd_list = RecipesBase.apply_recipe(next_series.d, next_series.args...) # recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
for recipedata in rd_list if !(typeof(next_series) <: RecipeData)
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes error("Inputs couldn't be processed... expected RecipeData but got: $next_series")
if !(typeof(recipedata) <: RecipeData) end
error("Inputs couldn't be processed... expected RecipeData but got: $recipedata") if isempty(next_series.args)
end _process_userrecipe(plt, kw_list, next_series)
else
if isempty(recipedata.args) rd_list = RecipesBase.apply_recipe(next_series.d, next_series.args...)
_process_userrecipe(plt, kw_list, recipedata) prepend!(still_to_process,rd_list)
else
# args are non-empty, so there's still processing to do... add it back to the queue
push!(still_to_process, recipedata)
end
end end
end end
@ -153,7 +150,7 @@ function _add_smooth_kw(kw_list::Vector{KW}, kw::KW)
if get(kw, :smooth, false) if get(kw, :smooth, false)
x, y = kw[:x], kw[:y] x, y = kw[:x], kw[:y]
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y) β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
sx = [minimum(x), maximum(x)] sx = [ignorenan_minimum(x), ignorenan_maximum(x)]
sy = β * sx + α sy = β * sx + α
push!(kw_list, merge(copy(kw), KW( push!(kw_list, merge(copy(kw), KW(
:seriestype => :path, :seriestype => :path,
@ -213,7 +210,7 @@ function _plot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
# TODO: init subplots here # TODO: init subplots here
_update_plot_args(plt, d) _update_plot_args(plt, d)
if !plt.init if !plt.init
plt.o = _create_backend_figure(plt) plt.o = Base.invokelatest(_create_backend_figure, plt)
# create the layout and subplots from the inputs # create the layout and subplots from the inputs
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr) plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
@ -262,12 +259,12 @@ function _subplot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
for kw in kw_list for kw in kw_list
# get the Subplot object to which the series belongs. # get the Subplot object to which the series belongs.
sps = get(kw, :subplot, :auto) sps = get(kw, :subplot, :auto)
sp = get_subplot(plt, cycle(sps == :auto ? plt.subplots : plt.subplots[sps], command_idx(kw_list,kw))) sp = get_subplot(plt, _cycle(sps == :auto ? plt.subplots : plt.subplots[sps], command_idx(kw_list,kw)))
kw[:subplot] = sp kw[:subplot] = sp
# extract subplot/axis attributes from kw and add to sp_attr # extract subplot/axis attributes from kw and add to sp_attr
attr = KW() attr = KW()
for (k,v) in kw for (k,v) in collect(kw)
if haskey(_subplot_defaults, k) || haskey(_axis_defaults_byletter, k) if haskey(_subplot_defaults, k) || haskey(_axis_defaults_byletter, k)
attr[k] = pop!(kw, k) attr[k] = pop!(kw, k)
end end
@ -277,6 +274,13 @@ function _subplot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
attr[Symbol(letter,k)] = v attr[Symbol(letter,k)] = v
end end
end end
for k in (:scale,), letter in (:x,:y,:z)
# Series recipes may need access to this information
lk = Symbol(letter,k)
if haskey(attr, lk)
kw[lk] = attr[lk]
end
end
end end
sp_attrs[sp] = attr sp_attrs[sp] = attr
end end
@ -297,7 +301,7 @@ end
# getting ready to add the series... last update to subplot from anything # getting ready to add the series... last update to subplot from anything
# that might have been added during series recipes # that might have been added during series recipes
function _prepare_subplot{T}(plt::Plot{T}, d::KW) function _prepare_subplot(plt::Plot{T}, d::KW) where T
st::Symbol = d[:seriestype] st::Symbol = d[:seriestype]
sp::Subplot{T} = d[:subplot] sp::Subplot{T} = d[:subplot]
sp_idx = get_subplot_index(plt, sp) sp_idx = get_subplot_index(plt, sp)
@ -323,7 +327,7 @@ end
function _override_seriestype_check(d::KW, st::Symbol) function _override_seriestype_check(d::KW, st::Symbol)
# do we want to override the series type? # do we want to override the series type?
if !is3d(st) if !is3d(st) && !(st in (:contour,:contour3d))
z = d[:z] z = d[:z]
if !isa(z, Void) && (size(d[:x]) == size(d[:y]) == size(z)) if !isa(z, Void) && (size(d[:x]) == size(d[:y]) == size(z))
st = (st == :scatter ? :scatter3d : :path3d) st = (st == :scatter ? :scatter3d : :path3d)
@ -353,13 +357,17 @@ end
function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol) function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol)
# adjust extrema and discrete info # adjust extrema and discrete info
if st == :image if st == :image
w, h = size(d[:z]) xmin, xmax = ignorenan_extrema(d[:x]); ymin, ymax = ignorenan_extrema(d[:y])
expand_extrema!(sp[:xaxis], (0,w)) expand_extrema!(sp[:xaxis], (xmin, xmax))
expand_extrema!(sp[:yaxis], (0,h)) expand_extrema!(sp[:yaxis], (ymin, ymax))
sp[:yaxis].d[:flip] = true elseif !(st in (:pie, :histogram, :bins2d, :histogram2d))
elseif !(st in (:pie, :histogram, :histogram2d))
expand_extrema!(sp, d) expand_extrema!(sp, d)
end end
# expand for zerolines (axes through origin)
if sp[:framestyle] in (:origin, :zerolines)
expand_extrema!(sp[:xaxis], 0.0)
expand_extrema!(sp[:yaxis], 0.0)
end
end end
function _add_the_series(plt, sp, d) function _add_the_series(plt, sp, d)
@ -390,6 +398,7 @@ function _process_seriesrecipe(plt::Plot, d::KW)
sp = _prepare_subplot(plt, d) sp = _prepare_subplot(plt, d)
_prepare_annotations(sp, d) _prepare_annotations(sp, d)
_expand_subplot_extrema(sp, d, st) _expand_subplot_extrema(sp, d, st)
_update_series_attributes!(d, plt, sp)
_add_the_series(plt, sp, d) _add_the_series(plt, sp, d)
else else

View File

@ -1,11 +1,15 @@
type CurrentPlot mutable struct CurrentPlot
nullableplot::Nullable{AbstractPlot} nullableplot::Nullable{AbstractPlot}
end end
const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}()) const CURRENT_PLOT = CurrentPlot(Nullable{AbstractPlot}())
isplotnull() = isnull(CURRENT_PLOT.nullableplot) isplotnull() = isnull(CURRENT_PLOT.nullableplot)
"""
current()
Returns the Plot object for the current plot
"""
function current() function current()
if isplotnull() if isplotnull()
error("No current plot/subplot") error("No current plot/subplot")
@ -29,7 +33,7 @@ convertSeriesIndex(plt::Plot, n::Int) = n
""" """
The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one: The main plot command. Use `plot` to create a new plot object, and `plot!` to add to an existing one:
``` ```
plot(args...; kw...) # creates a new plot window, and sets it to be the current plot(args...; kw...) # creates a new plot window, and sets it to be the current
@ -38,7 +42,9 @@ The main plot command. Use `plot` to create a new plot object, and `plot!` to a
``` ```
There are lots of ways to pass in data, and lots of keyword arguments... just try it and it will likely work as expected. There are lots of ways to pass in data, and lots of keyword arguments... just try it and it will likely work as expected.
When you pass in matrices, it splits by columns. See the documentation for more info. When you pass in matrices, it splits by columns. To see the list of available attributes, use the `plotattr([attr])`
function, where `attr` is the symbol `:Series:`, `:Subplot:`, `:Plot` or `:Axis`. Pass any attribute to `plotattr`
as a String to look up its docstring; e.g. `plotattr("seriestype")`.
""" """
# this creates a new plot with args/kw and sets it to be the current plot # this creates a new plot with args/kw and sets it to be the current plot
@ -60,7 +66,7 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
# build our plot vector from the args # build our plot vector from the args
n = length(plts_tail) + 1 n = length(plts_tail) + 1
plts = Array(Plot, n) plts = Array{Plot}(n)
plts[1] = plt1 plts[1] = plt1
for (i,plt) in enumerate(plts_tail) for (i,plt) in enumerate(plts_tail)
plts[i+1] = plt plts[i+1] = plt
@ -80,7 +86,7 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
# TODO: replace this with proper processing from a merged user_attr KW # TODO: replace this with proper processing from a merged user_attr KW
# update plot args, first with existing plots, then override with d # update plot args, first with existing plots, then override with d
for p in plts for p in plts
_update_plot_args(plt, p.attr) _update_plot_args(plt, copy(p.attr))
plt.n += p.n plt.n += p.n
end end
_update_plot_args(plt, d) _update_plot_args(plt, d)
@ -96,8 +102,13 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
end end
end end
# create the layout and initialize the subplots # create the layout
plt.layout, plt.subplots, plt.spmap = build_layout(layout, num_sp, copy(plts)) plt.layout, plt.subplots, plt.spmap = build_layout(layout, num_sp, copy(plts))
# do we need to link any axes together?
link_axes!(plt.layout, plt[:link])
# initialize the subplots
cmdidx = 1 cmdidx = 1
for (idx, sp) in enumerate(plt.subplots) for (idx, sp) in enumerate(plt.subplots)
_initialize_subplot(plt, sp) _initialize_subplot(plt, sp)
@ -121,9 +132,6 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
_update_subplot_args(plt, sp, d, idx, false) _update_subplot_args(plt, sp, d, idx, false)
end end
# do we need to link any axes together?
link_axes!(plt.layout, plt[:link])
# finish up # finish up
current(plt) current(plt)
_do_plot_show(plt, get(d, :show, default(:show))) _do_plot_show(plt, get(d, :show, default(:show)))

62
src/plotattr.jl Normal file
View File

@ -0,0 +1,62 @@
const _attribute_defaults = Dict(:Series => _series_defaults,
:Subplot => _subplot_defaults,
:Plot => _plot_defaults,
:Axis => _axis_defaults)
attrtypes() = join(keys(_attribute_defaults), ", ")
attributes(attrtype::Symbol) = sort(collect(keys(_attribute_defaults[attrtype])))
function lookup_aliases(attrtype, attribute)
attribute = Symbol(attribute)
attribute = in(attribute, keys(_keyAliases)) ? _keyAliases[attribute] : attribute
in(attribute, keys(_attribute_defaults[attrtype])) && return attribute
error("There is no attribute named $attribute in $attrtype")
end
"""
plotattr([attr])
Look up the properties of a Plots attribute, or specify an attribute type. Call `plotattr()` for options.
The information is the same as that given on https://juliaplots.github.io/attributes/.
"""
function plotattr()
println("Specify an attribute type to get a list of supported attributes. Options are $(attrtypes())")
end
function plotattr(attrtype::Symbol)
in(attrtype, keys(_attribute_defaults)) || error("Viable options are $(attrtypes())")
println("Defined $attrtype attributes are:\n$(join(attributes(attrtype), ", "))")
end
function plotattr(attribute::AbstractString)
attribute = Symbol(attribute)
attribute = in(attribute, keys(_keyAliases)) ? _keyAliases[attribute] : attribute
for (k, v) in _attribute_defaults
if in(attribute, keys(v))
return plotattr(k, "$attribute")
end
end
error("There is no attribute named $attribute")
end
function plotattr(attrtype::Symbol, attribute::AbstractString)
in(attrtype, keys(_attribute_defaults)) || ArgumentError("`attrtype` must match one of $(attrtypes())")
attribute = Symbol(lookup_aliases(attrtype, attribute))
desc = get(_arg_desc, attribute, "")
first_period_idx = findfirst(desc, '.')
typedesc = desc[1:first_period_idx-1]
desc = strip(desc[first_period_idx+1:end])
als = keys(filter((_,v)->v==attribute, _keyAliases)) |> collect |> sort
als = join(map(string,als), ", ")
def = _attribute_defaults[attrtype][attribute]
# Looks up the different elements and plots them
println("$attribute ", typedesc == "" ? "" : "{$typedesc}", "\n",
als == "" ? "" : "$als\n",
"\n$desc\n",
"$(attrtype) attribute, ", def == "" ? "" : " default: $def")
end

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,12 @@
# This should cut down on boilerplate code and allow more focused dispatch on type # This should cut down on boilerplate code and allow more focused dispatch on type
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now # note: returns meta information... mainly for use with automatic labeling from DataFrames for now
typealias FuncOrFuncs{F} Union{F, Vector{F}, Matrix{F}} const FuncOrFuncs{F} = Union{F, Vector{F}, Matrix{F}}
all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image), get(d, :seriestype, :none)) all3D(d::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image, :plots_heatmap), get(d, :seriestype, :none))
# unknown
convertToAnyVector(x, d::KW) = error("No user recipe defined for $(typeof(x))")
# missing # missing
convertToAnyVector(v::Void, d::KW) = Any[nothing], nothing convertToAnyVector(v::Void, d::KW) = Any[nothing], nothing
@ -17,10 +20,10 @@ convertToAnyVector(v::Void, d::KW) = Any[nothing], nothing
convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing convertToAnyVector(n::Integer, d::KW) = Any[zeros(0) for i in 1:n], nothing
# numeric vector # numeric vector
convertToAnyVector{T<:Number}(v::AVec{T}, d::KW) = Any[v], nothing convertToAnyVector(v::AVec{T}, d::KW) where {T<:Number} = Any[v], nothing
# string vector # string vector
convertToAnyVector{T<:AbstractString}(v::AVec{T}, d::KW) = Any[v], nothing convertToAnyVector(v::AVec{T}, d::KW) where {T<:AbstractString} = Any[v], nothing
function convertToAnyVector(v::AMat, d::KW) function convertToAnyVector(v::AMat, d::KW)
if all3D(d) if all3D(d)
@ -43,7 +46,7 @@ convertToAnyVector(v::Volume, d::KW) = Any[v], nothing
# convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing # convertToAnyVector(v::AVec{OHLC}, d::KW) = Any[v], nothing
# # dates # # dates
# convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::KW) = Any[dts], nothing convertToAnyVector{D<:Union{Date,DateTime}}(dts::AVec{D}, d::KW) = Any[dts], nothing
# list of things (maybe other vectors, functions, or something else) # list of things (maybe other vectors, functions, or something else)
function convertToAnyVector(v::AVec, d::KW) function convertToAnyVector(v::AVec, d::KW)
@ -96,8 +99,8 @@ nobigs(v) = v
end end
# not allowed # not allowed
compute_xyz{F<:Function}(x::Void, y::FuncOrFuncs{F}, z) = error("If you want to plot the function `$y`, you need to define the x values!") compute_xyz(x::Void, y::FuncOrFuncs{F}, z) where {F<:Function} = error("If you want to plot the function `$y`, you need to define the x values!")
compute_xyz{F<:Function}(x::Void, y::Void, z::FuncOrFuncs{F}) = error("If you want to plot the function `$z`, you need to define x and y values!") compute_xyz(x::Void, y::Void, z::FuncOrFuncs{F}) where {F<:Function} = error("If you want to plot the function `$z`, you need to define x and y values!")
compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!") compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!")
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -106,7 +109,7 @@ compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!")
# we are going to build recipes to do the processing and splitting of the args # we are going to build recipes to do the processing and splitting of the args
# ensure we dispatch to the slicer # ensure we dispatch to the slicer
immutable SliceIt end struct SliceIt end
# the catch-all recipes # the catch-all recipes
@recipe function f(::Type{SliceIt}, x, y, z) @recipe function f(::Type{SliceIt}, x, y, z)
@ -125,18 +128,26 @@ immutable SliceIt end
z = z.data z = z.data
end end
xs, _ = convertToAnyVector(x, d) xs, _ = convertToAnyVector(x, plotattributes)
ys, _ = convertToAnyVector(y, d) ys, _ = convertToAnyVector(y, plotattributes)
zs, _ = convertToAnyVector(z, d) zs, _ = convertToAnyVector(z, plotattributes)
fr = pop!(d, :fillrange, nothing) fr = pop!(plotattributes, :fillrange, nothing)
fillranges, _ = if typeof(fr) <: Number fillranges, _ = if typeof(fr) <: Number
([fr],nothing) ([fr],nothing)
else else
convertToAnyVector(fr, d) convertToAnyVector(fr, plotattributes)
end end
mf = length(fillranges) mf = length(fillranges)
rib = pop!(plotattributes, :ribbon, nothing)
ribbons, _ = if typeof(rib) <: Number
([fr],nothing)
else
convertToAnyVector(rib, plotattributes)
end
mr = length(ribbons)
# @show zs # @show zs
mx = length(xs) mx = length(xs)
@ -145,7 +156,7 @@ immutable SliceIt end
if mx > 0 && my > 0 && mz > 0 if mx > 0 && my > 0 && mz > 0
for i in 1:max(mx, my, mz) for i in 1:max(mx, my, mz)
# add a new series # add a new series
di = copy(d) di = copy(plotattributes)
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)] xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi) di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)
@ -153,6 +164,10 @@ immutable SliceIt end
fr = fillranges[mod1(i,mf)] fr = fillranges[mod1(i,mf)]
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr
# handle ribbons
rib = ribbons[mod1(i,mr)]
di[:ribbon] = isa(rib, Function) ? map(rib, di[:x]) : rib
push!(series_list, RecipeData(di, ())) push!(series_list, RecipeData(di, ()))
end end
end end
@ -160,10 +175,10 @@ immutable SliceIt end
end end
# this is the default "type recipe"... just pass the object through # this is the default "type recipe"... just pass the object through
@recipe f{T<:Any}(::Type{T}, v::T) = v @recipe f(::Type{T}, v::T) where {T<:Any} = v
# this should catch unhandled "series recipes" and error with a nice message # this should catch unhandled "series recipes" and error with a nice message
@recipe f{V<:Val}(::Type{V}, x, y, z) = error("The backend must not support the series type $V, and there isn't a series recipe defined.") @recipe f(::Type{V}, x, y, z) where {V<:Val} = error("The backend must not support the series type $V, and there isn't a series recipe defined.")
_apply_type_recipe(d, v) = RecipesBase.apply_recipe(d, typeof(v), v)[1].args[1] _apply_type_recipe(d, v) = RecipesBase.apply_recipe(d, typeof(v), v)[1].args[1]
@ -171,6 +186,7 @@ _apply_type_recipe(d, v) = RecipesBase.apply_recipe(d, typeof(v), v)[1].args[1]
# This sort of recipe should return a pair of functions... one to convert to number, # This sort of recipe should return a pair of functions... one to convert to number,
# and one to format tick values. # and one to format tick values.
function _apply_type_recipe(d, v::AbstractArray) function _apply_type_recipe(d, v::AbstractArray)
isempty(v) && return Float64[]
args = RecipesBase.apply_recipe(d, typeof(v[1]), v[1])[1].args args = RecipesBase.apply_recipe(d, typeof(v[1]), v[1])[1].args
if length(args) == 2 && typeof(args[1]) <: Function && typeof(args[2]) <: Function if length(args) == 2 && typeof(args[1]) <: Function && typeof(args[2]) <: Function
numfunc, formatter = args numfunc, formatter = args
@ -197,16 +213,16 @@ end
# end # end
# don't do anything for ints or floats # don't do anything for ints or floats
_apply_type_recipe{T<:Union{Integer,AbstractFloat}}(d, v::AbstractArray{T}) = v _apply_type_recipe(d, v::AbstractArray{T}) where {T<:Union{Integer,AbstractFloat}} = v
# handle "type recipes" by converting inputs, and then either re-calling or slicing # handle "type recipes" by converting inputs, and then either re-calling or slicing
@recipe function f(x, y, z) @recipe function f(x, y, z)
did_replace = false did_replace = false
newx = _apply_type_recipe(d, x) newx = _apply_type_recipe(plotattributes, x)
x === newx || (did_replace = true) x === newx || (did_replace = true)
newy = _apply_type_recipe(d, y) newy = _apply_type_recipe(plotattributes, y)
y === newy || (did_replace = true) y === newy || (did_replace = true)
newz = _apply_type_recipe(d, z) newz = _apply_type_recipe(plotattributes, z)
z === newz || (did_replace = true) z === newz || (did_replace = true)
if did_replace if did_replace
newx, newy, newz newx, newy, newz
@ -216,9 +232,9 @@ _apply_type_recipe{T<:Union{Integer,AbstractFloat}}(d, v::AbstractArray{T}) = v
end end
@recipe function f(x, y) @recipe function f(x, y)
did_replace = false did_replace = false
newx = _apply_type_recipe(d, x) newx = _apply_type_recipe(plotattributes, x)
x === newx || (did_replace = true) x === newx || (did_replace = true)
newy = _apply_type_recipe(d, y) newy = _apply_type_recipe(plotattributes, y)
y === newy || (did_replace = true) y === newy || (did_replace = true)
if did_replace if did_replace
newx, newy newx, newy
@ -227,7 +243,7 @@ end
end end
end end
@recipe function f(y) @recipe function f(y)
newy = _apply_type_recipe(d, y) newy = _apply_type_recipe(plotattributes, y)
if y !== newy if y !== newy
newy newy
else else
@ -240,7 +256,7 @@ end
@recipe function f(v1, v2, v3, v4, vrest...) @recipe function f(v1, v2, v3, v4, vrest...)
did_replace = false did_replace = false
newargs = map(v -> begin newargs = map(v -> begin
newv = _apply_type_recipe(d, v) newv = _apply_type_recipe(plotattributes, v)
if newv !== v if newv !== v
did_replace = true did_replace = true
end end
@ -267,13 +283,13 @@ function wrap_surfaces(d::KW)
end end
end end
@recipe f(n::Integer) = is3d(get(d,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing) @recipe f(n::Integer) = is3d(get(plotattributes,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing)
# return a surface if this is a 3d plot, otherwise let it be sliced up # return a surface if this is a 3d plot, otherwise let it be sliced up
@recipe function f{T<:Union{Integer,AbstractFloat}}(mat::AMat{T}) @recipe function f(mat::AMat{T}) where T<:Union{Integer,AbstractFloat}
if all3D(d) if all3D(plotattributes)
n,m = size(mat) n,m = size(mat)
wrap_surfaces(d) wrap_surfaces(plotattributes)
SliceIt, 1:m, 1:n, Surface(mat) SliceIt, 1:m, 1:n, Surface(mat)
else else
SliceIt, nothing, mat, nothing SliceIt, nothing, mat, nothing
@ -281,11 +297,11 @@ end
end end
# if a matrix is wrapped by Formatted, do similar logic, but wrap data with Surface # if a matrix is wrapped by Formatted, do similar logic, but wrap data with Surface
@recipe function f{T<:AbstractMatrix}(fmt::Formatted{T}) @recipe function f(fmt::Formatted{T}) where T<:AbstractMatrix
if all3D(d) if all3D(plotattributes)
mat = fmt.data mat = fmt.data
n,m = size(mat) n,m = size(mat)
wrap_surfaces(d) wrap_surfaces(plotattributes)
SliceIt, 1:m, 1:n, Formatted(Surface(mat), fmt.formatter) SliceIt, 1:m, 1:n, Formatted(Surface(mat), fmt.formatter)
else else
SliceIt, nothing, fmt, nothing SliceIt, nothing, fmt, nothing
@ -293,7 +309,7 @@ end
end end
# assume this is a Volume, so construct one # assume this is a Volume, so construct one
@recipe function f{T<:Number}(vol::AbstractArray{T,3}, args...) @recipe function f(vol::AbstractArray{T,3}, args...) where T<:Number
seriestype := :volume seriestype := :volume
SliceIt, nothing, Volume(vol, args...), nothing SliceIt, nothing, Volume(vol, args...), nothing
end end
@ -301,14 +317,16 @@ end
# # images - grays # # images - grays
@recipe function f{T<:Gray}(mat::AMat{T}) @recipe function f(mat::AMat{T}) where T<:Gray
n, m = size(mat)
if is_seriestype_supported(:image) if is_seriestype_supported(:image)
seriestype := :image seriestype := :image
n, m = size(mat) yflip --> true
SliceIt, 1:m, 1:n, Surface(mat) SliceIt, 1:m, 1:n, Surface(mat)
else else
seriestype := :heatmap seriestype := :heatmap
yflip --> true yflip --> true
cbar --> false
fillcolor --> ColorGradient([:black, :white]) fillcolor --> ColorGradient([:black, :white])
SliceIt, 1:m, 1:n, Surface(convert(Matrix{Float64}, mat)) SliceIt, 1:m, 1:n, Surface(convert(Matrix{Float64}, mat))
end end
@ -316,15 +334,18 @@ end
# # images - colors # # images - colors
@recipe function f{T<:Colorant}(mat::AMat{T}) @recipe function f(mat::AMat{T}) where T<:Colorant
n, m = size(mat)
if is_seriestype_supported(:image) if is_seriestype_supported(:image)
seriestype := :image seriestype := :image
n, m = size(mat) yflip --> true
SliceIt, 1:m, 1:n, Surface(mat) SliceIt, 1:m, 1:n, Surface(mat)
else else
seriestype := :heatmap seriestype := :heatmap
yflip --> true yflip --> true
z, d[:fillcolor] = replace_image_with_heatmap(mat) cbar --> false
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
SliceIt, 1:m, 1:n, Surface(z) SliceIt, 1:m, 1:n, Surface(z)
end end
end end
@ -353,16 +374,36 @@ end
# function without range... use the current range of the x-axis # function without range... use the current range of the x-axis
@recipe function f{F<:Function}(f::FuncOrFuncs{F}) @recipe function f(f::FuncOrFuncs{F}) where F<:Function
plt = d[:plot_object] plt = plotattributes[:plot_object]
xmin, xmax = try xmin, xmax = try
axis_limits(plt[1][:xaxis]) axis_limits(plt[1][:xaxis])
catch catch
-5, 5 xm = tryrange(f, [-5,-1,0,0.01])
xm, tryrange(f, filter(x->x>xm, [5,1,0.99, 0, -0.01]))
end end
f, xmin, xmax f, xmin, xmax
end end
# try some intervals over which the function may be defined
function tryrange(F::AbstractArray, vec)
rets = [tryrange(f, vec) for f in F] # get the preferred for each
maxind = maximum(indexin(rets, vec)) # get the last attempt that succeeded (most likely to fit all)
rets .= [tryrange(f, vec[maxind:maxind]) for f in F] # ensure that all functions compute there
rets[1]
end
function tryrange(F, vec)
for v in vec
try
tmp = F(v)
return v
catch
end
end
error("$F is not a Function, or is not defined at any of the values $vec")
end
# #
# # -------------------------------------------------------------------- # # --------------------------------------------------------------------
# # 2 arguments # # 2 arguments
@ -372,7 +413,7 @@ end
# # if functions come first, just swap the order (not to be confused with parametric functions... # # if functions come first, just swap the order (not to be confused with parametric functions...
# # as there would be more than one function passed in) # # as there would be more than one function passed in)
@recipe function f{F<:Function}(f::FuncOrFuncs{F}, x) @recipe function f(f::FuncOrFuncs{F}, x) where F<:Function
F2 = typeof(x) F2 = typeof(x)
@assert !(F2 <: Function || (F2 <: AbstractArray && F2.parameters[1] <: Function)) # otherwise we'd hit infinite recursion here @assert !(F2 <: Function || (F2 <: AbstractArray && F2.parameters[1] <: Function)) # otherwise we'd hit infinite recursion here
x, f x, f
@ -403,7 +444,7 @@ end
# seriestype := :path3d # seriestype := :path3d
# end # end
# end # end
wrap_surfaces(d) wrap_surfaces(plotattributes)
SliceIt, x, y, z SliceIt, x, y, z
end end
@ -413,7 +454,7 @@ end
@recipe function f(x::AVec, y::AVec, zf::Function) @recipe function f(x::AVec, y::AVec, zf::Function)
# x = X <: Number ? sort(x) : x # x = X <: Number ? sort(x) : x
# y = Y <: Number ? sort(y) : y # y = Y <: Number ? sort(y) : y
wrap_surfaces(d) wrap_surfaces(plotattributes)
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
end end
@ -421,13 +462,45 @@ end
# # surface-like... matrix grid # # surface-like... matrix grid
@recipe function f(x::AVec, y::AVec, z::AMat) @recipe function f(x::AVec, y::AVec, z::AMat)
if !like_surface(get(d, :seriestype, :none)) if !like_surface(get(plotattributes, :seriestype, :none))
d[:seriestype] = :contour plotattributes[:seriestype] = :contour
end end
wrap_surfaces(d) wrap_surfaces(plotattributes)
SliceIt, x, y, Surface(z) SliceIt, x, y, Surface(z)
end end
# # images - grays
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Gray
if is_seriestype_supported(:image)
seriestype := :image
yflip --> true
SliceIt, x, y, Surface(mat)
else
seriestype := :heatmap
yflip --> true
cbar --> false
fillcolor --> ColorGradient([:black, :white])
SliceIt, x, y, Surface(convert(Matrix{Float64}, mat))
end
end
# # images - colors
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Colorant
if is_seriestype_supported(:image)
seriestype := :image
yflip --> true
SliceIt, x, y, Surface(mat)
else
seriestype := :heatmap
yflip --> true
cbar --> false
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
SliceIt, x, y, Surface(z)
end
end
# #
# #
# # -------------------------------------------------------------------- # # --------------------------------------------------------------------
@ -440,19 +513,19 @@ end
xs = adapted_grid(f, (xmin, xmax)) xs = adapted_grid(f, (xmin, xmax))
xs, f xs, f
end end
@recipe function f{F<:Function}(fs::AbstractArray{F}, xmin::Number, xmax::Number) @recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function
xs = Any[adapted_grid(f, (xmin, xmax)) for f in fs] xs = Any[adapted_grid(f, (xmin, xmax)) for f in fs]
xs, fs xs, fs
end end
@recipe f{F<:Function,G<:Function}(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, u::AVec) = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u) @recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, u::AVec) where {F<:Function,G<:Function} = mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u)
@recipe f{F<:Function,G<:Function}(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) = fx, fy, linspace(umin, umax, n) @recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) where {F<:Function,G<:Function} = fx, fy, linspace(umin, umax, n)
# #
# # special handling... 3D parametric function(s) # # special handling... 3D parametric function(s)
@recipe function f{F<:Function,G<:Function,H<:Function}(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, u::AVec) @recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, u::AVec) where {F<:Function,G<:Function,H<:Function}
mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u) mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)
end end
@recipe function f{F<:Function,G<:Function,H<:Function}(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, umin::Number, umax::Number, numPoints = 200) @recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, umin::Number, umax::Number, numPoints = 200) where {F<:Function,G<:Function,H<:Function}
fx, fy, fz, linspace(umin, umax, numPoints) fx, fy, fz, linspace(umin, umax, numPoints)
end end
@ -467,28 +540,28 @@ end
# #
# # (x,y) tuples # # (x,y) tuples
@recipe f{R1<:Number,R2<:Number}(xy::AVec{Tuple{R1,R2}}) = unzip(xy) @recipe f(xy::AVec{Tuple{R1,R2}}) where {R1<:Number,R2<:Number} = unzip(xy)
@recipe f{R1<:Number,R2<:Number}(xy::Tuple{R1,R2}) = [xy[1]], [xy[2]] @recipe f(xy::Tuple{R1,R2}) where {R1<:Number,R2<:Number} = [xy[1]], [xy[2]]
# #
# # (x,y,z) tuples # # (x,y,z) tuples
@recipe f{R1<:Number,R2<:Number,R3<:Number}(xyz::AVec{Tuple{R1,R2,R3}}) = unzip(xyz) @recipe f(xyz::AVec{Tuple{R1,R2,R3}}) where {R1<:Number,R2<:Number,R3<:Number} = unzip(xyz)
@recipe f{R1<:Number,R2<:Number,R3<:Number}(xyz::Tuple{R1,R2,R3}) = [xyz[1]], [xyz[2]], [xyz[3]] @recipe f(xyz::Tuple{R1,R2,R3}) where {R1<:Number,R2<:Number,R3<:Number} = [xyz[1]], [xyz[2]], [xyz[3]]
# these might be points+velocity, or OHLC or something else # these might be points+velocity, or OHLC or something else
@recipe f{R1<:Number,R2<:Number,R3<:Number,R4<:Number}(xyuv::AVec{Tuple{R1,R2,R3,R4}}) = get(d,:seriestype,:path)==:ohlc ? OHLC[OHLC(t...) for t in xyuv] : unzip(xyuv) @recipe f(xyuv::AVec{Tuple{R1,R2,R3,R4}}) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = get(plotattributes,:seriestype,:path)==:ohlc ? OHLC[OHLC(t...) for t in xyuv] : unzip(xyuv)
@recipe f{R1<:Number,R2<:Number,R3<:Number,R4<:Number}(xyuv::Tuple{R1,R2,R3,R4}) = [xyuv[1]], [xyuv[2]], [xyuv[3]], [xyuv[4]] @recipe f(xyuv::Tuple{R1,R2,R3,R4}) where {R1<:Number,R2<:Number,R3<:Number,R4<:Number} = [xyuv[1]], [xyuv[2]], [xyuv[3]], [xyuv[4]]
# #
# # 2D FixedSizeArrays # # 2D FixedSizeArrays
@recipe f{T<:Number}(xy::AVec{FixedSizeArrays.Vec{2,T}}) = unzip(xy) @recipe f(xy::AVec{FixedSizeArrays.Vec{2,T}}) where {T<:Number} = unzip(xy)
@recipe f{T<:Number}(xy::FixedSizeArrays.Vec{2,T}) = [xy[1]], [xy[2]] @recipe f(xy::FixedSizeArrays.Vec{2,T}) where {T<:Number} = [xy[1]], [xy[2]]
# #
# # 3D FixedSizeArrays # # 3D FixedSizeArrays
@recipe f{T<:Number}(xyz::AVec{FixedSizeArrays.Vec{3,T}}) = unzip(xyz) @recipe f(xyz::AVec{FixedSizeArrays.Vec{3,T}}) where {T<:Number} = unzip(xyz)
@recipe f{T<:Number}(xyz::FixedSizeArrays.Vec{3,T}) = [xyz[1]], [xyz[2]], [xyz[3]] @recipe f(xyz::FixedSizeArrays.Vec{3,T}) where {T<:Number} = [xyz[1]], [xyz[2]], [xyz[3]]
# #
# # -------------------------------------------------------------------- # # --------------------------------------------------------------------
@ -508,13 +581,69 @@ end
# nothing # nothing
# end # end
splittable_kw(key, val, lengthGroup) = false
splittable_kw(key, val::AbstractArray, lengthGroup) = (key != :group) && size(val,1) == lengthGroup
splittable_kw(key, val::Tuple, lengthGroup) = all(splittable_kw.(key, val, lengthGroup))
splittable_kw(key, val::SeriesAnnotations, lengthGroup) = splittable_kw(key, val.strs, lengthGroup)
split_kw(key, val::AbstractArray, indices) = val[indices, fill(Colon(), ndims(val)-1)...]
split_kw(key, val::Tuple, indices) = Tuple(split_kw(key, v, indices) for v in val)
function split_kw(key, val::SeriesAnnotations, indices)
split_strs = split_kw(key, val.strs, indices)
return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor)
end
function groupedvec2mat(x_ind, x, y::AbstractArray, groupby, def_val = y[1])
y_mat = Array{promote_type(eltype(y), typeof(def_val))}(length(keys(x_ind)), length(groupby.groupLabels))
fill!(y_mat, def_val)
for i in 1:length(groupby.groupLabels)
xi = x[groupby.groupIds[i]]
yi = y[groupby.groupIds[i]]
y_mat[getindex.(x_ind, xi), i] = yi
end
return y_mat
end
groupedvec2mat(x_ind, x, y::Tuple, groupby) = Tuple(groupedvec2mat(x_ind, x, v, groupby) for v in y)
group_as_matrix(t) = false
# split the group into 1 series per group, and set the label and idxfilter for each # split the group into 1 series per group, and set the label and idxfilter for each
@recipe function f(groupby::GroupBy, args...) @recipe function f(groupby::GroupBy, args...)
for (i,glab) in enumerate(groupby.groupLabels) lengthGroup = maximum(union(groupby.groupIds...))
@series begin if !(group_as_matrix(args[1]))
label --> string(glab) for (i,glab) in enumerate(groupby.groupLabels)
idxfilter --> groupby.groupIds[i] @series begin
args label --> string(glab)
idxfilter --> groupby.groupIds[i]
for (key,val) in plotattributes
if splittable_kw(key, val, lengthGroup)
:($key) := split_kw(key, val, groupby.groupIds[i])
end
end
args
end
end end
else
g = args[1]
if length(g.args) == 1
x = zeros(Int, lengthGroup)
for indexes in groupby.groupIds
x[indexes] = 1:length(indexes)
end
last_args = g.args
else
x = g.args[1]
last_args = g.args[2:end]
end
x_u = unique(x)
x_ind = Dict(zip(x_u, 1:length(x_u)))
for (key,val) in plotattributes
if splittable_kw(key, val, lengthGroup)
:($key) := groupedvec2mat(x_ind, x, val, groupby)
end
end
label --> reshape(groupby.groupLabels, 1, :)
typeof(g)((x_u, (groupedvec2mat(x_ind, x, arg, groupby, NaN) for arg in last_args)...))
end end
end end

View File

@ -1,6 +1,6 @@
function Subplot{T<:AbstractBackend}(::T; parent = RootLayout()) function Subplot(::T; parent = RootLayout()) where T<:AbstractBackend
Subplot{T}( Subplot{T}(
parent, parent,
Series[], Series[],
@ -13,6 +13,11 @@ function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
) )
end end
"""
plotarea(subplot)
Return the bounding box of a subplot
"""
plotarea(sp::Subplot) = sp.plotarea plotarea(sp::Subplot) = sp.plotarea
plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox) plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox)
@ -32,14 +37,14 @@ get_subplot(plt::Plot, k) = plt.spmap[k]
get_subplot(series::Series) = series.d[:subplot] get_subplot(series::Series) = series.d[:subplot]
get_subplot_index(plt::Plot, idx::Integer) = Int(idx) get_subplot_index(plt::Plot, idx::Integer) = Int(idx)
get_subplot_index(plt::Plot, sp::Subplot) = findfirst(_ -> _ === sp, plt.subplots) get_subplot_index(plt::Plot, sp::Subplot) = findfirst(x -> x === sp, plt.subplots)
series_list(sp::Subplot) = sp.series_list # filter(series -> series.d[:subplot] === sp, sp.plt.series_list) series_list(sp::Subplot) = sp.series_list # filter(series -> series.d[:subplot] === sp, sp.plt.series_list)
function should_add_to_legend(series::Series) function should_add_to_legend(series::Series)
series.d[:primary] && series.d[:label] != "" && series.d[:primary] && series.d[:label] != "" &&
!(series.d[:seriestype] in ( !(series.d[:seriestype] in (
:hexbin,:histogram2d,:hline,:vline, :hexbin,:bins2d,:histogram2d,:hline,:vline,
:contour,:contourf,:contour3d,:surface,:wireframe, :contour,:contourf,:contour3d,:surface,:wireframe,
:heatmap, :pie, :image :heatmap, :pie, :image
)) ))

View File

@ -1,40 +1,168 @@
"""
theme(s::Symbol)
Specify the colour theme for plots.
"""
function theme(s::Symbol; kw...) function theme(s::Symbol; kw...)
# reset? defaults = _get_defaults(s)
if s == :none || s == :default _theme(s, defaults; kw...)
PlotUtils._default_gradient[] = :inferno end
default(;
bg = :white, function _get_defaults(s::Symbol)
bglegend = :match, thm = PlotThemes._themes[s]
bginside = :match, if :defaults in fieldnames(thm)
bgoutside = :match, return thm.defaults
fg = :auto, else # old PlotTheme type
fglegend = :match, defaults = KW(
fggrid = :match, :bg => thm.bg_secondary,
fgaxis = :match, :bginside => thm.bg_primary,
fgtext = :match, :fg => thm.lines,
fgborder = :match, :fgtext => thm.text,
fgguide = :match, :fgguide => thm.text,
palette = :auto :fglegend => thm.text,
:palette => thm.palette,
) )
return if thm.gradient != nothing
push!(defaults, :gradient => thm.gradient)
end
return defaults
end
end
function _theme(s::Symbol, defaults::KW; kw...)
# Reset to defaults to overwrite active theme
reset_defaults()
# Set the theme's gradient as default
if haskey(defaults, :gradient)
PlotUtils.clibrary(:misc)
PlotUtils.default_cgrad(default = :sequential, sequential = PlotThemes.gradient_name(s))
else
PlotUtils.clibrary(:Plots)
PlotUtils.default_cgrad(default = :sequential, sequential = :inferno)
end end
# update the default gradient and other defaults # maybe overwrite the theme's gradient
thm = PlotThemes._themes[s] kw = KW(kw)
if thm.gradient != nothing if haskey(kw, :gradient)
PlotUtils._default_gradient[] = PlotThemes.gradient_name(s) kwgrad = pop!(kw, :gradient)
for clib in clibraries()
if kwgrad in cgradients(clib)
PlotUtils.clibrary(clib)
PlotUtils.default_cgrad(default = :sequential, sequential = kwgrad)
break
end
end
end end
default(;
bg = thm.bg_secondary, # Set the theme's defaults
bginside = thm.bg_primary, default(; defaults..., kw...)
fg = thm.lines, return
fgtext = thm.text,
fgguide = thm.text,
fglegend = thm.text,
palette = thm.palette,
kw...
)
end end
@deprecate set_theme(s) theme(s) @deprecate set_theme(s) theme(s)
@userplot ShowTheme
_color_functions = KW(
:protanopic => protanopic,
:deuteranopic => deuteranopic,
:tritanopic => tritanopic,
)
_get_showtheme_args(thm::Symbol) = thm, identity
_get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func, identity)
@recipe function showtheme(st::ShowTheme)
thm, cfunc = _get_showtheme_args(st.args...)
defaults = _get_defaults(thm)
# get the gradient
gradient_colors = get(defaults, :gradient, cgrad(:inferno).colors)
gradient = cgrad(cfunc.(RGB.(gradient_colors)))
# get the palette
palette = get(defaults, :palette, get_color_palette(:auto, plot_color(:white), 17))
palette = cfunc.(RGB.(palette))
# apply the theme
for k in keys(defaults)
k in (:gradient, :palette) && continue
def = defaults[k]
arg = get(_keyAliases, k, k)
plotattributes[arg] = if typeof(def) <: Colorant
cfunc(RGB(def))
elseif eltype(def) <: Colorant
cfunc.(RGB.(def))
elseif contains(string(arg), "color")
cfunc.(RGB.(plot_color.(def)))
else
def
end
end
srand(1)
label := ""
colorbar := false
layout := (2, 3)
for j in 1:4
@series begin
subplot := 1
palette := palette
seriestype := :path
cumsum(randn(50))
end
@series begin
subplot := 2
seriestype := :scatter
palette := palette
marker := (:circle, :diamond, :star5, :square)[j]
randn(10), randn(10)
end
end
@series begin
subplot := 3
seriestype := :histogram
palette := palette
randn(1000) .+ (0:2:4)'
end
f(r) = sin(r) / r
_norm(x, y) = norm([x, y])
x = y = linspace(-3π, 3π, 30)
z = f.(_norm.(x, y'))
wi = 2:3:30
@series begin
subplot := 4
seriestype := :heatmap
seriescolor := gradient
ticks := -5:5:5
x, y, z
end
@series begin
subplot := 5
seriestype := :surface
seriescolor := gradient
x, y, z
end
n = 100
ts = linspace(0, 10π, n)
x = ts .* cos.(ts)
y = (0.1ts) .* sin.(ts)
z = 1:n
@series begin
subplot := 6
seriescolor := gradient
linewidth := 3
line_z := z
x, y, z
end
end

View File

@ -2,28 +2,24 @@
# TODO: I declare lots of types here because of the lacking ability to do forward declarations in current Julia # TODO: I declare lots of types here because of the lacking ability to do forward declarations in current Julia
# I should move these to the relevant files when something like "extern" is implemented # I should move these to the relevant files when something like "extern" is implemented
typealias AVec AbstractVector const AVec = AbstractVector
typealias AMat AbstractMatrix const AMat = AbstractMatrix
typealias KW Dict{Symbol,Any} const KW = Dict{Symbol,Any}
immutable PlotsDisplay <: Display end struct PlotsDisplay <: Display end
abstract AbstractBackend
abstract AbstractPlot{T<:AbstractBackend}
abstract AbstractLayout
# ----------------------------------------------------------- # -----------------------------------------------------------
immutable InputWrapper{T} struct InputWrapper{T}
obj::T obj::T
end end
wrap{T}(obj::T) = InputWrapper{T}(obj) wrap(obj::T) where {T} = InputWrapper{T}(obj)
Base.isempty(wrapper::InputWrapper) = false Base.isempty(wrapper::InputWrapper) = false
# ----------------------------------------------------------- # -----------------------------------------------------------
type Series mutable struct Series
d::KW d::KW
end end
@ -33,7 +29,7 @@ attr!(series::Series, v, k::Symbol) = (series.d[k] = v)
# ----------------------------------------------------------- # -----------------------------------------------------------
# a single subplot # a single subplot
type Subplot{T<:AbstractBackend} <: AbstractLayout mutable struct Subplot{T<:AbstractBackend} <: AbstractLayout
parent::AbstractLayout parent::AbstractLayout
series_list::Vector{Series} # arguments for each series series_list::Vector{Series} # arguments for each series
minpad::Tuple # leftpad, toppad, rightpad, bottompad minpad::Tuple # leftpad, toppad, rightpad, bottompad
@ -49,12 +45,12 @@ Base.show(io::IO, sp::Subplot) = print(io, "Subplot{$(sp[:subplot_index])}")
# ----------------------------------------------------------- # -----------------------------------------------------------
# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place # simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place
type Axis mutable struct Axis
sps::Vector{Subplot} sps::Vector{Subplot}
d::KW d::KW
end end
type Extrema mutable struct Extrema
emin::Float64 emin::Float64
emax::Float64 emax::Float64
end end
@ -62,12 +58,12 @@ Extrema() = Extrema(Inf, -Inf)
# ----------------------------------------------------------- # -----------------------------------------------------------
typealias SubplotMap Dict{Any, Subplot} const SubplotMap = Dict{Any, Subplot}
# ----------------------------------------------------------- # -----------------------------------------------------------
type Plot{T<:AbstractBackend} <: AbstractPlot{T} mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
backend::T # the backend type backend::T # the backend type
n::Int # number of series n::Int # number of series
attr::KW # arguments for the whole plot attr::KW # arguments for the whole plot

View File

@ -3,7 +3,7 @@ calcMidpoints(edges::AbstractVector) = Float64[0.5 * (edges[i] + edges[i+1]) for
"Make histogram-like bins of data" "Make histogram-like bins of data"
function binData(data, nbins) function binData(data, nbins)
lo, hi = extrema(data) lo, hi = ignorenan_extrema(data)
edges = collect(linspace(lo, hi, nbins+1)) edges = collect(linspace(lo, hi, nbins+1))
midpoints = calcMidpoints(edges) midpoints = calcMidpoints(edges)
buckets = Int[max(2, min(searchsortedfirst(edges, x), length(edges)))-1 for x in data] buckets = Int[max(2, min(searchsortedfirst(edges, x), length(edges)))-1 for x in data]
@ -109,12 +109,12 @@ function regressionXY(x, y)
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y) β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
# make a line segment # make a line segment
regx = [minimum(x), maximum(x)] regx = [ignorenan_minimum(x), ignorenan_maximum(x)]
regy = β * regx + α regy = β * regx + α
regx, regy regx, regy
end end
function replace_image_with_heatmap{T<:Colorant}(z::Array{T}) function replace_image_with_heatmap(z::Array{T}) where T<:Colorant
@show T, size(z) @show T, size(z)
n, m = size(z) n, m = size(z)
# idx = 0 # idx = 0
@ -137,15 +137,15 @@ function imageHack(d::KW)
end end
# --------------------------------------------------------------- # ---------------------------------------------------------------
"Build line segments for plotting"
type Segments{T} mutable struct Segments{T}
pts::Vector{T} pts::Vector{T}
end end
# Segments() = Segments{Float64}(zeros(0)) # Segments() = Segments{Float64}(zeros(0))
Segments() = Segments(Float64) Segments() = Segments(Float64)
Segments{T}(::Type{T}) = Segments(T[]) Segments(::Type{T}) where {T} = Segments(T[])
Segments(p::Int) = Segments(NTuple{2,Float64}[]) Segments(p::Int) = Segments(NTuple{2,Float64}[])
@ -157,7 +157,7 @@ to_nan(::Type{NTuple{2,Float64}}) = (NaN, NaN)
coords(segs::Segments{Float64}) = segs.pts coords(segs::Segments{Float64}) = segs.pts
coords(segs::Segments{NTuple{2,Float64}}) = Float64[p[1] for p in segs.pts], Float64[p[2] for p in segs.pts] coords(segs::Segments{NTuple{2,Float64}}) = Float64[p[1] for p in segs.pts], Float64[p[2] for p in segs.pts]
function Base.push!{T}(segments::Segments{T}, vs...) function Base.push!(segments::Segments{T}, vs...) where T
if !isempty(segments.pts) if !isempty(segments.pts)
push!(segments.pts, to_nan(T)) push!(segments.pts, to_nan(T))
end end
@ -167,7 +167,7 @@ function Base.push!{T}(segments::Segments{T}, vs...)
segments segments
end end
function Base.push!{T}(segments::Segments{T}, vs::AVec) function Base.push!(segments::Segments{T}, vs::AVec) where T
if !isempty(segments.pts) if !isempty(segments.pts)
push!(segments.pts, to_nan(T)) push!(segments.pts, to_nan(T))
end end
@ -181,24 +181,43 @@ end
# ----------------------------------------------------- # -----------------------------------------------------
# helper to manage NaN-separated segments # helper to manage NaN-separated segments
type SegmentsIterator mutable struct SegmentsIterator
args::Tuple args::Tuple
n::Int n::Int
end end
function iter_segments(args...) function iter_segments(args...)
tup = Plots.wraptuple(args) tup = Plots.wraptuple(args)
n = maximum(map(length, tup)) n = maximum(map(length, tup))
SegmentsIterator(tup, n) SegmentsIterator(tup, n)
end end
function iter_segments(series::Series)
x, y, z = series[:x], series[:y], series[:z]
if has_attribute_segments(series)
if series[:seriestype] in (:scatter, :scatter3d)
return [[i] for i in 1:length(y)]
else
return [i:(i + 1) for i in 1:(length(y) - 1)]
end
else
segs = UnitRange{Int}[]
args = is3d(series) ? (x, y, z) : (x, y)
for seg in iter_segments(args...)
push!(segs, seg)
end
return segs
end
end
# helpers to figure out if there are NaN values in a list of array types # helpers to figure out if there are NaN values in a list of array types
anynan(i::Int, args::Tuple) = any(a -> !isfinite(cycle(a,i)), args) anynan(i::Int, args::Tuple) = any(a -> try isnan(_cycle(a,i)) catch MethodError false end, args)
anynan(istart::Int, iend::Int, args::Tuple) = any(i -> anynan(i, args), istart:iend) anynan(istart::Int, iend::Int, args::Tuple) = any(i -> anynan(i, args), istart:iend)
allnan(istart::Int, iend::Int, args::Tuple) = all(i -> anynan(i, args), istart:iend) allnan(istart::Int, iend::Int, args::Tuple) = all(i -> anynan(i, args), istart:iend)
function Base.start(itr::SegmentsIterator) function Base.start(itr::SegmentsIterator)
nextidx = 1 nextidx = 1
if anynan(1, itr.args) if !any(isempty,itr.args) && anynan(1, itr.args)
_, nextidx = next(itr, 1) _, nextidx = next(itr, 1)
end end
nextidx nextidx
@ -231,8 +250,8 @@ end
# Find minimal type that can contain NaN and x # Find minimal type that can contain NaN and x
# To allow use of NaN separated segments with categorical x axis # To allow use of NaN separated segments with categorical x axis
float_extended_type{T}(x::AbstractArray{T}) = Union{T,Float64} float_extended_type(x::AbstractArray{T}) where {T} = Union{T,Float64}
float_extended_type{T<:Real}(x::AbstractArray{T}) = Float64 float_extended_type(x::AbstractArray{T}) where {T<:Real} = Float64
# ------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------
@ -243,56 +262,56 @@ notimpl() = error("This has not been implemented yet")
isnothing(x::Void) = true isnothing(x::Void) = true
isnothing(x) = false isnothing(x) = false
cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj _cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj
cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj _cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj
cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))] _cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))]
cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))] _cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]
cycle(v, idx::Int) = v _cycle(v, idx::Int) = v
cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices) _cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v,i), indices)
cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices) _cycle(v::AMat, indices::AVec{Int}) = map(i -> _cycle(v,i), indices)
cycle(v, indices::AVec{Int}) = fill(v, length(indices)) _cycle(v, indices::AVec{Int}) = fill(v, length(indices))
cycle(grad::ColorGradient, idx::Int) = cycle(grad.colors, idx) _cycle(grad::ColorGradient, idx::Int) = _cycle(grad.colors, idx)
cycle(grad::ColorGradient, indices::AVec{Int}) = cycle(grad.colors, indices) _cycle(grad::ColorGradient, indices::AVec{Int}) = _cycle(grad.colors, indices)
makevec(v::AVec) = v makevec(v::AVec) = v
makevec{T}(v::T) = T[v] makevec(v::T) where {T} = T[v]
"duplicate a single value, or pass the 2-tuple through" "duplicate a single value, or pass the 2-tuple through"
maketuple(x::Real) = (x,x) maketuple(x::Real) = (x,x)
maketuple{T,S}(x::Tuple{T,S}) = x maketuple(x::Tuple{T,S}) where {T,S} = x
mapFuncOrFuncs(f::Function, u::AVec) = map(f, u) mapFuncOrFuncs(f::Function, u::AVec) = map(f, u)
mapFuncOrFuncs{F<:Function}(fs::AVec{F}, u::AVec) = [map(f, u) for f in fs] mapFuncOrFuncs(fs::AVec{F}, u::AVec) where {F<:Function} = [map(f, u) for f in fs]
unzip{X,Y}(xy::AVec{Tuple{X,Y}}) = [t[1] for t in xy], [t[2] for t in xy] unzip(xy::AVec{Tuple{X,Y}}) where {X,Y} = [t[1] for t in xy], [t[2] for t in xy]
unzip{X,Y,Z}(xyz::AVec{Tuple{X,Y,Z}}) = [t[1] for t in xyz], [t[2] for t in xyz], [t[3] for t in xyz] unzip(xyz::AVec{Tuple{X,Y,Z}}) where {X,Y,Z} = [t[1] for t in xyz], [t[2] for t in xyz], [t[3] for t in xyz]
unzip{X,Y,U,V}(xyuv::AVec{Tuple{X,Y,U,V}}) = [t[1] for t in xyuv], [t[2] for t in xyuv], [t[3] for t in xyuv], [t[4] for t in xyuv] unzip(xyuv::AVec{Tuple{X,Y,U,V}}) where {X,Y,U,V} = [t[1] for t in xyuv], [t[2] for t in xyuv], [t[3] for t in xyuv], [t[4] for t in xyuv]
unzip{T}(xy::AVec{FixedSizeArrays.Vec{2,T}}) = T[t[1] for t in xy], T[t[2] for t in xy] unzip(xy::AVec{FixedSizeArrays.Vec{2,T}}) where {T} = T[t[1] for t in xy], T[t[2] for t in xy]
unzip{T}(xy::FixedSizeArrays.Vec{2,T}) = T[xy[1]], T[xy[2]] unzip(xy::FixedSizeArrays.Vec{2,T}) where {T} = T[xy[1]], T[xy[2]]
unzip{T}(xyz::AVec{FixedSizeArrays.Vec{3,T}}) = T[t[1] for t in xyz], T[t[2] for t in xyz], T[t[3] for t in xyz] unzip(xyz::AVec{FixedSizeArrays.Vec{3,T}}) where {T} = T[t[1] for t in xyz], T[t[2] for t in xyz], T[t[3] for t in xyz]
unzip{T}(xyz::FixedSizeArrays.Vec{3,T}) = T[xyz[1]], T[xyz[2]], T[xyz[3]] unzip(xyz::FixedSizeArrays.Vec{3,T}) where {T} = T[xyz[1]], T[xyz[2]], T[xyz[3]]
unzip{T}(xyuv::AVec{FixedSizeArrays.Vec{4,T}}) = T[t[1] for t in xyuv], T[t[2] for t in xyuv], T[t[3] for t in xyuv], T[t[4] for t in xyuv] unzip(xyuv::AVec{FixedSizeArrays.Vec{4,T}}) where {T} = T[t[1] for t in xyuv], T[t[2] for t in xyuv], T[t[3] for t in xyuv], T[t[4] for t in xyuv]
unzip{T}(xyuv::FixedSizeArrays.Vec{4,T}) = T[xyuv[1]], T[xyuv[2]], T[xyuv[3]], T[xyuv[4]] unzip(xyuv::FixedSizeArrays.Vec{4,T}) where {T} = T[xyuv[1]], T[xyuv[2]], T[xyuv[3]], T[xyuv[4]]
# given 2-element lims and a vector of data x, widen lims to account for the extrema of x # given 2-element lims and a vector of data x, widen lims to account for the extrema of x
function _expand_limits(lims, x) function _expand_limits(lims, x)
try try
e1, e2 = extrema(x) e1, e2 = ignorenan_extrema(x)
lims[1] = min(lims[1], e1) lims[1] = NaNMath.min(lims[1], e1)
lims[2] = max(lims[2], e2) lims[2] = NaNMath.max(lims[2], e2)
# catch err # catch err
# warn(err) # warn(err)
end end
nothing nothing
end end
expand_data(v, n::Integer) = [cycle(v, i) for i=1:n] expand_data(v, n::Integer) = [_cycle(v, i) for i=1:n]
# if the type exists in a list, replace the first occurence. otherwise add it to the end # if the type exists in a list, replace the first occurence. otherwise add it to the end
function addOrReplace(v::AbstractVector, t::DataType, args...; kw...) function addOrReplace(v::AbstractVector, t::DataType, args...; kw...)
@ -324,7 +343,7 @@ function replaceAliases!(d::KW, aliases::Dict{Symbol,Symbol})
end end
end end
createSegments(z) = collect(repmat(z',2,1))[2:end] createSegments(z) = collect(repmat(reshape(z,1,:),2,1))[2:end]
Base.first(c::Colorant) = c Base.first(c::Colorant) = c
Base.first(x::Symbol) = x Base.first(x::Symbol) = x
@ -332,32 +351,55 @@ Base.first(x::Symbol) = x
sortedkeys(d::Dict) = sort(collect(keys(d))) sortedkeys(d::Dict) = sort(collect(keys(d)))
"create an (n+1) list of the outsides of heatmap rectangles"
function heatmap_edges(v::AVec) const _scale_base = Dict{Symbol, Real}(
vmin, vmax = extrema(v) :log10 => 10,
extra = 0.5 * (vmax-vmin) / (length(v)-1) :log2 => 2,
vcat(vmin-extra, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra) :ln => e,
)
function _heatmap_edges(v::AVec)
vmin, vmax = ignorenan_extrema(v)
extra_min = (v[2] - v[1]) / 2
extra_max = (v[end] - v[end - 1]) / 2
vcat(vmin-extra_min, 0.5 * (v[1:end-1] + v[2:end]), vmax+extra_max)
end end
"create an (n+1) list of the outsides of heatmap rectangles"
function heatmap_edges(v::AVec, scale::Symbol = :identity)
f, invf = scalefunc(scale), invscalefunc(scale)
map(invf, _heatmap_edges(map(f,v)))
end
function calc_r_extrema(x, y) function calc_r_extrema(x, y)
xmin, xmax = extrema(x) xmin, xmax = ignorenan_extrema(x)
ymin, ymax = extrema(y) ymin, ymax = ignorenan_extrema(y)
r = 0.5 * min(xmax - xmin, ymax - ymin) r = 0.5 * NaNMath.min(xmax - xmin, ymax - ymin)
extrema(r) ignorenan_extrema(r)
end end
function convert_to_polar(x, y, r_extrema = calc_r_extrema(x, y)) function convert_to_polar(x, y, r_extrema = calc_r_extrema(x, y))
rmin, rmax = r_extrema rmin, rmax = r_extrema
phi, r = x, y theta, r = filter_radial_data(x, y, r_extrema)
r = 0.5 * (r - rmin) / (rmax - rmin) r = (r - rmin) / (rmax - rmin)
n = max(length(phi), length(r)) x = r.*cos.(theta)
x = zeros(n) y = r.*sin.(theta)
y = zeros(n) x, y
end
# Filters radial data for points within the axis limits
function filter_radial_data(theta, r, r_extrema::Tuple{Real, Real})
n = max(length(theta), length(r))
rmin, rmax = r_extrema
x, y = zeros(n), zeros(n)
for i in 1:n for i in 1:n
x[i] = cycle(r,i) * cos(cycle(phi,i)) x[i] = _cycle(theta, i)
y[i] = cycle(r,i) * sin(cycle(phi,i)) y[i] = _cycle(r, i)
end end
points = map((a, b) -> (a, b), x, y)
filter!(a -> a[2] >= rmin && a[2] <= rmax, points)
x = map(a -> a[1], points)
y = map(a -> a[2], points)
x, y x, y
end end
@ -374,7 +416,7 @@ isatom() = isdefined(Main, :Atom) && Main.Atom.isconnected()
function is_installed(pkgstr::AbstractString) function is_installed(pkgstr::AbstractString)
try try
Pkg.installed(pkgstr) === nothing ? false: true Pkg.installed(pkgstr) === nothing ? false : true
catch catch
false false
end end
@ -396,20 +438,20 @@ isvertical(d::KW) = get(d, :orientation, :vertical) in (:vertical, :v, :vert)
isvertical(series::Series) = isvertical(series.d) isvertical(series::Series) = isvertical(series.d)
ticksType{T<:Real}(ticks::AVec{T}) = :ticks ticksType(ticks::AVec{T}) where {T<:Real} = :ticks
ticksType{T<:AbstractString}(ticks::AVec{T}) = :labels ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels
ticksType{T<:AVec,S<:AVec}(ticks::Tuple{T,S}) = :ticks_and_labels ticksType(ticks::Tuple{T,S}) where {T<:AVec,S<:AVec} = :ticks_and_labels
ticksType(ticks) = :invalid ticksType(ticks) = :invalid
limsType{T<:Real,S<:Real}(lims::Tuple{T,S}) = :limits limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits
limsType(lims::Symbol) = lims == :auto ? :auto : :invalid limsType(lims::Symbol) = lims == :auto ? :auto : :invalid
limsType(lims) = :invalid limsType(lims) = :invalid
# axis_Symbol(letter, postfix) = Symbol(letter * postfix) # axis_Symbol(letter, postfix) = Symbol(letter * postfix)
# axis_symbols(letter, postfix...) = map(s -> axis_Symbol(letter, s), postfix) # axis_symbols(letter, postfix...) = map(s -> axis_Symbol(letter, s), postfix)
Base.convert{T<:Real}(::Type{Vector{T}}, rng::Range{T}) = T[x for x in rng] Base.convert(::Type{Vector{T}}, rng::Range{T}) where {T<:Real} = 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.convert(::Type{Vector{T}}, rng::Range{S}) where {T<:Real,S<:Real} = T[x for x in rng]
Base.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b))) Base.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b)))
@ -469,7 +511,7 @@ ok(tup::Tuple) = ok(tup...)
# compute one side of a fill range from a ribbon # compute one side of a fill range from a ribbon
function make_fillrange_side(y, rib) function make_fillrange_side(y, rib)
frs = zeros(length(y)) frs = zeros(length(y))
for (i, (yi, ri)) in enumerate(zip(y, Base.cycle(rib))) for (i, (yi, ri)) in enumerate(zip(y, Base.Iterators.cycle(rib)))
frs[i] = yi + ri frs[i] = yi + ri
end end
frs frs
@ -480,16 +522,44 @@ function make_fillrange_from_ribbon(kw::KW)
y, rib = kw[:y], kw[:ribbon] y, rib = kw[:y], kw[:ribbon]
rib = wraptuple(rib) rib = wraptuple(rib)
rib1, rib2 = -first(rib), last(rib) rib1, rib2 = -first(rib), last(rib)
kw[:ribbon] = nothing # kw[:ribbon] = nothing
kw[:fillrange] = make_fillrange_side(y, rib1), make_fillrange_side(y, rib2) kw[:fillrange] = make_fillrange_side(y, rib1), make_fillrange_side(y, rib2)
(get(kw, :fillalpha, nothing) == nothing) && (kw[:fillalpha] = 0.5)
end
#turn tuple of fillranges to one path
function concatenate_fillrange(x,y::Tuple)
rib1, rib2 = first(y), last(y)
yline = vcat(rib1,(rib2)[end:-1:1])
xline = vcat(x,x[end:-1:1])
return xline, yline
end end
function get_sp_lims(sp::Subplot, letter::Symbol) function get_sp_lims(sp::Subplot, letter::Symbol)
axis_limits(sp[Symbol(letter, :axis)]) axis_limits(sp[Symbol(letter, :axis)])
end end
"""
xlims([plt])
Returns the x axis limits of the current plot or subplot
"""
xlims(sp::Subplot) = get_sp_lims(sp, :x) xlims(sp::Subplot) = get_sp_lims(sp, :x)
"""
ylims([plt])
Returns the y axis limits of the current plot or subplot
"""
ylims(sp::Subplot) = get_sp_lims(sp, :y) ylims(sp::Subplot) = get_sp_lims(sp, :y)
"""
zlims([plt])
Returns the z axis limits of the current plot or subplot
"""
zlims(sp::Subplot) = get_sp_lims(sp, :z) zlims(sp::Subplot) = get_sp_lims(sp, :z)
xlims(plt::Plot, sp_idx::Int = 1) = xlims(plt[sp_idx]) xlims(plt::Plot, sp_idx::Int = 1) = xlims(plt[sp_idx])
ylims(plt::Plot, sp_idx::Int = 1) = ylims(plt[sp_idx]) ylims(plt::Plot, sp_idx::Int = 1) = ylims(plt[sp_idx])
zlims(plt::Plot, sp_idx::Int = 1) = zlims(plt[sp_idx]) zlims(plt::Plot, sp_idx::Int = 1) = zlims(plt[sp_idx])
@ -497,6 +567,136 @@ xlims(sp_idx::Int = 1) = xlims(current(), sp_idx)
ylims(sp_idx::Int = 1) = ylims(current(), sp_idx) ylims(sp_idx::Int = 1) = ylims(current(), sp_idx)
zlims(sp_idx::Int = 1) = zlims(current(), sp_idx) zlims(sp_idx::Int = 1) = zlims(current(), sp_idx)
function get_clims(sp::Subplot)
zmin, zmax = Inf, -Inf
z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface)
for series in series_list(sp)
for vals in (series[:seriestype] in z_colored_series ? series[:z] : nothing, series[:line_z], series[:marker_z], series[:fill_z])
if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Real)
zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals.surf)...)
elseif (vals != nothing) && (eltype(vals) <: Real)
zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals)...)
end
end
end
clims = sp[:clims]
if is_2tuple(clims)
isfinite(clims[1]) && (zmin = clims[1])
isfinite(clims[2]) && (zmax = clims[2])
end
return zmin < zmax ? (zmin, zmax) : (-0.1, 0.1)
end
_update_clims(zmin, zmax, emin, emax) = min(zmin, emin), max(zmax, emax)
function hascolorbar(series::Series)
st = series[:seriestype]
hascbar = st == :heatmap
if st == :contour
hascbar = (isscalar(series[:levels]) ? (series[:levels] > 1) : (length(series[:levels]) > 1)) && (length(unique(Array(series[:z]))) > 1)
end
if series[:marker_z] != nothing || series[:line_z] != nothing || series[:fill_z] != nothing
hascbar = true
end
# no colorbar if we are creating a surface LightSource
if xor(st == :surface, series[:fill_z] != nothing)
hascbar = true
end
return hascbar
end
function hascolorbar(sp::Subplot)
cbar = sp[:colorbar]
hascbar = false
if cbar != :none
for series in series_list(sp)
if hascolorbar(series)
hascbar = true
end
end
end
hascbar
end
function get_linecolor(series, i::Int = 1)
lc = series[:linecolor]
lz = series[:line_z]
if lz == nothing
isa(lc, ColorGradient) ? lc : plot_color(_cycle(lc, i))
else
cmin, cmax = get_clims(series[:subplot])
grad = isa(lc, ColorGradient) ? lc : cgrad()
grad[clamp((_cycle(lz, i) - cmin) / (cmax - cmin), 0, 1)]
end
end
function get_linealpha(series, i::Int = 1)
_cycle(series[:linealpha], i)
end
function get_linewidth(series, i::Int = 1)
_cycle(series[:linewidth], i)
end
function get_linestyle(series, i::Int = 1)
_cycle(series[:linestyle], i)
end
function get_fillcolor(series, i::Int = 1)
fc = series[:fillcolor]
fz = series[:fill_z]
if fz == nothing
isa(fc, ColorGradient) ? fc : plot_color(_cycle(fc, i))
else
cmin, cmax = get_clims(series[:subplot])
grad = isa(fc, ColorGradient) ? fc : cgrad()
grad[clamp((_cycle(fz, i) - cmin) / (cmax - cmin), 0, 1)]
end
end
function get_fillalpha(series, i::Int = 1)
_cycle(series[:fillalpha], i)
end
function get_markercolor(series, i::Int = 1)
mc = series[:markercolor]
mz = series[:marker_z]
if mz == nothing
isa(mc, ColorGradient) ? mc : plot_color(_cycle(mc, i))
else
cmin, cmax = get_clims(series[:subplot])
grad = isa(mc, ColorGradient) ? mc : cgrad()
grad[clamp((_cycle(mz, i) - cmin) / (cmax - cmin), 0, 1)]
end
end
function get_markeralpha(series, i::Int = 1)
_cycle(series[:markeralpha], i)
end
function get_markerstrokecolor(series, i::Int = 1)
msc = series[:markerstrokecolor]
isa(msc, ColorGradient) ? msc : _cycle(msc, i)
end
function get_markerstrokealpha(series, i::Int = 1)
_cycle(series[:markerstrokealpha], i)
end
function has_attribute_segments(series::Series)
# we want to check if a series needs to be split into segments just because
# of its attributes
for letter in (:x, :y, :z)
# If we have NaNs in the data they define the segments and
# SegmentsIterator is used
series[letter] != nothing && NaN in collect(series[letter]) && return false
end
series[:seriestype] == :shape && return false
# ... else we check relevant attributes if they have multiple inputs
return any((typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1) for attr in [:seriescolor, :seriesalpha, :linecolor, :linealpha, :linewidth, :fillcolor, :fillalpha, :markercolor, :markeralpha, :markerstrokecolor, :markerstrokealpha]) || any(typeof(series[attr]) <: AbstractArray{<:Real} for attr in (:line_z, :fill_z, :marker_z))
end
# --------------------------------------------------------------- # ---------------------------------------------------------------
makekw(; kw...) = KW(kw) makekw(; kw...) = KW(kw)
@ -523,7 +723,7 @@ 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: Allows temporary setting of backend and defaults for Plots. Settings apply only for the `do` block. Example:
``` ```
with(:gadfly, size=(400,400), type=:histogram) do with(:gr, size=(400,400), type=:histogram) do
plot(rand(10)) plot(rand(10))
plot(rand(10)) plot(rand(10))
end end
@ -608,7 +808,7 @@ end
# --------------------------------------------------------------- # ---------------------------------------------------------------
# --------------------------------------------------------------- # ---------------------------------------------------------------
type DebugMode mutable struct DebugMode
on::Bool on::Bool
end end
const _debugMode = DebugMode(false) const _debugMode = DebugMode(false)
@ -645,11 +845,11 @@ end
# used in updating an existing series # used in updating an existing series
extendSeriesByOne(v::UnitRange{Int}, n::Int = 1) = isempty(v) ? (1:n) : (minimum(v):maximum(v)+n) extendSeriesByOne(v::UnitRange{Int}, n::Int = 1) = isempty(v) ? (1:n) : (minimum(v):maximum(v)+n)
extendSeriesByOne(v::AVec, n::Integer = 1) = isempty(v) ? (1:n) : vcat(v, (1:n) + maximum(v)) extendSeriesByOne(v::AVec, n::Integer = 1) = isempty(v) ? (1:n) : vcat(v, (1:n) + ignorenan_maximum(v))
extendSeriesData{T}(v::Range{T}, z::Real) = extendSeriesData(float(collect(v)), z) extendSeriesData(v::Range{T}, z::Real) where {T} = extendSeriesData(float(collect(v)), z)
extendSeriesData{T}(v::Range{T}, z::AVec) = extendSeriesData(float(collect(v)), z) extendSeriesData(v::Range{T}, z::AVec) where {T} = extendSeriesData(float(collect(v)), z)
extendSeriesData{T}(v::AVec{T}, z::Real) = (push!(v, convert(T, z)); v) extendSeriesData(v::AVec{T}, z::Real) where {T} = (push!(v, convert(T, z)); v)
extendSeriesData{T}(v::AVec{T}, z::AVec) = (append!(v, convert(Vector{T}, z)); v) extendSeriesData(v::AVec{T}, z::AVec) where {T} = (append!(v, convert(Vector{T}, z)); v)
# ------------------------------------------------------- # -------------------------------------------------------
@ -667,14 +867,14 @@ function getxyz(plt::Plot, i::Integer)
tovec(d[:x]), tovec(d[:y]), tovec(d[:z]) tovec(d[:x]), tovec(d[:y]), tovec(d[:z])
end end
function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer) function setxy!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y}
series = plt.series_list[i] series = plt.series_list[i]
series.d[:x], series.d[:y] = xy series.d[:x], series.d[:y] = xy
sp = series.d[:subplot] sp = series.d[:subplot]
reset_extrema!(sp) reset_extrema!(sp)
_series_updated(plt, series) _series_updated(plt, series)
end end
function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) function setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z}
series = plt.series_list[i] series = plt.series_list[i]
series.d[:x], series.d[:y], series.d[:z] = xyz series.d[:x], series.d[:y], series.d[:z] = xyz
sp = series.d[:subplot] sp = series.d[:subplot]
@ -682,7 +882,7 @@ function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer)
_series_updated(plt, series) _series_updated(plt, series)
end end
function setxyz!{X,Y,Z<:AbstractMatrix}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) function setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z<:AbstractMatrix}
setxyz!(plt, (xyz[1], xyz[2], Surface(xyz[3])), i) setxyz!(plt, (xyz[1], xyz[2], Surface(xyz[3])), i)
end end
@ -691,8 +891,8 @@ end
# indexing notation # indexing notation
# Base.getindex(plt::Plot, i::Integer) = getxy(plt, i) # Base.getindex(plt::Plot, i::Integer) = getxy(plt, i)
Base.setindex!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer) = (setxy!(plt, xy, i); plt) Base.setindex!(plt::Plot, xy::Tuple{X,Y}, i::Integer) where {X,Y} = (setxy!(plt, xy, i); plt)
Base.setindex!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) = (setxyz!(plt, xyz, i); plt) Base.setindex!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z} = (setxyz!(plt, xyz, i); plt)
# ------------------------------------------------------- # -------------------------------------------------------
@ -804,10 +1004,10 @@ function Base.append!(plt::Plot, i::Integer, x::AVec, y::AVec, z::AVec)
end end
# tuples # tuples
Base.push!{X,Y}(plt::Plot, xy::Tuple{X,Y}) = push!(plt, 1, xy...) Base.push!(plt::Plot, xy::Tuple{X,Y}) where {X,Y} = push!(plt, 1, xy...)
Base.push!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}) = push!(plt, 1, xyz...) Base.push!(plt::Plot, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, 1, xyz...)
Base.push!{X,Y}(plt::Plot, i::Integer, xy::Tuple{X,Y}) = push!(plt, i, xy...) Base.push!(plt::Plot, i::Integer, xy::Tuple{X,Y}) where {X,Y} = push!(plt, i, xy...)
Base.push!{X,Y,Z}(plt::Plot, i::Integer, xyz::Tuple{X,Y,Z}) = push!(plt, i, xyz...) Base.push!(plt::Plot, i::Integer, xyz::Tuple{X,Y,Z}) where {X,Y,Z} = push!(plt, i, xyz...)
# ------------------------------------------------------- # -------------------------------------------------------
# push/append for all series # push/append for all series
@ -871,9 +1071,152 @@ mm2px(mm::Real) = float(px / MM_PER_PX)
"Smallest x in plot" "Smallest x in plot"
xmin(plt::Plot) = minimum([minimum(series.d[:x]) for series in plt.series_list]) xmin(plt::Plot) = ignorenan_minimum([ignorenan_minimum(series.d[:x]) for series in plt.series_list])
"Largest x in plot" "Largest x in plot"
xmax(plt::Plot) = maximum([maximum(series.d[:x]) for series in plt.series_list]) xmax(plt::Plot) = ignorenan_maximum([ignorenan_maximum(series.d[:x]) for series in plt.series_list])
"Extrema of x-values in plot" "Extrema of x-values in plot"
Base.extrema(plt::Plot) = (xmin(plt), xmax(plt)) ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt))
# ---------------------------------------------------------------
# get fonts from objects:
titlefont(sp::Subplot) = font(
sp[:titlefontfamily],
sp[:titlefontsize],
sp[:titlefontvalign],
sp[:titlefonthalign],
sp[:titlefontrotation],
sp[:titlefontcolor],
)
legendfont(sp::Subplot) = font(
sp[:legendfontfamily],
sp[:legendfontsize],
sp[:legendfontvalign],
sp[:legendfonthalign],
sp[:legendfontrotation],
sp[:legendfontcolor],
)
tickfont(ax::Axis) = font(
ax[:tickfontfamily],
ax[:tickfontsize],
ax[:tickfontvalign],
ax[:tickfonthalign],
ax[:tickfontrotation],
ax[:tickfontcolor],
)
guidefont(ax::Axis) = font(
ax[:guidefontfamily],
ax[:guidefontsize],
ax[:guidefontvalign],
ax[:guidefonthalign],
ax[:guidefontrotation],
ax[:guidefontcolor],
)
# ---------------------------------------------------------------
# converts unicode scientific notation unsupported by pgfplots and gr
# into a format that works
function convert_sci_unicode(label::AbstractString)
unicode_dict = Dict(
'⁰' => "0",
'¹' => "1",
'²' => "2",
'³' => "3",
'⁴' => "4",
'⁵' => "5",
'⁶' => "6",
'⁷' => "7",
'⁸' => "8",
'⁹' => "9",
'⁻' => "-",
"×10" => "×10^{",
)
for key in keys(unicode_dict)
label = replace(label, key, unicode_dict[key])
end
if contains(label, "10^{")
label = string(label, "}")
end
label
end
function straightline_data(series)
sp = series[:subplot]
xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp))
x, y = series[:x], series[:y]
n = length(x)
if n == 2
return straightline_data(xl, yl, x, y)
else
k, r = divrem(n, 3)
if r == 0
xdata, ydata = fill(NaN, n), fill(NaN, n)
for i in 1:k
inds = (3 * i - 2):(3 * i - 1)
xdata[inds], ydata[inds] = straightline_data(xl, yl, x[inds], y[inds])
end
return xdata, ydata
else
error("Misformed data. `straightline_data` either accepts vectors of length 2 or 3k. The provided series has length $n")
end
end
end
function straightline_data(xl, yl, x, y)
x_vals, y_vals = if y[1] == y[2]
if x[1] == x[2]
error("Two identical points cannot be used to describe a straight line.")
else
[xl[1], xl[2]], [y[1], y[2]]
end
elseif x[1] == x[2]
[x[1], x[2]], [yl[1], yl[2]]
else
# get a and b from the line y = a * x + b through the points given by
# the coordinates x and x
b = y[1] - (y[1] - y[2]) * x[1] / (x[1] - x[2])
a = (y[1] - y[2]) / (x[1] - x[2])
# get the data values
xdata = [clamp(x[1] + (x[1] - x[2]) * (ylim - y[1]) / (y[1] - y[2]), xl...) for ylim in yl]
xdata, a .* xdata .+ b
end
# expand the data outside the axis limits, by a certain factor too improve
# plotly(js) and interactive behaviour
factor = 100
x_vals = x_vals .+ (x_vals[2] - x_vals[1]) .* factor .* [-1, 1]
y_vals = y_vals .+ (y_vals[2] - y_vals[1]) .* factor .* [-1, 1]
return x_vals, y_vals
end
function shape_data(series)
sp = series[:subplot]
xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp))
x, y = series[:x], series[:y]
factor = 100
for i in eachindex(x)
if x[i] == -Inf
x[i] = xl[1] - factor * (xl[2] - xl[1])
elseif x[i] == Inf
x[i] = xl[2] + factor * (xl[2] - xl[1])
end
end
for i in eachindex(y)
if y[i] == -Inf
y[i] = yl[1] - factor * (yl[2] - yl[1])
elseif y[i] == Inf
y[i] = yl[2] + factor * (yl[2] - yl[1])
end
end
return x, y
end
function construct_categorical_data(x::AbstractArray, axis::Axis)
map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x)
end

View File

@ -1,10 +1,8 @@
StatPlots StatPlots
FactCheck
Images Images
ImageMagick ImageMagick
@osx QuartzImageIO @osx QuartzImageIO
GR GR 0.31.0
RDatasets RDatasets
VisualRegressionTests VisualRegressionTests
UnicodePlots UnicodePlots
Glob

View File

@ -15,8 +15,7 @@ end
using Plots using Plots
using StatPlots using StatPlots
using FactCheck using Base.Test
using Glob
default(size=(500,300)) default(size=(500,300))
@ -24,7 +23,7 @@ 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 # TODO: use julia's Condition type and the wait() and notify() functions to initialize a Window, then wait() on a condition that
# is referenced in a button press callback (the button clicked callback will call notify() on that condition) # is referenced in a button press callback (the button clicked callback will call notify() on that condition)
const _current_plots_version = v"0.9.6" const _current_plots_version = v"0.17.4"
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2) function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], eps = 1e-2)
@ -43,11 +42,8 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is
fn = "ref$idx.png" fn = "ref$idx.png"
# firgure out version info # firgure out version info
G = glob(joinpath(relpath(refdir), "*")) vns = filter(x->x[1] != '.', readdir(refdir))
# @show refdir fn G versions = sort(VersionNumber.(vns), rev = true)
slash = (@static is_windows() ? "\\" : "/")
versions = map(fn -> VersionNumber(split(fn, slash)[end]), G)
versions = reverse(sort(versions))
versions = filter(v -> v <= _current_plots_version, versions) versions = filter(v -> v <= _current_plots_version, versions)
# @show refdir fn versions # @show refdir fn versions
@ -99,7 +95,7 @@ function image_comparison_facts(pkg::Symbol;
for i in 1:length(Plots._examples) for i in 1:length(Plots._examples)
i in skip && continue i in skip && continue
if only == nothing || i in only if only == nothing || i in only
@fact image_comparison_tests(pkg, i, debug=debug, sigma=sigma, eps=eps) |> success --> true @test image_comparison_tests(pkg, i, debug=debug, sigma=sigma, eps=eps) |> success == true
end end
end end
end end

View File

@ -5,9 +5,11 @@ set -ex
sudo apt-get -qq update sudo apt-get -qq update
# sudo apt-get install -y wkhtmltopdf # sudo apt-get install -y wkhtmltopdf
sudo apt-get install -y xfonts-75dpi sudo apt-get install -y xfonts-75dpi xfonts-base
wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2/wkhtmltox-0.12.2_linux-trusty-amd64.deb wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.2.1/wkhtmltox-0.12.2.1_linux-precise-amd64.deb
sudo dpkg -i wkhtmltox-0.12.2_linux-trusty-amd64.deb sudo dpkg -i wkhtmltox-0.12.2.1_linux-precise-amd64.deb
# wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2/wkhtmltox-0.12.2_linux-trusty-amd64.deb
# sudo dpkg -i wkhtmltox-0.12.2_linux-trusty-amd64.deb
wkhtmltoimage http://www.google.com test.png wkhtmltoimage http://www.google.com test.png
ls ls

View File

@ -7,89 +7,136 @@ srand(1234)
default(show=false, reuse=true) default(show=false, reuse=true)
img_eps = isinteractive() ? 1e-2 : 10e-2 img_eps = isinteractive() ? 1e-2 : 10e-2
# facts("Gadfly") do @testset "GR" begin
# @fact gadfly() --> Plots.GadflyBackend() ENV["PLOTS_TEST"] = "true"
# @fact backend() --> Plots.GadflyBackend() ENV["GKSwstype"] = "100"
@test gr() == Plots.GRBackend()
@test backend() == Plots.GRBackend()
image_comparison_facts(:gr, eps=img_eps)
end
#@testset "PyPlot" begin
# @test pyplot() == Plots.PyPlotBackend()
# @test backend() == Plots.PyPlotBackend()
# #
# @fact typeof(plot(1:10)) --> Plots.Plot{Plots.GadflyBackend} # image_comparison_facts(:pyplot, eps=img_eps)
# @fact plot(Int[1,2,3], rand(3)) --> not(nothing) #end
# @fact plot(sort(rand(10)), rand(Int, 10, 3)) --> not(nothing)
# @fact plot!(rand(10,3), rand(10,3)) --> not(nothing) @testset "UnicodePlots" begin
@test unicodeplots() == Plots.UnicodePlotsBackend()
@test backend() == Plots.UnicodePlotsBackend()
# lets just make sure it runs without error
@test isa(plot(rand(10)), Plots.Plot) == true
end
# The plotlyjs testimages return a connection error on travis:
# connect: connection refused (ECONNREFUSED)
# @testset "PlotlyJS" begin
# @test plotlyjs() == Plots.PlotlyJSBackend()
# @test backend() == Plots.PlotlyJSBackend()
# #
# image_comparison_facts(:gadfly, skip=[4,6,23,24,27], eps=img_eps) # if is_linux() && isinteractive()
# image_comparison_facts(:plotlyjs,
# skip=[
# 2, # animation (skipped for speed)
# 27, # (polar plots) takes very long / not working
# 31, # animation (skipped for speed)
# ],
# eps=img_eps)
# end
# end # end
facts("PyPlot") do
@fact pyplot() --> Plots.PyPlotBackend()
@fact backend() --> Plots.PyPlotBackend()
image_comparison_facts(:pyplot, skip=[25,30], eps=img_eps) # InspectDR returns that error on travis:
end # ERROR: LoadError: InitError: Cannot open display:
# in Gtk.GLib.GError(::Gtk.##229#230) at /home/travis/.julia/v0.5/Gtk/src/GLib/gerror.jl:17
facts("GR") do # @testset "InspectDR" begin
@fact gr() --> Plots.GRBackend() # @test inspectdr() == Plots.InspectDRBackend()
@fact backend() --> Plots.GRBackend() # @test backend() == Plots.InspectDRBackend()
#
if is_linux() && isinteractive() # image_comparison_facts(:inspectdr,
image_comparison_facts(:gr, skip=[2,25,30], eps=img_eps) # skip=[
end # 2, # animation
end # 6, # heatmap not defined
# 10, # heatmap not defined
facts("Plotly") do # 22, # contour not defined
@fact plotly() --> Plots.PlotlyBackend() # 23, # pie not defined
@fact backend() --> Plots.PlotlyBackend() # 27, # polar plot not working
# 28, # heatmap not defined
# # until png generation is reliable on OSX, just test on linux # 31, # animation
# @static is_linux() && image_comparison_facts(:plotly, only=[1,3,4,7,8,9,10,11,12,14,15,20,22,23,27], eps=img_eps) # ],
end # eps=img_eps)
# end
# facts("Immerse") do # @testset "Plotly" begin
# @fact immerse() --> Plots.ImmerseBackend() # @test plotly() == Plots.PlotlyBackend()
# @fact backend() --> Plots.ImmerseBackend() # @test backend() == Plots.PlotlyBackend()
#
# # # until png generation is reliable on OSX, just test on linux
# # @static is_linux() && image_comparison_facts(:plotly, only=[1,3,4,7,8,9,10,11,12,14,15,20,22,23,27], eps=img_eps)
# end
# @testset "Immerse" begin
# @test immerse() == Plots.ImmerseBackend()
# @test backend() == Plots.ImmerseBackend()
# #
# # as long as we can plot anything without error, it should be the same as Gadfly # # as long as we can plot anything without error, it should be the same as Gadfly
# image_comparison_facts(:immerse, only=[1], eps=img_eps) # image_comparison_facts(:immerse, only=[1], eps=img_eps)
# end # end
# facts("PlotlyJS") do # @testset "PlotlyJS" begin
# @fact plotlyjs() --> Plots.PlotlyJSBackend() # @test plotlyjs() == Plots.PlotlyJSBackend()
# @fact backend() --> Plots.PlotlyJSBackend() # @test backend() == Plots.PlotlyJSBackend()
# #
# # as long as we can plot anything without error, it should be the same as Plotly # # as long as we can plot anything without error, it should be the same as Plotly
# image_comparison_facts(:plotlyjs, only=[1], eps=img_eps) # image_comparison_facts(:plotlyjs, only=[1], eps=img_eps)
# end # end
facts("UnicodePlots") do # @testset "Gadfly" begin
@fact unicodeplots() --> Plots.UnicodePlotsBackend() # @test gadfly() == Plots.GadflyBackend()
@fact backend() --> Plots.UnicodePlotsBackend() # @test backend() == Plots.GadflyBackend()
#
# lets just make sure it runs without error # @test typeof(plot(1:10)) == Plots.Plot{Plots.GadflyBackend}
@fact isa(plot(rand(10)), Plots.Plot) --> true # @test plot(Int[1,2,3], rand(3)) == not(nothing)
end # @test plot(sort(rand(10)), rand(Int, 10, 3)) == not(nothing)
# @test plot!(rand(10,3), rand(10,3)) == not(nothing)
#
# image_comparison_facts(:gadfly, skip=[4,6,23,24,27], eps=img_eps)
# end
facts("Axes") do
@testset "Axes" begin
p = plot() p = plot()
axis = p.subplots[1][:xaxis] axis = p.subplots[1][:xaxis]
@fact typeof(axis) --> Plots.Axis @test typeof(axis) == Plots.Axis
@fact Plots.discrete_value!(axis, "HI") --> (0.5, 1) @test Plots.discrete_value!(axis, "HI") == (0.5, 1)
@fact Plots.discrete_value!(axis, :yo) --> (1.5, 2) @test Plots.discrete_value!(axis, :yo) == (1.5, 2)
@fact extrema(axis) --> (0.5,1.5) @test Plots.ignorenan_extrema(axis) == (0.5,1.5)
@fact axis[:discrete_map] --> Dict{Any,Any}(:yo => 2, "HI" => 1) @test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1)
Plots.discrete_value!(axis, ["x$i" for i=1:5]) Plots.discrete_value!(axis, ["x$i" for i=1:5])
Plots.discrete_value!(axis, ["x$i" for i=0:2]) Plots.discrete_value!(axis, ["x$i" for i=0:2])
@fact extrema(axis) --> (0.5, 7.5) @test Plots.ignorenan_extrema(axis) == (0.5, 7.5)
end end
@testset "NoFail" begin
histogram([1, 0, 0, 0, 0, 0])
end
# tests for preprocessing recipes # tests for preprocessing recipes
# facts("recipes") do # @testset "recipes" begin
# user recipe # user recipe
@ -126,6 +173,4 @@ end
# end # end
FactCheck.exitstatus()
end # module end # module

View File

@ -1,5 +1,5 @@
# Pkg.clone("ImageMagick") Pkg.add("ImageMagick")
# Pkg.build("ImageMagick") Pkg.build("ImageMagick")
# Pkg.clone("GR") # Pkg.clone("GR")
# Pkg.build("GR") # Pkg.build("GR")
@ -9,13 +9,14 @@ Pkg.clone("https://github.com/JuliaPlots/PlotReferenceImages.jl.git")
# Pkg.clone("https://github.com/JuliaStats/KernelDensity.jl.git") # Pkg.clone("https://github.com/JuliaStats/KernelDensity.jl.git")
Pkg.clone("StatPlots") Pkg.clone("StatPlots")
Pkg.checkout("PlotUtils") # Pkg.checkout("PlotUtils")
# Pkg.clone("https://github.com/JunoLab/Blink.jl.git") # Pkg.clone("Blink")
# Pkg.build("Blink") # Pkg.build("Blink")
# import Blink # import Blink
# Blink.AtomShell.install() # Blink.AtomShell.install()
# Pkg.clone("https://github.com/spencerlyon2/PlotlyJS.jl.git") # Pkg.add("Rsvg")
# Pkg.add("PlotlyJS")
# Pkg.checkout("RecipesBase") # Pkg.checkout("RecipesBase")
# Pkg.clone("VisualRegressionTests") # Pkg.clone("VisualRegressionTests")
@ -25,4 +26,6 @@ ENV["PYTHON"] = ""
Pkg.add("PyPlot") Pkg.add("PyPlot")
Pkg.build("PyPlot") Pkg.build("PyPlot")
# Pkg.add("InspectDR")
Pkg.test("Plots"; coverage=false) Pkg.test("Plots"; coverage=false)