Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to use vector path markers #979

Merged
merged 79 commits into from Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
1eea766
add bezierpaths
jkrumbiegel May 24, 2021
f72ad9a
add bbox stuff, cairomakie routines
jkrumbiegel Dec 12, 2021
2160765
add elliptical arc to bezier conversion
jkrumbiegel Dec 13, 2021
9f61615
fix .y and closepath
jkrumbiegel Dec 13, 2021
243bd80
try enabling BezierPaths in texture atlas
jkrumbiegel Dec 13, 2021
c921c23
fix bug
jkrumbiegel Dec 14, 2021
95c47bc
better bitmap size, adjust triangles
jkrumbiegel Dec 14, 2021
a6f810e
options for bezier from string
jkrumbiegel Dec 14, 2021
c5b378b
add some docs
jkrumbiegel Dec 14, 2021
7b38224
improve examples
jkrumbiegel Dec 15, 2021
df34f5d
enable any size of bezierpath, normalized
jkrumbiegel Jan 9, 2022
88ffc4d
Merge branch 'master' into jk/bezierpaths
ffreyer Jan 14, 2022
6d353b6
fix scaling and offset
ffreyer Jan 14, 2022
968825a
fix bbox
ffreyer Jan 15, 2022
14c88de
fix offsets
ffreyer Jan 15, 2022
df87ffc
add Vector markersizes & fix aspect ratio
ffreyer Jan 17, 2022
3cec1ae
use billboarded rotations for all Real inputs
ffreyer Jan 17, 2022
5da7a75
center within unit-square and bbox ellipticalarc
jkrumbiegel Jan 18, 2022
cecc28a
reassign markers in marker_map
jkrumbiegel Jan 26, 2022
459537d
Merge commit '406978c89ad8ae5fb7ac294bd2fd34745bd5844c' into jk/bezie…
jkrumbiegel Mar 27, 2022
ff0aec4
cairomakie fixed
jkrumbiegel Mar 27, 2022
d1a464a
fix GLMakie
jkrumbiegel Mar 27, 2022
5097c22
fix test syntax
jkrumbiegel Mar 27, 2022
5fd5295
fix array of mixed markers
jkrumbiegel Mar 29, 2022
9d1394c
replace all default markers
jkrumbiegel Mar 29, 2022
b38ef9f
add test for most markers
jkrumbiegel Mar 29, 2022
d785488
microadjust markers
jkrumbiegel Mar 29, 2022
3929866
adjust bezier marker size relative to chars
jkrumbiegel Mar 29, 2022
029f9b4
make square little bit smaller
jkrumbiegel Mar 29, 2022
4782e1d
add test for more complex marker shapes
jkrumbiegel Mar 30, 2022
80964d2
correct bbox fitting logic
jkrumbiegel Mar 30, 2022
f4e3eb0
add docs
jkrumbiegel Mar 30, 2022
5879f89
fix sentence
jkrumbiegel Mar 30, 2022
4bc3d83
make testsets easier to read
jkrumbiegel Mar 30, 2022
4d1a05b
remove only for 1.3 compat
jkrumbiegel Mar 30, 2022
4d7a4cc
Merge branch 'master' into jk/bezierpaths
SimonDanisch May 18, 2022
6cd4cf9
remove old scale_x/y/z code
SimonDanisch May 18, 2022
6d7ae41
add primitive shape for bezier for WGLMakie
SimonDanisch May 18, 2022
5a823ed
Merge branch 'master' into jk/bezierpaths
jkrumbiegel Aug 24, 2022
590a2a6
Merge branch 'master' into jk/bezierpaths
SimonDanisch Aug 24, 2022
658b6a3
replace `@cell`
jkrumbiegel Aug 25, 2022
ca4d45b
fix elliptical arc scaling and rotation bugs
jkrumbiegel Aug 26, 2022
af1f9eb
test squishing without ellipticalarc
jkrumbiegel Aug 27, 2022
9936885
Merge branch 'master' into jk/bezierpaths
SimonDanisch Aug 31, 2022
767417c
first step at fixing WGLMakie + remove string for marker
SimonDanisch Aug 31, 2022
b6f263d
remove erroneous line from merge
jkrumbiegel Sep 3, 2022
9898e24
refactor marker scaling
SimonDanisch Sep 5, 2022
0ea06e5
Merge branch 'jk/bezierpaths' of https://github.com/JuliaPlots/Makie.…
SimonDanisch Sep 5, 2022
4a02102
refactor scaling code
SimonDanisch Sep 7, 2022
9f4ccef
fix segfault on windows
SimonDanisch Sep 7, 2022
088e927
fix tests
SimonDanisch Sep 7, 2022
2e39aa3
fix CairoMakie
SimonDanisch Sep 7, 2022
ee441e1
Merge branch 'master' into jk/bezierpaths
SimonDanisch Sep 7, 2022
745f42d
fix image marker
SimonDanisch Sep 8, 2022
df43b7d
add tests for marker_offset + data space
SimonDanisch Sep 8, 2022
e0f9524
implement wglmakie single image marker
SimonDanisch Sep 8, 2022
95e45ca
Merge branch 'jk/bezierpaths' of https://github.com/JuliaPlots/Makie.…
SimonDanisch Sep 8, 2022
651f8e2
fix CairoMakie newline rendering
SimonDanisch Sep 8, 2022
2667a38
fix newline rendering and clarify what's going on
SimonDanisch Sep 8, 2022
d12abc0
implement polygon markers and remove more string markers
SimonDanisch Sep 8, 2022
6db70aa
clean up spritemarker conversion some more
SimonDanisch Sep 8, 2022
13e097d
forgot import
SimonDanisch Sep 8, 2022
c253374
fetch all PRs in for stale removal
SimonDanisch Sep 10, 2022
db577e2
use constructor
SimonDanisch Sep 10, 2022
9713c47
bring back precompiles
SimonDanisch Sep 10, 2022
bc8551c
fixes + cleanup
SimonDanisch Sep 10, 2022
53bdd90
fix indentation
SimonDanisch Sep 12, 2022
13c6c5e
fix spelling
SimonDanisch Sep 12, 2022
95ddaba
make default marker :circle, same as in palette
jkrumbiegel Sep 12, 2022
bec42b8
fix image marker orientation
SimonDanisch Sep 12, 2022
9fcb950
Merge branch 'jk/bezierpaths' of https://github.com/JuliaPlots/Makie.…
SimonDanisch Sep 12, 2022
45665c6
fix spaces
jkrumbiegel Sep 12, 2022
2cb11ad
update bezier path docs
jkrumbiegel Sep 12, 2022
84e86e1
add more info about markersize and marker types
jkrumbiegel Sep 12, 2022
5f0eea3
change to Circle marker for failing tests
jkrumbiegel Sep 13, 2022
08ae92d
remove stars test
jkrumbiegel Sep 13, 2022
94d47cc
increase default markersize from 9 to 12
jkrumbiegel Sep 13, 2022
c948da9
Merge branch 'master' into jk/bezierpaths
jkrumbiegel Sep 14, 2022
6c32efb
remove unused variables
SimonDanisch Sep 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions CairoMakie/src/primitives.jl
Expand Up @@ -213,6 +213,7 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Scatter)
isnan(pos) && return

