ChartjsNodeCanvas icon indicating copy to clipboard operation
ChartjsNodeCanvas copied to clipboard

How to use chartjs-chart-financial

Open reynard80 opened this issue 4 years ago • 16 comments

Describe the bug How to use an external chart, like chartjs-chart-financial? How to register the 'plugin' with Chartjs?

I tried this:

import { OhlcElement, OhlcController, CandlestickElement, CandlestickController } from 'chartjs-chart-financial'
import Chart from 'chart.js/auto' // Easy way of importing everything

Chart.register(OhlcElement, OhlcController, CandlestickElement, CandlestickController)

However, I get a TypeError: Cannot read property 'register' of undefined.

How should I do this?

Versions

  • NodeJS version: 14.18.1
  • Chart.JS version: 3.6.0
  • Typescript version: 4.4.4

reynard80 avatar Nov 19 '21 14:11 reynard80

Hi there,

Please have a look at the plugin loading docs. Unfortunately, it is a bit confusing and complicated due to having to support all the different loading mechanics, so apologies for that. It is getting better with each release!

You dont load plugins directly as per your code but use the API, try this:

const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, plugins: {
    modern: ['chartjs-chart-financial']
} });

If that does not work try the other plugin loading options.

SeanSobey avatar Nov 19 '21 21:11 SeanSobey

Thanks for your clear reply. That seems to work.

However, I'm using typescript and type: 'candlestick' isn't working; I get a Type '"candlestick"' is not assignable to type 'keyof ChartTypeRegistry'. error. I guess because the type isn't registered yet. How to solve this?

    const imageBuffer = await canvasRenderService.renderToBuffer({
        type: 'candlestick',
        data: {
            ...
        }
    });

reynard80 avatar Nov 20 '21 10:11 reynard80

Hi, glad it worked. This issue is with the Chart.JS API, see their docs for how to resolve this, eg:

declare module 'chart.js' {
	interface ChartTypeRegistry {
		candlestick: ChartTypeRegistry['candlestick']
	}
}

SeanSobey avatar Nov 21 '21 11:11 SeanSobey

Thanks a lot for your support. I got it working, although in the end 'chartjs-chart-financial' doesn't seem to work on node, as I get a window error:

(node:8700) UnhandledPromiseRejectionWarning: ReferenceError: window is not defined
at afterBuildTicks (/home/../node_modules/chartjs-chart-financial/dist/chartjs-chart-financial.min.js:11:2437)
    at callback (/home/../node_modules/chart.js/dist/chart.js:800:15)
    at TimeSeriesScale._callHooks (/home/../node_modules/chart.js/dist/chart.js:5099:5)
    at TimeSeriesScale.afterBuildTicks (/home/../node_modules/chart.js/dist/chart.js:5115:10)
    at TimeSeriesScale.update (/home/../node_modules/chart.js/dist/chart.js:5037:10)
    ...

I discovered this issue.. I guess I have to try with different plugin loading options?

reynard80 avatar Nov 21 '21 18:11 reynard80

It's an 'issue' with chartjs-chart-financial, they are assuming a browser environment instead of nodejs. Looking at the code, seems to be looking for Luxon and I think you can mock the window reference:

const chartJSNodeCanvas = new ChartJSNodeCanvas({
	width, height, plugins: {
		modern: ['chartjs-chart-financial']
	}, chartCallback: () => {
		(global as any).window = (global as any).window || {};
		OR if you are using luxon
		(global as any).window = {
			luxon: freshRequire('luxon') // Or whatever the syntax is
		};
	}
});
const imageBuffer = await chartJSNodeCanvas.renderToBuffer({
	type: 'candlestick',
	data: {
		...
	}
});

I have not tested the above but the concept should be fine and hopefully understandable

SeanSobey avatar Nov 21 '21 21:11 SeanSobey

Thanks!

I still haven't got it working though. I realize this isn't due to your module, but my inexperience with JS. But maybe you're willing to help again.

I'm getting some error with fresh-require: TypeError: Cannot read property 'resolve' of undefined.

Apparently this index.js from fresh-require won't work:

function fresh(file, require) {
  file = require.resolve(file)

  var tmp = require.cache[file]
  delete require.cache[file]

  var mod = require(file)

  require.cache[file] = tmp

  return mod
}

