Search code examples
javascriptreactjshighchartsreact-grid-layout

how to make react highcharts responsive with react-grid-layout while resizing?


i want to make my charts responsive when i try to resize them. i am using two charts libraries for charts with react-grid-layout. (1) React recharts (2) react highcharts My react recharts resizing working fine and they are responsive but highcharts are not responsive when i try resize them their stays the same. can anyone help me? Below is the code. this is where my react-grid-layout code is. also look at the chart image. on resizing this blank area should be filled with chart width. enter image description here

    import React, { useState } from 'react';
    import { Responsive as ResponsiveGridLayout } from 'react-grid-layout';
    import { withSize } from 'react-sizeme';
    import { withSnackbar } from 'notistack';
    import html2canvas from 'html2canvas';
    import { jsPDF } from 'jspdf';
    import TopBar from './TopBar';
    import Widget from './Widget';
    import OverallAttendanceChart from './AttendanceCharts/OverallAttendanceChart';
    import HeadlinesChart from './AttendanceCharts/HeadlinesChart';
    import LineChartView from './AttendanceCharts/LineChart';
    import MonthlyChart from './AttendanceCharts/MonthlyChart';
    import DateChart from './AttendanceCharts/DateChart';
    import MatrixChart from './AttendanceCharts/MatrixChart';
    import WardsChart from './AttendanceCharts/WardsChart';
    import GroupedPieChart from './AttendanceCharts/GroupedPieChart';
    import StudentsAttendance from './StudentsAttendance';
    import Headlines from './Headlines';
    import { Box } from '@material-ui/core';
    import { CourseBasedAttendence } from '../Attendance/AttendanceCharts/CourseBasedAttendence';
    import ReportsFilter from '../ReportsFilter';
    
    const originalItems = [
      'a',
      'b',
      'c',
      'd',
      'e',
      'f',
      'g',
      'h',
      'i',
      'j',
      'k',
      'l',
      'm',
      'n',
      'o',
      'p',
    ];
    
    const initialLayouts = {
      lg: [
        { w: 4, h: 8, x: 0, y: 0, i: 'a', moved: false, static: false },
        { w: 12, h: 8, x: 0, y: 0, i: 'b', moved: false, static: false },
        { w: 12, h: 6, x: 6, y: 0, i: 'c', moved: false, static: false },
        { w: 6, h: 6, x: 0, y: 6, i: 'd', moved: false, static: false },
        { w: 12, h: 12, x: 0, y: 0, i: 'e', moved: false, static: false },
        { w: 12, h: 12, x: 0, y: 0, i: 'f', moved: false, static: false },
        { w: 12, h: 6, x: 6, y: 0, i: 'g', moved: false, static: false },
        { w: 6, h: 6, x: 0, y: 0, i: 'h', moved: false, static: false },
        { w: 12, h: 7, x: 6, y: 0, i: 'i', moved: false, static: false },
        { w: 4, h: 8, x: 0, y: 0, i: 'j', moved: false, static: false },
        { w: 4, h: 8, x: 0, y: 0, i: 'k', moved: false, static: false },
        { w: 4, h: 8, x: 0, y: 0, i: 'l', moved: false, static: false },
        { w: 4, h: 8, x: 0, y: 0, i: 'm', moved: false, static: false },
        { w: 4, h: 8, x: 0, y: 0, i: 'n', moved: false, static: false },
        { w: 4, h: 8, x: 0, y: 0, i: 'o', moved: false, static: false },
        { w: 4, h: 8, x: 0, y: 0, i: 'p', moved: false, static: false },
      ],
    };
    
    const componentList = {
      a: OverallAttendanceChart,
      b: GroupedPieChart,
      c: LineChartView,
      d: MonthlyChart,
      e: HeadlinesChart,
      f: DateChart,
      g: MatrixChart,
      h: WardsChart,
      i: CourseBasedAttendence,
      j: OverallAttendanceChart,
      k: OverallAttendanceChart,
      l: OverallAttendanceChart,
      m: OverallAttendanceChart,
      n: OverallAttendanceChart,
      o: OverallAttendanceChart,
      p: OverallAttendanceChart,
    };
    function Attendance({
      size: { width },
      enqueueSnackbar,
      averageMarkLevel,
      fetchCourseBasedAttendance,
      courseBasedStudentList,
      courseList,
      reportType,
      totalStudents,
      coursesAttendance,
      timeMonthAttendance,
    }) {
      const [items, setItems] = useState(originalItems);
      const [layouts, setLayouts] = useState(
        getFromLS('layouts') || initialLayouts,
      );
    
      const onLayoutChange = (_, allLayouts) => {
        setLayouts(allLayouts);
      };
      const onLayoutSave = () => {
        saveToLS('layouts', layouts);
      };
      const onRemoveItem = itemId => {
        setItems(items.filter(i => i !== itemId));
      };
      const onAddItem = itemId => {
        setItems([...items, itemId]);
      };
      const printDocument = () => {
        enqueueSnackbar('Downloading pdf...', {
          variant: 'success',
          autoHideDuration: 5000,
        });
        const input = document.getElementById('divToPrint1');
        html2canvas(input).then(canvas => {
          const imgWidth = 210;
          const pageHeight = 295;
          const imgHeight = (canvas.height * imgWidth) / canvas.width;
          let heightLeft = imgHeight;
    
          const imgData = canvas.toDataURL('image/png');
          const pdf = new jsPDF('portrait', 'mm', 'a4');
          let position = 0;
    
          pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
    
          heightLeft -= pageHeight;
    
          while (heightLeft >= 0) {
            position = heightLeft - imgHeight;
            pdf.addPage();
            pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
            heightLeft -= pageHeight;
          }
          pdf.save('download.pdf');
        });
      };
    
      return (
        <div id="divToPrint1">
          <Box
            style={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'flex-end',
            }}
          >
            <ReportsFilter
              fetchCourseBasedAttendance={fetchCourseBasedAttendance}
              courseList={courseList}
              reportType={reportType}
            />
            <TopBar
              onLayoutSave={onLayoutSave}
              items={items}
              onRemoveItem={onRemoveItem}
              onAddItem={onAddItem}
              originalItems={originalItems}
              pdf={printDocument}
            />
          </Box>
    
          <StudentsAttendance totalStudents={totalStudents} />
          <Headlines totalStudents={totalStudents} />
          <ResponsiveGridLayout
            className="layout"
            layouts={layouts}
            breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
            cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
            rowHeight={70}
            width={width}
            onLayoutChange={onLayoutChange}
          >
            {items.map(key => (
              <div
                key={key}
                className="widget"
                data-grid={{ w: 3, h: 2, x: 0, y: Infinity }}
              >
                <Widget
                  id={key}
                  onRemoveItem={onRemoveItem}
                  component={componentList[key]}
                  averageMarkLevel={averageMarkLevel}
                  courseBasedStudentList={courseBasedStudentList}
                  totalStudents={totalStudents}
                  coursesAttendance={coursesAttendance}
                  timeMonthAttendance={timeMonthAttendance}
                />
              </div>
            ))}
          </ResponsiveGridLayout>
        </div>
      );
    }
    
    export default withSnackbar(
      withSize({ refreshMode: 'debounce', refreshRate: 60 })(Attendance),
    );
    
    function getFromLS(key, reportId) {
      let ls = {};
      if (global.localStorage) {
        try {
          ls = JSON.parse(global.localStorage.getItem(`rgl-${reportId}`)) || {};
        } catch (e) {}
      }
      return ls[key];
    }
    
    function saveToLS(key, value, reportId) {
      if (global.localStorage) {
        global.localStorage.setItem(
          `rgl-${reportId}`,
          JSON.stringify({
            [key]: value,
          }),
        );
      }
    }

