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

[WIP] Refactor Plot type & road to 1.0 #1085

Closed
wants to merge 14 commits into from
193 changes: 67 additions & 126 deletions GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,41 @@ function Base.insert!(screen::GLScreen, scene::Scene, @nospecialize(x::Combined)
end
end

function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Union{Scatter, MeshScatter}))
function Base.insert!(screen::GLScreen, scene::Scene, plot::AbstractPlot)
draw_atomic(screen, scene, plot)
end

function add_to_screen!(screen, scene, robj)
for key in (:pixel_space, :view, :projection, :eyeposition, :projectionview)
if !haskey(robj.uniforms, key)
robj[key] = getfield(scene.camera, key)
end
end
robj[:resolution] = map(x-> Vec2f0(widths(x)), scene.camera.pixel_area)
push!(screen, scene, robj)
return robj
end

function draw_atomic(screen::GLScreen, scene::Scene, plot::Scatter)
# signals not supported for shading yet
gl_attributes = Dict{Symbol, Any}()
gl_attributes[:color] = plot.color
gl_attributes[:scale] = plot.markersize
gl_attributes[:marker_offset] = plot.marker_offset
gl_attributes[:strokecolor] = plot.strokecolor
gl_attributes[:strokewidth] = plot.strokewidth
gl_attributes[:scale_primitive] = plot.transform_marker
gl_attributes[:billboard] = true
gl_attributes[:distancefield] = plot.distancefield
gl_attributes[:uv_offset_width] = Vec4f0(0)
gl_attributes[:use_pixel_marker] = lift(x-> x <: Pixel, plot.markerspace)
positions = apply_transform(plot.basics.transformation.transform_func, plot.position)
robj = visualize((plot[:marker], positions), Style(:default), gl_attributes)
add_to_screen!(screen, scene, robj)
return robj
end

function draw_atomic(screen::GLScreen, scene::Scene, x::MeshScatter)
robj = cached_robj!(screen, scene, x) do gl_attributes
# signals not supported for shading yet
gl_attributes[:shading] = to_value(get(gl_attributes, :shading, true))
Expand Down Expand Up @@ -213,142 +247,49 @@ function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Union{Scat
end
end

function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Lines))
robj = cached_robj!(screen, scene, x) do gl_attributes
linestyle = pop!(gl_attributes, :linestyle)
data = Dict{Symbol, Any}(gl_attributes)
ls = to_value(linestyle)
if isnothing(ls)
data[:pattern] = ls
else
linewidth = gl_attributes[:thickness]
data[:pattern] = ls .* (to_value(linewidth) * 0.25)
end
positions = handle_view(x[1], data)
positions = apply_transform(transform_func_obs(x), positions)
handle_intensities!(data)
visualize(positions, Style(:lines), data)
function draw_atomic(screen::GLScreen, scene::Scene, plot::Lines)
gl_attributes = Dict{Symbol, Any}()
linestyle = plot.linestyle
linewidth = plot.linewidth
@show linestyle[]
if isnothing(linestyle[])
gl_attributes[:pattern] = linestyle[]
else
gl_attributes[:pattern] = map((ls, lw)-> ls .* (lw * 0.25), linestyle, linewidth)
end
gl_attributes[:thickness] = linewidth
positions = apply_transform(plot.basics.transformation.transform_func, plot.position)
gl_attributes[:color] = el32convert(plot.color)
robj = visualize(positions, Style(:lines), gl_attributes)
add_to_screen!(screen, scene, robj)
return robj
end

function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::LineSegments))
robj = cached_robj!(screen, scene, x) do gl_attributes
linestyle = pop!(gl_attributes, :linestyle)
data = Dict{Symbol, Any}(gl_attributes)
ls = to_value(linestyle)
if isnothing(ls)
data[:pattern] = ls
else
linewidth = gl_attributes[:thickness]
data[:pattern] = ls .* (to_value(linewidth) * 0.25)
end
positions = handle_view(x.converted[1], data)
positions = apply_transform(transform_func_obs(x), positions)
if haskey(data, :color) && data[:color][] isa AbstractVector{<: Number}
c = pop!(data, :color)
data[:color] = el32convert(c)
else
delete!(data, :color_map)
delete!(data, :color_norm)
end
visualize(positions, Style(:linesegment), data)
function draw_atomic(screen::GLScreen, scene::Scene, plot::LineSegments)
gl_attributes = Dict{Symbol, Any}()
linestyle = plot.linestyle
linewidth = plot.linewidth
@show linestyle[]
if isnothing(linestyle[])
gl_attributes[:pattern] = linestyle[]
else
gl_attributes[:pattern] = map((ls, lw)-> ls .* (lw * 0.25), linestyle, linewidth)
end
gl_attributes[:thickness] = linewidth
positions = apply_transform(plot.basics.transformation.transform_func, plot.position)
gl_attributes[:color] = el32convert(plot.color)
robj = visualize(positions, Style(:linesegment), gl_attributes)
add_to_screen!(screen, scene, robj)
return robj
end

value_or_first(x::AbstractArray) = first(x)
value_or_first(x::StaticArray) = x
value_or_first(x) = x

function draw_atomic(screen::GLScreen, scene::Scene,
x::Text{<:Tuple{<:Union{<:Makie.GlyphCollection, <:AbstractVector{<:Makie.GlyphCollection}}}})
function draw_atomic(screen::GLScreen, scene::Scene, x::Text)

robj = cached_robj!(screen, scene, x) do gl_attributes
glyphcollection = x[1]


res = map(x->Vec2f0(widths(x)), pixelarea(scene))
projview = scene.camera.projectionview
transfunc = Makie.transform_func_obs(scene)
pos = gl_attributes[:position]
space = gl_attributes[:space]
offset = gl_attributes[:offset]

# TODO: This is a hack before we get better updating of plot objects and attributes going.
# Here we only update the glyphs when the glyphcollection changes, if it's a singular glyphcollection.
# The if statement will be compiled away depending on the parameter of Text.
# This means that updates of a text vector and a separate position vector will still not work if only the text
# vector is triggered, but basically all internal objects use the vector of tuples version, and that triggers
# both glyphcollection and position, so it still works
if glyphcollection[] isa Makie.GlyphCollection
# here we use the glyph collection observable directly
gcollection = glyphcollection
else
# and here we wrap it into another observable
# so it doesn't trigger dimension mismatches
# the actual, new value gets then taken in the below lift with to_value
gcollection = Observable(glyphcollection)
end
glyph_data = lift(pos, gcollection, space, projview, res, offset, transfunc) do pos, gc, args...
preprojected_glyph_arrays(pos, to_value(gc), args...)
end

# unpack values from the one signal:
positions, offset, uv_offset_width, scale = map((1, 2, 3, 4)) do i
lift(getindex, glyph_data, i)
end

atlas = get_texture_atlas()

filter!(gl_attributes) do (k, v)
# These are liftkeys without model
!(k in (
:position, :space, :font,
:textsize, :rotation, :justification
))
end

gl_attributes[:color] = lift(glyphcollection) do gc
if gc isa AbstractArray
reduce(vcat, (Makie.collect_vector(g.colors, length(g.glyphs)) for g in gc),
init = RGBAf0[])
else
Makie.collect_vector(gc.colors, length(gc.glyphs))
end
end
gl_attributes[:stroke_color] = lift(glyphcollection) do gc
if gc isa AbstractArray
reduce(vcat, (Makie.collect_vector(g.strokecolors, length(g.glyphs)) for g in gc),
init = RGBAf0[])
else
Makie.collect_vector(gc.strokecolors, length(gc.glyphs))
end
end

gl_attributes[:rotation] = lift(glyphcollection) do gc
if gc isa AbstractArray
reduce(vcat, (Makie.collect_vector(g.rotations, length(g.glyphs)) for g in gc),
init = Quaternionf0[])
else
Makie.collect_vector(gc.rotations, length(gc.glyphs))
end
end

gl_attributes[:scale] = scale
gl_attributes[:offset] = offset
gl_attributes[:uv_offset_width] = uv_offset_width
gl_attributes[:distancefield] = get_texture!(atlas)
gl_attributes[:visible] = x.visible
robj = visualize((DISTANCEFIELD, positions), Style(:default), gl_attributes)
# Draw text in screenspace
if x.space[] == :screen
robj[:view] = Observable(Mat4f0(I))
robj[:projection] = scene.camera.pixel_space
robj[:projectionview] = scene.camera.pixel_space
end

return robj
end
return robj
return nothing
end

# el32convert doesn't copy for array of Float32
Expand Down
10 changes: 9 additions & 1 deletion MakieCore/Project.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
authors = ["Simon Danisch"]
name = "MakieCore"
uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b"
authors = ["Simon Danisch"]
version = "0.1.3"

[deps]
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
FreeTypeAbstraction = "663a7486-cb36-511b-a19d-713bb74d65c9"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

[compat]
Observables = "0.4"
Expand Down
34 changes: 32 additions & 2 deletions MakieCore/src/MakieCore.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
module MakieCore

using Observables
using Observables: to_value
using Observables: to_value, AbstractObservable
using Base: RefValue
using GeometryBasics
using ColorTypes
using Parameters
using StaticArrays
using LinearAlgebra
using Random
using FreeTypeAbstraction
using IntervalSets

using GeometryBasics: VecTypes


"""
abstract type Transformable
This is a bit of a weird name, but all scenes and plots are transformable,
so that's what they all have in common. This might be better expressed as traits.
"""
abstract type Transformable end

abstract type AbstractPlot{T} <: Transformable end
abstract type ScenePlot{T} <: AbstractPlot{T} end
abstract type AbstractScene <: Transformable end
abstract type AbstractScreen <: AbstractDisplay end

const SceneLike = Union{AbstractScene, AbstractPlot}

include("geometry/quaternions.jl")
include("geometry/projection_math.jl")
include("types.jl")
include("attributes.jl")
include("recipes.jl")
include("basic_plots.jl")
include("basic_plots/abstractplot.jl")
include("basic_plots/scatter.jl")
include("basic_plots/lines.jl")
include("basic_plots/text.jl")
include("basic_plots/others.jl")
include("conversion.jl")

end
9 changes: 0 additions & 9 deletions MakieCore/src/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,6 @@ Base.getindex(plot::AbstractPlot, idx::UnitRange{<:Integer}) = plot.converted[id
Base.setindex!(plot::AbstractPlot, value, idx::Integer) = (plot.input_args[idx][] = value)
Base.length(plot::AbstractPlot) = length(plot.converted)

function Base.getindex(x::AbstractPlot, key::Symbol)
argnames = argument_names(typeof(x), length(x.converted))
idx = findfirst(isequal(key), argnames)
if idx === nothing
return x.attributes[key]
else
x.converted[idx]
end
end

function Base.getindex(x::AttributeOrPlot, key::Symbol, key2::Symbol, rest::Symbol...)
dict = to_value(x[key])
Expand Down
91 changes: 91 additions & 0 deletions MakieCore/src/basic_plots/abstractplot.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
function update!(plot::AbstractPlot, values::Dict{Symbol})
for (key, value) in values
setproperty_noupdate!(plot, key, value)
end
plot.basics.on_update[] = keys(values)
return
end

function setproperty_noupdate!(scatter::T, field::Symbol, value::Any) where T <: AbstractPlot
converted = convert_attribute(scatter, value, Key(field))
FT = fieldtype(T, field)
if !(converted isa FT)
error("Attribute $(field) couldn't be converted to required type. Converted type $(typeof(converted)). Required type: $(FT)")
end
setfield!(scatter, field, converted)
return converted
end

function Base.setproperty!(plot::T, field::Symbol, value::Any) where T <: AbstractPlot
setproperty_noupdate!(plot, field, value)
plot.basics.on_update[] = Set([field])
return
end

function Base.setproperty!(plot::T, field::Symbol, value::Observable) where T <: AbstractPlot
f(value) = setproperty!(plot, field, value)
on(f, value)
f(value[])
return
end

function on_update(f, plot::T, ::Key{field}) where {T <: AbstractPlot, field}
FT = fieldtype(T, field)
return on(plot.basics.on_update) do updated_fields
if field in updated_fields
f(plot[field]::FT)
end
return
end
end

function Base.getindex(plot::T, field::Symbol) where T <: AbstractPlot
return getfield(plot, field)
end

function Base.getproperty(plot::T, field::Symbol) where T <: AbstractPlot
field === :basics && return getfield(plot, :basics)
field === :parent && return getfield(plot, :parent)
return get!(plot.basics.observables, field) do
obs = Observable{fieldtype(T, field)}(plot[field])
on_update(plot, Key(field)) do new_value
obs[] = new_value
end
return obs
end
end

mutable struct PlotBasics
on_update::Observable{Set{Symbol}}
on_event::Observable{Tuple{Symbol,Any}}
defaults::Set{Symbol}
observables::Dict{Symbol, Observable}
camera::Camera
transformation::Transformation
end

function PlotBasics()
return PlotBasics(
Observable{Set{Symbol}}(),
Observable{Tuple{Symbol,Any}}(),
Set{Symbol}(),
Dict{Symbol, Observable}(),
Camera(),
Transformation(),
)
end

function plot!(P::Type{<:AbstractPlot}, scene::AbstractScene, args...; attributes...)
scatter = P(args...; attributes...)
plot!(scene, scatter)
return scatter
end

function plot!(P::PlotFunc, scene::SceneLike, args...; kw_attributes...)
attributes = Attributes(kw_attributes)
plot!(scene, P, attributes, args...)
end

function plot!(scene::SceneLike, scatter::AbstractPlot)
push!(scene.plots, scatter)
end
Loading