reynard80 avatar Nov 23 '21 20:11 reynard80

It's difficult to provide a definitive fix with just looking at snippets of your code. The working solution will depend on many things, like whether you are using JS modules / typescript, potentially NodeJS version etc. For the above use my lib version of freshRequire, sorry I thought is was exported but it is actually not:

function 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;
};

SeanSobey avatar Nov 26 '21 23:11 SeanSobey

I still have problems with this, did you know find a fix?

SuchJitter avatar Jan 13 '22 17:01 SuchJitter

No, sorry. I finally gave up due to lack of time.

@SeanSobey, thanks for the help though.

reynard80 avatar Jan 14 '22 19:01 reynard80

@SuchJitter what issue are you experiencing?

SeanSobey avatar Jan 19 '22 00:01 SeanSobey

I am trying to integrate the candlestick plugin as well and I wrote a little prototype to save a chart.png to my local fs.

However the chart is empty and I am running out of tries&errors. It is pretty difficult to integrate the candlestick type.

Could you please help me with that? @SeanSobey

import { promises as fs } from 'fs';
import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
import ChartjsChartFinancial from 'chartjs-chart-financial';

function freshRequire(file: string) {
  const resolvedFile = require.resolve(file);
  const temp = require.cache[resolvedFile];
  delete require.cache[resolvedFile];
  // eslint-disable-next-line
  const modified = require(resolvedFile);
  require.cache[resolvedFile] = temp;
  return modified;
}

const render =  async () => {
    const width = 400; //px
    const height = 400; //px
    const chartJSNodeCanvas = new ChartJSNodeCanvas({
      width,
      height,
      backgroundColour: 'white',
      plugins: {
        modern: [ChartjsChartFinancial, require('chartjs-adapter-luxon')],
      },
      chartCallback: () => {
        (global as any).window = (global as any).window || {};
        (global as any).window = {
          luxon: freshRequire('luxon'), // Or whatever the syntax is
        };
      },
    });
    
    const configuration = {
      type: 'candlestick',
      data: {
        datasets: [
          {
            x: 1636069910000,
            o: 1,
            h: 2,
            l: 0.5,
            c: 1.75,
          },
          {
            x: 1636069930000,
            o: 1,
            h: 2,
            l: 0.5,
            c: 1.75,
          },
        ],
      },
      options: {},
    };

    // @ts-ignore because of candlestick type
    const image = await chartJSNodeCanvas.renderToBuffer(configuration);

    const path = process.cwd().concat('/chart.png');

    await fs.writeFile(path, image /* callback will go here */);
}

Unfortunately this is my saved image (no candlesticks, no x-axis dates, y-axis is wrong)

image

dimisus avatar Feb 18 '22 16:02 dimisus

Hi @dimisus,

Yeah, I had a propper look now and unfortunately getting the new(ish) adaptors working is not simple and definitely needs to be addressed in forthcoming versions of this lib. I managed to get it working with the below (at least I think so, based on the image :) ):

example

import { ChartJSNodeCanvas, ChartCallback, freshRequire } from './'; // Change to 'chartjs-node-canvas'
import { ChartConfiguration } from 'chart.js';
import { promises as fs } from 'fs';
import 'chartjs-chart-financial'; // For types only!

const width = 400;
const height = 400;
const configuration: ChartConfiguration = {
	type: 'candlestick',
	data: {
		datasets: [
			{
				label: 'some data',
				data: [
					{
						"x": 1491004800000,
						"o": 30.33,
						"h": 33.33,
						"l": 30.27,
						"c": 31.61
					},
					{
						"x": 1491177600000,
						"o": 32.17,
						"h": 37.09,
						"l": 31.7,
						"c": 33.75
					},
					{
						"x": 1491264000000,
						"o": 34.81,
						"h": 36.73,
						"l": 34.3,
						"c": 36.18
					},
					{
						"x": 1491350400000,
						"o": 36.95,
						"h": 40.58,
						"l": 33.43,
						"c": 37.19
					},
					{
						"x": 1491436800000,
						"o": 37.78,
						"h": 40.17,
						"l": 34.3,
						"c": 38.78
					},
					{
						"x": 1491523200000,
						"o": 38.18,
						"h": 41.52,
						"l": 36.58,
						"c": 37.21
					},
				]
			},
		],
	},
	options: {},
};
const chartJSNodeCanvas = new ChartJSNodeCanvas({
	width, height, plugins: {
		modern: [ 'chartjs-chart-financial' ],
		globalVariableLegacy: [ 'chartjs-adapter-luxon' ]
	}
});

