SciChart.js JavaScript 2D Charts API > Axis APIs > Axis Labels > Custom LabelProviders: Dynamic Dates on Zoom
Custom LabelProviders: Dynamic Dates on Zoom

Customisation in SciChart.js can go a level deeper than built-in label formatting by creating a custom labelprovider class.

In this page we're going to show a worked example of how we can create a custom label provider to handle dynamic date formatting on zoom.

To create a custom labelprovider to handle dynamic dates, first a class which inherits one of the LabelProvider classes listed here and override formatLabel or formatCursorLabel.

Inside the formatLabel function, parentAxis.visibleRange can be accessed to determine the visibleRange or zoom level of the axis.

Here we can provide some dynamic formatting of labels depending on the zoom level.

const {
  DateLabelProvider
} = SciChart;

// A custom class which inherits DateLabelProvider for dynamic date/time formatting
// You can also inherit NumericLabelProvider for number formatting
class DynamicDateLabelProvider extends DateLabelProvider {

  // Different thesholds of axis.visibleRange.max - min to trigger format changes
  SECONDS_IN_DAY = 86400;
  SECONDS_IN_HOUR = 60 * 60 * 6;
  SECONDS_IN_MINUTE = 60 * 30;

  constructor() {
    super();
    // Disable caching due to dynamic nature of the labels
    this.useCache = false;
  }

  // Called for each label
  formatLabel(dataValue) {
    const axisRange = this.parentAxis.visibleRange;
    // assuming label dataValue is a unix timestamp / 1000 (attached to Date axis)
    const unixTimeStamp = dataValue;
    const date = new Date(unixTimeStamp * 1000);
    const hours = date.getUTCHours();
    const minutes = date.getUTCMinutes();
    const seconds = date.getUTCSeconds();
    const milliseconds = date.getUTCMilliseconds();

    const hoursString = hours <= 9 ? `0${hours}` : hours.toString(10);
    const minutesString = minutes <= 9 ? `0${minutes}` : minutes.toString(10);
    const secondsString = seconds <= 9 ? `0${seconds}` : seconds.toString(10);

    if (axisRange.max - axisRange.min < this.SECONDS_IN_MINUTE) {
      // Format as 00m00s 000ms
      const millisecondsString = `00` + milliseconds.toString(10);
      return `${minutesString}m${secondsString}s ${millisecondsString}ms`;
    }
    else if (axisRange.max - axisRange.min < this.SECONDS_IN_HOUR) {
      // Format as HH:MM:SS
      return `${hoursString}:${minutesString}:${secondsString}`;
    } else if (axisRange.max - axisRange.min < this.SECONDS_IN_DAY) {
      // Format as HH:MM
      return `${hoursString}:${minutesString}`;
    } else {
      // Format as DD:MM:YY
      return date.toLocaleDateString("en-GB", {
        year: "2-digit",
        month: "2-digit",
        day: "2-digit"
      });
    }
  }

  // Called for each tooltip/cursor label
  formatCursorLabel(dataValue) {
    return this.formatLabel(dataValue);
  }
}

Next, apply the custom LabelProvider to an axis as follows:

const minDate = new Date("2023-03-01");
const maxDate = new Date("2023-03-03");

const xAxis = new DateTimeNumericAxis(wasmContext, {
  axisTitle: "X Axis with custom LabelProvider",
  // Note see DateTimeNumericAxis docs about unix timestamps / 1000
  visibleRange: new NumberRange(minDate.getTime() / 1000, maxDate.getTime() / 1000),
  // Apply the custom labelprovider we created before
  labelProvider: new DynamicDateLabelProvider()
});
sciChartSurface.xAxes.add(xAxis);
const { wasmContext, sciChartSurface } = await chartBuilder.build2DChart(divElementId, {
  surface: { theme: { type: EThemeProviderType.Dark } },
  xAxes: {
    type: EAxisType.NumericAxis,
    options: {
      axisTitle: "X Axis with custom LabelProvider",
      // Note see DateTimeNumericAxis docs about unix timestamps / 1000
      visibleRange: new NumberRange(minDate.getTime() / 1000, maxDate.getTime() / 1000),
      // Apply the custom labelprovider we created before
      labelProvider: new DynamicDateLabelProvider()
    }
  },
  yAxes: {
    type: EAxisType.NumericAxis,
  },
  modifiers: [
    { type: EChart2DModifierType.MouseWheelZoom }
  ]
});

This results in the following output:

  • At default zoom level the format DD/MM/YYYY is chosen
  • Zooming in and format changes to HH:mm
  • Zooming further still and format changes to HH:mm:ss
  • Once the axis range is less than a few minutes, label format changes to show minutes, seconds and milliseconds
<div id="scichart-root" ></div>
  
body { margin: 0; }
#scichart-root { width: 100%; height: 100vh; }
  
// #region ExampleA
const {
  DateLabelProvider
} = SciChart;

// A custom class which inherits DateLabelProvider for dynamic date/time formatting
// You can also inherit NumericLabelProvider for number formatting
class DynamicDateLabelProvider extends DateLabelProvider {

  // Different thesholds of axis.visibleRange.max - min to trigger format changes
  SECONDS_IN_DAY = 86400;
  SECONDS_IN_HOUR = 60 * 60 * 6;
  SECONDS_IN_MINUTE = 60 * 30;

  constructor() {
    super();
    // Disable caching due to dynamic nature of the labels
    this.useCache = false;
  }

