SciChart.js JavaScript 2D Charts API > Axis APIs > Multi Axis and Layout > Advanced Options - Custom Layout Managers
Advanced Options - Custom Layout Managers

In SciChart.js, all positioning and layout of axis are done with Layout Strategies. A list of default built-in strategies can be found below.

Layout Strategy Use on Apply to Behaviour
BottomAlignedOuterAxisLayoutStrategy X Axis Bottom side Default behaviour
BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy X Axis Bottom side Horizontal stacking behaviour
LeftAlignedOuterAxisLayoutStrategy Y Axis Left side Default behaviour
RightAlignedOuterAxisLayoutStrategy Y Axis Right side Default behaviour
TopAlignedOuterAxisLayoutStrategy X Axis Top side Default behaviour
LeftAlignedOuterVerticallyStackedAxisLayoutStrategy Y Axis Left side Vertical stacking behaviour
RightAlignedOuterVerticallyStackedAxisLayoutStrategy Y Axis Right side Vertical stacking behaviour
TopAlignedOuterHorizontallyStackedAxisLayoutStrategy X Axis Top side Horizontal stacking behaviour

 

By applying these strategies to the SciChartSurface you can achieve various layouts, such as Central Axis Layout or Vertically Stacked Axis Layout.

However, you can also create your own layout managers for custom or complex axis layouts.

Here's a worked example:

Example: Custom Stacked and Normal Axis Layout

A user on the SciChart Forums asked us how to combine Horizontally Stacked Axis behaviour with default behaviour, to achieve a layout like this:

This is possible by creating a custom layout strategy.

Step 1: create a class which inherits one of our default layout strategies. For this, we chose BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy as the base class.

// or for npm ... import { BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy } from "scichart";
const {
  BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy,
  BottomAlignedOuterAxisLayoutStrategy,
  getHorizontalAxisRequiredSize
} = SciChart;

// Example of creating a custom layout manager. First requested here https://www.scichart.com/questions/js/is-it-possible-to-create-two-xaxis-where-one-is-normal-and-the-other-one-is-horizontally-stacked-axis-layout
//
// Axis rendering  happens in 2 phases: measure & layout.
// Axis size and positioning is calculated by an axis layout strategy accordingly to the axisAlignment and isInner properties
// This custom Layout Strategy applies normal layout strategy to the first axis and the stacked strategy to the rest of bottom-aligned outer axes
class CustomAxisLayoutStrategy extends BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy {

  constructor() {
    super();

    /** The strategy used for normal (non-stacked) layout */
    this.defaultBottomOuterAxisLayoutStrategy = new BottomAlignedOuterAxisLayoutStrategy();
  }

  // override measureAxes from the base class
  measureAxes(sciChartSurface,  chartLayoutState,  axes) {
    const [firstAxis, ...stackedAxes] = axes;
    // measure stacked axes and max height (stackedAreaSize) required by them
    super.measureAxes(sciChartSurface, chartLayoutState, stackedAxes);
    const stackedAreaSize = chartLayoutState.bottomOuterAreaSize;

    // measure first axis with the regular logic
    this.defaultBottomOuterAxisLayoutStrategy.measureAxes(
      sciChartSurface,
      chartLayoutState,
      [firstAxis]
    );

    // calculate height required by the first axis and then the total height
    const firstAxisSize = getHorizontalAxisRequiredSize(firstAxis.axisLayoutState);
    chartLayoutState.bottomOuterAreaSize = firstAxisSize + stackedAreaSize;
  }

  // Override layoutAxes from the base class
  layoutAxes(left, top, right, bottom, axes) {
    const [firstAxis, ...stackedAxes] = axes;
    // layout first axis with the regular logic
    this.defaultBottomOuterAxisLayoutStrategy.layoutAxes(
      left, top, right, bottom, [firstAxis]
    );

    // after the layout phase we get axis.viewRect which specifies size and position of an axis
    // and then we can layout rest of the axes with stacked strategy beneath it.
    super.layoutAxes(left, firstAxis.viewRect.bottom, right, bottom, stackedAxes);
  }
}

