0

I have a Plotly graph with multiple traces which includes line as well as bar plots. If 2 traces have the same value at a point, the tooltip displays only the value of a single trace. I attempted to add custom tooltip for my traces, but it only displays a tooltip for a single trace, even when multiple traces have the same value. I also tried using the Plotly hover event, but it doesn't seem to work as expected; it gets overridden by the default tooltip.

const trace: any = [
  {
    x: this.data[this.group],
    y: this.data.Price,
    type: 'scatter',
    mode: 'lines+markers',
    name: 'Grid Price',
    line: { color: '#64323A', width: 3, shape: 'spline' },
    marker: { size: 7 },
    yaxis: 'y2',
    hovertemplate: '%{x}<br>Grid Price: %{customdata}<extra></extra>',
    customdata: this.data.Price.map((val: number) =>
      Number.isInteger(val) ? val.toFixed(0) : val.toFixed(3)) 
  },
  {
    x: this.data[this.group],
    y: this.data.Price.map(() => this.simulatorRun?.grid_used_to_firm_h2_output ? this.simulatorRun?.load_gain_price : 0),
    type: 'scatter',
    mode: 'lines+markers',
    name: 'Load Gain Price',
    line: { color: '#182D44', width: 3, shape: 'spline' },
    marker: { size: 7 },
    yaxis: 'y2',
    hovertemplate: '%{x}<br>Load Gain Price: %{customdata}<extra></extra>',
    customdata: this.data.Price.map(() =>
      this.simulatorRun?.grid_used_to_firm_h2_output ? this.simulatorRun?.load_gain_price : 0).map((val: string) => {
      const numVal = parseFloat(val); /// Convert string to a number
      return Number.isInteger(numVal) ? numVal.toFixed(0) : numVal.toFixed(3);
    })
  },
  {
    x: this.data[this.group],
    y: this.data.Price.map(() => this.simulatorRun?.grid_used_to_firm_h2_output ? this.simulatorRun?.load_shed_price : 0),
    type: 'scatter',
    mode: 'lines+markers',
    name: 'Load Shed Price',
    line: { color: '#273D1B', width: 3, shape: 'spline' },
    marker: { size: 7 },
    yaxis: 'y2',
    hovertemplate: '%{x}<br>Load Shed Price: %{customdata}<extra></extra>',
    customdata: this.data.Price.map(() =>
      this.simulatorRun?.grid_used_to_firm_h2_output ? this.simulatorRun?.load_shed_price : 0).map((val: string) => {
      const numVal = parseFloat(val); /// Convert string to a number
      return Number.isInteger(numVal) ? numVal.toFixed(0) : numVal.toFixed(3);
    })
  },
  {
    x: this.data[this.group],
    y: this.data.Price.map(() => this.simulatorRun?.sell_electricity ? this.simulatorRun?.electricity_price : 0),
    type: 'scatter',
    mode: 'lines+markers',
    name: 'Sell Electricity Price',
    line: { color: '#B38C3A', width: 3, shape: 'spline' },
    marker: { size: 7 },
    yaxis: 'y2',
    hovertemplate: '%{x}<br>Sell Electricity Price: %{customdata}<extra></extra>',
    customdata: this.data.Price.map(() => 
      this.simulatorRun?.sell_electricity ? this.simulatorRun?.electricity_price : 0).map((val: number) => 
        Number.isInteger(val) ? val.toFixed(0) : val.toFixed(3))
  },
  {
    x: this.data[this.group],
    y: this.data["Grid Withdrawals (MW)"],
    type: 'bar',
    name: 'Grid',
    marker: { color: '#046CC4' },
    yaxis: 'y1',
    hovertemplate: '%{x}<br>Grid: %{customdata}<extra></extra>',
    customdata: this.data["Grid Withdrawals (MW)"].map((val: number) => 
      Number.isInteger(val) ? val.toFixed(0) : val.toFixed(3))
  },
  {
    x: this.data[this.group],
    y: this.data["Grid Exports (MW)"],
    type: 'bar',
    name: 'Export',
    marker: { color: '#388454' },
    yaxis: 'y1',
    hovertemplate: '%{x}<br>Export: %{customdata}<extra></extra>',
    customdata: this.data["Grid Exports (MW)"].map((val: number) => 
      Number.isInteger(val) ? val.toFixed(0) : val.toFixed(3))
  },
];