Cairo.set_source_rgba(ctx, rgbatuple(col)...)

m = convert_attribute(marker, key"marker"(), key"scatter"())
Cairo.save(ctx)
if m isa Char
Expand Down Expand Up @@ -301,6 +302,46 @@ function draw_marker(ctx, marker::Rect, pos, scale, strokecolor, strokewidth, ma
Cairo.stroke(ctx)
end

function draw_marker(ctx, beziermarker::BezierPath, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
Cairo.save(ctx)

Cairo.translate(ctx, pos[1], pos[2])

# Cairo.rotate(ctx, to_2d_rotation(rotation))

Cairo.rotate(ctx, to_2d_rotation(rotation))
Cairo.scale(ctx, scale[1], -scale[2]) # flip y for cairo

draw_path(ctx, beziermarker)

Cairo.fill_preserve(ctx)

sc = to_color(strokecolor)

Cairo.set_source_rgba(ctx, rgbatuple(sc)...)
Cairo.set_line_width(ctx, Float64(strokewidth))
Cairo.stroke(ctx)

Cairo.restore(ctx)
end

draw_path(ctx, bp::BezierPath) = foreach(x -> path_command(ctx, x), bp.commands)
path_command(ctx, c::MoveTo) = Cairo.move_to(ctx, c.p...)
path_command(ctx, c::LineTo) = Cairo.line_to(ctx, c.p...)
path_command(ctx, c::CurveTo) = Cairo.curve_to(ctx, c.c1..., c.c2..., c.p...)
path_command(ctx, ::ClosePath) = Cairo.close_path(ctx)
function path_command(ctx, c::EllipticalArc)
Cairo.save(ctx)
Cairo.rotate(ctx, c.angle)
Cairo.scale(ctx, 1, c.r2 / c.r1)
if c.a2 > c.a1
Cairo.arc(ctx, c.c..., c.r1, c.a1, c.a2)
else
Cairo.arc_negative(ctx, c.c..., c.r1, c.a1, c.a2)
end
Cairo.restore(ctx)
end


################################################################################
# Text #
Expand Down
53 changes: 46 additions & 7 deletions GLMakie/src/GLVisualize/visualize/particles.jl
Expand Up @@ -11,7 +11,7 @@ the most sense for the datatype.
#3D primitives
const Primitives3D = Union{AbstractGeometry{3}, AbstractMesh}
#2D primitives AKA sprites, since they are shapes mapped onto a 2D rectangle
const Sprites = Union{AbstractGeometry{2}, Shape, Char, Type}
const Sprites = Union{AbstractGeometry{2}, Shape, Char, Type, Makie.BezierPath}
const AllPrimitives = Union{AbstractGeometry, Shape, Char, AbstractMesh}

using Makie: RectanglePacker
Expand Down Expand Up @@ -162,6 +162,7 @@ end
returns the Shape for the distancefield algorithm
"""
primitive_shape(::Char) = DISTANCEFIELD
primitive_shape(::BezierPath) = DISTANCEFIELD
primitive_shape(x::X) where {X} = primitive_shape(X)
primitive_shape(::Type{T}) where {T <: Circle} = CIRCLE
primitive_shape(::Type{T}) where {T <: Rect2} = RECTANGLE
Expand All @@ -180,18 +181,20 @@ Extracts the offset from a primitive.
primitive_offset(x, scale::Nothing) = Vec2f(0) # default offset
primitive_offset(x, scale) = const_lift(/, scale, -2f0) # default offset


"""
Extracts the uv offset and width from a primitive.
"""
primitive_uv_offset_width(c::Char) = glyph_uv_width!(c)
primitive_uv_offset_width(b::BezierPath) = glyph_uv_width!(b)
primitive_uv_offset_width(x) = Vec4f(0,0,1,1)

"""
Gets the texture atlas if primitive is a char.
"""
primitive_distancefield(x) = nothing
primitive_distancefield(::Char) = get_texture!(get_texture_atlas())
primitive_distancefield(::BezierPath) = get_texture!(get_texture_atlas())
primitive_distancefield(::Observable{BezierPath}) = get_texture!(get_texture_atlas())
primitive_distancefield(::Observable{Char}) = get_texture!(get_texture_atlas())

function _default(
Expand Down Expand Up @@ -276,13 +279,25 @@ combine_scales(scale, x::Nothing, y::Nothing, z::Nothing) = scale
combine_scales(s::Nothing, x, y, z::Nothing) = Vec2f.(x, y)
combine_scales(s::Nothing, x, y, z) = Vec3f.(x, y, z)

# Calculates the scaling factor from unpadded size -> padded size
# Here we assume the glyph to be representative of Makie.PIXELSIZE_IN_ATLAS[]
# regardless of its true size.
function char_scale_factor(char, font)
# uv * size(ta.data) / Makie.PIXELSIZE_IN_ATLAS[] is the padded glyph size
# normalized to the size the glyph was generated as.
ta = Makie.get_texture_atlas()
lbrt = glyph_uv_width!(ta, char, font)
width = Vec(lbrt[3] - lbrt[1], lbrt[4] - lbrt[2])
width * Vec2f(size(ta.data)) / Makie.PIXELSIZE_IN_ATLAS[]
uv_width = Vec(lbrt[3] - lbrt[1], lbrt[4] - lbrt[2])
full_pixel_size_in_atlas = uv_width * Vec2f(size(ta.data) .- 1)
full_pixel_size_in_atlas / Makie.PIXELSIZE_IN_ATLAS[]
end

# full_pad / unpadded_atlas_width
function bezierpath_pad_scale_factor(bp)
ta = Makie.get_texture_atlas()
lbrt = glyph_uv_width!(bp)
uv_width = Vec(lbrt[3] - lbrt[1], lbrt[4] - lbrt[2])
full_pixel_size_in_atlas = uv_width * Vec2f(size(ta.data) .- 1)
full_pad = 2f0 * Makie.GLYPH_PADDING[] # left + right pad
full_pad ./ (full_pixel_size_in_atlas .- full_pad)
end

# This works the same for x being widths and offsets
Expand All @@ -298,6 +313,14 @@ function rescale_glyph(str::String, font, xs::Vector)
map((char, x) -> x * char_scale_factor(char, font), collect(str), xs)
end

# padded_width = (unpadded_target_width + unpadded_target_width * pad_per_unit)
function rescale_bezierpath(bp::BezierPath, x)
x .* (1f0 .+ bezierpath_pad_scale_factor(bp)) .* widths(Makie.bbox(bp))
end
function offset_bezierpath(bp::BezierPath, offset)
bb = Makie.bbox(bp)
origin(bb) .+ offset .- 0.5f0 .* bezierpath_pad_scale_factor(bp) .* widths(bb)
end

"""
Main assemble functions for sprite particles.
Expand All @@ -323,6 +346,22 @@ function sprites(p, s, data)
# to offset.
data[:offset] = map(rescale_glyph, p[1], font, offset)
data[:scale] = map(rescale_glyph, p[1], font, scale)

elseif to_value(p[1]) isa BezierPath
scale = map(combine_scales,
pop!(data, :scale, Observable(nothing)),
pop!(data, :scale_x, Observable(nothing)),
pop!(data, :scale_y, Observable(nothing)),
pop!(data, :scale_z, Observable(nothing))
)
# TODO
# marker_offset should be Vec2f(0) by default for BezierPaths
# once that is the case we should switch to this offset
# offset = get(data, :offset, Observable(Vec2f(0)))
offset = Observable(Vec2f(0))
jkrumbiegel marked this conversation as resolved.
Show resolved Hide resolved

data[:offset] = map(offset_bezierpath, p[1], offset)
data[:scale] = map(rescale_bezierpath, p[1], scale)
end

@gen_defaults! data begin
Expand Down Expand Up @@ -353,7 +392,7 @@ function sprites(p, s, data)
stroke_width = 0f0
glow_width = 0f0
uv_offset_width = const_lift(primitive_uv_offset_width, p[1]) => GLBuffer

distancefield = primitive_distancefield(p[1]) => Texture
indices = const_lift(length, p[2]) => to_index_buffer
# rotation and billboard don't go along
Expand Down
81 changes: 81 additions & 0 deletions docs/examples/plotting_functions/scatter.md
Expand Up @@ -132,6 +132,87 @@ f
```
\end{examplefigure}

### Bezier path markers

You can also use bezier paths as markers.
A bezier path marker should fit into the square from -1 to 1 in x and y to be comparable in size to other default markers and to be correctly rendered by GLMakie, because here the path has to be rendered to a bitmap first .
jkrumbiegel marked this conversation as resolved.
Show resolved Hide resolved
In CairoMakie, paths are drawn as they are without an intermediate bitmap, so every size is possible.

A `BezierPath` contains a vector of path commands, these are `MoveTo`, `LineTo`, `CurveTo`, `EllipticalArc` and `ClosePath`.

Here is an example with a simple arrow that is centered on its tip, built from path elements.

\begin{examplefigure}{svg = true}
```julia
using CairoMakie
CairoMakie.activate!() # hide
Makie.inline!(true) # hide

arrow_path = BezierPath([
MoveTo(Point(0, 0)),
LineTo(Point(0.3, -0.3)),
LineTo(Point(0.15, -0.3)),
LineTo(Point(0.3, -1)),
LineTo(Point(0, -0.9)),
LineTo(Point(-0.3, -1)),
LineTo(Point(-0.15, -0.3)),
LineTo(Point(-0.3, -0.3)),
ClosePath()
])

scatter(1:5,
marker = arrow_path,
markersize = range(20, 50, length = 5),
rotations = range(0, 2pi, length = 6)[1:end-1],
)
```
\end{examplefigure}

Paths can have holes, just start a new subpath with `MoveTo` that is inside the main path.
The holes have to be in clockwise direction if the outside is in anti-clockwise direction, or vice versa.
For example, a circle with a square cut out can be made by one `EllipticalArc` that goes anticlockwise, and a square inside that goes clockwise:

\begin{examplefigure}{svg = true}
```julia
using CairoMakie
CairoMakie.activate!() # hide
Makie.inline!(true) # hide

circle_with_hole = BezierPath([
MoveTo(Point(1, 0)),
EllipticalArc(Point(0, 0), 1, 1, 0, 0, 2pi),
MoveTo(Point(0.5, 0.5)),
LineTo(Point(0.5, -0.5)),
LineTo(Point(-0.5, -0.5)),
LineTo(Point(-0.5, 0.5)),
ClosePath(),
])

scatter(1:5,
marker = circle_with_hole,
markersize = 30,
)
```
\end{examplefigure}

You can also create a bezier path from an [svg path specification string](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#path_commands).
You can automatically resize the path to the -1 to 1 square and flip the y-axis with the keywords `fit` and `yflip`.
Here's an example with an svg string that contains the bat symbol:

\begin{examplefigure}{svg = true}
```julia
using CairoMakie
CairoMakie.activate!() # hide
Makie.inline!(true) # hide

batsymbol_string = "M96.84 141.998c-4.947-23.457-20.359-32.211-25.862-13.887-11.822-22.963-37.961-16.135-22.041 6.289-3.005-1.295-5.872-2.682-8.538-4.191-8.646-5.318-15.259-11.314-19.774-17.586-3.237-5.07-4.994-10.541-4.994-16.229 0-19.774 21.115-36.758 50.861-43.694.446-.078.909-.154 1.372-.231-22.657 30.039 9.386 50.985 15.258 24.645l2.528-24.367 5.086 6.52H103.205l5.07-6.52 2.543 24.367c5.842 26.278 37.746 5.502 15.414-24.429 29.777 6.951 50.891 23.936 50.891 43.709 0 15.136-12.406 28.651-31.609 37.267 14.842-21.822-10.867-28.266-22.549-5.549-5.502-18.325-21.147-9.341-26.125 13.886z"

batsymbol = BezierPath(batsymbol_string, fit = true, flipy = true)

scatter(1:10, marker = batsymbol, markersize = 50, color = :black)
```
\end{examplefigure}

### Marker rotation

Markers can be rotated using the `rotations` attribute, which also allows to pass a vector.
Expand Down
4 changes: 4 additions & 0 deletions src/Makie.jl
Expand Up @@ -83,6 +83,7 @@ const NativeFont = FreeTypeAbstraction.FTFont

include("documentation/docstringextension.jl")
include("utilities/quaternions.jl")
include("bezier.jl")
include("interaction/PriorityObservable.jl")
include("types.jl")
include("utilities/utilities.jl")
Expand Down Expand Up @@ -160,6 +161,9 @@ include("interaction/inspector.jl")
include("documentation/documentation.jl")
include("display.jl")

# bezier paths
export BezierPath, MoveTo, LineTo, CurveTo, EllipticalArc, ClosePath

# help functions and supporting functions
export help, help_attributes, help_arguments

Expand Down