This is my widget.js code 


import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import IconButton from '@material-ui/core/IconButton';
import CloseIcon from '@material-ui/icons/Close';
import Typography from '@material-ui/core/Typography';

const useStyles = makeStyles({
  root: {
    width: '100%',
    height: '100%',
    display: 'flex',
    flexDirection: 'column',
  },
  header: {
    display: 'flex',
    alignItems: 'center',
    padding: '0.5rem',
  },
  spacer: {
    flexGrow: 1,
  },
  body: {
    padding: '0.5rem',
    flexGrow: 1,
  },
  typo: {
    textTransform: 'capitalize',
  },
});

const widgetNames = {
  a: 'Overall Attendance',
  b: 'Micro-cohorts',
  c: 'Attendance by Courses',
  d: 'No. of attendees by Months',
  e: 'Headline Chart',
  f: 'Attendance over Time',
  g: 'Attendance by Matrix',
  h: 'Individual Wards',
  i: 'Course Based Attendance',
  j: 'Boys ',
  k: 'Attendance for Pupil Premium (PP)',
  l: 'Attendance for Non- Pupil Premium (PP)',
  m: 'White British ',
  n: 'Non White British ',
  o: 'Attendance for English as Additional Language (EAL) ',
  p: 'Attendance for Non-English as Additional Language (EAL) ',
};
export default function Widget({
  id,
  onRemoveItem,
  component: Item,
  averageMarkLevel,
  courseBasedStudentList,
  totalStudents,
  coursesAttendance,
  timeMonthAttendance,
}) {
  const classes = useStyles();
  return (
    <Card className={classes.root}>
      <div className={classes.header}>
        <Typography variant="h6" gutterBottom className={classes.typo}>
          {widgetNames[id]}
        </Typography>
        <div className={classes.spacer} />
        <IconButton aria-label="delete" onClick={() => onRemoveItem(id)}>
          <CloseIcon fontSize="small" />
        </IconButton>
      </div>
      <div className={classes.body}>
        <Item
          averageMarkLevel={averageMarkLevel}
          courseBasedStudentList={courseBasedStudentList}
          totalStudents={totalStudents}
          coursesAttendance={coursesAttendance}
          timeMonthAttendance={timeMonthAttendance}
        />
      </div>
    </Card>
  );
}


