Compare commits

...

10 Commits

Author SHA1 Message Date
11aedd600f 1.0.11 2025-07-27 16:00:57 +02:00
60d505935b Added moving average computation 2025-07-27 16:00:23 +02:00
0093eda1d6 1.0.9 2025-07-21 11:02:52 +02:00
c6c8277c49 Fixed url 2025-07-21 11:02:02 +02:00
864ed9a5d2 1.0.8 2025-07-20 13:27:31 +02:00
193d073f85 Updated name 2025-07-20 13:26:40 +02:00
474865ddb4 Added support for maxTimeDelta and dateFormat 2025-07-19 10:37:12 +02:00
ac387674a4 1.0.6 2025-07-15 17:44:20 +02:00
a703a042e1 Updated types definitions 2025-07-15 17:44:08 +02:00
c6e02087c8 Fixed types generation 2025-07-15 17:39:24 +02:00
8 changed files with 175 additions and 56 deletions

1
.gitignore vendored
View File

@@ -1,2 +1 @@
node_modules/ node_modules/
src/index.js

View File

@@ -1,4 +1,4 @@
# uChart # µChart
Lightweight, canvasbased charting library written in TypeScript, designed for realtime data streams and small bundle size. Lightweight, canvasbased charting library written in TypeScript, designed for realtime data streams and small bundle size.
@@ -6,11 +6,40 @@ Lightweight, canvasbased charting library written in TypeScript, designed for
## Features ## Features
- 💡 **Zero external rendering deps** plain `<canvas>` - 💡 **Zero external deps** plain `<canvas>`
-**Realtime friendly** redraws only whats needed -**Realtime friendly** redraws only whats needed
- 📦 **ESM ready** authored as native ES modules - 📦 **ESM ready** authored as native ES modules
- 📝 **TypeScript types** included (`*.d.ts`) - 📝 **TypeScript types** included (`*.d.ts`)
- 🎛️ Configurable axes, ticks, colors & cursor readouts - 🎛️ Configurable axes, ticks, colors, date formatting & cursor readouts
## Options
The `createChartElement(seriesList, options)` function supports the following options:
| Option | Type | Description |
|-------------------|--------------------------|----------------------------------------------------------------------------|
| `width` | `number` | Fixed chart width in pixels (optional, defaults to container width) |
| `height` | `number` | Fixed chart height in pixels (optional, defaults to container height) |
| `min` | `number` | Minimum Y-axis value (optional, auto-computed if not set) |
| `max` | `number` | Maximum Y-axis value (optional, auto-computed if not set) |
| `showYAxis` | `boolean` | Whether to render Y-axis ticks and labels |
| `yTicks` | `number` | Number of Y-axis ticks (default: `5`) |
| `backgroundColor` | `string` | Optional background fill color |
| `maxTimeDelta` | `number \| null` | Gap in ms that breaks line continuity (default: `600`) |
| `dateFormat` | `(ts: number) => string` | Custom formatter for the cursor timestamp label |
| `averageData` | `number` | Compute simple moving average of lasy n data to smooth out line (optional) |
```ts
// Example: format timestamp to full Czech date+time
dateFormat: (ts) => new Date(ts).toLocaleString('cs-CZ', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
```
## Installation ## Installation
@@ -28,8 +57,8 @@ npm install @meteolab/uchart
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>uChart Interactive Test</title> <title>uChart Interactive Test</title>
<style> <style>
#chartContainer{width:1000px;height:400px;margin:20px auto;background:#111} #chartContainer { width: 1000px; height: 400px; margin: 20px auto; background:#111; }
body{background:#222} body { background:#222; }
</style> </style>
</head> </head>
<body> <body>
@@ -39,33 +68,47 @@ npm install @meteolab/uchart
import { createChartElement } from './dist/uchart.js'; import { createChartElement } from './dist/uchart.js';
const container = document.getElementById('chartContainer'); const container = document.getElementById('chartContainer');
const chart = createChartElement([], { showYAxis:true, yTicks:5 }); const chart = createChartElement([], {
container.appendChild(chart.element); showYAxis: true,
yTicks: 5,
dateFormat: (ts) => new Date(ts).toLocaleString('cs-CZ', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
}); container.appendChild(chart.element);
let phase = 0; let phase = 0;
function sine(len, shift, amp, off, f=1){ function generateSineSeries(len, shift, amp, off, freq = 1){
const now=Date.now(), d=[]; const now = Date.now();
const data=[];
for(let i=0;i<len;i++){ for(let i=0;i<len;i++){
const t=now+i*10, x=i/len*2*Math.PI*f, y=Math.sin(x+shift)*amp+off; const t = now + i*10;
d.push([t,y]); const x = i/len*2*Math.PI*freq;
const y = Math.sin(x+shift)*amp + off;
data.push([t,y]);
} }
return d; return data;
} }
function update(){ function update(){
chart.setSeries([ chart.setSeries([
{ data: sine(1500, phase, 20, 10, 1), strokeColor:'#0f0' }, { data: generateSineSeries(1500, phase, 20, 10, 1), strokeColor:'#0f0' },
{ data: sine(1500, phase, 20, 10, 2), strokeColor:'#f00' }, { data: generateSineSeries(1500, phase, 20, 10, 2), strokeColor:'#f00' },
{ data: sine(1500, phase, 20, 10, 0.5), strokeColor:'#00f' } { data: generateSineSeries(1500, phase, 20, 10, 0.5), strokeColor:'#00f' }
]); ]);
phase += 0.01; phase += 0.01;
} }
update(); update()
setInterval(update, 50); setInterval(update, 50);
</script> </script>
</body> </body>
</html> </html>
``` ```
## Test ## Test

32
dist/index.d.ts vendored Normal file
View File

@@ -0,0 +1,32 @@
export type ChartSeries = {
data: [number, number][];
strokeColor?: string;
};
export declare function drawChart(ctx: CanvasRenderingContext2D, seriesList: ChartSeries[], opts: {
width: number;
height: number;
min?: number;
max?: number;
showYAxis?: boolean;
yTicks?: number;
backgroundColor?: string;
cursorX?: number | null;
maxTimeDelta?: number | null;
dateFormat?: (ts: number) => string;
}): void;
export declare function createChartElement(seriesList: ChartSeries[], opts: {
width?: number;
height?: number;
min?: number;
max?: number;
showYAxis?: boolean;
yTicks?: number;
backgroundColor?: string;
maxTimeDelta?: number | null;
dateFormat?: (ts: number) => string;
}): {
element: HTMLDivElement;
setSeries(series: ChartSeries[]): void;
setOptions(o: Partial<typeof opts>): void;
destroy(): void;
};

2
dist/uchart.js vendored
View File

@@ -1 +1 @@
function W(t,m,a){let{width:o,height:d,min:F,max:n,showYAxis:T=!1,yTicks:w=5,cursorX:l=null}=a,k=10,E=d-k-10,x=m.flatMap(s=>s.data.map(i=>i[1])),y=m.flatMap(s=>s.data.map(i=>i[0])),p=F!==void 0?F:Math.min(...x),P=n!==void 0?n:Math.max(...x),e=P-p||1,r=Math.min(...y),A=Math.max(...y)-r||1;if(t.clearRect(0,0,o,d),T){t.beginPath(),t.strokeStyle="#aaa",t.lineWidth=1,t.font="15px sans-serif",t.fillStyle=m[0]?.strokeColor||"#000";for(let s=0;s<=w;s++){let i=p+e*s/w,c=k+(1-(i-p)/e)*E;t.moveTo(0,c),t.lineTo(5,c),t.fillText(i.toFixed(2),8,c+5)}t.stroke()}for(let s of m){let i=s.data,c=s.strokeColor||"#000";t.beginPath(),t.strokeStyle=c,t.lineWidth=1;let g=!1,C=null,b=600;for(let v=0;v<i.length;v++){let[h,f]=i[v];if(h==null||f==null||isNaN(h)||isNaN(f)||!isFinite(h)||!isFinite(f)||f<p||f>P){g=!1,C=null;continue}C!=null&&h-C>b&&(g=!1);let u=(h-r)/A*o,M=k+(1-(f-p)/e)*E;g?t.lineTo(u,M):(t.moveTo(u,M),g=!0),C=h}t.stroke()}if(l!==null&&l>=0&&l<=o){let s=r+l/o*A;t.beginPath(),t.strokeStyle="#888",t.lineWidth=1,t.moveTo(l,0),t.lineTo(l,d),t.stroke(),t.font="12px sans-serif";let i=15;t.fillStyle="#fff",t.fillText(new Date(s).toLocaleTimeString(),l+6,i),i+=15;for(let c of m){let{data:g,strokeColor:C="#fff"}=c,b=g[0],v=Math.abs(b[0]-s);for(let h=1;h<g.length;h++){let f=Math.abs(g[h][0]-s);f<v&&(v=f,b=g[h])}t.fillStyle=C,t.fillText(b[1].toFixed(2),l+6,i),i+=15}}}function X(t,m){let a=document.createElement("div");a.style.position="relative",a.style.width=m.width!==void 0?`${m.width}px`:"100%",a.style.height=m.height!==void 0?`${m.height}px`:"100%";let o=document.createElement("canvas"),d=document.createElement("canvas");[o,d].forEach(e=>{e.style.position="absolute",e.style.top=e.style.left="0",e.style.width=e.style.height="100%",a.appendChild(e)}),d.style.pointerEvents="none";let F=o.getContext("2d"),n=d.getContext("2d"),T=t,w={...m},l=null,k=!1,S=()=>{let e=a.clientWidth,r=a.clientHeight;!e||!r||o.width===e&&o.height===r||(o.width=e,o.height=r,d.width=e,d.height=r,x(),y(l))},E=typeof ResizeObserver<"u"?new ResizeObserver(S):null;E?.observe(a),window.addEventListener("resize",S);let x=()=>{W(F,T,{...w,width:o.width,height:o.height})},y=e=>{if(n.clearRect(0,0,d.width,d.height),e==null)return;let r=T.flatMap(u=>u.data.map(M=>M[1])),Y=T.flatMap(u=>u.data.map(M=>M[0])),A=w.min??Math.min(...r),i=(w.max??Math.max(...r))-A||1,c=Math.min(...Y),C=Math.max(...Y)-c||1,b=c+e/o.width*C;n.beginPath(),n.strokeStyle="#888",n.lineWidth=1,n.moveTo(e,0),n.lineTo(e,d.height),n.stroke(),n.font="12px sans-serif",n.fillStyle="#fff",n.fillText(new Date(b).toLocaleTimeString(),e>o.width-100?e-100:e+6,15);let v=10,f=d.height-v-10,z=30;for(let{data:u,strokeColor:M="#fff"}of T){let L=u[0],D=Math.abs(L[0]-b);for(let R=1;R<u.length;R++){let O=Math.abs(u[R][0]-b);O<D&&(D=O,L=u[R])}n.fillStyle=M,n.fillText(L[1].toFixed(2),e>o.width-100?e-100:e+6,z),z+=15;let V=v+(1-(L[1]-A)/i)*f;n.beginPath(),n.arc(e,V,3,0,Math.PI*2),n.fill()}},p=e=>{let r=a.getBoundingClientRect(),Y=o.width/r.width;l=(e.clientX-r.left)*Y,k||(k=!0,requestAnimationFrame(()=>{y(l),k=!1}))},P=()=>{l=null,y(null)};return a.addEventListener("mousemove",p),a.addEventListener("mouseleave",P),setTimeout(S,0),{element:a,setSeries(e){T=e,S(),x(),y(l)},setOptions(e){w={...w,...e},S(),x(),y(l)},destroy(){E?.disconnect(),window.removeEventListener("resize",S),a.removeEventListener("mousemove",p),a.removeEventListener("mouseleave",P)}}}export{X as createChartElement,W as drawChart}; function N(t,u,o){let{width:a,height:m,min:Y,max:n,showYAxis:x=!1,yTicks:w=5,cursorX:s=null,maxTimeDelta:P}=o,c=10,k=m-c-10,y=u.flatMap(l=>l.data.map(i=>i[1])),E=u.flatMap(l=>l.data.map(i=>i[0])),p=Y!==void 0?Y:Math.min(...y),e=n!==void 0?n:Math.max(...y),r=e-p||1,C=Math.min(...E),L=Math.max(...E)-C||1;if(t.clearRect(0,0,a,m),x){t.beginPath(),t.strokeStyle="#aaa",t.lineWidth=1,t.font="15px sans-serif",t.fillStyle=u[0]?.strokeColor||"#000";for(let l=0;l<=w;l++){let i=p+r*l/w,T=c+(1-(i-p)/r)*k;t.moveTo(0,T),t.lineTo(5,T),t.fillText(i.toFixed(2),8,T+5)}t.stroke()}for(let l of u){let i=l.data,T=l.strokeColor||"#000";t.beginPath(),t.strokeStyle=T,t.lineWidth=1;let g=!1,d=null,F=P||600;for(let b=0;b<i.length;b++){let[v,h]=i[b];if(v==null||h==null||isNaN(v)||isNaN(h)||!isFinite(v)||!isFinite(h)||h<p||h>e){g=!1,d=null;continue}d!=null&&v-d>F&&(g=!1);let f=(v-C)/L*a,M=c+(1-(h-p)/r)*k;g?t.lineTo(f,M):(t.moveTo(f,M),g=!0),d=v}t.stroke()}if(s!==null&&s>=0&&s<=a){let l=C+s/a*L;t.beginPath(),t.strokeStyle="#888",t.lineWidth=1,t.moveTo(s,0),t.lineTo(s,m),t.stroke(),t.font="12px sans-serif";let i=15;t.fillStyle="#fff";let T=o.dateFormat??(g=>new Date(g).toLocaleTimeString());t.fillText(T(l),s+6,i),i+=15;for(let g of u){let{data:d,strokeColor:F="#fff"}=g,b=d[0],v=Math.abs(b[0]-l);for(let h=1;h<d.length;h++){let S=Math.abs(d[h][0]-l);S<v&&(v=S,b=d[h])}t.fillStyle=F,t.fillText(b[1].toFixed(2),s+6,i),i+=15}}}function X(t,u){let o=document.createElement("div");o.style.position="relative",o.style.width=u.width!==void 0?`${u.width}px`:"100%",o.style.height=u.height!==void 0?`${u.height}px`:"100%";let a=document.createElement("canvas"),m=document.createElement("canvas");[a,m].forEach(e=>{e.style.position="absolute",e.style.top=e.style.left="0",e.style.width=e.style.height="100%",o.appendChild(e)}),m.style.pointerEvents="none";let Y=a.getContext("2d"),n=m.getContext("2d"),x=t,w={...u},s=null,P=!1,c=()=>{let e=o.clientWidth,r=o.clientHeight;!e||!r||a.width===e&&a.height===r||(a.width=e,a.height=r,m.width=e,m.height=r,k(),y(s))},R=typeof ResizeObserver<"u"?new ResizeObserver(c):null;R?.observe(o),window.addEventListener("resize",c);let k=()=>{N(Y,x,{...w,width:a.width,height:a.height})},y=e=>{if(n.clearRect(0,0,m.width,m.height),e==null)return;let r=x.flatMap(f=>f.data.map(M=>M[1])),C=x.flatMap(f=>f.data.map(M=>M[0])),z=w.min??Math.min(...r),l=(w.max??Math.max(...r))-z||1,i=Math.min(...C),g=Math.max(...C)-i||1,d=i+e/a.width*g;n.beginPath(),n.strokeStyle="#888",n.lineWidth=1,n.moveTo(e,0),n.lineTo(e,m.height),n.stroke(),n.font="12px sans-serif",n.fillStyle="#fff";let F=w.dateFormat??(f=>new Date(f).toLocaleTimeString());n.fillText(F(d),e>a.width-100?e-100:e+6,15);let b=10,h=m.height-b-10,S=30;for(let{data:f,strokeColor:M="#fff"}of x){let D=f[0],O=Math.abs(D[0]-d);for(let A=1;A<f.length;A++){let V=Math.abs(f[A][0]-d);V<O&&(O=V,D=f[A])}n.fillStyle=M,n.fillText(D[1].toFixed(2),e>a.width-100?e-100:e+6,S),S+=15;let W=b+(1-(D[1]-z)/l)*h;n.beginPath(),n.arc(e,W,3,0,Math.PI*2),n.fill()}},E=e=>{let r=o.getBoundingClientRect(),C=a.width/r.width;s=(e.clientX-r.left)*C,P||(P=!0,requestAnimationFrame(()=>{y(s),P=!1}))},p=()=>{s=null,y(null)};return o.addEventListener("mousemove",E),o.addEventListener("mouseleave",p),setTimeout(c,0),{element:o,setSeries(e){x=e,c(),k(),y(s)},setOptions(e){w={...w,...e},c(),k(),y(s)},destroy(){R?.disconnect(),window.removeEventListener("resize",c),o.removeEventListener("mousemove",E),o.removeEventListener("mouseleave",p)}}}export{X as createChartElement,N as drawChart};

View File

@@ -1,23 +1,33 @@
{ {
"name": "@meteolab/uchart", "name": "@meteolab/uchart",
"publishConfig": { "access": "public" }, "publishConfig": {
"version": "1.0.3", "access": "public"
},
"version": "1.0.11",
"description": "Lightweight charting library for the browser and Node.js", "description": "Lightweight charting library for the browser and Node.js",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://git.meteolab.online/mi/uChart" "url": "https://git.meteolab.online/mi/uChart"
}, },
"keywords": ["chart", "visualization", "typescript", "esbuild"], "keywords": [
"chart",
"visualization",
"typescript",
"esbuild"
],
"type": "module", "type": "module",
"files": ["dist"], "files": [
"dist"
],
"main": "./dist/uchart.js", "main": "./dist/uchart.js",
"module": "./dist/uchart.js", "module": "./dist/uchart.js",
"types": "./dist/uchart.d.ts",
"sideEffects": false, "sideEffects": false,
"outDir": "./dist",
"types": "./dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"types": "./dist/uchart.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/uchart.js", "import": "./dist/uchart.js",
"require": "./dist/uchart.js" "require": "./dist/uchart.js"
} }
@@ -25,14 +35,12 @@
"scripts": { "scripts": {
"clean": "rm -rf dist", "clean": "rm -rf dist",
"build:js": "esbuild src/index.ts --bundle --minify --format=esm --outfile=dist/uchart.js", "build:js": "esbuild src/index.ts --bundle --minify --format=esm --outfile=dist/uchart.js",
"build:types": "tsc -p tsconfig.json", "build:types": "tsc --declaration --emitDeclarationOnly",
"build": "npm run clean && npm run build:js && npm run build:types", "build": "npm run clean && npm run build:js && npm run build:types",
"prepublishOnly": "npm run build", "prepublishOnly": "npm run build",
"serve": "npx http-server . -c-1 --gzip" "serve": "npx http-server . -c-1 --gzip"
}, },
"dependencies": { "dependencies": {},
"axios": "^1.5.0"
},
"devDependencies": { "devDependencies": {
"esbuild": "^0.25.5", "esbuild": "^0.25.5",
"typescript": "^5.0.0" "typescript": "^5.0.0"

View File

@@ -3,6 +3,21 @@ export type ChartSeries = {
strokeColor?: string; strokeColor?: string;
}; };
function averageData(data: [number, number][], windowSize: number = 5): [number, number][] {
if (data.length < windowSize) return data;
const result: [number, number][] = [];
for (let i = 0; i < data.length; i++) {
let sum = 0;
let count = 0;
for (let j = Math.max(0, i - Math.floor(windowSize / 2)); j <= Math.min(data.length - 1, i + Math.floor(windowSize / 2)); j++) {
sum += data[j][1];
count++;
}
result.push([data[i][0], sum / count]);
}
return result;
}
export function drawChart( export function drawChart(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
seriesList: ChartSeries[], seriesList: ChartSeries[],
@@ -15,6 +30,9 @@ export function drawChart(
yTicks?: number; yTicks?: number;
backgroundColor?: string; backgroundColor?: string;
cursorX?: number | null; cursorX?: number | null;
maxTimeDelta?: number | null;
dateFormat?: (ts: number) => string;
averageData?:number | null;
} }
): void { ): void {
const { const {
@@ -24,7 +42,8 @@ export function drawChart(
max, max,
showYAxis = false, showYAxis = false,
yTicks = 5, yTicks = 5,
cursorX = null cursorX = null,
maxTimeDelta
} = opts; } = opts;
const topPadding = 10; const topPadding = 10;
@@ -59,7 +78,7 @@ export function drawChart(
} }
for (const series of seriesList) { for (const series of seriesList) {
const data = series.data; const data = averageData(series.data, opts.averageData ?? 1);
const color = series.strokeColor || '#000'; const color = series.strokeColor || '#000';
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = color; ctx.strokeStyle = color;
@@ -67,10 +86,12 @@ export function drawChart(
let penDown = false; let penDown = false;
let lastT: number | null = null; let lastT: number | null = null;
const MAX_TIME_DELTA = 600; // let MAX_TIME_DELTA = 600;
let max_time_delta = maxTimeDelta || 600;
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
const [ti, vi] = data[i]; const [ti, vi] = data[i];
const invalid = const invalid =
ti == null || ti == null ||
vi == null || vi == null ||
@@ -85,7 +106,7 @@ export function drawChart(
lastT = null; lastT = null;
continue; continue;
} }
if (lastT != null && ti - lastT > MAX_TIME_DELTA) penDown = false; if (lastT != null && ti - lastT > max_time_delta) penDown = false;
const x = ((ti - minX) / rangeX) * width; const x = ((ti - minX) / rangeX) * width;
const y = topPadding + (1 - (vi - minVal) / rangeY) * usableHeight; const y = topPadding + (1 - (vi - minVal) / rangeY) * usableHeight;
if (!penDown) { if (!penDown) {
@@ -111,7 +132,8 @@ export function drawChart(
ctx.font = '12px sans-serif'; ctx.font = '12px sans-serif';
let textY = 15; let textY = 15;
ctx.fillStyle = '#fff'; ctx.fillStyle = '#fff';
ctx.fillText(new Date(tAtCursor).toLocaleTimeString(), cursorX + 6, textY); const formatTime = opts.dateFormat ?? ((ts: number) => new Date(ts).toLocaleTimeString());
ctx.fillText(formatTime(tAtCursor), cursorX + 6, textY);
textY += 15; textY += 15;
for (const series of seriesList) { for (const series of seriesList) {
@@ -142,6 +164,8 @@ export function createChartElement(
showYAxis?: boolean; showYAxis?: boolean;
yTicks?: number; yTicks?: number;
backgroundColor?: string; backgroundColor?: string;
maxTimeDelta?: number | null;
dateFormat?: (ts: number) => string;
} }
) { ) {
const container = document.createElement('div'); const container = document.createElement('div');
@@ -217,7 +241,8 @@ export function createChartElement(
overlayCtx.font = '12px sans-serif'; overlayCtx.font = '12px sans-serif';
overlayCtx.fillStyle = '#fff'; overlayCtx.fillStyle = '#fff';
overlayCtx.fillText(new Date(tAtCursor).toLocaleTimeString(), cursorX > baseCanvas.width - 100 ? cursorX - 100 : cursorX + 6, 15); const formatTime = currentOpts.dateFormat ?? ((ts: number) => new Date(ts).toLocaleTimeString());
overlayCtx.fillText(formatTime(tAtCursor), cursorX > baseCanvas.width - 100 ? cursorX - 100 : cursorX + 6, 15);
const topPadding = 10; const topPadding = 10;
const bottomPadding = 10; const bottomPadding = 10;

View File

@@ -16,8 +16,18 @@
import { createChartElement } from './dist/uchart.js'; import { createChartElement } from './dist/uchart.js';
const container = document.getElementById('chartContainer'); const container = document.getElementById('chartContainer');
const chart = createChartElement([], { showYAxis:true, yTicks:5 }); const chart = createChartElement([], {
container.appendChild(chart.element); showYAxis: true,
yTicks: 5,
dateFormat: (ts) => new Date(ts).toLocaleString('cs-CZ', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
}); container.appendChild(chart.element);
let phase = 0; let phase = 0;

View File

@@ -2,11 +2,13 @@
"compilerOptions": { "compilerOptions": {
"target": "ES6", "target": "ES6",
"module": "ES6", "module": "ES6",
"declaration": true,
"moduleResolution": "node", "moduleResolution": "node",
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"strict": true, "strict": true,
"skipLibCheck": true "skipLibCheck": true,
"outDir": "./dist"
}, },
"include": ["src"] "include": ["src"]
} }