SciChart.js JavaScript 2D Charts API > ChartModifier API > CursorModifier (Crosshair + Tooltips) > Active Legends - CursorModifier output into a legend
Active Legends - CursorModifier output into a legend

Background reading:

If you haven't already, read the article The CursorModifier Type which will show you how to setup a CursorModifier with default options for tooltips. This article goes into further detail on customising the tooltip items (formatting, text content)

CursorModifier tooltipLegendTemplates

The CursorModifier supports a tooltipLegendTemplate property which allows you to specify a function to transform CursorModifier content into a legend which can be placed in the top left of the chart. This active legend updates with series values as you move the mouse.

Perhaps the best example of this is in financial charts, which require placing a legend in the top left of the chart to show the current hovered candle or series.

Here's a worked example below, which extends our Candlestick Chart - Volume Bars example with an active legend provided by CursorModifier.

// Add a CursorModifier with active legend to the chart
const cursorModifier = new CursorModifier({
  // X,Y offset in pixels for the active legend
  tooltipLegendOffsetX: 5,
  tooltipLegendOffsetY: 5,
  // Callback to format the legend
  tooltipLegendTemplate: (seriesInfos, svgAnnotation) => {
    let outputSvgString = "";

    // Foreach series there will be a seriesInfo supplied by SciChart. This contains info about the series under the mouse
    seriesInfos.forEach((seriesInfo, index) => {
      const y = 20 + index * 20;
      // use the series.stroke for the text color. If the series.stroke is transparent, use white
      let textColor = seriesInfo.stroke;
      if (textColor === undefined || parseColorToTArgb(textColor).opacity === 0) {
        textColor = "#ffffff";
      }
      // Default handling for Xy series
      let legendText = seriesInfo.formattedYValue;
      // Special handling for Ohlc series
      if (seriesInfo.dataSeriesType === EDataSeriesType.Ohlc) {
        legendText = `Open=${seriesInfo.formattedOpenValue} High=${seriesInfo.formattedHighValue} ` +
          `Low=${seriesInfo.formattedLowValue} Close=${seriesInfo.formattedCloseValue}`;

      }
      // Output one block of text per seriesInfo on the chart. Using seriesName (from dataSeries.dataSeriesName) as a prefix
      outputSvgString += `<text x="8" y="${y}" font-size="13" font-family="Verdana" fill="${textColor}">
          ${seriesInfo.seriesName}: ${legendText}
      </text>`;
    });

    return `<svg width="100%" height="100%">
              ${outputSvgString}
          </svg>`;
  }
});
sciChartSurface.chartModifiers.add(cursorModifier);

This results in the following output:

<div id="scichart-root" ></div>
  
body { margin: 0; }
#scichart-root { width: 100%; height: 100vh; }
  
// Helper class to fetch candlestick data from Binance via Rest API
const getCandles = async (
  symbol,
  interval,
  limit = 300
) => {
  let url = `https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}`;
  if (limit) {
    url += `&limit=${limit}`;
  }
  try {
    console.log(`SimpleBinanceClient: Fetching ${limit} candles of ${symbol} ${interval}`);
    const response = await fetch(url);
    // Returned data format is [ { date, open, high, low, close, volume }, ... ]
    const data = await response.json();
    // Map to { dateValues[], openValues[], highValues[], lowValues[], closeValues[] } expected by scichart.js
    const dateValues = [];
    const openValues = [];
    const highValues = [];
    const lowValues = [];
    const closeValues = [];
    const volumeValues = [];
    data.forEach(candle => {
      const [timestamp, open, high, low, close, volume] = candle;
      dateValues.push(timestamp / 1000); // SciChart expects Unix Timestamp / 1000
      openValues.push(parseFloat(open));
      highValues.push(parseFloat(high));
      lowValues.push(parseFloat(low));
      closeValues.push(parseFloat(close));
      volumeValues.push(parseFloat(volume));
    });
    return { dateValues, openValues, highValues, lowValues, closeValues, volumeValues };
  } catch (err) {
    console.error(err);
    return [];
  }
};

