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

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.