Lightweight Charts™

repository·master·Indexed 12 days ago

https://github.com/tradingview/lightweight-charts

A high-performance, small-footprint HTML5 canvas charting library designed for displaying financial data. Version 5.2.1 features a plugin system for extending functionality, including support for custom series, drawing primitives, and an accessibility plugin to meet WCAG 2.1 Level AA standards.

Tokens
80.1K
Snippets
241
Records
313
Agent score
96%

What's inside Lightweight Charts

  1. Overview of Lightweight Charts Plugins

    master

    Plugins allow you to extend the library's functionality to render custom elements like new series types, drawing tools, indicators, and watermarks.

    There are two primary ways to extend the library:

    1. Custom series: Define entirely new types of series with unique data structures and rendering logic.
    2. Primitives: Define custom visualizations, drawing tools, or annotations. Primitives are categorized by where they are attached:
      • Series primitives: Attached to a specific series; can render on the main pane, price scales, or time scales.
      • Pane primitives: Attached to a chart pane; used for chart-wide features like watermarks. Note that pane primitives cannot render on the price or time scales.

    Pro-tips:

    • Use the create-lwc-plugin npm package to scaffold a new plugin project.
    • Reference the Plugin Examples Demo for implementations of heatmaps, alerts, watermarks, and tooltips.
  2. Understand the Price Scale concept

    master

    A price scale (or price axis) is a vertical scale that maps prices to coordinates and vice versa. The conversion rules for mapping prices to the Y-axis depend on the price scale mode, the chart's height, and the visible range of the data.

    By default, every chart includes two visible price scales:

    1. Left price scale
    2. Right price scale

    You can also create an unlimited number of overlay price scales. These are hidden in the UI by default and allow you to plot series (like Volume) that have values significantly different from the main price data without affecting the existing visible scales.

  3. Attribution Requirements

    master
    The library is licensed under Apache License 2.0. When using the library, you must specify TradingView as the product creator and include an attribution notice. You can satisfy the link requirement by using the attributionLogo chart option within the LayoutOptions interface to display an appropriate link to TradingView on the chart itself.
  4. How to encapsulate Lightweight Charts™ using Shadow DOM

    master

    When wrapping Lightweight Charts™ in a Web Component, using the Shadow DOM is recommended to prevent styles from leaking in or out.

    • Use this.attachShadow({ mode: 'open' }) inside connectedCallback to create the shadow root.
    • All chart-related DOM elements (like the container div) and <style> tags must be appended to this.shadowRoot rather than document.body.
    • Use the :host CSS selector within your component's styles to control the dimensions and visibility of the custom element itself (e.g., setting :host { display: block; } to ensure it respects layout dimensions).
  5. How data update announcements work

    master

    When streaming live data, multiple panes may update simultaneously. To prevent screen readers from overlapping, all data-update announcements are routed through a single polite live region shared by the entire chart.

    Use announceDataUpdates in the addAccessibilityPlugin configuration to control which panes trigger these announcements:

    • 'active' (default): Only the last-focused pane announces updates. If no pane is focused, pane 0 is the default.
    • true: Every pane announces; simultaneous updates are combined into a single message in pane order (e.g., "Chart data updated. 3 series changed. ...").
    • false: No update announcements are made.
    • (paneIndex) => boolean: A function to decide per-pane if they should announce. Enabled panes' messages are combined.

    Note: You must use the chart-level controller (accessibility.applyOptions) to change this setting at runtime. Per-pane plugin.applyOptions cannot modify the shared update region.

  6. Architecting a component-based React wrapper for Lightweight Charts™

    master

    When building a complex React application with Lightweight Charts™, you should avoid a single monolithic component. Instead, use a component-based architecture where a Chart component acts as a container for multiple Series child components.

    The Lifecycle Challenge

    In a standard parent-child React setup, useEffect hooks run in a bottom-up order during instantiation but a top-down order during cleanup. This can cause issues where a Series component attempts to interact with a Chart instance that hasn't been fully initialized or has already been cleaned up.

    To ensure reliable interaction between components (e.g., adding data to a series or resizing the chart), use a combination of refs, useImperativeHandle, and React.Context:

    1. Chart Container: Create a parent component that manages the chart's lifecycle (creation and cleanup). It should provide a DOM element for rendering.
    2. Internal Reference: Use useRef to store an object containing methods for managing the chart and series (e.g., createSeries, removeSeries, resize).
    3. Exposing API: Use useImperativeHandle to expose these internal methods to parent components via refs.
    4. Propagating Access: Use React.Context.Provider to pass the internal reference object down the component tree. This allows any descendant Series component to access the chart instance and its methods directly without prop-drilling.

    This structure ensures that even if components are instantiated in a specific order, they can always access the necessary chart instance through the shared context or refs.

    import React, { useEffect, useImperativeHandle, useRef, createContext, forwardRef } from 'react';
    
    const Context = createContext();
    
    export const ParentComponent = forwardRef((props, ref) => {
        const internalRef = useRef({
            method1() {
                // Responsible for creating the chart
            },
            methodn() {
                // Responsible for cleaning up the chart
            },
        });
    
        useImperativeHandle(ref, () => {
            // Exposes part of/entirety of internalRef
        }, []);
    
        return (
            <Context.Provider value={internalRef.current}>
                {props.children}
            </Context.Provider>
        );
    });
    
    export const ChildComponent = forwardRef((props, ref) => {
        const internalRef = useRef({
            method1() {
                // Responsible for creating a series
            },
            methodn() {
                // Responsible for removing it
            },
        });
    
        useImperativeHandle(ref, () => {
            // Exposes part of/entirety of internalRef
        }, []);
    
        return (
            <Context.Provider value={internalRef.current}>
                {props.children}
            </Context.Provider>
        );
    });
  7. Understand the CanvasRenderingTarget2D interface

    master

    When developing plugins (such as Custom Series or Drawing Primitives), the renderer functions are provided with a CanvasRenderingTarget2D interface. This interface is used to execute drawing logic via the Browser's 2D Canvas API.

    CanvasRenderingTarget2D is provided by the fancy-canvas library and offers two distinct rendering scopes: useMediaCoordinateSpace and useBitmapCoordinateSpace.

  8. Understand Lightweight Charts Terminology

    master

    To effectively use the API for customization, familiarize yourself with these core concepts:

    • Data Series (aka data/dataset): A collection of data points representing a specific metric over time.
    • Series Type: Specifies the visual representation of data (e.g., a line series connects data points with straight segments).
    • Series: The combination of a specific Series Type and a Data Series.
    • Price Scale: The vertical axis (price axis) used to map prices to chart coordinates.
    • Time Scale: The horizontal axis (time axis) at the bottom of the chart displaying the time of bars.
    • Crosshair: The thin vertical and horizontal lines that center on a data point during interaction.
  9. Implement a custom horizontal scale with IHorzScaleBehavior

    master

    The IHorzScaleBehavior interface allows you to override the default horizontal scale (which uses Time values) to use custom types, such as price values. This is useful for creating Options charts or other non-time-based horizontal axes.

    To implement a custom scale, you must provide a class that implements the following core methods:

    • options(): Returns the current ChartOptionsImpl<HorzScaleItem>.
    • setOptions(options): Updates the current configuration options.
    • preprocessData(data): Processes series data before rendering.
    • updateFormatter(options): Updates the formatter based on LocalizationOptions.
    • createConverterToInternalObj(data): Returns a function to convert series data into internal horizontal scale items.
    • key(internalItem): Returns a unique identifier for an item.
    • cacheKey(internalItem): Returns a numeric key for caching.
    • convertHorzItemToInternal(item): Converts a custom scale item to an internal format.
    • formatHorzItem(item): Formats an internal item into a display string.
    • formatTickmark(item, localizationOptions): Formats a tick mark into a display string.
    • maxTickMarkWeight(marks): Determines the maximum weight for a set of tick marks.
    • fillWeightsForPoints(sortedTimePoints, startIndex): Assigns visual prominence weights to points.

    Example use case: Creating a price-based horizontal scale with customizable decimal precision.

    // Example of the interface structure
    export class MyCustomScale implements IHorzScaleBehavior<MyCustomItem> {
        public options(): ChartOptionsImpl<MyCustomItem> { /* ... */ }
        public setOptions(options: ChartOptionsImpl<MyCustomItem>): void { /* ... */ }
        public preprocessData(data: DataItem<MyCustomItem> | DataItem<MyCustomItem>[]): void { /* ... */ }
        public updateFormatter(options: LocalizationOptions<MyCustomItem>): void { /* ... */ }
        public createConverterToInternalObj(data: SeriesDataItemTypeMap<MyCustomItem>[SeriesType][]): HorzScaleItemConverterToInternalObj<MyCustomItem> { /* ... */ }
        public key(internalItem: InternalHorzScaleItem | MyCustomItem): InternalHorzScaleItemKey { /* ... */ }
        public cacheKey(internalItem: InternalHorzScaleItem): number { /* ... */ }
        public convertHorzItemToInternal(item: MyCustomItem): InternalHorzScaleItem { /* ... */ }
        public formatHorzItem(item: InternalHorzScaleItem): string { /* ... */ }
        public formatTickmark(item: TickMark, localizationOptions: LocalizationOptions<MyCustomItem>): string { /* ... */ }
        public maxTickMarkWeight(marks: TimeMark[]): TickMarkWeightValue { /* ... */ }
        public fillWeightsForPoints(sortedTimePoints: readonly Mutable<TimeScalePoint>[], startIndex: number): void { /* ... */ }
    }
  10. Understand the visual stacking order of series

    master

    When multiple series are added to a single chart, the order of calls to addSeries() determines their visual stacking order (Z-index).

    • The first series added will appear at the bottom of the stack.
    • Each subsequent series added will be placed on top of the previous ones.

    Example Scenario: If you want an area series to appear as a background behind candlesticks, you must call chart.addSeries(AreaSeries, ...) before calling chart.addSeries(CandlestickSeries, ...).

  11. Handle time zones manually in Lightweight Charts™

    master

    Lightweight Charts™ does not natively support time zones; it processes all date and time values in UTC. To display data in a specific time zone, you must manually adjust each bar's timestamp in your dataset so that the UTC timestamp corresponds to the local time in the target time zone.

    Example: To display a data point with UTC timestamp 2021-01-01T10:00:00.000Z in the Europe/Moscow time zone (UTC+03:00), you must add 3 hours to the timestamp, resulting in 2021-01-01T13:00:00.000Z.

    Important Considerations:

    • Adding an offset can change both the time and the date.
    • Offsets vary due to Daylight Saving Time (DST) or regional adjustments.
    • If your data is measured in business days without a time component, you generally should not adjust it to a time zone.
    // Conceptual logic: 
    // Original UTC: 2021-01-01T10:00:00.000Z
    // Target TZ: Europe/Moscow (UTC+03:00)
    // Adjusted Timestamp: 2021-01-01T13:00:00.000Z