Upgrading
How the public API changes between releases, and what to do about it. The rules behind this page are in the contributing guide.
- A deprecated API keeps working until the removal release named in its entry. Development builds log one
charton [deprecated]warning per API per page load, linking to its entry below; production builds log nothing. - Each entry shows the old and the new code.
- Each release that changes the public API gets a section under Releases.
Deprecated APIs
None yet.
Releases
Unreleased
0.1.0
The first release on npm. These entries are for code built from the repository before it; an app starting on 0.1.0 needs none of them.
Default toolbar items (#1014).
defaultToolbarItems()is now the symbol, interval and indicators on the left and indicator templates, bar replay, fullscreen and chart settings on the right; chart style and the price scale menu left the list. To keep them, add them back:toolbar: [...defaultToolbarItems(), chartStyle(), { ...scaleMode(), group: 'view' }]. A shell with notoolbaroption now also mounts the replay strip, and its picker-style search boxes are flat rows across the dialog. The command palette opens with its search box and category chips, lists nothing until something is typed or a chip picked, and finds the newchart-settings,appearance,light-modeanddark-modeactions and the chart settings' sections (ChartSettingsHandle.commandSource, idsettings; an app source with that id keeps its place).Minimal shell defaults: a default toolbar, no bottom bar (#1001). A shell given no
toolbarnow showsdefaultToolbarItems()(interval, chart style, indicators, price scale, settings, fullscreen); passtoolbar: []to keep the old empty top. A shell given nobottomBarnow has none; passbottomBar: true(or{}) for the bar it showed before. Shorthand options and item lists are unchanged. Chart grid cells keep no toolbar of their own: the grid's toolbar acts on the active cell.The bottom bar is a toolbar of items (#980).
bottomBarandcreateBottomBartake a list of items (or toolbar options) as the toolbar does, and the old options stay as shorthand fordefaultBottomBarItems(options), so a shell that passed them shows the same controls. What changed:BottomBarHandleis nowToolbarHandle, whoseelis the toolbar (role="toolbar") inside the.pc-bottombarstrip; slots andclassNames.bottomBargo on the strip, as before. The strip has norole, so tests or styles that read the bar's role or children from.pc-bottombarlook one level down (.pc-bottombar > .pc-toolbar). The.pc-bottombar-menuclass is gone (.pc-bottombar .pc-menu-triggerstyles the bar's menus), andrefresh()no longer turns the clock into a button: it looks again for settings made after it on its own. InscaleToggles: 'inline', the auto-fit toggle is a toolbar button rather than a chip. When the bar is narrow, the go-to-date, range, clock and session-bands items are hidden instead of moving into the "more" menu.Drawing labels pick their text color by contrast (#904).
labelPillwithout acoloroption used to write its text in the theme background; it now writes black or white, whichever contrasts more with the pill'sbackground(contrastText). Built-in level, price and pattern labels follow the same rule. A plugin tool that wants the old look passes the color itself:tslabelPill(ctx, env, text, x, y, { background, color: env.theme.background }); // the text color before #904Primitive lanes need the
depthLane()plugin (#912). The code that lays out and draws the lanes primitives reserve withlane()anddrawLane()moved from the chart into thedepthLane()plugin, whichbuiltins()and the UI shell include, so apps that passbuiltins(), use the shell or already use depth lanes see no change. An app that picks its own plugins and attaches a primitive withlane()(its own, orcreateDepthLane()throughattachPrimitive) adds the plugin:createChart(el, { plugins: [depthLane()] }). Without it, such a primitive gets no space and its lane is not drawn; development builds warn once (lanes-missing).The flat crosshair and time axis options are gone (#914). Use the nested fields, which were already the same settings. Saved layouts and stored chart settings need nothing: they never held the flat names.
tschart.applyOptions({ crosshair: { mode: 'close', labels: { price: false, time: false } } }); // was crosshairMode, crosshairPriceLabel, crosshairTimeLabel chart.applyOptions({ timeAxis: { height: 32 } }); // was timeAxisHeight: 32 const height = chart.options.timeAxis.height; // was chart.options.timeAxisHeightThe selection and its edit commands come with the drawings and indicators plugins (#909).
remove-selected,copy,cut,paste,undoandredoare registered bydrawingTools(),drawings()andindicators()(all inbuiltins()), so apps that passbuiltins()or use the shell see no change. On a chart with none of them,chart.actionslacks those commands,Delete,BackspaceandMod+C/X/V/Zstay with the page,getSelection()returns null andsetSelectiondoes nothing.The
hlcAreachart style israngeBand(#861). The style draws a close line with a band to the high and the low, and is named for that. Replacestyle: 'hlcArea'withstyle: 'rangeBand',chartTypeIcons.hlcAreawithchartTypeIcons.rangeBand, and the shell messagechartStyleHlcAreawithchartStyleRangeBandin your own translations. A saved layout or layout template that still hashlcArealoads with the main series ascandles, listsmainSeries.stylein itserrorsand logs anoption-rangewarning in development builds; save it again to store the new name. A starredhlcAreain the shell's chart style favourites shows as candles, with the same warning; starrangeBandinstead. The shell's style menu (chartStyles) is in a new order, grouped under headings: entries takesectionandsectionKey, and the new messageschartStyleSectionOhlc,chartStyleSectionRangeandchartStyleSectionClosename them.chartStyleVolumeCandlesreads "Volume-width candles" andchartStyleLineMarkers"Line with dots".One scale mode control in the bottom bar (#861). The
%andlogtoggles are one radio group, linear, log or percent, named by theseriesPlacementmessage ("Price scale"), with the newscaleLinearmessage for its first choice; an indexed scale checks the percent choice. Theautotoggle stays. The ratio lock left the bar for the chart settings (Axes, Axis lines), where the field's path ispriceScale.lockPriceToBarRatio:readChartSettingandapplyChartSettingsread and writepriceScale.paths on the main pane's price scale.scaleLogTitleandscalePercentTitlenow name thelogand%radios. The messagesscaleLockandscaleLockTitleare gone; drop them from your translations, and translatesetting.priceScale.lockPriceToBarRatioinstead. Tests or styles that looked for the bar'slockchip, or foraria-pressedon%andlog, look for the radios'aria-checkednow.minVisibleBarsis nowminBarCoverage. The pan limit is a share of the plot width that bars keep covering at either end (0 to 1, default 0.5), so it behaves the same at every zoom. Replace a bar count with a share:minVisibleBars: 10on a plot about 100 bars wide isminBarCoverage: 0.1. Other defaults changed with it (#857):barSpacing10,maxBarSpacing63,rightOffset5; pass the old values to keep the old view.Pane margins left unset are now sized to the legend and the price labels. Pass
marginTopandmarginBottomtoaddPaneorsetPriceScaleOptionsto fix them, andnullto give them back.getPriceScaleOptionsleaves automatic margins out. Layouts saved before this change stored every margin (0.1 and 0.08 on the main pane, 0.1 and 0.1 on others), so they load as fixed margins; deletemarginTopandmarginBottomfrom theirscalesentries, or callsetPriceScaleOptions({ marginTop: null, marginBottom: null }, pane)after loading, to use the new rule.Volume profiles without
rowssize their rows by pixels: the series (was 40 rows) and the fixed-range drawing (was 24) one row per 10 px of height, session profiles andcomputeVolumeProfileby the Rice rule from the bar count. The longest row now reaches the middle: serieswidth0.5 (was 0.3 for the visible range, 0.7 for sessions), drawingprofileWidth50 (was 30). Passrows/width(series) orstyle.rows/style.profileWidth(drawing) to keep a fixed layout. Fixed-range profiles drawn before this change storedrows: 24andprofileWidth: 30in their style, so they keep them; setrowsto 0 ("auto" in the settings dialog) andprofileWidthto 50 to adopt the new defaults.tscreateChart(el, { minBarCoverage: 0.1 }); // was minVisibleBars: 10Default shortcuts, settings sections and the crosshair date changed. None of these needs code to keep working, but apps that name them do:
No default shortcut uses Alt, or a letter or digit, which type-to-search keeps.
reset-viewisShift+End(wasAlt+R), and the shell'sgo-to-date(wasAlt+G) and thedraw-<tool>actions (draw-trendline,draw-hline,draw-vlineanddraw-fib-retracementhadAlt+T,Alt+H,Alt+V,Alt+F) have no default keys. Saved keymaps keep working, since they are keyed by action id. To keep an old key, bind it per action, which leaves other overrides alone:tschart.actions.setKeys('reset-view', ['Alt+R']); chart.actions.setKeys('draw-trendline', ['Alt+T']);The chart settings sections are
series,readout,axes,time,backgroundandmarkers(weresymbol,legend,scales,canvas,eventsandtime). Updatesettings.open(id)calls,sectionscallbacks that pick sections by id, and messages keyedsettings.<section>orsettings.<section>.<group>. Field keys (setting.<path>) are unchanged. The crosshair group moved toreadout, the session and time-axis mark fields totime, andtimeAxis.showSecondstotime.Four labels changed: "Time left in bar" (was "Countdown to bar close"), "Trading day dividers" ("Session breaks"), "Keep line angles when zooming" (the bottom bar's ratio lock) and "Trend line with stats" (the
info-linetool). Tests that look them up by text need the new text.The full date is
{ day: 'numeric', month: 'short', year: 'numeric' }throughIntl(Oct 16, 2026 14:30,16. Okt. 2026,2026年10月16日), and ISO 8601 (2026-10-16 14:30) when dates do not go throughIntl, instead ofWed 16 Oct '26. Besides the crosshair label, this text appears inchart.describe()and the chart's description, the new-bar announcement, the data table's time column (chart.describeTable()) and the default caption. Tests and screen-reader scripts that match those strings need the new form. Passlocalization.dateFormatfor another date.
Image capture is an opt-in plugin.
chart.captureImage(compositing every layer into one image, with its caption, padding, background and theme options) moved into the newimageCapture()plugin, so a chart that never takes a screenshot leaves that code out.builtins()includes it, and the UI shell adds it when the app's ownchart.pluginslist leaves it out, since its screenshot and copy-image buttons use it. Otherwise add it:tsimport { createChart, imageCapture } from '@chart-on/core'; const chart = createChart(el, { plugins: [imageCapture()] }); const blob = await chart.captureImage();Without it
captureImagethrows anot-registeredChartErrorat the call, withcontext{ call: 'captureImage', plugin: 'imageCapture()' }. A destroyed chart still resolves tonull. Data export (getDataTable,toCSV) needs no plugin.Access rules are one predicate. The
accessoption is{ allow, limits, refused }. Write the old lists as a function of{ kind, id }, wherekindis'drawing'or'indicator';maxIndicatorsmoves tolimits.indicators, andblockedis renamedrefused.drawings.setAccess()andindicators.setAccess()take the same rule (AccessRule,({ kind, id }) => boolean), ornull; theaccess:changeandindicators:access-changepayloads carryallowinstead ofaccess.chart.options.accessholds what the app set, so read a manager's rule fromdrawings.accessorindicators.access.ts// Before: access: { drawingTools: ['trendline', 'hline'], indicators: { mode: 'deny', indicators: ['vwap'] }, maxIndicators: 3, blocked: 'disable' } const tools = new Set(['trendline', 'hline']); createChart(el, { access: { allow: ({ kind, id }) => (kind === 'drawing' ? tools.has(id) : id !== 'vwap'), limits: { indicators: 3 }, refused: 'disable', }, });Storage adapters store a layout summary.
listLayoutsreturnsLayoutSummary({ id, name, savedAt, preview?: { symbol?, interval? } }) instead ofLayoutInfo, andsaveLayouttakes{ id?, summary, layout }. Build it withlayoutEntry(layout, name, id?):storage.saveLayout(layoutEntry(layout, 'Main', 'main'))replacesstorage.saveLayout({ id: 'main', name: 'Main', layout }). A custom adapter storessummarywith the id and returns it fromlistLayouts, without reading the layout. The local adapter converts its index from the old shape once, on the first read, so saved layouts stay listed.parseSessionStringis removed. Write the schedule as an object:'0930-1600:12345'becomes{ weekly: { mon: day, tue: day, wed: day, thu: day, fri: day } }withconst day = [{ start: '09:30', end: '16:00' }], and'24x7'becomesALWAYS_OPEN.Drawing tools sit in seven groups, and the cursor choices are two. Tool ids are unchanged, so saved drawings, favourites and shortcut bindings keep working. The
groupids changed, so a custom tool that joined a built-in flyout needs the new id, and the rail's message keys follow:Before Now group: 'lines'group: 'lines'the arrowtool:'lines''annotate''shapes','text','markers''annotate''channels','pitchforks', andfib-channel'parallels''fib'(exceptfib-channeland the time tools),'gann''ratios''cycles', andfib-time-zoneandfib-time'time''positions','volume''measure'crosshair: { cursor: 'arrow' }crosshair: { cursor: 'pointer' }(a stored'arrow'still loads as'pointer')crosshair: { cursor: 'dot' }'cross'or'pointer'(a stored'dot'loads as'cross')messages railGroupChannels,railGroupPitchforks,railGroupGann,railGroupFib,railGroupCycles,railGroupShapes,railGroupText,railGroupMarkers,railGroupPositions,railGroupVolumerailGroupAnnotate,railGroupParallels,railGroupRatios,railGroupTime(railGroupLines,railGroupPatternsandrailGroupMeasurestay)messages railCursorDot,railCursorArrowrailCursorPointerbuiltinToolsandpatternToolsare in the catalogue order, so code that picked tools by index should pick them bytype. Each rail flyout starts on its group's most general tool (Trend line for Lines, Text for Annotate, and so on).Picking a bar is a standalone function.
chart.pickBar()andchart.cancelPickBar()are now the@chart-on/coreexportspickBar(chart)andcancelPickBar(chart), so apps that do not pick bars leave the code out. They behave as before:tsimport { cancelPickBar, pickBar } from '@chart-on/core'; const picked = await pickBar(chart); // was chart.pickBar() cancelPickBar(chart); // was chart.cancelPickBar()Viewport bounds are an opt-in plugin.
chart.setViewportBoundsmoved into the newviewportBounds()plugin, so a chart without it leaves that code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. Otherwise add it:tsimport { createChart, viewportBounds } from '@chart-on/core'; const chart = createChart(el, { plugins: [viewportBounds()] });Without it,
setViewportBoundsthrows anot-registeredChartErrorandchart.viewportBoundsis null. An app that gives the UI shell its ownchart.pluginslist addsviewportBounds()to that list. Uninstalling the plugin removes the bounds.Point series are an opt-in plugin.
chart.addPointsmoved into the newpoints()plugin, so a chart without it leaves the point series code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. Otherwise add it:tsimport { createChart, points } from '@chart-on/core'; const chart = createChart(el, { plugins: [points()] });Without it,
addPointsthrows anot-registeredChartError. An app that gives the UI shell its ownchart.pluginslist addspoints()to that list.The bar-close countdown is an opt-in plugin. The time left in the newest bar, which the
countdownfeature flag shows under the last price label, moved into the newcountdown()plugin, so a chart without it leaves that code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. Otherwise add it:tsimport { countdown, createChart } from '@chart-on/core'; const chart = createChart(el, { plugins: [countdown()] });Without it the
countdownflag is kept and stays on by default, but shows nothing. Development builds warn once when the flag is set totrueexplicitly (countdown-missing). Nothing throws. The UI shell does not add the plugin itself: an app that gives the shell its ownchart.pluginslist addscountdown()to that list. Without it the shell's settings dialog leaves out the countdown switch.Drawing level defaults and colours changed. The built-in level tools take their default levels from each method: the Fib speed fan uses the retracement's Fibonacci levels, the Gann box Gann's range divisions (quarters shown, odd eighths and thirds hidden), the trend-based Fib time 0 and the powers of the golden ratio from φ⁻² to φ², and the pitchforks the tines (1) with the halfway lines (0.5) and the warning lines at 2 and 3 hidden. Every default level now carries
color: 'auto', the shared level colour rule, instead of a named theme level colour. Saved drawings keep their own levels and colours, so they draw as before; only new drawings and drawings withoutstyle.levelschange. Code that shows a level's colour, such as a custom levels editor, resolves'auto'with the newresolveLevelColor:tsimport { resolveLevelColor } from '@chart-on/core'; // Before: resolveThemeColor(level.color, chart.theme) ?? drawingColor const color = resolveLevelColor(level, chart.theme) ?? drawingColor;To keep the previous look for new drawings, set your own levels with
chart.drawings.setToolDefaults(type, { levels }). The named theme level colours (levelNeutral,levelRed…) still resolve.Data source requests and resets are renamed and reshaped. A data source written for an earlier version needs these changes:
Before Now Why request.countBackrequest.limitAn upper bound on the bars returned request.firstrequest.kind === 'initial'It duplicated kind{ bars, noData }{ bars, atStart }The bars reach the start of the history stream(symbol, interval, onBar, onReset)stream(symbol, interval, onBar)plusonReset(listener)Sources without a stream can report corrections too createPollingDataSource({ countBack })createPollingDataSource({ limit })The same name as the request field testDataSource(source, { countBack })testDataSource(source, { limit })The same name as the request field request.countBackin an indicator'sfetchrequest.barCountAn expected size, not a cap request.getBars(symbol)in an indicator'sfetchrequest.barsOf(symbol)No getprefixA reset is now reported through a subscription on the source, and names the symbol, and the interval unless every interval changed:
ts// Before stream(symbol, interval, onBar, onReset) { feed.on('corrected', ({ from, to }) => onReset?.({ from, to })); /* ... */ } // Now const resets = new Set<(reset: BarsReset) => void>(); feed.on('corrected', ({ symbol, interval, from, to }) => { for (const listener of resets) listener({ symbol, interval, range: { from, to } }); }); const source: DataSource = { /* symbol, search, history, stream(symbol, interval, onBar) */ onReset(listener) { resets.add(listener); return () => resets.delete(listener); }, };createPollingDataSourceno longer sends the previous bar again when a new one opens unless it changed. Code that counted on that extra update, for example to mark a bar as closed, should watch for the next bar instead.Data source marks are one method,
markers.getMarksandgetTimeMarksare one optionalmarkersmethod that takes a request object, and each item says where it goes.getAdjustmentsisadjustments, with the same request object. A data source and an app written for an earlier version need these changes:Before Now Why getMarks(symbol, interval, from, to, signal)markers(symbol, interval, { from, to, signal })A noun like the other source methods, with a request object like history; named after the annotation markers that draw themgetTimeMarks(symbol, interval, from, to, signal)markers(…)returning items withposition: 'timeAxis'One method for both placements getAdjustments(symbol, from, to, signal)adjustments(symbol, { from, to, signal })The same naming and request shape Mark,TimeMark,MarkPositionSourceMarker,SourceMarkerPositionOne item type, with a bar marker's field names tooltip: ['Earnings', 'beat']tooltip: 'Earnings\nbeat'Plain text with \nline breaks, like the other text fields; an array is now dropped as invalidchart.marks,chart.timeMarkschart.sourceMarkersOne getter for both placements marks:loaded,marks:error,marks:changeand thetimemarks:*onessourcemarkers:loaded,sourcemarkers:error,sourcemarkers:changeOne event family, named after the getter; payloads carry markersmark:click,timemark:clicksourcemarkers:clickwith{ markers, point }Its listeners make both bar markers and badges take presses chart.refreshMarks()Report a reset from the source ( onReset)One refetch path for bars and their markers chart.clearMarks()chart.applyOptions({ marks: false, timeMarks: false })With both options off nothing is fetched sourceMarks()plugin,@chart-on/core/source-markssourceMarkers(),@chart-on/core/source-markersNamed after what it fetches error code marks-failed, warningsource-marks-missingmarkers-failed,source-markers-missingThe same ts// Before const source: DataSource = { /* symbol, search, history */ getMarks: async (symbol, interval, from, to, signal) => [{ id: 'e1', time, text: 'E', tooltip: ['Earnings', 'beat'] }], getTimeMarks: async (symbol, interval, from, to, signal) => [{ id: 'n1', time, text: 'N' }], }; chart.on('mark:click', ({ marks }) => open(marks[0]!.id)); chart.refreshMarks(); // Now const source: DataSource = { /* symbol, search, history, onReset */ markers: async (symbol, interval, { from, to, signal }) => [ { id: 'e1', time, text: 'E', tooltip: 'Earnings\nbeat' }, { id: 'n1', time, position: 'timeAxis', text: 'N' }, ], }; chart.on('sourcemarkers:click', ({ markers }) => open(markers[0]!.id)); // In the source, when its events change: for (const listener of resets) listener({ symbol: 'ABC' });Ids are now unique across both placements, since they share one list. The time-axis band appears once the source has sent a
timeAxismarker, rather than whenever it hadgetTimeMarks. With only one ofmarksandtimeMarksoff, markers are still fetched, as before; with both off, none are, andchart.sourceMarkersis empty.Historyis an interface. The undo stack behindchart.historynow comes with the drawings and indicators plugins, the only ones that make undo steps, so a chart without them leaves it out. The exportedHistorytype, which was a class, is now an interface with its public members:canUndo,canRedo,undo(),redo()andclear(). It was only exported as a type, so no app could construct it or testinstanceof History, and calls onchart.historywork as before. Code that declared its own classimplements History, or reached the internal members (record,begin,commit,cancel,mute,drop,last,interrupt) through a cast, needs changing: an implementation needs only the five public members, and the internal ones are no longer reachable. On a chart without drawings or indicators,chart.historyhas nothing to undo:undo()andredo()return false andhistory:changenever fires.Localization is an opt-in plugin. Applying the
localizationoption (createLocalizer:Intlnumbers, digits and grouping, compact amounts, dates and hour cycles,messagesandtranslate) moved into the newlocalization()plugin, so a chart that stays in English leaves theIntlcode out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. An app that picks its own plugins, or passes none, and setslocalization(including the@chart-on/localespacks) adds it:tsimport { createChart, localization } from '@chart-on/core'; import { deDE } from '@chart-on/locales/de-DE'; const chart = createChart(el, { plugins: [localization()], localization: deDE });Without it the chart keeps the built-in English (
chart.localizationisenglishLocalizer), the option is kept and still checked at the call, and development builds warn once (localization-missing).createLocalizerandenglishLocalizerstay public exports, andenglishLocalizeris unchanged in what it returns.The last-point pulse is an opt-in plugin. The ring the
lastPointPulseseries option draws moved into the newlastPointPulse()plugin, so a chart that never pulses leaves that code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. An app that picks its own plugins, or passes none, and setslastPointPulseadds it:tsimport { createChart, lastPointPulse } from '@chart-on/core'; const chart = createChart(el, { plugins: [lastPointPulse()] }); chart.addLine({ lastPointPulse: 'on-update' });Without it the option is kept and checked as before but draws nothing, and development builds warn once (
pulse-missing). Nothing throws.Bar grouping is an opt-in plugin. The bucketing behind the
groupingoption (pixel and calendar buckets, the coarser levels series draw buckets from,chart.groupingandgrouping:change) moved into the newgrouping()plugin, so a chart that never groups bars leaves that code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. An app that picks its own plugins, or passes none, and setsgroupingadds it:tsimport { createChart, grouping } from '@chart-on/core'; const chart = createChart(el, { plugins: [grouping()], grouping: { mode: 'pixel' } });Without it the option is kept and checked as before, but bars draw singly, zooming stops at
minBarSpacing,chart.groupingis null,grouping:changenever fires, and development builds warn once (grouping-missing). Installed later, the view groups bars on the next frame.The kagi and point & figure styles are an opt-in plugin. Drawing a candle series in the
kagiorpointFigurestyle moved into the newpriceStyles()plugin, so a chart that never shows them leaves that code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. An app that picks its own plugins, or passes none, and shows either style adds it:tsimport { createChart, priceStyles } from '@chart-on/core'; const chart = createChart(el, { plugins: [priceStyles()] }); chart.addCandles({ style: 'kagi' });Without it a series in either style draws nothing, and development builds warn once (
price-styles-missing).priceBars,PriceBarBuilderand the other price-based bar types (renko,lineBreak,range) need no plugin.Reference lines are an opt-in plugin.
chart.referenceLinesand the lines thereferenceLinesoption turns on (previous close, visible high, low and average, bid, ask and extended hours) moved into the newreferenceLines()plugin, so a chart that never shows them leaves that code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. An app that picks its own plugins, or passes none, and shows reference lines adds it:tsimport { createChart, referenceLines } from '@chart-on/core'; const chart = createChart(el, { plugins: [referenceLines()], referenceLines: { previousClose: { visible: true } }, });Without it, no line draws, the last-price label always shows the newest close, and reading
chart.referenceLinesthrows anot-registeredChartError. ThereferenceLinesoption is kept, merged per line, and applied when the plugin is installed later (an unknown line or line style still throws at the call); development builds warn once (reference-lines-missing) when it turns a line on.ReferenceLinesanddefaultReferenceLinesOptionsstay public exports.Saving and loading layouts is an opt-in plugin.
chart.serializeLayoutandchart.loadLayoutmoved into the newlayouts()plugin, so a chart that never saves what the user arranged leaves that code out.builtins()includes it and the UI shell and chart grid add it, so apps that passbuiltins()or use the shell need no change. An app that picks its own plugins, or passes none, and saves or restores layouts adds it:tsimport { createChart, layouts } from '@chart-on/core'; const chart = createChart(el, { plugins: [layouts()] }); const saved = chart.serializeLayout();Without it,
serializeLayoutandloadLayoutthrow anot-registeredChartError, soautosavereports each save through itsonErrorandloadLastLayoutrejects.state:changeandlayoutLoadingneed no plugin.LAYOUT_VERSION,migrateLayoutand the layout types stay public exports.Loading through a data source is an opt-in plugin. The data controller behind
chart.setDataSource(symbol and interval loads, history paging, live bars and the snapshot cache) moved into the newdataSource()plugin, so a chart that only gets bars throughsetDataleaves that code out.builtins()includes it and the UI shell and chart grid add it, so apps that passbuiltins()or use the shell need no change. An app that picks its own plugins, or passes none, and attaches a source adds it:tsimport { createChart, dataSource } from '@chart-on/core'; const chart = createChart(el, { plugins: [dataSource()] }); chart.setDataSource(source);Without it,
setDataSource(source)throws anot-registeredChartErrorandchart.datastays null;setDataSource(null)still works. The framework wrappers'dataSourceprop needs it too: passplugins: [dataSource()](orbuiltins()) inoptions, created once. Without it the component does not attach the source and development builds warn once (data-source-missing) instead of throwing. Uninstalling the plugin detaches the source and keeps the bars it loaded.DataControllerstays a public export.The shell's order draft editor, order cluster list and bar replay strip are opt-in parts. They were on by default; like the other optional parts, each shell option now takes what its factory from
@chart-on/uireturns, so a shell without the option leaves the part's code out. Pass them to keep them:tsimport { clusterList, createChartShell, draftEditor, replayControls } from '@chart-on/ui'; createChartShell(el, { // was: on by default, or draftEditor: { quantityPresets, maxQuantity } draftEditor: draftEditor({ quantityPresets, maxQuantity }), // was: on by default clusterList: clusterList(), // was: on by default replayControls: replayControls(), });Where you passed
draftEditor: false,clusterList: falseorreplayControls: false, drop the option. Without a part, its handle (shell.draftEditor,shell.clusterList,shell.replayControls) is null,draft:editandannotations:clusterare left to your own listeners, and a replay runs without the floating strip. The draft editor and the cluster list need thetrading()plugin and the strip needsreplay(); the shell still adds both when your plugin list leaves them out. Development builds warn when an option still holds the old object or boolean.createDraftEditor,createClusterListandcreateReplayControlsare unchanged for use without the shell.The shell's symbol search, command palette, favourites, toasts and toolbar customization are opt-in parts. Each shell option takes what its factory from
@chart-on/uireturns, so a shell without the option leaves the part's code out. Favourites, the command palette and toasts were on by default; pass them to keep them. One line per part:tsimport { commandPalette, createChartShell, favorites, symbolSearch, toasts, toolbarCustomization } from '@chart-on/ui'; createChartShell(el, { // was: toolbar: { items, customizable: true, locked: ['symbol'], settings } toolbar: { items, customize: toolbarCustomization({ locked: ['symbol'] }), settings }, // was: symbolSearch: { source, onSelect } symbolSearch: symbolSearch({ source, onSelect }), // was: on by default, or commandPalette: { sources } commandPalette: commandPalette({ sources }), // was: on by default, or favorites: { tools } favorites: favorites({ tools }), // was: on by default (toasts: true) toasts: toasts(), });Where you passed
favorites: false,commandPalette: falseortoasts: false, drop the option.toolbarCustomization()also goes oncreateToolbarand on a chart grid'stoolbar. Without a part, its handle (shell.symbolSearch,shell.commandPalette,shell.favorites,shell.toasts) is null,symbolHeader()andcommandPaletteToolbarItem()are disabled,toolbar.openCustomization()does nothing andtoolbar.registerAction()throws. Development builds warn when an option still holds the old object or boolean. See optional parts.The built-in watermark is an opt-in plugin.
chart.watermarkand thewatermarkfeature flag moved into the newwatermark()plugin, so a minimal chart leaves the watermark code out.builtins()includes it and the UI shell adds it, so apps that use either need no change. An app that picks its own plugins and shows the watermark adds it:tsimport { createChart, watermark } from '@chart-on/core'; const chart = createChart(el, { plugins: [watermark()], features: { watermark: true } });Without it,
features.watermarkdraws nothing and readingchart.watermarkthrows anot-registeredChartError. Development builds warn once when the flag is set to true (watermark-missing).createWatermarkstays a plain export, so a watermark attached by hand withchart.attachPrimitivekeeps working.Corporate action adjustments are an opt-in plugin. Back-adjusting bars under the
adjustmentoption and marking corporate actions (adjustmentMarkers) moved into the newadjustments()plugin, so a minimal chart leaves that code out.builtins()includes it and the UI shell adds it, so apps that passbuiltins()or use the shell need no change. An app that picks its own plugins and whose source hasadjustmentsadds it (withannotations()for the markers):tsimport { adjustments, createChart } from '@chart-on/core'; const chart = createChart(el, { plugins: [adjustments()], adjustment: 'splits' });Without it, the chart still fetches the source's corporate actions but shows the bars as sent whatever
adjustmentsays, and marks no action. Development builds warn once (adjustments-missing).adjustBarsandadjustBarstay plain exports for bars you push yourself.Data source markers are an opt-in plugin. Fetching the data source's markers (
markers, see the entry on markers above) moved into the newsourceMarkers()plugin, so a minimal chart leaves that code out.builtins()includes it and the UI shell adds it, so apps that passbuiltins()or use the shell need no change. An app that picks its own plugins and serves markers adds it, withannotations()to draw them:tsimport { annotations, createChart, sourceMarkers } from '@chart-on/core'; const chart = createChart(el, { plugins: [annotations(), sourceMarkers()] });Without it, the chart never calls
markers,chart.sourceMarkersstays empty, and no time-axis band is added under the time axis. Development builds warn once (source-markers-missing).The shared WebGL context is an opt-in plugin (#911).
gl: 'shared'draws through the context the newsharedContext()plugin brings, so a chart without it leaves the shared context code out.builtins()includes it, andcreateChartGridadds it to each cell, so apps that passbuiltins()or use the UI shell or grid need no change. An app that setsgl: 'shared'with its own plugin list adds it there:tsimport { createChart, sharedContext } from '@chart-on/core'; const chart = createChart(el, { gl: 'shared', plugins: [sharedContext()] });The context is chosen at creation, so
chart.install(sharedContext())later changes nothing. Without the plugin, agl: 'shared'chart gets its own context,chart.options.glreads'own', and development builds warn once (shared-context-missing).The render statistics are an opt-in plugin (#910). Frame measuring (
renderStats,chart.frameStats,frame:render), therenderStatsOverlaypanel and GPU frame timing moved into the newstatsOverlay()plugin, so a minimal chart leaves their code out.builtins()includes it, and so does the UI shell's default plugin list, so apps that passbuiltins()or use the shell's defaults need no change. An app that picks its own plugins and usesrenderStats,renderStatsOverlay,chart.frameStatsorframe:renderadds it:tsimport { createChart, statsOverlay } from '@chart-on/core'; const chart = createChart(el, { plugins: [statsOverlay()], renderStatsOverlay: true });Without it, both options are kept but measure nothing:
chart.frameStatsstaysnull,frame:rendernever fires and the panel is not drawn (development builds warn once,stats-overlay-missing).chart.gpuMemory()needs no plugin. Plugins get the frame hook the statistics are built on,ctx.onFrame.Trade fills and trading lines are an opt-in plugin. The fill markers and the order, position, alert and draft lines moved out of
annotations()into the newtrading()plugin, so an app that only uses markers, price lines, zones, bands and text labels leaves their code out.builtins()includes it and the UI shell adds it, so apps that passbuiltins()or use the shell need no change. An app that passesannotations()and callssetTrades,addTrade,clearTrades,getTrades,tradeGroups,applyTradeOptions,tradeOptions,addTradeLine,addAlertLine,startOrderDraftorsyncTradesaddstrading()after it:tsimport { annotations, createChart, trading } from '@chart-on/core'; const chart = createChart(el, { plugins: [annotations(), trading()] });Without it, those calls throw a
not-registeredChartErrornamingtrading(). ReadingtradeLines,alertLines,draftLinesoralertsCrosseddoes not throw: without the plugin there are no such lines, so they answer empty. Annotations are never saved in layouts, so a layout loads the same with or without the plugin.Engine internals moved to
@chart-on/core/internal. ThePaneandTimelineclasses,layoutPanesandCOLLAPSED_PANE_HEIGHTare no longer exported from@chart-on/core.PaneandTimelinestay there as types, so code that nameschart.panesorchart.timelinekeeps compiling; only constructing them orinstanceofneeds the new entry. The series draw routinesdrawPolylineanddrawBaseline,resolveBaseLevel, and the volume profile binning stepsbinVolumeandprofileLayoutmoved there too; for a volume profile,computeVolumeProfileon the main entry does both binning steps and reuses anoutprofile. The new entry has no stability promise: what is in it can change in any release.ts// Before import { Pane, Timeline, binVolume, drawPolyline } from '@chart-on/core'; // After import { Pane, Timeline, binVolume, drawPolyline } from '@chart-on/core/internal';Depth lanes are an opt-in plugin.
chart.addDepthLaneneeds the newdepthLane()plugin, so a plaincreateChartbundle leaves the depth lane out.builtins()includes it, and the UI shell passesbuiltins()unless you setchart.plugins, so apps that use either need no change. Otherwise add it:tsimport { createChart, depthLane } from '@chart-on/core'; const chart = createChart(el, { plugins: [depthLane()] });Without it,
chart.addDepthLanethrows anot-registeredChartErrornaming the plugin.createDepthLanewithchart.attachPrimitivekeeps working without the plugin.Lines draw on the GPU by default. Line series, the line-drawn candle styles (
line,lineMarkers,step,area,baseline,hlcArea) and indicator lines and fills now default torenderer: 'gpu'. They draw below every series drawn on the 2D canvas, so azIndexthat put such a line above or below a canvas series (a curved line, a line with per-point colours, a custom series) no longer does. To keep the old drawing, opt out per series or indicator:tschart.addLine({ renderer: 'canvas' }); chart.addCandles({ style: 'area', renderer: 'canvas' }); chart.indicators.add('bbands', {}, { renderer: 'canvas' });Bar replay is an opt-in plugin.
chart.replayneeds the newreplay()plugin. A plaincreateChartbundle leaves the replay code out.builtins()includes it, and the UI shell adds it, so apps that passbuiltins()or use the shell need no change. Otherwise add it:tsimport { createChart, replay } from '@chart-on/core'; const chart = createChart(el, { plugins: [replay()] });Without it,
chart.replaythrows anot-registeredChartErrornaming the plugin. Data loading works as before. Uninstalling it ends a replay in progress and shows every bar again.Indicators and compared symbols are opt-in plugins too.
chart.indicatorsneeds the newindicators()plugin, andchart.compare()the newcomparisons()plugin. A plaincreateChartbundle leaves out the indicator manager, its runners and series, and the compare loaders, about 15% of it.builtins()includes both, and the UI shell adds them, so apps that passbuiltins()or use the shell need no change. Otherwise add what the chart uses:tsimport { comparisons, createChart, indicators } from '@chart-on/core'; const chart = createChart(el, { plugins: [indicators(), comparisons()] });Without them,
chart.indicatorsandchart.compare()throw anot-registeredChartErrornaming the plugin, and so does a plugin'sctx.registerIndicator;chart.comparedis empty. Putindicators()before plugins that register indicator definitions, such ashigherIntervalBars(). A chart without indicators keeps the indicators of a layout it loads, reports them inerrors, and writes them back unchanged when it saves; installing indicators later loads them. Itsaccess.indicatorsandaccess.maxIndicatorsoptions are kept and apply once indicators are installed.Annotations and drawings are opt-in plugins too.
chart.annotationsneeds the newannotations()plugin, andchart.drawingsneeds drawings:drawingTools()as before, or the newdrawings()for the manager with only your own tools. A plaincreateChartbundle leaves out the annotation layers, the drawing manager, its clipboard and commands, about a fifth of it.builtins()includes both, and the UI shell adds them, so apps that passbuiltins()or use the shell need no change. Otherwise add what the chart uses:tsimport { annotations, createChart, drawingTools } from '@chart-on/core'; const chart = createChart(el, { plugins: [annotations(), drawingTools()] });Without them,
chart.annotationsandchart.drawingsthrow anot-registeredChartErrornaming the plugin, and so do a plugin'sctx.registerDrawingToolandperSymbolDrawings. Marks and time marks from the data source, corporate action markers and indicator shape plots draw through annotations: without the plugin they do not show (development builds warn once,annotations-missing). A chart without drawings keeps the drawings and tool defaults of a layout it loads, reports them inerrors, and writes them back unchanged when it saves; installing drawings later loads them. The drawing commands (undo,redo,delete-drawingsand the others) anddraw-<type>come with drawings. The drawings plugins are named@chart-on/core/drawings(installedPlugins,uninstall), and a chart takes one ofdrawings()anddrawingTools().Heatmaps, volume profiles and the built-in drawing tools are opt-in plugins. A chart from
createChartno longer includes them, so bundles that do not use them leave their code out. Pass what a chart uses in itspluginsoption;builtins()restores the previous behaviour:tsimport { builtins, createChart } from '@chart-on/core'; const chart = createChart(el, { plugins: builtins() }); // or some of heatmap(), volumeProfile(), drawingTools(tools)Without them,
addHeatmap,addVolumeProfile, anddrawings.setToolordrawings.addwith a built-in tool throw anot-registeredChartErrorthat names the plugin to add. Saved drawings still load: those of a tool the chart does not have stay in the data and appear once it has it. The UI shell (createChartShell,createChartGridand the React and VueChartShell) passesbuiltins()unless you setchart.plugins, so apps built on it need no change. The React and VueChartcomponents are plain core charts: addpluginsto theiroptionsprop. See Opt-in features.Legend settings buttons.
createIndicatorSettings(and the UI shell) now declares the rows it opens withchart.provideSeriesSettings: indicators whose definition is registered. Once any provider is declared, the legend's settings button and the row menu's Settings… item show only on rows a provider covers. If your app also listens toseries:settingsfor other rows, declare them too, or their buttons disappear:tschart.on('series:settings', ({ series, indicator }) => { if (!indicator) openMySeriesPanel(series); }); chart.provideSeriesSettings((series, indicator) => indicator === null);Without any provider, every row still shows the button while something listens, as before.
chart.openSeriesSettings(series)returns false for rows no provider covers.The tool rail is a toolbar, not a navigation landmark. The shell's drawing tool rail was a
<nav class="pc-rail">; it is now a<div class="pc-rail" role="toolbar" aria-orientation="vertical">with the same label, since its buttons arm tools and take arrow keys rather than lead to other pages. Its look and behaviour do not change. Styles, scripts or tests that find it by its tag or its old role select it by class or by the toolbar role:ts// was: el.querySelector('nav.pc-rail'), or getByRole('navigation', { name: 'Drawing tools' }) el.querySelector('.pc-rail'); // or getByRole('toolbar', { name: 'Drawing tools' })CSS rules on
nav.pc-railbecome.pc-rail. The page no longer has a navigation landmark for the rail.Market status is an opt-in plugin.
chart.marketStatus()and the legend badge of themarketStatusoption (the market phase from thesessionandextendedSessionoptions, and the data delay) moved into the newmarketStatus()plugin, so a chart that shows neither leaves that code out.builtins()includes it, so apps that passbuiltins(), or use the UI shell with its default plugins, need no change. Otherwise add it:tsimport { createChart, marketStatus } from '@chart-on/core'; const chart = createChart(el, { plugins: [marketStatus()] }); chart.marketStatus(); // { phase, next, delay }Without it
chart.marketStatus()throws anot-registeredChartErrorat the call, withcontext{ call: 'marketStatus', plugin: 'marketStatus()' }, and themarketStatusoption is kept, stays on by default and shows no badge. Development builds warn once when the option is set totrueexplicitly (market-status-missing).marketPhase(regular, extended, zone, now)stays a standalone export that needs no plugin. The UI shell does not add the plugin itself: an app that gives the shell its ownchart.pluginslist addsmarketStatus()to that list. Without it the shell's settings dialog leaves out the market status switch.