Skip to content

Cannot read properties of undefined (reading 'activeDataLabelStyle') when working with drill downs #553

@bloodykheeng

Description

@bloodykheeng

these are the packages am using

"highcharts": "^12.4.0",
 "highcharts-react-official": "^3.2.2",
"next": "16.0.10",

im facing this error when am working with highcharts in next js

Uncaught TypeError: Cannot read properties of undefined (reading 'activeDataLabelStyle')
    at ht.q (drilldown.js:13:10015)
    at highcharts.js:9:2522
    at Array.forEach (<anonymous>)
    at U (highcharts.js:9:2500)
    at ht.r [as drawDataLabels] (highcharts.js:9:252082)
    at ht.render (highcharts.js:9:174040)
    at ht.redraw (highcharts.js:9:174321)
    at highcharts.js:9:202972
    at Array.forEach (<anonymous>)
    at a0.redraw (highcharts.js:9:202923)
    at a0.setSize (highcharts.js:9:209858)
    at highcharts.js:9:208978
q @ drilldown.js:13
(anonymous) @ highcharts.js:9
U @ highcharts.js:9
r @ highcharts.js:9
render @ highcharts.js:9
redraw @ highcharts.js:9
(anonymous) @ highcharts.js:9
redraw @ highcharts.js:9
setSize @ highcharts.js:9
(anonymous) @ highcharts.js:9
setTimeout
syncTimeout @ highcharts.js:9
reflow @ highcharts.js:9
e @ highcharts.js:9Understand this error
sellout-charts#pr_id_136_content_2:1 Unchecked runtime.lastError: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was receivedUnderstand this error
sellout-charts#pr_id_136_content_2:1 Unchecked runtime.lastError: A listener indicated an asynchronous res

but i realised its related to the drill down
and what i realised the moment i leave this line uncomented the error happens if i comment it it the error nolonger happens

// // ensure this comes last
// import "highcharts/modules/drilldown";

but i had a system built in this highcharts version and that error wasnt there at all

"highcharts": "^11.4.3",
        "highcharts-react-official": "^3.2.1",

here is my full code

'use client';

import React from "react";
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import moment from "moment";

// Import required Highcharts modules
import "highcharts/modules/exporting";
import "highcharts/modules/export-data";
import "highcharts/modules/full-screen";
import "highcharts/modules/no-data-to-display";
import "highcharts/modules/accessibility";

// these move hand in hand
import "highcharts/highcharts-more";
import "highcharts/modules/dumbbell";
import "highcharts/modules/lollipop";

// // ensure this comes last
// import "highcharts/modules/drilldown";

interface Product {
    id: string;
    name: string;
    sply_sales: number;
    actual_sales: number;
    target_sales: number;
}

interface SubCategory {
    id: string;
    name: string;
    sply_sales: number;
    actual_sales: number;
    target_sales: number;
    products: Product[];
}

interface Category {
    id: string;
    name: string;
    sply_sales: number;
    actual_sales: number;
    target_sales: number;
    subCategories: SubCategory[];
}

interface Agent {
    id: string;
    name: string;
    sply_sales: number;
    actual_sales: number;
    target_sales: number;
    categories: Category[];
}

interface AgentData {
    agents: Agent[];
}

interface AgentDrillChartProps {
    data: AgentData;
    requestParams: any;
    measure: "sales_value" | "quantity";
}

