Skip to content

Commit ede278b

Browse files
author
Niilo Keinänen
committed
Example of using LightningChart JS in Angular project
1 parent edddc64 commit ede278b

18 files changed

+306
-37
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# See http://help.github.com/ignore-files/ for more about ignoring files.
22

3+
node_modules
4+
35
# Compiled output
46
/dist
57
/tmp

.npmrc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
registry=https://registry.npmjs.org
2+
@arction:registry=https://registry.npmjs.org

README.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
# LcjsNgExample
1+
# LightningChart JS x Angular example
22

3-
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.1.
3+
- Project generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.1
4+
- Server side rendering and static site generation enabled.
5+
- Run adjacent test server with `cd test-server && npm i && node index`
6+
- Run with `ng serve` (requires globally installed Angular CLI), and open at http://localhost:4200.
7+
8+
For more information, please see [Angular x LightningChart JS documentation](https://lightningchart.com/js-charts/docs/frameworks/angular)
9+
10+
---
11+
12+
All below documentation was automatically generated by Angular CLI:
413

514
## Development server
615

package-lock.json

+22-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@angular/platform-server": "^17.2.0",
2222
"@angular/router": "^17.2.0",
2323
"@angular/ssr": "^17.2.1",
24+
"@arction/lcjs": "^5.1.1",
2425
"express": "^4.18.2",
2526
"rxjs": "~7.8.0",
2627
"tslib": "^2.3.0",
@@ -41,4 +42,4 @@
4142
"karma-jasmine-html-reporter": "~2.1.0",
4243
"typescript": "~5.3.2"
4344
}
44-
}
45+
}

src/app/app.component.html

+24-30
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@
5151
line-height: 100%;
5252
letter-spacing: -0.125rem;
5353
margin: 0;
54-
font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
55-
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
56-
"Segoe UI Symbol";
54+
font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI",
55+
Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
56+
"Segoe UI Emoji", "Segoe UI Symbol";
5757
}
5858

