# LightningChart JS developer documentation > The 1 stop developer guide for all things LightningChart JS This file contains all documentation content in a single document following the llmstxt.org standard. ## API A full LightningChart JS API reference is available under a separate resource. This is intended for understanding specific library methods, properties and exports at a detailed level as well as finding their relations to other entries. Please find the API reference [here](https://lightningchart.com/js-charts/api-documentation). --- ## Contact If you are a **commercial trial user**, you can take advantage of your special privileges for both technical and commercial queries. Learn more about [Trials and Integration](/services/trials-and-integration). On the other hand, if you have already finished your trial and are a **commercial customer**, please see [Support](/services/support). Non-commercial users don't have a dedicated contact channel, but you can post general questions and issues in [Stack Overflow](https://stackoverflow.com/questions/tagged/lightningchart). These are checked by LightningChart maintainers from time to time. --- ## Online examples Interactive Examples shows more realistic use cases than the simple snippets found in product and API docs. You can also see and experiment with the application code in there. Find the Interactive Examples [here](https://lightningchart.com/lightningchart-js-interactive-examples/). ![Interactive examples](/img/ie-light.png#light-mode-only)![Interactive examples](/img/ie-dark.png#dark-mode-only) Browsing through Interactive Examples can be a effective way to identify existing applications that have similarities to your own use case. However, not even remotely all possible use cases of LightningChart JS can be found as Interactive Examples. To help find out whether LightningChart JS can work in your use case, you can check out the [Features list](/features), or even better, [ask us directly!](/contact) --- ## Axis Here you can find guides for most often required configurations of Axis, a critical part of almost all chart types. ## Accessing axis This depends on the type of chart. ```ts const axisX = ChartXY.axisX const axisY = ChartXY.axisY const axisZ = Chart3D.axisZ const radialAxis = PolarChart.radialAxis const amplitudeAxis = PolarChart.amplitudeAxis const valueAxis = BarChart.valueAxis const categoryAxis = BarChart.categoryAxis ``` ## Axis title ```ts axis .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For `ChartXY`, `setTitlePosition` and `setTitleRotation` are also available. See [API documentation](/api) for more details. For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Axis interval / view By default, any axis automatically adjusts its "interval" [start - end] to display all series connected to it. The start-end interval can be explicitly set using the `setInterval` or `setDefaultInterval` methods. ```ts // Example, show interval between 0 and 100 axis.setInterval({ start: 0, end: 100 }) ``` By using `setDefaultInterval` this configured interval is also restored if the user triggers the "zoom to fit" interaction (double click chart area by default). ```ts axis.setDefaultInterval({ start: 0, end: 100 }) ``` ## Numeric axis Display numeric values using automatically placed axis ticks (`10`, `20`, `30`, etc.). This is the default axis behavior. ```ts axis.setTickStrategy(AxisTickStrategies.Numeric) ``` ![Chart with numeric axis](/img/1-million-point-line-trace-light.png#light-mode-only)![Chart with numeric axis](/img/1-million-point-line-trace-dark.png#dark-mode-only) ### Extreme ticks By default, ticks are placed on logical key values (e.g. 0, 10, 20, 30, ...) depending on active axis interval. Sometimes, with physically small charts you may get problems from not having enough ticks visible. One solution for this is to enable "extreme ticks", this simply means always displaying the axis start and end values, even if they are not "logical key values". ```ts axis.setTickStrategy(AxisTickStrategies.Numeric, (ticks) => ticks // Enable extreme ticks .setExtremeTickStyle(ticks.getMajorTickStyle()) ) ``` Even if extreme ticks are not enabled, in some cases they can be displayed as an automatic fallback if otherwise a small Numeric axis would only display 1 tick label. This behavior can be prevented with: ```ts axis.setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy .setFallBackToExtremeTicksAutomatically(false) ) ``` ## Date-Time axis Display seconds, minutes, hours, days, months and years using automatically placed axis ticks. ```ts axis.setTickStrategy(AxisTickStrategies.DateTime) ``` ![Chart with date-time axis](/img/date-time-axis-light.png#light-mode-only)![Chart with date-time axis](/img/date-time-axis-dark.png#dark-mode-only) Date-time axis intervals are defined as UTC timestamps, meaning timestamp = milliseconds since [1st January 1970](https://developer.mozilla.org/en-US/Web/JavaScript/Reference/Global_Objects/Date). ```ts axis.setInterval({ start: Date.UTC(2022, 0, 1), // 1st Jan 2022 end: Date.UTC(2022, 0, 31) // 31st Jan 2022 }) ``` ### Zoom ability By default, you can't zoom in closer than around **1 day range**. To improve on this, you can enable so called "high precision axis", which enables zooming to 1 millisecond range: ```ts const chart = lc.ChartXY({ defaultAxisX: { type: 'linear-highPrecision' } }) ``` If you need to display even smaller time steps, microseconds or nanoseconds, see [Nanosecond timestamps](/more-guides/nanosecond-timestamps/) ### Show year / month / day In some use cases you might want to have an additional label displaying for example what year or month is in question. One way of doing this is by enabling great ticks: ```ts chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, (strategy) => strategy.setGreatTickStyle(strategy.getMajorTickStyle().setTickLength(28).setTickStyle(emptyLine)), ) ``` These are automatically displayed on relevant zoom levels with just one (or two) extra labels so you don't have to display year/month/day on every tick label. ### Custom formatting Commonly used alternate formatting scheme of formatting every tick with "HH:MM:SS:MMM": ```ts const t1s = 1000 const t1m = 60 * 1000 const t1h = 60 * 60 * 1000 const t1d = 24 * 60 * 60 * 1000 const formatterX = (x) => { const hour = Math.floor((x % t1d) / t1h).toString().padStart(2, '0') const minute = Math.floor((x % t1h) / t1m).toString().padStart(2, '0') const second = Math.floor((x % t1m) / t1s).toString().padStart(2, '0') const millis = Math.floor((x % t1s)).toString().padStart(3, '0') return `${hour}:${minute}:${second}.${millis}` } chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, strategy => strategy .setFormatting(formatterX, formatterX, formatterX) .setCursorFormatter(formatterX) ) ``` Different formatting schemes can be applied by simply acting differently in the formatter logic based on current Axis interval (`Axis.getInterval()`) OR separately defining formatter functions for different zoom levels (`setFormattingDay`, `setFormattingHour`, etc.). ### Modifying default formatting without full override Built-in date-time formatting is based on `Intl.DateTimeFormat`, so it is possible to add global overrides - for example, adding `timeZone: "GMT"` to all zoom levels: ```ts chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, strategy => { const keys = Object.keys(strategy.toJS()) return strategy.withMutations((mutable) => { mutable.set('utc', true) // align ticks by GMT+0 rather than client timezone mutable.set('cursorFormatter', (x) => new Intl.DateTimeFormat(undefined, { timeZone: 'GMT', hour: '2-digit'}).format(x)) keys.forEach((key) => { if (!key.includes('formatOptions')) return const prevValue = strategy[key] if (typeof prevValue === 'function') return mutable.set(key, { ...prevValue, timeZone: 'GMT' }) }) }) }) ``` ## Time axis Display passing of time since a reference point using automatically placed axis ticks. ```ts axis.setTickStrategy(AxisTickStrategies.Time) ``` ![Chart with time axis](/img/time-axis-light.png#light-mode-only)![Chart with time axis](/img/time-axis-dark.png#dark-mode-only) Time axis intervals are defined as milliseconds. ```ts // Example, show range of 10 seconds axis.setInterval({ start: 0, end: 10_000 }) ``` Time axis is well suited to display "run-time", as in how long the application has been running: ```ts setInterval(() => { lineSeries.appendSample({ x: performance.now(), y: Math.random() }) }, 1000 / 60) ``` Alternatively, you can input UTC timestamps and specify which timestamp is displayed as 00:00:00: ```ts axis.setTickStrategy(AxisTickStrategies.Time, (ticks) => ticks.setTimeOrigin(-Date.now())) setInterval(() => { lineSeries.appendSample({ x: Date.now(), y: Math.random() }) }, 1000 / 60) ``` ### Custom formatting ```ts const formatMilliseconds = (ms) => { const seconds = Math.floor(ms / 1000); const milliseconds = Math.round(ms % 1000); return `${seconds.toString().padStart(2, '0')}:${milliseconds.toString().padStart(3, '0')}`; } chart.axisX.setTickStrategy(AxisTickStrategies.Time, strategy => strategy .setFormattingFunction((x, range) => formatMilliseconds(x)) ) ``` Custom formatting can be adjusted to active zoom level by referring to supplied `range` parameter. If needed, cursor formatting can be separately configured with `setCursorFormatter` method. ## Disable ticks ```ts axis.setTickStrategy(AxisTickStrategies.Empty) ``` ## Categorical axis The recommended way to realize categorical axis is found below: ```ts const categories = ['A', 'B', 'C', 'D', 'E'] chart.axisY .setDefaultInterval({ start: 0, end: categories.length - 1 }) .setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy .setCustomTickPlacement(() => categories.map((_, i) => ({ position: i }))) .setFormattingFunction((pos) => categories[Math.round(pos)] ?? '') .setCursorFormatter((pos) => categories[Math.round(pos)] ?? ''), ) ``` Y values would then correspond to categories like: - 0 => 'A' - 1 => 'B' - ... ## Custom tick placement logic If the different available built-in tick placement logics (numeric, time, datetime) don't suit your use case, you can override with your own. This logic can be done: - For 1 tick level only (major ticks generally). In this case, minor ticks or other tick levels are not shown. - For each tick level separately (i.e. major & minor) ```ts // Example, always show exactly 10 gridlines chart.axisY.setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy.setCustomTickPlacement((info) => new Array(10 + 1) .fill(0) .map((_, i) => ({ position: info.interval.start + (i / 10) * (info.interval.end - info.interval.start) })), ), ) ``` ```ts // Example, always show exactly 10 major gridlines and 5 minor gridlines between each chart.axisY.setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy.setCustomTickPlacement({ major: (info) => new Array(10 + 1) .fill(0) .map((_, i) => ({ position: info.interval.start + (i / 10) * (info.interval.end - info.interval.start) })), minor: (info) => { const majorStep = (info.interval.end - info.interval.start) / 10 const minorStep = majorStep / 5 const minorTicks: CustomTickPlacementResult[] = [] let step = 0 while (true) { const position = info.interval.start + step * minorStep if (Math.sign(info.interval.end - position) !== Math.sign(info.interval.end - info.interval.start)) break if (step % 5 === 0) { // Dont add minor tick here, would overlap with major tick } else { minorTicks.push({ position }) } step++ } return minorTicks }, }), ) ``` ## Custom ticks Independent, single axis ticks can be placed at any location by creating a "custom tick" object: ```ts const tick = chart.axisX.addCustomTick() .setValue(10) .setTextFormatter(_ => `Hello`) .setMarker(marker => marker .setTextFont(font => font.setWeight('bold')) .setTextFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ) ``` Custom ticks are available currently in XY, 3D and Parallel coordinate charts. In XY charts you can also show a different looking custom tick, so called "Pointable text box": ```ts const tick = chart.axisX.addCustomTick(UIElementBuilders.PointableTextBox) .setValue(10) .setTextFormatter(_ => `Hello`) .setMarker(marker => marker .setTextFont(font => font.setWeight('bold')) .setTextFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ) ``` ## Scrolling axis ```ts // Example, configure a scrolling axis that keeps interval with length: `10_000` chart.axisX .setScrollStrategy(AxisScrollStrategies.scrolling) .setDefaultInterval((state) => ({ end: state.dataMax ?? 0, start: (state.dataMax ?? 0) - 10_000, stopAxisAfter: false, })) ``` This results in the axis automatically revealing new data as it is spawned, but keeping the size of axis interval unchanged - resulting in older data going out of view. Axes can be configured to scroll in any direction. See [Scrolling to different directions](/more-guides/different-scroll-directions/) for more details. ### Real-time scrolling axis Often data arrives in applications in batches, say every 1 second, even though data points are monitored 100s per second. This is generally done to optimize data transfers. For this kind of use cases, scrolling automatically as time passed on should be enabled for a smoother display of incoming data: ```ts chart.axisX .setScrollStrategy(AxisScrollStrategies.scrolling({ realTime: true // (!) })) .setDefaultInterval((state) => ({ end: state.dataMax ?? 0, start: (state.dataMax ?? 0) - 10_000, stopAxisAfter: false, })) ``` By default, real time scrolling is configured for data points arriving around once every second. If data points arrive significantly more infrequently (e.g. every 5 seconds, 10 seconds, 1 minute, ...) then you should configure catch up threshold: ```ts axis.setScrollStrategy(AxisScrollStrategies.scrolling({ realTime: { catchUpThresholdMs: 10_000 } // (!) })) .setDefaultInterval((state) => ({ end: state.dataMax ?? 0, start: (state.dataMax ?? 0) - 10_000, stopAxisAfter: false, })) ``` This value is required to cope with situations where data flow is interrupted, and later continues, so that axis can "catch up" to live data. Alternatively, catching up can be disabled by setting the threshold value to an extremely large number. In cases where data flow may not be 100% stable, it can be useful to configure the time axis to scroll slightly _behind_ real-time so that latency variations are not displayed in any way: ```ts axis.setScrollStrategy(AxisScrollStrategies.scrolling({ realTime: { catchUpThresholdMs: 10_000 } })) .setDefaultInterval((state) => ({ end: (state.dataMax ?? 0) - 2_000, // (!) start: (state.dataMax ?? 0) - 12_000, // (!) stopAxisAfter: false, })) ``` ## Axis fitting By default, axes set their view to contain all data in visible range. e.g. imagine case with time X axis, and some value Y axis, the Y axis will adjust to show min-max range of all data in the visible time range. :::note The default axis behavior is to fit against visible data only, but actually only `PointLineAreaSeries` supports this. With other series, the behavior is always equal to `considerVisibleRangeOnly: false` ::: ### Fitting to whole data set rather than only visible data Recommended in use cases where the visible range of data fluctuates a lot, or for data exploration applications if user prefers that the Y axis remains static during zooming in/out etc. ```ts chart.axisY.setScrollStrategy( AxisScrollStrategies.fitting({ considerVisibleRangeOnly: false, }), ) ``` ### Fitting only one side of axis ```ts // Example, keep axis `start` always at 0 while fitting `end` side against visible data. chart.axisY .setScrollStrategy(AxisScrollStrategies.fitting({ start: false })) .setInterval({ start: 0, end: 1, stopAxisAfter: false }) ``` ## Controlling spacing around edges of axis By default, automatic scroll margins are enabled, which means the actual value is based on attached series and their style. For example, in case of point series, the scroll margins are set to half of point size. This can be controlled with `setScrollMargins` method: ```ts // Example, 20 pixels of empty space in upper series area chart.axisY.setScrollMargins({ start: 0, end: 20 }) ``` :::warning In this context, scrolling refers to **any** automatic axis adjusting to attached series. If axis interval is explicitly set with `setInterval` or `setDefaultInterval`, then scroll margins are **not** applied. ::: ## Axis units When you know what _Units_ your axis represents you can utilize `Axis.setUnits(unit: string)`, which might make your application development a little bit easier. ```ts chart.axisY .setTitle('Frequency') .setUnits('Hz') ``` Setting the Units only affects following things: - The unit is displayed after axis title (if specified) - e.g. "Frequency (Hz)" - The unit is displayed by default cursor formatters when pointing at series connected to that axis - e.g. "Y: 18.2 Hz" :::tip The main value in using `Axis.setUnits` is to avoid having to specify custom cursor formatting just because you need to display the units in there. ::: These behaviors can also be individually control like so: ```ts chart.axisY.setUnits('Hz', { displayInCursor: true, displayOnAxis: true }) ``` ## Multiple axes and positioning In `ChartXY`, you can have more than two axes, and axes can also be positioned in different locations. Additional axes can be added with `addAxisX` and `addAxisY` methods: ```ts const axisX2 = chart.addAxisX().setTitle('Extra Axis X') ``` By default, series are attached to the default axes. When an application has several axes, it is recommended to explicitly specify which axis each series should be connected to: ```ts const lineSeries = chart.addLineSeries({ xAxis: axisX2, yAxis: chart.axisY }) ``` ### Axis side Axes can be positioned at the opposite end of the chart (right for X axis, top for Y axis): ```ts const axisY2 = chart.addAxisY({ opposite: true }) ``` ![Opposite Y axis](/img/opposite-axis-light.png#light-mode-only)![Opposite Y axis](/img/opposite-axis-dark.png#dark-mode-only) For default axes, this has to be specified when the ChartXY is created: ```ts const chart = lc.ChartXY({ defaultAxisY: { opposite: true } }) ``` ### Stacked axes Multiple axes can also be stacked on top of each other. Most commonly this is used to share 1 X axis between many different Y axes and respective Series: ```ts chart.axisY.dispose() for (let i = 0; i < 3; i += 1) { const axisY = chart.addAxisY({ iStack: i }).setMargins(i > 0 ? 15 : 0, i < 2 ? 15 : 0) const series = chart .addLineSeries({ axisY }) .appendSamples({ yValues: new Array(100).fill(0).map((_) => Math.random()) }) } ``` ![Stacked Y axis](/img/stacked-axis-light.png#light-mode-only)![Stacked Y axis](/img/stacked-axis-dark.png#dark-mode-only) The key parts here are: 1. `iStack` parameter given to `addAxisY` method. This determines the vertical position of the Y axis. 2. `Axis.setMargins` is used to add empty space along vertical plane to avoid Y axis tick labels colliding, and adding a clear separation between channels. 3. For simplicity of implementation, the default Y axis is disposed before creating the stacked Y axes. The length of axes can be changed in two different ways: ```ts // Set relative length of axis to `0.5` // By default, every axis has relative length of `1`, so this operation would effectively make the axis half the length of others axis.setLength({ relative: 0.5 }) ``` ```ts // Set length of axis to 200 pixels exactly. axis.setLength({ pixels: 200 }) ``` ### Parallel axes Axes can also be placed parallel to each other: ```ts const axisY1 = chart.axisY const axisY2 = chart.addAxisY({ iParallel: 1 }) .setTickStrategy(AxisTickStrategies.Numeric, (ticks) => ticks.setTickStyle((major) => major.setGridStrokeStyle(emptyLine))) const series1 = chart.addLineSeries({ axisY: axisY1 }) .appendSamples({ yValues: new Array(100).fill(0).map((_) => Math.random()) }) const series2 = chart.addLineSeries({ axisY: axisY2 }) .appendSamples({ yValues: new Array(100).fill(0).map((_) => Math.random()) }) ``` ![Parallel Y axis](/img/parallel-axis-light.png#light-mode-only)![Parallel Y axis](/img/parallel-axis-dark.png#dark-mode-only) Generally, when there are parallel axis, you want to disable tick gridlines because it is practically impossible to recognize which axis they belong to. In above example, one of the Y axis has gridlines visible and the other not. Parallel axes can also be combined with [opposite axes](#axis-side), so that one Y axis is on left, and another on right. ## User interactions :::info Currently most axis types have no user interactions, except for those belonging to [`ChartXY`](/features/xy) ::: **Generally, the best way to configure user interactions of axes is to configure them via the owning chart.** Please see [Chart XY user interactions](/features/xy/#user-interactions) section for more information. However, further user interaction overrides can be configured on any individual axis. This essentially allows: 1. Controlling whether the particular axis is affected by _chart level interactions_ which by default affect all axes. 2. Controlling what user interactions are enabled above the particular axis - overriding the chart level configuration. ```ts axis.setUserInteractions({ chartInteractions: false // this axis will be unaffected by any and all "chart level" interactions }) axis.setUserInteractions({ chartInteractions: { pan: false, // alternatively, individual "chart level" interactions can be disabled like so zoom: false } }) ``` ## Styling axis ticks Axis ticks are configured via the `AxisTickStrategy` interface: ```ts axis.setTickStrategy(AxisTickStrategies.Numeric, tickStrategy => tickStrategy .setTickStyle(ticks => ticks .setLabelFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setLabelFont(font => font.setSize(10).setFamily('Segoe UI')) .setGridStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) .setTickStyle(emptyLine) ) ) ``` Alternatively instead of `setTickStyle`, you can style major and minor ticks separately using `setMajorTickStyle` / `setMinorTickStyle`. For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). Same syntax works for DateTime and Time axis tick strategies. ## Styling axis line ```ts // Hide axis line axis.setStrokeStyle(emptyLine) // Red axis line axis.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Hiding grid-lines ```ts axis.setTickStrategy(AxisTickStrategies.Numeric, tickStrategy => tickStrategy .setTickStyle(ticks => ticks .setGridStrokeStyle(emptyLine) ) ) ``` Same syntax works for DateTime and Time axis tick strategies. ## Zebra stripes Colored bands can be enabled on any axis to help visually distinguish regions along the axis. The `step` parameter defines the width of each stripe in axis units: ```ts // Numeric axis - stripes every 1000 axis units axis.setZebraStripes({ getLayout: () => ({ step: 1000 }) }) // DateTime axis - daily stripes axis.setZebraStripes({ getLayout: () => ({ step: 24 * 60 * 60 * 1000 }) // 1 day in milliseconds }) ``` By default, stripes start from axis value of `0`. This can be changed with the `start` parameter: ```ts const oneDay = 24 * 60 * 60 * 1000 // 1 day in milliseconds // Daily stripes starting at a specific date axis.setZebraStripes({ getLayout: () => ({ step: oneDay, start: Date.UTC(2026, 0, 1) }), }) // Daily stripes aligned to local timezone midnight // With DateTime axis, stripes anchor to Unix epoch (0 = midnight UTC). // Use timezone offset to align with local calendar days: axis.setZebraStripes({ getLayout: () => ({ step: oneDay, start: new Date().getTimezoneOffset() * 60 * 1000 }), }) ``` By default, only odd-numbered stripes are drawn. Use `color1` to change the default color, and `color2` to enable drawing even-numbered stripes: ```ts axis.setZebraStripes({ getLayout: () => ({ step: 1000 }), color1: ColorRGBA(240, 240, 240), color2: ColorRGBA(220, 220, 220) }) ``` The `getLayout` callback receives information about current axis state, which can be used to create dynamic stripe layouts: ```ts axis.setZebraStripes({ getLayout: (info) => { const interval = info.end - info.start const oneDay = 24 * 60 * 60 * 1000 if (interval <= 7 * oneDay) { // Week or less: daily stripes return { step: oneDay } } // More than a week: weekly stripes return { step: 7 * oneDay } } }) ``` ## Logarithmic axis Logarithmic axes can be enabled when creating an Axis or Chart. Here's how it looks for ChartXY: ```ts const chart = lc.ChartXY({ defaultAxisY: { type: 'logarithmic', base: 10, } }) ``` ...or for non-default Axis: ```ts const axisLog = chart.addAxisY({ type: 'logarithmic', base: 10 }) ``` Please note that logarithmic axis range is not defined at 0, so you should confirm that your data doesn't have exact 0 values. ## Inverted or reverted axis By default, axis fits visible data range so that min value is shown at axis "start" (left for X axis, bottom for Y axis). This can be reversed so that min value is shown at axis "end" (right for X axis, top for Y axis) like this: ```ts // Reverse axis interval axis.setInterval({ start: 1, end: 0, stopAxisAfter: false }) ``` ## Restricting axis interval ```ts // Example 1, prevent zooming outside active data set axis.setIntervalRestrictions((state) => ({ startMin: state.dataMin, endMax: state.dataMax, })) // Example 2, set max zoom in level (intervalMin) axis.setIntervalRestrictions({ intervalMin: 10 }) // Example 3, set max zoom out level (intervalMax) axis.setIntervalRestrictions({ intervalMax: 1000 }) // Example 4, disable any restrictions (by default axis are restricted to loaded data set) chart.forEachAxis(axis => axis.setIntervalRestrictions(undefined)) ``` ### Restrict zoom level to certain decimal count While there is no convenience feature for restricting axis by decimal count, it is relatively easy to achieve with `setIntervalRestrictions` by manually identifying the thresholds where built-in tick formatting changes number of decimals. ```ts // Some examples axis.setIntervalRestrictions({ intervalMin: 100 }) // max decimals = 0 axis.setIntervalRestrictions({ intervalMin: 10 }) // max decimals = 1 axis.setIntervalRestrictions({ intervalMin: 1 }) // max decimals = 2 ``` ## Following changes to axis interval ```ts axis.addEventListener('intervalchange', (event) => { console.log(event) }) ``` ## Synchronizing several axes Any number of axes can be conveniently synchronized with `synchronizeAxisIntervals` function. This ensures that their interval is always the same. ```ts synchronizeAxisIntervals(axis1, axis2, axis3) ``` It's worth pointing out that this operation creates a lasting connection between the axes. If any axis should be removed at any time, then the synchronization should be cleaned up: ```ts const syncHandle = synchronizeAxisIntervals(axis1, axis2, axis3) syncHandle.remove() ``` Syncing axes affects only axis intervals and stopped state. Scroll margins can't be synchronized in this way, but it can be done manually: ```ts axis1.setScrollMargins(5) axis2.setScrollMargins(5) axis3.setScrollMargins(5) ``` ## Removing series from affecting axis Individual series can be disabled from affecting any axis scrolling or fitting using `setAutoScrollingEnabled` method: ```ts series.setAutoScrollingEnabled(false) ``` ## Zoom range limitations LightningChart JS is based on WebGL powered graphics. This imposes some limitations when dealing with extremely large numbers, or numbers with extremely precise decimal points. The main way this limitation shows itself is Axis zoom range limitations, meaning axis preventing its interval [`start: number, end: number`] from going to ranges that could result in rendering errors. If your application results in Axis refusing to go to required zoom range, you should change the axis type to "high precision": ```ts const chart = lightningChart().ChartXY({ defaultAxisX: { type: 'linear-highPrecision' } }) ``` --- ## Bar Chart `BarChart` feature can be used for a number of different "bar" type visualizations: - Bar charts - Grouped bar charts - Stacked bar charts ```ts // Creation of BarChart const lc = lightningChart() const barChart = lc.BarChart() ``` ![Bar chart](/img/bar-chart-light.png#light-mode-only)![Bar chart](/img/bar-chart-dark.png#dark-mode-only) ## Bar chart Bar chart data is specified as an Array of JS objects with `category: string` and `value: number` properties. ```ts barChart.setData([ { category: 'Helsinki', value: 19.1 }, { category: 'New York', value: 20.6 }, { category: 'London', value: 16.6 }, { category: 'Budapest', value: 21.8 }, { category: 'Tallinn', value: 21.0 }, ]) ``` ## Grouped Bar Chart Bar chart can also display groups of categories. For example, employee counts of different countries grouped by department: ![Grouped Bar chart](/img/bar-chart-grouped-light.png#light-mode-only)![Grouped Bar chart](/img/bar-chart-grouped-dark.png#dark-mode-only) The only difference is in data supply method: ```ts barChart.setDataGrouped( ['Finland', 'Germany', 'UK'], [ { subCategory: 'Engineers', values: [48, 27, 24] }, { subCategory: 'Sales', values: [19, 40, 14] }, { subCategory: 'Marketing', values: [33, 33, 62] }, ], ) ``` ## Stacked Bar Chart Alternatively, Bar chart Groups can also be stacked instead of putting them next to each other, by using `setDataStacked` method: ```ts barChart.setDataStacked( ['1999', '2004', '2009', '2014', '2019', '2021'], [ { subCategory: 'Symbian OS', values: [1, 51, 43, 0, 0, 0] }, { subCategory: 'Palm OS', values: [66, 18, 1, 0, 0, 0] }, { subCategory: 'BlackBerry OS', values: [1, 7, 20, 0.5, 0, 0] }, { subCategory: 'Windows Mobile', values: [20, 13, 7, 2, 0, 0] }, { subCategory: 'iOS', values: [0, 0, 15, 19, 14, 27] }, { subCategory: 'Android', values: [0, 0, 8, 77, 82, 72] }, { subCategory: 'Other', values: [12, 11, 6, 1.5, 4, 1] }, ], ) ``` ![Stacked Bar chart](/img/bar-chart-stacked-light.png#light-mode-only)![Stacked Bar chart](/img/bar-chart-stacked-dark.png#dark-mode-only) ## Horizontal Bar Charts Bar charts are vertical by default, but they can be rotated by supplying an option during creation time: ```ts const barChart = lc.BarChart({ type: BarChartTypes.Horizontal }) ``` ## Sorting By default, Bar charts sort data in Descending order. This can be selected using `BarChart.setSorting` method: ```ts barChart.setSorting(BarChartSorting.Descending) barChart.setSorting(BarChartSorting.Ascending) barChart.setSorting(BarChartSorting.Alphabetical) barChart.setSorting(BarChartSorting.None) ``` ## Real-time data To connect real-time data to a Bar Chart, simply call `setData` (or `setDataGrouped` / `setDataStacked`) repeatedly to update previously displayed data. ## Value labels Bar chart labels are separated to two types: Value labels and Category labels. To configure Value labels, use `setValueLabels` method. ```ts // Example, specify value label position and formatting barChart.setValueLabels({ position: 'inside-bar-centered', formatter: (bar, category, value) => `${value.toFixed(1)} €` }) ``` ```ts // Example, set value label font size, rotation and fill style barChart.setValueLabels((labels) => ({ labelFont: labels.labelFont.setSize(10), labelRotation: 0, labelFillStyle: new SolidFill({ color: ColorRGBA(0, 0, 0) }), })) ``` ```ts // Example, hide value labels barChart.setValueLabels({ labelFillStyle: emptyFill }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Category labels Category labels work almost exactly same as Value labels, except for few differences: - `tickStyle` and `tickLength` properties available. - `position` property not available. - `formatter` syntax is different. ```ts // Example, specify category label formatting barChart.setCategoryLabels({ formatter: (category, categoryValuesTotal) => `${category} (${categoryValuesTotal})` }) ``` ```ts // Example, set category label font size, rotation and fill style barChart.setCategoryLabels((labels) => ({ labelFont: labels.labelFont.setSize(10), labelRotation: 0, labelFillStyle: new SolidFill({ color: ColorRGBA(0, 0, 0) }), })) ``` ```ts // Example, hide category labels barChart.setCategoryLabels({ labelFillStyle: emptyFill }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Bar style By default, bars are colored according to active [Theme](/more-guides/themes). Bars can also be styled individually by fetching a reference to any particular bar. ```ts // Example, set specific bar style by referencing `category` and optionally `subCategory` const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0) }) barChart.getBar(category: string, subCategory?: string).setFillStyle(fillRed) ``` ```ts // Example, set all bars style const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0) }) barChart.getBars().forEach((bar) => bar.setFillStyle(fillRed)) ``` Note that Bars are only created after `setData` (or equivalent) method is called. For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Chart title ```ts chart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `BarChart` has 3 different levels of backgrounds: - Series background (area enclosed by axes) - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts barChart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) .setSeriesBackgroundStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) barChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts barChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Animations ```ts // Example, disable all animations const barChart = lc.BarChart({ animationsEnabled: false }) ``` Different animations can also be individually configured: ```ts // Example, disable category position animation barChart.setAnimationCategoryPosition(false) ``` ```ts // Example, specify speed of bar value animations barChart.setAnimationValues(true, 2) ``` ## User interactions By default, Bar chart has practically no user interactions. `BarChartBar` objects expose an Event API that can be used to track user interactions on any particular Bar: ```ts barChart.getBar(category: string, subCategory?: string).addEventListener('click', (event, info) => { console.log('user clicked', info.category, info.subCategory, info.value) }) ``` ## Enable value axis tick gridlines By default, Bar charts don't have value axis tick grid lines. These can be enabled like this: ```ts barChart.valueAxis.setTickStrategy(AxisTickStrategies.Numeric, (ticks) => ticks .setMajorTickStyle((major) => major.setGridStrokeStyle(barChart.getTheme().xAxisNumericTicks.majorTickStyle.gridStrokeStyle)) .setMinorTickStyle((minor) => minor.setGridStrokeStyle(barChart.getTheme().xAxisNumericTicks.minorTickStyle.gridStrokeStyle)), ) ``` ## Coordinate translations LightningChartJS has a number of different coordinate systems that are not natively present on a web page - most importantly, the charts Axes. In many data visualization use cases it is critical to add annotations or markers at specific coordinates along the Axes and data series. Because of this, the ability to translate between coordinates on the web page and the axes is critical. In this context, "client" refers to the Web API Client coordinate system. ### Translating client coordinate to Axis ```ts chart.seriesBackground.addEventListener('click', (event) => { const locationBars = chart.translateCoordinate(event, chart.coordsBars) // locationBars tells the clicked location relative to Bar Chart value and category axes (category = 0, 1, 2, 3, ...). console.log(locationBars) }) ``` ### Translating Axis coordinate to client ```ts const locationBars = { iCategory: 1, value: 100 } const locationClient = chart.translateCoordinate(locationBars, chart.coordsClient) ``` ### Relative coordinate system Apart from the widely used "axis" and "client" coordinate system, there is another, the "relative" coordinate system. This is mostly utilized for LightningChart UI components that are positioned as pixel locations relative to the chart (e.g. 10 pixels from left edge etc.). ```ts // Example, translate bar coordinate to relative coordinate const locationBars = { iCategory: 1, value: 100 } const locationRelative = chart.translateCoordinate(locationBars, chart.coordsRelative) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Axis Please see common [Axis](/features/axis) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Cursor LightningChart JS has a built in Cursor functionality. This is activated when user places mouse over a chart, displaying information about the pointed chart component. ![Chart with cursor](/img/cursor-light.jpeg#light-mode-only)![Chart with cursor](/img/cursor-dark.jpeg#dark-mode-only) The cursor functionality can be customized in many ways, most of which are covered in below documentation. ## Enabling/disabling cursor ```ts // Disable cursor Chart.setCursorMode(undefined) // Enable cursor, showing only one series at a time Chart.setCursorMode('show-nearest') // Enable cursor, showing a "cut" of all series along the same coordinate (if applicable) Chart.setCursorMode('show-all') // Enable cursor when its directly above a chart component, series, etc. Chart.setCursorMode('show-pointed') ``` ## Cursor formatting :::tip Before overriding default cursor formatting, it is recommended to check if using [Axis units](/features/axis/#axis-units), configuring [Axis cursor formatting](/features/cursor/#axis-specific-cursor-formatting), configuring [LUT formatting](/features/cursor/#lut-formatting), configuring series name, or [overriding series formatting](#series-specific-cursor-formatting) would be enough. There are many benefits to NOT overriding default cursor formatting, so it should always be the preferred option! ::: Text displayed by cursors can be configured with `Chart.setCursorFormatting` method. In simple terms, this is done by supplying a callback function which receives information about the pointed data, and returns a list of text to display: ```ts chart.setCursorFormatting((_, hit, hits) => { return [ ['Cursor pointing at'], [hit.series], // returning a series will display the series color and its name automatically. ['X', '', hit.axisX.formatValue(hit.x)], // utilizing axis formatValue is useful for considering active zoom level and type of axis ['Y', '', hit.y.toFixed(2)], // empty string '' results in gap between cells [{ text: 'Example', font: { weight: 'bold' }, fillStyle: fillRed }] // any cell can also be styled individually ] }) const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0) }) ``` Here's a common code snippet to conveniently highlight specific rows as "headers": ```ts chart.setCursorFormatting((_, hit) => [ [{ component: hit.series, rowFillStyle: chart.getTheme().cursorResultTableHeaderBackgroundFillStyle }] ]) // or alternatively... chart.setCursorFormatting((_, hit) => [ [{ text: 'Header text', rowFillStyle: chart.getTheme().cursorResultTableHeaderBackgroundFillStyle }] ]) ``` ### Axis specific cursor formatting If you just need to configure how values along 1 specific Axis are formatted in cursors and cursor tick markers, the best way is to configure the cursor formatting of that Axes' tick strategy: ```ts chart.axisX.setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy .setCursorFormatter((x) => `${x.toFixed(2)} Hz`) ) ``` This takes effect whenever your application, or the library utilizes that Axes' `formatValue` method. For example, when a value along that Axis is displayed in a cursor, or a cursor tick marker. Alternatively, Axis titles also affect how values are displayed in cursors. Often simply specifying an axis title will do the trick: ```ts chart.axisX.setTitle('Time') chart.axisY.setTitle('Voltage') ``` ### Series specific data properties With some chart types, there are series specific data properties. For example, XY chart can contain Line Series, or Heatmap Series whose data properties are very different. By default, you'll have access to only the standard data properties such as `hit.x`, `hit.y` (in XY charts). In order to access any series specific properties, you must use a _Type Guard_: ```ts chart.setCursorFormatting((_, hit, hits) => { if (!isHitHeatmap(hit)) return undefined // `hit` is for a heatmap series return [ ['Column', '', hit.column.toFixed(0)], ['Row', '', hit.row.toFixed(0)], ['Intensity', '', hit.intensity.toFixed(3)], ] }) ``` These type guards all start with `isHit...` pattern, so you can find them from the [API documentation](/api). Here's a list of the most frequently needed type guards: - `isHitSampleXY` (for `PointLineAreaSeries`) - `isHitHeatmap` (for 2D heatmaps) If you need to show completely custom data values (not `x`, not `y`, not `value`, etc.) in result table, see [series specific cursor formatting](#series-specific-cursor-formatting). ### Series specific cursor formatting As described just about everywhere on this page, cursor formatting is defined on a per-chart basis. However, there are a couple of ways to alter how cursor formatting acts for a specific, single series. ```ts // By default, series name is displayed next to corresponding values in cursors series.setName('Projected revenue') ``` For more advanced formatting customization, there is `setCursorFormattingOverride` method: ```ts series.setCursorFormattingOverride((hit, before) => { if (!isHitSampleXY(hit)) return before // Example 1: extra data property that should be displayed in cursor is loaded into data set, but not mapped to either X/Y, only in cursor const customPropertyValue = hit.sample.myProperty // Example 2: extra data property is not loaded to data set, but instead it is referred from outside the chart library const customPropertyValue = data[hit.iSample].myProperty // Add an extra row to the cursor, displaying the property value return [...before, ['Custom:', '', customPropertyValue.toFixed(3)]] }) ``` This is generally recommended when you want to expand the default cursor formatting with extra rows, such as showing completely custom data properties (not `x`, `y`, `value`, etc.). ### LUT formatting If your application uses look-up-tables (for example, heatmap, dynamically color scatter plots, etc.), then look-up values are displayed in cursors. The formatting can be specified with a property of the `LUT` object: ```ts heatmapSeries.setFillStyle(new PalettedFill({ lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 10, color: ColorRGBA(0, 0, 255) }, ], formatter: (value) => value.toFixed(0) // show 0 decimals }), })) ``` The LUT formatter affects both cursors and legends if LUTs are displayed there. If LUT `percentageValues` is set to true, then this does not affect step display in legends. ### Multi-series cursors To display multiple series simultaneously in a cursor, ensure that cursor mode is set to `"show-all"` or `"show-all-interpolated"` and use `hits` parameter: ```ts chart .setCursorMode('show-all') .setCursorFormatting((_, __, hits) => { return [ ['X', '', hits[0].axisX.formatValue(hits[0].x)], ...hits.map((hit) => [hit.series, '', hit.axisY.formatValue(hit.y)]) ] }) ``` :::info Kindly note that multi-series cursors generally expect your series to have [progressive data patterns](/more-guides/optimizing-performance/#progressive-xy-series)! Furthermore, cursor modes like `"show-all"` and `"show-all-interpolated"` are only able to pick results that belong to the same chart. If your series are scattered across different charts, consider migrating to [Stacked Axis](/features/axis/#stacked-axes). ::: ### Icons Icons can also be shown in result table content: ```ts const image = new Image() image.src = 'my-image.png' const icon = chart.engine.addCustomIcon(image) chart.setCursorFormatting((_, hit, hits) => { return { icon, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) } }) ``` ## Controlling cursor per series / component All series and equivalent components that are pickable by cursor can be individually removed from cursor behavior with `setCursorEnabled` method: ```ts series.setCursorEnabled(false) // cursor won't pick on this series ``` ## Styling result table ### Text color and font ```ts chart.setCursor((cursor) => cursor .setResultTable((resultTable) => resultTable .setTextFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setTextFont((font) => font.setWeight('bold')) ) ) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ### Background fill and border ```ts chart.setCursor((cursor) => cursor .setResultTable((resultTable) => resultTable .setBackground((background) => background .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) })) ) ) ) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Styling point marker By default, cursors display a small crosshair at pointed location. ### Hide point marker ```ts chart.setCursor((cursor) => cursor .setPointMarkerVisible(false) ) ``` ### Shape, size and color ```ts chart.setCursorDynamicBehavior(false).setCursor((cursor) => cursor .setPointMarker((pointMarker) => pointMarker .setShape(PointShape.Circle) .setSize({ x: 5, y: 5 }) .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ thickness: 2, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) })) ) ) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Cursor interpolation By default, cursors snap to the closest real data point. Alternatively, automatic interpolation can be enabled to display interpolated data value at cursor location: ```ts // When using "show-all" mode and your data points do not align with each other you should enable interpolation: chart.setCursorMode('show-all-interpolated') chart.setCursorMode('show-nearest-interpolated') chart.setCursorMode('show-pointed-interpolated') ``` ## XY Cursors ### Hide tick markers ```ts chart.setCursor((cursor) => cursor .setTickMarkerXVisible(false) .setTickMarkerYVisible(false) ) ``` ### Style tick marker text ```ts chart.setCursor((cursor) => cursor .setTickMarkerY((tickMarker) => tickMarker .setTextFont((font) => font.setSize(10)) .setTextFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setTextRotation(0) ) ) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ### Style tick marker background ```ts chart.setCursor((cursor) => cursor.setTickMarkerY((tickMarker) => hasUIElementBackground(tickMarker) && tickMarker.setBackground((background) => background .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) })) ) )) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ### Hide grid lines ```ts chart.setCursor((cursor) => cursor .setGridStrokeXStyle(emptyLine) .setGridStrokeYStyle(emptyLine) ) ``` ### Style grid lines ```ts chart.setCursor((cursor) => cursor .setGridStrokeXStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## 3D Cursors Otherwise, 3D cursors work like with other charts, but they are only activated when user mouse is directly above a series which has mouse interactions enabled. e.g. there is no _Solve Nearest_ functionality in 3D. ## Map Chart Cursors `formatLongitudeLatitude` function is available for displaying longitude and latitude coordinates. ```ts mapChart.setCursorFormatting((_, hit) => { return [ [formatLongitudeLatitude(hit.longitude, hit.latitude)] [hit.region.name] ] }) ``` ## Custom cursor actions and events You can monitor what is displayed by the cursor at any given time, as well as trigger custom actions using cursor target changed event: ```ts chart.addEventListener('cursortargetchange', (event) => { // this callback is triggered whenever the cursor targets change. console.log(event) }) ``` ## Custom cursors The default cursors in LightningChart JS are fairly simple in terms of visual options (for example, rounded borders are not supported). When advanced CSS styling is needed, the recommended approach is to implement a _Custom Cursor_. Essentially, the idea is that instead of using the built-in way of displaying cursors, you can display any kind of cursor you want, either using LightningChart JS user interface elements (such as Custom Ticks, UI Textboxes, etc.) or even with custom HTML/JS/CSS or components of your UI framework (such as React, Angular, Vue, Next, etc...) Regardless, plugging a custom cursor to any chart is excessively simple, as you only need to use one method `setCustomCursor`, which will disable the built-in functionality and trigger a callback function whenever the custom cursor should display something or hide itself: ```ts chart.setCustomCursor((event) => { // this callback is triggered whenever the cursor targets change. // if cursor is hidden `event` will be undefined // Otherwise it will supply same information as `setCursorFormatting.` }) ``` For some examples of custom cursors, you can check out our examples gallery under [cursor](https://lightningchart.com/js-charts/interactive-examples/search.html?t=cursor) tag. ## Optimizing cursors In most use cases, cursors are extremely performant without any special attention. However, there are two cases where a little bit of performance optimization can be useful: **1: Large freeform data sets** e.g. point cloud with > 1 000 000 samples. Finding the closest data point from this data set is a very slow operation. > It is recommended to use `"show-pointed"` cursor mode for significantly better performance! **2: Large data sets that are heavy to render** e.g. large point clouds or 3D series with many data points. > For best performance, you can separate the cursor from chart re-rendering, by using a [custom cursor](#custom-cursors) and displaying the cursor either using direct HTML/JS/CSS or an UI framework. This way even if the cursor is changing, the heavy chart does not need to be re-rendered. :::tip This online example showcases both of these optimization approaches: [Large Scatter Chart](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-0016-largeScatterChartXY.html) ::: ## Manual cursors Charts also support ability to add manual cursors. These function exactly same as normal cursors, but instead of being automatically triggered by end user actions they can be freely controlled by the users own application logic. Manual cursors are created with `chart.addCursor()` method: ```ts const cursor = chart.addCursor() ``` Manual cursors are styled just like normal cursors (e.g. result table styling, point marker styling, tick marker styling, ...). Their position and content are set separately using `cursor.setPosition(...)` and `cursor.setResultTable(table => table.setContent(...))` methods: ```ts // Simple example of placing a manual cursor when clicking on a series series.addEventListener('click', (event, hit) => { cursor .setPosition(hit.cursorPosition) .setResultTable((table) => table.setContent(chart.getCursorFormatting()(chart, hit, [hit]))) }) ``` Manual cursors can also be placed on arbitrary locations. ```ts const cursor = chart .addCursor() .setPosition({ pointMarker: { x: 200, y: 0 }, pointMarkerScale: chart.coordsAxis, resultTable: { x: 200, y: 0 }, resultTableScale: chart.coordsAxis, }) .setResultTable((table) => table.setContent('Hello')) ``` --- ## Box 3D feature for rendering a collection of 3D boxes with individual locations and sizes. ```ts // Creation of a box series const boxSeries = chart.addBoxSeries() ``` ![3D box chart](/img/3d-box-light.png#light-mode-only)![3D box chart](/img/3d-box-dark.png#dark-mode-only) ## Adding data Boxes are inputted with `invalidateData` method: ```ts boxSeries.invalidateData([ { xCenter: 0, yCenter: 0, zCenter: 0, xSize: 1, ySize: 1, zSize: 1 } ]) ``` Alternatively, you can define boxes using min-max syntax: ```ts boxSeries.invalidateData([ { xMin: 0, xMax: 1, yMin: 0, yMax: 1, zMin: 0, zMax: 1 } ]) ``` ## Editing data Editing data of previously specified boxes is possible by assigning IDs to boxes: ```ts boxSeries.invalidateData([ { id: 0, xMin: 0, xMax: 1, yMin: 0, yMax: 1, zMin: 0, zMax: 1 } ]) // ... edit previously created box boxSeries.invalidateData([ { id: 0, xMin: 0, xMax: 1, yMin: 0, yMax: 3, zMin: 0, zMax: 1 } ]) ``` ## Box color ```ts boxSeries.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Rounded boxes ```ts boxSeries.setRoundedEdges(0.2) boxSeries.setRoundedEdges(undefined) ``` ## Individual box colors Boxes can be colored individually by setting fill style to `IndividualPointFill` and including `color` property for each box: ```ts const colorRed = ColorRGBA(255, 0, 0) boxSeries .setFillStyle(new IndividualPointFill()) .invalidateData([ { xMin: 0, xMax: 1, yMin: 0, yMax: 1, zMin: 0, zMax: 1, color: colorRed } ]) ``` ## Color by lookup table Alternative approach to individual colors, you can give each box a `number` value, and specify rules for getting a color from any number: ```ts boxSeries .setFillStyle(new PalettedFill({ lookUpProperty: 'value', lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) })) .invalidateData([ { xMin: 0, xMax: 1, yMin: 0, yMax: 1, zMin: 0, zMax: 1, value: 100 } ]) ``` ## Color by X, Y or Z Instead of using lookup values in data set, you can also use X, Y or Z coordinates directly, by changing the `lookUpProperty` value in `PalettedFill`. ```ts new PalettedFill({ lookUpProperty: 'x', // 'x', 'y' or 'z' lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) }) ``` ## 3D color shading This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ## Depth testing This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## Bubble A common visualization type that extends from a [Scatter chart](/features/d3/scatter). The bubble chart is a scatter chart with individual point sizes and/or colors. This way, the size or color of each point can represent a property of the data point. For example, older measurements can have less opacity. Or, more impactful events may have larger point size, etc. ![Chart with bubble series](/img/3d-bubbles-light.png#light-mode-only)![Chart with bubble series](/img/3d-bubbles-dark.png#dark-mode-only) ```ts // Creation of a bubble series const bubbleSeries = chart.addPointSeries({ individualPointSizeEnabled: true }) ``` **3D bubble series is technically just a 3D scatter series with individual point sizes and/or colors**. Here you can find guides specific to individual point sizes. For other guides, refer to [3D scatter series](/features/d3/scatter) ## Individual point sizes as pixels ```ts const bubbleSeries = chart.addPointSeries({ individualPointSizeEnabled: true }) bubbleSeries.add([ { x: 0, y: 0, z: 0, size: 10 }, { x: 1, y: 3, z: 0, size: 15 }, { x: 2, y: 2, z: 0, size: 5 }, ]) ``` ## Individual point sizes as axis dimensions Alternatively, you can also specify point sizes along X, Y and Z axes: ```ts const bubbleSeries = chart.addPointSeries({ individualPointSizeAxisEnabled: true }) bubbleSeries.add([ { x: 0, y: 0, z: 0, sizeAxisX: 2, sizeAxisY: 1, sizeAxisZ: 1 }, { x: 1, y: 3, z: 0, sizeAxisX: 1, sizeAxisY: 2, sizeAxisZ: 1 }, { x: 2, y: 2, z: 0, sizeAxisX: 1, sizeAxisY: 1, sizeAxisZ: 2 }, ]) ``` ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## 3D Chart `Chart3D` is a collection of series, axes and other chart components. It can be used to create an incredible number of different 3D data visualization elements. ```ts // Creation of Chart3D const lc = lightningChart() const chart = lc.Chart3D() ``` ![3D chart](/img/3d-bubbles-light.png#light-mode-only)![3D chart](/img/3d-bubbles-dark.png#dark-mode-only) ## Chart title ```ts chart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `Chart3D` has 3 different levels of backgrounds: - Series background (area enclosed by axes) - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts chart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) .setSeriesBackgroundStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) chart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Projection By default, `Chart3D` uses a _perspective projection_ (objects farther away are smaller). Alternatively, _orthographic projection_ is also supported. ```ts chart.setProjection('orthographic') ``` ## Bounding box style ```ts chart.setBoundingBoxStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Bounding box dimensions By default, `Chart3D` coordinate system is placed in a symmetrical 3D cube. This can be changed by configuring the relative ratios between the coordinate systems Y size compared to X for example: ```ts chart.setBoundingBox({ x: 2, y: 1, z: 2 }) ``` ![3D chart](/img/bounding-box-light.jpeg#light-mode-only)![3D chart](/img/bounding-box-dark.jpeg#dark-mode-only) ## Disable animations ```ts const chart = lc.Chart3D({ animationsEnabled: false }) ``` ## User interactions 3D chart user interactions can be configured with `setUserInteractions` method. Here you can find a handful of most commonly required examples. For more information, please see generic documentation about [`setUserInteractions`](/features/user-interactions). At time of writing, `Chart3D` comes with following set of available built-in user interactions: - **Rotate camera** (by dragging mouse or via pinch with touch devices) - **Zoom camera** (by mouse wheel) - **Panning axes** (by dragging mouse, using mouse wheel or via pinch with touch devices) - **Zooming axes** (by dragging mouse, using mouse wheel or via pinch with touch devices) - **Restore default view** (double click or keyboard) - **Restore previous view** (double click or keyboard) With touch devices, users can freely zoom and pan any X/Y/Z axis individually. This is done by placing 2 fingers on the chart. The closest axis that aligns with the fingers initial location is selected. Pinching allows zoom in/out while moving the fingers allows panning. ### Example: disable all user interactions ```ts chart.setUserInteractions(undefined) ``` ### Example: mouse wheel zoom Y axis always There are so many different ways user may want interactions to work in 3D charts. Furthermore compared to XY charts, it is basically impossible to make correct assumptions based on the chart structure. For this reason, the `setUserInteractions` method allows very detailed control of interaction behavior. ```ts // Example, zoom Y axis only on mouse wheel, keeping axis start stationary. chart.setUserInteractions({ zoom: { camera: false, wheel: {}, x: false, y: true, z: false, mode: 'keep-start', }, }) ``` ### Example: zooming on all X,Y,Z axes ```ts // Example, zoom X when ctrl is pressed, Y when shift is pressed and Z when alt is pressed chart3D.setUserInteractions({ zoom: { ctrl: { camera: false, x: true, y: false, z: false }, shift: { camera: false, x: false, y: true, z: false }, alt: { camera: false, x: false, y: false, z: true }, }, }) ``` For more information about user interactions, please see generic documentation about [`setUserInteractions`](/features/user-interactions). ## Camera angle and distance Camera angle and viewpoint can be controlled with `setCameraLocation` method: ```ts chart.setCameraLocation({ x: 0.52, y: 0.33, z: 1.06 }) chart.setCameraLocation({ x: 0.91, y: 1.07, z: 0.43 }) ``` By default, this only controls the location of the camera. It always points towards the center of the scene at a preset distance away from the center. If control over camera distance is needed, then `setCameraAutomaticFittingEnabled` method can be used: ```ts chart .setCameraAutomaticFittingEnabled(false) .setCameraLocation({ x: 1.8, y: 2.0, z: 0.8 }) ``` ## Coordinate translations LightningChartJS has a number of different coordinate systems that are not natively present on a web page - most importantly, the charts Axes. In many data visualization use cases it is critical to add annotations or markers at specific coordinates along the Axes and data series. Because of this, the ability to translate between coordinates on the web page and the axes is critical. In this context, "client" refers to the Web API Client coordinate system. ### Translating client coordinate to Axis `Chart3D` currently doesn't have a method for translating between client coordinates (2D) and 3D axes. If your use case requires this functionality, then please [get in touch](/contact) and we'll arrange it. ### Translating Axis coordinate to client `Chart3D` currently doesn't have a method for translating between client coordinates (2D) and 3D axes. If your use case requires this functionality, then please [get in touch](/contact) and we'll arrange it. ### World coordinate system Some elements of `Chart3D` are positioned in so called "world coordinates", such as the 3D camera and 3D mesh model vertices. World coordinates can be translated to 3D axis locations and vice versa. ```ts const locWorld = { x: 0, y: 0, z: 0 } const locAxis = chart.translateCoordinate(locWorld, chart.coordsWorld, chart.coordsAxis) ``` ## Cursors `Chart3D` cursors work otherwise same as in other charts, except they are only activated when users pointer is directly above a component (i.e. there is no "solve nearest" functionality). Please see common [Cursors](/features/cursor) section for more information. ## Axis Please see common [Axis](/features/axis) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Line ```ts // Creation of a 3D line series const lineSeries = chart.addLineSeries() ``` ![3D Chart with line series](/img/3d-line-series-light.png#light-mode-only)![3D Chart with line series](/img/3d-line-series-dark.png#dark-mode-only) ## Adding data Most 3D series types accept data as a list of JS objects with `x`, `y` and `z` number properties. ```ts lineSeries.add([ { x: 0, y: 0, z: 0 }, { x: 1, y: 3, z: 0 }, { x: 2, y: 2, z: 0 }, { x: 3, y: 5, z: 0 }, { x: 4, y: 6, z: 0 }, ]) ``` > A common user problem is that data is supplied as `string` instead of `number` - please confirm this if you are experiencing any strange issues. ```ts console.log(typeof y) // ---> 'number' ``` ## Editing or clearing data ```ts lineSeries.clear() ``` 3D line series does not have any convenience method for editing a range of existing data. To edit displayed data set, clear and re-specify full data set. ## Using timestamps Supply timestamps as number UTC timestamps. When using timestamps, you generally want to also setup a [Date-Time Axis](/features/axis/#date-time-axis). ## Data cleaning / maximum memory use 3D line series does not have built-in functionality for automatic data cleaning. If your use case requires this functionality, then please [get in touch](/contact). ## Changing stroke color ```ts const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0, 255) }) lineSeries.setStrokeStyle((stroke) => stroke.setFillStyle(fillRed)) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Changing stroke thickness ```ts lineSeries.setStrokeStyle((stroke) => stroke.setThickness(1)) ``` 3D series pixel sizes vary based on zoom level. To get stroke thickness of 1 pixels regardless of zoom level, you can set thickness to exactly `-1`: ```ts // Enable primitive line rendering - always 1 px, also considerably lighter on GPU. lineSeries.setStrokeStyle((stroke) => stroke.setThickness(-1)) ``` ## Dashed line series 3D line series does not currently support dashed stroke. If your use case requires this functionality, then please [get in touch](/contact). ## Data gaps This section works the same as for `2D Line`, to avoid duplication of guides, please refer to the section under [Line 2D](/features/xy/line) ## Color by lookup table Alternative approach to individual colors, you can give each data point a `number` value, and specify rules for getting a color from any number: ```ts const lineSeries = chart.addLineSeries({ individualLookupValuesEnabled: true, }) .setStrokeStyle((stroke) => stroke.setFillStyle( new PalettedFill({ lookUpProperty: 'value', lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) }) )) .add([ { x: 0, y: 0, z: 0, value: 0 }, { x: 1, y: 3, z: 0, value: 100 }, { x: 2, y: 2, z: 0, value: 50 }, ]) ``` `LUT` has also a `percentageValues` option, which you can use if you want to use % values of the present min-max lookup value range, rather than specific value steps: ```ts new LUT({ percentageValues: true, interpolate: true, steps: [ // 0 = 0 %, 1 = 100% { value: 0, color: ColorCSS('red') }, { value: 1, color: ColorCSS('green') }, ] }) ``` ## Color by X, Y or Z ```ts lineSeries.setStrokeStyle((stroke) => stroke.setFillStyle( new PalettedFill({ lookUpProperty: 'y', // 'x', 'y' or 'z' lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) }) )) ``` ## 3D color shading By default, 3D series are using shaded 3D color graphics. This means that the color of surfaces vary on perceived angle, giving a sense of depth and surface shape. This can be disabled to color every part of a series with a solid color, similar to 2D graphics: ```ts // Disable 3D color shading lineSeries.setColorShadingStyle(new ColorShadingStyles.Simple()) ``` ## Depth testing By default, WebGL depth testing is enabled. This means that any other 3D data that is drawn after a series AND is behind that series in the chart will not be rendered on the screen to save GPU processing. Normally, this is no concern. However, when visualizing transparent 3D objects this may result in unexpected visual results (objects missing from scene). Depth testing can be disabled with `setDepthTestEnabled`. ```ts lineSeries.setDepthTestEnabled(false) ``` ### Interactions with data points All series support tracking user interactions: ```ts lineSeries.addEventListener('click', (event, hit) => { // hit.x // hit.y // hit.z console.log(hit) }) ``` For all available events to track, please see [API](/api) --- ## Mesh model `Chart3D` includes a feature for rendering 3D models and objects. These objects can be colored dynamically based on data in real-time. ![Mesh model](/img/mesh-model-light.png#light-mode-only)![Mesh model](/img/mesh-model-dark.png#dark-mode-only) ```ts // Creation of mesh model series const modelSeries = chart.addMeshModel() ``` ## Loading model geometry Model geometry is supplied using `setModelFromObj` or `setModelGeometry` methods. The recommended way is to input model geometry from [`.obj` file format](https://en.wikipedia.org/wiki/Wavefront_.obj_file) file content and then supply [coloring](#model-coloring) by related material file content. Parameter to `setModelFromObj` method is the content of the an `.obj` file. ```ts fetch('model.obj') .then(r => r.text()) .then(objText => { modelSeries.setModelFromObj(objText) }) ``` One can also supply geometry by inputting vertices, indices and normals individually. Note that material is not supported when inputting geometry by `setModelGeometry` call. ```ts // npm i webgl-obj-loader fetch('model.obj') .then(r => r.text()) .then(objText => { const modelParsed = new Mesh(objText) modelSeries.setModelGeometry({ vertices: modelParsed.vertices, indices: modelParsed.indices, normals: modelParsed.vertexNormals, }) }) ``` ## Model positioning Mesh model positioning is controlled using a number of different methods: ```ts // Example, scale model to 0.1x size, position the models bottom at { x: 0, y: 0, z: 0 }, centered along X and Z axes. modelSeries .setScale(0.1) .setModelLocation({ x: 0, y: 0, z: 0 }) .setModelAlignment({ x: 0, y: -1, z: 0 }) .setModelRotation({ x: 0, y: 0, z: 0 }) ``` ## Model coloring Model can be colored in three different ways: * [Setting material](#setting-model-material) * [Setting fill style](#setting-fill-style) * [Coloring by data](#coloring-by-data) Whichever of the static method is called last will determine static coloring of the model. Coloring by data will overwrite model colors set by either of the two static setters. ### Setting model material Model material is supplied using `setModelMaterial` method. Parameter to `setModelMaterial` method is content of the related `.mtl` file. ```ts fetch('model.mtl') .then(r => r.text()) .then(materialText => { modelSeries.setModelMaterial(materialText) }) ``` ### Setting fill style Fill style will set model to be rendered with single color. ```ts modelSeries.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` ### Coloring by data The primary use case of LightningChart mesh models is to dynamically color different parts of the model based on an external data set. For example, imagine an airplane model colored by readings from temperature sensors. ```ts // 1) enable coloring based on vertex values modelSeries.setFillStyle(new PalettedFill({ lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 10, color: ColorRGBA(0, 255, 0) }, ], }), })) // 2) load number values associated with every vertex of the model modelSeries.setVertexValues((vertexWorldLocations) => { // Example, return random value for every model vertex return vertexWorldLocations.map((locationWorld) => Math.random() * 10) }) ``` Usually, in real applications the model is placed in a 3D chart where a varying number of "sensors" that generate the data for coloring are situated. The application code then assigns every sensor an axis coordinate (X,Y,Z) and number value (e.g. temperature). Then `setVertexValues` is used to iterate over every model vertex - for each one, the application finds how close that vertex is to the sensors and interpolates a value for it. You can find practical examples of this in our online examples: - [Airplane temperature example](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-1502-dynamicMeshModel.html) - [EEG brain activity example](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-1503-eegMeshModel.html) ## Textured models LightningChart JS mesh models do not currently support displaying textured models. If your use case would require this, please [let us know](/contact). ## 3D color shading This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ## Depth testing This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## Point Cloud A variation of [Scatter chart](/features/d3/scatter), the Point Cloud refers to a high density scatter chart where each point is displayed as 1 pixel on the screen. Common use cases include [Lidar](https://oceanservice.noaa.gov/facts/lidar.html#:~:text=Lidar%20%E2%80%94%20Light%20Detection%20and%20Ranging,Lighthouse%2C%20Dry%20Tortugas%2C%20Florida.) data visualization: ![Lidar chart](/img/lidar-light.png#light-mode-only)![Lidar chart](/img/lidar-dark.png#dark-mode-only) Compared to scatter charts, point clouds sacrifice geometrical complexity for higher performance and larger data sets. ```ts // Creation of a point cloud series const pointCloudSeries = chart.addPointSeries({ type: PointSeriesTypes3D.Pixelated, }) ``` **Point cloud series is technically just a 3D scatter series with some style API differences**. Here you can find guides specific to point cloud styling. For other guides, refer to [3D scatter series](/features/d3/scatter) ### Point color ```ts pointCloudSeries.setPointStyle((points) => points.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ### Point size ```ts pointCloudSeries.setPointStyle((points) => points.setSize(3)) ``` ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## Point-Line ```ts // Creation of a 3D point line series const pointLineSeries = chart.addPointLineSeries() ``` ![3D Chart with point line series](/img/3d-point-line-light.png#light-mode-only)![3D Chart with point line series](/img/3d-point-line-dark.png#dark-mode-only) **3D point line series is almost exactly same feature as 3D line series**. For points specific configurations and guides, keep reading, otherwise please refer to [3D Line](/features/d3/line). ### Point color ```ts pointLineSeries.setPointStyle((points) => points.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ### Point shape You can select between 'cube' and 'sphere' shapes. ```ts pointLineSeries.setPointStyle((points) => points.setShape('cube')) ``` ### Point size ```ts pointLineSeries.setPointStyle((points) => points.setSize(10)) ``` ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## Scatter A commonly needed visualization feature is to display a set of X+Y+Z points scattered around. ```ts // Creation of a 3D scatter series const scatterSeries = chart.addPointSeries() ``` ![Chart with scatter series](/img/3d-scatter-light.png#light-mode-only)![Chart with scatter series](/img/3d-scatter-dark.png#dark-mode-only) ### Adding data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ### Using timestamps This section works the same as for `3D Line`, to avoid duplication of guides, please refer to the section under [Line 3D](/features/d3/line) ### Point color ```ts scatterSeries.setPointStyle((points) => points.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) }))) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ### Point shape You can select between 'cube' and 'sphere' shapes. ```ts scatterSeries.setPointStyle((points) => points.setShape('cube')) ``` ### Point size ```ts scatterSeries.setPointStyle((points) => points.setSize(10)) ``` ### Individual colors ```ts const colorRed = new SolidFill({ color: ColorRGBA(255, 0, 0) }) const colorGreen = new SolidFill({ color: ColorRGBA(0, 255, 0) }) const scatterSeries = chart.addPointSeries({ individualPointColorEnabled: true, }) .setPointStyle((points) => points.setFillStyle(new IndividualPointFill())) .add([ { x: 0, y: 0, z: 0, color: colorRed }, { x: 1, y: 3, z: 0, color: colorRed }, { x: 2, y: 2, z: 0, color: colorGreen }, ]) ``` ### Color by lookup table Alternative approach to individual colors, you can give each data point a `number` value, and specify rules for getting a color from any number: ```ts const scatterSeries = chart.addPointSeries({ individualLookupValuesEnabled: true, }) .setPointStyle((points) => points.setFillStyle( new PalettedFill({ lookUpProperty: 'value', lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) }) )) .add([ { x: 0, y: 0, z: 0, value: 0 }, { x: 1, y: 3, z: 0, value: 100 }, { x: 2, y: 2, z: 0, value: 50 }, ]) ``` `LUT` has also a `percentageValues` option, which you can use if you want to use % values of the present min-max lookup value range, rather than specific value steps: ```ts new LUT({ percentageValues: true, interpolate: true, steps: [ // 0 = 0 %, 1 = 100% { value: 0, color: ColorCSS('red') }, { value: 1, color: ColorCSS('green') }, ] }) ``` ### Color by X, Y or Z Instead of using lookup values in data set, you can also use X, Y or Z coordinates directly, by changing the `lookUpProperty` value in `PalettedFill`. ```ts new PalettedFill({ lookUpProperty: 'x', // 'x', 'y' or 'z' lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) }) ``` ### Using timestamps This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ### Data cleaning / maximum memory use This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ## Editing or clearing data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ## 3D color shading This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ## Depth testing This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## Scrolling surface A variant of Surface, this feature is suitable for streaming applications where data is measured and visualized in real-time. ![Chart with scrolling surface series](/img/3d-scrolling-surface-light.png#light-mode-only)![Chart with scrolling surface series](/img/3d-scrolling-surface-dark.png#dark-mode-only) Scrolling surfaces work same as [Static surfaces](/features/d3/surface) for the most part. The main differences are creation syntax and data input. ```ts // Creation of scrolling surface series const surfaceSeries = chart.addSurfaceScrollingGridSeries({ // Scroll along X axis scrollDimension: 'columns', // Each sample has 5 values rows: 5, // Number of samples to keep in memory columns: 100, }) .setStart({ x: 0, z: 0 }) .setStep({ x: 1, z: 1 }) ``` ## Data input A sample is added to a scrolling surface with `addValues` method: ```ts surfaceSeries.addValues({ yValues: [0, 1, 2, 3, 4] }) ``` Alternatively, `invalidateValues` can also be used. This lets you control which sample index you are defining. ## Color by separate data set See static surface guide on topic [here](/features/d3/surface). If you want to use the same approach with a scrolling surface series, then supply intensity values like this: ```ts surfaceSeries.addValues({ yValues: [0, 1, 2, 3, 4], intensityValues: [100, 110, 90, 80, 20] }) ``` ## Using timestamps Most use cases of scrolling surfaces involve timestamped data. In these cases, samples realistically don't always arrive with the same time step. Generally this is worked around by configuring a "minimum perceived time step" for the surface: ```ts // Minimum time step that can be displayed by the surface // Smaller value means more precision but more RAM and GPU memory usage. const surfaceMinTimeStepMs = 1 // 1 millisecond surfaceSeries.setStep({ x: surfaceMinTimeStepMs, z: 1 }) ``` Then, input data is mapped from timestamps to "surface sample index", and inserted to surface using `invalidateValues` method. This also works even if data doesn't come in correct time order. ```ts let tFirstSample: number | undefined const handleIncomingData = (timestamp, sample) => { if (!tFirstSample) { tFirstSample = timestamp // Using date origin is required to display time series data in range of hours, minutes, seconds or lower chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, (strategy) => strategy.setDateOrigin(new Date(tFirstSample)), ) } // Calculate sample index from timestamp to place sample in correct location in surface. const iSample = Math.round((timestamp - tFirstSample) / surfaceMinTimeStepMs) surfaceSeries.invalidateIntensityValues({ iSample, values: [sample], }) } ``` The series automatically fills any gaps between samples by repeating the previous sample value. So, actually 1 sample can occupy several data slots in the series. :::info If the axis doesn't let you zoom in far enough, consider enabling ["high precision" axis](/features/axis/#zoom-ability) ::: ## 3D color shading This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ## Depth testing This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## Surface `Chart3D` includes a feature for rendering high resolution 3D surfaces. This can mean up to millions or even billions of data points displayed at high speed and colored according to user configured color rules. ![Surface chart](/img/3d-surface-light.png#light-mode-only)![Surface chart](/img/3d-surface-dark.png#dark-mode-only) This section is about so called "static surfaces", for scrolling and time heatmaps see [Scrolling surface](/features/d3/scrolling-surface). With LightningChart, Surface charts are _extremely_ similar to Heatmaps. As such, we recommend you to also check out the [Heatmap guide](/features/xy/heatmap). ```ts // Creation of surface series const surfaceSeries = chart .addSurfaceGridSeries({ columns: 3, rows: 4, dataOrder: 'columns', }) .setStart({ x: 0, z: 0 }) .setStep({ x: 1, z: 1 }) ``` ## Surface height map Surface series consume height data as uniform number matrixes. You can imagine this as a table of numbers, where each cell is an Y coordinate: | | column 0 | column 1 | column 2 | | - | --- | --- | --- | | row 0 | 0 | 4 | 8 | | row 1 | 1 | 5 | 9 | | row 2 | 2 | 6 | 10 | | row 3 | 3 | 7 | 11 | ```ts surfaceSeries.invalidateHeightMap([ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], ]) ``` "uniform" means that when you attach this data table to a pair of X and Z axes, the X and Z step between each column and row is always the same. ## Color by height The most common way of coloring a surface is to color each location along the surface by its Y coordinate. ```ts surfaceSeries.setFillStyle(new PalettedFill({ lookUpProperty: 'y', lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) })) ``` ## Color by separate data set Surfaces can also be colored by a separate data set from the height map. As in, each coordinate in the surface can have separate Y coordinate as well as other number Value which is used for coloring. ```ts surfaceSeries.setFillStyle(new PalettedFill({ lookUpProperty: 'value', lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 10, color: ColorRGBA(0, 0, 255) }, ], }), })) // NOTE: Intensity values do not affect surface geometry, but only coloring! surfaceSeries.invalidateIntensityValues([ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], ]) ``` ## Percentage color lookup `LUT` has also a `percentageValues` option, which you can use if you want to use % values of the present min-max lookup value range, rather than specific value steps: ```ts new LUT({ percentageValues: true, interpolate: true, steps: [ // 0 = 0 %, 1 = 100% { value: 0, color: ColorCSS('red') }, { value: 1, color: ColorCSS('green') }, ] }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Intensity interpolation ```ts // Enable intensity interpolation surfaceSeries.setIntensityInterpolation('bilinear') ``` ```ts // Disable intensity interpolation surfaceSeries.setIntensityInterpolation(undefined) ``` ## Disable wireframe ```ts surfaceSeries.setWireframeStyle(emptyLine) ``` ## Contours Contour lines can be enabled with `setContours` method: ```ts heatmapSeries.setContours({ valueSource: 'intensity', levels: [ { value: 25 }, { value: 50 }, { value: 75, strokeStyle: new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) }) } ] }) ``` Each contour level can have individual stroke style. It is also possible to choose between two different sources for contour data, `intensity` and `y`. ## Extrapolating surface data from scatter data set One common way of using surface charts is to extrapolate a surface height map from a scattered 3D data set. For example, see the picture below, which displays both Raw samples (as scatter points) and the extrapolated surface: ![Surface chart](/img/3d-surface-light.png#light-mode-only)![Surface chart](/img/3d-surface-dark.png#dark-mode-only) We have computation algorithms for performing this extrapolation, but they are not publicly exposed. If you would be interested in testing this, then please [get in touch](/contact). ## Partial data supply Surface also supports modifying only a sub set of the entire data array. ```ts surfaceSeries.invalidateHeightMap({ iColumn: number; iRow: number; values: number[][] }) ``` ## 3D color shading This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ## Depth testing This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/d3/line) --- ## Data Grid `DataGrid` is a feature that displays content as a flexible grid, with rows and columns and each cell having its own content. Data grid supports a variety of cell content types such as text, numbers, colors, icons and spark charts. ```ts // Creation of DataGrid const lc = lightningChart() const dataGrid = lc.DataGrid() ``` ![Data grid](/img/data-grid-light.png#light-mode-only)![Data grid](/img/data-grid-dark.png#dark-mode-only) ## Cell content Cell content can be set using a few different methods: ```ts dataGrid.setCellContent(0, 0, 'Hello') dataGrid.setRowContent(0, ['Column 0', 'Column 1']) dataGrid.setColumnContent(0, ['Row 0', 'Row 1']) dataGrid.setTableContent([ ['Header 0', 'Header 1'], ['Cell 0.0', 'Cell 0.1'], ['Cell 1.0', 'Cell 1.1'], ]) ``` Type of each cell content can be one of: `string`, `number`, `undefined`, `Icon` or `SparkChart`. ### Icons Icons are loaded from JavaScript `Image` objects. ```ts const myImage = new Image() myImage.src = 'picture.jpg' const icon = dataGrid.engine.addCustomIcon(myImage, { height: 32 }) dataGrid.setCellContent(0, 0, icon) ``` ### Spark charts Data grid offers following types of spark charts: line charts, bar charts, win-loss charts, area charts and pie charts. ```ts // Example, spark line chart let data: { x: number, y: number }[] dataGrid.setCellContent(0, 0, { type: 'spark-line', data, }) ``` For full list of available configurations, see `SparkChart` in [API reference](/api). ### Content alignment ```ts // Example, set content alignment of 1 cell dataGrid.setCellContentAlignment(0, 0, 'left-center') // Example, set default content alignment of all existing and future cells dataGrid.setCellsContentAlignment('left-center') ``` Familiar convenience methods are also available: - `DataGrid.setRowContentAlignment` - `DataGrid.setColumnContentAlignment` ## Cell backgrounds ```ts const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0) }) // Example, set background of 1 cell dataGrid.setCellBackgroundFillStyle(fillRed) // Example, set default background of all existing and future cells dataGrid.setCellsBackgroundFillStyle(fillRed) ``` Familiar convenience methods are also available: - `DataGrid.setRowBackgroundFillStyle` - `DataGrid.setColumnBackgroundFillStyle` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Cell borders ```ts // Example, set border visibility of 1 cell dataGrid.setCellBorders(0, 0, { bottom: true }) // Example, set default border visibility of all existing and future cells dataGrid.setCellsBorders(undefined) ``` ```ts // Example, border stroke style dataGrid.setCellsBorderStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Cell text font and color ```ts // Example, set text fill of 1 cell dataGrid.setCellTextFillStyle(0, 0, new SolidFill({ color: ColorRGBA(255, 0, 0) })) // Example, set default font of all existing and future cells dataGrid.setCellsTextFont((font) => font.setSize(10)) ``` Familiar convenience methods are also available: - `DataGrid.setRowTextFillStyle` - `DataGrid.setColumnTextFillStyle` - `DataGrid.setRowTextFont` - `DataGrid.setColumnTextFont` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Cell padding ```ts // Example, set padding of 1 cell dataGrid.setPadding(0, 0, { bottom: 10, top: 10 }) // Example, set default padding of all existing and future cells dataGrid.setCellsPadding(10) ``` Familiar convenience methods are also available: - `DataGrid.setRowPaddings` - `DataGrid.setColumnPaddings` ## Cell highlighting Cells can be highlighted, applying a brighter or darker color based on active color theme. ```ts dataGrid.setCellHighlight(0, 0, true) ``` Familiar convenience methods are also available: - `DataGrid.setRowHighlight` - `DataGrid.setColumnHighlight` ## Grid background color ```ts dataGrid.setGridBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Column widths and Row heights ```ts // Example usage, set first column width to exactly 200 pixels dataGrid.setColumnWidth(0, 200) // Example usage, set first column max width to 200 pixels dataGrid.setColumnWidth(0, { max: 200 }) // Example, set second row height to 100 px dataGrid.setRowHeight(1, 100) ``` ## Title ```ts dataGrid .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `DataGrid` has 2 different levels of backgrounds: - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts dataGrid .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) dataGrid.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts dataGrid.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const dataGrid = lc.ChartXY({ animationsEnabled: false }) ``` ## User interactions Data grid does not have any built-in user interactions. User interactions on cells can be tracked using the Event API: ```ts dataGrid.cells.addEventListener('click', (event, cell) => { console.log('user clicked on', cell) }) ``` ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Donut Chart Apart from high performance data visualization features, LightningChart JS also includes commonly needed standard features, such as Pie charts: ![Donut chart](/img/donut-light.png#light-mode-only)![Donut chart](/img/donut-dark.png#dark-mode-only) ```ts // Creation of donut chart const lc = lightningChart() const donutChart = lc.Pie() .setInnerRadius(60) ``` **Donut chart is technically just a Pie chart with configured "inner radius" property**. For majority of guidance, refer to [Pie chart](/features/pie). ## Displaying a title inside Donut chart This title can be added as an UI element positioned at chart center. For more information see: - Donut chart [online example](https://lightningchart.com/js-charts/interactive-examples/edit/lcjs-example-0451-donutChart.html) - [UI elements guide](/features/ui) --- ## Funnel Chart Apart from high performance data visualization features, LightningChart JS also includes commonly needed standard features, such as Funnel charts: ![Funnel chart](/img/funnel-light.png#light-mode-only)![Funnel chart](/img/funnel-dark.png#dark-mode-only) ```ts // Creation of Funnel chart const funnelChart = lc.Funnel() ``` ## Adding data ```ts const data = [ { name: 'Prospects', value: 2000, }, { name: 'Contacts', value: 1540, }, { name: 'Leads', value: 1095, }, { name: 'Customers', value: 549, }, ] funnelChart.addSlices(data) ``` ## Slice mode By default, Funnel chart displays values by giving each slice a height relative to its value. This can be reversed, to give slice a width relative to its value. This looks a bit different: ![Funnel chart variable width](/img/funnel-width-light.png#light-mode-only)![Funnel chart variable width](/img/funnel-width-dark.png#dark-mode-only) ## Additional Funnel chart configurations Some details about Funnel chart visual look are also available, see below methods for example: - `FunnelChart.setSliceGap` - `FunnelChart.setHeadWidth` - `FunnelChart.setNeckWidth` - `FunnelChart.setLabelSide` ## Label formatting ```ts funnelChart.setLabelFormatter(SliceLabelFormatters.Name) funnelChart.setLabelFormatter(SliceLabelFormatters.NamePlusRelativeValue) funnelChart.setLabelFormatter(SliceLabelFormatters.NamePlusValue) funnelChart.setLabelFormatter((slice, relativeValue) => `${slice.getName()}: ${slice.getValue().toFixed(1)}`) ``` ## Funnel chart types There are two different Funnel chart types available. The available APIs differ slightly between them. ### Labels on sides (default) ```ts const funnelChart = lc.Funnel({ type: FunnelChartTypes.LabelsOnSides }) ``` ### Labels inside slices ```ts const funnelChart = lc.Funnel({ type: FunnelChartTypes.LabelsInsideSlices }) ``` ![Funnel chart](/img/funnel-labels-inside-light.png#light-mode-only)![Funnel chart](/img/funnel-labels-inside-dark.png#dark-mode-only) ## Slice sorting ```ts funnelChart.setSliceSorter(SliceSorters.None) funnelChart.setSliceSorter(SliceSorters.SortByName) funnelChart.setSliceSorter(SliceSorters.SortByValueAscending) funnelChart.setSliceSorter(SliceSorters.SortByValueDescending) ``` ## Label colors and font ```ts funnelChart .setLabelFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 0) })) .setLabelFont((font) => font.setWeight('bold')) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Color slices by value ```ts funnelChart.setLUT( new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 100, color: ColorRGBA(0, 255, 0) }, ], }), ) ``` ## Slice colors Slice colors are set with 1 method that applies a fill style to every slice. Slices can be assigned different fill styles by returning different value based on slice index. ```ts const sliceFillStyles = [ new SolidFill({ color: ColorRGBA(255, 0, 0) }), new SolidFill({ color: ColorRGBA(0, 255, 0) }), new SolidFill({ color: ColorRGBA(0, 0, 255) }), ] funnelChart.setSliceFillStyle((index) => sliceFillStyles[index % sliceFillStyles.length]) ``` ## Slice user interactions User interactions on each slice can be tracked individually: ```ts const slice = funnelChart.addSlice("name", 10) slice.addEventListener('click', (event) => { console.log('user clicked slice') }) ``` ## Chart title ```ts funnelChart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `FunnelChart` has 2 different levels of backgrounds: - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts funnelChart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) funnelChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts funnelChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const funnelChart = lc.FunnelChart({ animationsEnabled: false }) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Gauge Chart (Linear) `Gauge` is used to display a single data value (number) on a straight horizontal or vertical scale. ```ts // Creation of Linear Gauge const lc = lightningChart() const gauge = lc.LinearGauge() ``` ![Linear Gauge chart](/img/linear-gauge-light.png#light-mode-only)![Linear Gauge chart](/img/linear-gauge-dark.png#dark-mode-only) ## Gauge ranges Linear gauge has two main different use cases. Either solid stepping gauge (like in picture above), or interpolated gauge. Generally, you configure gauge ranges slightly differently between the two use cases: ```ts // Solid stepping gauge gauge.setRanges([ { start: 0, color: ColorCSS('red') }, { start: 4, color: ColorCSS('green') }, { start: 7, end: 10, color: ColorCSS('blue') }, ]) gauge.setColorInterpolation(false) ``` ```ts // Interpolated gauge gauge.setRanges([ { start: 0, color: ColorCSS('red') }, { start: 5, color: ColorCSS('green') }, { start: 10, color: ColorCSS('blue') }, ]) gauge.setColorInterpolation(true) ``` ![Linear Gauge chart interpolated](/img/linear-gauge-interpolated-light.png#light-mode-only)![Linear Gauge chart interpolated](/img/linear-gauge-interpolated-dark.png#dark-mode-only) ## Setting gauge value ```ts gauge.setValue(50) ``` By default, value transition is animated. This can be disabled with: ```ts gauge.setValueAnimation(false) ``` ## Gauge orientation Orientation is specified when the chart is created. ```ts const gauge = lc.LinearGauge({ orientation: 'horizontal' // or 'vertical' }) ``` ## Gauge thickness ```ts // Thickness in pixels gauge.setBarThickness(80) ``` ## Enable color interpolation ```ts gauge.setColorInterpolation(true) ``` ## Disable automatic gradients Automatic gradients are applied by default, if color interpolation is disabled. ```ts gauge.setGradients(false) ``` ## Disable rounded edges ```ts gauge.setRoundedEdges(false) ``` ## Value label Value label is a text element that displays the current value along the gauge scale. ```ts gauge .setValueFormatter((value) => `${value.toFixed(1)} €`) .setValueLabelFont(font => font.setSize(10)) .setValueLabelFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) // Alignment controls value label positioning; 1 = right edge of bar, 2 = 50% bar thickness away from right edge of bar .setValueLabelAlignment(2) ``` ## Value marker In addition to value label, there is also a marker that displays some shape/icon at the current values location along the gauge scale. By default, this is a small line "-" that moves along the scale. ```ts // Below would display a red star at the center of the gauge gauge .setValueMarkerShape(PointShape.Star) .setValueMarkerSize(25) .setValueMarkerFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setValueMarkerStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 0) }) })) .setValueMarkerRotation(0) .setValueMarkerAlignment(0) ``` Value marker can also be a custom image. This works similarly as showing [Pictures in XY Chart](/features/xy/pictures). If using Icon approach, pass Icon as value marker shape. If using ImageFill approach, configure value marker fill style. ## Ticks ```ts gauge .setTickFormatter((value) => value.toFixed(1)) .setTickFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setTickFont((font) => font.setSize(10)) ``` ## Chart title ```ts gauge .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` ## Background style ```ts gauge.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` ## Space around chart ```ts gauge.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const gauge = lc.Gauge({ animationsEnabled: false }) ``` ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Gauge Chart (Radial) `Gauge` is used to display a single data value (number) in the form of a dial. ```ts // Creation of Gauge const lc = lightningChart() const gauge = lc.Gauge() ``` ![Gauge chart](/img/gauge-light.png#light-mode-only)![Gauge chart](/img/gauge-dark.png#dark-mode-only) ## Gauge data value and interval ```ts gauge .setInterval(0, 100) .setValue(90) ``` ## Gauge angle interval ```ts gauge.setAngleInterval(180, 0) ``` ## Value indicators ```ts gauge .setValueIndicators([ { start: 0, end: 25, color: ColorCSS('red') }, { start: 25, end: 50, color: ColorCSS('orange') }, { start: 50, end: 75, color: ColorCSS('yellow') }, { start: 75, end: 100, color: ColorCSS('green') }, ]) .setValueIndicatorThickness(25) .setGapBetweenBarAndValueIndicators(5) ``` ![Gauge chart](/img/valueIndicators-light.png#light-mode-only)![Gauge chart](/img/valueIndicators-dark.png#dark-mode-only) ## Gauge thickness ```ts // Thickness in pixels gauge.setBarThickness(80) ``` ## Disable rounded edges ```ts gauge.setRoundedEdges(false) ``` ## Gauge style ```ts gauge .setBarColor(ColorRGBA(255, 0, 0)) .setBarGradient(false) .setBarStrokeStyle( new SolidLine({ thickness: 2, fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 255) }), }), ) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background glow By default, background glow is colored dynamically according to the current gauge bar color. However, the background color can be set to fixed color or disabled completely. ```ts // Disable background glow chart.setGlowColor(undefined) // Use explicit glow color chart.setGlowColor(ColorRGBA(255, 0, 0, 64)) // Use dynamic coloring, set custom alpha value chart.setGlowColor({auto: true, alpha: 32}) ``` ## Value label ```ts gauge .setValueFormatter((value) => `${value.toFixed(1)} €`) .setValueLabelFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setValueLabelFont((font) => font.setSize(32)) ``` ## Unit label ```ts gauge .setUnitLabel('km/h') .setUnitLabelFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setUnitLabelFont((font) => font.setSize(16)) ``` ## Ticks ```ts gauge .setTickFormatter((value) => value.toFixed(1)) .setTickFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setTickFont((font) => font.setSize(24)) ``` ## Chart title ```ts gauge .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` ## Needle ```ts gauge .setNeedleLength(100) .setNeedleAlignment(-1) .setNeedleThickness(10) .setNeedleStrokeStyle( new SolidLine({ thickness: 2, fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 255) }), }), ) .setNeedleFillStyle( new SolidFill({ color: ColorRGBA(255, 0, 0), }), ) ``` ## Background style ```ts gauge.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` ## Space around chart ```ts gauge.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const gauge = lc.Gauge({ animationsEnabled: false }) ``` ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Interop with UI frameworks LightningChart JS is a frontend library for data visualization. It also includes some simple features for generic UI components, like text boxes and check boxes. However, it is NOT a full-fledged UI framework. As such, LightningChart JS is usually used together with an UI framework, like React, Angular, Vue, etc. At the simplest level, this just means that the UI layout and **content other than charts** (like text, buttons, dropdowns, pictures, navigations, etc.) are rendered with an UI framework and charts are just placed into `` elements as part of the user interface. However, in many cases there is also need to overlay general UI content **above the charts**. This can mean a lot of things, here are some examples: - Chart overlays - Info panels - Handles for resizing, moving or removing charts - Cursors and result tables* - Legends* Our recommendation is that general UI content like this is rendered with the dedicated UI framework, rather than LightningChart JS, even if the content is above the charts, for example. This allows reusing components and requires the developers to learn less new things since they can just use the UI framework they are familiar with. (*) LightningChart JS includes built-in features for [cursors](/features/cursor) and [legends](/features/legend). Using these is recommended unless more advanced styling (CSS) is needed. ------- Content from any UI framework or even direct HTML can be easily placed over LightningChart JS components. This can be done in two ways: 1. Overlay element as child of chart container DIV (position using coordinates relative to chart) ```html // As child element Overlay Text ``` 2. Overlay element positioned floating on document body (position using coordinates relative to web page top left) ```html Overlay Text ``` The first approach is slightly more convenient to use, the below examples assume that the HTML element is placed as child of chart container ``. ## Align HTML position to axes ```ts // Position HTML text element at corner of X and Y axes (left-bottom corner) chart.addEventListener('layoutchange', (event) => { const text = document.getElementById("text"); text.style.left = `${event.margins.left}px` text.style.top = `${event.margins.top + event.viewportHeight}px` text.style.transform = 'translateY(-100%)' }) ``` ## Position HTML at axis coordinate This can be done by translating Axis coordinates to pixel offset from chart: ```ts chart.forEachAxis((axis) => axis.addEventListener('intervalchange', (event) => { const locAxis = { x: 0, y: 0 } const locRelative = chart.translateCoordinate(locAxis, chart.coordsAxis, chart.coordsRelative) text.style.left = `${locRelative.x}px` text.style.bottom = `${locRelative.y}px` }) ) ``` It's worth pointing out that `chart.coordsRelative` coordinate system is relative to bottom-left of chart, so `text.style.bottom` is used, rather than `text.style.top`. More information about Coordinate translations can be found under each Chart type - for example, [XY Chart](/features/xy). Lastly, as a general summary on when to use UI framework for content above charts, and when maybe not: - If you need some UI that is deeply ingrained into the charts. For example, drawn between chart background and series, then you should look into suitable LightningChart features. - If you want to use CSS and in general have access to more styling options, then it is better to render with UI framework. --- ## Features LightningChart JS has a lot of features, and there are continuously more added in. Here you can find a list of currently included features as well as conclusive guides how to use them. --- ## Legend All charts will create a default legend on initialization, which can be accessed through the `legend` property of the chart: ```ts const legend = chart.legend ``` ![Chart with legend](/img/legend-light.png#light-mode-only)![Chart with legend](/img/legend-dark.png#dark-mode-only) Additional legends can be added to the chart with `addLegend`: ```ts // Add an additional "default" legend const legend2 = chart.addLegend() // Add a custom legend const customLegend = chart.addLegend({ customLegend: legendLogic }) ``` See [Custom Legend](#custom-legend) section for more details on creating custom legends. ## Legend options The legend can be configured with `LegendOptions` object, which can be passed to `ChartOptions.legend` property when creating the chart: ```ts const chart = lightningChart().ChartXY({ legend: { title: 'Legend', position: LegendPosition.RightCenter, }, }) ``` `LegendOptions` can also be updated with `legend.setOptions` method: ```ts legend.setOptions({ visible: false, }) ``` ### Automatic legend entries The default legend of the chart will automatically display all series and components created in the chart. This can be controlled with `LegendEntryOptions.show` property, which is set to `true` by default. ```ts // Disable automatic legend entries const chart = lightningChart().ChartXY({ legend: { entries: { show: false, }, }, }) // Show specific series in legend const series = chart.addPointLineAreaSeries({ legend: { show: true } }) ``` For user-created legends, entries are not automatically displayed. To add a series to the legend, use `legend.add` method: ```ts // Create a new legend and add a series to it const userLegend = chart.addLegend() userLegend.add(series) ``` ### Legend title ```ts // During chart creation const chart = lightningChart().ChartXY({ legend: { title: 'Legend', titleFont: { size: 10 }, titleFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), }, }) // After chart creation chart.legend.setOptions({ title: 'Legend', titleFont: { size: 10 }, titleFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ### Legend position For most chart types, the legend will be positioned on the bottom center of the screen by default, outside of the chart area. This can be changed by setting the `LegendOptions.position` property to value from `LegendPosition` enum, or a fixed pixel position as relative chart coordinates: ```ts // During chart creation, using LegendPosition enum const chart = lightningChart().ChartXY({ legend: { position: LegendPosition.RightCenter, }, }) // After chart creation, using fixed position chart.legend.setOptions({ position: { x: 0, y: 250, origin: UIOrigins.LeftTop }, orientation: LegendOrientation.Horizontal, }) ``` If you want to position the legend inside the chart area while using `LegendPosition` enum, set `LegendOptions.renderOnTop` to `true`: ```ts const chart = lightningChart().ChartXY({ legend: { renderOnTop: true, }, }) ``` Example of using multiple legends with fixed positions on stacked Y axes: ```ts chart.getDefaultAxisY().dispose() const channels = new Array(5).fill(0).map((_, i) => { const axisY = chart.addAxisY({ iStack: i }).setTitle(`Ch #${i + 1}`) const series = chart.addPointLineAreaSeries({ yAxis: axisY, dataPattern: 'ProgressiveX' }) const legend = chart.addLegend() legend.add(series) return { series, axisY, legend } }) chart.addEventListener('layoutchange', (event) => { channels.forEach((ch) => { const axisYLayout = event.axes.get(ch.axisY) if (!axisYLayout) return ch.legend.setOptions({ position: { x: axisYLayout.left + axisYLayout.width, y: axisYLayout.bottom + axisYLayout.height, origin: UIOrigins.LeftTop }, }) }) }) ``` ### Legend background Some chart types have a background for the legend by default, while others do not. This can be hidden or styled with `LegendOptions.backgroundVisible` ```ts // Hide background legend.setOptions({ backgroundVisible: false, }) ``` ```ts // Show background with custom style legend.setOptions({ backgroundVisible: true, backgroundFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), backgroundStrokeStyle: new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) }), }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Legend entries ### Legend entry options `LegendEntryOptions` can be configured in `SeriesOptions.legend` while creating the series: ```ts const series = chart.addPointLineAreaSeries({ legend: { show: true, text: 'Line Series', buttonSize: 25, buttonShape: PointShape.Triangle, }, }) ``` `LegendEntryOptions` can be updated with `legend.setEntryOptions`: ```ts legend.setEntryOptions(series, { show: false, }) ``` ### Style legend entries Applying same style for every entry should be done via `LegendOptions.entries`: ```ts // During chart creation const chart = lightningChart().ChartXY({ legend: { entries: { textFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), textFont: { size: 10 }, buttonShape: PointShape.Square, buttonFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), buttonSize: 10, }, }, }) // After chart creation chart.legend.setOptions({ entries: { textFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), textFont: { size: 10 }, buttonShape: PointShape.Square, buttonFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), buttonSize: 10, }, }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). Individual styles for specific entries should be applied using `LegendEntryOptions`: ```ts // During series creation const series = chart.addPointLineSeries({ legend: { textFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), textFont: { size: 10 }, buttonShape: PointShape.Square, buttonFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), buttonSize: 10, }, }) // After series creation chart.legend.setEntryOptions(series, { textFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), textFont: { size: 10 }, buttonShape: PointShape.Square, buttonFillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), buttonSize: 10, }) ``` ### Altering legend entries text ```ts legend.setEntryOptions(series, { text: 'Temperature', }) ``` Example of showing cursor values in legend entries: ```ts chart.addEventListener('cursortargetchange', (event) => { channels.forEach((ch) => { const hit = event.hits?.find((item) => item.series === ch.series) chart.legend.setEntryOptions(ch.series, { text: hit ? `${ch.series.getName()} (${hit.axisY.formatValue(hit.y)})` : ch.series.getName(), }) }) }) ``` ### Entry Icons ![Legend with Icons](/img/legend-icons-light.png#light-mode-only)![Legend with Icons](/img/legend-icons-dark.png#dark-mode-only) Normally, legend displays colored circles next to each legend entry. These can be replaced with a different `PointShape` or a custom Icon: ```ts // Change shape of all entries during chart creation using PointShape const chart = lightningChart().ChartXY({ legend: { entries: { buttonShape: PointShape.Triangle, }, }, }) // Change shape of specific entry during series creation using Icon const legendImage = new Image() legendImage.src = 'ekg.png' const icon = chart.engine.addCustomIcon(legendImage) const series = chart.addPointLineAreaSeries({ legend: { buttonShape: icon, }, }) ``` Alternatively, Icons can be associated with a series directly, which automatically results in them being displayed in the Legend: ```ts series.setIcon(icon) ``` ### Events Event callbacks can be attached to legend entries with `LegendEntryOptions.events`: ```ts const chart = lightningChart().ChartXY({ legend: { entries: { events: { pointermove: (event: MouseEvent,info: { entry: LegendEntry; allEntries: LegendEntry[] }) => { console.log('Pointer move on entry', info.entry) }, click: (event: MouseEvent, info: { entry: LegendEntry; allEntries: LegendEntry[] }) => { console.log('Click on entry', info.entry) }, }, }, }, }) ``` #### Click behavior By default, clicking on a legend entry will result in the respective component being hidden, until clicked again. This is equivalent to programmatical use of setVisible method. This behavior can be customized by passing predefined behavior from `LegendEntryClickBehaviors` or a custom callback function to `LegendEntryOptions.events.click`. The built-in click interaction can be disabled with `LegendEntryClickBehaviors.doNothing`. ```ts // During chart creation, change click behavior of all entries using LegendEntryClickBehaviors const chart = lightningChart().ChartXY({ legend: { entries: { events: { click: LegendEntryClickBehaviors.doNothing, }, }, }, }) // During series creation, change click behavior of specific entry using LegendEntryClickBehaviors const series = chart.addPointLineAreaSeries({ legend: { entries: { events: { click: LegendEntryClickBehaviors.focusClicked, }, }, }, }) // After series creation, change click behavior of specific entry using custom callback chart.legend.setEntryOptions(series, { events: { click: (event, info: { entry: LegendEntry; allEntries: LegendEntry[] }) => { // Hide all entries info.allEntries.forEach(entry => entry.setVisible(false)) }, } }) ``` ### Entries with LUT, color lookup table When a component with an active LUT (e.g. Heatmap series with `PalettedFill` style) is attached to legend, the legend will automatically display the color rules. Please note that this is only activated if the series style has a LUT. ![Legend with LUT](/img/heatmap-light.png#light-mode-only)![Legend with LUT](/img/heatmap-dark.png#dark-mode-only) If you want to disable automatic LUT element creation for legend entry, pass `null` to `LegendEntryOptions.lut`: ```ts const series = chart .addPointLineAreaSeries({ legend: { lut: null }, // Disable LUT on legend entry }) .setPointFillStyle( new PalettedFill({ lut: new LUT({ steps: [ { value: 0, color: ColorRGBA(0, 0, 0) }, { value: 10, color: ColorRGBA(255, 0, 0) }, { value: 100, color: ColorRGBA(0, 255, 0) }, ], interpolate: false, }), }), ) // Or alternatively, disable legend entry LUT for existing series/entry chart.legend.setEntryOptions(series, { lut: null }) ``` It is also possible to provide LUT directly to the legend as an entry: ```ts legend.add( new LUT({ steps: [ { value: 0, color: ColorRGBA(0, 0, 0) }, { value: 10, color: ColorRGBA(255, 0, 0) }, { value: 100, color: ColorRGBA(0, 255, 0) }, ], interpolate: false, }), { text: 'Temperature', buttonSize: 15 } ) ``` #### Legend LUT labels Legend LUT label formatting can be configured via `LUT.formatter` property: ```ts heatmapSeries.setFillStyle(new PalettedFill({ lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 10, color: ColorRGBA(0, 0, 255) }, ], formatter: (value) => value.toFixed(0) // show 0 decimals }), })) ``` The LUT formatter affects both legends and cursors. Alternatively, text displayed in each LUT label can be specified like this as well: ```ts new LUT({ steps: [ { value: 0, color: ColorRGBA(255, 0, 0), label: '0.0' }, { value: 1, color: ColorRGBA(0, 255, 0), label: '' }, { value: 2, color: ColorRGBA(0, 0, 255), label: '100 %' }, ] }) ``` #### Legend LUT unit Unit label displayed in legend can be configured via property of `LUT`: ```ts new LUT({ units: 'Voltage (V)' }) ``` #### LUT length and thickness ```ts // Modifying default values for all LUT entries during chart creation const chart = lightningChart().ChartXY({ legend: { lutLength: 100, lutThickness: 10, }, }) // Modifying values for specific entry during series creation const series = chart .addPointLineAreaSeries({ legend: { lutLength: 100, lutThickness: 10, }, }) .setPointFillStyle( new PalettedFill({ lut: new LUT({ steps: [ { value: 0, color: ColorRGBA(0, 0, 0) }, { value: 10, color: ColorRGBA(255, 0, 0) }, { value: 100, color: ColorRGBA(0, 255, 0) }, ], interpolate: false, }), }), ) // Modify existing entry LUT length and thickness chart.legend.setEntryOptions(entryWithLUT, { lutLength: 100, lutThickness: 10, }) ``` ## Custom legend Custom legends can be created by providing `LegendLogic` function to as a parameter to `addLegend` method, or by passing it to the `ChartOptions.legend.customLegend` property when creating the chart, which will override the default legend. `LegendLogic` returns a `LegendHandle` object, which contains the following methods: - `update`: Called before rendering the legend to update any internal state using `LegendRenderInfo`. A `Margin` object can be returned to allocate space for the legend within the chart area. - `plot`: Called to render the legend. - `dispose`: Called when the legend is disposed. It should clean up any resources used by the legend. Optional. Below is an example of custom legend logic to display a vertically scrollable legend using HTML. ```ts const legendLogic = (initConfig: LegendInitConfig) => { const legendDiv = document.createElement('div') initConfig.panel.engine.container.append(legendDiv) legendDiv.style.position = 'absolute' legendDiv.style.right = `0px` legendDiv.style.top = `0px` legendDiv.style.padding = '1rem' legendDiv.style.fontFamily = 'Segoe UI' legendDiv.style.userSelect = 'none' legendDiv.style.overflowY = 'auto' legendDiv.style.height = '100%' legendDiv.style.display = 'flex' legendDiv.style.flexDirection = 'column' const update = (renderInfo: LegendRenderInfo) => { legendDiv.innerHTML = '' renderInfo.entries.forEach((entry) => { const item = document.createElement('span') item.innerHTML = entry.options.text || '' item.style.cursor = 'pointer' item.style.color = entry.component && entry.component.getVisible && entry.component.getVisible() ? 'white' : 'gray' legendDiv.appendChild(item) const eventHandlerClick = entry.options.events?.click if (eventHandlerClick) item.addEventListener('click', (event) => eventHandlerClick(event, { entry, allEntries: renderInfo.entries })) }) const bounds = legendDiv.getBoundingClientRect() return { left: 0, right: bounds.width, top: 0, bottom: 0 } } const plot = (renderInfo: LegendRenderInfo) => {} const dispose = () => { legendDiv.remove() } return { plot, update, dispose } } const chart = lightningChart() .ChartXY({ legend: { customLegend: legendLogic, }, }) .setCursorMode('show-nearest') for (let i = 0; i < 30; i++) { chart .addLineSeries({ schema: { x: { auto: true }, y: { pattern: null } } }) .setName(`Series #${i + 1}`) .appendSamples({ y: new Array(10).fill(0).map(Math.random) }) } ``` --- ## Map Chart The `MapChart` can be used to display regions of the world in an interactive chart. Each region can be colored individually based on a data set. For example, population, temperature, etc. ![Map chart](/img/map-light.png#light-mode-only)![Map chart](/img/map-dark.png#dark-mode-only) ```ts // Creation of a Map Chart const lc = lightningChart() const mapChart = lc.Map({ type: MapTypes.Europe }) ``` The appeal of Map chart is a simplistic and easy to use solution to display regional differences, without requiring more complicated geospatial data visualization technology, like tile servers. Map chart runs completely in frontend. ## Access to LightningChart resources The Map chart feature utilizes static file assets distributed along side LightningChart library. In order to use `MapChart`, setting up local hosting of LC resources is needed. For instructions of setting up LightningChart resources, please see [LightningChart resources](/more-guides/lc-resources/) ## View A Map chart displays a specific view of the world. The currently available options are: - World - Europe - Africa - Asia - North America - South America - Australia - USA - Canada ```ts const mapChart = lc.Map({ type: MapTypes.World }) ``` ## Loading regions data The main idea of Map chart is to load different data values for separate regions displayed in the chart. Most map types (world, europe, africa, asia, NA, SA) separate regions by _Countries_, where as USA view separates regions to _States_ etc. Each regions data value is a single number (population, temperature, etc.). Countries are identified using the [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) standard as three-letter codes (e.g. Finland = "FIN") ```ts mapChart.invalidateRegionValues([ { ISO_A3: 'FIN', value: 1 }, // Finland value = 1 { ISO_A3: 'SWE', value: 2 } // Sweden value = 2 ]) ``` Alternatively, you can use callback syntax which requests a value for every region found in the map: ```ts mapChart.invalidateRegionValues((region, prev) => { console.log(region) return 0 }) ``` In case of Australia, USA and Canada views, the ISO_A3 standard is not applicable. Instead, regions are identified with `name` property: ```ts mapChart.invalidateRegionValues([ { name: 'Alabama', value: 1 }, { name: 'Alaska', value: 2 } ]) ``` ## Coloring regions by data ```ts mapChart.setFillStyle( new PalettedFill({ lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 10, color: ColorRGBA(0, 255, 0) }, { value: 100, color: ColorRGBA(0, 0, 255) }, ] // This property is used to specify fallback color for regions which have no data. color: ColorRGBA(255, 255, 255), }), }), ) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Real-time data For animated or real-time use cases, data displayed by Map chart can be easily updated during run-time as fast as needed by simply calling `invalidateRegionValues` method again. ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. ## Chart title ```ts chart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `MapChart` has 2 different levels of backgrounds: - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts chart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) chart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts chart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const chart = lc.MapChart({ animationsEnabled: false }) ``` ## Drilldown to regions Map chart currently doesn't expose a convenient method of finding which map region was clicked or otherwise interacted with. This is in the works currently, please [let us know](/contact) if you use case requires it. ## Overlay with XY features A common use case is to use Map chart as a geospatial background outline (where regions are), and do actual data visualization using XY features, such as [scatter charts](/features/xy/scatter), [bubble charts](/features/xy/bubble), [pictures](/features/xy/pictures) or [heatmaps](/features/xy/heatmap). ![Map + scatter chart](/img/map-scatter-light.jpg#light-mode-only)![Map + scatter chart](/img/map-scatter-dark.jpg#dark-mode-only) This can be done by creating a Map chart and XY chart completely overlaid above each other, make the XY chart background completely transparent and then synchronize the Map charts geospatial coordinate system with the XY axes: ```ts // Easiest way to overlay 2 charts is to use the same container DIV const mapChart = lc.Map({ container }) const chartXY = lc.ChartXY({ container }) chartXY .setBackgroundFillStyle(transparentFill) .setSeriesBackgroundFillStyle(transparentFill) .setSeriesBackgroundStrokeStyle(emptyLine) .setPadding(0) chartXY.engine.setBackgroundFillStyle(transparentFill) chartXY.getDefaultAxes().forEach((axis) => axis.setTickStrategy(AxisTickStrategies.Empty).setStrokeStyle(emptyLine)) mapChart.addEventListener('viewchange', (event) => { const { latitudeRange, longitudeRange, margin } = event chartXY.getDefaultAxisX().setInterval({ start: longitudeRange.start, end: longitudeRange.end }) chartXY.getDefaultAxisY().setInterval({ start: latitudeRange.start, end: latitudeRange.end }) }) // Now XY chart X = longitude and Y = latitude and the map chart and xy chart are aligned perfectly. chartXY.addPointSeries().add( // Location of Helsinki 60.1699° N, 24.9384° E { x: 24.9384, y: 60.1699 } ) ``` ## Custom Map charts If you want to display a map view that is not supported by the built-in map types, you have two options: 1) [Ask us](/contact) to add support for your requirement. 2) Implement a custom Map chart. Map chart is ultimately a fairly simple feature as its really just a group of Polygons with some convenience features. LightningChart JS XY charts expose a handy feature for displaying polygons, the [Polygon series](/features/xy/figures). It's fairly straightforward to find coordinate data of your desired map regions and loading those into a polygon series to display any kind of geospatial regions. A common file format available in internet is so called "GEOJSON", which is easy to parse in JavaScript (since its really just JSON). And the best thing is this file is just a list of polygons and their coordinates with some extra metadata. Below you can find a reference snippet of a functioning minimal code that reads in a .geojson file and displays its polygons using LightningChart polygon series. ```ts const polygonSeries = chart .addPolygonSeries() .setDefaultStyle((figure) => figure.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 0) }) })), ) fetch('japan.geojson') .then((r) => r.json()) .then((geojson: GeojsonFeatureCollection) => { geojson.features.forEach((feature) => { feature.geometry.coordinates.forEach((polygons) => { polygons.forEach((polygonCoordinates) => { // polygonCoordinates: [number, number][] // PolygonFigure expects: { x: number, y: number }[] const polygonCoordinatesXY = polygonCoordinates.map((tupleXY) => ({ x: tupleXY[0], y: tupleXY[1] })) const polygonFigure = polygonSeries.add(polygonCoordinatesXY) }) }) }) }) interface GeojsonFeatureCollection { features: Array<{ geometry: { coordinates: Array> } }> } ``` ![Custom Japan Map chart](/img/map-custom-light.jpeg#light-mode-only)![Custom Japan Map chart](/img/map-custom-dark.jpeg#dark-mode-only) --- ## Parallel Coordinate Chart The Parallel Coordinate Chart can be used to display a list of data points across an arbitrary number of measurement _Axes_. Each data point is displayed as a series of connected points along the axes which are positioned parallel to each other: ![Parallel Coordinate Chart](/img/parallel-light.png#light-mode-only)![Parallel Coordinate Chart](/img/parallel-dark.png#dark-mode-only) ```ts // Creation of a Parallel coordinate chart const lc = lightningChart() const chart = lc.ParallelCoordinateChart() ``` ## Axes Constructing a Parallel coordinate chart starts with defining the axes. The general recommended syntax is like this: ```ts const Axes = { Weight: 0, Height: 1, 'Property with space': 2 } chart.setAxes(Axes) ``` Afterwards, `Axis` objects can be referenced using `getAxis` method: ```ts const axisWeight = chart.getAxis(Axes.Weight) ``` There is no hard limit on the possible number of Axes, the only logical bottleneck is the available space in horizontal direction. For more documentation, please see common [Axis](/features/axis) section. ## Adding data In a parallel coordinate chart, one data point is represented with 1 `Series` object. The data point has 1 `Number` data property per every `Axis` in the chart: ```ts chart.addSeries().setName('Sample name').setData({ Weight: 71, Height: 171, 'Property with space': 10, }) ``` You can have up to hundreds of thousands of series in a parallel coordinate chart. For heavy applications, be sure to check out [Performance optimizations](#performance-optimizations). ## Performance optimizations Parallel coordinate charts with a lot of data points are quite heavy on GPU. Below you can find the most effective configurations to optimize performance: ```ts chart.setCursorMode('show-nearest') // show only 1 data point with cursor at a time chart.setSpline(false) // disable curved lines chart.setSeriesStrokeThickness(-1) // 1 px non-smoothed lines for smallest GPU strain ``` ## Coloring by value Parallel coordinate chart has built-in `LUT` (color lookup table) functionality, which allows dynamic coloring of series based on their value along a specific Axis: ```ts chart.setLUT({ axis: chart.getAxis(Axes.accuracy), lut: new LUT({ interpolate: true, steps: [ { value: 0.0, color: ColorRGBA(255, 0, 0) }, { value: 0.5, color: ColorRGBA(255, 255, 0) }, { value: 1.0, color: ColorRGBA(0, 255, 0) }, ], }), }) ``` ![Parallel Coordinate Chart with LUT](/img/parallel-lut-light.png#light-mode-only)![Parallel Coordinate Chart with LUT](/img/parallel-lut-dark.png#dark-mode-only) ## Range selectors Parallel coordinate chart has a powerful data analysis tool built-in - _Range Selectors_. A range selector defines a value range `[start, end]` along 1 specific Axis, and highlights the series whose value is within that range: ```ts chart.getAxis(Axes.accuracy).addRangeSelector().setInterval(0.9, 1.0) ``` ![Parallel Coordinate Chart with range selector](/img/parallel-range-light.png#light-mode-only)![Parallel Coordinate Chart with range selector](/img/parallel-range-dark.png#dark-mode-only) Range selectors can be created in two ways: 1. Built-in user interaction, double click on any Axis to create a Range selector there. 2. Programmatically, using `Axis.addRangeSelector` method, like in above example. Range selectors can also be freely moved/resized by dragging on them, or deleted by double clicking on one. This makes Range selectors a powerful exploratory data analysis tool that allows the user to find relations in data. You can also connect custom data analysis scripts to interact with the user interactions, for example: ```ts chart.addEventListener('seriesselect', (event) => { // `event` contains data that user has currently highlighted console.log(event.selectedSeries.map((series) => series.getData())) }) ``` If the chart contains several Range selectors, then only series that pass all the conditions are highlighted. ## Indicating value thresholds A common use case is to display value thresholds on different axes. E.g. which value range is _bad_ and which is _good_. This can be done by styling the Axis stroke style with a `PalettedFill`: ```ts chart.getAxis(Axes['Variable A']).setStrokeStyle( new SolidLine({ thickness: 4, fillStyle: new PalettedFill({ lookUpProperty: 'y', lut: new LUT({ interpolate: false, steps: [ // 0-50 = good { value: 0, color: ColorCSS('green') }, // > 50 bad { value: 50, color: ColorCSS('red') }, ], }), }), }), ) ``` ![Parallel Coordinate Chart with value thresholds](/img/parallel-thresholds-light.png#light-mode-only)![Parallel Coordinate Chart with value thresholds](/img/parallel-thresholds-dark.png#dark-mode-only) ## Curved lines By default, Parallel coordinate chart applies built-in curve interpolation to the data visualization. This can be disabled using `setSpline` method: ```ts // Disable curved lines chart.setSpline(false) ``` ## Chart title ```ts chart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `ParallelCoordinateChart` has 3 different levels of backgrounds: - Series background (area enclosed by axes) - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts chart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) .setSeriesBackgroundStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) chart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts chart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const chart = lc.ParallelCoordinateChart({ animationsEnabled: false }) ``` ## User interactions Built-in user interactions can be disabled individually, or all at once: ```ts chart.setMouseInteractionRangeSelectors(false) ``` List of all user built-in user interactions (or methods which toggle them): - `setMouseInteractionRangeSelectors` ```ts // Disable all interactions chart.setMouseInteractions(false) ``` Custom user interactions can be implemented using the Events API: ```ts chart.seriesBackground.addEventListener('click', (event) => { console.log('user clicked series area', event) }) ``` ## Coordinate translations LightningChartJS has a number of different coordinate systems that are not natively present on a web page - most importantly, the charts Axes. In many data visualization use cases it is critical to add annotations or markers at specific coordinates along the Axes and data series. Because of this, the ability to translate between coordinates on the web page and the axes is critical. In this context, "client" refers to the Web API Client coordinate system. ### Translating client coordinate to Axis ```ts chart.seriesBackground.addEventListener('click', (event) => { const locationAxis = chart.translateCoordinate(event, chart.coordsAxis) }) ``` ### Translating Axis coordinate to client ```ts const locationAxis = { x: 0, y: 0 } const locationClient = chart.translateCoordinate(locationAxis, chart.coordsAxis, chart.coordsClient) ``` ### Relative coordinate system Apart from the widely used "axis" and "client" coordinate system, there is another, the "relative" coordinate system. This is mostly utilized for LightningChart UI components that are positioned as pixel locations relative to the chart (e.g. 10 pixels from left edge etc.). ```ts // Example, translate axis to relative coordinate const locationAxis = { x: 0, y: 0 } const locationRelative = chart.translateCoordinate(locationAxis, chart.coordsAxis, chart.coordsRelative) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend :::warning Parallel coordinate chart Legend behavior is slightly different from other charts. User has to explicitly add every series they want displayed in the legend by using `Legend.add(series)` This is because Parallel coordinate charts can often have a large number of series. ::: Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Pie Chart Apart from high performance data visualization features, LightningChart JS also includes commonly needed standard features, such as Pie charts: ![Pie chart](/img/pie-light.png#light-mode-only)![Pie chart](/img/pie-dark.png#dark-mode-only) ```ts // Creation of Pie chart const pieChart = lc.Pie() ``` ## Adding data ```ts const data = [ { name: 'Planning', value: 40, }, { name: 'Development', value: 120, }, { name: 'Testing', value: 60, }, { name: 'Review', value: 24, }, { name: 'Bug Fixing', value: 90, }, ] const slices = data.map((item) => pieChart.addSlice(item.name, item.value)) ``` ## Label formatting ```ts pieChart.setLabelFormatter(SliceLabelFormatters.Name) pieChart.setLabelFormatter(SliceLabelFormatters.NamePlusRelativeValue) pieChart.setLabelFormatter(SliceLabelFormatters.NamePlusValue) pieChart.setLabelFormatter((slice, relativeValue) => `${slice.getName()}: ${slice.getValue().toFixed(1)}`) ``` ## Pie chart types There are two different Pie chart types available. The available APIs differ slightly between them. ### Labels on sides (default) ```ts const pieChart = lc.Pie({ type: PieChartTypes.LabelsOnSides }) ``` ### Labels inside slices ```ts const pieChart = lc.Pie({ type: PieChartTypes.LabelsInsideSlices }) ``` ![Pie chart](/img/pie-labels-inside-light.jpeg#light-mode-only)![Pie chart](/img/pie-labels-inside-dark.jpeg#dark-mode-only) ## Slice sorting ```ts pieChart.setSliceSorter(SliceSorters.None) pieChart.setSliceSorter(SliceSorters.SortByName) pieChart.setSliceSorter(SliceSorters.SortByValueAscending) pieChart.setSliceSorter(SliceSorters.SortByValueDescending) ``` ## Label colors and font ```ts pieChart .setLabelFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 0) })) .setLabelFont((font) => font.setWeight('bold')) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Color slices by value ```ts pieChart.setLUT( new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 100, color: ColorRGBA(0, 255, 0) }, ], }), ) ``` ## Slice colors Slice colors are set with 1 method that applies a fill style to every slice. Slices can be assigned different fill styles by returning different value based on slice index. ```ts const sliceFillStyles = [ new SolidFill({ color: ColorRGBA(255, 0, 0) }), new SolidFill({ color: ColorRGBA(0, 255, 0) }), new SolidFill({ color: ColorRGBA(0, 0, 255) }), ] pieChart.setSliceFillStyle((index) => sliceFillStyles[index % sliceFillStyles.length]) ``` ## Slice user interactions User interactions on each slice can be tracked individually: ```ts const slice = pieChart.addSlice("name", 10) slice.addEventListener('click', (event) => { console.log('user clicked slice') }) ``` ## Chart title ```ts pieChart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `PieChart` has 2 different levels of backgrounds: - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts pieChart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) pieChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts pieChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const pieChart = lc.PieChart({ animationsEnabled: false }) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Area ![Polar area chart](/img/polar-area-light.png#light-mode-only)![Polar area chart](/img/polar-area-dark.png#dark-mode-only) ```ts // Creation of a polar area series const areaSeries = chart.addAreaSeries() ``` ## Area fill ```ts areaSeries.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Area stroke ```ts areaSeries.setStorkeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Adding data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ## User interactions This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ## Solve nearest from location This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ## Auto connect ends This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) --- ## Heatmap `PolarChart` includes a feature for rendering high resolution Polar heat-maps. This can mean up to millions or even billions of data points displayed at high speed and colored according to user configured color rules. ![Polar heatmap chart](/img/polar-heatmap-light.png#light-mode-only)![Polar heatmap chart](/img/polar-heatmap-dark.png#dark-mode-only) With LightningChart, Polar heatmaps charts are _extremely_ similar to 2D Heatmaps. As such, we recommend you to also check out the [Heatmap guide](/features/xy/heatmap). ```ts // Creation of polar heatmap series const heatmapSeries = chart .addHeatmapSeries({ sectors: 4, annuli: 3, amplitudeStart: 0, amplitudeEnd: 1, }) ``` ## Data input Heatmaps consume data as uniform number matrixes. You can imagine this as a table of numbers: | | annulus 0 | annulus 1 | annulus 2 | | - | --- | --- | --- | | sector 0 | 0 | 4 | 8 | | sector 1 | 1 | 5 | 9 | | sector 2 | 2 | 6 | 10 | | sector 3 | 3 | 7 | 11 | ```ts heatmapSeries.invalidateIntensityValues([ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], ]) ``` "uniform" means that the amplitude and radial step between each measurement is always the same. ![Polar heatmap data values](/img/polar-heatmap-values-light.png#light-mode-only)![Polar heatmap data values](/img/polar-heatmap-values-dark.png#dark-mode-only) ## Color lookup Heatmaps are colored using a `LUT` (value \<-\> color lookup table). This defines the rules for picking a color from a data value. ```ts heatmapSeries.setFillStyle(new PalettedFill({ lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 10, color: ColorRGBA(0, 0, 255) }, ], }), })) ``` ![Polar heatmap](/img/polar-heatmap-lut-light.png#light-mode-only)![Polar heatmap](/img/polar-heatmap-lut-dark.png#dark-mode-only) For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Percentage color lookup `LUT` has also a `percentageValues` option, which you can use if you want to use % values of the present min-max lookup value range, rather than specific value steps: ```ts new LUT({ percentageValues: true, interpolate: true, steps: [ // 0 = 0 %, 1 = 100% { value: 0, color: ColorCSS('red') }, { value: 1, color: ColorCSS('green') }, ] }) ``` > **Polar heatmap currently doesn't support `percentageValues`!** If your use case requires this, please [contact us](/contact). ## Intensity interpolation ```ts // Enable intensity interpolation heatmapSeries.setIntensityInterpolation('bilinear') ``` ![Polar heatmap](/img/polar-heatmap-interpolate-light.png#light-mode-only)![Polar heatmap](/img/polar-heatmap-interpolate-dark.png#dark-mode-only) ```ts // Disable intensity interpolation heatmapSeries.setIntensityInterpolation(undefined) ``` ## Extrapolating heatmap data from scatter data set One common way of using heatmap charts is to extrapolate an uniform matrix from a scatter polar data set (something you would visualize with [Polar Scatter Chart](/features/polar/scatter)). For example, see the picture below, which displays both Raw samples (as scatter points) and the extrapolated heatmap: ![Polar heatmap chart](/img/polar-heatmap-light.png#light-mode-only)![Polar heatmap chart](/img/polar-heatmap-dark.png#dark-mode-only) We have computation algorithms for performing this extrapolation, but they are not publicly exposed. If you would be interested in testing this, then please [get in touch](/contact). ## Partial data supply Polar heatmap series also supports modifying only a sub set of the entire data array. ```ts heatmapSeries.invalidateIntensityValues({ iSector: number; iAnnulus: number; values: number[][] }) ``` ## User interactions This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ## Solve nearest from location This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) --- ## Polar Chart `PolarChart` is a collection of polar series, axes and other chart components. It can be used to create an incredible number of different data visualization elements in the context of visualizing radial data (data measured at different angles considering a central view point). ```ts // Creation of PolarChart const lc = lightningChart() const chart = lc.Polar() ``` ![Polar chart](/img/polar-light.png#light-mode-only)![Polar chart](/img/polar-dark.png#dark-mode-only) ## Polar radial direction By default, Polar chart begins with angle 0 being on right, and incrementing angle in counter-clockwise direction. The direction of angle direction can be changed with `PolarAxisRadial.setClockwise` method: ```ts chart.getRadialAxis().setClockwise(true) ``` ## Polar radial subdivisions ```ts // Show 6 radial ticks chart.getRadialAxis().setDivision(6) ``` ## Polar North angle By default, Polar chart begins with angle 0 being on right. This can be changed with `PolarAxisRadial.setNorth` method: ```ts // Use angle of 0 degrees as "North" (up). chart.getRadialAxis().setNorth(0) ``` ## Chart title ```ts chart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `PolarChart` has 3 different levels of backgrounds: - Series background (area enclosed by axes) - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts chart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) .setSeriesBackgroundStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) chart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts chart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const chart = lc.ChartXY({ animationsEnabled: false }) ``` ## Coordinate translations LightningChartJS has a number of different coordinate systems that are not natively present on a web page - most importantly, the charts Axes. In many data visualization use cases it is critical to add annotations or markers at specific coordinates along the Axes and data series. Because of this, the ability to translate between coordinates on the web page and the axes is critical. In this context, "client" refers to the Web API Client coordinate system. ### Translating client coordinate to Axis ```ts chart.seriesBackground.addEventListener('click', event => { const locationAxis = chart.translateCoordinate(event, chart.coordsAxis) // -> { angle: number, amplitude: number } }) ``` ### Translating Axis coordinate to client ```ts const locationAxis = { angle: 45, amplitude: 15 } const locationClient = chart.translateCoordinate(locationAxis, chart.coordsClient) ``` ### Relative coordinate system Apart from the widely used "axis" and "client" coordinate system, there is another, the "relative" coordinate system. This is mostly utilized for LightningChart UI components that are positioned as pixel locations relative to the chart (e.g. 10 pixels from left edge etc.). ```ts // Example, translate axis to relative coordinate const locationAxis = { angle: 45, amplitude: 15 } const locationRelative = chart.translateCoordinate(locationAxis, chart.coordsRelative) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Axis Polar chart has always two axes, Amplitude axis, and Radial axis. For the most part, they are used exactly like any other chart types axis (see common [Axis](/features/axis) section). Only differences to mention are with the Radial axis, specifically how ticks are formatted and styled: ```ts // Radial axis does not have `setTickStrategy` method contrary to other axes. Instead, it has `setTickFormattingFunction` and `setTickStyle` methods: polar .getRadialAxis() .setTickFormattingFunction((value) => // NOTE: value is radians ((value * 180) / Math.PI).toFixed(0), ) .setTickStyle((ticks) => ticks.setLabelFont((font) => font.setWeight('bold'))) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Line(Polar) ![Polar line chart](/img/polar-light.png#light-mode-only)![Polar line chart](/img/polar-dark.png#dark-mode-only) ```ts // Creation of a polar line series const lineSeries = chart.addLineSeries() ``` ## Adding data ```ts lineSeries.setData([ { angle: 0, amplitude: 50 }, { angle: 45, amplitude: 60 }, { angle: 90, amplitude: 35 }, // ... ]) ``` ## Changing stroke color ```ts const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0, 255) }) lineSeries.setStrokeStyle((stroke) => stroke.setFillStyle(fillRed)) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Changing stroke thickness ```ts lineSeries.setStrokeStyle((stroke) => stroke.setThickness(1)) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Dashed line series ```ts lineSeries.setStrokeStyle((stroke) => new DashedLine({ fillStyle: stroke.getFillStyle(), thickness: stroke.getThickness(), pattern: StipplePatterns.Dashed, })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Auto connect ends To close loop between first and last data point, use `setConnectDataAutomaticallyEnabled` method: ```ts lineSeries.setConnectDataAutomaticallyEnabled(true) ``` ## User interactions Custom user interactions can be implemented using the Events API: ```ts lineSeries.addEventListener('click', event => { console.log('user clicked series', event) }) ``` To find nearest data point to user interaction, use "solve nearest" functionality (see below). ## Solve nearest from location ```ts // Example 1, when user clicks on chart, log closest data point to console chart.seriesBackground.addEventListener('click', event => { const nearest = lineSeries.solveNearest(event) console.log(nearest) }) ``` ```ts // Example 2, solve nearest data point from an axis coordinate const locationAxis = { angle: 45, amplitude: 10 } const locationClient = chart.translateCoordinate(locationAxis, chart.coordsClient) const nearest = series.solveNearest(locationClient) console.log(nearest) ``` ### Interactions with data points All series support tracking user interactions: ```ts lineSeries.addEventListener('click', (event, hit) => { // hit.amplitude // hit.angle console.log(hit) }) ``` For all available events to track, please see [API](/api) --- ## Pictures A common need in different Polar Chart based visualizations is to show custom pictures positioned on axis coordinates. This can be done using a specifically configured [Scatter chart](/features/polar/scatter). There are two different approaches clearly separated in this documentation. ### Icons as Point shape `PolarPointSeries` supports point shape as a `Icon` object. This results in the Icon image source being used like a mask texture. The colors of the picture are overridden by whatever is the active fill style of the series. This can be convenient as you can use one picture and programmatically color it with various colors, or use advanced coloring approaches like dynamic coloring. ![Icon scatter chart](/img/polar-scatter-icon-light.png#light-mode-only)![Icon scatter chart](/img/polar-scatter-icon-dark.png#dark-mode-only) ```ts const image = new Image() image.crossOrigin = '' image.src = 'image.png' const iconSeries = chart .addPointSeries() .setPointShape(chart.engine.addCustomIcon(image)) .setEffect(false) // Point alignment can be used to offset the images relative to their size. For example, position image by its bottom. .setPointAlignment({ x: 0, y: -1.1 }) // When using Icons, point size is interpreted as multiplier of source image size. .setPointSize(0.8) .setData([{ amplitude: 0.5, angle: 45 }]) ``` For more documentation, please refer to documentation of [Scatter chart](/features/polar/scatter). ### Image Fill `PolarPointSeries` supports point fill style as a `ImageFill`. This results in points rendered as the supplied image source. ![Image fill scatter chart](/img/polar-image-fill-light.png#light-mode-only)![Image fill scatter chart](/img/polar-image-fill-dark.png#dark-mode-only) ```ts const image = new Image() image.crossOrigin = '' image.src = 'lightningchart.png' const eventSeries = chart .addPointSeries() .setPointFillStyle(new ImageFill({ source: image })) // Point alignment can be used to offset the images relative to their size. For example, position image by its bottom. .setPointAlignment({ x: 0, y: -1.1 }) // When using ImageFill, point size is interpreted as multiplier of source image size. .setPointSize(0.8) .setData([{ amplitude: 0.5, angle: 45 }]) ``` To clarify, this approach can be used to display a massive amount of custom pictures, by simply adding more samples to the series. For more documentation, please refer to documentation of [Scatter chart](/features/polar/scatter). --- ## Point-Line(Polar) ![Polar point line chart](/img/polar-point-line-light.png#light-mode-only)![Polar point line chart](/img/polar-point-line-dark.png#dark-mode-only) ```ts // Creation of a polar point line series const pointLineSeries = chart.addPointLineSeries() ``` For "points" oriented guides, please refer to [Scatter](/features/polar/scatter) section. Otherwise, the point line series functions exactly same as a [Line series](/features/polar/line). --- ## Scatter(Polar) ![Polar point chart](/img/polar-point-light.png#light-mode-only)![Polar point chart](/img/polar-point-dark.png#dark-mode-only) ```ts // Creation of a polar point series const pointSeries = chart.addPointSeries() ``` ## Point size ```ts pointSeries.setPointSize(5) ``` ## Point shape ```ts pointSeries.setPointShape(PointShape.Star) ``` ## Point color ```ts pointSeries.setPointFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Point border ```ts pointSeries.setPointStrokeStyle(new SolidLine({ thickness: 2, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Point rotation ```ts pointSeries.setPointRotation(45) ``` ## Adding data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ## User interactions This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ## Solve nearest from location This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/polar/line) --- ## Sector Polar sectors are complementary components within `PolarChart` which highlight a range along both Amplitude axis and Radial axis: ![Polar sector](/img/polar-sector-light.png#light-mode-only)![Polar sector](/img/polar-sector-dark.png#dark-mode-only) ```ts // Creation of polar sector const sector = chart.addSector() ``` ## Sector amplitude range ```ts sector .setAmplitudeStart(0.1) .setAmplitudeEnd(0.9) ``` ## Sector radial range ```ts sector .setAngleStart(25) .setAngleEnd(65) ``` ## Sector fill ```ts sector.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Sector border stroke ```ts sector.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## User interactions Custom user interactions can be added using Sector Events API: ```ts sector.addEventListener('click', event => { console.log('user clicked sector') }) ``` ## Hiding sector temporarily ```ts sector.setVisible(false) ``` --- ## Pyramid Chart Apart from high performance data visualization features, LightningChart JS also includes commonly needed standard features, such as Pyramid charts: ![Pyramid chart](/img/pyramid-light.png#light-mode-only)![Pyramid chart](/img/pyramid-dark.png#dark-mode-only) ```ts // Creation of Pyramid chart const pyramidChart = lc.Pyramid() ``` ## Adding data ```ts const data = [ { name: '2015 - 2016', value: 3, }, { name: '2016 - 2017', value: 5, }, { name: '2017 - 2018', value: 10, }, { name: '2018 - 2019', value: 17, }, ] pyramidChart.addSlices(data) ``` ## Slice mode By default, Pyramid chart displays values by giving each slice a height relative to its value. This can be reversed, to give slice a width relative to its value. This looks a bit different: ![Pyramid chart variable width](/img/pyramid-width-light.png#light-mode-only)![Pyramid chart variable width](/img/pyramid-width-dark.png#dark-mode-only) ## Additional Pyramid chart configurations Some details about Pyramid chart visual look are also available, see below methods for example: - `PyramidChart.setSliceGap` - `PyramidChart.setNeckWidth` - `PyramidChart.setLabelSide` ## Label formatting ```ts pyramidChart.setLabelFormatter(SliceLabelFormatters.Name) pyramidChart.setLabelFormatter(SliceLabelFormatters.NamePlusRelativeValue) pyramidChart.setLabelFormatter(SliceLabelFormatters.NamePlusValue) pyramidChart.setLabelFormatter((slice, relativeValue) => `${slice.getName()}: ${slice.getValue().toFixed(1)}`) ``` ## Pyramid chart types There are two different Pyramid chart types available. The available APIs differ slightly between them. ### Labels on sides (default) ```ts const pyramidChart = lc.Pyramid({ type: PyramidChartTypes.LabelsOnSides }) ``` ### Labels inside slices ```ts const pyramidChart = lc.Pyramid({ type: PyramidChartTypes.LabelsInsideSlices }) ``` ![Pyramid chart](/img/pyramid-labels-inside-light.png#light-mode-only)![Pyramid chart](/img/pyramid-labels-inside-dark.png#dark-mode-only) ## Slice sorting ```ts pyramidChart.setSliceSorter(SliceSorters.None) pyramidChart.setSliceSorter(SliceSorters.SortByName) pyramidChart.setSliceSorter(SliceSorters.SortByValueAscending) pyramidChart.setSliceSorter(SliceSorters.SortByValueDescending) ``` ## Label colors and font ```ts pyramidChart .setLabelFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 0) })) .setLabelFont((font) => font.setWeight('bold')) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Color slices by value ```ts pyramidChart.setLUT( new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 100, color: ColorRGBA(0, 255, 0) }, ], }), ) ``` ## Slice colors Slice colors are set with 1 method that applies a fill style to every slice. Slices can be assigned different fill styles by returning different value based on slice index. ```ts const sliceFillStyles = [ new SolidFill({ color: ColorRGBA(255, 0, 0) }), new SolidFill({ color: ColorRGBA(0, 255, 0) }), new SolidFill({ color: ColorRGBA(0, 0, 255) }), ] pyramidChart.setSliceFillStyle((index) => sliceFillStyles[index % sliceFillStyles.length]) ``` ## Slice user interactions User interactions on each slice can be tracked individually: ```ts const slice = pyramidChart.addSlice("name", 10) slice.addEventListener('click', (event) => { console.log('user clicked slice') }) ``` ## Chart title ```ts pyramidChart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `PyramidChart` has 2 different levels of backgrounds: - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts pyramidChart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) pyramidChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts pyramidChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const pyramidChart = lc.PyramidChart({ animationsEnabled: false }) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Radar Chart ```ts // Creation of radar chart const lc = lightningChart() const donutChart = lc.Spider() .setWebMode(SpiderWebMode.Circle) ``` ![Radar chart](/img/radar-light.png#light-mode-only)![Radar chart](/img/radar-dark.png#dark-mode-only) **Radar chart is technically just a Spider chart with specially configured "web mode"**. For majority of guidance, refer to [Spider chart](/features/spider). --- ## Reporting ## Saving charts to picture Prompting client to download a picture of a chart is as simple as triggering `saveToFile` method. This method is available on all chart types, as well as the `Dashboard` control. ```ts chart.saveToFile('screenshot', 'image/jpeg') ``` Please note that the download operation may be blocked by the clients browser. This can happen for example if the method is triggered without any user interaction. Usually you trigger the function in a click event handler. Alternatively, you can use `captureFrame` method to save a screenshot to a variable and send it over to a server for example: ```ts const dataBlob = chart.engine.captureFrame('image/jpeg') const dataUrl = chart.engine.captureFrame( 'image/png', undefined, // Return as data url instead of blob true ) ``` ## Creating PDF reports LightningChart JS lets you write charts into picture assets (see above) which can be embedded into PDF reports. As for how the actual PDF file is created, please refer to a Node PDF generator library such as [PDFKit](https://pdfkit.org/). ## LightningChart in server Normally, LightningChart JS is used as a frontend library for adding data visualization components as part of the user interface. It is also possible to use it on server-side as a tool for generating static pictures of charts. This can be useful in some specific scenarios, for example: - Creation of picture/PDF/etc. reports for storing or delivery to clients. - Applications where data is never transferred to client. Instead, only a picture of the data visualization is sent to the client device. Both these use cases are based on [`lcjs-headless`](https://github.com/Lightning-Chart/lcjs-headless), a separate support package that allows using LightningChart JS in headless mode, without an actual browser to display the charts. For detailed technical requirements, please refer to documentation of [`lcjs-headless`](https://github.com/Lightning-Chart/lcjs-headless). ### Basic use example Install `lcjs-headless` and `pngjs` for writing PNG files. ``` npm i @lightningchart/lcjs @lightningchart/lcjs-headless pngjs ``` Below is a simple use snippet that creates a chart and renders it to a PNG file. ```js // index.js const { lightningChart, renderToPNG } = require("@lightningchart/lcjs-headless"); const { Themes } = require("@lightningchart/lcjs"); const { PNG } = require("pngjs"); const fs = require("fs"); const lc = lightningChart({ license: 'my-deployment-key', licenseInformation: 'my-deployment-information' }); const chart = lc.ChartXY({ theme: Themes.light }); const chartOutput = renderToPNG(chart, 1920, 1080); const outputBuff = PNG.sync.write(chartOutput); fs.writeFileSync("./chart.png", outputBuff); ``` :::warning Running LCJS in server-side always requires a deployment license! Developer or other license types can't be used. ::: --- ## Spider Chart Spider Chart is generally used to compare multivariate quantitative data set. Each quantitative variable is represented on an axis starting from the same center point. ```ts // Creation of SpiderChart const lc = lightningChart() const spiderChart = lc.Spider() ``` ![Spider chart](/img/spider-light.png#light-mode-only)![Spider chart](/img/spider-dark.png#dark-mode-only) ```ts // Lock every axis interval from 0 to 100. spiderChart.setAxisInterval({ start: 0, end: 100, stopAxisAfter: true }) // Add series const series = spiderChart.addSeries() .setName('Product A') .addPoints( { axis: 'Category 1', value: 10 }, { axis: 'Category 2', value: 50 }, { axis: 'Category 3', value: 80 }, ) ``` ## Scale labels Spider chart value axis labels can be configured using various methods around "scale labels" context: - `SpiderChart.setScaleLabelFont` - alter font of scale labels - `SpiderChart.setScaleLabelFormatter` - alter formatting of scale labels - `SpiderChart.setScaleLabelStyle` - alter fill style of scale labels - `SpiderChart.setScaleLabelStrategy` - alter alignment of scale labels ## Axis labels Spider chart axis labels can be configured using various methods around "axis labels" context: - `SpiderChart.setAxisLabelFont` - alter font of axis labels - `SpiderChart.setAxisLabelStyle` - alter fill style of axis labels - `SpiderChart.setAxisLabelStrategy` - alter alignment of axis labels ## Series color ```ts series .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setPointFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Chart title ```ts spiderChart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `ChartXY` has 3 different levels of backgrounds: - Series background (area enclosed by axes) - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts spiderChart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) .setSeriesBackgroundStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) spiderChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts spiderChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const spiderChart = lc.Spider({ animationsEnabled: false }) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Sun Burst Chart Apart from high performance data visualization features, LightningChart JS also includes commonly needed standard features, such as Sun burst charts: ![Sun burst chart](/img/sunBurst-light.png#light-mode-only)![Sun burst chart](/img/sunBurst-dark.png#dark-mode-only) ```ts // Creation of Sun burst chart const sunBurstChart = lc.SunBurstChart() ``` ## Adding data ```ts const data = [{ name: 'TECHNOLOGY', children: [ { name: 'MSFT', value: 60.34 }, { name: 'AAPL', value: 52.75 }, { name: 'ORCL', value: 31.25 }, { name: 'ADBE', value: 81.71 }, { name: 'NVDA', value: 252.82 }, { name: 'AVGO', value: 104.86 }, { name: 'CRM', value: 104.83 }, { name: 'INTU', value: 66.01 }, ], }] sunBurstChart.setData(data) ``` ## Node Drilling The Sun burst chart allows users to drill down into specific nodes by clicking on them. This feature can be enabled or disabled using the `setUserInteractions` method. ```ts sunBurstChart.setUserInteractions({ drillDown: { click: false, }, }) ``` ## Programmatically Drilling Down The setDrillDownNode method allows you to focus on and render a specific node within the Sun burst. This is particularly useful when dealing with hierarchical data, as it enables a more detailed exploration of subsets of the data. To use this feature, you must first set your data using the setData method. Here’s an example: ```ts const data = [{ name: 'Level 1', children: [ { name: 'Level 1.1', value: 5 }, { name: 'Level 1.2', value: 10 }, { name: 'Level 1.3', children: [ { name: 'Level 1.3,1', value: 10}, {name: 'Level 1.3.2', value: 5} ] }, ],} ] sunBurstChart.setData(data) sunBurstChart.setDrillDownNode(data[0].children[2]) ``` ## Displayed Levels Count The setDisplayedLevelsCount method allows you to control the number of levels of children nodes to display in the Sun burst. ```ts sunBurstChart.setDisplayedLevelsCount(2) ``` ## Initial Path Button Text The setInitPathButtonText method allows you to set the text for the back button that returns the view to the first level of nodes. ```ts sunBurstChart.setInitPathButtonText('Reset') ``` ## Node colors The setNodeColoring method allows you to specify a set of colors to be used for the nodes in the Sun burst. In this example, the Sun burst nodes will cycle through the colors red, green, and blue. ```ts sunBurstChart.setNodeColoring([ColorCSS('red'), ColorCSS('green'), ColorCSS('blue')]) ``` ## Node colors by value The setNodeColoring method can also be used to color nodes based on their values. ```ts sunBurstChart.setNodeColoring( new PalettedFill({ lut: new LUT({ steps: [ { value: 0, color: ColorCSS('red') }, { value: 20, color: ColorCSS('blue') }, ], interpolate: true, }), }), ) ``` ## Node user interactions User interactions on each node can be tracked individually: ```ts sunBurstChart.nodes.addEventListener('click', (event, node) => { console.log(node) }) ``` ## Chart title ```ts sunBurstChart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `Sun burstChart` has 2 different levels of backgrounds: - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts sunBurstChart.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) sunBurstChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts sunBurstChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Tree Map Chart Apart from high performance data visualization features, LightningChart JS also includes commonly needed standard features, such as TreeMap charts: ![TreeMap chart](/img/treeMap-light.png#light-mode-only)![TreeMap chart](/img/treeMap-dark.png#dark-mode-only) ```ts // Creation of TreeMap chart const treeMapChart = lc.TreeMapChart() ``` ## Adding data ```ts const data = [{ name: 'TECHNOLOGY', children: [ { name: 'MSFT', value: 60.34 }, { name: 'AAPL', value: 52.75 }, { name: 'ORCL', value: 31.25 }, { name: 'ADBE', value: 81.71 }, { name: 'NVDA', value: 252.82 }, { name: 'AVGO', value: 104.86 }, { name: 'CRM', value: 104.83 }, { name: 'INTU', value: 66.01 }, ], }] treeMapChart.setData(data) ``` ## Node Drilling The TreeMap chart allows users to drill down into specific nodes by clicking on them. This feature can be enabled or disabled using the `setUserInteractions` method. ```ts treeMapChart.setUserInteractions({ drillDown: { click: false, }, }) ``` ## Programmatically Drilling Down The setDrillDownNode method allows you to focus on and render a specific node within the TreeMap. This is particularly useful when dealing with hierarchical data, as it enables a more detailed exploration of subsets of the data. To use this feature, you must first set your data using the setData method. Here’s an example: ```ts const data = [{ name: 'Level 1', children: [ { name: 'Level 1.1', value: 5 }, { name: 'Level 1.2', value: 10 }, { name: 'Level 1.3', children: [ { name: 'Level 1.3,1', value: 10}, {name: 'Level 1.3.2', value: 5} ] }, ],} ] treeMapChart.setData(data) treeMapChart.setDrillDownNode(data[0].children[2]) ``` ## Displayed Levels Count The setDisplayedLevelsCount method allows you to control the number of levels of children nodes to display in the TreeMap. ```ts treeMapChart.setDisplayedLevelsCount(2) ``` ## Initial Path Button Text The setInitPathButtonText method allows you to set the text for the back button that returns the view to the first level of nodes. ```ts treeMapChart.setInitPathButtonText('Reset') ``` ## Animation The setAnimationValues method allows you to enable or disable the animation of nodes' positions and adjust the animation speed. ```ts // Example, enable category position animation and increase speed. treeMapChart.setAnimationValues(true, 2) // Disable animation treeMapChart.setAnimationValues(false) ``` ## Disable animations ```ts const treeMapChart = lc.TreeMapChart({ animationsEnabled: false }) ``` ## Node colors The setNodeColoring method allows you to specify a set of colors to be used for the nodes in the TreeMap. In this example, the TreeMap nodes will cycle through the colors red, green, and blue. ```ts treeMapChart.setNodeColoring([ColorCSS('red'), ColorCSS('green'), ColorCSS('blue')]) ``` ## Node colors by value The setNodeColoring method can also be used to color nodes based on their values. ```ts treeMapChart.setNodeColoring( new PalettedFill({ lut: new LUT({ steps: [ { value: 0, color: ColorCSS('red') }, { value: 20, color: ColorCSS('blue') }, ], interpolate: true, }), }), ) ``` ## Node user interactions User interactions on each node can be tracked individually: ```ts treeMapChart.nodes.addEventListener('click', (event, node) => { console.log(node) }) ``` ## Chart title ```ts treeMapChart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `TreeMapChart` has 2 different levels of backgrounds: - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts treeMapChart.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) treeMapChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts treeMapChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## UI components LightningChart JS includes built-in features for simple UI components, like _Text Boxes_ and _Legends_. These can be created with `Chart.addUIElement` method: ```ts const textBox = chart.addUIElement(UIElementBuilders.TextBox) .setText('Hello') .setPosition({ x: 10, y: 10 }) .setOrigin(UIOrigins.LeftTop) .setVisible(true) ``` By default, UI elements are positioned as percentage of the owning chart, where `x: 10` = 10% from left, and `y: 10` = 10% from bottom. They can also be positioned in axis coordinates or pixels, by specifying the coordinate system: ```ts // Example, position UI in axis coordinates const axisX = chart.getDefaultAxisX() const axisY = chart.getDefaultAxisY() const textBox = chart.addUIElement(UIElementBuilders.TextBox, { x: axisX, y: axisY }) .setPosition({ x: 10, y: 10 }) ``` ```ts // Example, position UI in pixels relative to chart (10 pixels from left edge, 20 pixels from bottom edge) const textBox = chart.addUIElement(UIElementBuilders.TextBox, chart.coordsRelative) .setPosition({ x: 10, y: 20 }) .setOrigin(UIOrigins.LeftBottom) ``` These can be convenient for many every-day use cases, but if more advanced styling options are needed, then we suggest [Interop with UI frameworks](/features/html-interop) instead. --------------------- ## Changing font size ```ts textBox.setTextFont((font) => font.setSize(10)) ``` See [Styles and fonts](/more-guides/styles-colors-and-fonts/) for more information. ## UI backgrounds UI elements have a background component that is visible by default. It is configured via `setBackground` method: ```ts textBox.setBackground((background) => background // red fill, green 1 px border .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }), thickness: 1 })) ) ``` ```ts // Hide background textBox.setBackground((background) => background .setFillStyle(emptyFill) .setStrokeStyle(emptyLine) ) ``` ## Preventing user from moving UI elements ```ts textBox.setDraggingMode(UIDraggingModes.notDraggable) ``` or ```ts textBox.setMouseInteractions(false) ``` ## Positioning at corners of viewport ```ts const textBox = chart.addUIElement(undefined, chart.coordsRelative) chart.addEventListener('layoutchange', (event) => { textBox.setOrigin(UIOrigins.LeftTop).setPosition({ x: event.margins.left, y: event.margins.bottom + event.viewportHeight }) }, { fireImmediately: true }) ``` ## UI Layouts There are also basic features for positioning a number of UI elements relative to each other, _Layouts_: ```ts const uiLayout = chart.addUIElement(UILayoutBuilders.Column) .setPosition({ x: 10, y: 10 }) .setOrigin(UIOrigins.LeftTop) const label1 = uiLayout.addElement(UIElementBuilders.TextBox) .setText('Label #1') const label2 = uiLayout.addElement(UIElementBuilders.TextBox) .setText('Label #2') ``` --- ## User interactions LightningChart JS components come with a set of built-in user interactions. Some of them are enabled by default, others not. With some charts, the default enabled interactions can even vary based on the chart structure, most notably true for `ChartXY` where axis types, scroll strategies and series used may correspond to different default interaction scheme. The method `setUserInteractions` can be used to tweak, configure and override the default set of user interactions: ```ts // Example, rectangle zoom with mouse drag, zoom with wheel, pan with mouse wheel and CTRL pressed chart.setUserInteractions({ rectangleZoom: { lmb: {}, rmb: {}, }, zoom: { wheel: {}, ctrl: { wheel: false }, }, pan: { sensitivity: 2, drag: false, ctrl: { wheel: {}, }, }, restorePrevious: false, }) // Example, disable all user interactions chart.setUserInteractions(undefined) // Example, restore default interactions chart.setUserInteractions({}) ``` This method is used to override any selection of the available built-in interactions. These are numerous, and not all of them are enabled by default (as some conflict between others). As seen from the above example, the API allows very delicate control, for example enabling specific interactions depending on which mouse button is used or if ctrl/shift/alt is pressed down. When using `setUserInteractions` its important to remember that this method overrides the supplied information only, any unspecified properties retain their default values. For example, in above snippet the `"restoreDefault"` interaction retains its default behavior as it is unspecified. In majority of cases, `setUserInteractions` accepts an object where `key` identifies the targeted user interaction (such as "panning", "zooming", etc.). To disable an interaction entirely, you can pass `false` as its value, otherwise empty object (`{}`) signifies that the interaction is enabled without any overrides. ## Situational overrides Any interaction can have separate override states based on following global states: - Ctrl-key down - Shift-key down - Alt-key down - LMB used - RMB used - MMB used ```ts chart.setUserInteractions({ pan: { rmb: { drag: false, // disable drag to pan on RMB }, lmb: { drag: {}, // enable drag to pan on LMB }, }, rectangleZoom: false, // disable rectangle zoom - otherwise there are 2 conflicting interactions }) ``` ## Source overrides 1 interaction can be triggered from various different sources. For this reason, interactions can also have source specific overrides, which allow disabling that source or changing its behavior: ```ts chart.setUserInteractions({ pan: { // disable pan on mouse drag drag: false, lmb: { drag: false }, rmb: { drag: false }, wheel: {}, // ...but enable it when using mouse wheel }, zoom: { wheel: false, // disable mouse wheel zoom - otherwise there are 2 conflicting interactions }, }) ``` ## Practical tips The API of `setUserInteractions` is quite complicated as it has many layers of overrides and by nature is a very flexible method that can do many things instead of just few different things. By far, the easiest use experience is with type checking enabled. The API is strongly typed, so you will receive suggestions on what can be done and warnings if attempting to do something unsupported. If your development environment doesn't have type checking, maybe the best solution is to open one of our [online examples](https://lightningchart.com/js-charts/interactive-examples) in editor mode and experiment there. However, keep in mind that the default user interactions can vary based on the structure of charts, so if the example is structurally very different from your application there might be functional differences. :::tip When configuring enabled user interactions, it can be very helpful to debug the value of `console.log(chart.getUserInteractions())` to understand the active configuration. ::: ## Examples Examples of using `setUserInteractions` vary based on the object in question. Here's a list to feature examples of _most commonly used classes_ (not conclusive). - [`ChartXY`](/features/xy/#user-interactions) - [`Axis`](/features/axis/#user-interactions) - [`Chart3D`](/features/d3/#user-interactions) ## Callbacks Custom function callbacks can be assigned separately to each unique user interaction. ```ts chart.setUserInteractions({ rectangleZoom: { on: () => { console.log("rectangleZoom"); }, }, pan: { on: () => { console.log("pan"); }, }, zoom: { on: () => { console.log("zoom"); }, }, restoreDefault: { on: () => { console.log("restoreDefault"); }, }, }) ``` ## Custom user interactions In case where a required user interaction is not supported built-in, you can either: 1. Confirm from LightningChart whether this is indeed the case, and request its addition. 2. Implement it yourself as a custom interaction. The process of implementing a custom interaction is as follows: - Disable any conflicting built-in user interactions ```ts // Example, custom interaction with dragging LMB mouse in chart area, disable conflicting interactions chart.setUserInteractions({ pan: { lmb: false, rmb: { drag: {} }, }, rectangleZoom: { lmb: false, rmb: false, }, }) ``` - Add event listeners to track user interactions, in this case pressing mouse down and moving it. ```ts chart.seriesBackground.addEventListener('pointerdown', (eventDown) => { if (eventDown.button !== 0) return let prevCoord = eventDown const handleMove = (eventMove: PointerEvent) => { const delta = { x: eventMove.clientX - prevCoord.clientX, y: eventMove.clientY - prevCoord.clientY } prevCoord = eventMove // apply drag interaction here } const handleUp = (eventUp: PointerEvent) => { chart.engine.container.removeEventListener('pointermove', handleMove) chart.engine.container.removeEventListener('pointerup', handleUp) } chart.engine.container.addEventListener('pointermove', handleMove) chart.engine.container.addEventListener('pointerup', handleUp) }) ``` `chart.seriesBackground` is an event interface that allows users to add event listeners for interactions on the series background. For other purposes, other similar interfaces exist such as: `chart.background`, `chart.title`, `axis.title`. The above example is a very common boilerplate for tracking "dragging" events. They key part there is that `pointerdown` event is detected on the specific interactable object, whereas `pointermove` and `pointerup` are tracked globally, in this example using the container `` of the chart. :::tip If you want same customer interaction to apply on series background as well as over series, then it can be convenient to disable event tracking on series with `series.setPointerEvents(false)` ::: ## Drag & drop Interactable LightningChart JS objects can be used as sources and targets for HTML drag & drop. This works like usual, by using `draggable` property and `dragstart`, `dragover` and `drop` events: ```ts // Example, make stacked Y axes re-arrangeable by drag & drop axisY.setUserInteractions({ rectangleZoom: false }) axisY.draggable = true axisY.addEventListener('dragstart', (event) => { if (!event.dataTransfer) return event.dataTransfer.setData('text', JSON.stringify({ title: axisY.getTitle() })) }) axisY.addEventListener('dragover', (event) => { event.preventDefault() }) axisY.addEventListener('drop', (event) => { if (!event.dataTransfer) return const srcTitle = JSON.parse(event.dataTransfer.getData('text')).title if (!srcTitle) return chart.swapAxes(axisY, chart.getAxes().find(axis => axis.getTitle() === srcTitle)) }) ``` ## Frequently asked configurations Please see user interaction config examples under [XY Chart](/features/xy/#user-interactions) --- ## Area Range Area range series can fill sections between ranges of Y values. ![Chart with area range series](/img/area-range-light.png#light-mode-only)![Chart with area range series](/img/area-range-dark.png#dark-mode-only) ```ts // Creation of a area range series const areaRangeSeries = chart.addAreaRangeSeries() ``` ## Adding data ```ts areaRangeSeries.add([ { position: 0, low: 0, high: 10 }, { position: 1, low: 2, high: 13 }, { position: 2, low: 1, high: 6 }, { position: 3, low: 4, high: 7 } ]) ``` ## Styling ```ts areaRangeSeries .setLowFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setLowStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) })) .setHighFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setHighStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 255, 0) }) })) ``` ## Using timestamps Timestamps must be inputted as UTC timestamps (number). ```ts areaRangeSeries.appendSample({ position: Date.now(), low: 0, high: 1 }) ``` ### Connecting to non-default axis This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) --- ## Area(Xy) ```ts // Creation of an area series const areaSeries = chart.addAreaSeries() ``` ![Chart with area series](/img/area-chart-light.png#light-mode-only)![Chart with area series](/img/area-chart-dark.png#dark-mode-only) ## Adding data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Schema and data mapping This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Area color ```ts areaSeries.setAreaFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Area stroke Area series are actually a combination of drawing points, line stroke and area fill. For configuring the line stroke, see [Line](/features/xy/line) section. ## Using timestamps This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Data cleaning / maximum memory use This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Connecting to non-default axis This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Solve nearest from location This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Individual colors This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Color by lookup table This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Color by X or Y This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Color by gradient This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Edit samples data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Read back data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Data storage optimization This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Separating data sets from series This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Scaling, offsetting and transforming coordinates This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) --- ## Band Axis Bands are components under `ChartXY` `Axis`, which users can create and control freely. They highlight a value range (start, end) on 1 axis by projecting a rectangle across the whole chart. ![Chart with band](/img/bands-light.png#light-mode-only)![Chart with band](/img/bands-dark.png#dark-mode-only) ```ts // Creation of an axis band const band = axis.addBand() .setValueStart(2) .setValueEnd(5) ``` ## Fill color ```ts band.setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Border stroke ```ts band.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Draw order Bands can be drawn either above series, or below series. ```ts // band above series const band1 = axis.addBand(true) // band below series const band2 = axis.addBand(false) ``` ## User interactions By default, Band has built-in user interactions that allow user to move the band around. These can be disabled or configured with `setUserInteractions` method. Please see API reference for more details. ## Custom user interactions Custom user interactions can be implemented using the Events API: ```ts band.addEventListener('click', event => { console.log('user clicked band', event) }) ``` ## Disable highlighting ```ts band.setHighlightOnHover(false) ``` --- ## Bubble(Xy) A common visualization type that extends from a [Scatter chart](/features/xy/scatter). The bubble chart is a scatter chart with individual point sizes and/or colors. This way, the size or color of each point can represent a property of the data point. For example, older measurements can have less opacity. Or, more impactful events may have larger point size, etc. ![Chart with bubble series](/img/bubble-chart-light.png#light-mode-only)![Chart with bubble series](/img/bubble-chart-dark.png#dark-mode-only) ```ts // Creation of a bubble series const bubbleSeries = chart.addPointSeries() .appendSamples({ xValues: [4, 2, 6], yValues: [5, 2, 8], sizes: [10, 25, 5] }) .setDataMapping({ x: 'xValues', y: 'yValues', size: 'sizes' }) ``` ![Chart with bubble series](/img/bubble-chart-2-light.jpeg#light-mode-only)![Chart with bubble series](/img/bubble-chart-2-dark.jpeg#dark-mode-only) ## Adding data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Schema and data mapping This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Point color This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Point shape This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Point rotation This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Individual colors This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Color by lookup table This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Color by X or Y This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Color by gradient This section works the same as for `Scatter`, to avoid duplication of guides, please refer to the section under [Scatter](/features/xy/scatter) ## Using timestamps This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Data cleaning / maximum memory use This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Connecting to non-default axis This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Solve nearest from location This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Edit samples data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Read back data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Data storage optimization This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Separating data sets from series This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Scaling, offsetting and transforming coordinates This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) --- ## Constant Line Axis Constant Lines are components under `ChartXY` `Axis`, which users can create and control freely. They highlight a single coordinate on 1 axis by projecting a line across the whole chart. ![Chart with constant line](/img/constant-line-light.png#light-mode-only)![Chart with constant line](/img/constant-line-dark.png#dark-mode-only) ```ts // Creation of an axis constant line const constantLine = axis.addConstantLine() .setValue(5) ``` ## Stroke color ```ts constantLine.setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Draw order Constant lines can be drawn either above series, or below series. ```ts // constant line above series const constantLine1 = axis.addConstantLine(true) // constant line below series const constantLine2 = axis.addConstantLine(false) ``` ## User interactions By default, ConstantLine has built-in user interactions that allow user to move the band around. These can be disabled or configured with `setUserInteractions` method. Please see API reference for more details. ## Custom user interactions Custom user interactions can be implemented using the Events API: ```ts constantLine.addEventListener('click', event => { console.log('user clicked constant line', event) }) ``` ## Disable highlighting ```ts constantLine.setHighlightOnHover(false) ``` ## Dashed stroke ```ts constantLine.setStrokeStyle((stroke) => new DashedLine({ fillStyle: stroke.getFillStyle(), thickness: stroke.getThickness(), pattern: StipplePatterns.Dashed, })) ``` --- ## Figures and text This section covers a number of different series - so called "Figure Series". These can be used inside `ChartXY` to draw various custom shapes in the chart. For example, Text, Rectangles, Polygons, Ellipses, Line segments, Box and whiskers. ![Chart with figures](/img/figures-light.jpeg#light-mode-only)![Chart with figures](/img/figures-dark.jpeg#dark-mode-only) Different shapes are created using their own respective series type. However, the API for all figure series is almost completely same. The only differences are found in styling and specifying the figure dimensions. ## Text ```ts const textSeries = chart.addTextSeries() const textFigure = textSeries.add({ // All these properties are optional and can later be changed. For example `TextFigure.setText` text: 'Hello', location: { x: 0, y: 0 }, alignment: { x: -1, y: -1 }, margin: { left: 100, top: 0, right: 0, bottom: 0 }, rotation: 0, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }), font: new FontSettings({ size: 18, weight: 'bold' }), }) ``` `TextFigure` also has helper methods: - `getBoundingBox` (get boundaries in axis coordinates) and - `getSizePixels` (get size in pixels) ## Rectangles ```ts const rectangleSeries = chart.addRectangleSeries() const rectangleFigure = rectangleSeries .add({ x1: 0, y1: 0, x2: 10, y2: 10 }) setCornerRadius(10) // Optional, changing default style .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) ``` ## Polygons ```ts const polygonSeries = chart.addPolygonSeries() const polygonFigure = polygonSeries // Polygon geometry is defined as list of coordinates. Even high resolution polygons with tens of thousands of coordinates are supported. .add([ { x: 0, y: 0 }, { x: 5, y: 10 }, { x: 7, y: 3 } ]) // Optional, changing default style .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) ``` ## Ellipses ```ts const ellipseSeries = chart.addEllipseSeries() const ellipseFigure = ellipseSeries .add({ x: 5, y: 5, radiusX: 4, radiusY: 3 }) // Optional, changing default style .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Segments (Line) ```ts const segmentSeries = chart.addSegmentSeries() const segmentFigure = segmentSeries .add({ startX: 0, startY: 0, endX: 10, endY: 10 }) // Optional, changing default style .setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Box and whiskers ```ts const boxSeries = chart.addBoxSeries() const boxFigure = boxSeries .add({ // X position in chart, as well as width of box figure start: 1, end: 2, // Y dimensions lowerExtreme: 1, lowerQuartile: 2, extreme: 3, upperQuartile: 4, upperExtreme: 5 }) // Optional, changing default style .setBodyFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) .setMedianStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Optimizing performance Figures are managed under a series, which can house any number of individual figures. For performance reasons, it is best to create 1 series and all figures under it, rather than creating a large number of individual series. Another detail which can affect performance if your application has a large number of figures is sharing styles: ❌ bad: ```js for (...) { rectangleSeries .add({ ... }) // Each figure will receive a new instance of SolidFill, even though color is same (!) .setFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) } ``` ❎ good: ```ts const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0) }) for (...) { rectangleSeries .add({ ... }) .setFillStyle(fillRed) } ``` ## User interactions Custom user interactions can be added using the Events API: ```ts rectangleFigure.addEventListener('click', event => { console.log('user clicked figure', event) }) ``` ## More examples An example of combining text, rectangle and segment series to show time series annotations can be found in our online examples gallery, [here](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-0045-timeSeriesAnnotations.html). --- ## Freeform Line Instead of displaying progressive data, you can also display scatter or otherwise freeform data sets with line series. ![Chart with freeform line series](/img/freeform-line-light.png#light-mode-only)![Chart with freeform line series](/img/freeform-line-dark.png#dark-mode-only) ```ts // Creation of a freeform line series const freeformLineSeries = chart.addLineSeries() ``` For the main part, freeform line series works the same way as progressive. Please refer to [Line section](/features/xy/line) for more information. --- ## Heatmap(Xy) LightningChart JS has a powerful feature for displaying high resolution heatmaps. This can mean up to millions or even billions data points displayed at high speed and colored according to user configured color rules. ![Chart with heatmap series](/img/heatmap-light.png#light-mode-only)![Chart with heatmap series](/img/heatmap-dark.png#dark-mode-only) This section is about so called "static heatmaps", for scrolling and time heatmaps see [Scrolling heatmap](/features/xy/scrolling-heatmap). ```ts // Creation of heatmap series const heatmapSeries = chart.addHeatmapGridSeries({ columns: 3, rows: 4, dataOrder: 'columns', }) .setStart({ x: 0, y: 0 }) .setStep({ x: 1, y: 1 }) ``` ## Data input Heatmaps consume data as uniform number matrixes. You can imagine this as a table of numbers: | | column 0 | column 1 | column 2 | | - | --- | --- | --- | | row 0 | 0 | 4 | 8 | | row 1 | 1 | 5 | 9 | | row 2 | 2 | 6 | 10 | | row 3 | 3 | 7 | 11 | ```ts heatmapSeries.invalidateIntensityValues([ [0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], ]) ``` "uniform" means that when you attach this data table to a pair of X and Y axes, the X and Y step between each column and row is always the same. ![Chart with heatmap series](/img/heatmap-values-light.png#light-mode-only)![Chart with heatmap series](/img/heatmap-values-dark.png#dark-mode-only) ### Partial data supply Heatmap also supports modifying only a sub set of the entire data array. ```ts HeatmapGridSeries.invalidateIntensityValues(value: { iColumn: number; iRow: number; values: number[][] }) ``` ### Flat data input Heatmaps also support data input in flat format. This means that instead of supplying an array of arrays, you provide just one flattened array. ```ts // Example, provide entire heatmap dataset as single flat array const heatmap = chart.addHeatmapGridSeries({ columns: 3, rows: 4, dataOrder: 'columns', }) heatmap.invalidateIntensityValues([ // column 0 values 0, 1, 2, 3, // column 1 values 4, 5, 6, 7, // column 2 values 8, 9, 10, 11 ]) ``` Partial ranges of data can also be loaded as flat arrays: ```ts HeatmapGridSeries.invalidateIntensityValues(value: { iColumn: number; iRow: number; columns: number; rows: number; values: TypedArray | number[] }): this ``` ## Color lookup Heatmaps are colored using a `LUT` (value \<-\> color lookup table). This defines the rules for picking a color from a data value. ```ts heatmapSeries.setFillStyle(new PalettedFill({ lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorRGBA(255, 0, 0) }, { value: 10, color: ColorRGBA(0, 0, 255) }, ], }), })) ``` ![Chart with heatmap series](/img/heatmap-lut-light.jpeg#light-mode-only)![Chart with heatmap series](/img/heatmap-lut-dark.jpeg#dark-mode-only) For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Percentage color lookup `LUT` has also a `percentageValues` option, which you can use if you want to use % values of the present min-max lookup value range, rather than specific value steps: ```ts new LUT({ percentageValues: true, interpolate: true, steps: [ // 0 = 0 %, 1 = 100% { value: 0, color: ColorCSS('red') }, { value: 1, color: ColorCSS('green') }, ] }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Intensity interpolation ```ts // Enable intensity interpolation heatmapSeries.setIntensityInterpolation('bilinear') ``` ![Chart with heatmap series](/img/heatmap-interpolate-light.jpeg#light-mode-only)![Chart with heatmap series](/img/heatmap-interpolate-dark.jpeg#dark-mode-only) ```ts // Disable intensity interpolation heatmapSeries.setIntensityInterpolation(undefined) ``` ## Aggregation By default, heatmaps display the closest heatmap cell value to each pixel that is rendered on the screen. With dense, high resolution heatmaps, this can mean that there is no guarantee which data value is displayed in a pixel. Heatmap aggregation can be enabled to specify this behavior (which value to show when multiple cell values are contained by single pixel) at the expense of a performance hit. ```ts // Enable heatmap aggregation (max/min) heatmapSeries .setAggregation('max') // Bilinear interpolation is not supported simultaneously .setIntensityInterpolation('disabled') ``` ## Disable wireframe ```ts heatmapSeries.setWireframeStyle(emptyLine) ``` ## Contours Contour lines and or labels can be enabled with `setContours` method: ```ts heatmapSeries.setContours({ levels: [ { value: 25, label: 'warning' }, { value: 50 }, { value: 75, strokeStyle: new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) }) } ] }) ``` Each contour level can have individual stroke style, label formatting, label styling etc. ## Partial data supply Heatmap also supports modifying only a sub set of the entire data array. ```ts HeatmapGridSeries.invalidateIntensityValues(value: { iColumn: number; iRow: number; values: number[][] }) ``` ## Connecting to non-default axis This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ### Solve nearest from location This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ### Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Extrapolating heatmap data from scatter data set One common way of using heatmap charts is to extrapolate an uniform matrix from a scatter polar data set (something you would visualize with [Scatter Chart](/features/xy/scatter)). For example, see the picture below, which displays both Raw samples (as scatter points) and the extrapolated heatmap: ![heatmap chart](/img/heatmap-extrapolate-light.png#light-mode-only)![heatmap chart](/img/heatmap-extrapolate-dark.png#dark-mode-only) We have computation algorithms for performing this extrapolation, but they are not publicly exposed. If you would be interested in testing this, then please [get in touch](/contact). --- ## Histogram The best approach to displaying Histograms with LightningChart JS is by using `ChartXY` and `RectangleSeries`. Online example of this can be seen in [this example](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-1402-histogram.html). ```ts const chart = lightningChart() .ChartXY() .setTitle('Histogram chart') .setUserInteractions({ rectangleZoom: { y: false, }, zoom: { y: false, }, }) chart.axisX.setTitle('Age') chart.axisY.setTitle('Count') chart.axisX.setTickStrategy(AxisTickStrategies.Numeric, (strategy) => strategy.setCursorFormatter((x) => { const bin = bins.find((bin) => x >= bin.binStart && x <= bin.binEnd) if (!bin) return '' return `[${FormattingFunctions.Numeric(bin.binStart, chart.axisX.getInterval())}, ${FormattingFunctions.Numeric(bin.binEnd, chart.axisX.getInterval())}]` }), ) const barFillStyle = new SolidFill({ color: ColorRGBA(255, 215, 0) }) const barStrokeStyle = new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(16, 16, 16) }) }) const rectSeries = chart .addRectangleSeries() .setName('Histogram series') .setDefaultStyle((figure) => figure.setFillStyle(barFillStyle).setStrokeStyle(barStrokeStyle)) bins.forEach((bin) => { const rectFigure = rectSeries.add({ x1: bin.binStart, x2: bin.binEnd, y1: 0, y2: bin.values.length, }) }) rectSeries.setCursorBehavior({ location: (info) => ({ x: (info.x1 + info.x2) / 2, y: info.y2 }), }) ``` Result is an interactive histogram chart with cursor and zooming functionality: ![Histogram](/img/histogram-chart-light.png#light-mode-only)![Histogram](/img/histogram-chart-dark.png#dark-mode-only) In above snippet, `bins` data structure originates from below pre-calculation applied to a flat number array: ```ts // Calculation from number[] to `bins` data structure const calculateHistogramBins = (data: number[], numberOfBins: number) => { const minValue = Math.min(...data) const maxValue = Math.max(...data) const binSize = (maxValue - minValue) / numberOfBins const bins: { binStart: number, binEnd: number, values: number[] }[] = [] for (let i = 0; i < numberOfBins; i++) { const binStart = minValue + i * binSize const binEnd = minValue + (i + 1) * binSize bins.push({ binStart: Math.round(binStart * 100) / 100, binEnd: Math.round(binEnd * 100) / 100, values: [], }) } bins[numberOfBins - 1].binEnd = maxValue data.forEach((value) => { const binIndex = Math.floor((value - minValue) / binSize) if (binIndex >= 0 && binIndex < numberOfBins) { bins[binIndex].values.push(value) } }) return bins } ``` --- ## XY Chart `ChartXY` is a collection of series, axes and other chart components. It can be used to create an incredible number of different data visualization elements. ```ts // Creation of ChartXY const lc = lightningChart() const chart = lc.ChartXY() ``` ![Chart with line series](/img/1-million-point-line-trace-light.png#light-mode-only)![Chart with line series](/img/1-million-point-line-trace-dark.png#dark-mode-only) ## Chart title ```ts chart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setTitlePosition('series-left-top') ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `ChartXY` has 3 different levels of backgrounds: - Series background (area enclosed by axes) - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts chart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) .setSeriesBackgroundStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) chart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts chart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const chart = lc.ChartXY({ animationsEnabled: false }) ``` ## User interactions XY chart user interactions can be configured with `setUserInteractions` method. Here you can find a handful of most commonly required examples. For more information, please see generic documentation about [`setUserInteractions`](/features/user-interactions). At time of writing, `ChartXY` comes with following set of available built-in user interactions: - **Panning** (by dragging mouse, using mouse wheel or via pinch with touch devices) - **Zooming** (by dragging mouse, using mouse wheel or via pinch with touch devices) - **Rectangle zooming** (mouse/stylus/touch) - **Restore default view** (double click or keyboard) - **Restore previous view** (double click or keyboard) - **Pagination** (keyboard) ### Example: disable all user interactions ```ts chart.setUserInteractions(undefined) ``` ### Example: force pan/rectangle zoom along both X and Y dimensions ```ts chart.setUserInteractions({ pan: { x: true, y: true }, rectangleZoom: { x: true, y: true }, }) ``` ### Example: swap left and right mouse buttons ```ts chart.setUserInteractions({ pan: { lmb: { drag: {} }, rmb: false, }, rectangleZoom: { lmb: false, rmb: {}, }, }) ``` ### Example: modify wheel zoom behavior with scrolling axis (zoom toward pointer, stop scrolling axis) ```ts chart.setUserInteractions({ zoom: { mode: 'toward-pointer', stopScroll: true, }, }) ``` ### Example: switch "restore default view" to "restore previous view" ```ts chart.setUserInteractions({ restoreDefault: false, restorePrevious: { doubleClick: true, ctrl: { code: 'KeyZ', }, }, }) ``` ### Example: Mouse wheel pan on Y axis Above examples mostly configure interactions directly inside chart area. A different set of interactions can be defined for `xAxis` and `yAxis`, or for both using `axes`. ```ts chart.setUserInteractions({ yAxis: { pan: { wheel: {}, }, zoom: { wheel: false }, }, }) ``` It is also possible to configure different set of interactions for two different axes, even if both are Y for example: ```ts axis1.setUserInteractions(...) axis2.setUserInteractions(...) ``` ### Example: pan X/Y with mouse wheel depending on ctrl/shift keys 1. on mouse wheel scroll: zoom 2. on mouse wheel scroll + shift: pan on X axis 3. on mouse wheel scroll + ctrl: pan on Y axis ```ts chart.setUserInteractions({ zoom: { shift: false, ctrl: false, }, pan: { // lmb: {}, // <-- required on older versions than v8.1 due to a library bug wheel: false, shift: { wheel: { x: true, y: false }, }, ctrl: { wheel: { y: true, x: false }, }, }, }) ``` For more generic information about user interactions, please see generic documentation about [`setUserInteractions`](/features/user-interactions). ## Coordinate translations LightningChartJS has a number of different coordinate systems that are not natively present on a web page - most importantly, the charts Axes. In many data visualization use cases it is critical to add annotations or markers at specific coordinates along the Axes and data series. Because of this, the ability to translate between coordinates on the web page and the axes is critical. In this context, "client" refers to the Web API Client coordinate system. ### Translating client coordinate to Axis ```ts chart.seriesBackground.addEventListener('click', event => { const locationAxis = chart.translateCoordinate(event, chart.coordsAxis) }) ``` :::info `chart.coordsAxis` is a shorthand for `{ x: chart.axisX, y: chart.axisY }` ::: ### Translating Axis coordinate to client ```ts const locationAxis = { x: 0, y: 0 } const locationClient = chart.translateCoordinate(locationAxis, chart.coordsAxis, chart.coordsClient) ``` :::info `chart.coordsAxis` is a shorthand for `{ x: chart.axisX, y: chart.axisY }` ::: ### Relative coordinate system Apart from the widely used "axis" and "client" coordinate system, there is another, the "relative" coordinate system. This is mostly utilized for LightningChart UI components that are positioned as pixel locations relative to the chart (e.g. 10 pixels from left edge etc.). ```ts // Example, translate axis to relative coordinate const locationAxis = { x: 0, y: 0 } const locationRelative = chart.translateCoordinate(locationAxis, chart.coordsAxis, chart.coordsRelative) ``` ## Fixed aspect ratio In some use cases, the visible ratio between X and Y axes must have a specific value (e.g. `1/2`). While it is easy to fix a ratio between the width and height of the chart container using CSS, this doesn't directly correlate to the area enclosed by axes. Within a `ChartXY` the are concepts such as chart titles, axis titles and axis ticks which can use some space within the chart. To realize a fixed aspect ratio, you have to consider these as well. For purposes of fixed aspect ratio use cases, there is a convenience API: `ChartXYLayoutChangedEvent`. This doesn't directly set an aspect ratio, but instead gives information about the dimensions and layout within the chart which can be used to get a fixed aspect ratio. The most common way of doing this is to allocate extra width/height to chart paddings in order to fix the aspect ratio of axes area: ```ts // Example: Fixed aspect ratio, extra space is allocated as chart padding const aspectRatio = 1 // width / height chart.addEventListener('layoutchange', (layout) => { const spaceAvailable = { x: layout.viewportWidth + chart.getPadding().right, y: layout.viewportHeight + chart.getPadding().bottom } // Fit desired aspect ratio within space available in chart. let width let height if (spaceAvailable.x / spaceAvailable.y > aspectRatio) { height = spaceAvailable.y width = height * aspectRatio } else { width = spaceAvailable.x height = width / aspectRatio } chart.setPadding({ right: chart.getPadding().right + (layout.viewportWidth - width) + 10, bottom: chart.getPadding().bottom + (layout.viewportHeight - height) + 10, }) }) ``` ## Cursors Please see common [Cursors](/features/cursor) section. ## Axis Please see common [Axis](/features/axis) section. ## Legend Please see common [Legend](/features/legend) section. ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Line(Xy) The best and most appreciated feature of LightningChart JS is the simple `LineSeries`. This guide goes through most of the things you can do with it and how. ![Chart with line series](/img/1-million-point-line-trace-light.png#light-mode-only)![Chart with line series](/img/1-million-point-line-trace-dark.png#dark-mode-only) ```ts // Creation of a line series const lineSeries = chart.addLineSeries() ``` :::tip Line series are actually `PointLineAreaSeries` with disabled area fill and point fill. This is mainly for performance reasons - this 1 class can be styled to many different use cases. ::: ## Adding data There are many different ways you can add data to a Line series. The most basic way is to specify 2 number arrays, 1 for X coordinates, and another for Y coordinates. These arrays can be either primitive `number` arrays, or [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)s. ```ts lineSeries.appendSamples({ xValues: [0, 1, 2], yValues: [10, 12, 5] }) ``` Another common way of adding data is to read from a list of JavaScript objects: ```ts lineSeries.appendJSON( [ { i: 0, voltage: 4.2 }, { i: 1, voltage: 4.6 }, { i: 2, voltage: 4.5 }, ], ) ``` :::warning `appendJSON` will by default store **all** supplied data properties. If you only want to load specific properties into the data set, then you should whitelist them: ```ts lineSeries.appendJSON(data, { whitelist: ['index', 'value'] }) ``` ::: Data input can also be list of tuples: ```ts lineSeries.appendJSON([ [0, 100], [1, 108], [2, 105] ]) ``` > A common user problem is that data is supplied as `string` instead of `number` - please confirm this if you are experiencing any strange issues. ```ts console.log(typeof y) // ---> 'number' ``` Any value can also be supplied as a single `number` to use same value for all samples: ```ts lineSeries.appendSamples({ yValues: [10, 12, 5], size: 5 }) ``` You can also mark data properties for automatic incrementation: ```ts const lineSeries = chart.addLineSeries({ schema: { index: { auto: { start: 0, step: 1 } } } }) .appendSamples({ yValues: [10, 12, 5] }) ``` ## Schema and data mapping Both schema and data mapping concepts are technically optional (as in you can ignore them, supply data to series and most of the times it will work). However, depending on the use case you may need to understand what they are and how to configure them. **Schema defines what data can be stored in a series or data set** ```ts const series = chart.addLineSeries({ schema: { index: { auto: true }, x: { pattern: 'progressive' }, y: { storage: Float32Array }, pointSize: {} } }) ``` If schema is not defined, it is automatically configured to match first incoming data. First pushed data can also expand the configured schema - meaning you can configure known data properties, and still be able to consume other data properties that you may not know beforehand. **Data mapping specifies how data properties of the schema should be used** ```ts series.setDataMapping({ x: 'index', y: 'y', size: 'pointSize' }) ``` Series and data sets can contain any number of different data properties. The above example says: "use 'index' as X, 'y' as Y and 'pointSize' as size of points". If data mapping is not defined, it is automatically configured by looking at what is available and what are the names of data properties. **While this works most of the time, explicit data mapping configuration is recommended to not leave such a critical configuration up to chance**. Data mapping can be changed at any point during runtime. It must include at least `x` and `y`. Optionally also `color`, `size`, `lookupValue` and `rotation` can be specified. ### Data patterns Oftentimes, XY Series are used to visualize data that is progressive in 1 dimension. For example, in date-time use cases, the data is generally in increasing time order. **LightningChart JS series are heavily optimized for this particular scenario, where data is ordered.** Data patterns should be specified as part of schema: ```ts const series = chart.addLineSeries({ schema: { x: { pattern: 'progressive' }, y: { pattern: null } } }) ``` If data pattern is not specified in application code, LightningChart JS will automatically check if the data _seems_ progressive and if positive will automatically enable the progressive optimizations. This will result in a warning displayed in the console. :::tip Using automatic data pattern detection is OK during initial testing, proof of concept development, etc. But for long term, it is recommended to explicitly specify the schema and data patterns. If you want to make sure you don't miss any important configuration due to them being filled automatically, you may want to enable strict mode: ```ts // With strict mode, any configuration that is not explicitly specified will immediately throw an Error const series = chart.addLineSeries({ strictMode: true }) ``` ::: ## Using timestamps `PointLineAreaSeries` and `DataSetXY` APIs can directly consume timestamp data as any of following types: - [Date time string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format) - [Date object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) - UTC timestamp `number` (e.g. `Date.getTime()`) :::warning Features other than `PointLineAreaSeries`/`DataSetXY` currently only support direct input as UTC timestamp numbers! ::: ```ts // Example 1, date time string lineSeries.appendSample({ x: '2022-12-31T22:00:00.000Z', y: Math.random() }) ``` ```ts // Example 2, Date object lineSeries.appendSample({ x: new Date(2023, 0, 1), y: Math.random() }) ``` ```ts // Example 3, UTC timestamp number setInterval(() => { lineSeries.appendSample({ x: Date.now(), y: Math.random(), }) }, 100) ``` When using timestamps, you generally want to also setup a [Date-Time Axis](/features/axis/#date-time-axis). :::tip Displaying nanosecond timestamps requires some extra configurations. See [Nanosecond timestamps](/more-guides/nanosecond-timestamps/) for more details. ::: ## Data cleaning / maximum memory use In streaming use cases, memory usage can be limited by configuring the maximum number of samples that are retained. After this number is reached, the oldest samples will be dropped to keep only the max number of samples in memory. ```ts // Example, keep max 1 million samples. lineSeries.setMaxSampleCount(1_000_000) ``` Alternatively, if you are uncertain what value to use and don't want to allocate too much memory up-front, you can start small and automatically increase the buffer size as samples flow in: ```ts lineSeries.setMaxSampleCount({ mode: 'auto', max: 10_000_000 }) ``` This would first allocate only small amount of memory, progressively increase memory allocation as samples come in until eventually limiting sample count to 10 million. ## String data types The main purpose of LightningChart JS is to visualize numerical data. It is possible, however, to load data that is string typed and show it in cursors or custom interactions. This is a two step process: 1. Specify data property as non-numeric 2. When accessing the data property via library API, the type must be cast from number to string ```ts // Example, load non numeric data and display it in cursor const series = chart .addPointLineAreaSeries({ schema: { x: { pattern: 'progressive' }, y: { pattern: null }, extra: { nonNumeric: true }, }, }) .appendSamples({ x: [0, 1, 2, 3, 4], y: [0, 10, 5, 8, 9], extra: ['a', 'b', 'c', 'd', 'e'], }) .setCursorFormattingOverride((hit, before) => { return [...before, ['Extra', '', String(hit.sample['extra'])]] }) ``` Non-numeric data can't be used in the actual visualization at all, but it can be accessed in cursors, series interaction handlers with solve nearest / read back APIs. ## Changing stroke color ```ts const fillRed = new SolidFill({ color: ColorRGBA(255, 0, 0, 255) }) lineSeries.setStrokeStyle((stroke) => stroke.setFillStyle(fillRed)) ``` Color can also be defined in a handful of other ways. ```ts const fillBlue = new SolidFill({ color: ColorHEX('#0000ff') }) const fillGreen = new SolidFill({ color: ColorCSS('green') }) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Changing stroke thickness ```ts lineSeries.setStrokeStyle((stroke) => stroke.setThickness(1)) ``` By setting thickness to exactly `-1` you can enable "primitive line rendering". This is very light on GPU utilization (works well on weak machines), but results in lines being 1 pixel thick and not very smooth. ```ts // Enable primitive line rendering - 1 px thick and considerably lighter on GPU. lineSeries.setStrokeStyle((stroke) => stroke.setThickness(-1)) ``` ## Dashed line series ```ts lineSeries.setStrokeStyle((stroke) => new DashedLine({ fillStyle: stroke.getFillStyle(), thickness: stroke.getThickness(), pattern: StipplePatterns.Dashed, })) ``` ## Connecting to non-default axis If your ChartXY has several axes, then you can specify which axis the series should be connected to like this: ```ts const lineSeries = chart.addLineSeries({ xAxis: myXAxis, yAxis: myYAxis, }) ``` ## Data gaps If your data has gaps, which should not be connected in the line visualization, you can create a gap with a `NaN` Y value: ```ts lineSeries.appendSamples({ yValues: [1, 2, NaN, 2, 1] }) // ---> line from { x: 0, y: 1 } to { x: 1, y: 2 }, then GAP, starts again from { x: 3, y: 2 }. ``` Please note that the value has to be specifically `NaN` - `undefined`, `null` or any other value will not have the same effect. ## Solve nearest from location ```ts // Example 1, when user clicks on chart, log closest data point to console chart.seriesBackground.addEventListener('click', event => { const nearest = lineSeries.solveNearest(event) console.log(nearest) }) ``` ```ts // Example 2, solve nearest data point from an axis coordinate const locationAxis = { x: 10, y: 10 } const nearest = lineSeries.solveNearest(locationAxis) console.log(nearest) ``` Solve results include just about all possible information about the nearest data point: x, y, individual colors/lookup values if relevant, the sample index as well as any other data properties of that sample even if they are not mapped to the series. ## Interactions with data points All series support tracking user interactions: ```ts lineSeries.addEventListener('click', (event, hit) => { // hit.x // hit.y console.log(hit) }) ``` The best way to see what events are available is to have type checking in your development environment, and start writing `addEventListener` (they are strongly typed). Most commonly used events are: - `'click'` - `'contextmenu'` - `'dblclick'` - `'pointerenter'` - `'pointermove'` - `'pointerleave'` - `'pointerdown'` - `'pointerup'` In above example, the `hit` variable will include just about all possible information about the interacted data point: x, y, individual colors/lookup values if relevant, the sample index as well as any other data properties of that sample even if they are not mapped to the series. ## Individual colors To color line with per-data point colors: 1. Load color data (as numbers or `Color` objects) to a data property. 2. Map above data property to `"color"` 3. Set fill style to `IndividualPointFill`. ```ts const lineSeries = chart.addLineSeries({ schema: { x: { auto: true } }, }) .setStrokeStyle((stroke) => stroke.setFillStyle(new IndividualPointFill())) .setDataMapping({ x: 'x', y: 'yValues', color: 'colors' }) .appendSamples({ yValues: [10, 12, 9], colors: [0xff0000ff, 0xff00ff00, 0xff00ff00], // red, green, green }) ``` ![Individual color line series](/img/individual-color-line-light.jpeg#light-mode-only)![Individual color line series](/img/individual-color-line-dark.jpeg#dark-mode-only) Colors can also be supplied as `Color` objects created using one of the many Color factories (`ColorRGBA`, `ColorHEX`, etc.). However, for performance reasons you should avoid creating a large number of individual `Color` objects. ```ts const colorRed = new SolidFill({ color: ColorRGBA(255, 0, 0) }) const colorGreen = new SolidFill({ color: ColorRGBA(0, 255, 0) }) lineSeries .appendSamples({ yValues: [10, 12, 9], // This is fine (reuse same color objects) colors: [colorRed, colorGreen, colorGreen], }) ``` ## Color by lookup table Alternative approach to individual colors, you can give each data point a `number` value, and specify rules for getting a color from any number: ```ts const lineSeries = chart.addLineSeries({ schema: { x: { auto: true } }, }) .setStrokeStyle((stroke) => stroke.setFillStyle(new PalettedFill({ lookUpProperty: 'value', lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) }))) .setDataMapping({ x: 'x', y: 'yValues', lookupValue: 'lookupValues' }) .appendSamples({ yValues: [10, 12, 9], lookupValues: [0, 100, 50] }) ().add(chart) ``` ![Lookup color line series](/img/color-lookup-line-light.jpeg#light-mode-only)![Lookup color line series](/img/color-lookup-line-dark.jpeg#dark-mode-only) `LUT` has also a `percentageValues` option, which you can use if you want to use % values of the present min-max lookup value range, rather than specific value steps: ```ts new LUT({ percentageValues: true, interpolate: true, steps: [ // 0 = 0 %, 1 = 100% { value: 0, color: ColorCSS('red') }, { value: 1, color: ColorCSS('green') }, ] }) ``` ## Color by X or Y Instead of using lookup values in data set, you can also use X or Y coordinates directly, by changing the `lookUpProperty` value in `PalettedFill`. ```ts new PalettedFill({ lookUpProperty: 'x', // 'x' or 'y' lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) }) ``` ## Color by gradient More alternatives to dynamic coloring - you can also define a gradient (linear or radial), to color the line stroke. ```ts const lineSeries = chart.addLineSeries() .setStrokeStyle((stroke) => stroke.setFillStyle( new LinearGradientFill({ // down -> up angle: 0, stops: [ { offset: 0, color: ColorCSS('red') }, { offset: 1, color: ColorCSS('green') }, ], }) )) ``` ![Gradient color line series](/img/gradient-line-light.jpeg#light-mode-only)![Gradient color line series](/img/gradient-line-dark.jpeg#dark-mode-only) ## Edit samples data See also [Affine transforms](#scaling-offsetting-and-transforming-coordinates) ### alterSamplesStartingFrom This method allows you to specify a starting location in the existing data set with a _sample index_. This is a simple counter that points to the _n:th_ sample that has been pushed to the data set, in the order of appearance. ```ts lineSeries.appendSamples({ yValues: [0, 1, 2, 3, 4, 5] }) lineSeries.alterSamplesStartingFrom(1, { yValues: [10, 11] }) // ---> Data set = [0, 10, 11, 3, 4, 5] ``` `alterSamples` lets you modify any subset of existing data properties. It also supports automatically appending data if you attempt to modify samples that would come after the existing ones. ```ts // Example, load same value for all altered samples lineSeries.alterSamplesStartingFrom(1, { size: 5 }, { count: 5 }) ``` ### alterSamplesByIndex Alter samples with specific _sample index_. Can be used same as `alterSamplesStartingFrom` but for samples that are not in continuous order. ```ts lineSeries.appendSamples({ yValues: [0, 1, 2, 3, 4, 5] }) lineSeries.alterSamplesByIndex([0, 2], { yValues: [10, 11] }) // ---> Data set = [10, 1, 11, 3, 4, 5] ``` ```ts // Example, load same value for all altered samples lineSeries.alterSamplesByIndex([0, 2], { size: 5 }) ``` ### alterSamplesByMatch This method allows you to arbitrarily edit any number of existing samples without knowing their sample index. Instead, samples are selected by matching value of a specific data property. Generally, this would be some property that is unique between samples, such as X or ID. ```ts lineSeries.appendSamples({ xValues: [0, 1, 2, 3, 4], yValues: [100, 101, 102, 103, 104], }) lineSeries.alterSamplesByMatch( 'xValues', [2, 4], // Edit samples x=2 and x=4 { yValues: [200, 201], } ) ``` ```ts // Example, load same value for all altered samples lineSeries.alterSamplesByMatch('xValues', [2, 4], { size: 5 }) ``` ### fill This method allows you to load a single value for all existing samples in the data set: ```ts // Set point size of all samples to 5 pixels lineSeries.fill({ size: 5 }) ``` ## Data storage optimization By default, all data is stored as 64-bit floats. In many use cases, full 64-bit resolution is not necessary or even completely redundant (if source data has less precision). It is possible to individually configure each data properties data storage format: ```ts const lineSeries = chart.addLineSeries({ schema: { x: { storage: Float64Array }, y: { storage: Float32Array }, color: { storage: Uint32Array }, } }) ``` Available options are: - `Float64Array` - `Float32Array` - `Int32Array` - `Uint32Array` - `Int16Array` - `Uint16Array` - `Int8Array` - `Uint8Array` - `Uint8ClampedArray` Reducing number precision results in less memory usage being required to load same amount of data. :::warning Some browsers store typed array outside JS heap (which is commonly used to debug application memory usage). Thus, LightningChart JS memory usage is generally not perceivable by looking at JS heap size. Instead, developer tools memory snapshots are a better tool. ::: ## Avoiding input data duplication In order to feed data into a series or data set, the data must first be loaded to the JavaScript application. Later, the data is placed into LightningChart JS internal data storage. It is possible to completely avoid any data duplication between these two steps, effectively reducing memory consumption by up to 2x. If following conditions are met, then input data can be directly referenced by the library without making a copy: - The data set is empty. - Input data is supplied as [Typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays). - Max sample count is not configured, or exactly matches the number of incoming samples. - Input data is of same Typed array variant as the configured storage format. ```ts // Example of configured storage matching incoming data format const series = chart.addLineSeries({ schema: { x: { storage: Float64Array, ensureNoDuplication: true }, y: { storage: Float32Array, ensureNoDuplication: true } } }) let xValues: Float64Array let yValues: Float32Array series.appendSamples({ x: xValues, y: yValues }) ``` `ensureNoDuplication` is not necessary in above example. If it is set to `true`, then the application will throw an error if data would have to be duplicated. :::tip If your application requires referencing the same data that is visualized with LightningChart JS, the most efficient approach is to not keep any data cache in the application itself, but read back the data from LightningChart when required. Or if you can ensure that the input data is not duplicated inside the library, that is another good approach. ::: ## Multi-threading LightningChart JS line series can utilize several CPU cores in parallel to perform even faster and more efficiently. This is **not** enabled by default. **Requirements to use multi-threading:** - The JavaScript runtime must support usage of `SharedArrayBuffer` - Please refer to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer for more information. - Data must either be directly supplied with `SharedArrayBuffer`-backed Typed arrays, or the LC dataset must be configured to do this under the hood with `useSharedArrayBuffers` option. **When is multi-threading useful?** - You have several line series and want faster loading times - With multi-threading, loading times become generally **6x faster**. - You want to optimize the main thread for user interactivity at all times, even while large datasets are being loaded, or user is zooming in/out. - When using multi-threading, **CPU usage stays low regardless how massive datasets you are loading**. The heavy work will be done on other CPU core(s). **How to enable multi-threading?** It's really simple: ```ts const lineSeries = chart.addLineSeries({ useSharedArrayBuffers: true }) ``` By enabling the `useSharedArrayBuffers` flag, the LC DataSet will automatically ensure that its internal dataset is based on `SharedArrayBuffer`s, and whenever there is a suitable moment to utilize multi-threading the charts will do so. ### Async rendering After enabling multi-threading, there will be some differences in how the charts behave. Normally, all LightningChart JS functionality is completely synchronous - if you do something, it will be shown next frame. With multi-threading enabled, render updates can be postponed to following frames. This has two main implications: 1. During this short time, the chart will display a preview of the data. This is essentially a lossy, quick downsample of the data, that is soon after replaced with the proper dataset after being prepared for rendering. 2. `requestAnimationFrame` can't be used to know when the chart has updated. Instead, you need to use the `renderframe` event: ```ts chart.engine.addEventListener('renderframe', (event) => { if (!event.asyncRenderingPending) { // Chart has finished all sync and async updates } }) ``` ### Input data duplication When `useSharedArrayBuffers: true` is used, there are additional requirements for charts to not need to duplicate the user-supplied data. Namely, the user data must be backed by `SharedArrayBuffer` to begin with. Most of the times, it is not possible to fetch remote data to frontend as `SharedArrayBuffer`s without a duplication step in between. If you want to confirm this for your use case, please [contact our tech team](/contact). ### Worker allocation By default, multi-threading will create as many web workers as there are series requiring heavy CPU work, but no more than value of `navigator.hardwareConcurrency` (so things will work regardless of number of CPU cores). If you need to limit amount of webworkers utilized by LightningChart JS, you can specify a custom cap: ```ts const lc = lightningChart({ webWorkers: { maxWorkers: 4, }, }) ``` ## Read back data Existing data in a `PointLineAreaSeries` can be read back using `readback` method: ```ts lineSeries.readback() // xValues: TypedArray // yValues: TypedArray // iSampleFirst: number // lookupValues?: TypedArray // colors?: TypedArray // ids?: TypedArray // sizes?: TypedArray // rotations?: TypedArray ``` With progressive data sets you can also conveniently read back only data in given range: ```ts series.readBack({ onlyInRange: chart.xAxis.getInterval() }) series.readBack({ onlyInRange: { start: 0, end: 100 } }) ``` `series.readBack` only returns data that is actively used by the series (according to its data mapping). The full data set can be read via `DataSetXY`: ```ts lineSeries.getDataSet().readBack() // data: Record // iSampleFirst: number ``` ## Separating data sets from series In basic use cases, the recommended way to display data in a chart is to just 1. Create series 2. Push data into the series Under the hood, this automatically creates a `DataSetXY` and sets it as the active data set. Conversely, you can create this data set object in your application code and manage it yourself: ```ts const dataSet = new DataSetXY({ schema: { x: { auto: true }, y1: {}, y2: {} } }) series.setDataSet(dataSet) ``` :::tip All APIs that revolve around inputting data, such as `schema`, `appendSamples`, `setMaxSampleCount` etc. are available on both series and `DataSetXY`. ::: Separating the data set from series like this allows some potentially useful functionalities: ### Shared timestamps or other data ```ts const series1 = chart.addLineSeries() .setDataSet(dataSet, { x: 'x', y: 'y1' }) // <- data mapping const series2 = chart.addLineSeries() .setDataSet(dataSet, { x: 'x', y: 'y2' }) ``` While you could very easily do this without using `DataSetXY` at all, that would result in multiple data sets being created and X data storage being duplicated! Especially if you have large number of channels that have shared timestamps, using a shared data set is much more efficient! ### Switching between axis types It is currently not possible to move a series from an axis to another. But if you separate data set from series, then you can create series on both axes (reusing the same data set), and keep the other hidden. ## Scaling, offsetting and transforming coordinates It is possible to configure the library to apply an affine transform (offset + scaling) to rendered data points. This is very performant, but only affects rendered output. It can result in discrepancies in cursors and automatic axis scrolling. ```ts // Example syntax, multiple Y coordinates by 10 and subtract 5 series.setRenderTransform({ x: { scaling: 1, offset: 0 }, y: { scaling: 10, offset: -5 } }) ``` --- ## Pictures(Xy) A common need in different XY Chart based visualizations is to show custom pictures positioned on axis coordinates. For example, a picture bound to a timestamp on X axis. This can be done using a specifically configured [Scatter chart](/features/xy/scatter). There are two different approaches clearly separated in this documentation. ### Icons as Point shape `PointLineAreaSeries` supports point shape as a `Icon` object. This results in the Icon image source being used like a mask texture. The colors of the picture are overridden by whatever is the active fill style of the series. This can be convenient as you can use one picture and programmatically color it with various colors, or use advanced coloring approaches like dynamic coloring. Below picture shows scatter chart with icon shape, individual sizes, individual rotations and radial gradient fill. ![Icon scatter chart](/img/scatter-icon-light.png#light-mode-only)![Icon scatter chart](/img/scatter-icon-dark.png#dark-mode-only) ```ts const image = new Image() image.crossOrigin = '' image.src = 'image.png' const iconSeries = chart .addPointSeries() .setPointShape(chart.engine.addCustomIcon(image)) // Point alignment can be used to offset the images relative to their size. For example, position image by its bottom. .setPointAlignment({ x: 0, y: -1.1 }) // When using Icons, point size is interpreted as multiplier of source image size. .setPointSize(0.8) .appendSample({ x: 0, y: 0 }) ``` For more documentation, please refer to documentation of [Scatter chart](/features/xy/scatter). ### Image Fill `PointLineAreaSeries` supports point fill style as a `ImageFill`. This results in points rendered as the supplied image source. ![Image fill scatter chart](/img/scatter-image-fill-light.png#light-mode-only)![Image fill scatter chart](/img/scatter-image-fill-dark.png#dark-mode-only) ```ts const warningImage = new Image() warningImage.crossOrigin = '' warningImage.src = 'warning.png' const eventSeries = chart .addPointSeries() .setPointFillStyle(new ImageFill({ source: warningImage })) // Point alignment can be used to offset the images relative to their size. For example, position image by its bottom. .setPointAlignment({ x: 0, y: -1.1 }) // When using ImageFill, point size is interpreted as multiplier of source image size. .setPointSize(0.8) .appendSample({ x: 0, y: 0 }) ``` To clarify, this approach can be used to display a massive amount of custom pictures, by simply adding more samples to the series. For more documentation, please refer to documentation of [Scatter chart](/features/xy/scatter). --- ## Point-Line(Xy) Combination of displaying points and connected line strokes. ![Chart with point line series](/img/point-line-light.jpeg#light-mode-only)![Chart with point line series](/img/point-line-dark.jpeg#dark-mode-only) ```ts // Creation of a point line series const pointLineSeries = chart.addPointLineSeries() ``` NOTE: Point line series automatically hides points when the data is very dense or viewed with a small zoom level. This is intentional, as otherwise the visualization will just look silly. For "points" oriented guides, please refer to [Scatter](/features/xy/scatter) section. Otherwise, the point line series functions exactly same as a [Line series](/features/xy/line). --- ## Scatter(Xy) A commonly needed visualization feature is to display a set of X+Y points scattered around. ```ts // Creation of a scatter series const scatterSeries = chart.addPointSeries() ``` ![Chart with scatter series](/img/scatter-chart-light.png#light-mode-only)![Chart with scatter series](/img/scatter-chart-dark.png#dark-mode-only) ## Point color ```ts scatterSeries.setPointFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Point border ```ts scatterSeries.setPointStrokeStyle(new SolidLine({ thickness: 2, fillStyle: new SolidFill({ color: ColorRGBA(255, 0, 0) }) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Point shape There are a handful of shape options to choose from, like `Star`, `Circle`, `Square`, `Arrow`, etc. ```ts scatterSeries.setPointShape(PointShape.Star) ``` ## Point size ```ts // 5 pixels point size scatterSeries.setPointSize(5) ``` ## Point rotation ```ts // Rotate points by 45 degrees scatterSeries.setPointRotation(45) ``` ## Point stroke A stroke can be drawn along the edges of points with `setPointStrokeStyle` method: ```ts // Example, 2px thick black stroke series.setPointStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 0) }) })) ``` ## Point stroke A stroke can be drawn along the edges of points with `setPointStrokeStyle` method: ```ts // Example, 2px thick black stroke series.setPointStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 0) }) })) ``` ## Adding data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Schema and data mapping This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Individual colors This section works the same as for `Line`, with only exception being that `IndividualPointFill` should be set as **points** fill style rather than **line** fill style. ```ts const scatterSeries = chart.addPointSeries() .setPointFillStyle(new IndividualPointFill()) ``` ![Individual color scatter series](/img/individual-color-scatter-light.jpeg#light-mode-only)![Individual color scatter series](/img/individual-color-scatter-dark.jpeg#dark-mode-only) to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Individual rotations ```ts const scatterSeries = chart.addPointSeries() .appendSamples({ xValues: [4, 2, 6], yValues: [5, 2, 8], rotations: [0, 45, 135] }) .setDataMapping({ x: 'xValues', y: 'yValues', rotation: 'rotations' }) ``` ## Individual point sizes See [Bubble chart](/features/xy/bubble/) ## Color by lookup table This section works the same as for `Line`, with only exception being that `PalettedFill` should be set as **points** fill style rather than **line** fill style. ```ts const scatterSeries = chart.addPointSeries() .setPointFillStyle(new PalettedFill({ lookUpProperty: 'value', lut: new LUT({ interpolate: true, steps: [ { value: 0, color: ColorCSS('red') }, { value: 100, color: ColorCSS('green') }, ] }) })) ``` ![Lookup color scatter series](/img/color-lookup-scatter-light.jpeg#light-mode-only)![Lookup color scatter series](/img/color-lookup-scatter-dark.jpeg#dark-mode-only) to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ### Color by X or Y This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Color by gradient This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Using timestamps This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Data cleaning / maximum memory use This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Connecting to non-default axis This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Solve nearest from location This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Interactions with data points This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Edit samples data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Read back data This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Data storage optimization This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Separating data sets from series This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) ## Scaling, offsetting and transforming coordinates This section works the same as for `Line`, to avoid duplication of guides, please refer to the section under [Line](/features/xy/line) --- ## Scrolling heatmap A variant of Heatmap, this feature is suitable for streaming applications where data is measured and visualized in real-time. ![Chart with scrolling heatmap series](/img/scrolling-heatmap-light.png#light-mode-only)![Chart with scrolling heatmap series](/img/scrolling-heatmap-dark.png#dark-mode-only) Scrolling heatmaps work same as [Static heatmaps](/features/xy/heatmap) for the most part. The main differences are creation syntax and data input. ```ts // Creation of scrolling heatmap series const heatmapSeries = chart.addHeatmapScrollingGridSeries({ // Scroll along X axis scrollDimension: 'columns', // Each sample has 5 values resolution: 5, }) .setStart({ x: 0, y: 0 }) .setStep({ x: 1, y: 1 }) ``` ## Data input A sample is added to a scrolling heatmap with `addIntensityValues` method: ```ts heatmapSeries.addIntensityValues([ [0, 1, 2, 3, 4] ]) ``` Alternatively, `invalidateIntensityValues` can also be used. This lets you control which sample index you are defining. ## Using timestamps Most use cases of scrolling heatmaps use timestamped data. In these cases, samples realistically don't always arrive with the same time step. Generally this is worked around by configuring a "minimum perceived time step" for the heatmap: ```ts // Minimum time step that can be displayed by the heatmap // Smaller value means more precision but more RAM and GPU memory usage. const heatmapMinTimeStepMs = 1 // 1 millisecond heatmapSeries.setStep({ x: heatmapMinTimeStepMs, y: 1 }) ``` Then, input data is mapped from timestamps to "heatmap sample index", and inserted to heatmap using `invalidateIntensityValues` method. This also works even if data doesn't come in correct time order. ```ts let tFirstSample: number | undefined const handleIncomingData = (timestamp, sample) => { if (!tFirstSample) { tFirstSample = timestamp heatmapSeries.setStart({ x: timestamp, y: 0 }) } // Calculate sample index from timestamp to place sample in correct location in heatmap. const iSample = Math.round((timestamp - tFirstSample) / heatmapMinTimeStepMs) heatmapSeries.invalidateIntensityValues({ iSample, values: [sample], }) } ``` The heatmap automatically fills any gaps between samples by repeating the previous sample value. So, actually 1 sample can occupy several data slots in the heatmap. :::info If the axis doesn't let you zoom in far enough, consider enabling ["high precision" axis](/features/axis/#zoom-ability) ::: ## Data cleaning ```ts // Enable automatic cleaning of data that is not visible heatmapSeries.setDataCleaning({ minDataPointCount: 1 }) ``` Scrolling heatmap doesn't currently have a method to control maximum memory usage precisely. `setDataCleaning` effectively enables "lazy" data cleaning, which will prevent applications from running out of memory, but when this data cleaning exactly happens is not guaranteed. --- ## Spline The Spline series is a point-line series with an automatic polynomial interpolation that produces smooth curves. ![Chart with spline series](/img/spline-light.jpeg#light-mode-only)![Chart with spline series](/img/spline-dark.jpeg#dark-mode-only) ```ts // Creation of a spline series const splineSeries = chart.addSplineSeries() ``` For "points" oriented guides, please refer to [Scatter](/features/xy/scatter) section. Otherwise, the spline series functions exactly same as a [Line series](/features/xy/line). Spline area series can be realized by using functions from [Area series](/features/xy/area). :::tip Spline series are actually `PointLineAreaSeries` with suitable default settings applied. Spline can be enabled with `setCurvePreprocessing` method. ::: --- ## Step The Step series is a point-line series with an automatic stepping interpolation resulting in only sharp turns of curve. ![Chart with step series](/img/step-light.jpeg#light-mode-only)![Chart with step series](/img/step-dark.jpeg#dark-mode-only) ```ts // Creation of a step series const stepSeries = chart.addStepSeries() ``` For "points" oriented guides, please refer to [Scatter](/features/xy/scatter) section. Otherwise, the step series functions exactly same as a [Line series](/features/xy/line). Step area series can be realized by using functions from [Area series](/features/xy/area). :::tip Step series are actually `PointLineAreaSeries` with suitable default settings applied. Step can be enabled and configured with `setCurvePreprocessing` method. ```ts stepSeries.setCurvePreprocessing({ type: 'step', step: 'middle' }) ``` There are 3 different modes available: `'before'`, `'middle'`, `'after'`. ::: --- ## Sweeping updates ![Chart with sweeping line series](/img/sweeping-line-chart-light.png#light-mode-only)![Chart with sweeping line series](/img/sweeping-line-chart-dark.png#dark-mode-only) There is no built-in "sweeping update" mode or equivalent in LightningChart JS. However, there are multiple relatively simple ways to achieve this. The recommended approach depends on your performance requirement. ## Simpler and more straightforward approach > Recommended if your total stream rate is less than 100 000 points per second. > > (number of channels × frequency rate) This approach is based on using the data edit API of `PointLineAreaSeries` in combination with data gaps. Please reference the ["Sweeping Line Chart" example](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-0041-sweepingLineChartNew.html) for details. ## More performant but also complicated approach > Recommended if your total stream rate is more than 100 000 points per second. > > (number of channels × frequency rate) This approach is based on two separate Line series (one for old data, another for newer data) and a Rectangle series for hiding overlapping data. Please reference the ["Sweeping Line Chart Dashboard" example](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-0033-sweepingLineDashboard.html) for details. --- ## Zoom Band Chart The Zoom band chart is generally a complementary chart type used in conjunction with 1 or more [XY charts](/features/xy). Usually, it is used to display the full range of data at a completely zoomed out level, while allowing the user to inspect the data closer up in the `ChartXY`. ![Zoom band chart](/img/zoomband-light.png#light-mode-only)![Zoom band chart](/img/zoomband-dark.png#dark-mode-only) The Zoom band chart makes it convenient for users to interact with the displayed data range. For example, you can drag on the zoom band to move the view around, or click on a position that you want to see. ```ts // Creation of a Zoom band chart const lc = lightningChart() const zoomBandChart = lc.ZoomBandChart() ``` ## Access to LightningChart resources The Zoom band chart feature utilizes static file assets distributed along side LightningChart library. These are used by the built-in themes to display the "Knobs" that users can interact with. In order to display these picture assets, setting up local hosting of LC resources is needed. **This is optional, and only side effects are 1) Knobs don't show up 2) File not found error in console** For instructions of setting up LightningChart resources, please see [LightningChart resources](/more-guides/lc-resources/) ## Adding series to Zoom band chart The idea of Zoom band chart is that it displays _copies_ of series that belong to some other `ChartXY`. Series are attached to zoom band chart with `add` method: ```ts const lc = lightningChart() const chartXY = lc.ChartXY() const lineSeries = chartXY.addLineSeries() .appendSamples({ yValues: [10, 12, 15, 8, 5] }) const zoomBandChart = lc.ZoomBandChart() // (!) zoomBandChart.add(lineSeries) ``` The zoom band chart automatically matches and displays any data changes to the original series. ## Styling series in Zoom band chart By default, copied series in Zoom band chart use exactly same style as the original series. Alternatively, you can apply a different style to the series in Zoom band chart: ```ts const zbcSeries = zoomBandChart.add(lineSeries) .setStrokeStyle((stroke) => stroke.setThickness(1)) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Removing series Series can be removed from Zoom band chart using `disposeSeries` method: ```ts // Example, remove series from being displayed in zoom band chart // NOTE: parameter should be the "original series" zoomBandChart.disposeSeries(lineSeries) ``` This needs to be done only when you want to remove a series from zoom band chart. If you are removing the original series, then it is enough to `dispose()` that series to destroy it, which will automatically be reflected in Zoom band chart also. ## Axis Zoom band chart automatically mimics the axis of its attached series. For example, if you add a series to the Zoom band chart which is attached to a Date-Time axis, the Zoom band chart will also use a Date-Time axis. Otherwise, Zoom band chart axes work the same as `ChartXY` axes. **When using Date-Time axis with small time ranges (few days or smaller), you need to enable "high precision axis"**: ```ts const zoomBandChart = lc.ZoomBandChart({ defaultAxis: { type: 'linear-highPrecision' } }) ``` For more information, APIs and guides about Axis, please refer to common [Axis](/features/axis) section. ## Vertical orientation By default, Zoom band chart works on a "horizontal" manner, taking the X Axis of series as the "shared" axis (usually Date-Time). This can be also changed to "vertical": ```ts const zoomBandChart = lc.ZoomBandChart({ orientation: 'y' }) ``` ## Shared value axis Zoom band chart can display several series at once. These series can also be attached to different Y axis, and might represent different units (Voltage, Temperature, etc.). However, in Zoom band chart all the series are displayed in the same series area, without separating the different Y axes. By default, every unique value axis in Zoom band chart will have its own scale. For example, if you attach 2 series with different value axes, those series values will **not** be comparable to each other in the Zoom band chart: ![Zoom band chart](/img/zoomband-individual-scales-light.png#light-mode-only)![Zoom band chart](/img/zoomband-individual-scales-dark.png#dark-mode-only) On the other hand, if the 2 series belong to the same value axis (Y in this case), then they will share the same scale in Zoom band chart and be comparable: ![Zoom band chart](/img/zoomband-same-scale-light.png#light-mode-only)![Zoom band chart](/img/zoomband-same-scale-dark.png#dark-mode-only) There is also an option to force every series in Zoom band chart to use the same value Axis, even if the series belong to different value axes. This also shows the value axis ticks, as there is only 1 value axis in Zoom band chart to display in contrast to having several different axes. ```ts const zoomBandChart = lc.ZoomBandChart({ useSharedValueAxis: true }) ``` ![Zoom band chart](/img/zoomband-shared-scale-light.png#light-mode-only)![Zoom band chart](/img/zoomband-shared-scale-dark.png#dark-mode-only) ## Axis layout When the main chart has multiple Y axes with stacking (`iStack`) or parallel positioning (`iParallel`), you can configure the Zoom band chart to mirror this layout. By default, all series in the Zoom band chart overlap in the same area regardless of their axis positioning in the main chart. With `useAxisLayouts: true`, the Zoom band chart will display each series according to its axis position, matching the main chart's layout: ```ts const chart = lc.ChartXY() chart.getDefaultAxisY().dispose() // Create stacked axes in main chart const axisY1 = chart.addAxisY({ iStack: 0 }).setTitle('Temperature') const axisY2 = chart.addAxisY({ iStack: 1 }).setTitle('Pressure') const axisY3 = chart.addAxisY({ iStack: 2 }).setTitle('Humidity') const series1 = chart.addLineSeries({ axisY: axisY1 }) const series2 = chart.addLineSeries({ axisY: axisY2 }) const series3 = chart.addLineSeries({ axisY: axisY3 }) // Create Zoom band chart that mirrors the stacked layout const zoomBandChart = lc.ZoomBandChart({ useAxisLayouts: true }) zoomBandChart.add(series1) zoomBandChart.add(series2) zoomBandChart.add(series3) ``` :::note `useAxisLayouts` and `useSharedValueAxis` are mutually exclusive. If both are set, `useSharedValueAxis` takes precedence. ::: You can optionally override the axis position for each series when adding to the Zoom band chart: ```ts zoomBandChart.add(series1, { iStack: 2 }) zoomBandChart.add(series2, { iStack: 1 }) zoomBandChart.add(series3, { iStack: 0 }) ``` If not specified, the Zoom band chart mirrors the source axis positions automatically. ## Configuring view The position of the band is not configured via Zoom band chart, but rather the attached axis itself. ```ts chartXY.axisX.setInterval({ start: 0, end: 100 }) ``` For more details, refer to [Axis](/features/axis). ## Monitoring view change Zoom band chart doesn't expose a function for tracking changes to displayed view. However, the attached axes have interval change event which is triggered when Zoom band chart is interacted with: ```ts chartXY.axisX.addEventListener('intervalchange', (event) => { console.log(event) }) ``` ## Automatic alignment between Zoom band chart and attached charts By default, Zoom band chart aligns itself against attached charts. This is done by assuming that the charts are laid on top of each other, or next to each other. However, it is also a valid use case to not align ZBC and the other charts. In this case, you probably want to remove the automatic alignment of ZBC: ```ts // By explicitly configuring ZBC padding, automatic alignment to other charts is disabled zoomBandChart.setPadding({ left: 10, right: 10, top: 0 }) ``` ## Zoom band chart style The Zoom band charts splitters and defocus overlay can be styled individually: ```ts zoomBandChart .setSplitterStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 255) }) })) .setDefocusOverlayFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 0, 150) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Chart title ```ts zoomBandChart .setTitle('Voltage') .setTitleFont(font => font.setSize(10).setFamily('Segoe UI')) .setTitleFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Background style `ZoomBandChart` has 3 different levels of backgrounds: - Series background (area enclosed by axes) - Chart background (entire chart area) - Engine background (additional background shared by entire engine) - Understanding difference between chart/engine background is mainly useful if you are using the legacy `Dashboard` feature - in this case, engine background is shared across the whole dashboard. ```ts zoomBandChart .setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(255, 0, 0) })) .setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 255, 0) })) .setSeriesBackgroundStrokeStyle(new SolidLine({ thickness: 1, fillStyle: new SolidFill({ color: ColorRGBA(0, 0, 255) }) })) zoomBandChart.engine.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 255) })) ``` For more details about style API, please see [Styles, colors and fonts](/more-guides/styles-colors-and-fonts). ## Space around chart ```ts zoomBandChart.setPadding({ left: 10, right: 10, top: 10, bottom: 10 }) ``` ## Disable animations ```ts const zoomBandChart = lc.ZoomBandChart({ animationsEnabled: false }) ``` ## Positioning charts Please see common [Positioning charts](/more-guides/positioning-charts) section. ## Color themes Please see common [Color themes](/more-guides/themes) section. --- ## Android :::info For latest information of a **native** LightningChart library for Android, check [Android charts](https://lightningchart.com/android-charts/) ::: LightningChart JS is a JavaScript library for high-performance data visualization. It can be embedded into Android applications by placing a separate **Web application** (HTML/CSS/JS) into the code base and loading it inside a `WebView` component. The main application is still developed like any Android application, but the charting parts are isolated to a separate application, which is developed in `JavaScript` or `TypeScript`. Data is generally connected to the Android application, and transferred from there to the Web application. This example shows an example how this can be done. For more information about using Web-based content in Android application, you may refer to materials maintained by Android. See "Web-based content" in Android developer materials, for example. https://developer.android.com/develop/ui/views/layout/webapps Please find the latest examples and instructions for utilizing LightningChart JS in Android applications in our [GitHub template project](https://github.com/Lightning-Chart/lcjs-android-example). More Android materials: - https://lightningchart.com/blog/javascript-charts-in-android-studio/ - https://lightningchart.com/blog/cardiac-mobile-cardiovascular-app/ For the latest updates on Android front you may also [contact us directly](/contact). --- ## Angular Angular is one of the most popular web frontend frameworks at the moment. As such, it is extensively used by LightningChart JS users. Here you will find resources related to using LightningChart JS with Angular. ```sh npm i @lightningchart/lcjs ``` ## Basic usage boilerplate Here's the Angular boilerplate code for creating perhaps the most common LightningChart JS use case - a Line Chart component. ```sh ng generate component components/chart ``` 1. Prepare `` element where LightningChart is placed ```html ``` ```css /* chart.component.css */ div { width: 100%; height: 40vh; } ``` 2. Create and manage LightningChart JS components in Angular component ```ts // chart.component.ts export class ChartComponent implements OnDestroy { private destroyLC?: () => unknown constructor() { // Only in browser (not server side); afterNextRender(() => { const container = document.getElementById('chart') const lc = lightningChart() const chart = lc.ChartXY({ container, theme: Themes.light }) const lineSeries = chart.addLineSeries() .appendSamples({ yValues: [0, 10, 8, 4, 7] }) this.destroyLC = () => { lc.dispose() } } } ngOnDestroy(): void { if (this.destroyLC) this.destroyLC() } } ``` This shows the minimal starting point for using LightningChart JS in Angular. Next examples cover more options around getting data to the charts. ## Connecting to a data service (WebSocket example) In Angular, functionality such as fetching data from a server is usually separated into logical, clear sections in code bases. A common concept is **Service**: > _Service_ is a broad category encompassing any value, function, or feature that an application needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well. Next we'll walkthrough a simple example that covers: - Extremely minimalistic example `WebSocket` server that broadcasts random data points in real-time. - Creation of a Service that manages connections to above mentioned server. - Connecting the previous Chart component to this data service. Here is the code of the example server (can also be found in [GitHub](https://github.com/Lightning-Chart/lcjs-ng-template)): ```js const wss = new WebSocketServer({ port: 4201 }); wss.on("connection", async (ws) => { let closed = false; ws.onclose = () => { closed = true; }; while (!closed) { ws.send(Math.random() * 11); await new Promise((resolve) => setTimeout(resolve, 10)); } }); ``` Next, we setup the data service: ```sh ng generate service services/data ``` ```ts // data.service.tsimport { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class DataService { private observable?: Observable; constructor() {} public connect(): Observable { if (!this.observable) { this.observable = this.create(); } return this.observable; } private create(): Observable { const ws = new WebSocket('ws://localhost:4201'); ws.onopen = () => { console.log('WS connection successful'); }; const observable = new Observable((obs: Observer) => { ws.onmessage = (msg) => { const value = Number(msg.data); obs.next(value); }; ws.onerror = obs.error.bind(obs); ws.onclose = obs.complete.bind(obs); return ws.close.bind(ws); }); return observable; } } ``` Lastly, connecting the chart component to the data service, and setup a scrolling X axis: ```ts // chart.component.ts export class ChartComponent implements OnDestroy { ... constructor(dataService: DataService) { ... const axisX = chart .getDefaultAxisX() // Setup scrolling X axis .setScrollStrategy(AxisScrollStrategies.scrolling) .setDefaultInterval((state) => ({ end: state.dataMax ?? 0, start: (state.dataMax ?? 0) - 100, stopAxisAfter: false, })); const lineSeries = chart .addLineSeries() // Setup maximum amount of data points to keep in memory .setMaxSampleCount(10_000); const dataObservable = dataService.connect(); const dataSubscription = dataObservable.subscribe((value) => { lineSeries.appendSample({ y: value }); }); this.destroyLC = () => { chart.dispose(); dataSubscription.unsubscribe(); }; } } ``` This wraps up a minimal example of connecting LC based Angular chart component to a real-time data service. The full example code can be found in [GitHub](https://github.com/Lightning-Chart/lcjs-ng-template). If you consider use cases with static data (fetch data from server and show it), there is practically no difference. The logic of getting data is hidden behind the data service, and on component side only difference is that instead of pushing 1 sample at a time, you use `appendSamples` method to push many samples at once, and there is no need for a scrolling X axis. ## Best practices for LightningChart JS based Angular components Here you can find the most important best practices when developing Angular components using LightningChart JS. ### Streaming data applications LightningChart has extremely powerful APIs for appending new data points while keeping old data points around. :::tip For best results, new data points arriving should only result in `Series.appendSamples` or equivalent method being called, instead of clearing data / creating new series and re-defining full data set. ::: ### Optimizing apps with several charts visible at once / charts being initialized often If your application involves having more than 1 chart visible at same time, or charts are frequently destroyed and recreated, then you should use a **shared context** for massive performance gains. This can be done with a simple Service that manages a single LightningChart context and shares it between all LC-based components. ```sh ng generate service services/lc-context ``` ```ts // lc-context.service.ts @Injectable({ providedIn: 'root', }) export class LcContextService { private lc?: LightningChart; constructor() {} public getLightningChartContext() { if (this.lc) return this.lc; this.lc = lightningChart({ license: // Place your LCJS license key here // This should originate from a environment variable / secret. In development, license is assigned per developer, whereas in staging/production a deployment license is used. '', }); return this.lc; } } ``` Even if your application only has 1 chart visible at a time, it is still recommended to setup a service like this, because it provides a centralized place to manage LightningChart JS licensing in your code base. ```ts // chart.component.ts export class ChartComponent implements OnDestroy { constructor(lcContextService: LcContextService) { // Only in browser (not server side); afterNextRender(() => { const lc = lcContextService.getLightningChartContext(); ... // Cache function that destroys created LC resources this.destroyLC = () => { // NOTE: Important to dispose `chart` here, instead of `lc`. Otherwise we would dispose the shared LC context which may be used by other LC based components chart.dispose(); }; } } } ``` Full example code can be found in [GitHub](https://github.com/Lightning-Chart/lcjs-ng-template). ## Troubleshooting ### Server side rendering If your Angular application has server side rendering enabled, then you can run into unexpected issues if you try to invoke LightningChart JS functions in server side scripts. The most common way to see this is "`fp.atob` is not a function" error message, or something similar. There are a multitude of ways (provided by Angular itself) to ensure your code is running on browser instead of server. In our examples and documentation, we only utilized `afterNextRender` function (see above example snippets). ## Runnable examples For an example you can run and experiment with, see [Angular template on GitHub](https://github.com/Lightning-Chart/lcjs-ng-template). It covers same things that are described here. --- ## ASP .NET ASP.NET Core: Microsoft's lightweight, cross-platform framework for building high-performance web applications and APIs. It's modular, open-source, and supports diverse workloads, offering streamlined development and scalability. LightningChart JS can also be used inside a ASP.NET Core applications to add high performance charts with real-time capability to the mix. For user purposes of quickly evaluating this concept, we have prepared a small application (ASP.NET Core Web App) which you can check out [here](https://github.com/Lightning-Chart/lcjs-aspnet-template). For latest updates, please don't hesitate to [contact us](https://lightningchart.com/js-charts/docs/contact). --- ## Blazor [Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) is a web framework that enables developers to create web apps using C# and HTML. LightningChart JS can also be used inside a Blazor application to add high performance charts with real-time capability to the mix. :::info For latest information of a **native** LightningChart library for Blazor, check [Blazor charts](https://lightningchart.com/blazor-charts/) ::: Please find the latest demo of this use case, [here](https://github.com/niiloArction/Blazor-x-LCJS-2023). Some prospects have also used LightningChart JS in .NET MAUI Blazor Hybrid applications. A similar demo for this can be found [here](https://github.com/Lightning-Chart/Maui-Blazor-LCJS). Also, please don't hesitate to [contact us](https://lightningchart.com/js-charts/docs/contact) to ask for the latest updates or ask for more help on the topic. There are so many different frameworks around that it is quite difficult for us to keep the online materials always up to date. ## Developer licenses usage in Blazor All LightningChart JS licenses **except for Developer licenses** are completely air-tight, meaning the library makes no outbound network requests to validate the active license. In case of Developer license, however, there is a verification request, which Blazor blocks by default. To allow this check, the request has to be whitelisted: ```c // Startup.cs app.Use(async (context, next) => { if (env.IsDevelopment()) { context.Response.Headers.Add("Content-Security-Policy", "https://jslicensing.lightningchart.com/"); } }) ``` -------------- For legacy purposes, the original LightningChart JS template for Blazor can be found [here](https://lightningchart.com/js-charts/frameworks/blazor/). --- ## Capacitor (Ionic) Please find the original LightningChart JS template for Capacitor [here](https://lightningchart.com/js-charts/frameworks/ionic-capacitor/) and [get in touch](/contact) for the latest updates. --- ## Electron Electron is a popular development framework for Desktop applications. It allows you to utilize web technologies for applications that users can install on their personal computers and run like traditional PC applications. Compared to traditional web applications, Electron applications have some distinct advantages. For example, they have more control over the application window and process resources. They have access to more powerful multithreading capabilities from `Node.js` and the local filesystem, or even connecting to local databases, peripherals and Bluetooth. When it comes to handling differences between operating systems and creating installers, Electron does the hard things for you. What is the best part about Electron? You can make desktop applications and still utilize LightningChart JS for high-performance data visualization. ## Using LightningChart JS in Electron Electron content is generally just normal HTML, JavaScript and CSS. As such, using LightningChart JS is very straightforward. Simply add the library `IIFE` bundle in your Electron renderer HTML: `` And you can start using LightningChart JS without any gimmicks: ```js const { lightningChart } = lcjs; const lc = lcjs.lightningChart(); const container = document.getElementById("chart"); const chart = lc.ChartXY({ container }) ``` ## Example application We have prepared a small example application with Electron + LightningChart JS. Please find it in GitHub: https://github.com/Lightning-Chart/lcjs-electron-template In the above repository you can find more instructions on topics such as: - Running the application - Building the application to a desktop executable - Using a developer license - Transferring data from main process to renderer The application itself produces just a simple scrolling time series line chart, which receives a new random generated data point every 10 milliseconds. ## More materials - Electron based ECG visualization article: https://lightningchart.com/js-charts/frameworks/electron-js/ For latest updates, [get in touch](/contact). --- ## Flutter Flutter is a cross-platform framework developed by Google that lets developers build apps for iOS, Android, web, and desktop from a single Dart codebase. It uses its own rendering engine to draw widgets, providing consistent performance and look across all platforms. Flutter is known for its fast UI development (with features like Hot Reload), rich customizable widgets, and high performance. Flutter is not compatible with LightningChart JS. For latest information of a **native** LightningChart library for Flutter, check [Flutter charts](https://lightningchart.com/flutter-charts/) --- ## Frameworks LightningChart JS can be used with practically any **JavaScript frontend framework**, such as React, Vue, Angular, etc. It is also possible to use in **mobile and desktop** frameworks similarly to majority of modern web libraries. This makes it possible to develop cross platform solutions that reuse web components in a number of different platforms. In addition to this, LightningChart can even be used together with totally different program run-times and languages, like **Python and R**. Here you can find an up-to-date list of every single framework we have previously explored to some extent. --- ## iOS Please find the original LightningChart JS template for iOS [here](https://lightningchart.com/js-charts/frameworks/ios/) and [get in touch](/contact) for the latest updates. --- ## MAUI :::info For latest information of a **native** LightningChart library for MAUI, check [MAUI charts](https://lightningchart.com/maui-charts/) ::: .NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for creating native mobile and desktop apps with C# and XAML. In the past some users have found success using LightningChart JS as a part of a hybrid MAUI Blazor application (following [this Microsoft guide](https://learn.microsoft.com/en-us/aspnet/core/blazor/hybrid/tutorials/maui?view=aspnetcore-7.0)). We have made a minimal example of integrating LCJS and some forms of data transfer in such applications, which you can find [here](https://github.com/Lightning-Chart/Maui-Blazor-LCJS). We've also tested running some chart applications in a MAUI application directly. While this wasn't done in a realistic end user setting, it may also be of some help. You can find these materials [here](https://lightningchart.com/js-charts/frameworks/maui/) Lastly, please don't hesitate to [contact us](https://lightningchart.com/js-charts/docs/contact) to ask for the latest updates or ask for more help on the topic. There are so many different frameworks around that it is quite difficult for us to keep the online materials always up to date. --- ## Next.js The basic usage of LightningChart JS in Next.js is very similar compared to [React](../react): Chart component: ```js "use client"; const Chart = (props) => { const { id, data } = props; const containerRef = useRef(null); const chartRef = useRef(null); useEffect(() => { const container = containerRef.current; if (!container) { return; } const chart = lightningChart().ChartXY({ container }); const lineSeries = chart.addLineSeries() chartRef.current = { chart, lineSeries }; return () => { chart.dispose(); chartRef.current = null; }; }, []); useEffect(() => { const { lineSeries } = chartRef.current; lineSeries.clear().appendJSON(data); }, [data]); return ( ); }; export default Chart; ``` Main page: ```js "use client"; export default function Home() { return (
); } ``` For a full example with real-time data visualization, see [LightningChart JS x Next.js](https://github.com/Lightning-Chart/lcjs-nextjs-template) template project in GitHub. --- ## Python LightningChart is also available in Python, please see [LightningChart Python](https://lightningchart.com/python-charts) for more information. --- ## Qlik Please find the original LightningChart JS template for Qlik [here](https://lightningchart.com/js-charts/plugins/qlik/) and [get in touch](/contact) for the latest updates. --- ## Quasar Please find the original LightningChart JS template for Quasar [here](https://github.com/Lightning-Chart/lcjs-quasar-template) and [get in touch](/contact) for the latest updates. Here you can also find a related article https://lightningchart.com/js-charts/frameworks/quasar-js/ --- ## R Please find the original LightningChart JS template for R [here](https://lightningchart.com/js-charts/frameworks/r/) and [get in touch](/contact) for the latest updates. --- ## React Native React Native is a cross-platform framework developed by Meta that lets developers build native iOS and Android apps using JavaScript and React. It renders real native UI components, offering near-native performance while sharing most of the code across platforms. The framework is popular for its fast development cycles (thanks to features like Fast Refresh), large ecosystem, and ability to extend with native code when needed Even though React Native is developed with JavaScript, it is not generally compatible with JavaScript frontend libraries, such as LightningChart JS. For latest information of a **native** LightningChart library for React Native, check [React Native charts](https://lightningchart.com/react-native-charts/) --- ## React React is one of the most popular web frontend frameworks at the moment. As such, it is extensively used by LightningChart JS users. Here you will find resources related to using LightningChart JS with React. ```sh npm i @lightningchart/lcjs ``` ## Basic usage boilerplate Here's the React boilerplate code for creating perhaps the most common LightningChart JS use case - a Line Chart component. ```js /** * props.data: {x: number, y: number}[] */ const Chart = (props) => { const { data } = props; const id = useId() const [chart, setChart] = useState(undefined); // Create chart just once during lifecycle of component. useEffect(() => { const container = document.getElementById(id); if (!container) return const lc = lightningChart() const chart = lc.ChartXY({ theme: Themes.darkGold, container, }); const lineSeries = chart.addLineSeries(); setChart({ chart, lineSeries }); return () => { // Destroy chart when component lifecycle ends. lc.dispose(); }; }, [id]); // Update line series data whenever data prop changes. useEffect(() => { if (!chart || !data || chart.isDisposed()) return chart.lineSeries.clear().appendJSON(data); }, [chart, data]); return ; }; ``` We have prepared a minimal React application (created using `create-react-app`), which you can run to inspect and experiment with code like above. You can find it in [GitHub](https://github.com/Lightning-Chart/lcjs-react-template). ## Plug-and-play components for React We have also created a simple NPM package for a **React Time Series Chart**. If this fits your needs, you can get a kickstart by installing it directly from NPM and plugging it in your app. For more information, see [react-time-series-chart](https://github.com/Lightning-Chart/lcjs-react-template/tree/master/react-time-series-chart/). ## Best practices for LightningChart JS based React components Here you can find the most important best practices when developing React components using LightningChart JS. ### Avoiding heavy side effects on component re-rendering If your React component has _props_ like chart title, x axis title, etc. you should apply them in a separate `useEffect` so that charts and/or series don't have to be recreated when the prop changes. ❌ bad: ```js useEffect(() => { const container = document.getElementById(id); if (!container) return const lc = lightningChart() const chart = lc.ChartXY({ container }) .setTitle(props.chartTitle) return () => { // NOTE: Chart is disposed whenever props.chartTitle changes! lc.dispose(); }; }, [id, props.chartTitle]); ``` ❎ good: ```js const [chart, setChart] = useState(undefined); useEffect(() => { const container = document.getElementById(id); if (!container) return const lc = lightningChart() const chart = lc.ChartXY({ container }) setChart({ chart }) return () => { lc.dispose(); }; }, [id]); useEffect(() => { if (!chart) return // Whenever props.chartTitle changes, it is reapplied to existing chart without recreating it chart.setTitle(props.chartTitle) }, [chart, props.chartTitle]) ``` ### Streaming data applications If your application involves streaming in new data points frequently (several times per second), then the simple use example above will not perform very well. This is because, whenever a new data point is received (and `data` prop changes), the entire line series is cleared and then the entire data set is pushed in again. While this works, it is hardly efficient, because LightningChart has extremely powerful APIs for appending new data points while keeping old data points around. > For best results, new data points arriving should only result in `LineSeries.add()` or equivalent method being called, without re-rendering the React component, or clearing the previous data. This is generally achieved by handling historical data (points that exist when component is created) and streaming data (points that spawn after component is created) separately. Here is one example implementation of this: ```tsx const MyChartComponent = (props: { // Historical data - new data points arriving MUST NOT result in changing this prop! data?: { x: number, y: number }[], // Component is injected with an event subscription method, so it can listen to new data points arriving. onNewDataPoints: (clbk: (newDataPoints: { x: number, y: number }[]) => void) => () => void, }) => { const { data, onNewDataPoints } const id = useId(); useEffect(() => { const container = document.getElementById(id); if (!container) return const lc = lightningChart() const chart = lc.ChartXY({ container }); const lineSeries = chart.addLineSeries() // When component is created, add historical data in if (data) lineSeries.appendJSON(data) // When new data points arrive, push them directly to series with `add` without re-specifying entire data set. const offNewDataPoints = onNewDataPoints((newDataPoints) => { lineSeries.appendJSON(newDataPoints) }) return () => { lc.dispose(); offNewDataPoints() }; }, [id, data, onNewDataPoints]); return ; } ``` The key points here are: - `data` prop should not change during the life cycle of the component. It is used for supplying historical data that is already present when the component is created! - When new data points arrive, they are directly pushed to `LineSeries.appendJSON` - very efficient! Here's how this could look on the other side, where this chart component is used: ```tsx function App() { const refDataListener = useRef(undefined); // Connect to a websocket server and listen for data. useEffect(() => { const listener = refDataListener.current; if (!listener) return; const socket = new WebSocket('ws://my-data-server'); socket.onmessage = (e) => { const newDataPoints = JSON.parse(e.data) listener(newDataPoints) } return () => { socket.close() } }, [refDataListener]); // NOTE: Important to useMemo here, because otherwise `MyChartComponent` is re-rendered everytime `App` is. const handleNewDataPoints = useMemo(() => { return (clbk) => { refDataListener.current = clbk; return () => { refDataListener.current = undefined; }; }; }, []); return ( ); } ``` We've created a demonstration project of this kind of use case with React, TypeScript and WebSockets, which you can find in [GitHub](https://github.com/Lightning-Chart/lcjs-react-template-realtime) For more information about handling real-time data in general (not with React specifically), see [Real-Time Data Documentation](/more-guides/real-time-data/) ### Optimizing apps with several charts visible at once / charts being initialized often If your React application involves having more than 1 chart visible at same time, or charts are frequently destroyed and recreated, then you should use `LCContext` for massive performance gains. `LCContext` is a React context that you can use for sharing LightningChart resources between all charts that are created during the application lifetime. Integrating `LCContext` to your React application is a 3-step process: 1. Add `LCHost` and `LCContext` to your source code. You can find the latest version of this logic in our React template repository: [JavaScript](https://github.com/Lightning-Chart/lcjs-react-template/blob/master/src/LC.js) / [TypeScript](https://github.com/Lightning-Chart/lcjs-react-template/blob/master/src/LC.tsx). 2. Wrap your React components into a shared LC context by using `LCHost` near the top of your React render tree: ```tsx function App() { return ( // NOTE: LCHost should be defined at the top of component tree, before any and all LCJS based components // This let's them share the same LC context for performance benefits. ); } ``` 3. Use the `LCContext` instead of directly calling `lightningChart` in your LightningChart JS components: ❌ bad: ```js export function MyChartComponent(props) { const id = useId() useEffect(() => { const container = document.getElementById(id); if (!container) return const chart = lightningChart().ChartXY({ container }) return () => { chart.dispose(); }; }, [id]); } ``` ❎ good: ```js export function MyChartComponent(props) { const id = useId() const lc = useContext(LCContext); // ! useEffect(() => { const container = document.getElementById(id); if (!container) return const chart = lc.ChartXY({ container }) // ! return () => { chart.dispose(); }; }, [id, lc]); } ``` Using `LCContext` can increase chart loading time by up to **10x faster** and it also **completely removes** the limit of max 16 charts being visible at 1 time. ## Additional troubleshooting ### React specific crashes Often, LightningChart JS based React components involve several `useEffect` hooks, which relate to each other. A basic example: - 1st `useEffect` creates the LC context, a chart and a series. - 2nd `useEffect` updates the data within the series. If data changes, the whole chart is not destroyed. Sometimes, complicated effect setups can result in React triggering effect cleanup functions and effects themselves in seemingly strange order. In some cases, this has resulted in a React component first disposing a chart component, and then triggering an effect that tries to utilize that destroyed chart component. In most cases, this is not a problem and happens silently, but sometimes it has been found to crash the application. One easy workaround to this kind of issues is to add a sanity check before using chart components that confirms that they are not disposed already. ```tsx useEffect(() => { const { chart, series } = chartState // Sanity check if (!chart || chart.isDisposed()) return // Exit silently // Safe to use chart, series, etc... }, [chartState]) ``` --- ## SvelteKit [SvelteKit](https://svelte.dev/) is a wonderful web framework that has been gathering wide appreciation and interest among web developers recently. LightningChart JS can be used as a frontend building block in SvelteKit applications to add interactive and powerful data visualization components to the user interface. The latest template on how to integrate LightningChart JS in a SvelteKit application is maintained at [GitHub](https://github.com/Lightning-Chart/lcjs-sveltekit-template). Below you can find a minimal overview. - Install LightningChart JS `npm i @lightningchart/lcjs` - Create a DIV to house the chart ```html ``` - Create the chart using `onMount` lifecycle function ```html ``` - Pass data to LightningChart JS This example passes data to the chart using a simple import from the server, like so: ```js // routes/+page.server.js export function load() { const exampleData = []; let prevY = 0; for (let i = 0; i < 100000; i += 1) { const y = prevY + (Math.random() * 2 - 1); exampleData.push({ x: i, y }); prevY = y; } return { exampleData }; } ``` ```html ``` For more details and a minimal example, see [LCJS x SvelteKit template](https://github.com/Lightning-Chart/lcjs-sveltekit-template) --- ## Uno Please find the original LightningChart JS template for Uno [here](https://lightningchart.com/js-charts/frameworks/uno-platform/) and [get in touch](/contact) for the latest updates. --- ## Vanilla JS "Vanilla JS" refers to frontend applications that are directly created with JavaScript, HTML and CSS, without relying on any framework. While using a development framework can greatly improve a development project, it is not mandatory! By far the technically simplest way to realize the first LightningChart application is to create a blank HTML file and download LightningChart via a CDN service such as [jsdelivr](https://www.jsdelivr.com/). Here's how simple it can be: ```html ``` The above is probably the smallest independent application you could make. When saved to a HTML file (`app.html`), you can directly open the file with a web browser (Google Chrome or equivalent). In practice, the above HTML file actually contains all of the three primary building blocks (languages) of web applications - HTML, CSS and JavaScript. For a project template, see [`lcjs-html-example`](https://github.com/Lightning-Chart/lcjs-html-example) ## Intellisense / type information The biggest caveat in developing using the IIFE distribution is that there is no type information. When writing code, you don't see what the method names are and if you are passing the right arguments. This can be improved by placing your JavaScript code in separate `.js` files (as opposed to being inside script tags in `.html` files), and adding this reference to external type information: `/// ` The `lcjs.iife.d.ts` file is included in the `@lightningchart/lcjs` NPM release bundle. You can download it to your project for example by visiting [`jsdelivr` CDN](https://www.jsdelivr.com/package/npm/@lightningchart/lcjs?tab=files&path=dist). Alternatively, most of our template projects which utilize the IIFE distribution also include this file. For example, [`lcjs-html-example`](https://github.com/Lightning-Chart/lcjs-html-example) where you can also see an example how it is used. --- ## Vue Vue is one of the most popular web frontend frameworks at the moment. As such, it is extensively used by LightningChart JS users. Please find the LightningChart JS template for Vue [here](https://github.com/Lightning-Chart/lcjs-vue-template) and [get in touch](/contact) for the latest updates. :::danger Do not place LightningChart JS objects inside Vue `ref` objects! Users have often reported this to cause strange errors. Instead, `shallowRef` should be used. ::: ## Frequently asked questions ### Uncaught TypeError: Cannot read properties of undefined (reading 'get') This and other strange errors have often been reported due to placing LightningChart JS objects inside Vue `ref`s. Instead, `shallowRef` should be used. One example is documented [here](https://stackoverflow.com/questions/71376215/how-to-debug-lightning-chart-datetime-error). --- ## Wordpress Please find the original LightningChart JS template for Wordpress [here](https://lightningchart.com/js-charts/plugins/wordpress/) and [get in touch](/contact) for the latest updates. --- ## Xamarin Please find the original LightningChart JS template for Xamarin [here](https://lightningchart.com/js-charts/frameworks/xamarin/) and [get in touch](/contact) for the latest updates. --- ## Getting started LightningChart JS Developer Documentation LightningChart JS is a library for creating interactive and impressive data visualization applications with top performance. With LightningChart, developers can utilize high level components to embed extremely performant graphs into their frontend applications with minimal work. LightningChart JS can be installed via NPM or CDN service like [jsdelivr](https://www.jsdelivr.com/). ```sh npm i @lightningchart/lcjs ``` ```html ``` Here's how it looks in action: ```ts // ... or with IIFE: // const { lightningChart } = lcjs; const lc = lightningChart({ license: 'my-license-key' }) const chart = lc.ChartXY() chart .addLineSeries() .appendJSON(dataSet) // { x: number, y: number }[] ``` By default, LightningCharts are completely interactive. Try zooming, panning and using the cursor in the above chart. Apart from the modern looks and interactivity, what makes LightningChart special from other JavaScript charts is its performance. LightningChart is incredibly fast at loading large data sets, redrawing the charts upon interaction and handling streaming data. Behind the scenes, LightningChart utilizes the clients GPU to offload heavy graphics work required for data visualization, leading to extremely low CPU utilization when compared to other JavaScript chart libraries. [Learn more about performance](/technical-info#performance-and-gpu-utilization). LightningChart comes shipped with complete type definitions, which makes application integration a breeze especially when developing with TypeScript. We invite you to learn more about LightningChart. Below you can find some useful materials: ## Acquiring a license As you can see in the above example, to use LightningChart JS you need a _license key_. This can be a trial key, developer key or a deployment key. Regardless whether you are just trying LightningChart out, making a proof of concept or developing a LC based product, you can find your path to a suitable license key [here](https://lightningchart.com/js-charts/#license-key). When trialing LightningChart JS for commercial purposes, please see [Trials and Integration](/services/trials-and-integration) section to learn of your rights, terms of use, as well as **special privileges during the trial period**. ## Guides and documentation Full list of included features can be found under [Features](/features). This also includes guides with code snippets for all important capabilities of different features (e.g. "how to make 2D line chart"). Additional guides, which are not limited to any specific library feature can be found under [More guides](/more-guides/positioning-charts) ## Online examples gallery Find our [online interactive examples gallery](https://lightningchart.com/lightningchart-js-interactive-examples), where you can find functional examples along with the source code and live editor. ## Technical information Explore key details about performance, technical details and security concerns [here](/technical-info). ## Installation [How to install LCJS](/installation) ## Usage in popular UI frameworks Explore how to use LightningChart JS in the [most popular web UI frameworks](/frameworks/) out there. ---------------------------------- In the case you didn't find what you were looking for, please do not hesitate to [get in direct contact](/contact) with us OR try using the search box in the top right corner, and look for your key word (e.g. "Theme"). --- ## Installation LightningChart JS can be installed from NPM (Node Package Manager) with the following command, as long as you have Node.js installed. ```sh npm i @lightningchart/lcjs ``` Afterwards, you can use LightningChart like any external Node dependency. ```ts const lc = lightningChart({ license: 'my-license-key' }) const chart = lc.ChartXY(); ``` Alternatively, if your application can't directly consume Node packages, you can use the IIFE distribution to use LightningChart JS in virtually any web application. ```html ``` The IIFE build is publicly available via CDN services like [jsdelivr](https://www.jsdelivr.com/package/npm/@lightningchart/lcjs?tab=files), or it can be downloaded from NPM directly and saved as a local application asset. Regardless whether you are just trying LightningChart out, making a proof of concept or developing a LC based product, you can get a license key [here](https://lightningchart.com/js-charts). --- ## LightningChart and AI Generating code with large language models (LLMs) is a rapidly growing area of interest for developers. While LLMs are often capable of generating working code it can be a challenge to consistently generate code for libraries like LightningChart JS that have several different versions and use cases. This section is dedicated to hosting resources to help developers use AI tools with LightningChart JS better. :::tip Please share your own experiences with AI/LLM tools capability to work with LightningChart JS API at feedback.js@lightningchart.com ::: ## LightningChart MCP Server By far the easiest way to transform your AI Agents to high-performance charting specialists, is to install the LightningChart MCP server, found open source at [GitHub](https://github.com/Lightning-Chart/lightningchart-js-mcp-server). **What does this do?** - It strongly urges your agent to **refer to the official LightningChart documentation**, instead of hallucinated training data memories or random Stack Overflow searches. - Your agent receives an **LLM-optimized Index** of the entire LightningChart documentation, which is very effective for finding the most suitable documentation page depending on the task at hand, rather than using up a lot of context by reading massive chunks of documentation. - Last, but not least, we share our hand-written "common mistakes" section with the Agent context, which **prevents majority of recurring mistakes** that Agents otherwise tend to do **How to install it?** Refer to [GitHub](https://github.com/Lightning-Chart/lightningchart-js-mcp-server) for latest updates. ```bash // Example, Claude MCP claude mcp add --scope user lightningchart-js -- npx -y @lightningchart/mcp-server@latest ``` For Codex, write following configuration to `.codex/config.toml` ```toml [mcp_servers.lightningchart] command = "npx" args = ["-y", "@lightningchart/mcp-server"] enabled = true ``` Or add it through the user interface, with following specs: - Name: LightningChart - Transport: STDIO - Command: npx - Arguments: -y - Arguments: @lightningchart/mcp-server After installation, your Agent will automatically pull in the LightningChart knowledge context when it identifies a charting related prompt! ## LightningChart Agent Skill An alternative flavour to the MCP Server, is the official LightningChart JS Agent **Skill**. This is also found as open-source in [GitHub](https://github.com/Lightning-Chart/lightningchart-js-agent-skill). It does the exact same thing, only the installation and use flavour is different: **Installation** Copy the `lightningchart-js` folder into a skills directory supported by your agent. - Project-local Agent Skills location: `.agents/skills/lightningchart-js/` - OpenAI Codex local skills location: `.codex/skills/lightningchart-js/` - Claude local skills location: `.claude/skills/lightningchart-js` Restart the agent/tool. Some times, skills need to be explicitly invoked by mentioning them in your prompt. But in our experience, agents tend to pick the skill up proactively, as long as the task is related to charting. ## Importance of type checking As LightningChart JS is a strongly typed library, probably the single best thing you can do to improve the work quality of your agents is to instruct them to run a type check after any code change: ``` - After any code change, confirm that typings are OK using `npm run typecheck` ``` Where `typecheck` script will just run `tsc --noEmit` or equivalent. Then, if there are type errors (which are extremely common due to hallucinations or outdated training data), the agent will reattempt immediately rather than waiting for you to tell that it doesn't work. This is automatically suggested if you use the official LightningChart skill or MCP server. ## llms.txt In our experience LLM's are very poor at working with specific 3rd party libraries out of the box, because their training data is a mix of all the existing versions of that library. They also tend to hallucinate results by mixing data from various different libraries. For this purpose, we provide versions of our developer documentation and API reference in LLM friendly formats here: - lcjs-docs-llms.txt - lcjs-api-llms.txt **Our recommendation** is to ensure your LLM has access to these files and specifically instruct the LLM to use them as the source of truth on all matters LightningChart JS. This is also what the LightningChart Agent skill and MCP server do behind the scenes. If in practice your LLM attempts un-optimal approaches such as: - Guessing links to API reference - Scraping https://lightningchart.com/js-charts/docs/ directly - Google/Stack overflow searches with LCJS related keywords **Then it is most likely a sign that you should tweak or alter your prompt and reattempt the command.** If not already using the LightningChart Skill or MCP server, then you can simply download `lcjs-docs-llms.txt` and `lcjs-api-llms.txt` to your repository and add following section to your `AGENTS.md`, `CLAUDE.md` or equivalent: ``` - You MUST always use the local files lcjs-docs-llms.txt and lcjs-api-llms.txt as the only sources of truth for all matters regarding how to use LightningChart JS library / lcjs ``` ## Working with chat-based LLMs In our experience, chat-based LLMs have much more trouble following a seemingly simple instruction like "refer to X for guidance". Furthermore, there are great differences between platforms. For example: - GPT-5.2 - the LLM tells you that it will stick strictly to the specified documentation, then proceeds to dump hallucinated API usages that have never existed in the library - Sonnet 4.5 / Opus 4.5 - these models were able to follow the instructions well and provide up to date and well referenced recommendations You can confirm whether the model is listening to your requirement by additionally asking it to cite all references, and checking if it lists external sources. When receiving non-working results, the reason is often referencing several years old API documentation pages or just answering directly from training data. Ideally it would be sufficient to just include this in every new chat context: ``` - You MUST always use the https://lightningchart.com/js-charts/docs/llms.txt and https://lightningchart.com/js-charts/api-documentation/llms.txt as the only sources of truth for all matters regarding how to use LightningChart JS library / lcjs ``` Some chat tools also support loading skills / connecting MCP servers! :::info No one likes hallucinated chart code - please help us maintain an effective instruction set by sharing what works best for you at feedback.js@lightningchart.com ::: Here is also an alternate version of developer documentation intended for LLMs where all text content is put into one large file. In our experience, this doesn't work too well but you can give it a try. - lcjs-docs-llms-full.txt --- ## DuckDB Wasm [DuckDB Wasm](https://duckdb.org/docs/api/wasm/overview.html) is a frontend library that can be used to make data queries to remote databases or files. With LightningChart JS, it is especially prominent due to its ability to make database queries without a separate backend, as well its features for performant queries to heavy data sets. **Related external content:** - [LightningChart JS x DuckDB Wasm x Parquet proof of concept](https://github.com/Lightning-Chart/lcjs-duckdbwasm-parquet/tree/master) --- ## Related technologies This category is a living collection of potentially useful mentions to technologies that our customers are using related to their LightningChart JS applications. Here you may find documentation specific to a 3rd party technology, or links to standalone examples or proofs of concept. --- ## App deployment App deployment licenses are used for deployment scenarios where your LightningChart JS application is not tied to any specific domain. This includes for example: - Distributable products - Installable apps (mobile/PC, etc.) - Intranet deployments without specific domain All licenses are accessed through [Customer Portal](https://portal.lightningchart.com/). When licenses are purchased, they are assigned to 1 Portal account ("Team manager"). When using an App deployment license, internet connection is **not** required. The Team manager can access their purchased App deployment licenses through **Portal > Application Deployments**. ## Configuring app deployment licenses If a Team manager has a app deployment license that is not yet configured, they will see a button for "Set Application Deployment" in **Portal > Application Deployments** ![Picture from Customer Portal, showing set application deployments button](/img/set-application-deployment.png) App deployment licenses have to be branded with an **unique application name**, which is also tied to your company (according to value in **Portal > Account Profile > Company**). To start using an app deployment license, fill the application name into the field under "Application Name" and click "Set Application Deployment". **Please note that this application name or company name can't be changed afterwards!** ## Accessing app deployment license key If a Team manager has a configured App deployment license, they will see it listed in **Portal > Application Deployments**. They will also have access to a button: **"Get Deployment Key"**, which generates a license key you can use when deploying to production. ![Picture from Customer Portal, showing get deployment key button](/img/get-app-deployment-key.png) App deployment license keys are used slightly differently from other license types. You also need to supply your company name and application name when using the license: ```ts const lc = lightningChart({ license: 'app deployment license key', licenseInformation: { appTitle: 'My application name', company: 'My company name' } }) const chart = lc.ChartXY() ``` :::warning `"appTitle"` must match the Application Name given through the Portal UI. `"company"` must match your Portal Account Profile "Company" field value at the time when the "Get Deployment Key" button was used. ::: ## License version selector When generating or downloading license keys, there is a version selector widget (0001, 0002, etc.). This is needed for legacy support of older LightningChart JS versions. Use `0002` if you are using LightningChart v4.0.0 or higher, otherwise use `0001`. --- ## Developer licenses When developing a part of an application where LightningChart is visible, a **Developer** License is needed. For a team of software developers, everyone who needs to have the charts running/visible as part of their development activities needs their own developer license key. Developer licenses require internet connection for verification! If your development environment doesn't have internet connection, please let us know **before** purchasing a license. Developer licenses are purchased according to development team size, usually at the same time as deployment licenses. ## I've purchased a developer license, where can I get my key? All licenses are accessed through [Customer Portal](https://portal.lightningchart.com/). When licenses are purchased, they are assigned to 1 Portal account ("Team manager"). The Team manager can access all their developer licenses under **Portal > Licenses**. This view shows all available developer licenses (1 per purchased seat). The license key of a particular Developer seat can be downloaded by clicking on the "key" button ("Download license key"), highlighted below: ![Picture from Customer Portal, showing download developer license key button](/img/download-developer-license-key.png) You can also expose these controls to your developers so you don't have to be micromanaging their daily tasks (if they need to access the developer key, support or something else). See [Team management](/licenses/team-management) for more information. The developer license key is used like other licenses, by supplying it to the `lightningChart` function. ```ts const lc = lightningChart({ license: 'developer license key' }) const chart = lc.ChartXY() ``` ## License version selector When generating or downloading license keys, there is a version selector widget (0001, 0002, etc.). This is needed for legacy support of older LightningChart JS versions. Use `0002` if you are using LightningChart v4.0.0 or higher, otherwise use `0001`. ## Error: License key validation failed: Session exists already This error message is received when 1 developer license is used in more than 1 session simultaneously. A developer can, have several charts open, or even different applications using the same developer license simultaneously, as long as they are running on the same machine and same web browser. If 1 developer license is used between different browsers at same time, or on multiple different machines at same time, this error will be shown and the charts will not show. Sessions are reset rather quickly (minute or two), so generally the message should disappear shortly. If you have to do cross-browser testing etc. which requires quick switching between sessions you can temporarily disable developer session checks from **Portal > Licenses**, see below: ![Picture from Customer Portal, showing disable session checks button](/img/stop-developer-license-checks.png) If the problem persists even with only a single active session, you can try clearing your cookie and session data in browser. ------------ If you are an existing LightningChart customer, but you don't have Portal access, please [contact us](https://lightningchart.com/contact/) --- ## Licensing LightningChart JS is a license-protected library, meaning that in order to use it, you need a _license key_. This can be a trial key, developer key or a deployment key. Regardless whether you are just trying LightningChart out, making a proof of concept or developing a LC based product, you can find your path to a suitable license key [here](https://lightningchart.com/js-charts/#license-key). When trialing LightningChart JS for commercial purposes, please see [Trials and Integration](/services/trials-and-integration) section to learn of your rights, terms of use, as well as **special privileges during the trial period**. --- ## Sandbox sites LightningChart JS works without a license when deployed to specific popular sandbox sites. The current listed of enabled sandbox sites can be found here: - [Stack Overflow](https://stackoverflow.com/) - [Codepen](https://codepen.io/) - [JSFiddle](https://jsfiddle.net/) - [StackBlitz](https://stackblitz.com/) This can be used to troubleshoot and share information related to use of LightningChart JS without having to expose your personal license keys on the internet. You can, for example, post an interactive code snippet which displays a chart without having to supply a license. To do this, simply omit the license, and deploy the application to one of the allowed domains. --- ## Team management When you purchase LightningChart JS licenses, a Portal account created by the user will be assigned as "Team manager". The team manager is responsible of managing the customers development team by adding them to the team and assigning Developer licenses. Apart from this there is no difference between team members and managers. ## Adding developers to your team To assign developer licenses to your team members, the best way is to add them to your team via **Portal Team Management > Add new Team Member** This will automatically create a Portal account for them, and then you can assign a personal developer license for them. Additionally, the team members can access [technical support](/services/support) from their own Portal account. ## Assigning developer licenses LightningChart JS developer licenses are purchased based on seat-count. After purchase, you can see the available developer licenses in **Portal > Licenses**. A license can be assigned to a developer in your team from Portal > Team Management > Assign new team member for a license --- ## Trials You can start using LightningChart JS completely free of charge in the form of a 30-day Trial. Please proceed to [our web page](https://lightningchart.com/js-charts/#license-key) to get your Trial license key. Regardless whether you are just trying LightningChart out, making a proof of concept or developing a LC based product, you can find your path to a suitable license key [here](https://lightningchart.com/js-charts/#license-key). License keys are strings. To use them, pass the license string to `lightningChart` function when creating charts: ```ts const lc = lightningChart({ license: '...my-license-key...' }) const chart = lc.ChartXY() ``` When trialing LightningChart JS for commercial purposes, please see [Trials and Integration](/services/trials-and-integration) section to learn of your rights, terms of use, as well as **special privileges during the trial period**. For extended trials past 30 days, please [contact us](/contact) and we can provide extensions as required. --- ## Web deployment Web deployment licenses are used for deployment scenarios where your LightningChart JS application is deployed to a specific domain. This can be a public domain or internal domain, as long as it doesn't change afterwards. When using a Web deployment license, internet connection is **not** required. All licenses are accessed through [Customer Portal](https://portal.lightningchart.com/). When licenses are purchased, they are assigned to 1 Portal account ("Team manager"). The Team manager can access their purchased Web deployment licenses through **Portal > Deployment Domains**. ## Configuring web deployment domains If a Team manager has a web deployment license that is not domain-locked yet, they will see a button for "Set Deployment Domains" in **Portal > Deployment Domains** ![Picture from Customer Portal, showing set deployment domains button](/img/set-deployment-domains.png) Under "Set Deployment Domains", the Team manager can configure a combination of domains and subdomains that the license will work under. The amount of domains is decided on purchase, but can also be extended afterwards. The domains are sold in bundles of 1 domain + 2 subdomains of that domain. ![Picture from Customer Portal, domain lock plan](/img/domain-lock-plan.png) Example case: Domain: `lightningchart.com` Allows running on: - www.lightningchart.com - lightningchart.com - m.lightningchart.com - subdomain1.lightningchart.com (subdomain1 can be freely decided) - subdomain2.lightningchart.com (subdomain2 can be freely decided) :::tip For subdomains, enter only the subdomain prefix without repeating the full subdomain + domain. Example: In order to have support.lightningchart.com Enter only "support" as subdomain ::: **Please note that configured domains can't be changed afterwards!** If the domain-lock plan is altered, the web deployment license key has to be re-generated before changes take effect. ## Accessing web deployment license key If a Team manager has a domain-locked Web deployment license, they will see it listed in **Portal > Deployment Domains**. They will also have access to a button: **"Get Deployment Key"**, which generates a license key you can use when deploying to the configured domains. The license key is used in same way as other licenses. ![Picture from Customer Portal, showing get deployment key button](/img/get-web-deployment-key.png) ```ts const lc = lightningChart({ license: 'web deployment license key' }) const chart = lc.ChartXY() ``` ## Test domain Customers with a web deployment license also have access to 1 test domain. This can be used for staging environments, or other suitable purpose. The test domain can be set to any domain, and the charts will work when running in that domain. However, there will be a rather large watermark over the charts. ![Picture from Customer Portal, showing test domain field](/img/test-domain.png) If the test domain field is altered, the web deployment license key has to be re-generated before changes take effect. ## License version selector When generating or downloading license keys, there is a version selector widget (0001, 0002, etc.). This is needed for legacy support of older LightningChart JS versions. Use `0002` if you are using LightningChart v4.0.0 or higher, otherwise use `0001`. --- ## Custom fonts LightningChart JS can display any fonts that are loaded to the web page. In this aspect it works similar to any other library, or direct HTML display. To learn more about custom fonts in web applications, see for example [MDN web fonts](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Text_styling/Web_fonts) Custom fonts have to be completely loaded before starting to use the font. See also [HTML text rendering](/more-guides/html-text-rendering/). --- ## Scrolling to different direction This section continues from [Scrolling Axis](/features/axis). LightningChart JS Axes can be configured to scroll in any direction, left, right, up and down. Here you can find examples of each direction. ## Scrolling left -> right ```ts // Minimal example showcasing left -> right scrolling const chart = lightningChart().ChartXY() chart.axisX .setTickStrategy(AxisTickStrategies.Time) .setScrollStrategy(AxisScrollStrategies.scrolling) .setInterval({ start: 0, end: 5000, stopAxisAfter: false }) const series = chart.addLineSeries() setInterval(() => { series.appendSample({ x: window.performance.now(), y: Math.random() }) }, 100) ``` ## Scrolling right -> left Changing scrolling to go from right to left is as easy as reversing the X axis interval (start \<-\> end), see difference in `Axis.setInterval()`. ```ts // Minimal example showcasing right -> left scrolling const chart = lightningChart().ChartXY() chart.axisX .setTickStrategy(AxisTickStrategies.Time) .setScrollStrategy(AxisScrollStrategies.scrolling) .setInterval({ start: 5000, end: 0, stopAxisAfter: false }) const series = chart.addLineSeries() setInterval(() => { series.appendSample({ x: window.performance.now(), y: Math.random() }) }, 100) ``` ## Scrolling up -> down Vertical scrolling is also straightforward: - Apply scrolling axis configurations `setScrollStrategy`, `setInterval` on Y axis instead of X. - Change actual incoming data to have time dimension in Y coordinates rather than X. ```ts // Minimal example showcasing up -> down scrolling const chart = lightningChart().ChartXY() chart.axisY .setTickStrategy(AxisTickStrategies.Time) .setScrollStrategy(AxisScrollStrategies.scrolling) .setInterval({ start: 5000, end: 0, stopAxisAfter: false }) const series = chart.addLineSeries() setInterval(() => { series.appendSample({ x: Math.random(), y: window.performance.now() }) }, 100) ``` ## Scrolling down -> up Similarly as when reversing from right to left, going from down direction to up direction is as easy as reversing the Y axis interval (start \<-\> end), see change in `Axis.setInterval()`. ```ts // Minimal example showcasing down -> up scrolling const chart = lightningChart().ChartXY() chart.axisY .setTickStrategy(AxisTickStrategies.Time) .setScrollStrategy(AxisScrollStrategies.scrolling) .setInterval({ start: 0, end: 5000, stopAxisAfter: false }) const series = chart.addLineSeries() setInterval(() => { series.appendSample({ x: Math.random(), y: window.performance.now() }) }, 100) ``` There are some XY series that currently don't support other data patterns than `'ProgressiveX'`, namely: - `AreaRangeSeries` If your application would require this support, please don't hesitate to [contact us](/contact). --- ## Destroying components All LightningChart JS components can be permanently destroyed or cleaned with `dispose` method. ```ts series.dispose() chart.dispose() lc.dispose() ``` When destroying components that can own other components (e.g. series are owned by chart), it is enough to dispose the top level component (e.g. chart) ```ts chart.dispose() // This also disposes all series of the chart ``` Destroying components is important in applications where charts can be created many times during the application runtime, to avoid memory leaks. A common mistake is not disposing the top level "LightningChart reference": ```ts const lc = lightningChart() // `lc` also needs to be disposed before it is forgotten! lc.dispose() // This disposes all charts, series, etc. created using the `lc` reference. ``` --- ## Altering component draw order By default, the drawing order of series is the same as the order you create them in. ```ts // Example, lineSeries1 is drawn under lineSeries2 const lineSeries1 = chart.addLineSeries() const lineSeries2 = chart.addLineSeries() ``` This can be conveniently overridden using the `setDrawOrder` method: ```ts // Works similar to CSS zIndex, higher number results in being drawn later. lineSeries1.setDrawOrder({ seriesDrawOrderIndex: 10 }) lineSeries2.setDrawOrder({ seriesDrawOrderIndex: 1 }) ``` --- ## Grouping charts LightningChart JS excels at applications that require several charts to be visible on the same web page. In normal use cases, the library groups all charts to use a shared rendering engine for the best performance. All the user has to do, is ensure that they initialize only 1 instance of `LightningChart`: ```ts const lc = lightningChart() const chart1 = lc.ChartXY() const chart2 = lc.ChartXY() ... ``` Each individual chart can be positioned independently using CSS, just like normal ([Positioning charts](/more-guides/positioning-charts)). This makes it straight-forward to create applications involving complex layouts and hiding or rearranging charts. Below you can find some concrete examples of the most common Dashboard use cases: - [Re-arrangeable Dashboard](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-0511-dashboardDynamicStacked.html) - [Scrollable Dashboard](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-0029-htmlScrollDashboard.html) - [Dynamic Dashboard](https://lightningchart.com/js-charts/interactive-examples/examples/lcjs-example-0510-dashboardDynamic.html) ## Advanced use cases In most situations, charts grouping doesn't require any attention from user apart from the things listed above. However, there are WebGL context sharing configuration options available that can be relevant in very specific situations. ### Shared canvas mode By default, LightningChart gives each chart their own individual HTML `` element. In most cases, this is the best mode of operation. However, in some cases there is a potential alternative, "shared canvas mode", which uses 1 `` to display all charts of the LightningChart instance (created with `lightningChart` function). This can be more performant for example when: - Your application has 20+ charts visible at the same time. - Your application has only ~10 charts visible at the same time, but they have considerably heavy content. - Your application has multiple charts and are very large (for example, 4K display full-screen dashboard). Apart from these reasons, there may also be browser specific motivators to use shared canvas mode. At time of writing, Mozilla Firefox was tested to have relatively poor performance when using individual `` elements. Shared canvas mode can be enabled as follows: ```ts const lc = lightningChart({ sharedContextOptions: { useIndividualCanvas: false } }) ``` ### More information For all available shared context options, you can find `LightningChartOptions` in [API documentation](https://lightningchart.com/js-charts/api-documentation) and browse to `sharedContextOptions` property. ## Legacy Dashboard feature Before version 5.0.0 and the introduction of WebGL context sharing, chart grouping was usually done using the `Dashboard` feature. This could and still can be used to create a grid of panels where you can place any number of charts which reuse the same WebGL instance. `Dashboard` is still used in many online examples. **For new end user integrations, we recommend to not use `Dashboard` any more, and rather follow the basic guidance of grouping charts using WebGL context sharing.** Usage of `Dashboard` feature looks like this: ```ts const dashboard = lightningChart().Dashboard({ numberOfColumns: 2, numberOfRows: 2 }) const chart1 = dashboard.createChartXY({ columnIndex: 0, rowIndex: 0, }) ... ``` **Instead of this, we recommend to use `CSS` based layouts with each chart having their own `` container.** --- ## HTML text rendering Text rendering is heavy work. Historically, all LightningChart JS text has been rendered using WebGL for the best performance. However, WebGL rendered text has its cons as well. For this reason, starting with LightningChart JS v7.1, users can choose between WebGL text rendering and HTML text rendering. :::info HTML text rendering is released as a beta functionality to receive testing among user base. The API syntax and functionality may be later changed or developed further according to interactions with users. If you have any feedback on HTML text rendering for your particular use case, please [contact us](/contact/). ::: ## When should HTML text rendering be considered? 1. **You want the sharpest, best quality text**. HTML rendered text is clearly sharper than WebGL. 2. **You want to display [LaTeX](https://www.LaTeX-project.org/)** or other non-standard typesetting. 3. You want to display multi-line text elements. 4. You want to modify text display with `CSS` (drop shadows, background colors, transitions, etc.). 5. You want to allow accessibility features (such as translation, text to speech) to interact with LCJS text elements. ## What drawbacks can switching to HTML text rendering have? 1. **Increased CPU usage**. Depends on how many text elements there are, and how varying their content and positions are. 2. **Draw order control limitations**. All HTML text is always drawn above the owning charts visuals. For example, you can't hide a HTML text element with LightningChart JS components, unless its a different chart entirely. ## How to use HTML text rendering? ```ts // Example, enable HTML text rendering const chart = lc.ChartXY({ textRenderer: htmlTextRenderer, }) ``` Currently, `DataGrid` and `TextSeries` do not support HTML text rendering, meaning they are always rendered with WebGL. Additionally, HTML text rendering can't be used together with `lcjs-headless`. ## Displaying LaTeX / TeX LightningChart JS itself can't display LaTeX or TeX. Instead, this can be achieved with use of other libraries, such as [MathJax](https://www.mathjax.org/) or [KaTeX](https://katex.org/). LightningChart JS includes built-in API calls to MathJax and KaTeX libraries, if they are loaded to the web page. This integration was written using latest APIs available in February 2025, but there is no guarantee that it will work with any version of MathJax or KaTeX. **MathJax + LaTeX example:** ```ts // Examples of displaying LaTeX in LightningChart JS text elements chart.setTitle(`latex:\\[ \\int_a^b f(x) \, dx = F(b) - F(a) \\]`) chart.axisX.setTitle(`latex:\\(E = mc^2\\)`) ``` In order for this to work: 1. HTML text rendering must be enabled 2. MathJax library has to be loaded on the web page ```html ``` **KaTeX + TeX example:** ```ts // Examples of displaying TeX in LightningChart JS text elements chart.setTitle(`tex:c = \\pm\\sqrt{a^2 + b^2}`) ``` In order for this to work: 1. HTML text rendering must be enabled 2. KaTeX library has to be loaded on the web page ```html ``` ## Applying CSS to LCJS text elements When HTML text rendering is active, all LCJS text elements will be displayed by automatically created DOM elements. They can be identified using CSS classname `lcjs-text` More granular identification (e.g. what text object is in question?) has not been added as the use cases are unclear. Some CSS properties are assigned via JS by LCJS (to position the text objects on the web page). If you wish to override these properties via CSS, you have to use the `!important` syntax. ```css .lcjs-text { background-color: green; color: red !important; } ``` --- ## Setting up LightningChart resources LightningChart JS is distributed along with some resource files, which are required for select features only: - `ZoomBandChart` - `MapChart` - `Themes.cyberSpace` and `Themes.turquoiseHexagon` - `OnScreenMenu` When any of these features are used, the user has to ensure that the necessary resources are hosted on a file server where LightningChart JS can fetch them. In official LightningChart JS example frameworks, templates and projects, this setup is usually done beforehand using Webpack configurations or equivalent. The resources are distributed along with the NPM bundle under `@lightningchart/lcjs/dist/resources` folder. :::tip If this step is not done by the user application, the required resources will be automatically fetched from `lightningchart.com` (but only if the features are used by the application). This works but is not recommended for production usage. In this case, a warning message is displayed in console. ::: ## Example: Local file server ```sh cd node_modules/@lightningchart/lcjs/dist/resources npx http-server --cors ``` ```ts const lcjs = lightningChart({ resourcesBaseUrl: 'http://127.0.0.1:8081', // <--- or whichever port http-server assigned. }) ``` ## Example: Webpack config If you copy the `resources` folder to the Webpack build, then LightningChart JS finds it without having to change the default `resourcesBaseUrl` value. ```js // webpack.config.js plugins: [ new CopyPlugin({ patterns: [ { from: "node_modules/@lightningchart/lcjs/dist/resources", to: "resources" }, ], }), ] ``` --- ## Legacy XY features Legacy XY series `LineSeries`, `PointSeries`, `PointLineSeries`, `AreaSeries`, `StepSeries` and `SplineSeries` have been replaced with `PointLineAreaSeries` :::info This documentation section may not be completely up to date and is only left here to support users who are migrating from very old LightningChart JS versions. Present day documentation assumes that users have already migrated to using `PointLineAreaSeries`. ::: The transition to `PointLineAreaSeries` originally started with LightningChart JS v5.1.0. This is a massive refactoring, with an **incredibly long list of improvements**. These are all the result of gathering user feedback over several years and identifying points of improvement for the future - and now, finally realizing them. ## List of improvements (at original time of writing when v5.1 was released) **Performance** - Massively increased maximum data capacity. - able to load line charts with up to ~30x more data than before. - Massively improved memory efficiency. - up to 3000x less JS heap size usage than before. - Massively reduced Garbage Collection load in real-time applications. - Before GC ticks were a performance bottleneck in the most extreme applications out there. - Massively upgraded performance of beta equivalents for `PointLineSeries`, `StepSeries`, `SplineSeries` and `AreaSeries`, bringing them to the level of `LineSeries` and further. - Massively improved CPU efficiency of appending data points into beta equivalent for `PointSeries`. - up to 62x less CPU utilization than before. - Massively reduced latency when zooming into large progressive data sets. - Enables, for example, having a massive 10 million data point visualization and simply mouse wheeling into full zoom level without any visible lag. - Significantly improved `ZoomBandChart` loading time and memory efficiency. - up to 10x faster loading time when displaying large data sets in `ZoomBandChart`. - Significantly improved loading time of XY features. - line and scatter charts ~3x faster load time on average. - area, spline, step charts ~600x faster load time on average. - Significantly improved CPU efficiency of XY features with streaming data updates. - Significantly improved efficiency of consuming binary data / Typed arrays. - Significantly improved efficiency of consuming separate `number[]` arrays of X and Y values. - Significantly improved efficiency of consuming `JSON` data where property names are not exactly `x` and `y`. - Utilizing new method gives opportunity for significant performance boost in most existing applications. - Significantly improved performance of appending samples 1 at a time vs user having to group them in an array and feed all at once. - Frequent performance problem with customers. - Significantly improved performance of `LUT.percentageValues` in real-time scatter series use cases. - Added ability to display same data set in several series or charts without having to duplicate the data. - Added ability to supply per-sample colors as 32 bit integers vs heavy `Color` objects. Significantly improves performance and max data capacity in use cases with per-sample colors. **New features** - Added data edit API. Existing samples can be edited by sample index _or_ user specified id property. - Frequently requested by customers. - Added data read-back API. Data pushed to charts can be conveniently read back for any application specific purpose. - Frequently requested by customers. - Added ability to efficiently request currently visible data set from a graph. - Frequently requested by customers. - Added ability to optionally manage XY data sets separately from Series. This can have powerful advantages in specific use cases, such as changing axis type between linear/logarithmic without having to reload data set - just recreate the series, which is really fast. - Added Spline Area Series. - Added vertical Spline Series. - Added Step Area Series. - Added vertical Step Series. - Added per data point color Line Series. - Added per data point color Area Series. - Added per data point color Spline Series. - Added per data point color Step Series. - Added value lookup colored Area Series. - Added ability to further reduce memory consumption by specifying internal data storage format (64 bit, 32 bit, 16 bit, 8 bit, ...). - Added ability to specify hard max sample count for XY series. This clearly specifies the maximum memory allocation for the charts which can be crucial in certain applications. - Added ability to switch between Step / Spline / Area / Line / Point Line / Point visualizations at any point during run-time without any performance impact. - Added ability to switch between point shape at any point during run-time. - Added possibility for automatic data cleaning for Point Series - e.g. keep max 1 million points in memory **Quality of life** - Added built-in feature to sort incoming data to progressive time order automatically vs user having to do it. - Added automatic Area baseline (fill area all the way down from curve vs some hardcoded baseline Y value). - Makes it significantly easier to create sweeping charts (as opposed to scrolling), by using data edit API. **User experience** - Axis fitting now considers only visible data, rather than entire out of view history (progressive data patterns only). - Frequently requested by customers. - Added automatic hiding of points/markers when zoomed out, and displaying with animated opacity when zooming in. **Bug fixes** - Fixed zoomed out `LineSeries` displaying data gaps (`NaN`) incorrectly as connected lines. --- ## Mobile applications LightningChart JS is a JavaScript library so it doesn't directly integrate to any Mobile application development framework. There are, however, a number of different ways it can be used in a Mobile application. ## Web application used via Mobile web browser In this case, there's nothing fancy to do. You simply create a normal web application, use LightningChart JS completely normally, and then use the application via a Mobile devices web browser. To achieve mobile layouts of web content, refer to standard CSS practices for mobile users. ## Web application wrapped in a native WebView component Another way to realize mobile applications is to start similarly as above, creating a normal web application. However, instead of hosting it, the application is placed within a native Mobile application framework (e.g. Android studio, Xcode, React Native). Almost all frameworks tend to have a component like "WebView" or "iframe", which allows displaying web sites or web applications inside the Mobile app. ## Web-native mobile frameworks Some mobile app frameworks straight up support JavaScript runtimes and browser libraries, like LCJS. To our knowledge, the only framework like this is currently [Capacitor](https://capacitorjs.com/) In this case, there is nothing special to do from LCJS usage point of view. ## Examples and extended reading - [Create an Android cardiac mobile cardiovascular app](https://lightningchart.com/blog/cardiac-mobile-cardiovascular-app/) - [Create a High-Performance Charting App with Quasar JS](https://lightningchart.com/js-charts/frameworks/quasar-js/) - [The best guide to create Android charts in JavaScript](https://lightningchart.com/js-charts/frameworks/android/) - [3 JavaScript charts in Android Studio with LightningChart JS](https://lightningchart.com/blog/javascript-charts-in-android-studio/) --- ## Nanosecond timestamps Nanosecond and microsecond timestamps are unsupported out of the box, due to the numbers generally being too large and having too many decimals for LightningChart JS high precision axis (or normal JavaScript numbers for that matter). However, they can be realized with so called _"origin shift"_ approach. In practice this means: 1. Subtracting all timestamps by the value of first displayed timestamp rounded to closest date, midnight (so counting starts from near 0). 2. Informing the charts about the shift using `setDateOrigin` method. Additionally, nanosecond timestamps should be converted to milliseconds (divided by 1_000_000). :::tip This data preparation can either be done in backend or in the frontend. If done in frontend, then most likely you need to transfer the timestamp data as `BigInt64Array` because primitive JavaScript numbers can't accurately store nanosecond timestamps. ::: **Example code:** ```ts // data { x: BigInt64Array, y: Float64Array } const chart = lc.ChartXY({ defaultAxisX: { type: 'linear-highPrecision', }, }) const series = chart.addLineSeries() // LCJS can't directly consume BigInt64Array // shift bigint64 data to start from near 0 in order to store it in Float64Array without losing nanosecond precision const xsShifted = new Float64Array(count) const shiftDate = new Date(Number(data.x[0]) / 1_000_000) // shift position should be beginning of date (00:00:00.000) shiftDate.setHours(0, 0, 0, 0) // NOTE: if UTC is set to `true` on date time tick strategy, should use `setUTCHours` instead here const shiftOrigin = shiftDate.getTime() const shiftOriginNanos = BigInt(shiftOrigin) * BigInt(1_000_000) for (let i = 0; i < count; i++) { xsShifted[i] = Number(data.x[i] - shiftOriginNanos) / 1_000_000 } series.appendSamples({ xValues: xsShifted, yValues: data.y }) chart.axisX.setTickStrategy(AxisTickStrategies.DateTime, (strategy) => strategy .setDateOriginNanos(shiftDate) ) ``` At these extreme zoom in ranges, there _can_ be some limitations in supported series configurations. - Area fill not supported. - Thick line series not supported (should use -1 as stroke thickness) These can be disabled dynamically depending on user zooming with logic like this: ```ts chart.axisX.addEventListener("intervalchange", (event) => { const thickness = series.getStrokeStyle().getThickness(); const isThinLine = thickness < 0; const timeWindow = event.end - event.start; const shouldUseThinLine = timeWindow < 1; if (shouldUseThinLine !== isThinLine) { series.setStrokeStyle((stroke) => stroke.setThickness(shouldUseThinLine ? -1 : 2), ); } }); ``` Achievable zoom in capability depends on the total time range of loaded data set. For 1 week of data, it is as in above demonstration - min interval ~= 250 nanoseconds. If longer time period is loaded, then zoom limits kick in earlier. Here's a rough look up table for convenience: |time range|min interval| |:--|:--| | 8 hours | 10 nanoseconds | | 1 week | 250 nanoseconds | | 1 year | 12 microseconds | Minimum interval refers to value range on time axis, meaning perceivable minimum time step is considerably smaller than this. Like in above demonstration, you can clearly see steps of 1 nanosecond. --- ## Offscreen canvas LightningChart JS has a beta feature that allows isolating all chart operations to a separate web worker thread and displaying the output to the user interface via an `OffscreenCanvas`. :::info **What does the feature being "beta" mean?** LCJS follows strict semantic versioning practices, meaning APIs and behaviors are not compromised outside major version releases in which case they are explicitly listed with respective migration guidance. Features marked as **beta** are new, experimental and considered outside the semantic versioning practice. They can be changed if we find that the API/behavior needs reworking based on user feedback ::: ## When is this useful? This is mainly a performance optimization approach. By moving all utilization of LCJS into a web worker thread, it effectively removes all charting related CPU utilization from the main thread, which reduces lag and frees up processing power for the rest of the application. The approach is most conveniently usable if you don't need the charting data at all in the main thread, in which case you directly stream all necessary data to the web worker. That said, it is also possible to share charting data from the main-thread to a web worker chart using `SharedArrayBuffers` ## Usage syntax ```ts // Main thread // let canvas: HTMLCanvasElement const offscreen = canvas.transferControlToOffscreen(); const worker = new Worker(new URL("./worker.ts", import.meta.url), { type: "module", }); worker.postMessage(offscreen, [offscreen]); ``` ```ts // Web worker let chartState: undefined | { canvas: OffscreenCanvas, chart: ChartXY } self.onmessage = ({ data }) => { if (data.type === 'init') { const canvas = data.canvas as OffscreenCanvas; const lc = lightningChart({ offscreenCanvas: canvas, }); // ... use LCJS API normally - charts will be rendered wherever the original Canvas element is } }; ``` For dynamic sizing of the DOM element to work, you'll need to setup a `ResizeObserver` that triggers `layout()` method in the web worker side when the canvas size changes: ```ts // Main thread const observer = new ResizeObserver(() => { worker.postMessage({ type: 'resize', width: canvas.offsetWidth, height: canvas.offsetHeight, dpr: devicePixelRatio }) }) observer.observe(canvas) ``` ```ts // Web worker // ... if (data.type === 'resize') { if (!chartState) return chartState.canvas.width = Math.round(data.width * data.dpr) chartState.canvas.height = Math.round(data.height * data.dpr) chartState.chart.engine.layout() } ``` ## Limitations - There currently isn't any implementation of piping user interactions from UI thread to web workers (or LCJS library) so any charts created using this approach will not support any user interactivity. - LCJS inside web worker currently doesn't work with developer licenses. Using this approach currently requires either a deployment license or a special type of development license. Trial licenses also work. - WebGL context sharing is unimplemented with offscreen canvas mode. This means that when using this approach, you are limited to max 16 charts (or WebGL contexts) on the page. If you are interested in Offscreen canvas mode but feel that these limitations hinder you, please [contact us](/contact). --- ## Optimizing performance Here you can find some common pointers towards ensuring best application performance when using LightningChart JS. ## Several charts visible at once LightningChart JS excels at applications that require several charts to be visible on the same web page. See [Grouping charts](https://lightningchart.com/js-charts/docs/more-guides/grouping-charts/) for more details. ## Progressive XY Series Oftentimes, XY Series are used to visualize data that is progressive in 1 dimension. For example, in date-time use cases, the data is generally in increasing time order. LightningChart JS XY Series are **massively** optimized for this particular scenario, where data is ordered. See [Line series data patterns](/features/xy/line/#data-patterns) for more details. If for some reason your data is not ordered, but it could be, consider this a strong suggestion to sort it, as the performance difference can't be understated. ## Mapping data to correct format These days LightningChart JS can directly consume most common kinds of incoming data formats. However, historically this has not been the case, and now there are likely many references of highly inefficient data mapping that were previously needed. ❌ bad: ```ts // data: Array<{ timestamp: number, value: number }> lineSeries.appendJSON(data.map((p) => ({ x: p.timestamp, y: p.value }))) ``` This seemingly innocent operation can be extremely performance intensive. For example, if your data set has millions of samples, this effectively makes a duplicate Array of the entire data set - extremely wasteful on memory usage! ❎ good: ```ts // data: Array<{ timestamp: number, value: number }> lineSeries.appendJSON(data, { whitelist: ['timestamp', 'value'] }) ``` For more examples of available data consume APIs, see [Adding data](/features/xy/line/#adding-data) ## Columnar data formats For performance reasons, we recommend applications to manage data in columnar format: ```ts // Example of columnar data format const data = { xValues: [x1, x2, x3, ...] yValues: [y1, y2, y3, ...] } ``` Rather than row-oriented format: ```ts // Example of row-oriented format const data = [ { x: x1, y: y1 }, { x: x2, y: y2 }, { x: x3, y: y3 }, ... ] ``` Column oriented data can be consumed by LCJS significantly faster and more memory efficiently. ## Multi-threading If you are using XY Line series, then you can consider enabling [multi-threading](/features/xy/line/#multi-threading). It allows you to utilize all available CPU cores, rather than doing everything on the same UI thread. This not only massively speeds up chart loading times (generally 6x faster), but also comes with the advantage of dropping CPU utilization on the main thread to negligible amounts. ## Data Transfer Transferring data to the frontend can be a massive performance bottleneck. Please refer to the [WebSocket section](/more-guides/real-time-data/websocket) for the latest pointers on effective real-time data transfer. ## Effects The default Themes built into LightningChart JS come with shadow and glow effects enabled. These are pretty performance heavy, so for best performance they can be disabled. Effects can be disabled from 1 component (series, title, axis, legend, etc.) by using `setEffect(enabled: boolean)` methods. The exact method might have a prefix added to it, e.g. `setTitleEffect()`. ```ts // Example, disable effects from a point series pointSeries.setEffect(false) ``` All effects can be disabled via `Theme`: ```ts const chart = lightningChart().ChartXY({ theme: disableThemeEffects(Themes.darkGold) }) ``` ## Gradients If final client hardware (to be clear, the hardware connected to the display peripheral that shows the charts) has absolutely no GPU or is extremely weak, then even disabling gradients may increase performance significantly. Most commonly, gradients are present in default background fill styles, which can be disabled (and replaced with solid colors) like this: ```ts chart.engine.setBackgroundFillStyle(transparentFill) chart.setBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 0) })) chart.setSeriesBackgroundFillStyle(new SolidFill({ color: ColorRGBA(0, 0, 0) })) ``` In addition, gradients can also be found by default in bar charts, rectangle series, map charts, legends, etc. but these are less common and generally smaller render areas and thus less impactful. For configuring background colors, using the separate [Themes library](/more-guides/themes/#custom-themes) is recommended. ## Text shadows Since v7.0, built-in dark themes display subtle shadows behind text for improved contrast. In applications that display lots of text elements and charts, this can add considerable extra load. Text shadows can be conveniently disabled with `disableTextShadows` function. ```ts // Example, disable text shadows for improved performance const chart = lightningChart().ChartXY({ theme: disableTextShadows(Themes.darkGold) }) ``` ## WebGL text rendering By default, all LightningChart JS text elements are rendered using WebGL. This gives best performance. Alternatively, HTML text rendering can be enabled but its slower. More details [here](/more-guides/html-text-rendering). ## Mozilla Firefox There is currently a performance problem specific to Mozilla Firefox, where it performs significantly worse than almost all other browsers. It is reported that enabling the "Shared canvas mode" greatly alleviates these problems: ```ts const lc = lightningChart({ sharedContextOptions: { // Shared canvas mode useIndividualCanvas: false } }) ``` ## Data sets with no gaps If your data set doesn't include `NaN` values, then a slight performance increase can be enabled by informing of this: ```ts const series = chart.addLineSeries({ includesNaN: false // <-- }) ``` ## Pausing charts Chart updates can be temporarily paused, moving them to an "hibernation" state where they don't process any updates. This can be useful in specific situations, most importantly in cases where you have real-time charts that will be temporarily out of sight (for example, hidden by an overlay). ```ts chart.setPauseRendering(true) ``` While a chart is paused, you can continue to use all its APIs normally. It will postpone majority of heavy computations required for processing re-rendering until it is eventually unpaused. By default, paused charts do not re-render even if their DOM layout is changed, which makes them appear as a blank canvas on the web page. If this is detrimental, there is an automatic feature that can re-render paused charts only when there is a resize happening: ```ts chart.setPauseRendering(true, { unpauseDuringResize: true }) ``` --- ## Positioning charts LightningChart components are positioned using HTML `` elements and `CSS`. When a new LightningChart component is created, the user should supply reference to the `` that the chart should be fitted into. ```html ``` The `` should be created before the chart. --- ## Real-time data LightningChart JS is a real-time oriented data visualization library. This is really what sets it apart from other products - even when feeding data points in real-time, the chart will display updates with highest possible refresh rate and use minimal CPU power. **LightningChart JS can handle over million data points per second, visualizing everything with 60 FPS.** The most performant way to push real-time data flow into LightningCharts is with a single method call: `appendSamples({ xValues: Array, yValues: Array })` For more examples of available data consume APIs, see [Adding data](/features/xy/line/#adding-data) Calling methods for adding data points several times in conjunction should be avoided. For example, something like this: ```ts // Don't do this! myDataPoints.forEach((point) => { myLineSeries.appendSample(point) }) // Instead, do this: myLineSeries.appendJSON(myDataPoints) ``` The next sections show concrete examples on how to handle real-time data from a particular data transfer protocol / library, such as _WebSocket_, _SignalR_, etc. Other topics that are closely related to handling real-time data are: - [Scrolling axes](/features/axis/#scrolling-axis) and - _Data cleaning_. For documentation, refer to specific feature. For example, [Line Series](/features/xy/line) --- ## InfluxDB [InfluxDB](https://www.influxdata.com/) is a common Time Series Database. It is frequently used together with LightningChart JS for visualization of time series data and often real-time data. There is not much to consider when using LightningChart JS with InfluxDB. From viewpoint of LightningChart, it is a time series use case like any other. Time series documentation can be found under different Feature sections: - [Timestamps in Line Series](/features/xy/line#using-timestamps) - [Date-Time Axis](/features/axis#date-time-axis) :::tip Check out the related showcase project in GitHub: [LightningChart JS + React + Express + InfluxDB](https://github.com/Lightning-Chart/lcjs-react-express-influxdb) ::: --- ## RxJS [RxJS](https://rxjs.dev/) is an useful library for connecting real-time data to data visualizations (as well as other business logic). Because LightningChart JS series are oriented towards handling appending data operations, it is very easy to connect an RxJS _observable_ to them: ```ts // data: Observable<{ x: number, y: number }> data.subscribe((newSample) => lineSeries.appendSample(newSample)) // ... or // data: Observable<{ timestamp: number, measurement: number }> data.subscribe((newSample) => lineSeries.appendSample({ x: newSample.timestamp, y: newSample.measurement })) ``` --------------------------- Other topics that are closely related to handling real-time data: - [Scrolling axes](/features/axis/#scrolling-axis) and - _Data cleaning_. For documentation, refer to specific feature. For example, [Line Series](/features/xy/line) --- ## SignalR SignalR is a very popular real-time communication library from Microsoft, especially within .NET framework. It is also available as a [JavaScript client](https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client). This allows JavaScript applications to connect to data sources originating from various .NET frameworks very easily. Here's how it looks from JavaScript side: ```js const connection = new signalR.HubConnectionBuilder() .withUrl('/chart') .withAutomaticReconnect() .configureLogging(signalR.LogLevel.Information) .build() async function start() { try { await connection.start() console.log("SignalR Connected.") } catch (err) { console.log(err) setTimeout(start, 5000) } }; start() connection.on('add', (newDataPoints) => { // newDataPoints: { x: number, y: number }[] lineSeries.appendJSON(newDataPoints) }) ``` And an extract from ASP .NET Core example on the server side: ```csharp public struct DataPointXY { public double X { get; set; } public double Y { get; set; } public DataPointXY(double x, double y) { X = x; Y = y; } } ... DataPointXY[] dataPointArr = new DataPointXY[] { new DataPointXY(timestamp, measurement) }; await _hub.Clients.All.SendAsync( "add", dataPointArr, cancellationToken: stoppingToken ); ``` --------------------------- Other topics that are closely related to handling real-time data: - [Scrolling axes](/features/axis/#scrolling-axis) and - _Data cleaning_. For documentation, refer to specific feature. For example, [Line Series](/features/xy/line) --- ## WebSocket The [WebSocket API](https://developer.mozilla.org/en-US/Web/API/WebSockets_API) is a technology for two-way communication between a browser and a server. It can be used for very high performance data transfer. It's robust, flexible and well supported! The easiest way to send data for visualization using WebSocket is to pack messages into _stringified JSON_. Here's how that would look in code: ```ts // Server // newDataPoints: Array<{ timestamp: number, measurement: number }> ws.send(JSON.stringify(newDataPoints)) ``` ```ts // Client ws.onmessage = (e) => { const newDataPoints = JSON.parse(e.data) lineSeries.appendJSON(newDataPoints, { x: 'timestamp', y: 'measurement' }) } ``` This is **super easy**, and really not too bad performance-wise. However, it is not the most performant way to transfer data. Long decimals and property names add a lot of extra bandwidth that can be removed by using non-string formats, like binary: ```ts // Server // newMeasurements: Float32Array ws.send(newMeasurements) ``` ```ts // Client ws.onmessage = (e) => { const newMeasurements = e.data lineSeries.appendSamples({ yValues: newMeasurements }) } ``` The above example sends only Y measurements (and automatically increases X by 1 for every sample). Here's an example of sending both X and Y measurements. ```ts // Server // xValues: Float32Array // yValues: Float32Array // Combine x and y values into 1 array const xyValues = new Float32Array(xValues.length * 2); xyValues.set(xValues, 0); xyValues.set(yValues, xValues.length); ws.send(xyValues) ``` ```ts // Client ws.onmessage = (e) => { const data = e.data const sampleCount = data.length / 2; const xValues = data.subarray(0, sampleCount); const yValues = data.subarray(sampleCount); lineSeries.appendSamples({ xValues, yValues }) } ``` Often, LightningChart JS applications involve several channels which all receive real-time data. In this kind of setting, it is recommended to buffer data streams so that 1 _data package_ is sent every 10-20 milliseconds. For best performance, this data package should include all new data points for all the channels, rather than sending a separate message for every channel. This is easy to achieve with the _stringified JSON_ approach mentioned at the start, but in intensive use cases with large amounts of streaming data, it is recommended to use the binary formats, even if it requires a bit of extra work compared to the simple cases showcased above. ## Data transfer library We have prepared a small library dedicated to high performance data transfer between a server and application. It includes a few methods for different use cases (like having several charts, no timestamps, shared timestamps, etc.) and covers both encoding (backend) and decoding (frontend) messages. Here's a sample: ```ts // Server | 5 channels, 1 data point every 16 milliseconds setInterval(() => { const dataAllChannels = new Array(5).fill(0).map((_, ch) => { const xValues = [performance.now()]; const yValues = [Math.random() + ch]; return [xValues, yValues]; }); const msg = encodeMultiChannelArrXY(dataAllChannels); ws.send(msg); }, 1000 / 60); ``` ```ts // Client ws.onmessage = async (e) => { const raw = await e.data.arrayBuffer(); const dataAllChannels = decodeMultiChannelArrXY(raw); channels.forEach((series, i) => { series.appendSamples({ xValues: dataAllChannels[i][0], yValues: dataAllChannels[i][1] }) }); }; ``` Under the hood, this does all the binary magic and uses **~10x less bandwidth** (*) compared to transferring data as JSON. These methods are intended to be directly utilized by our customers. The source code of the methods is also available, so you can suit them to your exact needs. Please [contact us](/contact) if you want access to this separate data transfer library. (*) JSON bandwidth use fluctuates heavily based on uncertainties, like property names, decimal point counts, etc. --------------------------- Other topics that are closely related to handling real-time data: - [Scrolling axes](/features/axis/#scrolling-axis) and - _Data cleaning_. For documentation, refer to specific feature. For example, [Line Series](/features/xy/line) --- ## Styles, colors and fonts This guide is about Style API, which allows overriding the default style of any single part of a chart during run-time. **If you are looking to change the overall look and color of the charts as an entirety rather than individual details, then we suggest you look at [Themes](/more-guides/themes).** ------ All LightningChart JS styles, colors, fonts, etc. can be configured by the user. Most of them can even be changed at any point during run-time. For the purpose of describing different styles, LightningChart JS has its own set of classes which are critical to understand in order to customize the style of LightningChart. The most important style classes are the following: - `FillStyle` - describes the style for a filled area. - `LineStyle` - describes the style for a stroke or border. - `Color` - describes 1 color. - `FontSettings` - describes a font, including family, size, weight, etc. Many of these style classes are _abstract_, meaning that it has a number of implementations which you can choose from. For example, for a `LineStyle` you can use `SolidLine` or `DashedLine`. ```ts // Example, chart title color chart.setTitleFillStyle(new SolidFill({ color: ColorHEX("#ff0000") })); ``` ```ts // Example, chart title font chart.setTitleFont( new FontSettings({ family: "Segoe UI", size: 12, weight: "bold", style: "normal", }) ); ``` Here's one more example for configuring a `LineStyle`: ```ts // Example, configure stroke style of a line series LineSeries.setStrokeStyle( new SolidLine({ thickness: 2, fillStyle: new SolidFill({ color: ColorHEX("#ff0000"), }), }) ); ``` ## Color factories There are a handful of different syntaxes of defining colors: ```ts const colorRed = ColorRGBA(255, 0, 0, 255) const colorGreen = ColorHEX('#00ff00') const colorBlue = ColorCSS('blue') const colorRed2 = ColorHSV(0) ``` ## Partial style modifications Often you wouldn't ideally want to completely specify a new style, but rather only change some of the available properties, like font family for example. For this purpose, almost all style methods allow supplying a callback function that modifies the current style. ```ts // Example, change font family chart.setTitleFont((font) => font.setFamily("Segoe UI")); ``` ```ts // Example, change stroke thickness LineSeries.setStrokeStyle((stroke) => stroke.setThickness(1)) ``` ```ts // Example, change stroke to be dashed LineSeries.setStrokeStyle((stroke) => new DashedLine({ thickness: stroke.getThickness(), fillStyle: stroke.getFillStyle() })) ``` ## Style objects immutability All LightningChart style objects are _immutable_, meaning if you call a method like `FontSettings.setFamily()` it doesn't actually mutate the object, but instead creates a new object, so there is no risk of unexpected side effects. --- ## Themes This section explores a complete way to configure the visual look of LightningChart JS - _Themes_. At first level, LightningChart JS is distributed with a set of built-in Themes to choose from: - `darkGold` - `light` - `cyberSpace` - `turquoiseHexagon` ![Built in Color Theme - cyberSpace](/img/theme-cyberSpace.png) These themes are hand-crafted to be very impressive looking. However, often times users need chart components to fit into an existing visual design, rather than the other way around. For example, many software companies have their own brand colors and visual designs. A critical requirement often is that the charts can be configured to fit into the design. While this can be done using the [style API](/more-guides/styles-colors-and-fonts), it might be very impractical if there are multiple themes that should be implemented and/or multiple applications with different charts. So, a better approach is _Custom Themes_. ## Custom Themes When creating any LightningChart JS component, a _Theme_ object can be supplied. This object is essentially a long list of properties that contains a default style for every individual visual component and feature of the library. On paper, creating a completely custom theme is as simple as defining an object with all the properties of `Theme`. However, as there is a large number of properties, and many applications wouldn't ideally want to bother with every single one of them. **The easiest way to define a custom theme is to use the separate, open-source package [`@lightningchart/lcjs-themes`](https://www.npmjs.com/package/@lightningchart/lcjs-themes)** Here's how the usage looks: ```ts const myCustomTheme = makeCustomTheme({ isDark: true, gradients: true, effects: true, fontFamily: "Segoe UI, -apple-system, Verdana, Helvetica", backgroundColor: ColorHEX("#05183dff"), textColor: ColorHEX("#ffffffff"), dataColors: [ColorHEX("#ffff5b"), ColorHEX("#ffcd5b"), ColorHEX("#ff9b5b"), ColorHEX("#ffc4bc"), ColorHEX("#ff94b8"), ColorHEX("#db94c6"), ColorHEX("#ebc4e0"), ColorHEX("#a994c6"), ColorHEX("#94e2c6"), ColorHEX("#94ffb0"), ColorHEX("#b4ffa5")], axisColor: ColorHEX("#00000000"), gridLineColor: ColorHEX("#2e2e2eff"), uiBackgroundColor: ColorHEX("#020918ff"), uiBorderColor: ColorHEX("#ffffff"), dashboardSplitterColor: ColorHEX("#16173cff"), }) const chart = lightningChart().ChartXY({ theme: myCustomTheme }) ``` ![Custom theme](/img/theme-custom.png) As you can see, you only have to specify a few key properties, which makes it easy to get started fast. **You can also pick these values using our [Online Theme Editor](https://lightning-chart.github.io/lcjs-themes/)** Utilizing this simple approach is strongly recommended especially in early development stages to get quickly started up with your own brand coloring. Going forward, you can override individual Theme properties of the automatically generated theme as needed. For this stage, the source code of [`lcjs-themes`](https://github.com/Lightning-Chart/lcjs-themes/tree/main/src) can be used as a helpful reference. ```ts // Create flat theme, but override desired properties const flatTheme = makeFlatTheme({ isDark: true, gradients: true, effects: true, fontFamily: 'Segoe UI, -apple-system, Verdana, Helvetica', backgroundColor: ColorHEX('#181818ff'), textColor: ColorHEX('#ffffc8ff'), dataColors: [ColorHEX('#ffff5b'), ColorHEX('#ffcd5b'), ColorHEX('#ff9b5b')], axisColor: ColorHEX('#00000000'), gridLineColor: ColorHEX('#303030ff'), uiBackgroundColor: ColorHEX('#161616ff'), uiBorderColor: ColorHEX('#ffffff'), dashboardSplitterColor: ColorHEX('#2d2d2dff'), }) const customizedFlatTheme = { ...flatTheme, chartXYTitleFont: flatTheme.chartXYTitleFont.setFont((font) => font.setWeight('bold')) } ``` ## Up/downscaling chart elements Default chart element sizes, paddings, etc. general "sizing" can be adjusted using `scaleTheme`: ```ts const chart = lc.ChartXY({ theme: scaleTheme(Themes.darkGold, 0.5) // 50% smaller fonts, paddings, button sizes, etc. than normal }) ``` --- ## Customer Portal LightningChart Customer Portal can be found at https://portal.lightningchart.com/user/login It is exclusively for commercial users, and particularly needed **after trialing stops** and the customer moves to application deployment and further development. Your license purchases are also linked to your Portal account, so you can manage deployments and developer licenses yourself. You can create a Customer Portal account [here](https://portal.lightningchart.com/user/register). ------------ If you are an existing LightningChart customer, but you don't have Portal access, please [contact us](https://lightningchart.com/contact/) --- ## Customization Customizing LightningChart JS features to better fit a particular users requirements is very common. Usually, this is done by LightningChart developers themselves as a part of a trial closing: 1. Customer Trial 2. Test and talk with trial support 3. Mutual understanding of issues in way of integration to production 4. License deal + **customization service** to resolve the issues The customization itself can range from performance optimizations to new features or refactoring old features. The process is **usually very fast**, ranging from 1 to 8 weeks, depending on complexity. Customization can also be done for existing customers. To learn more, [ask us directly](https://lightningchart.com/contact/). --- ## Roadmap LightningChart JS is in active development since 2019. Since 2023, we have committed to delivering new version releases **every 3 months**. The update content is focused on: - Bringing new features that enable more use cases. - Making it easier to use the product. - Upgrading the product to look better. - Improving product performance. - Resolving customer issues. The history of update content can be observed from our [public roadmap](https://lightningchart.com/roadmap/?js) or in more detail from [version history page](https://lightningchart.com/js-charts/docs/version-history/). On-going and future product development is not publicly exposed. The best way to learn of LightningChart JS future improvements and direction is to [get directly in contact](https://lightningchart.com/contact/). --- ## Support Technical support is available to commercial users of LightningChart JS to help developers use LightningChart JS and resolve problem situations on users end. **If you are still trialing...** please either directly contact [our closest account manager](https://lightningchart.com/contact/) or leave a [general inquiry](https://lightningchart.com/contact/) using the same email you used to activate the trial. **Otherwise...** Support cases are categorized into 4 distinct types - **1) Licensing and commercial** - help with licensing or inquiring about possible new license requirements. - **2) Feedback** - simply inform us of anything you think is good for us to know, without necessarily requiring an immediate reaction. - **3) Bugs** - you believe there is a bug in the library or need help with the right configurations. - **4) Technical support** - you want to ask for guidelines / best practices / tips or need help with your use case. First, please identify which case best describes your situation. ## Licensing and commercial For help related to licensing or any other commercial topic, it is best to directly contact your own account manager (the one you have previously talked with). A list of our account managers and their contact informations can be found [here](https://lightningchart.com/contact/). ## Feedback If you simply want to share feedback without requiring an immediate reaction, we appreciate receiving your emails at: **feedback.js@lightningchart.com** ## Bugs If you believe you have found a bug, then the most important thing is **being able to reproduce it**. Here's a few different approaches to communicating with us over bugs: ### Bug reproduction by Interactive Examples Open any of our examples in "edit" mode, for example, [direct link](https://lightningchart.com/js-charts/interactive-examples/edit/lcjs-example-0017-largeLineChartXY.html?disable-animations=1&isList=true). Modify the code such that the bug is visible either directly or after interaction. **Include the following things in your Bug report:** - Full example code (either as as a .txt file attachment or pasted directly to message). - Description of the expected library behavior as well as the actual perceived behavior. - If bug needs some user interaction to show, description of required interactions. - Screenshot / video captures are extremely helpful additions in effective communication over bugs. ### Bug reproduction by Codepen / JSFiddle / Stack Overflow LightningChart JS works without any license in these code-sharing platforms. Instead of using our Interactive Examples, you can also share the related code as a link to one of these platforms. - [CodePen](https://codepen.io/) - [JSFiddle](https://jsfiddle.net/) ### Submitting a bug report When ready with your bug description and reproduction information, send an email to: **[support@lightningchart.com](mailto:support@lightningchart.com?subject=Bug%20report)** with subject: "Bug report". We also appreciate knowing how this bug affects you (i.e. is it blocking something, or just a minor nuisance). Please also make sure we can identify which company we are talking with. ## Technical support When you want to ask for guidelines, best practices, tips or just general help regarding your use case, send an email to: **[support@lightningchart.com](mailto:support@lightningchart.com?subject=Technical%20support)** with subject: "Technical support". Please also make sure we can identify which company we are talking with. -------- Our service desk is currently located in Finland (GMT+3). We try our best to process support requests within 1 workday regardless of the original time-zone. For non-commercial users, a community exists at [Stack Overflow](https://stackoverflow.com/questions/tagged/lightningchart), but there is no dedicated service from LightningChart developers. --- ## Trials and Integration Are you trialing LightningChart JS for commercial purposes? If yes, then we offer some special privileges for you: - **Full product access** for evaluation, testing and proof of concept creation. - **Dedicated expert support** for both commercial as well as technical queries. To access these benefits, please [start your trial](https://lightningchart.com/js-charts/free-trial/). If you have any questions to ask us, or would like to meet with us to discuss your use case, please either directly contact our closest [account manager](https://lightningchart.com/contact/) or leave a [general inquiry](https://lightningchart.com/contact/) using the same email you used to activate the trial. In addition to the above privileges, trial users also have access to **Integration services**. Briefly explained, when closing a trial period we can offer technical LightningChart JS expertise to help the customer integrate the library into their products. This can include consultation, integration work, and even LightningChart customization to meet customer requirements. --- ## Technical information ## Dependencies and telemetry LightningChart JS is a JavaScript/TypeScript library for placing high performance data visualization components in user interfaces. It is powered by WebGL, which is very well supported (see [support table](https://caniuse.com/webgl)). LightningChart JS has a minimal number of external dependencies (only 2 open source dependencies at time of writing 2023). You can check the up to date dependencies using [npmgraph](https://npmgraph.js.org/?q=%40arction%2Flcjs). LightningChart JS requires either **WebGL 2** _or_ WebGL 1 with following extensions to work: - [ANGLE_instanced_arrays](https://developer.mozilla.org/en-US/docs/Web/API/ANGLE_instanced_arrays) - [EXT_blend_minmax](https://developer.mozilla.org/en-US/docs/Web/API/EXT_blend_minmax) - [OES_element_index_uint](https://developer.mozilla.org/en-US/docs/Web/API/OES_element_index_uint) - [OES_standard_derivatives](https://developer.mozilla.org/en-US/docs/Web/API/OES_standard_derivatives) - [OES_vertex_array_object](https://developer.mozilla.org/en-US/docs/Web/API/OES_vertex_array_object) You can check if your browser meets these requirements at [webglreport.com](https://webglreport.com/?v=2). At time of writing (2024), ~99% of browsers meet these requirements ([caniuse.com](https://caniuse.com/)). LightningChart JS does not include any intrusive or non-intrusive tracking, use metrics, analytics reporting etc. In production deployments, the library doesn't make any* outbound network requests/calls (network requests are only used when developer licenses are used). (*) [Exception case](/more-guides/lc-resources/) when using select features requiring additional asset files and resources not hosted by user. ## TypeScript In TypeScript applications, minimum TypeScript version required is currently `4.9.3` (released Nov 15 2022). The LCJS library build includes Typings which can be used for Intellisense even when working with JavaScript. This typings is distributed in `@lightningchart/lcjs/dist/lcjs.d.ts` file. You can view the library build files in [jsdelivr](https://www.jsdelivr.com/package/npm/@lightningchart/lcjs?tab=files), for example. ## Performance and GPU utilization LightningChart JS runs completely on client device without requiring any server setup etc. Performance is largely affected by client hardware (CPU, GPU, RAM). Best performance is found on personal computers, gaming laptops, modern mobile phones and embedded devices with dedicated GPUs. With other hardware, you can expect lesser performance but still significantly better real-time capabilities than with traditional HTML/SVG/Canvas based chart solutions, as long as there is at least an integrated graphics processing unit on the device. LightningChart JS performance advantages are based on offloading heavy computations that traditional chart solutions run with CPU, to the GPU and utilizing low level graphic APIs directly in the most optimal manner possible for purposes of data visualization. The GPU driven algorithms of LCJS span from over 15 years of commercial experience with GPU accelerated data visualization, starting with the inception of the original GPU accelerated data visualization product, LightningChart .NET. LightningChart JS has been excessively benchmarked against virtually all popular players in JavaScript chart solution market (commercial, free, open-source). It's key performance advantages are: - Capacity of handling significantly more data (+10 million data points without a sweat). - Utilizing considerably less CPU. - Reacting to data or style changes and user interactions at 60 FPS always. - Real-time oriented design and API. - Fast loading of large data sets (10 million data points line chart in 0.3 seconds). - Perfect when you need to interact, zoom, analyze and operate on extremely heavy visualizations. Learn more about performance studies at our [web site](https://lightningchart.com/js-charts/performance/). For info about optimizing GPU usage when working with weak hardware, see [Optimizing performance](/more-guides/optimizing-performance/) ## Deployment and internet connection LightningChart JS can be deployed to virtually any kind of environment: SaaS, public web site, offline intranet, distributable device, [mobile application](/more-guides/mobile), desktop application. **It does not need installation of license wizards for trialing, developing or production deployments**. Internet connection is only required when using a Developer license in a local development environment. Otherwise chart can be used fully offline. ## End user legal agreement By downloading, installing, copying or otherwise using LightningChart®JS, you are agreeing to be bound to the terms of our end user legal agreement. Please find the latest EULA at our [web site](https://lightningchart.com/eula/lightningchart-js-commercial-license/). --- ## Axis range is limited If you are running into issues where an Axis (X or Y) doesn't zoom into the data, or won't let you manually zoom into desired range for some reason, please read section on [Axis zoom range limitations](/features/axis/#zoom-range-limitations). --- ## Chart is not scrolling If you run into the issue where you have a scrollable layout containing a chart, and the chart is not moving when scrolled, this happens because the chart is not aware that it is moving. The solution to this problem is to add a `scroll` event handler to your scrollable DIV, and inform the chart(s) that they are moving. ```ts // Example div.onscroll = () => { chart.engine.layout() } ``` In React or other UI frameworks, you may not have access to a `Chart` object in this scenario, so you can also call `layout` on the LightningChart context, which affects all charts created using that context: ```tsx // Example 2 const lc = useContext(LCContext); return ( { lc?.layout(); }} > ) ``` --- ## Chart is not interactable If you run into the issue where you have a scrollable layout containing a chart, and the chart becomes temporarily un-interactable after scrolling on the webpage, this happens because the chart is not aware that it is moving. The solution to this problem is to add a `scroll` event handler to your scrollable DIV, and inform the chart(s) that they are moving. ```ts // Example div.onscroll = () => { chart.engine.layout() } ``` :::tip This issue received some out of the box improvements in v7.0. ::: In React or other UI frameworks, you may not have access to a `Chart` object in this scenario, so you can also call `layout` on the LightningChart context, which affects all charts created using that context: ```tsx // Example 2 const lc = useContext(LCContext); return ( { lc?.layout(); }} > ) ``` --- ## How can I disable glow/shadow effect? Effects can be disabled from 1 component (series, title, axis, legend, etc.) by using `setEffect(enabled: boolean)` methods. The exact method might have a prefix added to it, e.g. `setTitleEffect()`. ```ts // Example, disable effects from a point series pointSeries.setEffect(false) ``` All effects can be disabled via `Theme`: ```ts const chart = lightningChart().ChartXY({ theme: disableThemeEffects(Themes.darkGold) }) ``` --- ## Error: license key is too old The "License key validation failed: License key is too old for this version" error can happen when you are using a license that was purchased with a subscription duration which expired before the current library version you are using was released. Whenever you purchase or renew a LightningChart license, you choose your subscription duration - i.e. how far away in the future will you receive new library versions and technical support. For any library versions that are released after the end of that subscription you will receive this error if trying to use the old license. :::tip **If you encounter this error, it means one of two things:** 1. You need to renew your licenses to utilize the latest library versions 2. You have already renewed your licenses, in which case you just need to re-download your license key(s) from the Customer Portal ::: ## Re-downloading your license keys after renewal After renewing your licenses and in order to use the latest library versions, you need to re-download your license keys. For this, please follow instructions here depending on the type of key: - [Developer licenses](/licenses/developer-licenses/) - [Web deployment licenses](/licenses/web-deployment/) - [App deployment licenses](/licenses/app-deployment/) --- ## Error: LightningChart JS Resources not found Please see [Setting up LightningChart resources](/more-guides/lc-resources/) --- ## Error: Session exists already The "License key validation failed: Session exists already" error can happen when you are using a developer license key from two different sessions at the same time. This can mean that: - Two different people are using the same license. - 1 person is using the same license in two different machines simultaneously. - 1 person is using the same license in two different browsers simultaneously. Please see section on [Developer license](/licenses/developer-licenses/#error-license-key-validation-failed-session-exists-already) for more details and ways to get around this. --- ## Flickering heatmap LightningChart JS 2D heatmaps can only display 1 heatmap value per 1 visible pixel on display. Very dense heatmaps can contain more heatmap values within 1 pixel. In this situation there is no distinct guarantee which of these heatmap values is displayed on the pixel. This may depend on the exact position of the heatmap, size of chart, and so on. If there is a scrolling Axis, the heatmap values displayed may vary between frames, resulting in observable flickering. --- ## Troubleshooting Do you have an issue? Please check below articles in case we have prepared an entry for it already. Otherwise, if you can find the help you needed, please [contact us!](/contact) --- ## How often are there new releases? Please see [Roadmap section](/services/roadmap). --- ## Restricted features ## LightningChart JS A few features within LightningChart JS have specific restrictions. This page lists these features and tells how to get access to them. ### Data Grid `DataGrid` is a grid control feature, which is bundled together with other features, but sold as a separate option. More information can be found on [our web site](https://lightningchart.com/js-charts/datagrid/). `DataGrid` can be used with trial licenses, but when moving to developer licenses it will only work if the option was purchased. ### OHLC Series `OHLCSeries` is a feature specific to Trading industry, and it is restricted to users who have explicitly bought the product for Trading use cases. This means that this feature will not work with any kind of license (trial/developer/deployment), unless approved by LightningChart. **If you are an existing Trading customer**, please contact your account manager or [submit this form](https://lightningchart.com/contact/). It's worth noting that `OHLCSeries` features were completely reworked and upgraded along-side this commercial change. If you are a new user and need `OHLCSeries` in your use case, please [contact us](/contact). ## LightningChart Python The free version of [LightningChart Python](https://lightningchart.com/python-charts/)'s *Data Scientist license* restricts the use of the following features: * 3D Chart * Polar Chart * Heatmap Series * Parallel Coordinate Chart * Real-time data visualization To use these features, activate your Data Scientist license subscription through the [LightningChart Portal](https://portal.lightningchart.com/user/login/). --- ## Text quality looks bad There have been some cases where LightningChart JS text sharpness looks bad (worse than other text on the web page). Here's some troubleshooting that you can try: 1. Add `` to the web page HTML. If this is not done, then LightningChart JS text will not look sharp on high DPI monitors. 2. Refresh the page. 3. Switch to [HTML text rendering](/more-guides/html-text-rendering/). 4. Try different [color Themes](/more-guides/themes) to see if the problem persists. --- ## Type errors If you run into type errors that seem to originate from within the LightningChart JS library, it is possible that there is a conflict between your applications TypeScript configuration and the LightningChart JS distributable. You can check the minimum supported TypeScript version from [Technical information](/technical-info) section. Known issues: - LightningChart JS v4.1.0 is suspected of introducing a type support degradation against TypeScript configuration `strictFunctionTypes: true`. This doesn't trigger in all applications and the exact circumstances are unclear. A fix to known cases is scheduled for v5.0.0. --- ## Warning: using publicly hosted resources Please see [Setting up LightningChart resources](/more-guides/lc-resources/)