feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+105
View File
@@ -0,0 +1,105 @@
<a name="ChartJSNodeCanvas"></a>
## ChartJSNodeCanvas
**Kind**: global class
* [ChartJSNodeCanvas](#ChartJSNodeCanvas)
* [new ChartJSNodeCanvas(options)](#new_ChartJSNodeCanvas_new)
* [.renderToDataURL(configuration, mimeType)](#ChartJSNodeCanvas+renderToDataURL)
* [.renderToDataURLSync(configuration, mimeType)](#ChartJSNodeCanvas+renderToDataURLSync)
* [.renderToBuffer(configuration, mimeType)](#ChartJSNodeCanvas+renderToBuffer)
* [.renderToBufferSync(configuration, mimeType)](#ChartJSNodeCanvas+renderToBufferSync)
* [.renderToStream(configuration, mimeType)](#ChartJSNodeCanvas+renderToStream)
* [.registerFont(path, options)](#ChartJSNodeCanvas+registerFont)
<a name="new_ChartJSNodeCanvas_new"></a>
### new ChartJSNodeCanvas(options)
Create a new instance of CanvasRenderService.
| Param | Description |
| --- | --- |
| options | Configuration for this instance |
<a name="ChartJSNodeCanvas+renderToDataURL"></a>
### chartJSNodeCanvas.renderToDataURL(configuration, mimeType)
Render to a data url.
**Kind**: instance method of [<code>ChartJSNodeCanvas</code>](#ChartJSNodeCanvas)
**See**: https://github.com/Automattic/node-canvas#canvastodataurl
| Param | Default | Description |
| --- | --- | --- |
| configuration | | The Chart JS configuration for the chart to render. |
| mimeType | <code>image/png</code> | The image format, `image/png` or `image/jpeg`. |
<a name="ChartJSNodeCanvas+renderToDataURLSync"></a>
### chartJSNodeCanvas.renderToDataURLSync(configuration, mimeType)
Render to a data url synchronously.
**Kind**: instance method of [<code>ChartJSNodeCanvas</code>](#ChartJSNodeCanvas)
**See**: https://github.com/Automattic/node-canvas#canvastodataurl
| Param | Default | Description |
| --- | --- | --- |
| configuration | | The Chart JS configuration for the chart to render. |
| mimeType | <code>image/png</code> | The image format, `image/png` or `image/jpeg`. |
<a name="ChartJSNodeCanvas+renderToBuffer"></a>
### chartJSNodeCanvas.renderToBuffer(configuration, mimeType)
Render to a buffer.
**Kind**: instance method of [<code>ChartJSNodeCanvas</code>](#ChartJSNodeCanvas)
**See**: https://github.com/Automattic/node-canvas#canvastobuffer
| Param | Default | Description |
| --- | --- | --- |
| configuration | | The Chart JS configuration for the chart to render. |
| mimeType | <code>image/png</code> | A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support) or `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas. |
<a name="ChartJSNodeCanvas+renderToBufferSync"></a>
### chartJSNodeCanvas.renderToBufferSync(configuration, mimeType)
Render to a buffer synchronously.
**Kind**: instance method of [<code>ChartJSNodeCanvas</code>](#ChartJSNodeCanvas)
**See**: https://github.com/Automattic/node-canvas#canvastobuffer
| Param | Default | Description |
| --- | --- | --- |
| configuration | | The Chart JS configuration for the chart to render. |
| mimeType | <code>image/png</code> | A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas. |
<a name="ChartJSNodeCanvas+renderToStream"></a>
### chartJSNodeCanvas.renderToStream(configuration, mimeType)
Render to a stream.
**Kind**: instance method of [<code>ChartJSNodeCanvas</code>](#ChartJSNodeCanvas)
**See**: https://github.com/Automattic/node-canvas#canvascreatepngstream
| Param | Default | Description |
| --- | --- | --- |
| configuration | | The Chart JS configuration for the chart to render. |
| mimeType | <code>image/png</code> | A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas. |
<a name="ChartJSNodeCanvas+registerFont"></a>
### chartJSNodeCanvas.registerFont(path, options)
Use to register the font with Canvas to use a font file that is not installed as a system font, this must be done before the Canvas is created.
**Kind**: instance method of [<code>ChartJSNodeCanvas</code>](#ChartJSNodeCanvas)
| Param | Description |
| --- | --- |
| path | The path to the font file. |
| options | The font options. |
**Example**
```js
registerFont('comicsans.ttf', { family: 'Comic Sans' });
```
+25
View File
@@ -0,0 +1,25 @@
# Changelog
## 5.0.0
Migrated to support chart.js v4.x.x, dropped support for 3.x.x
Upgraded to canvas v3.x.x
WIP support for animated charts, exporting to GIF.
## 4.0.0
Migrated to support chart.js v3.x.x, dropped support for 2.x.x
Removed the legacy API.
## 3.2.0
Another deploy for minor versions.
## 3.1.1
Added back legacy API to address a wrong version being deployed. See [#60](https://github.com/SeanSobey/ChartjsNodeCanvas/issues/60).
## 3.0.0
Rewrote `ChartJSNodeCanvas` API, the constructor and plugins in particular.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Sean Sobey
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+260
View File
@@ -0,0 +1,260 @@
<!-- [![Logo](./resources/logo.png)](https://github.com/SeanSobey/ChartjsNodeCanvas) -->
<a href="https://github.com/SeanSobey/ChartjsNodeCanvas" align="center">
<img src="https://raw.githubusercontent.com/SeanSobey/ChartjsNodeCanvas/HEAD/resources/logo.png">
</a>
# chartjs-node-canvas
<!-- [![CircleCI](https://circleci.com/gh/SeanSobey/ChartjsNodeCanvas.svg?style=svg)](https://circleci.com/gh/SeanSobey/ChartjsNodeCanvas) -->
[![GitHub](https://github.com/SeanSobey/ChartjsNodeCanvas/workflows/Node%20CI/badge.svg)](https://github.com/SeanSobey/ChartjsNodeCanvas/actions)
[![codecov](https://codecov.io/gh/SeanSobey/ChartjsNodeCanvas/branch/master/graph/badge.svg)](https://codecov.io/gh/SeanSobey/ChartjsNodeCanvas)
[![NPM](https://img.shields.io/npm/v/chartjs-node-canvas.svg)](https://www.npmjs.com/package/chartjs-node-canvas)
[![packagephobia publish](https://badgen.net/packagephobia/publish/chartjs-node-canvas@latest)](https://bundlephobia.com/result?p=chartjs-node-canvas)
<!-- [![bundlephobia](https://badgen.net/bundlephobia/min/chartjs-node-canvas@latest)](https://bundlephobia.com/result?p=chartjs-node-canvas) -->
<!-- [![packagephobia install](https://badgen.net/packagephobia/install/chartjs-node-canvas@latest)](https://bundlephobia.com/result?p=chartjs-node-canvas) -->
A Node JS renderer for [Chart.js](http://www.chartjs.org) using [canvas](https://github.com/Automattic/node-canvas).
Provides and alternative to [chartjs-node](https://www.npmjs.com/package/chartjs-node) that does not require jsdom (or the global variables that this requires) and allows chartJS as a peer dependency, so you can manage its version yourself.
## Contents
1. [Installation](#installation)
2. [Node JS version](#node-js-version)
3. [Features](#features)
4. [Limitations](#limitations)
5. [API](#api)
6. [Usage](#usage)
7. [Known Issues](#known-issues)
8. [Sponsors](#sponsors)
## Installation
```
npm i chartjs-node-canvas chart.js
```
### Node JS version
This is limited by the upstream dependency [canvas](https://github.com/Automattic/node-canvas).
See the GitHub Actions [yml](.github/workflows/nodejs.yml) section for the current supported Node version(s). You will need to do a `npm rebuild` to rebuild the canvas binaries.
### Charts.JS version
Currently supports 4.x.x. You are given the ability to maintain the version yourself via peer dependency, but be aware that going above the specified [version](./package.json) might result in errors.
## Features
* Supports all Chart JS features and charts.
* No heavy DOM virtualization libraries, thanks to a [pull request](https://github.com/chartjs/Chart.js/pull/5324) to chart.js allowing it to run natively on node, requiring only a Canvas API.
* Chart JS is a peer dependency, so you can bump and manage it yourself.
* Provides a callback with the global ChartJS variable, so you can use the [Global Configuration](https://www.chartjs.org/docs/latest/configuration/#global-configuration).
* Uses (similar to) [fresh-require](https://www.npmjs.com/package/fresh-require) for each instance of `ChartJSNodeCanvas`, so you can mutate the ChartJS global variable separately within each instance.
* Support for custom fonts.
## Limitations
### Node Modules
I hope to convert this package to a node module in the future, but since it uses the CommonJS API to manage memory for ChartJS this is not a simple task. It it a top priority for the next major release.
### Animations
Chart animation (and responsive resize) is disabled by this library. This is necessary since the animation API's required are not available in Node JS/canvas-node (this is not a browser environment after all).
This is the same as:
```js
Chart.defaults.animation = false;
Chart.defaults.responsive = false;
```
*Note this is WIP, see the [change log](CHANGELOG.md) for most recent development.
### SVG and PDF
For some unknown reason canvas requires use of the [sync](https://github.com/Automattic/node-canvas#canvastobuffer) API's to use SVG's or PDF's. This libraries which support these are:
* [renderToBufferSync](./API.md#ChartJSNodeCanvas+renderToBufferSync) ('application/pdf' | 'image/svg+xml')
* [renderToStream](./API.md#ChartJSNodeCanvas+renderToStream) ('application/pdf')
You also need to set the canvas type when you initialize the `ChartJSNodeCanvas` instance like the following:
```js
const { ChartJSNodeCanvas } = require('chartjs-node-canvas');
const chartJSNodeCanvas = new ChartJSNodeCanvas({ type: 'svg', width: 800, height: 600 });
```
## API
See the [API docs](https://github.com/SeanSobey/ChartjsNodeCanvas/blob/master/API.md).
## Usage
```js
const { ChartJSNodeCanvas } = require('chartjs-node-canvas');
const width = 400; //px
const height = 400; //px
const backgroundColour = 'white'; // Uses https://www.w3schools.com/tags/canvas_fillstyle.asp
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, backgroundColour});
(async () => {
const configuration = {
... // See https://www.chartjs.org/docs/latest/configuration
};
const image = await chartJSNodeCanvas.renderToBuffer(configuration);
const dataUrl = await chartJSNodeCanvas.renderToDataURL(configuration);
const stream = chartJSNodeCanvas.renderToStream(configuration);
})();
```
Also see the [example](./src/example.ts) and the generated [image](./example.png).
### Memory Management
Every instance of `ChartJSNodeCanvas` creates its own [canvas](https://github.com/Automattic/node-canvas). To ensure efficient memory and GC use make sure your implementation creates as few instances as possible and reuses them:
```js
// Re-use one service, or as many as you need for different canvas size requirements
const smallChartJSNodeCanvas = new ChartJSNodeCanvas({ width: 400, height: 400 });
const bigCChartJSNodeCanvas = new ChartJSNodeCanvas({ width: 2000, height: 2000 });
// Expose just the 'render' methods to downstream code so they don't have to worry about life-cycle management.
exports = {
renderSmallChart: (configuration) => smallChartJSNodeCanvas.renderToBuffer(configuration),
renderBigChart: (configuration) => bigCChartJSNodeCanvas.renderToBuffer(configuration)
};
```
### Custom Charts
Just use the ChartJS reference in the callback:
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, chartCallback: (ChartJS) => {
// New chart type example: https://www.chartjs.org/docs/latest/developers/charts.html
class MyType extends Chart.DatasetController {
}
Chart.register(MyType);
}
});
```
### Global Config
Just use the ChartJS reference in the callback:
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, chartCallback: (ChartJS) => {
// Global config example: https://www.chartjs.org/docs/latest/configuration/
ChartJS.defaults.elements.line.borderWidth = 2;
} });
```
### Custom Fonts
Just use the `registerFont` method:
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, chartCallback: (ChartJS) => {
// Just example usage
ChartJS.global.defaultFontFamily = 'VTKS UNAMOUR';
} });
// Register before rendering any charts
chartJSNodeCanvas.registerFont('./testData/VTKS UNAMOUR.ttf', { family: 'VTKS UNAMOUR' });
```
See the node-canvas [docs](https://github.com/Automattic/node-canvas#registerfont) and the chart js [docs](https://www.chartjs.org/docs/latest/general/fonts.html).
#### Windows
On windows you need to install the font first, before running your app. Otherwise you will get an error something like:
`Pango-WARNING **: 11:13:09.211: couldn't load font "vtks unamour Not-Rotated 12px", falling back to "Sans Not-Rotated 12px", expect ugly output.`
See [here](https://github.com/Automattic/node-canvas/issues/1643).
### Background color
Due to the many issues and question this includes a [convenience plugin](./src/backgroundColourPlugin.ts) to fill the otherwise transparent background. It uses the [fillStyle](https://www.w3schools.com/tags/canvas_fillstyle.asp) canvas API;
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, backgroundColour: 'purple' });
```
### Loading plugins
This library is designed to make loading plugins as simple as possible. For legacy plugins, you should just be able to add the module name to the appropriate array option and the library handles the rest.
The Chart.JS [plugin API](https://www.chartjs.org/docs/latest/developers/plugins.html) has changed over time and this requires compatibility options for the different ways plugins have been historically loaded. ChartJS Node Canvas has a `plugin` option with specifiers for the different ways supported plugin loading methods are handled. If you are not sure about your plugin, just try the different ones until your plugin loads:
#### Newer plugins
Let `ChartJSNodeCanvas` manage the lifecycle of the plugin itself, each instance will have a separate instance of the plugin:
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, plugins: {
modern: ['chartjs-plugin-annotation']
} });
```
You want to share the plugin instance, this may cause unwanted issues, use at own risk:
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, plugins: {
modern: [require('chartjs-plugin-annotation')]
} });
```
#### Older plugins
---
1. Plugin that expects a global Chart variable.
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, plugins: {
requireChartJSLegacy: ['<some plugin>']
}});
```
2. Plugins that `require` ChartJS themselves.
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, plugins: {
globalVariableLegacy: ['chartjs-plugin-crosshair']
} });
```
3. Register plugin directly with ChartJS:
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, plugins: {
requireLegacy: ['chartjs-plugin-datalabels']
} });
```
---
These approaches can be combined also:
```js
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, plugins: {
modern: ['chartjs-plugin-annotation'],
requireLegacy: ['chartjs-plugin-datalabels']
} });
```
See the [tests](src/index.e2e.spec.ts#106) for some examples.
## Known Issues
There is a problem with persisting config objects between render calls, see this [issue](https://github.com/SeanSobey/ChartjsNodeCanvas/issues/9) for details and workarounds.
## Sponsors
@athombv at https://homey.app
@@ -0,0 +1,21 @@
import { ChartConfiguration } from 'chart.js/auto';
import { ChartJSNodeCanvasBase, MimeType } from './chartJSNodeCanvasBase';
export declare class AnimatedChartJSNodeCanvas extends ChartJSNodeCanvasBase {
/**
* Render to a data url array.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToDataURL(configuration: ChartConfiguration, mimeType?: MimeType): Promise<ReadonlyArray<string>>;
/**
* Render to a buffer.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support) or `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToBuffer(configuration: ChartConfiguration, mimeType?: MimeType): Promise<ReadonlyArray<Buffer>>;
private renderChart;
}
@@ -0,0 +1,98 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnimatedChartJSNodeCanvas = void 0;
const chartJSNodeCanvasBase_1 = require("./chartJSNodeCanvasBase");
const animationFrameProvider = {
cancelAnimationFrame: (handle) => clearImmediate(handle),
requestAnimationFrame: (callback) => setImmediate(() => callback(Date.now())),
};
class AnimatedChartJSNodeCanvas extends chartJSNodeCanvasBase_1.ChartJSNodeCanvasBase {
/**
* Render to a data url array.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToDataURL(configuration, mimeType = 'image/png') {
const frames = [];
return new Promise((resolve, _reject) => {
this.renderChart(configuration, (chart) => {
const canvas = chart.canvas;
if (!canvas) {
throw new Error('Canvas is null');
}
const dataUrl = canvas.toDataURL(mimeType);
frames.push(dataUrl);
}, (chart) => {
resolve(frames);
chart.destroy();
});
});
}
/**
* Render to a buffer.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support) or `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToBuffer(configuration, mimeType = 'image/png') {
return new Promise((resolve, _reject) => {
const frames = [];
this.renderChart(configuration, (chart) => {
const canvas = chart.canvas;
if (!canvas) {
throw new Error('Canvas is null');
}
const buffer = canvas.toBuffer(mimeType);
frames.push(buffer);
}, (chart) => {
resolve(frames);
chart.destroy();
});
});
}
renderChart(configuration, onProgress, onComplete) {
const canvas = this._createCanvas(this._width, this._height, this._type);
canvas.style = canvas.style || {};
const options = Object.assign({}, configuration.options);
options.responsive = false;
const animation = options.animation || {};
if (!animation.duration) {
animation.duration = 1000;
}
const baseOnProgress = animation.onProgress;
animation.onProgress = (event) => {
const currentStep = event.currentStep; // type docs wrong?
const initial = !!event.initial ? event.initial : false; // added around 3.2.x
const progress = currentStep / event.numSteps;
if (baseOnProgress) {
//baseOnComplete(event.chart);
baseOnProgress.call(animation, event);
}
onProgress(event.chart, progress, initial);
};
const baseOnComplete = animation.onProgress;
animation.onComplete = (event) => {
const initial = !!event.initial ? event.initial : false; // added around 3.2.x
if (baseOnComplete) {
//baseOnComplete(event.chart);
baseOnComplete.call(animation, event);
}
onComplete(event.chart, initial);
};
const plugins = configuration.plugins || [];
const configuredChartConfig = Object.assign(Object.assign({}, configuration), { options, plugins });
global.window = global.window || {};
global.window.requestAnimationFrame = animationFrameProvider.requestAnimationFrame;
global.window.cancelAnimationFrame = animationFrameProvider.cancelAnimationFrame;
const context = canvas.getContext('2d');
global.Image = this._image; // Some plugins use this API
const chart = new this._chartJs(context, configuredChartConfig);
delete global.Image;
return chart;
}
}
exports.AnimatedChartJSNodeCanvas = AnimatedChartJSNodeCanvas;
//# sourceMappingURL=animatedChartJSNodeCanvas.js.map
@@ -0,0 +1 @@
{"version":3,"file":"animatedChartJSNodeCanvas.js","sourceRoot":"","sources":["../src/animatedChartJSNodeCanvas.ts"],"names":[],"mappings":";;;AAEA,mEAAkF;AAElF,MAAM,sBAAsB,GAA2B;IACtD,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,CAAC,MAAa,CAAC;IAC/D,qBAAqB,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAQ;CACpF,CAAC;AAKF,MAAa,yBAA0B,SAAQ,6CAAqB;IAEnE;;;;;;OAMG;IACI,eAAe,CAAC,aAAiC,EAAE,WAAqB,WAAW;QAEzF,MAAM,MAAM,GAAkB,EAAE,CAAC;QACjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;YACvC,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;gBAEzC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAgB,CAAC;gBACtC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACb,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;gBACnC,CAAC;gBACD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAC3C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;gBAEZ,OAAO,CAAC,MAAM,CAAC,CAAC;gBAChB,KAAK,CAAC,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACI,cAAc,CAAC,aAAiC,EAAE,WAAqB,WAAW;QAExF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;YAEvC,MAAM,MAAM,GAAkB,EAAE,CAAC;YACjC,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;gBAEzC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAgB,CAAC;gBACtC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACb,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;gBACnC,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACzC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrB,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;gBAEZ,OAAO,CAAC,MAAM,CAAC,CAAC;gBAChB,KAAK,CAAC,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,WAAW,CAAC,aAAiC,EAAE,UAAsB,EAAE,UAAsB;QAEpG,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACxE,MAAc,CAAC,KAAK,GAAI,MAAc,CAAC,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QACzD,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC;QAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;QAC1C,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;YACzB,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC3B,CAAC;QACD,MAAM,cAAc,GAAG,SAAS,CAAC,UAAU,CAAC;QAC5C,SAAS,CAAC,UAAU,GAAG,CAAC,KAAK,EAAE,EAAE;YAChC,MAAM,WAAW,GAAY,KAAa,CAAC,WAAW,CAAC,CAAC,mBAAmB;YAC3E,MAAM,OAAO,GAAG,CAAC,CAAE,KAAa,CAAC,OAAO,CAAC,CAAC,CAAE,KAAa,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAE,qBAAqB;YACjG,MAAM,QAAQ,GAAG,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC;YAC9C,IAAI,cAAc,EAAE,CAAC;gBACpB,8BAA8B;gBAC9B,cAAc,CAAC,IAAI,CAAC,SAAgB,EAAE,KAAK,CAAC,CAAC;YAC9C,CAAC;YACD,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC,CAAC;QACF,MAAM,cAAc,GAAG,SAAS,CAAC,UAAU,CAAC;QAC5C,SAAS,CAAC,UAAU,GAAG,CAAC,KAAK,EAAE,EAAE;YAChC,MAAM,OAAO,GAAG,CAAC,CAAE,KAAa,CAAC,OAAO,CAAC,CAAC,CAAE,KAAa,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAE,qBAAqB;YACjG,IAAI,cAAc,EAAE,CAAC;gBACpB,8BAA8B;gBAC9B,cAAc,CAAC,IAAI,CAAC,SAAgB,EAAE,KAAK,CAAC,CAAC;YAC9C,CAAC;YACD,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,EAAE,CAAC;QAC5C,MAAM,qBAAqB,mCAAQ,aAAa,KAAE,OAAO,EAAE,OAAO,GAAE,CAAC;QACrE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,qBAAqB,GAAG,sBAAsB,CAAC,qBAAqB,CAAC;QACnF,MAAM,CAAC,MAAM,CAAC,oBAAoB,GAAG,sBAAsB,CAAC,oBAAoB,CAAC;QACjF,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACvC,MAAc,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,4BAA4B;QACjE,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAE,OAAe,EAAE,qBAAqB,CAAC,CAAC;QACzE,OAAQ,MAAc,CAAC,KAAK,CAAC;QAC7B,OAAO,KAAK,CAAC;IACd,CAAC;CACD;AAlGD,8DAkGC"}
@@ -0,0 +1,9 @@
import { Chart as ChartJS, Plugin as ChartJSPlugin } from 'chart.js/auto';
export declare class BackgroundColourPlugin implements ChartJSPlugin {
private readonly _width;
private readonly _height;
private readonly _fillStyle;
readonly id: string;
constructor(_width: number, _height: number, _fillStyle: string);
beforeDraw(chart: ChartJS): boolean | void;
}
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BackgroundColourPlugin = void 0;
class BackgroundColourPlugin {
constructor(_width, _height, _fillStyle) {
this._width = _width;
this._height = _height;
this._fillStyle = _fillStyle;
this.id = 'chartjs-plugin-chartjs-node-canvas-background-colour';
}
beforeDraw(chart) {
const ctx = chart.ctx;
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = this._fillStyle;
ctx.fillRect(0, 0, this._width, this._height);
ctx.restore();
}
}
exports.BackgroundColourPlugin = BackgroundColourPlugin;
//# sourceMappingURL=backgroundColourPlugin.js.map
@@ -0,0 +1 @@
{"version":3,"file":"backgroundColourPlugin.js","sourceRoot":"","sources":["../src/backgroundColourPlugin.ts"],"names":[],"mappings":";;;AAGA,MAAa,sBAAsB;IAGlC,YACkB,MAAc,EACd,OAAe,EACf,UAAkB;QAFlB,WAAM,GAAN,MAAM,CAAQ;QACd,YAAO,GAAP,OAAO,CAAQ;QACf,eAAU,GAAV,UAAU,CAAQ;QALpB,OAAE,GAAW,sDAAsD,CAAC;IAMhF,CAAC;IAEE,UAAU,CAAC,KAAc;QAE/B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACtB,GAAG,CAAC,IAAI,EAAE,CAAC;QACX,GAAG,CAAC,wBAAwB,GAAG,kBAAkB,CAAC;QAClD,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC;QAChC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,GAAG,CAAC,OAAO,EAAE,CAAC;IACf,CAAC;CACD;AAlBD,wDAkBC"}
@@ -0,0 +1,46 @@
import { Readable } from 'stream';
import { ChartConfiguration } from 'chart.js/auto';
import { ChartJSNodeCanvasBase, MimeType } from './chartJSNodeCanvasBase';
export declare class ChartJSNodeCanvas extends ChartJSNodeCanvasBase {
/**
* Render to a data url.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType The image format, `image/png` or `image/jpeg`.
*/
renderToDataURL(configuration: ChartConfiguration, mimeType?: MimeType): Promise<string>;
/**
* Render to a data url synchronously.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType The image format, `image/png` or `image/jpeg`.
*/
renderToDataURLSync(configuration: ChartConfiguration, mimeType?: MimeType): string;
/**
* Render to a buffer.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support) or `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToBuffer(configuration: ChartConfiguration, mimeType?: MimeType): Promise<Buffer>;
/**
* Render to a buffer synchronously.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToBufferSync(configuration: ChartConfiguration, mimeType?: MimeType | 'application/pdf' | 'image/svg+xml'): Buffer;
/**
* Render to a stream.
* @see https://github.com/Automattic/node-canvas#canvascreatepngstream
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToStream(configuration: ChartConfiguration, mimeType?: MimeType | 'application/pdf'): Readable;
private renderChart;
}
@@ -0,0 +1,129 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChartJSNodeCanvas = void 0;
const chartJSNodeCanvasBase_1 = require("./chartJSNodeCanvasBase");
class ChartJSNodeCanvas extends chartJSNodeCanvasBase_1.ChartJSNodeCanvasBase {
/**
* Render to a data url.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType The image format, `image/png` or `image/jpeg`.
*/
renderToDataURL(configuration, mimeType = 'image/png') {
const chart = this.renderChart(configuration);
return new Promise((resolve, reject) => {
if (!chart.canvas) {
return reject(new Error('Canvas is null'));
}
const canvas = chart.canvas;
canvas.toDataURL(mimeType, (error, png) => {
chart.destroy();
if (error) {
return reject(error);
}
return resolve(png);
});
});
}
/**
* Render to a data url synchronously.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType The image format, `image/png` or `image/jpeg`.
*/
renderToDataURLSync(configuration, mimeType = 'image/png') {
const chart = this.renderChart(configuration);
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas;
const dataUrl = canvas.toDataURL(mimeType);
// Use this to destroy any chart instances that are created.
// This will clean up any references stored to the chart object within Chart.js, along with any associated event listeners attached by Chart.js.
// This must be called before the canvas is reused for a new chart.
chart.destroy();
return dataUrl;
}
/**
* Render to a buffer.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support) or `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToBuffer(configuration, mimeType = 'image/png') {
const chart = this.renderChart(configuration);
return new Promise((resolve, reject) => {
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas;
canvas.toBuffer((error, buffer) => {
chart.destroy();
if (error) {
return reject(error);
}
return resolve(buffer);
}, mimeType);
});
}
/**
* Render to a buffer synchronously.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToBufferSync(configuration, mimeType = 'image/png') {
const chart = this.renderChart(configuration);
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas;
const buffer = canvas.toBuffer(mimeType);
chart.destroy();
return buffer;
}
/**
* Render to a stream.
* @see https://github.com/Automattic/node-canvas#canvascreatepngstream
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
renderToStream(configuration, mimeType = 'image/png') {
const chart = this.renderChart(configuration);
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas;
setImmediate(() => chart.destroy());
switch (mimeType) {
case 'image/png':
return canvas.createPNGStream();
case 'image/jpeg':
return canvas.createJPEGStream();
case 'application/pdf':
return canvas.createPDFStream();
default:
throw new Error(`Un-handled mimeType: ${mimeType}`);
}
}
renderChart(configuration) {
const canvas = this._createCanvas(this._width, this._height, this._type);
canvas.style = canvas.style || {};
configuration.options = configuration.options || {};
configuration.options.responsive = false;
// Disable animation (otherwise charts will throw exceptions)
configuration.options.animation = false;
const context = canvas.getContext('2d');
global.Image = this._image; // Some plugins use this API
const chart = new this._chartJs(context, configuration);
delete global.Image;
return chart;
}
}
exports.ChartJSNodeCanvas = ChartJSNodeCanvas;
//# sourceMappingURL=chartJSNodeCanvas.js.map
@@ -0,0 +1 @@
{"version":3,"file":"chartJSNodeCanvas.js","sourceRoot":"","sources":["../src/chartJSNodeCanvas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
import { Readable } from 'stream';
import { Chart as ChartJS, ChartComponentLike } from 'chart.js/auto';
import { createCanvas, registerFont, Image } from 'canvas';
export type ChartJSNodeCanvasPlugins = {
/**
* Global plugins, see https://www.chartjs.org/docs/latest/developers/plugins.html.
*/
readonly modern?: ReadonlyArray<string | ChartComponentLike>;
/**
* This will work for plugins that `require` ChartJS themselves.
*/
readonly requireChartJSLegacy?: ReadonlyArray<string>;
/**
* This should work for any plugin that expects a global Chart variable.
*/
readonly globalVariableLegacy?: ReadonlyArray<string>;
/**
* This will work with plugins that just return a plugin object and do no specific loading themselves.
*/
readonly requireLegacy?: ReadonlyArray<string>;
};
export type ChartCallback = (chartJS: typeof ChartJS) => void | Promise<void>;
export type CanvasType = 'pdf' | 'svg';
export type MimeType = 'image/png' | 'image/jpeg';
export type Canvas = HTMLCanvasElement & {
toBuffer(callback: (err: Error | null, result: Buffer) => void, mimeType?: string, config?: any): void;
toBuffer(mimeType?: string, config?: any): Buffer;
createPNGStream(config?: any): Readable;
createJPEGStream(config?: any): Readable;
createPDFStream(config?: any): Readable;
};
export interface ChartJSNodeCanvasOptions {
/**
* The width of the charts to render, in pixels.
*/
readonly width: number;
/**
* The height of the charts to render, in pixels.
*/
readonly height: number;
/**
* Optional callback which is called once with a new ChartJS global reference as the only parameter.
*/
readonly chartCallback?: ChartCallback;
/**
* Optional canvas type ('PDF' or 'SVG'), see the [canvas pdf doc](https://github.com/Automattic/node-canvas#pdf-output-support).
*/
readonly type?: CanvasType;
/**
* Optional plugins to register.
*/
readonly plugins?: ChartJSNodeCanvasPlugins;
/**
* Optional background color for the chart, otherwise it will be transparent. Note, this will apply to all charts. See the [fillStyle](https://www.w3schools.com/tags/canvas_fillstyle.asp) canvas API used for possible values.
*/
readonly backgroundColour?: string;
}
export declare abstract class ChartJSNodeCanvasBase {
protected readonly _width: number;
protected readonly _height: number;
protected readonly _chartJs: typeof ChartJS;
protected readonly _createCanvas: typeof createCanvas;
protected readonly _registerFont: typeof registerFont;
protected readonly _image: typeof Image;
protected readonly _type?: CanvasType;
/**
* Create a new instance of CanvasRenderService.
*
* @param options Configuration for this instance
*/
constructor(options: ChartJSNodeCanvasOptions);
/**
* Use to register the font with Canvas to use a font file that is not installed as a system font, this must be done before the Canvas is created.
*
* @param path The path to the font file.
* @param options The font options.
* @example
* registerFont('comicsans.ttf', { family: 'Comic Sans' });
*/
registerFont(path: string, options: {
readonly family: string;
readonly weight?: string;
readonly style?: string;
}): void;
protected initialize(options: ChartJSNodeCanvasOptions): typeof ChartJS;
}
@@ -0,0 +1,90 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChartJSNodeCanvasBase = void 0;
const path_1 = require("path");
const freshRequire_1 = require("./freshRequire");
const backgroundColourPlugin_1 = require("./backgroundColourPlugin");
class ChartJSNodeCanvasBase {
/**
* Create a new instance of CanvasRenderService.
*
* @param options Configuration for this instance
*/
constructor(options) {
if (options === null || typeof (options) !== 'object') {
throw new Error('An options parameter object is required');
}
if (!options.width || typeof (options.width) !== 'number') {
throw new Error('A width option is required');
}
if (!options.height || typeof (options.height) !== 'number') {
throw new Error('A height option is required');
}
this._width = options.width;
this._height = options.height;
const canvas = (0, freshRequire_1.freshRequire)('canvas');
this._createCanvas = canvas.createCanvas;
this._registerFont = canvas.registerFont;
this._image = canvas.Image;
this._type = options.type && options.type.toLowerCase();
this._chartJs = this.initialize(options);
}
/**
* Use to register the font with Canvas to use a font file that is not installed as a system font, this must be done before the Canvas is created.
*
* @param path The path to the font file.
* @param options The font options.
* @example
* registerFont('comicsans.ttf', { family: 'Comic Sans' });
*/
registerFont(path, options) {
this._registerFont(path, options);
}
initialize(options) {
var _a, _b, _c, _d;
const chartJs = require('chart.js/auto');
if ((_a = options.plugins) === null || _a === void 0 ? void 0 : _a.requireChartJSLegacy) {
for (const plugin of options.plugins.requireChartJSLegacy) {
require(plugin);
delete require.cache[require.resolve(plugin)];
}
}
if ((_b = options.plugins) === null || _b === void 0 ? void 0 : _b.globalVariableLegacy) {
global.Chart = chartJs;
for (const plugin of options.plugins.globalVariableLegacy) {
(0, freshRequire_1.freshRequire)(plugin);
}
delete global.Chart;
}
if ((_c = options.plugins) === null || _c === void 0 ? void 0 : _c.modern) {
for (const plugin of options.plugins.modern) {
if (typeof plugin === 'string') {
chartJs.register((0, freshRequire_1.freshRequire)(plugin));
}
else {
chartJs.register(plugin);
}
}
}
if ((_d = options.plugins) === null || _d === void 0 ? void 0 : _d.requireLegacy) {
for (const plugin of options.plugins.requireLegacy) {
chartJs.register((0, freshRequire_1.freshRequire)(plugin));
}
}
if (options.chartCallback) {
options.chartCallback(chartJs);
}
if (options.backgroundColour) {
chartJs.register(new backgroundColourPlugin_1.BackgroundColourPlugin(options.width, options.height, options.backgroundColour));
}
const chartJsPath = (0, path_1.join)('node_modules', 'chart.js');
for (const key of Object.keys(require.cache)) {
if (key.includes(chartJsPath)) {
delete require.cache[key];
}
}
return chartJs;
}
}
exports.ChartJSNodeCanvasBase = ChartJSNodeCanvasBase;
//# sourceMappingURL=chartJSNodeCanvasBase.js.map
@@ -0,0 +1 @@
{"version":3,"file":"chartJSNodeCanvasBase.js","sourceRoot":"","sources":["../src/chartJSNodeCanvasBase.ts"],"names":[],"mappings":";;;AAIA,+BAAwC;AACxC,iDAA8C;AAC9C,qEAAkE;AA4DlE,MAAsB,qBAAqB;IAU1C;;;;OAIG;IACH,YAAY,OAAiC;QAE5C,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YACvD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAA,2BAAY,EAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,WAAW,EAAgB,CAAC;QACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;;;OAOG;IACI,YAAY,CAAC,IAAY,EAAE,OAAuF;QAExH,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC;IAES,UAAU,CAAC,OAAiC;;QAErD,MAAM,OAAO,GAAmB,OAAO,CAAC,eAAe,CAAC,CAAC;QAEzD,IAAI,MAAA,OAAO,CAAC,OAAO,0CAAE,oBAAoB,EAAE,CAAC;YAC3C,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC;gBAC3D,OAAO,CAAC,MAAM,CAAC,CAAC;gBAChB,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/C,CAAC;QACF,CAAC;QAED,IAAI,MAAA,OAAO,CAAC,OAAO,0CAAE,oBAAoB,EAAE,CAAC;YAC1C,MAAc,CAAC,KAAK,GAAG,OAAO,CAAC;YAChC,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC;gBAC3D,IAAA,2BAAY,EAAC,MAAM,CAAC,CAAC;YACtB,CAAC;YACD,OAAQ,MAAc,CAAC,KAAK,CAAC;QAC9B,CAAC;QAED,IAAI,MAAA,OAAO,CAAC,OAAO,0CAAE,MAAM,EAAE,CAAC;YAC7B,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC7C,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;oBAChC,OAAO,CAAC,QAAQ,CAAC,IAAA,2BAAY,EAAC,MAAM,CAAC,CAAC,CAAC;gBACxC,CAAC;qBAAM,CAAC;oBACP,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC1B,CAAC;YACF,CAAC;QACF,CAAC;QAED,IAAI,MAAA,OAAO,CAAC,OAAO,0CAAE,aAAa,EAAE,CAAC;YACpC,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;gBACpD,OAAO,CAAC,QAAQ,CAAC,IAAA,2BAAY,EAAC,MAAM,CAAC,CAAC,CAAC;YACxC,CAAC;QACF,CAAC;QAED,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;YAC3B,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QAED,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,+CAAsB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACvG,CAAC;QACD,MAAM,WAAW,GAAI,IAAA,WAAQ,EAAC,cAAc,EAAC,UAAU,CAAC,CAAC;QAEzD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9C,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC/B,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACF,CAAC;QAED,OAAO,OAAO,CAAC;IAChB,CAAC;CACD;AAtGD,sDAsGC"}
+1
View File
@@ -0,0 +1 @@
export {};
+71
View File
@@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const _1 = require("./");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_1 = require("fs");
async function main() {
const width = 400;
const height = 400;
const configuration = {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {},
plugins: [{
id: 'background-colour',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.restore();
}
}]
};
const chartCallback = (ChartJS) => {
ChartJS.defaults.responsive = true;
ChartJS.defaults.maintainAspectRatio = false;
};
console.log('here');
const chartJSNodeCanvas = new _1.ChartJSNodeCanvas({ width, height, chartCallback });
const buffer = await chartJSNodeCanvas.renderToBuffer(configuration);
await fs_1.promises.writeFile('./resources/example.png', buffer, 'base64');
const k = Object.keys(require.cache).find(key => key.includes(path_1.default.join('node_modules', 'chart.js')));
console.log('keys', k);
// const animatedChartJSNodeCanvas = new AnimatedChartJSNodeCanvas({ width, height, chartCallback });
// const buffers = await animatedChartJSNodeCanvas.renderToBuffer(configuration);
// const { Gif } = await import('make-a-gif');
// const gif = new Gif(width, height, 1);
// const totalDuration = 1000;
// const duration = totalDuration / buffers.length;
// await gif.setFrames(buffers.map(buffer => ({ src: new Uint8Array(buffer), duration })));
// // const data = await chartJSNodeCanvas.renderToDataURL(configuration);
// // console.log(data.length);
// const image = await gif.encode();
// await fs.writeFile('./resources/example.gif', image);
}
main();
//# sourceMappingURL=example.js.map
@@ -0,0 +1 @@
{"version":3,"file":"example.js","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":";;;AAAA,yBAAsD;AAGtD,wDAAwB;AACxB,2BAAoC;AAEpC,KAAK,UAAU,IAAI;IAElB,MAAM,KAAK,GAAG,GAAG,CAAC;IAClB,MAAM,MAAM,GAAG,GAAG,CAAC;IACnB,MAAM,aAAa,GAAuB;QACzC,IAAI,EAAE,KAAK;QACX,IAAI,EAAE;YACL,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC;YAC9D,QAAQ,EAAE,CAAC;oBACV,KAAK,EAAE,YAAY;oBACnB,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBAC1B,eAAe,EAAE;wBAChB,yBAAyB;wBACzB,yBAAyB;wBACzB,yBAAyB;wBACzB,yBAAyB;wBACzB,0BAA0B;wBAC1B,yBAAyB;qBACzB;oBACD,WAAW,EAAE;wBACZ,oBAAoB;wBACpB,uBAAuB;wBACvB,uBAAuB;wBACvB,uBAAuB;wBACvB,wBAAwB;wBACxB,uBAAuB;qBACvB;oBACD,WAAW,EAAE,CAAC;iBACd,CAAC;SACF;QACD,OAAO,EAAE,EACR;QACD,OAAO,EAAE,CAAC;gBACT,EAAE,EAAE,mBAAmB;gBACvB,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;oBACrB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;oBACtB,GAAG,CAAC,IAAI,EAAE,CAAC;oBACX,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC;oBACxB,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;oBAClC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACf,CAAC;aACD,CAAC;KACF,CAAC;IACF,MAAM,aAAa,GAAkB,CAAC,OAAO,EAAE,EAAE;QAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC;QACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,GAAG,KAAK,CAAC;IAC9C,CAAC,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEpB,MAAM,iBAAiB,GAAG,IAAI,oBAAiB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;IAClF,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IACrE,MAAM,aAAE,CAAC,SAAS,CAAC,yBAAyB,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEhE,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,cAAc,EAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACrG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEvB,qGAAqG;IACrG,iFAAiF;IACjF,8CAA8C;IAC9C,yCAAyC;IACzC,8BAA8B;IAC9B,mDAAmD;IACnD,2FAA2F;IAC3F,0EAA0E;IAC1E,+BAA+B;IAC/B,oCAAoC;IACpC,wDAAwD;AACzD,CAAC;AACD,IAAI,EAAE,CAAC"}
@@ -0,0 +1 @@
export {};
+21
View File
@@ -0,0 +1,21 @@
"use strict";
// ES Module version of fresh-require
// export const freshImport = async (modulePath: string): Promise<any> => {
// // For ES modules, we need to construct the full URL
// const moduleUrl = new URL(modulePath, import.meta.url).href;
Object.defineProperty(exports, "__esModule", { value: true });
// // Clear module from import cache if possible
// // Note: ES modules caching works differently than CommonJS
// try {
// // Force a new module instance by appending a cache-busting query parameter
// const timestamp = Date.now();
// const urlWithCacheBuster = `${moduleUrl}?cache=${timestamp}`;
// // Dynamic import with cache buster
// const module = await import(/* @vite-ignore */ urlWithCacheBuster);
// return module;
// } catch (error) {
// console.error(`Error importing module ${modulePath}:`, error);
// throw error;
// }
// };
//# sourceMappingURL=freshImport.js.map
@@ -0,0 +1 @@
{"version":3,"file":"freshImport.js","sourceRoot":"","sources":["../src/freshImport.ts"],"names":[],"mappings":";AAAA,qCAAqC;AACrC,2EAA2E;AAC3E,wDAAwD;AACxD,gEAAgE;;AAEhE,iDAAiD;AACjD,+DAA+D;AAC/D,SAAS;AACT,iFAAiF;AACjF,mCAAmC;AACnC,mEAAmE;AAEnE,yCAAyC;AACzC,yEAAyE;AACzE,oBAAoB;AACpB,qBAAqB;AACrB,oEAAoE;AACpE,kBAAkB;AAClB,KAAK;AACL,KAAK"}
@@ -0,0 +1 @@
export declare const freshRequire: (id: string) => any;
+14
View File
@@ -0,0 +1,14 @@
"use strict";
// https://github.com/hughsk/fresh-require
Object.defineProperty(exports, "__esModule", { value: true });
exports.freshRequire = void 0;
const freshRequire = (file) => {
const resolvedFile = require.resolve(file);
const temp = require.cache[resolvedFile];
delete require.cache[resolvedFile];
const modified = require(resolvedFile);
require.cache[resolvedFile] = temp;
return modified;
};
exports.freshRequire = freshRequire;
//# sourceMappingURL=freshRequire.js.map
@@ -0,0 +1 @@
{"version":3,"file":"freshRequire.js","sourceRoot":"","sources":["../src/freshRequire.ts"],"names":[],"mappings":";AAAA,0CAA0C;;;AAEnC,MAAM,YAAY,GAA2C,CAAC,IAAI,EAAE,EAAE;IAE5E,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACvC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;IACnC,OAAO,QAAQ,CAAC;AACjB,CAAC,CAAC;AARW,QAAA,YAAY,gBAQvB"}
+3
View File
@@ -0,0 +1,3 @@
export * from './animatedChartJSNodeCanvas';
export * from './chartJSNodeCanvas';
export * from './chartJSNodeCanvasBase';
+7
View File
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./animatedChartJSNodeCanvas"), exports);
tslib_1.__exportStar(require("./chartJSNodeCanvas"), exports);
tslib_1.__exportStar(require("./chartJSNodeCanvasBase"), exports);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,sEAA4C;AAC5C,8DAAoC;AACpC,kEAAwC"}
+66
View File
@@ -0,0 +1,66 @@
{
"name": "chartjs-node-canvas",
"version": "5.0.0",
"description": "A node renderer for Chart.js using canvas.",
"main": "./dist/index",
"type": "commonjs",
"types": "./dist/index.d.ts",
"scripts": {
"nvm": "cat .nvmrc | nvm use",
"build": "tsc",
"clean": "clean-dest -s ./src -d ./dist --file-map ./scripts/clean-dest --permanent --verbose && echo TODO: Delete .tsbuildinfo?",
"lint": "echo TODO: Add linting",
"test": "c8 --all mocha dist/**/*.spec.js",
"test-unit": "mocha --exclude dist/**/*.e2e.spec.js dist/**/*.spec.js",
"test-e2e": "mocha dist/**/*.e2e.spec.js",
"package-size": "node ./scripts/package-size",
"debug-test": "node --inspect-brk=33295 --nolazy node_modules/mocha/bin/_mocha dist/**/*.spec.js",
"watch-build": "tsc --watch",
"watch-clean": "nodemon --watch ./src -e ts --exec npm run-script clean",
"watch-test": "nodemon --watch ./dist -e js --exec npm run-script test",
"docs": "jsdoc2md dist/index.js > API.md"
},
"bugs": {
"url": "https://github.com/SeanSobey/ChartjsNodeCanvas/issues",
"email": "sean.m.sobey@gmail.com"
},
"author": {
"name": "Sean Sobey",
"email": "sean.m.sobey@gmail.com"
},
"homepage": "https://github.com/SeanSobey/ChartjsNodeCanvas",
"repository": {
"type": "git",
"url": "https://github.com/SeanSobey/ChartjsNodeCanvas.git"
},
"readme": "README.md",
"license": "MIT",
"dependencies": {
"canvas": "^3.1.0",
"tslib": "^2.8.1"
},
"peerDependencies": {
"chart.js": "^4.4.8"
},
"devDependencies": {
"@types/mocha": "^7.0.2",
"@types/node": "^16.10.4",
"@types/offscreencanvas": "^2019.7.3",
"c8": "^7.10.0",
"chart.js": "^4.4.8",
"chartjs-plugin-annotation": "^3.1.0",
"chartjs-plugin-crosshair": "^2.0.0",
"chartjs-plugin-datalabels": "^2.2.0",
"clean-dest": "^1.3.3",
"gifencoder": "^2.0.1",
"jsdoc-to-markdown": "^5.0.3",
"mocha": "^7.2.0",
"nodemon": "^2.0.13",
"release-it": "^14.11.6",
"resemblejs": "^4.0.0",
"source-map-support": "^0.5.20",
"ts-std-lib": "^1.2.2",
"typescript": "^5.7.3",
"wtfnode": "^0.9.1"
}
}
@@ -0,0 +1,111 @@
// @ts-expect-error moduleResolution:nodenext issue 54523
import { Chart as ChartJS, ChartConfiguration } from 'chart.js/auto';
import { ChartJSNodeCanvasBase, MimeType, Canvas } from './chartJSNodeCanvasBase';
const animationFrameProvider: AnimationFrameProvider = {
cancelAnimationFrame: (handle) => clearImmediate(handle as any),
requestAnimationFrame: (callback) => setImmediate(() => callback(Date.now())) as any,
};
type OnProgress = (chart: ChartJS, progress: number, initial: boolean) => void;
type OnComplete = (chart: ChartJS, initial: boolean) => void;
export class AnimatedChartJSNodeCanvas extends ChartJSNodeCanvasBase {
/**
* Render to a data url array.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
public renderToDataURL(configuration: ChartConfiguration, mimeType: MimeType = 'image/png'): Promise<ReadonlyArray<string>> {
const frames: Array<string> = [];
return new Promise((resolve, _reject) => {
this.renderChart(configuration, (chart) => {
const canvas = chart.canvas as Canvas;
if (!canvas) {
throw new Error('Canvas is null');
}
const dataUrl = canvas.toDataURL(mimeType);
frames.push(dataUrl);
}, (chart) => {
resolve(frames);
chart.destroy();
});
});
}
/**
* Render to a buffer.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support) or `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
public renderToBuffer(configuration: ChartConfiguration, mimeType: MimeType = 'image/png'): Promise<ReadonlyArray<Buffer>> {
return new Promise((resolve, _reject) => {
const frames: Array<Buffer> = [];
this.renderChart(configuration, (chart) => {
const canvas = chart.canvas as Canvas;
if (!canvas) {
throw new Error('Canvas is null');
}
const buffer = canvas.toBuffer(mimeType);
frames.push(buffer);
}, (chart) => {
resolve(frames);
chart.destroy();
});
});
}
private renderChart(configuration: ChartConfiguration, onProgress: OnProgress, onComplete: OnComplete): ChartJS {
const canvas = this._createCanvas(this._width, this._height, this._type);
(canvas as any).style = (canvas as any).style || {};
const options = Object.assign({}, configuration.options);
options.responsive = false;
const animation = options.animation || {};
if (!animation.duration) {
animation.duration = 1000;
}
const baseOnProgress = animation.onProgress;
animation.onProgress = (event) => {
const currentStep: number = (event as any).currentStep; // type docs wrong?
const initial = !!(event as any).initial ? (event as any).initial : false; // added around 3.2.x
const progress = currentStep / event.numSteps;
if (baseOnProgress) {
//baseOnComplete(event.chart);
baseOnProgress.call(animation as any, event);
}
onProgress(event.chart, progress, initial);
};
const baseOnComplete = animation.onProgress;
animation.onComplete = (event) => {
const initial = !!(event as any).initial ? (event as any).initial : false; // added around 3.2.x
if (baseOnComplete) {
//baseOnComplete(event.chart);
baseOnComplete.call(animation as any, event);
}
onComplete(event.chart, initial);
};
const plugins = configuration.plugins || [];
const configuredChartConfig = { ...configuration, options, plugins };
global.window = global.window || {};
global.window.requestAnimationFrame = animationFrameProvider.requestAnimationFrame;
global.window.cancelAnimationFrame = animationFrameProvider.cancelAnimationFrame;
const context = canvas.getContext('2d');
(global as any).Image = this._image; // Some plugins use this API
const chart = new this._chartJs((context as any), configuredChartConfig);
delete (global as any).Image;
return chart;
}
}
@@ -0,0 +1,22 @@
// @ts-expect-error moduleResolution:nodenext issue 54523
import { Chart as ChartJS, Plugin as ChartJSPlugin } from 'chart.js/auto';
export class BackgroundColourPlugin implements ChartJSPlugin {
public readonly id: string = 'chartjs-plugin-chartjs-node-canvas-background-colour';
public constructor(
private readonly _width: number,
private readonly _height: number,
private readonly _fillStyle: string
) { }
public beforeDraw(chart: ChartJS): boolean | void {
const ctx = chart.ctx;
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = this._fillStyle;
ctx.fillRect(0, 0, this._width, this._height);
ctx.restore();
}
}
@@ -0,0 +1,140 @@
import { Readable } from 'stream';
// @ts-expect-error moduleResolution:nodenext issue 54523
import { Chart as ChartJS, ChartConfiguration, ChartComponentLike } from 'chart.js/auto';
import { ChartJSNodeCanvasBase, MimeType, Canvas } from './chartJSNodeCanvasBase';
export class ChartJSNodeCanvas extends ChartJSNodeCanvasBase {
/**
* Render to a data url.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType The image format, `image/png` or `image/jpeg`.
*/
public renderToDataURL(configuration: ChartConfiguration, mimeType: MimeType = 'image/png'): Promise<string> {
const chart = this.renderChart(configuration);
return new Promise<string>((resolve, reject) => {
if (!chart.canvas) {
return reject(new Error('Canvas is null'));
}
const canvas = chart.canvas as Canvas;
canvas.toDataURL(mimeType, (error: Error | null, png: string) => {
chart.destroy();
if (error) {
return reject(error);
}
return resolve(png);
});
});
}
/**
* Render to a data url synchronously.
* @see https://github.com/Automattic/node-canvas#canvastodataurl
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType The image format, `image/png` or `image/jpeg`.
*/
public renderToDataURLSync(configuration: ChartConfiguration, mimeType: MimeType = 'image/png'): string {
const chart = this.renderChart(configuration);
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas as Canvas;
const dataUrl = canvas.toDataURL(mimeType);
// Use this to destroy any chart instances that are created.
// This will clean up any references stored to the chart object within Chart.js, along with any associated event listeners attached by Chart.js.
// This must be called before the canvas is reused for a new chart.
chart.destroy();
return dataUrl;
}
/**
* Render to a buffer.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support) or `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
public renderToBuffer(configuration: ChartConfiguration, mimeType: MimeType = 'image/png'): Promise<Buffer> {
const chart = this.renderChart(configuration);
return new Promise<Buffer>((resolve, reject) => {
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas as Canvas;
canvas.toBuffer((error: Error | null, buffer: Buffer) => {
chart.destroy();
if (error) {
return reject(error);
}
return resolve(buffer);
}, mimeType);
});
}
/**
* Render to a buffer synchronously.
* @see https://github.com/Automattic/node-canvas#canvastobuffer
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded ARGB32 data in native-endian byte order, top-to-bottom), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
public renderToBufferSync(configuration: ChartConfiguration, mimeType: MimeType | 'application/pdf' | 'image/svg+xml' = 'image/png'): Buffer {
const chart = this.renderChart(configuration);
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas as Canvas;
const buffer = canvas.toBuffer(mimeType);
chart.destroy();
return buffer;
}
/**
* Render to a stream.
* @see https://github.com/Automattic/node-canvas#canvascreatepngstream
*
* @param configuration The Chart JS configuration for the chart to render.
* @param mimeType A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `application/pdf` (for PDF canvases) and image/svg+xml (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
*/
public renderToStream(configuration: ChartConfiguration, mimeType: MimeType | 'application/pdf' = 'image/png'): Readable {
const chart = this.renderChart(configuration);
if (!chart.canvas) {
throw new Error('Canvas is null');
}
const canvas = chart.canvas as Canvas;
setImmediate(() => chart.destroy());
switch (mimeType) {
case 'image/png':
return canvas.createPNGStream();
case 'image/jpeg':
return canvas.createJPEGStream();
case 'application/pdf':
return canvas.createPDFStream();
default:
throw new Error(`Un-handled mimeType: ${mimeType}`);
}
}
private renderChart(configuration: ChartConfiguration): ChartJS {
const canvas = this._createCanvas(this._width, this._height, this._type);
(canvas as any).style = (canvas as any).style || {};
configuration.options = configuration.options || {};
configuration.options.responsive = false;
// Disable animation (otherwise charts will throw exceptions)
configuration.options.animation = false;
const context = canvas.getContext('2d');
(global as any).Image = this._image; // Some plugins use this API
const chart = new this._chartJs((context as any), configuration);
delete (global as any).Image;
return chart;
}
}
@@ -0,0 +1,169 @@
import { Readable } from 'stream';
// @ts-expect-error moduleResolution:nodenext issue 54523
import { Chart as ChartJS, ChartComponentLike } from 'chart.js/auto';
import { createCanvas, registerFont, Image } from 'canvas';
import { join as pathJoin } from 'path';
import { freshRequire } from './freshRequire';
import { BackgroundColourPlugin } from './backgroundColourPlugin';
export type ChartJSNodeCanvasPlugins = {
/**
* Global plugins, see https://www.chartjs.org/docs/latest/developers/plugins.html.
*/
readonly modern?: ReadonlyArray<string | ChartComponentLike>;
/**
* This will work for plugins that `require` ChartJS themselves.
*/
readonly requireChartJSLegacy?: ReadonlyArray<string>;
/**
* This should work for any plugin that expects a global Chart variable.
*/
readonly globalVariableLegacy?: ReadonlyArray<string>;
/**
* This will work with plugins that just return a plugin object and do no specific loading themselves.
*/
readonly requireLegacy?: ReadonlyArray<string>;
};
export type ChartCallback = (chartJS: typeof ChartJS) => void | Promise<void>;
export type CanvasType = 'pdf' | 'svg';
export type MimeType = 'image/png' | 'image/jpeg';
// https://github.com/Automattic/node-canvas#non-standard-apis
export type Canvas = HTMLCanvasElement & {
toBuffer(callback: (err: Error | null, result: Buffer) => void, mimeType?: string, config?: any): void;
toBuffer(mimeType?: string, config?: any): Buffer;
createPNGStream(config?: any): Readable;
createJPEGStream(config?: any): Readable;
createPDFStream(config?: any): Readable;
};
export interface ChartJSNodeCanvasOptions {
/**
* The width of the charts to render, in pixels.
*/
readonly width: number;
/**
* The height of the charts to render, in pixels.
*/
readonly height: number;
/**
* Optional callback which is called once with a new ChartJS global reference as the only parameter.
*/
readonly chartCallback?: ChartCallback;
/**
* Optional canvas type ('PDF' or 'SVG'), see the [canvas pdf doc](https://github.com/Automattic/node-canvas#pdf-output-support).
*/
readonly type?: CanvasType;
/**
* Optional plugins to register.
*/
readonly plugins?: ChartJSNodeCanvasPlugins;
/**
* Optional background color for the chart, otherwise it will be transparent. Note, this will apply to all charts. See the [fillStyle](https://www.w3schools.com/tags/canvas_fillstyle.asp) canvas API used for possible values.
*/
readonly backgroundColour?: string;
}
export abstract class ChartJSNodeCanvasBase {
protected readonly _width: number;
protected readonly _height: number;
protected readonly _chartJs: typeof ChartJS;
protected readonly _createCanvas: typeof createCanvas;
protected readonly _registerFont: typeof registerFont;
protected readonly _image: typeof Image;
protected readonly _type?: CanvasType;
/**
* Create a new instance of CanvasRenderService.
*
* @param options Configuration for this instance
*/
constructor(options: ChartJSNodeCanvasOptions) {
if (options === null || typeof (options) !== 'object') {
throw new Error('An options parameter object is required');
}
if (!options.width || typeof (options.width) !== 'number') {
throw new Error('A width option is required');
}
if (!options.height || typeof (options.height) !== 'number') {
throw new Error('A height option is required');
}
this._width = options.width;
this._height = options.height;
const canvas = freshRequire('canvas');
this._createCanvas = canvas.createCanvas;
this._registerFont = canvas.registerFont;
this._image = canvas.Image;
this._type = options.type && options.type.toLowerCase() as CanvasType;
this._chartJs = this.initialize(options);
}
/**
* Use to register the font with Canvas to use a font file that is not installed as a system font, this must be done before the Canvas is created.
*
* @param path The path to the font file.
* @param options The font options.
* @example
* registerFont('comicsans.ttf', { family: 'Comic Sans' });
*/
public registerFont(path: string, options: { readonly family: string, readonly weight?: string, readonly style?: string }): void {
this._registerFont(path, options);
}
protected initialize(options: ChartJSNodeCanvasOptions): typeof ChartJS {
const chartJs: typeof ChartJS = require('chart.js/auto');
if (options.plugins?.requireChartJSLegacy) {
for (const plugin of options.plugins.requireChartJSLegacy) {
require(plugin);
delete require.cache[require.resolve(plugin)];
}
}
if (options.plugins?.globalVariableLegacy) {
(global as any).Chart = chartJs;
for (const plugin of options.plugins.globalVariableLegacy) {
freshRequire(plugin);
}
delete (global as any).Chart;
}
if (options.plugins?.modern) {
for (const plugin of options.plugins.modern) {
if (typeof plugin === 'string') {
chartJs.register(freshRequire(plugin));
} else {
chartJs.register(plugin);
}
}
}
if (options.plugins?.requireLegacy) {
for (const plugin of options.plugins.requireLegacy) {
chartJs.register(freshRequire(plugin));
}
}
if (options.chartCallback) {
options.chartCallback(chartJs);
}
if (options.backgroundColour) {
chartJs.register(new BackgroundColourPlugin(options.width, options.height, options.backgroundColour));
}
const chartJsPath = pathJoin('node_modules','chart.js');
for (const key of Object.keys(require.cache)) {
if (key.includes(chartJsPath)) {
delete require.cache[key];
}
}
return chartJs;
}
}
+75
View File
@@ -0,0 +1,75 @@
import { ChartJSNodeCanvas, ChartCallback } from './';
// @ts-expect-error moduleResolution:nodenext issue 54523
import { ChartConfiguration } from 'chart.js/auto';
import path from 'path';
import { promises as fs } from 'fs';
async function main(): Promise<void> {
const width = 400;
const height = 400;
const configuration: ChartConfiguration = {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
},
plugins: [{
id: 'background-colour',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.restore();
}
}]
};
const chartCallback: ChartCallback = (ChartJS) => {
ChartJS.defaults.responsive = true;
ChartJS.defaults.maintainAspectRatio = false;
};
console.log('here');
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, chartCallback });
const buffer = await chartJSNodeCanvas.renderToBuffer(configuration);
await fs.writeFile('./resources/example.png', buffer, 'base64');
const k = Object.keys(require.cache).find(key => key.includes(path.join('node_modules','chart.js')));
console.log('keys', k);
// const animatedChartJSNodeCanvas = new AnimatedChartJSNodeCanvas({ width, height, chartCallback });
// const buffers = await animatedChartJSNodeCanvas.renderToBuffer(configuration);
// const { Gif } = await import('make-a-gif');
// const gif = new Gif(width, height, 1);
// const totalDuration = 1000;
// const duration = totalDuration / buffers.length;
// await gif.setFrames(buffers.map(buffer => ({ src: new Uint8Array(buffer), duration })));
// // const data = await chartJSNodeCanvas.renderToDataURL(configuration);
// // console.log(data.length);
// const image = await gif.encode();
// await fs.writeFile('./resources/example.gif', image);
}
main();
+20
View File
@@ -0,0 +1,20 @@
// ES Module version of fresh-require
// export const freshImport = async (modulePath: string): Promise<any> => {
// // For ES modules, we need to construct the full URL
// const moduleUrl = new URL(modulePath, import.meta.url).href;
// // Clear module from import cache if possible
// // Note: ES modules caching works differently than CommonJS
// try {
// // Force a new module instance by appending a cache-busting query parameter
// const timestamp = Date.now();
// const urlWithCacheBuster = `${moduleUrl}?cache=${timestamp}`;
// // Dynamic import with cache buster
// const module = await import(/* @vite-ignore */ urlWithCacheBuster);
// return module;
// } catch (error) {
// console.error(`Error importing module ${modulePath}:`, error);
// throw error;
// }
// };
+11
View File
@@ -0,0 +1,11 @@
// https://github.com/hughsk/fresh-require
export const freshRequire: /*NodeJS.Require*/ (id: string) => any = (file) => {
const resolvedFile = require.resolve(file);
const temp = require.cache[resolvedFile];
delete require.cache[resolvedFile];
const modified = require(resolvedFile);
require.cache[resolvedFile] = temp;
return modified;
};
+4
View File
@@ -0,0 +1,4 @@
// syntax:
// declare module '<module>';
declare module 'resemblejs'; // Has a 'declare global' that causes typescript to add a '/// <reference types="resemblejs" />'
+3
View File
@@ -0,0 +1,3 @@
export * from './animatedChartJSNodeCanvas';
export * from './chartJSNodeCanvas';
export * from './chartJSNodeCanvasBase';