async function cursorModifierActiveLegendsOnCandles(divElementId) {
  //
  const {
    SciChartSurface,
    CategoryAxis,
    NumericAxis,
    FastCandlestickRenderableSeries,
    OhlcDataSeries,
    SciChartJsNavyTheme,
    MouseWheelZoomModifier,
    ZoomPanModifier,
    ZoomExtentsModifier,
    EDataSeriesType,
    FastColumnRenderableSeries,
    CursorModifier,
    NumberRange,
    XyDataSeries,
    parseColorToTArgb,
    FastLineRenderableSeries,
    XyMovingAverageFilter
  } = SciChart;

  // or, for npm, import { SciChartSurface, ... } from "scichart"

  const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId, {
    theme: new SciChartJsNavyTheme()
  });
  sciChartSurface.xAxes.add(new CategoryAxis(wasmContext));
  sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { labelPrefix: "$", labelPrecision: 2 }));

  // Add a secondary axis for the volume bars
  sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { id: "VolumeAxisId", isVisible: false, growBy: new NumberRange(0, 4) }));

  // Data format is { dateValues[], openValues[], highValues[], lowValues[], closeValues[] }
  const { dateValues, openValues, highValues, lowValues, closeValues, volumeValues }
    = await getCandles("BTCUSDT", "1h", 100);

  //Add the Candlestick series
  const candlestickSeries = new FastCandlestickRenderableSeries(wasmContext, {
    dataSeries: new OhlcDataSeries(wasmContext, {
      xValues: dateValues,
      openValues,
      highValues,
      lowValues,
      closeValues,
      dataSeriesName: "BTC/USDT"
    }),
    strokeThickness: 1,
    dataPointWidth: 0.7,
    brushUp: "#33ff3377",
    brushDown: "#ff333377",
    strokeUp: "#77ff77",
    strokeDown: "#ff7777",
  });
  sciChartSurface.renderableSeries.add(candlestickSeries);

  // Add some moving averages using SciChart's filters/transforms API
  // when candleDataSeries updates, XyMovingAverageFilter automatically recomputes
  sciChartSurface.renderableSeries.add(
    new FastLineRenderableSeries(wasmContext, {
      dataSeries: new XyMovingAverageFilter(candlestickSeries.dataSeries, {
        dataSeriesName: "Moving Average (20)",
        length: 20
      }),
    })
  );

  sciChartSurface.renderableSeries.add(
    new FastLineRenderableSeries(wasmContext, {
      dataSeries: new XyMovingAverageFilter(candlestickSeries.dataSeries, {
        dataSeriesName: "Moving Average (50)",
        length: 50
      }),
    })
  );

  // Add a column series to render the volume bars
  sciChartSurface.renderableSeries.add(new FastColumnRenderableSeries(wasmContext, {
    dataSeries: new XyDataSeries(wasmContext, { xValues: dateValues, yValues: volumeValues, dataSeriesName: "Volume" }),
    yAxisId: "VolumeAxisId",
    strokeThickness: 0,
    dataPointWidth: 0.7,
    opacity: 0.47,
  }));

  // add interactivity for the example
  sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier({ excludedYAxisIds: ["VolumeAxisId"]}));
  sciChartSurface.chartModifiers.add(new ZoomPanModifier({ excludedYAxisIds: ["VolumeAxisId"]}));
  sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());

  // #region ExampleA
  // Add a CursorModifier with active legend to the chart
  const cursorModifier = new CursorModifier({
    // X,Y offset in pixels for the active legend
    tooltipLegendOffsetX: 5,
    tooltipLegendOffsetY: 5,
    // Callback to format the legend
    tooltipLegendTemplate: (seriesInfos, svgAnnotation) => {
      let outputSvgString = "";

      // Foreach series there will be a seriesInfo supplied by SciChart. This contains info about the series under the mouse
      seriesInfos.forEach((seriesInfo, index) => {
        const y = 20 + index * 20;
        // use the series.stroke for the text color. If the series.stroke is transparent, use white
        let textColor = seriesInfo.stroke;
        if (textColor === undefined || parseColorToTArgb(textColor).opacity === 0) {
          textColor = "#ffffff";
        }
        // Default handling for Xy series
        let legendText = seriesInfo.formattedYValue;
        // Special handling for Ohlc series
        if (seriesInfo.dataSeriesType === EDataSeriesType.Ohlc) {
          legendText = `Open=${seriesInfo.formattedOpenValue} High=${seriesInfo.formattedHighValue} ` +
            `Low=${seriesInfo.formattedLowValue} Close=${seriesInfo.formattedCloseValue}`;

        }
        // Output one block of text per seriesInfo on the chart. Using seriesName (from dataSeries.dataSeriesName) as a prefix
        outputSvgString += `<text x="8" y="${y}" font-size="13" font-family="Verdana" fill="${textColor}">
            ${seriesInfo.seriesName}: ${legendText}
        </text>`;
      });

      return `<svg width="100%" height="100%">
                ${outputSvgString}
            </svg>`;
    }
  });
  sciChartSurface.chartModifiers.add(cursorModifier);
  // #endregion
};

cursorModifierActiveLegendsOnCandles("scichart-root");


  

 

Using External placementDivId with the CursorModifier

Another way you can control the placement of the CursorModifier tooltip is using the placementDivId property. This places the standard CursorModifier tooltip into a div of your choice (which can be anywhere on the app).

Note, it does not currently work with tooltipLegendTemplate, however we are working on more options for styling, placement and configuration of tooltips soon.

Try the following code in your application:

// Add a CursorModifier with external placement div on the chart
// Expects <div id="legend-root" /> to be present in the DOM
const cursorModifier = new CursorModifier({
  placementDivId: "legend-root",
  showTooltip: true,
  tooltipContainerBackground: "#4682b433",
});
sciChartSurface.chartModifiers.add(cursorModifier);

This results in the following output.

<body>
    <div id="container">
        <div id="scichart-root" ></div>
        <div id="legend-root" ></div>
    </div>
</body>

  
body { margin: 0; }
#container { width: 100%; height: 100vh; position: relative; }
#scichart-root { width: 100%; height: 100%; position: relative; }
#legend-root { position: absolute; left: 20px; top: 20px; }

  
// Helper class to fetch candlestick data from Binance via Rest API
const getCandles = async (
  symbol,
  interval,
  limit = 300
) => {
  let url = `https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}`;
  if (limit) {
    url += `&limit=${limit}`;
  }
  try {
    console.log(`SimpleBinanceClient: Fetching ${limit} candles of ${symbol} ${interval}`);
    const response = await fetch(url);
    // Returned data format is [ { date, open, high, low, close, volume }, ... ]
    const data = await response.json();
    // Map to { dateValues[], openValues[], highValues[], lowValues[], closeValues[] } expected by scichart.js
    const dateValues = [];
    const openValues = [];
    const highValues = [];
    const lowValues = [];
    const closeValues = [];
    const volumeValues = [];
    data.forEach(candle => {
      const [timestamp, open, high, low, close, volume] = candle;
      dateValues.push(timestamp / 1000); // SciChart expects Unix Timestamp / 1000
      openValues.push(parseFloat(open));
      highValues.push(parseFloat(high));
      lowValues.push(parseFloat(low));
      closeValues.push(parseFloat(close));
      volumeValues.push(parseFloat(volume));
    });
    return { dateValues, openValues, highValues, lowValues, closeValues, volumeValues };
  } catch (err) {
    console.error(err);
    return [];
  }
};

async function cursorModifierActiveLegendsOnCandles(divElementId) {
  //
  const {
    SciChartSurface,
    CategoryAxis,
    NumericAxis,
    FastCandlestickRenderableSeries,
    OhlcDataSeries,
    SciChartJsNavyTheme,
    MouseWheelZoomModifier,
    ZoomPanModifier,
    ZoomExtentsModifier,
    EDataSeriesType,
    FastColumnRenderableSeries,
    CursorModifier,
    NumberRange,
    XyDataSeries,
    parseColorToTArgb,
    FastLineRenderableSeries,
    XyMovingAverageFilter
  } = SciChart;

  // or, for npm, import { SciChartSurface, ... } from "scichart"

  const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId, {
    theme: new SciChartJsNavyTheme()
  });
  sciChartSurface.xAxes.add(new CategoryAxis(wasmContext));
  sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { labelPrefix: "$", labelPrecision: 2 }));

  // Add a secondary axis for the volume bars
  sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { id: "VolumeAxisId", isVisible: false, growBy: new NumberRange(0, 4) }));

  // Data format is { dateValues[], openValues[], highValues[], lowValues[], closeValues[] }
  const { dateValues, openValues, highValues, lowValues, closeValues, volumeValues }
    = await getCandles("BTCUSDT", "1h", 100);

  //Add the Candlestick series
  const candlestickSeries = new FastCandlestickRenderableSeries(wasmContext, {
    dataSeries: new OhlcDataSeries(wasmContext, {
      xValues: dateValues,
      openValues,
      highValues,
      lowValues,
      closeValues,
      dataSeriesName: "BTC/USDT"
    }),
    strokeThickness: 1,
    dataPointWidth: 0.7,
    brushUp: "#33ff3377",
    brushDown: "#ff333377",
    strokeUp: "#77ff77",
    strokeDown: "#ff7777",
  });
  sciChartSurface.renderableSeries.add(candlestickSeries);

  // Add some moving averages using SciChart's filters/transforms API
  // when candleDataSeries updates, XyMovingAverageFilter automatically recomputes
  sciChartSurface.renderableSeries.add(
    new FastLineRenderableSeries(wasmContext, {
      dataSeries: new XyMovingAverageFilter(candlestickSeries.dataSeries, {
        dataSeriesName: "Moving Average (20)",
        length: 20
      }),
    })
  );

  sciChartSurface.renderableSeries.add(
    new FastLineRenderableSeries(wasmContext, {
      dataSeries: new XyMovingAverageFilter(candlestickSeries.dataSeries, {
        dataSeriesName: "Moving Average (50)",
        length: 50
      }),
    })
  );

  // Add a column series to render the volume bars
  sciChartSurface.renderableSeries.add(new FastColumnRenderableSeries(wasmContext, {
    dataSeries: new XyDataSeries(wasmContext, { xValues: dateValues, yValues: volumeValues, dataSeriesName: "Volume" }),
    yAxisId: "VolumeAxisId",
    strokeThickness: 0,
    dataPointWidth: 0.7,
    opacity: 0.47,
  }));

  // add interactivity for the example
  sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier({ excludedYAxisIds: ["VolumeAxisId"]}));
  sciChartSurface.chartModifiers.add(new ZoomPanModifier({ excludedYAxisIds: ["VolumeAxisId"]}));
  sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());

  // #region ExampleA
  // Add a CursorModifier with external placement div on the chart
  // Expects <div id="legend-root" /> to be present in the DOM
  const cursorModifier = new CursorModifier({
    placementDivId: "legend-root",
    showTooltip: true,
    tooltipContainerBackground: "#4682b433",
  });
  sciChartSurface.chartModifiers.add(cursorModifier);
  // #endregion
};

cursorModifierActiveLegendsOnCandles("scichart-root");


  

 

See Also