SeanSobey / ChartjsNodeCanvas

A node renderer for Chart.js using canvas.
MIT License
228 stars 70 forks source link

How to use chartjs-chart-financial #92

Open reynard80 opened 2 years ago

reynard80 commented 2 years ago

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

SeanSobey commented 2 years ago

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.

reynard80 commented 2 years ago

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: {
            ...
        }
    });
SeanSobey commented 2 years ago

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']
    }
}
reynard80 commented 2 years ago

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?

SeanSobey commented 2 years ago

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

reynard80 commented 2 years ago

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
}
SeanSobey commented 2 years ago

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;
};
SuchJitter commented 2 years ago

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

reynard80 commented 2 years ago

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

@SeanSobey, thanks for the help though.

SeanSobey commented 2 years ago

@SuchJitter what issue are you experiencing?

dimisus commented 2 years ago

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
SeanSobey commented 2 years ago

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",
}
mercteil commented 2 years ago

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.

skywalk1411 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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

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