Search code examples
reactjschartsreact-apexcharts

How to show apps icons at the top of chart bar using react-apex chart?


Hi i am using React Fuse theme suggested from one of my client. On Dashboard i am showing some chart line chart bar and line chart etc. According to the design there are app icons at the top of the chart bars. Look at the screenshot attached with this message I read apexcharts official documentation and try many other things which i found from internet but still got not any progress. Here i am sharing my function every thing works fine but i don't have any idea that how i can show these icons at the top of chart bars.

function PlatformCompareWidget(props) {
  const chartSeries = [
    {
      name: 'IOS',
      data: props.singupUserData?.map((item) => Number(item.ios).toFixed(2)),
      color: '#51ABFF',
    },
    {
      name: 'Android',
      data: props.singupUserData?.map((item) => Number(item.android).toFixed(2)),
      color: '#34C759',
    },
    {
      name: 'Web',
      data: props.singupUserData?.map((item) => Number(item.web).toFixed(2)),
      color: '#9994FF',
    },
  ]

  const filterSeries =
    props.platform === ''
      ? chartSeries
      : chartSeries.filter((series) => series.name.toLowerCase() === props.platform);

  const chartOptions = {
    series: filterSeries,
    options: {
      grid: {
        show: false, // Hide background horizontal lines
      },
      legend: {
        show: false,
      },
      chart: {
        type: 'bar',
        height: 350,
        toolbar: {
          show: false,
        },
      },
      plotOptions: {
        bar: {
          borderRadius: 6,
          horizontal: false,
          columnWidth: props.platform === '' ? '25%' : '8%',
          endingShape: 'rounded',
        },
      },
      dataLabels: {
        enabled: false,
      },
      stroke: {
        show: true,
        width: 2,
        colors: ['transparent'],
      },
      xaxis: {
        categories: props.singupUserData?.map((item) =>
          item.date
            ? format(parse(item.date, 'yyyy-MM-dd', new Date()), 'dd MMM yyyy')
            : format(parse(item.month, 'yyyy-MM', new Date()), 'MMM yyyy')
        ),
      },
      yaxis: {
        show: false,
      },
      fill: {
        opacity: 1,
      },
    },
  };

  return (
        <ReactApexChart
                className="flex-auto w-full"
                options={chartOptions.options}
                series={chartOptions.series}
                height={320}
                type="bar"
              />
  );
}

Solution

  • Apexcharts has annotations that can be used to place images at different places on the plot area.

    However, placing the annotations so that they are at the top of the bar is not as simple as it could be. In the particular case of your type of plot, the problem is with positioning the annotation/image on the x direction. Point annotation's x can either be a category (which is not good enough for each series in the category) or a pixel coordinate (which is not easy to measure exactly).

    The best solution I found is to enable dataLabels, place them on top of the bars, but make them invisible and use the space allocated automatically by apexcharts for the data labels to place the annotations.

    The creation of the annotations can be done in the mounted event, on a configuration like this:

    {
        chart: {
            // ....
            events:{
                mounted(chart, {globals}){
                    // ....
                    globals.dataLabelsRects.forEach(
                        (rects, seriesIndex) => {
                            rects.forEach(
                                (rect, dataIndex) =>
                                    chart.addPointAnnotation({
                                        x: rect.x,
                                        y: globals.series[seriesIndex][dataIndex],
                                        marker:{ // disable marker
                                            strokeWidth: 0,
                                            radius: 0,
                                            fillColor: 'rgba(0,0,0,0)'
                                        },
                                        image:{
                                            path: 'path or url for seriesIndex',
                                            offsetY: ... , // center vertically
                                            height: ... ,
                                            width: ....
                                        }
                                    }, true)
                            )
                        }
                    )
                }
            }
        }
    }
    

    If the plot is resizable

    Here's a full example in vanilla js

    const imagePaths = { // indexed by the series name
        IOS: 'https://i.sstatic.net/jTvFd.png',
        Android: 'https://i.sstatic.net/zyB8S.png',
        Web: 'https://i.sstatic.net/lpN4g.png'
    };
    const options = {
        series:[ {
            name: 'IOS',
            data: [554,182,211,130,190],
            color: '#51ABFF',
        },
        {
            name: 'Android',
            data: [268,268,168,145,124],
            color: '#34C759',
        },{
            name: 'Web',
            data: [287,321,164,112,22],
            color: '#9994FF',
        }],
        chart: {
            type: 'bar',
            height: 400,
            width: 800,
            events:{
                mounted(chart, {globals}){
                    let barWidth; //attempt at an approximation
                    try{ // too many optional chaining ops ?.
                        barWidth = globals.dataLabelsRects[1][0].x - globals.dataLabelsRects[0][0].x;
                    }
                    catch(e){}
                    barWidth = barWidth || 20;
                    globals.dataLabelsRects.forEach(
                        (rects, seriesIndex) => {
                            rects.forEach(
                                (rect, dataIndex) =>
                                    chart.addPointAnnotation({
                                        x: rect.x,
                                        y: globals.series[seriesIndex][dataIndex],
                                        marker:{strokeWidth: 0, radius: 0, fillColor: 'rgba(0,0,0,0)'},
                                        image:{
                                            path: imagePaths[globals.seriesNames[seriesIndex]],
                                            offsetY: -Math.ceil(barWidth/4),
                                            height: Math.ceil(barWidth/2),
                                            width: Math.ceil(barWidth/2)
                                        }
                                    }, true)
                            )
                        }
                    )
                }
            }
        },
    
        plotOptions:{
            bar: {
                columnWidth: '50%',
                dataLabels: {
                    position: 'center', // top, center, bottom
                    hideOverflowingLabels: false,
                }
            }
        },
        dataLabels:{
            enabled: true,
            style:{
                colors: ['rgba(0,0,0,0)'] // not real data labels, used as anchors
            },
            formatter: function () {
                return "";
            },
        },
        xaxis: {
            categories: [ 'January', 'February', 'March', 'April', 'May'],
        },
    
    };
    const ch = new ApexCharts(document.querySelector('#chart'), options);
    ch.render();
    <div id="chart"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/apexcharts/3.40.0/apexcharts.min.js" integrity="sha512-Kr1p/vGF2i84dZQTkoYZ2do8xHRaiqIa7ysnDugwoOcG0SbIx98erNekP/qms/hBDiBxj336//77d0dv53Jmew==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>