artus9033 / chartjs-plugin-dragdata

Draggable data points plugin for Chart.js
MIT License
265 stars 55 forks source link

using this plugin with Angular 15 #121

Closed pedromoto4 closed 1 month ago

pedromoto4 commented 1 year ago

Describe the bug I cant add this plugin data in the ChartOptions structure I receive this error. I'm not sure if im doing this the right way but I hope anyone can help me, the true is that to angular 15 with this plugin I cant find any example :/

Captura de ecrã 2023-04-28, às 02 04 00

this is my package "@angular/animations": "^15.2.0", "@angular/common": "^15.2.0", "@angular/compiler": "^15.2.0", "@angular/core": "^15.2.0", "@angular/forms": "^15.2.0", "@angular/platform-browser": "^15.2.0", "@angular/platform-browser-dynamic": "^15.2.0", "@angular/router": "^15.2.0", "chart.js": "^3.9.1", "chartjs-plugin-dragdata": "^2.2.5", "ng2-charts": "^4.1.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.12.0" the html <canvas baseChart width="400" height="400" [type]="'line'" [data]="lineChartData" [options]="lineChartOptions" [legend]="lineChartLegend">

xyz.component.ts import { Component } from '@angular/core'; import { ChartConfiguration, ChartOptions, ChartType } from "chart.js"; import 'chartjs-plugin-dragdata' ;

