chartjs / chartjs-chart-financial

Chart.js module for charting financial securities
MIT License
721 stars 196 forks source link

Using classic chart from Chart.js and a financial chart: Uncaught TypeError: Class extends value undefined is not a constructor or null #92

Open CreativeWarlock opened 3 years ago

CreativeWarlock commented 3 years ago

Hi mate,

I've run into a problem with employing your financial chart module together with another canvas displaying a chart from chart.js.

The browser console points the "Uncaught TypeError: Class extends value undefined is not a constructor or null" error to line 235 in chartjs-chart-financial.js:

class FinancialController extends Chart.BarController { ... }

Here are two templates being used in node red which are loaded into the front end. Both reference to individual canvasses with different IDs. (As you will see below, I try to destroy any previously existing chart instance)

First script:

<script src="https://cdn.jsdelivr.net/npm/luxon@1.24.1"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.0-beta.9/dist/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@0.2.1"></script>
<script src="../chartjs-chart-financial.js" type="text/javascript"></script>

<div style="width:1000px">
    <canvas id="candleStickChart"></canvas>
</div>

<div>
    Bar Type:
    <select id="type">
        <option value="candlestick" selected>Candlestick</option>
        <option value="ohlc">OHLC</option>
    </select>
    Scale Type:
    <select id="scale-type">
        <option value="linear" selected>Linear</option>
        <option value="logarithmic">Logarithmic</option>
    </select>
    Color Scheme:
    <select id="color-scheme">
        <option value="muted" selected>Muted</option>
        <option value="neon">Neon</option>
    </select>
    Border:
    <select id="border-color">
        <option value="true" selected>Yes</option>
        <option value="false">No</option>
    </select>
    <button id="update">Update</button>
    <button id="randomizeData">Randomize Data</button>
</div>

<script>

(function(scope) {
    var timer = setInterval(function() { //check that the luxon and other libs are loaded, if not wait
        if (!window.luxon) return;

        clearInterval(timer);
        var barCount = 60;
        var initialDateStr = '01 Apr 2017 00:00 Z';

        debugger;

        var ctx = document.getElementById('candleStickChart').getContext('2d');
        ctx.canvas.width = 1000;
        ctx.canvas.height = 250;

        if (chart)
            chart.destroy();

        var chart = new Chart(ctx, {
            type: 'candlestick',
            data: {
                datasets: [{
                    label: 'CHRT - Chart.js Corporation',
                    data: getRandomData(initialDateStr, barCount)
                }]
            }
        });

        var getRandomInt = function(max) {
            return Math.floor(Math.random() * Math.floor(max));
        };

        function randomNumber(min, max) {
            return Math.random() * (max - min) + min;
        }

        function randomBar(date, lastClose) {
            var open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
            var close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
            var high = randomNumber(Math.max(open, close), Math.max(open, close) * 1.1).toFixed(2);
            var low = randomNumber(Math.min(open, close) * 0.9, Math.min(open, close)).toFixed(2);
            return {
                t: date.valueOf(),
                o: open,
                h: high,
                l: low,
                c: close
            };

        }

        function getRandomData(dateStr, count) {
            var date = luxon.DateTime.fromRFC2822(dateStr);
            var data = [randomBar(date, 30)];
            while (data.length < count) {
                date = date.plus({days: 1});
                if (date.weekday <= 5) {
                    data.push(randomBar(date, data[data.length - 1].c));
                }
            }
            return data;
        }

        var update = function() {
            var dataset = chart.config.data.datasets[0];

            // candlestick vs ohlc
            var type = document.getElementById('type').value;
            dataset.type = type;

            // linear vs log
            var scaleType = document.getElementById('scale-type').value;
            chart.config.options.scales.y.type = scaleType;

            // color
            var colorScheme = document.getElementById('color-scheme').value;
            if (colorScheme === 'neon') {
                dataset.color = {
                    up: '#01ff01',
                    down: '#fe0000',
                    unchanged: '#999'
                };
            } else {
                delete dataset.color;
            }

            var border = document.getElementById('border-color').value;
            var defaultOpts = Chart.defaults.elements[type];
            if (border === 'true') {
                dataset.borderColor = defaultOpts.borderColor;
            } else {
                dataset.borderColor = {
                    up: defaultOpts.color.up,
                    down: defaultOpts.color.down,
                    unchanged: defaultOpts.color.up
                };
            }

            chart.update();
        };

        document.getElementById('update').addEventListener('click', update);

        document.getElementById('randomizeData').addEventListener('click', function() {
            chart.data.datasets.forEach(function(dataset) {
                dataset.data = getRandomData(initialDateStr, barCount);
            });
            update();
        });

    }, 100); // close out the setInterval 
})(scope);
</script>

Second script:

