Layout: panes, controls and PaneGroup
SciFinanceChart doesn't create any DOM itself. It asks a layout for chart panes through the IChartPaneLayoutApi interface. SciFinanceChartPanel and <SciFinanceChartView> create one for you, SciChartPaneSplitter, and pass its options through: this page covers those options, and the layout underneath for when you want to build your own.
The layout has three layers, from highest to lowest:
| Layer | API | Role |
|---|---|---|
| Layout component | Sci (React: inside <) | The DOM: panes, drag dividers, per-pane control bars, the strip that restores hidden panes, loading placeholders |
| PaneGroup | finance, or Sci | Headless pane state: order, sizes, visibility, direction, maximise |
| DashboardApi | internal | SciChart surfaces: creation, axis sync, vertical and horizontal groups |
Multi-chart and sub-chart modes
Every layout supports two modes, selected by useSubCharts:
- Sub-chart (
true, the default): all panes are sub-surfaces of a singleSciChartSurface. There is one WebGL context and one render loop, which is the fastest option with many panes. - Multi-chart (
false): each pane is an independentSciChartSurface.
To switch modes, rebuild: in plain TypeScript, cleanup() and create again; in React, change the key. Save the state first to keep the studies and layout (Saving and restoring).
The Multi-Pane Layout example below switches between sub-chart and multi-chart modes, stacked and side by side, toggles axis sync, adds a custom pane, and switches the touch layout and control bar.
Pane controls
Each pane has a control bar, shown while the pointer is over the pane. Its buttons:
- Lock size keeps the pane at its current size while others are resized.
- Y-axis autorange turns the pane's automatic vertical scaling on or off.
- Move up and down (left and right when side by side).
- Maximise the pane, or restore the others.
- Hide the pane. It comes back from the restore strip below the panes, where each hidden pane is a pill named after its studies, such as "RSI".
- Close the pane, detaching its studies.
Dividers between panes resize them by dragging, with the mouse or by touch. A drag skips locked panes, and carries on into the next pane once its neighbour reaches the minimum size (100 px).
paneControls sets up the control bar and dividers:
const financeChartPanel = await SciFinanceChartPanel.create(container, {
exchangeName: BinanceExchange.name, // name="Binance" matches the built-in BinanceExchange
symbol: "BTCUSDT",
interval: CandleInterval.ONE_DAY,
period: "6M",
lockPeriodWidth: false,
useSubCharts: true,
direction: "column",
paneSizing: { [PaneId.DEFAULT.value]: { grow: 4 } },
paneControls: { touch: false }, // the touch layout, for phones and tablets
studies: [new OHLCStudy(), new VolumeStudy(), new RSIStudy(), new MACDStudy(), new StochasticStudy()],
});
// Later, e.g. when the page switches to its phone layout:
financeChartPanel.setPaneControls({ touch: true });
Option (IPane) | Default | Meaning |
|---|---|---|
visible | true | Show each pane's control bar. |
touch | false | The layout for phones and tablets. Each control bar sits in a drawer at the pane's right edge, opened by tapping or swiping its handle, because touch screens have no hover. Dividers get a wider hit area with grip dots. |
buttons | all on | Buttons to leave out, e.g. { : lock, y, move, maximize, hide, close, axis, vertical, horizontal. |
- The chart doesn't detect touch screens. Your page decides, usually from its own breakpoint, and switches a live chart with
setPaneControls, which changes only the options given. See Mobile and Desktop Modes. - Axis sync and the V-/H-group toggles are off. A finance chart keeps every pane's X axis in step itself, so it leaves those three buttons out. Turn them on with
buttons: { axisSync: true, … }. - React.
<SciFinanceChartView paneControls={…}>applies changes to the live chart, unlikeconfig.
Multi-Pane Layout
- TS
- React
import "scichart-scitrader/scitrader.css";
import {
BinanceExchange,
CandleInterval,
defaultExchangeProvider,
idbPriceStore,
ISciFinanceChartPanelApi,
LayoutDirection,
MACDStudy,
OHLCStudy,
PaneId,
RSIStudy,
SciFinanceChartPanel,
StochasticStudy,
VolumeStudy,
} from "scichart-scitrader";
defaultExchangeProvider.registerExchange(BinanceExchange, idbPriceStore);
// <div id="toolbar"></div>
// <div id="chart" style="height: 800px"></div>
const container = document.getElementById("chart");
const toolbar = document.getElementById("toolbar");
// Hover the top of a pane for its control bar: lock size, Y-axis autorange, move, maximise, hide
// and close. Drag a divider to resize.
let paneControls = { touch: false, visible: true };
let financeChartPanel: ISciFinanceChartPanelApi = await SciFinanceChartPanel.create(container, {
exchangeName: BinanceExchange.name, // name="Binance" matches the built-in BinanceExchange
symbol: "BTCUSDT",
interval: CandleInterval.ONE_DAY,
period: "6M",
// true: every pane is a sub-chart of one SciChart surface (fastest). false: a surface per pane
useSubCharts: true,
// "column": panes stacked; "row": side by side
direction: "column",
// A price pane four times the size of each indicator pane
paneSizing: { [PaneId.DEFAULT.value]: { grow: 4 } },
paneControls,
studies: [new OHLCStudy(), new VolumeStudy(), new RSIStudy(), new MACDStudy(), new StochasticStudy()],
});
// Sub-chart mode and direction are fixed when a chart is created. To change them, rebuild it,
// carrying the studies and layout over with toJSON / fromJSON.
async function rebuild(useSubCharts: boolean, direction: LayoutDirection) {
const state = financeChartPanel.toJSON();
financeChartPanel.cleanup();
financeChartPanel = await SciFinanceChartPanel.fromJSON(
container,
{ ...state, layout: { ...state.layout, direction } },
{ useSubCharts, paneControls }
);
}
function addButton(label: string, onClick: () => void) {
const button = document.createElement("button");
button.textContent = label;
button.onclick = onClick;
toolbar.appendChild(button);
}
addButton("Sub-charts, stacked", () => rebuild(true, "column"));
addButton("Multi-chart, side by side", () => rebuild(false, "row"));
// paneGroupApi drives the same layout from code
let synced = true;
addButton("Toggle axis sync", () => {
synced = !synced;
financeChartPanel.paneGroupApi.toggleAxisSync(synced);
});
// A pane that isn't a chart, laid out with the others
addButton("Add custom pane", () => {
const content = document.createElement("div");
content.textContent = "Your content";
financeChartPanel.addCustomPane(content);
});
// The touch layout, for phones and tablets: each control bar moves into a drawer at the pane's
// right edge (tap or swipe its handle), and the dividers get a wider grip. The chart doesn't
// detect touch screens itself: your page decides, and setPaneControls switches a live chart.
addButton("Toggle touch layout", () => {
paneControls = { ...paneControls, touch: !paneControls.touch };
financeChartPanel.setPaneControls(paneControls);
});
addButton("Show / hide control bars", () => {
paneControls = { ...paneControls, visible: !paneControls.visible };
financeChartPanel.setPaneControls(paneControls);
});
import "scichart-scitrader/scitrader.css";
import * as React from "react";
import {
BinanceExchange,
CandleInterval,
defaultExchangeProvider,
idbPriceStore,
ISciFinanceChartPanelConfig,
ISciFinanceChartPanelState,
LayoutDirection,
MACDStudy,
OHLCStudy,
PaneId,
RSIStudy,
StochasticStudy,
VolumeStudy,
} from "scichart-scitrader";
import { ISciFinanceChartViewApi, SciFinanceChartView } from "scichart-scitrader-react";
defaultExchangeProvider.registerExchange(BinanceExchange, idbPriceStore);
interface ILayout {
useSubCharts: boolean;
direction: LayoutDirection;
/** The chart to carry over when the layout changes */
saved: ISciFinanceChartPanelState | null;
}
export function MultiPaneChart() {
const [api, setApi] = React.useState<ISciFinanceChartViewApi>();
const [layout, setLayout] = React.useState<ILayout>({ useSubCharts: true, direction: "column", saved: null });
const [synced, setSynced] = React.useState(true);
const [touch, setTouch] = React.useState(false);
const [controlBarVisible, setControlBarVisible] = React.useState(true);
const config = React.useMemo<ISciFinanceChartPanelConfig>(
() => ({
exchangeName: BinanceExchange.name, // name="Binance" matches the built-in BinanceExchange
symbol: "BTCUSDT",
interval: CandleInterval.ONE_DAY,
period: "6M",
direction: "column",
// A price pane four times the size of each indicator pane
paneSizing: { [PaneId.DEFAULT.value]: { grow: 4 } },
studies: [new OHLCStudy(), new VolumeStudy(), new RSIStudy(), new MACDStudy(), new StochasticStudy()],
}),
[]
);
// Sub-chart mode and direction are read once, when the chart is created. To change them, save the
// chart with toJSON and remount the view (a new key) restoring it with the new layout.
const changeLayout = (useSubCharts: boolean, direction: LayoutDirection) => {
const state = api?.toJSON();
setApi(undefined);
setLayout({
useSubCharts,
direction,
saved: state ? { ...state, layout: { ...state.layout, direction } } : null,
});
};
return (
<div style={{ display: "flex", flexDirection: "column", height: 800 }}>
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
<button disabled={!api} onClick={() => changeLayout(!layout.useSubCharts, layout.direction)}>
{layout.useSubCharts ? "Sub-charts" : "Multi-chart"}
</button>
<button
disabled={!api}
onClick={() => changeLayout(layout.useSubCharts, layout.direction === "column" ? "row" : "column")}
>
{layout.direction === "column" ? "Stacked" : "Side by side"}
</button>
<button
disabled={!api}
onClick={() => {
// paneGroupApi drives the same layout from code
api.paneGroupApi.toggleAxisSync(!synced);
setSynced(!synced);
}}
>
Axis sync: {synced ? "on" : "off"}
</button>
<button disabled={!api} onClick={() => api.addCustomPane(<div>Your content</div>)}>
Add custom pane
</button>
<button onClick={() => setTouch(!touch)}>Touch layout: {touch ? "on" : "off"}</button>
<button onClick={() => setControlBarVisible(!controlBarVisible)}>
Control bar: {controlBarVisible ? "shown" : "hidden"}
</button>
</div>
<div style={{ flex: 1 }}>
<SciFinanceChartView
key={`${layout.useSubCharts}-${layout.direction}`}
config={config}
useSubCharts={layout.useSubCharts}
restoreState={layout.saved}
// Unlike config, paneControls applies to the live chart
paneControls={{ touch, visible: controlBarVisible }}
onInit={setApi}
/>
</div>
</div>
);
}
Your own content in the control bar
The control bar starts with an info area for your content, such as the SDK's candle counter. It is hidden while empty. In plain TypeScript, financeChartPanel.getPaneInfoElement(paneId) returns the element to fill; in React, paneInfo renders into it:
<SciFinanceChartView
config={config}
paneInfo={(paneId) =>
paneId === PaneId.DEFAULT.value && chart ? <CandleCounter chart={chart.financeApi} /> : null
}
/>
PaneId.DEFAULT is the price pane. Return null to leave a pane's area empty.
Loading states
A pane shows what is happening while its data loads:
- Until its chart is ready, each pane shows a shimmering skeleton (
.st-pane-skeleton), or content of your own. - While a symbol or interval switch loads, each chart pane is covered by a translucent shimmer that also blocks input (
.st-pane-loading-overlay).
loading (IPaneLoadingOptions) changes them:
| Option | Default | Meaning |
|---|---|---|
placeholder | true | true for the skeleton, false for nothing, or a function ( for your own content. |
on | none | Called with each placeholder when its pane's chart is ready, to clean up what you put in it. |
overlay | true | Whether to show the switch overlay. |
Progress dots. The SDK has a placeholder that follows a pane's loading phases: three dots and a caption ("Initialising surface", "Configuring chart", "Loading data"). The chart reports the phases to a PaneProgressEmitter passed as services.progress. In React, give <SciFinanceChartView> a paneLoadingContent:
// One emitter per mounted chart: the chart reports each pane's loading phases to it.
const progress = React.useMemo(() => new PaneProgressEmitter(), [mount]);
const config = React.useMemo<ISciFinanceChartPanelConfig>(
() => ({ /* … */ services: { progress } }),
[progress]
);
<SciFinanceChartView
key={mount}
config={config}
paneLoadingContent={(paneId) => <PaneProgressDots emitter={progress} paneId={paneId} />}
/>
In plain TypeScript, loading: paneProgressDotsLoading(progress) does the same, and createPaneProgressDots(emitter, paneId) gives one pane's dots for a placeholder of your own.
Loading States and Pane Info
- TS
- React
import "scichart-scitrader/scitrader.css";
import {
BinanceExchange,
CandleCounter,
CandleInterval,
defaultExchangeProvider,
idbPriceStore,
IntervalSelector,
OHLCStudy,
PaneId,
PaneProgressEmitter,
paneProgressDotsLoading,
RSIStudy,
SciFinanceChartPanel,
VolumeStudy,
} from "scichart-scitrader";
defaultExchangeProvider.registerExchange(BinanceExchange, idbPriceStore);
// <div id="toolbar"></div>
// <div id="chart" style="height: 600px"></div>
// The chart reports each pane's loading phases (surface → configured → data) to this emitter.
// Pass it in `services.progress` so it hears them from the start, before create() resolves.
const progress = new PaneProgressEmitter();
const financeChartPanel = await SciFinanceChartPanel.create(document.getElementById("chart"), {
exchangeName: BinanceExchange.name, // name="Binance" matches the built-in BinanceExchange
symbol: "BTCUSDT",
interval: CandleInterval.FIVE_MINUTES,
period: "1D",
studies: [new OHLCStudy(), new VolumeStudy(), new RSIStudy()],
services: { progress },
loading: {
// Progress dots in each pane until its chart is ready, in place of the default skeleton
...paneProgressDotsLoading(progress),
// The shimmer over the panes while a symbol or interval change loads (on by default)
overlay: true,
},
});
const chart = financeChartPanel.financeApi; // SciFinanceChart
// Your own content at the start of a pane's control bar (hover the price pane to see it):
// here, the SDK's candle counter, "visible / loaded / ~total"
const counter = CandleCounter.create(financeChartPanel.getPaneInfoElement(PaneId.DEFAULT.value), { chart });
// Changing the interval covers the panes with the shimmer until the new candles are drawn
IntervalSelector.create(document.getElementById("toolbar"), {
value: CandleInterval.FIVE_MINUTES,
intervals: [CandleInterval.ONE_MINUTE, CandleInterval.FIVE_MINUTES, CandleInterval.ONE_HOUR],
onChange: (interval) => chart.changeInterval(interval),
});
// When the page goes away:
// counter.destroy(); financeChartPanel.cleanup();
import "scichart-scitrader/scitrader.css";
import * as React from "react";
import {
BinanceExchange,
CandleInterval,
defaultExchangeProvider,
idbPriceStore,
ISciFinanceChartPanelConfig,
OHLCStudy,
PaneId,
PaneProgressEmitter,
RSIStudy,
VolumeStudy,
} from "scichart-scitrader";
import {
CandleCounter,
IntervalSelector,
ISciFinanceChartViewApi,
PaneProgressDots,
SciFinanceChartView,
} from "scichart-scitrader-react";
defaultExchangeProvider.registerExchange(BinanceExchange, idbPriceStore);
export function LoadingStatesChart() {
const [api, setApi] = React.useState<ISciFinanceChartViewApi>();
const [interval, setInterval] = React.useState(CandleInterval.FIVE_MINUTES);
// Bumped by "Reload": a new key remounts the chart, so you can watch it load again
const [mount, setMount] = React.useState(0);
// One emitter per mounted chart: the chart reports each pane's loading phases to it
const progress = React.useMemo(() => new PaneProgressEmitter(), [mount]);
const config = React.useMemo<ISciFinanceChartPanelConfig>(
() => ({
exchangeName: BinanceExchange.name, // name="Binance" matches the built-in BinanceExchange
symbol: "BTCUSDT",
interval: CandleInterval.FIVE_MINUTES,
period: "1D",
studies: [new OHLCStudy(), new VolumeStudy(), new RSIStudy()],
services: { progress },
}),
[progress]
);
return (
<div style={{ display: "flex", flexDirection: "column", height: 600 }}>
<div style={{ display: "flex", gap: 8 }}>
<IntervalSelector
value={interval}
intervals={[CandleInterval.ONE_MINUTE, CandleInterval.FIVE_MINUTES, CandleInterval.ONE_HOUR]}
onChange={(next) => {
setInterval(next);
// The panes show the switch overlay until the new candles are drawn
api?.financeApi.changeInterval(next);
}}
/>
<button
onClick={() => {
setApi(undefined);
setInterval(CandleInterval.FIVE_MINUTES);
setMount((n) => n + 1);
}}
>
Reload
</button>
</div>
<div style={{ flex: 1 }}>
<SciFinanceChartView
key={mount}
config={config}
onInit={setApi}
// Progress dots in each pane until its chart is ready, in place of the skeleton
paneLoadingContent={(paneId) => <PaneProgressDots emitter={progress} paneId={paneId} />}
// Your content at the start of a pane's control bar: the candle counter on the price pane
paneInfo={(paneId) =>
paneId === PaneId.DEFAULT.value && api ? <CandleCounter chart={api.financeApi} /> : null
}
/>
</div>
</div>
);
}
IChartPaneLayoutApi
This is what a layout implements to host a SciFinanceChart:
| Method | Called when |
|---|---|
create → Promise< | A study needs a new pane |
remove | A pane's last study is detached |
set / is | Maximising a pane, or restoring all with null |
sync / unsync | Keeping X-axes in step across panes |
add / remove | Aligning Y-axis widths so the plot areas line up |
add / remove | Aligning panes placed side by side |
set | Showing X-axis labels only on the bottom pane |
SciChartPaneSplitter implements it. To host the chart in a different layout, implement it yourself, then use SciFinanceChart.createWithStudies(yourLayoutApi, options) directly.
SciChartPaneSplitter
import { SciChartPaneSplitter } from "scichart-scitrader";
const splitter = await SciChartPaneSplitter.create(container, {
panes: [], // SciFinanceChart adds chart panes itself
direction: "column",
useSubCharts: true,
createSuspended: true, // let the chart resume rendering once everything is attached
paneControls: { touch: false },
});
const chart = await SciFinanceChart.createWithStudies(splitter, { exchangeName, symbol, interval, studies });
This is what SciFinanceChartPanel.create does. The splitter provides the dividers, the control bars, the restore strip and the loading states described above. A splitter used directly shows every control-bar button, including axis sync and the V-/H-group toggles.
splitter.group is its PaneGroup, and splitter.addCustomPane() adds a pane that isn't a chart.
PaneGroup
Pane Sizes and Pane State shows the common tasks, with live examples: sizing panes, and maximising, hiding, closing and autoranging them in code.
PaneGroup is the single source of truth for layout state. The splitter reads snapshot(), renders it, and changes the layout only through PaneGroup's methods. PaneGroup then re-applies the chart rules itself, including axis labels on the bottom pane only, group membership, axis sync and chart visibility.
const group = financeChartPanel.paneGroupApi;
group.setDirection("row");
group.reorderPane(paneId, 0);
group.setPaneSizes([{ id: "default", size: 600 }]);
group.lockPaneSize(paneId, 120); // fixed height in pixels
group.toggleExpanded(paneId); // maximise / restore
group.setVisible(paneId, false);
group.toggleAxisSync(true); // X-axes in step across every pane
group.stateChanged.subscribe((snapshot) => render(snapshot));
API surface
| Group | Methods and events |
|---|---|
| Lifecycle | init, add, get, remove, cleanup |
| Order and layout | reorder, swap, set |
| Visibility | set, toggle, set, toggle |
| Sizing | set, set, lock, unlock |
| Coordination | toggle, sync, unsync, add/, add/, set |
| Queries | get, get, get, is, snapshot |
| Persistence | to, from |
| Events | state, pane, pane, order, visibility, expanded, direction, size, axis, vertical, horizontal, all |
SciChartPaneApi.createPaneManager() creates a multi-chart group, and createSubChartPaneManager() a sub-chart group. The sub-chart group adds setContainerElement and recalculateSubChartPositions.
When saving a finance chart, use toJSON({ includeChartCoordination: false }), which SciFinanceChartPanel.toJSON already does: SciFinanceChart rebuilds the surfaces itself. The default, true, also serialises SciChart surface state, for layouts of plain charts.
Under PaneGroup
PaneGroup creates and coordinates the SciChart surfaces through an internal layer, SciChartDashboardApi. It handles surface creation, axis sync, and the vertical and horizontal groups that line up axes. It isn't exported: use PaneGroup, which offers the same coordination with pane state on top.
Logger (Logger.enable(context)) traces the layout and data layers when debugging.
Notes
- Element ids must be unique. SciChart tracks surfaces by their element's
id, so two surfaces in elements with the same id collide, and creating the second silently deletes the first. The SDK gives an element without an id a unique one; ids you set yourself must not repeat. - Sub-chart mode stacks DOM layers. The parent surface sits behind (
z-index: 0). The flex container of panes sits on top (z-index: 1) withpointer-events: none, so mouse and touch reach the chart. - Legacy classes.
PaneLayoutManager,PaneManagerandLayoutManagerare superseded by the classes on this page and are not part of the public API.