diff --git a/.github/workflows/compilation-benchmark.yaml b/.github/workflows/compilation-benchmark.yaml index 091f76f5cbb..717f9037653 100644 --- a/.github/workflows/compilation-benchmark.yaml +++ b/.github/workflows/compilation-benchmark.yaml @@ -29,7 +29,7 @@ jobs: - uses: julia-actions/setup-julia@v1 with: version: '1' - include-all-prereleases: true + # include-all-prereleases: true arch: x64 - uses: julia-actions/cache@v1 - name: Benchmark diff --git a/CHANGELOG.md b/CHANGELOG.md index 40df52e0101..46aed3e90ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] +- **Breaking (sort of)** Added a new `@recipe` variant which allows documenting attributes directly where they are defined and validating that all attributes are known whenever a plot is created. This is not breaking in the sense that the API changes, but user code is likely to break because of misspelled attribute names etc. that have so far gone unnoticed. - **Breaking** Reworked line shaders in GLMakie and WGLMakie [#3558](https://github.com/MakieOrg/Makie.jl/pull/3558) - GLMakie: Removed support for per point linewidths - GLMakie: Adjusted dots (e.g. with `linestyle = :dot`) to bend across a joint diff --git a/GLMakie/src/GLAbstraction/GLTypes.jl b/GLMakie/src/GLAbstraction/GLTypes.jl index 206a8dda42a..0c1a6a1ebc0 100644 --- a/GLMakie/src/GLAbstraction/GLTypes.jl +++ b/GLMakie/src/GLAbstraction/GLTypes.jl @@ -385,6 +385,7 @@ function RenderObject( try data[k] = gl_convert(v) catch e + @error "gl_convert for key `$k` failed" rethrow(e) end diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index 9d56b84e865..113575f6fef 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -40,6 +40,30 @@ function generic_plot_attributes(attr) ) end +function mixin_generic_plot_attributes() + @DocumentedAttributes begin + transformation = automatic + "Sets a model matrix for the plot. This overrides adjustments made with `translate!`, `rotate!` and `scale!`." + model = automatic + "Controls whether the plot will be rendered or not." + visible = true + "Adjusts how the plot deals with transparency. In GLMakie `transparency = true` results in using Order Independent Transparency." + transparency = false + "Controls if the plot will draw over other plots. This specifically means ignoring depth checks in GL backends" + overdraw = false + "Adjusts whether the plot is rendered with ssao (screen space ambient occlusion). Note that this only makes sense in 3D plots and is only applicable with `fxaa = true`." + ssao = false + "sets whether this plot should be seen by `DataInspector`." + inspectable = true + "adjusts the depth value of a plot after all other transformations, i.e. in clip space, where `0 <= depth <= 1`. This only applies to GLMakie and WGLMakie and can be used to adjust render order (like a tunable overdraw)." + depth_shift = 0.0f0 + "sets the transformation space for box encompassing the plot. See `Makie.spaces()` for possible inputs." + space = :data + "adjusts whether the plot is rendered with fxaa (anti-aliasing, GLMakie only)." + fxaa = true + end +end + """ ### Color attributes @@ -76,6 +100,31 @@ function colormap_attributes(attr) ) end +function mixin_colormap_attributes() + @DocumentedAttributes begin + """ + Sets the colormap that is sampled for numeric `color`s. + `PlotUtils.cgrad(...)`, `Makie.Reverse(any_colormap)` can be used as well, or any symbol from ColorBrewer or PlotUtils. + To see all available color gradients, you can call `Makie.available_gradients()`. + """ + colormap = @inherit colormap :viridis + """ + The color transform function. Can be any function, but only works well together with `Colorbar` for `identity`, `log`, `log2`, `log10`, `sqrt`, `logit`, `Makie.pseudolog10` and `Makie.Symlog10`. + """ + colorscale = identity + "The values representing the start and end points of `colormap`." + colorrange = automatic + "The color for any value below the colorrange." + lowclip = automatic + "The color for any value above the colorrange." + highclip = automatic + "The color for NaN values." + nan_color = :transparent + "The alpha value of the colormap or color attribute. Multiple alphas like in `plot(alpha=0.2, color=(:red, 0.5)`, will get multiplied." + alpha = 1.0 + end +end + """ ### 3D shading attributes @@ -106,6 +155,21 @@ function shading_attributes(attr) ) end +function mixin_shading_attributes() + @DocumentedAttributes begin + "Sets the lighting algorithm used. Options are `NoShading` (no lighting), `FastShading` (AmbientLight + PointLight) or `MultiLightShading` (Multiple lights, GLMakie only). Note that this does not affect RPRMakie." + shading = automatic + "Sets how strongly the red, green and blue channel react to diffuse (scattered) light." + diffuse = 1.0 + "Sets how strongly the object reflects light in the red, green and blue channels." + specular = 0.2 + "Sets how sharp the reflection is." + shininess = 32.0f0 + "Sets a weight for secondary light calculation with inverted normals." + backlight = 0f0 + end +end + """ `calculated_attributes!(trait::Type{<: AbstractPlot}, plot)` trait version of calculated_attributes @@ -123,24 +187,14 @@ calculated_attributes!(plot::T) where T = calculated_attributes!(T, plot) image(image) Plots an image on a rectangle bounded by `x` and `y` (defaults to size of image). - -## Attributes - -### Specific to `Image` - -- `interpolate::Bool = true` sets whether colors should be interpolated. - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Image, x, y, image) do scene - attr = Attributes(; - interpolate = true, - fxaa = false, - ) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, [:black, :white]) +@recipe Image x y image begin + "Sets whether colors should be interpolated between pixels." + interpolate = true + mixin_generic_plot_attributes()... + mixin_colormap_attributes()... + fxaa = false + colormap = [:black, :white] end """ @@ -171,27 +225,12 @@ Pairs that are missing from the resulting grid will be treated as if `zvector` h If `x` and `y` are omitted with a matrix argument, they default to `x, y = axes(matrix)`. Note that `heatmap` is slower to render than `image` so `image` should be preferred for large, regularly spaced grids. - -## Attributes - -### Specific to `Heatmap` - -- `interpolate::Bool = false` sets whether colors should be interpolated. - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Heatmap, x, y, values) do scene - attr = Attributes(; - - interpolate = false, - - linewidth = 0.0, - fxaa = true, - ) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) +@recipe Heatmap x y values begin + "Sets whether colors should be interpolated" + interpolate = false + mixin_generic_plot_attributes()... + mixin_colormap_attributes()... end """ @@ -206,34 +245,23 @@ Available algorithms are: * `:absorptionrgba` => AbsorptionRGBA * `:additive` => AdditiveRGBA * `:indexedabsorption` => IndexedAbsorptionRGBA - -## Attributes - -### Specific to `Volume` - -- `algorithm::Union{Symbol, RaymarchAlgorithm} = :mip` sets the volume algorithm that is used. -- `isorange::Real = 0.05` sets the range of values picked up by the IsoValue algorithm. -- `isovalue = 0.5` sets the target value for the IsoValue algorithm. -- `interpolate::Bool = true` sets whether the volume data should be sampled with interpolation. - -$(Base.Docs.doc(shading_attributes!)) - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Volume, x, y, z, volume) do scene - attr = Attributes(; - - algorithm = :mip, - isovalue = 0.5, - isorange = 0.05, - interpolate = true, - fxaa = true, - ) - generic_plot_attributes!(attr) - shading_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) +@recipe Volume x y z volume begin + "Sets the volume algorithm that is used." + algorithm = :mip + "Sets the range of values picked up by the IsoValue algorithm." + isovalue = 0.5 + "Sets the target value for the IsoValue algorithm." + isorange = 0.05 + "Sets whether the volume data should be sampled with interpolation." + interpolate = true + "Enables depth write for Volume, so that volume correctly occludes other objects." + enable_depth = true + "Absorption multiplier for algorithm=:absorption. This changes how much light each voxel absorbs." + absorption = 1f0 + mixin_generic_plot_attributes()... + mixin_shading_attributes()... + mixin_colormap_attributes()... end """ @@ -242,30 +270,15 @@ end Plots a surface, where `(x, y)` define a grid whose heights are the entries in `z`. `x` and `y` may be `Vectors` which define a regular grid, **or** `Matrices` which define an irregular grid. - -## Attributes - -### Specific to `Surface` - -- `invert_normals::Bool = false` inverts the normals generated for the surface. This can be useful to illuminate the other side of the surface. -- `color = nothing`, can be set to an `Matrix{<: Union{Number, Colorant}}` to color surface independent of the `z` component. If `color=nothing`, it defaults to `color=z`. - -$(Base.Docs.doc(shading_attributes!)) - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Surface, x, y, z) do scene - attr = Attributes(; - color = nothing, - invert_normals = false, - - fxaa = true, - ) - shading_attributes!(attr) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) +@recipe Surface x y z begin + "Can be set to an `Matrix{<: Union{Number, Colorant}}` to color surface independent of the `z` component. If `color=nothing`, it defaults to `color=z`." + color = nothing + "Inverts the normals generated for the surface. This can be useful to illuminate the other side of the surface." + invert_normals = false + mixin_generic_plot_attributes()... + mixin_shading_attributes()... + mixin_colormap_attributes()... end """ @@ -276,33 +289,19 @@ end Creates a connected line plot for each element in `(x, y, z)`, `(x, y)` or `positions`. `NaN` values are displayed as gaps in the line. - -## Attributes - -### Specific to `Lines` - -- `color=theme(scene, :linecolor)` sets the color of the line. If no color is set, multiple calls to `line!` will cycle through the axis color palette. - Otherwise, one can set one color per line point by passing a `Vector{<:Colorant}`, or one colorant for the whole line. If color is a vector of numbers, the colormap args are used to map the numbers to colors. -- `cycle::Vector{Symbol} = [:color]` sets which attributes to cycle when creating multiple plots. -- `linestyle::Union{Nothing, Symbol, Linestyle} = nothing` sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`. -- `linewidth::Union{Real, Vector} = 1.5` sets the width of the line in pixel units. Note that linewidth may change between NaN-separated lines, but not per-point. - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Lines, positions) do scene - attr = Attributes(; - - color = theme(scene, :linecolor), - linewidth = theme(scene, :linewidth), - - linestyle = nothing, - fxaa = false, - cycle = [:color], - ) - generic_plot_attributes!(attr, ) - return colormap_attributes!(attr, theme(scene, :colormap)) +@recipe Lines positions begin + "The color of the line." + color = @inherit linecolor + "Sets the width of the line in screen units" + linewidth = @inherit linewidth + "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" + linestyle = nothing + "Sets which attributes to cycle when creating multiple plots." + cycle = [:color] + mixin_generic_plot_attributes()... + mixin_colormap_attributes()... + fxaa = false end """ @@ -327,8 +326,18 @@ $(Base.Docs.doc(colormap_attributes!)) $(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(LineSegments, positions) do scene - default_theme(scene, Lines) +@recipe LineSegments positions begin + "The color of the line." + color = @inherit linecolor + "Sets the width of the line in pixel units" + linewidth = @inherit linewidth + "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" + linestyle = nothing + "Sets which attributes to cycle when creating multiple plots." + cycle = [:color] + mixin_generic_plot_attributes()... + mixin_colormap_attributes()... + fxaa = false end # alternatively, mesh3d? Or having only mesh instead of poly + mesh and figure out 2d/3d via dispatch @@ -339,33 +348,17 @@ end mesh(xyz, faces) Plots a 3D or 2D mesh. Supported `mesh_object`s include `Mesh` types from [GeometryBasics.jl](https://github.com/JuliaGeometry/GeometryBasics.jl). - -## Attributes - -### Specific to `Mesh` - -- `color=theme(scene, :patchcolor)` sets the color of the mesh. Can be a `Vector{<:Colorant}` for per vertex colors or a single `Colorant`. - A `Matrix{<:Colorant}` can be used to color the mesh with a texture, which requires the mesh to contain texture coordinates. - Vector or Matrices of numbers can be used as well, which will use the colormap arguments to map the numbers to colors. -- `interpolate::Bool = false` sets whether colors should be interpolated. - -$(Base.Docs.doc(shading_attributes!)) - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Mesh, mesh) do scene - attr = Attributes(; - color = :black, - interpolate = true, - - fxaa = true, - cycle = [:color => :patchcolor], - ) - shading_attributes!(attr) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) +@recipe Mesh mesh begin + "Sets the color of the mesh. Can be a `Vector{<:Colorant}` for per vertex colors or a single `Colorant`. A `Matrix{<:Colorant}` can be used to color the mesh with a texture, which requires the mesh to contain texture coordinates." + color = @inherit patchcolor + "sets whether colors should be interpolated" + interpolate = true + cycle = [:color => :patchcolor] + matcap = nothing + mixin_generic_plot_attributes()... + mixin_shading_attributes()... + mixin_colormap_attributes()... end """ @@ -374,53 +367,41 @@ end scatter(x, y, z) Plots a marker for each element in `(x, y, z)`, `(x, y)`, or `positions`. - -## Attributes - -### Specific to `Scatter` - -- `color=theme(scene, :markercolor)` sets the color of the marker. If no color is set, multiple calls to `scatter!` will cycle through the axis color palette. - Otherwise, one can set one color per point by passing a `Vector{<:Colorant}`, or one colorant for the whole scatterplot. If color is a vector of numbers, the colormap args are used to map the numbers to colors. -- `cycle::Vector{Symbol} = [:color]` sets which attributes to cycle when creating multiple plots. -- `marker::Union{Symbol, Char, Matrix{<:Colorant}, BezierPath, Polygon}` sets the scatter marker. -- `markersize::Union{<:Real, Vec2f} = 9` sets the size of the marker. -- `markerspace::Symbol = :pixel` sets the space in which `markersize` is given. See `Makie.spaces()` for possible inputs. -- `strokewidth::Real = 0` sets the width of the outline around a marker. -- `strokecolor::Union{Symbol, <:Colorant} = :black` sets the color of the outline around a marker. -- `glowwidth::Real = 0` sets the size of a glow effect around the marker. -- `glowcolor::Union{Symbol, <:Colorant} = (:black, 0)` sets the color of the glow effect. -- `rotations::Union{Real, Billboard, Quaternion} = Billboard(0f0)` sets the rotation of the marker. A `Billboard` rotation is always around the depth axis. -- `transform_marker::Bool = false` controls whether the model matrix (without translation) applies to the marker itself, rather than just the positions. (If this is true, `scale!` and `rotate!` will affect the marker.) - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Scatter, positions) do scene - attr = Attributes(; - color = theme(scene, :markercolor), - - marker = theme(scene, :marker), - markersize = theme(scene, :markersize), - - strokecolor = theme(scene, :markerstrokecolor), - strokewidth = theme(scene, :markerstrokewidth), - glowcolor = (:black, 0.0), - glowwidth = 0.0, - - rotations = Billboard(), - marker_offset = automatic, - - transform_marker = false, # Applies the plots transformation to marker - distancefield = nothing, - uv_offset_width = (0.0, 0.0, 0.0, 0.0), - markerspace = :pixel, - - fxaa = false, - cycle = [:color], - ) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) +@recipe Scatter positions begin + "Sets the color of the marker. If no color is set, multiple calls to `scatter!` will cycle through the axis color palette." + color = @inherit markercolor + "Sets the scatter marker." + marker = @inherit marker + "Sets the size of the marker." + markersize = @inherit markersize + "Sets the color of the outline around a marker." + strokecolor = @inherit markerstrokecolor + "Sets the width of the outline around a marker." + strokewidth = @inherit markerstrokewidth + "Sets the color of the glow effect around the marker." + glowcolor = (:black, 0.0) + "Sets the size of a glow effect around the marker." + glowwidth = 0.0 + + "Sets the rotation of the marker. A `Billboard` rotation is always around the depth axis." + rotations = Billboard() + "The offset of the marker from the given position in `markerspace` units. Default is centered around the position (markersize * -0.5)." + marker_offset = automatic + "Controls whether the model matrix (without translation) applies to the marker itself, rather than just the positions. (If this is true, `scale!` and `rotate!` will affect the marker." + transform_marker = false + "Optional distancefield used for e.g. font and bezier path rendering. Will get set automatically." + distancefield = nothing + uv_offset_width = (0.0, 0.0, 0.0, 0.0) + "Sets the space in which `markersize` is given. See `Makie.spaces()` for possible inputs" + markerspace = :pixel + "Sets which attributes to cycle when creating multiple plots" + cycle = [:color] + "Enables depth-sorting of markers which can improve border artifacts. Currently supported in GLMakie only." + depthsorting = false + mixin_generic_plot_attributes()... + mixin_colormap_attributes()... + fxaa = false end """ @@ -430,39 +411,20 @@ end Plots a mesh for each element in `(x, y, z)`, `(x, y)`, or `positions` (similar to `scatter`). `markersize` is a scaling applied to the primitive passed as `marker`. - -## Attributes - -### Specific to `MeshScatter` - -- `color = theme(scene, :markercolor)` sets the color of the marker. If no color is set, multiple calls to `meshscatter!` will cycle through the axis color palette. - Otherwise, one can set one color per point by passing a `Vector{<:Colorant}`, or one colorant for the whole meshscatterplot. If color is a vector of numbers, the colormap args are used to map the numbers to colors. -- `cycle::Vector{Symbol} = [:color]` sets which attributes to cycle when creating multiple plots. -- `marker::Union{Symbol, GeometryBasics.GeometryPrimitive, GeometryBasics.Mesh}` sets the scattered mesh. -- `markersize::Union{<:Real, Vec3f} = 0.1` sets the scale of the mesh. This can be given as a Vector to apply to each scattered mesh individually. -- `rotations::Union{Real, Vec3f, Quaternion} = 0` sets the rotation of the mesh. A numeric rotation is around the z-axis, a `Vec3f` causes the mesh to rotate such that the the z-axis is now that vector, and a quaternion describes a general rotation. This can be given as a Vector to apply to each scattered mesh individually. - -$(Base.Docs.doc(shading_attributes!)) - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(MeshScatter, positions) do scene - attr = Attributes(; - color = theme(scene, :markercolor), - - marker = :Sphere, - markersize = 0.1, - rotations = 0.0, - space = :data, - - fxaa = true, - cycle = [:color], - ) - shading_attributes!(attr) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) +@recipe MeshScatter positions begin + "Sets the color of the marker." + color = @inherit markercolor + "Sets the scattered mesh." + marker = :Sphere + "Sets the scale of the mesh. This can be given as a `Vector` to apply to each scattered mesh individually." + markersize = 0.1 + "Sets the rotation of the mesh. A numeric rotation is around the z-axis, a `Vec3f` causes the mesh to rotate such that the the z-axis is now that vector, and a quaternion describes a general rotation. This can be given as a Vector to apply to each scattered mesh individually." + rotations = 0.0 + cycle = [:color] + mixin_generic_plot_attributes()... + mixin_shading_attributes()... + mixin_colormap_attributes()... end """ @@ -472,52 +434,49 @@ end Plots one or multiple texts passed via the `text` keyword. `Text` uses the `PointBased` conversion trait. - -## Attributes - -### Specific to `Text` - -- `color=theme(scene, :textcolor)` sets the color of the text. One can set one color per glyph by passing a `Vector{<:Colorant}`, or one colorant for the whole text. If color is a vector of numbers, the colormap args are used to map the numbers to colors. -- `text` specifies one piece of text or a vector of texts to show, where the number has to match the number of positions given. Makie supports `String` which is used for all normal text and `LaTeXString` which layouts mathematical expressions using `MathTeXEngine.jl`. -- `align::Tuple{Union{Symbol, Real}, Union{Symbol, Real}} = (:left, :bottom)` sets the alignment of the string w.r.t. `position`. Uses `:left, :center, :right, :top, :bottom, :baseline` or fractions. -- `font::Union{String, Vector{String}} = :regular` sets the font for the string or each character. -- `justification::Union{Real, Symbol} = automatic` sets the alignment of text w.r.t its bounding box. Can be `:left, :center, :right` or a fraction. Will default to the horizontal alignment in `align`. -- `rotation::Union{Real, Quaternion}` rotates text around the given position. -- `fontsize::Union{Real, Vec2f}` sets the size of each character. -- `markerspace::Symbol = :pixel` sets the space in which `fontsize` acts. See `Makie.spaces()` for possible inputs. -- `strokewidth::Real = 0` sets the width of the outline around a marker. -- `strokecolor::Union{Symbol, <:Colorant} = :black` sets the color of the outline around a marker. -- `glowwidth::Real = 0` sets the size of a glow effect around the marker. -- `glowcolor::Union{Symbol, <:Colorant} = (:black, 0)` sets the color of the glow effect. -- `word_wrap_width::Real = -1` specifies a linewidth limit for text. If a word overflows this limit, a newline is inserted before it. Negative numbers disable word wrapping. -- `transform_marker::Bool = false` controls whether the model matrix (without translation) applies to the glyph itself, rather than just the positions. (If this is true, `scale!` and `rotate!` will affect the text glyphs.) - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Text, positions) do scene - attr = Attributes(; - color = theme(scene, :textcolor), - - font = theme(scene, :font), - fonts = theme(scene, :fonts), - - strokecolor = (:black, 0.0), - strokewidth = 0, - align = (:left, :bottom), - rotation = 0.0, - fontsize = theme(scene, :fontsize), - position = (0.0, 0.0), - justification = automatic, - lineheight = 1.0, - markerspace = :pixel, - transform_marker = false, - offset = (0.0, 0.0), - word_wrap_width = -1, +@recipe Text positions begin + "Specifies one piece of text or a vector of texts to show, where the number has to match the number of positions given. Makie supports `String` which is used for all normal text and `LaTeXString` which layouts mathematical expressions using `MathTeXEngine.jl`." + text = "" + "Sets the color of the text. One can set one color per glyph by passing a `Vector{<:Colorant}`, or one colorant for the whole text. If color is a vector of numbers, the colormap args are used to map the numbers to colors." + color = @inherit textcolor + "Sets the font. Can be a `Symbol` which will be looked up in the `fonts` dictionary or a `String` specifying the (partial) name of a font or the file path of a font file" + font = @inherit font + "Used as a dictionary to look up fonts specified by `Symbol`, for example `:regular`, `:bold` or `:italic`." + fonts = @inherit fonts + "Sets the color of the outline around a marker." + strokecolor = (:black, 0.0) + "Sets the width of the outline around a marker." + strokewidth = 0 + "Sets the alignment of the string w.r.t. `position`. Uses `:left, :center, :right, :top, :bottom, :baseline` or fractions." + align = (:left, :bottom) + "Rotates text around the given position" + rotation = 0.0 + "The fontsize in units depending on `markerspace`." + fontsize = @inherit fontsize + "Deprecated: Specifies the position of the text. Use the positional argument to `text` instead." + position = (0.0, 0.0) + "Sets the alignment of text w.r.t its bounding box. Can be `:left, :center, :right` or a fraction. Will default to the horizontal alignment in `align`." + justification = automatic + "The lineheight multiplier." + lineheight = 1.0 + "Sets the space in which `fontsize` acts. See `Makie.spaces()` for possible inputs." + markerspace = :pixel + "Controls whether the model matrix (without translation) applies to the glyph itself, rather than just the positions. (If this is true, `scale!` and `rotate!` will affect the text glyphs.)" + transform_marker = false + "The offset of the text from the given position in `markerspace` units." + offset = (0.0, 0.0) + "Specifies a linewidth limit for text. If a word overflows this limit, a newline is inserted before it. Negative numbers disable word wrapping." + word_wrap_width = -1 + mixin_generic_plot_attributes()... + mixin_colormap_attributes()... + fxaa = false +end + +function deprecated_attributes(::Type{<:Text}) + ( + (; attribute = :textsize, message = "`textsize` has been renamed to `fontsize` in Makie v0.19. Please change all occurrences of `textsize` to `fontsize` or revert back to an earlier version.", error = true), ) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) end """ @@ -536,76 +495,93 @@ When a shape is given (essentially anything decomposable by `GeometryBasics`), i Plots polygons, which are defined by `coordinates` (the coordinates of the vertices) and `connectivity` (the edges between the vertices). - -## Attributes - -### Specific to `Poly` -- `color=theme(scene, :patchcolor)` sets the color of the poly. Can be a `Vector{<:Colorant}` for per vertex colors or a single `Colorant`. - A `Matrix{<:Colorant}` can be used to color the mesh with a texture, which requires the mesh to contain texture coordinates. - Vector or Matrices of numbers can be used as well, which will use the colormap arguments to map the numbers to colors. - One can also use `Makie.LinePattern`, to cover the poly with a regular stroke pattern. -- `strokecolor::Union{Symbol, <:Colorant} = :black` sets the color of the outline around a marker. -- `strokecolormap`::Union{Symbol, Vector{<:Colorant}} = :viridis` sets the colormap that is sampled for numeric `color`s. -- `strokewidth::Real = 0` sets the width of the outline around a marker. -- `linestyle::Union{Nothing, Symbol, Vector} = nothing` sets the pattern of the line (e.g. `:solid`, `:dot`, `:dashdot`) - -$(Base.Docs.doc(colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(Poly) do scene - attr = Attributes(; - color = theme(scene, :patchcolor), - - strokecolor = theme(scene, :patchstrokecolor), - strokecolormap = theme(scene, :colormap), - strokewidth = theme(scene, :patchstrokewidth), - linestyle = nothing, - - shading = NoShading, - fxaa = true, - - cycle = [:color => :patchcolor], - ) - generic_plot_attributes!(attr) - return colormap_attributes!(attr, theme(scene, :colormap)) -end - -@recipe(Wireframe) do scene - attr = Attributes(; - depth_shift = -1f-5, - ) - return merge!(attr, default_theme(scene, LineSegments)) +@recipe Poly begin + """ + Sets the color of the poly. Can be a `Vector{<:Colorant}` for per vertex colors or a single `Colorant`. + A `Matrix{<:Colorant}` can be used to color the mesh with a texture, which requires the mesh to contain texture coordinates. + Vector or Matrices of numbers can be used as well, which will use the colormap arguments to map the numbers to colors. + One can also use `Makie.LinePattern`, to cover the poly with a regular stroke pattern. + """ + color = @inherit patchcolor + "Sets the color of the outline around a marker." + strokecolor = @inherit patchstrokecolor + "Sets the colormap that is sampled for numeric `color`s." + strokecolormap = @inherit colormap + "Sets the width of the outline." + strokewidth = @inherit patchstrokewidth + "Sets the pattern of the line (e.g. `:solid`, `:dot`, `:dashdot`)" + linestyle = nothing + + shading = NoShading + + cycle = [:color => :patchcolor] + + mixin_generic_plot_attributes()... + mixin_colormap_attributes()... end -@recipe(Arrows, points, directions) do scene - attr = Attributes( - color = :black, - - arrowsize = automatic, - arrowhead = automatic, - arrowtail = automatic, - - linecolor = automatic, - linestyle = nothing, - align = :origin, - - normalize = false, - lengthscale = 1f0, - - colorscale = identity, - - quality = 32, - markerspace = :pixel, - ) +""" + wireframe(x, y, z) + wireframe(positions) + wireframe(mesh) - generic_plot_attributes!(attr) - shading_attributes!(attr) - colormap_attributes!(attr, theme(scene, :colormap)) +Draws a wireframe, either interpreted as a surface or as a mesh. +""" +@recipe Wireframe begin + documented_attributes(LineSegments)... + depth_shift = -1f-5 +end - attr[:fxaa] = automatic - attr[:linewidth] = automatic - # connect arrow + linecolor by default - get!(attr, :arrowcolor, attr[:linecolor]) - return attr +@recipe Arrows points directions begin + "Sets the color of arrowheads and lines. Can be overridden separately using `linecolor` and `arrowcolor`." + color = :black + """Scales the size of the arrow head. This defaults to + `0.3` in the 2D case and `Vec3f(0.2, 0.2, 0.3)` in the 3D case. For the latter + the first two components scale the radius (in x/y direction) and the last scales + the length of the cone. If the arrowsize is set to 1, the cone will have a + diameter and length of 1.""" + arrowsize = automatic + """Defines the marker (2D) or mesh (3D) that is used as + the arrow head. The default for is `'▲'` in 2D and a cone mesh in 3D. For the + latter the mesh should start at `Point3f(0)` and point in positive z-direction.""" + arrowhead = automatic + """Defines the mesh used to draw the arrow tail in 3D. + It should start at `Point3f(0)` and extend in negative z-direction. The default + is a cylinder. This has no effect on the 2D plot.""" + arrowtail = automatic + """Sets the color used for the arrow tail which is represented by a line in 2D. + Will copy `color` if set to `automatic`. + """ + linecolor = automatic + """Sets the linestyle used in 2D. Does not apply to 3D plots.""" + linestyle = nothing + """Sets how arrows are positioned. By default arrows start at + the given positions and extend along the given directions. If this attribute is + set to `:head`, `:lineend`, `:tailend`, `:headstart` or `:center` the given + positions will be between the head and tail of each arrow instead.""" + align = :origin + """By default the lengths of the directions given to `arrows` + are used to scale the length of the arrow tails. If this attribute is set to + true the directions are normalized, skipping this scaling.""" + normalize = false + """Scales the length of the arrow tail.""" + lengthscale = 1f0 + + """Defines the number of angle subdivisions used when generating + the arrow head and tail meshes. Consider lowering this if you have performance + issues. Only applies to 3D plots.""" + quality = 32 + markerspace = :pixel + + mixin_generic_plot_attributes()... + mixin_shading_attributes()... + mixin_colormap_attributes()... + + fxaa = automatic + """Scales the width/diameter of the arrow tail. + Defaults to `1` for 2D and `0.05` for the 3D case.""" + linewidth = automatic + """Sets the color of the arrow head. Will copy `color` if set to `automatic`.""" + arrowcolor = automatic end diff --git a/MakieCore/src/recipes.jl b/MakieCore/src/recipes.jl index 2e31672853c..3e52e57629d 100644 --- a/MakieCore/src/recipes.jl +++ b/MakieCore/src/recipes.jl @@ -189,6 +189,378 @@ macro recipe(theme_func, Tsym::Symbol, args::Symbol...) expr end +function attribute_names end +function documented_attributes end # this can be used for inheriting from other recipes + +attribute_names(_) = nothing + +Base.@kwdef struct AttributeMetadata + docstring::Union{Nothing,String} + default_expr::String # stringified expression, just needed for docs purposes +end + +update_metadata(am1::AttributeMetadata, am2::AttributeMetadata) = AttributeMetadata( + am2.docstring === nothing ? am1.docstring : am2.docstring, + am2.default_expr # TODO: should it be possible to overwrite only a docstring by not giving a default expr? +) + +struct DocumentedAttributes + d::Dict{Symbol,AttributeMetadata} + closure::Function +end + +macro DocumentedAttributes(expr::Expr) + if !(expr isa Expr && expr.head === :block) + throw(ArgumentError("Argument is not a begin end block")) + end + + metadata_exprs = [] + closure_exprs = [] + mixin_exprs = Expr[] + + for arg in expr.args + arg isa LineNumberNode && continue + + has_docs = arg isa Expr && arg.head === :macrocall && arg.args[1] isa GlobalRef + + if has_docs + docs = arg.args[3] + attr = arg.args[4] + else + docs = nothing + attr = arg + end + + is_attr_line = attr isa Expr && attr.head === :(=) && length(attr.args) == 2 + is_mixin_line = attr isa Expr && attr.head === :(...) && length(attr.args) == 1 + if !(is_attr_line || is_mixin_line) + error("$attr is neither a valid attribute line like `x = default_value` nor a mixin line like `some_mixin...`") + end + + if is_attr_line + sym = attr.args[1] + default = attr.args[2] + if !(sym isa Symbol) + error("$sym should be a symbol") + end + + push!(metadata_exprs, quote + am = AttributeMetadata(; docstring = $docs, default_expr = $(_default_expr_string(default))) + if haskey(d, $(QuoteNode(sym))) + d[$(QuoteNode(sym))] = update_metadata(d[$(QuoteNode(sym))], am) + else + d[$(QuoteNode(sym))] = am + end + end) + + if default isa Expr && default.head === :macrocall && default.args[1] === Symbol("@inherit") + if length(default.args) ∉ (3, 4) + error("@inherit works with 1 or 2 arguments, expression was $d") + end + if !(default.args[3] isa Symbol) + error("Argument 1 of @inherit must be a Symbol, got $(default.args[3])") + end + key = default.args[3] + _default = get(default.args, 4, :(error("Inherited key $($(QuoteNode(key))) not found in theme with no fallback given."))) + # first check scene theme + # then default value + d = :( + dict[$(QuoteNode(sym))] = if haskey(thm, $(QuoteNode(key))) + to_value(thm[$(QuoteNode(key))]) # only use value of theme entry + else + $(esc(_default)) + end + ) + push!(closure_exprs, d) + else + push!(closure_exprs, :( + dict[$(QuoteNode(sym))] = $(esc(default)) + )) + end + elseif is_mixin_line + # this intermediate variable is needed to evaluate each mixin only once + # and is inserted at the start of the final code block + gsym = gensym("mixin") + mixin = only(attr.args) + push!(mixin_exprs, quote + $gsym = $(esc(mixin)) + if !($gsym isa DocumentedAttributes) + error("Mixin was not a DocumentedAttributes but $($gsym)") + end + end) + + # the actual runtime values of the mixed in defaults + # are computed using the closure stored in the DocumentedAttributes + closure_exp = quote + # `scene` and `dict` here are defined below where this exp is interpolated into + merge!(dict, $gsym.closure(scene)) + end + push!(closure_exprs, closure_exp) + + # docstrings and default expressions of the mixed in + # DocumentedAttributes are inserted + metadata_exp = quote + for (key, value) in $gsym.d + if haskey(d, key) + error("Mixin `$($(QuoteNode(mixin)))` had the key :$key which already existed. It's not allowed for mixins to overwrite keys to avoid accidental overwrites. Drop those keys from the mixin first.") + end + d[key] = value + end + end + push!(metadata_exprs, metadata_exp) + else + error("Unreachable") + end + end + + quote + $(mixin_exprs...) + d = Dict{Symbol,AttributeMetadata}() + $(metadata_exprs...) + closure = function (scene) + thm = theme(scene) + dict = Dict{Symbol,Any}() + $(closure_exprs...) + return dict + end + DocumentedAttributes(d, closure) + end +end + +function is_attribute(T::Type{<:Plot}, sym::Symbol) + sym in attribute_names(T) +end + +function attribute_default_expressions(T::Type{<:Plot}) + Dict(k => v.default_expr for (k, v) in documented_attributes(T).d) +end + +function _attribute_docs(T::Type{<:Plot}) + Dict(k => v.docstring for (k, v) in documented_attributes(T).d) +end + + +macro recipe(Tsym::Symbol, args...) + + funcname_sym = to_func_name(Tsym) + funcname!_sym = Symbol("$(funcname_sym)!") + funcname! = esc(funcname!_sym) + PlotType = esc(Tsym) + funcname = esc(funcname_sym) + + syms = args[1:end-1] + for sym in syms + sym isa Symbol || throw(ArgumentError("Found argument that is not a symbol in the position where optional argument names should appear: $sym")) + end + attrblock = args[end] + if !(attrblock isa Expr && attrblock.head === :block) + throw(ArgumentError("Last argument is not a begin end block")) + end + # attrblock = expand_mixins(attrblock) + # attrs = [extract_attribute_metadata(arg) for arg in attrblock.args if !(arg isa LineNumberNode)] + + docs_placeholder = gensym() + + attr_placeholder = gensym() + + q = quote + # This part is as far as I know the only way to modify the docstring on top of the + # recipe, so that we can offer the convenience of automatic augmented docstrings + # but combine them with the simplicity of using a normal docstring. + # The trick is to mark some variable (in this case a gensymmed placeholder) with the + # Core.@__doc__ macro, which causes this variable to get assigned the docstring on top + # of the @recipe invocation. From there, it can then be retrieved, modified, and later + # attached to plotting function by using @doc again. We also delete the binding to the + # temporary variable so no unnecessary docstrings stay in place. + Core.@__doc__ $(esc(docs_placeholder)) = nothing + binding = Docs.Binding(@__MODULE__, $(QuoteNode(docs_placeholder))) + user_docstring = if haskey(Docs.meta(@__MODULE__), binding) + _docstring = @doc($docs_placeholder) + delete!(Docs.meta(@__MODULE__), binding) + _docstring + else + "No docstring defined.\n" + end + + + $(funcname)() = not_implemented_for($funcname) + const $(PlotType){$(esc(:ArgType))} = Plot{$funcname,$(esc(:ArgType))} + + # This weird syntax is so that the output of the macrocall can be escaped because it + # contains user expressions, without escaping what's passed to the macro because that + # messes with its transformation logic. Because we escape the whole block with the macro, + # we don't reference it by symbol but splice in the macro itself into the AST + # with `var"@DocumentedAttributes"` + const $attr_placeholder = $( + esc(Expr(:macrocall, var"@DocumentedAttributes", LineNumberNode(@__LINE__), attrblock)) + ) + + $(MakieCore).documented_attributes(::Type{<:$(PlotType)}) = $attr_placeholder + + $(MakieCore).plotsym(::Type{<:$(PlotType)}) = $(QuoteNode(Tsym)) + function ($funcname)(args...; kw...) + kwdict = Dict{Symbol, Any}(kw) + _create_plot($funcname, kwdict, args...) + end + function ($funcname!)(args...; kw...) + kwdict = Dict{Symbol, Any}(kw) + _create_plot!($funcname, kwdict, args...) + end + + function $(MakieCore).attribute_names(T::Type{<:$(PlotType)}) + keys(documented_attributes(T).d) + end + + function $(MakieCore).default_theme(scene, T::Type{<:$(PlotType)}) + Attributes(documented_attributes(T).closure(scene)) + end + + docstring_modified = make_recipe_docstring($PlotType, $(QuoteNode(Tsym)), $(QuoteNode(funcname_sym)),user_docstring) + @doc docstring_modified $funcname_sym + @doc "`$($(string(Tsym)))` is the plot type associated with plotting function `$($(string(funcname_sym)))`. Check the docstring for `$($(string(funcname_sym)))` for further information." $Tsym + @doc "`$($(string(funcname!_sym)))` is the mutating variant of plotting function `$($(string(funcname_sym)))`. Check the docstring for `$($(string(funcname_sym)))` for further information." $funcname!_sym + export $PlotType, $funcname, $funcname! + end + + if !isempty(syms) + push!( + q.args, + :( + $(esc(:($(MakieCore).argument_names)))(::Type{<:$PlotType}, len::Integer) = + $syms + ), + ) + end + + q +end + +function make_recipe_docstring(P::Type{<:Plot}, Tsym, funcname_sym, docstring) + io = IOBuffer() + + attr_docstrings = _attribute_docs(P) + + print(io, docstring) + + println(io, "## Plot type") + println(io, "The plot type alias for the `$funcname_sym` function is `$Tsym`.") + + println(io, "## Attributes") + println(io) + + names = sort(collect(attribute_names(P))) + exprdict = attribute_default_expressions(P) + for name in names + default = exprdict[name] + print(io, "**`", name, "`** = ", " `", default, "` — ") + println(io, something(attr_docstrings[name], "*No docs available.*")) + println(io) + end + + return String(take!(io)) +end + +# from MacroTools +isline(ex) = (ex isa Expr && ex.head === :line) || isa(ex, LineNumberNode) +rmlines(x) = x +function rmlines(x::Expr) + # Do not strip the first argument to a macrocall, which is + # required. + if x.head === :macrocall && length(x.args) >= 2 + Expr(x.head, x.args[1], nothing, filter(x->!isline(x), x.args[3:end])...) + else + Expr(x.head, filter(x->!isline(x), x.args)...) + end +end + +_default_expr_string(x) = string(rmlines(x)) +_default_expr_string(x::String) = repr(x) + +function extract_attribute_metadata(arg) + has_docs = arg isa Expr && arg.head === :macrocall && arg.args[1] isa GlobalRef + + if has_docs + docs = arg.args[3] + attr = arg.args[4] + else + docs = nothing + attr = arg + end + + if !(attr isa Expr && attr.head === :(=) && length(attr.args) == 2) + error("$attr is not a valid attribute line like :x[::Type] = default_value") + end + left = attr.args[1] + default = attr.args[2] + if left isa Symbol + attr_symbol = left + type = Any + else + if !(left isa Expr && left.head === :(::) && length(left.args) == 2) + error("$left is not a Symbol or an expression such as x::Type") + end + attr_symbol = left.args[1]::Symbol + type = left.args[2] + end + + (docs = docs, symbol = attr_symbol, type = type, default = default) +end + +function make_default_theme_expr(attrs, scenesym::Symbol) + + exprs = map(attrs) do a + + d = a.default + if d isa Expr && d.head === :macrocall && d.args[1] == Symbol("@inherit") + if length(d.args) != 4 + error("@inherit works with exactly 2 arguments, expression was $d") + end + if !(d.args[3] isa QuoteNode) + error("Argument 1 of @inherit must be a :symbol, got $(d.args[3])") + end + key, default = d.args[3:4] + # first check scene theme + # then default value + d = quote + if haskey(thm, $key) + to_value(thm[$key]) # only use value of theme entry + else + $default + end + end + end + + :(attr[$(QuoteNode(a.symbol))] = $d) + end + + quote + thm = theme($scenesym) + attr = Attributes() + $(exprs...) + attr + end +end + +function expand_mixins(attrblock::Expr) + Expr(:block, mapreduce(expand_mixin, vcat, attrblock.args)...) +end + +expand_mixin(x) = x +function expand_mixin(e::Expr) + if e.head === :macrocall && e.args[1] === Symbol("@mixin") + if length(e.args) != 3 && e.args[2] isa LineNumberNode && e.args[3] isa Symbol + error("Invalid mixin, needs to be of the format `@mixin some_mixin`, got $e") + end + mixin_ex = getproperty(MakieCore, e.args[3])()::Expr + if (mixin_ex.head !== :block) + error("Expected mixin to be a block expression (such as generated by `quote`)") + end + return mixin_ex.args + else + e + end +end + """ Plot(args::Vararg{<:DataType,N}) @@ -233,3 +605,98 @@ e.g.: ``` """ plottype(plot_args...) = Plot{plot} # default to dispatch to type recipes! + +# plot types can overload this to throw errors or show warnings when deprecated attributes are used. +# this is easier than if every plot type added manual checks in its `plot!` methods +deprecated_attributes(_) = () + +struct InvalidAttributeError <: Exception + plottype::Type + attributes::Set{Symbol} +end + +function print_columns(io::IO, v::Vector{String}; gapsize = 2, row_major = true, cols = displaysize(io)[2]) + lens = length.(v) # for unicode ligatures etc this won't work, but we don't use those for attribute names + function col_widths(ncols) + max_widths = zeros(Int, ncols) + for (i, len) in enumerate(lens) + j = mod1(i, ncols) + max_widths[j] = max(max_widths[j], len) + end + return max_widths + end + ncols = 1 + while true + widths = col_widths(ncols) + aggregated_width = (sum(widths) + (ncols-1) * gapsize) + if aggregated_width > cols + ncols = max(1, ncols-1) + break + end + ncols += 1 + end + widths = col_widths(ncols) + + for (i, (str, len)) in enumerate(zip(v, lens)) + j = mod1(i, ncols) + last_col = j == ncols + print(io, str) + remaining = widths[j] - len + !last_col * gapsize + for _ in 1:remaining + print(io, ' ') + end + if last_col + print(io, '\n') + end + end + + return +end + +function Base.showerror(io::IO, i::InvalidAttributeError) + n = length(i.attributes) + print(io, "Invalid attribute$(n > 1 ? "s" : "") ") + for (j, att) in enumerate(i.attributes) + j > 1 && print(io, j == length(i.attributes) ? " and " : ", ") + printstyled(io, att; color = :red, bold = true) + end + print(io, " for plot type ") + printstyled(io, i.plottype; color = :blue, bold = true) + println(io, ".") + nameset = sort(string.(collect(attribute_names(i.plottype)))) + println(io) + println(io, "The available plot attributes for $(i.plottype) are:") + println(io) + print_columns(io, nameset; cols = displaysize(stderr)[2]) + allowlist = attribute_name_allowlist() + println(io) + println(io) + println(io, "Generic attributes are:") + println(io) + print_columns(io, sort([string(a) for a in allowlist]); cols = displaysize(stderr)[2]) + println(io) +end + +function attribute_name_allowlist() + (:xautolimits, :yautolimits, :zautolimits, :label, :rasterize, :model, :transformation) +end + +function validate_attribute_keys(P::Type{<:Plot}, kw::Dict{Symbol}) + nameset = attribute_names(P) + nameset === nothing && return + allowlist = attribute_name_allowlist() + deprecations = deprecated_attributes(P)::Tuple{Vararg{NamedTuple{(:attribute, :message, :error), Tuple{Symbol, String, Bool}}}} + unknown = setdiff(keys(kw), nameset, allowlist, first.(deprecations)) + if !isempty(unknown) + throw(InvalidAttributeError(P, unknown)) + end + for (deprecated, message, should_error) in deprecations + if haskey(kw, deprecated) + if should_error + throw(ArgumentError(message)) + else + @warn message + end + end + end +end \ No newline at end of file diff --git a/MakieCore/src/types.jl b/MakieCore/src/types.jl index e80345d339c..d634647c2a4 100644 --- a/MakieCore/src/types.jl +++ b/MakieCore/src/types.jl @@ -79,6 +79,7 @@ mutable struct Plot{PlotFunc, T} <: ScenePlot{PlotFunc} parent::Union{AbstractScene,Plot} function Plot{Typ,T}(kw::Dict{Symbol, Any}, args::Vector{Any}, converted::NTuple{N, Observable}) where {Typ,T,N} + validate_attribute_keys(Plot{Typ}, kw) return new{Typ,T}(nothing, kw, args, converted, Attributes(), Plot[], Observables.ObserverFunction[]) end diff --git a/docs/reference/plots/arrows.md b/docs/reference/plots/arrows.md index 54cef0bb62b..c3a47e2b394 100644 --- a/docs/reference/plots/arrows.md +++ b/docs/reference/plots/arrows.md @@ -2,38 +2,6 @@ {{doc arrows}} -### Attributes - -- `arrowhead = automatic`: Defines the marker (2D) or mesh (3D) that is used as - the arrow head. The default for is `'▲'` in 2D and a cone mesh in 3D. For the - latter the mesh should start at `Point3f(0)` and point in positive z-direction. -- `arrowtail = automatic`: Defines the mesh used to draw the arrow tail in 3D. - It should start at `Point3f(0)` and extend in negative z-direction. The default - is a cylinder. This has no effect on the 2D plot. -- `quality = 32`: Defines the number of angle subdivisions used when generating - the arrow head and tail meshes. Consider lowering this if you have performance - issues. Only applies to 3D plots. -- `linecolor = :black`: Sets the color used for the arrow tail which is - represented by a line in 2D. -- `arrowcolor = linecolor`: Sets the color of the arrow head. -- `arrowsize = automatic`: Scales the size of the arrow head. This defaults to - `0.3` in the 2D case and `Vec3f(0.2, 0.2, 0.3)` in the 3D case. For the latter - the first two components scale the radius (in x/y direction) and the last scales - the length of the cone. If the arrowsize is set to 1, the cone will have a - diameter and length of 1. -- `linewidth = automatic`: Scales the width/diameter of the arrow tail. - Defaults to `1` for 2D and `0.05` for the 3D case. -- `lengthscale = 1f0`: Scales the length of the arrow tail. -- `linestyle = nothing`: Sets the linestyle used in 2D. Does not apply to 3D - plots. -- `normalize = false`: By default the lengths of the directions given to `arrows` - are used to scale the length of the arrow tails. If this attribute is set to - true the directions are normalized, skipping this scaling. -- `align = :origin`: Sets how arrows are positioned. By default arrows start at - the given positions and extend along the given directions. If this attribute is - set to `:head`, `:lineend`, `:tailend`, `:headstart` or `:center` the given - positions will be between the head and tail of each arrow instead. - ### Examples \begin{examplefigure}{} diff --git a/docs/reference/plots/lines.md b/docs/reference/plots/lines.md index 83c3e4f82e8..a41d6688d52 100644 --- a/docs/reference/plots/lines.md +++ b/docs/reference/plots/lines.md @@ -69,13 +69,13 @@ ps = rand(Point3f, 500) cs = rand(500) f = Figure(size = (600, 650)) Label(f[1, 1], "base", tellwidth = false) -lines(f[2, 1], ps, color = cs, markersize = 20, fxaa = false) +lines(f[2, 1], ps, color = cs, fxaa = false) Label(f[1, 2], "fxaa = true", tellwidth = false) -lines(f[2, 2], ps, color = cs, markersize = 20, fxaa = true) +lines(f[2, 2], ps, color = cs, fxaa = true) Label(f[3, 1], "transparency = true", tellwidth = false) -lines(f[4, 1], ps, color = cs, markersize = 20, transparency = true) +lines(f[4, 1], ps, color = cs, transparency = true) Label(f[3, 2], "overdraw = true", tellwidth = false) -lines(f[4, 2], ps, color = cs, markersize = 20, overdraw = true) +lines(f[4, 2], ps, color = cs, overdraw = true) f ``` \end{examplefigure} \ No newline at end of file diff --git a/docs/reference/plots/linesegments.md b/docs/reference/plots/linesegments.md index 2f336f9e2d4..d76ed361122 100644 --- a/docs/reference/plots/linesegments.md +++ b/docs/reference/plots/linesegments.md @@ -41,13 +41,13 @@ ps = rand(Point3f, 500) cs = rand(500) f = Figure(size = (600, 650)) Label(f[1, 1], "base", tellwidth = false) -linesegments(f[2, 1], ps, color = cs, markersize = 20, fxaa = false) +linesegments(f[2, 1], ps, color = cs, fxaa = false) Label(f[1, 2], "fxaa = true", tellwidth = false) -linesegments(f[2, 2], ps, color = cs, markersize = 20, fxaa = true) +linesegments(f[2, 2], ps, color = cs, fxaa = true) Label(f[3, 1], "transparency = true", tellwidth = false) -linesegments(f[4, 1], ps, color = cs, markersize = 20, transparency = true) +linesegments(f[4, 1], ps, color = cs, transparency = true) Label(f[3, 2], "overdraw = true", tellwidth = false) -linesegments(f[4, 2], ps, color = cs, markersize = 20, overdraw = true) +linesegments(f[4, 2], ps, color = cs, overdraw = true) f ``` \end{examplefigure} \ No newline at end of file diff --git a/docs/reference/plots/pie.md b/docs/reference/plots/pie.md index 78cee3f26f0..bec0f1e6868 100644 --- a/docs/reference/plots/pie.md +++ b/docs/reference/plots/pie.md @@ -2,27 +2,6 @@ {{doc pie}} - - -## Attributes - -### Generic - -- `normalize = true` sets whether the data will be normalized to the range [0, 2π]. -- `color` sets the color of the pie segments. It can be given as a single named color or a vector of the same length as the input data -- `strokecolor = :black` sets the color of the outline around the segments. -- `strokewidth = 1` sets the width of the outline around the segments. -- `vertex_per_deg = 1` defines the number of vertices per degree that are used to create the pie plot with polys. Increase if smoother circles are needed. -- `radius = 1` sets the radius for the pie plot. -- `inner_radius = 0` sets the inner radius of the plot. Choose as a value between 0 and `radius` to create a donut chart. -- `offset = 0` rotates the pie plot counterclockwise as given in radians. -- `transparency = false` adjusts how the plot deals with transparency. In GLMakie `transparency = true` results in using Order Independent Transparency. -- `inspectable = true` sets whether this plot should be seen by `DataInspector`. - -### Other - -Set the axis properties `autolimitaspect = 1` or `aspect = DataAspect()` to ensure that the pie chart looks like a circle and not an ellipsoid. - ## Examples \begin{examplefigure}{} diff --git a/docs/reference/plots/rainclouds.md b/docs/reference/plots/rainclouds.md index 2a5f434bcee..2233f2a3030 100644 --- a/docs/reference/plots/rainclouds.md +++ b/docs/reference/plots/rainclouds.md @@ -67,7 +67,7 @@ category_labels, data_array = mockup_categories_and_data_array(3) colors = Makie.wong_colors() rainclouds(category_labels, data_array; - xlabel = "Categories of Distributions", ylabel = "Samples", title = "My Title", + axis = (; xlabel = "Categories of Distributions", ylabel = "Samples", title = "My Title"), plot_boxplots = false, cloud_width=0.5, clouds=hist, hist_bins=50, color = colors[indexin(category_labels, unique(category_labels))]) ``` @@ -77,8 +77,8 @@ rainclouds(category_labels, data_array; \begin{examplefigure}{} ```julia rainclouds(category_labels, data_array; - ylabel = "Categories of Distributions", - xlabel = "Samples", title = "My Title", + axis = (; ylabel = "Categories of Distributions", + xlabel = "Samples", title = "My Title"), orientation = :horizontal, plot_boxplots = true, cloud_width=0.5, clouds=hist, color = colors[indexin(category_labels, unique(category_labels))]) @@ -88,8 +88,11 @@ rainclouds(category_labels, data_array; \begin{examplefigure}{} ```julia rainclouds(category_labels, data_array; - xlabel = "Categories of Distributions", - ylabel = "Samples", title = "My Title", + axis = (; + xlabel = "Categories of Distributions", + ylabel = "Samples", + title = "My Title" + ), plot_boxplots = true, cloud_width=0.5, clouds=hist, color = colors[indexin(category_labels, unique(category_labels))]) ``` @@ -99,7 +102,11 @@ rainclouds(category_labels, data_array; \begin{examplefigure}{} ```julia rainclouds(category_labels, data_array; - xlabel = "Categories of Distributions", ylabel = "Samples", title = "My Title", + axis = (; + xlabel = "Categories of Distributions", + ylabel = "Samples", + title = "My Title" + ), plot_boxplots = true, cloud_width=0.5, side = :right, violin_limits = extrema, color = colors[indexin(category_labels, unique(category_labels))]) ``` @@ -108,7 +115,11 @@ rainclouds(category_labels, data_array; \begin{examplefigure}{} ```julia rainclouds(category_labels, data_array; - xlabel = "Categories of Distributions", ylabel = "Samples", title = "My Title", + axis = (; + xlabel = "Categories of Distributions", + ylabel = "Samples", + title = "My Title", + ), plot_boxplots = true, cloud_width=0.5, side = :right, color = colors[indexin(category_labels, unique(category_labels))]) ``` @@ -119,7 +130,11 @@ rainclouds(category_labels, data_array; more_category_labels, more_data_array = mockup_categories_and_data_array(6) rainclouds(more_category_labels, more_data_array; - xlabel = "Categories of Distributions", ylabel = "Samples", title = "My Title", + axis = (; + xlabel = "Categories of Distributions", + ylabel = "Samples", + title = "My Title", + ), plot_boxplots = true, cloud_width=0.5, color = colors[indexin(more_category_labels, unique(more_category_labels))]) ``` @@ -129,8 +144,11 @@ rainclouds(more_category_labels, more_data_array; ```julia category_labels, data_array = mockup_categories_and_data_array(6) rainclouds(category_labels, data_array; - xlabel = "Categories of Distributions", - ylabel = "Samples", title = "My Title", + axis = (; + xlabel = "Categories of Distributions", + ylabel = "Samples", + title = "My Title", + ), plot_boxplots = true, cloud_width=0.5, color = colors[indexin(category_labels, unique(category_labels))]) ``` @@ -146,26 +164,30 @@ fig = Figure(size = (800*2, 600*5)) colors = [Makie.wong_colors(); Makie.wong_colors()] category_labels, data_array = mockup_categories_and_data_array(3) -rainclouds!(Axis(fig[1, 1]), category_labels, data_array; - title = "Left Side, with Box Plot", +rainclouds!( + Axis(fig[1, 1], title = "Left Side, with Box Plot"), + category_labels, data_array; side = :left, plot_boxplots = true, color = colors[indexin(category_labels, unique(category_labels))]) -rainclouds!(Axis(fig[2, 1]), category_labels, data_array; - title = "Left Side, without Box Plot", +rainclouds!( + Axis(fig[2, 1], title = "Left Side, without Box Plot"), + category_labels, data_array; side = :left, plot_boxplots = false, color = colors[indexin(category_labels, unique(category_labels))]) -rainclouds!(Axis(fig[1, 2]), category_labels, data_array; - title = "Right Side, with Box Plot", +rainclouds!( + Axis(fig[1, 2], title = "Right Side, with Box Plot"), + category_labels, data_array; side = :right, plot_boxplots = true, color = colors[indexin(category_labels, unique(category_labels))]) -rainclouds!(Axis(fig[2, 2]), category_labels, data_array; - title = "Right Side, without Box Plot", +rainclouds!( + Axis(fig[2, 2], title = "Right Side, without Box Plot"), + category_labels, data_array; side = :right, plot_boxplots = false, color = colors[indexin(category_labels, unique(category_labels))]) @@ -175,25 +197,27 @@ rainclouds!(Axis(fig[2, 2]), category_labels, data_array; # with and without clouds category_labels, data_array = mockup_categories_and_data_array(12) -rainclouds!(Axis(fig[3, 1:2]), category_labels, data_array; - title = "More categories. Default spacing.", +rainclouds!( + Axis(fig[3, 1:2], title = "More categories. Default spacing."), + category_labels, data_array; plot_boxplots = true, - dist_between_categories = 1.0, + gap = 1.0, color = colors[indexin(category_labels, unique(category_labels))]) -rainclouds!(Axis(fig[4, 1:2]), category_labels, data_array; - title = "More categories. Adjust space. (smaller cloud widths and smaller category distances)", +rainclouds!( + Axis(fig[4, 1:2], title = "More categories. Adjust space. (smaller cloud widths and smaller category distances)"), + category_labels, data_array; plot_boxplots = true, cloud_width = 0.3, - dist_between_categories = 0.5, + gap = 0.5, color = colors[indexin(category_labels, unique(category_labels))]) - -rainclouds!(Axis(fig[5, 1:2]), category_labels, data_array; - title = "More categories. Adjust space. No clouds.", +rainclouds!( + Axis(fig[5, 1:2], title = "More categories. Adjust space. No clouds."), + category_labels, data_array; plot_boxplots = true, clouds = nothing, - dist_between_categories = 0.5, + gap = 0.5, color = colors[indexin(category_labels, unique(category_labels))]) supertitle = Label(fig[0, :], "Cloud Plot Testing (Scatter, Violin, Boxplot)", fontsize=30) diff --git a/docs/reference/plots/stephist.md b/docs/reference/plots/stephist.md index cd4d6238045..43573242d38 100644 --- a/docs/reference/plots/stephist.md +++ b/docs/reference/plots/stephist.md @@ -14,7 +14,7 @@ data = randn(1000) f = Figure() stephist(f[1, 1], data, bins = 10) -stephist(f[1, 2], data, bins = 20, color = :red, strokewidth = 1, strokecolor = :black) +stephist(f[1, 2], data, bins = 20, color = :red, linewidth = 3) stephist(f[2, 1], data, bins = [-5, -2, -1, 0, 1, 2, 5], color = :gray) stephist(f[2, 2], data, normalization = :pdf) f diff --git a/src/basic_recipes/ablines.jl b/src/basic_recipes/ablines.jl index 3c2268b241e..95d14445597 100644 --- a/src/basic_recipes/ablines.jl +++ b/src/basic_recipes/ablines.jl @@ -3,16 +3,11 @@ Creates a line defined by `f(x) = slope * x + intercept` crossing a whole `Scene` with 2D projection at its current limits. You can pass one or multiple intercepts or slopes. - -All style attributes are the same as for `LineSegments`. """ -@recipe(ABLines) do scene - Theme(; - xautolimits = false, - yautolimits = false, - default_theme(LineSegments, scene)..., - cycle = :color, - ) +@recipe ABLines begin + xautolimits = false + yautolimits = false + MakieCore.documented_attributes(LineSegments)... end function Makie.plot!(p::ABLines) diff --git a/src/basic_recipes/annotations.jl b/src/basic_recipes/annotations.jl index 941614d2301..c792ad86501 100644 --- a/src/basic_recipes/annotations.jl +++ b/src/basic_recipes/annotations.jl @@ -2,12 +2,9 @@ annotations(strings::Vector{String}, positions::Vector{Point}) Plots an array of texts at each position in `positions`. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Annotations, text, position) do scene - default_theme(scene, Text) +@recipe Annotations text position begin + MakieCore.documented_attributes(Text)... end function convert_arguments(::Type{<: Annotations}, diff --git a/src/basic_recipes/arc.jl b/src/basic_recipes/arc.jl index b13042c402d..4445db15cf8 100644 --- a/src/basic_recipes/arc.jl +++ b/src/basic_recipes/arc.jl @@ -10,15 +10,11 @@ Examples: `arc(Point2f(0), 1, 0.0, π)` `arc(Point2f(1, 2), 0.3. π, -π)` - -## Attributes -$(ATTRIBUTES) """ -@recipe(Arc, origin, radius, start_angle, stop_angle) do scene - Attributes(; - default_theme(scene, Lines)..., - resolution = 361, - ) +@recipe Arc origin radius start_angle stop_angle begin + MakieCore.documented_attributes(Lines)... + "The number of line points approximating the arc." + resolution = 361 end function plot!(p::Arc) diff --git a/src/basic_recipes/arrows.jl b/src/basic_recipes/arrows.jl index c9a04553ea6..4fdd50d7bb3 100644 --- a/src/basic_recipes/arrows.jl +++ b/src/basic_recipes/arrows.jl @@ -22,9 +22,6 @@ grid. If a `Function` is provided in place of `u, v, [w]`, then it must accept a `Point` as input, and return an appropriately dimensioned `Point`, `Vec`, or other array-like output. - -## Attributes -$(ATTRIBUTES) """ arrows @@ -122,8 +119,8 @@ function plot!(arrowplot::Arrows{<: Tuple{AbstractVector{<: Point{N}}, V}}) wher fxaa, ssao, transparency, visible, inspectable ) - arrow_c = map((a, c)-> a === automatic ? c : a , arrowplot, arrowcolor, color) line_c = map((a, c)-> a === automatic ? c : a , arrowplot, linecolor, color) + arrow_c = map((a, c)-> a === automatic ? c : a , arrowplot, arrowcolor, color) fxaa_bool = lift(fxaa -> fxaa == automatic ? N == 3 : fxaa, arrowplot, fxaa) # automatic == fxaa for 3D marker_head = lift((ah, q) -> arrow_head(N, ah, q), arrowplot, arrowhead, quality) diff --git a/src/basic_recipes/band.jl b/src/basic_recipes/band.jl index 9a8de39b48d..6bdfd7ff5ec 100644 --- a/src/basic_recipes/band.jl +++ b/src/basic_recipes/band.jl @@ -4,17 +4,10 @@ Plots a band from `ylower` to `yupper` along `x`. The form `band(lower, upper)` plots a [ruled surface](https://en.wikipedia.org/wiki/Ruled_surface) between the points in `lower` and `upper`. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Band, lowerpoints, upperpoints) do scene - attr = Attributes(; - default_theme(scene, Mesh)..., - colorrange = automatic, - ) - attr[:shading][] = NoShading - attr +@recipe Band lowerpoints upperpoints begin + MakieCore.documented_attributes(Mesh)... + shading = NoShading end convert_arguments(::Type{<: Band}, x, ylower, yupper) = (Point2f.(x, ylower), Point2f.(x, yupper)) diff --git a/src/basic_recipes/barplot.jl b/src/basic_recipes/barplot.jl index 31fe1f95b85..437ce692f16 100644 --- a/src/basic_recipes/barplot.jl +++ b/src/basic_recipes/barplot.jl @@ -35,54 +35,53 @@ function bar_default_fillto(tf::Tuple, ys, offset, in_y_direction) end """ - barplot(x, y; kwargs...) + barplot(positions, heights; kwargs...) -Plots a barplot; `y` defines the height. `x` and `y` should be 1 dimensional. -Bar width is determined by the attribute `width`, shrunk by `gap` in the following way: -`width -> width * (1 - gap)`. - -## Attributes -$(ATTRIBUTES) +Plots a barplot. """ -@recipe(BarPlot, x, y) do scene - Attributes(; - fillto = automatic, - offset = 0.0, - color = theme(scene, :patchcolor), - alpha = 1.0, - colormap = theme(scene, :colormap), - colorscale = identity, - colorrange = automatic, - lowclip = automatic, - highclip = automatic, - nan_color = :transparent, - dodge = automatic, - n_dodge = automatic, - gap = 0.2, - dodge_gap = 0.03, - marker = Rect, - stack = automatic, - strokewidth = theme(scene, :patchstrokewidth), - strokecolor = theme(scene, :patchstrokecolor), - width = automatic, - direction = :y, - visible = theme(scene, :visible), - inspectable = theme(scene, :inspectable), - cycle = [:color => :patchcolor], - - bar_labels = nothing, - flip_labels_at = Inf, - label_rotation = 0π, - label_color = theme(scene, :textcolor), - color_over_background = automatic, - color_over_bar = automatic, - label_offset = 5, - label_font = theme(scene, :font), - label_size = theme(scene, :fontsize), - label_formatter = bar_label_formatter, - label_align = automatic, - transparency = false - ) +@recipe BarPlot x y begin + """Controls the baseline of the bars. This is zero in the default `automatic` case unless the barplot is in a log-scaled `Axis`. + With a log scale, the automatic default is half the minimum value because zero is an invalid value for a log scale. + """ + fillto = automatic + offset = 0.0 + color = @inherit patchcolor + MakieCore.mixin_generic_plot_attributes()... + MakieCore.mixin_colormap_attributes()... + dodge = automatic + n_dodge = automatic + """ + The final width of the bars is calculated as `w * (1 - gap)` where `w` is the width of each bar + as determined with the `width` attribute. + """ + gap = 0.2 + dodge_gap = 0.03 + stack = automatic + strokewidth = @inherit patchstrokewidth + strokecolor = @inherit patchstrokecolor + """ + The gapless width of the bars. If `automatic`, the width `w` is calculated as `minimum(diff(sort(unique(positions)))`. + The actual width of the bars is calculated as `w * (1 - gap)`. + """ + width = automatic + "Controls the direction of the bars, can be `:y` (vertical) or `:x` (horizontal)." + direction = :y + cycle = [:color => :patchcolor] + "Labels added at the end of each bar." + bar_labels = nothing + flip_labels_at = Inf + label_rotation = 0π + label_color = @inherit textcolor + color_over_background = automatic + color_over_bar = automatic + "The distance of the labels from the bar ends in screen units." + label_offset = 5 + "The font of the bar labels." + label_font = @inherit font + "The font size of the bar labels." + label_size = @inherit fontsize + label_formatter = bar_label_formatter + label_align = automatic end conversion_trait(::Type{<: BarPlot}) = PointBased() diff --git a/src/basic_recipes/bracket.jl b/src/basic_recipes/bracket.jl index 9a76143e342..20262386af1 100644 --- a/src/basic_recipes/bracket.jl +++ b/src/basic_recipes/bracket.jl @@ -7,28 +7,29 @@ Draws a bracket between each pair of points (x1, y1) and (x2, y2) with a text label at the midpoint. By default each label is rotated parallel to the line between the bracket points. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Bracket) do scene - Theme( - offset = 0, - width = 15, - text = "", - font = theme(scene, :font), - orientation = :up, - align = (:center, :center), - textoffset = automatic, - fontsize = theme(scene, :fontsize), - rotation = automatic, - color = theme(scene, :linecolor), - textcolor = theme(scene, :textcolor), - linewidth = theme(scene, :linewidth), - linestyle = :solid, - justification = automatic, - style = :curly, - ) +@recipe Bracket begin + "The offset of the bracket perpendicular to the line from start to end point in screen units. + The direction depends on the `orientation` attribute." + offset = 0 + """ + The width of the bracket (perpendicularly away from the line from start to end point) in screen units. + """ + width = 15 + text = "" + font = @inherit font + "Which way the bracket extends relative to the line from start to end point. Can be `:up` or `:down`." + orientation = :up + align = (:center, :center) + textoffset = automatic + fontsize = @inherit fontsize + rotation = automatic + color = @inherit linecolor + textcolor = @inherit textcolor + linewidth = @inherit linewidth + linestyle = :solid + justification = automatic + style = :curly end Makie.convert_arguments(::Type{<:Bracket}, point1::VecTypes, point2::VecTypes) = ([(Point2f(point1), Point2f(point2))],) diff --git a/src/basic_recipes/contourf.jl b/src/basic_recipes/contourf.jl index 89ff2ae4da3..e79cd5efa96 100644 --- a/src/basic_recipes/contourf.jl +++ b/src/basic_recipes/contourf.jl @@ -3,43 +3,43 @@ Plots a filled contour of the height information in `zs` at horizontal grid positions `xs` and vertical grid positions `ys`. - -The attribute `levels` can be either -- an `Int` that produces n equally wide levels or bands -- an `AbstractVector{<:Real}` that lists n consecutive edges from low to high, which result in n-1 levels or bands - -You can also set the `mode` attribute to `:relative`. -In this mode you specify edges by the fraction between minimum and maximum value of `zs`. -This can be used for example to draw bands for the upper 90% while excluding the lower 10% with `levels = 0.1:0.1:1.0, mode = :relative`. - -In :normal mode, if you want to show a band from `-Inf` to the low edge, -set `extendlow` to `:auto` for the same color as the first level, -or specify a different color (default `nothing` means no extended band) -If you want to show a band from the high edge to `Inf`, set `extendhigh` -to `:auto` for the same color as the last level, or specify a different color -(default `nothing` means no extended band). - -If `levels` is an `Int`, the contour plot will be rectangular as all `zs` will be covered. -This is why `Axis` defaults to tight limits for such contourf plots. -If you specify `levels` as an `AbstractVector{<:Real}`, however, note that the axis limits include the default margins because the contourf plot can have an irregular shape. -You can use `tightlimits!(ax)` to tighten the limits similar to the `Int` behavior. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Contourf) do scene - Theme( - levels = 10, - mode = :normal, - colormap = theme(scene, :colormap), - colorscale = identity, - extendlow = nothing, - extendhigh = nothing, - # TODO, Isoband doesn't seem to support nans? - nan_color = :transparent, - inspectable = theme(scene, :inspectable), - transparency = false - ) +@recipe Contourf begin + """ + Can be either + - an `Int` that produces n equally wide levels or bands + - an `AbstractVector{<:Real}` that lists n consecutive edges from low to high, which result in n-1 levels or bands + + If `levels` is an `Int`, the contourf plot will be rectangular as all `zs` values will be covered edge to edge. + This is why `Axis` defaults to tight limits for such contourf plots. + If you specify `levels` as an `AbstractVector{<:Real}`, however, note that the axis limits include the default margins because the contourf plot can have an irregular shape. + You can use `tightlimits!(ax)` to tighten the limits similar to the `Int` behavior. + """ + levels = 10 + """ + Determines how the `levels` attribute is interpreted, either `:normal` or `:relative`. + In `:normal` mode, the levels correspond directly to the z values. + In `:relative` mode, you specify edges by the fraction between minimum and maximum value of `zs`. + This can be used for example to draw bands for the upper 90% while excluding the lower 10% with `levels = 0.1:0.1:1.0, mode = :relative`. + """ + mode = :normal + colormap = @inherit colormap + colorscale = identity + """ + In `:normal` mode, if you want to show a band from `-Inf` to the low edge, + set `extendlow` to `:auto` to give the extension the same color as the first level, + or specify a color directly (default `nothing` means no extended band). + """ + extendlow = nothing + """ + In `:normal` mode, if you want to show a band from the high edge to `Inf`, set `extendhigh` + to `:auto` to give the extension the same color as the last level, or specify a color directly + (default `nothing` means no extended band). + """ + extendhigh = nothing + # TODO, Isoband doesn't seem to support nans? + nan_color = :transparent + MakieCore.mixin_generic_plot_attributes()... end # these attributes are computed dynamically and needed for colorbar e.g. diff --git a/src/basic_recipes/contours.jl b/src/basic_recipes/contours.jl index 861b9e249ed..b1406f89b98 100644 --- a/src/basic_recipes/contours.jl +++ b/src/basic_recipes/contours.jl @@ -9,39 +9,39 @@ end Creates a contour plot of the plane spanning `x::Vector`, `y::Vector`, `z::Matrix`. If only `z::Matrix` is supplied, the indices of the elements in `z` will be used as the `x` and `y` locations when plotting the contour. - -The attribute levels can be either - - an Int that produces n equally wide levels or bands - - an AbstractVector{<:Real} that lists n consecutive edges from low to high, which result in n-1 levels or bands - -To add contour labels, use `labels = true`, and pass additional label attributes such as `labelcolor`, `labelsize`, `labelfont` or `labelformatter`. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Contour) do scene - attr = Attributes(; - color = nothing, - levels = 5, - linewidth = 1.0, - linestyle = nothing, - enable_depth = true, - transparency = false, - labels = false, - - labelfont = theme(scene, :font), - labelcolor = nothing, # matches color by default - labelformatter = contour_label_formatter, - labelsize = 10, # arbitrary - ) - - - MakieCore.colormap_attributes!(attr, theme(scene, :colormap)) - MakieCore.generic_plot_attributes!(attr) - - return attr +@recipe Contour begin + """ + The color of the contour lines. If `nothing`, the color is determined by the numerical values of the + contour levels in combination with `colormap` and `colorrange`. + """ + color = nothing + """ + Controls the number and location of the contour lines. Can be either + + - an `Int` that produces n equally wide levels or bands + - an `AbstractVector{<:Real}` that lists n consecutive edges from low to high, which result in n-1 levels or bands + """ + levels = 5 + linewidth = 1.0 + linestyle = nothing + enable_depth = true + """ + If `true`, adds text labels to the contour lines. + """ + labels = false + "The font of the contour labels." + labelfont = @inherit font + "Color of the contour labels, if `nothing` it matches `color` by default." + labelcolor = nothing # matches color by default + """ + Formats the numeric values of the contour levels to strings. + """ + labelformatter = contour_label_formatter + "Font size of the contour labels" + labelsize = 10 # arbitrary + MakieCore.mixin_colormap_attributes()... + MakieCore.mixin_generic_plot_attributes()... end """ @@ -49,12 +49,9 @@ end Creates a 3D contour plot of the plane spanning x::Vector, y::Vector, z::Matrix, with z-elevation for each level. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Contour3d) do scene - default_theme(scene, Contour) +@recipe Contour3d begin + MakieCore.documented_attributes(Contour)... end angle(p1::Union{Vec2f,Point2f}, p2::Union{Vec2f,Point2f})::Float32 = @@ -160,6 +157,9 @@ function plot!(plot::Contour{<: Tuple{X, Y, Z, Vol}}) where {X, Y, Z, Vol} pop!(attr, :labelsize) pop!(attr, :labelcolor) pop!(attr, :labelformatter) + pop!(attr, :color) + pop!(attr, :linestyle) + pop!(attr, :linewidth) volume!(plot, attr, x, y, z, volume) end diff --git a/src/basic_recipes/datashader.jl b/src/basic_recipes/datashader.jl index d9bf42b2244..87179139c4b 100644 --- a/src/basic_recipes/datashader.jl +++ b/src/basic_recipes/datashader.jl @@ -273,7 +273,7 @@ end !!! warning This feature might change outside breaking releases, since the API is not yet finalized. - Please be vary of bugs in the implementation and open issues if you encounter odd behaviour. + Please be wary of bugs in the implementation and open issues if you encounter odd behaviour. Points can be any array type supporting iteration & getindex, including memory mapped arrays. If you have separate arrays for x and y coordinates and want to avoid conversion and copy, consider using: @@ -282,60 +282,61 @@ using Makie.StructArrays points = StructArray{Point2f}((x, y)) datashader(points) ``` -Do pay attention though, that if x and y don't have a fast iteration/getindex implemented, this might be slower then just copying it into a new array. +Do pay attention though, that if x and y don't have a fast iteration/getindex implemented, this might be slower than just copying the data into a new array. For best performance, use `method=Makie.AggThreads()` and make sure to start julia with `julia -tauto` or have the environment variable `JULIA_NUM_THREADS` set to the number of cores you have. - -## Attributes - -### Specific to `DataShader` - -- `agg = AggCount()` can be `AggCount()`, `AggAny()` or `AggMean()`. User extendable by overloading: - - - ```Julia - struct MyAgg{T} <: Makie.AggOp end - MyAgg() = MyAgg{Float64}() - Makie.Aggregation.null(::MyAgg{T}) where {T} = zero(T) - Makie.Aggregation.embed(::MyAgg{T}, x) where {T} = convert(T, x) - Makie.Aggregation.merge(::MyAgg{T}, x::T, y::T) where {T} = x + y - Makie.Aggregation.value(::MyAgg{T}, x::T) where {T} = x - ``` - -- `method = AggThreads()` can be `AggThreads()` or `AggSerial()`. -- `async::Bool = true` will calculate get_aggregation in a task, and skip any zoom/pan updates while busy. Great for interaction, but must be disabled for saving to e.g. png or when inlining in documenter. - -- `operation::Function = automatic` Defaults to `Makie.equalize_histogram` function which gets called on the whole get_aggregation array before display (`operation(final_aggregation_result)`). -- `local_operation::Function = identity` function which gets call on each element after the aggregation (`map!(x-> local_operation(x), final_aggregation_result)`). - -- `point_transform::Function = identity` function which gets applied to every point before aggregating it. -- `binsize::Number = 1` factor defining how many bins one wants per screen pixel. Set to n > 1 if you want a corser image. -- `show_timings::Bool = false` show how long it takes to aggregate each frame. -- `interpolate::Bool = true` If the resulting image should be displayed interpolated. - -$(Base.Docs.doc(MakieCore.colormap_attributes!)) - -$(Base.Docs.doc(MakieCore.generic_plot_attributes!)) """ -@recipe(DataShader, points) do scene - attr = Theme( - - agg = AggCount(), - method = AggThreads(), - async = true, - # Defaults to equalize_histogram - # just set to automatic, so that if one sets local_operation, one doesn't do equalize_histogram on top of things. - operation=automatic, - local_operation=identity, - - point_transform = identity, - binsize = 1, - show_timings = false, - - interpolate = true - ) - MakieCore.generic_plot_attributes!(attr) - return MakieCore.colormap_attributes!(attr, theme(scene, :colormap)) +@recipe DataShader points begin + """ + Can be `AggCount()`, `AggAny()` or `AggMean()`. User-extensible by overloading: + + ```julia + struct MyAgg{T} <: Makie.AggOp end + MyAgg() = MyAgg{Float64}() + Makie.Aggregation.null(::MyAgg{T}) where {T} = zero(T) + Makie.Aggregation.embed(::MyAgg{T}, x) where {T} = convert(T, x) + Makie.Aggregation.merge(::MyAgg{T}, x::T, y::T) where {T} = x + y + Makie.Aggregation.value(::MyAgg{T}, x::T) where {T} = x + ``` + """ + agg = AggCount() + """ + Can be `AggThreads()` or `AggSerial()` for threaded vs. serial aggregation. + """ + method = AggThreads() + """ + Will calculate `get_aggregation` in a task, and skip any zoom/pan updates while busy. Great for interaction, but must be disabled for saving to e.g. png or when inlining in Documenter. + """ + async = true + # Defaults to equalize_histogram + # just set to automatic, so that if one sets local_operation, one doesn't do equalize_histogram on top of things. + """ + Defaults to `Makie.equalize_histogram` function which gets called on the whole get_aggregation array before display (`operation(final_aggregation_result)`). + """ + operation=automatic + """ + Function which gets called on each element after the aggregation (`map!(x-> local_operation(x), final_aggregation_result)`). + """ + local_operation=identity + + """ + Function which gets applied to every point before aggregating it. + """ + point_transform = identity + """ + Factor defining how many bins one wants per screen pixel. Set to n > 1 if you want a coarser image. + """ + binsize = 1 + """ + Set to `true` to show how long it takes to aggregate each frame. + """ + show_timings = false + """ + If the resulting image should be displayed interpolated. + """ + interpolate = true + MakieCore.mixin_generic_plot_attributes()... + MakieCore.mixin_colormap_attributes()... end function fast_bb(points, f) @@ -400,8 +401,8 @@ function Makie.plot!(p::DataShader{<: Tuple{<: AbstractVector{<: Point}}}) return end p.raw_colorrange = colorrange - image!(p, canvas_with_aggregation; - operation=p.operation, local_operation=p.local_operation, interpolate=p.interpolate, + image!(p, canvas_with_aggregation, p.operation, p.local_operation; + interpolate=p.interpolate, MakieCore.generic_plot_attributes(p)..., MakieCore.colormap_attributes(p)...) return p @@ -458,20 +459,18 @@ function Makie.plot!(p::DataShader{<:Tuple{Dict{String, Vector{Point{2, Float32} colors = Dict(k => Makie.wong_colors()[i] for (i, (k, v)) in enumerate(categories)) p._categories = colors op = map(total -> (x -> log10(x + 1) / log10(total + 1)), toal_value) - for (k, canvas) in canvases + + for (k, canv) in canvases color = colors[k] cmap = [(color, 0.0), (color, 1.0)] - image!(p, canvas; colorrange=Vec2f(0, 1), colormap=cmap, operation=identity, local_operation=op) + image!(p, canv, identity, op; colorrange=Vec2f(0, 1), colormap=cmap) end return p end data_limits(p::DataShader) = p._boundingbox[] -used_attributes(::Canvas) = (:operation, :local_operation) - -function convert_arguments(P::Type{<:Union{MeshScatter,Image,Surface,Contour,Contour3d}}, canvas::Canvas; - operation=automatic, local_operation=identity) +function convert_arguments(P::Type{<:Union{MeshScatter,Image,Surface,Contour,Contour3d}}, canvas::Canvas, operation=automatic, local_operation=identity) pixel = Aggregation.get_aggregation(canvas; operation=operation, local_operation=local_operation) (xmin, ymin), (xmax, ymax) = extrema(canvas.bounds) return convert_arguments(P, xmin .. xmax, ymin .. ymax, pixel) diff --git a/src/basic_recipes/error_and_rangebars.jl b/src/basic_recipes/error_and_rangebars.jl index 95dc2ec39e1..f3ac3d46e54 100644 --- a/src/basic_recipes/error_and_rangebars.jl +++ b/src/basic_recipes/error_and_rangebars.jl @@ -13,24 +13,19 @@ Plots errorbars at xy positions, extending by errors in the given `direction`. If you want to plot intervals from low to high values instead of relative errors, use `rangebars`. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Errorbars) do scene - Theme( - whiskerwidth = 0, - color = theme(scene, :linecolor), - linewidth = theme(scene, :linewidth), - direction = :y, - visible = theme(scene, :visible), - colormap = theme(scene, :colormap), - colorscale = identity, - colorrange = automatic, - inspectable = theme(scene, :inspectable), - transparency = false, - cycle = [:color] - ) +@recipe Errorbars begin + "The width of the whiskers or line caps in screen units." + whiskerwidth = 0 + "The color of the lines. Can be an array to color each bar separately." + color = @inherit linecolor + "The thickness of the lines in screen units." + linewidth = @inherit linewidth + "The direction in which the bars are drawn. Can be `:x` or `:y`." + direction = :y + cycle = [:color] + MakieCore.mixin_colormap_attributes()... + MakieCore.mixin_generic_plot_attributes()... end @@ -43,24 +38,19 @@ Plots rangebars at `val` in one dimension, extending from `low` to `high` in the given the chosen `direction`. If you want to plot errors relative to a reference value, use `errorbars`. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Rangebars) do scene - Theme( - whiskerwidth = 0, - color = theme(scene, :linecolor), - linewidth = theme(scene, :linewidth), - direction = :y, - visible = theme(scene, :visible), - colormap = theme(scene, :colormap), - colorscale = identity, - colorrange = automatic, - inspectable = theme(scene, :inspectable), - transparency = false, - cycle = [:color] - ) +@recipe Rangebars begin + "The width of the whiskers or line caps in screen units." + whiskerwidth = 0 + "The color of the lines. Can be an array to color each bar separately." + color = @inherit linecolor + "The thickness of the lines in screen units." + linewidth = @inherit linewidth + "The direction in which the bars are drawn. Can be `:x` or `:y`." + direction = :y + cycle = [:color] + MakieCore.mixin_colormap_attributes()... + MakieCore.mixin_generic_plot_attributes()... end ### conversions for errorbars diff --git a/src/basic_recipes/hvlines.jl b/src/basic_recipes/hvlines.jl index a919629d6b6..7e95eb4457d 100644 --- a/src/basic_recipes/hvlines.jl +++ b/src/basic_recipes/hvlines.jl @@ -5,17 +5,14 @@ Create horizontal lines across a `Scene` with 2D projection. The lines will be placed at `ys` in data coordinates and `xmin` to `xmax` in scene coordinates (0 to 1). All three of these can have single or multiple values because they are broadcast to calculate the final line segments. - -All style attributes are the same as for `LineSegments`. """ -@recipe(HLines) do scene - Theme(; - xautolimits = false, - xmin = 0, - xmax = 1, - default_theme(scene, LineSegments)..., - cycle = :color, - ) +@recipe HLines begin + "The start of the lines in relative axis units (0 to 1) along the x dimension." + xmin = 0 + "The end of the lines in relative axis units (0 to 1) along the x dimension." + xmax = 1 + MakieCore.documented_attributes(LineSegments)... + cycle = [:color] end """ @@ -25,17 +22,14 @@ Create vertical lines across a `Scene` with 2D projection. The lines will be placed at `xs` in data coordinates and `ymin` to `ymax` in scene coordinates (0 to 1). All three of these can have single or multiple values because they are broadcast to calculate the final line segments. - -All style attributes are the same as for `LineSegments`. """ -@recipe(VLines) do scene - Theme(; - yautolimits = false, - ymin = 0, - ymax = 1, - default_theme(scene, LineSegments)..., - cycle = :color, - ) +@recipe VLines begin + "The start of the lines in relative axis units (0 to 1) along the y dimension." + ymin = 0 + "The start of the lines in relative axis units (0 to 1) along the y dimension." + ymax = 1 + MakieCore.documented_attributes(LineSegments)... + cycle = [:color] end function projview_to_2d_limits(pv) diff --git a/src/basic_recipes/hvspan.jl b/src/basic_recipes/hvspan.jl index cb082347705..51c391789fb 100644 --- a/src/basic_recipes/hvspan.jl +++ b/src/basic_recipes/hvspan.jl @@ -5,18 +5,15 @@ Create horizontal bands spanning across a `Scene` with 2D projection. The bands will be placed from `ys_low` to `ys_high` in data coordinates and `xmin` to `xmax` in scene coordinates (0 to 1). All four of these can have single or multiple values because they are broadcast to calculate the final spans. - -All style attributes are the same as for `Poly`. """ -@recipe(HSpan) do scene - Theme(; - xautolimits = false, - xmin = 0, - xmax = 1, - default_theme(Poly, scene)..., - cycle = [:color => :patchcolor], - ) - end +@recipe HSpan begin + "The start of the bands in relative axis units (0 to 1) along the x dimension." + xmin = 0 + "The end of the bands in relative axis units (0 to 1) along the x dimension." + xmax = 1 + MakieCore.documented_attributes(Poly)... + cycle = [:color => :patchcolor] +end """ vspan(xs_low, xs_high; ymin = 0.0, ymax = 1.0, attrs...) @@ -25,17 +22,14 @@ Create vertical bands spanning across a `Scene` with 2D projection. The bands will be placed from `xs_low` to `xs_high` in data coordinates and `ymin` to `ymax` in scene coordinates (0 to 1). All four of these can have single or multiple values because they are broadcast to calculate the final spans. - -All style attributes are the same as for `Poly`. """ -@recipe(VSpan) do scene - Theme(; - yautolimits = false, - ymin = 0, - ymax = 1, - default_theme(Poly, scene)..., - cycle = [:color => :patchcolor], - ) +@recipe VSpan begin + "The start of the bands in relative axis units (0 to 1) along the y dimension." + ymin = 0 + "The end of the bands in relative axis units (0 to 1) along the y dimension." + ymax = 1 + MakieCore.documented_attributes(Poly)... + cycle = [:color => :patchcolor] end function Makie.plot!(p::Union{HSpan, VSpan}) diff --git a/src/basic_recipes/pie.jl b/src/basic_recipes/pie.jl index e13033d39e9..43f0e1454c8 100644 --- a/src/basic_recipes/pie.jl +++ b/src/basic_recipes/pie.jl @@ -1,25 +1,23 @@ """ - pie(fractions; kwargs...) + pie(values; kwargs...) -Creates a pie chart with the given `fractions`. - -## Attributes -$(ATTRIBUTES) +Creates a pie chart from the given `values`. """ -@recipe(Pie, values) do scene - Theme( - normalize = true, - color = :gray, - strokecolor = :black, - strokewidth = 1, - vertex_per_deg = 1, - radius = 1, - inner_radius = 0, - offset = 0, - inspectable = theme(scene, :inspectable), - visible = true, - transparency = false - ) +@recipe Pie values begin + "If `true`, the sum of all values is normalized to 2π (a full circle)." + normalize = true + color = :gray + strokecolor = :black + strokewidth = 1 + "Controls how many polygon vertices are used for one degree of rotation." + vertex_per_deg = 1 + "The outer radius of the pie segments." + radius = 1 + "The inner radius of the pie segments. If this is larger than zero, the pie pieces become ring sections." + inner_radius = 0 + "The angular offset of the first pie segment from the (1, 0) vector in radians." + offset = 0 + MakieCore.mixin_generic_plot_attributes()... end function plot!(plot::Pie) diff --git a/src/basic_recipes/poly.jl b/src/basic_recipes/poly.jl index 8792eadfb0c..31e2b838f2c 100644 --- a/src/basic_recipes/poly.jl +++ b/src/basic_recipes/poly.jl @@ -164,7 +164,7 @@ end function plot!(plot::Mesh{<: Tuple{<: AbstractVector{P}}}) where P <: Union{AbstractMesh, Polygon} meshes = plot[1] - attributes = Attributes( + attrs = Attributes( visible = plot.visible, shading = plot.shading, fxaa = plot.fxaa, inspectable = plot.inspectable, transparency = plot.transparency, space = plot.space, ssao = plot.ssao, @@ -183,6 +183,8 @@ function plot!(plot::Mesh{<: Tuple{<: AbstractVector{P}}}) where P <: Union{Abst mesh_colors = Observable{Union{AbstractPattern, Matrix{RGBAf}, RGBColors, Float32}}() + interpolate_in_fragment_shader = Observable(false) + map!(plot, mesh_colors, plot.color, num_meshes) do colors, num_meshes # one mesh per color if colors isa AbstractVector && length(colors) == length(num_meshes) @@ -196,15 +198,15 @@ function plot!(plot::Mesh{<: Tuple{<: AbstractVector{P}}}) where P <: Union{Abst end end # For GLMakie (right now), to not interpolate between the colors (which are meant to be per mesh) - attributes[:interpolate_in_fragment_shader] = false + interpolate_in_fragment_shader[] = false return result else # If we have colors per vertex, we need to interpolate in fragment shader - attributes[:interpolate_in_fragment_shader] = true + interpolate_in_fragment_shader[] = true return to_color(colors) end end - attributes[:color] = mesh_colors + attrs[:color] = mesh_colors transform_func = plot.transformation.transform_func bigmesh = lift(plot, meshes, transform_func) do meshes, tf if isempty(meshes) @@ -214,5 +216,8 @@ function plot!(plot::Mesh{<: Tuple{<: AbstractVector{P}}}) where P <: Union{Abst return merge(triangle_meshes) end end - return mesh!(plot, attributes, bigmesh) + mpl = mesh!(plot, attrs, bigmesh) + # splice in internal attribute after creation to avoid validation + attributes(mpl)[:interpolate_in_fragment_shader] = interpolate_in_fragment_shader + return mpl end diff --git a/src/basic_recipes/raincloud.jl b/src/basic_recipes/raincloud.jl index 93b47b07fbc..c99e54eed7d 100644 --- a/src/basic_recipes/raincloud.jl +++ b/src/basic_recipes/raincloud.jl @@ -28,83 +28,100 @@ between each. - `data_array`: Typically `Vector{Float64}` used for to represent the datapoints to plot. # Keywords -- `gap=0.2`: Distance between elements of x-axis. -- `side=:left`: Can take values of `:left`, `:right`, determines where the violin plot will - be, relative to the scatter points -- `dodge`: vector of `Integer`` (length of data) of grouping variable to create multiple - side-by-side boxes at the same x position -- `dodge_gap = 0.03`: spacing between dodged boxes -- `n_dodge`: the number of categories to dodge (defaults to maximum(dodge)) -- `color`: a single color, or a vector of colors, one for each point ## Violin/Histogram Plot Specific Keywords -- `clouds=violin`: [violin, hist, nothing] to show cloud plots either as violin or histogram - plot, or no cloud plot. -- `hist_bins=30`: if `clouds=hist`, this passes down the number of bins to the histogram - call. -- `cloud_width=1.0`: Determines size of violin plot. Corresponds to `width` keyword arg in -`violin`. -- `orientation=:vertical` orientation of raindclouds (`:vertical` or `:horizontal`) -- `violin_limits=(-Inf, Inf)`: specify values to trim the `violin`. Can be a `Tuple` or a - `Function` (e.g. `datalimits=extrema`) - -## Box Plot Specific Keywords -- `plot_boxplots=true`: Boolean to show boxplots to summarize distribution of data. -- `boxplot_width=0.1`: Width of the boxplot in category x-axis absolute terms. -- `center_boxplot=true`: Determines whether or not to have the boxplot be centered in the - category. -- `whiskerwidth=0.5`: The width of the Q1, Q3 whisker in the boxplot. Value as a portion of - the `boxplot_width`. -- `strokewidth=1.0`: Determines the stroke width for the outline of the boxplot. -- `show_median=true`: Determines whether or not to have a line should the median value in - the boxplot. -- `boxplot_nudge=0.075`: Determines the distance away the boxplot should be placed from the - center line when `center_boxplot` is `false`. This is the value used to recentering the - boxplot. -- `show_boxplot_outliers`: show outliers in the boxplot as points (usually confusing when -paired with the scatter plot so the default is to not show them) ## Scatter Plot Specific Keywords - `side_nudge`: Default value is 0.02 if `plot_boxplots` is true, otherwise `0.075` default. - `jitter_width=0.05`: Determines the width of the scatter-plot bar in category x-axis absolute terms. -- `markersize=2`: Size of marker used for the scatter plot. - -## Axis General Keywords -- `title` -- `xlabel` -- `ylabel` """ -@recipe(RainClouds, category_labels, data_array) do scene - return Attributes( - side = :left, - orientation = :vertical, - center_boxplot = true, - # Cloud plot - cloud_width = 0.75, - violin_limits = (-Inf, Inf), - # Box Plot Settings - boxplot_width = 0.1, - whiskerwidth = 0.5, - strokewidth = 1.0, - show_median = true, - boxplot_nudge = 0.075, - - gap = 0.2, - - markersize = 2.0, - dodge = automatic, - n_dodge = automatic, - dodge_gap = 0.01, - - plot_boxplots = true, - show_boxplot_outliers = false, - clouds = violin, - hist_bins = 30, - - color = theme(scene, :patchcolor), - cycle = [:color => :patchcolor], - ) +@recipe RainClouds category_labels data_array begin + """ + Can take values of `:left`, `:right`, determines where the violin plot will be, + relative to the scatter points + """ + side = :left + """ + Orientation of rainclouds (`:vertical` or `:horizontal`) + """ + orientation = :vertical + """ + Whether or not to center the boxplot on the category. + """ + center_boxplot = true + # Cloud plot + """ + Determines size of violin plot. Corresponds to `width` keyword arg in `violin`. + """ + cloud_width = 0.75 + """ + Specify values to trim the `violin`. Can be a `Tuple` or a + `Function` (e.g. `datalimits=extrema`) + """ + violin_limits = (-Inf, Inf) + # Box Plot Settings + """ + Width of the boxplot on the category axis. + """ + boxplot_width = 0.1 + "The width of the Q1, Q3 whisker in the boxplot. Value as a portion of the `boxplot_width`." + whiskerwidth = 0.5 + "Determines the stroke width for the outline of the boxplot." + strokewidth = 1.0 + """ + Determines whether or not to have a line for the median value in the boxplot. + """ + show_median = true + """ + Determines the distance away the boxplot should be placed from the + center line when `center_boxplot` is `false`. This is the value used to recentering the + boxplot. + """ + boxplot_nudge = 0.075 + + "Distance between elements on the main axis (depending on `orientation`)." + gap = 0.2 + + """ + Size of marker used for the scatter plot. + """ + markersize = 2.0 + + """ + Vector of `Integer` (length of data) of grouping variable to create multiple + side-by-side boxes at the same x position + """ + dodge = automatic + """ + The number of categories to dodge (defaults to `maximum(dodge)`) + """ + n_dodge = automatic + "Spacing between dodged boxes." + dodge_gap = 0.01 + + "Whether to show boxplots to summarize distribution of data." + plot_boxplots = true + """ + Show outliers in the boxplot as points (usually confusing when + paired with the scatter plot so the default is to not show them) + """ + show_boxplot_outliers = false + """ + [`violin`, `hist`, `nothing`] how to show cloud plots, either as violin or histogram + plots, or not at all. + """ + clouds = violin + """ + If `clouds=hist`, this passes down the number of bins to the histogram call. + """ + hist_bins = 30 + + """ + A single color, or a vector of colors, one for each point. + """ + color = @inherit patchcolor + cycle = [:color => :patchcolor] end # create_jitter_array(length_data_array; jitter_width = 0.1, clamped_portion = 0.1) diff --git a/src/basic_recipes/scatterlines.jl b/src/basic_recipes/scatterlines.jl index cc2e02d4ec8..286c9ef5644 100644 --- a/src/basic_recipes/scatterlines.jl +++ b/src/basic_recipes/scatterlines.jl @@ -2,30 +2,28 @@ scatterlines(xs, ys, [zs]; kwargs...) Plots `scatter` markers and `lines` between them. - -## Attributes -$(ATTRIBUTES) """ -@recipe(ScatterLines) do scene - s_theme = default_theme(scene, Scatter) - l_theme = default_theme(scene, Lines) - Attributes( - color = l_theme.color, - colormap = l_theme.colormap, - colorscale = l_theme.colorscale, - colorrange = get(l_theme.attributes, :colorrange, automatic), - linestyle = l_theme.linestyle, - linewidth = l_theme.linewidth, - markercolor = automatic, - markercolormap = automatic, - markercolorrange = automatic, - markersize = s_theme.markersize, - strokecolor = s_theme.strokecolor, - strokewidth = s_theme.strokewidth, - marker = s_theme.marker, - inspectable = theme(scene, :inspectable), - cycle = [:color], - ) +@recipe ScatterLines begin + "The color of the line, and by default also of the scatter markers." + color = @inherit linecolor + "Sets the pattern of the line e.g. `:solid`, `:dot`, `:dashdot`. For custom patterns look at `Linestyle(Number[...])`" + linestyle = nothing + "Sets the width of the line in screen units" + linewidth = @inherit linewidth + markercolor = automatic + markercolormap = automatic + markercolorrange = automatic + "Sets the size of the marker." + markersize = @inherit markersize + "Sets the color of the outline around a marker." + strokecolor = @inherit markerstrokecolor + "Sets the width of the outline around a marker." + strokewidth = @inherit markerstrokewidth + "Sets the scatter marker." + marker = @inherit marker + MakieCore.mixin_generic_plot_attributes()... + MakieCore.mixin_colormap_attributes()... + cycle = [:color] end conversion_trait(::Type{<: ScatterLines}) = PointBased() diff --git a/src/basic_recipes/series.jl b/src/basic_recipes/series.jl index a0375a0790a..58197f95b47 100644 --- a/src/basic_recipes/series.jl +++ b/src/basic_recipes/series.jl @@ -1,17 +1,7 @@ """ - series(curves; - linewidth=2, - color=:lighttest, - solid_color=nothing, - labels=nothing, - # scatter arguments, if any is set != nothing, a scatterplot is added - marker=nothing, - markersize=nothing, - markercolor=automatic, - strokecolor=nothing, - strokewidth=nothing) + series(curves) Curves can be: * `AbstractVector{<: AbstractVector{<: Point2}}`: the native representation of a series as a vector of lines @@ -19,21 +9,20 @@ Curves can be: * `AbstractVector, AbstractMatrix`: the same as the above, but the first argument sets the x values for all lines * `AbstractVector{<: Tuple{X<: AbstractVector, Y<: AbstractVector}}`: A vector of tuples, where each tuple contains a vector for the x and y coordinates +If any of `marker`, `markersize`, `markercolor`, `strokecolor` or `strokewidth` is set != nothing, a scatterplot is added. """ -@recipe(Series, curves) do scene - Attributes( - linewidth=2, - color=:lighttest, - solid_color=nothing, - labels=nothing, - linestyle=:solid, - marker=nothing, - markersize=nothing, - markercolor=automatic, - strokecolor=nothing, - strokewidth=nothing, - space = :data, - ) +@recipe Series curves begin + linewidth=2 + color=:lighttest + solid_color=nothing + labels=nothing + linestyle=:solid + marker=nothing + markersize=nothing + markercolor=automatic + strokecolor=nothing + strokewidth=nothing + space = :data end replace_missing(x) = ismissing(x) ? NaN : x diff --git a/src/basic_recipes/spy.jl b/src/basic_recipes/spy.jl index eda01af1237..feab9efc753 100644 --- a/src/basic_recipes/spy.jl +++ b/src/basic_recipes/spy.jl @@ -10,21 +10,14 @@ spy(x) # or if you want to specify the range of x and y: spy(0..1, 0..1, x) ``` -## Attributes -$(ATTRIBUTES) """ -@recipe(Spy, x, y, z) do scene - Attributes( - marker = automatic, - markersize = automatic, - colormap = theme(scene, :colormap), - colorscale = identity, - colorrange = automatic, - framecolor = :black, - framesize = 1, - inspectable = theme(scene, :inspectable), - visible = theme(scene, :visible) - ) +@recipe Spy x y z begin + marker = automatic + markersize = automatic + framecolor = :black + framesize = 1 + MakieCore.mixin_generic_plot_attributes()... + MakieCore.mixin_colormap_attributes()... end function convert_arguments(::Type{<: Spy}, x::SparseArrays.AbstractSparseArray) diff --git a/src/basic_recipes/stairs.jl b/src/basic_recipes/stairs.jl index 4d74024c9b1..543cb9e2fba 100644 --- a/src/basic_recipes/stairs.jl +++ b/src/basic_recipes/stairs.jl @@ -3,21 +3,17 @@ Plot a stair function. -The `step` parameter can take the following values: -- `:pre`: horizontal part of step extends to the left of each value in `xs`. -- `:post`: horizontal part of step extends to the right of each value in `xs`. -- `:center`: horizontal part of step extends halfway between the two adjacent values of `xs`. - -The conversion trait of stem is `PointBased`. - -## Attributes -$(ATTRIBUTES) +The conversion trait of `stairs` is `PointBased`. """ -@recipe(Stairs) do scene - a = Attributes( - step = :pre, # :center :post - ) - merge(a, default_theme(scene, Lines)) +@recipe Stairs begin + """ + The `step` parameter can take the following values: + - `:pre`: horizontal part of step extends to the left of each value in `xs`. + - `:post`: horizontal part of step extends to the right of each value in `xs`. + - `:center`: horizontal part of step extends halfway between the two adjacent values of `xs`. + """ + step = :pre + MakieCore.documented_attributes(Lines)... end conversion_trait(::Type{<:Stairs}) = PointBased() diff --git a/src/basic_recipes/stem.jl b/src/basic_recipes/stem.jl index 682478762e5..91ab22462ef 100644 --- a/src/basic_recipes/stem.jl +++ b/src/basic_recipes/stem.jl @@ -3,40 +3,35 @@ Plots markers at the given positions extending from `offset` along stem lines. -`offset` can be a number, in which case it sets y for 2D, and z for 3D stems. -It can be a Point2 for 2D plots, as well as a Point3 for 3D plots. -It can also be an iterable of any of these at the same length as xs, ys, zs. - -The conversion trait of stem is `PointBased`. - -## Attributes -$(ATTRIBUTES) +The conversion trait of `stem` is `PointBased`. """ -@recipe(Stem) do scene - Attributes( - stemcolor = theme(scene, :linecolor), - stemcolormap = theme(scene, :colormap), - stemcolorrange = automatic, - stemwidth = theme(scene, :linewidth), - stemlinestyle = nothing, - trunkwidth = theme(scene, :linewidth), - trunklinestyle = nothing, - trunkcolor = theme(scene, :linecolor), - trunkcolormap = theme(scene, :colormap), - trunkcolorrange = automatic, - offset = 0, - marker = :circle, - markersize = theme(scene, :markersize), - color = theme(scene, :markercolor), - colormap = theme(scene, :colormap), - colorscale = identity, - colorrange = automatic, - strokecolor = theme(scene, :markerstrokecolor), - strokewidth = theme(scene, :markerstrokewidth), - visible = true, - inspectable = theme(scene, :inspectable), - cycle = [[:stemcolor, :color, :trunkcolor] => :color], - ) +@recipe Stem begin + stemcolor = @inherit linecolor + stemcolormap = @inherit colormap + stemcolorrange = automatic + stemwidth = @inherit linewidth + stemlinestyle = nothing + trunkwidth = @inherit linewidth + trunklinestyle = nothing + trunkcolor = @inherit linecolor + trunkcolormap = @inherit colormap + trunkcolorrange = automatic + """ + Can be a number, in which case it sets `y` for 2D, and `z` for 3D stems. + It can be a `Point2` for 2D plots, as well as a `Point3` for 3D plots. + It can also be an iterable of any of these at the same length as `xs`, `ys`, `zs`. + """ + offset = 0 + marker = :circle + markersize = @inherit markersize + color = @inherit markercolor + colormap = @inherit colormap + colorscale = identity + colorrange = automatic + strokecolor = @inherit markerstrokecolor + strokewidth = @inherit markerstrokewidth + MakieCore.mixin_generic_plot_attributes()... + cycle = [[:stemcolor, :color, :trunkcolor] => :color] end diff --git a/src/basic_recipes/streamplot.jl b/src/basic_recipes/streamplot.jl index 6855c9858c5..20d4b287506 100644 --- a/src/basic_recipes/streamplot.jl +++ b/src/basic_recipes/streamplot.jl @@ -11,34 +11,29 @@ v(x::Point2{T}) where T = Point2f(x[2], 4*x[1]) streamplot(v, -2..2, -2..2) ``` -One can choose the color of the lines by passing a function `color_func(dx::Point)` to the `color` attribute. -By default this is set to `norm`, but can be set to any function or composition of functions. -The `dx` which is passed to `color_func` is the output of `f` at the point being colored. - -## Attributes -$(ATTRIBUTES) - ## Implementation See the function `Makie.streamplot_impl` for implementation details. """ -@recipe(StreamPlot, f, limits) do scene - attr = Attributes( - stepsize = 0.01, - gridsize = (32, 32, 32), - maxsteps = 500, - color = norm, - - arrow_size = automatic, - arrow_head = automatic, - density = 1.0, - quality = 16, - - linewidth = theme(scene, :linewidth), - linestyle = nothing, - ) - MakieCore.colormap_attributes!(attr, theme(scene, :colormap)) - MakieCore.generic_plot_attributes!(attr) - return attr +@recipe StreamPlot f limits begin + stepsize = 0.01 + gridsize = (32, 32, 32) + maxsteps = 500 + """ + One can choose the color of the lines by passing a function `color_func(dx::Point)` to the `color` attribute. + This can be set to any function or composition of functions. + The `dx` which is passed to `color_func` is the output of `f` at the point being colored. + """ + color = norm + + arrow_size = automatic + arrow_head = automatic + density = 1.0 + quality = 16 + + linewidth = @inherit linewidth + linestyle = nothing + MakieCore.mixin_colormap_attributes()... + MakieCore.mixin_generic_plot_attributes()... end function convert_arguments(::Type{<: StreamPlot}, f::Function, xrange, yrange) diff --git a/src/basic_recipes/text.jl b/src/basic_recipes/text.jl index 1b42770ede3..9f3ae2af1b4 100644 --- a/src/basic_recipes/text.jl +++ b/src/basic_recipes/text.jl @@ -1,11 +1,10 @@ function check_textsize_deprecation(@nospecialize(dictlike)) if haskey(dictlike, :textsize) - throw(ArgumentError("The attribute `textsize` has been renamed to `fontsize` in Makie v0.19. Please change all occurrences of `textsize` to `fontsize` or revert back to an earlier version.")) + throw(ArgumentError("`textsize` has been renamed to `fontsize` in Makie v0.19. Please change all occurrences of `textsize` to `fontsize` or revert back to an earlier version.")) end end function plot!(plot::Text) - check_textsize_deprecation(plot) positions = plot[1] # attach a function to any text that calculates the glyph layout and stores it glyphcollections = Observable(GlyphCollection[]; ignore_equal_values=true) @@ -76,11 +75,13 @@ function plot!(plot::Text) pop!(attrs, :text) pop!(attrs, :align) pop!(attrs, :color) + pop!(attrs, :calculated_colors) t = text!(plot, glyphcollections; attrs..., position = positions) # remove attributes that the backends will choke on pop!(t.attributes, :font) pop!(t.attributes, :fonts) + pop!(t.attributes, :text) linesegments!(plot, linesegs_shifted; linewidth = linewidths, color = linecolors, space = :pixel) plot @@ -124,7 +125,9 @@ function _get_glyphcollection_and_linesegments(latexstring::LaTeXString, index, end function plot!(plot::Text{<:Tuple{<:AbstractString}}) - text!(plot, plot.position; text = plot[1], plot.attributes...) + attrs = copy(plot.attributes) + pop!(attrs, :calculated_colors) + text!(plot, plot.position; attrs..., text = plot[1]) plot end @@ -142,7 +145,9 @@ plot!(plot::Text{<:Tuple{<:GlyphCollection}}) = plot plot!(plot::Text{<:Tuple{<:AbstractArray{<:GlyphCollection}}}) = plot function plot!(plot::Text{<:Tuple{<:AbstractArray{<:AbstractString}}}) - text!(plot, plot.position; text = plot[1], plot.attributes...) + attrs = copy(plot.attributes) + pop!(attrs, :calculated_colors) + text!(plot, plot.position; attrs..., text = plot[1]) plot end @@ -158,8 +163,10 @@ function plot!(plot::Text{<:Tuple{<:AbstractArray{<:Tuple{<:Any, <:Point}}}}) attrs = plot.attributes pop!(attrs, :position) + pop!(attrs, :calculated_colors) + pop!(attrs, :text) - text!(plot, positions; text = strings, attrs...) + text!(plot, positions; attrs..., text = strings) # update both text and positions together on(plot, strings_and_positions) do str_pos diff --git a/src/basic_recipes/timeseries.jl b/src/basic_recipes/timeseries.jl index 7e7fc9ca8ed..510f2d2abd1 100644 --- a/src/basic_recipes/timeseries.jl +++ b/src/basic_recipes/timeseries.jl @@ -2,6 +2,7 @@ timeseries(x::Observable{{Union{Number, Point2}}}) Plots a sampled signal. + Usage: ```julia signal = Observable(1.0) @@ -20,11 +21,9 @@ end ``` """ -@recipe(TimeSeries, signal) do scene - Attributes( - history = 100; - default_theme(scene, Lines)... - ) +@recipe TimeSeries signal begin + history = 100 + MakieCore.documented_attributes(Lines)... end signal2point(signal::Number, start) = Point2f(time() - start, signal) diff --git a/src/basic_recipes/tooltip.jl b/src/basic_recipes/tooltip.jl index b94b557a74b..683e6ecd023 100644 --- a/src/basic_recipes/tooltip.jl +++ b/src/basic_recipes/tooltip.jl @@ -2,73 +2,53 @@ tooltip(position, string) tooltip(x, y, string) -Creates a tooltip pointing at `position` displaying the given `string` - -## Attributes - -### Generic - -- `visible::Bool = true` sets whether the plot will be rendered or not. -- `overdraw::Bool = false` sets whether the plot will draw over other plots. This specifically means ignoring depth checks in GL backends. -- `transparency::Bool = false` adjusts how the plot deals with transparency. In GLMakie `transparency = true` results in using Order Independent Transparency. -- `inspectable::Bool = true` sets whether this plot should be seen by `DataInspector`. -- `depth_shift::Float32 = 0f0` adjusts the depth value of a plot after all other transformations, i.e. in clip space, where `0 <= depth <= 1`. This only applies to GLMakie and WGLMakie and can be used to adjust render order (like a tunable overdraw). -- `space::Symbol = :data` sets the transformation space for positions of markers. See `Makie.spaces()` for possible inputs. - -### Tooltip specific - -- `offset = 10` sets the offset between the given `position` and the tip of the triangle pointing at that position. -- `placement = :above` sets where the tooltipü should be placed relative to `position`. Can be `:above`, `:below`, `:left`, `:right`. -- `align = 0.5` sets the alignment of the tooltip relative `position`. With `align = 0.5` the tooltip is centered above/below/left/right the `position`. -- `backgroundcolor = :white` sets the background color of the tooltip. -- `triangle_size = 10` sets the size of the triangle pointing at `position`. -- `outline_color = :black` sets the color of the tooltip outline. -- `outline_linewidth = 2f0` sets the linewidth of the tooltip outline. -- `outline_linestyle = nothing` sets the linestyle of the tooltip outline. - -- `textpadding = (4, 4, 4, 4)` sets the padding around text in the tooltip. This is given as `(left, right, bottom top)` offsets. -- `textcolor = theme(scene, :textcolor)` sets the text color. -- `fontsize = 16` sets the text size. -- `font = theme(scene, :font)` sets the font. -- `strokewidth = 0`: Gives text an outline if set to a positive value. -- `strokecolor = :white` sets the text outline color. -- `justification = :left` sets whether text is aligned to the `:left`, `:center` or `:right` within its bounding box. +Creates a tooltip pointing at `position` displaying the given `string """ -@recipe(Tooltip, position) do scene - Attributes(; - # General - text = "", - offset = 10, - placement = :above, - align = 0.5, - xautolimits = false, - yautolimits = false, - zautolimits = false, - overdraw = false, - depth_shift = 0f0, - transparency = false, - visible = true, - inspectable = false, - space = :data, - - # Text - textpadding = (4, 4, 4, 4), # LRBT - textcolor = theme(scene, :textcolor), - fontsize = 16, - font = theme(scene, :font), - strokewidth = 0, - strokecolor = :white, - justification = :left, +@recipe Tooltip position begin + # General + text = "" + "Sets the offset between the given `position` and the tip of the triangle pointing at that position." + offset = 10 + "Sets where the tooltip should be placed relative to `position`. Can be `:above`, `:below`, `:left`, `:right`." + placement = :above + "Sets the alignment of the tooltip relative `position`. With `align = 0.5` the tooltip is centered above/below/left/right the `position`." + align = 0.5 + xautolimits = false + yautolimits = false + zautolimits = false - # Background - backgroundcolor = :white, - triangle_size = 10, + # Text + "Sets the padding around text in the tooltip. This is given as `(left, right, bottom, top)` offsets." + textpadding = (4, 4, 4, 4) # LRBT + "Sets the text color." + textcolor = @inherit textcolor + "Sets the text size in screen units." + fontsize = 16 + "Sets the font." + font = @inherit font + "Gives text an outline if set to a positive value." + strokewidth = 0 + "Sets the text outline color." + strokecolor = :white + "Sets whether text is aligned to the `:left`, `:center` or `:right` within its bounding box." + justification = :left + + # Background + "Sets the background color of the tooltip." + backgroundcolor = :white + "Sets the size of the triangle pointing at `position`." + triangle_size = 10 - # Outline - outline_color = :black, - outline_linewidth = 2f0, - outline_linestyle = nothing, - ) + # Outline + "Sets the color of the tooltip outline." + outline_color = :black + "Sets the linewidth of the tooltip outline." + outline_linewidth = 2f0 + "Sets the linestyle of the tooltip outline." + outline_linestyle = nothing + + MakieCore.mixin_generic_plot_attributes()... + inspectable = false end convert_arguments(::Type{<: Tooltip}, x::Real, y::Real, str::AbstractString) = (Point2f(x, y), str) diff --git a/src/basic_recipes/tricontourf.jl b/src/basic_recipes/tricontourf.jl index e711345735d..0a1be854f5e 100644 --- a/src/basic_recipes/tricontourf.jl +++ b/src/basic_recipes/tricontourf.jl @@ -7,47 +7,50 @@ struct DelaunayTriangulation end Plots a filled tricontour of the height information in `zs` at the horizontal positions `xs` and vertical positions `ys`. A `Triangulation` from DelaunayTriangulation.jl can also be provided instead of `xs` and `ys` for specifying the triangles, otherwise an unconstrained triangulation of `xs` and `ys` is computed. - -## Attributes - -### Specific to `Tricontourf` - -- `levels = 10` can be either an `Int` which results in n bands delimited by n+1 equally spaced levels, or it can be an `AbstractVector{<:Real}` that lists n consecutive edges from low to high, which result in n-1 bands. -- `mode = :normal` sets the way in which a vector of levels is interpreted, if it's set to `:relative`, each number is interpreted as a fraction between the minimum and maximum values of `zs`. For example, `levels = 0.1:0.1:1.0` would exclude the lower 10% of data. -- `extendlow = nothing`. This sets the color of an optional additional band from `minimum(zs)` to the lowest value in `levels`. If it's `:auto`, the lower end of the colormap is picked and the remaining colors are shifted accordingly. If it's any color representation, this color is used. If it's `nothing`, no band is added. -- `extendhigh = nothing`. This sets the color of an optional additional band from the highest value of `levels` to `maximum(zs)`. If it's `:auto`, the high end of the colormap is picked and the remaining colors are shifted accordingly. If it's any color representation, this color is used. If it's `nothing`, no band is added. -- `triangulation = DelaunayTriangulation()`. The mode with which the points in `xs` and `ys` are triangulated. Passing `DelaunayTriangulation()` performs a Delaunay triangulation. You can also pass a preexisting triangulation as an `AbstractMatrix{<:Int}` with size (3, n), where each column specifies the vertex indices of one triangle, or as a `Triangulation` from DelaunayTriangulation.jl. - -### Generic - -- `visible::Bool = true` sets whether the plot will be rendered or not. -- `overdraw::Bool = false` sets whether the plot will draw over other plots. This specifically means ignoring depth checks in GL backends. -- `transparency::Bool = false` adjusts how the plot deals with transparency. In GLMakie `transparency = true` results in using Order Independent Transparency. -- `fxaa::Bool = false` adjusts whether the plot is rendered with fxaa (anti-aliasing). -- `inspectable::Bool = true` sets whether this plot should be seen by `DataInspector`. -- `depth_shift::Float32 = 0f0` adjusts the depth value of a plot after all other transformations, i.e. in clip space, where `0 <= depth <= 1`. This only applies to GLMakie and WGLMakie and can be used to adjust render order (like a tunable overdraw). -- `model::Makie.Mat4f` sets a model matrix for the plot. This replaces adjustments made with `translate!`, `rotate!` and `scale!`. -- `color` sets the color of the plot. It can be given as a named color `Symbol` or a `Colors.Colorant`. Transparency can be included either directly as an alpha value in the `Colorant` or as an additional float in a tuple `(color, alpha)`. The color can also be set for each scattered marker by passing a `Vector` of colors or be used to index the `colormap` by passing a `Real` number or `Vector{<: Real}`. -- `colormap::Union{Symbol, Vector{<:Colorant}} = :viridis` sets the colormap from which the band colors are sampled. -- `colorscale::Function = identity` color transform function. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Tricontourf) do scene - Theme( - levels = 10, - mode = :normal, - colormap = theme(scene, :colormap), - colorscale = identity, - extendlow = nothing, - extendhigh = nothing, - nan_color = :transparent, - inspectable = theme(scene, :inspectable), - transparency = false, - triangulation = DelaunayTriangulation(), - edges = nothing, - ) +@recipe Tricontourf begin + "Can be either an `Int` which results in n bands delimited by n+1 equally spaced levels, or it can be an `AbstractVector{<:Real}` that lists n consecutive edges from low to high, which result in n-1 bands." + levels = 10 + """ + Sets the way in which a vector of levels is interpreted, + if it's set to `:relative`, each number is interpreted as a fraction + between the minimum and maximum values of `zs`. + For example, `levels = 0.1:0.1:1.0` would exclude the lower 10% of data. + """ + mode = :normal + "Sets the colormap from which the band colors are sampled." + colormap = @inherit colormap + "Color transform function" + colorscale = identity + """ + This sets the color of an optional additional band from + `minimum(zs)` to the lowest value in `levels`. + If it's `:auto`, the lower end of the colormap is picked + and the remaining colors are shifted accordingly. + If it's any color representation, this color is used. + If it's `nothing`, no band is added. + """ + extendlow = nothing + """ + This sets the color of an optional additional band from + the highest value of `levels` to `maximum(zs)`. + If it's `:auto`, the high end of the colormap is picked + and the remaining colors are shifted accordingly. + If it's any color representation, this color is used. + If it's `nothing`, no band is added. + """ + extendhigh = nothing + nan_color = :transparent + """ + The mode with which the points in `xs` and `ys` are triangulated. + Passing `DelaunayTriangulation()` performs a Delaunay triangulation. + You can also pass a preexisting triangulation as an `AbstractMatrix{<:Int}` + with size (3, n), where each column specifies the vertex indices of one triangle, + or as a `Triangulation` from DelaunayTriangulation.jl. + """ + triangulation = DelaunayTriangulation() + edges = nothing + MakieCore.mixin_generic_plot_attributes()... end function Makie.used_attributes(::Type{<:Tricontourf}, ::AbstractVector{<:Real}, ::AbstractVector{<:Real}, ::AbstractVector{<:Real}) diff --git a/src/basic_recipes/triplot.jl b/src/basic_recipes/triplot.jl index 917e3ff4265..c746f6e101a 100644 --- a/src/basic_recipes/triplot.jl +++ b/src/basic_recipes/triplot.jl @@ -4,72 +4,63 @@ triplot(triangles::Triangulation; kwargs...) Plots a triangulation based on the provided position or `Triangulation` from DelaunayTriangulation.jl. - -## Attributes - -- `show_points = false` determines whether to plot the individual points. Note that this will only plot points included in the triangulation. -- `show_convex_hull = false` determines whether to plot the convex hull. -- `show_ghost_edges = false` determines whether to plot the ghost edges. -- `show_constrained_edges = false` determines whether to plot the constrained edges. -- `recompute_centers = false` determines whether to recompute the representative points for the ghost edge orientation. Note that this will mutate `tri.representative_point_list` directly. - -- `markersize = 12` sets the size of the points. -- `marker = :circle` sets the shape of the points. -- `markercolor = :black` sets the color of the points. -- `strokecolor = :black` sets the color of triangle edges. -- `strokewidth = 1` sets the linewidth of triangle edges. -- `linestyle = :solid` sets the linestyle of triangle edges. -- `triangle_color = (:white, 0.0)` sets the color of the triangles. - -- `convex_hull_color = :red` sets the color of the convex hull. -- `convex_hull_linestyle = :dash` sets the linestyle of the convex hull. -- `convex_hull_linewidth = 1` sets the width of the convex hull. - -- `ghost_edge_color = :blue` sets the color of the ghost edges. -- `ghost_edge_linestyle = :solid` sets the linestyle of the ghost edges. -- `ghost_edge_linewidth = 1` sets the width of the ghost edges. -- `ghost_edge_extension_factor = 0.1` sets the extension factor for the rectangle that the exterior ghost edges are extended onto. -- `bounding_box::Union{Automatic, Rect2, Tuple} = automatic`: Sets the bounding box for truncating ghost edges which can be a `Rect2` (or `BBox`) or a tuple of the form `(xmin, xmax, ymin, ymax)`. By default, the rectangle will be given by `[a - eΔx, b + eΔx] × [c - eΔy, d + eΔy]` where `e` is the `ghost_edge_extension_factor`, `Δx = b - a` and `Δy = d - c` are the lengths of the sides of the rectangle, and `[a, b] × [c, d]` is the bounding box of the points in the triangulation. - -- `constrained_edge_color = :magenta` sets the color of the constrained edges. -- `constrained_edge_linestyle = :solid` sets the linestyle of the constrained edges. -- `constrained_edge_linewidth = 1` sets the width of the constrained edges. """ -@recipe(Triplot, triangles) do scene - sc = default_theme(scene, Scatter) - return Attributes(; - # Toggles - show_points=false, - show_convex_hull=false, - show_ghost_edges=false, - show_constrained_edges=false, - recompute_centers=false, - - # Mesh settings - markersize=theme(scene, :markersize), - marker=theme(scene, :marker), - markercolor=sc.color, - strokecolor=theme(scene, :patchstrokecolor), - strokewidth=1, - linestyle=:solid, - triangle_color=(:white, 0.0), - - # Convex hull settings - convex_hull_color=:red, - convex_hull_linestyle=:dash, - convex_hull_linewidth=theme(scene, :linewidth), - - # Ghost edge settings - ghost_edge_color=:blue, - ghost_edge_linestyle=theme(scene, :linestyle), - ghost_edge_linewidth=theme(scene, :linewidth), - ghost_edge_extension_factor=0.1, - bounding_box=automatic, - - # Constrained edge settings - constrained_edge_color=:magenta, - constrained_edge_linestyle=theme(scene, :linestyle), - constrained_edge_linewidth=theme(scene, :linewidth)) +@recipe Triplot triangles begin + # Toggles + "Determines whether to plot the individual points. Note that this will only plot points included in the triangulation." + show_points=false + "Determines whether to plot the convex hull." + show_convex_hull=false + "Determines whether to plot the ghost edges." + show_ghost_edges=false + "Determines whether to plot the constrained edges." + show_constrained_edges=false + "Determines whether to recompute the representative points for the ghost edge orientation. Note that this will mutate `tri.representative_point_list` directly." + recompute_centers=false + + # Mesh settings + "Sets the size of the points." + markersize= @inherit markersize + "Sets the shape of the points." + marker= @inherit marker + "Sets the color of the points." + markercolor= @inherit markercolor + "Sets the color of triangle edges." + strokecolor= @inherit patchstrokecolor + "Sets the linewidth of triangle edges." + strokewidth=1 + "Sets the linestyle of triangle edges." + linestyle=:solid + "Sets the color of the triangles." + triangle_color= :transparent + + # Convex hull settings + "Sets the color of the convex hull." + convex_hull_color=:red + "Sets the linestyle of the convex hull." + convex_hull_linestyle=:dash + "Sets the width of the convex hull." + convex_hull_linewidth= @inherit linewidth + + # Ghost edge settings + "Sets the color of the ghost edges." + ghost_edge_color=:blue + "Sets the linestyle of the ghost edges." + ghost_edge_linestyle= @inherit linestyle + "Sets the width of the ghost edges." + ghost_edge_linewidth= @inherit linewidth + "Sets the extension factor for the rectangle that the exterior ghost edges are extended onto." + ghost_edge_extension_factor=0.1 + "Sets the bounding box for truncating ghost edges which can be a `Rect2` (or `BBox`) or a tuple of the form `(xmin, xmax, ymin, ymax)`. By default, the rectangle will be given by `[a - eΔx, b + eΔx] × [c - eΔy, d + eΔy]` where `e` is the `ghost_edge_extension_factor`, `Δx = b - a` and `Δy = d - c` are the lengths of the sides of the rectangle, and `[a, b] × [c, d]` is the bounding box of the points in the triangulation." + bounding_box=automatic + + # Constrained edge settings + "Sets the color of the constrained edges." + constrained_edge_color=:magenta + "Sets the linestyle of the constrained edges." + constrained_edge_linestyle= @inherit linestyle + "Sets the width of the constrained edges." + constrained_edge_linewidth= @inherit linewidth end function get_all_triangulation_points!(points, tri) diff --git a/src/basic_recipes/volumeslices.jl b/src/basic_recipes/volumeslices.jl index a43598099b5..4a8b19bf3a2 100644 --- a/src/basic_recipes/volumeslices.jl +++ b/src/basic_recipes/volumeslices.jl @@ -5,16 +5,11 @@ VolumeSlices volumeslices(x, y, z, v) Draws heatmap slices of the volume v - -## Attributes -$(ATTRIBUTES) """ -@recipe(VolumeSlices, x, y, z, volume) do scene - Attributes(; - default_theme(scene, Heatmap)..., - bbox_visible = true, - bbox_color = RGBAf(0.5, 0.5, 0.5, 0.5) - ) +@recipe VolumeSlices x y z volume begin + MakieCore.documented_attributes(Heatmap)... + bbox_visible = true + bbox_color = RGBAf(0.5, 0.5, 0.5, 0.5) end function plot!(plot::VolumeSlices) diff --git a/src/basic_recipes/voronoiplot.jl b/src/basic_recipes/voronoiplot.jl index 7ce3d6f87b2..2d6963a2f6b 100644 --- a/src/basic_recipes/voronoiplot.jl +++ b/src/basic_recipes/voronoiplot.jl @@ -8,44 +8,32 @@ Generates and plots a Voronoi tessalation from `heatmap`- or point-like data. The tessellation can also be passed directly as a `VoronoiTessellation` from DelaunayTriangulation.jl. - -## Attributes - -- `show_generators = true` determines whether to plot the individual generators. - -- `markersize = 12` sets the size of the points. -- `marker = :circle` sets the shape of the points. -- `markercolor = :black` sets the color of the points. - -- `strokecolor = :black` sets the strokecolor of the polygons. -- `strokewidth = 1` sets the width of the polygon stroke. -- `color = automatic` sets the color of the polygons. If `automatic`, the polygons will be individually colored according to the colormap. -- `unbounded_edge_extension_factor = 0.1` sets the extension factor for the unbounded edges, used in `DelaunayTriangulation.polygon_bounds`. -- `clip::Union{Automatic, Rect2, Circle, Tuple} = automatic` sets the clipping area for the generated polygons which can be a `Rect2` (or `BBox`), `Tuple` with entries `(xmin, xmax, ymin, ymax)` or as a `Circle`. Anything outside the specified area will be removed. If the `clip` is not set it is automatically determined using `unbounded_edge_extension_factor` as a `Rect`. - -$(Base.Docs.doc(MakieCore.colormap_attributes!)) """ -@recipe(Voronoiplot, vorn) do scene - th = default_theme(scene, Mesh) - sc = default_theme(scene, Scatter) - attr = Attributes(; - # Toggles - show_generators=true, - smooth=false, - - # Point settings - markersize=sc.markersize, - marker=sc.marker, - markercolor=sc.color, - - # Polygon settings - strokecolor=theme(scene, :patchstrokecolor), - strokewidth=1.0, - color=automatic, - unbounded_edge_extension_factor=0.1, - clip=automatic) - MakieCore.colormap_attributes!(attr, theme(scene, :colormap)) - return attr +@recipe Voronoiplot vorn begin + "Determines whether to plot the individual generators." + show_generators=true + smooth=false + + # Point settings + "Sets the size of the points." + markersize= @inherit markersize + "Sets the shape of the points." + marker= @inherit marker + "Sets the color of the points." + markercolor= @inherit markercolor + + # Polygon settings + "Sets the strokecolor of the polygons." + strokecolor= @inherit patchstrokecolor + "Sets the width of the polygon stroke." + strokewidth=1.0 + "Sets the color of the polygons. If `automatic`, the polygons will be individually colored according to the colormap." + color=automatic + "Sets the extension factor for the unbounded edges, used in `DelaunayTriangulation.polygon_bounds`." + unbounded_edge_extension_factor=0.1 + "Sets the clipping area for the generated polygons which can be a `Rect2` (or `BBox`), `Tuple` with entries `(xmin, xmax, ymin, ymax)` or as a `Circle`. Anything outside the specified area will be removed. If the `clip` is not set it is automatically determined using `unbounded_edge_extension_factor` as a `Rect`." + clip=automatic + MakieCore.mixin_colormap_attributes()... end function _clip_polygon(poly::Polygon, circle::Circle) diff --git a/src/basic_recipes/waterfall.jl b/src/basic_recipes/waterfall.jl index 1aa0bf787cb..562eb301ae4 100644 --- a/src/basic_recipes/waterfall.jl +++ b/src/basic_recipes/waterfall.jl @@ -4,30 +4,24 @@ Plots a [waterfall chart](https://en.wikipedia.org/wiki/Waterfall_chart) to visualize individual positive and negative components that add up to a net result as a barplot with stacked bars next to each other. - -## Attributes -$(ATTRIBUTES) - -Furthermore the same attributes as for `barplot` are supported. """ -@recipe(Waterfall, x, y) do scene - return Attributes(; - dodge=automatic, - n_dodge=automatic, - gap=0.2, - dodge_gap=0.03, - width=automatic, - cycle=[:color => :patchcolor], - stack=automatic, - show_direction=false, - marker_pos=:utriangle, - marker_neg=:dtriangle, - direction_color=theme(scene, :backgroundcolor), - show_final=false, - final_color=plot_color(:grey90, 0.5), - final_gap=automatic, - final_dodge_gap=0, - ) +@recipe Waterfall x y begin + color = @inherit patchcolor + dodge=automatic + n_dodge=automatic + gap=0.2 + dodge_gap=0.03 + width=automatic + cycle=[:color => :patchcolor] + stack=automatic + show_direction=false + marker_pos=:utriangle + marker_neg=:dtriangle + direction_color= @inherit backgroundcolor + show_final=false + final_color=plot_color(:grey90, 0.5) + final_gap=automatic + final_dodge_gap=0 end conversion_trait(::Type{<:Waterfall}) = PointBased() @@ -67,10 +61,20 @@ function Makie.plot!(p::Waterfall) ) end + bar_attrs = copy(p.attributes) + delete!(bar_attrs, :direction_color) + delete!(bar_attrs, :marker_pos) + delete!(bar_attrs, :final_color) + delete!(bar_attrs, :final_dodge_gap) + delete!(bar_attrs, :show_direction) + delete!(bar_attrs, :final_gap) + delete!(bar_attrs, :show_final) + delete!(bar_attrs, :marker_neg) + barplot!( p, lift(x -> x.xy, p, fromto); - p.attributes..., + bar_attrs..., fillto=lift(x -> x.fillto, p, fromto), stack=automatic, ) diff --git a/src/basic_recipes/wireframe.jl b/src/basic_recipes/wireframe.jl index bbf6d271e0d..d22fa051121 100644 --- a/src/basic_recipes/wireframe.jl +++ b/src/basic_recipes/wireframe.jl @@ -1,20 +1,3 @@ -""" - wireframe(x, y, z) - wireframe(positions) - wireframe(mesh) - -Draws a wireframe, either interpreted as a surface or as a mesh. - -## Attributes -$(ATTRIBUTES) -""" -wireframe - -""" -See [`wireframe`](@ref). -""" -wireframe! - function convert_arguments(::Type{<: Wireframe}, x::AbstractVector, y::AbstractVector, z::AbstractMatrix) (ngrid(x, y)..., z) end diff --git a/src/interfaces.jl b/src/interfaces.jl index bcd154eabf9..bc72bc46ed6 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -138,7 +138,7 @@ function Plot{Func}(args::Tuple, plot_attributes::Dict) where {Func} if used_attrs === () args_converted = convert_arguments(P, map(to_value, args)...) else - kw = [Pair(k, to_value(v)) for (k, v) in plot_attributes if k in used_attrs] + kw = [Pair(k, to_value(pop!(plot_attributes, k))) for k in used_attrs if haskey(plot_attributes, k)] args_converted = convert_arguments(P, map(to_value, args)...; kw...) end preconvert_attr = Attributes() diff --git a/src/makielayout/blocks.jl b/src/makielayout/blocks.jl index c6bf0bdb73f..a4646f5af7c 100644 --- a/src/makielayout/blocks.jl +++ b/src/makielayout/blocks.jl @@ -137,7 +137,7 @@ end function make_attr_dict_expr(attrs, sceneattrsym, curthemesym) - pairs = map(attrs) do a + exprs = map(attrs) do a d = a.default if d isa Expr && d.head === :macrocall && d.args[1] == Symbol("@inherit") @@ -162,10 +162,14 @@ function make_attr_dict_expr(attrs, sceneattrsym, curthemesym) end end - Expr(:call, :(=>), QuoteNode(a.symbol), d) + :(d[$(QuoteNode(a.symbol))] = $d) end - :(Dict($(pairs...))) + quote + d = Dict{Symbol,Any}() + $(exprs...) + d + end end @@ -205,44 +209,14 @@ function extract_attributes!(body) args = filter(x -> !(x isa LineNumberNode), attrs_block.args) - function extract_attr(arg) - has_docs = arg isa Expr && arg.head === :macrocall && arg.args[1] isa GlobalRef - - if has_docs - docs = arg.args[3] - attr = arg.args[4] - else - docs = nothing - attr = arg - end - - if !(attr isa Expr && attr.head === :(=) && length(attr.args) == 2) - error("$attr is not a valid attribute line like :x[::Type] = default_value") - end - left = attr.args[1] - default = attr.args[2] - if left isa Symbol - attr_symbol = left - type = Any - else - if !(left isa Expr && left.head === :(::) && length(left.args) == 2) - error("$left is not a Symbol or an expression such as x::Type") - end - attr_symbol = left.args[1]::Symbol - type = left.args[2] - end - - (docs = docs, symbol = attr_symbol, type = type, default = default) - end - - attrs = map(extract_attr, args) + attrs::Vector{Any} = map(MakieCore.extract_attribute_metadata, args) - lras = map(extract_attr, layout_related_attributes) + lras = map(MakieCore.extract_attribute_metadata, layout_related_attributes) for lra in lras i = findfirst(x -> x.symbol == lra.symbol, attrs) if i === nothing - push!(attrs, extract_attr(lra)) + push!(attrs, lra) end end diff --git a/src/stats/boxplot.jl b/src/stats/boxplot.jl index e6cbe36c2a0..eeb80996792 100644 --- a/src/stats/boxplot.jl +++ b/src/stats/boxplot.jl @@ -13,62 +13,58 @@ The boxplot has 3 components: median - an `errorbar` whose whiskers span `range * iqr` - points marking outliers, that is, data outside the whiskers -# Arguments +## Arguments - `x`: positions of the categories - `y`: variables within the boxes -# Keywords -- `weights`: vector of statistical weights (length of data). By default, each observation has weight `1`. -- `orientation=:vertical`: orientation of box (`:vertical` or `:horizontal`) -- `width=1`: width of the box before shrinking -- `gap=0.2`: shrinking factor, `width -> width * (1 - gap)` -- `show_notch=false`: draw the notch -- `notchwidth=0.5`: multiplier of `width` for narrowest width of notch -- `show_median=true`: show median as midline -- `range`: multiple of IQR controlling whisker length -- `whiskerwidth`: multiplier of `width` for width of T's on whiskers, or - `:match` to match `width` -- `show_outliers`: show outliers as points -- `dodge`: vector of `Integer` (length of data) of grouping variable to create multiple side-by-side boxes at the same `x` position -- `dodge_gap = 0.03`: spacing between dodged boxes """ -@recipe(BoxPlot, x, y) do scene - Theme( - weights = automatic, - color = theme(scene, :patchcolor), - colormap = theme(scene, :colormap), - colorscale=identity, - colorrange = automatic, - orientation = :vertical, - # box and dodging - width = automatic, - dodge = automatic, - n_dodge = automatic, - gap = 0.2, - dodge_gap = 0.03, - strokecolor = theme(scene, :patchstrokecolor), - strokewidth = theme(scene, :patchstrokewidth), - # notch - show_notch = false, - notchwidth = 0.5, - # median line - show_median = true, - mediancolor = theme(scene, :linecolor), - medianlinewidth = theme(scene, :linewidth), - # whiskers - range = 1.5, - whiskerwidth = 0.0, - whiskercolor = theme(scene, :linecolor), - whiskerlinewidth = theme(scene, :linewidth), - # outliers points - show_outliers = true, - marker = theme(scene, :marker), - markersize = theme(scene, :markersize), - outliercolor = automatic, - outlierstrokecolor = theme(scene, :markerstrokecolor), - outlierstrokewidth = theme(scene, :markerstrokewidth), - cycle = [:color => :patchcolor], - inspectable = theme(scene, :inspectable) - ) +@recipe BoxPlot x y begin + "Vector of statistical weights (length of data). By default, each observation has weight `1`." + weights = automatic + color = @inherit patchcolor + colormap = @inherit colormap + colorscale=identity + colorrange = automatic + "Orientation of box (`:vertical` or `:horizontal`)." + orientation = :vertical + # box and dodging + "Width of the box before shrinking." + width = automatic + "Vector of `Integer` (length of data) of grouping variable to create multiple side-by-side boxes at the same `x` position." + dodge = automatic + n_dodge = automatic + "Shrinking factor, `width -> width * (1 - gap)`." + gap = 0.2 + "Spacing between dodged boxes." + dodge_gap = 0.03 + strokecolor = @inherit patchstrokecolor + strokewidth = @inherit patchstrokewidth + # notch + "Draw the notch." + show_notch = false + "Multiplier of `width` for narrowest width of notch." + notchwidth = 0.5 + # median line + "Show median as midline." + show_median = true + mediancolor = @inherit linecolor + medianlinewidth = @inherit linewidth + # whiskers + "Multiple of IQR controlling whisker length." + range = 1.5 + "Multiplier of `width` for width of T's on whiskers, or `:match` to match `width`." + whiskerwidth = 0.0 + whiskercolor = @inherit linecolor + whiskerlinewidth = @inherit linewidth + # outliers points + "Show outliers as points." + show_outliers = true + marker = @inherit marker + markersize = @inherit markersize + outliercolor = automatic + outlierstrokecolor = @inherit markerstrokecolor + outlierstrokewidth = @inherit markerstrokewidth + cycle = [:color => :patchcolor] + inspectable = @inherit inspectable end conversion_trait(x::Type{<:BoxPlot}) = SampleBased() diff --git a/src/stats/crossbar.jl b/src/stats/crossbar.jl index 5fb77c29f0f..bd60b5abde4 100644 --- a/src/stats/crossbar.jl +++ b/src/stats/crossbar.jl @@ -6,49 +6,45 @@ The StatMakie.jl package is licensed under the MIT "Expat" License: crossbar(x, y, ymin, ymax; kwargs...) Draw a crossbar. A crossbar represents a range with a (potentially notched) box. It is most commonly used as part of the `boxplot`. -# Arguments +## Arguments - `x`: position of the box - `y`: position of the midline within the box - `ymin`: lower limit of the box - `ymax`: upper limit of the box -# Keywords -- `orientation=:vertical`: orientation of box (`:vertical` or `:horizontal`) -- `width=1`: width of the box before shrinking -- `gap=0.2`: shrinking factor, `width -> width * (1 - gap)` -- `show_notch=false`: draw the notch -- `notchmin=automatic`: lower limit of the notch -- `notchmax=automatic`: upper limit of the notch -- `notchwidth=0.5`: multiplier of `width` for narrowest width of notch -- `show_midline=true`: show midline """ -@recipe(CrossBar, x, y, ymin, ymax) do scene - t = Theme( - color=theme(scene, :patchcolor), - colormap=theme(scene, :colormap), - colorscale=identity, - colorrange=automatic, - orientation=:vertical, +@recipe CrossBar x y ymin ymax begin + color= @inherit patchcolor + colormap= @inherit colormap + colorscale=identity + colorrange=automatic + "Orientation of box (`:vertical` or `:horizontal`)." + orientation=:vertical # box and dodging - width = automatic, - dodge = automatic, - n_dodge = automatic, - gap = 0.2, - dodge_gap = 0.03, - strokecolor = theme(scene, :patchstrokecolor), - strokewidth = theme(scene, :patchstrokewidth), + "Width of the box before shrinking." + width = automatic + dodge = automatic + n_dodge = automatic + "Shrinking factor, `width -> width * (1 - gap)`." + gap = 0.2 + dodge_gap = 0.03 + strokecolor = @inherit patchstrokecolor + strokewidth = @inherit patchstrokewidth # notch - show_notch=false, - notchmin=automatic, - notchmax=automatic, - notchwidth=0.5, + "Whether to draw the notch." + show_notch=false + "Lower limit of the notch." + notchmin=automatic + "Upper limit of the notch." + notchmax=automatic + "Multiplier of `width` for narrowest width of notch." + notchwidth=0.5 # median line - show_midline=true, - midlinecolor=automatic, - midlinewidth=theme(scene, :linewidth), - inspectable = theme(scene, :inspectable), - cycle = [:color => :patchcolor], -) - t + "Show midline." + show_midline=true + midlinecolor=automatic + midlinewidth= @inherit linewidth + inspectable = @inherit inspectable + cycle = [:color => :patchcolor] end function Makie.plot!(plot::CrossBar) diff --git a/src/stats/density.jl b/src/stats/density.jl index 82034cce2e3..36d35bb8dc1 100644 --- a/src/stats/density.jl +++ b/src/stats/density.jl @@ -17,41 +17,38 @@ function convert_arguments(P::PlotFunc, d::KernelDensity.BivariateKDE) end """ - density(values; npoints = 200, offset = 0.0, direction = :x) + density(values) Plot a kernel density estimate of `values`. -`npoints` controls the resolution of the estimate, the baseline can be -shifted with `offset` and the `direction` set to `:x` or `:y`. -`bandwidth` and `boundary` are determined automatically by default. - -Statistical weights can be provided via the `weights` keyword argument. - -`color` is usually set to a single color, but can also be set to `:x` or -`:y` to color with a gradient. If you use `:y` when `direction = :x` (or vice versa), -note that only 2-element colormaps can work correctly. - -## Attributes -$(ATTRIBUTES) """ -@recipe(Density) do scene - Theme( - color = theme(scene, :patchcolor), - colormap = theme(scene, :colormap), - colorscale = identity, - colorrange = Makie.automatic, - strokecolor = theme(scene, :patchstrokecolor), - strokewidth = theme(scene, :patchstrokewidth), - linestyle = nothing, - strokearound = false, - npoints = 200, - offset = 0.0, - direction = :x, - boundary = automatic, - bandwidth = automatic, - weights = automatic, - cycle = [:color => :patchcolor], - inspectable = theme(scene, :inspectable) - ) +@recipe Density begin + """ + Usually set to a single color, but can also be set to `:x` or + `:y` to color with a gradient. If you use `:y` when `direction = :x` (or vice versa), + note that only 2-element colormaps can work correctly. + """ + color = @inherit patchcolor + colormap = @inherit colormap + colorscale = identity + colorrange = Makie.automatic + strokecolor = @inherit patchstrokecolor + strokewidth = @inherit patchstrokewidth + linestyle = nothing + strokearound = false + "The resolution of the estimated curve along the dimension set in `direction`." + npoints = 200 + "Shift the density baseline, for layering multiple densities on top of each other." + offset = 0.0 + "The dimension along which the `values` are distributed. Can be `:x` or `:y`." + direction = :x + "Boundary of the density estimation, determined automatically if `automatic`." + boundary = automatic + "Kernel density bandwidth, determined automatically if `automatic`." + bandwidth = automatic + "Assign a vector of statistical weights to `values`." + weights = automatic + cycle = [:color => :patchcolor] + inspectable = @inherit inspectable end function plot!(plot::Density{<:Tuple{<:AbstractVector}}) diff --git a/src/stats/distributions.jl b/src/stats/distributions.jl index 6f2221119f3..0586f590800 100644 --- a/src/stats/distributions.jl +++ b/src/stats/distributions.jl @@ -44,32 +44,19 @@ Possible values are the following. Broadly speaking, `qqline = :identity` is useful to see if `x` and `y` follow the same distribution, whereas `qqline = :fit` and `qqline = :fitrobust` are useful to see if the distribution of `y` can be obtained from the distribution of `x` via an affine transformation. - -Graphical attributes are -- `color` to control color of both line and markers (if `markercolor` is not specified) -- `linestyle` -- `linewidth` -- `markercolor` -- `strokecolor` -- `strokewidth` -- `marker` -- `markersize` """ -@recipe(QQPlot) do scene - s_theme = default_theme(scene, Scatter) - l_theme = default_theme(scene, Lines) - Attributes( - color = l_theme.color, - linestyle = l_theme.linestyle, - linewidth = l_theme.linewidth, - markercolor = automatic, - markersize = s_theme.markersize, - strokecolor = s_theme.strokecolor, - strokewidth = s_theme.strokewidth, - marker = s_theme.marker, - inspectable = theme(scene, :inspectable), - cycle = [:color], - ) +@recipe QQPlot begin + "Control color of both line and markers (if `markercolor` is not specified)." + color = @inherit linecolor + linestyle = nothing + linewidth = @inherit linewidth + markercolor = automatic + markersize = @inherit markersize + strokecolor = @inherit markerstrokecolor + strokewidth = @inherit markerstrokewidth + marker = @inherit marker + MakieCore.mixin_generic_plot_attributes()... + cycle = [:color] end """ @@ -78,8 +65,8 @@ end Shorthand for `qqplot(Normal(0,1), y)`, i.e., draw a Q-Q plot of `y` against the standard normal distribution. See `qqplot` for more details. """ -@recipe(QQNorm) do scene - default_theme(scene, QQPlot) +@recipe QQNorm begin + MakieCore.documented_attributes(QQPlot)... end # Compute points and line for the qqplot diff --git a/src/stats/ecdf.jl b/src/stats/ecdf.jl index f8038456a9e..9c89e3cc5b7 100644 --- a/src/stats/ecdf.jl +++ b/src/stats/ecdf.jl @@ -44,12 +44,9 @@ Plot the empirical cumulative distribution function (ECDF) of `values`. `npoints` controls the resolution of the plot. If `weights` for the values are provided, a weighted ECDF is plotted. - -## Attributes -$(ATTRIBUTES) """ -@recipe(ECDFPlot) do scene - default_theme(scene, Stairs) +@recipe ECDFPlot begin + MakieCore.documented_attributes(Stairs)... end used_attributes(::Type{<:ECDFPlot}, ::AbstractVector) = (:npoints, :weights) diff --git a/src/stats/hexbin.jl b/src/stats/hexbin.jl index c163f29bf0a..0cd4b9411eb 100644 --- a/src/stats/hexbin.jl +++ b/src/stats/hexbin.jl @@ -2,36 +2,19 @@ hexbin(xs, ys; kwargs...) Plots a heatmap with hexagonal bins for the observations `xs` and `ys`. - -## Attributes - -### Specific to `Hexbin` - -- `weights = nothing`: Weights for each observation. Can be `nothing` (each observation carries weight 1) or any `AbstractVector{<: Real}` or `StatsBase.AbstractWeights`. -- `bins = 20`: If an `Int`, sets the number of bins in x and y direction. If a `Tuple{Int, Int}`, sets the number of bins for x and y separately. -- `cellsize = nothing`: If a `Real`, makes equally-sided hexagons with width `cellsize`. If a `Tuple{Real, Real}` specifies hexagon width and height separately. -- `threshold::Int = 1`: The minimal number of observations in the bin to be shown. If 0, all zero-count hexagons fitting into the data limits will be shown. -- `colorscale = identity`: A function to scale the number of observations in a bin, eg. log10. - -### Generic - -- `colormap::Union{Symbol, Vector{<:Colorant}} = :viridis` -- `colorrange::Tuple(<:Real,<:Real} = Makie.automatic` sets the values representing the start and end points of `colormap`. """ -@recipe(Hexbin) do scene - return Attributes(; - colormap=theme(scene, :colormap), - colorscale=identity, - colorrange=Makie.automatic, - lowclip = automatic, - highclip = automatic, - nan_color = :transparent, - bins=20, - weights=nothing, - cellsize=nothing, - threshold=1, - strokewidth=0, - strokecolor=:black) +@recipe Hexbin begin + "If an `Int`, sets the number of bins in x and y direction. If a `Tuple{Int, Int}`, sets the number of bins for x and y separately." + bins=20 + "Weights for each observation. Can be `nothing` (each observation carries weight 1) or any `AbstractVector{<: Real}` or `StatsBase.AbstractWeights`." + weights=nothing + "If a `Real`, makes equally-sided hexagons with width `cellsize`. If a `Tuple{Real, Real}` specifies hexagon width and height separately." + cellsize=nothing + "The minimal number of observations in the bin to be shown. If 0, all zero-count hexagons fitting into the data limits will be shown." + threshold=1 + strokewidth=0 + strokecolor=:black + MakieCore.mixin_colormap_attributes()... end function spacings_offsets_nbins(bins::Tuple{Int,Int}, cellsize::Nothing, xmi, xma, ymi, yma) diff --git a/src/stats/hist.jl b/src/stats/hist.jl index 165f361bc07..0feb0024219 100644 --- a/src/stats/hist.jl +++ b/src/stats/hist.jl @@ -21,34 +21,35 @@ function _hist_center_weights(values, edges, normalization, scale_to, wgts) end """ - stephist(values; bins = 15, normalization = :none) + stephist(values) -Plot a step histogram of `values`. `bins` can be an `Int` to create that -number of equal-width bins over the range of `values`. -Alternatively, it can be a sorted iterable of bin edges. The histogram -can be normalized by setting `normalization`. - -Shares most options with `hist` plotting function. - -Statistical weights can be provided via the `weights` keyword argument. - -The following attributes can move the histogram around, -which comes in handy when placing multiple histograms into one plot: -* `scale_to = nothing`: allows to scale all values to a certain height - -## Attributes -$(ATTRIBUTES) +Plot a step histogram of `values`. """ -@recipe(StepHist, values) do scene - Attributes( - bins = 15, # Int or iterable of edges - normalization = :none, - weights = automatic, - cycle = [:color => :patchcolor], - color = theme(scene, :patchcolor), - linestyle = :solid, - scale_to = nothing, - ) +@recipe StepHist values begin + "Can be an `Int` to create that number of equal-width bins over the range of `values`. Alternatively, it can be a sorted iterable of bin edges." + bins = 15 # Int or iterable of edges + """Allows to apply a normalization to the histogram. + Possible values are: + + * `:pdf`: Normalize by sum of weights and bin sizes. Resulting histogram + has norm 1 and represents a PDF. + * `:density`: Normalize by bin sizes only. Resulting histogram represents + count density of input and does not have norm 1. Will not modify the + histogram if it already represents a density (`h.isdensity == 1`). + * `:probability`: Normalize by sum of weights only. Resulting histogram + represents the fraction of probability mass for each bin and does not have + norm 1. + * `:none`: Do not normalize. + """ + normalization = :none + "Allows to provide statistical weights." + weights = automatic + cycle = [:color => :patchcolor] + color = @inherit patchcolor + linewidth = @inherit linewidth + linestyle = :solid + "Allows to scale all values to a certain height." + scale_to = nothing end function Makie.plot!(plot::StepHist) @@ -83,61 +84,58 @@ function Makie.plot!(plot::StepHist) end """ - hist(values; bins = 15, normalization = :none) - -Plot a histogram of `values`. `bins` can be an `Int` to create that -number of equal-width bins over the range of `values`. -Alternatively, it can be a sorted iterable of bin edges. The histogram -can be normalized by setting `normalization`. Possible values are: - -* `:pdf`: Normalize by sum of weights and bin sizes. Resulting histogram - has norm 1 and represents a PDF. -* `:density`: Normalize by bin sizes only. Resulting histogram represents - count density of input and does not have norm 1. Will not modify the - histogram if it already represents a density (`h.isdensity == 1`). -* `:probability`: Normalize by sum of weights only. Resulting histogram - represents the fraction of probability mass for each bin and does not have - norm 1. -* `:none`: Do not normalize. - -Statistical weights can be provided via the `weights` keyword argument. - -The following attributes can move the histogram around, -which comes in handy when placing multiple histograms into one plot: -* `offset = 0.0`: adds an offset to every value -* `fillto = 0.0`: defines where the bar starts -* `scale_to = nothing`: allows to scale all values to a certain height -* `flip = false`: flips all values - -Color can either be: -* a vector of `bins` colors -* a single color -* `:values`, to color the bars with the values from the histogram - -## Attributes -$(ATTRIBUTES) + hist(values) + +Plot a histogram of `values`. """ -@recipe(Hist, values) do scene - Attributes( - bins = 15, # Int or iterable of edges - normalization = :none, - weights = automatic, - cycle = [:color => :patchcolor], - color = theme(scene, :patchcolor), - offset = 0.0, - fillto = automatic, - scale_to = nothing, - - bar_labels = nothing, - flip_labels_at = Inf, - label_color = theme(scene, :textcolor), - over_background_color = automatic, - over_bar_color = automatic, - label_offset = 5, - label_font = theme(scene, :font), - label_size = 20, - label_formatter = bar_label_formatter - ) +@recipe Hist values begin + """ + Can be an `Int` to create that number of equal-width bins over the range of `values`. Alternatively, it can be a sorted iterable of bin edges. + """ + bins = 15 + """ + Allows to normalize the histogram. Possible values are: + + * `:pdf`: Normalize by sum of weights and bin sizes. Resulting histogram + has norm 1 and represents a PDF. + * `:density`: Normalize by bin sizes only. Resulting histogram represents + count density of input and does not have norm 1. Will not modify the + histogram if it already represents a density (`h.isdensity == 1`). + * `:probability`: Normalize by sum of weights only. Resulting histogram + represents the fraction of probability mass for each bin and does not have + norm 1. + * `:none`: Do not normalize. + """ + normalization = :none + "Allows to statistically weight the observations." + weights = automatic + cycle = [:color => :patchcolor] + """ + Color can either be: + * a vector of `bins` colors + * a single color + * `:values`, to color the bars with the values from the histogram + """ + color = @inherit patchcolor + strokewidth = @inherit patchstrokewidth + strokecolor = @inherit patchstrokecolor + "Adds an offset to every value." + offset = 0.0 + "Defines where the bars start." + fillto = automatic + "Allows to scale all values to a certain height." + scale_to = nothing + bar_labels = nothing + flip_labels_at = Inf + label_color = @inherit textcolor + over_background_color = automatic + over_bar_color = automatic + label_offset = 5 + label_font = @inherit font + label_size = 20 + label_formatter = bar_label_formatter + "Set the direction of the bars." + direction = :y end function pick_hist_edges(vals, bins) @@ -179,8 +177,17 @@ function Makie.plot!(plot::Hist) bar_labels = lift(plot, plot.bar_labels) do x x === :values ? :y : x end + + bar_attrs = copy(plot.attributes) + delete!(bar_attrs, :over_background_color) + delete!(bar_attrs, :bins) + delete!(bar_attrs, :scale_to) + delete!(bar_attrs, :weights) + delete!(bar_attrs, :normalization) + delete!(bar_attrs, :over_bar_color) + # plot the values, not the observables, to be in control of updating - bp = barplot!(plot, points[]; width = widths[], gap = 0, plot.attributes..., fillto=plot.fillto, offset=plot.offset, bar_labels=bar_labels, color=color) + bp = barplot!(plot, points[]; width = widths[], gap = 0, bar_attrs..., fillto=plot.fillto, offset=plot.offset, bar_labels=bar_labels, color=color) # update the barplot points without triggering, then trigger with `width` on(plot, widths) do w diff --git a/src/stats/violin.jl b/src/stats/violin.jl index d3d7d5c9707..8ac5716fc6c 100644 --- a/src/stats/violin.jl +++ b/src/stats/violin.jl @@ -1,38 +1,39 @@ """ - violin(x, y; kwargs...) + violin(x, y) Draw a violin plot. -# Arguments +## Arguments - `x`: positions of the categories - `y`: variables whose density is computed -# Keywords -- `weights`: vector of statistical weights (length of data). By default, each observation has weight `1`. -- `orientation=:vertical`: orientation of the violins (`:vertical` or `:horizontal`) -- `width=1`: width of the box before shrinking -- `gap=0.2`: shrinking factor, `width -> width * (1 - gap)` -- `show_median=false`: show median as midline -- `side=:both`: specify `:left` or `:right` to only plot the violin on one side -- `datalimits`: specify values to trim the `violin`. Can be a `Tuple` or a `Function` (e.g. `datalimits=extrema`) """ -@recipe(Violin, x, y) do scene - Theme(; - default_theme(scene, Poly)..., - npoints = 200, - boundary = automatic, - bandwidth = automatic, - weights = automatic, - side = :both, - orientation = :vertical, - width = automatic, - dodge = automatic, - n_dodge = automatic, - gap = 0.2, - dodge_gap = 0.03, - datalimits = (-Inf, Inf), - max_density = automatic, - show_median = false, - mediancolor = theme(scene, :linecolor), - medianlinewidth = theme(scene, :linewidth), - ) +@recipe Violin x y begin + npoints = 200 + boundary = automatic + bandwidth = automatic + "vector of statistical weights (length of data). By default, each observation has weight `1`." + weights = automatic + "Specify `:left` or `:right` to only plot the violin on one side." + side = :both + "Orientation of the violins (`:vertical` or `:horizontal`)" + orientation = :vertical + "Width of the box before shrinking." + width = automatic + dodge = automatic + n_dodge = automatic + "Shrinking factor, `width -> width * (1 - gap)`." + gap = 0.2 + dodge_gap = 0.03 + "Specify values to trim the `violin`. Can be a `Tuple` or a `Function` (e.g. `datalimits=extrema`)." + datalimits = (-Inf, Inf) + max_density = automatic + "Show median as midline." + show_median = false + mediancolor = @inherit linecolor + medianlinewidth = @inherit linewidth + color = @inherit patchcolor + strokecolor = @inherit patchstrokecolor + strokewidth = @inherit patchstrokewidth + MakieCore.mixin_generic_plot_attributes()... + cycle = [:color => :patchcolor] end conversion_trait(::Type{<:Violin}) = SampleBased() diff --git a/test/pipeline.jl b/test/pipeline.jl index 6f18bc98729..fcc4c0c3ec0 100644 --- a/test/pipeline.jl +++ b/test/pipeline.jl @@ -147,3 +147,17 @@ end plots = test_default(rand(4, 4, 4)) @test all(x -> x isa Volume, plots) end + +@testset "validated attributes" begin + InvalidAttributeError = Makie.MakieCore.InvalidAttributeError + @test_throws InvalidAttributeError heatmap(zeros(10, 10); does_not_exist = 123) + @test_throws InvalidAttributeError image(zeros(10, 10); does_not_exist = 123) + @test_throws InvalidAttributeError scatter(1:10; does_not_exist = 123) + @test_throws InvalidAttributeError lines(1:10; does_not_exist = 123) + @test_throws InvalidAttributeError linesegments(1:10; does_not_exist = 123) + @test_throws InvalidAttributeError text(1:10; does_not_exist = 123) + @test_throws InvalidAttributeError volume(zeros(3, 3, 3); does_not_exist = 123) + @test_throws InvalidAttributeError meshscatter(1:10; does_not_exist = 123) + @test_throws InvalidAttributeError poly(Point2f[]; does_not_exist = 123) + @test_throws InvalidAttributeError mesh(rand(Point3f, 3); does_not_exist = 123) +end \ No newline at end of file diff --git a/test/text.jl b/test/text.jl index 4881afa3a12..4f9f377acd0 100644 --- a/test/text.jl +++ b/test/text.jl @@ -118,7 +118,7 @@ end text([L"text", L"text"], position = [Point2f(0, 0), Point2f(1, 1)]) text(collect(zip([L"text", L"text"], [Point2f(0, 0), Point2f(1, 1)]))) - err = ArgumentError("The attribute `textsize` has been renamed to `fontsize` in Makie v0.19. Please change all occurrences of `textsize` to `fontsize` or revert back to an earlier version.") + err = ArgumentError("`textsize` has been renamed to `fontsize` in Makie v0.19. Please change all occurrences of `textsize` to `fontsize` or revert back to an earlier version.") @test_throws err Label(Figure()[1, 1], "hi", textsize = 30) @test_throws err text(1, 2, text = "hi", textsize = 30) end