  // Called for each label
  formatLabel(dataValue) {
    const axisRange = this.parentAxis.visibleRange;
    // assuming label dataValue is a unix timestamp / 1000 (attached to Date axis)
    const unixTimeStamp = dataValue;
    const date = new Date(unixTimeStamp * 1000);
    const hours = date.getUTCHours();
    const minutes = date.getUTCMinutes();
    const seconds = date.getUTCSeconds();
    const milliseconds = date.getUTCMilliseconds();

    const hoursString = hours <= 9 ? `0${hours}` : hours.toString(10);
    const minutesString = minutes <= 9 ? `0${minutes}` : minutes.toString(10);
    const secondsString = seconds <= 9 ? `0${seconds}` : seconds.toString(10);

    if (axisRange.max - axisRange.min < this.SECONDS_IN_MINUTE) {
      // Format as 00m00s 000ms
      const millisecondsString = `00` + milliseconds.toString(10);
      return `${minutesString}m${secondsString}s ${millisecondsString}ms`;
    }
    else if (axisRange.max - axisRange.min < this.SECONDS_IN_HOUR) {
      // Format as HH:MM:SS
      return `${hoursString}:${minutesString}:${secondsString}`;
    } else if (axisRange.max - axisRange.min < this.SECONDS_IN_DAY) {
      // Format as HH:MM
      return `${hoursString}:${minutesString}`;
    } else {
      // Format as DD:MM:YY
      return date.toLocaleDateString("en-GB", {
        year: "2-digit",
        month: "2-digit",
        day: "2-digit"
      });
    }
  }

  // Called for each tooltip/cursor label
  formatCursorLabel(dataValue) {
    return this.formatLabel(dataValue);
  }
}
// #endregion

async function labelProviderClass(divElementId) {
  const {
    SciChartSurface,
    DateTimeNumericAxis,
    NumericAxis,
    SciChartJsNavyTheme,
    NumberRange,
    TextAnnotation,
    ECoordinateMode,
    EHorizontalAnchorPoint,
    ZoomPanModifier,
    MouseWheelZoomModifier
  } = SciChart;

  const addChartTitle = (sciChartSurface, titleText, subTitleText) => {
    // Note: we will be improving this shortly in scichart.js v3.1
    sciChartSurface.annotations.add(new TextAnnotation({
      text: titleText,
      x1: 0.5, y1: 0.5,
      yCoordShift: -50,
      xCoordinateMode: ECoordinateMode.Relative, yCoordinateMode: ECoordinateMode.Relative,
      horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
      opacity: 0.5,
      fontSize: 32,
      fontWeight: "Bold",
      textColor: "White",
    }));
    sciChartSurface.annotations.add(new TextAnnotation({
      text: subTitleText,
      x1: 0.5, y1: 0.5,
      xCoordinateMode: ECoordinateMode.Relative, yCoordinateMode: ECoordinateMode.Relative,
      horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
      opacity: 0.4,
      fontSize: 17,
      textColor: "White",
    }));
  };

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

  const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId, {
    theme: new SciChartJsNavyTheme()
  });

  addChartTitle(sciChartSurface, "Custom LabelProvider Class Example", "Zoom in using MouseWheel to see dynamic label formatting");
  sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { axisTitle: "Y Axis" }));

  // #region ExampleB

  const minDate = new Date("2023-03-01");
  const maxDate = new Date("2023-03-03");

  const xAxis = new DateTimeNumericAxis(wasmContext, {
    axisTitle: "X Axis with custom LabelProvider",
    // Note see DateTimeNumericAxis docs about unix timestamps / 1000
    visibleRange: new NumberRange(minDate.getTime() / 1000, maxDate.getTime() / 1000),
    // Apply the custom labelprovider we created before
    labelProvider: new DynamicDateLabelProvider()
  });
  sciChartSurface.xAxes.add(xAxis);
  // #endregion

  // For the example, we add zooming, panning and an annotation so you can see how dates react on zoom.
  sciChartSurface.chartModifiers.add(new ZoomPanModifier(), new MouseWheelZoomModifier());
};

labelProviderClass("scichart-root");





async function builderExample(divElementId) {
  const {
    chartBuilder,
    EThemeProviderType,
    NumberRange,
    EAxisType,
    EChart2DModifierType
  } = SciChart;

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

  const minDate = new Date("2023-03-01");
  const maxDate = new Date("2023-03-03");

  // #region ExampleC
  const { wasmContext, sciChartSurface } = await chartBuilder.build2DChart(divElementId, {
    surface: { theme: { type: EThemeProviderType.Dark } },
    xAxes: {
      type: EAxisType.NumericAxis,
      options: {
        axisTitle: "X Axis with custom LabelProvider",
        // Note see DateTimeNumericAxis docs about unix timestamps / 1000
        visibleRange: new NumberRange(minDate.getTime() / 1000, maxDate.getTime() / 1000),
        // Apply the custom labelprovider we created before
        labelProvider: new DynamicDateLabelProvider()
      }
    },
    yAxes: {
      type: EAxisType.NumericAxis,
    },
    modifiers: [
      { type: EChart2DModifierType.MouseWheelZoom }
    ]
  });
  // #endregion
};



// Uncomment this to use the builder example
  //builderExample("scichart-root");


  
Zoom IN on the above example using the mousewheel to see dynamic label formatting