├── .gitignore
├── .npmignore
├── .prettierignore
├── .travis.yml
├── LICENSE
├── README.md
├── babel.config.js
├── examples
├── browser.html
├── custom_chart_example.js
├── image_fill.js
├── short_url_example.js
├── signed_url.js
├── simple_example.js
└── to_file_example.js
├── package.json
├── src
└── index.ts
├── test
├── index.test.ts
└── setupJest.js
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | venv
2 | build
3 |
4 | ### Vim ###
5 | [._]*.s[a-w][a-z]
6 | [._]s[a-w][a-z]
7 | *.un~
8 | Session.vim
9 | .netrwhist
10 | *~
11 |
12 |
13 | ### OSX ###
14 | .DS_Store
15 | .AppleDouble
16 | .LSOverride
17 |
18 | # Icon must end with two \r
19 | Icon
20 |
21 |
22 | # Thumbnails
23 | ._*
24 |
25 | # Files that might appear in the root of a volume
26 | .DocumentRevisions-V100
27 | .fseventsd
28 | .Spotlight-V100
29 | .TemporaryItems
30 | .Trashes
31 | .VolumeIcon.icns
32 |
33 | # Directories potentially created on remote AFP share
34 | .AppleDB
35 | .AppleDesktop
36 | Network Trash Folder
37 | Temporary Items
38 | .apdisk
39 |
40 |
41 | ### Python ###
42 | # Byte-compiled / optimized / DLL files
43 | __pycache__/
44 | *.py[cod]
45 | *$py.class
46 |
47 | # C extensions
48 | *.so
49 |
50 | # Distribution / packaging
51 | .Python
52 | env/
53 | build/
54 | develop-eggs/
55 | dist/
56 | downloads/
57 | eggs/
58 | .eggs/
59 | lib/
60 | lib64/
61 | parts/
62 | sdist/
63 | var/
64 | *.egg-info/
65 | .installed.cfg
66 | *.egg
67 |
68 | # PyInstaller
69 | # Usually these files are written by a python script from a template
70 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
71 | *.manifest
72 | *.spec
73 |
74 | # Installer logs
75 | pip-log.txt
76 | pip-delete-this-directory.txt
77 |
78 | # Unit test / coverage reports
79 | htmlcov/
80 | .tox/
81 | .coverage
82 | .coverage.*
83 | .cache
84 | nosetests.xml
85 | coverage.xml
86 | *,cover
87 |
88 | # Translations
89 | *.mo
90 | *.pot
91 |
92 | # Django stuff:
93 | *.log
94 |
95 | # Sphinx documentation
96 | docs/_build/
97 |
98 | # PyBuilder
99 | target/
100 |
101 |
102 | ### Node ###
103 | # Logs
104 | logs
105 | *.log
106 | npm-debug.log*
107 |
108 | # Runtime data
109 | pids
110 | *.pid
111 | *.seed
112 |
113 | # Directory for instrumented libs generated by jscoverage/JSCover
114 | lib-cov
115 |
116 | # Coverage directory used by tools like istanbul
117 | coverage
118 |
119 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
120 | .grunt
121 |
122 | # node-waf configuration
123 | .lock-wscript
124 |
125 | # Compiled binary addons (http://nodejs.org/api/addons.html)
126 | build/Release
127 |
128 | # Dependency directory
129 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
130 | node_modules
131 |
132 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | test/
3 | examples/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 10
4 | cache: yarn
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Ian Webster
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | QuickChart for Javascript
2 | ---
3 | [](https://www.npmjs.com/package/quickchart-js)
4 | [](https://www.npmjs.com/package/quickchart-js)
5 | [](https://travis-ci.com/typpo/quickchart-js)
6 |
7 | This is a Javascript client for [quickchart.io](https://quickchart.io), a web service for generating static charts. View the main QuickChart repository [here](https://github.com/typpo/quickchart).
8 |
9 | # Installation
10 |
11 | If you are using npm:
12 |
13 | ```
14 | npm install quickchart-js
15 | ```
16 |
17 | # Usage
18 |
19 | This library provides a **QuickChart** object. Import it, instantiate it, and set your [Chart.js](https://www.chartjs.org) config:
20 |
21 | ```js
22 | const QuickChart = require('quickchart-js');
23 |
24 | const myChart = new QuickChart();
25 | myChart.setConfig({
26 | type: 'bar',
27 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
28 | });
29 | ```
30 |
31 | Use `getUrl()` on your quickchart object to get the encoded URL that renders your chart:
32 |
33 | ```js
34 | console.log(myChart.getUrl());
35 | // Prints: https://quickchart.io/chart?c=%7Btype%3A%27bar%27%2Cdata%3A%7Blabels%3A%5B%27Hello+world%27%2C%27Foo+bar%27%5D%2Cdatasets%3A%5B%7Blabel%3A%27Foo%27%2Cdata%3A%5B1%2C2%5D%7D%5D%7D%7D&w=500&h=300&bkg=transparent&f=png
36 | ```
37 |
38 | If you have a large or complicated chart, use `getShortUrl()` on your quickchart object to get a fixed-length URL using the quickchart.io web service:
39 | ```js
40 | const url = await myChart.getShortUrl();
41 | console.log(url);
42 | // Prints: https://quickchart.io/chart/render/f-a1d3e804-dfea-442c-88b0-9801b9808401
43 | ```
44 |
45 | Or write it to disk:
46 | ```js
47 | myChart.toFile('/tmp/mychart.png');
48 | ```
49 |
50 | The URLs produce this chart image:
51 |
52 |
53 |
54 | ## Creating a QuickChart object
55 |
56 | If you have an account ID and API key, authenticate using the QuickChart constructor:
57 |
58 | ```js
59 | const qc = new QuickChart(apiKey, accountId);
60 | ```
61 |
62 | To use the free (community) version, leave it blank:
63 |
64 | ```js
65 | const qc = new QuickChart();
66 | ```
67 |
68 | ## Customizing your chart
69 |
70 | ### setConfig(chart: Object | string)
71 |
72 | Use this config to customize the Chart.js config object that defines your chart. You must set this before generating a URL!
73 |
74 | ### setWidth(width: int)
75 |
76 | Sets the width of the chart in pixels. Defaults to 500.
77 |
78 | ### setHeight(height: int)
79 |
80 | Sets the height of the chart in pixels. Defaults to 300.
81 |
82 | ### setFormat(format: string)
83 |
84 | Sets the format of the chart. Defaults to `png`. `svg` is also valid.
85 |
86 | ### setBackgroundColor(color: string)
87 |
88 | Sets the background color of the chart. Any valid HTML color works. Defaults to `#ffffff` (white). Also takes `rgb`, `rgba`, and `hsl` values.
89 |
90 | ### setDevicePixelRatio(ratio: float)
91 |
92 | Sets the device pixel ratio of the chart. This will multiply the number of pixels by the value. This is usually used for retina displays. Defaults to 1.0.
93 |
94 | ### setVersion(version: string)
95 |
96 | Sets the Chart.js version to use (e.g. `2.9.4` or `3.4.0`). Valid options are shown in the [documentation](https://quickchart.io/documentation/#parameters).
97 |
98 | ### setHost(host: string)
99 |
100 | Sets the host of generated URLs. `quickchart.io` by default.
101 |
102 | ### setScheme(scheme: string)
103 |
104 | Sets the scheme of generated URLs. `https` by default.
105 |
106 | ## Getting outputs
107 |
108 | There are two ways to get a URL for your chart object.
109 |
110 | ### getUrl(): string
111 |
112 | Returns a URL that will display the chart image when loaded.
113 |
114 | ### getShortUrl(): Promise
115 |
116 | Uses the quickchart.io web service to create a fixed-length chart URL that displays the chart image. The Promise resolves with a URL such as `https://quickchart.io/chart/render/f-a1d3e804-dfea-442c-88b0-9801b9808401`.
117 |
118 | Note that short URLs expire after a few days for users of the free service. You can [subscribe](https://quickchart.io/pricing/) to keep them around longer.
119 |
120 | ### getSignedUrl(): string
121 |
122 | Returns a URL that displays the chart image. It is signed with your user account to bypass rate limitations.
123 |
124 | ### toBinary(): Promise
125 |
126 | Creates a binary buffer that contains your chart image.
127 |
128 | ### toDataUrl(): Promise
129 |
130 | Returns a base 64 data URL beginning with `data:image/png;base64`.
131 |
132 | ### toFile(pathOrDescriptor: PathLike | FileHandle): Promise
133 |
134 | Given a filepath string or a writable file handle, creates a file containing your chart image.
135 |
136 | ## More examples
137 |
138 | Check out the `examples/` directory to see other usage. Here's a simple test that uses some of the custom parameters:
139 |
140 | ```js
141 | const qc = new QuickChart();
142 |
143 | qc.setConfig({
144 | type: 'bar',
145 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
146 | });
147 | qc.setWidth(500).setHeight(300).setBackgroundColor('transparent');
148 |
149 | console.log(qc.getUrl());
150 | // https://quickchart.io/chart?c=%7Btype%3A%27bar%27%2Cdata%3A%7Blabels%3A%5B%27Hello+world%27%2C%27Foo+bar%27%5D%2Cdatasets%3A%5B%7Blabel%3A%27Foo%27%2Cdata%3A%5B1%2C2%5D%7D%5D%7D%7D&w=500&h=300&bkg=transparent&f=png
151 | ```
152 |
153 | Here's a more complicated chart that includes some Javascript:
154 |
155 | ```js
156 | qc.setConfig({
157 | type: 'bar',
158 | data: {
159 | labels: ['January', 'February', 'March', 'April', 'May'],
160 | datasets: [
161 | {
162 | label: 'Dogs',
163 | data: [50, 60, 70, 180, 190],
164 | },
165 | ],
166 | },
167 | options: {
168 | scales: {
169 | yAxes: [
170 | {
171 | ticks: {
172 | callback: function (value) {
173 | return '$' + value;
174 | },
175 | },
176 | },
177 | ],
178 | },
179 | },
180 | });
181 | qc.setWidth(500).setHeight(300).setBackgroundColor('#0febc2');
182 |
183 | console.log(qc.getUrl());
184 | // https://quickchart.io/chart?c=%7Btype%3A%27bar%27%2Cdata%3A%7Blabels%3A%5B%27January%27%2C%27February%27%2C%27March%27%2C%27April%27%2C%27May%27%5D%2Cdatasets%3A%5B%7Blabel%3A%27Dogs%27%2Cdata%3A%5B50%2C60%2C70%2C180%2C190%5D%7D%5D%7D%2Coptions%3A%7Bscales%3A%7ByAxes%3A%5B%7Bticks%3A%7Bcallback%3Afunction+%28value%29+%7B%0A++return+%27%24%27+%2B+value%3B%0A%7D%7D%7D%5D%7D%7D%7D&w=500&h=300&bkg=%230febc2&f=png
185 | ```
186 |
187 | As we customize these charts, the URLs are getting a little long for my liking. There's a `getShortUrl` function that uses the QuickChart.io web service to generate a short(er), fixed-length URL:
188 |
189 | ```js
190 | // Fill the chart with data from 0 to 100
191 | const data = [...Array(100).keys()];
192 | qc.setConfig({
193 | type: 'bar',
194 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data }] },
195 | });
196 |
197 | async function printShortUrl() {
198 | const url = await qc.getShortUrl();
199 | console.log(url);
200 | }
201 | printShortUrl();
202 | // https://quickchart.io/chart/render/f-a1d3e804-dfea-442c-88b0-9801b9808401
203 | ```
204 |
205 | ## Using built-in QuickChart functions
206 |
207 | QuickChart has builtin functions: `getImageFill`, `getGradientFill`, `getGradientFillHelper`, and `pattern.draw`. These functions can be accessed via the `QuickChart` class. For example:
208 |
209 | ```js
210 | const qc = new QuickChart();
211 | qc.setConfig({
212 | type: 'bar',
213 | data: {
214 | labels: ['Hello world', 'Foo bar'],
215 | datasets: [
216 | {
217 | label: 'Foo',
218 | data: [1, 2],
219 | backgroundColor: QuickChart.getGradientFillHelper('horizontal', ['red', 'green']),
220 | },
221 | ],
222 | },
223 | });
224 | ```
225 |
226 | # Building the library
227 |
228 | To build this library locally, run:
229 |
230 | ```
231 | yarn build
232 | ```
233 |
234 | To run tests:
235 |
236 | ```
237 | yarn test
238 | ```
239 |
240 | If you're editing the library and running examples, you may want to continuously build the library in the background:
241 |
242 | ```
243 | yarn build:watch
244 |
245 | # ...
246 |
247 | node examples/simple_example.js
248 | ```
249 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
3 | };
4 |
--------------------------------------------------------------------------------
/examples/browser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/custom_chart_example.js:
--------------------------------------------------------------------------------
1 | const QuickChart = require('../build/quickchart.cjs');
2 |
3 | const qc = new QuickChart();
4 |
5 | qc.setConfig({
6 | type: 'bar',
7 | data: {
8 | labels: ['January', 'February', 'March', 'April', 'May'],
9 | datasets: [
10 | {
11 | label: 'Dogs',
12 | data: [50, 60, 70, 180, 190],
13 | },
14 | ],
15 | },
16 | options: {
17 | scales: {
18 | yAxes: [
19 | {
20 | ticks: {
21 | callback: function (value) {
22 | return '$' + value;
23 | },
24 | },
25 | },
26 | ],
27 | },
28 | },
29 | });
30 | qc.setWidth(500).setHeight(300).setBackgroundColor('#0febc2');
31 |
32 | console.log(qc.getUrl());
33 | // https://quickchart.io/chart?c=%7Btype%3A%27bar%27%2Cdata%3A%7Blabels%3A%5B%27January%27%2C%27February%27%2C%27March%27%2C%27April%27%2C%27May%27%5D%2Cdatasets%3A%5B%7Blabel%3A%27Dogs%27%2Cdata%3A%5B50%2C60%2C70%2C180%2C190%5D%7D%5D%7D%2Coptions%3A%7Bscales%3A%7ByAxes%3A%5B%7Bticks%3A%7Bcallback%3Afunction+%28value%29+%7B%0A++return+%27%24%27+%2B+value%3B%0A%7D%7D%7D%5D%7D%7D%7D&w=500&h=300&bkg=%230febc2&f=png
34 |
--------------------------------------------------------------------------------
/examples/image_fill.js:
--------------------------------------------------------------------------------
1 | const QuickChart = require('../build/quickchart.cjs');
2 |
3 | const config = {
4 | type: 'radar',
5 | data: {
6 | labels: ['A', 'B', 'C'],
7 | datasets: [
8 | {
9 | backgroundColor: QuickChart.getImageFill(
10 | 'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569__340.jpg',
11 | ),
12 | borderColor: 'green',
13 | borderWidth: 1,
14 | pointRadius: 0,
15 | data: [1, 2, 3],
16 | },
17 | ],
18 | },
19 | options: {
20 | legend: {
21 | display: false,
22 | },
23 | scale: {
24 | ticks: {
25 | beginAtZero: true,
26 | },
27 | angleLines: {
28 | display: false,
29 | },
30 | pointLabels: {
31 | display: false,
32 | },
33 | },
34 | },
35 | };
36 |
37 | const myChart = new QuickChart();
38 | myChart.setConfig(config);
39 |
40 | myChart.toFile('/tmp/chart.png');
41 |
--------------------------------------------------------------------------------
/examples/short_url_example.js:
--------------------------------------------------------------------------------
1 | const QuickChart = require('../build/quickchart.cjs');
2 |
3 | const qc = new QuickChart();
4 |
5 | // Fill the chart with data from 0 to 100
6 | const data = [...Array(100).keys()];
7 | qc.setConfig({
8 | type: 'bar',
9 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data }] },
10 | });
11 |
12 | // Print the regular URL...
13 | console.log(qc.getUrl());
14 | // https://quickchart.io/chart?c=%7Btype%3A%27bar%27%2Cdata%3A%7Blabels%3A%5B%27Hello+world%27%2C%27Foo+bar%27%5D%2Cdatasets%3A%5B%7Blabel%3A%27Foo%27%2Cdata%3A%5B0%2C1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22%2C23%2C24%2C25%2C26%2C27%2C28%2C29%2C30%2C31%2C32%2C33%2C34%2C35%2C36%2C37%2C38%2C39%2C40%2C41%2C42%2C43%2C44%2C45%2C46%2C47%2C48%2C49%2C50%2C51%2C52%2C53%2C54%2C55%2C56%2C57%2C58%2C59%2C60%2C61%2C62%2C63%2C64%2C65%2C66%2C67%2C68%2C69%2C70%2C71%2C72%2C73%2C74%2C75%2C76%2C77%2C78%2C79%2C80%2C81%2C82%2C83%2C84%2C85%2C86%2C87%2C88%2C89%2C90%2C91%2C92%2C93%2C94%2C95%2C96%2C97%2C98%2C99%5D%7D%5D%7D%7D&w=500&h=300&bkg=%23ffffff&f=png
15 |
16 | // That's a long URL! Maybe we want a shorter version (requires an HTTP request to QuickChart.io)
17 | async function printShortUrl() {
18 | const url = await qc.getShortUrl();
19 | console.log(url);
20 | }
21 | printShortUrl();
22 | // https://quickchart.io/chart/render/f-a1d3e804-dfea-442c-88b0-9801b9808401
23 | // Much shorter and more manageable :)
24 |
--------------------------------------------------------------------------------
/examples/signed_url.js:
--------------------------------------------------------------------------------
1 | const QuickChart = require('../build/quickchart.cjs');
2 |
3 | const qc = new QuickChart('abc123', '12345');
4 |
5 | qc.setConfig({
6 | type: 'bar',
7 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
8 | });
9 | qc.setWidth(500).setHeight(300).setBackgroundColor('transparent');
10 |
11 | console.log(qc.getSignedUrl());
12 | // https://quickchart.io/chart?c=%7Btype%3A%27bar%27%2Cdata%3A%7Blabels%3A%5B%27Hello+world%27%2C%27Foo+bar%27%5D%2Cdatasets%3A%5B%7Blabel%3A%27Foo%27%2Cdata%3A%5B1%2C2%5D%7D%5D%7D%7D&w=500&h=300&bkg=transparent&f=png&v=2.9.4&sig=0c4cf0eb43b200bf523407f403412932cdeccb8d2a9317731ff18c7b887b5c78&accountId=12345
13 |
--------------------------------------------------------------------------------
/examples/simple_example.js:
--------------------------------------------------------------------------------
1 | const QuickChart = require('../build/quickchart.cjs');
2 |
3 | const qc = new QuickChart();
4 |
5 | qc.setConfig({
6 | type: 'bar',
7 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
8 | });
9 | qc.setWidth(500).setHeight(300).setBackgroundColor('transparent');
10 |
11 | console.log(qc.getUrl());
12 | // https://quickchart.io/chart?c=%7Btype%3A%27bar%27%2Cdata%3A%7Blabels%3A%5B%27Hello+world%27%2C%27Foo+bar%27%5D%2Cdatasets%3A%5B%7Blabel%3A%27Foo%27%2Cdata%3A%5B1%2C2%5D%7D%5D%7D%7D&w=500&h=300&bkg=transparent&f=png
13 |
--------------------------------------------------------------------------------
/examples/to_file_example.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | const QuickChart = require('../build/quickchart.cjs');
4 |
5 | const qc = new QuickChart();
6 |
7 | qc.setConfig({
8 | type: 'bar',
9 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
10 | });
11 | qc.setWidth(500).setHeight(300).setBackgroundColor('transparent');
12 |
13 | async function saveChart() {
14 | // Write file to disk
15 | await qc.toFile('/tmp/chart.png');
16 | }
17 | saveChart();
18 |
19 | console.log('Written to /tmp/chart.png');
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quickchart-js",
3 | "version": "3.1.3",
4 | "description": "Javascript client for QuickChart.io",
5 | "main": "build/quickchart.cjs.js",
6 | "module": "build/quickchart.mjs",
7 | "browser": "build/quickchart.js",
8 | "types": "build/typescript/index.d.ts",
9 | "exports": {
10 | ".": {
11 | "require": "./build/quickchart.cjs.js",
12 | "import": "./build/quickchart.mjs",
13 | "types": "./build/typescript/index.d.ts"
14 | }
15 | },
16 | "repository": "https://github.com/typpo/quickchart-js",
17 | "author": "Ian Webster",
18 | "license": "MIT",
19 | "prepublish": "yarn build",
20 | "scripts": {
21 | "test": "jest --testPathIgnorePatterns=examples/",
22 | "format": "prettier --single-quote --trailing-comma all --print-width 100 --write \"**/*.{js,ts}\"",
23 | "build": "tsc && yarn build:browser && yarn build:esm && yarn build:cjs",
24 | "build:browser": "esbuild src/index.ts --bundle --sourcemap --external:fs --external:crypto --target=es2015 --global-name=QuickChart --tsconfig=tsconfig.json --footer:js=\"QuickChart = QuickChart.default\" --outfile=build/quickchart.js",
25 | "build:cjs": "esbuild src/index.ts --format=cjs --sourcemap --tree-shaking=true --platform=node --target=node10.4 --global-name=QuickChart --tsconfig=tsconfig.json --footer:js=\"module.exports = module.exports.default;\" --outfile=build/quickchart.cjs.js",
26 | "build:esm": "esbuild src/index.ts --sourcemap --tree-shaking=true --platform=node --target=node10.4 --global-name=QuickChart --tsconfig=tsconfig.json --format=esm --outfile=build/quickchart.mjs",
27 | "build:watch": "esbuild src/index.ts --watch --sourcemap --tree-shaking=true --platform=node --target=node10.4 --global-name=QuickChart --tsconfig=tsconfig.json --footer:js=\"module.exports = module.exports.default;\" --outfile=build/quickchart.cjs.js",
28 | "prepublishOnly": "yarn build",
29 | "publish": "yarn publish"
30 | },
31 | "resolutions": {
32 | "moment": "^2.29.4"
33 | },
34 | "dependencies": {
35 | "cross-fetch": "^3.1.5",
36 | "javascript-stringify": "^2.1.0"
37 | },
38 | "devDependencies": {
39 | "@babel/preset-env": "^7.16.4",
40 | "@babel/preset-typescript": "^7.16.0",
41 | "@types/chart.js": "^2.9.37",
42 | "@types/jest": "^27.0.3",
43 | "esbuild": "^0.14.1",
44 | "jest": "^27.4.3",
45 | "jest-fetch-mock": "^3.0.3",
46 | "prettier": "^2.3.2",
47 | "typescript": "^4.5.2"
48 | },
49 | "jest": {
50 | "automock": false,
51 | "resetMocks": false,
52 | "setupFiles": [
53 | "./test/setupJest.js"
54 | ]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import fetch from 'cross-fetch';
2 | import { stringify } from 'javascript-stringify';
3 |
4 | import type { PathLike } from 'fs';
5 | import type { FileHandle } from 'fs/promises';
6 | import type { ChartConfiguration } from 'chart.js';
7 | import type { Response } from 'cross-fetch';
8 |
9 | const SPECIAL_FUNCTION_REGEX: RegExp = /['"]__BEGINFUNCTION__(.*?)__ENDFUNCTION__['"]/g;
10 |
11 | const USER_AGENT = `quickchart-js/3.1.0`;
12 |
13 | interface PostData {
14 | chart: string;
15 | width?: number;
16 | height?: number;
17 | format?: string;
18 | version?: string;
19 | backgroundColor?: string;
20 | devicePixelRatio?: number;
21 | key?: string;
22 | }
23 |
24 | interface GradientFillOption {
25 | offset: number;
26 | color: string;
27 | }
28 |
29 | interface GradientDimensionOption {
30 | width?: number;
31 | height?: number;
32 | }
33 |
34 | function doStringify(chartConfig: ChartConfiguration): string | undefined {
35 | const str = stringify(chartConfig);
36 | if (!str) {
37 | return undefined;
38 | }
39 | return str.replace(SPECIAL_FUNCTION_REGEX, '$1');
40 | }
41 |
42 | function postJson(url: string, payload: PostData): Promise {
43 | return fetch(url, {
44 | method: 'POST',
45 | headers: {
46 | 'User-Agent': USER_AGENT,
47 | 'Content-Type': 'application/json',
48 | },
49 | body: JSON.stringify(payload),
50 | });
51 | }
52 |
53 | class QuickChart {
54 | private host: string;
55 | private scheme: string;
56 | private width: number;
57 | private height: number;
58 | private devicePixelRatio: number;
59 | private backgroundColor: string;
60 | private format: string;
61 | private version: string;
62 |
63 | private chart?: string;
64 | private apiKey?: string;
65 | private accountId?: string;
66 |
67 | constructor(apiKey?: string, accountId?: string) {
68 | this.apiKey = apiKey;
69 | this.accountId = accountId;
70 |
71 | this.host = 'quickchart.io';
72 | this.scheme = 'https';
73 |
74 | this.chart = undefined;
75 | this.width = 500;
76 | this.height = 300;
77 | this.devicePixelRatio = 1.0;
78 | this.backgroundColor = '#ffffff';
79 | this.format = 'png';
80 | this.version = '2.9.4';
81 | }
82 |
83 | setConfig(chartConfig: string | ChartConfiguration): QuickChart {
84 | this.chart = typeof chartConfig === 'string' ? chartConfig : doStringify(chartConfig);
85 | return this;
86 | }
87 |
88 | setHost(host: string): QuickChart {
89 | this.host = host;
90 | return this;
91 | }
92 |
93 | setScheme(scheme: string): QuickChart {
94 | this.scheme = scheme;
95 | return this;
96 | }
97 |
98 | setWidth(width: number): QuickChart {
99 | this.width = width;
100 | return this;
101 | }
102 |
103 | setHeight(height: number): QuickChart {
104 | this.height = height;
105 | return this;
106 | }
107 |
108 | setBackgroundColor(color: string): QuickChart {
109 | this.backgroundColor = color;
110 | return this;
111 | }
112 |
113 | setDevicePixelRatio(ratio: number): QuickChart {
114 | this.devicePixelRatio = ratio;
115 | return this;
116 | }
117 |
118 | setFormat(fmt: string): QuickChart {
119 | this.format = fmt;
120 | return this;
121 | }
122 |
123 | setVersion(version: string): QuickChart {
124 | this.version = version;
125 | return this;
126 | }
127 |
128 | isValid(): boolean {
129 | if (!this.chart) {
130 | return false;
131 | }
132 | return true;
133 | }
134 |
135 | private getBaseUrl(): string {
136 | return `${this.scheme}://${this.host}`;
137 | }
138 |
139 | private getUrlObject(): URL {
140 | if (!this.isValid()) {
141 | throw new Error('You must call setConfig before getUrl');
142 | }
143 | const ret = new URL(`${this.getBaseUrl()}/chart`);
144 | ret.searchParams.append('c', this.chart!);
145 | ret.searchParams.append('w', String(this.width));
146 | ret.searchParams.append('h', String(this.height));
147 | ret.searchParams.append('ref', 'qc-js');
148 | if (this.devicePixelRatio !== 1.0) {
149 | ret.searchParams.append('devicePixelRatio', String(this.devicePixelRatio));
150 | }
151 | if (this.backgroundColor) {
152 | ret.searchParams.append('bkg', this.backgroundColor);
153 | }
154 | if (this.format) {
155 | ret.searchParams.append('f', this.format);
156 | }
157 | if (this.version) {
158 | ret.searchParams.append('v', this.version);
159 | }
160 | if (this.apiKey) {
161 | ret.searchParams.append('key', this.apiKey);
162 | }
163 | return ret;
164 | }
165 |
166 | getUrl(): string {
167 | return this.getUrlObject().href;
168 | }
169 |
170 | getSignedUrl(): string {
171 | if (!this.accountId || !this.apiKey) {
172 | throw new Error(
173 | 'You must set accountId and apiKey in the QuickChart constructor to use getSignedUrl()',
174 | );
175 | }
176 | const crypto = require('crypto');
177 | const urlObj = this.getUrlObject();
178 | const chartStr = urlObj.searchParams.get('c');
179 |
180 | const signature = crypto.createHmac('sha256', this.apiKey).update(chartStr).digest('hex');
181 | urlObj.searchParams.append('sig', signature);
182 | urlObj.searchParams.append('accountId', this.accountId);
183 | urlObj.searchParams.delete('key');
184 | return urlObj.href;
185 | }
186 |
187 | getPostData(): PostData {
188 | if (!this.isValid()) {
189 | throw new Error('You must call setConfig creating post data');
190 | }
191 |
192 | const { width, height, chart, format, version, backgroundColor, devicePixelRatio, apiKey } =
193 | this;
194 | const postData: PostData = {
195 | width,
196 | height,
197 | chart: chart!,
198 | };
199 | if (format) {
200 | postData.format = format;
201 | }
202 | if (version) {
203 | postData.version = version;
204 | }
205 | if (backgroundColor) {
206 | postData.backgroundColor = backgroundColor;
207 | }
208 | if (devicePixelRatio) {
209 | postData.devicePixelRatio = devicePixelRatio;
210 | }
211 | if (apiKey) {
212 | postData.key = apiKey;
213 | }
214 | return postData;
215 | }
216 |
217 | async getShortUrl(): Promise {
218 | if (!this.isValid()) {
219 | throw new Error('You must call setConfig before getUrl');
220 | }
221 | if (this.host !== 'quickchart.io') {
222 | throw new Error('Short URLs must use quickchart.io host');
223 | }
224 |
225 | const resp = await postJson(`${this.getBaseUrl()}/chart/create`, this.getPostData());
226 | if (!resp.ok) {
227 | const quickchartError = resp.headers.get('x-quickchart-error');
228 | const details = quickchartError ? `\n${quickchartError}` : '';
229 | throw new Error(`Chart shorturl creation failed with status code ${resp.status}${details}`);
230 | }
231 |
232 | const json = (await resp.json()) as undefined | { success?: boolean; url?: string };
233 | if (!json || !json.success || !json.url) {
234 | throw new Error('Received failure response from chart shorturl endpoint');
235 | } else {
236 | return json.url;
237 | }
238 | }
239 |
240 | async toBinary(): Promise {
241 | if (!this.isValid()) {
242 | throw new Error('You must call setConfig before getUrl');
243 | }
244 |
245 | const resp = await postJson(`${this.getBaseUrl()}/chart`, this.getPostData());
246 | if (!resp.ok) {
247 | const quickchartError = resp.headers.get('x-quickchart-error');
248 | const details = quickchartError ? `\n${quickchartError}` : '';
249 | throw new Error(`Chart creation failed with status code ${resp.status}${details}`);
250 | }
251 | const data = await resp.arrayBuffer();
252 | return Buffer.from(data);
253 | }
254 |
255 | async toDataUrl(): Promise {
256 | const buf = await this.toBinary();
257 | const b64buf = buf.toString('base64');
258 | const type = this.format === 'svg' ? 'svg+xml' : 'png';
259 | return `data:image/${type};base64,${b64buf}`;
260 | }
261 |
262 | async toFile(pathOrDescriptor: PathLike | FileHandle): Promise {
263 | const fs = require('fs');
264 | const buf = await this.toBinary();
265 | fs.writeFileSync(pathOrDescriptor, buf);
266 | }
267 |
268 | static getGradientFillHelper(
269 | direction: string,
270 | colors: string[],
271 | dimensions?: GradientDimensionOption,
272 | ): string {
273 | return `__BEGINFUNCTION__getGradientFillHelper(${JSON.stringify(direction)}, ${JSON.stringify(
274 | colors,
275 | )}, ${JSON.stringify(dimensions)})__ENDFUNCTION__`;
276 | }
277 |
278 | static getGradientFill(
279 | colorOptions: GradientFillOption[],
280 | linearGradient: [number, number, number, number],
281 | ): string {
282 | return `__BEGINFUNCTION__getGradientFill(${JSON.stringify(colorOptions)}, ${JSON.stringify(
283 | linearGradient,
284 | )})__ENDFUNCTION__`;
285 | }
286 |
287 | static getImageFill(url: string): string {
288 | return `__BEGINFUNCTION__getImageFill(${JSON.stringify(url)})__ENDFUNCTION__`;
289 | }
290 |
291 | static pattern = {
292 | draw: function (
293 | shapeType: string,
294 | backgroundColor: string,
295 | patternColor: string,
296 | requestedSize: number,
297 | ): string {
298 | return `__BEGINFUNCTION__pattern.draw(${JSON.stringify(shapeType)}, ${JSON.stringify(
299 | backgroundColor,
300 | )}, ${JSON.stringify(patternColor)}, ${JSON.stringify(requestedSize)})__ENDFUNCTION__`;
301 | },
302 | };
303 | }
304 |
305 | export default QuickChart;
306 |
--------------------------------------------------------------------------------
/test/index.test.ts:
--------------------------------------------------------------------------------
1 | import fetchMock from 'jest-fetch-mock';
2 |
3 | import QuickChart from '../src/index';
4 |
5 | test('basic chart, no auth', () => {
6 | const qc = new QuickChart();
7 | qc.setConfig({
8 | type: 'bar',
9 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
10 | });
11 |
12 | expect(qc.getUrl()).toContain('Hello+world');
13 | expect(qc.getUrl()).toContain('/chart?');
14 | expect(qc.getUrl()).toContain('w=500');
15 | expect(qc.getUrl()).toContain('h=300');
16 | });
17 |
18 | test('basic chart with custom host', () => {
19 | const qc = new QuickChart();
20 | qc.setHost('foo.com');
21 | qc.setScheme('http');
22 | qc.setConfig({
23 | type: 'bar',
24 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
25 | });
26 |
27 | expect(qc.getUrl()).toContain('http://foo.com/chart?');
28 | expect(qc.getUrl()).toContain('Hello+world');
29 | expect(qc.getUrl()).toContain('w=500');
30 | expect(qc.getUrl()).toContain('h=300');
31 | });
32 |
33 | test('basic chart with auth', () => {
34 | const qc = new QuickChart('abc123', '12345');
35 | qc.setConfig({
36 | type: 'bar',
37 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
38 | });
39 |
40 | expect(qc.getUrl()).toContain('Hello+world');
41 | expect(qc.getUrl()).toContain('/chart?');
42 | expect(qc.getUrl()).toContain('w=500');
43 | expect(qc.getUrl()).toContain('h=300');
44 | expect(qc.getUrl()).toContain('key=abc123');
45 | });
46 |
47 | test('basic chart with auth, signed', () => {
48 | const qc = new QuickChart('abc123', '12345');
49 | qc.setConfig({
50 | type: 'bar',
51 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
52 | });
53 |
54 | const url = qc.getSignedUrl();
55 | expect(url).toContain('Hello+world');
56 | expect(url).toContain('/chart?');
57 | expect(url).toContain('w=500');
58 | expect(url).toContain('h=300');
59 |
60 | expect(url).not.toContain('key=');
61 | expect(url).toContain('accountId=12345');
62 | expect(url).toContain('sig=');
63 | });
64 |
65 | test('basic chart, string', () => {
66 | const qc = new QuickChart();
67 | qc.setConfig(`{
68 | type: 'bar',
69 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
70 | }`);
71 |
72 | expect(qc.getUrl()).toContain('Hello+world');
73 | expect(qc.getUrl()).toContain('/chart?');
74 | expect(qc.getUrl()).toContain('w=500');
75 | expect(qc.getUrl()).toContain('h=300');
76 | });
77 |
78 | test('basic chart with gradient', () => {
79 | const qc = new QuickChart();
80 | qc.setConfig({
81 | type: 'bar',
82 | data: {
83 | labels: ['Hello world', 'Foo bar'],
84 | datasets: [
85 | {
86 | label: 'Foo',
87 | data: [1, 2],
88 | backgroundColor: QuickChart.getGradientFillHelper('horizontal', ['red', 'green']),
89 | },
90 | ],
91 | },
92 | });
93 |
94 | expect(qc.getUrl()).toContain('Hello+world');
95 | expect(qc.getUrl()).toContain('/chart?');
96 | expect(qc.getUrl()).toContain('w=500');
97 | expect(qc.getUrl()).toContain('h=300');
98 | expect(qc.getUrl()).toContain('getGradientFillHelper');
99 | });
100 |
101 | test('basic chart, width and height', () => {
102 | const qc = new QuickChart();
103 | qc.setConfig({
104 | type: 'bar',
105 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
106 | });
107 |
108 | qc.setWidth(800).setHeight(500);
109 |
110 | expect(qc.getUrl()).toContain('Hello+world');
111 | expect(qc.getUrl()).toContain('w=800');
112 | expect(qc.getUrl()).toContain('h=500');
113 | });
114 |
115 | test('basic chart, other params', () => {
116 | const qc = new QuickChart();
117 | qc.setConfig({
118 | type: 'bar',
119 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
120 | });
121 |
122 | qc.setBackgroundColor('#000000').setDevicePixelRatio(2.0).setFormat('svg').setVersion('3');
123 |
124 | expect(qc.getUrl()).toContain('Hello+world');
125 | expect(qc.getUrl()).toContain('devicePixelRatio=2');
126 | expect(qc.getUrl()).toContain('f=svg');
127 | expect(qc.getUrl()).toContain('bkg=%23000000');
128 | expect(qc.getUrl()).toContain('v=3');
129 | });
130 |
131 | test('js chart', () => {
132 | const qc = new QuickChart();
133 | qc.setConfig({
134 | type: 'bar',
135 | data: {
136 | labels: ['January', 'February', 'March', 'April', 'May'],
137 | datasets: [
138 | {
139 | label: 'Dogs',
140 | data: [50, 60, 70, 180, 190],
141 | },
142 | ],
143 | },
144 | options: {
145 | scales: {
146 | yAxes: [
147 | {
148 | ticks: {
149 | // @ts-ignore
150 | callback: function (value) {
151 | return '$' + value;
152 | },
153 | },
154 | },
155 | ],
156 | },
157 | },
158 | });
159 |
160 | expect(qc.getUrl()).toContain('Dogs');
161 | expect(qc.getUrl()).toContain('callback%3Afunction+%28value');
162 | });
163 |
164 | test('postdata for basic chart, no auth', () => {
165 | const qc = new QuickChart();
166 | qc.setConfig({
167 | type: 'bar',
168 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
169 | });
170 |
171 | const postData = qc.getPostData();
172 | expect(postData.chart).toContain('Hello world');
173 | expect(postData.width).toEqual(500);
174 | expect(postData.height).toEqual(300);
175 | expect(postData.format).toEqual('png');
176 | expect(postData.backgroundColor).toEqual('#ffffff');
177 | expect(postData.devicePixelRatio).toBeCloseTo(1);
178 | });
179 |
180 | test('postdata for basic chart with auth', () => {
181 | const qc = new QuickChart('abc123', '12345');
182 | qc.setConfig({
183 | type: 'bar',
184 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
185 | });
186 |
187 | const postData = qc.getPostData();
188 | expect(postData.chart).toContain('Hello world');
189 | expect(postData.width).toEqual(500);
190 | expect(postData.height).toEqual(300);
191 | expect(postData.format).toEqual('png');
192 | expect(postData.backgroundColor).toEqual('#ffffff');
193 | expect(postData.devicePixelRatio).toBeCloseTo(1);
194 | expect(postData.key).toEqual('abc123');
195 | });
196 |
197 | test('postdata for basic chart with params', () => {
198 | const qc = new QuickChart();
199 | qc.setConfig({
200 | type: 'bar',
201 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
202 | });
203 |
204 | qc.setWidth(400)
205 | .setHeight(200)
206 | .setFormat('svg')
207 | .setBackgroundColor('transparent')
208 | .setDevicePixelRatio(2.0);
209 |
210 | const postData = qc.getPostData();
211 | expect(postData.chart).toContain('Hello world');
212 | expect(postData.width).toEqual(400);
213 | expect(postData.height).toEqual(200);
214 | expect(postData.format).toEqual('svg');
215 | expect(postData.backgroundColor).toEqual('transparent');
216 | expect(postData.devicePixelRatio).toBeCloseTo(2);
217 | });
218 |
219 | test('postdata for js chart', () => {
220 | const qc = new QuickChart();
221 | qc.setConfig({
222 | type: 'bar',
223 | data: {
224 | labels: ['January', 'February', 'March', 'April', 'May'],
225 | datasets: [
226 | {
227 | label: 'Dogs',
228 | data: [50, 60, 70, 180, 190],
229 | },
230 | ],
231 | },
232 | options: {
233 | scales: {
234 | yAxes: [
235 | {
236 | ticks: {
237 | // @ts-ignore
238 | callback: function (value) {
239 | return '$' + value;
240 | },
241 | },
242 | },
243 | ],
244 | },
245 | },
246 | });
247 |
248 | qc.setWidth(400)
249 | .setHeight(200)
250 | .setFormat('svg')
251 | .setBackgroundColor('transparent')
252 | .setDevicePixelRatio(2.0);
253 |
254 | const postData = qc.getPostData();
255 | expect(postData.chart).toContain('callback:function (val');
256 | expect(postData.width).toEqual(400);
257 | expect(postData.height).toEqual(200);
258 | expect(postData.format).toEqual('svg');
259 | expect(postData.backgroundColor).toEqual('transparent');
260 | expect(postData.devicePixelRatio).toBeCloseTo(2);
261 | });
262 |
263 | test('getShortUrl for chart, no auth', async () => {
264 | const mockResp = {
265 | success: true,
266 | url: 'https://quickchart.io/chart/render/9a560ba4-ab71-4d1e-89ea-ce4741e9d232',
267 | };
268 | fetchMock.mockResponseOnce(JSON.stringify(mockResp));
269 |
270 | const qc = new QuickChart();
271 | qc.setConfig({
272 | type: 'bar',
273 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
274 | });
275 |
276 | await expect(qc.getShortUrl()).resolves.toEqual(mockResp.url);
277 | });
278 |
279 | test('getShortUrl for chart js error', async () => {
280 | fetchMock.mockResponseOnce(() => {
281 | throw new Error('Request timed out');
282 | });
283 |
284 | const qc = new QuickChart();
285 | qc.setConfig({
286 | type: 'bar',
287 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
288 | });
289 |
290 | await expect(qc.getShortUrl()).rejects.toThrow('Request timed out');
291 | });
292 |
293 | test('getShortUrl for chart bad status code', async () => {
294 | fetchMock.mockResponseOnce('', {
295 | status: 502,
296 | });
297 |
298 | const qc = new QuickChart();
299 | qc.setConfig({
300 | type: 'bar',
301 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
302 | });
303 |
304 | await expect(qc.getShortUrl()).rejects.toThrow('failed with status code');
305 | });
306 |
307 | test('getShortUrl for chart bad status code with error detail', async () => {
308 | fetchMock.mockResponseOnce('', {
309 | status: 400,
310 | headers: {
311 | 'x-quickchart-error': 'foo bar',
312 | },
313 | });
314 |
315 | const qc = new QuickChart();
316 | qc.setConfig({
317 | type: 'bar',
318 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
319 | });
320 |
321 | await expect(qc.getShortUrl()).rejects.toThrow('foo bar');
322 | });
323 |
324 | test('getShortUrl api failure', async () => {
325 | fetchMock.mockResponseOnce(
326 | JSON.stringify({
327 | success: false,
328 | }),
329 | );
330 |
331 | const qc = new QuickChart();
332 | qc.setConfig({
333 | type: 'bar',
334 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
335 | });
336 |
337 | await expect(qc.getShortUrl()).rejects.toThrow('failure response');
338 | expect(fetch).toHaveBeenCalled();
339 | });
340 |
341 | test('toBinary, no auth', async () => {
342 | const mockData = Buffer.from('bWVvdw==', 'base64');
343 | // https://github.com/jefflau/jest-fetch-mock/issues/218
344 | fetchMock.mockResponseOnce(() => Promise.resolve({ body: mockData as unknown as string }));
345 |
346 | const qc = new QuickChart();
347 | qc.setConfig({
348 | type: 'bar',
349 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
350 | });
351 |
352 | await expect(qc.toBinary()).resolves.toEqual(mockData);
353 | });
354 |
355 | test('toDataUrl, no auth', async () => {
356 | fetchMock.mockResponseOnce(() =>
357 | Promise.resolve({ body: Buffer.from('bWVvdw==', 'base64') as unknown as string }),
358 | );
359 |
360 | const qc = new QuickChart();
361 | qc.setConfig({
362 | type: 'bar',
363 | data: { labels: ['Hello world', 'Foo bar'], datasets: [{ label: 'Foo', data: [1, 2] }] },
364 | });
365 |
366 | await expect(qc.toDataUrl()).resolves.toEqual('data:image/png;base64,bWVvdw==');
367 | });
368 |
369 | test('no chart specified throws error', async () => {
370 | const qc = new QuickChart();
371 | expect(() => {
372 | qc.getUrl();
373 | }).toThrow();
374 | });
375 |
--------------------------------------------------------------------------------
/test/setupJest.js:
--------------------------------------------------------------------------------
1 | const fetchMock = require('jest-fetch-mock');
2 | fetchMock.enableMocks();
3 | jest.setMock('cross-fetch', fetchMock);
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build/typescript",
4 | "lib": ["es2015", "dom"],
5 | "declaration": true,
6 | "noUnusedLocals": false,
7 | "noImplicitAny": true,
8 | "strictNullChecks": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitOverride": true,
14 | "alwaysStrict": true,
15 | "esModuleInterop": true
16 | },
17 | "include": ["src/**/*"]
18 | }
--------------------------------------------------------------------------------