Breaking this code down:

Applying the strategy like this to a chart:

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

// Apply your layout manager
sciChartSurface.layoutManager.bottomOuterAxesLayoutStrategy = new CustomAxisLayoutStrategy();

// Create some X Axis
const ID_X_AXIS_1 = "xAxis0";
const ID_X_AXIS_2 = "xAxis1";
const ID_X_AXIS_3 = "xAxis2";
const ID_X_AXIS_4 = "xAxis3";
const options = { drawMajorBands: false, drawMajorGridLines: false, drawMinorGridLines: false };
const xAxis1 = new NumericAxis(wasmContext, {
  id: ID_X_AXIS_1,
  axisTitle: ID_X_AXIS_1,
  drawMajorBands: true,
  drawMajorGridLines: true,
  drawMinorGridLines: true,
});
const xAxis2 = new NumericAxis(wasmContext, {
  id: ID_X_AXIS_2,
  axisTitle: ID_X_AXIS_2,
  ...options,
});
const xAxis3 = new NumericAxis(wasmContext, {
  id: ID_X_AXIS_3,
  axisTitle: ID_X_AXIS_3,
  ...options,
});
const xAxis4 = new NumericAxis(wasmContext, {
  id: ID_X_AXIS_4,
  axisTitle: ID_X_AXIS_4,
  ...options,
});
const yAxis1 = new NumericAxis(wasmContext, {
  axisTitle: "yAxis",
  backgroundColor: "#50C7E022",
  axisBorder: {color: "#50C7E0", borderLeft: 1 },
  axisTitleStyle: { fontSize: 13 },
});

// Add the axis to the chart
sciChartSurface.xAxes.add(xAxis1, xAxis2, xAxis3, xAxis4);
sciChartSurface.yAxes.add(yAxis1);

// To make it clearer what's happening, colour the axis backgrounds & borders
const axisColors = ["#50C7E0", "#EC0F6C", "#30BC9A", "#F48420" ];
sciChartSurface.xAxes.asArray().forEach((xAxis, index) => {
  xAxis.backgroundColor = axisColors[index] + "22";
  xAxis.axisBorder = {color: axisColors[index], borderTop: 1};
  xAxis.axisTitleStyle.fontSize = 13;
});

You can now get the following output.

<div id="scichart-root" ></div>
  
body { margin: 0; }
#scichart-root { width: 100%; height: 100vh; }
  
// #region ExampleA
// or for npm ... import { BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy } from "scichart";
const {
  BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy,
  BottomAlignedOuterAxisLayoutStrategy,
  getHorizontalAxisRequiredSize
} = SciChart;

// Example of creating a custom layout manager. First requested here https://www.scichart.com/questions/js/is-it-possible-to-create-two-xaxis-where-one-is-normal-and-the-other-one-is-horizontally-stacked-axis-layout
//
// Axis rendering  happens in 2 phases: measure & layout.
// Axis size and positioning is calculated by an axis layout strategy accordingly to the axisAlignment and isInner properties
// This custom Layout Strategy applies normal layout strategy to the first axis and the stacked strategy to the rest of bottom-aligned outer axes
class CustomAxisLayoutStrategy extends BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy {

  constructor() {
    super();

    /** The strategy used for normal (non-stacked) layout */
    this.defaultBottomOuterAxisLayoutStrategy = new BottomAlignedOuterAxisLayoutStrategy();
  }

  // override measureAxes from the base class
  measureAxes(sciChartSurface,  chartLayoutState,  axes) {
    const [firstAxis, ...stackedAxes] = axes;
    // measure stacked axes and max height (stackedAreaSize) required by them
    super.measureAxes(sciChartSurface, chartLayoutState, stackedAxes);
    const stackedAreaSize = chartLayoutState.bottomOuterAreaSize;

    // measure first axis with the regular logic
    this.defaultBottomOuterAxisLayoutStrategy.measureAxes(
      sciChartSurface,
      chartLayoutState,
      [firstAxis]
    );

    // calculate height required by the first axis and then the total height
    const firstAxisSize = getHorizontalAxisRequiredSize(firstAxis.axisLayoutState);
    chartLayoutState.bottomOuterAreaSize = firstAxisSize + stackedAreaSize;
  }