const AgentDrillChart: React.FC<AgentDrillChartProps> = ({
    data,
    requestParams,
    measure,
}) => {
    const formatArray = (arr: any[], key: string): string => {
        if (!Array.isArray(arr) || arr.length === 0) return "";
        return arr.map((item) => item[key]).join(", ");
    };

    const formatFilters = (dataFilters: any): string => {
        const startDate = dataFilters?.start_date ? moment(dataFilters.start_date).format("MMMM Do, YYYY") : null;
        const endDate = dataFilters?.end_date ? moment(dataFilters.end_date).format("MMMM Do, YYYY") : null;

        const agents = formatArray(dataFilters?.agents, "name");
        const salesAssociates = formatArray(dataFilters?.salesAssociates, "name");
        const regions = formatArray(dataFilters?.regions, "name");
        const territories = formatArray(dataFilters?.territories, "name");
        const routes = formatArray(dataFilters?.routes, "name");
        const statuses = formatArray(dataFilters?.statuses, "label");
        const orderBy = dataFilters?.orderBy?.label;
        const dataLimit = dataFilters?.dataLimit?.label;
        const dataLimitNumber = dataFilters?.dataLimitNumber;
        const viewInterval = dataFilters?.viewInterval?.label;
        const productCategories = formatArray(dataFilters?.categories, "name");
        const productSubCategories = formatArray(dataFilters?.subcategories, "name");
        const products = formatArray(dataFilters?.products, "name");

        let sentence = "Filters: ";
        if (agents) sentence += `Distributors: ${agents}. `;
        if (salesAssociates) sentence += `Sales Associates: ${salesAssociates}. `;
        if (regions) sentence += `Regions: ${regions}. `;
        if (territories) sentence += `Territories: ${territories}. `;
        if (routes) sentence += `Routes: ${routes}. `;
        if (statuses) sentence += `Statuses: ${statuses}. `;
        if (orderBy) sentence += `Order By: ${orderBy}. `;
        if (dataLimit) sentence += `Data Limit: ${dataLimit}. `;
        if (dataLimitNumber) sentence += `Data Limit No: ${dataLimitNumber}. `;
        if (viewInterval) sentence += `View Interval: ${viewInterval}. `;
        if (productCategories) sentence += `Product Categories: ${productCategories}. `;
        if (productSubCategories) sentence += `Product Sub Categories: ${productSubCategories}. `;
        if (products) sentence += `Products: ${products}. `;
        if (startDate) sentence += `Start Date: ${startDate}. `;
        if (endDate) sentence += `End Date: ${endDate}.`;

        return sentence.trim();
    };

    const calculatePercentageChange = (actual: number, comparison: number): number => {
        return ((actual - comparison) / comparison) * 100;
    };

    const formatData = (items: any[], level: string) => {
        return items.map((item) => ({
            name: item.name,
            sply: item.sply_sales,
            actual: item.actual_sales,
            target: item.target_sales,
            drilldown: level !== "products" ? item.id : undefined,
        }));
    };

    const createDrilldownSeries = (items: any[], level: string) => {
        return items.map((item) => {
            const nextLevel = level === "agents" ? "categories" : level === "categories" ? "subCategories" : "products";
            const hasNextLevel = item[nextLevel] && item[nextLevel].length > 0;
            const drilldownData = hasNextLevel ? formatData(item[nextLevel], nextLevel) : [];

            return {
                id: item.id,
                data: drilldownData,
                drilldown: hasNextLevel,
            };
        });
    };

    const createPercentageChangeSeries = (items: any[], comparisonType: string): any => {
        return {
            type: "lollipop",
            name: `Percentage Change Actual vs. ${comparisonType}`,
            data: items.map((item) => {
                const percentageChange = calculatePercentageChange(item.actual_sales, item[`${comparisonType.toLowerCase()}_sales`]);
                return {
                    name: item.name,
                    y: percentageChange,
                    color: percentageChange < 90 ? "#FF0000" : percentageChange > 100 ? "#008000" : "#FFBF00",
                };
            }),
            yAxis: 1,
            tooltip: {
                pointFormat: "{series.name}: <b>{point.y:,.0f}%</b>",
            },
            marker: {
                enabled: true,
                lineColor: undefined,
                symbol: "circle",
            },
            dataLabels: {
                enabled: true,
                format: "{point.y:,.0f}%",
            },
            showInLegend: false,
        };
    };

    const chartData = data?.agents || [];
    const chartTitle = formatFilters(requestParams);

    const drilldownSeries = [
        ...createDrilldownSeries(chartData, "agents"),
        ...chartData.flatMap((item) => createDrilldownSeries(item.categories, "categories")),
        ...chartData.flatMap((item) => item.categories.flatMap((category) => createDrilldownSeries(category.subCategories, "subCategories"))),
    ];

    const options: Highcharts.Options = {
        chart: {
            type: "bar",
            height: 800,
            scrollablePlotArea: {
                minHeight: 500,
                scrollPositionX: 1,
            },
            events: {
                drilldown: function (e: any) {
                    if (!e.seriesOptions) {
                        const chart: any = this;
                        const series = chart.userOptions.drilldown?.series?.find((s: any) => s.id === e.point.drilldown);

                        if (series && series.data && series.data.length > 0) {
                            const splyData = series?.data.map((item: any) => ({ name: item.name, y: item.sply, drilldown: item.drilldown }));
                            const actualData = series?.data.map((item: any) => ({ name: item.name, y: item.actual, drilldown: item.drilldown }));
                            const targetData = series?.data.map((item: any) => ({ name: item.name, y: item.target, drilldown: item.drilldown }));

                            chart.addSingleSeriesAsDrilldown(e.point, {
                                name: "SPLY Sales",
                                data: splyData,
                                color: Highcharts.getOptions().colors?.[0],
                            });
                            chart.addSingleSeriesAsDrilldown(e.point, {
                                name: "Actual Sales",
                                data: actualData,
                                color: Highcharts.getOptions().colors?.[1],
                            });
                            chart.addSingleSeriesAsDrilldown(e.point, {
                                name: "Target Sales",
                                data: targetData,
                                color: Highcharts.getOptions().colors?.[2],
                            });

                            chart.addSingleSeriesAsDrilldown(e.point, {
                                type: "lollipop",
                                name: "Percentage Change Actual vs. SPLY",
                                data: series.data.map((item: any) => {
                                    const percentageChange = calculatePercentageChange(item.actual, item.sply);
                                    return {
                                        name: item.name,
                                        y: percentageChange,
                                        color: percentageChange < 90 ? "#FF0000" : percentageChange > 100 ? "#008000" : "#FFBF00",
                                    };
                                }),
                                yAxis: 1,
                                tooltip: {
                                    pointFormat: "{series.name}: <b>{point.y:,.0f}%</b>",
                                },
                                marker: {
                                    enabled: true,
                                    lineColor: undefined,
                                    symbol: "circle",
                                },
                                dataLabels: {
                                    enabled: true,
                                    format: "{point.y:,.0f}%",
                                },
                                showInLegend: false,
                            });

                            chart.addSingleSeriesAsDrilldown(e.point, {
                                type: "lollipop",
                                name: "Percentage Change Actual vs. Target",
                                data: series.data.map((item: any) => {
                                    const percentageChange = calculatePercentageChange(item.actual, item.target);
                                    return {
                                        name: item.name,
                                        y: percentageChange,
                                        color: percentageChange < 90 ? "#FF0000" : percentageChange > 100 ? "#008000" : "#FFBF00",
                                    };
                                }),
                                yAxis: 1,
                                tooltip: {
                                    pointFormat: "{series.name}: <b>{point.y:,.0f}%</b>",
                                },
                                marker: {
                                    enabled: true,
                                    lineColor: undefined,
                                    symbol: "circle",
                                },
                                dataLabels: {
                                    enabled: true,
                                    format: "{point.y:,.0f}%",
                                },
                                showInLegend: false,
                            });

                            chart.applyDrilldown();
                        } else {
                            e.preventDefault();
                        }
                    }
                },
            },
        },
        accessibility: {
            enabled: true,
            keyboardNavigation: {
                enabled: false,
            },
        },
        title: {
            text: `Agents Sales Performance <br/> ${chartTitle}`,
        },
        xAxis: {
            type: "category",
            uniqueNames: false,
        },
        yAxis: [
            {
                title: {
                    text: "Sales",
                },
                width: "60%",
                lineWidth: 2,
                offset: 0,
                labels: {
                    formatter: function () {
                        const value = this.value as number;
                        if (value >= 1000000000) {
                            return value / 1000000000 + "b";
                        }
                        if (value >= 1000000) {
                            return value / 1000000 + "m";
                        }
                        if (value >= 1000) {
                            return value / 1000 + "k";
                        }
                        return String(value);
                    },
                },
            },
            {
                title: {
                    text: "Percentage Change",
                },
                left: "65%",
                width: "30%",
                offset: 0,
                lineWidth: 2,
            },
        ],
        legend: {
            enabled: true,
        },
        plotOptions: {
            series: {
                borderWidth: 0,
                dataLabels: {
                    enabled: true,
                    format: "{point.y:,.0f}",
                    style: {
                        color: "#000000",
                    },
                },
                point: {
                    events: {
                        click: function () {
                            if (!(this as any).drilldown) {
                                return false;
                            }
                        },
                    },
                },
            },
        },
        credits: {
            enabled: false,
        },
        noData: {
            style: {
                fontWeight: "bold",
                fontSize: "16px",
                color: "#303030",
            },
            position: {
                align: "center",
                verticalAlign: "middle",
            },
        },
        series: [
            {
                type: "bar",
                name: "SPLY Sales",
                data: chartData.map((item) => ({
                    name: item.name,
                    y: item.sply_sales,
                    drilldown: item.id,
                    color: Highcharts.getOptions().colors?.[0],
                })),
                tooltip: {
                    pointFormat: "{series.name}: <b>{point.y:,.0f}</b>",
                },
            },
            {
                type: "bar",
                name: "Actual Sales",
                data: chartData.map((item) => ({
                    name: item.name,
                    y: item.actual_sales,
                    drilldown: item.id,
                    color: Highcharts.getOptions().colors?.[1],
                })),
                tooltip: {
                    pointFormat: "{series.name}: <b>{point.y:,.0f}</b>",
                },
            },
            {
                type: "bar",
                name: "Target Sales",
                data: chartData.map((item) => ({
                    name: item.name,
                    y: item.target_sales,
                    drilldown: item.id,
                    color: Highcharts.getOptions().colors?.[2],
                })),
                tooltip: {
                    pointFormat: "{series.name}: <b>{point.y:,.0f}</b>",
                },
            },
            createPercentageChangeSeries(chartData, "SPLY"),
            createPercentageChangeSeries(chartData, "Target"),
        ],
        drilldown: {
            allowPointDrilldown: false,
            breadcrumbs: {
                position: {
                    align: "right",
                },
            },
            activeDataLabelStyle: {
                color: "#000000",
                textDecoration: "none",
                textOutline: "1px contrast",
            },
            series: drilldownSeries as any,
        },
    };

    return (
        <div>
            <HighchartsReact highcharts={Highcharts} options={options} immutable={true} />
        </div>
    );
};

