Phasor Diagram Chart Example

Demonstrates how to create an Angular Phasor Diagram chart in SciChart.js, combining a Cartesian surface containing fifo Mountains, with a Polar subsurface with annotations depicting vectors.

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    PolarNumericAxis,
3    SciChartSurface,
4    NumberRange,
5    EDraggingGripPoint,
6    EAxisAlignment,
7    EPolarAxisMode,
8    LineArrowAnnotation,
9    XyDataSeries,
10    NumericAxis,
11    ESciChartSurfaceType,
12    LineAnnotation,
13    Rect,
14    SciChartPolarSubSurface,
15    RadianLabelProvider,
16    SplineMountainRenderableSeries,
17    EAutoRange,
18    PolarArcAnnotation,
19    Thickness,
20    Logger,
21} from "scichart";
22import { appTheme } from "../../../theme";
23
24function calculateDiameterProjection(x: number, y: number) {
25    return {
26        x2: x % Math.PI < Math.PI ? Math.PI / 2 : (Math.PI * 3) / 2,
27        y2: y * Math.sin(x),
28    };
29}
30
31export async function drawExample(rootElement: string | HTMLDivElement) {
32    const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
33        theme: appTheme.SciChartJsTheme,
34    });
35    // Add master axes
36    const xAxis = new NumericAxis(wasmContext, {
37        labelStyle: {
38            color: "white",
39            padding: new Thickness(0, 0, 8, 0), // add a bit of bottom padding to labels
40        },
41        growBy: new NumberRange(0, 0.34), // start the mountain drawing at around 0.25 of the chart (from polar center)
42        isInnerAxis: true, // to not push chart upwards and overcomplicate centering calculations
43        autoRange: EAutoRange.Always, // allow change of xAxis range on fifo changes
44        flippedCoordinates: true, // increment from right to left
45
46        autoTicks: false,
47        majorDelta: Math.PI / 4,
48        labelProvider: new RadianLabelProvider(),
49    });
50    sciChartSurface.xAxes.add(xAxis);
51
52    const yAxis = new NumericAxis(wasmContext, {
53        axisAlignment: EAxisAlignment.Right,
54        autoTicks: false,
55        majorDelta: 0.5,
56        labelStyle: { color: "#FFFFFF" },
57    });
58    sciChartSurface.yAxes.add(yAxis);
59
60    // calculate yAxis range based on the aspect ratio of the chart (to sync the yRange with the polar chart diameter)
61    const aspectRatio = sciChartSurface.viewRect.height / sciChartSurface.viewRect.width; // likely a value like 0.66
62    const padding = aspectRatio * 2;
63
64    yAxis.visibleRange = new NumberRange(
65        -2 * padding, // -2 is the min value that `sumVector` can reach
66        2 * padding // same here, but positive
67    );
68
69    // add the 3 cartesian mountain projections of the polar vectors
70    const vector1Mountain = new SplineMountainRenderableSeries(wasmContext, {
71        dataSeries: new XyDataSeries(wasmContext, {
72            xValues: [],
73            yValues: [],
74            fifoCapacity: 300, // allow 300 points at once until sweeping happens
75        }),
76        fill: appTheme.VividRed + "22",
77        stroke: appTheme.VividRed,
78        strokeThickness: 3,
79    });
80
81    const vector2Mountain = new SplineMountainRenderableSeries(wasmContext, {
82        dataSeries: new XyDataSeries(wasmContext, {
83            xValues: [],
84            yValues: [],
85            fifoCapacity: 300,
86        }),
87        fill: appTheme.VividGreen + "22",
88        stroke: appTheme.VividGreen,
89        strokeThickness: 3,
90    });
91
92    const vectorSumMountain = new SplineMountainRenderableSeries(wasmContext, {
93        dataSeries: new XyDataSeries(wasmContext, {
94            xValues: [],
95            yValues: [],
96            fifoCapacity: 300,
97        }),
98        fill: "#DDDDDD22",
99        stroke: "#AAAAAA",
100        strokeThickness: 3,
101    });
102
103    sciChartSurface.renderableSeries.add(vectorSumMountain, vector1Mountain, vector2Mountain);
104
105    // Add polar subchart on the left half of the screen
106    const polarSub = SciChartPolarSubSurface.createSubSurface(sciChartSurface, {
107        surfaceType: ESciChartSurfaceType.Polar2D,
108        position: new Rect(0, 0, 0.5, 1), // left half of the screen
109    });
110
111    // add angular x / y axes
112    const angularAxis = new PolarNumericAxis(wasmContext, {
113        polarAxisMode: EPolarAxisMode.Angular,
114        axisAlignment: EAxisAlignment.Top,
115        isInnerAxis: true,
116        visibleRange: new NumberRange(0, Math.PI * 2), // full circle
117        labelStyle: { color: "#FFFFFF" },
118
119        majorGridLineStyle: {
120            color: "#FFFFFF44",
121            strokeThickness: 1,
122        },
123        drawMajorTickLines: false,
124        drawMinorTickLines: false,
125        drawMinorGridLines: false,
126
127        autoTicks: false,
128        majorDelta: Math.PI / 2,
129        labelProvider: new RadianLabelProvider(),
130    });
131    polarSub.xAxes.add(angularAxis);
132
133    const radialAxis = new PolarNumericAxis(wasmContext, {
134        polarAxisMode: EPolarAxisMode.Radial,
135        axisAlignment: EAxisAlignment.Left,
136        labelPrecision: 0,
137        visibleRange: new NumberRange(0, 2), // max value of `vectorSum`, since our 2 vectors are normalized to [0, 1]
138
139        drawMinorGridLines: false,
140        autoTicks: false,
141        majorDelta: 1,
142        startAngle: Math.PI / 2, // Start at the top (12 o'clock)
143    });
144    polarSub.yAxes.add(radialAxis);
145
146    // Now we add pointer annotations (vectors, projections, sum vector, angle arc)
147
148    // 1. Vector1 (red one)
149    const vector1 = new LineArrowAnnotation({
150        x1: 0,
151        y1: 0,
152        x2: 2.8,
153        y2: 1,
154        strokeThickness: 3,
155        stroke: appTheme.VividRed,
156        arrowStyle: {
157            headLength: 10,
158            headWidth: 10,
159            headDepth: 0.6,
160        },
161        isEditable: true,
162        dragPoints: [EDraggingGripPoint.x2y2],
163        selectionBoxStroke: appTheme.VividRed + "44",
164    });
165    const vector1ToDiameter = new LineAnnotation({
166        // (optional) - projection of vector1 to the diameter line
167        stroke: appTheme.VividRed,
168        strokeThickness: 3,
169    });
170
171    // 2. Vector2 (green one)
172    const vector2 = new LineArrowAnnotation({
173        x1: 0,
174        y1: 0,
175        x2: 1,
176        y2: 0.8,
177        strokeThickness: 3,
178        stroke: appTheme.VividGreen,
179        arrowStyle: {
180            headLength: 10,
181            headWidth: 10,
182            headDepth: 0.6,
183        },
184        isEditable: true,
185        dragPoints: [EDraggingGripPoint.x2y2],
186        selectionBoxStroke: appTheme.VividGreen + "44",
187    });
188    const vector2ToDiameter = new LineAnnotation({
189        // (optional)
190        stroke: appTheme.VividGreen,
191        strokeThickness: 3,
192    });
193
194    // 3. VectorSum (white one)
195    const vectorSum = new LineArrowAnnotation({
196        x1: 0,
197        y1: 0,
198        strokeThickness: 3,
199        stroke: "white",
200        arrowStyle: {
201            headLength: 10,
202            headWidth: 10,
203            headDepth: 0.6,
204        },
205        isEditable: false,
206    });
207    const vectorSumToDiameter = new LineAnnotation({
208        // (optional)
209        stroke: "#AAAAAA",
210        strokeThickness: 3,
211    });
212
213    // (optional) draw lines from x2y2 of `vector1` and `vector2` to the x2y2 of `vectorSum`
214    const vector1ToSumLine = new LineAnnotation({
215        stroke: "gray",
216        strokeThickness: 2,
217    });
218    const vector2ToSumLine = new LineAnnotation({
219        stroke: "gray",
220        strokeThickness: 2,
221    });
222
223    // 4. Arc annotation acting as a degree indicator between `vector1` and `vector2`
224    const vectorArc = new PolarArcAnnotation({
225        y2: 0, // startRadius is always 0, the rest (y1, x2, x1) will be calculated in `updatePolarAnnotations()`
226        stroke: "#FFFFFF",
227        fill: "#FFFFFF22",
228    });
229
230    // the appending order of the annotation is important, as the `vectorArc` will be rendered first,
231    // and so below all the other annotations, and so on
232    polarSub.annotations.add(
233        vectorArc,
234        vector1ToSumLine,
235        vector2ToSumLine,
236        vector1ToDiameter,
237        vector2ToDiameter,
238        vectorSumToDiameter,
239        vector1,
240        vector2,
241        vectorSum
242    );
243
244    function updatePolarAnnotations() {
245        if (!vector1 || !vector2) {
246            return;
247        }
248
249        // Normalize to (0, 1)
250        vector1.y2 = Math.max(0, Math.min(vector1.y2, 1));
251        vector2.y2 = Math.max(0, Math.min(vector2.y2, 1));
252
253        const v1x = vector1.y2 * Math.cos(vector1.x2); // x component of vector1
254        const v1y = vector1.y2 * Math.sin(vector1.x2); // y component of vector1
255
256        const v2x = vector2.y2 * Math.cos(vector2.x2); // x component of vector2
257        const v2y = vector2.y2 * Math.sin(vector2.x2); // y component of vector2
258
259        const sumX = v1x + v2x;
260        const sumY = v1y + v2y;
261
262        // Convert back to polar coordinates
263        vectorSum.x2 = Math.atan2(sumY, sumX); // angle
264        vectorSum.y2 = Math.sqrt(sumX * sumX + sumY * sumY); // magnitude
265
266        // Update perpendicular projections
267        // vector1ToDiameter
268        vector1ToDiameter.x1 = vector1.x2;
269        vector1ToDiameter.y1 = vector1.y2;
270
271        const v1Projection = calculateDiameterProjection(vector1.x2, vector1.y2);
272        vector1ToDiameter.x2 = v1Projection.x2;
273        vector1ToDiameter.y2 = v1Projection.y2;
274
275        // vector2ToDiameter
276        vector2ToDiameter.x1 = vector2.x2;
277        vector2ToDiameter.y1 = vector2.y2;
278
279        const v2Projection = calculateDiameterProjection(vector2.x2, vector2.y2);
280        vector2ToDiameter.x2 = v2Projection.x2;
281        vector2ToDiameter.y2 = v2Projection.y2;
282
283        // vectorSumToDiameter
284        vectorSumToDiameter.x1 = vectorSum.x2;
285        vectorSumToDiameter.y1 = vectorSum.y2;
286        const sumProjection = calculateDiameterProjection(vectorSum.x2, vectorSum.y2);
287        vectorSumToDiameter.x2 = sumProjection.x2;
288        vectorSumToDiameter.y2 = sumProjection.y2;
289
290        // Update lines to sum vector
291        vector1ToSumLine.x1 = vector1.x2;
292        vector1ToSumLine.y1 = vector1.y2;
293        vector1ToSumLine.x2 = vectorSum.x2;
294        vector1ToSumLine.y2 = vectorSum.y2;
295
296        vector2ToSumLine.x1 = vector2.x2;
297        vector2ToSumLine.y1 = vector2.y2;
298        vector2ToSumLine.x2 = vectorSum.x2;
299        vector2ToSumLine.y2 = vectorSum.y2;
300
301        // Update the arc annotation from vector1 to vector2, ensuring the angle is within [0, PI]
302        // We always want the shortest arc, so we calculate the angle difference and adjust accordingly
303        const angle1 = vector1.x2 % (2 * Math.PI);
304        const angle2 = vector2.x2 % (2 * Math.PI);
305
306        let delta = (angle2 - angle1 + 2 * Math.PI) % (2 * Math.PI);
307
308        if (delta > Math.PI) {
309            // The arc goes the long way around with the angle > 180 degrees, so we reverse direction
310            vectorArc.x1 = angle2;
311            vectorArc.x2 = angle1;
312        } else {
313            vectorArc.x1 = angle1;
314            vectorArc.x2 = angle2;
315        }
316
317        // optionally keep height of the angle arc smaller than smallest vector
318        vectorArc.y1 = Math.min(vector1.y2, vector2.y2) / 2;
319
320        // optionally add value to the mountains if the animation is not running
321        if (!isAnimating) {
322            updateCartesianProjection();
323        }
324    }
325    updatePolarAnnotations(); // call once to init sum, projections and arc
326
327    // On change of any of the 2 vectors, update the annotations
328    vector1.dragDelta.subscribe((relativeCoords) => updatePolarAnnotations());
329    vector2.dragDelta.subscribe((relativeCoords) => updatePolarAnnotations());
330
331    function updateCartesianProjection() {
332        // calculate with the height and angle, the projection of the vectors from 0-2
333        const vector1Projection = calculateDiameterProjection(vector1.x2, vector1.y2);
334        const vector2Projection = calculateDiameterProjection(vector2.x2, vector2.y2);
335        const vectorSumProjection = calculateDiameterProjection(vectorSum.x2, vectorSum.y2);
336
337        if (!vector1Mountain.dataSeries || !vector2Mountain.dataSeries || !vectorSumMountain.dataSeries) {
338            return; // Ensure dataSeries are initialized
339        }
340        const lastXIndex = vector1Mountain.dataSeries.count() - 1;
341        const pastX = vector1Mountain.dataSeries.getNativeXValues().get(lastXIndex) ?? 0;
342        const newX = pastX + Math.PI / 180; // Increment by 1 degree in radians
343
344        // Update the data series of the mountains
345        (vector1Mountain.dataSeries as XyDataSeries).append(newX, vector1Projection.y2);
346        (vector2Mountain.dataSeries as XyDataSeries).append(newX, vector2Projection.y2);
347        (vectorSumMountain.dataSeries as XyDataSeries).append(newX, vectorSumProjection.y2);
348    }
349
350    // Rotate vectors by a small angle
351    function rotateVectors() {
352        const angleIncrement = Math.PI / 180; // 1 degree in radians
353        vector1.x2 += angleIncrement;
354        vector2.x2 += angleIncrement;
355        vector1.dragDelta.raiseEvent(); // trigger annotation update (see lines 305-306)
356    }
357
358    // Animation controls
359    let isAnimating = true;
360
361    function animate() {
362        if (!isAnimating) return;
363
364        updateCartesianProjection();
365        rotateVectors();
366
367        requestAnimationFrame(animate);
368    }
369
370    animate(); // call once to start the animation
371
372    return {
373        sciChartSurface,
374        wasmContext,
375        controls: {
376            startAnimation: () => {
377                if (isAnimating) return; // Prevent multiple animations (speeding things up uncontrollably)
378                isAnimating = true;
379                animate();
380            },
381            stopAnimation: () => {
382                isAnimating = false;
383            },
384        },
385    };
386}
387