This is my highcharts code 


import React from 'react';
import Highcharts from 'highcharts/highcharts';
import highchartsMore from 'highcharts/highcharts-more';
import solidGauge from 'highcharts/modules/solid-gauge';
import HighchartsReact from 'highcharts-react-official';

highchartsMore(Highcharts);
solidGauge(Highcharts);

const chartOptions = {
  chart: {
    type: 'solidgauge',
  },
  credits: {
    enabled: false,
  },
  title: {
    text: '',
  },
  pane: {
    center: ['50%', '70%'],
    size: '100%',
    startAngle: -90,
    endAngle: 90,
    background: {
      backgroundColor:
        Highcharts.defaultOptions.legend.backgroundColor || '#EEE',
      innerRadius: '60%',
      outerRadius: '100%',
      shape: 'arc',
    },
  },

  yAxis: {
    min: 0,
    max: 100,
    stops: [[0, '#ff3118'], [0.5, '#ffd600'], [1, '#00bc06']],

    lineWidth: 0,
    tickWidth: 0,
    minorTickInterval: null,
    tickAmount: 2,
    title: {
      y: -70,
    },
    labels: {
      y: 16,
    },
  },

  exporting: {
    enabled: false,
  },

  tooltip: {
    enabled: false,
  },
  plotOptions: {
    solidgauge: {
      dataLabels: {
        y: 5,
        borderWidth: 0,
        useHTML: true,
      },
    },
  },
};
const chartOptionsLevel = {
  chart: {
    type: 'solidgauge',
  },
  credits: {
    enabled: false,
  },
  title: {
    text: 'Level',
  },
  pane: {
    center: ['50%', '70%'],
    size: '100%',
    startAngle: -90,
    endAngle: 90,
    background: {
      backgroundColor:
        Highcharts.defaultOptions.legend.backgroundColor || '#EEE',
      innerRadius: '60%',
      outerRadius: '100%',
      shape: 'arc',
    },
  },

  yAxis: {
    min: 0,
    max: 100,
    stops: [[0, '#ff3118'], [0.5, '#ffd600'], [1, '#00bc06']],

    lineWidth: 0,
    tickWidth: 0,
    minorTickInterval: null,
    tickAmount: 2,
    title: {
      y: -70,
    },
    labels: {
      y: 16,
    },
  },

  exporting: {
    enabled: false,
  },

  tooltip: {
    enabled: false,
  },
  plotOptions: {
    solidgauge: {
      dataLabels: {
        y: 5,
        borderWidth: 0,
        useHTML: true,
      },
    },
  },
};

const data = 65.14666666666666;

const getChartOptions = (size, anchorEl, range, title) => {
  if (size) {
    chartOptions.chart.width = size.width;
    chartOptions.chart.height = size.height;
  }
  if (typeof data === 'number') {
    chartOptions.series = [
      {
        data: [parseFloat(data.toFixed(2))],
        dataLabels: {
          format:
            '<div style="text-align:center">' +
            '<span style="font-size:22px">{y}</span><br/>' +
            '<span style="font-size:12px;opacity:0.4">%</span>' +
            '</div>',
        },
        tooltip: {
          valueSuffix: '%',
        },
      },
    ];
    if (anchorEl) Highcharts.chart(anchorEl, chartOptions);
  }
  if (typeof range === 'number') {
    chartOptions.yAxis.max = range;
  }
  if (title && title.length > 1) {
    chartOptions.title.text = title;
  }
  if (title === 'Level') {
    chartOptionsLevel.series = [
      {
        data: [parseFloat(data.toFixed(2))],
        dataLabels: {
          format:
            '<div style="text-align:center">' +
            '<span style="font-size:22px">{y}</span><br/>' +
            '<span style="font-size:12px;opacity:0.4">%</span>' +
            '</div>',
        },
        tooltip: {
          valueSuffix: '%',
        },
      },
    ];
    chartOptionsLevel.yAxis.max = range;
    return chartOptionsLevel;
  }

  return JSON.parse(JSON.stringify(chartOptions));
};

function OverallAttendanceChart(props) {
  const { size, anchorEl, range, title } = props;

  return (
    <HighchartsReact
      highcharts={Highcharts}
      options={getChartOptions(data, size, anchorEl, range, title)}
    />
  );
}

export default OverallAttendanceChart;

Solution

  • I fixed the issue . i simply import the highcharts in attendance.js file 
    then in onLayoutChange function i write this code .
      const onLayoutChange = (_, allLayouts) => {
        setLayouts(allLayouts);
        for (let i = 0; i < Highcharts.charts.length; i += 1) {
          if (Highcharts.charts[i] !== undefined) {
            Highcharts.charts[i].reflow();
          }
        }
      };
    
    then call this function in react-grid-layout onLayout change
            onLayoutChange={() => onLayoutChange()}
    . i am posting answer if anyone else stuck with the same issue. because while working with highcharts in react-grid-layout you gonna face this issue. reason is reflow which highcharts mentioned in their documentation. 
    if anyone have more better approach please write your answer.