Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2495d2a1e9 | |||
| 0a97b08c72 | |||
| e0a89c55ac | |||
| 75ac34b719 | |||
| 2072c54c53 | |||
| 6e1ed63005 | |||
| 17e59daeb4 | |||
| 69e83ef89f | |||
| b2707f5c8a | |||
| 3dcc7e5556 | |||
| 57ebeae1ad | |||
| 3e1258aa0c | |||
| 96e1b1d105 | |||
| fe80e486aa | |||
| ad051f21c1 | |||
| ac24a4ae19 | |||
| 7ae43ee9f6 | |||
| 0e79070ad8 | |||
| f11b976574 | |||
| 59da5d5deb | |||
| 0deb7038e6 | |||
| 2ef692c690 | |||
| 9228c0b9ec | |||
| 89d02b1d67 | |||
| a63a9248d6 | |||
| 79777adc07 | |||
| 5619feb6c0 | |||
| f15a721db6 | |||
| d6b3b68cd9 | |||
| 78785fa8fe | |||
| 74c69f575f | |||
| 12e34bb24f | |||
| 4ac5484b4f | |||
| 8306619faf | |||
| 5673477428 | |||
| 84c5b67574 | |||
| e71a15d46e | |||
| daf9556bed | |||
| b6a7c1036c | |||
| 8c2a66b00c | |||
| a1182e0ebe | |||
| 750492f9e3 | |||
| c6893f6cb5 | |||
| 068523fac1 | |||
| 6d6d4f14b5 |
@@ -11,6 +11,44 @@
|
||||
---
|
||||
## (current master)
|
||||
|
||||
## 0.26.2
|
||||
- improve empty animation build error
|
||||
- fix GR axis flip for heatmaps and images
|
||||
- fix ribbons specified as tuples
|
||||
- add Char recipe
|
||||
- fix Plotly plots with single-element series
|
||||
- rewrite PlotlyJS backend
|
||||
|
||||
## 0.26.1
|
||||
- handle `Char`s as input data
|
||||
- fix html saving for Plotly
|
||||
- expand ~ in paths on UNIX systems
|
||||
- convertToAnyVector clean-up
|
||||
- fix color_palette grouping issue
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
+2
-1
@@ -1,12 +1,13 @@
|
||||
name = "Plots"
|
||||
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
|
||||
author = ["Tom Breloff (@tbreloff)"]
|
||||
version = "0.25.3"
|
||||
version = "0.26.2"
|
||||
|
||||
[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"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#### 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:
|
||||
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
module Plots
|
||||
|
||||
_current_plots_version = v"0.25.0"
|
||||
_current_plots_version = v"0.26.1"
|
||||
|
||||
using Reexport
|
||||
|
||||
@@ -8,6 +8,8 @@ import GeometryTypes
|
||||
using Dates, Printf, Statistics, Base64, LinearAlgebra, Random
|
||||
import SparseArrays: findnz
|
||||
|
||||
using FFMPEG
|
||||
|
||||
@reexport using RecipesBase
|
||||
import RecipesBase: plot, plot!, animate
|
||||
using Base.Meta
|
||||
|
||||
+14
-9
@@ -60,31 +60,36 @@ end
|
||||
|
||||
file_extension(fn) = Base.Filesystem.splitext(fn)[2][2:end]
|
||||
|
||||
gif(anim::Animation, fn = giffn(); kw...) = buildanimation(anim.dir, fn; kw...)
|
||||
mov(anim::Animation, fn = movfn(); kw...) = buildanimation(anim.dir, fn, false; kw...)
|
||||
mp4(anim::Animation, fn = mp4fn(); kw...) = buildanimation(anim.dir, fn, false; kw...)
|
||||
gif(anim::Animation, fn = giffn(); kw...) = buildanimation(anim, fn; kw...)
|
||||
mov(anim::Animation, fn = movfn(); kw...) = buildanimation(anim, fn, false; kw...)
|
||||
mp4(anim::Animation, fn = mp4fn(); kw...) = buildanimation(anim, fn, false; kw...)
|
||||
|
||||
|
||||
function buildanimation(animdir::AbstractString, fn::AbstractString,
|
||||
function buildanimation(anim::Animation, fn::AbstractString,
|
||||
is_animated_gif::Bool=true;
|
||||
fps::Integer = 20, loop::Integer = 0,
|
||||
variable_palette::Bool=false,
|
||||
show_msg::Bool=true)
|
||||
fn = abspath(fn)
|
||||
if length(anim.frames) == 0
|
||||
throw(ArgumentError("Cannot build empty animations"))
|
||||
end
|
||||
|
||||
fn = abspath(expanduser(fn))
|
||||
animdir = anim.dir
|
||||
|
||||
if is_animated_gif
|
||||
if variable_palette
|
||||
# generate a colorpalette for each frame for highest quality, but larger filesize
|
||||
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
|
||||
# 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
|
||||
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
|
||||
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
|
||||
|
||||
show_msg && @info("Saved animation to ", fn)
|
||||
|
||||
@@ -1337,9 +1337,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
if !ispolar(sp)
|
||||
xmin, xmax, ymin, ymax = xy_lims
|
||||
m, n = length(x), length(y)
|
||||
xinds = sort(1:m, rev = xaxis[:flip])
|
||||
yinds = sort(1:n, rev = yaxis[:flip])
|
||||
z = reshape(reshape(z, m, n)[xinds, yinds], m*n)
|
||||
GR.setspace(zmin, zmax, 0, 90)
|
||||
grad = isa(series[:fillcolor], ColorGradient) ? series[:fillcolor] : cgrad()
|
||||
colors = [plot_color(grad[clamp((zi-zmin) / (zmax-zmin), 0, 1)], series[:fillalpha]) for zi=z]
|
||||
@@ -1454,9 +1451,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
elseif st == :image
|
||||
z = transpose_z(series, series[:z].surf, true)'
|
||||
w, h = size(z)
|
||||
xinds = sort(1:w, rev = xaxis[:flip])
|
||||
yinds = sort(1:h, rev = yaxis[:flip])
|
||||
z = z[xinds, yinds]
|
||||
xmin, xmax = ignorenan_extrema(series[:x]); ymin, ymax = ignorenan_extrema(series[:y])
|
||||
if eltype(z) <: Colors.AbstractGray
|
||||
grey = round.(UInt8, clamp.(float(z) * 255, 0, 255))
|
||||
|
||||
+21
-14
@@ -347,7 +347,7 @@ function plotly_layout(plt::Plot)
|
||||
end
|
||||
|
||||
function plotly_layout_json(plt::Plot)
|
||||
JSON.json(plotly_layout(plt))
|
||||
JSON.json(plotly_layout(plt), 4)
|
||||
end
|
||||
|
||||
|
||||
@@ -631,11 +631,9 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
|
||||
(isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple))
|
||||
|
||||
segments = iter_segments(series)
|
||||
plotattributes_outs = Vector{KW}(undef, (hasfillrange ? 2 : 1 ) * length(segments))
|
||||
plotattributes_outs = fill(KW(), (hasfillrange ? 2 : 1 ) * length(segments))
|
||||
|
||||
for (i,rng) in enumerate(segments)
|
||||
!isscatter && length(rng) < 2 && continue
|
||||
|
||||
plotattributes_out = deepcopy(plotattributes_base)
|
||||
plotattributes_out[:showlegend] = i==1 ? should_add_to_legend(series) : false
|
||||
plotattributes_out[:legendgroup] = series[:label]
|
||||
@@ -800,13 +798,16 @@ function plotly_series(plt::Plot)
|
||||
end
|
||||
|
||||
# get json string for a list of dictionaries, each representing the series params
|
||||
plotly_series_json(plt::Plot) = JSON.json(plotly_series(plt))
|
||||
plotly_series_json(plt::Plot) = JSON.json(plotly_series(plt), 4)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
html_head(plt::Plot{PlotlyBackend}) = plotly_html_head(plt)
|
||||
html_body(plt::Plot{PlotlyBackend}) = plotly_html_body(plt)
|
||||
|
||||
const ijulia_initialized = Ref(false)
|
||||
|
||||
function html_head(plt::Plot{PlotlyBackend})
|
||||
function plotly_html_head(plt::Plot)
|
||||
local_file = ("file://" * plotly_local_file_path)
|
||||
plotly = use_local_dependencies[] ? local_file : plotly_remote_file_path
|
||||
if isijulia() && !ijulia_initialized[]
|
||||
@@ -822,12 +823,10 @@ function html_head(plt::Plot{PlotlyBackend})
|
||||
""")
|
||||
ijulia_initialized[] = true
|
||||
end
|
||||
# IJulia just needs one initialization
|
||||
isijulia() && return ""
|
||||
return "<script src=$(repr(plotly))></script>"
|
||||
end
|
||||
|
||||
function html_body(plt::Plot{PlotlyBackend}, style = nothing)
|
||||
function plotly_html_body(plt, style = nothing)
|
||||
if style == nothing
|
||||
w, h = plt[:size]
|
||||
style = "width:$(w)px;height:$(h)px;"
|
||||
@@ -843,17 +842,14 @@ function html_body(plt::Plot{PlotlyBackend}, style = nothing)
|
||||
html
|
||||
end
|
||||
|
||||
function js_body(plt::Plot{PlotlyBackend}, uuid)
|
||||
function js_body(plt::Plot, uuid)
|
||||
js = """
|
||||
PLOT = document.getElementById('$(uuid)');
|
||||
Plotly.plot(PLOT, $(plotly_series_json(plt)), $(plotly_layout_json(plt)));
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _show(io::IO, ::MIME"application/vnd.plotly.v1+json", plot::Plot{PlotlyBackend})
|
||||
function plotly_show_js(io::IO, plot::Plot)
|
||||
data = []
|
||||
for series in plot.series_list
|
||||
append!(data, plotly_series(plot, series))
|
||||
@@ -862,6 +858,17 @@ function _show(io::IO, ::MIME"application/vnd.plotly.v1+json", plot::Plot{Plotly
|
||||
JSON.print(io, Dict(:data => data, :layout => layout))
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
function _show(io::IO, ::MIME"application/vnd.plotly.v1+json", plot::Plot{PlotlyBackend})
|
||||
plotly_show_js(io, plot)
|
||||
end
|
||||
|
||||
|
||||
function _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend})
|
||||
write(io, standalone_html(plt))
|
||||
end
|
||||
|
||||
|
||||
function _display(plt::Plot{PlotlyBackend})
|
||||
standalone_html_window(plt)
|
||||
|
||||
+29
-63
@@ -1,78 +1,44 @@
|
||||
# https://github.com/sglyon/PlotlyJS.jl
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _create_backend_figure(plt::Plot{PlotlyJSBackend})
|
||||
if !isplotnull() && plt[:overwrite_figure] && isa(current().o, PlotlyJS.SyncPlot)
|
||||
PlotlyJS.SyncPlot(PlotlyJS.Plot(), options = current().o.options)
|
||||
else
|
||||
PlotlyJS.plot()
|
||||
function plotlyjs_syncplot(plt::Plot{PlotlyJSBackend})
|
||||
plt[:overwrite_figure] && closeall()
|
||||
plt.o = PlotlyJS.plot()
|
||||
traces = PlotlyJS.GenericTrace[]
|
||||
for series_dict in plotly_series(plt)
|
||||
plotly_type = pop!(series_dict, :type)
|
||||
push!(traces, PlotlyJS.GenericTrace(plotly_type; series_dict...))
|
||||
end
|
||||
PlotlyJS.addtraces!(plt.o, traces...)
|
||||
layout = plotly_layout(plt)
|
||||
w, h = plt[:size]
|
||||
PlotlyJS.relayout!(plt.o, layout, width = w, height = h)
|
||||
return plt.o
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
function _series_added(plt::Plot{PlotlyJSBackend}, series::Series)
|
||||
syncplot = plt.o
|
||||
pdicts = plotly_series(plt, series)
|
||||
for pdict in pdicts
|
||||
typ = pop!(pdict, :type)
|
||||
gt = PlotlyJS.GenericTrace(typ; pdict...)
|
||||
PlotlyJS.addtraces!(syncplot, gt)
|
||||
end
|
||||
const _plotlyjs_mimeformats = Dict(
|
||||
"application/pdf" => "pdf",
|
||||
"image/png" => "png",
|
||||
"image/svg+xml" => "svg",
|
||||
"image/eps" => "eps",
|
||||
)
|
||||
|
||||
for (mime, fmt) in _plotlyjs_mimeformats
|
||||
@eval _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plotlyjs_syncplot(plt), format = $fmt)
|
||||
end
|
||||
|
||||
function _series_updated(plt::Plot{PlotlyJSBackend}, series::Series)
|
||||
xsym, ysym = (ispolar(series) ? (:t,:r) : (:x,:y))
|
||||
kw = KW(xsym => (series.plotattributes[:x],), ysym => (series.plotattributes[:y],))
|
||||
z = series[:z]
|
||||
if z != nothing
|
||||
kw[:z] = (isa(z,Surface) ? transpose_z(series, series[:z].surf, false) : z,)
|
||||
end
|
||||
PlotlyJS.restyle!(
|
||||
plt.o,
|
||||
findfirst(isequal(series), plt.series_list),
|
||||
kw
|
||||
)
|
||||
end
|
||||
# Use the Plotly implementation for json and html:
|
||||
_show(io::IO, mime::MIME"application/vnd.plotly.v1+json", plt::Plot{PlotlyJSBackend}) = plotly_show_js(io, plt)
|
||||
|
||||
html_head(plt::Plot{PlotlyJSBackend}) = plotly_html_head(plt)
|
||||
html_body(plt::Plot{PlotlyJSBackend}) = plotly_html_body(plt)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
_show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) = write(io, standalone_html(plt))
|
||||
|
||||
function _update_plot_object(plt::Plot{PlotlyJSBackend})
|
||||
pdict = plotly_layout(plt)
|
||||
syncplot = plt.o
|
||||
w,h = plt[:size]
|
||||
PlotlyJS.relayout!(syncplot, pdict, width = w, height = h)
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
_show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) = show(io, MIME("text/html"), plt.o)
|
||||
_show(io::IO, ::MIME"image/svg+xml", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="svg")
|
||||
_show(io::IO, ::MIME"image/png", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="png")
|
||||
_show(io::IO, ::MIME"application/pdf", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="pdf")
|
||||
_show(io::IO, ::MIME"image/eps", plt::Plot{PlotlyJSBackend}) = PlotlyJS.savefig(io, plt.o, format="eps")
|
||||
|
||||
function _show(io::IO, m::MIME"application/vnd.plotly.v1+json", plt::Plot{PlotlyJSBackend})
|
||||
show(io, m, plt.o)
|
||||
end
|
||||
|
||||
|
||||
function write_temp_html(plt::Plot{PlotlyJSBackend})
|
||||
filename = string(tempname(), ".html")
|
||||
savefig(plt, filename)
|
||||
filename
|
||||
end
|
||||
|
||||
function _display(plt::Plot{PlotlyJSBackend})
|
||||
if get(ENV, "PLOTS_USE_ATOM_PLOTPANE", true) in (true, 1, "1", "true", "yes")
|
||||
display(plt.o)
|
||||
else
|
||||
standalone_html_window(plt)
|
||||
end
|
||||
end
|
||||
_display(plt::Plot{PlotlyJSBackend}) = display(plotlyjs_syncplot(plt))
|
||||
|
||||
@require WebIO = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" begin
|
||||
function WebIO.render(plt::Plot{PlotlyJSBackend})
|
||||
|
||||
+4
-1
@@ -20,7 +20,10 @@ 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["application/vnd.plotly.v1+json"] = Dict(
|
||||
:data => plotly_series(plt),
|
||||
:layout => plotly_layout(plt)
|
||||
)
|
||||
out
|
||||
end
|
||||
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ type is inferred from the file extension. All backends support png and pdf
|
||||
file types, some also support svg, ps, eps, html and tex.
|
||||
"""
|
||||
function savefig(plt::Plot, fn::AbstractString)
|
||||
|
||||
fn = abspath(expanduser(fn))
|
||||
# get the extension
|
||||
local ext
|
||||
try
|
||||
|
||||
@@ -1099,6 +1099,12 @@ timeformatter(t) = string(Dates.Time(Dates.Nanosecond(t)))
|
||||
@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{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)))
|
||||
|
||||
# -------------------------------------------------
|
||||
# Characters
|
||||
|
||||
@recipe f(::Type{<:AbstractChar}, ::AbstractChar) = (string, string)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Complex Numbers
|
||||
|
||||
+40
-65
@@ -7,67 +7,47 @@
|
||||
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now
|
||||
|
||||
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))
|
||||
|
||||
# unknown
|
||||
convertToAnyVector(x, plotattributes::KW) = error("No user recipe defined for $(typeof(x))")
|
||||
|
||||
# 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/string vector
|
||||
convertToAnyVector(v::AVec{T}, plotattributes::KW) where {T<:Union{Number,String,Missing}} = Any[handlemissings(v)], 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
|
||||
prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
|
||||
prepareSeriesData(::Nothing) = nothing
|
||||
prepareSeriesData(s::SeriesData) = handlemissings(s)
|
||||
|
||||
handlemissings(v) = v
|
||||
handlemissings(v::AbstractArray{Union{T,Missing}}) where T <: Number = replace(v, missing => NaN)
|
||||
handlemissings(v::AbstractArray{Union{T,Missing}}) where T <: String = replace(v, missing => "")
|
||||
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)
|
||||
|
||||
# function
|
||||
convertToAnyVector(f::Function, plotattributes::KW) = Any[f], nothing
|
||||
# default: assume x represents a single series
|
||||
convertToAnyVector(x) = Any[prepareSeriesData(x)]
|
||||
|
||||
# surface
|
||||
convertToAnyVector(s::Surface, plotattributes::KW) = Any[s], nothing
|
||||
# fixed number of blank series
|
||||
convertToAnyVector(n::Integer) = Any[zeros(0) for i in 1:n]
|
||||
|
||||
# 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
|
||||
# vector of data points is a single series
|
||||
convertToAnyVector(v::AVec{<:DataPoint}) = Any[prepareSeriesData(v)]
|
||||
|
||||
# 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(v::AVec) = vcat((convertToAnyVector(vi) for vi in v)...)
|
||||
|
||||
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)]
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Fillranges & ribbons
|
||||
|
||||
|
||||
function convertToAnyVector(args...)
|
||||
error("In convertToAnyVector, could not handle the argument types: $(map(typeof, args[1:end-1]))")
|
||||
end
|
||||
process_fillrange(range::Number) = [range]
|
||||
process_fillrange(range) = convertToAnyVector(range)
|
||||
|
||||
process_ribbon(ribbon::Number) = [ribbon]
|
||||
process_ribbon(ribbon) = convertToAnyVector(ribbon)
|
||||
# ribbon as a tuple: (lower_ribbons, upper_ribbons)
|
||||
process_ribbon(ribbon::Tuple{Any,Any}) = collect(zip(convertToAnyVector(ribbon[1]),
|
||||
convertToAnyVector(ribbon[2])))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@@ -130,24 +110,17 @@ struct SliceIt end
|
||||
z = z.data
|
||||
end
|
||||
|
||||
xs, _ = convertToAnyVector(x, plotattributes)
|
||||
ys, _ = convertToAnyVector(y, plotattributes)
|
||||
zs, _ = convertToAnyVector(z, plotattributes)
|
||||
xs = convertToAnyVector(x)
|
||||
ys = convertToAnyVector(y)
|
||||
zs = convertToAnyVector(z)
|
||||
|
||||
|
||||
fr = pop!(plotattributes, :fillrange, nothing)
|
||||
fillranges, _ = if typeof(fr) <: Number
|
||||
([fr],nothing)
|
||||
else
|
||||
convertToAnyVector(fr, plotattributes)
|
||||
end
|
||||
fillranges = process_fillrange(fr)
|
||||
mf = length(fillranges)
|
||||
|
||||
rib = pop!(plotattributes, :ribbon, nothing)
|
||||
ribbons, _ = if typeof(rib) <: Number
|
||||
([fr],nothing)
|
||||
else
|
||||
convertToAnyVector(rib, plotattributes)
|
||||
end
|
||||
ribbons = process_ribbon(rib)
|
||||
mr = length(ribbons)
|
||||
|
||||
# @show zs
|
||||
@@ -288,8 +261,10 @@ end
|
||||
|
||||
@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
|
||||
@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)
|
||||
n,m = size(mat)
|
||||
wrap_surfaces(plotattributes)
|
||||
@@ -312,7 +287,7 @@ end
|
||||
end
|
||||
|
||||
# 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
|
||||
SliceIt, nothing, Volume(vol, args...), nothing
|
||||
end
|
||||
@@ -581,7 +556,7 @@ end
|
||||
# end
|
||||
|
||||
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::SeriesAnnotations, lengthGroup) = splittable_kw(key, val.strs, lengthGroup)
|
||||
|
||||
|
||||
@@ -63,6 +63,13 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
@testset "EmptyAnim" begin
|
||||
anim = @animate for i in []
|
||||
end
|
||||
|
||||
@test_throws ArgumentError gif(anim)
|
||||
end
|
||||
|
||||
@testset "Segments" begin
|
||||
function segments(args...)
|
||||
segs = UnitRange{Int}[]
|
||||
|
||||
Reference in New Issue
Block a user