  // Override layoutAxes from the base class
  layoutAxes(left, top, right, bottom, axes) {
    const [firstAxis, ...stackedAxes] = axes;
    // layout first axis with the regular logic
    this.defaultBottomOuterAxisLayoutStrategy.layoutAxes(
      left, top, right, bottom, [firstAxis]
    );

    // after the layout phase we get axis.viewRect which specifies size and position of an axis
    // and then we can layout rest of the axes with stacked strategy beneath it.
    super.layoutAxes(left, firstAxis.viewRect.bottom, right, bottom, stackedAxes);
  }
}
// #endregion

async function customLayoutManager(divElementId) {

  // Demonstrates how to apply a custom layout manager in SciChart.js
  const {
    SciChartSurface,
    NumericAxis,
    SciChartJsNavyTheme,
  } = SciChart;

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

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

  // Apply your layout manager
  sciChartSurface.layoutManager.bottomOuterAxesLayoutStrategy = new CustomAxisLayoutStrategy();

  // Create some X Axis
  const ID_X_AXIS_1 = "xAxis0";
  const ID_X_AXIS_2 = "xAxis1";
  const ID_X_AXIS_3 = "xAxis2";
  const ID_X_AXIS_4 = "xAxis3";
  const options = { drawMajorBands: false, drawMajorGridLines: false, drawMinorGridLines: false };
  const xAxis1 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_1,
    axisTitle: ID_X_AXIS_1,
    drawMajorBands: true,
    drawMajorGridLines: true,
    drawMinorGridLines: true,
  });
  const xAxis2 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_2,
    axisTitle: ID_X_AXIS_2,
    ...options,
  });
  const xAxis3 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_3,
    axisTitle: ID_X_AXIS_3,
    ...options,
  });
  const xAxis4 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_4,
    axisTitle: ID_X_AXIS_4,
    ...options,
  });
  const yAxis1 = new NumericAxis(wasmContext, {
    axisTitle: "yAxis",
    backgroundColor: "#50C7E022",
    axisBorder: {color: "#50C7E0", borderLeft: 1 },
    axisTitleStyle: { fontSize: 13 },
  });

  // Add the axis to the chart
  sciChartSurface.xAxes.add(xAxis1, xAxis2, xAxis3, xAxis4);
  sciChartSurface.yAxes.add(yAxis1);

  // To make it clearer what's happening, colour the axis backgrounds & borders
  const axisColors = ["#50C7E0", "#EC0F6C", "#30BC9A", "#F48420" ];
  sciChartSurface.xAxes.asArray().forEach((xAxis, index) => {
    xAxis.backgroundColor = axisColors[index] + "22";
    xAxis.axisBorder = {color: axisColors[index], borderTop: 1};
    xAxis.axisTitleStyle.fontSize = 13;
  });

  // #endregion

  const {
    ZoomPanModifier,
    PinchZoomModifier,
    ZoomExtentsModifier,
    MouseWheelZoomModifier,
    FastLineRenderableSeries,
    XyDataSeries,
    TextAnnotation
  } = SciChart;

  // Let's add some series to the chart to show how they also behave with axis
  const getOptions = (index, offset = 0) => {
    const xValues = Array.from(Array(50).keys());
    const yValues = xValues.map(x => Math.sin(x * 0.4 + index) + offset);

    return {
      xAxisId: `xAxis${index}`,
      stroke: axisColors[index],
      strokeThickness: 3,
      dataSeries: new XyDataSeries(wasmContext, { xValues, yValues })
    };
  };

  sciChartSurface.renderableSeries.add(new FastLineRenderableSeries(wasmContext, {...getOptions(0, 1)}));
  sciChartSurface.renderableSeries.add(new FastLineRenderableSeries(wasmContext, {...getOptions(1)}));
  sciChartSurface.renderableSeries.add(new FastLineRenderableSeries(wasmContext, {...getOptions(2)}));
  sciChartSurface.renderableSeries.add(new FastLineRenderableSeries(wasmContext, {...getOptions(3)}));

  // We will also add some annotations to explain to the user
  sciChartSurface.annotations.add(new TextAnnotation({
    text: "Blue series uses xAxis0 and is stretched horizontally",
    x1: 0,
    y1: 2,
    textColor: axisColors[0],
  }));

  sciChartSurface.annotations.add(new TextAnnotation({
    text: "Using xAxis1",
    x1: 0,
    y1: 1.1,
    xAxisId: ID_X_AXIS_2,
    textColor: axisColors[1],
  }));

  sciChartSurface.annotations.add(new TextAnnotation({
    text: "Using xAxis2",
    x1: 0,
    y1: 1.1,
    xAxisId: ID_X_AXIS_3,
    textColor: axisColors[2],
  }));

  sciChartSurface.annotations.add(new TextAnnotation({
    text: "Using xAxis3",
    x1: 0,
    y1: 1.1,
    xAxisId: ID_X_AXIS_4,
    textColor: axisColors[3],
  }));

  sciChartSurface.chartModifiers.add(
    new ZoomPanModifier(),
    new PinchZoomModifier(),
    new ZoomExtentsModifier(),
    new MouseWheelZoomModifier({ applyToSeriesViewRect: false })
  );
};

