Custom horizontal scale
A line chart with prices instead of dates on the horizontal scale.
Code
// Lightweight Charts™ Example: Horizontal Price Scale
// https://tradingview.github.io/lightweight-charts/examples/chart-types/custom-horizontal-scale
function markWithGreaterWeight(a, b) {
return a.weight > b.weight ? a : b;
}
/** @type {import('lightweight-charts').IHorzScaleBehavior} */
class HorzScaleBehaviorPrice {
constructor() {
this._options = {};
}
options() {
return this._options;
}
setOptions(options) {
this._options = options;
}
preprocessData(data) {}
updateFormatter(options) {
if (!this._options) {
return;
}
this._options.localization = options;
}
createConverterToInternalObj(data) {
return price => price;
}
key(internalItem) {
return internalItem;
}
cacheKey(internalItem) {
return internalItem;
}
convertHorzItemToInternal(item) {
return item;
}
formatHorzItem(item) {
return item.toFixed(this._precision());
}
formatTickmark(item, localizationOptions) {
return item.time.toFixed(this._precision());
}
maxTickMarkWeight(marks) {
return marks.reduce(markWithGreaterWeight, marks[0]).weight;
}
fillWeightsForPoints(sortedTimePoints, startIndex) {
const priceWeight = price => {
if (price === Math.ceil(price / 100) * 100) {
return 8;
}
if (price === Math.ceil(price / 50) * 50) {
return 7;
}
if (price === Math.ceil(price / 25) * 25) {
return 6;
}
if (price === Math.ceil(price / 10) * 10) {
return 5;
}
if (price === Math.ceil(price / 5) * 5) {
return 4;
}
if (price === Math.ceil(price)) {
return 3;
}
if (price * 2 === Math.ceil(price * 2)) {
return 1;
}
return 0;
};
for (let index = startIndex; index < sortedTimePoints.length; ++index) {
sortedTimePoints[index].timeWeight = priceWeight(
sortedTimePoints[index].time
);
}
}
_precision() {
return this._options.localization.precision;
}
}
const horzItemBehavior = new HorzScaleBehaviorPrice();
const chartOptions = {
layout: {
textColor: 'black',
background: { type: 'solid', color: 'white' },
},
grid: {
vertLines: { color: '#D6DCDE' },
horzLines: { color: '#D6DCDE' },
},
localization: {
precision: 2, // custom option
},
};
/** @type {import('lightweight-charts').IChartApi} */
const chart = createChartEx(
document.getElementById('container'),
horzItemBehavior,
chartOptions
);
const lineSeries = chart.addSeries(LineSeries, { color: '#2962FF' });
const data = [];
for (let i = 0; i < 5000; i++) {
data.push({
time: i * 0.25,
value: Math.sin(i / 100) + i / 500,
});
}
lineSeries.setData(data);
chart.timeScale().fitContent();
The code expects the library’s exports, such as createChart, to be imported and a div with the id container on the page. Copy as HTML gives a page that runs on its own.
How it works
By default the horizontal scale holds time values. An IHorzScaleBehavior tells the chart how to handle other values instead, such as the prices an options chart needs.
- A class implements
IHorzScaleBehaviorfor numbers. The example'sHorzScaleBehaviorPriceis explained in full below. createChartExcreates the chart with an instance of that class instead of the default time behavior.- The series data uses numbers as
time.
For the built-in price-based scale, use createOptionsChart instead. Implement your own behavior when you need other values or formatting.
The IHorzScaleBehavior interface
The IHorzScaleBehavior interface consists of several methods that you need to
implement to customize the horizontal scale behavior. Here's a breakdown of each
method and its purpose:
options
public options(): ChartOptionsImpl<HorzScaleItem>
This method returns the chart's current configuration options. These options include various settings that control the appearance and behavior of the chart. Implement this method to return the current options of your horizontal scale behavior.
setOptions
public setOptions(options: ChartOptionsImpl<HorzScaleItem>): void
This method allows you to set or update the chart's configuration options. The
provided options parameter will contain the settings you want to apply. Use
this method to update the options when necessary.
preprocessData
public preprocessData(data: DataItem<HorzScaleItem> | DataItem<HorzScaleItem>[]): void
This method processes the series data before it is used by the chart. It receives an array of data items or a single data item. You can implement this method to preprocess or modify data as needed before it is rendered.
updateFormatter
public updateFormatter(options: LocalizationOptions<HorzScaleItem>): void
This method updates the formatter used for displaying the horizontal scale items based on localization options. Implement this to set custom formatting settings, such as locale-specific date or number formats.
createConverterToInternalObj
public createConverterToInternalObj(data: SeriesDataItemTypeMap<HorzScaleItem>[SeriesType][]): HorzScaleItemConverterToInternalObj<HorzScaleItem>
This method creates and returns a function that converts series data items into internal horizontal scale items. Implementing this method is essential for transforming your custom data into the format required by the chart's internal mechanisms.
key
public key(internalItem: InternalHorzScaleItem | HorzScaleItem): InternalHorzScaleItemKey
This method returns a unique key for a given horizontal scale item. It's used internally by the chart to identify and manage items uniquely. Implement this method to provide a unique identifier for each item.
cacheKey
public cacheKey(internalItem: InternalHorzScaleItem): number
This method returns a cache key for a given internal horizontal scale item. This key helps the chart to cache and retrieve items efficiently. Implement this method to return a numeric key for caching purposes.
convertHorzItemToInternal
public convertHorzItemToInternal(item: HorzScaleItem): InternalHorzScaleItem
This method converts a horizontal scale item into an internal item that the chart can use. Implementing this method ensures that your custom data type is correctly transformed for internal use.
formatHorzItem
public formatHorzItem(item: InternalHorzScaleItem): string
This method formats a horizontal scale item into a display string. The returned string will be used for displaying the item on the chart. Implement this method to format your items in the desired way (e.g., with a specific number of decimal places).
formatTickmark
public formatTickmark(item: TickMark, localizationOptions: LocalizationOptions<HorzScaleItem>): string
This method formats a horizontal scale tick mark into a display string. The tick mark represents significant points on the horizontal scale. Implement this method to customize how tick marks are displayed.
maxTickMarkWeight
public maxTickMarkWeight(marks: TimeMark[]): TickMarkWeightValue
This method determines the maximum weight for a set of tick marks, which influences their display prominence. Implement this method to specify the weight of the most significant tick mark.
fillWeightsForPoints
public fillWeightsForPoints(sortedTimePoints: readonly Mutable<TimeScalePoint>[], startIndex: number): void
This method assigns weights to the sorted time points. These weights influence the tick marks' visual prominence. Implement this method to provide a weighting system for your horizontal scale items.
Implementing a price-based scale
This is the implementation the example uses: a horizontal scale of prices, with a custom option for the number of decimal places.
- Define the custom localization options interface
Extend the LocalizationOptions
interface to include a precision property.
export interface CustomLocalizationOptions
extends LocalizationOptions<HorzScalePriceItem> {
precision: number;
}
- Define the type alias
Define a type alias for the horizontal scale item representing price values.
export type HorzScalePriceItem = number;
- Implement the custom horizontal scale behavior class
The HorzScaleBehaviorPrice class implements the IHorzScaleBehavior
interface, with additional logic to handle the precision provided in the custom
localization options.
function markWithGreaterWeight(a: TimeMark, b: TimeMark): TimeMark {
return a.weight > b.weight ? a : b;
}
export class HorzScaleBehaviorPrice implements IHorzScaleBehavior<HorzScalePriceItem> {
private _options!: ChartOptionsImpl<HorzScalePriceItem>;
public options(): ChartOptionsImpl<HorzScalePriceItem> {
return this._options;
}
public setOptions(options: ChartOptionsImpl<HorzScalePriceItem>): void {
this._options = options;
}
public preprocessData(
data: DataItem<HorzScalePriceItem> | DataItem<HorzScalePriceItem>[]
): void {
// un-needed in this example because we do not require any additional
// data processing for this scale.
// The method is still required to be implemented in the class.
}
public updateFormatter(options: CustomLocalizationOptions): void {
if (!this._options) {
return;
}
this._options.localization = options;
}
public createConverterToInternalObj(
data: SeriesDataItemTypeMap<HorzScalePriceItem>[SeriesType][]
): HorzScaleItemConverterToInternalObj<HorzScalePriceItem> {
return (price: number) => price as unknown as InternalHorzScaleItem;
}
public key(
internalItem: InternalHorzScaleItem | HorzScalePriceItem
): InternalHorzScaleItemKey {
return internalItem as InternalHorzScaleItemKey;
}
public cacheKey(internalItem: InternalHorzScaleItem): number {
return internalItem as unknown as number;
}
public convertHorzItemToInternal(
item: HorzScalePriceItem
): InternalHorzScaleItem {
return item as unknown as InternalHorzScaleItem;
}
public formatHorzItem(item: InternalHorzScaleItem): string {
return (item as unknown as number).toFixed(this._precision());
}
public formatTickmark(
item: TickMark,
localizationOptions: LocalizationOptions<HorzScalePriceItem>
): string {
return (item.time as unknown as number).toFixed(this._precision());
}
public maxTickMarkWeight(marks: TimeMark[]): TickMarkWeightValue {
return marks.reduce(markWithGreaterWeight, marks[0]).weight;
}
public fillWeightsForPoints(
sortedTimePoints: readonly Mutable<TimeScalePoint>[],
startIndex: number
): void {
const priceWeight = (price: number) => {
if (price === Math.ceil(price / 100) * 100) {
return 8;
}
if (price === Math.ceil(price / 50) * 50) {
return 7;
}
if (price === Math.ceil(price / 25) * 25) {
return 6;
}
if (price === Math.ceil(price / 10) * 10) {
return 5;
}
if (price === Math.ceil(price / 5) * 5) {
return 4;
}
if (price === Math.ceil(price)) {
return 3;
}
if (price * 2 === Math.ceil(price * 2)) {
return 1;
}
return 0;
};
for (let index = startIndex; index < sortedTimePoints.length; ++index) {
sortedTimePoints[index].timeWeight = priceWeight(
sortedTimePoints[index].time as unknown as number
);
}
}
private _precision(): number {
return (this._options.localization as CustomLocalizationOptions).precision;
}
}
This class provides additional precision control through localization options, allowing formatted price values to use a specific number of decimal places.
Using it with createChartEx
To use the custom horizontal scale behavior, instantiate the
HorzScaleBehaviorPrice class and pass it to
createChartEx.
You can pass the custom option for precision within the localization
property of the chart options.
const horzItemBehavior = new HorzScaleBehaviorPrice();
const chart = LightweightCharts.createChartEx(container, horzItemBehavior, {
localization: {
precision: 2, // custom option
},
});
const s1 = chart.addSeries(LightweightCharts.LineSeries);
const data = [];
for (let i = 0; i < 5000; i++) {
data.push({
time: i * 0.25,
value: Math.sin(i / 100),
});
}
s1.setData(data);