// Needs to run after the constructor but before any render function
(global as any).window = (global as any).window || {};
(global as any).window.luxon = freshRequire('luxon'); // Can just use normal require();

async function main(): Promise<void> {

	const buffer = await chartJSNodeCanvas.renderToBuffer(configuration);
	await fs.writeFile('./example.png', buffer, 'base64');
}
main();

node version: 16.13.0 this lib version: 4.1.6

deps:

{
...
    "chart.js": "^3.5.1",
    canvas": "^2.8.0",
    "chartjs-adapter-luxon": "^1.1.0",
    "chartjs-chart-financial": "^0.1.1",
    "luxon": "^2.3.0",
}

SeanSobey avatar Feb 20 '22 18:02 SeanSobey

I managed it to run properly with everything.

I will follow up shortly with a working example posting here. Thank you for looking into it.

mercteil avatar Feb 20 '22 18:02 mercteil

I managed it to run properly with everything.

I will follow up shortly with a working example posting here. Thank you for looking into it.

any none ts example ?

skywalk1411 avatar Jun 17 '22 21:06 skywalk1411

let data = { "chart": { "result": [{ "meta": { "currency": "USD", "symbol": "TSLA", "exchangeName": "NMS", "instrumentType": "EQUITY", "firstTradeDate": 1277818200, "regularMarketTime": 1655480451, "gmtoffset": -14400, "timezone": "EDT", "exchangeTimezoneName": "America/New_York", "regularMarketPrice": 651.915, "chartPreviousClose": 696.69, "previousClose": 639.3, "scale": 3, "priceHint": 2, "currentTradingPeriod": { "pre": { "timezone": "EDT", "start": 1655452800, "end": 1655472600, "gmtoffset": -14400 }, "regular": { "timezone": "EDT", "start": 1655472600, "end": 1655496000, "gmtoffset": -14400 }, "post": { "timezone": "EDT", "start": 1655496000, "end": 1655510400, "gmtoffset": -14400 } }, "tradingPeriods": { "pre": [[{ "timezone": "EDT", "start": 1654848000, "end": 1654867800, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655107200, "end": 1655127000, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655193600, "end": 1655213400, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655280000, "end": 1655299800, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655366400, "end": 1655386200, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655452800, "end": 1655472600, "gmtoffset": -14400 }]], "post": [[{ "timezone": "EDT", "start": 1654891200, "end": 1654905600, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655150400, "end": 1655164800, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655236800, "end": 1655251200, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655323200, "end": 1655337600, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655409600, "end": 1655424000, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655496000, "end": 1655510400, "gmtoffset": -14400 }]], "regular": [[{ "timezone": "EDT", "start": 1654867800, "end": 1654891200, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655127000, "end": 1655150400, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655213400, "end": 1655236800, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655299800, "end": 1655323200, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655386200, "end": 1655409600, "gmtoffset": -14400 }], [{ "timezone": "EDT", "start": 1655472600, "end": 1655496000, "gmtoffset": -14400 }]] }, "dataGranularity": "1h", "range": "1wk", "validRanges": ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"] }, "timestamp": [1654875000, 1654878600, 1654882200, 1654885800, 1654889400, 1654891200, 1654894800, 1654898400, 1654902000, 1655107200, 1655110800, 1655114400, 1655118000, 1655121600, 1655125200, 1655127000, 1655130600, 1655134200, 1655137800, 1655141400, 1655145000, 1655148600, 1655150400, 1655154000, 1655157600, 1655161200, 1655193600, 1655197200, 1655200800, 1655204400, 1655208000, 1655211600, 1655213400, 1655217000, 1655220600, 1655224200, 1655227800, 1655231400, 1655235000, 1655236800, 1655240400, 1655244000, 1655247600, 1655280000, 1655283600, 1655287200, 1655290800, 1655294400, 1655298000, 1655299800, 1655303400, 1655307000, 1655310600, 1655314200, 1655317800, 1655321400, 1655323200, 1655326800, 1655330400, 1655334000, 1655366400, 1655370000, 1655373600, 1655377200, 1655380800, 1655384400, 1655386200, 1655389800, 1655393400, 1655397000, 1655400600, 1655404200, 1655407800, 1655409600, 1655413200, 1655416800, 1655420400, 1655452800, 1655456400, 1655460000, 1655463600, 1655467200, 1655470800, 1655472600, 1655476200, 1655479800, 1655480451], "indicators": { "quote": [{ "low": [683.739990234375, 685.08837890625, 687.0800170898438, 693.5250244140625, 694.7000122070312, 692, 696.69, 704.01, 705.12, 672.3, 672.5, 673.13, 666.07, 662.21, 665, 644.2100219726562, 645.8101196289062, 658.5, 652.260009765625, 651.280029296875, 644.8499755859375, 644.0499877929688, 647.2, 647.21, 648.11, 642.55, 651.03, 653.06, 650.5, 650.8, 647.04, 650.2, 635.2100219726562, 636.2000122070312, 653.4199829101562, 659.1199951171875, 662.1500244140625, 653.1400146484375, 658.7301025390625, 644.425, 661.11, 661.0701, 664.05, 665.02, 657, 658.57, 656.93, 657.0012, 663.98, 654.4500122070312, 667.239990234375, 673.5, 677.6699829101562, 670.864990234375, 674.2465209960938, 694.219970703125, 650.185, 696.51, 682.0904, 703, 675, 668.59, 671, 669, 666.66, 668.43, 653.3400268554688, 651.0399780273438, 642.4199829101562, 640.6599731445312, 628.6900024414062, 626.0999755859375, 630.1019897460938, 638.13, 637.03, 635.77, 634.25, 638.05, 645.39, 646.4, 647.2, 639.91, 634.1712, 640, 639.5900268554688, 648.60009765625, 651.9154052734375], "high": [693.9400024414062, 692.419921875, 698.1799926757812, 703.4959716796875, 701.9000244140625, 754.03, 704.61, 706, 710, 696.99, 680, 678.78, 678.78, 689.99, 669.5, 679.9000244140625, 666.619873046875, 668.4000244140625, 669.5, 658.8599853515625, 654.8798828125, 650.5499877929688, 695.92, 649.5, 651.95, 648.99, 666, 661.44, 657, 656, 670, 656.92, 656.875, 656.5659790039062, 666.9716796875, 678.989990234375, 672.8681030273438, 670.0499877929688, 665.80517578125, 678.39, 662.67, 669.87, 669.65, 670, 668.8, 661.98, 666.88, 670, 668, 685.6099853515625, 678.3900146484375, 684.9600219726562, 686.2199096679688, 692, 706.9899291992188, 704.969970703125, 713.24, 702.42, 706.55, 706.48, 700, 677.47, 675.59, 675.47, 705.98, 676.98, 675.5, 662.7698974609375, 655.760009765625, 647.22998046875, 646.5599975585938, 635.75, 639.8250122070312, 684.855, 640.35, 639.5, 639.3, 649.77, 648.6, 650, 650, 649.9988, 641.24, 662.908203125, 655.3300170898438, 652.3599853515625, 651.9154052734375], "close": [689.0360717773438, 689.6199951171875, 693.3800048828125, 700.6500244140625, 696.8099975585938, 703.26, 704.205, 705.5, 709.5, 677.19, 676.49, 677.09, 670.3, 667.65, 668.54, 649.3900146484375, 661.8400268554688, 663.3499755859375, 654.010009765625, 652.9749755859375, 645.39501953125, 647.2000122070312, 648.51, 648.3, 648.655, 643.5, 660.03, 655.35, 651.05, 652.64, 650.31, 654.65, 638.7501220703125, 654.6699829101562, 663.7224731445312, 672, 667.875, 659.1298828125, 663.0700073242188, 662.01, 661.2, 665.65, 669.35, 668.5, 660.34, 658.57, 666.88, 665.6, 665.61, 675.510009765625, 675.9000244140625, 684.9302978515625, 684.989990234375, 681.8400268554688, 700.7999877929688, 699, 697.005, 702.18, 705.58, 706.01, 677.13, 673.75, 671, 672, 673.8999, 668.9, 660.9099731445312, 655.1715087890625, 646.77001953125, 644.22998046875, 629.3499755859375, 634.3499755859375, 639.4400024414062, 640, 638.78, 636.2, 635, 646.71, 647.5, 649, 648, 641.25, 635.67, 645.1799926757812, 650.9299926757812, 650.239990234375, 651.9154052734375], "open": [687.2000122070312, 689.0900268554688, 689.5599975585938, 693.5499877929688, 700.6500244140625, 696.81, 703.46, 704.35, 705.5, 688.89, 677.1, 676.49, 677, 670.1, 667.73, 673.125, 649.219970703125, 661.4400024414062, 663.2249755859375, 653.9099731445312, 652.9749755859375, 645.22998046875, 647.2, 648.75, 648.3, 648.95, 663, 660.1, 655.25, 651.17, 652.64, 650.31, 654.8599853515625, 639.0454711914062, 654.7050170898438, 663.7990112304688, 672.151611328125, 667.8800048828125, 659.22998046875, 662.67, 662.02, 661.8, 665.53, 666.1, 668.63, 660.34, 658.97, 666.63, 666, 660, 675.780029296875, 675.969970703125, 684.8250122070312, 684.989990234375, 682.0499877929688, 700.8200073242188, 699, 697.2, 702.01, 705.01, 682.92, 677.47, 673.8, 671.44, 672.02, 673.55, 668.2100219726562, 660.711669921875, 655.2750244140625, 646.7999877929688, 644.47998046875, 629.2100219726562, 634.1799926757812, 639.3, 639.5, 638.99, 636.2, 646, 646.72, 647.5, 649.23, 648, 641, 640.2999877929688, 645.4240112304688, 651, 651.9154052734375], "volume": [0, 2138653, 2656807, 3447561, 2291854, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11718618, 6084947, 3426430, 3116769, 2246924, 3304549, 2841535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9409898, 5131220, 4506320, 4049230, 3092697, 3340868, 2187321, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9571118, 4153034, 3211932, 2908003, 5796322, 8118163, 4059946, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9780837, 4499596, 3608944, 3625349, 4269841, 5128005, 3463778, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10039068, 5079605, 437867, 0] }] } }], "error": null } };

let newData = [];

for (let i = 0; i <= data.chart.result[0].timestamp.length; i++) {
    let temp = {};
    temp.x = data.chart.result[0].timestamp[i];
    temp.o = Number(data.chart.result[0].indicators.quote[0].open[i]);
    temp.h = Number(data.chart.result[0].indicators.quote[0].high[i]);
    temp.l = Number(data.chart.result[0].indicators.quote[0].low[i]);
    temp.c = Number(data.chart.result[0].indicators.quote[0].close[i]);
    newData.push(temp);
}
console.log('data build finished.');
let fs = require("fs");
const { ChartJSNodeCanvas } = require('chartjs-node-canvas');
/*const { ChartJSNodeCanvas, ChartCallback, freshRequire } = require('chartjs-node-canvas');
const { ChartConfiguration } = require('chart.js');*/
require('chartjs-chart-financial');
const width = 400;
const height = 270;
const chartCallback = (ChartJS) => {
    console.log('chart built')
};
const chartJSNodeCanvas = new ChartJSNodeCanvas({
	width, height, plugins: {
		//modern: ['chartjs-chart-financial'],
		globalVariableLegacy: [ 'chartjs-adapter-luxon' ]
	}, chartCallback: () => {
        global.window = global.window || {};
        global.window.luxon = require('luxon');
	}
});
console.log('canvas build finished.');
const createImage = async () => {
    const configuration = {
        type: 'candlestick',
        data:
        {
            datasets: [{
                label: 'test',
                data: newData
            }]
        },
        options: {
            label: {
                display: false
            },
            title: {
                display: false
            },
            legend: {
                display: false
            }
        }
    }
    let imageData = await chartJSNodeCanvas.renderToBuffer(configuration);
    await fs.promises.writeFile("chart.png", imageData);
    console.log('file saved')
};
createImage();``` I have the same problem, chart display but no candlesticks @SeanSobey @mercteil 

skywalk1411 avatar Jun 17 '22 23:06 skywalk1411

nvm it works perfectly, I just had too many candles :3

https://stonkch.art/i/btc-usd

skywalk1411 avatar Jun 18 '22 00:06 skywalk1411