From 7ad058b1f99fa4f42d075d169c80507a4cca3ed8 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Mon, 7 Feb 2022 18:48:09 +0100 Subject: [PATCH] Add `surfaceplot`, `isosurface` (support 3d plots) (#217) Co-authored-by: Johnny Chen --- .gitignore | 1 + Project.toml | 7 + README.md | 110 ++++-- docs/generate_docs.jl | 51 ++- src/UnicodePlots.jl | 13 + src/canvas/lookupcanvas.jl | 8 +- src/colormaps.jl | 4 +- src/common.jl | 18 +- src/description.jl | 22 +- src/interface/boxplot.jl | 12 +- src/interface/contourplot.jl | 26 +- src/interface/densityplot.jl | 2 +- src/interface/heatmap.jl | 22 +- src/interface/isosurface.jl | 142 ++++++++ src/interface/lineplot.jl | 62 ++-- src/interface/scatterplot.jl | 13 +- src/interface/surfaceplot.jl | 188 ++++++++++ src/plot.jl | 116 ++++-- src/volume.jl | 336 ++++++++++++++++++ test/references/isosurface/hyperboloid.txt | 18 + test/references/isosurface/sphere.txt | 18 + test/references/isosurface/torus.txt | 18 + test/references/lineplot/color_vector.txt | 18 + test/references/surfaceplot/matrix.txt | 18 + test/references/surfaceplot/single_color.txt | 18 + test/references/surfaceplot/slice_lines.txt | 18 + test/references/surfaceplot/slice_scatter.txt | 18 + test/references/surfaceplot/sombrero.txt | 18 + .../surfaceplot/sombrero_aspect.txt | 18 + .../surfaceplot/sombrero_zscale.txt | 18 + test/references/volume/cube_0.5.txt | 19 + test/references/volume/cube_1.txt | 19 + test/references/volume/cube_2.txt | 19 + test/references/volume/cube_orthographic.txt | 19 + test/references/volume/cube_perspective.txt | 19 + test/references/volume/ellipsoid_xy.txt | 19 + test/references/volume/ellipsoid_xz.txt | 19 + test/references/volume/ellipsoid_yz.txt | 19 + test/runtests.jl | 6 +- test/tst_common.jl | 9 +- test/tst_contourplot.jl | 5 +- test/tst_isosurface.jl | 32 ++ test/tst_lineplot.jl | 8 + test/tst_surfaceplot.jl | 46 +++ test/tst_volume.jl | 147 ++++++++ 45 files changed, 1626 insertions(+), 130 deletions(-) create mode 100644 src/interface/isosurface.jl create mode 100644 src/interface/surfaceplot.jl create mode 100644 src/volume.jl create mode 100644 test/references/isosurface/hyperboloid.txt create mode 100644 test/references/isosurface/sphere.txt create mode 100644 test/references/isosurface/torus.txt create mode 100644 test/references/lineplot/color_vector.txt create mode 100644 test/references/surfaceplot/matrix.txt create mode 100644 test/references/surfaceplot/single_color.txt create mode 100644 test/references/surfaceplot/slice_lines.txt create mode 100644 test/references/surfaceplot/slice_scatter.txt create mode 100644 test/references/surfaceplot/sombrero.txt create mode 100644 test/references/surfaceplot/sombrero_aspect.txt create mode 100644 test/references/surfaceplot/sombrero_zscale.txt create mode 100644 test/references/volume/cube_0.5.txt create mode 100644 test/references/volume/cube_1.txt create mode 100644 test/references/volume/cube_2.txt create mode 100644 test/references/volume/cube_orthographic.txt create mode 100644 test/references/volume/cube_perspective.txt create mode 100644 test/references/volume/ellipsoid_xy.txt create mode 100644 test/references/volume/ellipsoid_xz.txt create mode 100644 test/references/volume/ellipsoid_yz.txt create mode 100644 test/tst_isosurface.jl create mode 100644 test/tst_surfaceplot.jl create mode 100644 test/tst_volume.jl diff --git a/.gitignore b/.gitignore index 6623747c..d22ae2cb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ .DS_Store docs/imgs/ +.old/ diff --git a/Project.toml b/Project.toml index 5abe6f34..28335576 100644 --- a/Project.toml +++ b/Project.toml @@ -9,12 +9,19 @@ version = "2.7.0" Contour = "d38c429a-6771-53c6-b99e-75d170b6e991" Crayons = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MarchingCubes = "299715c1-40a9-479a-aaf9-4a633d36f717" +NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" [compat] Contour = "0.5" Crayons = "4.1" +MarchingCubes = "0.1" +NaNMath = "0.3" +StaticArrays = "0.12, 1" StatsBase = "0.32, 0.33" julia = "1.6" diff --git a/README.md b/README.md index aadbf4bc..ca4e5001 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # UnicodePlots [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE.md) [![PkgEval](https://juliaci.github.io/NanosoldierReports/pkgeval_badges/U/UnicodePlots.named.svg)](https://juliaci.github.io/NanosoldierReports/pkgeval_badges/U/UnicodePlots.html) [![CI](https://github.com/JuliaPlots/UnicodePlots.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/JuliaPlots/UnicodePlots.jl/actions/workflows/ci.yml) [![Coverage Status](https://codecov.io/gh/JuliaPlots/UnicodePlots.jl/branch/master/graphs/badge.svg?branch=master)](https://app.codecov.io/gh/JuliaPlots/UnicodePlots.jl) [![UnicodePlots Downloads](https://shields.io/endpoint?url=https://pkgs.genieframework.com/api/v1/badge/UnicodePlots)](https://pkgs.genieframework.com?packages=UnicodePlots) @@ -23,6 +23,8 @@ Here is a list of the main high-level functions for common scenarios: * [`densityplot`](https://github.com/JuliaPlots/UnicodePlots.jl#density-plot) (Density Plot) * [`contourplot`](https://github.com/JuliaPlots/UnicodePlots.jl#contour-plot) (Contour Plot) * [`heatmap`](https://github.com/JuliaPlots/UnicodePlots.jl#heatmap-plot) (Heatmap Plot) + * [`surfaceplot`](https://github.com/JuliaPlots/UnicodePlots.jl#surface-plot) (Surface Plot - 3D) + * [`isosurface`](https://github.com/JuliaPlots/UnicodePlots.jl#isosurface-plot) (Isosurface Plot - 3D) Here is a quick hello world example of a typical use-case: @@ -31,7 +33,7 @@ using UnicodePlots plt = lineplot([-1, 2, 3, 7], [-1, 2, 9, 4], title="Example Plot", name="my line", xlabel="x", ylabel="y", border=:dotted) ``` -![Basic Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/lineplot1.png) +![Basic Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/lineplot1.png) There are other types of `Canvas` available (see section [Low-level Interface](https://github.com/JuliaPlots/UnicodePlots.jl#low-level-interface)). @@ -42,35 +44,35 @@ lineplot([-1, 2, 3, 7], [-1, 2, 9, 4], title="Example Plot", name="my line", xlabel="x", ylabel="y", canvas=DotCanvas, border=:ascii) ``` -![Basic Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/lineplot2.png) +![Basic Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/lineplot2.png) Some plot methods have a mutating variant that ends with a exclamation mark: ```julia lineplot!(plt, [0, 4, 8], [10, 1, 10], color=:blue, name="other line") ``` -![Basic Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/lineplot3.png) +![Basic Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/lineplot3.png) #### Scatterplot ```julia scatterplot(randn(50), randn(50), title="My Scatterplot", border=:dotted) ``` -![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/scatterplot1.png) +![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/scatterplot1.png) Axis scaling (`xscale` and/or `yscale`) is supported: choose from (`:identity`, `:ln`, `:log2`, `:log10`) or use an arbitrary scale function: ```julia scatterplot(1:10, 1:10, xscale=:log10, yscale=:ln, border=:dotted) ``` -![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/scatterplot2.png) +![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/scatterplot2.png) For the axis scale exponent, one can revert to using `ASCII` characters instead of `Unicode` ones using the keyword `unicode_exponent=false`: ```julia scatterplot(1:10, 1:10, xscale=:log10, yscale=:ln, border=:dotted, unicode_exponent=false) ``` -![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/scatterplot3.png) +![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/scatterplot3.png) Using a `marker` is supported, choose a `Char`, a unit length `String` or a symbol name such as `:circle` (more from `keys(UnicodePlots.MARKERS)`). One can also provide a vector of `marker`s and/or `color`s as in the following example: @@ -78,28 +80,28 @@ Using a `marker` is supported, choose a `Char`, a unit length `String` or a symb scatterplot([1, 2, 3], [3, 4, 1], marker=[:circle, '😀', "∫"], color=[:red, nothing, :yellow], border=:dotted) ``` -![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/scatterplot4.png) +![Scatterplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/scatterplot4.png) #### Lineplot ```julia lineplot([1, 2, 7], [9, -6, 8], title="My Lineplot", border=:dotted) ``` -![Lineplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/lineplot4.png) +![Lineplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/lineplot4.png) It's also possible to specify a function and a range: ```julia plt = lineplot([cos, sin], -π/2, 2π, border=:dotted) ``` -![Lineplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/lineplot5.png) +![Lineplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/lineplot5.png) You can also plot lines by specifying an intercept and slope: ```julia lineplot!(plt, -.5, .2, name="line") ``` -![Lineplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/lineplot6.png) +![Lineplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/lineplot6.png) #### Staircase plot @@ -108,7 +110,7 @@ lineplot!(plt, -.5, .2, name="line") stairs([1, 2, 4, 7, 8], [1, 3, 4, 2, 7], color=:red, style=:post, title="My Staircase Plot", border=:dotted) ``` -![Staircase](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/stairs1.png) +![Staircase](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/stairs1.png) #### Barplot @@ -117,7 +119,7 @@ barplot(["Paris", "New York", "Moskau", "Madrid"], [2.244, 8.406, 11.92, 3.165], title="Population") ``` -![Barplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/barplot1.png) +![Barplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/barplot1.png) *Note*: You can use the keyword argument `symbols` to specify the characters that should be used to plot the bars (e.g. `symbols=['#']`). @@ -126,28 +128,28 @@ barplot(["Paris", "New York", "Moskau", "Madrid"], ```julia histogram(randn(1000) .* .1, nbins=15, closed=:left) ``` -![Histogram](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/histogram1.png) +![Histogram](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/histogram1.png) The `histogram` function also supports axis scaling using the parameter `xscale`: ```julia histogram(randn(1000) .* .1, nbins=15, closed=:right, xscale=:log10) ``` -![Histogram](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/histogram2.png) +![Histogram](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/histogram2.png) #### Boxplot ```julia boxplot([1, 3, 3, 4, 6, 10]) ``` -![Boxplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/boxplot1.png) +![Boxplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/boxplot1.png) ```julia boxplot(["one", "two"], [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6, 7, 8, 9]], title="Grouped Boxplot", xlabel="x") ``` -![Boxplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/boxplot2.png) +![Boxplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/boxplot2.png) #### Sparsity Pattern @@ -155,7 +157,7 @@ boxplot(["one", "two"], using SparseArrays spy(sprandn(50, 120, .05), border=:dotted) ``` -![Spy](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/spy1.png) +![Spy](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/spy1.png) Plotting the zeros pattern is also possible using `show_zeros=true`: @@ -163,7 +165,7 @@ Plotting the zeros pattern is also possible using `show_zeros=true`: using SparseArrays spy(sprandn(50, 120, .9), show_zeros=true, border=:dotted) ``` -![Spy](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/spy2.png) +![Spy](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/spy2.png) #### Density Plot @@ -171,14 +173,14 @@ spy(sprandn(50, 120, .9), show_zeros=true, border=:dotted) plt = densityplot(randn(1000), randn(1000)) densityplot!(plt, randn(1000) .+ 2, randn(1000) .+ 2) ``` -![Densityplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/densityplot1.png) +![Densityplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/densityplot1.png) #### Contour Plot ```julia contourplot(-3:.01:3, -7:.01:3, (x, y) -> exp(-(x / 2)^2 - ((y + 2) / 4)^2), border=:dotted) ``` -![Contourplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/contourplot1.png) +![Contourplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/contourplot1.png) The keyword `levels` controls the number of contour levels. One can also choose a `colormap` as with `heatmap`, and disable the colorbar using `colorbar=false`. @@ -187,7 +189,7 @@ The keyword `levels` controls the number of contour levels. One can also choose ```julia heatmap(repeat(collect(0:10)', outer=(11, 1)), zlabel="z") ``` -![Heatmap](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/heatmap1.png) +![Heatmap](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/heatmap1.png) The `heatmap` function also supports axis scaling using the parameters `xfact`, `yfact` and axis offsets after scaling using `xoffset` and `yoffset`. @@ -200,7 +202,37 @@ The `zlabel` option and `zlabel!` method may be used to set the `z` axis (colorb ```julia heatmap(collect(0:30) * collect(0:30)', xfact=.1, yfact=.1, xoffset=-1.5, colormap=:inferno) ``` -![Heatmap](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/heatmap2.png) +![Heatmap](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/heatmap2.png) + +#### Surface Plot + +Plot a colored surface using height values `z` above a `x-y` plane, in three dimensions (masking values using `NaN`s is supported). + +```julia +sombrero(x, y) = 15sinc(√(x^2 + y^2) / π) +surfaceplot(-8:.5:8, -8:.5:8, sombrero, colormap=:jet, border=:dotted) +``` +![Surfaceplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/surfaceplot1.png) + +Use `lines=true` to increase the density (underlying call to `lineplot` instead of `scatterplot`). To plot a slice in 3D, use an anonymous function which maps to a constant value: `zscale=z -> a_constant`: + +```julia +surfaceplot( + -8:.5:8, -8:.5:8, (x, y) -> 15sinc(√(x^2 + y^2) / π), + zscale=z -> 0, lines=true, azimuth=-90, elevation=90, colormap=:jet, border=:dotted +) +``` +![Surfaceplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/surfaceplot2.png) + +#### Isosurface Plot + +Uses [`MarchingCubes.jl`](https://github.com/t-bltg/MarchingCubes.jl) to extract an isosurface, where `isovalue` controls the surface isovalue. Using `centroid` enables plotting the triangulation centroids instead of the triangle vertices (better for small plots). Back face culling (hide not visible facets) can be activated using `cull=true`. One can use the legacy 'Marching Cubes' algorithm using `legacy=true`. + +```julia +torus(x, y, z, r=0.2, R=0.5) = (√(x^2 + y^2) - R)^2 + z^2 - r^2 +isosurface(-1:.1:1, -1:.1:1, -1:.1:1, torus, cull=true, zoom=2, elevation=50, border=:dotted) +``` +![Isosurface](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/isosurface.png) ### Options @@ -218,28 +250,28 @@ All plots support the set (or a subset) of the following named parameters: ```julia lineplot(sin, 1:.5:20, labels=false, border=:dotted) ``` - ![Labels](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/labels.png) + ![Labels](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/labels.png) - `border::Symbol = :solid`: plot bounding box style (`:corners`, `:solid`, `:bold`, `:dashed`, `:dotted`, `:ascii`, `:none`). ```julia lineplot([-1., 2, 3, 7], [1.,2, 9, 4], canvas=DotCanvas, border=:bold) ``` - ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/border_bold.png) + ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/border_bold.png) ```julia lineplot([-1., 2, 3, 7], [1.,2, 9, 4], canvas=DotCanvas, border=:dashed) ``` - ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/border_dashed.png) + ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/border_dashed.png) ```julia lineplot([-1., 2, 3, 7], [1.,2, 9, 4], border=:dotted) ``` - ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/border_dotted.png) + ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/border_dotted.png) ```julia lineplot([-1., 2, 3, 7], [1.,2, 9, 4], border=:none) ``` - ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/border_none.png) + ![Border](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/border_none.png) - `margin::Int = 3`: number of empty characters to the left of the whole plot. - `padding::Int = 1`: left and right space between the labels and the canvas. @@ -248,13 +280,13 @@ All plots support the set (or a subset) of the following named parameters: ```julia lineplot(sin, 1:.5:20, width=60, border=:dotted) ``` - ![Width](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/width.png) + ![Width](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/width.png) - `height::Int = 15`: number of canvas rows. ```julia lineplot(sin, 1:.5:20, height=18, border=:dotted) ``` - ![Height](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/height.png) + ![Height](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/height.png) - `xlim::Tuple = (0, 0)`: plotting range for the `x` axis (`(0, 0)` stands for automatic). - `ylim::Tuple = (0, 0)`: plotting range for the `y` axis. @@ -267,12 +299,24 @@ All plots support the set (or a subset) of the following named parameters: - `grid::Bool = true`: draws grid-lines at the origin. - `compact::Bool = false`: compact plot labels. - `unicode_exponent::Bool = true`: use `Unicode` symbols for exponents: e.g. `10²⸱¹` instead of `10^2.1`. + - `projection::Symbol = :orthographic`: projection for 3D plots (`:orthographic`, `:perspective`, or `Matrix-View-Projection` (MVP) matrix). + - `axes3d::Bool = true`: draw 3d axes (x -> red, y -> green, z -> blue). + - `elevation::Float = 35.26439`: elevation angle above or below the `floor` plane (`-90 ≤ θ ≤ 90`). + - `azimuth::Float = 45.0`: azimutal angle around the `up` vector (`-180° ≤ φ ≤ 180°`). + - `zoom::Float = 1.0`: zooming factor in 3D. + - `up::Symbol = :z`: up vector (`:x`, `:y` or `:z`), prefix with `m -> -` or `p -> +` to change the sign e.g. `:mz` for `-z` axis pointing upwards. + - `near::Float = 1.0`: distance to the near clipping plane (`:perspective` projection only). + - `far::Float = 100.0`: distance to the far clipping plane (`:perspective` projection only). - `blend::Bool = true`: blend colors on the underlying canvas. - `fix_ar::Bool = false`: fix terminal aspect ratio (experimental). - `visible::Bool = true`: visible canvas. *Note*: If you want to print the plot into a file but have monospace issues with your font, you should probably try setting `border=:ascii` and `canvas=AsciiCanvas` (or `canvas=DotCanvas` for scatterplots). +### 3D plots + +3d plots use a so-called "Matrix-View-Projection" transformation matrix `MVP` on input data to project 3D plots to a 2D screen. Use keywords`elevation`, `azimuth`, `up` or `zoom` to control the "View" matrix, a.k.a., camera. The `projection` type for `MVP` can be set to either `:perspective` or `orthographic`. Displaying the `x-`, `y-`, and `z-` axes can be controlled using the `axes3d` keyword. For better resolution, use wider and taller `Plot` size, which can be also be achieved using the unexported `UnicodePlots.default_size!(width=60)` for all future plots. + ### Methods * `title!(plot::Plot, title::String)` @@ -311,7 +355,7 @@ The method `label!` is responsible for the setting all the textual decorations o end plt ``` - ![Decorate](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/decorate.png) + ![Decorate](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/decorate.png) * `annotate!(plot::Plot, x::Number, y::Number, text::AbstractString; kw...)` @@ -346,7 +390,7 @@ lines!(canvas, 0., 1., .5, 0., :yellow) # virtual space pixel!(canvas, 5, 8, :red) # pixel space Plot(canvas, border=:dotted) ``` -![Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/canvas.png) +![Canvas](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/canvas.png) You can access the height and width of the canvas (in characters) with `nrows(canvas)` and `ncols(canvas)` respectively. You can use those functions in combination with `printrow` to embed the canvas anywhere you wish. For example, `printrow(STDOUT, canvas, 3)` writes the third character row of the canvas to the standard output. @@ -359,7 +403,7 @@ lines!(canvas, .25, 1., .5, 0., :yellow) lines!(canvas, .2, .8, 1., 0., :red) Plot(canvas, border=:dotted) ``` -![Blending](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/doc/imgs/2.x/blending.png) +![Blending](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/blending.png) The following types of `Canvas` are implemented: diff --git a/docs/generate_docs.jl b/docs/generate_docs.jl index 65db46f3..351e7a5e 100644 --- a/docs/generate_docs.jl +++ b/docs/generate_docs.jl @@ -55,6 +55,20 @@ function main() contourplot1 = ("Contourplot", "contourplot(-3:.01:3, -7:.01:3, (x, y) -> exp(-(x / 2)^2 - ((y + 2) / 4)^2), border=:dotted)"), heatmap1 = ("Heatmap", """heatmap(repeat(collect(0:10)', outer=(11, 1)), zlabel="z")"""), heatmap2 = ("Heatmap", "heatmap(collect(0:30) * collect(0:30)', xfact=.1, yfact=.1, xoffset=-1.5, colormap=:inferno)"), + surfaceplot1 = ("Surfaceplot", """ + sombrero(x, y) = 15sinc(√(x^2 + y^2) / π) + surfaceplot(-8:.5:8, -8:.5:8, sombrero, colormap=:jet, border=:dotted) + """), + surfaceplot2 = ("Surfaceplot", """ + surfaceplot( + -8:.5:8, -8:.5:8, (x, y) -> 15sinc(√(x^2 + y^2) / π), + zscale=z -> 0, lines=true, azimuth=-90, elevation=90, colormap=:jet, border=:dotted + ) + """), + isosurface = ("Isosurface", """ + torus(x, y, z, r=0.2, R=0.5) = (√(x^2 + y^2) - R)^2 + z^2 - r^2 + isosurface(-1:.1:1, -1:.1:1, -1:.1:1, torus, cull=true, zoom=2, elevation=50, border=:dotted) + """), width = ("Width", "lineplot(sin, 1:.5:20, width=60, border=:dotted)"), height = ("Height", "lineplot(sin, 1:.5:20, height=18, border=:dotted)"), labels = ("Labels", "lineplot(sin, 1:.5:20, labels=false, border=:dotted)"), @@ -99,7 +113,7 @@ function main() examples = NamedTuple{keys(exs)}(( MD(Paragraph( - "```julia\n$(rstrip(e[2]))\n```\n![$(e[1])]($docs_url/doc/imgs/$ver/$k.png)" + "```julia\n$(rstrip(e[2]))\n```\n![$(e[1])]($docs_url/$ver/$k.png)" )) for (k, e) in pairs(exs) ) ) @@ -158,6 +172,8 @@ Here is a list of the main high-level functions for common scenarios: - [`densityplot`](https://github.com/JuliaPlots/UnicodePlots.jl#density-plot) (Density Plot) - [`contourplot`](https://github.com/JuliaPlots/UnicodePlots.jl#contour-plot) (Contour Plot) - [`heatmap`](https://github.com/JuliaPlots/UnicodePlots.jl#heatmap-plot) (Heatmap Plot) + - [`surfaceplot`](https://github.com/JuliaPlots/UnicodePlots.jl#surface-plot) (Surface Plot - 3D) + - [`isosurface`](https://github.com/JuliaPlots/UnicodePlots.jl#isosurface-plot) (Isosurface Plot - 3D) Here is a quick hello world example of a typical use-case: @@ -258,6 +274,26 @@ The `zlabel` option and `zlabel!` method may be used to set the `z` axis (colorb $(examples.heatmap2) +#### Surface Plot + +Plot a colored surface using height values `z` above a `x-y` plane, in three dimensions (masking values using `NaN`s is supported). + +$(examples.surfaceplot1) + +Use `lines=true` to increase the density (underlying call to `lineplot` instead of `scatterplot`). +To plot a slice in 3D, use an anonymous function which maps to a constant value: `zscale=z -> a_constant`: + +$(examples.surfaceplot2) + +#### Isosurface Plot + +Uses [`MarchingCubes.jl`](https://github.com/t-bltg/MarchingCubes.jl) to extract an isosurface, where `isovalue` controls the surface isovalue. +Using `centroid` enables plotting the triangulation centroids instead of the triangle vertices (better for small plots). +Back face culling (hide not visible facets) can be activated using `cull=true`. +One can use the legacy 'Marching Cubes' algorithm using `legacy=true`. + +$(examples.isosurface) + ### Options All plots support the set (or a subset) of the following named parameters: @@ -266,6 +302,14 @@ All plots support the set (or a subset) of the following named parameters: _Note_: If you want to print the plot into a file but have monospace issues with your font, you should probably try setting `border=:ascii` and `canvas=AsciiCanvas` (or `canvas=DotCanvas` for scatterplots). +### 3D plots + +3d plots use a so-called "Matrix-View-Projection" transformation matrix `MVP` on input data to project 3D plots to a 2D screen. +Use keywords`elevation`, `azimuth`, `up` or `zoom` to control the "View" matrix, a.k.a., camera. +The `projection` type for `MVP` can be set to either `:perspective` or `orthographic`. +Displaying the `x-`, `y-`, and `z-` axes can be controlled using the `axes3d` keyword. +For better resolution, use wider and taller `Plot` size, which can be also be achieved using the unexported `UnicodePlots.default_size!(width=60)` for all future plots. + ### Methods - `title!(plot::Plot, title::String)` @@ -373,6 +417,7 @@ Inspired by [TextPlots.jl](https://github.com/sunetos/TextPlots.jl), which in tu mkpath("imgs/$ver") open("imgs/gen_imgs.jl", "w") do io println(io, """ + # WARNING: this file has been automatically generated, please update UnicodePlots/docs/generate_docs.jl instead using UnicodePlots, StableRNGs, SparseArrays include(joinpath(dirname(pathof(UnicodePlots)), "..", "test", "fixes.jl")) @@ -393,11 +438,13 @@ Inspired by [TextPlots.jl](https://github.com/sunetos/TextPlots.jl), which in tu open("imgs/gen_imgs.sh", "w") do io write(io, """ #!/usr/bin/env bash + # WARNING: this file has been automatically generated, please update UnicodePlots/docs/generate_docs.jl instead \${JULIA-julia} gen_imgs.jl for f in $ver/*.txt; do html=\${f%.txt}.html cat \$f | \${ANSI2HTML-ansi2html} --input-encoding=utf-8 --output-encoding=utf-8 >\$html + sed -i "s,background-color: #000000,background-color: #1b1b1b," \$html \${WKHTMLTOIMAGE-wkhtmltoimage} --quiet --crop-w 800 --quality 85 \$html \${html%.html}.png done @@ -410,7 +457,7 @@ Inspired by [TextPlots.jl](https://github.com/sunetos/TextPlots.jl), which in tu plain_readme = plain(readme) write(stdout, plain_readme) open("../README.md", "w") do io - write(io, "\n") + write(io, "\n") write(io, plain_readme) end return diff --git a/src/UnicodePlots.jl b/src/UnicodePlots.jl index 03dd8bc3..a4f9816b 100644 --- a/src/UnicodePlots.jl +++ b/src/UnicodePlots.jl @@ -4,6 +4,11 @@ using Dates using Crayons using StatsBase: Histogram, fit, percentile using SparseArrays: AbstractSparseMatrix, findnz +using LinearAlgebra +using StaticArrays + +import MarchingCubes +import NaNMath import Contour export GraphicsArea, @@ -50,6 +55,10 @@ export GraphicsArea, scatterplot!, contourplot, contourplot!, + surfaceplot, + surfaceplot!, + isosurface, + isosurface!, stairs, stairs!, histogram, @@ -59,6 +68,7 @@ export GraphicsArea, spy, boxplot, boxplot!, + MVP, savefig include("common.jl") @@ -77,6 +87,7 @@ include("canvas/dotcanvas.jl") include("canvas/heatmapcanvas.jl") include("description.jl") +include("volume.jl") include("plot.jl") include("colormaps.jl") @@ -84,6 +95,8 @@ include("interface/barplot.jl") include("interface/histogram.jl") include("interface/scatterplot.jl") include("interface/contourplot.jl") +include("interface/surfaceplot.jl") +include("interface/isosurface.jl") include("interface/lineplot.jl") include("interface/stairs.jl") include("interface/densityplot.jl") diff --git a/src/canvas/lookupcanvas.jl b/src/canvas/lookupcanvas.jl index fec38193..905da7c9 100644 --- a/src/canvas/lookupcanvas.jl +++ b/src/canvas/lookupcanvas.jl @@ -64,10 +64,10 @@ function CreateLookupCanvas( visible, pixel_width, pixel_height, - Float64(origin_x), - Float64(origin_y), - Float64(width), - Float64(height), + float(origin_x), + float(origin_y), + float(width), + float(height), xscale, yscale, ) diff --git a/src/colormaps.jl b/src/colormaps.jl index efeb1086..633a3bdb 100644 --- a/src/colormaps.jl +++ b/src/colormaps.jl @@ -1420,8 +1420,10 @@ function rgb2ansi(rgb) end function cmapcolor(z, minz, maxz, cmap) - i = if minz == maxz + i = if minz == maxz || z < minz 1 + elseif z > maxz + length(cmap) else 1 + round(Int, ((z - minz) / (maxz - minz)) * (length(cmap) - 1)) end diff --git a/src/common.jl b/src/common.jl index 27b3022e..c2e0816e 100644 --- a/src/common.jl +++ b/src/common.jl @@ -186,7 +186,8 @@ function char_marker(marker::MarkerType)::Char if marker isa Symbol get(MARKERS, marker, MARKERS[:circle]) else - length(marker) == 1 || throw(error("`marker` keyword has a non unit length")) + length(marker) == 1 || + throw(ArgumentError("`marker` keyword has a non unit length")) marker[1] end end @@ -208,6 +209,8 @@ function transform_name(tr, basename = "") string(basename, " [", name, "]") end +as_float(x) = eltype(x) <: AbstractFloat ? x : float.(x) + roundable(num::Number) = isinteger(num) & (typemin(Int) <= num < typemax(Int)) compact_repr(num::Number) = repr(num, context = :compact => true) @@ -257,25 +260,24 @@ function plotting_range(xmin, xmax) diff = xmax - xmin xmax = round_up_tick(xmax, diff) xmin = round_down_tick(xmin, diff) - Float64(xmin), Float64(xmax) + float(xmin), float(xmax) end function plotting_range_narrow(xmin, xmax) diff = xmax - xmin xmax = round_up_subtick(xmax, diff) xmin = round_down_subtick(xmin, diff) - Float64(xmin), Float64(xmax) + float(xmin), float(xmax) end extend_limits(vec, limits) = extend_limits(vec, limits, :identity) function extend_limits(vec, limits, scale::Union{Symbol,Function}) - mi, ma = map(Float64, extrema(limits)) + mi, ma = as_float(extrema(limits)) if mi == 0 && ma == 0 - mi, ma = map(Float64, extrema(vec)) + mi, ma = as_float(extrema(vec)) end - diff = ma - mi - if diff == 0 + if ma - mi == 0 ma = mi + 1 mi = mi - 1 end @@ -313,6 +315,8 @@ function crayon_256_color(color::UserColorType)::ColorType Crayons.val(ansicolor) end +complement(color) = (col = crayon_256_color(color)) === nothing ? nothing : ~col + julia_color(color::Integer)::JuliaColorType = Int(color) julia_color(color::Nothing)::JuliaColorType = :normal julia_color(color::Symbol)::JuliaColorType = color diff --git a/src/description.jl b/src/description.jl index 670c726c..364091d7 100644 --- a/src/description.jl +++ b/src/description.jl @@ -21,6 +21,14 @@ const KEYWORDS = ( colorbar_lim = (0, 1), colorbar_border = :solid, colormap = :viridis, + projection = :orthographic, + elevation = round(atand(1 / √2); digits = 6), + azimuth = 45.0, + axes3d = true, + near = 1.0, + far = 100.0, + zoom = 1.0, + up = :z, colorbar = false, unicode_exponent = true, compact = false, @@ -35,6 +43,7 @@ const DESCRIPTION = ( # NOTE: this named tuple has to stay ordered x = "horizontal position for each point", y = "vertical position for each point", + z = "depth position for each point", symbols = "characters used to render the bars", title = "text displayed on top of the plot", name = "current drawing annotation displayed on the right", @@ -61,6 +70,14 @@ const DESCRIPTION = ( grid = "draws grid-lines at the origin", compact = "compact plot labels", unicode_exponent = "use `Unicode` symbols for exponents: e.g. `10²⸱¹` instead of `10^2.1`", + projection = "projection for 3D plots (`:orthographic`, `:perspective`, or `Matrix-View-Projection` (MVP) matrix)", + axes3d = "draw 3d axes (x -> red, y -> green, z -> blue)", + elevation = "elevation angle above or below the `floor` plane (`-90 ≤ θ ≤ 90`)", + azimuth = "azimutal angle around the `up` vector (`-180° ≤ φ ≤ 180°`)", + zoom = "zooming factor in 3D", + up = "up vector (`:x`, `:y` or `:z`), prefix with `m -> -` or `p -> +` to change the sign e.g. `:mz` for `-z` axis pointing upwards", + near = "distance to the near clipping plane (`:perspective` projection only)", + far = "distance to the far clipping plane (`:perspective` projection only)", blend = "blend colors on the underlying canvas", fix_ar = "fix terminal aspect ratio (experimental)", visible = "visible canvas", @@ -69,6 +86,8 @@ const DESCRIPTION = ( const Z_DESCRIPTION = (:zlabel, :zlim, :colorbar, :colormap, :colorbar_lim, :colorbar_border) +const PROJ_DESCRIPTION = (:projection, :azimuth, :elevation, :up, :zoom, :axes3d) + const DEFAULT_KW = ( # does not have to stay ordered :name, @@ -97,6 +116,7 @@ const DEFAULT_EXCLUDED = ( :visible, # internals :fix_ar, # experimental Z_DESCRIPTION..., # by default for 2D data + PROJ_DESCRIPTION..., # 3D plots ) base_type(x) = replace(string(typeof(x).name.name), "64" => "") @@ -129,7 +149,7 @@ function keywords( remove::Tuple = (), ) all_kw = (; KEYWORDS..., extra...) - candidates = keys(extra) ∪ filter(x -> x ∈ add ∪ default, DEFAULT_KW) + candidates = keys(extra) ∪ filter(x -> x ∈ add ∪ default, keys(KEYWORDS)) # extra goes first ! kw = filter(x -> x ∉ setdiff(exclude ∪ remove, add), candidates) @assert allunique(kw) # extra check join((k isa Symbol ? "$k = $(all_kw[k] |> repr)" : k for k in kw), ", ") diff --git a/src/interface/boxplot.jl b/src/interface/boxplot.jl index a3cd8185..32405129 100644 --- a/src/interface/boxplot.jl +++ b/src/interface/boxplot.jl @@ -82,13 +82,13 @@ function boxplot( mean_x = (min_x + max_x) / 2 min_x_str = compact_repr( - roundable(min_x) ? round(Int, Float64(min_x), RoundNearestTiesUp) : min_x, + roundable(min_x) ? round(Int, float(min_x), RoundNearestTiesUp) : min_x, ) mean_x_str = compact_repr( - roundable(mean_x) ? round(Int, Float64(mean_x), RoundNearestTiesUp) : mean_x, + roundable(mean_x) ? round(Int, float(mean_x), RoundNearestTiesUp) : mean_x, ) max_x_str = compact_repr( - roundable(max_x) ? round(Int, Float64(max_x), RoundNearestTiesUp) : max_x, + roundable(max_x) ? round(Int, float(max_x), RoundNearestTiesUp) : max_x, ) label!(plot, :bl, min_x_str, color = :light_black) label!(plot, :b, mean_x_str, color = :light_black) @@ -129,13 +129,13 @@ function boxplot!( max_x = plot.graphics.max_x mean_x = (min_x + max_x) / 2 min_x_str = compact_repr( - roundable(min_x) ? round(Int, Float64(min_x), RoundNearestTiesUp) : min_x, + roundable(min_x) ? round(Int, float(min_x), RoundNearestTiesUp) : min_x, ) mean_x_str = compact_repr( - roundable(mean_x) ? round(Int, Float64(mean_x), RoundNearestTiesUp) : mean_x, + roundable(mean_x) ? round(Int, float(mean_x), RoundNearestTiesUp) : mean_x, ) max_x_str = compact_repr( - roundable(max_x) ? round(Int, Float64(max_x), RoundNearestTiesUp) : max_x, + roundable(max_x) ? round(Int, float(max_x), RoundNearestTiesUp) : max_x, ) label!(plot, :bl, min_x_str) label!(plot, :b, mean_x_str) diff --git a/src/interface/contourplot.jl b/src/interface/contourplot.jl index e2b3506c..624f5f51 100644 --- a/src/interface/contourplot.jl +++ b/src/interface/contourplot.jl @@ -13,7 +13,7 @@ $(arguments( ( A = "`Matrix` of interest for which contours are extracted, or `Function` evaluated as `f(x, y)`", levels = "the number of contour levels", - ); add = (Z_DESCRIPTION..., :canvas), remove = (:blend, :grid) + ); add = (Z_DESCRIPTION..., :x, :y, :canvas), remove = (:blend, :grid) )) # Author(s) @@ -44,6 +44,9 @@ julia> contourplot(-1:.1:1, -1:.1:1, (x, y) -> √(x^2 + y^2)) ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ ``` +# See also + +[`Plot`](@ref), [`scatterplot`](@ref) """ function contourplot( x::AbstractVector, @@ -53,27 +56,24 @@ function contourplot( name::AbstractString = KEYWORDS.name, levels::Integer = 3, colormap = KEYWORDS.colormap, - blend = false, - grid = false, - colorbar = true, + colorbar::Bool = true, + blend::Bool = false, + grid::Bool = false, kw..., ) callback = colormap_callback(colormap) plot = Plot( extrema(x) |> collect, extrema(y) |> collect, + nothing, canvas; blend = blend, grid = grid, - colorbar = colorbar, colormap = callback, + colorbar = colorbar, kw..., ) - if A isa Function - X = repeat(x', length(y), 1) - Y = repeat(y, 1, length(x)) - A = map(A, X, Y) |> Matrix - end + A isa Function && (A = A.(x', y)) contourplot!(plot, x, y, A; name = name, levels = levels, colormap = callback) end @@ -84,12 +84,12 @@ function contourplot!( A::AbstractMatrix; name::AbstractString = "", levels::Integer = 3, - colormap = :viridis, + colormap = KEYWORDS.colormap, ) name == "" || label!(plot, :r, string(name)) plot.colormap = callback = colormap_callback(colormap) - mA, MA = extrema(A) + mA, MA = NaNMath.extrema(as_float(A)) for cl in Contour.levels(Contour.contours(y, x, A, levels)) color = callback(Contour.level(cl), mA, MA) @@ -104,6 +104,8 @@ end """ contourplot(A; kw...) +# Usage + Draws a contour plot of matrix `A` along axis `x` and `y` on a new canvas. The `y` axis is flipped by default: diff --git a/src/interface/densityplot.jl b/src/interface/densityplot.jl index 59180bf2..691fbd9a 100644 --- a/src/interface/densityplot.jl +++ b/src/interface/densityplot.jl @@ -62,7 +62,7 @@ function densityplot( name = KEYWORDS.name, kw..., ) - plot = Plot(x, y, DensityCanvas; grid = grid, kw...) + plot = Plot(x, y, nothing, DensityCanvas; grid = grid, kw...) scatterplot!(plot, x, y; color = color, name = name) end diff --git a/src/interface/heatmap.jl b/src/interface/heatmap.jl index 2e9b473e..3f7ec70d 100644 --- a/src/interface/heatmap.jl +++ b/src/interface/heatmap.jl @@ -31,9 +31,24 @@ A plot object of type `Plot{HeatmapCanvas}`. - Rowan Katekar (github.com/rjkat) +# Examples + +```julia-repl +julia> heatmap(repeat(collect(0:10)', outer=(11, 1)), zlabel="z") + ┌───────────┐ + 11 │▄▄▄▄▄▄▄▄▄▄▄│ ┌──┐ 10 + │▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│ + │▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│ + │▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│ z + │▄▄▄▄▄▄▄▄▄▄▄│ │▄▄│ + 1 │▄▄▄▄▄▄▄▄▄▄▄│ └──┘ 0 + └───────────┘ + 1 11 +``` + # See also -`Plot`, `scatterplot`, `HeatmapCanvas` +`Plot`, `HeatmapCanvas` """ function heatmap( A::AbstractMatrix; @@ -138,11 +153,12 @@ function heatmap( end kw = (; kw..., colorbar = colorbar) - xs = length(X) > 0 ? [X[1], X[end]] : Float64[0, 0] - ys = length(Y) > 0 ? [Y[1], Y[end]] : Float64[0, 0] + xs = length(X) > 0 ? [X[1], X[end]] : [0.0, 0.0] + ys = length(Y) > 0 ? [Y[1], Y[end]] : [0.0, 0.0] plot = Plot( xs, ys, + nothing, HeatmapCanvas; grid = false, colormap = callback, diff --git a/src/interface/isosurface.jl b/src/interface/isosurface.jl new file mode 100644 index 00000000..f9af7e40 --- /dev/null +++ b/src/interface/isosurface.jl @@ -0,0 +1,142 @@ +""" + isosurface(x, y, z, V; kw...) + +Extract and plot isosurface from volumetric data, or implicit function. + +# Usage + + isosurface(x, y, z, V; $(keywords((isovalue = 0, centroid = true); add = (Z_DESCRIPTION..., PROJ_DESCRIPTION..., :canvas), remove = (:blend, :grid, :name)))) + +# Arguments + +$(arguments( + ( + V = "`Array` (volume) of interest for which a surface is extracted, or `Function` evaluated as `f(x, y, z)`", + isovalue = "chosen surface isovalue", + cull = "cull (hide) back faces", + legacy = "use the legacy Marching Cubes algorithm instead of the topology enhanced algorithm", + centroid = "display triangulation centroid instead of triangle vertices", + ); add = (Z_DESCRIPTION..., PROJ_DESCRIPTION..., :x, :y, :z, :canvas), remove = (:blend, :grid) +)) + +# Author(s) + +- T Bltg (github.com/t-bltg) + +# Examples + +```julia-repl +julia> torus(x, y, z, r = .2, R = .5) = (√(x^2 + y^2) - R)^2 + z^2 - r^2 +julia> isosurface(-1:.1:1, -1:.1:1, -1:.1:1, torus, elevation = 50, zoom = 2, cull = true) + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢀⢀⠠⢄⢄⠄⠄⡠⡠⠤⡀⡀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠔⠌⠜⠀⠡⠠⠁⠡⠠⠁⠈⠄⠌⠈⠄⠌⠀⠪⠠⠤⡀⡀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⡠⠔⠅⠌⠈⠄⠌⠀⡁⡀⠨⡀⠄⠠⠡⠠⡀⠥⠠⠈⠀⠡⠠⠁⠡⠱⠢⡀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⢀⣜⠃⡁⠄⢊⠈⠄⡰⠐⡀⢂⡞⢈⡰⡨⡂⢆⡁⣱⠔⢁⠘⢀⠠⠁⡑⠠⢈⠘⣲⡀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⡚⠥⠁⡀⠂⢂⠈⠄⡖⡫⠗⠋⠉⠀⠀⠀⠀⠀⠈⠉⠉⠱⢝⠱⠠⠁⡐⠐⢀⠈⢌⢧⠀⠀⠀⠀│ + │⠀⠀⠀⠀⡟⠤⠁⡀⠂⢂⢈⠄⢮⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡿⠠⡁⡐⠐⢀⠈⢤⢚⠀⠀⠀⠀│ + │⠀⠀⠀⠀⢽⠗⠅⡀⠂⢂⢀⠂⢂⠓⡢⡄⣀⠀⠀⠀⠀⠀⢀⡀⢀⢰⠂⡑⠐⡀⡐⠐⠀⠨⢔⡟⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠈⠭⠄⠌⠰⠀⡀⠂⢂⠀⡐⠐⠌⠔⠑⠅⠡⠊⠢⠡⠂⢂⠀⡐⠐⣀⠐⠠⡁⡃⡑⠁⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠈⠨⠅⠄⠌⠀⡆⢀⠐⠐⢀⠐⠐⠀⠄⠂⠂⡀⠂⡂⡀⠂⢂⠀⡂⢐⠔⠈⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⢥⢀⡂⡃⠇⠃⡁⡡⠨⡈⡨⠈⠬⠨⠢⢈⢠⠑⠒⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠋⠓⠂⠊⠒⠂⠚⠘⠚⠙⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⣀⠼⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠔⠉⠀⠀⠀⠈⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ +``` + +# See also + +[`Plot`](@ref), [`scatterplot`](@ref) +""" +function isosurface( + x::AbstractVector, + y::AbstractVector, + z::AbstractVector, + V::Union{Function,AbstractArray}; + canvas::Type = BrailleCanvas, + color::UserColorType = KEYWORDS.color, + projection::Union{MVP,Symbol} = KEYWORDS.projection, + isovalue::Number = 0, + centroid::Bool = true, + legacy::Bool = false, + cull::Bool = false, + kw..., +) + V isa Function && (V = V.(x, y', reshape(z, 1, 1, length(z)))) + + plot = Plot( + extrema(x) |> collect, + extrema(y) |> collect, + extrema(z) |> collect, + canvas; + projection = projection, + kw..., + ) + isosurface!( + plot, + x, + y, + z, + V; + color = color, + isovalue = isovalue, + centroid = centroid, + legacy = legacy, + cull = cull, + ) +end + +function isosurface!( + plot::Plot{<:Canvas}, + x::AbstractVector, + y::AbstractVector, + z::AbstractVector, + V::AbstractArray; + color::UserColorType = KEYWORDS.color, + isovalue::Number = 0, + centroid::Bool = true, + legacy::Bool = false, + cull::Bool = false, +) + color = color == :auto ? next_color!(plot) : color + + mc = MarchingCubes.MC(V, Int; x = collect(x), y = collect(y), z = collect(z)) + (legacy ? MarchingCubes.march_legacy : MarchingCubes.march)(mc, isovalue) + + xs = float(eltype(x))[] + ys = float(eltype(y))[] + zs = float(eltype(z))[] + cs = UserColorType[] + + for (i1, i2, i3) in mc.triangles + (i1 <= 0 || i2 <= 0 || i3 <= 0) && continue # invalid triangle + v1 = mc.vertices[i1] + v2 = mc.vertices[i2] + v3 = mc.vertices[i3] + back_face = ( + dot(mc.normals[i1], plot.projection.view_dir) < 0 && + dot(mc.normals[i2], plot.projection.view_dir) < 0 && + dot(mc.normals[i3], plot.projection.view_dir) < 0 + ) + (cull && back_face) && continue + vc = back_face ? complement(color) : color + if centroid + c = (v1 .+ v2 .+ v3) ./ 3 + push!(xs, c[1]) + push!(ys, c[2]) + push!(zs, c[3]) + push!(cs, vc) + else + push!(xs, v1[1], v2[1], v3[1]) + push!(ys, v1[2], v2[2], v3[2]) + push!(zs, v1[3], v2[3], v3[3]) + push!(cs, vc, vc, vc) + end + end + # triangles vertices or centroid + scatterplot!(plot, xs, ys, zs; color = cs) +end diff --git a/src/interface/lineplot.jl b/src/interface/lineplot.jl index 1fdb0364..fdc79adc 100644 --- a/src/interface/lineplot.jl +++ b/src/interface/lineplot.jl @@ -12,7 +12,6 @@ This means that the two vectors must be of the same length and ordering. # Usage lineplot([x], y; $(keywords(; add = (:canvas,))) - lineplot(fun, [start], [stop]; kw...) # Arguments @@ -20,7 +19,7 @@ This means that the two vectors must be of the same length and ordering. $(arguments( ( fun = "a unary function ``f: R -> R`` that should be evaluated, and drawn as a path from `start` to `stop` (numbers in the domain)", - head_tail = "pass the symbol `:head`, `:tail` or `:both` to color the head/tail of the line with the complement of the chosen color", + head_tail = "color the line head and/or tail with the complement of the chosen color (`:head`, `:tail`, `:both`)", x = "horizontal position for each point (can be a real number or of type `TimeType`), if omitted, the axes of `y` will be used as `x`", ) ; add = (:x, :y, :canvas) )) @@ -67,18 +66,16 @@ julia> lineplot([1, 2, 7], [9, -6, 8], title = "My Lineplot") """ function lineplot( x::AbstractVector, - y::AbstractVector; + y::AbstractVector, + z::Union{AbstractVector,Nothing} = nothing; canvas::Type = KEYWORDS.canvas, color::UserColorType = KEYWORDS.color, name::AbstractString = KEYWORDS.name, head_tail::Union{Nothing,Symbol} = nothing, kw..., ) - idx = map(x, y) do i, j - isfinite(i) && isfinite(j) - end - plot = Plot(x[idx], y[idx], canvas; kw...) - lineplot!(plot, x, y; color = color, name = name, head_tail = head_tail) + plot = Plot(x, y, z, canvas; kw...) + lineplot!(plot, x, y, z; color = color, name = name, head_tail = head_tail) end lineplot(y::AbstractVector; kw...) = lineplot(axes(y, 1), y; kw...) @@ -86,21 +83,42 @@ lineplot(y::AbstractVector; kw...) = lineplot(axes(y, 1), y; kw...) function lineplot!( plot::Plot{<:Canvas}, x::AbstractVector, - y::AbstractVector; - color::UserColorType = KEYWORDS.color, + y::AbstractVector, + z::Union{AbstractVector,Nothing} = nothing; + color::Union{UserColorType,AbstractVector} = KEYWORDS.color, name::AbstractString = KEYWORDS.name, head_tail::Union{Nothing,Symbol} = nothing, ) color = color == :auto ? next_color!(plot) : color - name == "" || label!(plot, :r, string(name), color) - lines!(plot, x, y, color) + col_vec = color isa AbstractVector + name == "" || label!(plot, :r, string(name), col_vec ? first(color) : color) + if col_vec + length(x) == length(y) == length(color) || + throw(ArgumentError("Invalid color vector")) + for i in eachindex(color) + lines!(plot, x[i], y[i], z === nothing ? z : z[i], color[i]) + end + else + lines!(plot, x, y, z, color) + end (head_tail === nothing || length(x) == 0 || length(y) == 0) && return plot - head_tail_color = (col = crayon_256_color(color)) === nothing ? nothing : ~col if head_tail in (:head, :both) - points!(plot, last(x), last(y), head_tail_color) + points!( + plot, + last(x), + last(y), + z === nothing ? z : last(z), + complement(col_vec ? last(color) : color), + ) end if head_tail in (:tail, :both) - points!(plot, first(x), first(y), head_tail_color) + points!( + plot, + first(x), + first(y), + z === nothing ? z : first(z), + complement(col_vec ? first(color) : color), + ) end plot end @@ -158,7 +176,7 @@ function lineplot( ylabel = "f(x)", kw..., ) - y = Float64[f(i) for i in x] + y = [float(f(i)) for i in x] name = name == "" ? string(nameof(f), "(x)") : name plot = lineplot(x, y; name = name, xlabel = xlabel, ylabel = ylabel, kw...) end @@ -172,14 +190,14 @@ function lineplot( kw..., ) diff = abs(endx - startx) - x = startx:(diff / (3 * width)):endx + x = startx:(diff / 3width):endx lineplot(f, x; width = width, kw...) end lineplot(f::Function; kw...) = lineplot(f, -10, 10; kw...) function lineplot!(plot::Plot{<:Canvas}, f::Function, x::AbstractVector; name = "", kw...) - y = Float64[f(i) for i in x] + y = [float(f(i)) for i in x] name = name == "" ? string(nameof(f), "(x)") : name lineplot!(plot, x, y; name = name, kw...) end @@ -192,7 +210,7 @@ function lineplot!( kw..., ) diff = abs(endx - startx) - x = startx:(diff / (3ncols(plot.graphics))):endx + x = startx:(diff / 3ncols(plot.graphics)):endx lineplot!(plot, f, x; kw...) end @@ -224,9 +242,9 @@ function _lineplot(F::AbstractVector{<:Function}, args...; color = :auto, name = ), ) ) - tcolor = color_is_vec ? color[1] : color - tname = name_is_vec ? name[1] : name - plot = lineplot(F[1], args...; color = tcolor, name = tname, kw...) + tcolor = color_is_vec ? first(color) : color + tname = name_is_vec ? first(name) : name + plot = lineplot(first(F), args...; color = tcolor, name = tname, kw...) for i in 2:n tcolor = color_is_vec ? color[i] : color tname = name_is_vec ? name[i] : name diff --git a/src/interface/scatterplot.jl b/src/interface/scatterplot.jl index 683c2864..0b536be1 100644 --- a/src/interface/scatterplot.jl +++ b/src/interface/scatterplot.jl @@ -62,15 +62,16 @@ julia> scatterplot(randn(50), randn(50), title = "My Scatterplot") """ function scatterplot( x::AbstractVector, - y::AbstractVector; + y::AbstractVector, + z::Union{AbstractVector,Nothing} = nothing; canvas::Type = KEYWORDS.canvas, color::Union{UserColorType,AbstractVector} = KEYWORDS.color, marker::Union{MarkerType,AbstractVector} = :pixel, name = "", kw..., ) - plot = Plot(x, y, canvas; kw...) - scatterplot!(plot, x, y; color = color, name = name, marker = marker) + plot = Plot(x, y, z, canvas; kw...) + scatterplot!(plot, x, y, z; color = color, name = name, marker = marker) end scatterplot(y::AbstractVector; kw...) = scatterplot(axes(y, 1), y; kw...) @@ -78,7 +79,8 @@ scatterplot(y::AbstractVector; kw...) = scatterplot(axes(y, 1), y; kw...) function scatterplot!( plot::Plot{<:Canvas}, x::AbstractVector, - y::AbstractVector; + y::AbstractVector, + z::Union{AbstractVector,Nothing} = nothing; color::Union{UserColorType,AbstractVector} = KEYWORDS.color, marker::Union{MarkerType,AbstractVector} = :pixel, name = "", @@ -87,8 +89,9 @@ function scatterplot!( name == "" || label!(plot, :r, string(name), color isa AbstractVector ? color[1] : color) if marker ∈ (:pixel, :auto) - points!(plot, x, y, color) + points!(plot, x, y, z, color) else + z === nothing || throw(ArgumentError("unsupported scatter with 3D data")) for (xi, yi, mi, ci) in zip(x, y, iterable(marker), iterable(color)) annotate!(plot, xi, yi, char_marker(mi); color = ci) end diff --git a/src/interface/surfaceplot.jl b/src/interface/surfaceplot.jl new file mode 100644 index 00000000..f8c27b3d --- /dev/null +++ b/src/interface/surfaceplot.jl @@ -0,0 +1,188 @@ +""" + surfaceplot(x, y, A; kw...) + +Draws a 3D surface plot on a new canvas (masking values using `NaN`s is supported). +To plot a slice one can pass an anonymous function which maps to a constant height: `zscale = z -> a_constant`. + +# Usage + + surfaceplot(x, y, A; $(keywords((; lines = false); add = (Z_DESCRIPTION..., PROJ_DESCRIPTION..., :canvas), remove = (:blend, :grid)))) + +# Arguments + +$(arguments( + ( + A = "`Matrix` of surface heights, or `Function` evaluated as `f(x, y)`", + lines = "use `lineplot` instead of `scatterplot` (for regular increasing data)", + zscale = "scale heights (`:identity`, `:aspect`, tuple of (min, max) values, or arbitrary scale function)", + ); add = (Z_DESCRIPTION..., PROJ_DESCRIPTION..., :x, :y, :canvas), remove = (:blend, :grid, :name) +)) + +# Author(s) + +- T Bltg (github.com/t-bltg) + +# Examples + +```julia-repl +julia> sombrero(x, y) = 15sinc(√(x^2 + y^2) / π) +julia> surfaceplot(-8:.5:8, -8:.5:8, sombrero) + ┌────────────────────────────────────────┐ ⠀⠀⠀⠀ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ ┌──┐ 15.0 + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡫⢟⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡔⠌⡡⢢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣰⣭⣊⣑⣭⣆⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⠴⣚⠯⡫⢝⣿⣿⡻⣟⣿⣿⡫⢝⠽⣓⠦⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣯⡭⠭⠒⢊⠔⢱⢍⡘⡖⣳⢃⡩⡎⠢⡑⠒⠭⢭⣽⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣞⠭⠭⠥⢒⣿⣋⣵⢜⡧⣮⣙⣿⡒⠬⠭⠭⣳⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠴⢾⠿⠯⠛⠛⠽⢿⣯⣶⣮⡽⠛⠫⡉⠃⠙⢉⠝⠛⢯⣵⣶⣽⡿⠯⠛⠛⠽⠿⡷⠦⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣿⣿⢶⣶⣶⡺⣗⣶⣶⡶⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⢷⢵⢾⡷⡮⡾⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⣀⠤⠧⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠢⢕⡯⠔⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + -1 │⠐⠉⠀⠀⠀⠀⠈⠑⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ └──┘ -3.0 + └────────────────────────────────────────┘ ⠀⠀⠀⠀ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ +``` + +# See also + +[`Plot`](@ref), [`scatterplot`](@ref) +""" +function surfaceplot( + x::AbstractVecOrMat, + y::AbstractVecOrMat, + A::Union{Function,AbstractVecOrMat}; + canvas::Type = BrailleCanvas, + color::UserColorType = nothing, # NOTE: nothing as default (uses colormap), but allow single color + colormap = KEYWORDS.colormap, + colorbar::Bool = true, + projection::Union{MVP,Symbol} = KEYWORDS.projection, + zscale::Union{Symbol,Function,NTuple{2}} = :identity, + lines::Bool = false, + zlim = KEYWORDS.zlim, + kw..., +) + X, Y = if x isa AbstractVector && y isa AbstractVector && !(A isa AbstractVector) + repeat(x, 1, length(y)), repeat(y', length(x), 1) + else + x, y + end + H = A isa Function ? A.(X, Y) : A + + ex = extrema(x) + ey = extrema(y) + eh = NaNMath.extrema(as_float(H)) + + if zscale === :identity + ez = eh + Z = H + elseif zscale isa Function + ez = zscale.(eh) + Z = zscale.(H) + elseif zscale === :aspect || zscale isa NTuple{2} + mh, Mh = eh + mz, Mz = ez = if zscale === :aspect + diff(ex |> collect) > diff(ey |> collect) ? ex : ey + else + zscale + end + Z = (H .- mh) .* ((Mz - mz) / (Mh - mh)) .+ mz + else + throw(ArgumentError("zscale=$zscale not understood")) + end + + plot = + Plot(collect(ex), collect(ey), collect(ez), canvas; projection = projection, kw...) + surfaceplot!( + plot, + X, + Y, + Z, + H; + color = color, + colormap = colormap, + colorbar = colorbar, + lines = lines, + zlim = zlim, + ) +end + +function surfaceplot!( + plot::Plot{<:Canvas}, + X::AbstractMatrix, + Y::AbstractMatrix, + Z::AbstractMatrix, + H::AbstractMatrix; + color::UserColorType = nothing, + colormap = KEYWORDS.colormap, + colorbar::Bool = true, + lines::Bool = false, + zlim = KEYWORDS.zlim, +) + length(X) == length(Y) == length(Z) == length(H) || + throw(DimensionMismatch("X, Y, Z and H must have same length")) + + plot.colorbar_lim = mh, Mh = zlim == (0, 0) ? NaNMath.extrema(as_float(H)) : zlim + plot.colormap = callback = colormap_callback(colormap) + plot.colorbar = colorbar && color === nothing + + if (cmapped = color === nothing) + color = map(h -> isfinite(h) ? callback(h, mh, Mh) : nothing, H) + end + + if lines + m, n = size(X) + @inbounds for j in axes(X, 2), i in axes(X, 1) + c = cmapped ? color[i, j] : color + scatter = false + if i < m + lines!(plot, X[i:(i + 1), j], Y[i:(i + 1), j], Z[i:(i + 1), j], c) + else + scatter = true + end + if j < n + lines!(plot, X[i, j:(j + 1)], Y[i, j:(j + 1)], Z[i, j:(j + 1)], c) + else + scatter = true + end + if i < m && j < n + lines!( + plot, + [X[i, j], X[i + 1, j + 1]], + [Y[i, j], Y[i + 1, j + 1]], + [Z[i, j], Z[i + 1, j + 1]], + c, + ) + lines!( + plot, + [X[i + 1, j], X[i, j + 1]], + [Y[i + 1, j], Y[i, j + 1]], + [Z[i + 1, j], Z[i, j + 1]], + c, + ) + end + scatter && points!(plot, X[i, j], Y[i, j], Z[i, j], c) + end + else + scatterplot!( + plot, + @view(X[:]), + @view(Y[:]), + @view(Z[:]); + color = cmapped ? @view(color[:]) : color, + ) + end + plot +end + +""" + surfaceplot(A; kw...) + +# Usage + +Draws a surface plot of matrix `A` along axis `x` and `y` on a new canvas. +""" +surfaceplot(A::AbstractMatrix; kw...) = surfaceplot(axes(A, 1), axes(A, 2), A; kw...) diff --git a/src/plot.jl b/src/plot.jl index 54853c08..8b18aef4 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -11,13 +11,13 @@ additional information such as a title, border, and axis labels. Plot(graphics; $(keywords(; default = (), add = (:title, :xlabel, :ylabel, :zlabel, :border, :margin, :padding, :compact, :labels)))) - Plot(x, y, canvas; $(keywords())) + Plot(x, y, z, canvas; $(keywords())) # Arguments $(arguments( (; graphics = "the `GraphicsArea` (e.g. a subtype of `Canvas`) that the plot should decorate"); - add = (:x, :y, :canvas) + add = (:x, :y, :z, :canvas) )) # Methods @@ -66,6 +66,7 @@ mutable struct Plot{T<:GraphicsArea} colorbar_border::Symbol colorbar_lim::Tuple{Number,Number} autocolor::Int + projection::Union{MVP,Nothing} end function Plot( @@ -83,6 +84,7 @@ function Plot( colorbar_border::Symbol = KEYWORDS.colorbar_border, colorbar_lim = KEYWORDS.colorbar_lim, colormap::Any = nothing, + projection::Union{MVP,Nothing} = nothing, ignored..., ) where {T<:GraphicsArea} margin >= 0 || throw(ArgumentError("Margin must be greater than or equal to 0")) @@ -116,6 +118,7 @@ function Plot( colorbar_border, colorbar_lim, 0, + projection, ) if compact xlabel != "" && label!(p, :b, xlabel) @@ -124,9 +127,38 @@ function Plot( p end +""" + validate_input(x, y, z = nothing) + +# Description + +Check for invalid input (length) and selects only finite input data. +""" +function validate_input( + x::AbstractVector{<:Number}, + y::AbstractVector{<:Number}, + z::Union{AbstractVector{<:Number},Nothing} = nothing, +) + if z !== nothing + length(x) == length(y) == length(z) || + throw(DimensionMismatch("x, y and z must have same length")) + idx = map(x, y, z) do i, j, k + isfinite(i) && isfinite(j) && isfinite(k) + end + x[idx], y[idx], z[idx] + else + length(x) == length(y) || throw(DimensionMismatch("x and y must have same length")) + idx = map(x, y) do i, j + isfinite(i) && isfinite(j) + end + x[idx], y[idx], z + end +end + function Plot( - X::AbstractVector{<:Number}, - Y::AbstractVector{<:Number}, + x::AbstractVector{<:Number}, + y::AbstractVector{<:Number}, + z::Union{AbstractVector{<:Number},Nothing} = nothing, ::Type{C} = BrailleCanvas; title::AbstractString = KEYWORDS.title, xlabel::AbstractString = KEYWORDS.xlabel, @@ -152,10 +184,12 @@ function Plot( grid::Bool = KEYWORDS.grid, min_width::Int = 5, min_height::Int = 2, + projection::Union{MVP,Symbol,Nothing} = nothing, + axes3d = KEYWORDS.axes3d, + kw..., ) where {C<:Canvas} length(xlim) == length(ylim) == 2 || throw(ArgumentError("xlim and ylim must be tuples or vectors of length 2")) - length(X) == length(Y) || throw(DimensionMismatch("X and Y must have same length")) width === nothing && (width = DEFAULT_WIDTH[]) height === nothing && (height = DEFAULT_HEIGHT[]) @@ -163,19 +197,44 @@ function Plot( (visible = width > 0) && (width = max(width, min_width)) height = max(height, min_height) - min_x, max_x = extend_limits(X, xlim, xscale) - min_y, max_y = extend_limits(Y, ylim, yscale) + x, y, z = validate_input(x, y, z) - p_width = max_x - min_x - p_height = max_y - min_y + if projection !== nothing # 3D + (xscale !== :identity || yscale !== :identity) && + throw(ArgumentError("xscale or yscale are unsupported in 3D")) + + projection isa Symbol && (projection = MVP(x, y, z; kw...)) + + # normalized coordinates, but allow override (artifact for zooming): + # using `xlim = (-0.5, 0.5)` & `ylim = (-0.5, 0.5)` + # should be close to using `zoom = 2` + mx, Mx = if xlim == (0, 0) + -1.0, 1.0 + else + as_float(xlim) + end + my, My = if ylim == (0, 0) + -1.0, 1.0 + else + as_float(ylim) + end + + grid = blend = false + else # 2D + mx, Mx = extend_limits(x, xlim, xscale) + my, My = extend_limits(y, ylim, yscale) + end + + p_width = Mx - mx + p_height = My - my canvas = C( width, height, blend = blend, visible = visible, - origin_x = min_x, - origin_y = min_y, + origin_x = mx, + origin_y = my, width = p_width, height = p_height, xscale = xscale, @@ -196,12 +255,13 @@ function Plot( colorbar = colorbar, colorbar_border = colorbar_border, colorbar_lim = colorbar_lim, + projection = projection, ) base_x = xscale isa Symbol ? get(BASES, xscale, nothing) : nothing base_y = yscale isa Symbol ? get(BASES, yscale, nothing) : nothing m_x, M_x, m_y, M_y = map( v -> compact_repr(roundable(v) ? round(Int, v, RoundNearestTiesUp) : v), - (min_x, max_x, min_y, max_y), + (mx, Mx, my, My), ) if unicode_exponent m_x, M_x = map(v -> base_x !== nothing ? superscript(v) : v, (m_x, M_x)) @@ -214,25 +274,21 @@ function Plot( label!(plot, :bl, base_x_str * m_x, color = :light_black) label!(plot, :br, base_x_str * M_x, color = :light_black) if grid - if min_y < 0 < max_y - for i in range( - min_x, - stop = max_x, - length = width * x_pixel_per_char(typeof(canvas)), - ) + if my < 0 < My + for i in range(mx, stop = Mx, length = width * x_pixel_per_char(typeof(canvas))) points!(plot, i, 0.0, :normal) end end - if min_x < 0 < max_x - for i in range( - min_y, - stop = max_y, - length = height * y_pixel_per_char(typeof(canvas)), - ) + if mx < 0 < Mx + for i in + range(my, stop = My, length = height * y_pixel_per_char(typeof(canvas))) points!(plot, 0.0, i, :normal) end end end + + (projection !== nothing && axes3d) && draw_axes!(plot, 0.8 .* [mx, my]) + plot end @@ -487,18 +543,24 @@ function annotate!( plot end +transform(tr, args...) = args # catch all +transform(tr::Union{MVP,Nothing}, x, y, c::UserColorType) = (x, y, c) +transform(tr::Union{MVP,Nothing}, x, y, z::Nothing, c::UserColorType) = (x, y, c) # drop z +transform(tr::MVP, x, y, z::Union{AbstractVector,Number}, args...) = + (tr(vcat(x', y', z'))..., args...) + function lines!(plot::Plot{<:Canvas}, args...; kw...) - lines!(plot.graphics, args...; kw...) + lines!(plot.graphics, transform(plot.projection, args...)...; kw...) plot end function pixel!(plot::Plot{<:Canvas}, args...; kw...) - pixel!(plot.graphics, args...; kw...) + pixel!(plot.graphics, transform(plot.projection, args...)...; kw...) plot end function points!(plot::Plot{<:Canvas}, args...; kw...) - points!(plot.graphics, args...; kw...) + points!(plot.graphics, transform(plot.projection, args...)...; kw...) plot end diff --git a/src/volume.jl b/src/volume.jl new file mode 100644 index 00000000..c9366b56 --- /dev/null +++ b/src/volume.jl @@ -0,0 +1,336 @@ +translate_4x4(v) = @SMatrix([ + 1 0 0 v[1] + 0 1 0 v[2] + 0 0 1 v[3] + 0 0 0 1 +]) + +scale_4x4(v) = @SMatrix([ + v[1] 0 0 0 + 0 v[2] 0 0 + 0 0 v[3] 0 + 0 0 0 1 +]) + +rotd_x(θ) = @SMatrix([ + 1 0 0 0 + 0 cosd(θ) -sind(θ) 0 + 0 sind(θ) +cosd(θ) 0 + 0 0 0 1 +]) + +rotd_y(θ) = @SMatrix([ + +cosd(θ) 0 sind(θ) 0 + 0 1 0 0 + -sind(θ) 0 cosd(θ) 0 + 0 0 0 1 +]) + +rotd_z(θ) = @SMatrix([ + cosd(θ) -sind(θ) 0 0 + sind(θ) +cosd(θ) 0 0 + 0 0 1 0 + 0 0 0 1 +]) + +""" + lookat(eye, target, up_vector) + +# Description + +Computes the scene camera (see songho.ca/opengl/gl_camera.html). + +# Arguments + + - `eye`: position of the camera in world space (e.g. [0, 0, 10]). + - `target`: target point to look at in world space (usually to origin = [0, 0, 0]). + - `up_vector`: up vector (usually +z = [0, 0, 1]). +""" +function lookat(eye, target = [0, 0, 0], up_vector = [0, 0, 1]) + f = normalize(eye - target) # forward vector + l = normalize(cross(up_vector, f)) # left vector + u = cross(f, l) # up vector + + @SMatrix( + [ + l[1] l[2] l[3] -dot(l, eye) + u[1] u[2] u[3] -dot(u, eye) + f[1] f[2] f[3] -dot(f, eye) + 0 0 0 1 + ] + ), + f +end + +""" + frustum(l, r, b, t, n, f) + +# Description + +Computes the perspective projection matrix (see songho.ca/opengl/gl_projectionmatrix.html#perspective). + +# Arguments + + - `l`: left coordinate of the vertical clipping plane. + - `r` : right coordinate of the vertical clipping plane. + - `b`: bottom coordinate of the horizontal clipping plane. + - `t`: top coordinate of the horizontal clipping plane. + - `n`: distance to the near depth clipping plane. + - `f`: distance to the far depth clipping plane. +""" +function frustum(l, r, b, t, n, f) + @assert n > 0 && f > 0 + *( + @SMatrix([ # scale + 2n/(r - l) 0 0 0 + 0 2n/(t - b) 0 0 + 0 0 1 0 + 0 0 0 1 + ]), + @SMatrix([ # translate + 1 0 0 (l + r)/2n + 0 1 0 (b + t)/2n + 0 0 1 0 + 0 0 0 1 + ]), + @SMatrix([ # perspective + -1 0 0 0 # flip x + 0 -1 0 0 # flip y + 0 0 (f + n)/(f - n) -2f * n/(f - n) + 0 0 1 0 + ]), + ) +end + +""" + ortho(l, r, b, t, n, f) + +# Description + +Computes the orthographic projection matrix (see songho.ca/opengl/gl_projectionmatrix.html#ortho). + +# Arguments + + - `l`: left coordinate of the vertical clipping plane. + - `r` : right coordinate of the vertical clipping plane. + - `b`: bottom coordinate of the horizontal clipping plane. + - `t`: top coordinate of the horizontal clipping plane. + - `n`: distance to the near depth clipping plane. + - `f`: distance to the far depth clipping plane. +""" +ortho(l, r, b, t, n, f) = *( + @SMatrix([ # scale + 2/(r - l) 0 0 0 + 0 2/(t - b) 0 0 + 0 0 2/(f - n) 0 + 0 0 0 1 + ]), + @SMatrix([ # translate + 1 0 0 -(l + r)/2 + 0 1 0 -(b + t)/2 + 0 0 1 -(n + f)/2 + 0 0 0 1 + ]), +) + +abstract type Projection end + +struct Orthographic{T} <: Projection where {T} + A::SMatrix{4,4,T} + Orthographic(args::T...) where {T} = new{float(T)}(ortho(args...)) +end + +struct Perspective{T} <: Projection where {T} + A::SMatrix{4,4,T} + Perspective(args::T...) where {T} = new{float(T)}(frustum(args...)) +end + +(t::Projection)(p::AbstractVecOrMat) = t.A * p + +""" + ctr_len_diag(x, y, z) + +# Description + +Computes data center, minimum and maximum points, and cube diagonal. +""" +function ctr_len_diag(x, y, z) + mx, Mx = NaNMath.extrema(as_float(x)) + my, My = NaNMath.extrema(as_float(y)) + mz, Mz = NaNMath.extrema(as_float(z)) + + lx = Mx - mx + ly = My - my + lz = Mz - mz + + ( + @SVector([mx + 0.5lx, my + 0.5ly, mz + 0.5lz]), + @SVector([mx, my, mz]), + @SVector([Mx, My, Mz]), + @SVector([lx, ly, lz]), + √(lx^2 + ly^2 + lz^2), + ) +end + +cube_corners(mx, Mx, my, My, mz, Mz) = [ + mx mx mx mx Mx Mx Mx Mx + my my My My my my My My + mx Mz mx Mz mx Mz mx Mz +] + +function view_matrix(center, distance, elevation, azimuth, up) + up_str = string(up) + shift = if (up_axis = Symbol(up_str[end])) === :x + 0 + elseif up_axis === :y + 1 + elseif up_axis === :z + 2 + else + throw(ArgumentError("up=$up not understood")) + end + # we support :x -> +x, :px -> +x or :mx -> -x + up_vector = circshift( + [ + length(up_str) == 1 ? 1 : (p = +1, m = -1)[Symbol(up_str[1])] + 0 + 0 + ], + shift, + ) + cam_move = circshift( + distance .* [ + sind(elevation) + cosd(azimuth) * cosd(elevation) + sind(azimuth) * cosd(elevation) + ], + shift, + ) + lookat(center .+ cam_move, center, up_vector) +end + +""" + MVP(x, y, z; $(keywords(; default = (), add = (:x, :y, :z, :projection, :elevation, :azimuth, :zoom, :up)))) + +# Description + +Build up the "Model - View - Projection" transformation matrix (see codinglabs.net/article_world_view_projection_matrix.aspx). + +This is typically used to adjust how 3D plot is viewed, see also +the `projection` keyword in [`surfaceplot`](@ref), [`isosurface`](@ref). +""" +struct MVP{T} + mvp_mat::SMatrix{4,4,T} + mvp_ortho_mat::SMatrix{4,4,T} + mvp_persp_mat::SMatrix{4,4,T} + view_dir::SVector{3,T} + dist::T + ortho::Bool + function MVP( + x, + y, + z; + projection = KEYWORDS.projection, + elevation = KEYWORDS.elevation, + azimuth = KEYWORDS.azimuth, + zoom = KEYWORDS.zoom, + near = KEYWORDS.near, + far = KEYWORDS.far, + up = KEYWORDS.up, + ) + @assert projection ∈ (:orthographic, :perspective) + @assert -180 ≤ azimuth ≤ 180 + @assert -90 ≤ elevation ≤ 90 + + F = float(eltype(x)) + ortho = projection === :orthographic + ctr, mini, maxi, len, diag = ctr_len_diag(x, y, z) + + # half the diagonal (cam distance to the center) + disto = dist = (diag / 2) / zoom + distp = disto / 2 + + # avoid `NaN`s in `V` when `elevation` is close to ±90 + δ = 100eps() + elev = max(-90 + δ, min(elevation, 90 - δ)) + + # Model Matrix + M = I # we don't scale, nor translate, nor rotate input data + + # View Matrix + V_ortho, view_dir = view_matrix(ctr, disto, elev, azimuth, up) + V_persp, view_dir = view_matrix(ctr, distp, elev, azimuth, up) + V = ortho ? V_ortho : V_persp + + # Projection Matrix + P_ortho = Orthographic(-disto, disto, -disto, disto, -disto, disto) + P_persp = Perspective(-distp, distp, -distp, distp, near, far) + P = ortho ? P_ortho : P_persp + + new{F}(P(V * M), P_ortho(V_ortho * M), P_persp(V_persp * M), view_dir, dist, ortho) + end +end + +transform_matrix(t::MVP, n::Symbol) = + (user = t.mvp_mat, orthographic = t.mvp_ortho_mat, perspective = t.mvp_persp_mat)[n] + +is_ortho(t::MVP, n::Symbol) = (user = t.ortho, orthographic = true, perspective = false)[n] + +function (t::MVP{T})(p::AbstractMatrix, n::Symbol = :user) where {T} + # homogeneous coordinates + dat = transform_matrix(t, n) * (size(p, 1) == 4 ? p : vcat(p, ones(1, size(p, 2)))) + xs, ys, zs, ws = dat[1, :], dat[2, :], dat[3, :], dat[4, :] + @inbounds for (i, w) in enumerate(ws) + if abs(w) > eps(T) + xs[i] /= w + ys[i] /= w + zs[i] /= w + end + end + is_ortho(t, n) ? (xs, ys) : (xs ./ zs, ys ./ zs) +end + +function (t::MVP{T})(v::Union{AbstractVector,NTuple{3}}, n::Symbol = :user) where {T} + # homogeneous coordinates + x, y, z, w = transform_matrix(t, n) * [v..., 1] + if abs(w) > eps(T) + x /= w + y /= w + z /= w + end + is_ortho(t, n) ? (x, y) : (x / z, y / z) +end + +""" + draw_axes!(plot; p = [0, 0, 0], len = nothing) + +# Description + +Draws (X, Y, Z) cartesian coordinates axes in (R, G, B) colors, at position `p = (x, y, z)`. +If `p = (x, y)` is given, draws at screen coordinates. +""" +function draw_axes!(plot, p = [0, 0, 0], len = nothing, scale = 0.25) + T = plot.projection + # constant apparent size + l = len === nothing ? scale .* @SVector([T.dist, T.dist, T.dist]) : len + + proj = :orthographic # force axes projection + + pos = if length(p) == 2 + (transform_matrix(T, proj) \ vcat(p, 0, 1))[1:3] + else + p + end + + axis(p, d) = begin + e = copy(p) + e[d] += l[d] + T(hcat(p, e), proj) + end + + lines!(plot.graphics, axis(float(pos), 1)..., color = :red) + lines!(plot.graphics, axis(float(pos), 2)..., color = :green) + lines!(plot.graphics, axis(float(pos), 3)..., color = :blue) + + plot +end diff --git a/test/references/isosurface/hyperboloid.txt b/test/references/isosurface/hyperboloid.txt new file mode 100644 index 00000000..8e1319c9 --- /dev/null +++ b/test/references/isosurface/hyperboloid.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⢀⠄⠂⠠⡐⠐⡀⡀⢀⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⡀⠠⠠⠨⡂⠠⡂⡢⠁⢄⢈⠄⢄⢄⠂⢐⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠤⠪⢂⠄⢕⠐⠅⠄⢌⠂⢄⠠⡡⠠⡠⡃⠀⡊⢁⠒⠡⡚⡨⠰⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠞⣌⣥⠩⡂⡨⠂⠪⢀⠠⠡⠀⠤⠡⠀⢔⠐⡠⢌⢐⠍⠴⣃⠟⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠚⢵⣥⡥⢁⡡⠠⡁⡁⠌⠌⢈⠂⠂⡠⡑⠨⣶⣭⠟⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠶⡵⣥⣠⣅⣬⡦⣤⡣⡬⣶⠼⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⣿⣱⣿⡑⣏⣿⢽⡷⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡖⠷⡛⠸⠓⢛⠗⢙⡛⢛⢟⠶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⢴⡺⡻⡂⡌⡑⠐⡁⡁⡐⡐⢈⠄⠄⠑⡁⢚⠻⣛⣤⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⣦⢩⡛⣐⠅⢑⠄⢔⠈⠀⢂⠂⠂⢂⠂⠪⠠⠑⢊⠨⣂⠲⡙⡦⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠒⡄⡬⠂⡪⠠⡂⠂⢨⠊⠂⠐⡑⠑⠠⡑⠀⢅⡈⠤⡐⠡⡓⠰⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠁⠐⠐⠠⠑⠑⠄⠕⠀⢊⢈⠊⠂⠨⡂⠨⠀⠁⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠁⠈⠂⠄⠌⠂⠠⠁⠁⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/isosurface/sphere.txt b/test/references/isosurface/sphere.txt new file mode 100644 index 00000000..79a48d95 --- /dev/null +++ b/test/references/isosurface/sphere.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢀⢠⢤⢰⠂⡖⡆⡤⡄⡀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⡊⣚⢙⠨⢈⠐⠀⠅⠂⡁⠅⡋⣓⢑⢤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡐⠅⠢⠂⠠⠢⢈⠔⢈⡀⢂⡁⠢⡁⠔⠄⠐⠔⠨⢂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⢼⠢⡑⡡⢉⠐⠠⠀⠔⢀⠀⠂⡀⠢⠀⠄⠂⡉⢌⢊⠔⡧⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⣧⠣⢁⠂⠅⡘⢀⠊⠠⠀⠠⠆⠀⠄⠑⡀⢃⠨⠐⡈⠜⣼⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⠌⡂⠌⢐⠀⠌⠠⠁⡈⢀⡀⢁⠈⠄⠡⠀⡂⠡⢐⠡⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣂⠅⢂⠨⠀⠅⢂⡂⠐⡂⢐⡐⠨⠀⠅⡐⠨⣐⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢖⢌⢂⠡⡁⠆⠰⠆⠰⢈⠌⡐⡡⡲⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠑⠒⠨⠔⠦⠅⠒⠊⠁⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/isosurface/torus.txt b/test/references/isosurface/torus.txt new file mode 100644 index 00000000..66a16b7c --- /dev/null +++ b/test/references/isosurface/torus.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢀⢀⠠⢄⢄⠄⠄⡠⡠⠤⡀⡀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠔⠌⠜⠀⠡⠠⠁⠡⠠⠁⠈⠄⠌⠈⠄⠌⠀⠪⠠⠤⡀⡀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⡠⠔⠅⠌⠈⠄⠌⠀⡁⡀⠨⡀⠄⠠⠡⠠⡀⠥⠠⠈⠀⠡⠠⠁⠡⠱⠢⡀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⢀⣜⠃⡁⠄⢊⠈⠄⡰⠐⡀⢂⡞⢈⡰⡨⡂⢆⡁⣱⠔⢁⠘⢀⠠⠁⡑⠠⢈⠘⣲⡀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⡚⠥⠁⡀⠂⢂⠈⠄⡖⡫⠗⠋⠉⠀⠀⠀⠀⠀⠈⠉⠉⠱⢝⠱⠠⠁⡐⠐⢀⠈⢌⢧⠀⠀⠀⠀│ + │⠀⠀⠀⠀⡟⠤⠁⡀⠂⢂⢈⠄⢮⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡿⠠⡁⡐⠐⢀⠈⢤⢚⠀⠀⠀⠀│ + │⠀⠀⠀⠀⢽⠗⠅⡀⠂⢂⢀⠂⢂⠓⡢⡄⣀⠀⠀⠀⠀⠀⢀⡀⢀⢰⠂⡑⠐⡀⡐⠐⠀⠨⢔⡟⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠈⠭⠄⠌⠰⠀⡀⠂⢂⠀⡐⠐⠌⠔⠑⠅⠡⠊⠢⠡⠂⢂⠀⡐⠐⣀⠐⠠⡁⡃⡑⠁⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠈⠨⠅⠄⠌⠀⡆⢀⠐⠐⢀⠐⠐⠀⠄⠂⠂⡀⠂⡂⡀⠂⢂⠀⡂⢐⠔⠈⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⢥⢀⡂⡃⠇⠃⡁⡡⠨⡈⡨⠈⠬⠨⠢⢈⢠⠑⠒⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠋⠓⠂⠊⠒⠂⠚⠘⠚⠙⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⣀⠼⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠔⠉⠀⠀⠀⠈⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/lineplot/color_vector.txt b/test/references/lineplot/color_vector.txt new file mode 100644 index 00000000..584f2511 --- /dev/null +++ b/test/references/lineplot/color_vector.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ + 9 │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠉⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀⠀⠀⠑⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠈⠒⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠤⡀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⢄⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⣀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⢀⡠⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⣀⠤⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⡇⠀⡠⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠤⠤⠤⢤⡤⡷⠭⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤│ + -1 │⣀⠤⠊⠁⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀7⠀ \ No newline at end of file diff --git a/test/references/surfaceplot/matrix.txt b/test/references/surfaceplot/matrix.txt new file mode 100644 index 00000000..a41a2ff2 --- /dev/null +++ b/test/references/surfaceplot/matrix.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ ⠀⠀⠀⠀ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ ┌──┐ 10.0 + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠄⠀⡁⠠⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠀⢐⠀⡂⠀⡅⠀⡂⢨⠀⢐⠀⡂⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⢀⠀⠆⠀⠇⠘⠀⠈⠀⠁⠀⠁⠀⠁⠈⠀⠈⠀⠁⠀⠃⠸⠀⠰⠀⡀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠄⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠠⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ └──┘ 0.1 + └────────────────────────────────────────┘ ⠀⠀⠀⠀ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ \ No newline at end of file diff --git a/test/references/surfaceplot/single_color.txt b/test/references/surfaceplot/single_color.txt new file mode 100644 index 00000000..b2cb626a --- /dev/null +++ b/test/references/surfaceplot/single_color.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⢀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⡀⠐⠀⠂⠀⠂⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠠⠀⠀⠠⠀⠁⠀⠁⠈⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠄⠀⠀⠂⠀⠀⠄⠀⠁⠈⠀⠁⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢀⠀⠁⠠⠀⠀⠐⠀⠀⠐⠀⠀⠀⠄⠈⠀⡁⢈⠀⡂⠀⠀⠀⠀⠀⡀⢀⠀⡀⢀⠀⠀⠀│ + │⠀⠠⠀⡀⢀⠀⠄⢀⠀⠂⠀⠀⠂⠀⠀⠂⠀⠀⠂⠀⠀⠐⠀⡀⠠⠀⡂⢰⠀⡆⢘⠀⠇⢈⠀⠂⠐⠀⠂⠀│ + │⠀⠀⠀⠁⢐⠀⠂⠀⠀⡀⠈⠀⠀⠈⠀⠀⠈⠀⠀⠀⠁⠠⠀⡀⢈⠀⠅⠐⠀⠁⠈⠀⠁⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⡁⠈⠀⠀⠠⠀⠁⠀⠀⠁⠀⠀⠁⠀⠄⢀⠀⡀⠠⠀⠂⠈⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠨⠀⡁⢀⠀⠄⠈⠀⡀⠈⠀⠄⠀⡀⢀⠀⠄⢈⠀⡁⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⢀⠀⡀⠐⠀⠀⢐⠀⠄⠀⡄⢰⠀⡃⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠂⠰⠀⠅⠰⠀⠃⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/surfaceplot/slice_lines.txt b/test/references/surfaceplot/slice_lines.txt new file mode 100644 index 00000000..48626398 --- /dev/null +++ b/test/references/surfaceplot/slice_lines.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ ⠀⠀⠀⠀ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ ┌──┐ 10.0 + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀│ │▄▄│ + │⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀│ │▄▄│ + │⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀│ │▄▄│ + │⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀│ │▄▄│ + │⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀│ │▄▄│ + │⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀│ │▄▄│ + │⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀│ │▄▄│ + │⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠸⠤⠤⠤⠤⠠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ └──┘ 0.0 + └────────────────────────────────────────┘ ⠀⠀⠀⠀ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ \ No newline at end of file diff --git a/test/references/surfaceplot/slice_scatter.txt b/test/references/surfaceplot/slice_scatter.txt new file mode 100644 index 00000000..b4bfaf05 --- /dev/null +++ b/test/references/surfaceplot/slice_scatter.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ ⠀⠀⠀⠀ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ ┌──┐ 10.0 + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⣭⢨⢨⢨⢨⢨⡅⡅⡅⡅⡅⡅⣭⢨⢨⢨⢨⢨⡅⡅⡅⡅⡅⣭⢨⢨⢨⢨⢨⢨⡅⡅⡅⡅⡅⣭⠀⠀│ │▄▄│ + │⠀⠀⣶⢰⢰⢰⢰⢰⡆⡆⡆⡆⡆⡆⣶⢰⢰⢰⢰⢰⡆⡆⡆⡆⡆⣶⢰⢰⢰⢰⢰⢰⡆⡆⡆⡆⡆⣶⠀⠀│ │▄▄│ + │⠀⠀⣶⢰⢰⢰⢰⢰⡆⡆⡆⡆⡆⡆⣶⢰⢰⢰⢰⢰⡆⡆⡆⡆⡆⣶⢰⢰⢰⢰⢰⢰⡆⡆⡆⡆⡆⣶⠀⠀│ │▄▄│ + │⠀⠀⠶⠰⠰⠰⠰⠰⠆⠆⠆⠆⠆⠆⠶⠰⠰⠰⠰⠰⠆⠆⠆⠆⠆⠶⠰⠰⠰⠰⠰⠰⠆⠆⠆⠆⠆⠶⠀⠀│ │▄▄│ + │⠀⠀⠿⠸⠸⠸⠸⠸⠇⠇⠇⠇⠇⠇⠿⠸⠸⠸⠸⠸⠇⠇⠇⠇⠇⠿⠸⠸⠸⠸⠸⠸⠇⠇⠇⠇⠇⠿⠀⠀│ │▄▄│ + │⠀⠀⠿⠸⠸⠸⠸⠸⠇⠇⠇⠇⠇⠇⠿⠸⠸⠸⠸⠸⠇⠇⠇⠇⠇⠿⠸⠸⠸⠸⠸⠸⠇⠇⠇⠇⠇⠿⠀⠀│ │▄▄│ + │⠀⠀⣛⢘⢘⢘⢘⢘⡃⡃⡃⡃⡃⡃⣛⢘⢘⢘⢘⢘⡃⡃⡃⡃⡃⣛⢘⢘⢘⢘⢘⢘⡃⡃⡃⡃⡃⣛⠀⠀│ │▄▄│ + │⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠸⠤⠤⠤⠤⠠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ └──┘ 0.0 + └────────────────────────────────────────┘ ⠀⠀⠀⠀ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ \ No newline at end of file diff --git a/test/references/surfaceplot/sombrero.txt b/test/references/surfaceplot/sombrero.txt new file mode 100644 index 00000000..38855b78 --- /dev/null +++ b/test/references/surfaceplot/sombrero.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ ⠀⠀⠀⠀ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ ┌──┐ 30.0 + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⣡⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡉⢍⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⢇⡹⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡣⡠⣄⢜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⠆⡰⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⢴⡶⣶⢿⣿⣿⡿⣶⢶⡦⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡿⡭⠥⠎⡵⠗⡟⢻⠺⢮⠱⠬⢭⢿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣟⢿⣏⣒⠊⣟⡑⡜⢧⢊⣻⠑⣒⣹⡿⣻⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠤⠾⠋⠀⠀⢻⣟⣯⣿⣷⣿⣿⣾⣿⣽⣻⡟⠀⠀⠙⠷⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣯⡿⣧⣟⣻⣼⢿⣽⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠷⢯⡽⠾⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ └──┘ -7.0 + └────────────────────────────────────────┘ ⠀⠀⠀⠀ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ \ No newline at end of file diff --git a/test/references/surfaceplot/sombrero_aspect.txt b/test/references/surfaceplot/sombrero_aspect.txt new file mode 100644 index 00000000..cff05cd3 --- /dev/null +++ b/test/references/surfaceplot/sombrero_aspect.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ ⠀⠀⠀⠀ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ ┌──┐ 30.0 + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⡃⢝⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠭⠂⠒⠭⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣠⣤⣴⣥⡅⣭⣬⣦⣤⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣖⡻⠝⡪⢒⢵⣥⡫⠇⠼⢝⣬⡮⡒⢕⠫⢟⣲⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⢗⣒⣊⡩⠔⢁⢎⣐⡱⡁⢏⢎⣂⡱⡈⠢⢍⣑⣒⡺⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⣠⣾⡿⠿⣿⣿⣕⣒⣒⣊⣽⣯⡮⠵⠅⠮⠮⢵⣽⣯⣑⣒⣒⣪⣿⣿⠿⢿⣷⣄⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠐⠻⠿⠛⠛⠛⠛⠽⢿⣶⣶⡾⠓⠉⠢⠈⡀⢁⠁⠔⠉⠚⢷⣶⣶⡿⠯⠛⠛⠛⠛⠿⠟⠂⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠿⣯⣯⣓⢶⣷⡆⣶⣾⡶⣚⣽⣽⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⡳⡻⡃⢟⢟⢞⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠪⠆⡵⠕⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ └──┘ -7.0 + └────────────────────────────────────────┘ ⠀⠀⠀⠀ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ \ No newline at end of file diff --git a/test/references/surfaceplot/sombrero_zscale.txt b/test/references/surfaceplot/sombrero_zscale.txt new file mode 100644 index 00000000..5c612966 --- /dev/null +++ b/test/references/surfaceplot/sombrero_zscale.txt @@ -0,0 +1,18 @@ + ┌────────────────────────────────────────┐ ⠀⠀⠀⠀ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ ┌──┐ 30.0 + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡫⢟⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡔⠌⡡⢢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣰⣭⣊⣑⣭⣆⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⠴⣚⠯⡫⢝⣿⣿⡻⣟⣿⣿⡫⢝⠽⣓⠦⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣯⡭⠭⠒⢊⠔⢱⢍⡘⡖⣳⢃⡩⡎⠢⡑⠒⠭⢭⣽⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣞⠭⠭⠥⢒⣿⣋⣵⢜⡧⣮⣙⣿⡒⠬⠭⠭⣳⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠴⢾⠿⠯⠛⠛⠽⢿⣯⣶⣮⡽⠛⠫⡉⠃⠙⢉⠝⠛⢯⣵⣶⣽⡿⠯⠛⠛⠽⠿⡷⠦⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣿⣿⢶⣶⣶⡺⣗⣶⣶⡶⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⢷⢵⢾⡷⡮⡾⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + │⠀⠀⣀⠤⠧⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠢⢕⡯⠔⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ │▄▄│ + -1 │⠐⠉⠀⠀⠀⠀⠈⠑⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ └──┘ -7.0 + └────────────────────────────────────────┘ ⠀⠀⠀⠀ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ ⠀⠀⠀⠀ \ No newline at end of file diff --git a/test/references/volume/cube_0.5.txt b/test/references/volume/cube_0.5.txt new file mode 100644 index 00000000..513b06ea --- /dev/null +++ b/test/references/volume/cube_0.5.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀zoom=0.5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⠁⡏⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠎⣀⠤⠧⢄⡱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠓⠛⠒⠒⠒⠒⠚⠛⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/volume/cube_1.txt b/test/references/volume/cube_1.txt new file mode 100644 index 00000000..2dd6ac21 --- /dev/null +++ b/test/references/volume/cube_1.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀zoom=1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⡧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠁⠀⡇⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠎⠀⠀⠀⡇⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡼⠁⠀⠀⣀⠤⠧⢄⡀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢋⡠⠔⠒⠉⠀⠀⠀⠀⠈⠑⠒⠤⣑⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠛⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/volume/cube_2.txt b/test/references/volume/cube_2.txt new file mode 100644 index 00000000..53e2cb29 --- /dev/null +++ b/test/references/volume/cube_2.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀zoom=2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠞⠁⡏⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠋⠀⠀⡇⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⠀⠀⠀⠀⡇⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⠃⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠘⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠁⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⣠⠎⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠧⢄⡀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⢀⡴⠁⠀⠀⠀⠀⢀⡠⠔⠒⠉⠀⠀⠀⠀⠈⠑⠒⠤⣀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⣠⠊⠀⢀⣀⠤⠒⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠑⠢⢄⣀⠀⠑⡄⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢀⣜⠥⠔⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠒⠬⢦⡀⠀⠀⠀│ + │⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/volume/cube_orthographic.txt b/test/references/volume/cube_orthographic.txt new file mode 100644 index 00000000..6f3cae21 --- /dev/null +++ b/test/references/volume/cube_orthographic.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀proj=orthographic⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠤⡦⢄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠔⠊⠁⠀⠀⡇⠀⠀⠉⠒⠤⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⠒⠉⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠈⠑⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢀⣀⠤⠒⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠑⠢⢄⡀⠀⠀⠀│ + │⠀⠀⠀⢸⠉⠒⠤⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠤⠒⠉⡇⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠈⠑⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⣀⠤⠒⠊⠁⠀⠀⠀⠀⡇⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠑⠢⢄⣀⠀⠀⡇⠀⣀⡠⠔⠊⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣉⠶⡷⢍⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⠒⠉⠀⠀⡇⠀⠈⠑⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⢀⣀⠤⠒⠉⠁⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠉⠑⠢⢄⣀⠀⠀⠀⠀⡇⠀⠀⠀│ + │⠀⠀⠀⢸⣀⠤⠔⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠒⠤⢄⡇⠀⠀⠀│ + │⠀⠀⠀⠈⠑⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠒⠊⠁⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠉⠑⠢⢄⣀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⣀⡠⠔⠊⠉⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⢀⡠⠼⠤⣀⠀⠀⠀⠀⠀⠀⠀⠉⠒⠤⢄⡀⠀⠀⡇⠀⢀⡠⠤⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠊⠁⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠒⠗⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/volume/cube_perspective.txt b/test/references/volume/cube_perspective.txt new file mode 100644 index 00000000..2258bcf4 --- /dev/null +++ b/test/references/volume/cube_perspective.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀proj=perspective⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠉⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠉⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠑⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠊⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠈⡖⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⢲⠁⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⢀⠧⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠼⡀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⡠⠒⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⢄⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⡀⠀⠀⢸⣀⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⣀⡇⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⡇⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠧⠤⠤⠤⠤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/volume/ellipsoid_xy.txt b/test/references/volume/ellipsoid_xy.txt new file mode 100644 index 00000000..151191c0 --- /dev/null +++ b/test/references/volume/ellipsoid_xy.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀plane=xy⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⡀⠀⠠⠠⠀⠠⠈⠁⢐⠀⠠⠈⠈⠠⠀⠀⠀⡅⠀⠀⠄⠁⠁⠄⠀⡂⠈⠁⠄⠀⠄⠄⠀⢀⠀⠀⠀│ + │⠀⠀⠠⠄⡁⠰⢈⠀⠀⠰⡁⠀⠀⠀⠰⢈⢈⠀⡁⠀⠆⢈⠀⡁⡁⠆⠀⠀⠀⢈⠆⠀⠀⡁⠆⢈⠠⠄⠀⠀│ + │⠀⠀⠀⠁⠀⠐⠐⠀⠐⢀⡀⠨⠀⠐⢀⢀⠐⠀⠀⠀⡃⠀⠀⠂⡀⡀⠂⠀⠅⢀⡀⠂⠀⠂⠂⠀⠈⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠸⠤⠤⠤⠤⠠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/volume/ellipsoid_xz.txt b/test/references/volume/ellipsoid_xz.txt new file mode 100644 index 00000000..39f4a0f1 --- /dev/null +++ b/test/references/volume/ellipsoid_xz.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀plane=xz⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⢀⠀⢀⠐⠐⢐⠀⠂⠀⡃⠐⠀⡂⠂⠂⡀⠀⡀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠠⠠⠀⠠⠀⠀⠠⠀⠀⠀⠠⠀⠀⠀⠀⠄⠀⠀⠀⠄⠀⠀⠀⠄⠀⠀⠄⠀⠄⠄⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠂⠂⠀⠐⠀⠀⠀⠂⠀⠀⠀⠐⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⠂⠀⠀⠀⠐⠀⠀⠀⠂⠀⠐⠐⠀⠀⠀│ + │⠀⠀⠠⠄⠀⠠⠀⠀⠀⠠⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠄⠀⠀⠀⠀⠄⠀⠀⠀⠀⠄⠀⠀⠀⠄⠀⠠⠄⠀⠀│ + │⠀⠀⠀⠄⠄⠀⠠⠀⠀⠀⠄⠀⠀⠀⠠⠀⠀⠀⠀⠀⠄⠀⠀⠀⠀⠄⠀⠀⠀⠠⠀⠀⠀⠄⠀⠠⠠⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠐⠐⠀⠐⠀⠀⠐⠀⠀⠀⠐⠀⠀⠀⠀⠂⠀⠀⠀⠂⠀⠀⠀⠂⠀⠀⠂⠀⠂⠂⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠈⠀⠈⠠⠠⠨⠀⠄⠀⡅⠠⠀⠅⠄⠄⠁⠀⠁⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠸⠤⠤⠤⠤⠠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/references/volume/ellipsoid_yz.txt b/test/references/volume/ellipsoid_yz.txt new file mode 100644 index 00000000..7ec3ce3f --- /dev/null +++ b/test/references/volume/ellipsoid_yz.txt @@ -0,0 +1,19 @@ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀plane=yz⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ┌────────────────────────────────────────┐ + 1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡐⡒⣓⢂⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠤⠤⠠⠀⠄⠄⠤⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠒⠐⠐⠀⠂⠂⠂⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠄⠄⠠⠀⠄⠄⠠⠠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠤⠠⠠⠀⠄⠄⠄⠤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠒⠒⠐⠀⠂⠂⠒⠒⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠡⠥⡭⠌⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + │⠀⠀⠀⠸⠤⠤⠤⠤⠠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ + └────────────────────────────────────────┘ + ⠀-1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀1⠀ \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index fe6057d0..d9880f6e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using UnicodePlots, ReferenceTests, Test using ReferenceTests: BeforeAfterFull using Dates: Date, Day import Random: seed! +using LinearAlgebra using ColorTypes using StableRNGs using StatsBase @@ -68,10 +69,13 @@ withenv("FORCE_COLOR" => "X") do # github.com/JuliaPlots/UnicodePlots.jl/issues "tst_histogram.jl", "tst_lineplot.jl", "tst_scatterplot.jl", - "tst_contourplot.jl", "tst_spy.jl", "tst_boxplot.jl", "tst_heatmap.jl", + "tst_contourplot.jl", + "tst_volume.jl", + "tst_surfaceplot.jl", + "tst_isosurface.jl", "tst_deprecated_warns.jl", ) @testset "$test" begin diff --git a/test/tst_common.jl b/test/tst_common.jl index f4fbf5c1..f5560fa8 100644 --- a/test/tst_common.jl +++ b/test/tst_common.jl @@ -2,7 +2,7 @@ @testset "types" begin @test UnicodePlots.plotting_range(0, 1) === (0.0, 1.0) @test UnicodePlots.plotting_range(0.0, 1) === (0.0, 1.0) - @test UnicodePlots.plotting_range(0, 1.0f0) === (0.0, 1.0) + @test UnicodePlots.plotting_range(0, 1.0f0) === (0.0, 1.0f0) @test UnicodePlots.plotting_range(0x0, 0x1) === (0.0, 1.0) end @@ -18,7 +18,7 @@ end @testset "types" begin @test UnicodePlots.plotting_range_narrow(0, 1) === (0.0, 1.0) @test UnicodePlots.plotting_range_narrow(0.0, 1) === (0.0, 1.0) - @test UnicodePlots.plotting_range_narrow(0, 1.0f0) === (0.0, 1.0) + @test UnicodePlots.plotting_range_narrow(0, 1.0f0) === (0.0, 1.0f0) @test UnicodePlots.plotting_range_narrow(0x0, 0x1) === (0.0, 1.0) end @@ -75,6 +75,11 @@ end @test UnicodePlots.colormap_callback([1, 2, 3]) isa Function @test UnicodePlots.colormap_callback(nothing) === nothing + # clamp in range + values = collect(1:10) + callback = UnicodePlots.colormap_callback(:viridis) + colors = [callback(v, values[2], values[end - 1]) for v in values] + # en.wikipedia.org/wiki/ANSI_escape_code#8-bit @test UnicodePlots.rgb2ansi((0, 0, 0)) == 016 # black @test UnicodePlots.rgb2ansi((1, 0, 0)) == 196 # red diff --git a/test/tst_contourplot.jl b/test/tst_contourplot.jl index 77680323..a642426f 100644 --- a/test/tst_contourplot.jl +++ b/test/tst_contourplot.jl @@ -1,7 +1,6 @@ gaussian_2d(x = -3:0.01:3, y = -7:0.01:3; x₀ = 0, y₀ = -2, σx = 1, σy = 2) = begin - X = repeat(x', length(y), 1) - Y = repeat(y, 1, length(x)) - x, y, map((x, y) -> exp(-((x - x₀) / 2σx)^2 - ((y - y₀) / 2σy)^2), X, Y) + g = (x, y) -> exp(-((x - x₀) / 2σx)^2 - ((y - y₀) / 2σy)^2) + x, y, g.(x', y) end @testset "arbitrary colormap" begin diff --git a/test/tst_isosurface.jl b/test/tst_isosurface.jl new file mode 100644 index 00000000..71e4a574 --- /dev/null +++ b/test/tst_isosurface.jl @@ -0,0 +1,32 @@ +@testset "sphere" begin + p = isosurface( + -3:0.2:3, + -3:0.2:3, + -3:0.2:3, + (x, y, z) -> x^2 + y^2 + z^2 - 2; + centroid = false, + axes3d = false, + cull = true, + zoom = 2, + ) + test_ref("references/isosurface/sphere.txt", @show_col(p)) +end + +@testset "torus" begin + torus(x, y, z, r = 0.2, R = 0.5) = (√(x^2 + y^2) - R)^2 + z^2 - r^2 + p = isosurface( + -1:0.1:1, + -1:0.1:1, + -1:0.1:1, + torus; + elevation = 50, + cull = true, + zoom = 2, + ) + test_ref("references/isosurface/torus.txt", @show_col(p)) +end + +@testset "hyperboloid" begin + p = isosurface(-3:0.6:3, -3:0.6:3, -3:0.6:3, (x, y, z) -> x^2 + y^2 - z^2 - 1;) + test_ref("references/isosurface/hyperboloid.txt", @show_col(p)) +end diff --git a/test/tst_lineplot.jl b/test/tst_lineplot.jl index 2df5e4fd..4f917f31 100644 --- a/test/tst_lineplot.jl +++ b/test/tst_lineplot.jl @@ -258,3 +258,11 @@ end lineplot!(p, [0.0, 1.0], [0.5, 0.5], head_tail = :both, name = "both", color = :blue) test_ref("references/lineplot/arrows.txt", @show_col(p)) end + +@testset "color vector" begin + x = [[-1, 2], [2, 3], [3, 7]] + y = [[-1, 2], [2, 9], [9, 4]] + p = Plot([-1, 7], [-1, 9]) + lineplot!(p, x, y, color = [:red, :green, :blue]) + test_ref("references/lineplot/color_vector.txt", @show_col(p)) +end diff --git a/test/tst_surfaceplot.jl b/test/tst_surfaceplot.jl new file mode 100644 index 00000000..7cef3139 --- /dev/null +++ b/test/tst_surfaceplot.jl @@ -0,0 +1,46 @@ +@testset "zscale" begin + sombrero(x, y) = 30sinc(√(x^2 + y^2) / π) + + p = surfaceplot(-8:0.5:8, -8:0.5:8, sombrero) + test_ref("references/surfaceplot/sombrero.txt", @show_col(p)) + + p = surfaceplot(-8:0.5:8, -8:0.5:8, sombrero, zscale = :aspect) + test_ref("references/surfaceplot/sombrero_aspect.txt", @show_col(p)) + + p = surfaceplot(-8:0.5:8, -8:0.5:8, sombrero, zscale = h -> h / 2) + test_ref("references/surfaceplot/sombrero_zscale.txt", @show_col(p)) + + @test_throws ArgumentError surfaceplot([1 2; 3 4], zscale = :not_supported) +end + +@testset "single color - no colormap" begin + p = surfaceplot(0:0.5:(2π), 0:0.5:(2π), (x, y) -> sin(x) + cos(y), color = :yellow) + test_ref("references/surfaceplot/single_color.txt", @show_col(p)) +end + +@testset "matrix" begin + p = surfaceplot(collect(1:10) * collect(0.1:0.1:1)') + test_ref("references/surfaceplot/matrix.txt", @show_col(p)) +end + +@testset "slice" begin + data = zeros(40, 20, 20) + x, y, z = (axes(data, d) for d in 1:ndims(data)) + xc, yc, zc = 30, 8, 8 # centers + xr, yr, zr = 10, 6, 3 # radii + + for k in z, j in y, i in x # ellipsoid + data[i, j, k] = ((i - xc) / xr)^2 + ((j - yc) / yr)^2 + ((k - zc) / zr)^2 + end + + # NOTE: projection precision issues, force azimuth and elevation + kw = (; zscale = z -> zc, colormap = :jet, azimuth = -90, elevation = 90) + + z = data[:, :, zc] + + p = surfaceplot(x, y, z; kw...) + test_ref("references/surfaceplot/slice_scatter.txt", @show_col(p)) + + p = surfaceplot(x, y, z; kw..., lines = true) + test_ref("references/surfaceplot/slice_lines.txt", @show_col(p)) +end diff --git a/test/tst_volume.jl b/test/tst_volume.jl new file mode 100644 index 00000000..8bd61eb5 --- /dev/null +++ b/test/tst_volume.jl @@ -0,0 +1,147 @@ +@testset "camera" begin + # NOTE: the output of view_matrix uses the OpenGL convention: + # x pointing to the east, y pointing to the north, z pointing out of the screen, towards the user + # when our up vector = y (openGL default), V looks like I + # up vector = :x -> cam_dir = :y + # up vector = :y -> cam_dir = :z + # up vector = :z -> cam_dir = :x + V, dir = UnicodePlots.view_matrix([0, 0, 0], 1, 0, 0, :x) + @test V ≈ [ + 0 0 1 0 + 1 0 0 0 + 0 1 0 -1 + 0 0 0 1 + ] + @test dir == [0, 1, 0] + V, dir = UnicodePlots.view_matrix([0, 0, 0], 1, 0, 0, :y) + @test V ≈ [ + 1 0 0 0 + 0 1 0 0 + 0 0 1 -1 + 0 0 0 1 + ] + @test dir == [0, 0, 1] + V, dir = UnicodePlots.view_matrix([0, 0, 0], 1, 0, 0, :z) + @test V ≈ [ + 0 1 0 0 + 0 0 1 0 + 1 0 0 -1 + 0 0 0 1 + ] + @test dir == [1, 0, 0] + + @test_throws ArgumentError UnicodePlots.view_matrix([0, 0, 0], 1, 0, 0, :not_supported) + + @test UnicodePlots.scale_4x4([1, 1, 1]) ≈ I + @test UnicodePlots.translate_4x4([0, 0, 0]) ≈ I + @test UnicodePlots.rotd_x(0) ≈ I + @test UnicodePlots.rotd_y(0) ≈ I + @test UnicodePlots.rotd_z(0) ≈ I + + T = MVP([-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0]) + @test length(T([1, 2, 3])) == 2 + @test length(T((1, 2, 3))) == 2 + + corners = UnicodePlots.cube_corners(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0) + @test size(corners) == (3, 8) + @test all(-1 .<= corners .<= 1) +end + +@testset "azimuth / elevation" begin + ellipsoid(θs = (-π / 2):(π / 10):(π / 2), ϕs = (-π):(π / 10):π, a = 2, b = 0.5, c = 1) = + ( + [a * cos(θ) .* cos(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)] |> vec, + [b * cos(θ) .* sin(ϕ) for (ϕ, θ) in Iterators.product(ϕs, θs)] |> vec, + [c * sin(θ) for (ϕ, θ) in Iterators.product(ϕs, θs)] |> vec, + ) + + x, y, z = ellipsoid() + for (plane, az, el) in [("yz", 0, 0), ("xz", -90, 0), ("xy", -90, 90)] + p = Plot( + x, + y, + z, + projection = :orthographic, + elevation = el, + azimuth = az, + title = "plane=$plane", + ) + scatterplot!(p, x, y, z) + + test_ref("references/volume/ellipsoid_$plane.txt", @show_col(p)) + end +end + +@testset "cube" begin + for proj in (:orthographic, :perspective) + ortho = proj === :orthographic + + T = MVP( + [-1, 1], + [-1, 1], + [-1, 1]; + projection = proj, + elevation = ortho ? UnicodePlots.KEYWORDS.elevation : 0, + azimuth = ortho ? UnicodePlots.KEYWORDS.azimuth : 0, + ) + @test T.ortho == ortho + + cube = ( + # near plane square + [(-1, 1, 1), (1, 1, 1)], + [(-1, -1, 1), (-1, 1, 1)], + [(-1, -1, 1), (1, -1, 1)], + [(1, -1, 1), (1, 1, 1)], + # transversal edges + [(1, 1, -1), (1, 1, 1)], + [(-1, 1, -1), (-1, 1, 1)], + [(-1, -1, -1), (-1, -1, 1)], + [(1, -1, -1), (1, -1, 1)], + # far plane square + [(-1, 1, -1), (1, 1, -1)], + [(-1, -1, -1), (-1, 1, -1)], + [(-1, -1, -1), (1, -1, -1)], + [(1, -1, -1), (1, 1, -1)], + ) + + segment2xyz(s) = [s[1][1], s[2][1]], [s[1][2], s[2][2]], [s[1][3], s[2][3]] + + p = lineplot(segment2xyz(cube[1])..., projection = T, title = "proj=$proj") + for s in cube[2:end] + lineplot!(p, segment2xyz(s)...) + end + + test_ref("references/volume/cube_$proj.txt", @show_col(p)) + end +end + +@testset "zoom" begin + for zoom in (0.5, 1, 2) + T = MVP([-1, 1], [-1, 1], [-1, 1]; zoom = zoom) + + tetrahedron = ( + # 1st triangle + [(0, 0, 0), (1, 0, 0)], + [(1, 0, 0), (0, 0, 1)], + [(0, 0, 1), (0, 0, 0)], + # 2nd triangle + [(0, 0, 0), (0, 1, 0)], + [(0, 1, 0), (0, 0, 1)], + [(0, 0, 1), (0, 0, 0)], + # 3rd triangle + [(1, 0, 0), (0, 1, 0)], + [(0, 1, 0), (0, 0, 1)], + [(0, 0, 1), (1, 0, 0)], + ) + + segments2xyz(segments) = ( + [p[1] for s in segments for p in s], + [p[2] for s in segments for p in s], + [p[3] for s in segments for p in s], + ) + + title = "zoom=$zoom" + p = lineplot(segments2xyz(tetrahedron)..., projection = T, title = title) + test_ref("references/volume/cube_$(zoom).txt", @show_col(p)) + end +end