export default AgentDrillChart;

Here is how i use the component

"use client";

import React, { useState } from "react";
import { Tooltip } from "primereact/tooltip";
import { Button } from "primereact/button";
import { useQuery } from "@tanstack/react-query";

import moment from "moment";

import SelloutFiltersButton, { selectedFiltersType, } from "@/components/admin-panel/filters/sellout/SelloutFiltersButton";

import RobotProcessorLottie from "@/assets/nice-lotties/robot-processor-lottie.json";
import MaterialUiLoaderLottie from "@/assets/nice-lotties/material-ui-loading-lottie.json";
import SnailErrorLottie from "@/assets/nice-lotties/snail-error-lottie.json";

import useHandleQueryError from "@/hooks/useHandleQueryError";

import { getSelloutAgentPdtDrillSVSAVsTChart } from "@/services/dashboard/sellout/barcharts-service";

import dynamic from "next/dynamic";
const Lottie = dynamic(() => import("lottie-react"), { ssr: false });

const AgentDrillChart = dynamic(() => import("./charts/AgentDrillChart"), {
    ssr: false,
    loading: () => (
        <div className="flex justify-center items-center h-96">
            <span className="text-gray-500 dark:text-gray-400">Loading chart...</span>
        </div>
    )
});



interface AgentWithProductDrillPerformanceChartProps {
    measure?: "sales_value" | "quantity";
}