/// Configure layout and responsive options
const commonTickfontSize = this.group === '48h' || this.group === 'Day' ? 12 : 14;  /// Adjust font size based on the group
const tickAngle = this.group === '48h' ? -30 : this.group === 'Day' ? -45 : 0;  /// Rotate for  48h and Day
const legendYPosition = this.group === '48h' ? -0.3 : this.group === 'Day' ? -0.55 : -0.2;  /// Adjust the legend position based on group

const layout: Partial<Plotly.Layout> = {
  hovermode: 'closest',
  title: {
    text: 'Grid Usage/Grid Price vs Time',
    font: { size: 14 },
    x: 0.5,
    xanchor: 'center',
  },
  xaxis: {
    tickvals: this.data[this.group],
    ticktext: this.data[this.group].map((val: any) => this.utilsService.getDateTextForChart(val, this.group)),
    tickfont: { size: commonTickfontSize, color: '#000000' },
    showgrid: true,
    tickangle: tickAngle,
  },
  yaxis: {
    title: {
      text: 'Operating Points (MW)',
      font: { size: 16, color: '#000000' },
      standoff: 30, /// Adds space between the title and the axis
    },
    tickfont: { size: 14, color: '#000000' },
    showgrid: false,
    rangemode: 'tozero'
  },
  yaxis2: {
    tickfont: { size: 14, color: '#000000' },
    overlaying: 'y',
    side: 'right',
    showgrid: false,
    rangemode: 'tozero'
  },
  annotations: [ ///reverse yaxis2 title
    {
      xref: 'paper',
      yref: 'paper',
      x: 1.05,
      y: 0.7,
      text: 'Grid Price ($/MWh)',
      showarrow: false,
      font: { size: 16 },
      textangle: '90' as any, /// Rotate the text
    }
  ],
  legend: {
    orientation: 'h',
    font: { size: 14, color: '#000000' },
    x: 0.5,
    y: legendYPosition,
    xanchor: 'center',
    yanchor: 'bottom',
  },
  dragmode: undefined, /// Disable dragging for zoom/pan
  barmode: 'stack',
  margin: { t: 50, b: 50, l: 50, r: 70 },
  bargap: 0.7,
  plot_bgcolor: '#F8F8F8',
  paper_bgcolor: '#F8F8F8',
};

const config: Partial<Plotly.Config> = {
  responsive: true,
  displayModeBar: true,
  modeBarButtonsToRemove: ['select2d', 'lasso2d', 'autoScale2d', 'zoomIn2d', 'zoomOut2d'] as Plotly.ModeBarDefaultButtons[], /// Disable box and lasso select, autoscale, zoom in, zoom out buttons
  toImageButtonOptions: {
    format: 'png',
    filename: 'Grid Usage/Grid Price vs Time',
    height: 500,
    width: 1300,
    scale: 1,
  },
  displaylogo: false,
};

Plotly.react(this.plot, trace, layout, config);
this.plotInited.emit({ plot: this.plot });

/// Event listeners for zoom and pan
this.plot.on('plotly_relayout', (eventData: any) => {
  if (eventData['xaxis.range[0]'] || eventData['xaxis.range[1]']) {
    this.plotZoomed.emit({ event: eventData });
  }
});

this.plot.on('plotly_click', (eventData: any) => {
  this.plotClicked.emit({ event: eventData, item: eventData.points });
});    

this.plot.on('plotly_hover', (eventData: any) => {
  const annotations: Partial<Plotly.Annotations>[] = [];

  eventData.points.forEach((point: any) => {
    const xVal = point.x;
    const yVal = point.y;
    const traceName = point.data.name;

    // Find traces with the same x and y values
    const tracesWithSameValue = eventData.points.filter(
      (p: any) => p.x === xVal && p.y === yVal
    );

    if (tracesWithSameValue.length > 1) {
      const tooltipContent = tracesWithSameValue
        .map(
          (trace: any) =>
            `${trace.data.name}: ${trace.y.toFixed(3)}`
        )
        .join('<br>');

      // Create a custom annotation
      annotations.push({
        x: xVal,
        y: yVal,
        text: tooltipContent,
        showarrow: false,
        font: { size: 12 },
        align: 'left' as 'left',
        bgcolor: 'rgba(255,255,255,0.8)',
        bordercolor: '#ccc',
        borderwidth: 1,
      });
    }
  });

  // Update the layout with custom annotations
  Plotly.relayout('chart', { annotations });
});

// Remove annotations on unhover
this.plot.on('plotly_unhover', () => {
  Plotly.relayout('chart', { annotations: [] });
});

}

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.