@Component({ selector: 'app-xyz', templateUrl: './xyz.component.html', styleUrls: ['./xyz.component.scss'] }) export class XyzComponent {

title = 'ng2-charts-demo';

public lineChartData: ChartConfiguration<'line'>['data'] = { labels: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July' ], datasets: [ { data: [ 65, 59, 80, 81, 56, 55, 40 ], label: 'Series A', fill: true, tension: 0.5, borderColor: 'black', backgroundColor: 'rgba(255,0,0,0.3)', } ] };

public lineChartOptions: ChartOptions<'line'> = { responsive: false, plugins:{ dragData: { round: 1, showTooltip: true, onDragStart: function(e) { // console.log(e) }, onDrag: function(e, datasetIndex, index, value) {
e.target.style.cursor = 'grabbing' if (value < 0) return false // console.log(e, datasetIndex, index, value) }, onDragEnd: function(e, datasetIndex, index, value) { e.target.style.cursor = 'default' // console.log(datasetIndex, index, value) }, } } }; public lineChartLegend = true;

constructor( ){ } }

CSorel-Catalyte commented 1 year ago

Did you find a solution to this? I'm having the same issue.

artus9033 commented 8 months ago

@pedromoto4 @CSorel-Catalyte this issue originates from a lack of proper typings - the global chart.js's typings need to be augmented to accept the plugin's configuration. Migration to TS (which is going to happen in the near future) will solve this.

death7654 commented 4 months ago

is this issue resolved?

Megaemce commented 2 months ago

The issue is not sorted as the TypeScript version is still not online. My way around this issue is like so:

import { ChartOptions } from "chart.js";

type RadarOptionsType = ChartOptions<"radar"> & {
    plugins?: {
        dragData?: {
            round?: number;
            showTooltip?: boolean;
            onDragStart?: (e: MouseEvent, element: any) => boolean | void;
            onDrag?: (
                e: MouseEvent,
                datasetIndex: number,
                index: number,
                value: number
            ) => boolean | void;
            onDragEnd?: (e: MouseEvent) => void;
        };
    };
};

const options: RadarOptionsType = {
    layout: {
        // layout options here
    },
    scales: {
       // scale options here
    },
    plugins: {
        // other plugins options here
        dragData: {
            round: 0,
            showTooltip: false,
            onDragStart: (e: MouseEvent, element: any) => { 
                                  // onDragStart body here
            },
            onDrag: (e: MouseEvent, datasetIndex: number, index: number, value: number) => {
                                 // onDrag body here
            },
            onDragEnd: (e: MouseEvent) { 
                                 // onDragEnd body here
            },
        },
    },
    // other options here
};

and then

import { Radar } from "react-chartjs-2";
import { Chart } from "chart.js";
import ChartDragDataPlugin from "@/app/plugins/ChartDragDataPlugin.js"; // let's call this a polyfill shall we?

Chart.register(
    ChartDragDataPlugin
);

// you can add all the props that you use within your chart/project
type ChartDataType = {
    labels: string[];
    datasets: {
        label: string;
        data: number[];
        backgroundColor?: string;
        borderColor?: string;
        pointBackgroundColor?: string;
        pointBorderColor?: string;
        pointRadius?: number;
        borderWidth?: number;
        pointBorderWidth?: number;
        pointHoverRadius?: number;
        fill?: boolean;
        zIndex?: number;
        datalabels?: {
            display: boolean;
        };
    }[];
};

interface RadarProps {
    data: ChartDataType;
    options: RadarOptionsType;
}

<Radar data={data} options={options} />;

[!IMPORTANT] Mark that we import the JavaScript file here

and the polyfile file, which is just a copy of dragdata plugin source (but in my case limited only to radar, you can copy the rest of the code from here)

import { drag } from "d3-drag";
import { select } from "d3-selection";

let element, rAxisID, type, eventSettings;
let isDragging = false;

function getSafe(func) {
    try {
        return func();
    } catch (e) {
        return "";
    }
}

const getElement = (e, chartInstance, callback) => {
    element = chartInstance.getElementsAtEventForMode(
        e,
        "nearest",
        { intersect: true },
        false
    )[0];
    type = chartInstance.config.type;

    if (element && type === "radar") {
        let datasetIndex = element.datasetIndex;
        let index = element.index;
        eventSettings = getSafe(
            () => chartInstance.config.options.plugins.tooltip.animation
        );

        const datasetMeta = chartInstance.getDatasetMeta(datasetIndex);
        rAxisID = datasetMeta.rAxisID;

        if (
            chartInstance.config.options.scales[rAxisID] &&
            chartInstance.config.options.scales[rAxisID].dragData === false
        ) {
            element = null;
            return;
        }

        if (
            chartInstance.config.options.plugins.dragData.showTooltip ===
                undefined ||
            chartInstance.config.options.plugins.dragData.showTooltip
        ) {
            if (!chartInstance.config.options.plugins.tooltip)
                chartInstance.config.options.plugins.tooltip = {};
            chartInstance.config.options.plugins.tooltip.animation = false;
        }

        if (typeof callback === "function" && element) {
            if (callback(e, datasetIndex, index) === false) {
                element = null;
            }
        }
    }
};

function roundValue(value, pos) {
    if (!isNaN(pos) && pos >= 0) {
        return Math.round(value * Math.pow(10, pos)) / Math.pow(10, pos);
    }
    return value;
}

function calcRadar(e, chartInstance) {
    let x, y, v;
    if (e.touches) {
        x =
            e.touches[0].clientX -
            chartInstance.canvas.getBoundingClientRect().left;
        y =
            e.touches[0].clientY -
            chartInstance.canvas.getBoundingClientRect().top;
    } else {
        x = e.clientX - chartInstance.canvas.getBoundingClientRect().left;
        y = e.clientY - chartInstance.canvas.getBoundingClientRect().top;
    }
    let rScale = chartInstance.scales[rAxisID];
    let d = Math.sqrt(
        Math.pow(x - rScale.xCenter, 2) + Math.pow(y - rScale.yCenter, 2)
    );
    let scalingFactor = rScale.drawingArea / (rScale.max - rScale.min);
    if (rScale.options.ticks.reverse) {
        v = rScale.max - d / scalingFactor;
    } else {
        v = rScale.min + d / scalingFactor;
    }

    v = roundValue(v, chartInstance.config.options.plugins.dragData.round);

    v = v > rScale.max ? rScale.max : v;
    v = v < rScale.min ? rScale.min : v;

    return v;
}

const updateData = (e, chartInstance, pluginOptions, callback) => {
    if (element) {
        let datasetIndex = element.datasetIndex;
        let index = element.index;

        isDragging = true;

        let dataPoint = calcRadar(e, chartInstance);

        if (
            !callback ||
            (typeof callback === "function" &&
                callback(e, datasetIndex, index, dataPoint) !== false)
        ) {
            chartInstance.data.datasets[datasetIndex].data[index] = dataPoint;
            chartInstance.update("none");
        }
    }
};

const dragEndCallback = (e, chartInstance, callback) => {
    isDragging = false;

    if (chartInstance.config.options.plugins.tooltip) {
        chartInstance.config.options.plugins.tooltip.animation = eventSettings;
        chartInstance.update("none");
    }

    if (typeof callback === "function" && element) {
        const datasetIndex = element.datasetIndex;
        const index = element.index;
        let value = chartInstance.data.datasets[datasetIndex].data[index];
        return callback(e, datasetIndex, index, value);
    }
};

const ChartDragDataPlugin = {
    id: "dragdata",
    afterInit: function (chartInstance) {
        if (
            chartInstance.config.options.plugins &&
            chartInstance.config.options.plugins.dragData
        ) {
            const pluginOptions = chartInstance.config.options.plugins.dragData;
            select(chartInstance.canvas).call(
                drag()
                    .container(chartInstance.canvas)
                    .on("start", (e) =>
                        getElement(
                            e.sourceEvent,
                            chartInstance,
                            pluginOptions.onDragStart
                        )
                    )
                    .on("drag", (e) =>
                        updateData(
                            e.sourceEvent,
                            chartInstance,
                            pluginOptions,
                            pluginOptions.onDrag
                        )
                    )
                    .on("end", (e) =>
                        dragEndCallback(
                            e.sourceEvent,
                            chartInstance,
                            pluginOptions.onDragEnd
                        )
                    )
            );
        }
    },
    beforeEvent: function (chart) {
        if (isDragging) {
            if (chart.tooltip) chart.tooltip.update();
            return false;
        }
    },
};

export default ChartDragDataPlugin;

Mark that you also need to update tsconfig.json file

"include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts", 
    "app/plugins/ChartDragDataPlugin.js"
  ],

image

The profit? Well you actually don't need to use this plugin at all, we just created a new one with types The downside? It's ugly and it stinks...