customLayoutManager("scichart-root");



  

Reversing the Order of Stacked & Stretched Axis

What if you wanted to swap the order of the stretched xAxis and the stacked xAxis?

This is really simple, just modify the layoutAxes function like this.

// Use the base horizontal stacked layout first, before default layout to switch the order of axis
layoutAxes(left, top, right, bottom, axes) {
  const [firstAxis, ...stackedAxes] = axes;
  // layout stacked axes first
  super.layoutAxes(left, top, right, bottom, stackedAxes);

  // then get the top offset for the normalAxis with stackedAxis.viewRect.bottom
  const stackedAxis = stackedAxes[0]
  this.defaultBottomOuterAxisLayoutStrategy.layoutAxes(
    left,
    stackedAxis.viewRect.bottom,
    right,
    bottom,
    [firstAxis] // normal axis
  );
}


Now the axis order are swapped, resulting in this output.

<div id="scichart-root" ></div>
  
body { margin: 0; }
#scichart-root { width: 100%; height: 100vh; }
  
// or for npm ... import { BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy } from "scichart";
const {
  BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy,
  BottomAlignedOuterAxisLayoutStrategy,
  getHorizontalAxisRequiredSize
} = SciChart;

// Example of creating a custom layout manager. First requested here https://www.scichart.com/questions/js/is-it-possible-to-create-two-xaxis-where-one-is-normal-and-the-other-one-is-horizontally-stacked-axis-layout
//
// Axis rendering  happens in 2 phases: measure & layout.
// Axis size and positioning is calculated by an axis layout strategy accordingly to the axisAlignment and isInner properties
// This custom Layout Strategy applies normal layout strategy to the first axis and the stacked strategy to the rest of bottom-aligned outer axes
class CustomAxisLayoutStrategy extends BottomAlignedOuterHorizontallyStackedAxisLayoutStrategy {

  constructor() {
    super();

    /** The strategy used for normal (non-stacked) layout */
    this.defaultBottomOuterAxisLayoutStrategy = new BottomAlignedOuterAxisLayoutStrategy();
  }

  // override measureAxes from the base class
  measureAxes(sciChartSurface,  chartLayoutState,  axes) {
    const [firstAxis, ...stackedAxes] = axes;
    // measure stacked axes and max height (stackedAreaSize) required by them
    super.measureAxes(sciChartSurface, chartLayoutState, stackedAxes);
    const stackedAreaSize = chartLayoutState.bottomOuterAreaSize;

    // measure first axis with the regular logic
    this.defaultBottomOuterAxisLayoutStrategy.measureAxes(
      sciChartSurface,
      chartLayoutState,
      [firstAxis]
    );

    // calculate height required by the first axis and then the total height
    const firstAxisSize = getHorizontalAxisRequiredSize(firstAxis.axisLayoutState);
    chartLayoutState.bottomOuterAreaSize = firstAxisSize + stackedAreaSize;
  }

