Angular Point Line 3D Chart

Our team demonstrates how to create a Angular 3D Point Line Chart using SciChart.js, capable of creating detailed 3D JavaScript Charts.

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

Radix2FFT.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
1import { Radix2FFT } from "../../../FeaturedApps/ScientificCharts/AudioAnalyzer/Radix2FFT";
2import {
3    CameraController,
4    HeatmapLegend,
5    MouseWheelZoomModifier3D,
6    NumericAxis3D,
7    OrbitModifier3D,
8    parseColorToUIntArgb,
9    PointLineRenderableSeries3D,
10    ResetCamera3DModifier,
11    SciChart3DSurface,
12    TGradientStop,
13    Vector3,
14    XyzDataSeries3D,
15} from "scichart";
16import { appTheme } from "../../../theme";
17
18type TMetadata = {
19    vertexColor: number;
20    pointScale: number;
21};
22
23// This function generates some spectral data for the waterfall chart
24const createSpectralData = (n: number) => {
25    const spectraSize = 1024;
26    const timeData = new Array(spectraSize);
27
28    // Generate some random data with spectral components
29    for (let i = 0; i < spectraSize; i++) {
30        timeData[i] =
31            4.0 * Math.sin((2 * Math.PI * i) / (20 + n * 0.2)) +
32            10 * Math.sin((2 * Math.PI * i) / (10 + n * 0.01)) +
33            20 * Math.sin((2 * Math.PI * i) / (5 + n * -0.002)) +
34            3.0 * Math.random();
35    }
36
37    // Do a fourier-transform on the data to get the frequency domain
38    const transform = new Radix2FFT(spectraSize);
39    const yValues = transform.run(timeData);
40    // .slice(0, 300); // We only want the first N points just to make the example cleaner
41
42    // This is just setting a floor to make the data cleaner for the example
43    for (let i = 0; i < yValues.length; i++) {
44        yValues[i] =
45            yValues[i] < -30 || yValues[i] > -5 ? (yValues[i] < -30 ? -30 : Math.random() * 9 - 6) : yValues[i];
46    }
47    yValues[0] = -30;
48
49    // we need x-values (sequential numbers) for the frequency data
50    const xValues = yValues.map((value, index) => index);
51
52    return { xValues, yValues };
53};
54
55// SCICHART CODE
56export const drawExample = async (rootElement: string | HTMLDivElement) => {
57    const { sciChart3DSurface, wasmContext } = await SciChart3DSurface.create(rootElement, {
58        theme: appTheme.SciChartJsTheme,
59    });
60    sciChart3DSurface.worldDimensions = new Vector3(300, 100, 300);
61    sciChart3DSurface.camera = new CameraController(wasmContext, {
62        position: new Vector3(-141.6, 310.29, 393.32),
63        target: new Vector3(0, 50, 0),
64    });
65
66    sciChart3DSurface.chartModifiers.add(new MouseWheelZoomModifier3D());
67    sciChart3DSurface.chartModifiers.add(new OrbitModifier3D());
68    sciChart3DSurface.chartModifiers.add(new ResetCamera3DModifier());
69
70    sciChart3DSurface.xAxis = new NumericAxis3D(wasmContext, {
71        axisTitle: "Frequency (Hz)",
72        drawMinorGridLines: false,
73        drawMajorGridLines: false,
74        tickLabelsOffset: 20,
75    });
76    sciChart3DSurface.yAxis = new NumericAxis3D(wasmContext, {
77        axisTitle: "Power (dB)",
78        drawMinorGridLines: false,
79        drawMajorGridLines: false,
80        tickLabelsOffset: 20,
81    });
82    sciChart3DSurface.zAxis = new NumericAxis3D(wasmContext, {
83        axisTitle: "Time (s)",
84        drawMinorGridLines: false,
85        drawMajorGridLines: false,
86        tickLabelsOffset: 20,
87    });
88
89    for (let i = 0; i < 50; i++) {
90        // Create some data for the example
91        // xValues are frequency values (Hz)
92        // yValues are heights or magnitude
93        const { xValues, yValues } = createSpectralData(i);
94        // zValues are the 3rd dimension where we will spread out our series in time
95        const zValues = Array.from({ length: xValues.length }).map((_) => i * 2);
96
97        // Metadata in scichart.js 3D controls color 3D line series. It can also hold additional optional properties
98        // Below we format the data for yValues into metadata colour coded and scaled depending on the value
99        const metadata = formatMetadata(yValues, [
100            { offset: 1, color: appTheme.VividPink },
101            { offset: 0.9, color: appTheme.VividOrange },
102            { offset: 0.7, color: appTheme.MutedRed },
103            { offset: 0.5, color: appTheme.VividGreen },
104            { offset: 0.3, color: appTheme.VividSkyBlue },
105            { offset: 0.2, color: appTheme.Indigo },
106            { offset: 0, color: appTheme.DarkIndigo },
107        ]);
108
109        // Add a 3D Point-Line chart
110        sciChart3DSurface.renderableSeries.add(
111            new PointLineRenderableSeries3D(wasmContext, {
112                dataSeries: new XyzDataSeries3D(wasmContext, {
113                    xValues,
114                    yValues,
115                    zValues,
116                    metadata,
117                }),
118                strokeThickness: 3,
119                opacity: 0.5,
120            })
121        );
122    }
123
124    return { sciChartSurface: sciChart3DSurface, wasmContext };
125};
126
127function formatMetadata(valuesArray: number[], gradientStops: TGradientStop[]): TMetadata[] {
128    const low = Math.min(...valuesArray);
129    const high = Math.max(...valuesArray);
130
131    const sGradientStops = gradientStops.sort((a, b) => (a.offset > b.offset ? 1 : -1));
132    // Compute a scaling factor from 0...1 where values in valuesArray at the lower end correspond to 0 and
133    // values at the higher end correspond to 1
134    return valuesArray.map((x) => {
135        // scale from 0..1 for the values
136        const valueScale = (x - low) / (high - low);
137        // Find the nearest gradient stop index
138        const index = sGradientStops.findIndex((gs) => gs.offset >= valueScale);
139        // const nextIndex = Math.min(index + 1, sGradientStops.length - 1);
140        // work out the colour of this point
141        const color1 = parseColorToUIntArgb(sGradientStops[index].color);
142        // const color2 = parseColorToUIntArgb(sGradientStops[nextIndex].color);
143        // const ratio = (valueScale - sGradientStops[index].offset) / (sGradientStops[nextIndex].offset - sGradientStops[index].offset)
144        // const colorScale = uintArgbColorLerp(color1, color2, ratio)
145        // console.log(`valueScale ${valueScale} low ${sGradientStops[index].offset} high ${sGradientStops[nextIndex].offset} ratio ${ratio}`);
146        return { pointScale: 0.1 + valueScale, vertexColor: color1 };
147    });
148}
149
150export const drawHeatmapLegend = async (rootElement: string | HTMLDivElement) => {
151    const { heatmapLegend, wasmContext } = await HeatmapLegend.create(rootElement, {
152        theme: {
153            ...appTheme.SciChartJsTheme,
154            sciChartBackground: appTheme.DarkIndigo + "BB",
155            loadingAnimationBackground: appTheme.DarkIndigo + "BB",
156        },
157        yAxisOptions: {
158            isInnerAxis: true,
159            labelStyle: {
160                fontSize: 12,
161                color: appTheme.ForegroundColor,
162            },
163            axisBorder: {
164                borderRight: 1,
165                color: appTheme.ForegroundColor + "77",
166            },
167            majorTickLineStyle: {
168                color: appTheme.ForegroundColor,
169                tickSize: 6,
170                strokeThickness: 1,
171            },
172            minorTickLineStyle: {
173                color: appTheme.ForegroundColor,
174                tickSize: 3,
175                strokeThickness: 1,
176            },
177        },
178        colorMap: {
179            minimum: -30,
180            maximum: 0,
181            gradientStops: [
182                { offset: 1, color: appTheme.VividPink },
183                { offset: 0.9, color: appTheme.VividOrange },
184                { offset: 0.7, color: appTheme.MutedRed },
185                { offset: 0.5, color: appTheme.VividGreen },
186                { offset: 0.3, color: appTheme.VividSkyBlue },
187                { offset: 0.15, color: appTheme.Indigo },
188                { offset: 0, color: appTheme.DarkIndigo },
189            ],
190        },
191    });
192
193    heatmapLegend.innerSciChartSurface.sciChartSurface.title = "Power (dB)";
194    heatmapLegend.innerSciChartSurface.sciChartSurface.padding.top = 0;
195    heatmapLegend.innerSciChartSurface.sciChartSurface.titleStyle = { fontSize: 12, color: appTheme.ForegroundColor };
196
197    return { sciChartSurface: heatmapLegend.innerSciChartSurface.sciChartSurface };
198};
199

