Merge branch 'master' into plotly_autoresize

This commit is contained in:
Anshul Singhvi 2019-08-04 23:13:16 +05:30 committed by GitHub
commit 86ff99f214
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 2669 additions and 6789 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ examples/.ipynb_checkpoints/*
examples/meetup/.ipynb_checkpoints/* examples/meetup/.ipynb_checkpoints/*
deps/plotly-latest.min.js deps/plotly-latest.min.js
deps/build.log deps/build.log
deps/deps.jl
Manifest.toml

View File

@ -4,57 +4,17 @@ os:
- linux - linux
# - osx # - osx
julia: julia:
# - 1.0 - 1.1
- nightly - nightly
# matrix:
# allow_failures:
# - julia: nightly
# # before install: matrix:
# # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi allow_failures:
# # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install wkhtmltopdf; fi - julia: nightly
# ref: http://askubuntu.com/a/556672 for the wkhtmltopdf apt repository info
sudo: required sudo: required
before_install: before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pwd ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pwd ; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./test/install_wkhtmltoimage.sh ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./test/install_wkhtmltoimage.sh ; fi
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:pov/wkhtmltopdf ; fi
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y wkhtmltopdf ; fi
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wkhtmltopdf -V ; fi
# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wkhtmltoimage -V ; fi
# echo 'exec xvfb-run -a -s "-screen 0 640x480x16" wkhtmltopdf "$@"' | sudo tee /usr/local/bin/wkhtmltopdf.sh >/dev/null
# sudo chmod a+x /usr/local/bin/wkhtmltopdf.sh
# # borrowed from Blink.jl's travis file
# matrix:
# include:
# - os: linux
# julia: 0.4
# env: TESTCMD="xvfb-run julia"
# - os: osx
# julia: 0.4
# env: TESTCMD="julia"
notifications: notifications:
email: true email: true
# uncomment the following lines to override the default test script
# script:
# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
# - julia -e 'import Pkg; Pkg.add(Pkg.PackageSpec(path=pwd())); Pkg.build("Plots")'
# - julia test/travis_commands.jl
# - julia -e 'Pkg.clone("ImageMagick"); Pkg.build("ImageMagick")'
# - julia -e 'Pkg.clone("GR"); Pkg.build("GR")'
# # - julia -e 'Pkg.clone("https://github.com/tbreloff/ImageMagick.jl.git"); Pkg.checkout("ImageMagick","tb_write"); Pkg.build("ImageMagick")'
# - julia -e 'Pkg.clone("https://github.com/tbreloff/ExamplePlots.jl.git");'
# # - julia -e 'Pkg.clone("https://github.com/JunoLab/Blink.jl.git"); Pkg.build("Blink"); import Blink; Blink.AtomShell.install()'
# # - julia -e 'Pkg.clone("https://github.com/spencerlyon2/PlotlyJS.jl.git")'
# - julia -e 'ENV["PYTHON"] = ""; Pkg.add("PyPlot"); Pkg.build("PyPlot")'
#
# # - $TESTCMD -e 'Pkg.test("Plots"; coverage=false)'
# - julia -e 'Pkg.test("Plots"; coverage=false)'
# # - julia -e 'cd(Pkg.dir("Plots")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder()); Codecov.submit(process_folder())'

116
NEWS.md
View File

@ -10,7 +10,113 @@
--- ---
## (current master) ## (current master)
- All new development should target Julia 1.x!
## 0.26.0
- use FFMPEG.jl
- add missing method for convertToAnyVector
## 0.25.3
- add areaplot
- allow missing in z_color arguments
- more general tuple recipe
- stephist logscale improvements
## 0.25.2
- improvements to handle missings
- pyplot: allow setting the color gradient for z values
- document :colorbar_entry
- limit number of automatic bins
- fix ENV['PLOTS_DEFAULT_BACKEND']
- don't let aspect_ratio impact subplot size
- implement arrowstyle for GR
- fix bug in plotly_convert_to_datetime
- improve missing support
- gr: polar heatmaps
- make sure show returns nothing
## 0.25.1
- fix gr_display
## 0.25.0
- Replace StaticArrays with GeometryTypes
- Contour fixes for GR
## 0.24.0
- Update to the new PyCall and PyPlot API
- fix drawing of ticks
- fix y label position with GR
## 0.23.2
- pyplot fixes
- Add option :tex_output_standalone to set the 'include_preamble' argument in the PGFPlots backend.
- fix ticks
- support plotly json mime
- fix image axis limits
- default to radius 0 at center for polar plots
## 0.23.1
- slightly faster load time
- fixed errant MethodError
- fix bar plots with unicodeplots
- better colorbars for contour
- add volume seriestype for GR
- fix passing a tuple to custom ticks
- add vline to pgfplots
- add tex output for pyplot
- better 3d axis labels for GR
## 0.23.0
- compatible with StatPlots -> StatsPlots name shift
- fix histograms for vectors with NaN and Inf
- change gif behaviour (remove cache-busting)
- improved docstrings for shorthands functions
- fix font rotation for pyplot
- fix greyscale images for pyplot
- clamp greyscale images with values outside 0,1
- support keyword argument for font options
- allow vector of markers for pyplot scatter
## 0.22.5
- improve behaviour of plotlyjs backend
## 0.22.4
- Add support for discrete contourf plots with GR
## 0.22.3
- Fix the `showtheme` function
## 0.22.2
- Allow annotations to accept a Tuple instead of the result of a text call (making it possible to specify font characteristics in recipes). E.g. `annotations = (2, 4, ("test", :right, 8, :red))` is the same as `annotations = (2, 4, text("test", :right, 8, :red))`
## 0.22.1
- push PlotsDisplay just after REPLDisplay
## 0.22.0
- deprecate GLVisualize
- allow 1-row and 1-column heatmaps
- add portfoliodecomposition recipe from PlotRecipes
- solve Shape bug
- simplify PyPlot backend installation
- fix wireframe bug in PyPlot
- fix color bug in PyPlot
- minor bug fixes in gr and pyplot
## 0.21.0
- Compatibility with StaticArrays 0.9.0
- Up GR min version to 0.35
- fix :mirror
## 0.20.6
- fixes for PlotDocs.jl
- fix gr axis color argument
- Shapes for inspectdr
- don't load plotly js file by default
## 0.20.5
- fix precompilation issue when depending on Plots
## 0.20.4
- honour `html_output_format` in Juno
## 0.20.3 ## 0.20.3
- implement guide position in gr, pyplot and pgfplots - implement guide position in gr, pyplot and pgfplots
@ -153,7 +259,7 @@ Many updates, min julia 1.0
- add `reset_defaults()` function to reset plot defaults - add `reset_defaults()` function to reset plot defaults
- update syntax to 0.6 - update syntax to 0.6
- make `fill = true` fill to 0 rather than to 1 - make `fill = true` fill to 0 rather than to 1
- use new `@df` syntax in StatPlots examples - use new `@df` syntax in StatsPlots examples
- allow changing the color of legend box - allow changing the color of legend box
- implement `title_location` for gr - implement `title_location` for gr
- add `hline` marker to pgfplots - fixes errorbars - add `hline` marker to pgfplots - fixes errorbars
@ -305,7 +411,7 @@ Many updates, min julia 1.0
- added dependency on PlotThemes - added dependency on PlotThemes
- set_theme --> theme - set_theme --> theme
- remove Compat from REQUIRE - remove Compat from REQUIRE
- warning for DataFrames without StatPlots - warning for DataFrames without StatsPlots
- closeall exported and implemented for gr/pyplot - closeall exported and implemented for gr/pyplot
- fix DateTime recipe - fix DateTime recipe
- reset theme with theme(:none) - reset theme with theme(:none)
@ -427,8 +533,8 @@ Many updates, min julia 1.0
#### 0.8.0 #### 0.8.0
- added dependency on PlotUtils - added dependency on PlotUtils
- BREAKING: removed DataFrames support (now in StatPlots.jl) - BREAKING: removed DataFrames support (now in StatsPlots.jl)
- BREAKING: removed boxplot/violin/density recipes (now in StatPlots.jl) - BREAKING: removed boxplot/violin/density recipes (now in StatsPlots.jl)
- GR: - GR:
- inline iterm2 support - inline iterm2 support
- trisurface support - trisurface support

57
Project.toml Normal file
View File

@ -0,0 +1,57 @@
name = "Plots"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
author = ["Tom Breloff (@tbreloff)"]
version = "0.26.0"
[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Contour = "d38c429a-6771-53c6-b99e-75d170b6e991"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
FFMPEG = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
GeometryTypes = "4d00f742-c7ba-57c2-abde-4428a4b178cb"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
PlotThemes = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
PlotUtils = "995b91a9-d308-5afd-9ec6-746e21dbc043"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
Showoff = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[compat]
FixedPointNumbers = "≥ 0.3.0"
GR = "≥ 0.31.0"
PlotThemes = "≥ 0.1.3"
PlotUtils = "≥ 0.4.1"
RecipesBase = "≥ 0.6.0"
StatsBase = "≥ 0.14.0"
julia = "≥ 1.0.0"
[extras]
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
RDatasets = "ce6b1742-4840-55fa-b093-852dadbb1d8b"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92"
BinaryProvider = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
[targets]
test = ["BinaryProvider", "Pkg", "Test", "Random", "StatsPlots", "VisualRegressionTests", "LaTeXStrings", "Images", "ImageMagick", "RDatasets", "FileIO", "UnicodePlots"]

View File

@ -11,7 +11,7 @@
#### Created by Tom Breloff (@tbreloff) #### Created by Tom Breloff (@tbreloff)
#### Maintained by the [JuliaPlot members](https://github.com/orgs/JuliaPlots/people) #### Maintained by the [JuliaPlots 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:

16
REQUIRE
View File

@ -1,16 +0,0 @@
julia 1.0
RecipesBase 0.6.0
PlotUtils 0.4.1
PlotThemes 0.1.3
Reexport
StaticArrays 0.5
FixedPointNumbers 0.3
Measures
Showoff
StatsBase 0.14.0
JSON
NaNMath
Requires
Contour
GR 0.34.0

View File

@ -1,6 +1,6 @@
environment: environment:
matrix: matrix:
- julia_version: 0.7 # - julia_version: 0.7
- julia_version: 1 - julia_version: 1
- julia_version: nightly - julia_version: nightly

16
deps/build.jl vendored
View File

@ -1,8 +1,18 @@
#TODO: download https://cdn.plot.ly/plotly-latest.min.js to deps/ if it doesn't exist #TODO: download https://cdn.plot.ly/plotly-latest.min.js to deps/ if it doesn't exist
file_path = ""
local_fn = joinpath(dirname(@__FILE__), "plotly-latest.min.js") if get(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL", "false") == "true"
if !isfile(local_fn) global file_path
local_fn = joinpath(dirname(@__FILE__), "plotly-latest.min.js")
if !isfile(local_fn)
@info("Cannot find deps/plotly-latest.min.js... downloading latest version.") @info("Cannot find deps/plotly-latest.min.js... downloading latest version.")
download("https://cdn.plot.ly/plotly-latest.min.js", local_fn) download("https://cdn.plot.ly/plotly-latest.min.js", local_fn)
isfile(local_fn) && (file_path = local_fn)
else
file_path = local_fn
end
end
open("deps.jl", "w") do io
println(io, "const plotly_local_file_path = $(repr(file_path))")
end end

View File

@ -1,12 +1,15 @@
module Plots module Plots
_current_plots_version = v"0.25.0"
using Reexport using Reexport
import StaticArrays import GeometryTypes
using StaticArrays.FixedSizeArrays using Dates, Printf, Statistics, Base64, LinearAlgebra, Random
using Dates, Printf, Statistics, Base64, LinearAlgebra
import SparseArrays: findnz import SparseArrays: findnz
using FFMPEG
@reexport using RecipesBase @reexport using RecipesBase
import RecipesBase: plot, plot!, animate import RecipesBase: plot, plot!, animate
using Base.Meta using Base.Meta
@ -18,6 +21,17 @@ import JSON
using Requires using Requires
if isfile(joinpath(@__DIR__, "..", "deps", "deps.jl"))
include(joinpath(@__DIR__, "..", "deps", "deps.jl"))
else
# This is a bit dirty, but I don't really see why anyone should be forced
# to build Plots, while it will just include exactly the below line
# as long as `ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] = "true"` is not set.
# If the above env is set + `plotly_local_file_path == ""``,
# it will warn in the __init__ function to run build
const plotly_local_file_path = ""
end
export export
grid, grid,
bbox, bbox,
@ -72,7 +86,6 @@ export
backends, backends,
backend_name, backend_name,
backend_object, backend_object,
add_backend,
aliases, aliases,
Shape, Shape,
@ -168,103 +181,15 @@ include("arg_desc.jl")
include("plotattr.jl") include("plotattr.jl")
include("backends.jl") include("backends.jl")
include("output.jl") include("output.jl")
include("ijulia.jl")
include("fileio.jl")
include("init.jl") include("init.jl")
include("backends/plotly.jl") include("backends/plotly.jl")
include("backends/gr.jl") include("backends/gr.jl")
include("backends/web.jl") include("backends/web.jl")
# --------------------------------------------------------- include("shorthands.jl")
@shorthands scatter
@shorthands bar
@shorthands barh
@shorthands histogram
@shorthands barhist
@shorthands stephist
@shorthands scatterhist
@shorthands histogram2d
@shorthands density
@shorthands heatmap
@shorthands plots_heatmap
@shorthands hexbin
@shorthands sticks
@shorthands hline
@shorthands vline
@shorthands hspan
@shorthands vspan
@shorthands ohlc
@shorthands contour
@shorthands contourf
@shorthands contour3d
@shorthands surface
@shorthands wireframe
@shorthands path3d
@shorthands scatter3d
@shorthands boxplot
@shorthands violin
@shorthands quiver
@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)
"Plot with 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...)
"Add xlabel to an existing plot"
xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...)
"Add ylabel to an existing plot"
ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...)
"Set xlims for an existing plot"
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...)
ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...)
zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin,zmax), kw...)
"Set xticks for an existing plot"
xticks!(v::TicksArgs; kw...) where {T<:Real} = plot!(; xticks = v, kw...)
"Set yticks for an existing plot"
yticks!(v::TicksArgs; 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::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...)
"Flip the current plots' y axis"
yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...)
"Specify x axis attributes for an existing plot"
xaxis!(args...; kw...) = plot!(; xaxis = args, kw...)
"Specify x axis attributes for an existing plot"
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}
global title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...) global title!(plt::PlotOrSubplot, s::AbstractString; kw...) = plot!(plt; title = s, kw...)

View File

@ -76,15 +76,15 @@ function buildanimation(animdir::AbstractString, fn::AbstractString,
if variable_palette if variable_palette
# generate a colorpalette for each frame for highest quality, but larger filesize # generate a colorpalette for each frame for highest quality, but larger filesize
palette="palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1" palette="palettegen=stats_mode=single[pal],[0:v][pal]paletteuse=new=1"
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`) ffmpeg_exe(`-v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -lavfi "$palette" -y $fn`)
else else
# generate a colorpalette first so ffmpeg does not have to guess it # 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"`) ffmpeg_exe(`-v 0 -i $(animdir)/%06d.png -vf "palettegen=stats_mode=diff" -y "$(animdir)/palette.bmp"`)
# then apply the palette to get better results # 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`) ffmpeg_exe(` -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -i "$(animdir)/palette.bmp" -lavfi "paletteuse=dither=sierra2_4a" -y $fn`)
end end
else else
run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`) ffmpeg_exe(`-v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)
end end
show_msg && @info("Saved animation to ", fn) show_msg && @info("Saved animation to ", fn)
@ -93,16 +93,25 @@ end
# write out html to view the gif... note the rand call which is a hack so the image doesn't get cached # write out html to view the gif
function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif) function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif)
ext = file_extension(agif.filename) ext = file_extension(agif.filename)
write(io, if ext == "gif" write(io, if ext == "gif"
"<img src=\"$(relpath(agif.filename))?$(rand())>\" />" "<img src=\"$(relpath(agif.filename))\" />"
elseif ext in ("mov", "mp4") elseif ext in ("mov", "mp4")
"<video controls><source src=\"$(relpath(agif.filename))?$(rand())>\" type=\"video/$ext\"></video>" "<video controls><source src=\"$(relpath(agif.filename)) type=\"video/$ext\"></video>"
else else
error("Cannot show animation with extension $ext: $agif") error("Cannot show animation with extension $ext: $agif")
end) end)
return nothing
end
# Only gifs can be shown via image/gif
Base.showable(::MIME"image/gif", agif::AnimatedGif) = file_extension(agif.filename) == "gif"
function Base.show(io::IO, ::MIME"image/gif", agif::AnimatedGif)
open(fio-> write(io, fio), agif.filename)
end end

View File

@ -10,7 +10,7 @@ const _arg_desc = KW(
:linewidth => "Number. Width of the line (in pixels)", :linewidth => "Number. Width of the line (in pixels)",
:linecolor => "Color Type. Color of the line (for path and bar stroke). `:match` will take the value from `:seriescolor`, (though histogram/bar types use `:black` as a default).", :linecolor => "Color Type. Color of the line (for path and bar stroke). `:match` will take the value from `:seriescolor`, (though histogram/bar types use `:black` as a default).",
:linealpha => "Number in [0,1]. The alpha/opacity override for the line. `nothing` (the default) means it will take the alpha value of linecolor.", :linealpha => "Number in [0,1]. The alpha/opacity override for the line. `nothing` (the default) means it will take the alpha value of linecolor.",
:fillrange => "Number or AbstractVector. Fills area from this to y for line-types, sets the base for bar/stick types, and similar for other types.", :fillrange => "Number or AbstractVector. Fills area between fillrange and y for line-types, sets the base for bar/stick types, and similar for other types.",
:fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.", :fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.",
:fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.", :fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.",
:markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).", :markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).",
@ -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 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. `range(min(x), stop = extrema(x), length = 25)`", :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. `range(minimum(x), stop = maximum(x), length = 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",
@ -30,7 +30,7 @@ const _arg_desc = KW(
:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.", :marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).", :line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).",
:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.", :fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.",
:levels => "Integer, NTuple{2,Integer}. Number of levels (or x-levels/y-levels) for a contour type.", :levels => "Integer, NTuple{2,Integer}, or AbstractVector. Levels or number of levels (or x-levels/y-levels) for a contour type.",
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).", :orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)", :bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",
:bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).", :bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).",
@ -49,6 +49,7 @@ const _arg_desc = KW(
: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.",
:primary => "Bool. Does this count as a 'real series'? For example, you could have a path (primary), and a scatter (secondary) as 2 separate series, maybe with different data (see sticks recipe for an example). The secondary series will get the same color, etc as the primary.", :primary => "Bool. Does this count as a 'real series'? For example, you could have a path (primary), and a scatter (secondary) as 2 separate series, maybe with different data (see sticks recipe for an example). The secondary series will get the same color, etc as the primary.",
:hover => "nothing or vector of strings. Text to display when hovering over each data point.", :hover => "nothing or vector of strings. Text to display when hovering over each data point.",
:colorbar_entry => "Bool. Include this series in the color bar? Set to `false` to exclude.",
# plot args # plot args
:plot_title => "String. Title for the whole plot (not the subplots) (Note: Not currently implemented)", :plot_title => "String. Title for the whole plot (not the subplots) (Note: Not currently implemented)",
@ -63,6 +64,7 @@ const _arg_desc = KW(
:link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).", :link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).",
:overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).", :overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).",
: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.",
:tex_output_standalone => "Bool. When writing tex output, should the source include a preamble for a standalone document class.",
: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.", :thickness_scaling => "Number. Scale for the thickness of all line elements like lines, borders, axes, grid lines, ... defaults to 1.",
@ -98,7 +100,7 @@ const _arg_desc = KW(
:legendfont => "Font. Font of legend items.", :legendfont => "Font. Font of legend items.",
: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 `aspect_ratio` x-units.",
:margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.", :margin => "Measure (multiply by `mm`, `px`, etc). Base for individual margins... not directly used. Specifies the extra padding around subplots.",
:left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.", :left_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding to the left of the subplot.",
:top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.", :top_margin => "Measure (multiply by `mm`, `px`, etc) or `:match` (matches `:margin`). Specifies the extra padding on the top of the subplot.",
@ -117,7 +119,7 @@ const _arg_desc = KW(
: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, :plain or :auto. A method which converts a number to a string for tick labeling.",
:tickfontfamily => "String or Symbol. Font family of tick labels.", :tickfontfamily => "String or Symbol. Font family of tick labels.",
:tickfontsize => "Integer. Font pointsize of tick labels.", :tickfontsize => "Integer. Font pointsize of tick labels.",
:tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center", :tickfonthalign => "Symbol. Font horizontal alignment of tick labels: :hcenter, :left, :right or :center",
@ -149,4 +151,5 @@ const _arg_desc = KW(
:tick_direction => "Symbol. Direction of the ticks. `:in` or `:out`", :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`", :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`.", :widen => "Bool. Widen the axis limits by a small factor to avoid cut-off markers and lines at the borders. Defaults to `true`.",
:draw_arrow => "Bool. Draw arrow at the end of the axis.",
) )

View File

@ -232,6 +232,7 @@ const _bar_width = 0.8
const _series_defaults = KW( const _series_defaults = KW(
:label => "AUTO", :label => "AUTO",
:colorbar_entry => true,
:seriescolor => :auto, :seriescolor => :auto,
:seriesalpha => nothing, :seriesalpha => nothing,
:seriestype => :path, :seriestype => :path,
@ -298,6 +299,7 @@ const _plot_defaults = KW(
:link => :none, :link => :none,
:overwrite_figure => true, :overwrite_figure => true,
:html_output_format => :auto, :html_output_format => :auto,
:tex_output_standalone => false,
: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
@ -390,6 +392,7 @@ const _axis_defaults = KW(
:minorgrid => false, :minorgrid => false,
:showaxis => true, :showaxis => true,
:widen => true, :widen => true,
:draw_arrow => false,
) )
const _suppress_warnings = Set{Symbol}([ const _suppress_warnings = Set{Symbol}([
@ -735,6 +738,10 @@ function processMarkerArg(plotattributes::KW, arg)
elseif allAlphas(arg) elseif allAlphas(arg)
plotattributes[:markeralpha] = arg plotattributes[:markeralpha] = arg
# bool
elseif typeof(arg) <: Bool
plotattributes[:markershape] = arg ? :circle : :none
# markersize # markersize
elseif allReals(arg) elseif allReals(arg)
plotattributes[:markersize] = arg plotattributes[:markersize] = arg
@ -1023,7 +1030,7 @@ function preprocessArgs!(plotattributes::KW)
arrow() arrow()
elseif a in (false, nothing, :none) elseif a in (false, nothing, :none)
nothing nothing
elseif !(typeof(a) <: Arrow) elseif !(typeof(a) <: Arrow || typeof(a) <: AbstractArray{Arrow})
arrow(wraptuple(a)...) arrow(wraptuple(a)...)
else else
a a
@ -1050,8 +1057,8 @@ function preprocessArgs!(plotattributes::KW)
# warnings for moved recipes # warnings for moved recipes
st = get(plotattributes, :seriestype, :path) st = get(plotattributes, :seriestype, :path)
if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatPlots) if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatsPlots)
@warn("seriestype $st has been moved to StatPlots. To use: \`Pkg.add(\"StatPlots\"); using StatPlots\`") @warn("seriestype $st has been moved to StatsPlots. To use: \`Pkg.add(\"StatsPlots\"); using StatsPlots\`")
end end
return return
@ -1180,7 +1187,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, :outertopright) elseif val in (:right, :left, :top, :bottom, :inside, :best, :legend, :topright, :topleft, :bottomleft, :bottomright, :outertopright, :outertopleft, :outertop, :outerright, :outerleft, :outerbottomright, :outerbottomleft, :outerbottom)
val val
else else
error("Invalid symbol for legend: $val") error("Invalid symbol for legend: $val")
@ -1508,19 +1515,19 @@ 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 or ColorGradient
# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically # and assigns a color automatically
function getSeriesRGBColor(c, sp::Subplot, n::Int) function get_series_color(c, sp::Subplot, n::Int, seriestype)
if c == :auto if c == :auto
c = autopick(sp[:color_palette], n) c = like_surface(seriestype) ? cgrad() : 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 getSeriesRGBColor(c::AbstractArray, sp::Subplot, n::Int) function get_series_color(c::AbstractArray, sp::Subplot, n::Int, seriestype)
map(x->getSeriesRGBColor(x, sp, n), c) map(x->get_series_color(x, sp, n, seriestype), c)
end end
function ensure_gradient!(plotattributes::KW, csym::Symbol, asym::Symbol) function ensure_gradient!(plotattributes::KW, csym::Symbol, asym::Symbol)
@ -1565,21 +1572,23 @@ function _update_series_attributes!(plotattributes::KW, plt::Plot, sp::Subplot)
end end
# update series color # update series color
plotattributes[:seriescolor] = getSeriesRGBColor(plotattributes[:seriescolor], sp, plotIndex) scolor = plotattributes[:seriescolor]
stype = plotattributes[:seriestype]
plotattributes[:seriescolor] = scolor = get_series_color(scolor, sp, plotIndex, stype)
# 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)
plotattributes[csym] = if plotattributes[csym] == :auto plotattributes[csym] = if plotattributes[csym] == :auto
plot_color(if has_black_border_for_default(plotattributes[:seriestype]) && s == :line plot_color(if has_black_border_for_default(stype) && s == :line
sp[:foreground_color_subplot] sp[:foreground_color_subplot]
else else
plotattributes[:seriescolor] scolor
end) end)
elseif plotattributes[csym] == :match elseif plotattributes[csym] == :match
plot_color(plotattributes[:seriescolor]) plot_color(scolor)
else else
getSeriesRGBColor(plotattributes[csym], sp, plotIndex) get_series_color(plotattributes[csym], sp, plotIndex, stype)
end end
end end
@ -1587,9 +1596,9 @@ function _update_series_attributes!(plotattributes::KW, plt::Plot, sp::Subplot)
plotattributes[:markerstrokecolor] = if plotattributes[:markerstrokecolor] == :match plotattributes[:markerstrokecolor] = if plotattributes[:markerstrokecolor] == :match
plot_color(sp[:foreground_color_subplot]) plot_color(sp[:foreground_color_subplot])
elseif plotattributes[:markerstrokecolor] == :auto elseif plotattributes[:markerstrokecolor] == :auto
getSeriesRGBColor(plotattributes[:markercolor], sp, plotIndex) get_series_color(plotattributes[:markercolor], sp, plotIndex, stype)
else else
getSeriesRGBColor(plotattributes[:markerstrokecolor], sp, plotIndex) get_series_color(plotattributes[:markerstrokecolor], sp, plotIndex, stype)
end end
# if marker_z, fill_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

View File

@ -117,7 +117,7 @@ end
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
Base.show(io::IO, axis::Axis) = dumpdict(axis.plotattributes, "Axis", true) Base.show(io::IO, axis::Axis) = dumpdict(io, axis.plotattributes, "Axis", true)
# Base.getindex(axis::Axis, k::Symbol) = getindex(axis.plotattributes, k) # Base.getindex(axis::Axis, k::Symbol) = getindex(axis.plotattributes, k)
Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.plotattributes, v, ks...) Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.plotattributes, v, ks...)
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.plotattributes, k) Base.haskey(axis::Axis, k::Symbol) = haskey(axis.plotattributes, k)
@ -152,8 +152,8 @@ scalefunc(scale::Symbol) = x -> get(_scale_funcs, scale, identity)(Float64(x))
invscalefunc(scale::Symbol) = x -> get(_inv_scale_funcs, scale, identity)(Float64(x)) invscalefunc(scale::Symbol) = x -> get(_inv_scale_funcs, scale, identity)(Float64(x))
labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string) labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string)
function optimal_ticks_and_labels(axis::Axis, ticks = nothing) function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
amin,amax = axis_limits(axis) amin, amax = axis_limits(sp, axis[:letter])
# scale the limits # scale the limits
scale = axis[:scale] scale = axis[:scale]
@ -238,9 +238,9 @@ function optimal_ticks_and_labels(axis::Axis, ticks = nothing)
end 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(sp::Subplot, axis::Axis)
ticks = _transform_ticks(axis[:ticks]) ticks = _transform_ticks(axis[:ticks])
ticks in (nothing, false) && return nothing ticks in (:none, nothing, false) && return nothing
# treat :native ticks as :auto # treat :native ticks as :auto
ticks = ticks == :native ? :auto : ticks ticks = ticks == :native ? :auto : ticks
@ -251,7 +251,7 @@ function get_ticks(axis::Axis)
# discrete ticks... # discrete ticks...
n = length(dvals) n = length(dvals)
rng = if ticks == :auto rng = if ticks == :auto
Int[round(Int,i) for i in range(1, stop=n, length=15)] Int[round(Int,i) for i in range(1, stop=n, length=min(n,15))]
else # if ticks == :all else # if ticks == :all
1:n 1:n
end end
@ -261,7 +261,7 @@ function get_ticks(axis::Axis)
(collect(0:pi/4:7pi/4), string.(0:45:315)) (collect(0:pi/4:7pi/4), string.(0:45:315))
else else
# compute optimal ticks and labels # compute optimal ticks and labels
optimal_ticks_and_labels(axis) optimal_ticks_and_labels(sp, axis)
end end
elseif typeof(ticks) <: Union{AVec, Int} elseif typeof(ticks) <: Union{AVec, Int}
if !isempty(dvals) && typeof(ticks) <: Int if !isempty(dvals) && typeof(ticks) <: Int
@ -269,7 +269,7 @@ function get_ticks(axis::Axis)
axis[:continuous_values][rng], dvals[rng] axis[:continuous_values][rng], dvals[rng]
else else
# override ticks, but get the labels # override ticks, but get the labels
optimal_ticks_and_labels(axis, ticks) optimal_ticks_and_labels(sp, axis, ticks)
end end
elseif typeof(ticks) <: NTuple{2, Any} elseif typeof(ticks) <: NTuple{2, Any}
# assuming we're passed (ticks, labels) # assuming we're passed (ticks, labels)
@ -286,12 +286,12 @@ _transform_ticks(ticks) = ticks
_transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(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]) _transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2])
function get_minor_ticks(axis,ticks) function get_minor_ticks(sp, axis, ticks)
axis[:minorticks] in (nothing, false) && !axis[:minorgrid] && return nothing axis[:minorticks] in (:none, nothing, false) && !axis[:minorgrid] && return nothing
ticks = ticks[1] ticks = ticks[1]
length(ticks) < 2 && return nothing length(ticks) < 2 && return nothing
amin, amax = axis_limits(axis) amin, amax = axis_limits(sp, axis[:letter])
#Add one phantom tick either side of the ticks to ensure minor ticks extend to the axis limits #Add one phantom tick either side of the ticks to ensure minor ticks extend to the axis limits
if length(ticks) > 2 if length(ticks) > 2
ratio = (ticks[3] - ticks[2])/(ticks[2] - ticks[1]) ratio = (ticks[3] - ticks[2])/(ticks[2] - ticks[1])
@ -459,7 +459,7 @@ const _widen_seriestypes = (:line, :path, :steppre, :steppost, :sticks, :scatter
function default_should_widen(axis::Axis) function default_should_widen(axis::Axis)
should_widen = false should_widen = false
if !is_2tuple(axis[:lims]) if !(is_2tuple(axis[:lims]) || axis[:lims] == :round)
for sp in axis.sps for sp in axis.sps
for series in series_list(sp) for series in series_list(sp)
if series.plotattributes[:seriestype] in _widen_seriestypes if series.plotattributes[:seriestype] in _widen_seriestypes
@ -479,11 +479,13 @@ function round_limits(amin,amax)
end 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(sp, letter, should_widen = default_should_widen(sp[Symbol(letter, :axis)]), consider_aspect = true)
axis = sp[Symbol(letter, :axis)]
ex = axis[:extrema] ex = axis[:extrema]
amin, amax = ex.emin, ex.emax amin, amax = ex.emin, ex.emax
lims = axis[:lims] lims = axis[:lims]
if (isa(lims, Tuple) || isa(lims, AVec)) && length(lims) == 2 has_user_lims = (isa(lims, Tuple) || isa(lims, AVec)) && length(lims) == 2
if has_user_lims
if isfinite(lims[1]) if isfinite(lims[1])
amin = lims[1] amin = lims[1]
end end
@ -497,12 +499,12 @@ 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 ispolar(axis.sps[1]) amin, amax = if ispolar(axis.sps[1])
if axis[:letter] == :x if axis[:letter] == :x
amin, amax = 0, 2pi amin, amax = 0, 2pi
elseif lims == :auto elseif lims == :auto
#widen max radius so ticks dont overlap with theta axis #widen max radius so ticks dont overlap with theta axis
amin, amax + 0.1 * abs(amax - amin) 0, amax + 0.1 * abs(amax - amin)
else else
amin, amax amin, amax
end end
@ -513,6 +515,32 @@ function axis_limits(axis::Axis, should_widen::Bool = default_should_widen(axis)
else else
amin, amax amin, amax
end end
if !has_user_lims && consider_aspect && letter in (:x, :y) && !(sp[:aspect_ratio] in (:none, :auto) || is3d(:sp))
aspect_ratio = isa(sp[:aspect_ratio], Number) ? sp[:aspect_ratio] : 1
plot_ratio = height(plotarea(sp)) / width(plotarea(sp))
dist = amax - amin
if letter == :x
yamin, yamax = axis_limits(sp, :y, default_should_widen(sp[:yaxis]), false)
ydist = yamax - yamin
axis_ratio = aspect_ratio * ydist / dist
factor = axis_ratio / plot_ratio
else
xamin, xamax = axis_limits(sp, :x, default_should_widen(sp[:xaxis]), false)
xdist = xamax - xamin
axis_ratio = aspect_ratio * dist / xdist
factor = plot_ratio / axis_ratio
end
if factor > 1
center = (amin + amax) / 2
amin = center + factor * (amin - center)
amax = center + factor * (amax - center)
end
end
return amin, amax
end end
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@ -586,12 +614,12 @@ end
# compute the line segments which should be drawn for this axis # compute the line segments which should be drawn for this axis
function axis_drawing_info(sp::Subplot) function axis_drawing_info(sp::Subplot)
xaxis, yaxis = sp[:xaxis], sp[:yaxis] xaxis, yaxis = sp[:xaxis], sp[:yaxis]
xmin, xmax = axis_limits(xaxis) xmin, xmax = axis_limits(sp, :x)
ymin, ymax = axis_limits(yaxis) ymin, ymax = axis_limits(sp, :y)
xticks = get_ticks(xaxis) xticks = get_ticks(sp, xaxis)
yticks = get_ticks(yaxis) yticks = get_ticks(sp, yaxis)
xminorticks = get_minor_ticks(xaxis,xticks) xminorticks = get_minor_ticks(sp, xaxis, xticks)
yminorticks = get_minor_ticks(yaxis,yticks) yminorticks = get_minor_ticks(sp, yaxis, yticks)
xaxis_segs = Segments(2) xaxis_segs = Segments(2)
yaxis_segs = Segments(2) yaxis_segs = Segments(2)
xtick_segs = Segments(2) xtick_segs = Segments(2)
@ -614,14 +642,14 @@ function axis_drawing_info(sp::Subplot)
end end
push!(xaxis_segs, (xmin, y1), (xmax, y1)) push!(xaxis_segs, (xmin, y1), (xmax, y1))
# don't show the 0 tick label for the origin framestyle # don't show the 0 tick label for the origin framestyle
if sp[:framestyle] == :origin && !(xticks in (nothing,false)) && length(xticks) > 1 if sp[:framestyle] == :origin && !(xticks in (:none, nothing, false)) && length(xticks) > 1
showticks = xticks[1] .!= 0 showticks = xticks[1] .!= 0
xticks = (xticks[1][showticks], xticks[2][showticks]) xticks = (xticks[1][showticks], xticks[2][showticks])
end end
end end
sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine
end end
if !(xaxis[:ticks] in (nothing, false)) if !(xaxis[:ticks] in (:none, nothing, false))
f = scalefunc(yaxis[:scale]) f = scalefunc(yaxis[:scale])
invf = invscalefunc(yaxis[:scale]) invf = invscalefunc(yaxis[:scale])
ticks_in = xaxis[:tick_direction] == :out ? -1 : 1 ticks_in = xaxis[:tick_direction] == :out ? -1 : 1
@ -642,7 +670,7 @@ function axis_drawing_info(sp::Subplot)
xaxis[:grid] && push!(xgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid xaxis[:grid] && push!(xgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid
end end
end end
if !(xaxis[:minorticks] in (nothing, false)) || xaxis[:minorgrid] if !(xaxis[:minorticks] in (:none, nothing, false)) || xaxis[:minorgrid]
f = scalefunc(yaxis[:scale]) f = scalefunc(yaxis[:scale])
invf = invscalefunc(yaxis[:scale]) invf = invscalefunc(yaxis[:scale])
ticks_in = xaxis[:tick_direction] == :out ? -1 : 1 ticks_in = xaxis[:tick_direction] == :out ? -1 : 1
@ -675,14 +703,14 @@ function axis_drawing_info(sp::Subplot)
end end
push!(yaxis_segs, (x1, ymin), (x1, ymax)) push!(yaxis_segs, (x1, ymin), (x1, ymax))
# don't show the 0 tick label for the origin framestyle # don't show the 0 tick label for the origin framestyle
if sp[:framestyle] == :origin && !(yticks in (nothing,false)) && length(yticks) > 1 if sp[:framestyle] == :origin && !(yticks in (:none, nothing,false)) && length(yticks) > 1
showticks = yticks[1] .!= 0 showticks = yticks[1] .!= 0
yticks = (yticks[1][showticks], yticks[2][showticks]) yticks = (yticks[1][showticks], yticks[2][showticks])
end end
end end
sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine
end end
if !(yaxis[:ticks] in (nothing, false)) if !(yaxis[:ticks] in (:none, nothing, false))
f = scalefunc(xaxis[:scale]) f = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale]) invf = invscalefunc(xaxis[:scale])
ticks_in = yaxis[:tick_direction] == :out ? -1 : 1 ticks_in = yaxis[:tick_direction] == :out ? -1 : 1
@ -703,7 +731,7 @@ function axis_drawing_info(sp::Subplot)
yaxis[:grid] && push!(ygrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid yaxis[:grid] && push!(ygrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid
end end
end end
if !(yaxis[:minorticks] in (nothing, false)) || yaxis[:minorgrid] if !(yaxis[:minorticks] in (:none, nothing, false)) || yaxis[:minorgrid]
f = scalefunc(xaxis[:scale]) f = scalefunc(xaxis[:scale])
invf = invscalefunc(xaxis[:scale]) invf = invscalefunc(xaxis[:scale])
ticks_in = yaxis[:tick_direction] == :out ? -1 : 1 ticks_in = yaxis[:tick_direction] == :out ? -1 : 1

View File

@ -7,16 +7,20 @@ const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none)
const _backends = Symbol[] const _backends = Symbol[]
const _initialized_backends = Set{Symbol}() const _initialized_backends = Set{Symbol}()
const _default_backends = (:none, :gr, :plotly) const _default_backends = (:none, :gr, :plotly)
const _backendPackage = Dict{Symbol, Symbol}()
const _backend_packages = Dict{Symbol, Symbol}()
"Returns a list of supported backends" "Returns a list of supported backends"
backends() = _backends backends() = _backends
"Returns the name of the current backend" "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_package(pkg::Symbol) = pkg in _default_backends ? :Plots : Symbol("Plots", _backendPackage[pkg]) function _backend_instance(sym::Symbol)::AbstractBackend
backend_package_name(sym::Symbol) = sym in _default_backends ? :Plots : _backendPackage[sym] haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym")
end
backend_package_name(sym::Symbol) = _backend_packages[sym]
macro init_backend(s) macro init_backend(s)
package_str = string(s) package_str = string(s)
@ -26,13 +30,13 @@ macro init_backend(s)
esc(quote esc(quote
struct $T <: AbstractBackend end struct $T <: AbstractBackend end
export $sym export $sym
$sym(; kw...) = (default(; kw...); backend(Symbol($str))) $sym(; kw...) = (default(; kw...); backend($T()))
backend_name(::$T) = Symbol($str) backend_name(::$T) = Symbol($str)
backend_package_name(pkg::$T) = backend_package_name(Symbol($str)) backend_package_name(pkg::$T) = backend_package_name(Symbol($str))
push!(_backends, Symbol($str)) push!(_backends, Symbol($str))
_backendType[Symbol($str)] = $T _backendType[Symbol($str)] = $T
_backendSymbol[$T] = Symbol($str) _backendSymbol[$T] = Symbol($str)
_backendPackage[Symbol($str)] = Symbol($package_str) _backend_packages[Symbol($str)] = Symbol($package_str)
# include("backends/" * $str * ".jl") # include("backends/" * $str * ".jl")
end) end)
end end
@ -42,12 +46,6 @@ end
# --------------------------------------------------------- # ---------------------------------------------------------
function add_backend(pkg::Symbol)
@info("To do a standard install of $pkg, copy and run this:\n\n")
println(add_backend_string(_backend_instance(pkg)))
println()
end
# don't do anything as a default # don't do anything as a default
_create_backend_figure(plt::Plot) = nothing _create_backend_figure(plt::Plot) = nothing
_prepare_plot_object(plt::Plot) = nothing _prepare_plot_object(plt::Plot) = nothing
@ -77,8 +75,8 @@ end
text_size(lab::AbstractString, sz::Number, rot::Number = 0) = text_size(length(lab), sz, rot) text_size(lab::AbstractString, sz::Number, rot::Number = 0) = text_size(length(lab), sz, rot)
# account for the size/length/rotation of tick labels # account for the size/length/rotation of tick labels
function tick_padding(axis::Axis) function tick_padding(sp::Subplot, axis::Axis)
ticks = get_ticks(axis) ticks = get_ticks(sp, axis)
if ticks == nothing if ticks == nothing
0mm 0mm
else else
@ -108,10 +106,10 @@ end
# to fit ticks, tick labels, guides, colorbars, etc. # to fit ticks, tick labels, guides, colorbars, etc.
function _update_min_padding!(sp::Subplot) function _update_min_padding!(sp::Subplot)
# TODO: something different when `is3d(sp) == true` # TODO: something different when `is3d(sp) == true`
leftpad = tick_padding(sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis]) leftpad = tick_padding(sp, sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis])
toppad = sp[:top_margin] + title_padding(sp) toppad = sp[:top_margin] + title_padding(sp)
rightpad = sp[:right_margin] rightpad = sp[:right_margin]
bottompad = tick_padding(sp[:xaxis]) + sp[:bottom_margin] + guide_padding(sp[:xaxis]) bottompad = tick_padding(sp, sp[:xaxis]) + sp[:bottom_margin] + guide_padding(sp[:xaxis])
# switch them? # switch them?
if sp[:xaxis][:mirror] if sp[:xaxis][:mirror]
@ -138,33 +136,22 @@ CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym))
# --------------------------------------------------------- # ---------------------------------------------------------
function pickDefaultBackend() _fallback_default_backend() = backend(GRBackend())
function _pick_default_backend()
env_default = get(ENV, "PLOTS_DEFAULT_BACKEND", "") env_default = get(ENV, "PLOTS_DEFAULT_BACKEND", "")
if env_default != "" if env_default != ""
sym = Symbol(lowercase(env_default)) sym = Symbol(lowercase(env_default))
if sym in _backends if sym in _backends
if sym in _initialized_backends backend(sym)
return backend(sym)
else else
@warn("You have set `PLOTS_DEFAULT_BACKEND=$env_default` but `$(backend_package_name(sym))` is not loaded.") @warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t" *
end
else
@warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t",
join(sort(_backends), "\n\t")) join(sort(_backends), "\n\t"))
_fallback_default_backend()
end end
else
_fallback_default_backend()
end end
# 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
# features, speed, and robustness
# for pkgstr in ("GR", "PyPlot", "PlotlyJS", "PGFPlots", "UnicodePlots", "InspectDR", "GLVisualize")
# if pkgstr in keys(Pkg.installed())
# return backend(Symbol(lowercase(pkgstr)))
# end
# end
# the default if nothing else is installed
backend(:gr)
end end
@ -174,10 +161,8 @@ end
Returns the current plotting package name. Initializes package on first call. Returns the current plotting package name. Initializes package on first call.
""" """
function backend() function backend()
global CURRENT_BACKEND
if CURRENT_BACKEND.sym == :none if CURRENT_BACKEND.sym == :none
pickDefaultBackend() _pick_default_backend()
end end
CURRENT_BACKEND.pkg CURRENT_BACKEND.pkg
@ -188,20 +173,13 @@ Set the plot backend.
""" """
function backend(pkg::AbstractBackend) function backend(pkg::AbstractBackend)
sym = backend_name(pkg) sym = backend_name(pkg)
if sym in _initialized_backends if !(sym in _initialized_backends)
CURRENT_BACKEND.sym = backend_name(pkg)
CURRENT_BACKEND.pkg = pkg
else
# try
_initialize_backend(pkg) _initialize_backend(pkg)
push!(_initialized_backends, sym) push!(_initialized_backends, sym)
CURRENT_BACKEND.sym = backend_name(pkg)
CURRENT_BACKEND.pkg = pkg
# catch
# add_backend(sym)
# end
end end
backend() CURRENT_BACKEND.sym = sym
CURRENT_BACKEND.pkg = pkg
pkg
end end
function backend(sym::Symbol) function backend(sym::Symbol)
@ -209,15 +187,15 @@ function backend(sym::Symbol)
backend(_backend_instance(sym)) backend(_backend_instance(sym))
else else
@warn("`:$sym` is not a supported backend.") @warn("`:$sym` is not a supported backend.")
end
backend() backend()
end
end end
const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse] const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize]
function warn_on_deprecated_backend(bsym::Symbol) function warn_on_deprecated_backend(bsym::Symbol)
if bsym in _deprecated_backends if bsym in _deprecated_backends
@warn("Backend $bsym has been deprecated. It may not work as originally intended.") @warn("Backend $bsym has been deprecated.")
end end
end end
@ -268,17 +246,11 @@ end
# @init_backend Immerse
# @init_backend Gadfly
@init_backend PyPlot @init_backend PyPlot
# @init_backend Qwt
@init_backend UnicodePlots @init_backend UnicodePlots
# @init_backend Winston
# @init_backend Bokeh
@init_backend Plotly @init_backend Plotly
@init_backend PlotlyJS @init_backend PlotlyJS
@init_backend GR @init_backend GR
@init_backend GLVisualize
@init_backend PGFPlots @init_backend PGFPlots
@init_backend InspectDR @init_backend InspectDR
@init_backend HDF5 @init_backend HDF5
@ -322,72 +294,191 @@ function _initialize_backend(pkg::AbstractBackend)
end end
end end
function add_backend_string(pkg::AbstractBackend) _initialize_backend(pkg::GRBackend) = nothing
sym = backend_package_name(pkg)
""" _initialize_backend(pkg::PlotlyBackend) = nothing
using Pkg
Pkg.add("$sym")
"""
end
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# glvisualize # gr
function _initialize_backend(::GLVisualizeBackend; kw...) const _gr_attr = merge_with_base_supported([
@eval Main begin :annotations,
import GLVisualize, GeometryTypes, Reactive, GLAbstraction, GLWindow, Contour :background_color_legend, :background_color_inside, :background_color_outside,
import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0, GLNormalMesh, SimpleRectangle, Point, Vec :foreground_color_legend, :foreground_color_grid, :foreground_color_axis,
import FileIO, Images :foreground_color_text, :foreground_color_border,
export GLVisualize :label,
import Reactive: Signal :seriescolor, :seriesalpha,
import GLAbstraction: Style :linecolor, :linestyle, :linewidth, :linealpha,
import GLVisualize: visualize :markershape, :markercolor, :markersize, :markeralpha,
import Plots.GL :markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
import UnicodeFun :fillrange, :fillcolor, :fillalpha,
end :bins,
end :layout,
:title, :window_title,
:guide, :lims, :ticks, :scale, :flip,
:match_dimensions,
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign,
:titlefontrotation, :titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfonthalign, :legendfontvalign,
:legendfontrotation, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfonthalign, :tickfontvalign,
:tickfontrotation, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefonthalign, :guidefontvalign,
:guidefontrotation, :guidefontcolor,
:grid, :gridalpha, :gridstyle, :gridlinewidth,
:legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry,
:fill_z, :line_z, :marker_z, :levels,
:ribbon, :quiver,
:orientation,
:overwrite_figure,
:polar,
:aspect_ratio,
:normalize, :weights,
:inset_subplots,
:bar_width,
:arrow,
:framestyle,
:tick_direction,
:camera,
:contour_labels,
])
const _gr_seriestype = [
:path, :scatter, :straightline,
:heatmap, :pie, :image,
:contour, :path3d, :scatter3d, :surface, :wireframe, :volume,
:shape
]
const _gr_style = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
const _gr_marker = _allMarkers
const _gr_scale = [:identity, :log10]
is_marker_supported(::GRBackend, shape::Shape) = true
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# hdf5 # plotly
function _initialize_backend(::HDF5Backend) const _plotly_attr = merge_with_base_supported([
@eval Main begin :annotations,
import HDF5 :background_color_legend, :background_color_inside, :background_color_outside,
export HDF5 :foreground_color_legend, :foreground_color_guide,
end :foreground_color_grid, :foreground_color_axis,
end :foreground_color_text, :foreground_color_border,
:foreground_color_title,
:label,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
:fillrange, :fillcolor, :fillalpha,
:bins,
:title, :title_location,
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign,
:titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefontcolor,
:window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :gridalpha, :gridlinewidth,
:legend, :colorbar, :colorbar_title, :colorbar_entry,
:marker_z, :fill_z, :line_z, :levels,
:ribbon, :quiver,
:orientation,
# :overwrite_figure,
:polar,
:normalize, :weights,
# :contours,
:aspect_ratio,
:hover,
:inset_subplots,
:bar_width,
:clims,
:framestyle,
:tick_direction,
:camera,
:contour_labels,
])
const _plotly_seriestype = [
:path, :scatter, :pie, :heatmap,
:contour, :surface, :wireframe, :path3d, :scatter3d, :shape, :scattergl,
:straightline
]
const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot]
const _plotly_marker = [
:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle,
:cross, :xcross, :pentagon, :hexagon, :octagon, :vline, :hline
]
const _plotly_scale = [:identity, :log10]
defaultOutputFormat(plt::Plot{Plots.PlotlyBackend}) = "html"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# PGFPLOTS # pgfplots
function add_backend_string(::PGFPlotsBackend) const _pgfplots_attr = merge_with_base_supported([
""" :annotations,
using Pkg :background_color_legend,
Pkg.add("PGFPlots") :background_color_inside,
Pkg.build("PGFPlots") # :background_color_outside,
""" # :foreground_color_legend,
end :foreground_color_grid, :foreground_color_axis,
:foreground_color_text, :foreground_color_border,
:label,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
:fillrange, :fillcolor, :fillalpha,
:bins,
# :bar_width, :bar_edges,
:title,
# :window_title,
:guide, :guide_position, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :legend,
:colorbar, :colorbar_title,
:fill_z, :line_z, :marker_z, :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
# :overwrite_figure,
:polar,
# :normalize, :weights, :contours,
:aspect_ratio,
# :match_dimensions,
:tick_direction,
:framestyle,
:camera,
:contour_labels,
])
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_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :pentagon, :hline, :vline] #vcat(_allMarkers, Shape)
const _pgfplots_scale = [:identity, :ln, :log2, :log10]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# plotlyjs # plotlyjs
function add_backend_string(::PlotlyJSBackend) function _initialize_backend(pkg::PlotlyJSBackend)
""" @eval Main begin
using Pkg import PlotlyJS, ORCA
Pkg.add("PlotlyJS") export PlotlyJS
Pkg.add("Rsvg") end
import Blink
Blink.AtomShell.install()
"""
end end
const _plotlyjs_attr = _plotly_attr
const _plotlyjs_seriestype = _plotly_seriestype
const _plotlyjs_style = _plotly_style
const _plotlyjs_marker = _plotly_marker
const _plotlyjs_scale = _plotly_scale
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# pyplot # pyplot
function _initialize_backend(::PyPlotBackend) function _initialize_backend(::PyPlotBackend)
@eval Main begin @eval Main begin
import PyPlot, PyCall import PyPlot
import LaTeXStrings
export PyPlot export PyPlot
@ -396,25 +487,177 @@ function _initialize_backend(::PyPlotBackend)
end end
end end
function add_backend_string(::PyPlotBackend) const _pyplot_attr = merge_with_base_supported([
""" :annotations,
using Pkg :background_color_legend, :background_color_inside, :background_color_outside,
Pkg.add("PyPlot") :foreground_color_grid, :foreground_color_legend, :foreground_color_title,
Pkg.add("PyCall") :foreground_color_axis, :foreground_color_border, :foreground_color_guide, :foreground_color_text,
Pkg.add("LaTeXStrings") :label,
withenv("PYTHON" => "") do :linecolor, :linestyle, :linewidth, :linealpha,
Pkg.build("PyCall") :markershape, :markercolor, :markersize, :markeralpha,
Pkg.build("PyPlot") :markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
end :fillrange, :fillcolor, :fillalpha,
""" :bins, :bar_width, :bar_edges, :bar_position,
end :title, :title_location, :titlefont,
:window_title,
:guide, :guide_position, :lims, :ticks, :scale, :flip, :rotation,
:titlefontfamily, :titlefontsize, :titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefontcolor,
:grid, :gridalpha, :gridstyle, :gridlinewidth,
:legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry,
: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,
:stride,
:framestyle,
:tick_direction,
:camera,
:contour_labels,
])
const _pyplot_seriestype = [
:path, :steppre, :steppost, :shape, :straightline,
:scatter, :hexbin, #:histogram2d, :histogram,
# :bar,
:heatmap, :pie, :image,
:contour, :contour3d, :path3d, :scatter3d, :surface, :wireframe
]
const _pyplot_style = [:auto, :solid, :dash, :dot, :dashdot]
const _pyplot_marker = vcat(_allMarkers, :pixel)
const _pyplot_scale = [:identity, :ln, :log2, :log10]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# unicodeplots # unicodeplots
function add_backend_string(::UnicodePlotsBackend)
""" const _unicodeplots_attr = merge_with_base_supported([
using Pkg :label,
Pkg.add("UnicodePlots") :legend,
Pkg.build("UnicodePlots") :seriescolor,
""" :seriesalpha,
end :linestyle,
:markershape,
:bins,
:title,
:guide, :lims,
])
const _unicodeplots_seriestype = [
:path, :scatter, :straightline,
# :bar,
:shape,
:histogram2d,
:spy
]
const _unicodeplots_style = [:auto, :solid]
const _unicodeplots_marker = [:none, :auto, :circle]
const _unicodeplots_scale = [:identity]
# ------------------------------------------------------------------------------
# hdf5
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]
# ------------------------------------------------------------------------------
# inspectdr
const _inspectdr_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,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:markerstrokestyle, #Causes warning not to have it... what is this?
:fillcolor, :fillalpha, #:fillrange,
# :bins, :bar_width, :bar_edges, :bar_position,
:title, :title_location,
:window_title,
:guide, :lims, :scale, #:ticks, :flip, :rotation,
:titlefontfamily, :titlefontsize, :titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefontcolor,
:grid, :legend, #:colorbar,
# :marker_z,
# :line_z,
# :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
:overwrite_figure,
:polar,
# :normalize, :weights,
# :contours, :aspect_ratio,
:match_dimensions,
# :clims,
# :inset_subplots,
:dpi,
# :colorbar_title,
])
const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot]
const _inspectdr_seriestype = [
:path, :scatter, :shape, :straightline, #, :steppre, :steppost
]
#see: _allMarkers, _shape_keys
const _inspectdr_marker = Symbol[
:none, :auto,
:circle, :rect, :diamond,
:cross, :xcross,
:utriangle, :dtriangle, :rtriangle, :ltriangle,
:pentagon, :hexagon, :heptagon, :octagon,
:star4, :star5, :star6, :star7, :star8,
:vline, :hline, :+, :x,
]
const _inspectdr_scale = [:identity, :ln, :log2, :log10]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -16,16 +16,19 @@ Read from .hdf5 file using:
#==TODO #==TODO
=============================================================================== ===============================================================================
1. Support more features 1. Support more features.
- SeriesAnnotations & GridLayout known to be missing. - GridLayout known not to be working.
3. Improve error handling. 2. Improve error handling.
- Will likely crash if file format is off. - Will likely crash if file format is off.
2. Save data in a folder parallel to "plot". 3. Save data in a folder parallel to "plot".
- Will make it easier for users to locate data. - Will make it easier for users to locate data.
- Use HDF5 reference to link data? - Use HDF5 reference to link data?
3. Develop an actual versioned file format. 4. Develop an actual versioned file format.
- Should have some form of backward compatibility. - Should have some form of backward compatibility.
- Should be reliable for archival purposes. - Should be reliable for archival purposes.
5. Fix construction of plot object with hdf5plot_read.
- Not building object correctly when backends do not natively support
a certain feature (ex: :steppre)
==# ==#
import FixedPointNumbers: N0f8 #In core Julia import FixedPointNumbers: N0f8 #In core Julia
@ -56,53 +59,13 @@ const HDF5PLOT_PLOTREF = HDF5Plot_PlotRef(nothing)
#Simple sub-structures that can just be written out using _hdf5plot_gwritefields: #Simple sub-structures that can just be written out using _hdf5plot_gwritefields:
const HDF5PLOT_SIMPLESUBSTRUCT = Union{Font, BoundingBox, const HDF5PLOT_SIMPLESUBSTRUCT = Union{Font, BoundingBox,
GridLayout, RootLayout, ColorGradient, SeriesAnnotations, PlotText GridLayout, RootLayout, ColorGradient, SeriesAnnotations, PlotText,
Shape,
} }
#== #==
===============================================================================# ===============================================================================#
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 is_marker_supported(::HDF5Backend, shape::Shape) = true
if length(HDF5PLOT_MAP_TELEM2STR) < 1 if length(HDF5PLOT_MAP_TELEM2STR) < 1
@ -125,7 +88,8 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1
"GRIDLAYOUT" => GridLayout, "GRIDLAYOUT" => GridLayout,
"ROOTLAYOUT" => RootLayout, "ROOTLAYOUT" => RootLayout,
"SERIESANNOTATIONS" => SeriesAnnotations, "SERIESANNOTATIONS" => SeriesAnnotations,
# "PLOTTEXT" => PlotText, "PLOTTEXT" => PlotText,
"SHAPE" => Shape,
"COLORGRADIENT" => ColorGradient, "COLORGRADIENT" => ColorGradient,
"AXIS" => Axis, "AXIS" => Axis,
"SURFACE" => Surface, "SURFACE" => Surface,
@ -284,6 +248,14 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _hdf5plot_gwrite(grp, k::String, v) #Default function _hdf5plot_gwrite(grp, k::String, v) #Default
T = typeof(v)
if !(T <: Number || T <: String)
tstr = string(T)
path = HDF5.name(grp) * "/" * k
@info("Type not supported: $tstr\npath: $path")
# @show v
return
end
grp[k] = v grp[k] = v
_hdf5plot_writetype(grp, k, HDF5PlotNative) _hdf5plot_writetype(grp, k, HDF5PlotNative)
end end
@ -338,10 +310,6 @@ function _hdf5plot_gwrite(grp, k::String, v::Colorant)
end end
#Custom vector (when not using simple numeric type): #Custom vector (when not using simple numeric type):
function _hdf5plot_gwritearray(grp, k::String, v::Array{T}) where T 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) vgrp = HDF5.g_create(grp, k)
_hdf5plot_writetype(vgrp, Array) #ANY _hdf5plot_writetype(vgrp, Array) #ANY
sz = size(v) sz = size(v)
@ -351,7 +319,7 @@ function _hdf5plot_gwritearray(grp, k::String, v::Array{T}) where T
coord = lidx[iter] coord = lidx[iter]
elem = v[iter] elem = v[iter]
idxstr = join(coord, "_") idxstr = join(coord, "_")
_hdf5plot_gwrite(vgrp, "v$idxstr", v[iter]) _hdf5plot_gwrite(vgrp, "v$idxstr", elem)
end end
_hdf5plot_gwrite(vgrp, "dim", [sz...]) _hdf5plot_gwrite(vgrp, "dim", [sz...])
@ -402,10 +370,6 @@ end
# return # return
# end # end
function _hdf5plot_gwrite(grp, k::String, v::SeriesAnnotations)
#Currently no support for SeriesAnnotations
return
end
function _hdf5plot_gwrite(grp, k::String, v::Subplot) function _hdf5plot_gwrite(grp, k::String, v::Subplot)
grp = HDF5.g_create(grp, k) grp = HDF5.g_create(grp, k)
_hdf5plot_gwrite(grp, "index", v[:subplot_index]) _hdf5plot_gwrite(grp, "index", v[:subplot_index])
@ -528,6 +492,29 @@ function _hdf5plot_read(grp, k::String, T::Type{HDF5CTuple}, dtid)
v = _hdf5plot_read(grp, k, Array, dtid) v = _hdf5plot_read(grp, k, Array, dtid)
return tuple(v...) return tuple(v...)
end end
function _hdf5plot_read(grp, k::String, T::Type{PlotText}, dtid)
grp = HDF5.g_open(grp, k)
str = _hdf5plot_read(grp, "str")
font = _hdf5plot_read(grp, "font")
return PlotText(str, font)
end
function _hdf5plot_read(grp, k::String, T::Type{SeriesAnnotations}, dtid)
grp = HDF5.g_open(grp, k)
strs = _hdf5plot_read(grp, "strs")
font = _hdf5plot_read(grp, "font")
baseshape = _hdf5plot_read(grp, "baseshape")
scalefactor = _hdf5plot_read(grp, "scalefactor")
return SeriesAnnotations(strs, font, baseshape, scalefactor)
end
function _hdf5plot_read(grp, k::String, T::Type{Shape}, dtid)
grp = HDF5.g_open(grp, k)
x = _hdf5plot_read(grp, "x")
y = _hdf5plot_read(grp, "y")
return Shape(x, y)
end
function _hdf5plot_read(grp, k::String, T::Type{ColorGradient}, dtid) function _hdf5plot_read(grp, k::String, T::Type{ColorGradient}, dtid)
grp = HDF5.g_open(grp, k) grp = HDF5.g_open(grp, k)
@ -601,11 +588,6 @@ end
function _hdf5plot_read(sp::Subplot, subpath::String, f) function _hdf5plot_read(sp::Subplot, subpath::String, f)
f = f::HDF5.HDF5File #Assert 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")) grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list"))
nseries = _hdf5plot_readcount(grp) nseries = _hdf5plot_readcount(grp)
@ -617,6 +599,12 @@ function _hdf5plot_read(sp::Subplot, subpath::String, f)
_hdf5_merge!(sp.series_list[end].plotattributes, kwlist) _hdf5_merge!(sp.series_list[end].plotattributes, kwlist)
end end
#Perform after adding series... otherwise values get overwritten:
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/attr"))
kwlist = KW()
_hdf5plot_read(grp, kwlist)
_hdf5_merge!(sp.attr, kwlist)
return return
end end

View File

@ -14,61 +14,6 @@ Add in functionality to Plots.jl:
=# =#
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
#TODO: remove features
const _inspectdr_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,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:markerstrokestyle, #Causes warning not to have it... what is this?
:fillcolor, :fillalpha, #:fillrange,
# :bins, :bar_width, :bar_edges, :bar_position,
:title, :title_location,
:window_title,
:guide, :lims, :scale, #:ticks, :flip, :rotation,
:titlefontfamily, :titlefontsize, :titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefontcolor,
:grid, #:gridalpha, :gridstyle, :gridlinewidth, #alhpa & linewidth are per plot - not per subplot
:legend, #:legendtitle, :colorbar,
# :marker_z,
# :line_z,
# :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
:overwrite_figure,
:polar,
# :normalize, :weights,
# :contours, :aspect_ratio,
:match_dimensions,
# :clims,
# :inset_subplots,
:dpi,
# :colorbar_title,
])
const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot]
const _inspectdr_seriestype = [
:path, :scatter, :shape, :straightline, #, :steppre, :steppost
]
#see: _allMarkers, _shape_keys
const _inspectdr_marker = Symbol[
:none, :auto,
:circle, :rect, :diamond,
:cross, :xcross,
:utriangle, :dtriangle, :rtriangle, :ltriangle,
:pentagon, :hexagon, :heptagon, :octagon,
:star4, :star5, :star6, :star7, :star8,
:vline, :hline, :+, :x,
]
const _inspectdr_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::InspectDRBackend, shape::Shape) = true is_marker_supported(::InspectDRBackend, shape::Shape) = true
@ -349,8 +294,8 @@ function _inspectdr_setupsubplot(sp::Subplot{InspectDRBackend})
plot.xscale = _inspectdr_getscale(xaxis[:scale], false) plot.xscale = _inspectdr_getscale(xaxis[:scale], false)
strip.yscale = _inspectdr_getscale(yaxis[:scale], true) strip.yscale = _inspectdr_getscale(yaxis[:scale], true)
xmin, xmax = axis_limits(xaxis) xmin, xmax = axis_limits(sp, :x)
ymin, ymax = axis_limits(yaxis) ymin, ymax = axis_limits(sp, :y)
if ispolar(sp) if ispolar(sp)
#Plots.jl appears to give (xmin,xmax) ≜ (Θmin,Θmax) & (ymin,ymax) ≜ (rmin,rmax) #Plots.jl appears to give (xmin,xmax) ≜ (Θmin,Θmax) & (ymin,ymax) ≜ (rmin,rmax)
rmax = NaNMath.max(abs(ymin), abs(ymax)) rmax = NaNMath.max(abs(ymin), abs(ymax))

View File

@ -2,47 +2,6 @@
# significant contributions by: @pkofod # significant contributions by: @pkofod
const _pgfplots_attr = merge_with_base_supported([
:annotations,
:background_color_legend,
:background_color_inside,
# :background_color_outside,
# :foreground_color_legend,
:foreground_color_grid, :foreground_color_axis,
:foreground_color_text, :foreground_color_border,
:label,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
:fillrange, :fillcolor, :fillalpha,
:bins,
# :bar_width, :bar_edges,
:title,
# :window_title,
:guide, :guide_position, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :legend,
:colorbar, :colorbar_title,
:fill_z, :line_z, :marker_z, :levels,
# :ribbon, :quiver, :arrow,
# :orientation,
# :overwrite_figure,
:polar,
# :normalize, :weights, :contours,
:aspect_ratio,
# :match_dimensions,
:tick_direction,
:framestyle,
:camera,
:contour_labels,
])
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_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_linestyles = KW( const _pgfplots_linestyles = KW(
@ -67,7 +26,8 @@ const _pgfplots_markers = KW(
:star6 => "asterisk", :star6 => "asterisk",
:diamond => "diamond*", :diamond => "diamond*",
:pentagon => "pentagon*", :pentagon => "pentagon*",
:hline => "-" :hline => "-",
:vline => "|"
) )
const _pgfplots_legend_pos = KW( const _pgfplots_legend_pos = KW(
@ -211,7 +171,7 @@ function pgf_series(sp::Subplot, series::Series)
elseif st == :shape elseif st == :shape
shape_data(series) shape_data(series)
elseif ispolar(sp) elseif ispolar(sp)
theta, r = filter_radial_data(plotattributes[:x], plotattributes[:y], axis_limits(sp[:yaxis])) theta, r = plotattributes[:x], plotattributes[:y]
rad2deg.(theta), r rad2deg.(theta), r
else else
plotattributes[:x], plotattributes[:y] plotattributes[:x], plotattributes[:y]
@ -401,13 +361,13 @@ function pgf_axis(sp::Subplot, letter)
# limits # limits
# TODO: support zlims # TODO: support zlims
if letter != :z if letter != :z
lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(axis)) : axis_limits(axis) lims = ispolar(sp) && letter == :x ? rad2deg.(axis_limits(sp, :x)) : axis_limits(sp, letter)
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, :native)) && framestyle != :none if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none
ticks = get_ticks(axis) ticks = get_ticks(sp, axis)
#pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values #pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values
tick_values = ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] : ticks[1] tick_values = ispolar(sp) && letter == :x ? [rad2deg.(ticks[1])[3:end]..., 360, 405] : ticks[1]
push!(style, string(letter, "tick = {", join(tick_values,","), "}")) push!(style, string(letter, "tick = {", join(tick_values,","), "}"))
@ -439,14 +399,18 @@ function pgf_axis(sp::Subplot, letter)
# framestyle # framestyle
if framestyle in (:axes, :origin) if framestyle in (:axes, :origin)
axispos = framestyle == :axes ? "left" : "middle" axispos = framestyle == :axes ? "left" : "middle"
# the * after lines disables the arrows at the axes if axis[:draw_arrow]
push!(style, string("axis lines* = ", axispos)) push!(style, string("axis ", letter, " line = ", axispos))
else
# the * after line disables the arrow at the axis
push!(style, string("axis ", letter, " line* = ", axispos))
end
end end
if framestyle == :zerolines if framestyle == :zerolines
push!(style, string("extra ", letter, " ticks = 0")) push!(style, string("extra ", letter, " ticks = 0"))
push!(style, string("extra ", letter, " tick labels = ")) 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), "}}")) push!(style, string("extra ", letter, " tick style = {grid = major, major grid style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_border], 1.0), "}}"))
end end
if !axis[:showaxis] if !axis[:showaxis]
@ -455,7 +419,7 @@ function pgf_axis(sp::Subplot, letter)
if !axis[:showaxis] || framestyle in (:zerolines, :grid, :none) if !axis[:showaxis] || framestyle in (:zerolines, :grid, :none)
push!(style, string(letter, " axis line style = {draw opacity = 0}")) push!(style, string(letter, " axis line style = {draw opacity = 0}"))
else else
push!(style, string(letter, " axis line style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_axis], 1.0), "}")) push!(style, string(letter, " axis line style = {", pgf_linestyle(pgf_thickness_scaling(sp), axis[:foreground_color_border], 1.0), "}"))
end end
# return the style list and KW args # return the style list and KW args
@ -604,7 +568,7 @@ end
function _show(io::IO, mime::MIME"application/x-tex", plt::Plot{PGFPlotsBackend}) function _show(io::IO, mime::MIME"application/x-tex", plt::Plot{PGFPlotsBackend})
fn = tempname()*".tex" fn = tempname()*".tex"
PGFPlots.save(fn, backend_object(plt), include_preamble=false) PGFPlots.save(fn, backend_object(plt), include_preamble=plt.attr[:tex_output_standalone])
write(io, read(open(fn), String)) write(io, read(open(fn), String))
end end

View File

@ -1,60 +1,6 @@
# https://plot.ly/javascript/getting-started # https://plot.ly/javascript/getting-started
const _plotly_attr = merge_with_base_supported([
:annotations,
:background_color_legend, :background_color_inside, :background_color_outside,
:foreground_color_legend, :foreground_color_guide,
:foreground_color_grid, :foreground_color_axis,
:foreground_color_text, :foreground_color_border,
:foreground_color_title,
:label,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
:fillrange, :fillcolor, :fillalpha,
:bins,
:title, :title_location,
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign,
:titlefontcolor,
:legendfontfamily, :legendfontsize, :legendfontcolor,
:tickfontfamily, :tickfontsize, :tickfontcolor,
:guidefontfamily, :guidefontsize, :guidefontcolor,
:window_title,
:guide, :lims, :ticks, :scale, :flip, :rotation,
:tickfont, :guidefont, :legendfont,
:grid, :gridalpha, :gridlinewidth,
:legend, :colorbar, :colorbar_title,
:marker_z, :fill_z, :line_z, :levels,
:ribbon, :quiver,
:orientation,
# :overwrite_figure,
:polar,
:normalize, :weights,
# :contours,
:aspect_ratio,
:hover,
:inset_subplots,
:bar_width,
:clims,
:framestyle,
:tick_direction,
:camera,
:contour_labels,
])
const _plotly_seriestype = [
:path, :scatter, :pie, :heatmap,
:contour, :surface, :wireframe, :path3d, :scatter3d, :shape, :scattergl,
:straightline
]
const _plotly_style = [:auto, :solid, :dash, :dot, :dashdot]
const _plotly_marker = [
:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle,
:cross, :xcross, :pentagon, :hexagon, :octagon, :vline, :hline
]
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_framestyles = [:box, :axes, :zerolines, :grid, :none]
@ -71,30 +17,9 @@ end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
const plotly_remote_file_path = "https://cdn.plot.ly/plotly-latest.min.js"
const _plotly_js_path = joinpath(dirname(@__FILE__), "..", "..", "deps", "plotly-latest.min.js")
const _plotly_js_path_remote = "https://cdn.plot.ly/plotly-latest.min.js"
_js_code = open(read, _plotly_js_path, "r")
# borrowed from https://github.com/plotly/plotly.py/blob/2594076e29584ede2d09f2aa40a8a195b3f3fc66/plotly/offline/offline.py#L64-L71 c/o @spencerlyon2
_js_script = """
<script type='text/javascript'>
define('plotly', function(require, exports, module) {
$(_js_code)
});
require(['plotly'], function(Plotly) {
window.Plotly = Plotly;
});
</script>
"""
# if we're in IJulia call setupnotebook to load js and css
if isijulia()
display("text/html", _js_script)
end
# if isatom() # if isatom()
# import Atom # import Atom
# Atom.@msg evaljs(_js_code) # Atom.@msg evaljs(_js_code)
@ -102,8 +27,6 @@ end
using UUIDs using UUIDs
push!(_initialized_backends, :plotly) push!(_initialized_backends, :plotly)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
const _plotly_legend_pos = KW( const _plotly_legend_pos = KW(
@ -115,7 +38,7 @@ const _plotly_legend_pos = KW(
:bottomright => [1., 0.], :bottomright => [1., 0.],
:topright => [1., 1.], :topright => [1., 1.],
:topleft => [0., 1.] :topleft => [0., 1.]
) )
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(v::Tuple{S,T}) where {S<:Real, T<:Real} = v plotly_legend_pos(v::Tuple{S,T}) where {S<:Real, T<:Real} = v
@ -191,8 +114,8 @@ function plotly_apply_aspect_ratio(sp::Subplot, plotarea, pcts)
if aspect_ratio == :equal if aspect_ratio == :equal
aspect_ratio = 1.0 aspect_ratio = 1.0
end end
xmin,xmax = axis_limits(sp[:xaxis]) xmin,xmax = axis_limits(sp, :x)
ymin,ymax = axis_limits(sp[:yaxis]) ymin,ymax = axis_limits(sp, :y)
want_ratio = ((xmax-xmin) / (ymax-ymin)) / aspect_ratio want_ratio = ((xmax-xmin) / (ymax-ymin)) / aspect_ratio
parea_ratio = width(plotarea) / height(plotarea) parea_ratio = width(plotarea) / height(plotarea)
if want_ratio > parea_ratio if want_ratio > parea_ratio
@ -251,7 +174,7 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
ax[:tickangle] = -axis[:rotation] ax[:tickangle] = -axis[:rotation]
ax[:type] = plotly_scale(axis[:scale]) ax[:type] = plotly_scale(axis[:scale])
lims = axis_limits(axis) lims = axis_limits(sp, letter)
if axis[:ticks] != :native || axis[:lims] != :auto if axis[:ticks] != :native || axis[:lims] != :auto
ax[:range] = map(scalefunc(axis[:scale]), lims) ax[:range] = map(scalefunc(axis[:scale]), lims)
@ -263,14 +186,9 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
ax[:tickcolor] = framestyle in (:zerolines, :grid) || !axis[:showaxis] ? rgba_string(invisible()) : rgb_string(axis[:foreground_color_axis]) ax[:tickcolor] = framestyle in (:zerolines, :grid) || !axis[:showaxis] ? rgba_string(invisible()) : rgb_string(axis[:foreground_color_axis])
ax[:linecolor] = rgba_string(axis[:foreground_color_axis]) ax[:linecolor] = rgba_string(axis[:foreground_color_axis])
# flip
if axis[:flip]
ax[:range] = reverse(ax[:range])
end
# ticks # ticks
if axis[:ticks] != :native if axis[:ticks] != :native
ticks = get_ticks(axis) ticks = get_ticks(sp, axis)
ttype = ticksType(ticks) ttype = ticksType(ticks)
if ttype == :ticks if ttype == :ticks
ax[:tickmode] = "array" ax[:tickmode] = "array"
@ -285,20 +203,24 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
ax[:showgrid] = false ax[:showgrid] = false
end end
# flip
if axis[:flip]
ax[:range] = reverse(ax[:range])
end
ax ax
end end
function plotly_polaraxis(axis::Axis) function plotly_polaraxis(sp::Subplot, axis::Axis)
ax = KW( ax = KW(
:visible => axis[:showaxis], :visible => axis[:showaxis],
:showline => axis[:grid], :showline => axis[:grid],
) )
if axis[:letter] == :x if axis[:letter] == :x
ax[:range] = rad2deg.(axis_limits(axis)) ax[:range] = rad2deg.(axis_limits(sp, :x))
else else
ax[:range] = axis_limits(axis) ax[:range] = axis_limits(sp, :y)
ax[:orientation] = -90 ax[:orientation] = -90
end end
@ -360,8 +282,8 @@ function plotly_layout(plt::Plot)
), ),
) )
elseif ispolar(sp) elseif ispolar(sp)
plotattributes_out[Symbol("angularaxis$(spidx)")] = plotly_polaraxis(sp[:xaxis]) plotattributes_out[Symbol("angularaxis$(spidx)")] = plotly_polaraxis(sp, sp[:xaxis])
plotattributes_out[Symbol("radialaxis$(spidx)")] = plotly_polaraxis(sp[:yaxis]) plotattributes_out[Symbol("radialaxis$(spidx)")] = plotly_polaraxis(sp, sp[:yaxis])
else else
plotattributes_out[Symbol("xaxis$(x_idx)")] = plotly_axis(plt, sp[:xaxis], sp) plotattributes_out[Symbol("xaxis$(x_idx)")] = plotly_axis(plt, sp[:xaxis], sp)
# don't allow yaxis to be reupdated/reanchored in a linked subplot # don't allow yaxis to be reupdated/reanchored in a linked subplot
@ -431,7 +353,7 @@ end
function plotly_colorscale(grad::ColorGradient, α) function plotly_colorscale(grad::ColorGradient, α)
[[grad.values[i], rgba_string(plot_color(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::Colorant,α) = plotly_colorscale(_as_gradient(c),α)
function plotly_colorscale(c::AbstractVector{<:RGBA}, α) function plotly_colorscale(c::AbstractVector{<:RGBA}, α)
if length(c) == 1 if length(c) == 1
return [[0.0, rgba_string(plot_color(c[1], α))], [1.0, rgba_string(plot_color(c[1], α))]] return [[0.0, rgba_string(plot_color(c[1], α))], [1.0, rgba_string(plot_color(c[1], α))]]
@ -513,7 +435,7 @@ plotly_native_data(axis::Axis, a::Surface) = Surface(plotly_native_data(axis, a.
function plotly_convert_to_datetime(x::AbstractArray, formatter::Function) function plotly_convert_to_datetime(x::AbstractArray, formatter::Function)
if formatter == datetimeformatter if formatter == datetimeformatter
map(xi -> replace(formatter(xi), "T", " "), x) map(xi -> replace(formatter(xi), "T" => " "), x)
elseif formatter == dateformatter elseif formatter == dateformatter
map(xi -> string(formatter(xi), " 00:00:00"), x) map(xi -> string(formatter(xi), " 00:00:00"), x)
elseif formatter == timeformatter elseif formatter == timeformatter
@ -583,13 +505,13 @@ function plotly_series(plt::Plot, series::Series)
plotattributes_out[:showscale] = hascolorbar(sp) plotattributes_out[:showscale] = hascolorbar(sp)
elseif st == :contour elseif st == :contour
filled = isfilledcontour(series)
plotattributes_out[:type] = "contour" plotattributes_out[:type] = "contour"
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z
# plotattributes_out[:showscale] = series[:colorbar] != :none plotattributes_out[:ncontours] = series[:levels] + 2
plotattributes_out[:ncontours] = series[:levels] plotattributes_out[:contours] = KW(:coloring => filled ? "fill" : "lines", :showlabels => series[:contour_labels] == true)
plotattributes_out[:contours] = KW(:coloring => series[:fillrange] != nothing ? "fill" : "lines", :showlabels => series[:contour_labels] == true)
plotattributes_out[:colorscale] = plotly_colorscale(series[:linecolor], series[:linealpha]) plotattributes_out[:colorscale] = plotly_colorscale(series[:linecolor], series[:linealpha])
plotattributes_out[:showscale] = hascolorbar(sp) plotattributes_out[:showscale] = hascolorbar(sp) && hascolorbar(series)
elseif st in (:surface, :wireframe) elseif st in (:surface, :wireframe)
plotattributes_out[:type] = "surface" plotattributes_out[:type] = "surface"
@ -851,7 +773,7 @@ end
function plotly_polar!(plotattributes_out::KW, series::Series) function plotly_polar!(plotattributes_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])
theta, r = filter_radial_data(pop!(plotattributes_out, :x), pop!(plotattributes_out, :y), axis_limits(series[:subplot][:yaxis])) theta, r = pop!(plotattributes_out, :x), pop!(plotattributes_out, :y)
plotattributes_out[:t] = rad2deg.(theta) plotattributes_out[:t] = rad2deg.(theta)
plotattributes_out[:r] = r plotattributes_out[:r] = r
end end
@ -881,12 +803,27 @@ plotly_series_json(plt::Plot) = JSON.json(plotly_series(plt))
# ---------------------------------------------------------------- # ----------------------------------------------------------------
const _use_remote = Ref(false) const ijulia_initialized = Ref(false)
function html_head(plt::Plot{PlotlyBackend}) function html_head(plt::Plot{PlotlyBackend})
jsfilename = _use_remote[] ? _plotly_js_path_remote : ("file://" * _plotly_js_path) local_file = ("file://" * plotly_local_file_path)
# "<script src=\"$(joinpath(dirname(@__FILE__),"..","..","deps","plotly-latest.min.js"))\"></script>" plotly = use_local_dependencies[] ? local_file : plotly_remote_file_path
"<script src=\"$jsfilename\"></script>" if isijulia() && !ijulia_initialized[]
# using requirejs seems to be key to load a js depency in IJulia!
# https://requirejs.org/docs/start.html
# https://github.com/JuliaLang/IJulia.jl/issues/345
display("text/html", """
<script type="text/javascript">
requirejs([$(repr(plotly))], function(p) {
window.Plotly = p
});
</script>
""")
ijulia_initialized[] = true
end
# IJulia just needs one initialization
isijulia() && return ""
return "<script src=$(repr(plotly))></script>"
end end
function html_body(plt::Plot{PlotlyBackend}, style = nothing) function html_body(plt::Plot{PlotlyBackend}, style = nothing)
@ -929,11 +866,16 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _show(io::IO, ::MIME"application/vnd.plotly.v1+json", plot::Plot{PlotlyBackend})
function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend}) data = []
write(io, html_head(plt) * html_body(plt)) for series in plot.series_list
append!(data, plotly_series(plot, series))
end
layout = plotly_layout(plot)
JSON.print(io, Dict(:data => data, :layout => layout))
end end
function _display(plt::Plot{PlotlyBackend}) function _display(plt::Plot{PlotlyBackend})
standalone_html_window(plt) standalone_html_window(plt)
end end

View File

@ -1,18 +1,11 @@
# https://github.com/sglyon/PlotlyJS.jl
# https://github.com/spencerlyon2/PlotlyJS.jl
const _plotlyjs_attr = _plotly_attr
const _plotlyjs_seriestype = _plotly_seriestype
const _plotlyjs_style = _plotly_style
const _plotlyjs_marker = _plotly_marker
const _plotlyjs_scale = _plotly_scale
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
function _create_backend_figure(plt::Plot{PlotlyJSBackend}) function _create_backend_figure(plt::Plot{PlotlyJSBackend})
if !isplotnull() && plt[:overwrite_figure] && isa(current().o, PlotlyJS.SyncPlot) if !isplotnull() && plt[:overwrite_figure] && isa(current().o, PlotlyJS.SyncPlot)
PlotlyJS.SyncPlot(PlotlyJS.Plot(), current().o.view) PlotlyJS.SyncPlot(PlotlyJS.Plot(), options = current().o.options)
else else
PlotlyJS.plot() PlotlyJS.plot()
end end
@ -56,23 +49,16 @@ end
# ---------------------------------------------------------------- # ----------------------------------------------------------------
function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) = show(io, MIME("text/html"), plt.o)
if isijulia() && !_use_remote[] _show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="svg")
write(io, PlotlyJS.html_body(PlotlyJS.JupyterPlot(plt.o))) _show(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="png")
else _show(io::IO, ::MIME"application/pdf", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="pdf")
show(io, MIME("text/html"), plt.o) _show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="eps")
end
function _show(io::IO, m::MIME"application/vnd.plotly.v1+json", plt::Plot{PlotlyJSBackend})
show(io, m, plt.o)
end end
function plotlyjs_save_hack(io::IO, plt::Plot{PlotlyJSBackend}, ext::String)
tmpfn = tempname() * "." * ext
PlotlyJS.savefig(plt.o, tmpfn)
write(io, read(open(tmpfn)))
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"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")
function write_temp_html(plt::Plot{PlotlyJSBackend}) function write_temp_html(plt::Plot{PlotlyJSBackend})
filename = string(tempname(), ".html") filename = string(tempname(), ".html")

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,6 @@
# https://github.com/Evizero/UnicodePlots.jl # https://github.com/Evizero/UnicodePlots.jl
const _unicodeplots_attr = merge_with_base_supported([
:label,
:legend,
:seriescolor,
:seriesalpha,
:linestyle,
:markershape,
:bins,
:title,
:guide, :lims,
])
const _unicodeplots_seriestype = [
:path, :scatter, :straightline,
# :bar,
:shape,
:histogram2d,
:spy
]
const _unicodeplots_style = [:auto, :solid]
const _unicodeplots_marker = [:none, :auto, :circle]
const _unicodeplots_scale = [:identity]
# don't warn on unsupported... there's just too many warnings!! # don't warn on unsupported... there's just too many warnings!!
warnOnUnsupported_args(::UnicodePlotsBackend, plotattributes::KW) = nothing warnOnUnsupported_args(::UnicodePlotsBackend, plotattributes::KW) = nothing
@ -49,8 +27,8 @@ function rebuildUnicodePlot!(plt::Plot, width, height)
for sp in plt.subplots for sp in plt.subplots
xaxis = sp[:xaxis] xaxis = sp[:xaxis]
yaxis = sp[:yaxis] yaxis = sp[:yaxis]
xlim = axis_limits(xaxis) xlim = axis_limits(sp, :x)
ylim = axis_limits(yaxis) ylim = axis_limits(sp, :y)
# make vectors # make vectors
xlim = [xlim[1], xlim[2]] xlim = [xlim[1], xlim[2]]
@ -138,7 +116,7 @@ function addUnicodeSeries!(o, plotattributes::KW, addlegend::Bool, xlim, ylim)
x, y = if st == :straightline x, y = if st == :straightline
straightline_data(plotattributes) straightline_data(plotattributes)
elseif st == :shape elseif st == :shape
shape_data(series) shape_data(plotattributes)
else else
[collect(float(plotattributes[s])) for s in (:x, :y)] [collect(float(plotattributes[s])) for s in (:x, :y)]
end end

View File

@ -42,8 +42,14 @@ function write_temp_html(plt::AbstractPlot)
end end
function standalone_html_window(plt::AbstractPlot) function standalone_html_window(plt::AbstractPlot)
old = use_local_dependencies[] # save state to restore afterwards
# if we open a browser ourself, we can host local files, so
# when we have a local plotly downloaded this is the way to go!
use_local_dependencies[] = isfile(plotly_local_file_path)
filename = write_temp_html(plt) filename = write_temp_html(plt)
open_browser_window(filename) open_browser_window(filename)
# restore for other backends
use_local_dependencies[] = old
end end
# uses wkhtmltopdf/wkhtmltoimage: http://wkhtmltopdf.org/downloads.html # uses wkhtmltopdf/wkhtmltoimage: http://wkhtmltopdf.org/downloads.html

View File

@ -1,7 +1,7 @@
const P2 = FixedSizeArrays.Vec{2,Float64} const P2 = GeometryTypes.Point2{Float64}
const P3 = FixedSizeArrays.Vec{3,Float64} const P3 = GeometryTypes.Point3{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))
@ -256,8 +256,24 @@ mutable struct Font
color::Colorant color::Colorant
end end
"Create a Font from a list of unordered features" """
function font(args...) font(args...)
Create a Font from a list of features. Values may be specified either as
arguments (which are distinguished by type/value) or as keyword arguments.
# Arguments
- `family`: AbstractString. "serif" or "sans-serif" or "monospace"
- `pointsize`: Integer. Size of font in points
- `halign`: Symbol. Horizontal alignment (:hcenter, :left, or :right)
- `valign`: Symbol. Vertical aligment (:vcenter, :top, or :bottom)
- `rotation`: Real. Angle of rotation for text in degrees (use a non-integer type)
- `color`: Colorant or Symbol
# Examples
```julia-repl
julia> font(8)
julia> font(family="serif",halign=:center,rotation=45.0)
```
"""
function font(args...;kw...)
# defaults # defaults
family = "sans-serif" family = "sans-serif"
@ -301,6 +317,32 @@ function font(args...)
end end
end end
for symbol in keys(kw)
if symbol == :family
family = kw[:family]
elseif symbol == :pointsize
pointsize = kw[:pointsize]
elseif symbol == :halign
halign = kw[:halign]
if halign == :center
halign = :hcenter
end
@assert halign in (:hcenter, :left, :right)
elseif symbol == :valign
valign = kw[:valign]
if valign == :center
valign = :vcenter
end
@assert valign in (:vcenter, :top, :bottom)
elseif symbol == :rotation
rotation = kw[:rotation]
elseif symbol == :color
color = parse(Colorant, kw[:color])
else
@warn("Unused font kwarg: $symbol")
end
end
Font(family, pointsize, halign, valign, rotation, color) Font(family, pointsize, halign, valign, rotation, color)
end end
@ -344,15 +386,16 @@ end
PlotText(str) = PlotText(string(str), font()) PlotText(str) = PlotText(string(str), font())
""" """
text(string, args...) text(string, args...; kw...)
Create a PlotText object wrapping a string with font info, for plot annotations Create a PlotText object wrapping a string with font info, for plot annotations.
`args` and `kw` are passed to `font`.
""" """
text(t::PlotText) = t text(t::PlotText) = t
text(t::PlotText, font::Font) = PlotText(t.str, font) 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...;kw...)
PlotText(string(str), font(args...)) PlotText(string(str), font(args...;kw...))
end end
Base.length(t::PlotText) = length(t.str) Base.length(t::PlotText) = length(t.str)
@ -491,7 +534,7 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
# with a list of custom shapes for each # with a list of custom shapes for each
msw,msh = anns.scalefactor msw,msh = anns.scalefactor
msize = Float64[] msize = Float64[]
shapes = Vector{Shape}(length(anns.strs)) shapes = Vector{Shape}(undef, length(anns.strs))
for i in eachindex(anns.strs) for i in eachindex(anns.strs)
str = _cycle(anns.strs,i) str = _cycle(anns.strs,i)
@ -509,7 +552,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(anns.baseshape, i)
shapes[i] = scale(baseshape, msw*xscale/maxscale, msh*yscale/maxscale, (0,0)) shapes[i] = scale(baseshape, msw*xscale/maxscale, msh*yscale/maxscale, (0,0))
end end
series[:markershape] = shapes series[:markershape] = shapes
@ -554,7 +597,7 @@ function process_annotation(sp::Subplot, xs, ys, labs, font = font())
alphabet = "abcdefghijklmnopqrstuvwxyz" alphabet = "abcdefghijklmnopqrstuvwxyz"
push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font))) push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
else else
push!(anns, (x, y, isa(lab, PlotText) ? lab : text(lab, font))) push!(anns, (x, y, isa(lab, PlotText) ? lab : isa(lab, Tuple) ? text(lab...) : text(lab, font)))
end end
end end
anns anns
@ -569,7 +612,7 @@ function process_annotation(sp::Subplot, positions::Union{AVec{Symbol},Symbol},
alphabet = "abcdefghijklmnopqrstuvwxyz" alphabet = "abcdefghijklmnopqrstuvwxyz"
push!(anns, (pos, text(string("(", alphabet[sp[:subplot_index]], ")"), font))) push!(anns, (pos, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
else else
push!(anns, (pos, isa(lab, PlotText) ? lab : text(lab, font))) push!(anns, (pos, isa(lab, PlotText) ? lab : isa(lab, Tuple) ? text(lab...) : text(lab, font)))
end end
end end
anns anns
@ -736,7 +779,7 @@ end
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
"create a BezierCurve for plotting" "create a BezierCurve for plotting"
mutable struct BezierCurve{T <: FixedSizeArrays.Vec} mutable struct BezierCurve{T <: GeometryTypes.Point}
control_points::Vector{T} control_points::Vector{T}
end end
@ -749,9 +792,6 @@ function (bc::BezierCurve)(t::Real)
p p
end end
# 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
# 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
coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, range(range..., stop=n, length=50)) coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, range(range..., stop=n, length=50))

View File

@ -1,208 +0,0 @@
# https://github.com/bokeh/Bokeh.jl
supported_attrs(::BokehBackend) = merge_with_base_supported([
# :annotations,
# :axis,
# :background_color,
:linecolor,
# :color_palette,
# :fillrange,
# :fillcolor,
# :fillalpha,
# :foreground_color,
:group,
# :label,
# :layout,
# :legend,
:seriescolor, :seriesalpha,
:linestyle,
:seriestype,
:linewidth,
# :linealpha,
:markershape,
:markercolor,
:markersize,
# :markeralpha,
# :markerstrokewidth,
# :markerstrokecolor,
# :markerstrokestyle,
# :n,
# :bins,
# :nc,
# :nr,
# :pos,
# :smooth,
# :show,
:size,
:title,
# :window_title,
:x,
# :xguide,
# :xlims,
# :xticks,
:y,
# :yguide,
# :ylims,
# :yrightlabel,
# :yticks,
# :xscale,
# :yscale,
# :xflip,
# :yflip,
# :z,
# :tickfont,
# :guidefont,
# :legendfont,
# :grid,
# :surface,
# :levels,
])
supported_types(::BokehBackend) = [:path, :scatter]
supported_styles(::BokehBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supported_markers(::BokehBackend) = [:none, :auto, :circle, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5]
supported_scales(::BokehBackend) = [:identity, :ln]
is_subplot_supported(::BokehBackend) = false
# --------------------------------------------------------------------------------------
function _initialize_backend(::BokehBackend; kw...)
@eval begin
@warn("Bokeh is no longer supported... many features will likely be broken.")
import Bokeh
export Bokeh
end
end
const _glyphtypes = KW(
:circle => :Circle,
:rect => :Square,
:diamond => :Diamond,
:utriangle => :Triangle,
:dtriangle => :InvertedTriangle,
# :pentagon =>
# :hexagon =>
# :heptagon =>
# :octagon =>
:cross => :Cross,
:xcross => :X,
:star5 => :Asterisk,
)
function bokeh_glyph_type(plotattributes::KW)
st = plotattributes[:seriestype]
mt = plotattributes[:markershape]
if st == :scatter && mt == :none
mt = :circle
end
# if we have a marker, use that
if st == :scatter || mt != :none
return _glyphtypes[mt]
end
# otherwise return a line
return :Line
end
function get_stroke_vector(linestyle::Symbol)
dash = 12
dot = 3
gap = 2
linestyle == :solid && return Int[]
linestyle == :dash && return Int[dash, gap]
linestyle == :dot && return Int[dot, gap]
linestyle == :dashdot && return Int[dash, gap, dot, gap]
linestyle == :dashdotdot && return Int[dash, gap, dot, gap, dot, gap]
error("unsupported linestyle: ", linestyle)
end
# ---------------------------------------------------------------------------
# function _create_plot(pkg::BokehBackend, plotattributes::KW)
function _create_backend_figure(plt::Plot{BokehBackend})
# TODO: create the window/canvas/context that is the plot within the backend (call it `o`)
# TODO: initialize the plot... title, xlabel, bgcolor, etc
datacolumns = Bokeh.BokehDataSet[]
tools = Bokeh.tools()
filename = tempname() * ".html"
title = plt.attr[:title]
w, h = plt.attr[:size]
xaxis_type = plt.attr[:xscale] == :log10 ? :log : :auto
yaxis_type = plt.attr[:yscale] == :log10 ? :log : :auto
# legend = plt.attr[:legend] ? xxxx : nothing
legend = nothing
extra_args = KW() # TODO: we'll put extra settings (xlim, etc) here
Bokeh.Plot(datacolumns, tools, filename, title, w, h, xaxis_type, yaxis_type, legend) #, extra_args)
# Plot(bplt, pkg, 0, plotattributes, KW[])
end
# function _series_added(::BokehBackend, plt::Plot, plotattributes::KW)
function _series_added(plt::Plot{BokehBackend}, series::Series)
bdata = Dict{Symbol, Vector}(:x => collect(series.plotattributes[:x]), :y => collect(series.plotattributes[:y]))
glyph = Bokeh.Bokehjs.Glyph(
glyphtype = bokeh_glyph_type(plotattributes),
linecolor = webcolor(plotattributes[:linecolor]), # shape's stroke or line color
linewidth = plotattributes[:linewidth], # shape's stroke width or line width
fillcolor = webcolor(plotattributes[:markercolor]),
size = ceil(Int, plotattributes[:markersize] * 2.5), # magic number 2.5 to keep in same scale as other backends
dash = get_stroke_vector(plotattributes[:linestyle])
)
legend = nothing # TODO
push!(plt.o.datacolumns, Bokeh.BokehDataSet(bdata, glyph, legend))
# push!(plt.seriesargs, plotattributes)
# plt
end
# ----------------------------------------------------------------
# TODO: override this to update plot items (title, xlabel, etc) after creation
function _update_plot_object(plt::Plot{BokehBackend}, plotattributes::KW)
end
# ----------------------------------------------------------------
# accessors for x/y data
# function getxy(plt::Plot{BokehBackend}, i::Int)
# series = plt.o.datacolumns[i].data
# series[:x], series[:y]
# end
#
# function setxy!(plt::Plot{BokehBackend}, xy::Tuple{X,Y}, i::Integer)
# series = plt.o.datacolumns[i].data
# series[:x], series[:y] = xy
# plt
# end
# ----------------------------------------------------------------
# ----------------------------------------------------------------
function Base.show(io::IO, ::MIME"image/png", plt::AbstractPlot{BokehBackend})
# TODO: write a png to io
@warn("mime png not implemented")
end
function Base.display(::PlotsDisplay, plt::Plot{BokehBackend})
Bokeh.showplot(plt.o)
end
# function Base.display(::PlotsDisplay, plt::Subplot{BokehBackend})
# # TODO: display/show the subplot
# end

View File

@ -1,744 +0,0 @@
# https://github.com/dcjones/Gadfly.jl
supported_attrs(::GadflyBackend) = merge_with_base_supported([
:annotations,
:background_color, :foreground_color, :color_palette,
:group, :label, :seriestype,
:seriescolor, :seriesalpha,
:linecolor, :linestyle, :linewidth, :linealpha,
:markershape, :markercolor, :markersize, :markeralpha,
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
:fillrange, :fillcolor, :fillalpha,
:bins, :n, :nc, :nr, :layout, :smooth,
:title, :window_title, :show, :size,
:x, :xguide, :xlims, :xticks, :xscale, :xflip,
:y, :yguide, :ylims, :yticks, :yscale, :yflip,
:z,
:tickfont, :guidefont, :legendfont,
:grid, :legend, :colorbar,
:marker_z, :levels,
:xerror, :yerror,
:ribbon, :quiver,
:orientation,
])
supported_types(::GadflyBackend) = [
:path,
:scatter, :hexbin,
:bar,
:contour, :shape
]
supported_styles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supported_markers(::GadflyBackend) = vcat(_allMarkers, Shape)
supported_scales(::GadflyBackend) = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
is_subplot_supported(::GadflyBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::GadflyBackend; kw...)
@eval begin
import Gadfly, Compose
export Gadfly, Compose
include(joinpath(dirname(@__FILE__), "gadfly_shapes.jl"))
end
end
# ---------------------------------------------------------------------------
# immutable MissingVec <: AbstractVector{Float64} end
# Base.size(v::MissingVec) = (1,)
# Base.getindex(v::MissingVec, i::Integer) = 0.0
function createGadflyPlotObject(plotattributes::KW)
gplt = Gadfly.Plot()
gplt.mapping = Dict()
gplt.data_source = Gadfly.DataFrames.DataFrame()
# gplt.layers = gplt.layers[1:0]
gplt.layers = [Gadfly.layer(Gadfly.Geom.point(tag=:remove), x=zeros(1), y=zeros(1));] # x=MissingVec(), y=MissingVec());]
gplt.guides = Gadfly.GuideElement[Gadfly.Guide.xlabel(plotattributes[:xguide]),
Gadfly.Guide.ylabel(plotattributes[:yguide]),
Gadfly.Guide.title(plotattributes[:title])]
gplt
end
# ---------------------------------------------------------------------------
function getLineGeom(plotattributes::KW)
st = plotattributes[:seriestype]
xbins, ybins = maketuple(plotattributes[:bins])
if st == :hexb
Gadfly.Geom.hexbin(xbincount = xbins, ybincount = ybins)
elseif st == :histogram2d
Gadfly.Geom.histogram2d(xbincount = xbins, ybincount = ybins)
elseif st == :histogram
Gadfly.Geom.histogram(bincount = xbins,
orientation = isvertical(plotattributes) ? :vertical : :horizontal,
position = plotattributes[:bar_position] == :stack ? :stack : :dodge)
elseif st == :path
Gadfly.Geom.path
elseif st in (:bar, :sticks)
Gadfly.Geom.bar
elseif st == :steppost
Gadfly.Geom.step
elseif st == :steppre
Gadfly.Geom.step(direction = :vh)
elseif st == :hline
Gadfly.Geom.hline
elseif st == :vline
Gadfly.Geom.vline
elseif st == :contour
Gadfly.Geom.contour(levels = plotattributes[:levels])
# elseif st == :shape
# Gadfly.Geom.polygon(fill = true, preserve_order = true)
else
nothing
end
end
function get_extra_theme_args(plotattributes::KW, k::Symbol)
# gracefully handles old Gadfly versions
extra_theme_args = KW()
try
extra_theme_args[:line_style] = Gadfly.get_stroke_vector(plotattributes[k])
catch err
if string(err) == "UndefVarError(:get_stroke_vector)"
Base.warn_once("Gadfly.get_stroke_vector failed... do you have an old version of Gadfly?")
else
rethrow()
end
end
extra_theme_args
end
function getGadflyLineTheme(plotattributes::KW)
st = plotattributes[:seriestype]
lc = convertColor(getColor(plotattributes[:linecolor]), plotattributes[:linealpha])
fc = convertColor(getColor(plotattributes[:fillcolor]), plotattributes[:fillalpha])
Gadfly.Theme(;
default_color = (st in (:histogram,:histogram2d,:hexbin,:bar,:sticks) ? fc : lc),
line_width = (st == :sticks ? 1 : plotattributes[:linewidth]) * Gadfly.px,
# line_style = Gadfly.get_stroke_vector(plotattributes[:linestyle]),
lowlight_color = x->RGB(fc), # fill/ribbon
lowlight_opacity = alpha(fc), # fill/ribbon
bar_highlight = RGB(lc), # bars
get_extra_theme_args(plotattributes, :linestyle)...
)
end
# add a line as a new layer
function addGadflyLine!(plt::Plot, numlayers::Int, plotattributes::KW, geoms...)
gplt = getGadflyContext(plt)
gfargs = vcat(geoms..., getGadflyLineTheme(plotattributes))
kwargs = KW()
st = plotattributes[:seriestype]
# add a fill?
if plotattributes[:fillrange] != nothing && st != :contour
fillmin, fillmax = map(makevec, maketuple(plotattributes[:fillrange]))
nmin, nmax = length(fillmin), length(fillmax)
kwargs[:ymin] = Float64[min(y, fillmin[mod1(i, nmin)], fillmax[mod1(i, nmax)]) for (i,y) in enumerate(plotattributes[:y])]
kwargs[:ymax] = Float64[max(y, fillmin[mod1(i, nmin)], fillmax[mod1(i, nmax)]) for (i,y) in enumerate(plotattributes[:y])]
push!(gfargs, Gadfly.Geom.ribbon)
end
if st in (:hline, :vline)
kwargs[st == :hline ? :yintercept : :xintercept] = plotattributes[:y]
else
if st == :sticks
w = 0.01 * mean(diff(plotattributes[:x]))
kwargs[:xmin] = plotattributes[:x] - w
kwargs[:xmax] = plotattributes[:x] + w
elseif st == :contour
kwargs[:z] = plotattributes[:z].surf
addGadflyContColorScale(plt, plotattributes[:linecolor])
end
kwargs[:x] = plotattributes[st == :histogram ? :y : :x]
kwargs[:y] = plotattributes[:y]
end
# # add the layer
Gadfly.layer(gfargs...; order=numlayers, kwargs...)
end
# ---------------------------------------------------------------------------
get_shape(sym::Symbol) = _shapes[sym]
get_shape(shape::Shape) = shape
# extract the underlying ShapeGeometry object(s)
getMarkerGeom(shapes::AVec) = gadflyshape(map(get_shape, shapes))
getMarkerGeom(other) = gadflyshape(get_shape(other))
# getMarkerGeom(shape::Shape) = gadflyshape(shape)
# getMarkerGeom(shape::Symbol) = gadflyshape(_shapes[shape])
# getMarkerGeom(shapes::AVec) = gadflyshape(map(gadflyshape, shapes)) # map(getMarkerGeom, shapes)
function getMarkerGeom(plotattributes::KW)
if plotattributes[:seriestype] == :shape
Gadfly.Geom.polygon(fill = true, preserve_order = true)
else
getMarkerGeom(plotattributes[:markershape])
end
end
function getGadflyMarkerTheme(plotattributes::KW, attr::KW)
c = getColor(plotattributes[:markercolor])
α = plotattributes[:markeralpha]
if α != nothing
c = RGBA(RGB(c), α)
end
ms = plotattributes[:markersize]
ms = if typeof(ms) <: AVec
@warn("Gadfly doesn't support variable marker sizes... using the average: $(mean(ms))")
mean(ms) * Gadfly.px
else
ms * Gadfly.px
end
Gadfly.Theme(;
default_color = c,
default_point_size = ms,
discrete_highlight_color = c -> RGB(getColor(plotattributes[:markerstrokecolor])),
highlight_width = plotattributes[:markerstrokewidth] * Gadfly.px,
line_width = plotattributes[:markerstrokewidth] * Gadfly.px,
# get_extra_theme_args(plotattributes, :markerstrokestyle)...
)
end
function addGadflyContColorScale(plt::Plot{GadflyBackend}, c)
plt.attr[:colorbar] == :none && return
if !isa(c, ColorGradient)
c = default_gradient()
end
push!(getGadflyContext(plt).scales, Gadfly.Scale.ContinuousColorScale(p -> RGB(getColorZ(c, p))))
end
function addGadflyMarker!(plt::Plot, numlayers::Int, plotattributes::KW, attr::KW, geoms...)
gfargs = vcat(geoms..., getGadflyMarkerTheme(plotattributes, attr), getMarkerGeom(plotattributes))
kwargs = KW()
# handle continuous color scales for the markers
zcolor = plotattributes[:marker_z]
if zcolor != nothing && typeof(zcolor) <: AVec
kwargs[:color] = zcolor
addGadflyContColorScale(plt, plotattributes[:markercolor])
end
Gadfly.layer(gfargs...; x = plotattributes[:x], y = plotattributes[:y], order=numlayers, kwargs...)
end
# ---------------------------------------------------------------------------
function addToGadflyLegend(plt::Plot, plotattributes::KW)
if plt.attr[:legend] != :none && plotattributes[:label] != ""
gplt = getGadflyContext(plt)
# add the legend if needed
if all(g -> !isa(g, Gadfly.Guide.ManualColorKey), gplt.guides)
pushfirst!(gplt.guides, Gadfly.Guide.manual_color_key("", AbstractString[], Color[]))
end
# now add the series to the legend
for guide in gplt.guides
if isa(guide, Gadfly.Guide.ManualColorKey)
# TODO: there's a BUG in gadfly if you pass in the same color more than once,
# since gadfly will call unique(colors), but doesn't also merge the rows that match
# Should ensure from this side that colors which are the same are merged together
c = getColor(plotattributes[plotattributes[:markershape] == :none ? :linecolor : :markercolor])
foundit = false
# extend the label if we found this color
for i in 1:length(guide.colors)
if RGB(c) == guide.colors[i]
guide.labels[i] *= ", " * plotattributes[:label]
foundit = true
end
end
# didn't find the color, so add a new entry into the legend
if !foundit
push!(guide.labels, plotattributes[:label])
push!(guide.colors, c)
end
end
end
end
end
getGadflySmoothing(smooth::Bool) = smooth ? [Gadfly.Geom.smooth(method=:lm)] : Any[]
getGadflySmoothing(smooth::Real) = [Gadfly.Geom.smooth(method=:loess, smoothing=float(smooth))]
function addGadflySeries!(plt::Plot, plotattributes::KW)
layers = Gadfly.Layer[]
gplt = getGadflyContext(plt)
# add a regression line?
# TODO: make more flexible
smooth = getGadflySmoothing(plotattributes[:smooth])
# lines
geom = getLineGeom(plotattributes)
if geom != nothing
prepend!(layers, addGadflyLine!(plt, length(gplt.layers), plotattributes, geom, smooth...))
smooth = Any[] # don't add a regression for markers too
end
# special handling for ohlc and scatter
st = plotattributes[:seriestype]
# if st == :ohlc
# error("Haven't re-implemented after refactoring")
if st in (:histogram2d, :hexbin) && (isa(plotattributes[:fillcolor], ColorGradient) || isa(plotattributes[:fillcolor], ColorFunction))
push!(gplt.scales, Gadfly.Scale.ContinuousColorScale(p -> RGB(getColorZ(plotattributes[:fillcolor], p))))
elseif st == :scatter && plotattributes[:markershape] == :none
plotattributes[:markershape] = :circle
end
# markers
if plotattributes[:markershape] != :none || st == :shape
prepend!(layers, addGadflyMarker!(plt, length(gplt.layers), plotattributes, plt.attr, smooth...))
end
st in (:histogram2d, :hexbin, :contour) || addToGadflyLegend(plt, plotattributes)
# now save the layers that apply to this series
plotattributes[:gadflylayers] = layers
prepend!(gplt.layers, layers)
end
# ---------------------------------------------------------------------------
# NOTE: I'm leaving this here and commented out just in case I want to implement again... it was hacky code to create multi-colored line segments
# # colorgroup
# z = plotattributes[:z]
# # handle line segments of different colors
# cscheme = plotattributes[:linecolor]
# if isa(cscheme, ColorVector)
# # create a color scale, and set the color group to the index of the color
# push!(gplt.scales, Gadfly.Scale.color_discrete_manual(cscheme.v...))
# # this is super weird, but... oh well... for some reason this creates n separate line segments...
# # create a list of vertices that go: [x1,x2,x2,x3,x3, ... ,xi,xi, ... xn,xn] (same for y)
# # then the vector passed to the "color" keyword should be a vector: [1,1,2,2,3,3,4,4, ..., i,i, ... , n,n]
# csindices = Int[mod1(i,length(cscheme.v)) for i in 1:length(plotattributes[:y])]
# cs = collect(repeat(csindices', 2, 1))[1:end-1]
# grp = collect(repeat((1:length(plotattributes[:y]))', 2, 1))[1:end-1]
# plotattributes[:x], plotattributes[:y] = map(createSegments, (plotattributes[:x], plotattributes[:y]))
# colorgroup = [(:linecolor, cs), (:group, grp)]
# ---------------------------------------------------------------------------
function addGadflyTicksGuide(gplt, ticks, isx::Bool)
ticks == :auto && return
# remove the ticks?
if ticks in (:none, false, nothing)
return addOrReplace(gplt.guides, isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks; label=false)
end
ttype = ticksType(ticks)
# just the values... put ticks here, but use standard labels
if ttype == :ticks
gtype = isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks
replaceType(gplt.guides, gtype(ticks = collect(ticks)))
# set the ticks and the labels
# Note: this is pretty convoluted, but I think it works. We set the ticks using Gadfly.Guide,
# and then set the label function (wraps a dict lookup) through a continuous Gadfly.Scale.
elseif ttype == :ticks_and_labels
gtype = isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks
replaceType(gplt.guides, gtype(ticks = collect(ticks[1])))
# # TODO add xtick_label function (given tick, return label??)
# # Scale.x_discrete(; labels=nothing, levels=nothing, order=nothing)
# filterGadflyScale(gplt, isx)
# gfunc = isx ? Gadfly.Scale.x_discrete : Gadfly.Scale.y_discrete
# labelmap = Dict(zip(ticks...))
# labelfunc = val -> labelmap[val]
# push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc))
filterGadflyScale(gplt, isx)
gfunc = isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous
labelmap = Dict(zip(ticks...))
labelfunc = val -> labelmap[val]
push!(gplt.scales, gfunc(labels = labelfunc))
else
error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks)
end
end
continuousAndSameAxis(scale, isx::Bool) = isa(scale, Gadfly.Scale.ContinuousScale) && scale.vars[1] == (isx ? :x : :y)
filterGadflyScale(gplt, isx::Bool) = filter!(scale -> !continuousAndSameAxis(scale, isx), gplt.scales)
function getGadflyScaleFunction(plotattributes::KW, isx::Bool)
scalekey = isx ? :xscale : :yscale
hasScaleKey = haskey(plotattributes, scalekey)
if hasScaleKey
scale = plotattributes[scalekey]
scale == :ln && return isx ? Gadfly.Scale.x_log : Gadfly.Scale.y_log, hasScaleKey, log
scale == :log2 && return isx ? Gadfly.Scale.x_log2 : Gadfly.Scale.y_log2, hasScaleKey, log2
scale == :log10 && return isx ? Gadfly.Scale.x_log10 : Gadfly.Scale.y_log10, hasScaleKey, log10
scale == :asinh && return isx ? Gadfly.Scale.x_asinh : Gadfly.Scale.y_asinh, hasScaleKey, asinh
scale == :sqrt && return isx ? Gadfly.Scale.x_sqrt : Gadfly.Scale.y_sqrt, hasScaleKey, sqrt
end
isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous, hasScaleKey, identity
end
function addGadflyLimitsScale(gplt, plotattributes::KW, isx::Bool)
gfunc, hasScaleKey, func = getGadflyScaleFunction(plotattributes, isx)
# do we want to add min/max limits for the axis?
limsym = isx ? :xlims : :ylims
limargs = Any[]
# map :auto to nothing, otherwise add to limargs
lims = get(plotattributes, limsym, :auto)
if lims == :auto
lims = nothing
else
if limsType(lims) == :limits
push!(limargs, (:minvalue, min(lims...)))
push!(limargs, (:maxvalue, max(lims...)))
else
error("Invalid input for $(isx ? "xlims" : "ylims"): ", lims)
end
end
# replace any current scales with this one
if hasScaleKey || !isempty(limargs)
filterGadflyScale(gplt, isx)
push!(gplt.scales, gfunc(; limargs...))
end
lims, func
end
function updateGadflyAxisFlips(gplt, plotattributes::KW, xlims, ylims, xfunc, yfunc)
if isa(gplt.coord, Gadfly.Coord.Cartesian)
gplt.coord = Gadfly.Coord.cartesian(
gplt.coord.xvars,
gplt.coord.yvars;
xmin = xlims == nothing ? gplt.coord.xmin : xfunc(minimum(xlims)),
xmax = xlims == nothing ? gplt.coord.xmax : xfunc(maximum(xlims)),
ymin = ylims == nothing ? gplt.coord.ymin : yfunc(minimum(ylims)),
ymax = ylims == nothing ? gplt.coord.ymax : yfunc(maximum(ylims)),
xflip = get(plotattributes, :xflip, gplt.coord.xflip),
yflip = get(plotattributes, :yflip, gplt.coord.yflip),
fixed = gplt.coord.fixed,
aspect_ratio = gplt.coord.aspect_ratio,
raster = gplt.coord.raster
)
else
gplt.coord = Gadfly.Coord.Cartesian(
xflip = get(plotattributes, :xflip, false),
yflip = get(plotattributes, :yflip, false)
)
end
end
function findGuideAndSet(gplt, t::DataType, args...; kw...)
for (i,guide) in enumerate(gplt.guides)
if isa(guide, t)
gplt.guides[i] = t(args...; kw...)
end
end
end
function updateGadflyGuides(plt::Plot, plotattributes::KW)
gplt = getGadflyContext(plt)
haskey(plotattributes, :title) && findGuideAndSet(gplt, Gadfly.Guide.title, string(plotattributes[:title]))
haskey(plotattributes, :xguide) && findGuideAndSet(gplt, Gadfly.Guide.xlabel, string(plotattributes[:xguide]))
haskey(plotattributes, :yguide) && findGuideAndSet(gplt, Gadfly.Guide.ylabel, string(plotattributes[:yguide]))
xlims, xfunc = addGadflyLimitsScale(gplt, plotattributes, true)
ylims, yfunc = addGadflyLimitsScale(gplt, plotattributes, false)
ticks = get(plotattributes, :xticks, :auto)
if ticks == :none
_remove_axis(plt, true)
else
addGadflyTicksGuide(gplt, ticks, true)
end
ticks = get(plotattributes, :yticks, :auto)
if ticks == :none
_remove_axis(plt, false)
else
addGadflyTicksGuide(gplt, ticks, false)
end
updateGadflyAxisFlips(gplt, plotattributes, xlims, ylims, xfunc, yfunc)
end
function updateGadflyPlotTheme(plt::Plot, plotattributes::KW)
kwargs = KW()
# colors
insidecolor, gridcolor, textcolor, guidecolor, legendcolor =
map(s -> getColor(plotattributes[s]), (
:background_color_inside,
:foreground_color_grid,
:foreground_color_text,
:foreground_color_guide,
:foreground_color_legend
))
# # hide the legend?
leg = plotattributes[plotattributes[:legend] == :none ? :colorbar : :legend]
if leg != :best
kwargs[:key_position] = leg == :inside ? :right : leg
end
if !get(plotattributes, :grid, true)
kwargs[:grid_color] = gridcolor
end
# fonts
tfont, gfont, lfont = plotattributes[:tickfont], plotattributes[:guidefont], plotattributes[:legendfont]
getGadflyContext(plt).theme = Gadfly.Theme(;
background_color = insidecolor,
minor_label_color = textcolor,
minor_label_font = tfont.family,
minor_label_font_size = tfont.pointsize * Gadfly.pt,
major_label_color = guidecolor,
major_label_font = gfont.family,
major_label_font_size = gfont.pointsize * Gadfly.pt,
key_title_color = guidecolor,
key_title_font = gfont.family,
key_title_font_size = gfont.pointsize * Gadfly.pt,
key_label_color = legendcolor,
key_label_font = lfont.family,
key_label_font_size = lfont.pointsize * Gadfly.pt,
plot_padding = 1 * Gadfly.mm,
kwargs...
)
end
# ----------------------------------------------------------------
function createGadflyAnnotationObject(x, y, val::AbstractString)
Gadfly.Guide.annotation(Compose.compose(
Compose.context(),
Compose.text(x, y, val)
))
end
function createGadflyAnnotationObject(x, y, txt::PlotText)
halign = (txt.font.halign == :hcenter ? Compose.hcenter : (txt.font.halign == :left ? Compose.hleft : Compose.hright))
valign = (txt.font.valign == :vcenter ? Compose.vcenter : (txt.font.valign == :top ? Compose.vtop : Compose.vbottom))
rotations = (txt.font.rotation == 0.0 ? [] : [Compose.Rotation(txt.font.rotation, Compose.Point(Compose.x_measure(x), Compose.y_measure(y)))])
Gadfly.Guide.annotation(Compose.compose(
Compose.context(),
Compose.text(x, y, txt.str, halign, valign, rotations...),
Compose.font(string(txt.font.family)),
Compose.fontsize(txt.font.pointsize * Gadfly.pt),
Compose.stroke(txt.font.color),
Compose.fill(txt.font.color)
))
end
function _add_annotations(plt::Plot{GadflyBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns
push!(plt.o.guides, createGadflyAnnotationObject(ann...))
end
end
# ---------------------------------------------------------------------------
# create a blank Gadfly.Plot object
# function _create_plot(pkg::GadflyBackend, plotattributes::KW)
# gplt = createGadflyPlotObject(plotattributes)
# Plot(gplt, pkg, 0, plotattributes, KW[])
# end
function _create_backend_figure(plt::Plot{GadflyBackend})
createGadflyPlotObject(plt.attr)
end
# plot one data series
# function _series_added(::GadflyBackend, plt::Plot, plotattributes::KW)
function _series_added(plt::Plot{GadflyBackend}, series::Series)
# first clear out the temporary layer
gplt = getGadflyContext(plt)
if gplt.layers[1].geom.tag == :remove
gplt.layers = gplt.layers[2:end]
end
addGadflySeries!(plt, series.plotattributes)
# push!(plt.seriesargs, plotattributes)
# plt
end
function _update_plot_object(plt::Plot{GadflyBackend}, plotattributes::KW)
updateGadflyGuides(plt, plotattributes)
updateGadflyPlotTheme(plt, plotattributes)
end
# ----------------------------------------------------------------
# accessors for x/y data
# TODO: need to save all the layer indices which apply to this series
function getGadflyMappings(plt::Plot, i::Integer)
@assert i > 0 && i <= plt.n
mappings = [l.mapping for l in plt.seriesargs[i][:gadflylayers]]
end
function getxy(plt::Plot{GadflyBackend}, i::Integer)
mapping = getGadflyMappings(plt, i)[1]
mapping[:x], mapping[:y]
end
function setxy!(plt::Plot{GadflyBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
for mapping in getGadflyMappings(plt, i)
mapping[:x], mapping[:y] = xy
end
plt
end
# ----------------------------------------------------------------
# # create the underlying object (each backend will do this differently)
# function _create_subplot(subplt::Subplot{GadflyBackend}, isbefore::Bool)
# isbefore && return false # wait until after plotting to create the subplots
# subplt.o = nothing
# true
# end
function _remove_axis(plt::Plot{GadflyBackend}, isx::Bool)
gplt = getGadflyContext(plt)
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks; label=false)
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xlabel : Gadfly.Guide.ylabel, "")
end
function _expand_limits(lims, plt::Plot{GadflyBackend}, isx::Bool)
for l in getGadflyContext(plt).layers
_expand_limits(lims, l.mapping[isx ? :x : :y])
end
end
# ----------------------------------------------------------------
getGadflyContext(plt::Plot{GadflyBackend}) = plt.o
# getGadflyContext(subplt::Subplot{GadflyBackend}) = buildGadflySubplotContext(subplt)
# # create my Compose.Context grid by hstacking and vstacking the Gadfly.Plot objects
# function buildGadflySubplotContext(subplt::Subplot)
# rows = Any[]
# row = Any[]
# for (i,(r,c)) in enumerate(subplt.layout)
#
# # add the Plot object to the row
# push!(row, getGadflyContext(subplt.plts[i]))
#
# # add the row
# if c == ncols(subplt.layout, r)
# push!(rows, Gadfly.hstack(row...))
# row = Any[]
# end
# end
#
# # stack the rows
# Gadfly.vstack(rows...)
# end
setGadflyDisplaySize(w,h) = Compose.set_default_graphic_size(w * Compose.px, h * Compose.px)
setGadflyDisplaySize(plt::Plot) = setGadflyDisplaySize(plt.attr[:size]...)
# setGadflyDisplaySize(subplt::Subplot) = setGadflyDisplaySize(getattr(subplt, 1)[:size]...)
# -------------------------------------------------------------------------
function doshow(io::IO, func, plt::AbstractPlot{P}) where P<:Union{GadflyBackend,ImmerseBackend}
gplt = getGadflyContext(plt)
setGadflyDisplaySize(plt)
Gadfly.draw(func(io, Compose.default_graphic_width, Compose.default_graphic_height), gplt)
end
getGadflyWriteFunc(::MIME"image/png") = Gadfly.PNG
getGadflyWriteFunc(::MIME"image/svg+xml") = Gadfly.SVG
# getGadflyWriteFunc(::MIME"text/html") = Gadfly.SVGJS
getGadflyWriteFunc(::MIME"application/pdf") = Gadfly.PDF
getGadflyWriteFunc(::MIME"application/postscript") = Gadfly.PS
getGadflyWriteFunc(::MIME"application/x-tex") = Gadfly.PGF
getGadflyWriteFunc(m::MIME) = error("Unsupported in Gadfly/Immerse: ", m)
for mime in (MIME"image/png", MIME"image/svg+xml", MIME"application/pdf", MIME"application/postscript", MIME"application/x-tex")
@eval function Base.show(io::IO, ::$mime, plt::AbstractPlot{P}) where P<:Union{GadflyBackend,ImmerseBackend}
func = getGadflyWriteFunc($mime())
doshow(io, func, plt)
end
end
function Base.display(::PlotsDisplay, plt::Plot{GadflyBackend})
setGadflyDisplaySize(plt.attr[:size]...)
display(plt.o)
end
# function Base.display(::PlotsDisplay, subplt::Subplot{GadflyBackend})
# setGadflyDisplaySize(getattr(subplt,1)[:size]...)
# ctx = buildGadflySubplotContext(subplt)
#
# # taken from Gadfly since I couldn't figure out how to do it directly
#
# filename = string(Gadfly.tempname(), ".html")
# output = open(filename, "w")
#
# plot_output = IOBuffer()
# Gadfly.draw(Gadfly.SVGJS(plot_output, Compose.default_graphic_width,
# Compose.default_graphic_height, false), ctx)
# plotsvg = takebuf_string(plot_output)
#
# write(output,
# """
# <!DOCTYPE html>
# <html>
# <head>
# <title>Gadfly Plot</title>
# <meta charset="utf-8">
# </head>
# <body>
# <script charset="utf-8">
# $(readall(Compose.snapsvgjs))
# </script>
# <script charset="utf-8">
# $(readall(Gadfly.gadflyjs))
# </script>
# $(plotsvg)
# </body>
# </html>
# """)
# close(output)
# Gadfly.open_file(filename)
# end

View File

@ -1,93 +0,0 @@
# Geometry which displays arbitrary shapes at given (x, y) positions.
# note: vertices is a list of shapes
struct ShapeGeometry <: Gadfly.GeometryElement
vertices::AbstractVector #{Tuple{Float64,Float64}}
tag::Symbol
function ShapeGeometry(shape; tag::Symbol=Gadfly.Geom.empty_tag)
new(shape, tag)
end
end
# TODO: add for PR
# const shape = ShapeGeometry
function Gadfly.element_aesthetics(::ShapeGeometry)
[:x, :y, :size, :color]
end
# Generate a form for a shape geometry.
#
# Args:
# geom: shape geometry.
# theme: the plot's theme.
# aes: aesthetics.
#
# Returns:
# A compose Form.
#
function Gadfly.render(geom::ShapeGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics)
# TODO: add for PR
# Gadfly.assert_aesthetics_defined("Geom.shape", aes, :x, :y)
# Gadfly.assert_aesthetics_equal_length("Geom.shape", aes,
# element_aesthetics(geom)...)
default_aes = Gadfly.Aesthetics()
default_aes.color = Gadfly.DataFrames.PooledDataArray(RGBA{Float32}[theme.default_color])
default_aes.size = Compose.Measure[theme.default_point_size]
aes = Gadfly.inherit(aes, default_aes)
lw_hover_scale = 10
lw_ratio = theme.line_width / aes.size[1]
aes_x, aes_y = Gadfly.concretize(aes.x, aes.y)
ctx = Compose.compose!(
Compose.context(),
make_polygon(geom, aes.x, aes.y, aes.size),
Compose.fill(aes.color),
Compose.linewidth(theme.highlight_width))
if aes.color_key_continuous != nothing && aes.color_key_continuous
Compose.compose!(ctx,
Compose.stroke(map(theme.continuous_highlight_color, aes.color)))
else
Compose.compose!(ctx,
Compose.stroke(map(theme.discrete_highlight_color, aes.color)),
Compose.svgclass([Gadfly.svg_color_class_from_label(Gadfly.escape_id(aes.color_label([c])[1]))
for c in aes.color]))
end
return Compose.compose!(Compose.context(order=4), Compose.svgclass("geometry"), ctx)
end
function gadflyshape(sv::Shape)
ShapeGeometry(Any[vertices(sv)])
end
function gadflyshape(sv::AVec{Shape})
ShapeGeometry(Any[vertices(s) for s in sv])
end
# create a Compose context given a ShapeGeometry and the xs/ys/sizes
function make_polygon(geom::ShapeGeometry, xs::AbstractArray, ys::AbstractArray, rs::AbstractArray)
n = max(length(xs), length(ys), length(rs))
T = Tuple{Compose.Measure, Compose.Measure}
polys = Array(Vector{T}, n)
for i in 1:n
x = Compose.x_measure(xs[mod1(i, length(xs))])
y = Compose.y_measure(ys[mod1(i, length(ys))])
r = rs[mod1(i, length(rs))]
polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in _cycle(geom.vertices, i)]
end
Gadfly.polygon(polys, geom.tag)
end
# ---------------------------------------------------------------------------------------------

View File

@ -1,186 +0,0 @@
# https://github.com/JuliaGraphics/Immerse.jl
supported_attrs(::ImmerseBackend) = supported_attrs(GadflyBackend())
supported_types(::ImmerseBackend) = supported_types(GadflyBackend())
supported_styles(::ImmerseBackend) = supported_styles(GadflyBackend())
supported_markers(::ImmerseBackend) = supported_markers(GadflyBackend())
supported_scales(::ImmerseBackend) = supported_scales(GadflyBackend())
is_subplot_supported(::ImmerseBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::ImmerseBackend; kw...)
@eval begin
import Immerse, Gadfly, Compose, Gtk
export Immerse, Gadfly, Compose, Gtk
include(joinpath(dirname(@__FILE__), "gadfly_shapes.jl"))
end
end
function createImmerseFigure(plotattributes::KW)
w,h = plotattributes[:size]
figidx = Immerse.figure(; name = plotattributes[:window_title], width = w, height = h)
Immerse.Figure(figidx)
end
# ----------------------------------------------------------------
# create a blank Gadfly.Plot object
# function _create_plot(pkg::ImmerseBackend, plotattributes::KW)
# # create the underlying Gadfly.Plot object
# gplt = createGadflyPlotObject(plotattributes)
#
# # save both the Immerse.Figure and the Gadfly.Plot
# Plot((nothing,gplt), pkg, 0, plotattributes, KW[])
# end
function _create_backend_figure(plt::Plot{ImmerseBackend})
(nothing, createGadflyPlotObject(plt.attr))
end
# # plot one data series
# function _series_added(::ImmerseBackend, plt::Plot, plotattributes::KW)
# addGadflySeries!(plt, plotattributes)
# push!(plt.seriesargs, plotattributes)
# plt
# end
function _series_added(plt::Plot{ImmerseBackend}, series::Series)
addGadflySeries!(plt, series.plotattributes)
end
function _update_plot_object(plt::Plot{ImmerseBackend}, plotattributes::KW)
updateGadflyGuides(plt, plotattributes)
updateGadflyPlotTheme(plt, plotattributes)
end
# ----------------------------------------------------------------
function _add_annotations(plt::Plot{ImmerseBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns
push!(getGadflyContext(plt).guides, createGadflyAnnotationObject(ann...))
end
end
# ----------------------------------------------------------------
# accessors for x/y data
function getxy(plt::Plot{ImmerseBackend}, i::Integer)
mapping = getGadflyMappings(plt, i)[1]
mapping[:x], mapping[:y]
end
function setxy!(plt::Plot{ImmerseBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
for mapping in getGadflyMappings(plt, i)
mapping[:x], mapping[:y] = xy
end
plt
end
# ----------------------------------------------------------------
# function _create_subplot(subplt::Subplot{ImmerseBackend}, isbefore::Bool)
# return false
# # isbefore && return false
# end
#
# function showSubplotObject(subplt::Subplot{ImmerseBackend})
# # create the Gtk window with vertical box vsep
# plotattributes = getattr(subplt,1)
# w,h = plotattributes[:size]
# vsep = Gtk.GtkBoxLeaf(:v)
# win = Gtk.GtkWindowLeaf(vsep, plotattributes[:window_title], w, h)
#
# figindices = []
# row = Gtk.GtkBoxLeaf(:h)
# push!(vsep, row)
# for (i,(r,c)) in enumerate(subplt.layout)
# plt = subplt.plts[i]
#
# # get the components... box is the main plot GtkBox, and canvas is the GtkCanvas where it's plotted
# box, toolbar, canvas = Immerse.createPlotGuiComponents()
#
# # add the plot's box to the row
# push!(row, box)
#
# # create the figure and store the index returned for destruction later
# figidx = Immerse.figure(canvas)
# push!(figindices, figidx)
#
# fig = Immerse.figure(figidx)
# plt.o = (fig, plt.o[2])
#
# # add the row
# if c == ncols(subplt.layout, r)
# row = Gtk.GtkBoxLeaf(:h)
# push!(vsep, row)
# end
#
# end
#
# # destructor... clean up plots
# Gtk.on_signal_destroy((x...) -> ([Immerse.dropfig(Immerse._display,i) for i in figindices]; subplt.o = nothing), win)
#
# subplt.o = win
# true
# end
function _remove_axis(plt::Plot{ImmerseBackend}, isx::Bool)
gplt = getGadflyContext(plt)
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks; label=false)
addOrReplace(gplt.guides, isx ? Gadfly.Guide.xlabel : Gadfly.Guide.ylabel, "")
end
function _expand_limits(lims, plt::Plot{ImmerseBackend}, isx::Bool)
for l in getGadflyContext(plt).layers
_expand_limits(lims, l.mapping[isx ? :x : :y])
end
end
# ----------------------------------------------------------------
getGadflyContext(plt::Plot{ImmerseBackend}) = plt.o[2]
# getGadflyContext(subplt::Subplot{ImmerseBackend}) = buildGadflySubplotContext(subplt)
function Base.display(::PlotsDisplay, plt::Plot{ImmerseBackend})
fig, gplt = plt.o
if fig == nothing
fig = createImmerseFigure(plt.attr)
Gtk.on_signal_destroy((x...) -> (Immerse.dropfig(Immerse._display, fig.figno); plt.o = (nothing,gplt)), fig.canvas)
plt.o = (fig, gplt)
end
Immerse.figure(fig.figno; displayfig = false)
display(gplt)
end
# function Base.display(::PlotsDisplay, subplt::Subplot{ImmerseBackend})
#
# # if we haven't created the window yet, do it
# if subplt.o == nothing
# showSubplotObject(subplt)
# end
#
# # display the plots by creating a fresh Immerse.Figure object from the GtkCanvas and Gadfly.Plot
# for plt in subplt.plts
# fig, gplt = plt.o
# Immerse.figure(fig.figno; displayfig = false)
# display(gplt)
# end
#
# # o is the window... show it
# showall(subplt.o)
# end

View File

@ -1,308 +0,0 @@
# https://github.com/tbreloff/Qwt.jl
supported_attrs(::QwtBackend) = merge_with_base_supported([
:annotations,
:linecolor,
:fillrange,
:fillcolor,
:label,
:legend,
:seriescolor, :seriesalpha,
:linestyle,
:linewidth,
:markershape,
:markercolor,
:markersize,
:bins,
:pos,
:title,
:window_title,
:guide, :lims, :ticks, :scale,
])
supported_types(::QwtBackend) = [:path, :scatter, :hexbin, :bar]
supported_markers(::QwtBackend) = [:none, :auto, :rect, :circle, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :star8, :hexagon]
supported_scales(::QwtBackend) = [:identity, :log10]
is_subplot_supported(::QwtBackend) = true
# --------------------------------------------------------------------------------------
function _initialize_backend(::QwtBackend; kw...)
@eval begin
@warn("Qwt is no longer supported... many features will likely be broken.")
import Qwt
export Qwt
end
end
# -------------------------------
const _qwtAliases = KW(
:bins => :heatmap_n,
:fillrange => :fillto,
:linewidth => :width,
:markershape => :marker,
:hexbin => :heatmap,
:path => :line,
:steppost => :step,
:steppre => :stepinverted,
:star5 => :star1,
:star8 => :star2,
)
function fixcolors(plotattributes::KW)
for (k,v) in plotattributes
if typeof(v) <: ColorScheme
plotattributes[k] = getColor(v)
end
end
end
function replaceQwtAliases(plotattributes, s)
if haskey(_qwtAliases, plotattributes[s])
plotattributes[s] = _qwtAliases[plotattributes[s]]
end
end
function adjustQwtKeywords(plt::Plot{QwtBackend}, iscreating::Bool; kw...)
plotattributes = KW(kw)
st = plotattributes[:seriestype]
if st == :scatter
plotattributes[:seriestype] = :none
if plotattributes[:markershape] == :none
plotattributes[:markershape] = :circle
end
elseif st in (:hline, :vline)
addLineMarker(plt, plotattributes)
plotattributes[:seriestype] = :none
plotattributes[:markershape] = :circle
plotattributes[:markersize] = 1
if st == :vline
plotattributes[:x], plotattributes[:y] = plotattributes[:y], plotattributes[:x]
end
elseif !iscreating && st == :bar
plotattributes = barHack(; kw...)
elseif !iscreating && st == :histogram
plotattributes = barHack(; histogramHack(; kw...)...)
end
replaceQwtAliases(plotattributes, :seriestype)
replaceQwtAliases(plotattributes, :markershape)
for k in keys(plotattributes)
if haskey(_qwtAliases, k)
plotattributes[_qwtAliases[k]] = plotattributes[k]
end
end
plotattributes[:x] = collect(plotattributes[:x])
plotattributes[:y] = collect(plotattributes[:y])
plotattributes
end
# function _create_plot(pkg::QwtBackend, plotattributes::KW)
function _create_backend_figure(plt::Plot{QwtBackend})
fixcolors(plt.attr)
dumpdict(plt.attr,"\n\n!!! plot")
o = Qwt.plot(zeros(0,0); plt.attr..., show=false)
# plt = Plot(o, pkg, 0, plotattributes, KW[])
# plt
end
# function _series_added(::QwtBackend, plt::Plot, plotattributes::KW)
function _series_added(plt::Plot{QwtBackend}, series::Series)
plotattributes = adjustQwtKeywords(plt, false; series.plotattributes...)
fixcolors(plotattributes)
dumpdict(plotattributes,"\n\n!!! plot!")
Qwt.oplot(plt.o; plotattributes...)
# push!(plt.seriesargs, plotattributes)
# plt
end
# ----------------------------------------------------------------
function updateLimsAndTicks(plt::Plot{QwtBackend}, plotattributes::KW, isx::Bool)
lims = get(plotattributes, isx ? :xlims : :ylims, nothing)
ticks = get(plotattributes, isx ? :xticks : :yticks, nothing)
w = plt.o.widget
axisid = Qwt.QWT.QwtPlot[isx ? :xBottom : :yLeft]
if typeof(lims) <: Union{Tuple,AVec} && length(lims) == 2
if isx
plt.o.autoscale_x = false
else
plt.o.autoscale_y = false
end
w[:setAxisScale](axisid, lims...)
end
if typeof(ticks) <: AbstractRange
if isx
plt.o.autoscale_x = false
else
plt.o.autoscale_y = false
end
w[:setAxisScale](axisid, float(minimum(ticks)), float(maximum(ticks)), float(step(ticks)))
elseif !(ticks in (nothing, :none, :auto))
@warn("Only Range types are supported for Qwt xticks/yticks. typeof(ticks)=$(typeof(ticks))")
end
# change the scale
scalesym = isx ? :xscale : :yscale
if haskey(plotattributes, scalesym)
scaletype = plotattributes[scalesym]
scaletype == :identity && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLinearScaleEngine())
# scaletype == :log && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLogScaleEngine(e))
# scaletype == :log2 && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLogScaleEngine(2))
scaletype == :log10 && w[:setAxisScaleEngine](axisid, Qwt.QWT.QwtLog10ScaleEngine())
scaletype in supported_scales() || @warn("Unsupported scale type: ", scaletype)
end
end
function _update_plot_object(plt::Plot{QwtBackend}, plotattributes::KW)
haskey(plotattributes, :title) && Qwt.title(plt.o, plotattributes[:title])
haskey(plotattributes, :xguide) && Qwt.xlabel(plt.o, plotattributes[:xguide])
haskey(plotattributes, :yguide) && Qwt.ylabel(plt.o, plotattributes[:yguide])
updateLimsAndTicks(plt, plotattributes, true)
updateLimsAndTicks(plt, plotattributes, false)
end
function _update_plot_pos_size(plt::AbstractPlot{QwtBackend}, plotattributes::KW)
haskey(plotattributes, :size) && Qwt.resizewidget(plt.o, plotattributes[:size]...)
haskey(plotattributes, :pos) && Qwt.movewidget(plt.o, plotattributes[:pos]...)
end
# ----------------------------------------------------------------
# curve.setPen(Qt.QPen(Qt.QColor(color), linewidth, self.getLineStyle(linestyle)))
function addLineMarker(plt::Plot{QwtBackend}, plotattributes::KW)
for yi in plotattributes[:y]
marker = Qwt.QWT.QwtPlotMarker()
ishorizontal = (plotattributes[:seriestype] == :hline)
marker[:setLineStyle](ishorizontal ? 1 : 2)
marker[ishorizontal ? :setYValue : :setXValue](yi)
qcolor = Qwt.convertRGBToQColor(getColor(plotattributes[:linecolor]))
linestyle = plt.o.widget[:getLineStyle](string(plotattributes[:linestyle]))
marker[:setLinePen](Qwt.QT.QPen(qcolor, plotattributes[:linewidth], linestyle))
marker[:attach](plt.o.widget)
end
# marker[:setValue](x, y)
# marker[:setLabel](Qwt.QWT.QwtText(val))
# marker[:attach](plt.o.widget)
end
function createQwtAnnotation(plt::Plot, x, y, val::PlotText)
marker = Qwt.QWT.QwtPlotMarker()
marker[:setValue](x, y)
qwttext = Qwt.QWT.QwtText(val.str)
qwttext[:setFont](Qwt.QT.QFont(val.font.family, val.font.pointsize))
qwttext[:setColor](Qwt.convertRGBToQColor(getColor(val.font.color)))
marker[:setLabel](qwttext)
marker[:attach](plt.o.widget)
end
function createQwtAnnotation(plt::Plot, x, y, val::AbstractString)
marker = Qwt.QWT.QwtPlotMarker()
marker[:setValue](x, y)
marker[:setLabel](Qwt.QWT.QwtText(val))
marker[:attach](plt.o.widget)
end
function _add_annotations(plt::Plot{QwtBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns
createQwtAnnotation(plt, ann...)
end
end
# ----------------------------------------------------------------
# accessors for x/y data
function getxy(plt::Plot{QwtBackend}, i::Int)
series = plt.o.lines[i]
series.x, series.y
end
function setxy!(plt::Plot{QwtBackend}, xy::Tuple{X,Y}, i::Integer) where {X,Y}
series = plt.o.lines[i]
series.x, series.y = xy
plt
end
# -------------------------------
# -------------------------------
# # create the underlying object (each backend will do this differently)
# function _create_subplot(subplt::Subplot{QwtBackend}, isbefore::Bool)
# isbefore && return false
# i = 0
# rows = Any[]
# row = Any[]
# for (i,(r,c)) in enumerate(subplt.layout)
# push!(row, subplt.plts[i].o)
# if c == ncols(subplt.layout, r)
# push!(rows, Qwt.hsplitter(row...))
# row = Any[]
# end
# end
# # for rowcnt in subplt.layout.rowcounts
# # push!(rows, Qwt.hsplitter([plt.o for plt in subplt.plts[(1:rowcnt) + i]]...))
# # i += rowcnt
# # end
# subplt.o = Qwt.vsplitter(rows...)
# # Qwt.resizewidget(subplt.o, getattr(subplt,1)[:size]...)
# # Qwt.moveToLastScreen(subplt.o) # hack so it goes to my center monitor... sorry
# true
# end
function _expand_limits(lims, plt::Plot{QwtBackend}, isx::Bool)
for series in plt.o.lines
_expand_limits(lims, isx ? series.x : series.y)
end
end
function _remove_axis(plt::Plot{QwtBackend}, isx::Bool)
end
# ----------------------------------------------------------------
function Base.show(io::IO, ::MIME"image/png", plt::Plot{QwtBackend})
Qwt.refresh(plt.o)
Qwt.savepng(plt.o, "/tmp/dfskjdhfkh.png")
write(io, readall("/tmp/dfskjdhfkh.png"))
end
# function Base.show(io::IO, ::MIME"image/png", subplt::Subplot{QwtBackend})
# for plt in subplt.plts
# Qwt.refresh(plt.o)
# end
# Qwt.savepng(subplt.o, "/tmp/dfskjdhfkh.png")
# write(io, readall("/tmp/dfskjdhfkh.png"))
# end
function Base.display(::PlotsDisplay, plt::Plot{QwtBackend})
Qwt.refresh(plt.o)
Qwt.showwidget(plt.o)
end
# function Base.display(::PlotsDisplay, subplt::Subplot{QwtBackend})
# for plt in subplt.plts
# Qwt.refresh(plt.o)
# end
# Qwt.showwidget(subplt.o)
# end

View File

@ -1,272 +0,0 @@
# https://github.com/nolta/Winston.jl
# credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation
supported_attrs(::WinstonBackend) = merge_with_base_supported([
:annotations,
:linecolor,
:fillrange,
:fillcolor,
:label,
:legend,
:seriescolor, :seriesalpha,
:linestyle,
:linewidth,
:markershape,
:markercolor,
:markersize,
:bins,
:title,
:window_title,
:guide, :lims, :scale,
])
supported_types(::WinstonBackend) = [:path, :scatter, :bar]
supported_styles(::WinstonBackend) = [:auto, :solid, :dash, :dot, :dashdot]
supported_markers(::WinstonBackend) = [:none, :auto, :rect, :circle, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5]
supported_scales(::WinstonBackend) = [:identity, :log10]
is_subplot_supported(::WinstonBackend) = false
# --------------------------------------------------------------------------------------
function _initialize_backend(::WinstonBackend; kw...)
@eval begin
# ENV["WINSTON_OUTPUT"] = "gtk"
@warn("Winston is no longer supported... many features will likely be broken.")
import Winston, Gtk
export Winston, Gtk
end
end
# ---------------------------------------------------------------------------
## dictionaries for conversion of Plots.jl names to Winston ones.
const winston_linestyle = KW(:solid=>"solid",
:dash=>"dash",
:dot=>"dotted",
:dashdot=>"dotdashed"
)
const winston_marker = KW(:none=>".",
:rect => "square",
:circle=>"circle",
:diamond=>"diamond",
:utriangle=>"triangle",
:dtriangle=>"down-triangle",
:cross => "plus",
:xcross => "cross",
:star5 => "asterisk"
)
function _before_update(plt::Plot{WinstonBackend})
Winston.ghf(plt.o)
end
# ---------------------------------------------------------------------------
function _create_backend_figure(plt::Plot{WinstonBackend})
Winston.FramedPlot(
title = plt.attr[:title],
xlabel = plt.attr[:xguide],
ylabel = plt.attr[:yguide]
)
end
copy_remove(plotattributes::KW, s::Symbol) = delete!(copy(plotattributes), s)
function addRegressionLineWinston(plotattributes::KW, wplt)
xs, ys = regressionXY(plotattributes[:x], plotattributes[:y])
Winston.add(wplt, Winston.Curve(xs, ys, kind="dotted"))
end
function getWinstonItems(plt::Plot)
if isa(plt.o, Winston.FramedPlot)
wplt = plt.o
window, canvas = nothing, nothing
else
window, canvas, wplt = plt.o
end
window, canvas, wplt
end
function _series_added(plt::Plot{WinstonBackend}, series::Series)
plotattributes = series.plotattributes
window, canvas, wplt = getWinstonItems(plt)
# until we call it normally, do the hack
if plotattributes[:seriestype] == :bar
plotattributes = barHack(;plotattributes...)
end
e = KW()
e[:color] = getColor(plotattributes[:linecolor])
e[:linewidth] = plotattributes[:linewidth]
e[:kind] = winston_linestyle[plotattributes[:linestyle]]
e[:symbolkind] = winston_marker[plotattributes[:markershape]]
# markercolor # same choices as `color`, or :match will set the color to be the same as `color`
e[:symbolsize] = plotattributes[:markersize] / 5
# pos # (Int,Int), move the enclosing window to this position
# screen # Integer, move enclosing window to this screen number (for multiscreen desktops)
## lintype :path, :step, :stepinverted, :sticks, :dots, :none, :histogram2d, :hexbin, :histogram, :bar
if plotattributes[:seriestype] == :none
Winston.add(wplt, Winston.Points(plotattributes[:x], plotattributes[:y]; copy_remove(e, :kind)..., color=getColor(plotattributes[:markercolor])))
elseif plotattributes[:seriestype] == :path
x, y = plotattributes[:x], plotattributes[:y]
Winston.add(wplt, Winston.Curve(x, y; e...))
fillrange = plotattributes[:fillrange]
if fillrange != nothing
if isa(fillrange, AbstractVector)
y2 = fillrange
else
y2 = Float64[fillrange for yi in y]
end
Winston.add(wplt, Winston.FillBetween(x, y, x, y2, fillcolor=getColor(plotattributes[:fillcolor])))
end
elseif plotattributes[:seriestype] == :scatter
if plotattributes[:markershape] == :none
plotattributes[:markershape] = :circle
end
# elseif plotattributes[:seriestype] == :step
# fn = Winston.XXX
# elseif plotattributes[:seriestype] == :stepinverted
# fn = Winston.XXX
elseif plotattributes[:seriestype] == :sticks
Winston.add(wplt, Winston.Stems(plotattributes[:x], plotattributes[:y]; e...))
# elseif plotattributes[:seriestype] == :dots
# fn = Winston.XXX
# elseif plotattributes[:seriestype] == :histogram2d
# fn = Winston.XXX
# elseif plotattributes[:seriestype] == :hexbin
# fn = Winston.XXX
elseif plotattributes[:seriestype] == :histogram
hst = hist(plotattributes[:y], plotattributes[:bins])
Winston.add(wplt, Winston.Histogram(hst...; copy_remove(e, :bins)...))
# elseif plotattributes[:seriestype] == :bar
# # fn = Winston.XXX
else
error("seriestype $(plotattributes[:seriestype]) not supported by Winston.")
end
# markershape
if plotattributes[:markershape] != :none
Winston.add(wplt, Winston.Points(plotattributes[:x], plotattributes[:y]; copy_remove(e, :kind)..., color=getColor(plotattributes[:markercolor])))
end
# optionally add a regression line
plotattributes[:smooth] && plotattributes[:seriestype] != :histogram && addRegressionLineWinston(plotattributes, wplt)
# push!(plt.seriesargs, plotattributes)
# plt
end
# ----------------------------------------------------------------
const _winstonNames = KW(
:xlims => :xrange,
:ylims => :yrange,
:xscale => :xlog,
:yscale => :ylog,
)
function _update_plot_object(plt::Plot{WinstonBackend}, plotattributes::KW)
window, canvas, wplt = getWinstonItems(plt)
for k in (:xguide, :yguide, :title, :xlims, :ylims)
if haskey(plotattributes, k)
Winston.setattr(wplt, string(get(_winstonNames, k, k)), plotattributes[k])
end
end
for k in (:xscale, :yscale)
if haskey(plotattributes, k)
islogscale = plotattributes[k] == :log10
Winston.setattr(wplt, (k == :xscale ? :xlog : :ylog), islogscale)
end
end
end
# ----------------------------------------------------------------
function createWinstonAnnotationObject(plt::Plot{WinstonBackend}, x, y, val::AbstractString)
Winston.text(x, y, val)
end
function _add_annotations(plt::Plot{WinstonBackend}, anns::AVec{Tuple{X,Y,V}}) where {X,Y,V}
for ann in anns
createWinstonAnnotationObject(plt, ann...)
end
end
# ----------------------------------------------------------------
# function _create_subplot(subplt::Subplot{WinstonBackend}, isbefore::Bool)
# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example
# end
# ----------------------------------------------------------------
function addWinstonLegend(plt::Plot, wplt)
if plt.attr[:legend] != :none
Winston.legend(wplt, [sd[:label] for sd in plt.seriesargs])
end
end
function Base.show(io::IO, ::MIME"image/png", plt::AbstractPlot{WinstonBackend})
window, canvas, wplt = getWinstonItems(plt)
addWinstonLegend(plt, wplt)
show(io, "image/png", wplt)
end
function Base.display(::PlotsDisplay, plt::Plot{WinstonBackend})
window, canvas, wplt = getWinstonItems(plt)
if window == nothing
if Winston.output_surface != :gtk
error("Gtk is the only supported display for Winston in Plots. Set `output_surface = gtk` in src/Winston.ini")
end
# initialize window
w,h = plt.attr[:size]
canvas = Gtk.GtkCanvasLeaf()
window = Gtk.GtkWindowLeaf(canvas, plt.attr[:window_title], w, h)
plt.o = (window, canvas, wplt)
end
addWinstonLegend(plt, wplt)
Winston.display(canvas, wplt)
Gtk.showall(window)
end
# function Base.display(::PlotsDisplay, subplt::Subplot{WinstonBackend})
# # TODO: display/show the Subplot object
# end

File diff suppressed because it is too large Load Diff

View File

@ -1,415 +0,0 @@
abstract type ColorScheme end
Base.getindex(scheme::ColorScheme, i::Integer) = getColor(scheme, i)
export
cgrad
cgrad() = default_gradient()
function cgrad(arg, values = nothing; alpha = nothing, scale = :identity)
colors = ColorGradient(arg, alpha=alpha).colors
values = if values != nothing
values
elseif scale in (:log, :log10)
log10(range(1, stop=10, length=30))
elseif scale == :log2
log2(range(1, stop=2, length=30))
elseif scale == :ln
log(range(1, stop=pi, length=30))
elseif scale in (:exp, :exp10)
(exp10(range(0, stop=1, length=30)) - 1) / 9
else
range(0, stop=1, length=length(colors))
end
ColorGradient(colors, values)
end
# --------------------------------------------------------------
getColor(scheme::ColorScheme) = getColor(scheme, 1)
getColorVector(scheme::ColorScheme) = [getColor(scheme)]
colorscheme(scheme::ColorScheme) = scheme
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, vals::AVec{T}; kw...) where {T<:Real} = ColorGradient(s, vals; kw...)
colorscheme(cs::AVec, vs::AVec; kw...) = ColorGradient(cs, vs; kw...)
colorscheme(cs::AVec{T}; kw...) where {T<:Colorant} = ColorGradient(cs; kw...)
colorscheme(f::Function; kw...) = ColorFunction(f; 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(c::Colorant; kw...) = ColorWrapper(c; kw...)
# --------------------------------------------------------------
convertColor(c::AbstractString) = parse(Colorant, c)
convertColor(c::Symbol) = parse(Colorant, string(c))
convertColor(c::Colorant) = c
convertColor(cvec::AbstractVector) = map(convertColor, cvec)
convertColor(c::ColorScheme) = c
convertColor(v::Nothing) = RGBA(0,0,0,0)
convertColor(b::Bool) = b ? RGBA(0,0,0,1) : RGBA(0,0,0,0)
function convertColor(c, α::Real)
c = convertColor(c)
RGBA(RGB(getColor(c)), α)
end
convertColor(cs::AVec, α::Real) = map(c -> convertColor(c, α), cs)
convertColor(c, α::Nothing) = convertColor(c)
# backup... try to convert
getColor(c) = convertColor(c)
# --------------------------------------------------------------
function darken(c, v=0.1)
rgba = convert(RGBA, c)
r = max(0, min(rgba.r - v, 1))
g = max(0, min(rgba.g - v, 1))
b = max(0, min(rgba.b - v, 1))
RGBA(r,g,b,rgba.alpha)
end
function lighten(c, v=0.3)
darken(c, -v)
end
# --------------------------------------------------------------
const _rainbowColors = [colorant"purple", colorant"blue", colorant"green", colorant"orange", colorant"red"]
const _testColors = [colorant"darkblue", colorant"blueviolet", colorant"darkcyan",colorant"green",
darken(colorant"yellow",0.3), colorant"orange", darken(colorant"red",0.2)]
const _gradients = KW(
:blues => [colorant"lightblue", colorant"darkblue"],
:reds => [colorant"lightpink", colorant"darkred"],
:greens => [colorant"lightgreen", colorant"darkgreen"],
:redsblues => [colorant"darkred", RGB(0.8,0.85,0.8), colorant"darkblue"],
:bluesreds => [colorant"darkblue", RGB(0.8,0.85,0.8), colorant"darkred"],
:heat => [colorant"lightyellow", colorant"orange", colorant"darkred"],
:grays => [RGB(.95,.95,.95),RGB(.05,.05,.05)],
:rainbow => _rainbowColors,
:lightrainbow => map(lighten, _rainbowColors),
:darkrainbow => map(darken, _rainbowColors),
:darktest => _testColors,
:lighttest => map(c -> lighten(c, 0.3), _testColors),
)
function register_gradient_colors(name::Symbol, colors::AVec{C}) where C<:Colorant
_gradients[name] = colors
end
include("color_gradients.jl")
default_gradient() = ColorGradient(:inferno)
# --------------------------------------------------------------
"Continuous gradient between values. Wraps a list of bounding colors and the values they represent."
struct ColorGradient <: ColorScheme
colors::Vector
values::Vector
function ColorGradient(cs::AVec, vals::AVec{S} = range(0, stop=1, length=length(cs)); alpha = nothing) where S<:Real
if length(cs) == length(vals)
return new(convertColor(cs,alpha), collect(vals))
end
# # otherwise interpolate evenly between the minval and maxval
# minval, maxval = minimum(vals), maximum(vals)
# vs = Float64[interpolate(minval, maxval, w) for w in range(0, stop = 1, length = length(cs))]
# new(convertColor(cs,alpha), vs)
# interpolate the colors for each value
vals = merge(range(0, stop=1, length=length(cs)), vals)
grad = ColorGradient(cs)
cs = [getColorZ(grad, z) for z in range(0, stop=1, length=length(vals))]
new(convertColor(cs, alpha), vals)
end
end
Base.getindex(cs::ColorGradient, i::Integer) = getColor(cs, i)
Base.getindex(cs::ColorGradient, z::Number) = getColorZ(cs, z)
# create a gradient from a symbol (blues, reds, etc) and vector of boundary values
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))))
cs = _gradients[s]
if vals == 0:0
vals = range(0, stop=1, length=length(cs))
end
ColorGradient(cs, vals; kw...)
end
# function ColorGradient{T<:Real}(cs::AVec, vals::AVec{T} = range(0, stop = 1, length = length(cs)); kw...)
# ColorGradient(map(convertColor, cs), vals; kw...)
# end
function ColorGradient(grad::ColorGradient; alpha = nothing)
ColorGradient(convertColor(grad.colors, alpha), grad.values)
end
# anything else just gets the default gradient
function ColorGradient(cw; alpha=nothing)
ColorGradient(default_gradient(), alpha=alpha)
end
getColor(gradient::ColorGradient, idx::Int) = gradient.colors[mod1(idx, length(gradient.colors))]
function getColorZ(gradient::ColorGradient, z::Real)
cs = gradient.colors
vs = gradient.values
n = length(cs)
@assert n > 0 && n == length(vs)
# can we just return the first color?
if z <= vs[1] || n == 1
return cs[1]
end
# find the bounding colors and interpolate
for i in 2:n
if z <= vs[i]
return interpolate_rgb(cs[i-1], cs[i], (z - vs[i-1]) / (vs[i] - vs[i-1]))
end
end
# if we get here, return the last color
cs[end]
end
getColorVector(gradient::ColorGradient) = gradient.colors
# for 0.3
Colors.RGBA(c::Colorant) = RGBA(red(c), green(c), blue(c), alpha(c))
Colors.RGB(c::Colorant) = RGB(red(c), green(c), blue(c))
function interpolate_rgb(c1::Colorant, c2::Colorant, w::Real)
rgb1 = RGBA(c1)
rgb2 = RGBA(c2)
r = interpolate(rgb1.r, rgb2.r, w)
g = interpolate(rgb1.g, rgb2.g, w)
b = interpolate(rgb1.b, rgb2.b, w)
a = interpolate(rgb1.alpha, rgb2.alpha, w)
RGBA(r, g, b, a)
end
function interpolate(v1::Real, v2::Real, w::Real)
(1-w) * v1 + w * v2
end
# --------------------------------------------------------------
"Wraps a function, taking an index and returning a Colorant"
struct ColorFunction <: ColorScheme
f::Function
end
getColor(scheme::ColorFunction, idx::Int) = scheme.f(idx)
# --------------------------------------------------------------
"Wraps a function, taking an z-value and returning a Colorant"
struct ColorZFunction <: ColorScheme
f::Function
end
getColorZ(scheme::ColorZFunction, z::Real) = scheme.f(z)
# --------------------------------------------------------------
"Wraps a vector of colors... may be vector of Symbol/String/Colorant"
struct ColorVector <: ColorScheme
v::Vector{Colorant}
ColorVector(v::AVec; alpha = nothing) = new(convertColor(v,alpha))
end
getColor(scheme::ColorVector, idx::Int) = convertColor(scheme.v[mod1(idx, length(scheme.v))])
getColorVector(scheme::ColorVector) = scheme.v
# --------------------------------------------------------------
"Wraps a single color"
struct ColorWrapper <: ColorScheme
c::RGBA
ColorWrapper(c::Colorant; alpha = nothing) = new(convertColor(c, alpha))
end
ColorWrapper(s::Symbol; alpha = nothing) = ColorWrapper(convertColor(parse(Colorant, s), alpha))
getColor(scheme::ColorWrapper, idx::Int) = scheme.c
getColorZ(scheme::ColorWrapper, z::Real) = scheme.c
convertColor(c::ColorWrapper, α::Nothing) = c.c
# --------------------------------------------------------------
isbackgrounddark(bgcolor::Color) = Lab(bgcolor).l < 0.5
# move closer to lighter/darker depending on background value
function adjustAway(val, bgval, vmin=0., vmax=100.)
if bgval < 0.5 * (vmax+vmin)
tmp = max(val, bgval)
return 0.5 * (tmp + max(tmp, vmax))
else
tmp = min(val, bgval)
return 0.5 * (tmp + min(tmp, vmin))
end
end
# borrowed from http://stackoverflow.com/a/1855903:
lightnessLevel(c::Colorant) = 0.299 * red(c) + 0.587 * green(c) + 0.114 * blue(c)
isdark(c::Colorant) = lightnessLevel(c) < 0.5
islight(c::Colorant) = !isdark(c)
function convertHexToRGB(h::Unsigned)
mask = 0x0000FF
RGB([(x & mask) / 0xFF for x in (h >> 16, h >> 8, h)]...)
end
# note: I found this list of hex values in a comment by Tatarize here: http://stackoverflow.com/a/12224359
const _masterColorList = [
0xFFFFFF, 0x000000, 0x0000FF, 0x00FF00, 0xFF0000, 0x01FFFE, 0xFFA6FE, 0xFFDB66, 0x006401, 0x010067,
0x95003A, 0x007DB5, 0xFF00F6, 0xFFEEE8, 0x774D00, 0x90FB92, 0x0076FF, 0xD5FF00, 0xFF937E, 0x6A826C,
0xFF029D, 0xFE8900, 0x7A4782, 0x7E2DD2, 0x85A900, 0xFF0056, 0xA42400, 0x00AE7E, 0x683D3B, 0xBDC6FF,
0x263400, 0xBDD393, 0x00B917, 0x9E008E, 0x001544, 0xC28C9F, 0xFF74A3, 0x01D0FF, 0x004754, 0xE56FFE,
0x788231, 0x0E4CA1, 0x91D0CB, 0xBE9970, 0x968AE8, 0xBB8800, 0x43002C, 0xDEFF74, 0x00FFC6, 0xFFE502,
0x620E00, 0x008F9C, 0x98FF52, 0x7544B1, 0xB500FF, 0x00FF78, 0xFF6E41, 0x005F39, 0x6B6882, 0x5FAD4E,
0xA75740, 0xA5FFD2, 0xFFB167, 0x009BFF, 0xE85EBE
]
const _allColors = map(convertHexToRGB, _masterColorList)
const _darkColors = filter(isdark, _allColors)
const _lightColors = filter(islight, _allColors)
const _sortedColorsForDarkBackground = vcat(_lightColors, reverse(_darkColors[2:end]))
const _sortedColorsForLightBackground = vcat(_darkColors, reverse(_lightColors[2:end]))
const _defaultNumColors = 17
# --------------------------------------------------------------
# Methods to automatically generate gradients for color selection based on
# background color and a short list of seed colors
# here are some magic constants that could be changed if you really want
const _lightness_darkbg = [80.0]
const _lightness_lightbg = [60.0]
const _lch_c_const = [60]
function adjust_lch(color, l, c)
lch = convert(LCHab, color)
convert(RGB, LCHab(l, c, lch.h))
end
function lightness_from_background(bgcolor)
bglight = convert(LCHab, bgcolor).l
bglight < 50.0 ? _lightness_darkbg[1] : _lightness_lightbg[1]
end
function gradient_from_list(cs)
zvalues = Plots.get_zvalues(length(cs))
indices = sortperm(zvalues)
sorted_colors = map(RGBA, cs[indices])
sorted_zvalues = zvalues[indices]
ColorGradient(sorted_colors, sorted_zvalues)
end
function generate_colorgradient(bgcolor = colorant"white";
color_bases = color_bases=[colorant"steelblue",colorant"orangered"],
lightness = lightness_from_background(bgcolor),
chroma = _lch_c_const[1],
n = _defaultNumColors)
seed_colors = vcat(bgcolor, map(c -> adjust_lch(c, lightness, chroma), color_bases))
colors = distinguishable_colors(n,
seed_colors,
lchoices=Float64[lightness],
cchoices=Float64[chroma],
hchoices=range(0, stop=340, length=20)
)[2:end]
gradient_from_list(colors)
end
function get_color_palette(palette, bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer)
grad = if palette == :auto
generate_colorgradient(bgcolor)
else
ColorGradient(palette)
end
zrng = get_zvalues(numcolors)
RGBA[getColorZ(grad, z) for z in zrng]
end
function get_color_palette(palette::Vector{C},
bgcolor::Union{Colorant,ColorWrapper}, numcolors::Integer) where C<:Colorant
palette
end
# ----------------------------------------------------------------------------------
function getpctrange(n::Int)
n > 0 || error()
n == 1 && return zeros(1)
zs = [0.0, 1.0]
for i in 3:n
sorted = sort(zs)
diffs = diff(sorted)
widestj = 0
widest = 0.0
for (j,d) in enumerate(diffs)
if d > widest
widest = d
widestj = j
end
end
push!(zs, sorted[widestj] + 0.5 * diffs[widestj])
end
zs
end
function get_zvalues(n::Int)
offsets = getpctrange(ceil(Int,n/4)+1)/4
offsets = vcat(offsets[1], offsets[3:end])
zvalues = Float64[]
for offset in offsets
append!(zvalues, offset + [0.0, 0.5, 0.25, 0.75])
end
vcat(zvalues[1], 1.0, zvalues[2:n-1])
end
# ----------------------------------------------------------------------------------
make255(x) = round(Int, 255 * x)
function webcolor(c::Color)
@sprintf("rgb(%d, %d, %d)", [make255(f(c)) for f in [red,green,blue]]...)
end
function webcolor(c::TransparentColor)
@sprintf("rgba(%d, %d, %d, %1.3f)", [make255(f(c)) for f in [red,green,blue]]..., alpha(c))
end
webcolor(cs::ColorScheme) = webcolor(getColor(cs))
webcolor(c) = webcolor(convertColor(c))
webcolor(c, α) = webcolor(convertColor(getColor(c), α))
# ----------------------------------------------------------------------------------
# converts a symbol or string into a colorant (Colors.RGB), and assigns a color automatically
function getSeriesRGBColor(c, sp::Subplot, n::Int)
if c == :auto
c = autopick(sp[:color_palette], n)
end
# c should now be a subtype of ColorScheme
colorscheme(c)
end

View File

@ -1,63 +0,0 @@
# TODO:
"""
- load Contours.jl similar to DataFrames
- method to build grid from x/y/z vectors
- method to wrap contours creation
- method to plot contours as custom shapes (TODO: create Stroke and Fill types and add markerstroke/markerfill args)
"""
# # ----------------------------------------------------------
# # ----------------------------------------------------------
# immutable Vertex
# x::Float64
# y::Float64
# z::Float64
# end
# immutable Edge
# v::Vertex
# u::Vertex
# end
# # ----------------------------------------------------------
# # one rectangle's z-values and the center vertex
# # z is ordered: topleft, topright, bottomright, bottomleft
# immutable GridRect
# z::Vector{Float64}
# center::Vertex
# data::Vector{Vertex}
# end
# type Grid
# xs::Vector{Float64}
# ys::Vector{Float64}
# rects::Matrix{GridRect}
# end
# function splitDataEvenly(v::AbstractVector{Float64}, n::Int)
# vs = sort(v)
# end
# # the goal here is to create the vertical and horizontal partitions
# # which define the grid, so that the data is somewhat evenly split
# function bucketData(x, y, z)
# end
# function buildGrid(x, y, z)
# # create
# end

View File

@ -1,99 +0,0 @@
# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
# Special handling for: no args, xmin/xmax, parametric, dataframes
# Then once inputs have been converted, build the series args, map functions, etc.
# 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
const FuncOrFuncs = Union{Function, AVec{Function}}
all3D(plotattributes::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image), get(plotattributes, :seriestype, :none))
# missing
convertToAnyVector(v::Nothing, plotattributes::KW) = Any[nothing], nothing
# fixed number of blank series
convertToAnyVector(n::Integer, plotattributes::KW) = Any[zeros(0) for i in 1:n], nothing
# numeric vector
convertToAnyVector(v::AVec{T}, plotattributes::KW) where {T<:Number} = Any[v], nothing
# string vector
convertToAnyVector(v::AVec{T}, plotattributes::KW) where {T<:AbstractString} = Any[v], nothing
function convertToAnyVector(v::AMat, plotattributes::KW)
if all3D(plotattributes)
Any[Surface(v)]
else
Any[v[:,i] for i in 1:size(v,2)]
end, nothing
end
# function
convertToAnyVector(f::Function, plotattributes::KW) = Any[f], nothing
# surface
convertToAnyVector(s::Surface, plotattributes::KW) = Any[s], nothing
# # vector of OHLC
# convertToAnyVector(v::AVec{OHLC}, plotattributes::KW) = Any[v], nothing
# dates
convertToAnyVector(dts::AVec{D}, plotattributes::KW) where {D<:Union{Date,DateTime}} = Any[dts], nothing
# list of things (maybe other vectors, functions, or something else)
function convertToAnyVector(v::AVec, plotattributes::KW)
if all(x -> typeof(x) <: Number, v)
# all real numbers wrap the whole vector as one item
Any[convert(Vector{Float64}, v)], nothing
else
# something else... treat each element as an item
vcat(Any[convertToAnyVector(vi, plotattributes)[1] for vi in v]...), nothing
# Any[vi for vi in v], nothing
end
end
convertToAnyVector(t::Tuple, plotattributes::KW) = Any[t], nothing
function convertToAnyVector(args...)
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
end
# --------------------------------------------------------------------
# TODO: can we avoid the copy here? one error that crops up is that mapping functions over the same array
# result in that array being shared. push!, etc will add too many items to that array
compute_x(x::Nothing, y::Nothing, z) = 1:size(z,1)
compute_x(x::Nothing, y, z) = 1:size(y,1)
compute_x(x::Function, y, z) = map(x, y)
compute_x(x, y, z) = copy(x)
# compute_y(x::Void, y::Function, z) = error()
compute_y(x::Nothing, y::Nothing, z) = 1:size(z,2)
compute_y(x, y::Function, z) = map(y, x)
compute_y(x, y, z) = copy(y)
compute_z(x, y, z::Function) = map(z, x, y)
compute_z(x, y, z::AbstractMatrix) = Surface(z)
compute_z(x, y, z::Nothing) = nothing
compute_z(x, y, z) = copy(z)
nobigs(v::AVec{BigFloat}) = map(Float64, v)
nobigs(v::AVec{BigInt}) = map(Int64, v)
nobigs(v) = v
@noinline function compute_xyz(x, y, z)
x = compute_x(x,y,z)
y = compute_y(x,y,z)
z = compute_z(x,y,z)
nobigs(x), nobigs(y), nobigs(z)
end
# not allowed
compute_xyz(x::Nothing, y::FuncOrFuncs, z) = error("If you want to plot the function `$y`, you need to define the x values!")
compute_xyz(x::Nothing, y::Nothing, z::FuncOrFuncs) = error("If you want to plot the function `$z`, you need to define x and y values!")
compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all nothing!")
# --------------------------------------------------------------------

View File

@ -86,8 +86,9 @@ yaxis!("YLABEL", :log10)
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 FileIO, PlotReferenceImages import FileIO
img = FileIO.load(joinpath(dirname(pathof(PlotReferenceImages)), "..", "Plots","pyplot","0.7.0","ref1.png")) path = download("http://juliaplots.org/PlotReferenceImages.jl/Plots/pyplot/0.7.0/ref1.png")
img = FileIO.load(path)
plot(img) plot(img)
end)] end)]
), ),
@ -435,8 +436,37 @@ each line segment or marker in the plot.
end)] end)]
), ),
PlotExample("Portfolio Composition maps",
"""
see: http://stackoverflow.com/a/37732384/5075246
""",
[:(begin
using Random
Random.seed!(111)
tickers = ["IBM", "Google", "Apple", "Intel"]
N = 10
D = length(tickers)
weights = rand(N,D)
weights ./= sum(weights, dims = 2)
returns = sort!((1:N) + D*randn(N))
portfoliocomposition(weights, returns, labels = permutedims(tickers))
end)]
),
] ]
# Some constants for PlotDocs and PlotReferenceImages
_animation_examples = [2, 30]
_backend_skips = Dict(
:gr => [25, 30],
:pyplot => [25, 30],
:plotlyjs => [2, 21, 25, 30, 31],
:pgfplots => [2, 5, 6, 10, 16, 20, 22, 23, 25, 28, 30],
)
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
# make and display one plot # make and display one plot
@ -473,7 +503,7 @@ function test_examples(pkgname::Symbol; debug = false, disp = true, sleep = noth
plts[i] = plt plts[i] = plt
catch ex catch ex
# TODO: put error info into markdown? # TODO: put error info into markdown?
warn("Example $pkgname:$i:$(_examples[i].header) failed with: $ex") @warn("Example $pkgname:$i:$(_examples[i].header) failed with: $ex")
end end
if sleep != nothing if sleep != nothing
Base.sleep(sleep) Base.sleep(sleep)

24
src/fileio.jl Normal file
View File

@ -0,0 +1,24 @@
# ---------------------------------------------------------
# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert
_fileio_load(@nospecialize(filename::AbstractString)) = FileIO.load(filename::AbstractString)
_fileio_save(@nospecialize(filename::AbstractString), @nospecialize(x)) = FileIO.save(filename::AbstractString, x)
function _show_pdfbackends(io::IO, ::MIME"image/png", plt::Plot)
fn = tempname()
# first save a pdf file
pdf(plt, fn)
# load that pdf into a FileIO Stream
s = _fileio_load(fn * ".pdf")
# save a png
pngfn = fn * ".png"
_fileio_save(pngfn, s)
# now write from the file
write(io, read(open(pngfn), String))
end
const PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend}

59
src/ijulia.jl Normal file
View File

@ -0,0 +1,59 @@
const use_local_dependencies = Ref(false)
const use_local_plotlyjs = Ref(false)
function _init_ijulia_plotting()
# IJulia is more stable with local file
use_local_plotlyjs[] = isfile(plotly_local_file_path)
ENV["MPLBACKEND"] = "Agg"
end
"""
Add extra jupyter mimetypes to display_dict based on the plot backed.
The default is nothing, except for plotly based backends, where it
adds data for `application/vnd.plotly.v1+json` that is used in
frontends like jupyterlab and nteract.
"""
_ijulia__extra_mime_info!(plt::Plot, out::Dict) = out
function _ijulia__extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict)
out["application/vnd.plotly.v1+json"] = JSON.lower(plt.o)
out
end
function _ijulia__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
_ijulia__extra_mime_info!(plt, out)
out
end

View File

@ -1,22 +1,33 @@
function __init__() using REPL
function _plots_defaults()
if isdefined(Main, :PLOTS_DEFAULTS) if isdefined(Main, :PLOTS_DEFAULTS)
if haskey(Main.PLOTS_DEFAULTS, :theme) Dict{Symbol,Any}(Main.PLOTS_DEFAULTS)
theme(Main.PLOTS_DEFAULTS[:theme]) else
Dict{Symbol,Any}()
end end
for (k,v) in Main.PLOTS_DEFAULTS end
function __init__()
user_defaults = _plots_defaults()
if haskey(user_defaults, :theme)
theme(user_defaults[:theme])
end
for (k,v) in user_defaults
k == :theme || default(k, v) k == :theme || default(k, v)
end end
end
pushdisplay(PlotsDisplay()) insert!(Base.Multimedia.displays, findlast(x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay())
atreplinit(i -> begin atreplinit(i -> begin
if PlotDisplay() in Base.Multimedia.displays while PlotsDisplay() in Base.Multimedia.displays
popdisplay(PlotsDisplay()) popdisplay(PlotsDisplay())
end end
pushdisplay(PlotsDisplay()) insert!(Base.Multimedia.displays, findlast(x -> x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay())
end) end)
@require GLVisualize = "4086de5b-f4b6-55f3-abb0-b8c73827585f" include(joinpath(@__DIR__, "backends", "glvisualize.jl"))
@require HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" include(joinpath(@__DIR__, "backends", "hdf5.jl")) @require HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" include(joinpath(@__DIR__, "backends", "hdf5.jl"))
@require InspectDR = "d0351b0e-4b05-5898-87b3-e2a8edfddd1d" include(joinpath(@__DIR__, "backends", "inspectdr.jl")) @require InspectDR = "d0351b0e-4b05-5898-87b3-e2a8edfddd1d" include(joinpath(@__DIR__, "backends", "inspectdr.jl"))
@require PGFPlots = "3b7a836e-365b-5785-a47d-02c71176b4aa" include(joinpath(@__DIR__, "backends", "pgfplots.jl")) @require PGFPlots = "3b7a836e-365b-5785-a47d-02c71176b4aa" include(joinpath(@__DIR__, "backends", "pgfplots.jl"))
@ -24,60 +35,26 @@ function __init__()
@require PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" include(joinpath(@__DIR__, "backends", "pyplot.jl")) @require PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" include(joinpath(@__DIR__, "backends", "pyplot.jl"))
@require UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" include(joinpath(@__DIR__, "backends", "unicodeplots.jl")) @require UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" include(joinpath(@__DIR__, "backends", "unicodeplots.jl"))
# ---------------------------------------------------------
# IJulia
# ---------------------------------------------------------
@require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin @require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin
if IJulia.inited if IJulia.inited
_init_ijulia_plotting()
""" IJulia.display_dict(plt::Plot) = _ijulia_display_dict(plt)
Add extra jupyter mimetypes to display_dict based on the plot backed. end
The default is nothing, except for plotly based backends, where it
adds data for `application/vnd.plotly.v1+json` that is used in
frontends like jupyterlab and nteract.
"""
_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
function _extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict) if haskey(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL")
out["application/vnd.plotly.v1+json"] = Dict( use_local_plotlyjs[] = ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] == "true"
:data => plotly_series(plt), use_local_dependencies[] = isfile(plotly_local_file_path) && use_local_plotlyjs[]
:layout => plotly_layout(plt) if use_local_plotlyjs[] && !isfile(plotly_local_file_path)
) @warn("PLOTS_HOST_DEPENDENCY_LOCAL is set to true, but no local plotly file found. run Pkg.build(\"Plots\") and make sure PLOTS_HOST_DEPENDENCY_LOCAL is set to true")
out
end 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 else
error("Unsupported output type $output_type") use_local_dependencies[] = use_local_plotlyjs[]
end
_extra_mime_info!(plt, out)
out
end end
ENV["MPLBACKEND"] = "Agg"
end @require FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" begin
_show(io::IO, mime::MIME"image/png", plt::Plot{<:PDFBackends}) = _show_pdfbackends(io, mime, plt)
end end
end end

View File

@ -111,7 +111,7 @@ function resolve_mixed(mix::MixedMeasures, sp::Subplot, letter::Symbol)
pct += mix.len / totlen pct += mix.len / totlen
end end
if pct != 0 if pct != 0
amin, amax = axis_limits(sp[Symbol(letter,:axis)]) amin, amax = axis_limits(sp, letter)
xy += pct * (amax-amin) xy += pct * (amax-amin)
end end
xy xy

View File

@ -55,9 +55,7 @@ tex(fn::AbstractString) = tex(current(), fn)
function html(plt::Plot, fn::AbstractString) function html(plt::Plot, fn::AbstractString)
fn = addExtension(fn, "html") fn = addExtension(fn, "html")
io = open(fn, "w") io = open(fn, "w")
_use_remote[] = true
show(io, MIME("text/html"), plt) show(io, MIME("text/html"), plt)
_use_remote[] = false
close(io) close(io)
end end
html(fn::AbstractString) = html(current(), fn) html(fn::AbstractString) = html(current(), fn)
@ -156,7 +154,6 @@ end
const _best_html_output_type = KW( const _best_html_output_type = KW(
:pyplot => :png, :pyplot => :png,
:unicodeplots => :txt, :unicodeplots => :txt,
:glvisualize => :png,
:plotlyjs => :html, :plotlyjs => :html,
:plotly => :html :plotly => :html
) )
@ -192,7 +189,7 @@ end
# for writing to io streams... first prepare, then callback # for writing to io streams... first prepare, then callback
for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml", for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml",
"application/eps", "application/pdf", "application/postscript", "application/eps", "application/pdf", "application/postscript",
"application/x-tex") "application/x-tex", "application/vnd.plotly.v1+json")
@eval function Base.show(io::IO, m::MIME{Symbol($mime)}, plt::Plot) @eval function Base.show(io::IO, m::MIME{Symbol($mime)}, plt::Plot)
if haskey(io, :juno_plotsize) if haskey(io, :juno_plotsize)
showjuno(io, m, plt) showjuno(io, m, plt)
@ -200,6 +197,7 @@ for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml
prepare_output(plt) prepare_output(plt)
_show(io, m, plt) _show(io, m, plt)
end end
return nothing
end end
end end
@ -210,30 +208,6 @@ _show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt)
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
const PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend}
if is_installed("FileIO")
@eval import FileIO
function _show(io::IO, ::MIME"image/png", plt::Plot{<:PDFBackends})
fn = tempname()
# first save a pdf file
pdf(plt, fn)
# load that pdf into a FileIO Stream
s = FileIO.load(fn * ".pdf")
# save a png
pngfn = fn * ".png"
FileIO.save(pngfn, s)
# now write from the file
write(io, read(open(pngfn), String))
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)

View File

@ -166,9 +166,9 @@ end
function _plot!(plt::Plot, plotattributes::KW, args::Tuple) function _plot!(plt::Plot, plotattributes::KW, args::Tuple)
plotattributes[:plot_object] = plt plotattributes[:plot_object] = plt
if !isempty(args) && !isdefined(Main, :StatPlots) && if !isempty(args) && !isdefined(Main, :StatsPlots) &&
first(split(string(typeof(args[1])), ".")) == "DataFrames" first(split(string(typeof(args[1])), ".")) == "DataFrames"
@warn("You're trying to plot a DataFrame, but this functionality is provided by StatPlots") @warn("You're trying to plot a DataFrame, but this functionality is provided by StatsPlots")
end end
# -------------------------------- # --------------------------------

View File

@ -18,7 +18,7 @@ end
plotattr([attr]) plotattr([attr])
Look up the properties of a Plots attribute, or specify an attribute type. Call `plotattr()` for options. 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/. The information is the same as that given on https://docs.juliaplots.org/latest/attributes/.
""" """
function plotattr() function plotattr()
println("Specify an attribute type to get a list of supported attributes. Options are $(attrtypes())") println("Specify an attribute type to get a list of supported attributes. Options are $(attrtypes())")
@ -50,8 +50,13 @@ function plotattr(attrtype::Symbol, attribute::AbstractString)
desc = get(_arg_desc, attribute, "") desc = get(_arg_desc, attribute, "")
first_period_idx = findfirst(isequal('.'), desc) first_period_idx = findfirst(isequal('.'), desc)
if isnothing(first_period_idx)
typedesc = ""
desc = strip(desc)
else
typedesc = desc[1:first_period_idx-1] typedesc = desc[1:first_period_idx-1]
desc = strip(desc[first_period_idx+1:end]) desc = strip(desc[first_period_idx+1:end])
end
als = keys(filter(x->x[2]==attribute, _keyAliases)) |> collect |> sort als = keys(filter(x->x[2]==attribute, _keyAliases)) |> collect |> sort
als = join(map(string,als), ", ") als = join(map(string,als), ", ")
def = _attribute_defaults[attrtype][attribute] def = _attribute_defaults[attrtype][attribute]

View File

@ -47,16 +47,47 @@ end
num_series(x::AMat) = size(x,2) num_series(x::AMat) = size(x,2)
num_series(x) = 1 num_series(x) = 1
RecipesBase.apply_recipe(plotattributes::KW, ::Type{T}, plt::AbstractPlot) where {T} = throw(MethodError("Unmatched plot recipe: $T")) RecipesBase.apply_recipe(plotattributes::KW, ::Type{T}, plt::AbstractPlot) where {T} = throw(MethodError(T, "Unmatched plot recipe: $T"))
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# for seriestype `line`, need to sort by x values # for seriestype `line`, need to sort by x values
const POTENTIAL_VECTOR_ARGUMENTS = [
:seriescolor, :seriesalpha,
:linecolor, :linealpha, :linewidth, :linestyle, :line_z,
:fillcolor, :fillalpha, :fill_z,
:markercolor, :markeralpha, :markershape, :marker_z,
:markerstrokecolor, :markerstrokealpha,
:yerror, :yerror,
:series_annotations, :fillrange
]
@recipe function f(::Type{Val{:line}}, x, y, z) @recipe function f(::Type{Val{:line}}, x, y, z)
indices = sortperm(x) indices = sortperm(x)
x := x[indices] x := x[indices]
y := y[indices] y := y[indices]
# sort vector arguments
for arg in POTENTIAL_VECTOR_ARGUMENTS
if typeof(plotattributes[arg]) <: AVec
plotattributes[arg] = _cycle(plotattributes[arg], indices)
end
end
# a tuple as fillrange has to be handled differently
if typeof(plotattributes[:fillrange]) <: Tuple
lower, upper = plotattributes[:fillrange]
if typeof(lower) <: AVec
lower = _cycle(lower, indices)
end
if typeof(upper) <: AVec
upper = _cycle(upper, indices)
end
plotattributes[:fillrange] = (lower, upper)
end
if typeof(z) <: AVec if typeof(z) <: AVec
z := z[indices] z := z[indices]
end end
@ -65,19 +96,6 @@ RecipesBase.apply_recipe(plotattributes::KW, ::Type{T}, plt::AbstractPlot) where
end end
@deps line path @deps line path
function hvline_limits(axis::Axis)
vmin, vmax = axis_limits(axis)
if vmin >= vmax
if isfinite(vmin)
vmax = vmin + 1
else
vmin, vmax = 0.0, 1.1
end
end
vmin, vmax
end
@recipe function f(::Type{Val{:hline}}, x, y, z) @recipe function f(::Type{Val{:hline}}, x, y, z)
n = length(y) n = length(y)
newx = repeat(Float64[-1, 1, NaN], n) newx = repeat(Float64[-1, 1, NaN], n)
@ -222,11 +240,12 @@ end
n = length(x) n = length(x)
fr = plotattributes[:fillrange] fr = plotattributes[:fillrange]
if fr == nothing if fr == nothing
yaxis = plotattributes[:subplot][:yaxis] sp = plotattributes[:subplot]
yaxis = sp[:yaxis]
fr = if yaxis[:scale] == :identity fr = if yaxis[:scale] == :identity
0.0 0.0
else else
NaNMath.min(axis_limits(yaxis)[1], ignorenan_minimum(y)) NaNMath.min(axis_limits(sp, :y)[1], ignorenan_minimum(y))
end end
end end
newx, newy = zeros(3n), zeros(3n) newx, newy = zeros(3n), zeros(3n)
@ -519,13 +538,15 @@ function _stepbins_path(edge, weights, baseline::Real, xscale::Symbol, yscale::S
w, it_state_w = it_tuple_w w, it_state_w = it_tuple_w
if (log_scale_x && a 0) if (log_scale_x && a 0)
a = b/_logScaleBases[xscale]^3 a = oftype(a, b/_logScaleBases[xscale]^3)
end end
if isnan(w) if isnan(w)
if !isnan(last_w) if !isnan(last_w)
push!(x, a) push!(x, a)
push!(y, baseline) push!(y, baseline)
push!(x, NaN)
push!(y, NaN)
end end
else else
if isnan(last_w) if isnan(last_w)
@ -538,8 +559,8 @@ function _stepbins_path(edge, weights, baseline::Real, xscale::Symbol, yscale::S
push!(y, w) push!(y, w)
end end
a = b a = oftype(a, b)
last_w = w last_w = oftype(last_w, w)
it_tuple_e = iterate(edge, it_state_e) it_tuple_e = iterate(edge, it_state_e)
it_tuple_w = iterate(weights, it_state_w) it_tuple_w = iterate(weights, it_state_w)
@ -586,12 +607,13 @@ end
end end
Plots.@deps stepbins path Plots.@deps stepbins path
wand_edges(x...) = (@warn("Load the StatPlots package in order to use :wand bins. Defaulting to :auto", once = true); :auto) wand_edges(x...) = (@warn("Load the StatsPlots package in order to use :wand bins. Defaulting to :auto", once = true); :auto)
function _auto_binning_nbins(vs::NTuple{N,AbstractVector}, dim::Integer; mode::Symbol = :auto) where N function _auto_binning_nbins(vs::NTuple{N,AbstractVector}, dim::Integer; mode::Symbol = :auto) where N
_cl(x) = ceil(Int, NaNMath.max(x, one(x))) max_bins = 10_000
_cl(x) = min(ceil(Int, max(x, one(x))), max_bins)
_iqr(v) = (q = quantile(v, 0.75) - quantile(v, 0.25); q > 0 ? q : oftype(q, 1)) _iqr(v) = (q = quantile(v, 0.75) - quantile(v, 0.25); q > 0 ? q : oftype(q, 1))
_span(v) = ignorenan_maximum(v) - ignorenan_minimum(v) _span(v) = maximum(v) - minimum(v)
n_samples = length(LinearIndices(first(vs))) n_samples = length(LinearIndices(first(vs)))
@ -616,7 +638,7 @@ function _auto_binning_nbins(vs::NTuple{N,AbstractVector}, dim::Integer; mode::S
elseif mode == :fd # FreedmanDiaconis rule elseif mode == :fd # FreedmanDiaconis rule
_cl(_span(v) / (2 * _iqr(v) / nd)) _cl(_span(v) / (2 * _iqr(v) / nd))
elseif mode == :wand elseif mode == :wand
wand_edges(v) # this makes this function not type stable, but the type instability does not propagate _cl(wand_edges(v)) # this makes this function not type stable, but the type instability does not propagate
else else
error("Unknown auto-binning mode $mode") error("Unknown auto-binning mode $mode")
end end
@ -635,11 +657,19 @@ _hist_edges(vs::NTuple{N,AbstractVector}, binning::Union{Integer, Symbol, Abstra
_hist_norm_mode(mode::Symbol) = mode _hist_norm_mode(mode::Symbol) = mode
_hist_norm_mode(mode::Bool) = mode ? :pdf : :none _hist_norm_mode(mode::Bool) = mode ? :pdf : :none
_filternans(vs::NTuple{1,AbstractVector}) = filter!.(isfinite, vs)
function _filternans(vs::NTuple{N,AbstractVector}) where N
_invertedindex(v, not) = [j for (i,j) in enumerate(v) if !(i not)]
nots = union(Set.(findall.(!isfinite, vs))...)
_invertedindex.(vs, Ref(nots))
end
function _make_hist(vs::NTuple{N,AbstractVector}, binning; normed = false, weights = nothing) where N function _make_hist(vs::NTuple{N,AbstractVector}, binning; normed = false, weights = nothing) where N
edges = _hist_edges(vs, binning) localvs = _filternans(vs)
edges = _hist_edges(localvs, binning)
h = float( weights == nothing ? h = float( weights == nothing ?
StatsBase.fit(StatsBase.Histogram, vs, edges, closed = :left) : StatsBase.fit(StatsBase.Histogram, localvs, edges, closed = :left) :
StatsBase.fit(StatsBase.Histogram, vs, StatsBase.Weights(weights), edges, closed = :left) StatsBase.fit(StatsBase.Histogram, localvs, StatsBase.Weights(weights), edges, closed = :left)
) )
normalize!(h, mode = _hist_norm_mode(normed)) normalize!(h, mode = _hist_norm_mode(normed))
end end
@ -1053,7 +1083,7 @@ end
# ------------------------------------------------- # -------------------------------------------------
"Adds a+bx... straight line over the current plot, without changing the axis limits" "Adds ax+b... straight line over the current plot, without changing the axis limits"
abline!(plt::Plot, a, b; kw...) = plot!(plt, [0, 1], [b, b+a]; seriestype = :straightline, kw...) abline!(plt::Plot, a, b; kw...) = plot!(plt, [0, 1], [b, b+a]; seriestype = :straightline, kw...)
abline!(args...; kw...) = abline!(current(), args...; kw...) abline!(args...; kw...) = abline!(current(), args...; kw...)
@ -1069,6 +1099,7 @@ timeformatter(t) = string(Dates.Time(Dates.Nanosecond(t)))
@recipe f(::Type{Date}, dt::Date) = (dt -> Dates.value(dt), dateformatter) @recipe f(::Type{Date}, dt::Date) = (dt -> Dates.value(dt), dateformatter)
@recipe f(::Type{DateTime}, dt::DateTime) = (dt -> Dates.value(dt), datetimeformatter) @recipe f(::Type{DateTime}, dt::DateTime) = (dt -> Dates.value(dt), datetimeformatter)
@recipe f(::Type{Dates.Time}, t::Dates.Time) = (t -> Dates.value(t), timeformatter) @recipe f(::Type{Dates.Time}, t::Dates.Time) = (t -> Dates.value(t), timeformatter)
@recipe f(::Type{P}, t::P) where P <: Dates.Period = (t -> Dates.value(t), t -> string(P(t)))
# ------------------------------------------------- # -------------------------------------------------
# Complex Numbers # Complex Numbers
@ -1131,3 +1162,49 @@ end
title := string(grad.args[1]) title := string(grad.args[1])
z z
end end
# Moved in from PlotRecipes - see: http://stackoverflow.com/a/37732384/5075246
@userplot PortfolioComposition
# this shows the shifting composition of a basket of something over a variable
# - "returns" are the dependent variable
# - "weights" are a matrix where the ith column is the composition for returns[i]
# - since each polygon is its own series, you can assign labels easily
@recipe function f(pc::PortfolioComposition)
weights, returns = pc.args
n = length(returns)
weights = cumsum(weights, dims = 2)
seriestype := :shape
# create a filled polygon for each item
for c=1:size(weights,2)
sx = vcat(weights[:,c], c==1 ? zeros(n) : reverse(weights[:,c-1]))
sy = vcat(returns, reverse(returns))
@series Plots.isvertical(plotattributes) ? (sx, sy) : (sy, sx)
end
end
"""
areaplot([x,] y)
areaplot!([x,] y)
Draw a stacked area plot of the matrix y.
# Examples
```julia-repl
julia> areaplot(1:3, [1 2 3; 7 8 9; 4 5 6], seriescolor = [:red :green :blue], fillalpha = [0.2 0.3 0.4])
```
"""
@userplot AreaPlot
@recipe function f(a::AreaPlot)
data = cumsum(a.args[end], dims=2)
x = length(a.args) == 1 ? (1:size(data, 1)) : a.args[1]
seriestype := :line
for i in 1:size(data, 2)
@series begin
fillrange := i > 1 ? data[:,i-1] : 0
x, data[:,i]
end
end
end

View File

@ -7,72 +7,33 @@
# 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
const FuncOrFuncs{F} = Union{F, Vector{F}, Matrix{F}} const FuncOrFuncs{F} = Union{F, Vector{F}, Matrix{F}}
const DataPoint = Union{Number, AbstractString, Missing}
const SeriesData = Union{AVec{<:DataPoint}, Function, Surface, Volume}
all3D(plotattributes::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image, :plots_heatmap), get(plotattributes, :seriestype, :none)) prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
prepareSeriesData(::Nothing) = nothing
prepareSeriesData(s::SeriesData) = handlemissings(s)
# unknown handlemissings(v) = v
convertToAnyVector(x, plotattributes::KW) = error("No user recipe defined for $(typeof(x))") handlemissings(v::AbstractArray{Union{T,Missing}}) where T <: Number = replace(v, missing => NaN)
handlemissings(v::AbstractArray{Union{T,Missing}}) where T <: AbstractString = replace(v, missing => "")
handlemissings(s::Surface) = Surface(handlemissings(s.surf))
handlemissings(v::Volume) = Volume(handlemissings(v.v), v.x_extents, v.y_extents, v.z_extents)
# missing # default: assume x represents a single series
convertToAnyVector(v::Nothing, plotattributes::KW) = Any[nothing], nothing convertToAnyVector(x) = Any[prepareSeriesData(x)]
# fixed number of blank series # fixed number of blank series
convertToAnyVector(n::Integer, plotattributes::KW) = Any[zeros(0) for i in 1:n], nothing convertToAnyVector(n::Integer) = Any[zeros(0) for i in 1:n]
# numeric vector # vector of data points is a single series
convertToAnyVector(v::AVec{T}, plotattributes::KW) where {T<:Number} = Any[v], nothing convertToAnyVector(v::AVec{<:DataPoint}) = Any[prepareSeriesData(v)]
convertToAnyVector(v::AVec{Union{Missing, T}}, plotattributes::KW) where {T<:Number} = Any[replace(v, missing => NaN)], nothing
# string vector
convertToAnyVector(v::AVec{T}, plotattributes::KW) where {T<:AbstractString} = Any[v], nothing
convertToAnyVector(v::AVec{Union{Missing, T}}, plotattributes::KW) where {T<:AbstractString} = Any[replace(v, missing => "")], nothing
function convertToAnyVector(v::AMat, plotattributes::KW)
v = handlemissings(v)
if all3D(plotattributes)
Any[Surface(v)]
else
Any[v[:,i] for i in 1:size(v,2)]
end, nothing
end
handlemissings(v::AMat) = v
handlemissings(v::AMat{T}) where T <: Number = replace(v, missing => NaN)
handlemissings(v::AMat{T}) where T <: String = replace(v, missing => "")
# function
convertToAnyVector(f::Function, plotattributes::KW) = Any[f], nothing
# surface
convertToAnyVector(s::Surface, plotattributes::KW) = Any[s], nothing
# volume
convertToAnyVector(v::Volume, plotattributes::KW) = Any[v], nothing
# # vector of OHLC
# convertToAnyVector(v::AVec{OHLC}, plotattributes::KW) = Any[v], nothing
# # dates
convertToAnyVector(dts::AVec{D}, plotattributes::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, plotattributes::KW) convertToAnyVector(v::AVec) = vcat((convertToAnyVector(vi) for vi in v)...)
if all(x -> typeof(x) <: Number, v)
# all real numbers wrap the whole vector as one item
Any[convert(Vector{Float64}, v)], nothing
else
# something else... treat each element as an item
vcat(Any[convertToAnyVector(vi, plotattributes)[1] for vi in v]...), nothing
# Any[vi for vi in v], nothing
end
end
convertToAnyVector(t::Tuple, plotattributes::KW) = Any[t], nothing # Matrix is split into columns
convertToAnyVector(v::AMat{<:DataPoint}) = Any[prepareSeriesData(v[:,i]) for i in 1:size(v,2)]
function convertToAnyVector(args...)
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -135,23 +96,24 @@ struct SliceIt end
z = z.data z = z.data
end end
xs, _ = convertToAnyVector(x, plotattributes) xs = convertToAnyVector(x)
ys, _ = convertToAnyVector(y, plotattributes) ys = convertToAnyVector(y)
zs, _ = convertToAnyVector(z, plotattributes) zs = convertToAnyVector(z)
fr = pop!(plotattributes, :fillrange, nothing) fr = pop!(plotattributes, :fillrange, nothing)
fillranges, _ = if typeof(fr) <: Number fillranges = if typeof(fr) <: Number
([fr],nothing) [fr]
else else
convertToAnyVector(fr, plotattributes) convertToAnyVector(fr)
end end
mf = length(fillranges) mf = length(fillranges)
rib = pop!(plotattributes, :ribbon, nothing) rib = pop!(plotattributes, :ribbon, nothing)
ribbons, _ = if typeof(rib) <: Number ribbons = if typeof(rib) <: Number
([fr],nothing) [rib]
else else
convertToAnyVector(rib, plotattributes) convertToAnyVector(rib)
end end
mr = length(ribbons) mr = length(ribbons)
@ -193,8 +155,9 @@ _apply_type_recipe(plotattributes, v) = RecipesBase.apply_recipe(plotattributes,
# 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(plotattributes, v::AbstractArray) function _apply_type_recipe(plotattributes, v::AbstractArray)
isempty(v) && return Float64[] isempty(skipmissing(v)) && return Float64[]
args = RecipesBase.apply_recipe(plotattributes, typeof(v[1]), v[1])[1].args x = first(skipmissing(v))
args = RecipesBase.apply_recipe(plotattributes, typeof(x), x)[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
Formatted(map(numfunc, v), formatter) Formatted(map(numfunc, v), formatter)
@ -292,8 +255,10 @@ end
@recipe f(n::Integer) = is3d(get(plotattributes,: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)
all3D(plotattributes::KW) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image, :plots_heatmap), get(plotattributes, :seriestype, :none))
# 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(mat::AMat{T}) where T<:Union{Integer,AbstractFloat} @recipe function f(mat::AMat{T}) where T<:Union{Integer,AbstractFloat,Missing}
if all3D(plotattributes) if all3D(plotattributes)
n,m = size(mat) n,m = size(mat)
wrap_surfaces(plotattributes) wrap_surfaces(plotattributes)
@ -316,26 +281,33 @@ end
end end
# assume this is a Volume, so construct one # assume this is a Volume, so construct one
@recipe function f(vol::AbstractArray{T,3}, args...) where T<:Number @recipe function f(vol::AbstractArray{T,3}, args...) where T<:Union{Number,Missing}
seriestype := :volume seriestype := :volume
SliceIt, nothing, Volume(vol, args...), nothing SliceIt, nothing, Volume(vol, args...), nothing
end end
# # images - grays # # images - grays
function clamp_greys!(mat::AMat{T}) where T<:Gray
for i in eachindex(mat)
mat[i].val < 0 && (mat[i] = Gray(0))
mat[i].val > 1 && (mat[i] = Gray(1))
end
mat
end
@recipe function f(mat::AMat{T}) where T<:Gray @recipe function f(mat::AMat{T}) where T<:Gray
n, m = size(mat) n, m = size(mat)
if is_seriestype_supported(:image) if is_seriestype_supported(:image)
seriestype := :image seriestype := :image
yflip --> true yflip --> true
SliceIt, 1:m, 1:n, Surface(mat) SliceIt, 1:m, 1:n, Surface(clamp_greys!(mat))
else else
seriestype := :heatmap seriestype := :heatmap
yflip --> true yflip --> true
cbar --> false 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(clamp!(convert(Matrix{Float64}, mat), 0., 1.))
end end
end end
@ -384,10 +356,11 @@ end
@recipe function f(f::FuncOrFuncs{F}) where F<:Function @recipe function f(f::FuncOrFuncs{F}) where F<:Function
plt = plotattributes[:plot_object] plt = plotattributes[:plot_object]
xmin, xmax = try xmin, xmax = try
axis_limits(plt[1][:xaxis]) axis_limits(plt[1], :x)
catch catch
xm = tryrange(f, [-5,-1,0,0.01]) xinv = invscalefunc(get(plotattributes, :xscale, :identity))
xm, tryrange(f, filter(x->x>xm, [5,1,0.99, 0, -0.01])) xm = tryrange(f, xinv.([-5,-1,0,0.01]))
xm, tryrange(f, filter(x->x>xm, xinv.([5,1,0.99, 0, -0.01])))
end end
f, xmin, xmax f, xmin, xmax
@ -517,16 +490,23 @@ end
# #
# # special handling... xmin/xmax with parametric function(s) # # special handling... xmin/xmax with parametric function(s)
@recipe function f(f::Function, xmin::Number, xmax::Number) @recipe function f(f::Function, xmin::Number, xmax::Number)
xs = adapted_grid(f, (xmin, xmax)) xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
xs = _scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
xs, f xs, f
end end
@recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function @recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function
xs = Any[adapted_grid(f, (xmin, xmax)) for f in fs] xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
xs = Any[_scaled_adapted_grid(f, xscale, yscale, xmin, xmax) for f in fs]
xs, fs xs, fs
end end
@recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, u::AVec) where {F<:Function,G<:Function} = 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(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) where {F<:Function,G<:Function} = fx, fy, range(umin, stop = umax, length = n) @recipe f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, umin::Number, umax::Number, n = 200) where {F<:Function,G<:Function} = fx, fy, range(umin, stop = umax, length = n)
function _scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
(xf, xinv), (yf, yinv) = ((scalefunc(s),invscalefunc(s)) for s in (xscale,yscale))
xinv.(adapted_grid(yf∘f∘xinv, xf.((xmin, xmax))))
end
# #
# # special handling... 3D parametric function(s) # # special handling... 3D parametric function(s)
@recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, u::AVec) where {F<:Function,G<:Function,H<:Function} @recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, u::AVec) where {F<:Function,G<:Function,H<:Function}
@ -539,36 +519,17 @@ end
# #
# #
# # -------------------------------------------------------------------- # # --------------------------------------------------------------------
# # Lists of tuples and FixedSizeArrays # # Lists of tuples and GeometryTypes.Points
# # -------------------------------------------------------------------- # # --------------------------------------------------------------------
# #
# # if we get an unhandled tuple, just splat it in
@recipe f(tup::Tuple) = tup
# @recipe f(v::AVec{<:Tuple}) = unzip(v)
# # (x,y) tuples @recipe f(v::AVec{<:GeometryTypes.Point}) = unzip(v)
@recipe f(xy::AVec{Tuple{R1,R2}}) where {R1<:Number,R2<:Number} = unzip(xy) @recipe f(tup::Tuple) = [tup]
@recipe f(xy::Tuple{R1,R2}) where {R1<:Number,R2<:Number} = [xy[1]], [xy[2]] @recipe f(p::GeometryTypes.Point) = [p]
# # Special case for 4-tuples in :ohlc series
# # (x,y,z) tuples @recipe f(xyuv::AVec{<:Tuple{R1,R2,R3,R4}}) where {R1,R2,R3,R4} = get(plotattributes,:seriestype,:path)==:ohlc ? OHLC[OHLC(t...) for t in xyuv] : unzip(xyuv)
@recipe f(xyz::AVec{Tuple{R1,R2,R3}}) where {R1<:Number,R2<:Number,R3<:Number} = unzip(xyz)
@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
@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(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
@recipe f(xy::AVec{FixedSizeArrays.Vec{2,T}}) where {T<:Number} = unzip(xy)
@recipe f(xy::FixedSizeArrays.Vec{2,T}) where {T<:Number} = [xy[1]], [xy[2]]
#
# # 3D FixedSizeArrays
@recipe f(xyz::AVec{FixedSizeArrays.Vec{3,T}}) where {T<:Number} = unzip(xyz)
@recipe f(xyz::FixedSizeArrays.Vec{3,T}) where {T<:Number} = [xyz[1]], [xyz[2]], [xyz[3]]
# #
# # -------------------------------------------------------------------- # # --------------------------------------------------------------------
@ -589,7 +550,7 @@ end
# end # end
splittable_kw(key, val, lengthGroup) = false splittable_kw(key, val, lengthGroup) = false
splittable_kw(key, val::AbstractArray, lengthGroup) = (key != :group) && size(val,1) == lengthGroup splittable_kw(key, val::AbstractArray, lengthGroup) = !(key in (:group, :color_palette)) && size(val,1) == lengthGroup
splittable_kw(key, val::Tuple, lengthGroup) = all(splittable_kw.(key, val, 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) splittable_kw(key, val::SeriesAnnotations, lengthGroup) = splittable_kw(key, val.strs, lengthGroup)

455
src/shorthands.jl Normal file
View File

@ -0,0 +1,455 @@
"""
scatter(x,y)
scatter!(x,y)
Make a scatter plot of y vs x.
# Examples
```julia-repl
julia> scatter([1,2,3],[4,5,6],markersize=[3,4,5],markercolor=[:red,:green,:blue])
julia> scatter([(1,4),(2,5),(3,6)])
```
"""
@shorthands scatter
"""
bar(x,y)
bar!(x,y)
Make a bar plot of y vs x.
# Arguments
- $(_document_argument("bar_position"))
- $(_document_argument("bar_width"))
- $(_document_argument("bar_edges"))
- $(_document_argument("orientation"))
# Examples
```julia-repl
julia> bar([1,2,3],[4,5,6],fillcolor=[:red,:green,:blue],fillalpha=[0.2,0.4,0.6])
julia> bar([(1,4),(2,5),(3,6)])
```
"""
@shorthands bar
@shorthands barh
"""
histogram(x)
histogram!(x)
Plot a histogram.
# Arguments
- `x`: AbstractVector of values to be binned
- $(_document_argument("bins"))
- `weights`: Vector of weights for the values in `x`, for weighted bin counts
- $(_document_argument("normalize"))
- $(_document_argument("bar_position"))
- $(_document_argument("bar_width"))
- $(_document_argument("bar_edges"))
- $(_document_argument("orientation"))
# Example
```julia-repl
julia> histogram([1,2,1,1,4,3,8],bins=0:8)
```
"""
@shorthands histogram
"""
barhist(x)
barhist!(x)
Make a histogram bar plot. See `histogram`.
"""
@shorthands barhist
"""
stephist(x)
stephist(x)
Make a histogram step plot (bin counts are represented using horizontal lines
instead of bars). See `histogram`.
"""
@shorthands stephist
"""
scatterhist(x)
scatterhist!(x)
Make a histogram scatter plot (bin counts are represented using points
instead of bars). See `histogram`.
"""
@shorthands scatterhist
"""
histogram2d(x,y)
histogram2d!(x,y)
Plot a two-dimensional histogram.
# Arguments
- `bins`: Number of bins (if an `Integer`) or bin edges (if an `AbtractVector`)
- `weights`: Vector of weights for the values in `x`. Each entry of x contributes
its weight to the height of its bin.
# Example
```julia-repl
julia> histogram2d(randn(10_000),randn(10_000))
```
"""
@shorthands histogram2d
"""
density(x)
density!(x)
Make a line plot of a kernel density estimate of x.
# Arguments
- `x`: AbstractVector of samples for probability density estimation
# Example
```julia-repl
julia> using StatsPlots
julia> density(randn(100_000))
```
"""
@shorthands density
"""
heatmap(x,y,z)
heatmap!(x,y,z)
Plot a heatmap of the rectangular array `z`.
# Example
```julia-repl
julia> heatmap(randn(10,10))
```
"""
@shorthands heatmap
@shorthands plots_heatmap
"""
hexbin(x,y)
hexbin!(x,y)
Make a hexagonal binning plot (a histogram of the observations `(x[i],y[i])`
with hexagonal bins)
# Example
```julia-repl
julia> hexbin(randn(10_000), randn(10_000))
```
"""
@shorthands hexbin
"""
sticks(x,y)
sticks!(x,y)
Draw a stick plot of y vs x.
# Example
```julia-repl
julia> sticks(1:10)
```
"""
@shorthands sticks
"""
hline(y)
hline!(y)
Draw horizontal lines at positions specified by the values in
the AbstractVector `y`
# Example
```julia-repl
julia> hline([-1,0,2])
```
"""
@shorthands hline
"""
vline(x)
vline!(x)
Draw vertical lines at positions specified by the values in
the AbstractVector `x`
# Example
```julia-repl
julia> vline([-1,0,2])
```
"""
@shorthands vline
"""
hspan(y)
Draw a rectangle between the horizontal line at position `y[1]`
and the horizontal line at position `y[2]`. If `length(y) ≥ 4`,
then further rectangles are drawn between `y[3]` and `y[4]`,
`y[5]` and `y[6]`, and so on. If `length(y)` is odd, then the
last entry of `y` is ignored.
# Example
```julia-repl
julia> hspan(1:6)
```
"""
@shorthands hspan
"""
vspan(x)
Draw a rectangle between the vertical line at position `x[1]`
and the vertical line at position `x[2]`. If `length(x) ≥ 4`,
then further rectangles are drawn between `x[3]` and `x[4]`,
`x[5]` and `x[6]`, and so on. If `length(x)` is odd, then the
last entry of `x` is ignored.
# Example
```julia-repl
julia> vspan(1:6)
```
"""
@shorthands vspan
"""
ohlc(x,y::Vector{OHLC})
ohlc!(x,y::Vector{OHLC})
Make open-high-low-close plot. Each entry of y is represented by a vertical
segment extending from the low value to the high value, with short horizontal
segments on the left and right indicating the open and close values, respectively.
# Example
```julia-repl
julia> meanprices = cumsum(randn(100))
julia> y = OHLC[(p+rand(),p+1,p-1,p+rand()) for p in meanprices]
julia> ohlc(y)
```
"""
@shorthands ohlc
"""
contour(x,y,z)
contour!(x,y,z)
Draw contour lines of the `Surface` z.
# Arguments
- `levels`: Contour levels (if `AbstractVector`) or number of levels (if `Integer`)
- `fill`: Bool. Fill area between contours or draw contours only (false by default)
# Example
```julia-repl
julia> x = y = range(-20, 20, length = 100)
julia> contour(x, y, (x, y) -> x^2 + y^2)
```
"""
@shorthands contour
"An alias for `contour` with fill = true."
@shorthands contourf
@shorthands contour3d
"""
surface(x,y,z)
surface!(x,y,z)
Draw a 3D surface plot.
# Example
```julia-repl
julia> x = y = range(-3, 3, length = 100)
julia> surface(x, y, (x, y) -> sinc(norm([x, y])))
```
"""
@shorthands surface
"""
wireframe(x,y,z)
wireframe!(x,y,z)
Draw a 3D wireframe plot.
# Example
```julia-repl
julia> wireframe(1:10,1:10,randn(10,10))
```
"""
@shorthands wireframe
"""
path3d(x,y,z)
path3d!(x,y,z)
Plot a 3D path from `(x[1],y[1],z[1])` to `(x[2],y[2],z[2])`,
..., to `(x[end],y[end],z[end])`.
# Example
```julia-repl
julia> path3d([0,1,2,3],[0,1,4,9],[0,1,8,27])
```
"""
@shorthands path3d
"""
scatter3d(x,y,z)
scatter3d!(x,y,z)
Make a 3D scatter plot.
# Example
```julia-repl
julia> scatter3d([0,1,2,3],[0,1,4,9],[0,1,8,27])
```
"""
@shorthands scatter3d
"""
boxplot(x, y)
boxplot!(x, y)
Make a box and whisker plot.
# Keyword arguments
- `notch`: Bool. Notch the box plot? (false)
- `range`: Real. Values more than range*IQR below the first quartile
or above the third quartile are shown as outliers (1.5)
- `outliers`: Bool. Show outliers? (true)
- `whisker_width`: Real or Symbol. Length of whiskers (:match)
# Example
```julia-repl
julia> using StatsPlots
julia> boxplot(repeat([1,2,3],outer=100),randn(300))
```
"""
@shorthands boxplot
"""
violin(x,y,z)
violin!(x,y,z)
Make a violin plot.
# Example
```julia-repl
julia> violin(repeat([1,2,3],outer=100),randn(300))
```
"""
@shorthands violin
"""
quiver(x,y,quiver=(u,v))
quiver!(x,y,quiver=(u,v))
Make a quiver (vector field) plot. The `i`th vector extends
from `(x[i],y[i])` to `(x[i] + u[i], y[i] + v[i])`.
# Example
```julia-repl
julia> quiver([1,2,3],[3,2,1],quiver=([1,1,1],[1,2,3]))
```
"""
@shorthands quiver
"""
curves(x,y)
curves!(x,y)
Draw a Bezier curve from `(x[1],y[1])` to `(x[end],y[end])`
with control points `(x[2],y[2]), ..., (x[end-1],y[end]-1)`
# Example
```julia-repl
julia> curves([1,2,3,4],[1,1,2,4])
```
"""
@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)
"Plot with 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...)
"Add xlabel to an existing plot"
xlabel!(s::AbstractString; kw...) = plot!(; xlabel = s, kw...)
"Add ylabel to an existing plot"
ylabel!(s::AbstractString; kw...) = plot!(; ylabel = s, kw...)
"Set xlims for an existing plot"
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...)
ylims!(ymin::Real, ymax::Real; kw...) = plot!(; ylims = (ymin,ymax), kw...)
zlims!(zmin::Real, zmax::Real; kw...) = plot!(; zlims = (zmin,zmax), kw...)
"Set xticks for an existing plot"
xticks!(v::TicksArgs; kw...) where {T<:Real} = plot!(; xticks = v, kw...)
"Set yticks for an existing plot"
yticks!(v::TicksArgs; 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...)
"""
annotate!(anns...)
Add annotations to an existing plot.
# Arguments
- `anns`: An `AbstractVector` of tuples of the form (x,y,text). The text object
can be an String or PlotText
# Example
```julia-repl
julia> plot(1:10)
julia> annotate!([(7,3,"(7,3)"),(3,7,text("hey", 14, :left, :top, :green))])
```
"""
annotate!(anns...; 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...)
"Flip the current plots' y axis"
yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...)
"Specify x axis attributes for an existing plot"
xaxis!(args...; kw...) = plot!(; xaxis = args, kw...)
"Specify y axis attributes for an existing plot"
yaxis!(args...; kw...) = plot!(; yaxis = args, kw...)
xgrid!(args...; kw...) = plot!(; xgrid = args, kw...)
ygrid!(args...; kw...) = plot!(; ygrid = args, kw...)

View File

@ -100,7 +100,7 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
end end
end end
srand(1) Random.seed!(1)
label := "" label := ""
colorbar := false colorbar := false

View File

@ -194,7 +194,9 @@ end
function iter_segments(series::Series) function iter_segments(series::Series)
x, y, z = series[:x], series[:y], series[:z] x, y, z = series[:x], series[:y], series[:z]
if has_attribute_segments(series) if x == nothing
return UnitRange{Int}[]
elseif has_attribute_segments(series)
if series[:seriestype] in (:scatter, :scatter3d) if series[:seriestype] in (:scatter, :scatter3d)
return [[i] for i in 1:length(y)] return [[i] for i in 1:length(y)]
else else
@ -212,36 +214,19 @@ 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 -> try isnan(_cycle(a,i)) catch MethodError false end, 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(args::Tuple) = i -> anynan(i,args)
allnan(istart::Int, iend::Int, args::Tuple) = all(i -> anynan(i, args), istart:iend) anynan(istart::Int, iend::Int, args::Tuple) = any(anynan(args), istart:iend)
allnan(istart::Int, iend::Int, args::Tuple) = all(anynan(args), istart:iend)
function Base.iterate(itr::SegmentsIterator, nextidx::Int = 1) function Base.iterate(itr::SegmentsIterator, nextidx::Int = 1)
nextidx > itr.n && return nothing i = findfirst(!anynan(itr.args), nextidx:itr.n)
if nextidx == 1 && !any(isempty,itr.args) && anynan(1, itr.args) i === nothing && return nothing
nextidx = 2 nextval = nextidx + i - 1
end
i = istart = iend = nextidx j = findfirst(anynan(itr.args), nextval:itr.n)
nextnan = j === nothing ? itr.n + 1 : nextval + j - 1
# find the next NaN, and iend is the one before nextval:nextnan-1, nextnan
while i <= itr.n + 1
if i > itr.n || anynan(i, itr.args)
# done... array end or found NaN
iend = i-1
break
end
i += 1
end
# find the next non-NaN, and set nextidx
while i <= itr.n
if !anynan(i, itr.args)
break
end
i += 1
end
istart:iend, i
end end
# Find minimal type that can contain NaN and x # Find minimal type that can contain NaN and x
@ -273,6 +258,9 @@ _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)
_as_gradient(grad::ColorGradient) = grad
_as_gradient(c::Colorant) = ColorGradient([c,c])
makevec(v::AVec) = v makevec(v::AVec) = v
makevec(v::T) where {T} = T[v] makevec(v::T) where {T} = T[v]
@ -283,18 +271,17 @@ 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(fs::AVec{F}, u::AVec) where {F<:Function} = [map(f, u) for f in fs] mapFuncOrFuncs(fs::AVec{F}, u::AVec) where {F<:Function} = [map(f, u) for f in fs]
unzip(xy::AVec{Tuple{X,Y}}) where {X,Y} = [t[1] for t in xy], [t[2] for t in xy] for i in 2:4
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] @eval begin
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(v::Union{AVec{<:Tuple{Vararg{T,$i} where T}},
AVec{<:GeometryTypes.Point{$i}}}) = $(Expr(:tuple, (:([t[$j] for t in v]) for j=1:i)...))
end
end
unzip(xy::AVec{FixedSizeArrays.Vec{2,T}}) where {T} = T[t[1] for t in xy], T[t[2] for t in xy] unzip(v::Union{AVec{<:GeometryTypes.Point{N}},
unzip(xy::FixedSizeArrays.Vec{2,T}) where {T} = T[xy[1]], T[xy[2]] AVec{<:Tuple{Vararg{T,N} where T}}}) where N = error("$N-dimensional unzip not implemented.")
unzip(v::Union{AVec{<:GeometryTypes.Point},
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] AVec{<:Tuple}}) = error("Can't unzip points of different dimensions.")
unzip(xyz::FixedSizeArrays.Vec{3,T}) where {T} = T[xyz[1]], T[xyz[2]], T[xyz[3]]
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(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)
@ -357,6 +344,7 @@ const _scale_base = Dict{Symbol, Real}(
) )
function _heatmap_edges(v::AVec) function _heatmap_edges(v::AVec)
length(v) == 1 && return v[1] .+ [-0.5, 0.5]
vmin, vmax = ignorenan_extrema(v) vmin, vmax = ignorenan_extrema(v)
extra_min = (v[2] - v[1]) / 2 extra_min = (v[2] - v[1]) / 2
extra_max = (v[end] - v[end - 1]) / 2 extra_max = (v[end] - v[end - 1]) / 2
@ -369,38 +357,14 @@ function heatmap_edges(v::AVec, scale::Symbol = :identity)
map(invf, _heatmap_edges(map(f,v))) map(invf, _heatmap_edges(map(f,v)))
end end
function calc_r_extrema(x, y) function convert_to_polar(theta, r, r_extrema = ignorenan_extrema(r))
xmin, xmax = ignorenan_extrema(x)
ymin, ymax = ignorenan_extrema(y)
r = 0.5 * NaNMath.min(xmax - xmin, ymax - ymin)
ignorenan_extrema(r)
end
function convert_to_polar(x, y, r_extrema = calc_r_extrema(x, y))
rmin, rmax = r_extrema rmin, rmax = r_extrema
theta, r = filter_radial_data(x, y, r_extrema)
r = (r .- rmin) ./ (rmax .- rmin) r = (r .- rmin) ./ (rmax .- rmin)
x = r.*cos.(theta) x = r.*cos.(theta)
y = r.*sin.(theta) y = r.*sin.(theta)
x, y x, y
end 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
x[i] = _cycle(theta, i)
y[i] = _cycle(r, i)
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
end
function fakedata(sz...) function fakedata(sz...)
y = zeros(sz...) y = zeros(sz...)
for r in 2:size(y,1) for r in 2:size(y,1)
@ -412,14 +376,6 @@ end
isijulia() = :IJulia in nameof.(collect(values(Base.loaded_modules))) isijulia() = :IJulia in nameof.(collect(values(Base.loaded_modules)))
isatom() = :Atom in nameof.(collect(values(Base.loaded_modules))) isatom() = :Atom in nameof.(collect(values(Base.loaded_modules)))
function is_installed(pkgstr::AbstractString)
try
Pkg.installed(pkgstr) === nothing ? false : true
catch
false
end
end
istuple(::Tuple) = true istuple(::Tuple) = true
istuple(::Any) = false istuple(::Any) = false
isvector(::AVec) = true isvector(::AVec) = true
@ -438,7 +394,7 @@ isvertical(series::Series) = isvertical(series.plotattributes)
ticksType(ticks::AVec{T}) where {T<:Real} = :ticks ticksType(ticks::AVec{T}) where {T<:Real} = :ticks
ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels
ticksType(ticks::Tuple{T,S}) where {T<:AVec,S<:AVec} = :ticks_and_labels ticksType(ticks::Tuple{T,S}) where {T<:Union{AVec,Tuple},S<:Union{AVec,Tuple}} = :ticks_and_labels
ticksType(ticks) = :invalid ticksType(ticks) = :invalid
limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits
@ -534,7 +490,7 @@ function concatenate_fillrange(x,y::Tuple)
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, letter)
end end
""" """
@ -571,9 +527,9 @@ function get_clims(sp::Subplot)
z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface) z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface)
for series in series_list(sp) 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]) 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) if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Union{Missing, Real})
zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals.surf)...) zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals.surf)...)
elseif (vals != nothing) && (eltype(vals) <: Real) elseif (vals != nothing) && (eltype(vals) <: Union{Missing, Real})
zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals)...) zmin, zmax = _update_clims(zmin, zmax, ignorenan_extrema(vals)...)
end end
end end
@ -588,35 +544,50 @@ end
_update_clims(zmin, zmax, emin, emax) = min(zmin, emin), max(zmax, emax) _update_clims(zmin, zmax, emin, emax) = min(zmin, emin), max(zmax, emax)
function hascolorbar(series::Series) @enum ColorbarStyle cbar_gradient cbar_fill cbar_lines
st = series[:seriestype]
hascbar = st == :heatmap function colorbar_style(series::Series)
if st == :contour colorbar_entry = series[:colorbar_entry]
hascbar = (isscalar(series[:levels]) ? (series[:levels] > 1) : (length(series[:levels]) > 1)) && (length(unique(Array(series[:z]))) > 1) if !(colorbar_entry isa Bool)
@warn "Non-boolean colorbar_entry ignored."
colorbar_entry = true
end end
if series[:marker_z] != nothing || series[:line_z] != nothing || series[:fill_z] != nothing
hascbar = true if !colorbar_entry
nothing
elseif isfilledcontour(series)
cbar_fill
elseif iscontour(series)
cbar_lines
elseif series[:seriestype] (:heatmap,:surface) ||
any(series[z] !== nothing for z [:marker_z,:line_z,:fill_z])
cbar_gradient
else
nothing
end end
# no colorbar if we are creating a surface LightSource
if xor(st == :surface, series[:fill_z] != nothing)
hascbar = true
end
return hascbar
end end
function hascolorbar(sp::Subplot) hascolorbar(series::Series) = colorbar_style(series) !== nothing
cbar = sp[:colorbar] hascolorbar(sp::Subplot) = sp[:colorbar] != :none && any(hascolorbar(s) for s in series_list(sp))
hascbar = false
if cbar != :none iscontour(series::Series) = series[:seriestype] == :contour
for series in series_list(sp) isfilledcontour(series::Series) = iscontour(series) && series[:fillrange] !== nothing
if hascolorbar(series)
hascbar = true function contour_levels(series::Series, clims)
iscontour(series) || error("Not a contour series")
zmin, zmax = clims
levels = series[:levels]
if levels isa Integer
levels = range(zmin, stop=zmax, length=levels+2)
if !isfilledcontour(series)
levels = levels[2:end-1]
end end
end end
end levels
hascbar
end end
for comp in (:line, :fill, :marker) for comp in (:line, :fill, :marker)
compcolor = string(comp, :color) compcolor = string(comp, :color)
@ -653,6 +624,9 @@ for comp in (:line, :fill, :marker)
end end
end end
single_color(c, v = 0.5) = c
single_color(grad::ColorGradient, v = 0.5) = grad[v]
function get_linewidth(series, i::Int = 1) function get_linewidth(series, i::Int = 1)
_cycle(series[:linewidth], i) _cycle(series[:linewidth], i)
end end
@ -680,7 +654,7 @@ function has_attribute_segments(series::Series)
end end
series[:seriestype] == :shape && return false series[:seriestype] == :shape && return false
# ... else we check relevant attributes if they have multiple inputs # ... 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)) 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 for attr in (:line_z, :fill_z, :marker_z))
end end
# --------------------------------------------------------------- # ---------------------------------------------------------------
@ -733,7 +707,7 @@ function with(f::Function, args...; kw...)
# save the backend # save the backend
if CURRENT_BACKEND.sym == :none if CURRENT_BACKEND.sym == :none
pickDefaultBackend() _pick_default_backend()
end end
oldbackend = CURRENT_BACKEND.sym oldbackend = CURRENT_BACKEND.sym
@ -745,7 +719,7 @@ function with(f::Function, args...; kw...)
end end
# # TODO: generalize this strategy to allow args as much as possible # # TODO: generalize this strategy to allow args as much as possible
# # as in: with(:gadfly, :scatter, :legend, :grid) do; ...; end # # as in: with(:gr, :scatter, :legend, :grid) do; ...; end
# # TODO: can we generalize this enough to also do something similar in the plot commands?? # # TODO: can we generalize this enough to also do something similar in the plot commands??
# k = :seriestype # k = :seriestype
@ -803,24 +777,24 @@ function debugplots(on = true)
_debugMode.on = on _debugMode.on = on
end end
debugshow(x) = show(x) debugshow(io, x) = show(io, x)
debugshow(x::AbstractArray) = print(summary(x)) debugshow(io, x::AbstractArray) = print(io, summary(x))
function dumpdict(plotattributes::KW, prefix = "", alwaysshow = false) function dumpdict(io::IO, plotattributes::KW, prefix = "", alwaysshow = false)
_debugMode.on || alwaysshow || return _debugMode.on || alwaysshow || return
println() println(io)
if prefix != "" if prefix != ""
println(prefix, ":") println(io, prefix, ":")
end end
for k in sort(collect(keys(plotattributes))) for k in sort(collect(keys(plotattributes)))
@printf("%14s: ", k) @printf("%14s: ", k)
debugshow(plotattributes[k]) debugshow(io, plotattributes[k])
println() println(io)
end end
println() println(io)
end end
DD(plotattributes::KW, prefix = "") = dumpdict(plotattributes, prefix, true) DD(io::IO, plotattributes::KW, prefix = "") = dumpdict(io, plotattributes, prefix, true)
DD(plotattributes::KW, prefix = "") = DD(stdout, plotattributes, prefix)
function dumpcallstack() function dumpcallstack()
error() # well... you wanted the stacktrace, didn't you?!? error() # well... you wanted the stacktrace, didn't you?!?
@ -1206,3 +1180,37 @@ end
function construct_categorical_data(x::AbstractArray, axis::Axis) function construct_categorical_data(x::AbstractArray, axis::Axis)
map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x) map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x)
end end
_fmt_paragraph(paragraph::AbstractString;kwargs...) = _fmt_paragraph(IOBuffer(),paragraph,0;kwargs...)
function _fmt_paragraph(io::IOBuffer,
remaining_text::AbstractString,
column_count::Integer;
fillwidth=60,
leadingspaces=0)
kwargs = (fillwidth = fillwidth, leadingspaces = leadingspaces)
m = match(r"(.*?) (.*)",remaining_text)
if isa(m,Nothing)
if column_count + length(remaining_text) fillwidth
print(io,remaining_text)
String(take!(io))
else
print(io,"\n"*" "^leadingspaces*remaining_text)
String(take!(io))
end
else
if column_count + length(m[1]) fillwidth
print(io,"$(m[1]) ")
_fmt_paragraph(io,m[2],column_count + length(m[1]) + 1;kwargs...)
else
print(io,"\n"*" "^leadingspaces*"$(m[1]) ")
_fmt_paragraph(io,m[2],leadingspaces;kwargs...)
end
end
end
function _document_argument(S::AbstractString)
_fmt_paragraph("`$S`: "*_arg_desc[Symbol(S)],leadingspaces = 6 + length(S))
end

1
test/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
reference_images

View File

@ -1,10 +0,0 @@
StatPlots
Images
ImageMagick
@osx QuartzImageIO
FileIO
GR 0.31.0
RDatasets
VisualRegressionTests
UnicodePlots
LaTeXStrings

View File

@ -1,35 +0,0 @@
using Pkg
# need this to use Conda
# ENV["PYTHON"] = ""
to_add = [
PackageSpec(url="https://github.com/JuliaPlots/PlotReferenceImages.jl.git"),
# PackageSpec(url="https://github.com/JuliaStats/KernelDensity.jl.git"),
PackageSpec(name="PlotUtils", rev="master"),
PackageSpec(name="RecipesBase", rev="master"),
# PackageSpec(name="Blink", rev="master"),
# PackageSpec(name="Rsvg", rev="master"),
# PackageSpec(name="PlotlyJS", rev="master"),
# PackageSpec(name="VisualRegressionTests", rev="master"),
# PackageSpec("PyPlot"),
# PackageSpec("InspectDR"),
]
if isinteractive()
append!(to_add, [
PackageSpec(name="FileIO"),
PackageSpec(name="ImageMagick"),
PackageSpec(name="UnicodePlots"),
PackageSpec(name="VisualRegressionTests"),
])
end
Pkg.add(to_add)
Pkg.build("ImageMagick")
# Pkg.build("GR")
# Pkg.build("Blink")
# import Blink
# Blink.AtomShell.install()
# Pkg.build("PyPlot")

View File

@ -1,50 +1,46 @@
import Plots._current_plots_version
using VisualRegressionTests # Taken from MakieGallery
# using ExamplePlots """
Downloads the reference images from ReferenceImages for a specific version
if isinteractive() """
@eval Main import Gtk function download_reference(version = v"0.0.1")
download_dir = abspath(@__DIR__, "reference_images")
isdir(download_dir) || mkpath(download_dir)
tarfile = joinpath(download_dir, "reference_images.zip")
url = "https://github.com/JuliaPlots/PlotReferenceImages.jl/archive/v$(version).tar.gz"
refpath = joinpath(download_dir, "PlotReferenceImages.jl-$(version)")
if !isdir(refpath) # if not yet downloaded
@info "downloading reference images for version $version"
download(url, tarfile)
BinaryProvider.unpack(tarfile, download_dir)
# check again after download
if !isdir(refpath)
error("Something went wrong while downloading reference images. Plots can't be compared to references")
else
rm(tarfile, force = true)
end
else
@info "using reference images for version $version (already downloaded)"
end
refpath
end end
# import DataFrames, RDatasets const ref_image_dir = download_reference()
# don't let pyplot use a gui... it'll crash
# note: Agg will set gui -> :none in PyPlot
# ENV["MPLBACKEND"] = "Agg"
# try
# @eval import PyPlot
# @info("Matplotlib version: $(PyPlot.matplotlib[:__version__])")
# end
using Plots
# using StatPlots
import PlotReferenceImages
using Random
using Test
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
# is referenced in a button press callback (the button clicked callback will call notify() on that condition)
const _current_plots_version = v"0.20.3"
function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], tol = 1e-2) function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = isinteractive(), sigma = [1,1], tol = 1e-2)
Plots._debugMode.on = debug Plots._debugMode.on = debug
example = Plots._examples[idx] example = Plots._examples[idx]
Plots.theme(:default)
@info("Testing plot: $pkg:$idx:$(example.header)") @info("Testing plot: $pkg:$idx:$(example.header)")
backend(pkg) backend(pkg)
backend() backend()
default(size=(500,300))
# ensure consistent results # ensure consistent results
Random.seed!(1234) Random.seed!(1234)
# reference image directory setup # reference image directory setup
# refdir = joinpath(Pkg.dir("ExamplePlots"), "test", "refimg", string(pkg)) refdir = joinpath(ref_image_dir, "Plots", string(pkg))
refdir = joinpath(dirname(pathof(PlotReferenceImages)), "..", "Plots", string(pkg))
fn = "ref$idx.png" fn = "ref$idx.png"
# firgure out version info # firgure out version info
@ -76,7 +72,9 @@ function image_comparison_tests(pkg::Symbol, idx::Int; debug = false, popup = is
# test function # test function
func = (fn, idx) -> begin func = (fn, idx) -> begin
map(eval, example.exprs) expr = Expr(:block)
append!(expr.args, example.exprs)
eval(expr)
png(fn) png(fn)
end end

View File

@ -1,8 +1,12 @@
module PlotsTests using VisualRegressionTests
using Plots
using Random
using BinaryProvider
using Test
using FileIO
using GeometryTypes
include("add_packages.jl")
include("imgcomp.jl") include("imgcomp.jl")
# don't actually show the plots # don't actually show the plots
Random.seed!(1234) Random.seed!(1234)
default(show=false, reuse=true) default(show=false, reuse=true)
@ -14,109 +18,25 @@ img_tol = isinteractive() ? 1e-2 : 10e-2
@test gr() == Plots.GRBackend() @test gr() == Plots.GRBackend()
@test backend() == Plots.GRBackend() @test backend() == Plots.GRBackend()
@static if Sys.islinux()
image_comparison_facts(:gr, tol=img_tol, skip = [25, 30]) image_comparison_facts(:gr, tol=img_tol, skip = [25, 30])
end
end end
#@testset "PyPlot" begin
# @test pyplot() == Plots.PyPlotBackend()
# @test backend() == Plots.PyPlotBackend()
#
# image_comparison_facts(:pyplot, tol=img_tol)
#end
@testset "UnicodePlots" begin @testset "UnicodePlots" begin
@test unicodeplots() == Plots.UnicodePlotsBackend() @test unicodeplots() == Plots.UnicodePlotsBackend()
@test backend() == Plots.UnicodePlotsBackend() @test backend() == Plots.UnicodePlotsBackend()
# lets just make sure it runs without error # lets just make sure it runs without error
@test isa(plot(rand(10)), Plots.Plot) == true p = plot(rand(10))
@test isa(p, Plots.Plot) == true
@test isa(display(p), Nothing) == true
p = bar(randn(10))
@test isa(p, Plots.Plot) == true
@test isa(display(p), Nothing) == true
end 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()
#
# if Sys.islinux() && isinteractive()
# image_comparison_facts(:plotlyjs,
# skip=[
# 2, # animation (skipped for speed)
# 27, # (polar plots) takes very long / not working
# 31, # animation (skipped for speed)
# ],
# tol=img_tol)
# end
# end
# InspectDR returns that error on travis:
# 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
# @testset "InspectDR" begin
# @test inspectdr() == Plots.InspectDRBackend()
# @test backend() == Plots.InspectDRBackend()
#
# image_comparison_facts(:inspectdr,
# skip=[
# 2, # animation
# 6, # heatmap not defined
# 10, # heatmap not defined
# 22, # contour not defined
# 23, # pie not defined
# 27, # polar plot not working
# 28, # heatmap not defined
# 31, # animation
# ],
# tol=img_tol)
# end
# @testset "Plotly" begin
# @test plotly() == Plots.PlotlyBackend()
# @test backend() == Plots.PlotlyBackend()
#
# # # until png generation is reliable on OSX, just test on linux
# # @static Sys.islinux() && image_comparison_facts(:plotly, only=[1,3,4,7,8,9,10,11,12,14,15,20,22,23,27], tol=img_tol)
# 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
# image_comparison_facts(:immerse, only=[1], tol=img_tol)
# end
# @testset "PlotlyJS" begin
# @test plotlyjs() == Plots.PlotlyJSBackend()
# @test backend() == Plots.PlotlyJSBackend()
#
# # as long as we can plot anything without error, it should be the same as Plotly
# image_comparison_facts(:plotlyjs, only=[1], tol=img_tol)
# end
# @testset "Gadfly" begin
# @test gadfly() == Plots.GadflyBackend()
# @test backend() == Plots.GadflyBackend()
#
# @test typeof(plot(1:10)) == Plots.Plot{Plots.GadflyBackend}
# @test plot(Int[1,2,3], rand(3)) == not(nothing)
# @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], tol=img_tol)
# end
@testset "Axes" begin @testset "Axes" begin
p = plot() p = plot()
axis = p.subplots[1][:xaxis] axis = p.subplots[1][:xaxis]
@ -132,46 +52,44 @@ end
end end
@testset "NoFail" begin @testset "NoFail" begin
histogram([1, 0, 0, 0, 0, 0]) plots = [histogram([1, 0, 0, 0, 0, 0]),
plot([missing]),
plot([missing; 1:4]),
plot([fill(missing,10); 1:4]),
plot([1 1; 1 missing]),
plot(["a" "b"; missing "d"], [1 2; 3 4])]
for plt in plots
display(plt)
end
end end
# tests for preprocessing recipes @testset "Segments" begin
function segments(args...)
segs = UnitRange{Int}[]
for seg in iter_segments(args...)
push!(segs,seg)
end
segs
end
# @testset "recipes" begin nan10 = fill(NaN,10)
@test segments(11:20) == [1:10]
@test segments([NaN]) == []
@test segments(nan10) == []
@test segments([nan10; 1:5]) == [11:15]
@test segments([1:5;nan10]) == [1:5]
@test segments([nan10; 1:5; nan10; 1:5; nan10]) == [11:15, 26:30]
@test segments([NaN; 1], 1:10) == [2:2, 4:4, 6:6, 8:8, 10:10]
@test segments([nan10; 1:15], [1:15; nan10]) == [11:15]
end
# user recipe @testset "Utils" begin
zipped = ([(1,2)], [("a","b")], [(1,"a"),(2,"b")],
# type T end [(1,2),(3,4)], [(1,2,3),(3,4,5)], [(1,2,3,4),(3,4,5,6)],
# @recipe function f(::T) [(1,2.0),(missing,missing)], [(1,missing),(missing,"a")],
# line := (3,0.3,:red) [(missing,missing)], [(missing,missing,missing),("a","b","c")])
# marker := (20,0.5,:blue,:o) for z in zipped
# bg := :yellow @test isequal(collect(zip(Plots.unzip(z)...)), z)
# rand(10) @test isequal(collect(zip(Plots.unzip(Point.(z))...)), z)
# end end
# plot(T()) end
# plot recipe
# @recipe function f(::Type{Val{:hiplt}},plt::Plot)
# line := (3,0.3,:red)
# marker := (20,0.5,:blue,:o)
# t := :path
# bg:=:green
# ()
# end
# plot(rand(10),t=:hiplt)
# series recipe
# @recipe function f(::Type{Val{:hi}},x,y,z)
# line := (3,0.3,:red)
# marker := (20,0.5,:blue,:o)
# t := :path
# ()
# end
# plot(rand(10),t=:hiplt)
# end
end # module

View File

@ -1,36 +0,0 @@
import SnoopCompile
### Log the compiles
# This only needs to be run once (to generate "/tmp/plots_compiles.csv")
# SnoopCompile.@snoop "/tmp/plots_compiles.csv" begin
# include(joinpath(dirname(@__FILE__), "runtests.jl"))
# end
# ----------------------------------------------------------
### Parse the compiles and generate precompilation scripts
# This can be run repeatedly to tweak the scripts
# IMPORTANT: we must have the module(s) defined for the parcelation
# step, otherwise we will get no precompiles for the Plots module
using Plots
data = SnoopCompile.read("/tmp/plots_compiles.csv")
# The Plots tests are run inside a module PlotsTest, so all
# the precompiles get credited to PlotsTest. Credit them to Plots instead.
subst = Dict("PlotsTests"=>"Plots")
# Blacklist helps fix problems:
# - MIME uses type-parameters with symbols like :image/png, which is
# not parseable
blacklist = ["MIME"]
# Use these two lines if you want to create precompile functions for
# individual packages
pc, discards = SnoopCompile.parcel(data[end:-1:1,2], subst=subst, blacklist=blacklist)
SnoopCompile.write("/tmp/precompile", pc)
pdir = joinpath(dirname(@__FILE__), "..")
run(`cp /tmp/precompile/precompile_Plots.jl $pdir/src/precompile.jl`)