Skip to main content
← Back to examples

Custom horizontal scale

Chart types

A line chart with prices instead of dates on the horizontal scale.

Interactive example — scroll, zoom, and hover the chart.

Code​

View on GitHub
// 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.

  1. A class implements IHorzScaleBehavior for numbers. The example's HorzScaleBehaviorPrice is explained in full below.
  2. createChartEx creates the chart with an instance of that class instead of the default time behavior.
  3. 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.

  1. Define the custom localization options interface

Extend the LocalizationOptions interface to include a precision property.

export interface CustomLocalizationOptions
extends LocalizationOptions<HorzScalePriceItem> {
precision: number;
}
  1. Define the type alias

Define a type alias for the horizontal scale item representing price values.

export type HorzScalePriceItem = number;
  1. 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);

Resources​