Angular 3D Point Line Chart Example

Overview

This example demonstrates how to integrate SciChart.js into an Angular standalone application to render an advanced 3D point-line chart. It visualizes spectral data transformed through a Fourier algorithm and applies dynamic metadata for color and scaling. The implementation leverages the scichart-angular package to initialize and render chart components using Angular’s standalone component architecture.

Technical Implementation

The chart is configured using the initChart callbacks provided by the Angular components. In the drawExample function, a SciChart3DSurface is created with a specified world dimension and camera settings which are enhanced by adding 3D modifiers such as OrbitModifier3D and MouseWheelZoomModifier3D. Spectral data is generated dynamically and processed using a Fourier transform, while a helper function formats the metadata so that each point is rendered with the correct color and scale as outlined in the Point Line 3D Chart Documentation. The use of a WebAssembly context (wasmContext) ensures that the rendering is optimized for high performance, following guidelines available in Memory Best Practices.

Features and Capabilities

The example provides advanced features including real-time spectral data visualization and sophisticated 3D navigation. It supports multiple interactive camera controls and the ability to reset the camera view using advanced modifiers such as ResetCamera3DModifier. A separate heatmap legend is also rendered alongside the main chart to provide additional context, showcasing the capability for layered chart designs and dynamic metadata styling.

Integration and Best Practices

Developers can adopt this example as a blueprint for integrating SciChart.js in Angular environments. The utilization of Angular’s standalone components, along with the initChart callback mechanism, allows for clean and modular chart initialization. By following the practices detailed in Getting Started with SciChart JS, and effectively using 3D camera controls, developers can achieve efficient rendering and optimal user experience. This example reinforces the importance of reusing the WebAssembly context for performance optimizations and demonstrates best practices in building interactive, high-performance 3D charts in Angular.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.