2

I'm trying to change bar colours based on value, which is easy enough in JS, but when I implement the code in a TSX file, it gives me an error:

enter image description here

As you can see, VS Code is telling me that dataItem is not a property of target when the file has a TSX extension. If I change the extension to JSX, the code executes and works as expected, but I want to use TypeScript

I'm using the code almost verbatim from the example enter image description here enter image description here

In the example link above, the TS and JS code is identical. I don't get why the dataItem property of target is recognised in JS but not in TS.

Here is the entire code in my file:

import React, { useEffect } from 'react';

import * as am5 from "@amcharts/amcharts5";
import * as am5xy from "@amcharts/amcharts5/xy";
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";

import { AxisRenderer } from '@amcharts/amcharts5/.internal/charts/xy/axes/AxisRenderer';

interface IChartData {
     name: string; 
     metricName: string;
     value: number 
}

interface IChartProps {
    title: string;
    chartData: Array<IChartData>;
    chartContainer: string;
    showLegend: boolean;
    //orientation: am4core.PointerOrientation;
    colorPositiveValues: string;
    colorNegativeValues: string;
    hideValueAxisLabels?: boolean;
    hideCategoryAxisLabels?: boolean;
    hideGridLines?: boolean;
    useCursor?: boolean;
    valueAxisTitle: string;
    showSavedWasted?: boolean;
}



export const StackedBarChart = (props: IChartProps ) => {

    useEffect(() => {

        /* Chart code */
        // Create root element
        // https://www.amcharts.com/docs/v5/getting-started/#Root_element
        let root = am5.Root.new(props.chartContainer);

        // Set themes
        // https://www.amcharts.com/docs/v5/concepts/themes/
        root.setThemes([
        am5themes_Animated.new(root)
        ]);

        // Create chart
        // https://www.amcharts.com/docs/v5/charts/xy-chart/
        let chart = root.container.children.push(
        am5xy.XYChart.new(root, {
            panX: false,
            panY: false,
            wheelX: "panX",
            wheelY: "zoomX",
            layout: root.verticalLayout,
            arrangeTooltips: false
        })
        );

        // Use only absolute numbers
        chart.getNumberFormatter().set("numberFormat", "#.#s");

        // Add legend
        // https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
        let legend = chart.children.push(
            am5.Legend.new(root, {
                centerX: am5.p50,
                x: am5.p50
            })
        );

        // Create axes
        // https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
        let yAxis = chart.yAxes.push(
        am5xy.CategoryAxis.new(root, {
            categoryField: "name",
            renderer: am5xy.AxisRendererY.new(root, {
            inversed: true,
            cellStartLocation: 0.1,
            cellEndLocation: 0.9
            })
        })
        );

        yAxis.data.setAll(props.chartData);

        let xAxis = chart.xAxes.push(
            am5xy.ValueAxis.new(root, {
                renderer: am5xy.AxisRendererX.new(root, {})
            })
        );


        createSeries(chart, root, xAxis, yAxis, "value", am5.p100, "right", -3);
        // createSeries("female", 0, "left", 4);

        // Add cursor
        // https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
        let cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
            behavior: "zoomY"
        }));

        cursor.lineY.set("forceHidden", true);
        cursor.lineX.set("forceHidden", true);

        // Make stuff animate on load
        // https://www.amcharts.com/docs/v5/concepts/animations/
        chart.appear(1000, 100);



    return () => {
        root.dispose();
        };
    }, []);


    // Add series
    // https://www.amcharts.com/docs/v5/charts/xy-chart/series/
    function createSeries(chart: am5xy.XYChart, root: am5.Root, xAxis: am5xy.ValueAxis<AxisRenderer>, yAxis: am5xy.CategoryAxis<AxisRenderer>, field:any, labelCenterX:any, pointerOrientation:any, rangeValue:any) {
        let series = chart.series.push(
            am5xy.ColumnSeries.new(root, {
                xAxis: xAxis,
                yAxis: yAxis,
                valueXField: field,
                categoryYField: "name",
                sequencedInterpolation: true,
                // clustered: false,
                // tooltip: am5.Tooltip.new(root, {
                //     pointerOrientation: pointerOrientation,
                //     labelText: "{categoryY}: {valueX}"
                // })
            })
        );
        console.log('columns', series.columns);
        series.columns.template.adapters.add("fill", function(fill, target) {
            console.log('target', target.dataItem.get("valueX"));   // <- this is where the problem is
            if (target.dataItem.get("valueX") > 0) {
              return am5.color('#a4282a');
            }
            else {
               return am5.color('#3c6eb1');;
            }
          });


        series.columns.template.setAll({
            height: am5.p100
        });

        series.bullets.push(function() {
            return am5.Bullet.new(root, {
            locationX: 1,
            locationY: 0.5,
            sprite: am5.Label.new(root, {
                centerY: am5.p50,
                text: "{valueX}",
                populateText: true,
                centerX: labelCenterX
            })
            });
        });

        series.data.setAll(props.chartData);
        series.appear();


        let rangeDataItem = xAxis.makeDataItem({
            value: rangeValue
        });
        xAxis.createAxisRange(rangeDataItem);
        rangeDataItem.get("grid").setAll({
            strokeOpacity: 1,
            stroke: series.get("stroke")
        });

        let label = rangeDataItem.get("label");
        label.setAll({
            text: field.toUpperCase(),
            fontSize: "1.1em",
            fill: series.get("stroke"),
            paddingTop: 10,
            isMeasured: false,
            centerX: labelCenterX
        });
        label.adapters.add("dy", function() {
            return -chart.plotContainer.height();
        });

        return series;
    }


    return (
        <>
            <div id={props.chartContainer} style={{ width: "100%", height: "500px" }}></div>

        </>
    )

}
Adam Hey
  • 1,512
  • 1
  • 20
  • 24

1 Answers1

1

well, it's not the most elegant solution, but casting the target parameter as any satisfied the compiler enter image description here

I had a similar issue with a Heat Map, too. This was related to the value displayed in the legend. enter image description here

Type "visible" ?? value the name of the property in the data, so this should accept any string. enter image description here

By setting the "value" string as a type any, the error went away. Even though it's still a string . enter image description here

Adam Hey
  • 1,512
  • 1
  • 20
  • 24
  • While this may work for your projects tsconfig, it certainly won't work for all. the proper solution is to go digging in the source of the library and find the type for `target`, and then type the `target` argument in the callback definition. Typescript can be a pain in this regard, but the benefits of strict typing are totally worth it for me. – r3wt Jan 28 '22 at 14:31
  • Thanks for the comment. I did do what you suggested and the type in the source was just `O`. I established that it `RoundedRecctangle` at runtime, but this object does not have `dataItem` as a property. I did try other object types from the source and documentation, but nothing but `any` solved the issue. Like I said, it's not ideal, but with v5 being so new, there is not a hell of a lot of info in the wild – Adam Hey Jan 28 '22 at 15:44