Skip to content
Snippets Groups Projects
Bar.tsx 10.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    import { ScaleBand, ScaleLinear } from 'd3-scale'
    
    import { FluidType, TimeStep } from 'enums'
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    import { DateTime } from 'luxon'
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    import { Dataload } from 'models'
    
    import React, { useEffect, useState } from 'react'
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    import DateChartService from 'services/dateChart.service'
    
    import {
    
      setCurrentDataChartIndex,
    
      setSelectedDate,
    
    } from 'store/chart/chart.slice'
    
    import { useAppDispatch, useAppSelector } from 'store/hooks'
    
    import { formatDate } from 'utils/date'
    import { getFluidName, getFluidUnit } from 'utils/utils'
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    interface BarProps {
      index: number
    
    Yoan VALLET's avatar
    Yoan VALLET committed
      dataload: Dataload
    
      compare: boolean
    
    Yoan VALLET's avatar
    Yoan VALLET committed
      compareDataload: Dataload | null
    
      fluidType: FluidType
    
      timeStep: TimeStep
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
      xScale: ScaleBand<string>
      yScale: ScaleLinear<number, number>
      height: number
    
      isSwitching: boolean
    
    Rémi PAPIN's avatar
    Rémi PAPIN committed
      isDuel?: boolean
    
      isMultiMissingFluid?: boolean
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    }
    
    
    const Bar = ({
      index,
      dataload,
    
      compareDataload,
    
      fluidType,
    
      timeStep,
    
      xScale,
      yScale,
      height,
      isSwitching,
    
    Rémi PAPIN's avatar
    Rémi PAPIN committed
      isDuel,
    
    }: BarProps) => {
    
      const dispatch = useAppDispatch()
    
        chart: { selectedDate, currentTimeStep },
    
        global: { fluidStatus },
      } = useAppSelector(state => state.ecolyo)
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
      const [clicked, setClicked] = useState(false)
      const [animationEnded, setAnimationEnded] = useState(false)
      const [compareAnimationEnded, setCompareAnimationEnded] = useState(false)
    
    Romain CREY's avatar
    Romain CREY committed
    
    
      const isMulti = fluidType === FluidType.MULTIFLUID
    
      const FLUIDNAME = getFluidName(fluidType).toUpperCase()
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
      const handleClick = () => {
    
        if (!isSwitching && !isDuel && clickable) {
    
          setClicked(true)
    
          dispatch(setSelectedDate(dataload.date))
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
      }
    
      const onAnimationEnd = () => {
        setClicked(false)
        setAnimationEnded(true)
      }
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
      const onCompareAnimationEnd = () => {
        setClicked(false)
        setCompareAnimationEnded(true)
      }
    
    
      /** Get date of each fluid to display placeholder bar for multifluid */
      const lastDataDates = fluidStatus.map(
        fluid => fluid.lastDataDate ?? DateTime.local(0)
      )
    
      /** Pick current fluid date or max for multi */
      let lastDataDate = isMulti
    
        ? DateTime.max(...lastDataDates)
        : lastDataDates[fluidType]
    
      /** Remove a day when viewing half-hour */
      lastDataDate = lastDataDate.plus({
        days: currentTimeStep === TimeStep.HALF_AN_HOUR ? 1 : 0,
      })
    
      /** True when between lastData and today's range */
      const isDataMissingOrUpcoming = Boolean(
    
        dataload.date > lastDataDate && dataload.date < DateTime.local()
      )
    
    
    
      /** Adjust value for upcoming data (placeholder bars) */
    
      if (isDataMissingOrUpcoming && average) {
    
        if (average > 1) {
    
          // Use 10% of average if average is above 1
          value = average * 0.1
    
        } else if (average < 1) {
    
          value = 0.2
    
      const yScaleValue = yScale(value) ?? 0
      const yScaleCompareValue = yScale(compareDataload?.value ?? 0) ?? 0
    
      const xScaleValue =
        xScale(dataload.date.toLocaleString(DateTime.DATETIME_SHORT)) ?? 0
    
    Yoan VALLET's avatar
    Yoan VALLET committed
      const isSelectedDate = isDuel
        ? false
        : new DateChartService().compareStepDate(
            timeStep,
            selectedDate,
            dataload.date
          )
    
    Romain CREY's avatar
    Romain CREY committed
    
    
      const clickedAnim = clicked ? 'bounce-2 delay' : ''
      const disabled = clickable ? '' : 'disabled'
      const selected = isSelectedDate ? 'selected' : ''
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    
    
      const getBarClass = () => {
    
        const upcoming = dataload.value === -1 ? 'bar-UPCOMING' : ''
    
        const baseStyles = [
          `bar-${FLUIDNAME}`,
          upcoming,
          weekdays,
          selected,
          disabled,
        ]
          .filter(Boolean)
          .join(' ')
    
    
        if (clicked) {
    
          return `${baseStyles} ${clickedAnim}`
    
        return `${baseStyles} bounce-1 delay--${index}`
    
      }
    
      const getCompareBarClass = () => {
    
        const animate = `bounce-2 delay--${clicked ? 0 : index}`
        const animationClass = compareAnimationEnded ? '' : animate
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    
    
        return `bar-compare-${FLUIDNAME} ${selected} ${animationClass} ${clickedAnim}`
    
      }
    
      const barBackgroundClass = isSelectedDate
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    
      const getBandWidth = (): number => {
    
        return compare ? xScale.bandwidth() / 2 : xScale.bandwidth()
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
      }
    
      const topRoundedRect = (
    
        x: number,
        y: number,
        width: number,
        height: number
    
      ): string => {
    
        const radius = height > 4 ? 4 : height / 4
    
        return `
          M${x},${y + radius}
          a${radius},${radius} 0 0 1 ${radius},${-radius}
          h${width - 2 * radius}
          a${radius},${radius} 0 0 1 ${radius},${radius}
          v${height - radius}
          h${-width}
          z`
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
      }
    
      useEffect(() => {
    
    Yoan VALLET's avatar
    Yoan VALLET committed
        if (isSelectedDate && !isDuel) {
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
          setClicked(true)
    
          dispatch(setCurrentDataChartIndex(index))
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
        }
    
    Yoan VALLET's avatar
    Yoan VALLET committed
      }, [dispatch, isSelectedDate, isDuel, index])
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    
      return (
        <g>
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          {height > 0 && (
    
            <g
              transform={`translate(${xScaleValue}, -40)`}
    
              className={`barContainer ${disabled}`}
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
              <rect
    
                onClick={!weekdays ? handleClick : () => undefined}
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
                x="0"
                y="0"
    
                width={compare ? getBandWidth() * 2 : getBandWidth()}
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
                height={height + 40}
                className={`background-${barBackgroundClass}`}
    
    Romain CREY's avatar
    Romain CREY committed
                fill="#E0E0E0"
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
              />
            </g>
    
          {/* placeholder bar for upcoming values */}
    
          {height > 0 && isDataMissingOrUpcoming && (
    
            <g
              transform={`translate(${xScaleValue}, ${yScaleValue})`}
              className="barFill"
            >
              <defs>
                <linearGradient id="gradient" x1="0" x2="0" y1="0" y2="1">
                  <stop id="stop-color-1" offset="0%" />
                  <stop id="stop-color-2" offset="100%" />
                </linearGradient>
              </defs>
              <path
                d={topRoundedRect(
                  compare ? getBandWidth() : 0,
                  0,
                  weekdays ? 3 : getBandWidth(),
                  height - yScaleValue
                )}
                className={getBarClass()}
                onClick={!weekdays ? handleClick : () => undefined}
                onAnimationEnd={onAnimationEnd}
              />
            </g>
          )}
    
          {/* hashed or filled bars */}
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          {height > 0 && dataload.value >= 0 && isMultiMissingFluid ? (
    
            <g
              transform={`translate(${xScaleValue}, ${yScaleValue})`}
              fill="#00000"
    
              className="barFill"
    
              <defs>
                <linearGradient
                  id="gradient"
    
                  x1="0"
                  x2="0"
                  y1="0"
                  y2="1"
                >
                  <stop id="stop-color-1" offset="0%" />
                  <stop id="stop-color-2" offset="100%" />
                </linearGradient>
                <pattern
                  id="diagonalHatch"
                  patternUnits="userSpaceOnUse"
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                  width="8"
                  height="8"
    
                  <rect x="0" y="0" width="100%" height="100%" fill="#121212" />
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                  <path d="M0 8h20z" strokeWidth="3" stroke="#71612E" fill="none" />
    
                </pattern>
                <pattern
                  id="diagonalHatchSelected"
                  patternUnits="userSpaceOnUse"
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                  width="8"
                  height="8"
    
                  <rect x="0" y="0" width="100%" height="100%" fill="#121212" />
    
    Guilhem CARRON's avatar
    Guilhem CARRON committed
                  <path d="M0 8h20z" strokeWidth="3" stroke="#E3B82A" fill="none" />
    
                </pattern>
              </defs>
              <path
                d={topRoundedRect(
    
                  compare ? getBandWidth() : 0,
    
                  0,
                  getBandWidth(),
                  height - yScaleValue
                )}
    
                stroke={!isSelectedDate ? '#71612E' : '#e3b82a'}
    
                fill={
                  isSelectedDate
                    ? 'url(#diagonalHatchSelected)'
                    : 'url(#diagonalHatch)'
                }
    
                onClick={!weekdays ? handleClick : () => undefined}
    
                onAnimationEnd={onAnimationEnd}
              />
            </g>
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
          ) : (
    
            Boolean(height > 0 && dataload.value && dataload.value >= 0) && (
    
              // default colored bar
    
              <g
                className="barValue"
                transform={`translate(${xScaleValue}, ${yScaleValue})`}
                tabIndex={focusable ? 0 : -1}
                onKeyDown={event => {
                  if (event.key === ' ') {
                    event.preventDefault() // prevent from scrolling page
                    handleClick()
                  }
                }}
              >
                <title>
                  {t('consumption.accessibility.bar', {
                    date: formatDate(currentTimeStep, dataload.date),
                    value: Math.round(dataload.value * 100) / 100,
                    unit: getFluidUnit(fluidType),
                  })}
                </title>
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
                <defs>
                  <linearGradient
                    id="gradient"
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
                    x1="0"
                    x2="0"
                    y1="0"
                    y2="1"
                  >
                    <stop id="stop-color-1" offset="0%" />
                    <stop id="stop-color-2" offset="100%" />
                  </linearGradient>
                </defs>
                <path
                  d={topRoundedRect(
    
                    compare ? getBandWidth() : 0,
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
                    0,
                    weekdays ? 3 : getBandWidth(),
                    height - yScaleValue
                  )}
    
                  className={isDuel ? 'bar-duel' : getBarClass()}
    
                  onClick={!weekdays ? handleClick : () => undefined}
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
                  onAnimationEnd={onAnimationEnd}
                />
              </g>
            )
          )}
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    
    
          {compare && compareDataload && compareDataload.value >= 0 && (
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
            <g transform={`translate(${xScaleValue}, ${yScaleCompareValue})`}>
              <defs>
                <linearGradient
                  id="gradient-compare"
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
                  x1="0"
                  x2="0"
                  y1="0"
                  y2="1"
                >
                  <stop id="stop-compare-color-1" offset="0%" />
                  <stop id="stop-compare-color-2" offset="100%" />
                </linearGradient>
              </defs>
              <path
                d={topRoundedRect(
                  0,
                  0,
                  getBandWidth(),
                  height - yScaleCompareValue
                )}
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
                onClick={handleClick}
                onAnimationEnd={onCompareAnimationEnd}
              />
            </g>
          )}
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
        </g>
      )
    }
    
    export default Bar