Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 703bdf6cfe | |||
| e7f79e9144 | |||
| 79fd7f282d | |||
| 3078a44fce | |||
| ba4f2665e0 | |||
| 5504f59098 | |||
| 265439fb2b | |||
| 450b36bdde | |||
| f1e7e3a4de | |||
| becfa33ee8 | |||
| ec5fa44f2a | |||
| 3152d4b4fe | |||
| 66d2e4746a | |||
| 39b10f05b9 | |||
| 5739acd044 | |||
| d1fb11ce97 | |||
| aa46f584b6 | |||
| 7c87cbb425 | |||
| 09de2ebc0e | |||
| 64f7dc1aed | |||
| 122b4d91a8 | |||
| d9efbbcf5b | |||
| d3629f3cfe | |||
| a9c659aa21 | |||
| 5d37264487 | |||
| 2fc6f9220d | |||
| c976a6de83 | |||
| 08c0f6d9a3 | |||
| fafb0d9510 | |||
| df6fa6fd68 | |||
| 8b5fc95443 | |||
| 4e877a206e | |||
| c428dcdb3e | |||
| 77b5951037 | |||
| a1c59b7904 | |||
| 8ae8d74c42 | |||
| eba4d2442c | |||
| d1938c5e94 | |||
| 3dbe5cc3a2 | |||
| 7a6617efe3 | |||
| 123eb15434 | |||
| df13011bd3 | |||
| 3368fd2c98 | |||
| 5a78c492bc | |||
| 8e98d1dfde | |||
| 2373dda3c4 | |||
| 0456ddfe3a | |||
| fe0858ebcf | |||
| 6bc38eaea3 | |||
| 43456b09f6 | |||
| fae1cb4884 | |||
| 9b4c66be9a | |||
| 2d29ab7017 | |||
| 3b3a09302f | |||
| fe92a384b7 | |||
| befe20700a | |||
| 41b34e59c7 | |||
| c01ee17d88 | |||
| 8c9422f470 | |||
| 7c88256ca4 | |||
| f5fe2692e2 | |||
| d00920ca0e | |||
| ef36c6bd0a | |||
| a64d84ae70 | |||
| 9c1b30f774 | |||
| ebfe97a692 | |||
| d8c4210e8f | |||
| a9c0073aff | |||
| d04bb42f7b | |||
| bf849d90a7 | |||
| 95157004b6 | |||
| c861664aea | |||
| 72983102e3 | |||
| 693aaa51d7 | |||
| 41a32f7782 | |||
| 5d99c19de1 | |||
| d942660f91 | |||
| c238d42e83 | |||
| 03a27994d5 | |||
| 1f7773ae4c | |||
| 4a5c82ec7f | |||
| 70f207f5cc | |||
| a1cd3d8f95 | |||
| 848b53e7d7 | |||
| 987456760c | |||
| 4c43f0ea2f | |||
| 7d69795364 | |||
| 059985c91c | |||
| 9fe3428a55 | |||
| 92d8bd7836 | |||
| 2a88ab7726 | |||
| d8f516a456 | |||
| 28ce7d7995 | |||
| b6139e2403 | |||
| 80ec6f03b4 | |||
| 330739298c | |||
| bd9cd64e7a | |||
| f73b281996 | |||
| f180c666d2 | |||
| 908779f0c6 | |||
| 345c3bc928 | |||
| bbdf1dcc0e | |||
| 5da2d85420 | |||
| 5f524d039f | |||
| 824d502c37 | |||
| 36ff017c4f | |||
| 3ef9fe142c | |||
| 4bf0c496d8 | |||
| 9bf4bda852 | |||
| fa7f10a6e9 | |||
| 483e45db8a | |||
| e6097f4534 | |||
| 749f56084a | |||
| 2fa4a63f02 | |||
| 9b1249abce | |||
| a05d31a472 | |||
| 9f7474476a | |||
| 5f744dac68 | |||
| c3a9eb8866 | |||
| a6aa39e3cc | |||
| 1c66f4bf90 | |||
| dc5ae4ef96 | |||
| d9dba5091b | |||
| c94746f3f2 | |||
| b6dddcb15b | |||
| 2849082f1f | |||
| 038cf2b133 | |||
| a9e2a5f639 | |||
| c9421be214 | |||
| 9fc707bd94 | |||
| 236cc4671e | |||
| a301366ef6 | |||
| c1959cb993 | |||
| ecf71a43d3 | |||
| b65dfc6767 |
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please search existing issues to avoid duplicates.
|
||||
|
||||
## Details
|
||||
|
||||
|
||||
### Backends
|
||||
|
||||
This bug occurs on ( insert `x` below )
|
||||
|
||||
Backend | yes | no | untested
|
||||
-------------|-----|-----|---------
|
||||
gr (default) | | |
|
||||
pyplot | | |
|
||||
plotly | | |
|
||||
plotlyjs | | |
|
||||
pgfplotsx | | |
|
||||
inspectdr | | |
|
||||
|
||||
### Versions
|
||||
|
||||
Plots.jl version:
|
||||
Backend version:
|
||||
Output of `versioninfo()`:
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature or enhancement
|
||||
title: "[FR]"
|
||||
labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please search existing issues to avoid duplicates.
|
||||
+7
-5
@@ -1,7 +1,7 @@
|
||||
name = "Plots"
|
||||
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
|
||||
author = ["Tom Breloff (@tbreloff)"]
|
||||
version = "1.0.5"
|
||||
version = "1.1.0"
|
||||
|
||||
[deps]
|
||||
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||
@@ -22,6 +22,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
|
||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
|
||||
RecipesPipeline = "01d81517-befc-4cb6-b9ec-a95719d0359c"
|
||||
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
|
||||
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
|
||||
Showoff = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
|
||||
@@ -40,13 +41,14 @@ JSON = "0.21"
|
||||
Measures = "0.3"
|
||||
NaNMath = "0.3"
|
||||
PGFPlotsX = "1.2.0"
|
||||
PlotThemes = "1"
|
||||
PlotUtils = "0.6.5"
|
||||
PlotThemes = "2"
|
||||
PlotUtils = "1"
|
||||
RecipesBase = "1"
|
||||
RecipesPipeline = "0.1.3"
|
||||
Reexport = "0.2"
|
||||
Requires = "0.5, 1.0"
|
||||
Requires = "0.5, 1"
|
||||
Showoff = "0.3.1"
|
||||
StatsBase = "0.32"
|
||||
StatsBase = "0.32, 0.33"
|
||||
julia = "1"
|
||||
|
||||
[extras]
|
||||
|
||||
Vendored
+2
@@ -44,7 +44,9 @@ blacklist = [
|
||||
# the following are not visible to Plots, only its dependencies
|
||||
"CategoricalArrays",
|
||||
"FixedPointNumbers",
|
||||
"OffsetArrays",
|
||||
"SparseArrays",
|
||||
"StaticArrays",
|
||||
r"#{1,2}[^\"#]+#{1,2}\d+",
|
||||
]
|
||||
|
||||
|
||||
+20
-1
@@ -167,6 +167,26 @@ using .PlotMeasures
|
||||
import .PlotMeasures: Length, AbsoluteLength, Measure, width, height
|
||||
# ---------------------------------------------------------
|
||||
|
||||
import RecipesPipeline
|
||||
import RecipesPipeline: SliceIt,
|
||||
DefaultsDict,
|
||||
Formatted,
|
||||
AbstractSurface,
|
||||
Surface,
|
||||
Volume,
|
||||
is3d,
|
||||
is_surface,
|
||||
needs_3d_axes,
|
||||
group_as_matrix, # for StatsPlots
|
||||
reset_kw!,
|
||||
pop_kw!,
|
||||
scale_func,
|
||||
inverse_scale_func,
|
||||
unzip,
|
||||
dateformatter,
|
||||
datetimeformatter,
|
||||
timeformatter
|
||||
|
||||
include("types.jl")
|
||||
include("utils.jl")
|
||||
include("components.jl")
|
||||
@@ -175,7 +195,6 @@ include("args.jl")
|
||||
include("themes.jl")
|
||||
include("plot.jl")
|
||||
include("pipeline.jl")
|
||||
include("series.jl")
|
||||
include("layouts.jl")
|
||||
include("subplots.jl")
|
||||
include("recipes.jl")
|
||||
|
||||
+3
-2
@@ -72,10 +72,11 @@ const _arg_desc = KW(
|
||||
:display_type => "Symbol (`:auto`, `:gui`, or `:inline`). When supported, `display` will either open a GUI window or plot inline.",
|
||||
:extra_kwargs => "KW (Dict{Symbol,Any}). Pass a map of extra keyword args which may be specific to a backend.",
|
||||
:fontfamily => "String or Symbol. Default font family for title, legend entries, tick labels and guides",
|
||||
:warn_on_unsupported => "Bool. Warn on unsupported attributes, series types and marker shapes",
|
||||
|
||||
# subplot args
|
||||
:title => "String. Subplot title.",
|
||||
:title_location => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`",
|
||||
:titlelocation => "Symbol. Position of subplot title. Values: `:left`, `:center`, `:right`",
|
||||
:titlefontfamily => "String or Symbol. Font family of subplot title.",
|
||||
:titlefontsize => "Integer. Font pointsize of subplot title.",
|
||||
:titlefonthalign => "Symbol. Font horizontal alignment of subplot title: :hcenter, :left, :right or :center",
|
||||
@@ -89,7 +90,7 @@ const _arg_desc = KW(
|
||||
:foreground_color_legend => "Color Type or `:match` (matches `:foreground_color_subplot`). Foreground color of the legend.",
|
||||
:foreground_color_title => "Color Type or `:match` (matches `:foreground_color_subplot`). Color of subplot title.",
|
||||
:color_palette => "Vector of colors (cycle through) or color gradient (generate list from gradient) or `:auto` (generate a color list using `Colors.distiguishable_colors` and custom seed colors chosen to contrast with the background). The color palette is a color list from which series colors are automatically chosen.",
|
||||
:legend => "Bool (show the legend?) or Symbol (legend position). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` , `:inline` (note: only some may be supported in each backend)",
|
||||
:legend => "Bool (show the legend?) or (x,y) tuple or Symbol (legend position). Bottom left corner of legend is placed at (x,y). Symbol values: `:none`, `:best`, `:right`, `:left`, `:top`, `:bottom`, `:inside`, `:legend`, `:topright`, `:topleft`, `:bottomleft`, `:bottomright` , `:inline` (note: only some may be supported in each backend)",
|
||||
:legendfontfamily => "String or Symbol. Font family of legend entries.",
|
||||
:legendfontsize => "Integer. Font pointsize of legend entries.",
|
||||
:legendfonthalign => "Symbol. Font horizontal alignment of legend entries: :hcenter, :left, :right or :center",
|
||||
|
||||
+50
-99
@@ -86,13 +86,10 @@ const _surface_like = [:contour, :contourf, :contour3d, :heatmap, :surface, :wir
|
||||
|
||||
like_histogram(seriestype::Symbol) = seriestype in _histogram_like
|
||||
like_line(seriestype::Symbol) = seriestype in _line_like
|
||||
like_surface(seriestype::Symbol) = seriestype in _surface_like
|
||||
like_surface(seriestype::Symbol) = RecipesPipeline.is_surface(seriestype)
|
||||
|
||||
is3d(seriestype::Symbol) = seriestype in _3dTypes
|
||||
is3d(series::Series) = is3d(series.plotattributes)
|
||||
is3d(plotattributes::AKW) = trueOrAllTrue(is3d, Symbol(plotattributes[:seriestype]))
|
||||
|
||||
is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
|
||||
RecipesPipeline.is3d(series::Series) = RecipesPipeline.is3d(series.plotattributes)
|
||||
RecipesPipeline.is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d"
|
||||
ispolar(sp::Subplot) = string(sp.attr[:projection]) == "polar"
|
||||
ispolar(series::Series) = ispolar(series.plotattributes[:subplot])
|
||||
|
||||
@@ -308,12 +305,13 @@ const _plot_defaults = KW(
|
||||
:thickness_scaling => 1,
|
||||
:display_type => :auto,
|
||||
:extra_kwargs => KW(),
|
||||
:warn_on_unsupported => true,
|
||||
)
|
||||
|
||||
|
||||
const _subplot_defaults = KW(
|
||||
:title => "",
|
||||
:title_location => :center, # also :left or :right
|
||||
:titlelocation => :center, # also :left or :right
|
||||
:fontfamily_subplot => :match,
|
||||
:titlefontfamily => :match,
|
||||
:titlefontsize => 14,
|
||||
@@ -483,10 +481,7 @@ is_default_attribute(k) = k in _internal_args || k in _all_args || is_axis_attr_
|
||||
|
||||
makeplural(s::Symbol) = Symbol(string(s,"s"))
|
||||
|
||||
autopick(arr::AVec, idx::Integer) = arr[mod1(idx,length(arr))]
|
||||
autopick(notarr, idx::Integer) = notarr
|
||||
|
||||
autopick_ignore_none_auto(arr::AVec, idx::Integer) = autopick(setdiff(arr, [:none, :auto]), idx)
|
||||
autopick_ignore_none_auto(arr::AVec, idx::Integer) = _cycle(setdiff(arr, [:none, :auto]), idx)
|
||||
autopick_ignore_none_auto(notarr, idx::Integer) = notarr
|
||||
|
||||
function aliasesAndAutopick(plotattributes::AKW, sym::Symbol, aliases::Dict{Symbol,Symbol}, options::AVec, plotIndex::Int)
|
||||
@@ -603,7 +598,7 @@ add_aliases(:aspect_ratio, :aspectratio, :axis_ratio, :axisratio, :ratio)
|
||||
add_aliases(:match_dimensions, :transpose, :transpose_z)
|
||||
add_aliases(:subplot, :sp, :subplt, :splt)
|
||||
add_aliases(:projection, :proj)
|
||||
add_aliases(:title_location, :title_loc, :titleloc, :title_position, :title_pos, :titlepos, :titleposition, :title_align, :title_alignment)
|
||||
add_aliases(:titlelocation, :title_location, :title_loc, :titleloc, :title_position, :title_pos, :titlepos, :titleposition, :title_align, :title_alignment)
|
||||
add_aliases(:series_annotations, :series_ann, :seriesann, :series_anns, :seriesanns, :series_annotation, :text, :txt, :texts, :txts)
|
||||
add_aliases(:html_output_format, :format, :fmt, :html_format)
|
||||
add_aliases(:orientation, :direction, :dir)
|
||||
@@ -617,6 +612,7 @@ add_aliases(:framestyle, :frame_style, :frame, :axesstyle, :axes_style, :boxstyl
|
||||
add_aliases(:tick_direction, :tickdirection, :tick_dir, :tickdir, :tick_orientation, :tickorientation, :tick_or, :tickor)
|
||||
add_aliases(:camera, :cam, :viewangle, :view_angle)
|
||||
add_aliases(:contour_labels, :contourlabels, :clabels, :clabs)
|
||||
add_aliases(:warn_on_unsupported, :warn)
|
||||
|
||||
# add all pluralized forms to the _keyAliases dict
|
||||
for arg in keys(_series_defaults)
|
||||
@@ -683,9 +679,12 @@ function default(k::Symbol, v)
|
||||
k in _suppress_warnings || error("Unknown key: ", k)
|
||||
end
|
||||
|
||||
function default(; kw...)
|
||||
function default(; reset = true, kw...)
|
||||
if reset && isempty(kw)
|
||||
reset_defaults()
|
||||
end
|
||||
kw = KW(kw)
|
||||
preprocessArgs!(kw)
|
||||
RecipesPipeline.preprocess_attributes!(kw)
|
||||
for (k,v) in kw
|
||||
default(k, v)
|
||||
end
|
||||
@@ -938,11 +937,11 @@ function _add_markershape(plotattributes::AKW)
|
||||
end
|
||||
|
||||
"Handle all preprocessing of args... break out colors/sizes/etc and replace aliases."
|
||||
function preprocessArgs!(plotattributes::AKW)
|
||||
function RecipesPipeline.preprocess_attributes!(plotattributes::AKW)
|
||||
replaceAliases!(plotattributes, _keyAliases)
|
||||
|
||||
# handle axis args common to all axis
|
||||
args = pop_kw!(plotattributes, :axis, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, :axis, ())
|
||||
for arg in wraptuple(args)
|
||||
for letter in (:x, :y, :z)
|
||||
process_axis_arg!(plotattributes, arg, letter)
|
||||
@@ -951,7 +950,7 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
# handle axis args
|
||||
for letter in (:x, :y, :z)
|
||||
asym = Symbol(letter, :axis)
|
||||
args = pop_kw!(plotattributes, asym, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, asym, ())
|
||||
if !(typeof(args) <: Axis)
|
||||
for arg in wraptuple(args)
|
||||
process_axis_arg!(plotattributes, arg, letter)
|
||||
@@ -969,7 +968,7 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
end
|
||||
|
||||
# handle grid args common to all axes
|
||||
args = pop_kw!(plotattributes, :grid, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, :grid, ())
|
||||
for arg in wraptuple(args)
|
||||
for letter in (:x, :y, :z)
|
||||
processGridArg!(plotattributes, arg, letter)
|
||||
@@ -978,13 +977,13 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
# handle individual axes grid args
|
||||
for letter in (:x, :y, :z)
|
||||
gridsym = Symbol(letter, :grid)
|
||||
args = pop_kw!(plotattributes, gridsym, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, gridsym, ())
|
||||
for arg in wraptuple(args)
|
||||
processGridArg!(plotattributes, arg, letter)
|
||||
end
|
||||
end
|
||||
# handle minor grid args common to all axes
|
||||
args = pop_kw!(plotattributes, :minorgrid, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, :minorgrid, ())
|
||||
for arg in wraptuple(args)
|
||||
for letter in (:x, :y, :z)
|
||||
processMinorGridArg!(plotattributes, arg, letter)
|
||||
@@ -993,14 +992,14 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
# handle individual axes grid args
|
||||
for letter in (:x, :y, :z)
|
||||
gridsym = Symbol(letter, :minorgrid)
|
||||
args = pop_kw!(plotattributes, gridsym, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, gridsym, ())
|
||||
for arg in wraptuple(args)
|
||||
processMinorGridArg!(plotattributes, arg, letter)
|
||||
end
|
||||
end
|
||||
# handle font args common to all axes
|
||||
for fontname in (:tickfont, :guidefont)
|
||||
args = pop_kw!(plotattributes, fontname, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, fontname, ())
|
||||
for arg in wraptuple(args)
|
||||
for letter in (:x, :y, :z)
|
||||
processFontArg!(plotattributes, Symbol(letter, fontname), arg)
|
||||
@@ -1010,7 +1009,7 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
# handle individual axes font args
|
||||
for letter in (:x, :y, :z)
|
||||
for fontname in (:tickfont, :guidefont)
|
||||
args = pop_kw!(plotattributes, Symbol(letter, fontname), ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, Symbol(letter, fontname), ())
|
||||
for arg in wraptuple(args)
|
||||
processFontArg!(plotattributes, Symbol(letter, fontname), arg)
|
||||
end
|
||||
@@ -1031,14 +1030,14 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
|
||||
# fonts
|
||||
for fontname in (:titlefont, :legendfont, :legendtitlefont)
|
||||
args = pop_kw!(plotattributes, fontname, ())
|
||||
args = RecipesPipeline.pop_kw!(plotattributes, fontname, ())
|
||||
for arg in wraptuple(args)
|
||||
processFontArg!(plotattributes, fontname, arg)
|
||||
end
|
||||
end
|
||||
|
||||
# handle line args
|
||||
for arg in wraptuple(pop_kw!(plotattributes, :line, ()))
|
||||
for arg in wraptuple(RecipesPipeline.pop_kw!(plotattributes, :line, ()))
|
||||
processLineArg(plotattributes, arg)
|
||||
end
|
||||
|
||||
@@ -1052,7 +1051,7 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
processMarkerArg(plotattributes, arg)
|
||||
anymarker = true
|
||||
end
|
||||
reset_kw!(plotattributes, :marker)
|
||||
RecipesPipeline.reset_kw!(plotattributes, :marker)
|
||||
if haskey(plotattributes, :markershape)
|
||||
plotattributes[:markershape] = _replace_markershape(plotattributes[:markershape])
|
||||
if plotattributes[:markershape] == :none && plotattributes[:seriestype] in (:scatter, :scatterbins, :scatterhist, :scatter3d) #the default should be :auto, not :none, so that :none can be set explicitly and would be respected
|
||||
@@ -1066,7 +1065,7 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
for arg in wraptuple(get(plotattributes, :fill, ()))
|
||||
processFillArg(plotattributes, arg)
|
||||
end
|
||||
reset_kw!(plotattributes, :fill)
|
||||
RecipesPipeline.reset_kw!(plotattributes, :fill)
|
||||
|
||||
# handle series annotations
|
||||
if haskey(plotattributes, :series_annotations)
|
||||
@@ -1115,75 +1114,16 @@ function preprocessArgs!(plotattributes::AKW)
|
||||
return
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
"A special type that will break up incoming data into groups, and allow for easier creation of grouped plots"
|
||||
mutable struct GroupBy
|
||||
groupLabels::Vector # length == numGroups
|
||||
groupIds::Vector{Vector{Int}} # list of indices for each group
|
||||
end
|
||||
|
||||
|
||||
# this is when given a vector-type of values to group by
|
||||
function extractGroupArgs(v::AVec, args...; legendEntry = string)
|
||||
groupLabels = sort(collect(unique(v)))
|
||||
n = length(groupLabels)
|
||||
if n > 100
|
||||
@warn("You created n=$n groups... Is that intended?")
|
||||
end
|
||||
groupIds = Vector{Int}[filter(i -> v[i] == glab, eachindex(v)) for glab in groupLabels]
|
||||
GroupBy(map(legendEntry, groupLabels), groupIds)
|
||||
end
|
||||
|
||||
legendEntryFromTuple(ns::Tuple) = join(ns, ' ')
|
||||
|
||||
# this is when given a tuple of vectors of values to group by
|
||||
function extractGroupArgs(vs::Tuple, args...)
|
||||
isempty(vs) && return GroupBy([""], [axes(args[1],1)])
|
||||
v = map(tuple, vs...)
|
||||
extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple)
|
||||
end
|
||||
|
||||
# allow passing NamedTuples for a named legend entry
|
||||
legendEntryFromTuple(ns::NamedTuple) =
|
||||
join(["$k = $v" for (k, v) in pairs(ns)], ", ")
|
||||
|
||||
function extractGroupArgs(vs::NamedTuple, args...)
|
||||
isempty(vs) && return GroupBy([""], [axes(args[1],1)])
|
||||
v = map(NamedTuple{keys(vs)}∘tuple, values(vs)...)
|
||||
extractGroupArgs(v, args...; legendEntry = legendEntryFromTuple)
|
||||
end
|
||||
|
||||
# expecting a mapping of "group label" to "group indices"
|
||||
function extractGroupArgs(idxmap::Dict{T,V}, args...) where {T, V<:AVec{Int}}
|
||||
groupLabels = sortedkeys(idxmap)
|
||||
groupIds = Vector{Int}[collect(idxmap[k]) for k in groupLabels]
|
||||
GroupBy(groupLabels, groupIds)
|
||||
end
|
||||
|
||||
filter_data(v::AVec, idxfilter::AVec{Int}) = v[idxfilter]
|
||||
filter_data(v, idxfilter) = v
|
||||
|
||||
function filter_data!(plotattributes::AKW, idxfilter)
|
||||
for s in (:x, :y, :z)
|
||||
plotattributes[s] = filter_data(get(plotattributes, s, nothing), idxfilter)
|
||||
end
|
||||
end
|
||||
|
||||
function _filter_input_data!(plotattributes::AKW)
|
||||
idxfilter = pop!(plotattributes, :idxfilter, nothing)
|
||||
if idxfilter !== nothing
|
||||
filter_data!(plotattributes, idxfilter)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
const _already_warned = Dict{Symbol,Set{Symbol}}()
|
||||
const _to_warn = Set{Symbol}()
|
||||
|
||||
function warnOnUnsupported_args(pkg::AbstractBackend, plotattributes)
|
||||
function warn_on_unsupported_args(pkg::AbstractBackend, plotattributes)
|
||||
if !get(plotattributes, :warn_on_unsupported, _plot_defaults[:warn_on_unsupported])
|
||||
return
|
||||
end
|
||||
empty!(_to_warn)
|
||||
bend = backend_name(pkg)
|
||||
already_warned = get!(_already_warned, bend, Set{Symbol}())
|
||||
@@ -1207,7 +1147,10 @@ end
|
||||
# _markershape_supported(pkg::AbstractBackend, shape::Shape) = Shape in supported_markers(pkg)
|
||||
# _markershape_supported(pkg::AbstractBackend, shapes::AVec) = all([_markershape_supported(pkg, shape) for shape in shapes])
|
||||
|
||||
function warnOnUnsupported(pkg::AbstractBackend, plotattributes)
|
||||
function warn_on_unsupported(pkg::AbstractBackend, plotattributes)
|
||||
if !get(plotattributes, :warn_on_unsupported, _plot_defaults[:warn_on_unsupported])
|
||||
return
|
||||
end
|
||||
if !is_seriestype_supported(pkg, plotattributes[:seriestype])
|
||||
@warn("seriestype $(plotattributes[:seriestype]) is unsupported with $pkg. Choose from: $(supported_seriestypes(pkg))")
|
||||
end
|
||||
@@ -1219,7 +1162,10 @@ function warnOnUnsupported(pkg::AbstractBackend, plotattributes)
|
||||
end
|
||||
end
|
||||
|
||||
function warnOnUnsupported_scales(pkg::AbstractBackend, plotattributes::AKW)
|
||||
function warn_on_unsupported_scales(pkg::AbstractBackend, plotattributes::AKW)
|
||||
if !get(plotattributes, :warn_on_unsupported, _plot_defaults[:warn_on_unsupported])
|
||||
return
|
||||
end
|
||||
for k in (:xscale, :yscale, :zscale, :scale)
|
||||
if haskey(plotattributes, k)
|
||||
v = plotattributes[k]
|
||||
@@ -1277,7 +1223,7 @@ function slice_arg!(plotattributes_in, plotattributes_out,
|
||||
v
|
||||
end
|
||||
if remove_pair
|
||||
reset_kw!(plotattributes_in, k)
|
||||
RecipesPipeline.reset_kw!(plotattributes_in, k)
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -1461,8 +1407,7 @@ end
|
||||
function _update_subplot_colors(sp::Subplot)
|
||||
# background colors
|
||||
color_or_nothing!(sp.attr, :background_color_subplot)
|
||||
bg = plot_color(sp[:background_color_subplot])
|
||||
sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], bg, 30)
|
||||
sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], 30)
|
||||
color_or_nothing!(sp.attr, :background_color_legend)
|
||||
color_or_nothing!(sp.attr, :background_color_inside)
|
||||
|
||||
@@ -1538,7 +1483,7 @@ end
|
||||
|
||||
# update a subplots args and axes
|
||||
function _update_subplot_args(plt::Plot, sp::Subplot, plotattributes_in, subplot_index::Int, remove_pair::Bool)
|
||||
anns = pop_kw!(sp.attr, :annotations)
|
||||
anns = RecipesPipeline.pop_kw!(sp.attr, :annotations)
|
||||
|
||||
# # grab those args which apply to this subplot
|
||||
for k in keys(_subplot_defaults)
|
||||
@@ -1565,9 +1510,9 @@ end
|
||||
# and assigns a color automatically
|
||||
function get_series_color(c, sp::Subplot, n::Int, seriestype)
|
||||
if c == :auto
|
||||
c = like_surface(seriestype) ? cgrad() : autopick(sp[:color_palette], n)
|
||||
c = like_surface(seriestype) ? cgrad() : _cycle(sp[:color_palette], n)
|
||||
elseif isa(c, Int)
|
||||
c = autopick(sp[:color_palette], c)
|
||||
c = _cycle(sp[:color_palette], c)
|
||||
end
|
||||
plot_color(c)
|
||||
end
|
||||
@@ -1577,7 +1522,13 @@ function get_series_color(c::AbstractArray, sp::Subplot, n::Int, seriestype)
|
||||
end
|
||||
|
||||
function ensure_gradient!(plotattributes::AKW, csym::Symbol, asym::Symbol)
|
||||
if !isa(plotattributes[csym], ColorGradient)
|
||||
if plotattributes[csym] isa ColorPalette
|
||||
α = nothing
|
||||
if !(plotattributes[asym] isa AbstractVector)
|
||||
α = plotattributes[asym]
|
||||
end
|
||||
plotattributes[csym] = cgrad(plotattributes[csym], categorical = true, alpha = α)
|
||||
elseif !(plotattributes[csym] isa ColorGradient)
|
||||
plotattributes[csym] = typeof(plotattributes[asym]) <: AbstractVector ? cgrad() : cgrad(alpha = plotattributes[asym])
|
||||
end
|
||||
end
|
||||
|
||||
+26
-47
@@ -17,7 +17,7 @@ function Axis(sp::Subplot, letter::Symbol, args...; kw...)
|
||||
:show => true, # show or hide the axis? (useful for linked subplots)
|
||||
)
|
||||
|
||||
attr = Attr(explicit, _axis_defaults_byletter[letter])
|
||||
attr = DefaultsDict(explicit, _axis_defaults_byletter[letter])
|
||||
|
||||
# update the defaults
|
||||
attr!(Axis([sp], attr), args...; kw...)
|
||||
@@ -85,7 +85,7 @@ function attr!(axis::Axis, args...; kw...)
|
||||
end
|
||||
|
||||
# then preprocess keyword arguments
|
||||
preprocessArgs!(KW(kw))
|
||||
RecipesPipeline.preprocess_attributes!(KW(kw))
|
||||
|
||||
# then override for any keywords... only those keywords that already exists in plotattributes
|
||||
for (k,v) in kw
|
||||
@@ -117,33 +117,11 @@ Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.plotattributes, v,
|
||||
Base.haskey(axis::Axis, k::Symbol) = haskey(axis.plotattributes, k)
|
||||
ignorenan_extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
|
||||
|
||||
|
||||
const _scale_funcs = Dict{Symbol,Function}(
|
||||
:log10 => log10,
|
||||
:log2 => log2,
|
||||
:ln => log,
|
||||
)
|
||||
const _inv_scale_funcs = Dict{Symbol,Function}(
|
||||
:log10 => exp10,
|
||||
:log2 => exp2,
|
||||
:ln => exp,
|
||||
)
|
||||
|
||||
# const _label_func = Dict{Symbol,Function}(
|
||||
# :log10 => x -> "10^$x",
|
||||
# :log2 => x -> "2^$x",
|
||||
# :ln => x -> "e^$x",
|
||||
# )
|
||||
|
||||
const _label_func = Dict{Symbol,Function}(
|
||||
:log10 => x -> "10^$x",
|
||||
:log2 => x -> "2^$x",
|
||||
:ln => x -> "e^$x",
|
||||
)
|
||||
|
||||
|
||||
scalefunc(scale::Symbol) = x -> get(_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)
|
||||
|
||||
function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
|
||||
@@ -151,7 +129,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
|
||||
|
||||
# scale the limits
|
||||
scale = axis[:scale]
|
||||
sf = scalefunc(scale)
|
||||
sf = RecipesPipeline.scale_func(scale)
|
||||
|
||||
# If the axis input was a Date or DateTime use a special logic to find
|
||||
# "round" Date(Time)s as ticks
|
||||
@@ -161,7 +139,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
|
||||
# rather than on the input format
|
||||
# TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime
|
||||
if ticks === nothing && scale == :identity
|
||||
if axis[:formatter] == dateformatter
|
||||
if axis[:formatter] == RecipesPipeline.dateformatter
|
||||
# optimize_datetime_ticks returns ticks and labels(!) based on
|
||||
# integers/floats corresponding to the DateTime type. Thus, the axes
|
||||
# limits, which resulted from converting the Date type to integers,
|
||||
@@ -172,7 +150,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
|
||||
k_min = 2, k_max = 4)
|
||||
# Now the ticks are converted back to floats corresponding to Dates.
|
||||
return ticks / 864e5, labels
|
||||
elseif axis[:formatter] == datetimeformatter
|
||||
elseif axis[:formatter] == RecipesPipeline.datetimeformatter
|
||||
return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4)
|
||||
end
|
||||
end
|
||||
@@ -196,11 +174,11 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
|
||||
# chosen ticks is not too much bigger than amin - amax:
|
||||
strict_span = false,
|
||||
)
|
||||
axis[:lims] = map(invscalefunc(scale), (viewmin, viewmax))
|
||||
axis[:lims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax))
|
||||
else
|
||||
scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks)))
|
||||
end
|
||||
unscaled_ticks = map(invscalefunc(scale), scaled_ticks)
|
||||
unscaled_ticks = map(RecipesPipeline.inverse_scale_func(scale), scaled_ticks)
|
||||
|
||||
labels = if any(isfinite, unscaled_ticks)
|
||||
formatter = axis[:formatter]
|
||||
@@ -305,7 +283,7 @@ function get_minor_ticks(sp, axis, ticks)
|
||||
minorticks = typeof(ticks[1])[]
|
||||
for (i,hi) in enumerate(ticks[2:end])
|
||||
lo = ticks[i]
|
||||
if isfinite(lo) && hi > lo
|
||||
if isfinite(lo) && isfinite(hi) && hi > lo
|
||||
append!(minorticks,collect(lo + (hi-lo)/n :(hi-lo)/n: hi - (hi-lo)/2n))
|
||||
end
|
||||
end
|
||||
@@ -400,7 +378,7 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
|
||||
if fr === nothing && plotattributes[:seriestype] == :bar
|
||||
fr = 0.0
|
||||
end
|
||||
if fr !== nothing && !all3D(plotattributes)
|
||||
if fr !== nothing && !RecipesPipeline.is3d(plotattributes)
|
||||
axis = sp.attr[vert ? :yaxis : :xaxis]
|
||||
if typeof(fr) <: Tuple
|
||||
for fri in fr
|
||||
@@ -445,7 +423,7 @@ end
|
||||
|
||||
# push the limits out slightly
|
||||
function widen(lmin, lmax, scale = :identity)
|
||||
f, invf = scalefunc(scale), invscalefunc(scale)
|
||||
f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale)
|
||||
span = f(lmax) - f(lmin)
|
||||
# eps = NaNMath.max(1e-16, min(1e-2span, 1e-10))
|
||||
eps = NaNMath.max(1e-16, 0.03span)
|
||||
@@ -484,11 +462,12 @@ function axis_limits(sp, letter, should_widen = default_should_widen(sp[Symbol(l
|
||||
lims = axis[:lims]
|
||||
has_user_lims = (isa(lims, Tuple) || isa(lims, AVec)) && length(lims) == 2
|
||||
if has_user_lims
|
||||
if isfinite(lims[1])
|
||||
amin = lims[1]
|
||||
lmin, lmax = lims
|
||||
if lmin != :auto && isfinite(lmin)
|
||||
amin = lmin
|
||||
end
|
||||
if isfinite(lims[2])
|
||||
amax = lims[2]
|
||||
if lmax != :auto && isfinite(lmax)
|
||||
amax = lmax
|
||||
end
|
||||
end
|
||||
if amax <= amin && isfinite(amin)
|
||||
@@ -514,7 +493,7 @@ function axis_limits(sp, letter, should_widen = default_should_widen(sp[Symbol(l
|
||||
amin, amax
|
||||
end
|
||||
|
||||
if !has_user_lims && consider_aspect && letter in (:x, :y) && !(sp[:aspect_ratio] in (:none, :auto) || is3d(:sp))
|
||||
if !has_user_lims && consider_aspect && letter in (:x, :y) && !(sp[:aspect_ratio] in (:none, :auto) || RecipesPipeline.is3d(:sp))
|
||||
aspect_ratio = isa(sp[:aspect_ratio], Number) ? sp[:aspect_ratio] : 1
|
||||
plot_ratio = height(plotarea(sp)) / width(plotarea(sp))
|
||||
dist = amax - amin
|
||||
@@ -648,8 +627,8 @@ function axis_drawing_info(sp::Subplot)
|
||||
sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine
|
||||
end
|
||||
if !(xaxis[:ticks] in (:none, nothing, false))
|
||||
f = scalefunc(yaxis[:scale])
|
||||
invf = invscalefunc(yaxis[:scale])
|
||||
f = RecipesPipeline.scale_func(yaxis[:scale])
|
||||
invf = RecipesPipeline.inverse_scale_func(yaxis[:scale])
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
t = invf(f(0) + 0.012 * (f(ymax) - f(ymin)))
|
||||
(-t, t)
|
||||
@@ -702,8 +681,8 @@ function axis_drawing_info(sp::Subplot)
|
||||
sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine
|
||||
end
|
||||
if !(yaxis[:ticks] in (:none, nothing, false))
|
||||
f = scalefunc(xaxis[:scale])
|
||||
invf = invscalefunc(xaxis[:scale])
|
||||
f = RecipesPipeline.scale_func(xaxis[:scale])
|
||||
invf = RecipesPipeline.inverse_scale_func(xaxis[:scale])
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
t = invf(f(0) + 0.012 * (f(xmax) - f(xmin)))
|
||||
(-t, t)
|
||||
@@ -794,8 +773,8 @@ function axis_drawing_info_3d(sp::Subplot)
|
||||
sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2, z2), (xmax, y2, z2)) # top spine
|
||||
end
|
||||
if !(xaxis[:ticks] in (:none, nothing, false))
|
||||
f = scalefunc(yaxis[:scale])
|
||||
invf = invscalefunc(yaxis[:scale])
|
||||
f = RecipesPipeline.scale_func(yaxis[:scale])
|
||||
invf = RecipesPipeline.inverse_scale_func(yaxis[:scale])
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
t = invf(f(0) + 0.012 * (f(ymax) - f(ymin)))
|
||||
(-t, t)
|
||||
@@ -869,8 +848,8 @@ function axis_drawing_info_3d(sp::Subplot)
|
||||
sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin, z2), (x2, ymax, z2)) # right spine
|
||||
end
|
||||
if !(yaxis[:ticks] in (:none, nothing, false))
|
||||
f = scalefunc(xaxis[:scale])
|
||||
invf = invscalefunc(xaxis[:scale])
|
||||
f = RecipesPipeline.scale_func(xaxis[:scale])
|
||||
invf = RecipesPipeline.inverse_scale_func(xaxis[:scale])
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
t = invf(f(0) + 0.012 * (f(xmax) - f(xmin)))
|
||||
(-t, t)
|
||||
@@ -944,8 +923,8 @@ function axis_drawing_info_3d(sp::Subplot)
|
||||
sp[:framestyle] in (:semi, :box) && push!(zborder_segs, (x2, y2, zmin), (x2, y2, zmax))
|
||||
end
|
||||
if !(zaxis[:ticks] in (:none, nothing, false))
|
||||
f = scalefunc(xaxis[:scale])
|
||||
invf = invscalefunc(xaxis[:scale])
|
||||
f = RecipesPipeline.scale_func(xaxis[:scale])
|
||||
invf = RecipesPipeline.inverse_scale_func(xaxis[:scale])
|
||||
tick_start, tick_stop = if sp[:framestyle] == :origin
|
||||
t = invf(f(0) + 0.012 * (f(ymax) - f(ymin)))
|
||||
(-t, t)
|
||||
|
||||
+6
-6
@@ -30,7 +30,7 @@ macro init_backend(s)
|
||||
esc(quote
|
||||
struct $T <: AbstractBackend end
|
||||
export $sym
|
||||
$sym(; kw...) = (default(; kw...); backend($T()))
|
||||
$sym(; kw...) = (default(; reset = false, kw...); backend($T()))
|
||||
backend_name(::$T) = Symbol($str)
|
||||
backend_package_name(pkg::$T) = backend_package_name(Symbol($str))
|
||||
push!(_backends, Symbol($str))
|
||||
@@ -104,7 +104,7 @@ end
|
||||
# Set the (left, top, right, bottom) minimum padding around the plot area
|
||||
# to fit ticks, tick labels, guides, colorbars, etc.
|
||||
function _update_min_padding!(sp::Subplot)
|
||||
# TODO: something different when `is3d(sp) == true`
|
||||
# TODO: something different when `RecipesPipeline.is3d(sp) == true`
|
||||
leftpad = tick_padding(sp, sp[:yaxis]) + sp[:left_margin] + guide_padding(sp[:yaxis])
|
||||
toppad = sp[:top_margin] + title_padding(sp)
|
||||
rightpad = sp[:right_margin]
|
||||
@@ -378,7 +378,7 @@ const _plotly_attr = merge_with_base_supported([
|
||||
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle,
|
||||
:fillrange, :fillcolor, :fillalpha,
|
||||
:bins,
|
||||
:title, :title_location,
|
||||
:title, :titlelocation,
|
||||
:titlefontfamily, :titlefontsize, :titlefonthalign, :titlefontvalign,
|
||||
:titlefontcolor,
|
||||
:legendfontfamily, :legendfontsize, :legendfontcolor,
|
||||
@@ -505,7 +505,7 @@ const _pyplot_attr = merge_with_base_supported([
|
||||
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
:fillrange, :fillcolor, :fillalpha,
|
||||
:bins, :bar_width, :bar_edges, :bar_position,
|
||||
:title, :title_location, :titlefont,
|
||||
:title, :titlelocation, :titlefont,
|
||||
:window_title,
|
||||
:guide, :guide_position, :lims, :ticks, :scale, :flip, :rotation,
|
||||
:titlefontfamily, :titlefontsize, :titlefontcolor,
|
||||
@@ -582,7 +582,7 @@ const _hdf5_attr = merge_with_base_supported([
|
||||
:markerstrokewidth, :markerstrokecolor, :markerstrokealpha,
|
||||
:fillrange, :fillcolor, :fillalpha,
|
||||
:bins, :bar_width, :bar_edges, :bar_position,
|
||||
:title, :title_location, :titlefont,
|
||||
:title, :titlelocation, :titlefont,
|
||||
:window_title,
|
||||
:guide, :lims, :ticks, :scale, :flip, :rotation,
|
||||
:tickfont, :guidefont, :legendfont,
|
||||
@@ -629,7 +629,7 @@ const _inspectdr_attr = merge_with_base_supported([
|
||||
:markerstrokestyle, #Causes warning not to have it... what is this?
|
||||
:fillcolor, :fillalpha, #:fillrange,
|
||||
# :bins, :bar_width, :bar_edges, :bar_position,
|
||||
:title, :title_location,
|
||||
:title, :titlelocation,
|
||||
:window_title,
|
||||
:guide, :lims, :scale, #:ticks, :flip, :rotation,
|
||||
:titlefontfamily, :titlefontsize, :titlefontcolor,
|
||||
|
||||
+52
-51
@@ -67,6 +67,11 @@ const gr_font_family = Dict(
|
||||
"palatino" => 26
|
||||
)
|
||||
|
||||
const gr_vector_font = Dict(
|
||||
"serif-roman" => 232,
|
||||
"sans-serif" => 233
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
gr_color(c) = gr_color(c, color_type(c))
|
||||
@@ -390,6 +395,8 @@ function gr_set_font(f::Font; halign = f.halign, valign = f.valign,
|
||||
GR.setcharup(sind(-rotation), cosd(-rotation))
|
||||
if haskey(gr_font_family, family)
|
||||
GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING)
|
||||
elseif haskey(gr_vector_font, family)
|
||||
GR.settextfontprec(gr_vector_font[family], 3)
|
||||
end
|
||||
gr_set_textcolor(color)
|
||||
GR.settextalign(gr_halign[halign], gr_valign[valign])
|
||||
@@ -428,7 +435,7 @@ function gr_viewport_from_bbox(sp::Subplot{GRBackend}, bb::BoundingBox, w, h, vi
|
||||
viewport[3] = viewport_canvas[4] * (1.0 - bottom(bb) / h)
|
||||
viewport[4] = viewport_canvas[4] * (1.0 - top(bb) / h)
|
||||
if hascolorbar(sp)
|
||||
viewport[2] -= gr_colorbar_ratio * (1 + is3d(sp) / 2)
|
||||
viewport[2] -= gr_colorbar_ratio * (1 + RecipesPipeline.is3d(sp) / 2)
|
||||
end
|
||||
viewport
|
||||
end
|
||||
@@ -436,8 +443,8 @@ end
|
||||
# change so we're focused on the viewport area
|
||||
function gr_set_viewport_cmap(sp::Subplot)
|
||||
GR.setviewport(
|
||||
viewport_plotarea[2] + (is3d(sp) ? 0.07 : 0.02),
|
||||
viewport_plotarea[2] + (is3d(sp) ? 0.10 : 0.05),
|
||||
viewport_plotarea[2] + (RecipesPipeline.is3d(sp) ? 0.07 : 0.02),
|
||||
viewport_plotarea[2] + (RecipesPipeline.is3d(sp) ? 0.10 : 0.05),
|
||||
viewport_plotarea[3],
|
||||
viewport_plotarea[4]
|
||||
)
|
||||
@@ -527,7 +534,7 @@ function gr_draw_colorbar(cbar::GRColorbar, sp::Subplot, clims)
|
||||
GR.setwindow(xmin, xmax, zmin, zmax)
|
||||
if !isempty(cbar.gradients)
|
||||
series = cbar.gradients
|
||||
gr_set_gradient(_cbar_unique(gr_get_color.(series),"color"))
|
||||
gr_set_gradient(_cbar_unique(get_colorgradient.(series),"color"))
|
||||
gr_set_transparency(_cbar_unique(get_fillalpha.(series), "fill alpha"))
|
||||
GR.cellarray(xmin, xmax, zmax, zmin, 1, 256, 1000:1255)
|
||||
end
|
||||
@@ -535,7 +542,7 @@ function gr_draw_colorbar(cbar::GRColorbar, sp::Subplot, clims)
|
||||
if !isempty(cbar.fills)
|
||||
series = cbar.fills
|
||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||
gr_set_gradient(_cbar_unique(gr_get_color.(series), "color"))
|
||||
gr_set_gradient(_cbar_unique(get_colorgradient.(series), "color"))
|
||||
gr_set_transparency(_cbar_unique(get_fillalpha.(series), "fill alpha"))
|
||||
levels = _cbar_unique(contour_levels.(series, Ref(clims)), "levels")
|
||||
# GR implicitly uses the maximal z value as the highest level
|
||||
@@ -554,7 +561,7 @@ function gr_draw_colorbar(cbar::GRColorbar, sp::Subplot, clims)
|
||||
|
||||
if !isempty(cbar.lines)
|
||||
series = cbar.lines
|
||||
gr_set_gradient(_cbar_unique(gr_get_color.(series),"color"))
|
||||
gr_set_gradient(_cbar_unique(get_colorgradient.(series),"color"))
|
||||
gr_set_line(_cbar_unique(get_linewidth.(series), "line width"),
|
||||
_cbar_unique(get_linestyle.(series), "line style"),
|
||||
_cbar_unique(get_linecolor.(series, Ref(clims)), "line color"))
|
||||
@@ -650,25 +657,10 @@ function gr_set_gradient(c)
|
||||
end
|
||||
|
||||
function gr_set_gradient(series::Series)
|
||||
color = gr_get_color(series)
|
||||
color = get_colorgradient(series)
|
||||
color !== nothing && gr_set_gradient(color)
|
||||
end
|
||||
|
||||
function gr_get_color(series::Series)
|
||||
st = series[:seriestype]
|
||||
if st in (:surface, :heatmap) || isfilledcontour(series)
|
||||
series[:fillcolor]
|
||||
elseif st in (:contour, :wireframe)
|
||||
series[:linecolor]
|
||||
elseif series[:marker_z] !== nothing
|
||||
series[:markercolor]
|
||||
elseif series[:line_z] !== nothing
|
||||
series[:linecolor]
|
||||
elseif series[:fill_z] !== nothing
|
||||
series[:fillcolor]
|
||||
end
|
||||
end
|
||||
|
||||
# this is our new display func... set up the viewport_canvas, compute bounding boxes, and display each subplot
|
||||
function gr_display(plt::Plot, fmt="")
|
||||
GR.clearws()
|
||||
@@ -815,6 +807,7 @@ end
|
||||
|
||||
function _update_min_padding!(sp::Subplot{GRBackend})
|
||||
dpi = sp.plt[:thickness_scaling]
|
||||
ENV["GKS_ENCODING"] = "utf8"
|
||||
if !haskey(ENV, "GKSwstype")
|
||||
if isijulia()
|
||||
ENV["GKSwstype"] = "svg"
|
||||
@@ -833,7 +826,7 @@ function _update_min_padding!(sp::Subplot{GRBackend})
|
||||
toppad += h
|
||||
end
|
||||
|
||||
if is3d(sp)
|
||||
if RecipesPipeline.is3d(sp)
|
||||
xaxis, yaxis, zaxis = sp[:xaxis], sp[:yaxis], sp[:zaxis]
|
||||
xticks, yticks, zticks = get_ticks(sp, xaxis), get_ticks(sp, yaxis), get_ticks(sp, zaxis)
|
||||
# Add margin for x and y ticks
|
||||
@@ -872,7 +865,8 @@ function _update_min_padding!(sp::Subplot{GRBackend})
|
||||
tickfont(zaxis),
|
||||
halign = (zaxis[:mirror] ? :left : :right),
|
||||
valign = (:top, :vcenter, :bottom)[sign(zaxis[:rotation]) + 2],
|
||||
rotation = zaxis[:rotation]
|
||||
rotation = zaxis[:rotation],
|
||||
color = zaxis[:tickfontcolor],
|
||||
)
|
||||
l = 0.01 + first(gr_get_ticks_size(zticks, zaxis[:rotation]))
|
||||
w = 1mm + gr_plot_size[1] * l * px
|
||||
@@ -1053,7 +1047,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
|
||||
# fill in the plot area background
|
||||
bg = plot_color(sp[:background_color_inside])
|
||||
is3d(sp) || gr_fill_viewport(viewport_plotarea, bg)
|
||||
RecipesPipeline.is3d(sp) || gr_fill_viewport(viewport_plotarea, bg)
|
||||
|
||||
# reduced from before... set some flags based on the series in this subplot
|
||||
# TODO: can these be generic flags?
|
||||
@@ -1124,7 +1118,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
gr_set_font(tickfont(xaxis))
|
||||
GR.setlinewidth(sp.plt[:thickness_scaling])
|
||||
|
||||
if is3d(sp)
|
||||
if RecipesPipeline.is3d(sp)
|
||||
zmin, zmax = axis_limits(sp, :z)
|
||||
GR.setspace(zmin, zmax, round.(Int, sp[:camera])...)
|
||||
|
||||
@@ -1135,7 +1129,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
plot_area_x = [xmin, xmin, xmin, xmax, xmax, xmax, xmin]
|
||||
plot_area_y = [ymin, ymin, ymax, ymax, ymax, ymin, ymin]
|
||||
plot_area_z = [zmin, zmax, zmax, zmax, zmin, zmin, zmin]
|
||||
x_bg, y_bg = unzip(GR.wc3towc.(plot_area_x, plot_area_y, plot_area_z))
|
||||
x_bg, y_bg = RecipesPipeline.unzip(GR.wc3towc.(plot_area_x, plot_area_y, plot_area_z))
|
||||
GR.fillarea(x_bg, y_bg)
|
||||
|
||||
# draw the grid lines
|
||||
@@ -1230,7 +1224,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
tickfont(xaxis),
|
||||
halign = (:left, :hcenter, :right)[sign(xaxis[:rotation]) + 2],
|
||||
valign = (xaxis[:mirror] ? :bottom : :top),
|
||||
rotation = xaxis[:rotation]
|
||||
rotation = xaxis[:rotation],
|
||||
color = xaxis[:tickfontcolor],
|
||||
)
|
||||
yt = if sp[:framestyle] == :origin
|
||||
0
|
||||
@@ -1260,7 +1255,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
tickfont(yaxis),
|
||||
halign = (:left, :hcenter, :right)[sign(yaxis[:rotation]) + 2],
|
||||
valign = (yaxis[:mirror] ? :bottom : :top),
|
||||
rotation = yaxis[:rotation]
|
||||
rotation = yaxis[:rotation],
|
||||
color = yaxis[:tickfontcolor],
|
||||
)
|
||||
xt = if sp[:framestyle] == :origin
|
||||
0
|
||||
@@ -1290,7 +1286,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
tickfont(zaxis),
|
||||
halign = (zaxis[:mirror] ? :left : :right),
|
||||
valign = (:top, :vcenter, :bottom)[sign(zaxis[:rotation]) + 2],
|
||||
rotation = zaxis[:rotation]
|
||||
rotation = zaxis[:rotation],
|
||||
color = zaxis[:tickfontcolor],
|
||||
)
|
||||
xt = if sp[:framestyle] == :origin
|
||||
0
|
||||
@@ -1336,7 +1333,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
end
|
||||
|
||||
xticks, yticks, xspine_segs, yspine_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs = axis_drawing_info(sp)
|
||||
# @show xticks yticks #spine_segs grid_segs
|
||||
|
||||
# draw the grid lines
|
||||
if xaxis[:grid]
|
||||
@@ -1424,14 +1420,16 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
end
|
||||
|
||||
# border
|
||||
intensity = sp[:framestyle] == :semi ? 0.5 : 1.0
|
||||
intensity = sp[:framestyle] == :semi ? 0.5 : 1
|
||||
if sp[:framestyle] in (:box, :semi)
|
||||
GR.setclip(0)
|
||||
gr_set_line(intensity, :solid, xaxis[:foreground_color_border])
|
||||
gr_set_transparency(xaxis[:foreground_color_border], intensity)
|
||||
gr_polyline(coords(xborder_segs)...)
|
||||
gr_set_line(intensity, :solid, yaxis[:foreground_color_border])
|
||||
gr_set_transparency(yaxis[:foreground_color_border], intensity)
|
||||
gr_polyline(coords(yborder_segs)...)
|
||||
GR.setclip(1)
|
||||
end
|
||||
end
|
||||
# end
|
||||
@@ -1440,7 +1438,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
GR.savestate()
|
||||
if sp[:title] != ""
|
||||
gr_set_font(titlefont(sp))
|
||||
loc = sp[:title_location]
|
||||
loc = sp[:titlelocation]
|
||||
if loc == :left
|
||||
xpos = viewport_plotarea[1]
|
||||
halign = GR.TEXT_HALIGN_LEFT
|
||||
@@ -1454,7 +1452,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
GR.settextalign(halign, GR.TEXT_VALIGN_TOP)
|
||||
gr_text(xpos, viewport_subplot[4], sp[:title])
|
||||
end
|
||||
if is3d(sp)
|
||||
if RecipesPipeline.is3d(sp)
|
||||
if xaxis[:guide] != ""
|
||||
gr_set_font(
|
||||
guidefont(xaxis),
|
||||
@@ -1651,34 +1649,32 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
|
||||
elseif st == :heatmap
|
||||
zmin, zmax = clims
|
||||
fillgrad = _as_gradient(series[:fillcolor])
|
||||
if !ispolar(sp)
|
||||
GR.setspace(zmin, zmax, 0, 90)
|
||||
GR.setspace(clims..., 0, 90)
|
||||
x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z]))
|
||||
w, h = length(x) - 1, length(y) - 1
|
||||
z_normalized = map(x -> GR.jlgr.normalize_color(x, zmin, zmax), z)
|
||||
if is_uniformly_spaced(x) && is_uniformly_spaced(y)
|
||||
# For uniformly spaced data use GR.drawimage, which can be
|
||||
# much faster than GR.nonuniformcellarray, especially for
|
||||
# pdf output, and also supports alpha values.
|
||||
# Note that drawimage draws uniformly spaced data correctly
|
||||
# even on log scales, where it is visually non-uniform.
|
||||
fillgrad = _as_gradient(series[:fillcolor])
|
||||
colors = plot_color.(fillgrad[z_normalized], series[:fillalpha])
|
||||
colors[isnan.(z_normalized)] .= RGBA(0,0,0,0)
|
||||
colors = plot_color.(get(fillgrad, z, clims), series[:fillalpha])
|
||||
rgba = gr_color.(colors)
|
||||
GR.drawimage(first(x), last(x), last(y), first(y), w, h, rgba)
|
||||
else
|
||||
(something(series[:fillalpha],1) < 1 || any(_gr_gradient_alpha .< 1)) && @warn(
|
||||
"GR: transparency not supported in non-uniform heatmaps. Alpha values ignored.")
|
||||
z_normalized = map(x -> isnan(x) ? 256/255 : x, z_normalized) # results in color index = 1256 -> transparent
|
||||
colors = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized]
|
||||
GR.nonuniformcellarray(x, y, w, h, colors)
|
||||
if something(series[:fillalpha],1) < 1 || any(_gr_gradient_alpha .< 1)
|
||||
@warn "GR: transparency not supported in non-uniform heatmaps. Alpha values ignored."
|
||||
end
|
||||
colors = get(fillgrad, z, clims)
|
||||
z_normalized = map(c -> c == invisible() ? 256/255 : PlotUtils.getinverse(fillgrad, c), colors)
|
||||
rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized]
|
||||
GR.nonuniformcellarray(x, y, w, h, rgba)
|
||||
end
|
||||
else
|
||||
phimin, phimax = 0.0, 360.0 # nonuniform polar array is not yet supported in GR.jl
|
||||
nx, ny = length(series[:x]), length(series[:y])
|
||||
z_normalized = map(x -> GR.jlgr.normalize_color(x, zmin, zmax), z)
|
||||
colors = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized]
|
||||
xmin, xmax, ymin, ymax = xy_lims
|
||||
rmax = data_lims[4]
|
||||
GR.setwindow(-rmax, rmax, -rmax, rmax)
|
||||
@@ -1688,8 +1684,11 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
if series[:y][end] != ny
|
||||
@warn "Right now only the maximum value of y (r) is taken into account."
|
||||
end
|
||||
colors = get(fillgrad, z, clims)
|
||||
z_normalized = map(c -> c == invisible() ? 256/255 : PlotUtils.getinverse(fillgrad.colors, c), colors)
|
||||
rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized]
|
||||
# GR.polarcellarray(0, 0, phimin, phimax, ymin, ymax, nx, ny, colors)
|
||||
GR.polarcellarray(0, 0, phimin, phimax, 0, ymax, nx, ny, colors)
|
||||
GR.polarcellarray(0, 0, phimin, phimax, 0, ymax, nx, ny, rgba)
|
||||
# Right now only the maximum value of y (r) is taken into account.
|
||||
# This is certainly not perfect but nonuniform polar array is not yet supported in GR.jl
|
||||
end
|
||||
@@ -1711,7 +1710,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
|
||||
# draw markers
|
||||
if st == :scatter3d || series[:markershape] != :none
|
||||
x2, y2 = unzip(map(GR.wc3towc, x, y, z))
|
||||
x2, y2 = RecipesPipeline.unzip(map(GR.wc3towc, x, y, z))
|
||||
gr_draw_markers(series, x2, y2, clims)
|
||||
end
|
||||
|
||||
@@ -1808,7 +1807,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
if sp[:legend] == :inline && should_add_to_legend(series)
|
||||
gr_set_font(legendfont(sp))
|
||||
gr_set_textcolor(plot_color(sp[:legendfontcolor]))
|
||||
if sp[:yaxis][:mirror]
|
||||
if sp[:yaxis][:mirror]
|
||||
(_,i) = sp[:xaxis][:flip] ? findmax(x) : findmin(x)
|
||||
GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF)
|
||||
offset = -0.01
|
||||
@@ -1827,7 +1826,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
hascolorbar(sp) && gr_draw_colorbar(cbar, sp, get_clims(sp))
|
||||
|
||||
# add the legend
|
||||
if !(sp[:legend] in(:none, :inline))
|
||||
if !(sp[:legend] in(:none, :inline))
|
||||
GR.savestate()
|
||||
GR.selntran(0)
|
||||
GR.setscale(0)
|
||||
@@ -1910,7 +1909,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
end
|
||||
for ann in sp[:annotations]
|
||||
x, y, val = locate_annotation(sp, ann...)
|
||||
x, y = if is3d(sp)
|
||||
x, y = if RecipesPipeline.is3d(sp)
|
||||
gr_w3tondc(x, y, z)
|
||||
else
|
||||
GR.wctondc(x, y)
|
||||
@@ -1936,6 +1935,7 @@ gr_set_output(wstype::String) = (_gr_wstype[] = wstype)
|
||||
|
||||
for (mime, fmt) in _gr_mimeformats
|
||||
@eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GRBackend})
|
||||
ENV["GKS_ENCODING"] = "utf8"
|
||||
GR.emergencyclosegks()
|
||||
filepath = tempname() * "." * $fmt
|
||||
env = get(ENV, "GKSwstype", "0")
|
||||
@@ -1954,6 +1954,7 @@ for (mime, fmt) in _gr_mimeformats
|
||||
end
|
||||
|
||||
function _display(plt::Plot{GRBackend})
|
||||
ENV["GKS_ENCODING"] = "utf8"
|
||||
if plt[:display_type] == :inline
|
||||
GR.emergencyclosegks()
|
||||
filepath = tempname() * ".pdf"
|
||||
|
||||
@@ -83,7 +83,7 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1
|
||||
"ARRAY" => Array, #Dict won't allow Array to be key in HDF5PLOT_MAP_TELEM2STR
|
||||
|
||||
#Sub-structure types:
|
||||
"ATTR" => Attr,
|
||||
"DEFAULTSDICT" => DefaultsDict,
|
||||
"FONT" => Font,
|
||||
"BOUNDINGBOX" => BoundingBox,
|
||||
"GRIDLAYOUT" => GridLayout,
|
||||
@@ -395,12 +395,12 @@ function _hdf5plot_write(grp, plotattributes::KW)
|
||||
end
|
||||
return
|
||||
end
|
||||
function _hdf5plot_write(grp, plotattributes::Attr)
|
||||
function _hdf5plot_write(grp, plotattributes::DefaultsDict)
|
||||
for (k, v) in plotattributes
|
||||
kstr = string(k)
|
||||
_hdf5plot_gwrite(grp, kstr, v)
|
||||
end
|
||||
_hdf5plot_writetype(grp, Attr)
|
||||
_hdf5plot_writetype(grp, DefaultsDict)
|
||||
end
|
||||
|
||||
|
||||
@@ -558,14 +558,14 @@ parent = RootLayout()
|
||||
|
||||
return GridLayout(parent, minpad, bbox, grid, widths, heights, attr)
|
||||
end
|
||||
function _hdf5plot_read(grp, T::Type{Attr})
|
||||
attr = Attr(KW(), _plot_defaults)
|
||||
function _hdf5plot_read(grp, T::Type{DefaultsDict})
|
||||
attr = DefaultsDict(KW(), _plot_defaults)
|
||||
v = _hdf5plot_read(grp, attr)
|
||||
return attr
|
||||
end
|
||||
function _hdf5plot_read(grp, k::String, T::Type{Axis})
|
||||
grp = HDF5.g_open(grp, k)
|
||||
plotattributes = Attr(KW(), _plot_defaults)
|
||||
plotattributes = DefaultsDict(KW(), _plot_defaults)
|
||||
_hdf5plot_read(grp, plotattributes)
|
||||
return Axis([], plotattributes)
|
||||
end
|
||||
@@ -610,7 +610,7 @@ function _hdf5plot_readattr(grp, plotattributes::AbstractDict)
|
||||
return
|
||||
end
|
||||
_hdf5plot_read(grp, plotattributes::KW) = _hdf5plot_readattr(grp, plotattributes)
|
||||
_hdf5plot_read(grp, plotattributes::Attr) = _hdf5plot_readattr(grp, plotattributes)
|
||||
_hdf5plot_read(grp, plotattributes::DefaultsDict) = _hdf5plot_readattr(grp, plotattributes)
|
||||
|
||||
# Read main plot structures:
|
||||
# ----------------------------------------------------------------
|
||||
@@ -623,7 +623,7 @@ function _hdf5plot_read(sp::Subplot, subpath::String, f)
|
||||
|
||||
for i in 1:nseries
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/series_list/series$i"))
|
||||
seriesinfo = Attr(KW(), _plot_defaults)
|
||||
seriesinfo = DefaultsDict(KW(), _plot_defaults)
|
||||
_hdf5plot_read(grp, seriesinfo)
|
||||
plot!(sp, seriesinfo[:x], seriesinfo[:y]) #Add data & create data structures
|
||||
_hdf5_merge!(sp.series_list[end].plotattributes, seriesinfo)
|
||||
@@ -631,7 +631,7 @@ function _hdf5plot_read(sp::Subplot, subpath::String, f)
|
||||
|
||||
#Perform after adding series... otherwise values get overwritten:
|
||||
grp = HDF5.g_open(f, _hdf5_plotelempath("$subpath/attr"))
|
||||
attr = Attr(KW(), _plot_defaults)
|
||||
attr = DefaultsDict(KW(), _plot_defaults)
|
||||
_hdf5plot_read(grp, attr)
|
||||
_hdf5_merge!(sp.attr, attr)
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ function pgf_series(sp::Subplot, series::Series)
|
||||
# function args
|
||||
args = if st == :contour
|
||||
plotattributes[:z].surf, plotattributes[:x], plotattributes[:y]
|
||||
elseif is3d(st)
|
||||
elseif RecipesPipeline.is3d(st)
|
||||
plotattributes[:x], plotattributes[:y], plotattributes[:z]
|
||||
elseif st == :straightline
|
||||
straightline_data(series)
|
||||
@@ -271,7 +271,7 @@ function pgf_fillrange_series(series, i, fillrange, args...)
|
||||
push!(style, _pgf_series_extrastyle[st])
|
||||
end
|
||||
kw[:style] = join(style, ',')
|
||||
func = is3d(series) ? PGFPlots.Linear3 : PGFPlots.Linear
|
||||
func = RecipesPipeline.is3d(series) ? PGFPlots.Linear3 : PGFPlots.Linear
|
||||
return func(pgf_fillrange_args(fillrange, args...)...; kw...)
|
||||
end
|
||||
|
||||
@@ -444,7 +444,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
|
||||
|
||||
# add to style/kw for each axis
|
||||
for letter in (:x, :y, :z)
|
||||
if letter != :z || is3d(sp)
|
||||
if letter != :z || RecipesPipeline.is3d(sp)
|
||||
axisstyle, axiskw = pgf_axis(sp, letter)
|
||||
append!(style, axisstyle)
|
||||
merge!(kw, axiskw)
|
||||
@@ -494,7 +494,7 @@ function _update_plot_object(plt::Plot{PGFPlotsBackend})
|
||||
if any(s[:seriestype] == :contour for s in series_list(sp))
|
||||
kw[:view] = "{0}{90}"
|
||||
kw[:colorbar] = !(sp[:colorbar] in (:none, :off, :hide, false))
|
||||
elseif is3d(sp)
|
||||
elseif RecipesPipeline.is3d(sp)
|
||||
azim, elev = sp[:camera]
|
||||
kw[:view] = "{$(azim)}{$(elev)}"
|
||||
end
|
||||
|
||||
+91
-39
@@ -123,7 +123,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
fg_alpha = alpha(plot_color(sp[:foreground_color_legend]))
|
||||
title_cstr = plot_color(sp[:titlefontcolor])
|
||||
title_a = alpha(title_cstr)
|
||||
title_loc = sp[:title_location]
|
||||
title_loc = sp[:titlelocation]
|
||||
bgc_inside = plot_color(sp[:background_color_inside])
|
||||
bgc_inside_a = alpha(bgc_inside)
|
||||
axis_opt = PGFPlotsX.Options(
|
||||
@@ -180,13 +180,12 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
push!(axis_opt["legend style"], "at={($x, $y)}")
|
||||
else
|
||||
push!(
|
||||
axis_opt,
|
||||
"legend pos" =>
|
||||
get(_pgfplotsx_legend_pos, sp[:legend], "outer north east"),
|
||||
axis_opt["legend style"],
|
||||
get(_pgfplotsx_legend_pos, sp[:legend], ("at" => string((1.02, 1)), "anchor" => "north west"))...
|
||||
)
|
||||
end
|
||||
for letter in (:x, :y, :z)
|
||||
if letter != :z || is3d(sp)
|
||||
if letter != :z || RecipesPipeline.is3d(sp)
|
||||
pgfx_axis!(axis_opt, sp, letter)
|
||||
end
|
||||
end
|
||||
@@ -201,12 +200,13 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
# As it is likely that all series within the same axis use the same
|
||||
# colormap this should not cause any problem.
|
||||
for series in series_list(sp)
|
||||
for col in (:markercolor, :fillcolor, :linecolor)
|
||||
if typeof(series.plotattributes[col]) == ColorGradient
|
||||
if hascolorbar(series)
|
||||
cg = get_colorgradient(series)
|
||||
cm = pgfx_colormap(get_colorgradient(series))
|
||||
PGFPlotsX.push_preamble!(
|
||||
pgfx_plot.the_plot,
|
||||
"""\\pgfplotsset{
|
||||
colormap={plots$(sp.attr[:subplot_index])}{$(pgfx_colormap(series.plotattributes[col]))},
|
||||
colormap={plots$(sp.attr[:subplot_index])}{$cm},
|
||||
}""",
|
||||
)
|
||||
push!(
|
||||
@@ -214,10 +214,16 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
"colorbar" => nothing,
|
||||
"colormap name" => "plots$(sp.attr[:subplot_index])",
|
||||
)
|
||||
if cg isa PlotUtils.CategoricalColorGradient
|
||||
push!(
|
||||
axis_opt,
|
||||
"colormap access" => "piecewise const",
|
||||
"colorbar sampled" => nothing,
|
||||
)
|
||||
end
|
||||
# goto is needed to break out of col and series for
|
||||
@goto colorbar_end
|
||||
end
|
||||
end
|
||||
end
|
||||
@label colorbar_end
|
||||
|
||||
@@ -225,11 +231,11 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
axis_opt,
|
||||
"colorbar style" => PGFPlotsX.Options(
|
||||
"title" => sp[:colorbar_title],
|
||||
"point meta max" => get_clims(sp)[2],
|
||||
"point meta min" => get_clims(sp)[1],
|
||||
),
|
||||
"point meta max" => get_clims(sp)[2],
|
||||
"point meta min" => get_clims(sp)[1],
|
||||
)
|
||||
if is3d(sp)
|
||||
if RecipesPipeline.is3d(sp)
|
||||
azim, elev = sp[:camera]
|
||||
push!(axis_opt, "view" => (azim, elev))
|
||||
end
|
||||
@@ -241,6 +247,16 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
PGFPlotsX.Axis
|
||||
end
|
||||
axis = axisf(axis_opt)
|
||||
if sp[:legendtitle] !== nothing
|
||||
push!(axis, PGFPlotsX.Options("\\addlegendimage{empty legend}" => nothing))
|
||||
push!(
|
||||
axis,
|
||||
PGFPlotsX.LegendEntry(
|
||||
string("\\hspace{-.6cm}{\\textbf{", sp[:legendtitle], "}}"),
|
||||
false,
|
||||
),
|
||||
)
|
||||
end
|
||||
for (series_index, series) in enumerate(series_list(sp))
|
||||
# give each series a uuid for fillbetween
|
||||
series_id = uuid4()
|
||||
@@ -252,7 +268,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
|
||||
"color" => single_color(opt[:linecolor]),
|
||||
"name path" => string(series_id),
|
||||
)
|
||||
if is3d(series) || st == :heatmap
|
||||
if RecipesPipeline.is3d(series) || st == :heatmap
|
||||
series_func = PGFPlotsX.Plot3
|
||||
else
|
||||
series_func = PGFPlotsX.Plot
|
||||
@@ -423,7 +439,7 @@ end
|
||||
opt[:x], opt[:y], Array(opt[:z])'
|
||||
elseif st in (:heatmap, :surface, :wireframe)
|
||||
surface_to_vecs(opt[:x], opt[:y], opt[:z])
|
||||
elseif is3d(st)
|
||||
elseif RecipesPipeline.is3d(st)
|
||||
opt[:x], opt[:y], opt[:z]
|
||||
elseif st == :straightline
|
||||
straightline_data(series)
|
||||
@@ -611,22 +627,22 @@ const _pgfplotsx_markers = KW(
|
||||
)
|
||||
|
||||
const _pgfplotsx_legend_pos = KW(
|
||||
:top => "north",
|
||||
:bottom => "south",
|
||||
:left => "west",
|
||||
:right => "east",
|
||||
:bottomleft => "south west",
|
||||
:bottomright => "south east",
|
||||
:topright => "north east",
|
||||
:topleft => "north west",
|
||||
:outertop => "north",
|
||||
:outerbottom => "outer south",
|
||||
:outerleft => "outer west",
|
||||
:outerright => "outer east",
|
||||
:outerbottomleft => "outer south west",
|
||||
:outerbottomright => "outer south east",
|
||||
:outertopright => "outer north east",
|
||||
:outertopleft => "outer north west",
|
||||
:top => ("at" => string((0.5, 0.98)), "anchor" => "north"),
|
||||
:bottom => ("at" => string((0.5, 0.02)), "anchor" => "south"),
|
||||
:left => ("at" => string((0.02, 0.5)), "anchor" => "west"),
|
||||
:right => ("at" => string((0.98, 0.5)), "anchor" => "east"),
|
||||
:bottomleft => ("at" => string((0.02, 0.02)), "anchor" => "south west"),
|
||||
:bottomright => ("at" => string((0.98, 0.02)), "anchor" => "south east"),
|
||||
:topright => ("at" => string((0.98, 0.98)), "anchor" => "north east"),
|
||||
:topleft => ("at" => string((-0.02, 0.98)), "anchor" => "north west"),
|
||||
:outertop => ("at" => string((0.5, 1.02)), "anchor" => "south"),
|
||||
:outerbottom => ("at" => string((0.5, -0.02)), "anchor" => "north"),
|
||||
:outerleft => ("at" => string((-0.02, 0.5)), "anchor" => "east"),
|
||||
:outerright => ("at" => string((1.02, 0.5)), "anchor" => "west"),
|
||||
:outerbottomleft => ("at" => string((-0.02, -0.02)), "anchor" => "north east"),
|
||||
:outerbottomright => ("at" => string((1.02, -0.02)), "anchor" => "north west"),
|
||||
:outertopright => ("at" => string((1.02, 1)), "anchor" => "north west"),
|
||||
:outertopleft => ("at" => string((-0.02, 1)), "anchor" => "north east"),
|
||||
)
|
||||
|
||||
const _pgfx_framestyles = [:box, :axes, :origin, :zerolines, :grid, :none]
|
||||
@@ -668,17 +684,19 @@ function pgfx_filllegend!(series_opt, opt)
|
||||
}""")
|
||||
end
|
||||
|
||||
function pgfx_colormap(grad::ColorGradient)
|
||||
join(map(grad.colors) do c
|
||||
pgfx_colormap(cl::PlotUtils.AbstractColorList) = pgfx_colormap(color_list(cl))
|
||||
function pgfx_colormap(v::Vector{<:Colorant})
|
||||
join(map(v) do c
|
||||
@sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c), blue(c))
|
||||
end, "\n")
|
||||
end
|
||||
function pgfx_colormap(grad::Vector{<:Colorant})
|
||||
join(map(grad) do c
|
||||
@sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c), blue(c))
|
||||
function pgfx_colormap(cg::ColorGradient)
|
||||
join(map(1:length(cg)) do i
|
||||
@sprintf("rgb(%.8fcm)=(%.8f,%.8f,%.8f)", cg.values[i], red(cg.colors[i]), green(cg.colors[i]), blue(cg.colors[i]))
|
||||
end, "\n")
|
||||
end
|
||||
|
||||
|
||||
function pgfx_framestyle(style::Symbol)
|
||||
if style in _pgfx_framestyles
|
||||
return style
|
||||
@@ -759,7 +777,7 @@ function pgfx_marker(plotattributes, i = 1)
|
||||
a_stroke = alpha(cstr_stroke)
|
||||
mark_size =
|
||||
pgfx_thickness_scaling(plotattributes) *
|
||||
0.5 *
|
||||
0.75 *
|
||||
_cycle(plotattributes[:markersize], i)
|
||||
return PGFPlotsX.Options(
|
||||
"mark" =>
|
||||
@@ -771,7 +789,7 @@ function pgfx_marker(plotattributes, i = 1)
|
||||
"fill" => cstr,
|
||||
"fill opacity" => a,
|
||||
"line width" =>
|
||||
pgfx_thickness_scaling(plotattributes) *
|
||||
pgfx_thickness_scaling(plotattributes) * 0.75 *
|
||||
_cycle(plotattributes[:markerstrokewidth], i),
|
||||
"rotate" => if shape == :dtriangle
|
||||
180
|
||||
@@ -875,7 +893,7 @@ function pgfx_fillrange_series!(axis, series, series_func, i, fillrange, rng)
|
||||
fillrange_opt = merge(fillrange_opt, pgfx_marker(series, i))
|
||||
push!(fillrange_opt, "forget plot" => nothing)
|
||||
opt = series.plotattributes
|
||||
args = is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) :
|
||||
args = RecipesPipeline.is3d(series) ? (opt[:x][rng], opt[:y][rng], opt[:z][rng]) :
|
||||
(opt[:x][rng], opt[:y][rng])
|
||||
push!(
|
||||
axis,
|
||||
@@ -1014,6 +1032,8 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
|
||||
# ticks on or off
|
||||
if axis[:ticks] in (nothing, false, :none) || framestyle == :none
|
||||
push!(opt, "$(letter)majorticks" => "false")
|
||||
elseif framestyle in (:grid, :zerolines)
|
||||
push!(opt, "$letter tick style" => PGFPlotsX.Options("draw" => "none"))
|
||||
end
|
||||
|
||||
# grid on or off
|
||||
@@ -1029,6 +1049,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
|
||||
push!(opt, string(letter, :min) => lims[1], string(letter, :max) => lims[2])
|
||||
|
||||
if !(axis[:ticks] in (nothing, false, :none, :native)) && framestyle != :none
|
||||
# ticks
|
||||
ticks = get_ticks(sp, axis)
|
||||
#pgf plot ignores ticks with angle below 90 when xmin = 90 so shift values
|
||||
tick_values =
|
||||
@@ -1094,6 +1115,37 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
|
||||
axis[:gridstyle],
|
||||
),
|
||||
)
|
||||
|
||||
# minor ticks
|
||||
# NOTE: PGFPlots would provide "minor x ticks num", but this only places minor ticks
|
||||
# between major ticks and not outside first and last tick to the axis limits.
|
||||
# Hence, we hack around with extra ticks. Unfortunately this conflicts with
|
||||
# `:zerolines` framestyle hack. So minor ticks are not working with
|
||||
# `:zerolines`.
|
||||
minor_ticks = get_minor_ticks(sp, axis, ticks)
|
||||
if minor_ticks !== nothing
|
||||
minor_ticks =
|
||||
ispolar(sp) && letter == :x ? [rad2deg.(minor_ticks)[3:end]..., 360, 405] :
|
||||
minor_ticks
|
||||
push!(
|
||||
opt,
|
||||
string("extra ", letter, " ticks") => string("{", join(minor_ticks, ","), "}"),
|
||||
)
|
||||
push!(opt, string("extra ", letter, " tick labels") => "")
|
||||
push!(
|
||||
opt,
|
||||
string("extra ", letter, " tick style") => PGFPlotsX.Options(
|
||||
"grid" => axis[:minorgrid] ? "major" : "none",
|
||||
string(letter, " grid style") => pgfx_linestyle(
|
||||
pgfx_thickness_scaling(sp) * axis[:minorgridlinewidth],
|
||||
axis[:foreground_color_minor_grid],
|
||||
axis[:minorgridalpha],
|
||||
axis[:minorgridstyle],
|
||||
),
|
||||
"major tick length" => typeof(axis[:minorticks]) <: Integer && axis[:minorticks] > 1 || axis[:minorticks] ? "0.1cm" : "0"
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# framestyle
|
||||
@@ -1115,7 +1167,7 @@ function pgfx_axis!(opt::PGFPlotsX.Options, sp::Subplot, letter)
|
||||
opt,
|
||||
string("extra ", letter, " tick style") => PGFPlotsX.Options(
|
||||
"grid" => "major",
|
||||
"major grid style" => pgfx_linestyle(
|
||||
string(letter, " grid style") => pgfx_linestyle(
|
||||
pgfx_thickness_scaling(sp),
|
||||
axis[:foreground_color_border],
|
||||
1.0,
|
||||
|
||||
+25
-15
@@ -162,7 +162,7 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
|
||||
|
||||
if letter in (:x,:y)
|
||||
ax[:domain] = plotly_domain(sp, letter)
|
||||
if is3d(sp)
|
||||
if RecipesPipeline.is3d(sp)
|
||||
# don't link 3d axes for synchronized interactivity
|
||||
x_idx = y_idx = sp[:subplot_index]
|
||||
else
|
||||
@@ -176,7 +176,7 @@ function plotly_axis(plt::Plot, axis::Axis, sp::Subplot)
|
||||
lims = axis_limits(sp, letter)
|
||||
|
||||
if axis[:ticks] != :native || axis[:lims] != :auto
|
||||
ax[:range] = map(scalefunc(axis[:scale]), lims)
|
||||
ax[:range] = map(RecipesPipeline.scale_func(axis[:scale]), lims)
|
||||
end
|
||||
|
||||
if !(axis[:ticks] in (nothing, :none, false))
|
||||
@@ -245,7 +245,7 @@ function plotly_layout(plt::Plot)
|
||||
# but vertically just below the top of the subplot bounding box
|
||||
if sp[:title] != ""
|
||||
bb = plotarea(sp)
|
||||
tpos = sp[:title_location]
|
||||
tpos = sp[:titlelocation]
|
||||
xmm = if tpos == :left
|
||||
left(bb)
|
||||
elseif tpos == :right
|
||||
@@ -263,8 +263,8 @@ function plotly_layout(plt::Plot)
|
||||
# set to supported framestyle
|
||||
sp[:framestyle] = _plotly_framestyle(sp[:framestyle])
|
||||
|
||||
# if any(is3d, seriesargs)
|
||||
if is3d(sp)
|
||||
# if any(RecipesPipeline.is3d, seriesargs)
|
||||
if RecipesPipeline.is3d(sp)
|
||||
azim = sp[:camera][1] - 90 #convert azimuthal to match GR behaviour
|
||||
theta = 90 - sp[:camera][2] #spherical coordinate angle from z axis
|
||||
plotattributes_out[:scene] = KW(
|
||||
@@ -350,19 +350,29 @@ function plotly_layout_json(plt::Plot)
|
||||
end
|
||||
|
||||
|
||||
function plotly_colorscale(grad::ColorGradient, α)
|
||||
[[grad.values[i], rgba_string(plot_color(grad.colors[i], α))] for i in eachindex(grad.colors)]
|
||||
end
|
||||
plotly_colorscale(c::Colorant,α) = plotly_colorscale(_as_gradient(c),α)
|
||||
function plotly_colorscale(c::AbstractVector{<:RGBA}, α)
|
||||
plotly_colorscale(cg::ColorGradient, α = nothing) =
|
||||
[[v, rgba_string(plot_color(cg.colors[v], α))] for v in cg.values]
|
||||
function plotly_colorscale(c::AbstractVector{<:Colorant}, α = nothing)
|
||||
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], α))],
|
||||
]
|
||||
else
|
||||
vals = range(0.0, stop=1.0, length=length(c))
|
||||
vals = range(0.0, stop = 1.0, length = length(c))
|
||||
return [[vals[i], rgba_string(plot_color(c[i], α))] for i in eachindex(c)]
|
||||
end
|
||||
end
|
||||
# plotly_colorscale(c, alpha = nothing) = plotly_colorscale(cgrad(), alpha)
|
||||
function plotly_colorscale(cg::PlotUtils.CategoricalColorGradient, α = nothing)
|
||||
n = length(cg)
|
||||
cinds = repeat(1:n, inner = 2)
|
||||
vinds = vcat((i:(i + 1) for i in 1:n)...)
|
||||
return [
|
||||
[cg.values[vinds[i]], rgba_string(plot_color(color_list(cg)[cinds[i]], α))]
|
||||
for i in eachindex(cinds)
|
||||
]
|
||||
end
|
||||
plotly_colorscale(c, α = nothing) = plotly_colorscale(_as_gradient(c), α)
|
||||
|
||||
|
||||
const _plotly_markers = KW(
|
||||
@@ -748,11 +758,11 @@ function plotly_colorbar_hack(series::Series, plotattributes_base::KW, sym::Symb
|
||||
plotattributes_out = deepcopy(plotattributes_base)
|
||||
cmin, cmax = get_clims(series[:subplot])
|
||||
plotattributes_out[:showlegend] = false
|
||||
plotattributes_out[:type] = is3d(series) ? :scatter3d : :scatter
|
||||
plotattributes_out[:type] = RecipesPipeline.is3d(series) ? :scatter3d : :scatter
|
||||
plotattributes_out[:hoverinfo] = :none
|
||||
plotattributes_out[:mode] = :markers
|
||||
plotattributes_out[:x], plotattributes_out[:y] = [series[:x][1]], [series[:y][1]]
|
||||
if is3d(series)
|
||||
if RecipesPipeline.is3d(series)
|
||||
plotattributes_out[:z] = [series[:z][1]]
|
||||
end
|
||||
# zrange = zmax == zmin ? 1 : zmax - zmin # if all marker_z values are the same, plot all markers same color (avoids division by zero in next line)
|
||||
|
||||
@@ -40,11 +40,9 @@ _show(io::IO, ::MIME"text/html", plt::Plot{PlotlyJSBackend}) = write(io, standal
|
||||
|
||||
_display(plt::Plot{PlotlyJSBackend}) = display(plotlyjs_syncplot(plt))
|
||||
|
||||
@require WebIO = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" begin
|
||||
function WebIO.render(plt::Plot{PlotlyJSBackend})
|
||||
plt_html = sprint(show, MIME("text/html"), plt)
|
||||
return WebIO.render(dom"div"(innerHTML=plt_html))
|
||||
end
|
||||
function PlotlyJS.WebIO.render(plt::Plot{PlotlyJSBackend})
|
||||
plt_html = sprint(show, MIME("text/html"), plt)
|
||||
return PlotlyJS.WebIO.render(PlotlyJS.WebIO.dom"div"(innerHTML=plt_html))
|
||||
end
|
||||
|
||||
function closeall(::PlotlyJSBackend)
|
||||
|
||||
+94
-45
@@ -64,16 +64,23 @@ end
|
||||
py_color(s) = py_color(parse(Colorant, string(s)))
|
||||
py_color(c::Colorant) = (red(c), green(c), blue(c), alpha(c))
|
||||
py_color(cs::AVec) = map(py_color, cs)
|
||||
py_color(grad::ColorGradient) = py_color(grad.colors)
|
||||
py_color(grad::PlotUtils.AbstractColorList) = py_color(color_list(grad))
|
||||
py_color(c::Colorant, α) = py_color(plot_color(c, α))
|
||||
|
||||
function py_colormap(grad::ColorGradient)
|
||||
pyvals = [(z, py_color(grad[z])) for z in grad.values]
|
||||
function py_colormap(cg::ColorGradient)
|
||||
pyvals = collect(zip(cg.values, py_color(PlotUtils.color_list(cg))))
|
||||
cm = pycolors."LinearSegmentedColormap"."from_list"("tmp", pyvals)
|
||||
cm."set_bad"(color=(0,0,0,0.0), alpha=0.0)
|
||||
cm
|
||||
end
|
||||
py_colormap(c::Colorant) = py_colormap(_as_gradient(c))
|
||||
function py_colormap(cg::PlotUtils.CategoricalColorGradient)
|
||||
r = range(0, stop = 1, length = 256)
|
||||
pyvals = collect(zip(r, py_color(cg[r])))
|
||||
cm = pycolors."LinearSegmentedColormap"."from_list"("tmp", pyvals)
|
||||
cm."set_bad"(color=(0,0,0,0.0), alpha=0.0)
|
||||
cm
|
||||
end
|
||||
py_colormap(c) = py_colormap(_as_gradient(c))
|
||||
|
||||
|
||||
function py_shading(c, z)
|
||||
@@ -229,11 +236,6 @@ function fix_xy_lengths!(plt::Plot{PyPlotBackend}, series::Series)
|
||||
end
|
||||
end
|
||||
|
||||
py_linecolor(series::Series) = py_color(series[:linecolor])
|
||||
py_markercolor(series::Series) = py_color(series[:markercolor])
|
||||
py_markerstrokecolor(series::Series) = py_color(series[:markerstrokecolor])
|
||||
py_fillcolor(series::Series) = py_color(series[:fillcolor])
|
||||
|
||||
py_linecolormap(series::Series) = py_colormap(series[:linecolor])
|
||||
py_markercolormap(series::Series) = py_colormap(series[:markercolor])
|
||||
py_fillcolormap(series::Series) = py_colormap(series[:fillcolor])
|
||||
@@ -448,7 +450,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
# :norm => pycolors["Normalize"](; extrakw...)
|
||||
# )
|
||||
# lz = _cycle(series[:line_z], 1:n)
|
||||
# handle = if is3d(st)
|
||||
# handle = if RecipesPipeline.is3d(st)
|
||||
# line_segments = [[(x[j], y[j], z[j]) for j in rng] for rng in segments]
|
||||
# lc = pyart3d["Line3DCollection"](line_segments; kw...)
|
||||
# lc[:set_array](lz)
|
||||
@@ -478,7 +480,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
# end
|
||||
|
||||
a = series[:arrow]
|
||||
if a !== nothing && !is3d(st) # TODO: handle 3d later
|
||||
if a !== nothing && !RecipesPipeline.is3d(st) # TODO: handle 3d later
|
||||
if typeof(a) != Arrow
|
||||
@warn("Unexpected type for arrow: $(typeof(a))")
|
||||
else
|
||||
@@ -486,8 +488,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
:arrowstyle => "simple,head_length=$(a.headlength),head_width=$(a.headwidth)",
|
||||
:shrinkA => 0,
|
||||
:shrinkB => 0,
|
||||
:edgecolor => py_linecolor(series),
|
||||
:facecolor => py_linecolor(series),
|
||||
:edgecolor => py_color(get_linecolor(series)),
|
||||
:facecolor => py_color(get_linecolor(series)),
|
||||
:linewidth => py_thickness_scale(plt, get_linewidth(series)),
|
||||
:linestyle => py_linestyle(st, get_linestyle(series)),
|
||||
)
|
||||
@@ -533,16 +535,19 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
handle = []
|
||||
x,y = xyargs
|
||||
shapes = series[:markershape]
|
||||
msc = py_markerstrokecolor(series)
|
||||
msc = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series))
|
||||
lw = py_thickness_scale(plt, series[:markerstrokewidth])
|
||||
for i=eachindex(y)
|
||||
extrakw[:c] = _cycle(markercolor, i)
|
||||
if series[:marker_z] !== nothing
|
||||
extrakw[:c] = [py_color(get_markercolor(series, i), get_markercoloralpha(series, i))]
|
||||
end
|
||||
|
||||
push!(handle, ax."scatter"(_cycle(x,i), _cycle(y,i);
|
||||
label = series[:label],
|
||||
zorder = series[:series_plotindex] + 0.5,
|
||||
marker = py_marker(_cycle(shapes,i)),
|
||||
s = py_thickness_scale(plt, _cycle(series[:markersize],i) .^ 2),
|
||||
facecolors = py_color(get_markercolor(series, i), get_markercoloralpha(series, i)),
|
||||
edgecolors = msc,
|
||||
linewidths = lw,
|
||||
extrakw...
|
||||
@@ -582,7 +587,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
zorder = series[:series_plotindex] + 0.5,
|
||||
marker = prev_marker,
|
||||
s = cur_scale_list,
|
||||
edgecolors = py_markerstrokecolor(series),
|
||||
edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)), # Do we need include i?
|
||||
linewidths = py_thickness_scale(plt, series[:markerstrokewidth]),
|
||||
facecolors = cur_color_list,
|
||||
extrakw...
|
||||
@@ -603,7 +608,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
zorder = series[:series_plotindex] + 0.5,
|
||||
marker = prev_marker,
|
||||
s = cur_scale_list,
|
||||
edgecolors = py_markerstrokecolor(series),
|
||||
edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)),
|
||||
linewidths = py_thickness_scale(plt, series[:markerstrokewidth]),
|
||||
facecolors = cur_color_list,
|
||||
extrakw...
|
||||
@@ -618,7 +623,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
zorder = series[:series_plotindex] + 0.5,
|
||||
marker = py_marker(series[:markershape]),
|
||||
s = py_thickness_scale(plt, series[:markersize] .^ 2),
|
||||
edgecolors = py_markerstrokecolor(series),
|
||||
edgecolors = py_color(get_markerstrokecolor(series), get_markerstrokealpha(series)),
|
||||
linewidths = py_thickness_scale(plt, series[:markerstrokewidth]),
|
||||
extrakw...
|
||||
)
|
||||
@@ -632,7 +637,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
zorder = series[:series_plotindex],
|
||||
gridsize = series[:bins],
|
||||
linewidths = py_thickness_scale(plt, series[:linewidth]),
|
||||
edgecolors = py_linecolor(series),
|
||||
edgecolors = py_color(get_linecolor(series)),
|
||||
cmap = py_fillcolormap(series), # applies to the pcolorfast object
|
||||
extrakw...
|
||||
)
|
||||
@@ -705,7 +710,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
rstride = series[:stride][1],
|
||||
cstride = series[:stride][2],
|
||||
linewidth = py_thickness_scale(plt, series[:linewidth]),
|
||||
edgecolor = py_linecolor(series),
|
||||
edgecolor = py_color(get_linecolor(series)),
|
||||
extrakw...
|
||||
)
|
||||
push!(handles, handle)
|
||||
@@ -731,7 +736,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
zorder = series[:series_plotindex],
|
||||
cmap = py_fillcolormap(series),
|
||||
linewidth = py_thickness_scale(plt, series[:linewidth]),
|
||||
edgecolor = py_linecolor(series),
|
||||
edgecolor = py_color(get_linecolor(series)),
|
||||
extrakw...
|
||||
)
|
||||
push!(handles, handle)
|
||||
@@ -753,7 +758,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
end
|
||||
handle = ax."imshow"(z;
|
||||
zorder = series[:series_plotindex],
|
||||
cmap = py_colormap(cgrad([:black, :white])),
|
||||
cmap = py_colormap(cgrad(plot_color([:black, :white]))),
|
||||
vmin = 0.0,
|
||||
vmax = 1.0,
|
||||
extent = (xmin-0.5, xmax+0.5, ymax+0.5, ymin-0.5)
|
||||
@@ -989,7 +994,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
|
||||
|
||||
# title
|
||||
if sp[:title] != ""
|
||||
loc = lowercase(string(sp[:title_location]))
|
||||
loc = lowercase(string(sp[:titlelocation]))
|
||||
func = if loc == "left"
|
||||
:_left_title
|
||||
elseif loc == "right"
|
||||
@@ -1050,7 +1055,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
|
||||
end
|
||||
|
||||
# framestyle
|
||||
if !ispolar(sp) && !is3d(sp)
|
||||
if !ispolar(sp) && !RecipesPipeline.is3d(sp)
|
||||
ax.spines["left"]."set_linewidth"(py_thickness_scale(plt, 1))
|
||||
ax.spines["bottom"]."set_linewidth"(py_thickness_scale(plt, 1))
|
||||
if sp[:framestyle] == :semi
|
||||
@@ -1103,13 +1108,23 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
|
||||
ticks[2][ticks[1] .== 0] .= ""
|
||||
end
|
||||
axis[:ticks] != :native ? py_set_ticks(ax, ticks, letter) : nothing
|
||||
pyaxis."set_tick_params"(direction = axis[:tick_direction] == :out ? "out" : "in")
|
||||
|
||||
intensity = 0.5 # This value corresponds to scaling of other grid elements
|
||||
pyaxis."set_tick_params"(direction = axis[:tick_direction] == :out ? "out" : "in", width=py_thickness_scale(plt, intensity))
|
||||
|
||||
getproperty(ax, Symbol("set_", letter, "label"))(axis[:guide])
|
||||
if get(axis.plotattributes, :flip, false)
|
||||
getproperty(ax, Symbol("invert_", letter, "axis"))()
|
||||
end
|
||||
pyaxis."label"."set_fontsize"(py_thickness_scale(plt, axis[:guidefontsize]))
|
||||
pyaxis."label"."set_family"(axis[:guidefontfamily])
|
||||
|
||||
if (letter == :y && !RecipesPipeline.is3d(sp))
|
||||
pyaxis."label"."set_rotation"(axis[:guidefontrotation] + 90)
|
||||
else
|
||||
pyaxis."label"."set_rotation"(axis[:guidefontrotation])
|
||||
end
|
||||
|
||||
for lab in getproperty(ax, Symbol("get_", letter, "ticklabels"))()
|
||||
lab."set_fontsize"(py_thickness_scale(plt, axis[:tickfontsize]))
|
||||
lab."set_family"(axis[:tickfontfamily])
|
||||
@@ -1126,6 +1141,35 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
|
||||
else
|
||||
pyaxis."grid"(false)
|
||||
end
|
||||
#
|
||||
|
||||
if axis[:minorticks] > 1
|
||||
pyaxis."set_minor_locator"(PyPlot.matplotlib.ticker.AutoMinorLocator(axis[:minorticks]))
|
||||
pyaxis."set_tick_params"(
|
||||
which = "minor",
|
||||
direction = axis[:tick_direction] == :out ? "out" : "in",
|
||||
width=py_thickness_scale(plt, intensity))
|
||||
end
|
||||
|
||||
if axis[:minorgrid]
|
||||
if !(axis[:minorticks] > 1) # Check if ticks were already configured
|
||||
ax."minorticks_on"()
|
||||
end
|
||||
pyaxis."set_tick_params"(
|
||||
which = "minor",
|
||||
direction = axis[:tick_direction] == :out ? "out" : "in",
|
||||
width=py_thickness_scale(plt, intensity))
|
||||
|
||||
pyaxis."grid"(true,
|
||||
which = "minor",
|
||||
color = fgcolor,
|
||||
linestyle = py_linestyle(:line, axis[:minorgridstyle]),
|
||||
linewidth = py_thickness_scale(plt, axis[:minorgridlinewidth]),
|
||||
alpha = axis[:minorgridalpha])
|
||||
end
|
||||
|
||||
|
||||
|
||||
py_set_axis_colors(sp, ax, axis)
|
||||
end
|
||||
|
||||
@@ -1161,7 +1205,7 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
|
||||
end
|
||||
|
||||
#camera/view angle
|
||||
if is3d(sp)
|
||||
if RecipesPipeline.is3d(sp)
|
||||
#convert azimuthal to match GR behaviour
|
||||
#view_init(elevation, azimuthal) so reverse :camera args
|
||||
ax."view_init"((sp[:camera].-(90,0))[end:-1:1]...)
|
||||
@@ -1298,25 +1342,29 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
|
||||
if should_add_to_legend(series)
|
||||
clims = get_clims(sp, series)
|
||||
# add a line/marker and a label
|
||||
push!(handles, if series[:seriestype] == :shape || series[:fillrange] !== nothing
|
||||
pypatches."Patch"(
|
||||
edgecolor = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)),
|
||||
facecolor = py_color(single_color(get_fillcolor(series, clims)), get_fillalpha(series)),
|
||||
linewidth = py_thickness_scale(plt, clamp(get_linewidth(series), 0, 5)),
|
||||
linestyle = py_linestyle(series[:seriestype], get_linestyle(series))
|
||||
)
|
||||
elseif series[:seriestype] in (:path, :straightline, :scatter)
|
||||
PyPlot.plt."Line2D"((0,1),(0,0),
|
||||
color = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)),
|
||||
linewidth = py_thickness_scale(plt, clamp(get_linewidth(series), 0, 5)),
|
||||
linestyle = py_linestyle(:path, get_linestyle(series)),
|
||||
marker = py_marker(first(series[:markershape])),
|
||||
markeredgecolor = py_color(single_color(get_markerstrokecolor(series)), get_markerstrokealpha(series)),
|
||||
markerfacecolor = py_color(single_color(get_markercolor(series, clims)), get_markeralpha(series))
|
||||
)
|
||||
else
|
||||
series[:serieshandle][1]
|
||||
end)
|
||||
push!(handles,
|
||||
if series[:seriestype] == :shape || series[:fillrange] !== nothing
|
||||
pypatches."Patch"(
|
||||
edgecolor = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)),
|
||||
facecolor = py_color(single_color(get_fillcolor(series, clims)), get_fillalpha(series)),
|
||||
linewidth = py_thickness_scale(plt, clamp(get_linewidth(series), 0, 5)),
|
||||
linestyle = py_linestyle(series[:seriestype], get_linestyle(series))
|
||||
)
|
||||
elseif series[:seriestype] in (:path, :straightline, :scatter)
|
||||
PyPlot.plt."Line2D"((0,1),(0,0),
|
||||
color = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)),
|
||||
linewidth = py_thickness_scale(plt, clamp(get_linewidth(series), 0, 5)),
|
||||
linestyle = py_linestyle(:path, get_linestyle(series)),
|
||||
marker = py_marker(_cycle(series[:markershape], 1)),
|
||||
# markersize = py_thickness_scale(plt, series[:markersize]), # In case we decide that markersize needs to be scaled in the legend too
|
||||
markeredgecolor = py_color(single_color(get_markerstrokecolor(series)), get_markerstrokealpha(series)),
|
||||
markerfacecolor = py_color(single_color(get_markercolor(series, clims)), get_markeralpha(series)),
|
||||
markeredgewidth = py_thickness_scale(plt, series[:markerstrokewidth])
|
||||
)
|
||||
else
|
||||
series[:serieshandle][1]
|
||||
end
|
||||
)
|
||||
push!(labels, series[:label])
|
||||
end
|
||||
end
|
||||
@@ -1332,6 +1380,7 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
|
||||
facecolor = py_color(sp[:background_color_legend]),
|
||||
edgecolor = py_color(sp[:foreground_color_legend]),
|
||||
framealpha = alpha(plot_color(sp[:background_color_legend])),
|
||||
fancybox = false # makes the legend box square
|
||||
)
|
||||
frame = leg."get_frame"()
|
||||
frame."set_linewidth"(py_thickness_scale(plt, 1))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
# don't warn on unsupported... there's just too many warnings!!
|
||||
warnOnUnsupported_args(::UnicodePlotsBackend, plotattributes::KW) = nothing
|
||||
warn_on_unsupported_args(::UnicodePlotsBackend, plotattributes::KW) = nothing
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
@@ -126,7 +126,7 @@ function addUnicodeSeries!(o, plotattributes, addlegend::Bool, xlim, ylim)
|
||||
color = plotattributes[:linecolor] in UnicodePlots.color_cycle ? plotattributes[:linecolor] : :auto
|
||||
|
||||
# add the series
|
||||
x, y = Plots.unzip(collect(Base.Iterators.filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y))))
|
||||
x, y = RecipesPipeline.unzip(collect(Base.Iterators.filter(xy->isfinite(xy[1])&&isfinite(xy[2]), zip(x,y))))
|
||||
func(o, x, y; color = color, name = label)
|
||||
end
|
||||
|
||||
|
||||
+4
-50
@@ -29,7 +29,7 @@ end
|
||||
|
||||
Construct a polygon to be plotted
|
||||
"""
|
||||
Shape(verts::AVec) = Shape(unzip(verts)...)
|
||||
Shape(verts::AVec) = Shape(RecipesPipeline.unzip(verts)...)
|
||||
Shape(s::Shape) = deepcopy(s)
|
||||
|
||||
get_xs(shape::Shape) = shape.x
|
||||
@@ -595,8 +595,8 @@ function process_annotation(sp::Subplot, xs, ys, labs, font = font())
|
||||
ylength = length(methods(length, (typeof(ys),))) == 0 ? 1 : length(ys)
|
||||
for i in 1:max(xlength, ylength, length(labs))
|
||||
x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i)
|
||||
x = typeof(x) <: TimeType ? Dates.value(x) : x
|
||||
y = typeof(y) <: TimeType ? Dates.value(y) : y
|
||||
x = typeof(x) <: TimeType ? Dates.value(x) : x
|
||||
y = typeof(y) <: TimeType ? Dates.value(y) : y
|
||||
if lab == :auto
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyz"
|
||||
push!(anns, (x, y, text(string("(", alphabet[sp[:subplot_index]], ")"), font)))
|
||||
@@ -652,23 +652,6 @@ end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
abstract type AbstractSurface end
|
||||
|
||||
"represents a contour or surface mesh"
|
||||
struct Surface{M<:AMat} <: AbstractSurface
|
||||
surf::M
|
||||
end
|
||||
|
||||
Surface(f::Function, x, y) = Surface(Float64[f(xi,yi) for yi in y, xi in x])
|
||||
|
||||
Base.Array(surf::Surface) = surf.surf
|
||||
|
||||
for f in (:length, :size, :axes)
|
||||
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
|
||||
end
|
||||
Base.copy(surf::Surface) = Surface(copy(surf.surf))
|
||||
Base.eltype(surf::Surface{T}) where {T} = eltype(T)
|
||||
|
||||
function expand_extrema!(a::Axis, surf::Surface)
|
||||
ex = a[:extrema]
|
||||
for vi in surf.surf
|
||||
@@ -688,28 +671,7 @@ end
|
||||
# # I don't want to clash with ValidatedNumerics, but this would be nice:
|
||||
# ..(a::T, b::T) = (a,b)
|
||||
|
||||
struct Volume{T}
|
||||
v::Array{T,3}
|
||||
x_extents::Tuple{T,T}
|
||||
y_extents::Tuple{T,T}
|
||||
z_extents::Tuple{T,T}
|
||||
end
|
||||
|
||||
default_extents(::Type{T}) where {T} = (zero(T), one(T))
|
||||
|
||||
function Volume(v::Array{T,3},
|
||||
x_extents = default_extents(T),
|
||||
y_extents = default_extents(T),
|
||||
z_extents = default_extents(T)) where T
|
||||
Volume(v, x_extents, y_extents, z_extents)
|
||||
end
|
||||
|
||||
Base.Array(vol::Volume) = vol.v
|
||||
for f in (:length, :size)
|
||||
@eval Base.$f(vol::Volume, args...) = $f(vol.v, args...)
|
||||
end
|
||||
Base.copy(vol::Volume{T}) where {T} = Volume{T}(copy(vol.v), vol.x_extents, vol.y_extents, vol.z_extents)
|
||||
Base.eltype(vol::Volume{T}) where {T} = T
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
@@ -773,14 +735,6 @@ function add_arrows(func::Function, x::AVec, y::AVec)
|
||||
end
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"Represents data values with formatting that should apply to the tick labels."
|
||||
struct Formatted{T}
|
||||
data::T
|
||||
formatter::Function
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
"create a BezierCurve for plotting"
|
||||
mutable struct BezierCurve{T <: GeometryTypes.Point}
|
||||
@@ -798,7 +752,7 @@ end
|
||||
|
||||
@deprecate curve_points coords
|
||||
|
||||
coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, Base.range(range..., length=n))
|
||||
coords(curve::BezierCurve, n::Integer = 30; range = [0,1]) = map(curve, Base.range(first(range), stop=last(range), length=n))
|
||||
|
||||
# build a BezierCurve which leaves point p vertically upwards and arrives point q vertically upwards.
|
||||
# may create a loop if necessary. Assumes the view is [0,1]
|
||||
|
||||
+20
-13
@@ -74,7 +74,7 @@ const _examples = PlotExample[
|
||||
rand(11, 4),
|
||||
lab = "lines",
|
||||
w = 3,
|
||||
palette = :grays,
|
||||
palette = cgrad(:grays),
|
||||
fill = 0,
|
||||
α = 0.6,
|
||||
)
|
||||
@@ -322,7 +322,7 @@ const _examples = PlotExample[
|
||||
plot(
|
||||
Plots.fakedata(100, 10),
|
||||
layout = 4,
|
||||
palette = [:grays :blues :heat :lightrainbow],
|
||||
palette = cgrad.([:grays :blues :heat :lightrainbow]),
|
||||
bg_inside = [:orange :pink :darkblue :black],
|
||||
)
|
||||
end
|
||||
@@ -586,7 +586,7 @@ const _examples = PlotExample[
|
||||
rand(100, 6),
|
||||
layout = @layout([a b; c]),
|
||||
title = ["A" "B" "C"],
|
||||
title_location = :left,
|
||||
titlelocation = :left,
|
||||
left_margin = [20mm 0mm],
|
||||
bottom_margin = 10px,
|
||||
xrotation = 60,
|
||||
@@ -986,21 +986,28 @@ const _examples = PlotExample[
|
||||
]
|
||||
|
||||
# Some constants for PlotDocs and PlotReferenceImages
|
||||
_animation_examples = [2, 30]
|
||||
_animation_examples = [2, 31]
|
||||
_backend_skips = Dict(
|
||||
:gr => [25, 30],
|
||||
:pyplot => [25, 30],
|
||||
:pyplot => [2, 25, 30, 31],
|
||||
:plotlyjs => [2, 21, 24, 25, 30, 31],
|
||||
:plotly => [2, 21, 24, 25, 30, 31],
|
||||
:pgfplots => [2, 5, 6, 10, 16, 20, 22, 23, 25, 28, 30, 31, 34, 37, 38, 39],
|
||||
:pgfplotsx => [ 6, # images
|
||||
10, # histogram2d
|
||||
16, # pgfplots thinks the upper panel is too small
|
||||
22, # contourf
|
||||
23, # pie
|
||||
32, # spy
|
||||
38, # histogram2d
|
||||
] )
|
||||
:pgfplotsx => [
|
||||
2, # animation
|
||||
6, # images
|
||||
10, # histogram2d
|
||||
16, # pgfplots thinks the upper panel is too small
|
||||
22, # contourf
|
||||
23, # pie
|
||||
25, # @df
|
||||
30, # @df
|
||||
31, # animation
|
||||
32, # spy
|
||||
38, # histogram2d
|
||||
45, # wireframe
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-5
@@ -3,7 +3,7 @@ using REPL
|
||||
|
||||
function _plots_defaults()
|
||||
if isdefined(Main, :PLOTS_DEFAULTS)
|
||||
Dict{Symbol,Any}(Main.PLOTS_DEFAULTS)
|
||||
copy(Dict{Symbol,Any}(Main.PLOTS_DEFAULTS))
|
||||
else
|
||||
Dict{Symbol,Any}()
|
||||
end
|
||||
@@ -13,11 +13,9 @@ 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)
|
||||
theme(pop!(user_defaults, :theme))
|
||||
end
|
||||
default(; user_defaults...)
|
||||
|
||||
insert!(Base.Multimedia.displays, findlast(x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay())
|
||||
|
||||
|
||||
+90
-90
@@ -1,54 +1,51 @@
|
||||
|
||||
|
||||
defaultOutputFormat(plt::Plot) = "png"
|
||||
|
||||
function png(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "png")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("image/png"), plt)
|
||||
close(io)
|
||||
fn = addExtension(fn, "png")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("image/png"), plt)
|
||||
close(io)
|
||||
end
|
||||
png(fn::AbstractString) = png(current(), fn)
|
||||
|
||||
function svg(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "svg")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("image/svg+xml"), plt)
|
||||
close(io)
|
||||
fn = addExtension(fn, "svg")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("image/svg+xml"), plt)
|
||||
close(io)
|
||||
end
|
||||
svg(fn::AbstractString) = svg(current(), fn)
|
||||
|
||||
|
||||
function pdf(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "pdf")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("application/pdf"), plt)
|
||||
close(io)
|
||||
fn = addExtension(fn, "pdf")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("application/pdf"), plt)
|
||||
close(io)
|
||||
end
|
||||
pdf(fn::AbstractString) = pdf(current(), fn)
|
||||
|
||||
|
||||
function ps(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "ps")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("application/postscript"), plt)
|
||||
close(io)
|
||||
fn = addExtension(fn, "ps")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("application/postscript"), plt)
|
||||
close(io)
|
||||
end
|
||||
ps(fn::AbstractString) = ps(current(), fn)
|
||||
|
||||
function eps(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "eps")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("image/eps"), plt)
|
||||
close(io)
|
||||
fn = addExtension(fn, "eps")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("image/eps"), plt)
|
||||
close(io)
|
||||
end
|
||||
eps(fn::AbstractString) = eps(current(), fn)
|
||||
|
||||
function tex(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "tex")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("application/x-tex"), plt)
|
||||
close(io)
|
||||
fn = addExtension(fn, "tex")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("application/x-tex"), plt)
|
||||
close(io)
|
||||
end
|
||||
tex(fn::AbstractString) = tex(current(), fn)
|
||||
|
||||
@@ -69,48 +66,39 @@ end
|
||||
html(fn::AbstractString) = html(current(), fn)
|
||||
|
||||
function txt(plt::Plot, fn::AbstractString)
|
||||
fn = addExtension(fn, "txt")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("text/plain"), plt)
|
||||
close(io)
|
||||
fn = addExtension(fn, "txt")
|
||||
io = open(fn, "w")
|
||||
show(io, MIME("text/plain"), plt)
|
||||
close(io)
|
||||
end
|
||||
txt(fn::AbstractString) = txt(current(), fn)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
const _savemap = Dict(
|
||||
"png" => png,
|
||||
"svg" => svg,
|
||||
"pdf" => pdf,
|
||||
"ps" => ps,
|
||||
"ps" => ps,
|
||||
"eps" => eps,
|
||||
"tex" => tex,
|
||||
"json" => json,
|
||||
"html" => html,
|
||||
"tikz" => tex,
|
||||
"txt" => txt,
|
||||
)
|
||||
)
|
||||
|
||||
function getExtension(fn::AbstractString)
|
||||
pieces = split(fn, ".")
|
||||
length(pieces) > 1 || error("Can't extract file extension: ", fn)
|
||||
ext = pieces[end]
|
||||
haskey(_savemap, ext) || error("Invalid file extension: ", fn)
|
||||
ext
|
||||
end
|
||||
const _extension_map = Dict("tikz" => "tex")
|
||||
|
||||
function addExtension(fn::AbstractString, ext::AbstractString)
|
||||
try
|
||||
oldext = getExtension(fn)
|
||||
if oldext == ext
|
||||
return fn
|
||||
oldfn, oldext = splitext(fn)
|
||||
oldext = chop(oldext, head = 1, tail = 0)
|
||||
if get(_extension_map, oldext, oldext) == ext
|
||||
return fn
|
||||
else
|
||||
return "$fn.$ext"
|
||||
return string(fn, ".", ext)
|
||||
end
|
||||
catch
|
||||
return "$fn.$ext"
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -121,27 +109,28 @@ 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
|
||||
ext = getExtension(fn)
|
||||
catch
|
||||
# if we couldn't extract the extension, add the default
|
||||
ext = defaultOutputFormat(plt)
|
||||
fn = addExtension(fn, ext)
|
||||
end
|
||||
fn = abspath(expanduser(fn))
|
||||
|
||||
# save it
|
||||
func = get(_savemap, ext) do
|
||||
error("Unsupported extension $ext with filename ", fn)
|
||||
end
|
||||
func(plt, fn)
|
||||
# get the extension
|
||||
fn, ext = splitext(fn)
|
||||
ext = chop(ext, head = 1, tail = 0)
|
||||
if isempty(ext)
|
||||
ext = defaultOutputFormat(plt)
|
||||
end
|
||||
|
||||
# save it
|
||||
if haskey(_savemap, ext)
|
||||
func = _savemap[ext]
|
||||
return func(plt, fn)
|
||||
else
|
||||
error("Invalid file extension: ", fn)
|
||||
end
|
||||
end
|
||||
savefig(fn::AbstractString) = savefig(current(), fn)
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
"""
|
||||
gui([plot])
|
||||
|
||||
@@ -164,17 +153,13 @@ end
|
||||
_do_plot_show(plt, showval::Bool) = showval && gui(plt)
|
||||
function _do_plot_show(plt, showval::Symbol)
|
||||
showval == :gui && gui(plt)
|
||||
showval in (:inline,:ijulia) && inline(plt)
|
||||
showval in (:inline, :ijulia) && inline(plt)
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
const _best_html_output_type = KW(
|
||||
:pyplot => :png,
|
||||
:unicodeplots => :txt,
|
||||
:plotlyjs => :html,
|
||||
:plotly => :html
|
||||
)
|
||||
const _best_html_output_type =
|
||||
KW(:pyplot => :png, :unicodeplots => :txt, :plotlyjs => :html, :plotly => :html)
|
||||
|
||||
# a backup for html... passes to svg or png depending on the html_output_format arg
|
||||
function _show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
@@ -184,7 +169,12 @@ function _show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
end
|
||||
if output_type == :png
|
||||
# @info("writing png to html output")
|
||||
print(io, "<img src=\"data:image/png;base64,", base64encode(show, MIME("image/png"), plt), "\" />")
|
||||
print(
|
||||
io,
|
||||
"<img src=\"data:image/png;base64,",
|
||||
base64encode(show, MIME("image/png"), plt),
|
||||
"\" />",
|
||||
)
|
||||
elseif output_type == :svg
|
||||
# @info("writing svg to html output")
|
||||
show(io, MIME("image/svg+xml"), plt)
|
||||
@@ -196,7 +186,7 @@ function _show(io::IO, ::MIME"text/html", plt::Plot)
|
||||
end
|
||||
|
||||
# delegate showable to _show instead
|
||||
function Base.showable(m::M, plt::P) where {M<:MIME, P<:Plot}
|
||||
function Base.showable(m::M, plt::P) where {M <: MIME, P <: Plot}
|
||||
return hasmethod(_show, Tuple{IO, M, P})
|
||||
end
|
||||
|
||||
@@ -205,21 +195,31 @@ function _display(plt::Plot)
|
||||
end
|
||||
|
||||
# for writing to io streams... first prepare, then callback
|
||||
for mime in ("text/plain", "text/html", "image/png", "image/eps", "image/svg+xml",
|
||||
"application/eps", "application/pdf", "application/postscript",
|
||||
"application/x-tex", "application/vnd.plotly.v1+json")
|
||||
for mime in (
|
||||
"text/plain",
|
||||
"text/html",
|
||||
"image/png",
|
||||
"image/eps",
|
||||
"image/svg+xml",
|
||||
"application/eps",
|
||||
"application/pdf",
|
||||
"application/postscript",
|
||||
"application/x-tex",
|
||||
"application/vnd.plotly.v1+json",
|
||||
)
|
||||
@eval function Base.show(io::IO, m::MIME{Symbol($mime)}, plt::Plot)
|
||||
if haskey(io, :juno_plotsize)
|
||||
showjuno(io, m, plt)
|
||||
showjuno(io, m, plt)
|
||||
else
|
||||
prepare_output(plt)
|
||||
_show(io, m, plt)
|
||||
prepare_output(plt)
|
||||
_show(io, m, plt)
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
end
|
||||
|
||||
Base.show(io::IO, m::MIME"application/prs.juno.plotpane+html", plt::Plot) = showjuno(io, MIME("text/html"), plt)
|
||||
Base.show(io::IO, m::MIME"application/prs.juno.plotpane+html", plt::Plot) =
|
||||
showjuno(io, MIME("text/html"), plt)
|
||||
|
||||
# default text/plain for all backends
|
||||
_show(io::IO, ::MIME{Symbol("text/plain")}, plt::Plot) = show(io, plt)
|
||||
@@ -258,25 +258,25 @@ function showjuno(io::IO, m, plt)
|
||||
|
||||
scale = minimum(jsize[i] / sz[i] for i in 1:2)
|
||||
plt[:size] = [s * scale for s in sz]
|
||||
plt[:dpi] = jratio*Plots.DPI
|
||||
plt[:dpi] = jratio * Plots.DPI
|
||||
plt[:thickness_scaling] *= scale
|
||||
|
||||
prepare_output(plt)
|
||||
try
|
||||
_showjuno(io, m, plt)
|
||||
_showjuno(io, m, plt)
|
||||
finally
|
||||
plt[:size] = sz
|
||||
plt[:dpi] = dpi
|
||||
plt[:thickness_scaling] = thickness_scaling
|
||||
plt[:size] = sz
|
||||
plt[:dpi] = dpi
|
||||
plt[:thickness_scaling] = thickness_scaling
|
||||
end
|
||||
end
|
||||
|
||||
function _showjuno(io::IO, m::MIME"image/svg+xml", plt)
|
||||
if Symbol(plt.attr[:html_output_format]) ≠ :svg
|
||||
throw(MethodError(show, (typeof(m), typeof(plt))))
|
||||
else
|
||||
_show(io, m, plt)
|
||||
end
|
||||
if Symbol(plt.attr[:html_output_format]) ≠ :svg
|
||||
throw(MethodError(show, (typeof(m), typeof(plt))))
|
||||
else
|
||||
_show(io, m, plt)
|
||||
end
|
||||
end
|
||||
|
||||
Base.showable(::MIME"application/prs.juno.plotpane+html", plt::Plot) = false
|
||||
|
||||
+129
-232
@@ -1,127 +1,74 @@
|
||||
# Error for aliases used in recipes
|
||||
function warn_on_recipe_aliases!(plotattributes, recipe_type, args...)
|
||||
# RecipesPipeline API
|
||||
|
||||
## Warnings
|
||||
|
||||
function RecipesPipeline.warn_on_recipe_aliases!(
|
||||
plt::Plot,
|
||||
plotattributes,
|
||||
recipe_type,
|
||||
args...,
|
||||
)
|
||||
for k in keys(plotattributes)
|
||||
if !is_default_attribute(k)
|
||||
dk = get(_keyAliases, k, k)
|
||||
if k !== dk
|
||||
@warn "Attribute alias `$k` detected in the $recipe_type recipe defined for the signature $(signature_string(Val{recipe_type}, args...)). To ensure expected behavior it is recommended to use the default attribute `$dk`."
|
||||
@warn "Attribute alias `$k` detected in the $recipe_type recipe defined for the signature $(_signature_string(Val{recipe_type}, args...)). To ensure expected behavior it is recommended to use the default attribute `$dk`."
|
||||
end
|
||||
plotattributes[dk] = pop_kw!(plotattributes, k)
|
||||
plotattributes[dk] = RecipesPipeline.pop_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
function warn_on_recipe_aliases!(v::AbstractVector, recipe_type, args...)
|
||||
foreach(x -> warn_on_recipe_aliases!(x, recipe_type, args...), v)
|
||||
function RecipesPipeline.warn_on_recipe_aliases!(
|
||||
plt::Plot,
|
||||
v::AbstractVector,
|
||||
recipe_type,
|
||||
args...,
|
||||
)
|
||||
foreach(x -> RecipesPipeline.warn_on_recipe_aliases!(plt, x, recipe_type, args...), v)
|
||||
end
|
||||
function warn_on_recipe_aliases!(rd::RecipeData, recipe_type, args...)
|
||||
warn_on_recipe_aliases!(rd.plotattributes, recipe_type, args...)
|
||||
function RecipesPipeline.warn_on_recipe_aliases!(
|
||||
plt::Plot,
|
||||
rd::RecipeData,
|
||||
recipe_type,
|
||||
args...,
|
||||
)
|
||||
RecipesPipeline.warn_on_recipe_aliases!(plt, rd.plotattributes, recipe_type, args...)
|
||||
end
|
||||
|
||||
function signature_string(::Type{Val{:user}}, args...)
|
||||
function _signature_string(::Type{Val{:user}}, args...)
|
||||
return string("(::", join(string.(typeof.(args)), ", ::"), ")")
|
||||
end
|
||||
signature_string(::Type{Val{:type}}, T) = "(::Type{$T}, ::$T)"
|
||||
signature_string(::Type{Val{:plot}}, st) = "(::Type{Val{:$st}}, ::AbstractPlot)"
|
||||
signature_string(::Type{Val{:series}}, st) = "(::Type{Val{:$st}}, x, y, z)"
|
||||
_signature_string(::Type{Val{:type}}, T) = "(::Type{$T}, ::$T)"
|
||||
_signature_string(::Type{Val{:plot}}, st) = "(::Type{Val{:$st}}, ::AbstractPlot)"
|
||||
_signature_string(::Type{Val{:series}}, st) = "(::Type{Val{:$st}}, x, y, z)"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# preprocessing
|
||||
|
||||
function series_idx(kw_list::AVec{KW}, kw::AKW)
|
||||
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
|
||||
## Grouping
|
||||
|
||||
RecipesPipeline.splittable_attribute(plt::Plot, key, val::SeriesAnnotations, len) =
|
||||
RecipesPipeline.splittable_attribute(plt, key, val.strs, len)
|
||||
|
||||
function RecipesPipeline.split_attribute(plt::Plot, key, val::SeriesAnnotations, indices)
|
||||
split_strs = _RecipesPipeline.split_attribute(key, val.strs, indices)
|
||||
return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor)
|
||||
end
|
||||
|
||||
function _expand_seriestype_array(plotattributes::AKW, args)
|
||||
sts = get(plotattributes, :seriestype, :path)
|
||||
if typeof(sts) <: AbstractArray
|
||||
reset_kw!(plotattributes, :seriestype)
|
||||
rd = Vector{RecipeData}(undef, size(sts, 1))
|
||||
for r in axes(sts, 1)
|
||||
dc = copy(plotattributes)
|
||||
dc[:seriestype] = sts[r:r,:]
|
||||
rd[r] = RecipeData(dc, args)
|
||||
end
|
||||
rd
|
||||
else
|
||||
RecipeData[RecipeData(copy(plotattributes), args)]
|
||||
end
|
||||
end
|
||||
|
||||
function _preprocess_args(plotattributes::AKW, args, still_to_process::Vector{RecipeData})
|
||||
# the grouping mechanism is a recipe on a GroupBy object
|
||||
# we simply add the GroupBy object to the front of the args list to allow
|
||||
# the recipe to be applied
|
||||
if haskey(plotattributes, :group)
|
||||
args = (extractGroupArgs(plotattributes[:group], args...), args...)
|
||||
end
|
||||
## Preprocessing attributes
|
||||
|
||||
# if we were passed a vector/matrix of seriestypes and there's more than one row,
|
||||
# we want to duplicate the inputs, once for each seriestype row.
|
||||
if !isempty(args)
|
||||
append!(still_to_process, _expand_seriestype_array(plotattributes, args))
|
||||
end
|
||||
RecipesPipeline.preprocess_attributes!(plt::Plot, plotattributes) =
|
||||
RecipesPipeline.preprocess_attributes!(plotattributes) # in src/args.jl
|
||||
|
||||
# remove subplot and axis args from plotattributes... they will be passed through in the kw_list
|
||||
if !isempty(args)
|
||||
for (k,v) in plotattributes
|
||||
if k in _all_subplot_args || k in _all_axis_args
|
||||
reset_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
RecipesPipeline.is_axis_attribute(plt::Plot, attr) = is_axis_attr_noletter(attr) # in src/args.jl
|
||||
|
||||
args
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# user recipes
|
||||
RecipesPipeline.is_subplot_attribute(plt::Plot, attr) = is_subplot_attr(attr) # in src/args.jl
|
||||
|
||||
|
||||
function _process_userrecipes(plt::Plot, plotattributes::AKW, args)
|
||||
still_to_process = RecipeData[]
|
||||
args = _preprocess_args(plotattributes, args, still_to_process)
|
||||
## User recipes
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
# we are keeping a stack of series that still need to be processed.
|
||||
# each pass through the loop, we pop one off and apply the recipe.
|
||||
# the recipe will return a list a Series objects... the ones that are
|
||||
# finished (no more args) get added to the kw_list, the ones that are not
|
||||
# are placed on top of the stack and are then processed further.
|
||||
kw_list = KW[]
|
||||
while !isempty(still_to_process)
|
||||
# grab the first in line to be processed and either add it to the kw_list or
|
||||
# pass it through apply_recipe to generate a list of RecipeData objects (data + attributes)
|
||||
# for further processing.
|
||||
next_series = popfirst!(still_to_process)
|
||||
# recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
|
||||
if !(typeof(next_series) <: RecipeData)
|
||||
error("Inputs couldn't be processed... expected RecipeData but got: $next_series")
|
||||
end
|
||||
if isempty(next_series.args)
|
||||
_process_userrecipe(plt, kw_list, next_series)
|
||||
else
|
||||
rd_list = RecipesBase.apply_recipe(
|
||||
next_series.plotattributes,
|
||||
next_series.args...
|
||||
)
|
||||
warn_on_recipe_aliases!(rd_list, :user, next_series.args...)
|
||||
prepend!(still_to_process,rd_list)
|
||||
end
|
||||
end
|
||||
|
||||
# don't allow something else to handle it
|
||||
plotattributes[:smooth] = false
|
||||
kw_list
|
||||
end
|
||||
|
||||
function _process_userrecipe(plt::Plot, kw_list::Vector{KW}, recipedata::RecipeData)
|
||||
# when the arg tuple is empty, that means there's nothing left to recursively
|
||||
# process... finish up and add to the kw_list
|
||||
kw = recipedata.plotattributes
|
||||
preprocessArgs!(kw)
|
||||
function RecipesPipeline.process_userrecipe!(plt::Plot, kw_list, kw)
|
||||
_preprocess_userrecipe(kw)
|
||||
warnOnUnsupported_scales(plt.backend, kw)
|
||||
|
||||
warn_on_unsupported_scales(plt.backend, kw)
|
||||
# add the plot index
|
||||
plt.n += 1
|
||||
kw[:series_plotindex] = plt.n
|
||||
@@ -135,18 +82,17 @@ end
|
||||
function _preprocess_userrecipe(kw::AKW)
|
||||
_add_markershape(kw)
|
||||
|
||||
# if there was a grouping, filter the data here
|
||||
_filter_input_data!(kw)
|
||||
|
||||
# map marker_z if it's a Function
|
||||
if isa(get(kw, :marker_z, nothing), Function)
|
||||
# TODO: should this take y and/or z as arguments?
|
||||
kw[:marker_z] = isa(kw[:z], Nothing) ? map(kw[:marker_z], kw[:x], kw[:y]) : map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
|
||||
kw[:marker_z] = isa(kw[:z], Nothing) ? map(kw[:marker_z], kw[:x], kw[:y]) :
|
||||
map(kw[:marker_z], kw[:x], kw[:y], kw[:z])
|
||||
end
|
||||
|
||||
# map line_z if it's a Function
|
||||
if isa(get(kw, :line_z, nothing), Function)
|
||||
kw[:line_z] = isa(kw[:z], Nothing) ? map(kw[:line_z], kw[:x], kw[:y]) : map(kw[:line_z], kw[:x], kw[:y], kw[:z])
|
||||
kw[:line_z] = isa(kw[:z], Nothing) ? map(kw[:line_z], kw[:x], kw[:y]) :
|
||||
map(kw[:line_z], kw[:x], kw[:y], kw[:z])
|
||||
end
|
||||
|
||||
# convert a ribbon into a fillrange
|
||||
@@ -178,59 +124,43 @@ function _add_smooth_kw(kw_list::Vector{KW}, kw::AKW)
|
||||
β, α = convert(Matrix{Float64}, [x ones(length(x))]) \ convert(Vector{Float64}, y)
|
||||
sx = [ignorenan_minimum(x), ignorenan_maximum(x)]
|
||||
sy = β .* sx .+ α
|
||||
push!(kw_list, merge(copy(kw), KW(
|
||||
:seriestype => :path,
|
||||
:x => sx,
|
||||
:y => sy,
|
||||
:fillrange => nothing,
|
||||
:label => "",
|
||||
:primary => false,
|
||||
)))
|
||||
push!(
|
||||
kw_list,
|
||||
merge(
|
||||
copy(kw),
|
||||
KW(
|
||||
:seriestype => :path,
|
||||
:x => sx,
|
||||
:y => sy,
|
||||
:fillrange => nothing,
|
||||
:label => "",
|
||||
:primary => false,
|
||||
),
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# plot recipes
|
||||
|
||||
# Grab the first in line to be processed and pass it through apply_recipe
|
||||
# to generate a list of RecipeData objects (data + attributes).
|
||||
# If we applied a "plot recipe" without error, then add the returned datalist's KWs,
|
||||
# otherwise we just add the original KW.
|
||||
function _process_plotrecipe(plt::Plot, kw::AKW, kw_list::Vector{KW}, still_to_process::Vector{KW})
|
||||
if !isa(get(kw, :seriestype, nothing), Symbol)
|
||||
# seriestype was never set, or it's not a Symbol, so it can't be a plot recipe
|
||||
push!(kw_list, kw)
|
||||
return
|
||||
end
|
||||
try
|
||||
st = kw[:seriestype]
|
||||
st = kw[:seriestype] = get(_typeAliases, st, st)
|
||||
datalist = RecipesBase.apply_recipe(kw, Val{st}, plt)
|
||||
warn_on_recipe_aliases!(datalist, :plot, st)
|
||||
for data in datalist
|
||||
preprocessArgs!(data.plotattributes)
|
||||
if data.plotattributes[:seriestype] == st
|
||||
error("Plot recipe $st returned the same seriestype: $(data.plotattributes)")
|
||||
end
|
||||
push!(still_to_process, data.plotattributes)
|
||||
end
|
||||
catch err
|
||||
if isa(err, MethodError)
|
||||
push!(kw_list, kw)
|
||||
else
|
||||
rethrow()
|
||||
end
|
||||
end
|
||||
return
|
||||
RecipesPipeline.get_axis_limits(plt::Plot, f, letter) = axis_limits(plt[1], letter)
|
||||
|
||||
|
||||
## Plot recipes
|
||||
|
||||
RecipesPipeline.type_alias(plt::Plot) = get(_typeAliases, st, st)
|
||||
|
||||
|
||||
## Plot setup
|
||||
|
||||
function RecipesPipeline.plot_setup!(plt::Plot, plotattributes, kw_list)
|
||||
_plot_setup(plt, plotattributes, kw_list)
|
||||
_subplot_setup(plt, plotattributes, kw_list)
|
||||
end
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# setup plot and subplot
|
||||
|
||||
# TODO: Should some of this logic be moved to RecipesPipeline?
|
||||
function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
# merge in anything meant for the Plot
|
||||
for kw in kw_list, (k,v) in kw
|
||||
for kw in kw_list, (k, v) in kw
|
||||
haskey(_plot_defaults, k) && (plotattributes[k] = pop!(kw, k))
|
||||
end
|
||||
|
||||
@@ -241,7 +171,7 @@ function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
|
||||
# create the layout and subplots from the inputs
|
||||
plt.layout, plt.subplots, plt.spmap = build_layout(plt.attr)
|
||||
for (idx,sp) in enumerate(plt.subplots)
|
||||
for (idx, sp) in enumerate(plt.subplots)
|
||||
sp.plt = plt
|
||||
sp.attr[:subplot_index] = idx
|
||||
end
|
||||
@@ -266,7 +196,7 @@ function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
else
|
||||
parent = plt.layout
|
||||
end
|
||||
sp = Subplot(backend(), parent=parent)
|
||||
sp = Subplot(backend(), parent = parent)
|
||||
sp.plt = plt
|
||||
push!(plt.subplots, sp)
|
||||
push!(plt.inset_subplots, sp)
|
||||
@@ -282,28 +212,34 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
# Subplot/Axis attributes set by a user/series recipe apply only to the
|
||||
# Subplot object which they belong to.
|
||||
# TODO: allow matrices to still apply to all subplots
|
||||
sp_attrs = Dict{Subplot,Any}()
|
||||
sp_attrs = Dict{Subplot, Any}()
|
||||
for kw in kw_list
|
||||
# get the Subplot object to which the series belongs.
|
||||
sps = get(kw, :subplot, :auto)
|
||||
sp = get_subplot(plt, _cycle(sps == :auto ? plt.subplots : plt.subplots[sps], series_idx(kw_list,kw)))
|
||||
sp = get_subplot(
|
||||
plt,
|
||||
_cycle(
|
||||
sps == :auto ? plt.subplots : plt.subplots[sps],
|
||||
series_idx(kw_list, kw),
|
||||
),
|
||||
)
|
||||
kw[:subplot] = sp
|
||||
|
||||
# extract subplot/axis attributes from kw and add to sp_attr
|
||||
attr = KW()
|
||||
for (k,v) in collect(kw)
|
||||
for (k, v) in collect(kw)
|
||||
if is_subplot_attr(k) || is_axis_attr(k)
|
||||
attr[k] = pop!(kw, k)
|
||||
end
|
||||
if is_axis_attr_noletter(k)
|
||||
v = pop!(kw, k)
|
||||
for letter in (:x,:y,:z)
|
||||
attr[Symbol(letter,k)] = v
|
||||
for letter in (:x, :y, :z)
|
||||
attr[Symbol(letter, k)] = v
|
||||
end
|
||||
end
|
||||
for k in (:scale,), letter in (:x,:y,:z)
|
||||
for k in (:scale,), letter in (:x, :y, :z)
|
||||
# Series recipes may need access to this information
|
||||
lk = Symbol(letter,k)
|
||||
lk = Symbol(letter, k)
|
||||
if haskey(attr, lk)
|
||||
kw[lk] = attr[lk]
|
||||
end
|
||||
@@ -313,7 +249,7 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
end
|
||||
|
||||
# override subplot/axis args. `sp_attrs` take precendence
|
||||
for (idx,sp) in enumerate(plt.subplots)
|
||||
for (idx, sp) in enumerate(plt.subplots)
|
||||
attr = if !haskey(plotattributes, :subplot) || plotattributes[:subplot] == idx
|
||||
merge(plotattributes, get(sp_attrs, sp, KW()))
|
||||
else
|
||||
@@ -326,9 +262,34 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW})
|
||||
link_axes!(plt.layout, plt[:link])
|
||||
end
|
||||
|
||||
function series_idx(kw_list::AVec{KW}, kw::AKW)
|
||||
Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1
|
||||
end
|
||||
|
||||
|
||||
## Series recipes
|
||||
|
||||
function RecipesPipeline.slice_series_attributes!(plt::Plot, kw_list, kw)
|
||||
sp::Subplot = kw[:subplot]
|
||||
# in series attributes given as vector with one element per series,
|
||||
# select the value for current series
|
||||
_slice_series_args!(kw, plt, sp, series_idx(kw_list, kw))
|
||||
end
|
||||
|
||||
RecipesPipeline.series_defaults(plt::Plot) = _series_defaults # in args.jl
|
||||
|
||||
RecipesPipeline.is_seriestype_supported(plt::Plot, st) = is_seriestype_supported(st)
|
||||
|
||||
function RecipesPipeline.add_series!(plt::Plot, plotattributes)
|
||||
sp = _prepare_subplot(plt, plotattributes)
|
||||
_expand_subplot_extrema(sp, plotattributes, plotattributes[:seriestype])
|
||||
_update_series_attributes!(plotattributes, plt, sp)
|
||||
_add_the_series(plt, sp, plotattributes)
|
||||
end
|
||||
|
||||
# getting ready to add the series... last update to subplot from anything
|
||||
# that might have been added during series recipes
|
||||
function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where T
|
||||
function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where {T}
|
||||
st::Symbol = plotattributes[:seriestype]
|
||||
sp::Subplot{T} = plotattributes[:subplot]
|
||||
sp_idx = get_subplot_index(plt, sp)
|
||||
@@ -337,7 +298,7 @@ function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where T
|
||||
st = _override_seriestype_check(plotattributes, st)
|
||||
|
||||
# change to a 3d projection for this subplot?
|
||||
if is3d(st)
|
||||
if RecipesPipeline.needs_3d_axes(st)
|
||||
sp.attr[:projection] = "3d"
|
||||
end
|
||||
|
||||
@@ -349,14 +310,12 @@ function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where T
|
||||
sp
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# series types
|
||||
|
||||
function _override_seriestype_check(plotattributes::AKW, st::Symbol)
|
||||
# do we want to override the series type?
|
||||
if !is3d(st) && !(st in (:contour,:contour3d))
|
||||
if !RecipesPipeline.is3d(st) && !(st in (:contour, :contour3d))
|
||||
z = plotattributes[:z]
|
||||
if !isa(z, Nothing) && (size(plotattributes[:x]) == size(plotattributes[:y]) == size(z))
|
||||
if !isa(z, Nothing) &&
|
||||
(size(plotattributes[:x]) == size(plotattributes[:y]) == size(z))
|
||||
st = (st == :scatter ? :scatter3d : :path3d)
|
||||
plotattributes[:seriestype] = st
|
||||
end
|
||||
@@ -364,27 +323,11 @@ function _override_seriestype_check(plotattributes::AKW, st::Symbol)
|
||||
st
|
||||
end
|
||||
|
||||
function _prepare_annotations(sp::Subplot, plotattributes::AKW)
|
||||
# strip out series annotations (those which are based on series x/y coords)
|
||||
# and add them to the subplot attr
|
||||
sp_anns = annotations(sp[:annotations])
|
||||
# series_anns = annotations(pop!(plotattributes, :series_annotations, []))
|
||||
# if isa(series_anns, SeriesAnnotations)
|
||||
# series_anns.x = plotattributes[:x]
|
||||
# series_anns.y = plotattributes[:y]
|
||||
# elseif length(series_anns) > 0
|
||||
# x, y = plotattributes[:x], plotattributes[:y]
|
||||
# nx, ny, na = map(length, (x,y,series_anns))
|
||||
# n = max(nx, ny, na)
|
||||
# series_anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(series_anns[mod1(i,na)])) for i=1:n]
|
||||
# end
|
||||
# sp.attr[:annotations] = vcat(sp_anns, series_anns)
|
||||
end
|
||||
|
||||
function _expand_subplot_extrema(sp::Subplot, plotattributes::AKW, st::Symbol)
|
||||
# adjust extrema and discrete info
|
||||
if st == :image
|
||||
xmin, xmax = ignorenan_extrema(plotattributes[:x]); ymin, ymax = ignorenan_extrema(plotattributes[:y])
|
||||
xmin, xmax = ignorenan_extrema(plotattributes[:x])
|
||||
ymin, ymax = ignorenan_extrema(plotattributes[:y])
|
||||
expand_extrema!(sp[:xaxis], (xmin, xmax))
|
||||
expand_extrema!(sp[:yaxis], (ymin, ymax))
|
||||
elseif !(st in (:pie, :histogram, :bins2d, :histogram2d))
|
||||
@@ -398,56 +341,10 @@ function _expand_subplot_extrema(sp::Subplot, plotattributes::AKW, st::Symbol)
|
||||
end
|
||||
|
||||
function _add_the_series(plt, sp, plotattributes)
|
||||
warnOnUnsupported_args(plt.backend, plotattributes)
|
||||
warnOnUnsupported(plt.backend, plotattributes)
|
||||
warn_on_unsupported_args(plt.backend, plotattributes)
|
||||
warn_on_unsupported(plt.backend, plotattributes)
|
||||
series = Series(plotattributes)
|
||||
push!(plt.series_list, series)
|
||||
push!(sp.series_list, series)
|
||||
_series_added(plt, series)
|
||||
end
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
# this method recursively applies series recipes when the seriestype is not supported
|
||||
# natively by the backend
|
||||
function _process_seriesrecipe(plt::Plot, plotattributes::AKW)
|
||||
#println("process $(typeof(plotattributes))")
|
||||
# replace seriestype aliases
|
||||
st = Symbol(plotattributes[:seriestype])
|
||||
st = plotattributes[:seriestype] = get(_typeAliases, st, st)
|
||||
|
||||
# shapes shouldn't have fillrange set
|
||||
if plotattributes[:seriestype] == :shape
|
||||
plotattributes[:fillrange] = nothing
|
||||
end
|
||||
|
||||
# if it's natively supported, finalize processing and pass along to the backend, otherwise recurse
|
||||
if is_seriestype_supported(st)
|
||||
sp = _prepare_subplot(plt, plotattributes)
|
||||
_prepare_annotations(sp, plotattributes)
|
||||
_expand_subplot_extrema(sp, plotattributes, st)
|
||||
_update_series_attributes!(plotattributes, plt, sp)
|
||||
_add_the_series(plt, sp, plotattributes)
|
||||
|
||||
else
|
||||
# get a sub list of series for this seriestype
|
||||
x, y, z = plotattributes[:x], plotattributes[:y], plotattributes[:z]
|
||||
datalist = RecipesBase.apply_recipe(plotattributes, Val{st}, x, y, z)
|
||||
warn_on_recipe_aliases!(datalist, :series, st)
|
||||
|
||||
# assuming there was no error, recursively apply the series recipes
|
||||
for data in datalist
|
||||
if isa(data, RecipeData)
|
||||
preprocessArgs!(data.plotattributes)
|
||||
if data.plotattributes[:seriestype] == st
|
||||
error("The seriestype didn't change in series recipe $st. This will cause a StackOverflow.")
|
||||
end
|
||||
_process_seriesrecipe(plt, data.plotattributes)
|
||||
else
|
||||
@warn("Unhandled recipe: $(data)")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
nothing
|
||||
end
|
||||
|
||||
+6
-84
@@ -49,7 +49,7 @@ as a String to look up its docstring; e.g. `plotattr("seriestype")`.
|
||||
function plot(args...; kw...)
|
||||
# this creates a new plot with args/kw and sets it to be the current plot
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
RecipesPipeline.preprocess_attributes!(plotattributes)
|
||||
|
||||
# create an empty Plot then process
|
||||
plt = Plot()
|
||||
@@ -61,7 +61,7 @@ end
|
||||
# note: we split into plt1 and plts_tail so we can dispatch correctly
|
||||
function plot(plt1::Plot, plts_tail::Plot...; kw...)
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
RecipesPipeline.preprocess_attributes!(plotattributes)
|
||||
|
||||
# build our plot vector from the args
|
||||
n = length(plts_tail) + 1
|
||||
@@ -153,7 +153,7 @@ end
|
||||
# this adds to a specific plot... most plot commands will flow through here
|
||||
function plot!(plt::Plot, args...; kw...)
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
RecipesPipeline.preprocess_attributes!(plotattributes)
|
||||
# merge!(plt.user_attr, plotattributes)
|
||||
_plot!(plt, plotattributes, args)
|
||||
end
|
||||
@@ -163,87 +163,11 @@ end
|
||||
# this is the core plotting function. recursively apply recipes to build
|
||||
# a list of series KW dicts.
|
||||
# note: at entry, we only have those preprocessed args which were passed in... no default values yet
|
||||
function _plot!(plt::Plot, plotattributes::AKW, args::Tuple)
|
||||
plotattributes[:plot_object] = plt
|
||||
|
||||
if !isempty(args) && !isdefined(Main, :StatsPlots) &&
|
||||
first(split(string(typeof(args[1])), ".")) == "DataFrames"
|
||||
@warn("You're trying to plot a DataFrame, but this functionality is provided by StatsPlots")
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
# "USER RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
kw_list = _process_userrecipes(plt, plotattributes, args)
|
||||
|
||||
# @info(1)
|
||||
# map(DD, kw_list)
|
||||
|
||||
|
||||
# --------------------------------
|
||||
# "PLOT RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
# "plot recipe", which acts like a series type, and is processed before
|
||||
# the plot layout is created, which allows for setting layouts and other plot-wide attributes.
|
||||
# we get inputs which have been fully processed by "user recipes" and "type recipes",
|
||||
# so we can expect standard vectors, surfaces, etc. No defaults have been set yet.
|
||||
still_to_process = kw_list
|
||||
kw_list = KW[]
|
||||
while !isempty(still_to_process)
|
||||
next_kw = popfirst!(still_to_process)
|
||||
_process_plotrecipe(plt, next_kw, kw_list, still_to_process)
|
||||
end
|
||||
|
||||
# @info(2)
|
||||
# map(DD, kw_list)
|
||||
|
||||
# --------------------------------
|
||||
# Plot/Subplot/Layout setup
|
||||
# --------------------------------
|
||||
_plot_setup(plt, plotattributes, kw_list)
|
||||
_subplot_setup(plt, plotattributes, kw_list)
|
||||
|
||||
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
|
||||
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
|
||||
|
||||
# --------------------------------
|
||||
# "SERIES RECIPES"
|
||||
# --------------------------------
|
||||
|
||||
# @info(3)
|
||||
# map(DD, kw_list)
|
||||
|
||||
for kw in kw_list
|
||||
sp::Subplot = kw[:subplot]
|
||||
|
||||
# in series attributes given as vector with one element per series,
|
||||
# select the value for current series
|
||||
_slice_series_args!(kw, plt, sp, series_idx(kw_list,kw))
|
||||
|
||||
|
||||
series_attr = Attr(kw, _series_defaults)
|
||||
# now we have a fully specified series, with colors chosen. we must recursively handle
|
||||
# series recipes, which dispatch on seriestype. If a backend does not natively support a seriestype,
|
||||
# we check for a recipe that will convert that series type into one made up of lower-level components.
|
||||
# For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot,
|
||||
# and a step plot is really just a path. So any backend that supports drawing a path will implicitly
|
||||
# be able to support step, bar, and histogram plots (and any recipes that use those components).
|
||||
_process_seriesrecipe(plt, series_attr)
|
||||
end
|
||||
|
||||
# --------------------------------
|
||||
|
||||
function _plot!(plt::Plot, plotattributes, args)
|
||||
RecipesPipeline.recipe_pipeline!(plt, plotattributes, args)
|
||||
current(plt)
|
||||
|
||||
# do we want to force display?
|
||||
# if plt[:show]
|
||||
# gui(plt)
|
||||
# end
|
||||
_do_plot_show(plt, plt[:show])
|
||||
|
||||
plt
|
||||
return plt
|
||||
end
|
||||
|
||||
|
||||
@@ -288,5 +212,3 @@ function plot!(sp::Subplot, args...; kw...)
|
||||
plt = sp.plt
|
||||
plot!(plt, args...; kw..., subplot = findfirst(isequal(sp), plt.subplots))
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
+363
-451
File diff suppressed because it is too large
Load Diff
+116
-22
@@ -457,6 +457,8 @@ end
|
||||
()
|
||||
end
|
||||
@deps plots_heatmap shape
|
||||
is_3d(::Type{Val{:plots_heatmap}}) = true
|
||||
RecipesPipeline.is_surface(::Type{Val{:plots_heatmap}}) = true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Histograms
|
||||
@@ -1017,7 +1019,7 @@ error_tuple(x::Tuple) = x
|
||||
|
||||
function error_coords(errorbar, errordata, otherdata...)
|
||||
ed = Vector{float_extended_type(errordata)}(undef, 0)
|
||||
od = [Float64[] for _ in otherdata]
|
||||
od = [Vector{float_extended_type(odi)}(undef, 0) for odi in otherdata]
|
||||
for (i, edi) in enumerate(errordata)
|
||||
for (j, odj) in enumerate(otherdata)
|
||||
odi = _cycle(odj, i)
|
||||
@@ -1162,7 +1164,7 @@ function quiver_using_hack(plotattributes::AKW)
|
||||
)
|
||||
end
|
||||
|
||||
plotattributes[:x], plotattributes[:y] = Plots.unzip(pts[2:end])
|
||||
plotattributes[:x], plotattributes[:y] = RecipesPipeline.unzip(pts[2:end])
|
||||
# KW[plotattributes]
|
||||
end
|
||||
|
||||
@@ -1178,6 +1180,118 @@ end
|
||||
@deps quiver shape path
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 1 argument
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# images - grays
|
||||
function clamp_greys!(mat::AMat{<: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{<:Gray})
|
||||
n, m = axes(mat)
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, m, n, Surface(clamp_greys!(mat))
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
fillcolor --> ColorGradient([:black, :white])
|
||||
SliceIt, m, n, Surface(clamp!(convert(Matrix{Float64}, mat), 0.0, 1.0))
|
||||
end
|
||||
end
|
||||
|
||||
# images - colors
|
||||
@recipe function f(mat::AMat{T}) where {T <: Colorant}
|
||||
n, m = axes(mat)
|
||||
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, m, n, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
aspect_ratio --> :equal
|
||||
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
|
||||
SliceIt, m, n, Surface(z)
|
||||
end
|
||||
end
|
||||
|
||||
# plotting arbitrary shapes/polygons
|
||||
|
||||
@recipe function f(shape::Shape)
|
||||
seriestype --> :shape
|
||||
coords(shape)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AVec{Shape})
|
||||
seriestype --> :shape
|
||||
coords(shapes)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AMat{Shape})
|
||||
seriestype --> :shape
|
||||
for j in axes(shapes, 2)
|
||||
@series coords(vec(shapes[:, j]))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 3 arguments
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# images - grays
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T <: Gray}
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, x, y, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
fillcolor --> ColorGradient([:black, :white])
|
||||
SliceIt, x, y, Surface(convert(Matrix{Float64}, mat))
|
||||
end
|
||||
end
|
||||
|
||||
# images - colors
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T <: Colorant}
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, x, y, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
|
||||
SliceIt, x, y, Surface(z)
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Lists of tuples and GeometryTypes.Points
|
||||
# --------------------------------------------------------------------
|
||||
@recipe f(v::AVec{<:GeometryTypes.Point}) = RecipesPipeline.unzip(v)
|
||||
@recipe f(p::GeometryTypes.Point) = [p]
|
||||
|
||||
# Special case for 4-tuples in :ohlc series
|
||||
@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] :
|
||||
RecipesPipeline.unzip(xyuv)
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
# TODO: move OHLC to PlotRecipes finance.jl
|
||||
@@ -1301,26 +1415,6 @@ abline!(plt::Plot, a, b; kw...) =
|
||||
|
||||
abline!(args...; kw...) = abline!(current(), args...; kw...)
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Dates & Times
|
||||
|
||||
dateformatter(dt) = string(Date(Dates.UTD(dt)))
|
||||
datetimeformatter(dt) = string(DateTime(Dates.UTM(dt)))
|
||||
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
|
||||
|
||||
|
||||
-659
@@ -1,659 +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{F} = Union{F, Vector{F}, Matrix{F}}
|
||||
const MaybeNumber = Union{Number, Missing}
|
||||
const MaybeString = Union{AbstractString, Missing}
|
||||
const DataPoint = Union{MaybeNumber, MaybeString}
|
||||
|
||||
prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
|
||||
prepareSeriesData(::Nothing) = nothing
|
||||
prepareSeriesData(t::Tuple{T, T}) where {T<:Number} = t
|
||||
prepareSeriesData(f::Function) = f
|
||||
prepareSeriesData(ar::AbstractRange{<:Number}) = ar
|
||||
function prepareSeriesData(a::AbstractArray{<:MaybeNumber})
|
||||
f = isimmutable(a) ? replace : replace!
|
||||
a = f(x -> ismissing(x) || isinf(x) ? NaN : x, map(float, a))
|
||||
end
|
||||
prepareSeriesData(a::AbstractArray{<:Missing}) = fill(NaN, axes(a))
|
||||
prepareSeriesData(a::AbstractArray{<:MaybeString}) = replace(x -> ismissing(x) ? "" : x, a)
|
||||
prepareSeriesData(s::Surface{<:AMat{<:MaybeNumber}}) = Surface(prepareSeriesData(s.surf))
|
||||
prepareSeriesData(s::Surface) = s # non-numeric Surface, such as an image
|
||||
prepareSeriesData(v::Volume) = Volume(prepareSeriesData(v.v), v.x_extents, v.y_extents, v.z_extents)
|
||||
|
||||
# default: assume x represents a single series
|
||||
series_vector(x, plotattributes) = [prepareSeriesData(x)]
|
||||
|
||||
# fixed number of blank series
|
||||
series_vector(n::Integer, plotattributes) = [zeros(0) for i in 1:n]
|
||||
|
||||
# vector of data points is a single series
|
||||
series_vector(v::AVec{<:DataPoint}, plotattributes) = [prepareSeriesData(v)]
|
||||
|
||||
# list of things (maybe other vectors, functions, or something else)
|
||||
function series_vector(v::AVec, plotattributes)
|
||||
if all(x -> x isa MaybeNumber, v)
|
||||
series_vector(Vector{MaybeNumber}(v), plotattributes)
|
||||
elseif all(x -> x isa MaybeString, v)
|
||||
series_vector(Vector{MaybeString}(v), plotattributes)
|
||||
else
|
||||
vcat((series_vector(vi, plotattributes) for vi in v)...)
|
||||
end
|
||||
end
|
||||
|
||||
# Matrix is split into columns
|
||||
function series_vector(v::AMat{<:DataPoint}, plotattributes)
|
||||
if all3D(plotattributes)
|
||||
[prepareSeriesData(Surface(v))]
|
||||
else
|
||||
[prepareSeriesData(v[:, i]) for i in axes(v, 2)]
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Fillranges & ribbons
|
||||
|
||||
|
||||
process_fillrange(range::Number, plotattributes) = [range]
|
||||
process_fillrange(range, plotattributes) = series_vector(range, plotattributes)
|
||||
|
||||
process_ribbon(ribbon::Number, plotattributes) = [ribbon]
|
||||
process_ribbon(ribbon, plotattributes) = series_vector(ribbon, plotattributes)
|
||||
# ribbon as a tuple: (lower_ribbons, upper_ribbons)
|
||||
process_ribbon(ribbon::Tuple{S, T}, plotattributes) where {S, T} = collect(zip(
|
||||
series_vector(ribbon[1], plotattributes),
|
||||
series_vector(ribbon[2], plotattributes),
|
||||
))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
compute_x(x::Nothing, y::Nothing, z) = axes(z,1)
|
||||
compute_x(x::Nothing, y, z) = axes(y,1)
|
||||
compute_x(x::Function, y, z) = map(x, y)
|
||||
compute_x(x, y, z) = x
|
||||
|
||||
compute_y(x::Nothing, y::Nothing, z) = axes(z,2)
|
||||
compute_y(x, y::Function, z) = map(y, x)
|
||||
compute_y(x, y, z) = 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) = 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{F}, z) where {F<:Function} = error("If you want to plot the function `$y`, you need to define the x values!")
|
||||
compute_xyz(x::Nothing, y::Nothing, z::FuncOrFuncs{F}) where {F<:Function} = error("If you want to plot the function `$z`, you need to define x and y values!")
|
||||
compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all nothing!")
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
||||
# we are going to build recipes to do the processing and splitting of the args
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# The catch-all SliceIt recipe
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# ensure we dispatch to the slicer
|
||||
struct SliceIt end
|
||||
|
||||
# The `SliceIt` recipe finishes user and type recipe processing.
|
||||
# It splits processed data into individual series data, stores in copied `plotattributes`
|
||||
# for each series and returns no arguments.
|
||||
@recipe function f(::Type{SliceIt}, x, y, z)
|
||||
|
||||
# handle data with formatting attached
|
||||
if typeof(x) <: Formatted
|
||||
xformatter := x.formatter
|
||||
x = x.data
|
||||
end
|
||||
if typeof(y) <: Formatted
|
||||
yformatter := y.formatter
|
||||
y = y.data
|
||||
end
|
||||
if typeof(z) <: Formatted
|
||||
zformatter := z.formatter
|
||||
z = z.data
|
||||
end
|
||||
|
||||
xs = series_vector(x, plotattributes)
|
||||
ys = series_vector(y, plotattributes)
|
||||
zs = series_vector(z, plotattributes)
|
||||
|
||||
fr = pop!(plotattributes, :fillrange, nothing)
|
||||
fillranges = process_fillrange(fr, plotattributes)
|
||||
mf = length(fillranges)
|
||||
|
||||
rib = pop!(plotattributes, :ribbon, nothing)
|
||||
ribbons = process_ribbon(rib, plotattributes)
|
||||
mr = length(ribbons)
|
||||
|
||||
mx = length(xs)
|
||||
my = length(ys)
|
||||
mz = length(zs)
|
||||
if mx > 0 && my > 0 && mz > 0
|
||||
for i in 1:max(mx, my, mz)
|
||||
# add a new series
|
||||
di = copy(plotattributes)
|
||||
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
|
||||
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)
|
||||
|
||||
# handle fillrange
|
||||
fr = fillranges[mod1(i,mf)]
|
||||
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr
|
||||
|
||||
# handle ribbons
|
||||
rib = ribbons[mod1(i,mr)]
|
||||
di[:ribbon] = isa(rib, Function) ? map(rib, di[:x]) : rib
|
||||
|
||||
push!(series_list, RecipeData(di, ()))
|
||||
end
|
||||
end
|
||||
nothing # don't add a series for the main block
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Apply type recipes
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# this is the default "type recipe"... just pass the object through
|
||||
@recipe f(::Type{T}, v::T) where T = v
|
||||
|
||||
# this should catch unhandled "series recipes" and error with a nice message
|
||||
@recipe f(::Type{V}, x, y, z) where {V<:Val} = error("The backend must not support the series type $V, and there isn't a series recipe defined.")
|
||||
|
||||
function _apply_type_recipe(plotattributes, v, letter)
|
||||
_preprocess_axis_args!(plotattributes, letter)
|
||||
rdvec = RecipesBase.apply_recipe(plotattributes, typeof(v), v)
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(v))
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
return rdvec[1].args[1]
|
||||
end
|
||||
|
||||
# Handle type recipes when the recipe is defined on the elements.
|
||||
# This sort of recipe should return a pair of functions... one to convert to number,
|
||||
# and one to format tick values.
|
||||
function _apply_type_recipe(plotattributes, v::AbstractArray, letter)
|
||||
_preprocess_axis_args!(plotattributes, letter)
|
||||
# First we try to apply an array type recipe.
|
||||
w = RecipesBase.apply_recipe(plotattributes, typeof(v), v)[1].args[1]
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(v))
|
||||
# If the type did not change try it element-wise
|
||||
if typeof(v) == typeof(w)
|
||||
isempty(skipmissing(v)) && return Float64[]
|
||||
x = first(skipmissing(v))
|
||||
args = RecipesBase.apply_recipe(plotattributes, typeof(x), x)[1].args
|
||||
warn_on_recipe_aliases!(plotattributes, :type, typeof(x))
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
if length(args) == 2 && all(arg -> arg isa Function, args)
|
||||
numfunc, formatter = args
|
||||
return Formatted(map(numfunc, v), formatter)
|
||||
else
|
||||
return v
|
||||
end
|
||||
end
|
||||
_postprocess_axis_args!(plotattributes, letter)
|
||||
return w
|
||||
end
|
||||
|
||||
# special handling for Surface... need to properly unwrap and re-wrap
|
||||
_apply_type_recipe(
|
||||
plotattributes,
|
||||
v::Surface{<:AMat{<:Union{AbstractFloat, Integer, AbstractString, Missing}}},
|
||||
letter,
|
||||
) = v
|
||||
function _apply_type_recipe(plotattributes, v::Surface, letter)
|
||||
ret = _apply_type_recipe(plotattributes, v.surf, letter)
|
||||
if typeof(ret) <: Formatted
|
||||
Formatted(Surface(ret.data), ret.formatter)
|
||||
else
|
||||
Surface(ret)
|
||||
end
|
||||
end
|
||||
|
||||
# don't do anything vectors of datapoints and for nothing
|
||||
_apply_type_recipe(plotattributes, v::Nothing, letter) = v
|
||||
_apply_type_recipe(
|
||||
plotattributes,
|
||||
v::AbstractArray{<:Union{AbstractFloat, Integer, AbstractString, Missing}},
|
||||
letter,
|
||||
) = v
|
||||
|
||||
# axis args before type recipes should still be mapped to all axes
|
||||
function _preprocess_axis_args!(plotattributes)
|
||||
for (k, v) in plotattributes
|
||||
if is_axis_attr_noletter(k)
|
||||
pop!(plotattributes, k)
|
||||
for l in (:x, :y, :z)
|
||||
lk = Symbol(l, k)
|
||||
haskey(plotattributes, lk) || (plotattributes[lk] = v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function _preprocess_axis_args!(plotattributes, letter)
|
||||
plotattributes[:letter] = letter
|
||||
_preprocess_axis_args!(plotattributes)
|
||||
end
|
||||
|
||||
# axis args in type recipes should only be applied to the current axis
|
||||
function _postprocess_axis_args!(plotattributes, letter)
|
||||
pop!(plotattributes, :letter)
|
||||
if letter in (:x, :y, :z)
|
||||
for (k, v) in plotattributes
|
||||
if is_axis_attr_noletter(k)
|
||||
pop!(plotattributes, k)
|
||||
lk = Symbol(letter, k)
|
||||
haskey(plotattributes, lk) || (plotattributes[lk] = v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Fallback user recipes calling type recipes
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# handle "type recipes" by converting inputs, and then either re-calling or slicing
|
||||
@recipe function f(x, y, z)
|
||||
wrap_surfaces!(plotattributes, x, y, z)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x, :x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
y === newy || (did_replace = true)
|
||||
newz = _apply_type_recipe(plotattributes, z, :z)
|
||||
z === newz || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy, newz
|
||||
else
|
||||
SliceIt, x, y, z
|
||||
end
|
||||
end
|
||||
@recipe function f(x, y)
|
||||
wrap_surfaces!(plotattributes, x, y)
|
||||
did_replace = false
|
||||
newx = _apply_type_recipe(plotattributes, x, :x)
|
||||
x === newx || (did_replace = true)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
y === newy || (did_replace = true)
|
||||
if did_replace
|
||||
newx, newy
|
||||
else
|
||||
SliceIt, x, y, nothing
|
||||
end
|
||||
end
|
||||
@recipe function f(y)
|
||||
wrap_surfaces!(plotattributes, y)
|
||||
newy = _apply_type_recipe(plotattributes, y, :y)
|
||||
if y !== newy
|
||||
newy
|
||||
else
|
||||
SliceIt, nothing, y, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# if there's more than 3 inputs, it can't be passed directly to SliceIt
|
||||
# so we'll apply_type_recipe to all of them
|
||||
@recipe function f(v1, v2, v3, v4, vrest...)
|
||||
did_replace = false
|
||||
newargs = map(v -> begin
|
||||
newv = _apply_type_recipe(plotattributes, v, :unknown)
|
||||
if newv !== v
|
||||
did_replace = true
|
||||
end
|
||||
newv
|
||||
end, (v1, v2, v3, v4, vrest...))
|
||||
if !did_replace
|
||||
error("Couldn't process recipe args: $(map(typeof, (v1, v2, v3, v4, vrest...)))")
|
||||
end
|
||||
newargs
|
||||
end
|
||||
|
||||
|
||||
# helper function to ensure relevant attributes are wrapped by Surface
|
||||
function wrap_surfaces!(plotattributes, args...) end
|
||||
wrap_surfaces!(plotattributes, x::AMat, y::AMat, z::AMat) = wrap_surfaces!(plotattributes)
|
||||
wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::AMat) = wrap_surfaces!(plotattributes)
|
||||
function wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::Surface)
|
||||
wrap_surfaces!(plotattributes)
|
||||
end
|
||||
function wrap_surfaces!(plotattributes)
|
||||
if haskey(plotattributes, :fill_z)
|
||||
v = plotattributes[:fill_z]
|
||||
if !isa(v, Surface)
|
||||
plotattributes[:fill_z] = Surface(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 1 argument
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@recipe f(n::Integer) = is3d(get(plotattributes,:seriestype,:path)) ? (SliceIt, n, n, n) : (SliceIt, n, n, nothing)
|
||||
|
||||
all3D(plotattributes) = 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)
|
||||
if all3D(plotattributes)
|
||||
n, m = axes(mat)
|
||||
m, n, Surface(mat)
|
||||
else
|
||||
nothing, mat, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# if a matrix is wrapped by Formatted, do similar logic, but wrap data with Surface
|
||||
@recipe function f(fmt::Formatted{<:AMat})
|
||||
if all3D(plotattributes)
|
||||
mat = fmt.data
|
||||
n, m = axes(mat)
|
||||
m, n, Formatted(Surface(mat), fmt.formatter)
|
||||
else
|
||||
nothing, fmt, nothing
|
||||
end
|
||||
end
|
||||
|
||||
# assume this is a Volume, so construct one
|
||||
@recipe function f(vol::AbstractArray{<:MaybeNumber, 3}, args...)
|
||||
seriestype := :volume
|
||||
SliceIt, nothing, Volume(vol, args...), nothing
|
||||
end
|
||||
|
||||
|
||||
# images - grays
|
||||
function clamp_greys!(mat::AMat{<: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{<:Gray})
|
||||
n, m = axes(mat)
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, m, n, Surface(clamp_greys!(mat))
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
fillcolor --> ColorGradient([:black, :white])
|
||||
SliceIt, m, n, Surface(clamp!(convert(Matrix{Float64}, mat), 0., 1.))
|
||||
end
|
||||
end
|
||||
|
||||
# images - colors
|
||||
@recipe function f(mat::AMat{T}) where T<:Colorant
|
||||
n, m = axes(mat)
|
||||
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, m, n, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
aspect_ratio --> :equal
|
||||
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
|
||||
SliceIt, m, n, Surface(z)
|
||||
end
|
||||
end
|
||||
|
||||
# plotting arbitrary shapes/polygons
|
||||
|
||||
@recipe function f(shape::Shape)
|
||||
seriestype --> :shape
|
||||
coords(shape)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AVec{Shape})
|
||||
seriestype --> :shape
|
||||
coords(shapes)
|
||||
end
|
||||
|
||||
@recipe function f(shapes::AMat{Shape})
|
||||
seriestype --> :shape
|
||||
for j in axes(shapes,2)
|
||||
@series coords(vec(shapes[:,j]))
|
||||
end
|
||||
end
|
||||
|
||||
# Dicts: each entry is a data point (x,y)=(key,value)
|
||||
|
||||
@recipe f(d::AbstractDict) = collect(keys(d)), collect(values(d))
|
||||
|
||||
# function without range... use the current range of the x-axis
|
||||
|
||||
@recipe function f(f::FuncOrFuncs{F}) where F<:Function
|
||||
plt = plotattributes[:plot_object]
|
||||
xmin, xmax = if haskey(plotattributes, :xlims)
|
||||
plotattributes[:xlims]
|
||||
else
|
||||
try
|
||||
axis_limits(plt[1], :x)
|
||||
catch
|
||||
xinv = invscalefunc(get(plotattributes, :xscale, :identity))
|
||||
xm = PlotUtils.tryrange(f, xinv.([-5,-1,0,0.01]))
|
||||
xm, PlotUtils.tryrange(f, filter(x->x>xm, xinv.([5,1,0.99, 0, -0.01])))
|
||||
end
|
||||
end
|
||||
|
||||
f, xmin, xmax
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 2 arguments
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# if functions come first, just swap the order (not to be confused with parametric
|
||||
# functions... as there would be more than one function passed in)
|
||||
|
||||
@recipe function f(f::FuncOrFuncs{F}, x) where F<:Function
|
||||
F2 = typeof(x)
|
||||
@assert !(F2 <: Function || (F2 <: AbstractArray && F2.parameters[1] <: Function)) # otherwise we'd hit infinite recursion here
|
||||
x, f
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# 3 arguments
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# surface-like... function
|
||||
@recipe function f(x::AVec, y::AVec, zf::Function)
|
||||
x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
|
||||
end
|
||||
|
||||
# surface-like... matrix grid
|
||||
@recipe function f(x::AVec, y::AVec, z::AMat)
|
||||
if !like_surface(get(plotattributes, :seriestype, :none))
|
||||
plotattributes[:seriestype] = :contour
|
||||
end
|
||||
x, y, Surface(z)
|
||||
end
|
||||
|
||||
# images - grays
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Gray
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, x, y, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
fillcolor --> ColorGradient([:black, :white])
|
||||
SliceIt, x, y, Surface(convert(Matrix{Float64}, mat))
|
||||
end
|
||||
end
|
||||
|
||||
# images - colors
|
||||
@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where T<:Colorant
|
||||
if is_seriestype_supported(:image)
|
||||
seriestype := :image
|
||||
yflip --> true
|
||||
SliceIt, x, y, Surface(mat)
|
||||
else
|
||||
seriestype := :heatmap
|
||||
yflip --> true
|
||||
cbar --> false
|
||||
z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
|
||||
SliceIt, x, y, Surface(z)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Parametric functions
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# special handling... xmin/xmax with parametric function(s)
|
||||
@recipe function f(f::Function, xmin::Number, xmax::Number)
|
||||
xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
|
||||
_scaled_adapted_grid(f, xscale, yscale, xmin, xmax)
|
||||
end
|
||||
@recipe function f(fs::AbstractArray{F}, xmin::Number, xmax::Number) where F<:Function
|
||||
xscale, yscale = [get(plotattributes, sym, :identity) for sym=(:xscale,:yscale)]
|
||||
unzip(_scaled_adapted_grid.(fs, xscale, yscale, xmin, xmax))
|
||||
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}, 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))
|
||||
xs, ys = adapted_grid(yf∘f∘xinv, xf.((xmin, xmax)))
|
||||
xinv.(xs), yinv.(ys)
|
||||
end
|
||||
|
||||
# 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}
|
||||
mapFuncOrFuncs(fx, u), mapFuncOrFuncs(fy, u), mapFuncOrFuncs(fz, u)
|
||||
end
|
||||
@recipe function f(fx::FuncOrFuncs{F}, fy::FuncOrFuncs{G}, fz::FuncOrFuncs{H}, umin::Number, umax::Number, numPoints = 200) where {F<:Function,G<:Function,H<:Function}
|
||||
fx, fy, fz, range(umin, stop = umax, length = numPoints)
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Lists of tuples and GeometryTypes.Points
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@recipe f(v::AVec{<:Tuple}) = unzip(v)
|
||||
@recipe f(v::AVec{<:GeometryTypes.Point}) = unzip(v)
|
||||
@recipe f(tup::Tuple) = [tup]
|
||||
@recipe f(p::GeometryTypes.Point) = [p]
|
||||
|
||||
# Special case for 4-tuples in :ohlc series
|
||||
@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)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# handle grouping
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
splittable_kw(key, val, lengthGroup) = false
|
||||
splittable_kw(key, val::AbstractArray, lengthGroup) = !(key in (:group, :color_palette)) && length(axes(val,1)) == lengthGroup
|
||||
splittable_kw(key, val::Tuple, lengthGroup) = all(splittable_kw.(key, val, lengthGroup))
|
||||
splittable_kw(key, val::SeriesAnnotations, lengthGroup) = splittable_kw(key, val.strs, lengthGroup)
|
||||
|
||||
split_kw(key, val::AbstractArray, indices) = val[indices, fill(Colon(), ndims(val)-1)...]
|
||||
split_kw(key, val::Tuple, indices) = Tuple(split_kw(key, v, indices) for v in val)
|
||||
function split_kw(key, val::SeriesAnnotations, indices)
|
||||
split_strs = split_kw(key, val.strs, indices)
|
||||
return SeriesAnnotations(split_strs, val.font, val.baseshape, val.scalefactor)
|
||||
end
|
||||
|
||||
function groupedvec2mat(x_ind, x, y::AbstractArray, groupby, def_val = y[1])
|
||||
y_mat = Array{promote_type(eltype(y), typeof(def_val))}(undef, length(keys(x_ind)), length(groupby.groupLabels))
|
||||
fill!(y_mat, def_val)
|
||||
for i in eachindex(groupby.groupLabels)
|
||||
xi = x[groupby.groupIds[i]]
|
||||
yi = y[groupby.groupIds[i]]
|
||||
y_mat[getindex.(Ref(x_ind), xi), i] = yi
|
||||
end
|
||||
return y_mat
|
||||
end
|
||||
|
||||
groupedvec2mat(x_ind, x, y::Tuple, groupby) = Tuple(groupedvec2mat(x_ind, x, v, groupby) for v in y)
|
||||
|
||||
group_as_matrix(t) = false
|
||||
|
||||
# split the group into 1 series per group, and set the label and idxfilter for each
|
||||
@recipe function f(groupby::GroupBy, args...)
|
||||
lengthGroup = maximum(union(groupby.groupIds...))
|
||||
if !(group_as_matrix(args[1]))
|
||||
for (i,glab) in enumerate(groupby.groupLabels)
|
||||
@series begin
|
||||
label --> string(glab)
|
||||
idxfilter --> groupby.groupIds[i]
|
||||
for (key,val) in plotattributes
|
||||
if splittable_kw(key, val, lengthGroup)
|
||||
:($key) := split_kw(key, val, groupby.groupIds[i])
|
||||
end
|
||||
end
|
||||
args
|
||||
end
|
||||
end
|
||||
else
|
||||
g = args[1]
|
||||
if length(g.args) == 1
|
||||
x = zeros(Int, lengthGroup)
|
||||
for indexes in groupby.groupIds
|
||||
x[indexes] = eachindex(indexes)
|
||||
end
|
||||
last_args = g.args
|
||||
else
|
||||
x = g.args[1]
|
||||
last_args = g.args[2:end]
|
||||
end
|
||||
x_u = unique(sort(x))
|
||||
x_ind = Dict(zip(x_u, eachindex(x_u)))
|
||||
for (key,val) in plotattributes
|
||||
if splittable_kw(key, val, lengthGroup)
|
||||
:($key) := groupedvec2mat(x_ind, x, val, groupby)
|
||||
end
|
||||
end
|
||||
label --> reshape(groupby.groupLabels, 1, :)
|
||||
typeof(g)((x_u, (groupedvec2mat(x_ind, x, arg, groupby, NaN) for arg in last_args)...))
|
||||
end
|
||||
end
|
||||
+5
-4
@@ -251,7 +251,7 @@ Draw contour lines of the `Surface` z.
|
||||
|
||||
# Example
|
||||
```julia-repl
|
||||
julia> x = y = range(-20, 20, length = 100)
|
||||
julia> x = y = range(-20, stop = 20, length = 100)
|
||||
julia> contour(x, y, (x, y) -> x^2 + y^2)
|
||||
```
|
||||
"""
|
||||
@@ -272,7 +272,7 @@ Draw a 3D surface plot.
|
||||
# Example
|
||||
```julia-repl
|
||||
julia> using LinearAlgebra
|
||||
julia> x = y = range(-3, 3, length = 100)
|
||||
julia> x = y = range(-3, stop = 3, length = 100)
|
||||
julia> surface(x, y, (x, y) -> sinc(norm([x, y])))
|
||||
```
|
||||
"""
|
||||
@@ -438,8 +438,9 @@ julia> plot(1:10)
|
||||
julia> annotate!([(7,3,"(7,3)"),(3,7,text("hey", 14, :left, :top, :green))])
|
||||
```
|
||||
"""
|
||||
annotate!(anns...; kw...) = plot!(; annotation = collect(anns), kw...)
|
||||
annotate!(anns::AVec{T}; kw...) where {T<:Tuple} = plot!(; annotation = anns, kw...)
|
||||
annotate!(anns...; kw...) = plot!(; annotation = anns, kw...)
|
||||
annotate!(anns::Tuple...; kw...) = plot!(; annotation = collect(anns), kw...)
|
||||
annotate!(anns::AVec{<:Tuple}; kw...) = plot!(; annotation = anns, kw...)
|
||||
|
||||
"Flip the current plots' x axis"
|
||||
xflip!(flip::Bool = true; kw...) = plot!(; xflip = flip, kw...)
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ function Subplot(::T; parent = RootLayout()) where T<:AbstractBackend
|
||||
(20mm, 5mm, 2mm, 10mm),
|
||||
defaultbox,
|
||||
defaultbox,
|
||||
Attr(KW(), _subplot_defaults),
|
||||
DefaultsDict(KW(), _subplot_defaults),
|
||||
nothing,
|
||||
nothing
|
||||
)
|
||||
|
||||
+9
-19
@@ -14,25 +14,15 @@ function _theme(s::Symbol, defaults::AKW; kw...)
|
||||
|
||||
# Set the theme's gradient as default
|
||||
if haskey(defaults, :colorgradient)
|
||||
PlotUtils.clibrary(:misc)
|
||||
PlotUtils.default_cgrad(default = :sequential, sequential = PlotThemes.gradient_name(s))
|
||||
pop!(defaults, :colorgradient)
|
||||
PlotUtils.default_cgrad(pop!(defaults, :colorgradient))
|
||||
else
|
||||
PlotUtils.clibrary(:Plots)
|
||||
PlotUtils.default_cgrad(default = :sequential, sequential = :inferno)
|
||||
PlotUtils.default_cgrad(:default)
|
||||
end
|
||||
|
||||
# maybe overwrite the theme's gradient
|
||||
kw = KW(kw)
|
||||
if haskey(kw, :colorgradient)
|
||||
kwgrad = pop!(kw, :colorgradient)
|
||||
for clib in clibraries()
|
||||
if kwgrad in cgradients(clib)
|
||||
PlotUtils.clibrary(clib)
|
||||
PlotUtils.default_cgrad(default = :sequential, sequential = kwgrad)
|
||||
break
|
||||
end
|
||||
end
|
||||
PlotUtils.default_cgrad(pop!(kw, :colorgradient))
|
||||
end
|
||||
|
||||
# Set the theme's defaults
|
||||
@@ -57,12 +47,12 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
|
||||
defaults = PlotThemes._themes[thm].defaults
|
||||
|
||||
# get the gradient
|
||||
gradient_colors = get(defaults, :colorgradient, cgrad(:inferno).colors)
|
||||
gradient_colors = color_list(cgrad(get(defaults, :colorgradient, :default)))
|
||||
colorgradient = cgrad(cfunc.(RGB.(gradient_colors)))
|
||||
|
||||
# get the palette
|
||||
palette = get(defaults, :palette, get_color_palette(:auto, plot_color(:white), 17))
|
||||
palette = cfunc.(RGB.(palette))
|
||||
cp = color_list(palette(get(defaults, :palette, :default)))
|
||||
cp = cfunc.(RGB.(cp))
|
||||
|
||||
# apply the theme
|
||||
for k in keys(defaults)
|
||||
@@ -89,7 +79,7 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
|
||||
for j in 1:4
|
||||
@series begin
|
||||
subplot := 1
|
||||
palette := palette
|
||||
color_palette := cp
|
||||
seriestype := :path
|
||||
cumsum(randn(50))
|
||||
end
|
||||
@@ -97,7 +87,7 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
|
||||
@series begin
|
||||
subplot := 2
|
||||
seriestype := :scatter
|
||||
palette := palette
|
||||
color_palette := cp
|
||||
marker := (:circle, :diamond, :star5, :square)[j]
|
||||
randn(10), randn(10)
|
||||
end
|
||||
@@ -106,7 +96,7 @@ _get_showtheme_args(thm::Symbol, func::Symbol) = thm, get(_color_functions, func
|
||||
@series begin
|
||||
subplot := 3
|
||||
seriestype := :histogram
|
||||
palette := palette
|
||||
color_palette := cp
|
||||
randn(1000) .+ (0:2:4)'
|
||||
end
|
||||
|
||||
|
||||
+5
-60
@@ -18,65 +18,10 @@ end
|
||||
wrap(obj::T) where {T} = InputWrapper{T}(obj)
|
||||
Base.isempty(wrapper::InputWrapper) = false
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
struct Attr <: AbstractDict{Symbol,Any}
|
||||
explicit::KW
|
||||
defaults::KW
|
||||
end
|
||||
|
||||
function Base.getindex(attr::Attr, k)
|
||||
return haskey(attr.explicit, k) ? attr.explicit[k] : attr.defaults[k]
|
||||
end
|
||||
Base.haskey(attr::Attr, k) = haskey(attr.explicit,k) || haskey(attr.defaults,k)
|
||||
Base.get(attr::Attr, k, default) = haskey(attr, k) ? attr[k] : default
|
||||
function Base.get!(attr::Attr, k, default)
|
||||
v = if haskey(attr, k)
|
||||
attr[k]
|
||||
else
|
||||
attr.defaults[k] = default
|
||||
end
|
||||
return v
|
||||
end
|
||||
function Base.delete!(attr::Attr, k)
|
||||
haskey(attr.explicit, k) && delete!(attr.explicit, k)
|
||||
haskey(attr.defaults, k) && delete!(attr.defaults, k)
|
||||
end
|
||||
Base.length(attr::Attr) = length(union(keys(attr.explicit), keys(attr.defaults)))
|
||||
function Base.iterate(attr::Attr)
|
||||
exp_keys = keys(attr.explicit)
|
||||
def_keys = setdiff(keys(attr.defaults), exp_keys)
|
||||
key_list = collect(Iterators.flatten((exp_keys, def_keys)))
|
||||
iterate(attr, (key_list, 1))
|
||||
end
|
||||
function Base.iterate(attr::Attr, (key_list, i))
|
||||
i > length(key_list) && return nothing
|
||||
k = key_list[i]
|
||||
(k=>attr[k], (key_list, i+1))
|
||||
end
|
||||
|
||||
Base.copy(attr::Attr) = Attr(copy(attr.explicit), attr.defaults)
|
||||
|
||||
RecipesBase.is_explicit(attr::Attr, k) = haskey(attr.explicit,k)
|
||||
isdefault(attr::Attr, k) = !is_explicit(attr,k) && haskey(attr.defaults,k)
|
||||
|
||||
Base.setindex!(attr::Attr, v, k) = attr.explicit[k] = v
|
||||
|
||||
# Reset to default value and return dict
|
||||
reset_kw!(attr::Attr, k) = is_explicit(attr, k) ? delete!(attr.explicit, k) : attr
|
||||
# Reset to default value and return old value
|
||||
pop_kw!(attr::Attr, k) = is_explicit(attr, k) ? pop!(attr.explicit, k) : attr.defaults[k]
|
||||
pop_kw!(attr::Attr, k, default) = is_explicit(attr, k) ? pop!(attr.explicit, k) : get(attr.defaults, k, default)
|
||||
# Fallbacks for dicts without defaults
|
||||
reset_kw!(d::AKW, k) = delete!(d, k)
|
||||
pop_kw!(d::AKW, k) = pop!(d, k)
|
||||
pop_kw!(d::AKW, k, default) = pop!(d, k, default)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
mutable struct Series
|
||||
plotattributes::Attr
|
||||
plotattributes::DefaultsDict
|
||||
end
|
||||
|
||||
attr(series::Series, k::Symbol) = series.plotattributes[k]
|
||||
@@ -91,7 +36,7 @@ mutable struct Subplot{T<:AbstractBackend} <: AbstractLayout
|
||||
minpad::Tuple # leftpad, toppad, rightpad, bottompad
|
||||
bbox::BoundingBox # the canvas area which is available to this subplot
|
||||
plotarea::BoundingBox # the part where the data goes
|
||||
attr::Attr # args specific to this subplot
|
||||
attr::DefaultsDict # args specific to this subplot
|
||||
o # can store backend-specific data... like a pyplot ax
|
||||
plt # the enclosing Plot object (can't give it a type because of no forward declarations)
|
||||
end
|
||||
@@ -103,7 +48,7 @@ Base.show(io::IO, sp::Subplot) = print(io, "Subplot{$(sp[:subplot_index])}")
|
||||
# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place
|
||||
mutable struct Axis
|
||||
sps::Vector{Subplot}
|
||||
plotattributes::Attr
|
||||
plotattributes::DefaultsDict
|
||||
end
|
||||
|
||||
mutable struct Extrema
|
||||
@@ -122,7 +67,7 @@ const SubplotMap = Dict{Any, Subplot}
|
||||
mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
|
||||
backend::T # the backend type
|
||||
n::Int # number of series
|
||||
attr::Attr # arguments for the whole plot
|
||||
attr::DefaultsDict # arguments for the whole plot
|
||||
series_list::Vector{Series} # arguments for each series
|
||||
o # the backend's plot object
|
||||
subplots::Vector{Subplot}
|
||||
@@ -133,7 +78,7 @@ mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T}
|
||||
end
|
||||
|
||||
function Plot()
|
||||
Plot(backend(), 0, Attr(KW(), _plot_defaults), Series[], nothing,
|
||||
Plot(backend(), 0, DefaultsDict(KW(), _plot_defaults), Series[], nothing,
|
||||
Subplot[], SubplotMap(), EmptyLayout(),
|
||||
Subplot[], false)
|
||||
end
|
||||
|
||||
+36
-19
@@ -1,8 +1,8 @@
|
||||
|
||||
function replace_image_with_heatmap(z::Array{T}) where T<:Colorant
|
||||
n, m = size(z)
|
||||
colors = ColorGradient(vec(z))
|
||||
newz = reshape(range(0, stop=1, length=n*m), n, m)
|
||||
colors = palette(vec(z))
|
||||
newz = reshape(1:n*m, n, m)
|
||||
newz, colors
|
||||
end
|
||||
|
||||
@@ -79,7 +79,7 @@ function iter_segments(series::Series)
|
||||
end
|
||||
else
|
||||
segs = UnitRange{Int}[]
|
||||
args = is3d(series) ? (x, y, z) : (x, y)
|
||||
args = RecipesPipeline.is3d(series) ? (x, y, z) : (x, y)
|
||||
for seg in iter_segments(args...)
|
||||
push!(segs, seg)
|
||||
end
|
||||
@@ -130,10 +130,12 @@ _cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v,i), indices)
|
||||
_cycle(v::AMat, indices::AVec{Int}) = map(i -> _cycle(v,i), indices)
|
||||
_cycle(v, indices::AVec{Int}) = fill(v, length(indices))
|
||||
|
||||
_cycle(grad::ColorGradient, idx::Int) = _cycle(grad.colors, idx)
|
||||
_cycle(grad::ColorGradient, indices::AVec{Int}) = _cycle(grad.colors, indices)
|
||||
_cycle(cl::PlotUtils.AbstractColorList, idx::Int) = cl[mod1(idx, end)]
|
||||
_cycle(cl::PlotUtils.AbstractColorList, idx::AVec{Int}) = cl[mod1.(idx, end)]
|
||||
|
||||
_as_gradient(grad::ColorGradient) = grad
|
||||
_as_gradient(grad) = grad
|
||||
_as_gradient(v::AbstractVector{<:Colorant}) = cgrad(v)
|
||||
_as_gradient(cp::ColorPalette) = cgrad(cp, categorical = true)
|
||||
_as_gradient(c::Colorant) = ColorGradient([c,c])
|
||||
|
||||
makevec(v::AVec) = v
|
||||
@@ -143,19 +145,16 @@ makevec(v::T) where {T} = T[v]
|
||||
maketuple(x::Real) = (x,x)
|
||||
maketuple(x::Tuple{T,S}) where {T,S} = x
|
||||
|
||||
mapFuncOrFuncs(f::Function, u::AVec) = map(f, u)
|
||||
mapFuncOrFuncs(fs::AVec{F}, u::AVec) where {F<:Function} = [map(f, u) for f in fs]
|
||||
|
||||
for i in 2:4
|
||||
@eval begin
|
||||
unzip(v::Union{AVec{<:Tuple{Vararg{T,$i} where T}},
|
||||
RecipesPipeline.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(v::Union{AVec{<:GeometryTypes.Point{N}},
|
||||
RecipesPipeline.unzip(v::Union{AVec{<:GeometryTypes.Point{N}},
|
||||
AVec{<:Tuple{Vararg{T,N} where T}}}) where N = error("$N-dimensional unzip not implemented.")
|
||||
unzip(v::Union{AVec{<:GeometryTypes.Point},
|
||||
RecipesPipeline.unzip(v::Union{AVec{<:GeometryTypes.Point},
|
||||
AVec{<:Tuple}}) = error("Can't unzip points of different dimensions.")
|
||||
|
||||
# given 2-element lims and a vector of data x, widen lims to account for the extrema of x
|
||||
@@ -190,7 +189,7 @@ end
|
||||
|
||||
function replaceAlias!(plotattributes::AKW, k::Symbol, aliases::Dict{Symbol,Symbol})
|
||||
if haskey(aliases, k)
|
||||
plotattributes[aliases[k]] = pop_kw!(plotattributes, k)
|
||||
plotattributes[aliases[k]] = RecipesPipeline.pop_kw!(plotattributes, k)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -229,7 +228,7 @@ end
|
||||
|
||||
"create an (n+1) list of the outsides of heatmap rectangles"
|
||||
function heatmap_edges(v::AVec, scale::Symbol = :identity, isedges::Bool = false)
|
||||
f, invf = scalefunc(scale), invscalefunc(scale)
|
||||
f, invf = RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale)
|
||||
map(invf, _heatmap_edges(map(f,v), isedges))
|
||||
end
|
||||
|
||||
@@ -524,8 +523,7 @@ for comp in (:line, :fill, :marker)
|
||||
if z === nothing
|
||||
isa(c, ColorGradient) ? c : plot_color(_cycle(c, i))
|
||||
else
|
||||
grad = isa(c, ColorGradient) ? c : cgrad()
|
||||
grad[clamp((_cycle(z, i) - cmin) / (cmax - cmin), 0, 1)]
|
||||
get(get_gradient(c), z[i], (cmin, cmax))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -543,9 +541,28 @@ for comp in (:line, :fill, :marker)
|
||||
end
|
||||
end
|
||||
|
||||
function get_colorgradient(series::Series)
|
||||
st = series[:seriestype]
|
||||
if st in (:surface, :heatmap) || isfilledcontour(series)
|
||||
series[:fillcolor]
|
||||
elseif st in (:contour, :wireframe)
|
||||
series[:linecolor]
|
||||
elseif series[:marker_z] !== nothing
|
||||
series[:markercolor]
|
||||
elseif series[:line_z] !== nothing
|
||||
series[:linecolor]
|
||||
elseif series[:fill_z] !== nothing
|
||||
series[:fillcolor]
|
||||
end
|
||||
end
|
||||
|
||||
single_color(c, v = 0.5) = c
|
||||
single_color(grad::ColorGradient, v = 0.5) = grad[v]
|
||||
|
||||
get_gradient(c) = cgrad()
|
||||
get_gradient(cg::ColorGradient) = cg
|
||||
get_gradient(cp::ColorPalette) = cgrad(cp, categorical = true)
|
||||
|
||||
function get_linewidth(series, i::Int = 1)
|
||||
_cycle(series[:linewidth], i)
|
||||
end
|
||||
@@ -838,7 +855,7 @@ end
|
||||
|
||||
function attr!(series::Series; kw...)
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
RecipesPipeline.preprocess_attributes!(plotattributes)
|
||||
for (k,v) in plotattributes
|
||||
if haskey(_series_defaults, k)
|
||||
series[k] = v
|
||||
@@ -852,7 +869,7 @@ end
|
||||
|
||||
function attr!(sp::Subplot; kw...)
|
||||
plotattributes = KW(kw)
|
||||
preprocessArgs!(plotattributes)
|
||||
RecipesPipeline.preprocess_attributes!(plotattributes)
|
||||
for (k,v) in plotattributes
|
||||
if haskey(_subplot_defaults, k)
|
||||
sp[k] = v
|
||||
@@ -1074,7 +1091,7 @@ end
|
||||
function shape_data(series, expansion_factor = 1)
|
||||
sp = series[:subplot]
|
||||
xl, yl = isvertical(series) ? (xlims(sp), ylims(sp)) : (ylims(sp), xlims(sp))
|
||||
x, y = series[:x], series[:y]
|
||||
x, y = copy(series[:x]), copy(series[:y])
|
||||
factor = 100
|
||||
for i in eachindex(x)
|
||||
if x[i] == -Inf
|
||||
|
||||
+19
-18
@@ -47,26 +47,27 @@ img_tol = is_ci() ? 1e-2 : Sys.islinux() ? 1e-3 : 0.1
|
||||
|
||||
## Uncomment the following lines to update reference images for different backends
|
||||
|
||||
#=
|
||||
@testset "GR" begin
|
||||
image_comparison_facts(:gr, tol=img_tol, skip = Plots._backend_skips[:gr])
|
||||
end
|
||||
# @testset "GR" begin
|
||||
# image_comparison_facts(:gr, tol=img_tol, skip = Plots._backend_skips[:gr])
|
||||
# end
|
||||
#
|
||||
# plotly()
|
||||
# @testset "Plotly" begin
|
||||
# image_comparison_facts(:plotly, tol=img_tol, skip = Plots._backend_skips[:plotlyjs])
|
||||
# end
|
||||
#
|
||||
# pyplot()
|
||||
# @testset "PyPlot" begin
|
||||
# image_comparison_facts(:pyplot, tol=img_tol, skip = Plots._backend_skips[:pyplot])
|
||||
# end
|
||||
#
|
||||
# pgfplotsx()
|
||||
# @testset "PGFPlotsX" begin
|
||||
# image_comparison_facts(:pgfplotsx, tol=img_tol, skip = Plots._backend_skips[:pgfplotsx])
|
||||
# end
|
||||
|
||||
plotly()
|
||||
@testset "Plotly" begin
|
||||
image_comparison_facts(:plotly, tol=img_tol, skip = Plots._backend_skips[:plotlyjs])
|
||||
end
|
||||
# 10 Histogram2D
|
||||
|
||||
pyplot()
|
||||
@testset "PyPlot" begin
|
||||
image_comparison_facts(:pyplot, tol=img_tol, skip = Plots._backend_skips[:pyplot])
|
||||
end
|
||||
|
||||
pgfplots()
|
||||
@testset "PGFPlots" begin
|
||||
image_comparison_facts(:pgfplots, tol=img_tol, skip = Plots._backend_skips[:pgfplots])
|
||||
end
|
||||
=#
|
||||
##
|
||||
|
||||
@testset "Backends" begin
|
||||
|
||||
@@ -71,7 +71,7 @@ end
|
||||
pl = scatter!(
|
||||
y,
|
||||
zcolor = abs.(y .- 0.5),
|
||||
m = (:heat, 0.8, Plots.stroke(1, :green)),
|
||||
m = (:hot, 0.8, Plots.stroke(1, :green)),
|
||||
ms = 10 * abs.(y .- 0.5) .+ 4,
|
||||
lab = ["grad", "", "ient"],
|
||||
)
|
||||
@@ -84,7 +84,7 @@ end
|
||||
@test marker.options["mark"] == "*"
|
||||
@test marker.options["mark options"]["color"] ==
|
||||
RGBA{Float64}(colorant"green", 0.8)
|
||||
@test marker.options["mark options"]["line width"] == 1
|
||||
@test marker.options["mark options"]["line width"] == 0.75 # 1px is 0.75pt
|
||||
end # testset
|
||||
@testset "Plot in pieces" begin
|
||||
pic = plot(rand(100) / 3, reg = true, fill = (0, :green))
|
||||
@@ -122,7 +122,7 @@ end
|
||||
plot(
|
||||
Plots.fakedata(100, 10),
|
||||
layout = 4,
|
||||
palette = [:grays :blues :heat :lightrainbow],
|
||||
palette = [:grays :blues :hot :rainbow],
|
||||
bg_inside = [:orange :pink :darkblue :black],
|
||||
)
|
||||
end # testset
|
||||
@@ -211,7 +211,7 @@ end
|
||||
marker_z = ((x, y) -> begin
|
||||
x + y
|
||||
end),
|
||||
color = :bluesreds,
|
||||
color = :bwr,
|
||||
legend = false,
|
||||
)
|
||||
plot(p1, p2)
|
||||
|
||||
Reference in New Issue
Block a user