Phasor Diagram Chart - Angular

Overview

This Angular example showcases a Phasor Diagram Chart using SciChart.js, demonstrating vector visualization through polar coordinates and cartesian projections. The implementation uses the scichart-angular package for Angular integration.

Technical Implementation

The chart is initialized via drawExample() in the component template using <scichart-angular>. The polar chart is created with SciChartPolarSubSurface, while vector interactions are handled through LineArrowAnnotation with EDraggingGripPoint configurations.

Features and Capabilities

The example features real-time vector operations with polar-to-cartesian projections, animated vector rotation, and interactive editing. The PolarArcAnnotation visualizes angles between vectors, while SplineMountainRenderableSeries shows cartesian projections.

Integration and Best Practices

The implementation follows Angular best practices with standalone components and proper resource cleanup. Performance is optimized through WebAssembly rendering and efficient data updates using Angular's change detection strategy.

angular Chart Examples & Demos

See Also: Scientific & Medical Charts (10 Demos)

Angular Vital Signs ECG/EKG Medical Demo | SciChart.js

Angular Vital Signs ECG/EKG Medical Demo

In this example we are simulating four channels of data showing that SciChart.js can be used to draw real-time ECG/EKG charts and graphs to monitor heart reate, body temperature, blood pressure, pulse rate, SPO2 blood oxygen, volumetric flow and more.

