Skip to content

Commit

Permalink
Interpolate color (lines on surfaceplot) - performance fixes (#224)
Browse files Browse the repository at this point in the history
  • Loading branch information
t-bltg authored Feb 17, 2022
1 parent c3dd3d0 commit dda54d5
Show file tree
Hide file tree
Showing 32 changed files with 560 additions and 445 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- WARNING: this file has been automatically generated, please update UnicodePlots/docs/generate_docs.jl instead, and run $ julia generate_docs.jl to render README.md !! -->
# 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)
[![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) [![JuliaHub deps](https://juliahub.com/docs/UnicodePlots/deps.svg)](https://juliahub.com/ui/Packages/UnicodePlots/Ctj9q?t=2) [![UnicodePlots Downloads](https://shields.io/endpoint?url=https://pkgs.genieframework.com/api/v1/badge/UnicodePlots)](https://pkgs.genieframework.com?packages=UnicodePlots)

Advanced [`Unicode`](https://en.wikipedia.org/wiki/Unicode) plotting library designed for use in `Julia`'s `REPL`.

Expand Down Expand Up @@ -214,12 +214,12 @@ 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`:
Use `lines=true` to increase the density (underlying call to `lineplot` instead of `scatterplot`, with color interpolation). 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
-2:2, -2:2, (x, y) -> 15sinc((x^2 + y^2) / π),
zscale=z -> 0, lines=true, colormap=:jet, border=:dotted
)
```
![Surfaceplot](https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs/2.x/surfaceplot2.png)
Expand Down Expand Up @@ -295,12 +295,12 @@ All plots support the set (or a subset) of the following named parameters:
- `colormap::Symbol = :viridis`: choose from (`:viridis`, `:cividis`, `:plasma`, `:jet`, `:gray`, `keys(UnicodePlots.COLOR_MAP_DATA)`...), or supply a function `f: (z, zmin, zmax) -> Int(0-255)`, or a vector of RGB tuples.
- `colorbar_lim::Tuple = (0, 1)`: colorbar limit.
- `colorbar_border::Symbol = :solid`: color bar bounding box style (`:solid`, `:bold`, `:dashed`, `:dotted`, `:ascii`, `:none`).
- `canvas::DataType = BrailleCanvas`: type of canvas used for drawing.
- `canvas::UnionAll = BrailleCanvas`: type of canvas used for drawing.
- `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).
- `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.
Expand Down
23 changes: 16 additions & 7 deletions docs/generate_docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ function main()
"""),
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
-2:2, -2:2, (x, y) -> 15sinc(√(x^2 + y^2) / π),
zscale=z -> 0, lines=true, colormap=:jet, border=:dotted
)
"""),
isosurface = ("Isosurface", """
Expand Down Expand Up @@ -150,6 +150,7 @@ function main()
[![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)
[![JuliaHub deps](https://juliahub.com/docs/UnicodePlots/deps.svg)](https://juliahub.com/ui/Packages/UnicodePlots/Ctj9q?t=2)
[![UnicodePlots Downloads](https://shields.io/endpoint?url=https://pkgs.genieframework.com/api/v1/badge/UnicodePlots)](https://pkgs.genieframework.com?packages=UnicodePlots)
Advanced [`Unicode`](https://en.wikipedia.org/wiki/Unicode) plotting library designed for use in `Julia`'s `REPL`.
Expand Down Expand Up @@ -280,14 +281,14 @@ Plot a colored surface using height values `z` above a `x-y` plane, in three dim
$(examples.surfaceplot1)
Use `lines=true` to increase the density (underlying call to `lineplot` instead of `scatterplot`).
Use `lines=true` to increase the density (underlying call to `lineplot` instead of `scatterplot`, with color interpolation).
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.
Uses [`MarchingCubes.jl`](https://github.com/JuliaGeometry/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`.
Expand Down Expand Up @@ -439,14 +440,22 @@ Inspired by [TextPlots.jl](https://github.com/sunetos/TextPlots.jl), which in tu
write(io, """
#!/usr/bin/env bash
# WARNING: this file has been automatically generated, please update UnicodePlots/docs/generate_docs.jl instead
echo '== julia =='
\${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
txt2png() {
html=\${1%.txt}.html
cat \$1 | \${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
}
echo '== txt2png =='
for f in $ver/*.txt; do
txt2png \$f & pids+=(\$!)
done
wait \${pids[@]}
rm $ver/*.txt
rm $ver/*.html
Expand Down
93 changes: 56 additions & 37 deletions src/canvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ pixel!(c::Canvas, pixel_x::Integer, pixel_y::Integer; color::UserColorType = :no
pixel!(c, pixel_x, pixel_y, color)

function points!(c::Canvas, x::Number, y::Number, color::UserColorType)
origin_x(c) <= (xs = fscale(x, c.xscale)) <= origin_x(c) + width(c) || return c
origin_y(c) <= (ys = fscale(y, c.yscale)) <= origin_y(c) + height(c) || return c
origin_x(c) (xs = c.xscale(x)) origin_x(c) + width(c) || return c
origin_y(c) (ys = c.yscale(y)) origin_y(c) + height(c) || return c
pixel_x = (xs - origin_x(c)) / width(c) * pixel_width(c)
pixel_y = pixel_height(c) - (ys - origin_y(c)) / height(c) * pixel_height(c)
pixel!(c, floor(Int, pixel_x), floor(Int, pixel_y), color)
Expand All @@ -32,7 +32,7 @@ function points!(
Y::AbstractVector,
color::AbstractVector{T},
) where {T<:UserColorType}
(length(X) == length(color) && length(X) == length(Y)) ||
length(X) == length(Y) == length(color) ||
throw(DimensionMismatch("X, Y, and color must be the same length"))
for i in 1:length(X)
points!(c, X[i], Y[i], color[i])
Expand All @@ -50,48 +50,66 @@ function lines!(
y1::Number,
x2::Number,
y2::Number,
color::UserColorType,
c_or_v1::Union{AbstractFloat,UserColorType}, # either floating point values or colors
c_or_v2::Union{AbstractFloat,UserColorType} = nothing,
col_cb::Union{Nothing,Function} = nothing, # color callback (map values to colors)
)
x1 = fscale(x1, c.xscale)
x2 = fscale(x2, c.xscale)
y1 = fscale(y1, c.yscale)
y2 = fscale(y2, c.yscale)
x1s = c.xscale(x1)
x2s = c.xscale(x2)
y1s = c.yscale(y1)
y2s = c.yscale(y2)

mx = origin_x(c)
Mx = origin_x(c) + width(c)
(x1 < mx && x2 < mx) || (x1 > Mx && x2 > Mx) && return c
(mx x1s Mx || mx x2s Mx) || return c

my = origin_y(c)
My = origin_y(c) + height(c)
(y1 < my && y2 < my) || (y1 > My && y2 > My) && return c
(my y1s My || my y2s My) || return c

x2p(x) = (x - mx) / width(c) * pixel_width(c)
y2p(y) = pixel_height(c) - (y - my) / height(c) * pixel_height(c)

Δx = x2p(x2) - (cur_x = x2p(x1))
Δy = y2p(y2) - (cur_y = y2p(y1))
Δx = x2p(x2s) - (cur_x = x2p(x1s))
Δy = y2p(y2s) - (cur_y = y2p(y1s))

nsteps = abs(Δx) > abs(Δy) ? abs(Δx) : abs(Δy)
nsteps = min(nsteps, typemax(Int32)) # hard limit
nsteps = min(max(abs(Δx), abs(Δy)), typemax(Int32)) # hard limit
len = min(floor(Int, nsteps), typemax(Int16)) # performance limit

δx = Δx / nsteps
δy = Δy / nsteps

px, Px = extrema([x2p(mx), x2p(Mx)])
py, Py = extrema([y2p(my), y2p(My)])

pixel!(c, floor(Int, cur_x), floor(Int, cur_y), color)
max_num_iter = typemax(Int16) # performance limit
for _ in if nsteps > max_num_iter
range(1, stop = nsteps, length = max_num_iter)
px, Px = x2p(mx), x2p(Mx)
py, Py = y2p(my), y2p(My)
px, Px = min(px, Px), max(px, Px)
py, Py = min(py, Py), max(py, Py)

if c_or_v1 isa AbstractFloat && c_or_v2 isa AbstractFloat && col_cb !== nothing
pixel!(c, floor(Int, cur_x), floor(Int, cur_y), col_cb(c_or_v1))
start_x, start_y = cur_x, cur_y
= 1 / (Δx^2 + Δy^2)
for _ in range(1, length = len)
cur_x += δx
cur_y += δy
(cur_y < py || cur_y > Py) && continue
(cur_x < px || cur_x > Px) && continue
weight = ((cur_x - start_x)^2 + (cur_y - start_y)^2) *
pixel!(
c,
floor(Int, cur_x),
floor(Int, cur_y),
col_cb((1 - weight) * c_or_v1 + weight * c_or_v2),
)
end
else
range(1, stop = nsteps, step = 1)
end
cur_x += δx
cur_y += δy
(cur_y < py || cur_y > Py) && continue
(cur_x < px || cur_x > Px) && continue
pixel!(c, floor(Int, cur_x), floor(Int, cur_y), color)
pixel!(c, floor(Int, cur_x), floor(Int, cur_y), c_or_v1)
for _ in range(1, length = len)
cur_x += δx
cur_y += δy
(cur_y < py || cur_y > Py) && continue
(cur_x < px || cur_x > Px) && continue
pixel!(c, floor(Int, cur_x), floor(Int, cur_y), c_or_v1)
end
end
c
end
Expand All @@ -107,11 +125,12 @@ lines!(

function lines!(c::Canvas, X::AbstractVector, Y::AbstractVector, color::UserColorType)
length(X) == length(Y) || throw(DimensionMismatch("X and Y must be the same length"))
for i in 1:(length(X) - 1)
if !(isfinite(X[i]) && isfinite(X[i + 1]) && isfinite(Y[i]) && isfinite(Y[i + 1]))
continue
end
lines!(c, X[i], Y[i], X[i + 1], Y[i + 1], color)
for i in 2:length(X)
isfinite(X[i - 1]) || continue
isfinite(Y[i - 1]) || continue
isfinite(X[i]) || continue
isfinite(Y[i]) || continue
lines!(c, X[i - 1], Y[i - 1], X[i], Y[i], color)
end
c
end
Expand Down Expand Up @@ -229,8 +248,8 @@ function annotate!(
halign = :center,
valign = :center,
)
xs = fscale(x, c.xscale)
ys = fscale(y, c.yscale)
xs = c.xscale(x)
ys = c.yscale(y)
pixel_x = (xs - origin_x(c)) / width(c) * pixel_width(c)
pixel_y = pixel_height(c) - (ys - origin_y(c)) / height(c) * pixel_height(c)

Expand All @@ -244,8 +263,8 @@ function annotate!(
end

function annotate!(c::Canvas, x::Number, y::Number, text::Char, color::UserColorType)
xs = fscale(x, c.xscale)
ys = fscale(y, c.yscale)
xs = c.xscale(x)
ys = c.yscale(y)
pixel_x = (xs - origin_x(c)) / width(c) * pixel_width(c)
pixel_y = pixel_height(c) - (ys - origin_y(c)) / height(c) * pixel_height(c)

Expand Down
16 changes: 8 additions & 8 deletions src/canvas/asciicanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ with `lineplot`.
For `scatterplot` we suggest to use the `DotCanvas`
instead.
"""
struct AsciiCanvas <: LookupCanvas
grid::Array{UInt16,2}
colors::Array{ColorType,2}
struct AsciiCanvas{XS<:Function,YS<:Function} <: LookupCanvas
grid::Matrix{UInt16}
colors::Matrix{ColorType}
min_max::NTuple{2,UInt64}
blend::Bool
visible::Bool
Expand All @@ -119,18 +119,18 @@ struct AsciiCanvas <: LookupCanvas
origin_y::Float64
width::Float64
height::Float64
xscale::Union{Symbol,Function}
yscale::Union{Symbol,Function}
xscale::XS
yscale::YS
end

@inline x_pixel_per_char(::Type{AsciiCanvas}) = 3
@inline y_pixel_per_char(::Type{AsciiCanvas}) = 3
@inline x_pixel_per_char(::Type{C}) where {C<:AsciiCanvas} = 3
@inline y_pixel_per_char(::Type{C}) where {C<:AsciiCanvas} = 3

@inline lookup_encode(::AsciiCanvas) = ascii_signs
@inline lookup_decode(::AsciiCanvas) = ascii_decode

AsciiCanvas(args...; kw...) =
CreateLookupCanvas(AsciiCanvas, (0b000_000_000, 0b111_111_111), args...; kw...)
CreateLookupCanvas(AsciiCanvas, UInt16, (0b000_000_000, 0b111_111_111), args...; kw...)

function char_point!(
c::AsciiCanvas,
Expand Down
16 changes: 8 additions & 8 deletions src/canvas/blockcanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ This canvas effectively turns every character
into 4 pixels that can individually be manipulated
using binary operations.
"""
struct BlockCanvas <: LookupCanvas
grid::Array{UInt16,2}
colors::Array{ColorType,2}
struct BlockCanvas{XS<:Function,YS<:Function} <: LookupCanvas
grid::Matrix{UInt16}
colors::Matrix{ColorType}
min_max::NTuple{2,UInt64}
blend::Bool
visible::Bool
Expand All @@ -44,18 +44,18 @@ struct BlockCanvas <: LookupCanvas
origin_y::Float64
width::Float64
height::Float64
xscale::Union{Symbol,Function}
yscale::Union{Symbol,Function}
xscale::XS
yscale::YS
end

@inline x_pixel_per_char(::Type{BlockCanvas}) = 2
@inline y_pixel_per_char(::Type{BlockCanvas}) = 2
@inline x_pixel_per_char(::Type{C}) where {C<:BlockCanvas} = 2
@inline y_pixel_per_char(::Type{C}) where {C<:BlockCanvas} = 2

@inline lookup_encode(::BlockCanvas) = block_signs
@inline lookup_decode(::BlockCanvas) = block_decode

BlockCanvas(args...; kw...) =
CreateLookupCanvas(BlockCanvas, (0b0000, 0b1111), args...; kw...)
CreateLookupCanvas(BlockCanvas, UInt16, (0b0000, 0b1111), args...; kw...)

function char_point!(
c::BlockCanvas,
Expand Down
28 changes: 14 additions & 14 deletions src/canvas/braillecanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ the Braille symbols to represent individual pixel.
This effectively turns every character into 8 pixels
that can individually be manipulated using binary operations.
"""
struct BrailleCanvas <: Canvas
grid::Array{Char,2}
colors::Array{ColorType,2}
struct BrailleCanvas{XS<:Function,YS<:Function} <: Canvas
grid::Matrix{Char}
colors::Matrix{ColorType}
blend::Bool
visible::Bool
pixel_width::Int
Expand All @@ -22,8 +22,8 @@ struct BrailleCanvas <: Canvas
origin_y::Float64
width::Float64
height::Float64
xscale::Union{Symbol,Function}
yscale::Union{Symbol,Function}
xscale::XS
yscale::YS
end

@inline pixel_width(c::BrailleCanvas) = c.pixel_width
Expand All @@ -35,8 +35,8 @@ end
@inline nrows(c::BrailleCanvas) = size(c.grid, 2)
@inline ncols(c::BrailleCanvas) = size(c.grid, 1)

@inline x_pixel_per_char(::Type{BrailleCanvas}) = 2
@inline y_pixel_per_char(::Type{BrailleCanvas}) = 4
@inline x_pixel_per_char(::Type{C}) where {C<:BrailleCanvas} = 2
@inline y_pixel_per_char(::Type{C}) where {C<:BrailleCanvas} = 4

function BrailleCanvas(
char_width::Int,
Expand All @@ -47,17 +47,17 @@ function BrailleCanvas(
origin_y::Number = 0.0,
width::Number = 1.0,
height::Number = 1.0,
xscale::Union{Symbol,Function} = :identity,
yscale::Union{Symbol,Function} = :identity,
xscale::Function = identity,
yscale::Function = identity,
)
width > 0 || throw(ArgumentError("width has to be positive"))
height > 0 || throw(ArgumentError("height has to be positive"))
char_width = max(char_width, 5)
char_height = max(char_height, 2)
pixel_width = char_width * x_pixel_per_char(BrailleCanvas)
char_width = max(char_width, 5)
char_height = max(char_height, 2)
pixel_width = char_width * x_pixel_per_char(BrailleCanvas)
pixel_height = char_height * y_pixel_per_char(BrailleCanvas)
grid = fill(Char(BLANK_BRAILLE), char_width, char_height)
colors = fill(nothing, char_width, char_height)
grid = fill(Char(BLANK_BRAILLE), char_width, char_height)
colors = Array{ColorType}(nothing, char_width, char_height)
BrailleCanvas(
grid,
colors,
Expand Down
Loading

0 comments on commit dda54d5

Please sign in to comment.