  // #region ExampleA
  // Use the base horizontal stacked layout first, before default layout to switch the order of axis
  layoutAxes(left, top, right, bottom, axes) {
    const [firstAxis, ...stackedAxes] = axes;
    // layout stacked axes first
    super.layoutAxes(left, top, right, bottom, stackedAxes);

    // then get the top offset for the normalAxis with stackedAxis.viewRect.bottom
    const stackedAxis = stackedAxes[0]
    this.defaultBottomOuterAxisLayoutStrategy.layoutAxes(
      left,
      stackedAxis.viewRect.bottom,
      right,
      bottom,
      [firstAxis] // normal axis
    );
  }
  // #endregion
}


async function customLayoutManager(divElementId) {

  // Demonstrates how to apply a custom layout manager in SciChart.js
  const {
    SciChartSurface,
    NumericAxis,
    SciChartJsNavyTheme,
  } = SciChart;

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

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

  // Apply your layout manager
  sciChartSurface.layoutManager.bottomOuterAxesLayoutStrategy = new CustomAxisLayoutStrategy();

  // Create some X Axis
  const ID_X_AXIS_1 = "xAxis0";
  const ID_X_AXIS_2 = "xAxis1";
  const ID_X_AXIS_3 = "xAxis2";
  const ID_X_AXIS_4 = "xAxis3";
  const options = { drawMajorBands: false, drawMajorGridLines: false, drawMinorGridLines: false };
  const xAxis1 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_1,
    axisTitle: ID_X_AXIS_1,
    drawMajorBands: true,
    drawMajorGridLines: true,
    drawMinorGridLines: true,
  });
  const xAxis2 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_2,
    axisTitle: ID_X_AXIS_2,
    ...options,
  });
  const xAxis3 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_3,
    axisTitle: ID_X_AXIS_3,
    ...options,
  });
  const xAxis4 = new NumericAxis(wasmContext, {
    id: ID_X_AXIS_4,
    axisTitle: ID_X_AXIS_4,
    ...options,
  });
  const yAxis1 = new NumericAxis(wasmContext, {
    axisTitle: "yAxis",
    backgroundColor: "#50C7E022",
    axisBorder: {color: "#50C7E0", borderLeft: 1 },
    axisTitleStyle: { fontSize: 13 },
  });

  // Add the axis to the chart
  sciChartSurface.xAxes.add(xAxis1, xAxis2, xAxis3, xAxis4);
  sciChartSurface.yAxes.add(yAxis1);

  // To make it clearer what's happening, colour the axis backgrounds & borders
  const axisColors = ["#50C7E0", "#EC0F6C", "#30BC9A", "#F48420" ];
  sciChartSurface.xAxes.asArray().forEach((xAxis, index) => {
    xAxis.backgroundColor = axisColors[index] + "22";
    xAxis.axisBorder = {color: axisColors[index], borderTop: 1};
    xAxis.axisTitleStyle.fontSize = 13;
  });

  const { TextAnnotation, EHorizontalAnchorPoint, ECoordinateMode, EAnnotationLayer } = SciChart;
  const textOpts = {
    xCoordinateMode: ECoordinateMode.Relative,
    yCoordinateMode: ECoordinateMode.Relative,
    x1: 0.5,
    y1: 0.5,
    horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
    opacity: 0.33,
    textColor: "White",
  };
  sciChartSurface.annotations.add(new TextAnnotation({
    text: "Custom Layout Manager Example",
    fontSize: 36,
    yCoordShift: -50,
    ... textOpts,
  }));
  sciChartSurface.annotations.add(new TextAnnotation({
    text: "Reversing the order of Stacked and Stretched X-Axis",
    fontSize: 20,
    ... textOpts,
  }));
};

customLayoutManager("scichart-root");