Skip to content
Snippets Groups Projects
ApexCharts.js 9.71 KiB
import React from 'react'
import PropTypes from 'prop-types'
import intl from 'react-intl-universal'
import { withStyles } from '@material-ui/core/styles'
import ApexCharts from 'apexcharts'
import purple from '@material-ui/core/colors/purple'
import CircularProgress from '@material-ui/core/CircularProgress'
import MenuItem from '@material-ui/core/MenuItem'
import FormControl from '@material-ui/core/FormControl'
import Select from '@material-ui/core/Select'
import Typography from '@material-ui/core/Typography'
import GeneralDialog from '../main_layout/GeneralDialog'
import InstaceList from '../main_layout/InstanceList'

const defaultPadding = 32
const smallScreenPadding = 8

const styles = theme => ({
  selectContainer: {
    display: 'flex',
    alignItems: 'center',
    marginBottom: theme.spacing(1)
  },
  formControl: {
    marginLeft: theme.spacing(1)
  }
})

/**
 * A component for rendering charts with ApexCharts.
 */
class ApexChart extends React.Component {
  constructor (props) {
    super(props)
    this.chartRef = React.createRef()
    const { apexChartsConfig } = this.props
    let { resultClass, resultClassConfig } = this.props
    if (resultClassConfig.dropdownForResultClasses) {
      resultClass = resultClassConfig.defaultResultClass
      resultClassConfig = resultClassConfig.resultClasses[resultClass]
    }
    this.state = {
      resultClass,
      resultClassConfig,
      createChartData: resultClassConfig.createChartData
        ? apexChartsConfig[resultClassConfig.createChartData]
        : apexChartsConfig[resultClassConfig.chartTypes[0].createChartData],
      chartType: resultClassConfig.dropdownForChartTypes ? resultClassConfig.chartTypes[0].id : null,
      dialogData: null
    }
  }

  componentDidMount = () => {
    const { results } = this.props
    const { doNotRenderOnMount } = this.props.resultClassConfig
    if (results && results.length > 0 && !doNotRenderOnMount) {
      this.renderChart()
    }
    this.props.fetchData({
      perspectiveID: this.props.perspectiveConfig.id,
      resultClass: this.state.resultClass,
      facetClass: this.props.facetClass,
      facetID: this.props.facetID,
      uri: this.props.perspectiveState && this.props.perspectiveState.instanceTableData
        ? this.props.perspectiveState.instanceTableData.id
        : null
    })
  }

  componentDidUpdate = (prevProps, prevState) => {
    if (prevProps.resultUpdateID !== this.props.resultUpdateID) {
      this.renderChart()
    }
    const { pageType = 'facetResults' } = this.props
    if (pageType === 'facetResults' && prevProps.facetUpdateID !== this.props.facetUpdateID) {
      this.props.fetchData({
        perspectiveID: this.props.perspectiveConfig.id,
        resultClass: this.state.resultClass,
        facetClass: this.props.facetClass,
        facetID: this.props.facetID
      })
    }
    if (prevState.resultClass !== this.state.resultClass) {
      this.props.fetchData({
        perspectiveID: this.props.perspectiveConfig.id,
        resultClass: this.state.resultClass,
        facetClass: this.props.facetClass,
        facetID: this.props.facetID
      })
    }
    if (prevState.chartType !== this.state.chartType) {
      this.renderChart()
    }
    if (prevProps.screenSize !== this.props.screenSize) {
      this.renderChart()
    }
    if (prevProps.instanceAnalysisDataUpdateID !== this.props.instanceAnalysisDataUpdateID) {
      this.setState({
        dialogData: this.props.instanceAnalysisData
      })
    }
  }

  componentWillUnmount () {
    if (!this.chart == null) {
      this.chart.destroy()
    }
  }

  renderChart = () => {
    if (this.props.results) {
      // Destroy the previous chart
      if (this.chart !== undefined) {
        this.chart.destroy()
      }
      let chartTypeObj = null
      const { resultClassConfig, chartType } = this.state
      if (resultClassConfig.dropdownForChartTypes) {
        chartTypeObj = resultClassConfig.chartTypes.find(chartTypeObj => chartTypeObj.id === chartType)
      }
      this.chart = new ApexCharts(
        this.chartRef.current,
        this.state.createChartData({
          ...this.props,
          resultClassConfig: this.state.resultClassConfig,
          chartTypeObj,
          fetchInstanceAnalysis: this.props.fetchInstanceAnalysis
        })
      )
      this.chart.render()
    }
  }

  handleResultClassOnChange = event => {
    const { apexChartsConfig } = this.props
    const newResultClass = event.target.value
    const resultClassConfig = this.props.resultClassConfig.resultClasses[newResultClass]
    this.setState({
      resultClass: newResultClass,
      resultClassConfig,
      createChartData: resultClassConfig.createChartData
        ? apexChartsConfig[resultClassConfig.createChartData]
        : apexChartsConfig[resultClassConfig.chartTypes[0].createChartData],
      chartType: resultClassConfig.dropdownForChartTypes ? resultClassConfig.chartTypes[0].id : null
    })
  }

