plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
16.92k stars 1.86k forks source link

Add 2D triangulation plot #4509

Open ndem0 opened 4 years ago

ndem0 commented 4 years ago

It would be very useful for me adding a new chart type to deal with 2D triangulation (or mesh).

At the current implementation, I found different workarounds but with some limitations:

I apologize in advance if this feature is already implemented and I missed it.

dicengine commented 3 years ago

I realize this isn't exactly a triangulation (or mesh) plot, but the contour trace type can be used for scattered data in 2D much in the same way as I think you intend to. Example: https://stackoverflow.com/questions/24313761/d3-js-3d-array-interpolation The data does not have to be in grid format. The documentation states that the x and y parameters of a contour plot "set the x (or y) coordinates" which is pretty ambiguous, but I've verified that you can supply scattered point coordinates. The z parameter is your scalar field you'd like to render.

ndem0 commented 1 year ago

I revive this issue after 3 years since it's actually (at least to the best of my knowledge) an open problem. The scatter solution worked great so far, but we start to face several complications by applying too many (more than 100) points. The interpolation that needs to be done requires a non-negligible amount of CPU time, making the entire page frozen at any time.

In most cases, we do not want really to interpolate since we already know the "intensity/color" for all the triangles, so I'm wondering:

  1. if something similar to what I'm looking for is already implemented (during these 3 years)
  2. if not, what would be a starting point to create the new component? It would be really similar to Mesh3d but only for 2D meshes (so without rotation effects, lights management, etc)
ndem0 commented 1 year ago

Any suggestion?

alexcjohnson commented 1 year ago

I think the right solution here is to rewrite how the contour trace handles "triplet" data - ie x/y/z all 1D arrays so that you end up with a collection of (x,y,z) triplets rather than a grid of 2D z vs 1D x and y. Currently we take that input, smoosh it into a grid and then interpolate, but as described in #3166 that doesn't scale well at all. See also #3503 where we discussed applying this to ternary phase diagrams.

As described in those issues, we would first do Delaunay triangulation from the (x,y) points, then create a "marching triangles" algorithm, like the "marching squares" algorithm the gridded contour plots use but at least it should be simpler since there are many fewer cases to consider, and then we can plug this into the plotting routines we already have for contour plots.

It's a big project, and not something that's on our internal roadmap right now unless someone is interested in sponsoring it, but we'd be happy to support if anyone is interested in working toward a PR.

mursalfk commented 11 months ago

Hello everyone. We had been working on this issue by ourselves, and me, along with @ndem0 have been able to generate a 2D Triangulation Mesh (sort of) using the Scatter Plot (go.Scatter). First we tried to create simple triangles by manipulating the data (just a little bit, that too without changing the outcomes) to generate exact triangles over the scatter in 2D that we were getting as a mesh.

As seen in the code shared below, in 'code_block_1' we are able to get out 2D Visualization. But the Problem here is that it's response time is a little bit slow than that of Real-Time. We found out that this is because of the for loop that is generating several traces of triangles. Well, that was the first solution that we created, but at the same time, we created another problem :laughing:

# code_block_1

def create_2d_triangluation(vertices_list, color):
    data = []
    color = color/color.max()
    for i, (vertices, color_) in enumerate(zip(vertices_list, color)):
        r, g, b, a = cm.viridis(color[i]) 
        color_ = f'rgb({int(r * 255)}, {int(g * 255)}, {int(b * 255)})'
        centroid_x = np.mean(vertices[0])
        centroid_y = np.mean(vertices[1])
        trace = go.Scatter(
            x=vertices[0],
            y=vertices[1],
            mode='lines',
            fill="toself",
            line=dict(
                color='white',
                width=0
            ),
            fillcolor=color_,
            text=f'Triangle {i+1}<br>X: {centroid_x:.2f}<br>Y: {centroid_y:.2f}',
            hoverinfo='text'
        )
        data.append(trace)
    layout = go.Layout(showlegend=True)
    return {'data': data, 'layout': layout}

After encountering this problem, we tried to use another approach, that was, by simply using the lines of the scatter plot. Now it is amazingly fast, even faster than that of 3D Mesh.

As seen below, in the 'code_block_2', we have eliminated the usage of for loop, that is making the entire process really fast. But again, there's a Problem that we are facing. We are unable to provide coloring between the lines, to show the exact colors of the plot.

Yes, we can, and we have provided colors to the markers inside the plot, but they are not as good and as we want them to be.

#code_block_2

def create_2d_triangluation(vertices_list, color, size=1, marker=1):
    color_normalized = color / color.max()
    colors_rgb = (cm.viridis(color_normalized)[:, :3] * 255).astype(int)

    # Separating the vertices in x and y
    vertices_list_x = vertices_list[:, 0, :]
    vertices_list_y = vertices_list[:, 1, :]

    # Adding None to separate the triangles
    list_of_none = np.array([None] * len(vertices_list_x)).reshape(-1, 1)
    list_of_zero = np.array([0] * len(vertices_list_x)).reshape(-1, 1)

    # Adding None to separate the triangles
    vertices_list_x = np.hstack((vertices_list_x, vertices_list_x[:,0].reshape(-1, 1), list_of_none))
    vertices_list_y = np.hstack((vertices_list_y, vertices_list_y[:,0].reshape(-1, 1), list_of_none))
    # Adding 0 to separate the triangles colors
    colors_rgb = np.hstack((colors_rgb, colors_rgb[:,0].reshape(-1, 1), list_of_zero))

    # Flattening the arrays
    vertices_list_x = vertices_list_x.flatten()
    vertices_list_y = vertices_list_y.flatten()
    colors_rgb = colors_rgb.flatten()

    data = []

    trace = go.Scatter(
        x=vertices_list_x,
        y=vertices_list_y,
        mode='lines+markers',
        line=dict(
            color='blue',
            width=size,
        ),
        marker=dict(
            size=marker,
            color=colors_rgb,
            colorscale='Viridis',
            showscale=False,
            colorbar=dict(
                thickness=10,
                ypad=2,
                xpad=0,
                len=0.8,
                lenmode='fraction',
            )
        ),
        fill='toself',
        fillcolor='white',
        text=None,
        hoverinfo=None
    )

    data.append(trace)

    layout = go.Layout(showlegend=True)
    return {'data': data, 'layout': layout}

HELP NEEDED: We are looking for some guideline, or some help, so that we can add colors in-between the lines. If anyone has any idea as to where and in which component we can make some changes, to get our required results, that would be really helpful. Either on traces, or using the JavaScript manipulation, or anything else, would really help us a lot.

Thank You

kerrykey commented 8 months ago

Hi, I tried versions of these in julia and ran into the same slowness problem with option 1 when there are more than about 10k triangles (and thus more than 10k traces). I also tried using mesh3d(); it works great with respect to speed and ease of use (test up to 1M triangles), but it's super clunky to then try to force it to look 2D by setting the z axis aspect ratio to a negligible value, turning off z tick values etc. And then the zoom is a still a clunky 3D zoom with rotations rather than just +/- and panning. Would be awesome if Plotly released a mesh2d() function that is the 2D equivalent of mesh3d().