79686773

Date: 2025-07-02 03:06:26
Score: 2
Natty:
Report link

Plotly.js creates a global stylesheet that is used to show the tooltip (called in plotly.js "hover..." hoverbox, hoverlayer, hoverlabel), as well as for other features - for instance, you can see the "modelbar" (the icon menu that's by default at the top-right of the plot div) is misplaced in your shadow-dom version.

The issue is thus the fact that the global stylesheets are not applied to the shadow DOM. Based on the information from this Medium article by EisenbergEffect I applied the global stylesheets to the shadow root of your sankey-sd, using the function:

function addGlobalStylesToShadowRoot(shadowRoot) {
   const globalSheets = Array.from(document.styleSheets)
      .map(x => {
         const sheet = new CSSStyleSheet();
         const css = Array.from(x.cssRules).map(rule => rule.cssText).join(' ');
         sheet.replaceSync(css);
         return sheet;
      });

   shadowRoot.adoptedStyleSheets.push(
      ...globalSheets
   );
}

applied in the constructor of class SankeySD:

   class SankeySD extends HTMLElement {
      constructor() {
         super();
         this.attachShadow({ mode: 'open' });
         addGlobalStylesToShadowRoot(this.shadowRoot);
      }
      // ............... other methods
   }

and it did enable the tooltip and corrected the position of the modelbar.

Here's a stack snippet demo, based on your original code:

//from https://eisenbergeffect.medium.com/using-global-styles-in-shadow-dom-5b80e802e89d
function addGlobalStylesToShadowRoot(shadowRoot) {
   const globalSheets = Array.from(document.styleSheets)
      .map(x => {
         const sheet = new CSSStyleSheet();
         const css = Array.from(x.cssRules).map(rule => rule.cssText).join(' ');
         sheet.replaceSync(css);
         return sheet;
      });

   shadowRoot.adoptedStyleSheets.push(
      ...globalSheets
   );
}
window.addEventListener('DOMContentLoaded', () => {
   class SankeySD extends HTMLElement {
      constructor() {
         super();
         this.attachShadow({ mode: 'open' });
         addGlobalStylesToShadowRoot(this.shadowRoot);
      }
      connectedCallback() {
         const chartDiv = document.createElement('div');
         chartDiv.id = 'chart';
         chartDiv.style.width = '100%';
         chartDiv.style.height = '100%';
         chartDiv.style.minWidth = '500px';
         chartDiv.style.minHeight = '400px';
         this.shadowRoot.appendChild(chartDiv);

         const labels = ["Start", "Middle", "Begin", "End", "Final"];
         const labelIndex = new Map(labels.map((label, i) => [label, i]));
         const links = [
            { source: "Start", target: "Middle", value: 5, label: "Test" },
            { source: "Start", target: "Middle", value: 3, label: "Test2" },
            { source: "Middle", target: "Start", value: 1, label: "" },
            { source: "Start", target: "End", value: 2, label: "" },
            { source: "Begin", target: "Middle", value: 5, label: "Test" },
            { source: "Middle", target: "End", value: 3, label: "" },
            { source: "Final", target: "Final", value: 0.0001, label: "" }
         ];
         const sources = links.map(link => labelIndex.get(link.source));
         const targets = links.map(link => labelIndex.get(link.target));
         const values = links.map(link => link.value);

         const customData = links.map(link => [link.source, link.target, link.value]);

         const trace = {
            type: "sankey",
            orientation: "h",
            arrangement: "fixed",
            node: {
               label: labels,
               pad: 15,
               thickness: 20,
               line: { color: "black", width: 0.5 },
               hoverlabel: {
                  bgcolor: "white",
                  bordercolor: "darkgrey",
                  font: {
                     color: "black",
                     family: "Open Sans, Arial",
                     size: 14
                  }
               },
               hovertemplate: '%{label}<extra></extra>',
               color: ["#a6cee3", "#1f78b4", "#b2df8a", "#a9b1b9", "#a9b1b9" ]
            },
            link: {
               source: sources,
               target: targets,
               value: values,
               arrowlen: 20,
               pad: 20,
               thickness: 20,
               line: { color: "black", width: 0.2 },
               color: sources.map(i => ["#a6cee3", "#1f78b4", "#b2df8a", "#a9b1b9", "#a9b1b9"][i]),
               customdata: customData,
               hoverlabel: {
                  bgcolor: "white",
                  bordercolor: "darkgrey",
                  font: {
                     color: "black",
                     family: "Open Sans, Arial",
                     size: 14
                  }
               },
               hovertemplate:
                  '<b>%{customdata[0]}</b> → <b>%{customdata[1]}</b><br>' +
                  'Flow Value: <b>%{customdata[2]}</b><extra></extra>'
            }
         };

         const layout = {
            font: { size: 14 },
            //margin: { t: 20, l: 10, r: 10, b: 10 },
            //hovermode: 'closest'
         };

         Plotly.newPlot(chartDiv, [trace], layout, { responsive: true, displayModeBar: true })
            .then((plot) => {
               chartDiv.on('plotly_click', function(eventData) {
                  console.log(eventData);
                  if (!eventData || !eventData.points || !eventData.points.length) return;
                  const point = eventData.points[0];
                  if (typeof point.pointIndex === "number") {
                     const nodeLabel = point.label;
                     alert("Node clicked: " + nodeLabel + "\nNode index: " + point.pointIndex);
                     console.log("Node clicked:", point);
                  } else if (typeof point.pointNumber === "number") {
                     const linkIdx = point.pointNumber;
                     const linkData = customData[linkIdx];
                     alert(
                        "Link clicked: " +
                        linkData[0] + " → " + linkData[1] +
                        "\nValue: " + linkData[2] +
                        "\nLink index: " + linkIdx
                     );
                     console.log("Link clicked:", point);
                  } else {
                     console.log("Clicked background", point);
                  }
               });
            });
      }
   }
   customElements.define('sankey-sd', SankeySD);
});
html, body {
   height: 100%;
   margin: 0;
}
sankey-sd {
   display: block;
   width: 100vw;
   height: 100vh;
}
<sankey-sd></sankey-sd>
<script src="https://cdn.plot.ly/plotly-3.0.1.min.js" charset="utf-8"></script>
<!-- also works with v 2.30.1-->

The click feature is not caused by the shadow DOM; in this fiddle that uses the same plot configuration, but without the shadow DOM, the behaviour is the same - there's always a point.pointNumber and never point.pointIndex.

I can't find the code you have used, can you please show the version that works? In any case, this might be another question, as there should not be multiple issues per post, if their solutions are unrelated.

Reasons:
  • Blacklisted phrase (1): another question
  • Blacklisted phrase (0.5): medium.com
  • RegEx Blacklisted phrase (2.5): can you please show
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • High reputation (-1):
Posted by: kikon