Angular Polar Signals Intelligence Dashboard

Creates a Angular Polar Signals Intelligence Dashboard using SciChart.js, with the following features: DataLabels, Rounded corners, Gradient-palette fill, startup animations.

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    PolarMouseWheelZoomModifier,
3    PolarZoomExtentsModifier,
4    PolarPanModifier,
5    PolarNumericAxis,
6    SciChartPolarSurface,
7    EPolarAxisMode,
8    NumberRange,
9    EAxisAlignment,
10    EPolarLabelMode,
11    HeatmapColorMap,
12    UniformHeatmapDataSeries,
13    PolarUniformHeatmapRenderableSeries,
14    HeatmapLegend,
15    Thickness,
16} from "scichart";
17import { appTheme } from "../../../theme";
18
19const COLOR_MAP = new HeatmapColorMap({
20    minimum: 0,
21    maximum: 1,
22    gradientStops: [
23        { offset: 0, color: appTheme.VividPink },
24        { offset: 0.2, color: appTheme.VividOrange },
25        { offset: 0.4, color: appTheme.MutedRed },
26        { offset: 0.6, color: appTheme.VividSkyBlue },
27        { offset: 0.8, color: appTheme.Indigo },
28        { offset: 1, color: appTheme.DarkIndigo },
29    ],
30});
31
32/******************************************************************************
33 * 1) Simple seeded RNG (LCG) + Perlin Noise implementation
34 ******************************************************************************/
35class LCG {
36    private state: number;
37    constructor(seed: number) { this.state = seed & 0xffffffff; }
38    next() {
39        this.state = (1664525 * this.state + 1013904223) & 0xffffffff;
40        return this.state / 0x100000000;
41    }
42}
43
44class Perlin2D {
45    private perm: Uint8Array;
46    constructor(seed: number) {
47        const rng = new LCG(seed);
48        // build and shuffle perm[0..255]
49        this.perm = new Uint8Array(512);
50        const p = new Uint8Array(256);
51        for (let i = 0; i < 256; i++) {
52            p[i] = i;
53        }
54        for (let i = 255; i > 0; i--) {
55            const j = Math.floor(rng.next() * (i + 1));
56            [p[i], p[j]] = [p[j], p[i]];
57        }
58        // duplicate
59        for (let i = 0; i < 512; i++) {
60            this.perm[i] = p[i & 255];
61        }
62    }
63    private fade(t: number) { 
64        return t * t * t * (t * (t * 6 - 15) + 10); 
65    }
66    private lerp(a: number, b: number, t: number) { 
67        return a + t * (b - a); 
68    }
69    private grad(hash: number, x: number, y: number) {
70        // 8 possible gradients
71        const h = hash & 7;
72        const u = h < 4 ? x : y;
73        const v = h < 4 ? y : x;
74        return ((h & 1) ? -u : u) + ((h & 2) ? -2 * v : 2 * v);
75    }
76    public noise(x: number, y: number) {
77        const X = Math.floor(x) & 255;
78        const Y = Math.floor(y) & 255;
79        const xf = x - Math.floor(x);
80        const yf = y - Math.floor(y);
81        const u = this.fade(xf);
82        const v = this.fade(yf);
83    
84        const aa = this.perm[X + this.perm[Y]] 
85        const ab = this.perm[X + this.perm[Y+1]] 
86        const ba = this.perm[X +1 + this.perm[Y]] 
87        const bb = this.perm[X +1 + this.perm[Y+1]]
88    
89        const x1 = this.lerp(
90            this.grad(aa, xf, yf),
91            this.grad(ba, xf - 1, yf), 
92            u
93        );
94        const x2 = this.lerp(
95            this.grad(ab, xf, yf - 1),
96            this.grad(bb, xf - 1, yf - 1),
97            u
98        );
99        // normalize to [0, 1]
100        return (this.lerp(x1, x2, v) + 1) * 0.5;
101    }
102}
103
104const W = 355;
105const H = 100;
106const perlin = new Perlin2D(1999); // seed
107
108const heatmap: Array<number[]> = new Array(H);
109for (let i = 0; i < H; i++) {
110    heatmap[i] = new Array(W);
111}
112
113// instead of x*0.02 … use circular coords:
114const ANGULAR_SCALE = 0.3;   // tweak to stretch/shrink features around the circle
115const DEPTH_SCALE = 0.02;  // your existing “forward” scale
116const TWO_PI = Math.PI * 2;
117
118function fillInitial() {
119    for (let y = 0; y < H; y++) {
120        const depth = y * DEPTH_SCALE;
121        for (let i = 0; i < W; i++) {
122            const theta = (i / W) * TWO_PI;
123            const nx = Math.cos(theta) * ANGULAR_SCALE;
124            const ny = Math.sin(theta) * ANGULAR_SCALE;
125            heatmap[y][i] = perlin.noise(nx + depth, ny + depth);
126        }
127    }
128}
129
130export const drawExample = async (rootElement: string | HTMLDivElement) => {
131    const { sciChartSurface, wasmContext } = await SciChartPolarSurface.create(rootElement, {
132        theme: appTheme.SciChartJsTheme,
133        padding: new Thickness(0, 60, 0, 0),
134    });
135
136    const radialAxis = new PolarNumericAxis(wasmContext, {
137        polarAxisMode: EPolarAxisMode.Radial,
138        axisAlignment: EAxisAlignment.Right,
139        visibleRangeLimit: new NumberRange(0, H),
140        drawMajorTickLines: false,
141        drawMinorTickLines: false,
142        labelPrecision: 0,
143        labelStyle: {
144            color: "white"
145        },
146        startAngle: Math.PI / 2, // start at 12 o'clock
147    });
148    sciChartSurface.yAxes.add(radialAxis);
149
150    const angularAxis = new PolarNumericAxis(wasmContext, {
151        polarAxisMode: EPolarAxisMode.Angular,
152        axisAlignment: EAxisAlignment.Top,
153        flippedCoordinates: true,
154        drawMajorTickLines: false,
155        drawMinorTickLines: false,
156        labelPrecision: 0,
157        labelStyle: {
158            color: "white"
159        },
160        totalAngle: Math.PI * 2, // full circle
161        startAngle: Math.PI / 2, // start at 12 o'clock
162    });
163    sciChartSurface.xAxes.add(angularAxis);
164
165    // 3.3 Prepare & define dataSeries
166    fillInitial();
167    const dataSeries = new UniformHeatmapDataSeries(wasmContext, {
168        xStart: 0,
169        xStep: 1,
170        yStart: 0,
171        yStep: 1,
172        zValues: heatmap,
173    });
174
175    const heatmapSeries = new PolarUniformHeatmapRenderableSeries(wasmContext, {
176        dataSeries,
177        colorMap: COLOR_MAP,
178        stroke: "white",
179        strokeThickness: 2,
180    });
181    sciChartSurface.renderableSeries.add(heatmapSeries);
182
183    sciChartSurface.chartModifiers.add(
184        new PolarPanModifier(),
185        new PolarZoomExtentsModifier(),
186        new PolarMouseWheelZoomModifier()
187    );
188
189    // 3.4 Animate
190    let vY = H; // virtual y‐coordinate of next row
191    const speed = 1; // rows per frame
192
193    const addNewRow = () => {
194        const row = new Array<number>(W);
195        const depth = vY * DEPTH_SCALE;
196    
197        for (let i = 0; i < W; i++) {
198            const theta = (i / W) * TWO_PI;
199            const nx = Math.cos(theta) * ANGULAR_SCALE;
200            const ny = Math.sin(theta) * ANGULAR_SCALE;
201            row[i]= perlin.noise(nx + depth, ny + depth);
202        }
203        vY += speed;
204    
205        heatmap.pop();
206        heatmap.unshift(row);
207        dataSeries.setZValues(heatmap);
208    }
209
210    const changeHeatmapFully = () => {
211        for (let y = 0; y < H; y++) {
212            const depth = y * DEPTH_SCALE;
213            for (let i = 0; i < W; i++) {
214                const theta = (i / W) * TWO_PI;
215                const nx = Math.cos(theta) * ANGULAR_SCALE;
216                const ny = Math.sin(theta) * ANGULAR_SCALE;
217                heatmap[y][i] = perlin.noise(nx + depth, ny + depth + Math.random() * 0.1);
218            }
219        }
220        dataSeries.setZValues(heatmap);
221    }
222
223    const animate = () => {
224        addNewRow();
225        // changeHeatmapFully();
226    
227        requestAnimationFrame(animate);
228    };
229
230    requestAnimationFrame(animate);
231
232    return { sciChartSurface, wasmContext };
233};
234
235export const drawHeatmapLegend = async (rootElement: string | HTMLDivElement) => {
236    const { heatmapLegend } = await HeatmapLegend.create(rootElement, {
237        theme: {
238            ...appTheme.SciChartJsTheme,
239            sciChartBackground: appTheme.DarkIndigo + "BB",
240            loadingAnimationBackground: appTheme.DarkIndigo + "BB",
241        },
242        yAxisOptions: {
243            isInnerAxis: true,
244            labelStyle: { fontSize: 14, color: appTheme.ForegroundColor },
245            axisBorder: { borderRight: 2, color: appTheme.ForegroundColor },
246            majorTickLineStyle: {
247                color: appTheme.ForegroundColor,
248                tickSize: 8,
249                strokeThickness: 2,
250            },
251            minorTickLineStyle: {
252                color: appTheme.ForegroundColor,
253                tickSize: 4,
254                strokeThickness: 1,
255            },
256        },
257        colorMap: COLOR_MAP
258    });
259
260    return { sciChartSurface: heatmapLegend.innerSciChartSurface.sciChartSurface };
261};
SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.