const AgentWithProductDrillPerformanceChart: React.FC<AgentWithProductDrillPerformanceChartProps> = ({
    measure = "sales_value",
}) => {
    const [selectedFilters, setSelectedFilters] = useState<selectedFiltersType>({
        start_date: moment().startOf("year").toDate(),
        end_date: moment().toDate(),
        year: null,
        months: [],
        categories: [],
        subcategories: [],
        products: [],
        channels: [],
        regions: [],
        territories: [],
        routes: [],
        order_by: "Ascending",
        data_limit: 5,
        view_interval: "Default",
    });

    const getSelloutAgentPdtDrillSVSAVsTChartQuery = useQuery({
        queryKey: [
            "sellout-agent-drill-performance",
            measure,
            ...Object.values(selectedFilters),
        ],
        queryFn: () =>
            getSelloutAgentPdtDrillSVSAVsTChart({
                measure: measure,
                ...selectedFilters,
            }),
    });

    useHandleQueryError(getSelloutAgentPdtDrillSVSAVsTChartQuery);

    if (getSelloutAgentPdtDrillSVSAVsTChartQuery?.isPending) {
        return (
            <div className="col-span-12">
                <div className="w-full flex items-center justify-center">
                    <div style={{ maxWidth: "400px" }}>
                        <Lottie animationData={RobotProcessorLottie} style={{ height: "300px" }} loop={true} />
                        <Lottie
                            animationData={MaterialUiLoaderLottie}
                            style={{ height: "50px" }}
                            loop={true}
                        />
                    </div>
                </div>
            </div>
        );
    }

    if (getSelloutAgentPdtDrillSVSAVsTChartQuery?.isError) {
        return (
            <div className="w-full flex items-center justify-center">
                <div style={{ maxWidth: "400px" }}>
                    <Lottie animationData={SnailErrorLottie} loop={true} />
                </div>
            </div>
        );
    }

    const seriesData = getSelloutAgentPdtDrillSVSAVsTChartQuery?.data?.data?.data;
    const requestParams = getSelloutAgentPdtDrillSVSAVsTChartQuery?.data?.data?.requestParams;

    return (
        <>
            <div className="col-span-12 text-right mb-3">
                <SelloutFiltersButton
                    selectedFilters={selectedFilters}
                    setSelectedFilters={setSelectedFilters}
                    sectionsToShow={{
                        dateRange: true,
                        products: true,
                        administrative: true,
                        dataLimits: true,
                    }}
                />
            </div>

            <div className="overflow-y-auto" style={{ height: "400px" }}>
                <AgentDrillChart
                    data={seriesData}
                    requestParams={requestParams}
                    measure={measure}
                />
            </div>
        </>
    );
};

export default AgentWithProductDrillPerformanceChart;

any advice or clarity on this issue will highly be appreciated

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions