MakieOrg / Makie.jl

Interactive data visualizations and plotting in Julia
https://docs.makie.org/stable
MIT License
2.43k stars 312 forks source link

Color uniform per element #1369

Open MathieuMorlighem opened 3 years ago

MathieuMorlighem commented 3 years ago

Hello,

again... this is more a feature request but I generally use Mesh to plot fields of size "number of vertices" or "number of elements". It is easy to do the first one using Makie's Mesh function, but for the second one I had to create multiple polygons and call poly instead:

using GLMakie
import ColorSchemes.jet
triangles=[1 2 3;1 3 4]
x=[0. 1. 1. 0.]
y=[0. 0. 1. 1.]
ps = [Makie.GeometryBasics.Polygon([Point2(x[triangles[i,1]], y[triangles[i,1]]), Point2(x[triangles[i,2]], y[triangles[i,2]]), Point2(x[triangles[i,3]], y[triangles[i,3]])])
                             for i in 1:size(triangles,1)]
Makie.poly(ps, color = [1.;2.], colormap = jet)

Screen Shot 2021-10-06 at 4 07 20 PM

It works, but it would nice to have this done automatically by Mesh. I admit that I may be expecting too much of Mesh (e.g. to behave like MATLAB's patch function), with other features like edgecolor=true for example...

Thank you! Mathieu

ffreyer commented 3 years ago

I'm not sure what exactly you want, but you can generate a mesh fairly easily from that input:

using GeometryBasics
positions = Point2f.(x, y)[:] # colon to convert 1xN Array to Vector
faces = [GLTriangleFace(triangles[i, :]) for i in axes(triangles, 1)]
m = normal_mesh(positions, faces)

Though if you want per-face colors you'll have to seperate them. I.e. duplicate the vertices that are used by both faces

using GLMakie, GeometryBasics
import ColorSchemes.jet

triangles=[1 2 3;5 6 4]
x=[0. 1. 1. 0. 0. 1.]
y=[0. 0. 1. 1. 0. 1.]

positions = Point2f.(x, y)[:] # colon to convert 1xN Array to Vector
faces = [GLTriangleFace(triangles[i, :]) for i in axes(triangles, 1)]
m = normal_mesh(positions, faces)

mesh(m, color = [1,1,1, 2,2,2], colormap=jet, shading = false)
MathieuMorlighem commented 3 years ago

Hello @ffreyer, thanks a lot for your response, this is a much more elegant way to achieve the same result. But I guess I was expecting Makie's mesh to understand something like this:

using GLMakie
import ColorSchemes.jet
Makie.mesh( [0.0 0.0; 1.0 0.0; 1.0 1.0; 0.0 1.0;], [1 2 3;1 3 4], shading = false, elementcolor = [1;2], colormap=jet)
BambOoxX commented 2 years ago

@ffreyer Thanks for the proposal. It works indeed for triangular meshes, but seems to fail on quadrangular ones, any idea why ?

It seems like some point metadata is overwritten, but I do not understand why (maybe that's the issue). In fact, as the goal here is to plot one color per face / element, it does not make much sense to rely on vertex metadata as they may differ for the same vertex linked to multiple faces.

I do agree with @mmorligh, that an interface taking directly a vector of values per element would be useful.

Currently it seems to fail at line 456 of drawing_primitives (GLMakie v0.5.5)

    elseif to_value(color) isa AbstractVector{<: Union{Number, Colorant}}
        mesh = lift(mesh, color) do mesh, color
            return GeometryBasics.pointmeta(mesh, color=el32convert(color))
        end

Since a facemeta function also exists a quick fix might just be

    elseif to_value(color) isa AbstractVector{<: Union{Number, Colorant}}
        mesh = lift(mesh, color) do mesh, color
           if length(coordinates(mesh) == length(color)
                return GeometryBasics.pointmeta(mesh, color=el32convert(color))
           elseif length(faces(mesh)) == length(color)
                 return GeometryBasics.facemeta(mesh, color=el32convert(color))
        end

quadrangles =[1 2 3 4 ;5 6 4 1] x=[0. 1. 1. 0. 0. 1.] y=[0. 0. 1. 1. 0. 1.]

positions = Point2f.(x, y)[:] # colon to convert 1xN Array to Vector faces = [QuadFace(quadrangles[i, :]) for i in axes(quadrangles , 1)] m = normal_mesh(positions, faces)

mesh(m, color = [1,1,1,1,2, 2,2,2], colormap=jet, shading = false)

ffreyer commented 2 years ago

Vertices are basically a zip(positions, color) (and all other metadata). You have 6 positions and 8 colors here, which can't be combined into vertex data cleanly. That's what GeometryBasics is complaining about. You need unique indices for quads, i.e. [1 2 3 4; 5 6 7 8], with a position and color per for each index if you want to have per face colors.

Another way of handling this would be to use meshscatter. Something like

center = Point2f[(0, 0), (1.5, 0)]
sizes = Vec2f[(1, 1), (2, 1)]
face_colors = [:red, :blue]
meshscatter(centers, marker = Rect2f(-0.5, -0.5, 1, 1), markersize = sizes, color = face_colors, shading = false)
MathieuMorlighem commented 2 years ago

Thanks @ffreyer that makes sense, but for large meshes, it is a bit cumbersome to have to renumber the vertices every time one wants to plot...

BambOoxX commented 2 years ago

That's what GeometryBasics is complaining about. You need unique indices for quads, i.e. [1 2 3 4; 5 6 7 8], with a position and color per for each index if you want to have per face colors.

Hmmm so I guess I understand now

Though if you want per-face colors you'll have to seperate them. I.e. duplicate the vertices that are used by both faces

you mean that one needs to duplicate the actual points, not only the metadata).

As @mmorligh said this indeed feels cumbersome, do you think the facemeta could be leveraged to improve mesh ?

juliohm commented 2 years ago

We do this internally in MeshViz.jl if all you need is plot a field over elements or over vertices. Just pass a color vector of the right size and the package with handle things for you. Take a look at the test suite.

Em ter., 5 de abr. de 2022 04:53, BambOoxX @.***> escreveu:

That's what GeometryBasics is complaining about. You need unique indices for quads, i.e. [1 2 3 4; 5 6 7 8], with a position and color per for each index if you want to have per face colors.

Hmmm so I guess I understand now

Though if you want per-face colors you'll have to seperate them. I.e. duplicate the vertices that are used by both faces

you mean that one needs to duplicate the actual points, not only the metadata).

As @mmorligh https://github.com/mmorligh said this indeed feels cumbersome, do you think the facemeta could be leveraged to improve mesh ?

— Reply to this email directly, view it on GitHub https://github.com/JuliaPlots/Makie.jl/issues/1369#issuecomment-1088379782, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAZQW3L67VN3A2PHFPP5HHDVDPWQRANCNFSM5FPSYFOQ . You are receiving this because you are subscribed to this thread.Message ID: @.***>

BambOoxX commented 2 years ago

@juliohm Unfortunately, for my part, I can't rely on MeshViz, I have other stuff to plot in addition to the mesh

juliohm commented 2 years ago

Can you elaborate on why this inhibits the usage of MeshViz.jl? Do you understand that MeshViz.jl are Makie recipes right? You can use any Makie command after the plot of the mesh.

Em ter., 5 de abr. de 2022 05:54, BambOoxX @.***> escreveu:

@juliohm https://github.com/juliohm Unfortunately, for my part, I can't rely on MeshViz, I have other stuff to plot in addition to the mesh

— Reply to this email directly, view it on GitHub https://github.com/JuliaPlots/Makie.jl/issues/1369#issuecomment-1088443007, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAZQW3JTTOJF6PV2PJWNBETVDP5UXANCNFSM5FPSYFOQ . You are receiving this because you were mentioned.Message ID: @.***>

BambOoxX commented 2 years ago

At the moment, it's just that all of my plotting functions use on the GeometryBasics.Mesh type which doe not seem to be compatible with viz....

BambOoxX commented 2 years ago

@ffreyer Regarding your advice to use a meshscatter, it does not seem to work with arbitrary quadrangular shapes

ffreyer commented 2 years ago

You can make it work as long as the meshes/quads you scatter are just translation, rotation and scaling away from each other. So two quads like

quad1 = Point2f[(0, 0), (0, 1), (1, 1), (1, 0)]
quad2 = Point2f[(0, 0), (0, -1), (-1, -2), (-1, -1)]

wouldn't work in a single meshscatter. But if you want to scattering just quad2 works, you just have to make a mesh out of it first.

I mostly mentioned this because I recently used meshscatter to build a heatmap where row lengths were not uniform.