Angular Chart with Logarithmic Axis Example | SciChart.js

Angular Chart with Logarithmic Axis Example

Demonstrates Logarithmic Axis on a Angular Chart using SciChart.js. SciChart supports logarithmic axis with scientific or engineering notation and positive and negative values

LiDAR 3D Point Cloud of Geospatial Data | SciChart.js

LiDAR 3D Point Cloud of Geospatial Data

Demonstrating the capability of SciChart.js to create JavaScript 3D Point Cloud charts and visualize LiDAR data from the UK Defra Survey.

Angular Chart with Vertically Stacked Axes | SciChart.js

Angular Chart with Vertically Stacked Axes

Demonstrates Vertically Stacked Axes on a Angular Chart using SciChart.js, allowing data to overlap

Realtime Audio Spectrum Analyzer Chart | SciChart.js Demo

Realtime Audio Spectrum Analyzer Chart Example

See the frequency of recordings with the Angular audio spectrum analyzer example from SciChart. This real-time audio visualizer demo uses a Fourier Transform.

Realtime Audio Analyzer Bars Demo | SciChart.js Demo

Realtime Audio Analyzer Bars Demo

Demonstrating the capability of SciChart.js to create a JavaScript Audio Analyzer Bars and visualize the Fourier-Transform of an audio waveform in realtime.

Interactive Waterfall Chart | Angular Charts | SciChart.js

Interactive Waterfall Spectral Chart

Demonstrates how to create a Waterfall chart in SciChart.js, showing chromotragraphy data with interactive selection of points.

NEW!
Angular Correlation Plot | Angular Charts | SciChart.js Demo

Angular Correlation Plot

Create Angular Correlation Plot with high performance SciChart.js. Easily render pre-defined point types. Supports custom shapes. Get your free trial now.

NEW!
Angular Semiconductors Dashboard | JavaScript Charts | SciChart.js

Angular Semiconductors Dashboard

Angular **Semiconductors Dashboard** using SciChart.js, by leveraging the **FastRectangleRenderableSeries**, and its `customTextureOptions` property to have a custom tiling texture fill.

NEW!
Angular Wafer Analysis Chart | JavaScript Charts | SciChart.js

Angular Wafer Analysis Chart

Angular **Wafer Analysis Chart** using SciChart.js, by leveraging the **FastRectangleRenderableSeries**, and crossfilter to enable live filtering.

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