5959
p {
@@ -226,37 +226,32 @@
226226
</defs>
227227
</svg>
228228
<h1>Hello, {{ title }}</h1>
229-
<p>Congratulations! Your app is running. 🎉</p>
229+
<app-chart></app-chart>
230230
</div>
231231
<div class="divider" role="separator" aria-label="Divider"></div>
232232
<div class="right-side">
233233
<div class="pill-group">
234-
@for (item of [
235-
{ title: 'Explore the Docs', link: 'https://angular.dev' },
236-
{ title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
237-
{ title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
238-
{ title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
239-
{ title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
240-
]; track item.title) {
241-
<a
242-
class="pill"
243-
[href]="item.link"
244-
target="_blank"
245-
rel="noopener"
234+
@for (item of [ { title: 'Explore the Docs', link: 'https://angular.dev'
235+
}, { title: 'Learn with Tutorials', link:
236+
'https://angular.dev/tutorials' }, { title: 'CLI Docs', link:
237+
'https://angular.dev/tools/cli' }, { title: 'Angular Language Service',
238+
link: 'https://angular.dev/tools/language-service' }, { title: 'Angular
239+
DevTools', link: 'https://angular.dev/tools/devtools' }, ]; track
240+
item.title) {
241+
<a class="pill" [href]="item.link" target="_blank" rel="noopener">
242+
<span>{{ item.title }}</span>
243+
<svg
244+
xmlns="http://www.w3.org/2000/svg"
245+
height="14"
246+
viewBox="0 -960 960 960"
247+
width="14"
248+
fill="currentColor"
246249
>
247-
<span>{{ item.title }}</span>
248-
<svg
249-
xmlns="http://www.w3.org/2000/svg"
250-
height="14"
251-
viewBox="0 -960 960 960"
252-
width="14"
253-
fill="currentColor"
254-
>
255-
<path
256-
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
257-
/>
258-
</svg>
259-
</a>
250+
<path
251+
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
252+
/>
253+
</svg>
254+
</a>
260255
}
261256
</div>
262257
<div class="social-links">
@@ -332,5 +327,4 @@ <h1>Hello, {{ title }}</h1>
332327
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * * -->
333328
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
334329

335-
336330
<router-outlet />

src/app/app.component.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Component } from '@angular/core';
22
import { RouterOutlet } from '@angular/router';
3+
import { ChartComponent } from './components/chart/chart.component';
34

45
@Component({
56
selector: 'app-root',
67
standalone: true,
7-
imports: [RouterOutlet],
8+
imports: [RouterOutlet, ChartComponent],
89
templateUrl: './app.component.html',
9-
styleUrl: './app.component.css'
10+
styleUrl: './app.component.css',
1011
})
1112
export class AppComponent {
1213
title = 'lcjs-ng-example';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
div {
2+
width: 100%;
3+
height: 40vh;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div [id]="this.containerId"></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { ChartComponent } from './chart.component';
4+
5+
describe('ChartComponent', () => {
6+
let component: ChartComponent;
7+
let fixture: ComponentFixture<ChartComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [ChartComponent]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(ChartComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Component, OnDestroy, afterNextRender } from '@angular/core';
2+
import { AxisScrollStrategies, Themes } from '@arction/lcjs';
3+
import { DataService } from '../../services/data/data.service';
4+
import { LcContextService } from '../../services/lc-context/lc-context.service';
5+
6+
@Component({
7+
selector: 'app-chart',
8+
standalone: true,
9+
imports: [],
10+
templateUrl: './chart.component.html',
11+
styleUrl: './chart.component.css',
12+
})
13+
export class ChartComponent implements OnDestroy {
14+
containerId: string = '';
15+
destroyLC?: () => unknown;
16+
17+
constructor(dataService: DataService, lcContextService: LcContextService) {
18+
// Generate random ID to us as the containerId for the chart and the target div id
19+
this.containerId = (Math.random() * 1_000_000_000).toFixed(0);
20+
21+
// Only in browser (not server side); initialize LightningChart JS components
22+
afterNextRender(() => {
23+
const lc = lcContextService.getLightningChartContext();
24+
const container = document.getElementById(
25+
this.containerId
26+
) as HTMLDivElement;
27+
const chart = lc.ChartXY({ container, theme: Themes.light });
28+
const axisX = chart
29+
.getDefaultAxisX()
30+
.setScrollStrategy(AxisScrollStrategies.progressive)
31+
.setDefaultInterval((state) => ({
32+
end: state.dataMax ?? 0,
33+
start: (state.dataMax ?? 0) - 100,
34+
stopAxisAfter: false,
35+
}));
36+
const areaSeries = chart
37+
.addPointLineAreaSeries({ dataPattern: 'ProgressiveX' })
38+
.setMaxSampleCount(10_000);
39+
40+
const dataObservable = dataService.connect();
41+
const dataSubscription = dataObservable.subscribe((value) => {
42+
areaSeries.appendSample({ y: value });
43+
});
44+
45+
// Cache function that destroys created LC resources
46+
this.destroyLC = () => {
47+
// 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
48+
chart.dispose();
49+
dataSubscription.unsubscribe();
50+
};
51+
});
52+
}
53+
54+
ngOnDestroy(): void {
55+
if (this.destroyLC) this.destroyLC();
56+
}
57+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { DataService } from './data.service';
4+
5+
describe('DataService', () => {
6+
let service: DataService;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
service = TestBed.inject(DataService);
11+
});
12+
13+
it('should be created', () => {
14+
expect(service).toBeTruthy();
15+
});
16+
});

src/app/services/data/data.service.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Injectable } from '@angular/core';
2+
import { Observable, Observer } from 'rxjs';
3+
4+
@Injectable({
5+
providedIn: 'root',
6+
})
7+
export class DataService {
8+
private observable?: Observable<number>;
9+
10+
constructor() {}
11+
12+
public connect(): Observable<number> {
13+
if (!this.observable) {
14+
this.observable = this.create();
15+
}
16+
return this.observable;
17+
}
18+
19+
private create(): Observable<number> {
20+
const ws = new WebSocket('ws://localhost:4201');
21+
ws.onopen = () => {
22+
console.log('WS connection successful');
23+
};
24+
const observable = new Observable((obs: Observer<number>) => {
25+
ws.onmessage = (msg) => {
26+
const value = Number(msg.data);
27+
obs.next(value);
28+
};
29+
ws.onerror = obs.error.bind(obs);
30+
ws.onclose = obs.complete.bind(obs);
31+
return ws.close.bind(ws);
32+
});
33+
return observable;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { LcContextService } from './lc-context.service';
4+
5+
describe('LcContextService', () => {
6+
let service: LcContextService;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
service = TestBed.inject(LcContextService);
11+
});
12+
13+
it('should be created', () => {
14+
expect(service).toBeTruthy();
15+
});
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Injectable } from '@angular/core';
2+
import { LightningChart, lightningChart } from '@arction/lcjs';
3+
4+
@Injectable({
5+
providedIn: 'root',
6+
})
7+
export class LcContextService {
8+
private lc?: LightningChart;
9+
10+
constructor() {}
11+
12+
public getLightningChartContext() {
13+
if (this.lc) return this.lc;
14+
this.lc = lightningChart({
15+
license:
16+
// Place your LCJS license key here
17+
// This should originate from a environment variable / secret. In development, license is assigned per developer, whereas in staging/production a deployment license is used.
18+
'',
19+
});
20+
return this.lc;
21+
}
22+
}

test-server/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { WebSocketServer } from "ws";
2+
3+
const wss = new WebSocketServer({ port: 4201 });
4+
5+
wss.on("connection", async (ws) => {
6+
let closed = false;
7+
ws.onclose = () => {
8+
closed = true;
9+
};
10+
while (!closed) {
11+
ws.send(Math.random() * 11);
12+
await new Promise((resolve) => setTimeout(resolve, 1000 / 60));
13+
}
14+
});

0 commit comments

Comments
 (0)