  handleChartTypeOnChange = event => {
    const { resultClassConfig } = this.state
    const chartType = event.target.value
    const chartTypeObj = resultClassConfig.chartTypes.find(chartTypeObj => chartTypeObj.id === chartType)
    this.setState({
      chartType,
      createChartData: this.props.apexChartsConfig[chartTypeObj.createChartData]
    })
  }

  handleDialogOnClose = event => this.setState({ dialogData: null })

  isSmallScreen = () => {
    const { screenSize } = this.props
    return screenSize === 'xs' || screenSize === 'sm'
  }

  getHeightForRootContainer = () => {
    if (this.isSmallScreen()) {
      return 'auto'
    }
    const rootHeightReduction = this.props.portalConfig.layoutConfig.tabHeight + 2 * defaultPadding + 1
    return `calc(100% - ${rootHeightReduction}px)`
  }

  getHeightForChartContainer = () => {
    const { dropdownForResultClasses, dropdownForChartTypes } = this.props.resultClassConfig
    if (this.isSmallScreen()) {
      return 600
    }
    let chartHeightReduction = 0
    if (dropdownForResultClasses) {
      chartHeightReduction += 40 // dropdown height
    }
    if (dropdownForChartTypes) {
      chartHeightReduction += 40 // dropdown height
    }
    return `calc(100% - ${chartHeightReduction}px)`
  }

  render () {
    // static configs from props
    const { classes, fetching, resultClassConfig } = this.props
    const { pageType = 'facetResults', dropdownForResultClasses, resultClasses } = resultClassConfig
    // dynamic configs from state
    const { dropdownForChartTypes, chartTypes } = this.state.resultClassConfig
    let rootStyle = {
      width: '100%',
      height: '100%'
    }
    if (pageType === 'facetResults' || pageType === 'instancePage') {
      const padding = this.isSmallScreen() ? smallScreenPadding : defaultPadding
      rootStyle = {
        height: this.getHeightForRootContainer(),
        width: `calc(100% - ${2 * padding}px)`,
        padding: padding,
        backgroundColor: '#fff',
        borderTop: '1px solid rgba(224, 224, 224, 1)'
      }
    }
    const spinnerContainerStyle = {
      display: 'flex',
      width: '100%',
      height: '100%',
      alignItems: 'center',
      justifyContent: 'center'
    }
    const chartContainerStyle = {
      width: '100%',
      height: this.getHeightForChartContainer()
    }
    let dropdownText = intl.get('apexCharts.grouping')
    if (this.props.xaxisType === 'numeric') {
      dropdownText = intl.get('apexCharts.property')
    }
    return (
      <div style={rootStyle}>
        {dropdownForResultClasses &&
          <div className={classes.selectContainer}>
            <Typography>{dropdownText}</Typography>
            <FormControl className={classes.formControl}>
              <Select
                id='select-result-class'
                value={this.state.resultClass}
                onChange={this.handleResultClassOnChange}
              >
                {Object.keys(resultClasses).map(resultClass =>
                  <MenuItem key={resultClass} value={resultClass}>{intl.get(`apexCharts.resultClasses.${resultClass}`)}</MenuItem>
                )}
              </Select>
            </FormControl>
          </div>}
        {dropdownForChartTypes &&
          <div className={classes.selectContainer}>
            <Typography>{intl.get('apexCharts.chartType')}</Typography>
            <FormControl className={classes.formControl}>
              <Select
                id='select-chart-type'
                value={this.state.chartType}
                onChange={this.handleChartTypeOnChange}
              >
                {chartTypes.map(chartType =>
                  <MenuItem key={chartType.id} value={chartType.id}>{intl.get(`apexCharts.${chartType.id}`)}</MenuItem>
                )}
              </Select>
            </FormControl>
          </div>}
        {fetching &&
          <div style={spinnerContainerStyle}>
            <CircularProgress style={{ color: purple[500] }} thickness={5} />
          </div>}
        {!fetching &&
          <div style={chartContainerStyle}>
            <div ref={this.chartRef} />
          </div>}
        {this.state.dialogData &&
          <GeneralDialog open maxWidth='sm' onClose={this.handleDialogOnClose}>
            <Typography><b>Maakunta:</b> {this.state.dialogData[0].selectedProvinceLabel}</Typography>
            <Typography><b>Aikakausi:</b> {this.state.dialogData[0].selectedPeriodLabel}</Typography>
            <InstaceList
              data={this.state.dialogData}
              listHeadingSingleInstance={this.props.listHeadingSingleInstance}
              listHeadingMultipleInstances={this.props.listHeadingMultipleInstances}
            />
          </GeneralDialog>}
      </div>
    )
  }
}

ApexChart.propTypes = {
  fetchData: PropTypes.func.isRequired,
  resultClass: PropTypes.string,
  facetClass: PropTypes.string
}

export const ApexChartComponent = ApexChart

export default withStyles(styles)(ApexChart)