<script>
    //(function(scope) {
        var ctx = document.getElementById("avgPriceChangesChart");

        var numberOfSignals = '{{flow.selected_symbol_avgByNumberOfSignals}}';
        var shortestPriceSeries = '{{flow.selected_symbol_shortestPriceSeries}}';
        var omittedPriceSeries = '{{flow.selected_symbol_omittedPriceSeries}}';
        var symbol = '{{flow.selected_symbol}}';

        var demoData = {
            labels: ["D1", "D2", "D3", "D4", "D5", "D6"],
            datasets: [
                {
                    label: "Signal 1",
                    backgroundColor: 'rgb(255, 99, 132)',
                    data: [0.2,-0.4,0.6,0.9,0.4,0.5]
                },
                {
                    label: "Signal 2",
                    backgroundColor: 'rgb(132, 99, 255)',
                    data: [-0.3,0.5,0.6,0.4,0.2,0.6]
                },
                {
                    label: "Signal 3",
                    backgroundColor: 'rgb(132, 255, 99)',
                    data: [0.1,-0.1,0.4,-0.2,0.4,0.8]
                }
            ]
        };

        function createChart(someData) {
        var avgPriceChangesChart = new Chart(ctx, {
            type: "bar",
            data: someData,
            options: {
                animation: {duration: 0},
                title: {
                    responsive: true,
                    display: true,
                    text: "Averaged Price Changes (%) after " + numberOfSignals + "  signals occurred in " + symbol
                            + ".\nShortest price series: " + shortestPriceSeries + "\n"
                            + ".\nOmitted price series (due to <20 price changes): " + omittedPriceSeries + "\n",
                    fontSize: 16,
                    fontColor: 'rgb(255,255,255)'
                },
                legend: {
                    display: true,
                    fontColor: 'white',
                    position: 'top'
                },

                maintainAspectRatio: false,

                scales: {
                    xAxes: [{
                        barPercentage: 1.0,
                        barThickness: 6,    // number (pixels) or 'flex'
                        maxBarThickness: 8, // number (pixels)
                        stacked: false,
                        gridLines: {
                            color: 'rgb(96,96,96)',
                            display: true
                        },
                        ticks: {
                          fontColor: 'white',
                          autoSkip: false
                        }
                    }],
                    yAxes: [{
                        stacked: false,
                        gridLines: {
                            color: 'rgb(96,96,96)',
                            display: true
                        },
                        ticks: {
                            beginAtZero: true,
                            fontColor: 'white',
                            min: -1,
                            max: 1
                        }//,
                        /*type: 'logarithmic'
                        afterBuildTicks: function (chartObj) { //Build ticks labelling as per your need
                            chartObj.ticks = [];
                            chartObj.ticks.push(0.01);
                            chartObj.ticks.push(0.1);
                            chartObj.ticks.push(1);
                            chartObj.ticks.push(10);
                            chartObj.ticks.push(100);
                        }//*/
                    }]
                }
            }
            });

            return avgPriceChangesChart;
        }

        /*if (avgPriceChangesChart !== undefined && Boolean(avgPriceChangesChart)) {
            //alert("Trying to destroy old chart instance...");
            debugger;
            avgPriceChangesChart.destroy();
        }*/

        // for simplicity create a chart with simple demo data only. Nothing fancy.
        avgPriceChangesChart = createChart(demoData); //msg.payload.data);

        /*scope.$watch('msg', function(msg) {
            if (msg) {
                if (avgPriceChangesChart !== undefined && Boolean(avgPriceChangesChart)) {
                    //alert("Trying to destroy old chart instance...");
                    debugger;
                    avgPriceChangesChart.destroy();
                }*/

               /* debugger;
                avgPriceChangesChart = createChart(msg.payload.data);

                //avgPriceChangesChart.data.datasets[0].data[2] = 50; // Would update the first dataset's value of 'March' to be 50
                //avgPriceChangesChart.data = msg.payload.data;
                //avgPriceChangesChart["data"] = msg.payload.data;
                avgPriceChangesChart.update();
            }
        });*/
    //}) (scope);
</script>

The whole stack trace from the console looks as follows:

app.min.js:20 Uncaught TypeError: Class extends value undefined is not a constructor or null
    at <anonymous>:238:41
    at <anonymous>:14:76
    at <anonymous>:15:2
    at b (app.min.js:20)
    at Function.globalEval (app.min.js:20)
    at Object.dataFilter (app.min.js:20)
    at app.min.js:20
    at l (app.min.js:20)
    at XMLHttpRequest.<anonymous> (app.min.js:20)
    at Object.send (app.min.js:20)
.... 
VM2648 chart.js:5161 Uncaught Error: "candlestick" is not a registered controller.
    at Registry._get (VM2648 chart.js:5161)
    at Registry.getController (VM2648 chart.js:5106)
    at Chart.buildOrUpdateControllers (VM2648 chart.js:6188)
    at Chart.update (VM2648 chart.js:6227)
    at new Chart (VM2648 chart.js:5988)
    at <anonymous>:20:21
_get @ VM2648 chart.js:5161
getController @ VM2648 chart.js:5106
buildOrUpdateControllers @ VM2648 chart.js:6188
update @ VM2648 chart.js:6227
Chart @ VM2648 chart.js:5988
(anonymous)
VM2648 chart.js:5945 Uncaught Error: Canvas is already in use. Chart with ID '0' must be destroyed before the canvas can be reused.
    at new Chart (VM2648 chart.js:5945)
    at <anonymous>:20:21
Chart @ VM2648 chart.js:5945
(anonymous)
VM2648 chart.js:5161 Uncaught Error: "candlestick" is not a registered controller.
    at Registry._get (VM2648 chart.js:5161)
    at Registry.getController (VM2648 chart.js:5106)
    at Chart.buildOrUpdateControllers (VM2648 chart.js:6188)
    at Chart.update (VM2648 chart.js:6227)
    at Chart._resize (VM2648 chart.js:6053)
    at Chart.resize (VM2648 chart.js:6027)
    at listener (VM2648 chart.js:6509)
    at VM2648 chart.js:1776
    at VM2648 chart.js:34

I would be very much appreciate any hints to what I'm doing wrong.

Cheers, Marcel