Part 3 of 3: Plotly in React – Merge Multiple Selections using polygon-clipping

How to elegantly merge, or union, multiple polygonal selections in plotly

In this article…

In this article I will be showing you how to take the plotly chart you created in Part 2 and automatically merge your overlapping selections together.

What do you mean by merging selections?

Currently your plot’s selections overlap each other. If you draw a circle, and then draw another circle next to it with some overlap between them, you’ll create a sort of venn diagram. The two selections are still very much distinct. Merging the selections will cause the inner, overlapping lines to be removed so those two circles will instead become one polygon and will create more of a peanut shape.

Instead of having to crack open our geometry books to figure out how to perform a union on multiple polygons, we’ll use a small and clever library called polygon-clipping .

What you should know beforehand
  • Parts 1 and 2 of this 3 part series
  • Good understanding of Javascript
  • Basic understanding of Typescript (this app will be in Typescript!)
  • Intermediate understanding of React and React hooks

  • Steps in creating this merged selections data plot
  • Import polygon-clipping into our app
  • Create method getMergedPolygonsFromSelections
  • Retrieve paths from polygons
  • Convert paths to shapes
  • Pass shapes to Plot
  • Import polygon-clipping into our app

    In the Add Dependency text field, import the `polygon-clipping` package

    Create method getMergedPolygonsFromSelections

    Create a new file called getMergedPolygonsFromSelections and populate is as below:

    import { PlotSelectionState } from "./types";
    import polygonClipping from "polygon-clipping";
     
    export const getMergedPolygonsFromSelections = (selections: PlotSelectionState[]) => {
     const polys = selections.map((selection) => {
       const { lassoPoints, range } = selection;
       let poly: polygonClipping.Polygon = [[[0, 0]]];
     
       if (lassoPoints) {
         poly = [
           lassoPoints.x.map((lassoPointX, i) => [lassoPointX, lassoPoints.y[i]])
         ];
       } else if (range) {
         const { x, y } = range;
     
         // creating a box counterclockwise from lower left point
         poly = [
           [
             [x[0], y[0]],
             [x[1], y[0]],
             [x[1], y[1]],
             [x[0], y[1]],
             [x[0], y[0]]
           ]
         ];
       }
     
       return poly;
     });
     
     // union all the polygons together
     return polygonClipping.union(polys);
    };
     
    
    Explanation of code:

  • This function simply takes the selections and returns the union of ALL polygons inside.
  • Because a selection could be either a box or a lasso, we need two different methods for creating the coordinate array that polygon clipping expects.

  • Retrieve paths from polygons

    In your App.tsx, import the function you just created

    import { getMergedPolygonsFromSelections } from "./getMergedPolygonsFromSelections";
    Then in your main component, add the following code:
     const mergedPolygons = getMergedPolygonsFromSelections(allSelections);
     
     const paths = mergedPolygons.map((unionPolygon) => {
       let path = "";
       unionPolygon[0].forEach((pair, i) => {
         path += `${i === 0 ? "M" : "L"}${pair[0]},${pair[1]}`;
       });
       return path;
     });
    
    Explanation of changes:

  • We are passing in our selections to getMergedPolygonsFromSelections to retrieve the merged polygons and storing it in variable mergedPolygons.
  • We then iterate over each new polygon and use their coordinates to create an SVG line path for each merged polygon.
  • – Read more about SVG line paths here.

    Convert paths to shapes

    In App.tsx, import `Shape` from ‘plotly.js`:

    import { Shape } from "plotly.js";
    Then, add this code under the code you added in the previous step:
    const shapesToUse = paths.map(
       (path) =>
         ({
           type: "path",
           path,
           xref: "x",
           yref: "y"
         } as Partial<Shape>)
     );
    
    Explanation of code:

  • Given our new array of string paths, we create a Shape for each one.
  • We’ll be passing these shapes to the Plot in our final step

  • Pass shapes to Plot

    In Plot.layout, replace the value passed to the shapes prop with shapesToUse

             shapes: shapesToUse
    You’re selection shapes will now automatically be merged!

    Conclusion

    These three articles gave you a little taste of what plotly is capable of doing. While dealing almost exclusively with scatterplots, plotly provides histograms, 2D density plots, heatmaps, 3D line and surface plots, and much more. To discover all that plotly has to offer for the javascript library, go to https://plotly.com/javascript/.

    3-Part Series:

    • Part 1 of 3: Plotly in React – Real time data visualization
    • Part 2 of 3: Plotly in React – Display Multiple Selections with Annotations
    • Part 3 of 3: Part 3 of 3: Plotly in React – Merge Multiple Selections using polygon-clipping

    About Intertech

    Intertech is a Software Development Consulting Firm that provides single and multiple turnkey software development teams, available on your schedule and configured to achieve success as defined by your requirements independently or in co-development with your team. Intertech teams combine proven full-stack, DevOps, Agile-experienced lead consultants with Delivery Management, User Experience, Software Development, and QA experts in Business Process Automation (BPA), Microservices, Client- and Server-Side Web Frameworks of multiple technologies, Custom Portal and Dashboard development, Cloud Integration and Migration (Azure and AWS), and so much more. Each Intertech employee leads with the soft skills necessary to explain complex concepts to stakeholders and team members alike and makes your business more efficient, your data more valuable, and your team better. In addition, Intertech is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.