d3-array#extent JavaScript Examples

The following examples show how to use d3-array#extent. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: data-generator-utils.js    From ThreatMapper with Apache License 2.0 7 votes vote down vote up
// Inspired by Lee Byron's test data generator.
/* eslint-disable */
function bumpLayer(n, maxValue) {
  function bump(a) {
    const x = 1 / (0.1 + Math.random());
    const y = 2 * Math.random() - 0.5;
    const z = 10 / (0.1 + Math.random());
    for (let i = 0; i < n; i++) {
      const w = (i / n - y) * z;
      a[i] += x * Math.exp(-w * w);
    }
  }

  const a = [];
  let i;
  for (i = 0; i < n; ++i) a[i] = 0;
  for (i = 0; i < 5; ++i) bump(a);
  const values = a.map(function(d) { return Math.max(0, d * maxValue); });
  const s = scaleLinear().domain(extent(values)).range([0, maxValue]);
  return values.map(s);
}
Example #2
Source File: sparklines.js    From website with Apache License 2.0 5 votes vote down vote up
StatisticSparkline = ({ data, field, color }) => {
  const height = 40
  const width = 120
  const marginTop = 5
  const marginRight = 20
  const dates = []
  const values = []
  const averages = []
  data.forEach((item, key) => {
    if (key < 14) {
      return
    }
    let sum = 0
    for (let i = key; i > key - 7; i -= 1) {
      sum += data[i][field]
    }
    averages.push({
      value: sum / 7,
      date: DateTime.fromISO(item.date).toJSDate(),
    })
  })

  averages.forEach(item => {
    dates.push(item.date)
    values.push(item.value)
  })

  const dateDomain = extent(dates)

  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([0, width])

  const yMaxEffective = max(values)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height, 0])

  const lineFn = line()
    .defined(d => !Number.isNaN(d.value) && d.value !== null)
    .curve(curveNatural)
    .x(d => xScaleTime(d.date))
    .y(d => yScale(d.value))

  return (
    <svg
      className={sparklineStyles.sparkline}
      viewBox={`0 0 ${width + marginRight} ${height - marginTop}`}
      aria-hidden
    >
      <g transform={`translate(0 ${marginTop})`}>
        <defs>
          <marker
            id={`triangle-${field}`}
            refX="10"
            refY="6"
            markerWidth="30"
            markerHeight="30"
            markerUnits="userSpaceOnUse"
            orient="auto"
          >
            <path d="M 0 0 12 6 0 12 3 6" style={{ fill: color }} />
          </marker>
        </defs>
        <path
          d={lineFn(averages)}
          stroke={color}
          strokeWidth={3}
          fill="none"
          markerEnd={`url(#triangle-${field})`}
        />
      </g>
    </svg>
  )
}
Example #3
Source File: area-chart.js    From website with Apache License 2.0 4 votes vote down vote up
AreaChart = ({
  annotations,
  data,
  fill,
  height,
  labelOrder,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop,
  xTicks,
  width,
  yFormat,
  yMax,
  yTicks,
  showTicks,
  focusable,
  dateExtent,
  renderTooltipContents,
}) => {
  const grouped = nest()
    .key(d => d.label)
    .entries(data)

  const sorted = !labelOrder
    ? grouped
    : labelOrder
        .map(label => {
          const match = grouped.find(d => d.key === label)
          return match
        })
        .filter(d => d)

  const domain = dateExtent || extent(data, d => d.date)
  const valueMax = max(data, d => d.value)

  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom
  const fillFn = typeof fill === 'string' ? fill : d => fill(d.key)
  const xScale = scaleTime()
    .domain(domain)
    .range([0, width - totalXMargin])
  const yScale = scaleLinear()
    .domain([0, yMax || valueMax])
    .range([height - totalYMargin, 0])

  const a = area()
    .x(d => xScale(d.date))
    .y0(d => yScale(d.value))
    .y1(height - totalYMargin)

  const [tooltip, setTooltip] = useState(null)
  function svgPoint(svg, x, y) {
    const pt = svg.createSVGPoint()
    pt.x = x
    pt.y = y
    return pt.matrixTransform(svg.getScreenCTM().inverse())
  }
  const handleMouseMove = event => {
    const isTouchEvent = !event.clientX
    if (!renderTooltipContents) return
    const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
    const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
    const result = svgPoint(event.currentTarget, eventX, eventY)
    const date = xScale.invert(result.x - marginLeft + 8)
    date.setHours(0, 0, 0)
    setTooltip({
      top: isTouchEvent ? eventY - 130 : eventY + 10,
      left: isTouchEvent ? eventX - 80 : eventX + 5,
      date,
    })
  }
  const handleMouseLeave = () => setTooltip(null)
  const dateMap = useMemo(
    () =>
      merge(
        ...data.map(d => ({
          [d.date]: {
            date: d.date,
            [d.label]: d.value,
          },
        })),
      ),
    [data],
  )

  const handleTouchEndCapture = event => {
    setTooltip(null)
    event.preventDefault()
  }

  return (
    <>
      <svg
        className={chartStyles.chart}
        viewBox={`0 0 ${width} ${height}`}
        focusable={focusable}
        aria-hidden
        onTouchStart={handleMouseMove}
        onTouchEndCapture={handleTouchEndCapture}
        onMouseMoveCapture={handleMouseMove}
        onMouseLeave={handleMouseLeave}
      >
        {showTicks ? (
          <g transform={`translate(${marginLeft} ${marginTop})`}>
            <g transform={`translate(0 ${height - totalYMargin})`}>
              {xScale.ticks(xTicks).map(tick => (
                <text
                  className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                  key={tick}
                  x={xScale(tick)}
                  y={20}
                >
                  {formatDate(tick)}
                </text>
              ))}
            </g>
            <g>
              {yScale.ticks(yTicks).map((tick, i) => (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 4}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {yFormat
                        ? yFormat(tick, i, yScale.ticks(yTicks).length)
                        : formatNumber(tick)}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ))}
            </g>
          </g>
        ) : (
          <line
            className={chartStyles.gridLine}
            x1={0}
            x2={width}
            y1={height - 1}
            y2={height - 1}
          />
        )}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {sorted.map(d => (
            <path key={d.key} d={a(d.values)} opacity={0.8} fill={fillFn(d)} />
          ))}
        </g>
        {annotations && (
          <g transform={`translate(${marginLeft} ${marginTop})`}>
            {annotations.map(d => (
              <line
                key={d.date}
                stroke="black"
                strokeWidth="2px"
                x1={xScale(d.date) - 1}
                x2={xScale(d.date) - 1}
                y1="0"
                y2={height - marginTop - marginBottom}
              />
            ))}
          </g>
        )}
      </svg>
      {tooltip && renderTooltipContents && dateMap[tooltip.date] && (
        <Tooltip {...tooltip}>
          {renderTooltipContents(dateMap[tooltip.date])}
        </Tooltip>
      )}
    </>
  )
}
Example #4
Source File: bar-chart.js    From website with Apache License 2.0 4 votes vote down vote up
BarChart = ({
  data,
  lineData,
  refLineData,
  annotations,
  handleAnnotationClick,
  fill,
  lineColor,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop,
  showTicks,
  width,
  height,
  yMax,
  yTicks,
  lastXTick,
  renderTooltipContents,
  perCapLabel,
}) => {
  const chartRef = useRef()
  const [tooltip, setTooltip] = useState(null)
  // Used for tooltip optimization
  const [timeoutRef, setTimeoutRef] = useState(null)
  const [keyboardFocus, setKeyboardFocus] = useState(false)

  // Used when placing annotations
  const getValueForDate = date => {
    const dateData = data.find(d => d.date.getTime() === date.getTime())
    return dateData && dateData.value
  }
  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom
  // The x range is over the area in which the chart should be displaying.
  // We don't use an X transform to place it in the correct spot, we use range
  // instead
  const xScale = scaleBand()
    .domain(data.map(d => d.date))
    .range([marginLeft, width - marginRight])
    .padding(0.1)
  const dateDomain = extent(data, d => d.date)
  // Should probably refactor to use a single x-axis scale
  // but the bars make use of the band.
  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([marginLeft, width - marginRight])

  const yMaxEffective =
    yMax || max([...data, ...(refLineData || [])], d => d.value)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height - totalYMargin, 0])

  const msInOneMonth = 2628000000
  const monthlyTickInterval = Math.ceil(
    Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
  )

  const xTickAmount = timeMonth.every(monthlyTickInterval)

  const yTicksThreshold = 4
  const yTicksEffective =
    yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold

  const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()

  let lineFn = null
  if (lineData) {
    lineFn = line()
      .defined(d => !Number.isNaN(d.value) && d.value !== null)
      .curve(curveCardinal)
      .x(d => xScaleTime(d.date))
      .y(d => yScale(d.value))
  }
  const hover = (event, d) => {
    // Ensure that tooltip doesn't flash when transitioning between bars
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    const isTouchEvent = !event.clientX
    const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
    const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
    setTooltip({
      top: isTouchEvent ? eventY - 130 : eventY + 10,
      left: isTouchEvent ? eventX - 80 : eventX + 5,
      d,
    })
  }

  const mouseOut = () => {
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    setTimeoutRef(setTimeout(() => setTooltip(null), 200))
  }

  useEffect(() => {
    if (keyboardFocus === false || typeof data[keyboardFocus] === 'undefined') {
      return
    }
    const column = data[keyboardFocus]
    setTooltip({
      top: chartRef.current.getBoundingClientRect().top,
      left: chartRef.current.getBoundingClientRect().left,
      d: column,
    })
  }, [keyboardFocus])

  return (
    <>
      <svg
        className={classnames(chartStyles.chart, styles.chart)}
        viewBox={`0 0 ${width} ${height}`}
        tabIndex="0"
        aria-hidden
        ref={chartRef}
        onBlur={() => {
          setTooltip(null)
          setKeyboardFocus(false)
        }}
        onKeyDown={event => {
          if (event.key === 'Escape') {
            setTooltip(null)
            setKeyboardFocus(false)
            chartRef.current.blur()
          }
          if (event.key === 'ArrowRight') {
            setKeyboardFocus(
              keyboardFocus < data.length ? keyboardFocus + 1 : data.length,
            )
          }
          if (
            (event.shiftKey && event.key === 'Tab') ||
            event.key === 'ArrowLeft'
          ) {
            setKeyboardFocus(keyboardFocus > 0 ? keyboardFocus - 1 : 0)
          }
        }}
      >
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          <text className={classnames(chartStyles.label, styles.directions)}>
            Use arrows to move, Escape to leave.
          </text>
        </g>
        {/* y ticks */}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {yScale.ticks(yTicksEffective).map(
            (tick, i) =>
              i < showTicks && (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 6}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {formatNumber(tick)}
                      {tick > 0 &&
                        perCapLabel /* this only displays if passed */}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ),
          )}
        </g>
        {/* x ticks (dates) */}
        <g transform={`translate(0, ${height - marginBottom})`}>
          {xScaleTime.ticks(xTickAmount).map(d => (
            <Fragment key={`x-${d}`}>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                key={d}
                x={xScaleTime(d)}
                y="20"
              >{`${formatDate(d)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(d)}
                y1="0"
                x2={xScaleTime(d)}
                y2="5"
              />
            </Fragment>
          ))}
          {lastXTick && (
            <>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                x={xScaleTime(lastTime)}
                y="20"
              >{`${formatDate(lastTime)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(lastTime)}
                y1="0"
                x2={xScaleTime(lastTime)}
                y2="5"
              />
            </>
          )}
        </g>

        <mask id="dataMask">
          <rect
            x="0"
            y="0"
            width={width - marginRight}
            height={height - totalYMargin}
            fill="white"
          />
        </mask>

        {/* data */}
        <g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
          {/* bars (data) */}
          {data.map((d, key) => (
            <rect
              key={d.date + d.value}
              x={xScale(d.date)}
              y={yScale(d.value)}
              height={yScale(0) - yScale(d.value)}
              width={xScale.bandwidth()}
              fillOpacity={lineData ? 1 : 0.8}
              fill={fill}
              className={classnames(
                renderTooltipContents && styles.interactiveBar,
                key === keyboardFocus && styles.selected,
              )}
              onMouseOver={event => hover(event, d)}
              onFocus={event => hover(event, d)}
              onMouseOut={mouseOut}
              onBlur={mouseOut}
            />
          ))}
          {/* line */}
          {lineData && (
            <path
              d={lineFn(lineData)}
              stroke={lineColor}
              strokeWidth="3"
              fill="none"
            />
          )}
          {/* reference line */}
          {refLineData && (
            <path
              d={lineFn(refLineData)}
              stroke="black"
              strokeWidth="2"
              strokeDasharray="4"
              fill="none"
            />
          )}
        </g>
        {/* annotations */}
        {annotations && (
          <g transform={`translate(0 ${marginTop})`}>
            {annotations
              .filter(
                annotation =>
                  xScaleTime(annotation.date) >= xScaleTime(dateDomain[0]) &&
                  xScaleTime(annotation.date) <= xScaleTime(dateDomain[1]),
              )
              .map(d => (
                <AnnotationBubble
                  content={d}
                  xScaleTime={xScaleTime}
                  yScale={yScale}
                  handleAnnotationClick={handleAnnotationClick}
                  getValueForDate={getValueForDate}
                />
              ))}
          </g>
        )}
      </svg>
      {renderTooltipContents && tooltip && (
        <Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
      )}
    </>
  )
}
Example #5
Source File: line-chart.js    From website with Apache License 2.0 4 votes vote down vote up
LineChart = ({
  data,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop = 0,
  showTicks,
  width,
  height,
  yMax,
  yTicks,
  lastXTick,
  perCapLabel,
  renderTooltipContents,
}) => {
  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom

  const dates = []
  const values = []
  data.forEach(item => {
    item.data.forEach(row => {
      dates.push(row.date)
      values.push(row.value)
    })
  })

  const [tooltip, setTooltip] = useState(null)
  const [timeoutRef, setTimeoutRef] = useState(null)

  const hover = (event, dataLine) => {
    // Ensure that tooltip doesn't flash when transitioning between bars
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    const isTouchEvent = !event.clientX
    const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
    const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY

    setTooltip({
      top: isTouchEvent ? eventY - 130 : eventY + 10,
      left: isTouchEvent ? eventX - 80 : eventX + 5,
      d: dataLine,
    })
  }

  const mouseOut = () => {
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    setTimeoutRef(setTimeout(() => setTooltip(null), 200))
  }

  const dateDomain = extent(dates)

  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([marginLeft, width - marginRight])

  const yMaxEffective = yMax || max(values)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height - totalYMargin, 0])

  const msInOneMonth = 2628000000
  const monthlyTickInterval = Math.ceil(
    Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
  )

  const xTickAmount = timeMonth.every(monthlyTickInterval)

  const yTicksThreshold = 4
  const yTicksEffective =
    yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold

  const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()

  const lineFn = line()
    .defined(d => !Number.isNaN(d.value) && d.value !== null)
    .curve(curveCardinal)
    .x(d => xScaleTime(d.date))
    .y(d => yScale(d.value))

  return (
    <>
      <svg
        className={chartStyles.chart}
        viewBox={`0 0 ${width} ${height}`}
        aria-hidden
      >
        {/* y ticks */}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {yScale.ticks(yTicksEffective).map(
            (tick, i) =>
              i < showTicks && (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 6}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {formatNumber(tick)}
                      {tick > 0 &&
                        perCapLabel /* this only displays if passed */}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ),
          )}
        </g>
        {/* x ticks (dates) */}
        <g transform={`translate(0, ${height - marginBottom})`}>
          {xScaleTime.ticks(xTickAmount).map(d => (
            <Fragment key={`x-${d}`}>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                key={d}
                x={xScaleTime(d)}
                y="20"
              >{`${formatDate(d)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(d)}
                y1="0"
                x2={xScaleTime(d)}
                y2="5"
              />
            </Fragment>
          ))}
          {lastXTick && (
            <>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                x={xScaleTime(lastTime)}
                y="20"
              >{`${formatDate(lastTime)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(lastTime)}
                y1="0"
                x2={xScaleTime(lastTime)}
                y2="5"
              />
            </>
          )}
        </g>

        <mask id="dataMask">
          <rect
            x="0"
            y="0"
            width={width - marginRight}
            height={height - totalYMargin}
            fill="white"
          />
        </mask>

        {/* data */}
        <g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
          {data && (
            <>
              {data.map(dataLine => (
                <>
                  <path
                    d={lineFn(dataLine.data)}
                    stroke={dataLine.color}
                    strokeWidth={dataLine.stroke}
                    fill="none"
                  />
                  {/* Add a wider hidden path for tooltips. */}
                  <path
                    d={lineFn(dataLine.data)}
                    stroke="transparent"
                    strokeWidth={6}
                    fill="none"
                    onMouseOver={event => hover(event, dataLine)}
                    onFocus={event => hover(event, dataLine)}
                    onMouseOut={mouseOut}
                    onBlur={mouseOut}
                  />
                </>
              ))}
            </>
          )}
        </g>
      </svg>
      {renderTooltipContents && tooltip && (
        <Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
      )}
    </>
  )
}
Example #6
Source File: multi-line-chart.js    From website with Apache License 2.0 4 votes vote down vote up
MultiLineChart = ({
  data,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop = 0,
  showTicks,
  width,
  height,
  yMax,
  yTicks,
  lastXTick,
  perCapLabel,
}) => {
  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom

  const dates = []
  const values = []

  data.forEach(item => {
    Object.keys(item.data).forEach(category => {
      item.data[category].forEach(row => {
        dates.push(row.date)
        values.push(row.value)
      })
    })
  })

  const dateDomain = extent(dates)

  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([marginLeft, width - marginRight])

  const yMaxEffective = yMax || max(values)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height - totalYMargin, 0])

  const msInOneMonth = 2628000000
  const monthlyTickInterval = Math.ceil(
    Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
  )

  const xTickAmount = timeMonth.every(monthlyTickInterval)

  const yTicksThreshold = 4
  const yTicksEffective =
    yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold

  const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()

  const lineFn = line()
    .defined(d => !Number.isNaN(d.value) && d.value !== null)
    .curve(curveCardinal)
    .x(d => xScaleTime(d.date))
    .y(d => yScale(d.value))

  // TODO make this dry-er w/r/t the single line chart component

  return (
    <>
      <svg
        className={chartStyles.chart}
        viewBox={`0 0 ${width} ${height}`}
        aria-hidden
      >
        {/* y ticks */}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {yScale.ticks(yTicksEffective).map(
            (tick, i) =>
              i < showTicks && (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 6}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {formatNumber(tick)}
                      {tick > 0 &&
                        perCapLabel /* this only displays if passed */}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ),
          )}
        </g>
        {/* x ticks (dates) */}
        <g transform={`translate(0, ${height - marginBottom})`}>
          {xScaleTime.ticks(xTickAmount).map(d => (
            <Fragment key={`x-${d}`}>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                key={d}
                x={xScaleTime(d)}
                y="20"
              >{`${formatDate(d)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(d)}
                y1="0"
                x2={xScaleTime(d)}
                y2="5"
              />
            </Fragment>
          ))}
          {lastXTick && (
            <>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                x={xScaleTime(lastTime)}
                y="20"
              >{`${formatDate(lastTime)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(lastTime)}
                y1="0"
                x2={xScaleTime(lastTime)}
                y2="5"
              />
            </>
          )}
        </g>

        <mask id="dataMask">
          <rect
            x="0"
            y="0"
            width={width - marginRight}
            height={height - totalYMargin}
            fill="white"
          />
        </mask>

        {/* data */}
        <g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
          {data && (
            <>
              {data.map(item => (
                <>
                  {Object.keys(item.data).map(category => (
                    <path
                      d={lineFn(item.data[category])}
                      stroke={item.colorMap[category]}
                      strokeWidth="1px"
                      fill="none"
                    />
                  ))}
                </>
              ))}
            </>
          )}
        </g>
      </svg>
    </>
  )
}
Example #7
Source File: contours.js    From cs-wiki with GNU General Public License v3.0 4 votes vote down vote up
export default function() {
  var dx = 1,
      dy = 1,
      threshold = thresholdSturges,
      smooth = smoothLinear;

  function contours(values) {
    var tz = threshold(values);

    // Convert number of thresholds into uniform thresholds.
    if (!Array.isArray(tz)) {
      const e = extent(values), ts = tickStep(e[0], e[1], tz);
      tz = ticks(Math.floor(e[0] / ts) * ts, Math.floor(e[1] / ts - 1) * ts, tz);
    } else {
      tz = tz.slice().sort(ascending);
    }

    return tz.map(value => contour(values, value));
  }

  // Accumulate, smooth contour rings, assign holes to exterior rings.
  // Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js
  function contour(values, value) {
    var polygons = [],
        holes = [];

    isorings(values, value, function(ring) {
      smooth(ring, values, value);
      if (area(ring) > 0) polygons.push([ring]);
      else holes.push(ring);
    });

    holes.forEach(function(hole) {
      for (var i = 0, n = polygons.length, polygon; i < n; ++i) {
        if (contains((polygon = polygons[i])[0], hole) !== -1) {
          polygon.push(hole);
          return;
        }
      }
    });

    return {
      type: "MultiPolygon",
      value: value,
      coordinates: polygons
    };
  }

  // Marching squares with isolines stitched into rings.
  // Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js
  function isorings(values, value, callback) {
    var fragmentByStart = new Array,
        fragmentByEnd = new Array,
        x, y, t0, t1, t2, t3;

    // Special case for the first row (y = -1, t2 = t3 = 0).
    x = y = -1;
    t1 = values[0] >= value;
    cases[t1 << 1].forEach(stitch);
    while (++x < dx - 1) {
      t0 = t1, t1 = values[x + 1] >= value;
      cases[t0 | t1 << 1].forEach(stitch);
    }
    cases[t1 << 0].forEach(stitch);

    // General case for the intermediate rows.
    while (++y < dy - 1) {
      x = -1;
      t1 = values[y * dx + dx] >= value;
      t2 = values[y * dx] >= value;
      cases[t1 << 1 | t2 << 2].forEach(stitch);
      while (++x < dx - 1) {
        t0 = t1, t1 = values[y * dx + dx + x + 1] >= value;
        t3 = t2, t2 = values[y * dx + x + 1] >= value;
        cases[t0 | t1 << 1 | t2 << 2 | t3 << 3].forEach(stitch);
      }
      cases[t1 | t2 << 3].forEach(stitch);
    }

    // Special case for the last row (y = dy - 1, t0 = t1 = 0).
    x = -1;
    t2 = values[y * dx] >= value;
    cases[t2 << 2].forEach(stitch);
    while (++x < dx - 1) {
      t3 = t2, t2 = values[y * dx + x + 1] >= value;
      cases[t2 << 2 | t3 << 3].forEach(stitch);
    }
    cases[t2 << 3].forEach(stitch);

    function stitch(line) {
      var start = [line[0][0] + x, line[0][1] + y],
          end = [line[1][0] + x, line[1][1] + y],
          startIndex = index(start),
          endIndex = index(end),
          f, g;
      if (f = fragmentByEnd[startIndex]) {
        if (g = fragmentByStart[endIndex]) {
          delete fragmentByEnd[f.end];
          delete fragmentByStart[g.start];
          if (f === g) {
            f.ring.push(end);
            callback(f.ring);
          } else {
            fragmentByStart[f.start] = fragmentByEnd[g.end] = {start: f.start, end: g.end, ring: f.ring.concat(g.ring)};
          }
        } else {
          delete fragmentByEnd[f.end];
          f.ring.push(end);
          fragmentByEnd[f.end = endIndex] = f;
        }
      } else if (f = fragmentByStart[endIndex]) {
        if (g = fragmentByEnd[startIndex]) {
          delete fragmentByStart[f.start];
          delete fragmentByEnd[g.end];
          if (f === g) {
            f.ring.push(end);
            callback(f.ring);
          } else {
            fragmentByStart[g.start] = fragmentByEnd[f.end] = {start: g.start, end: f.end, ring: g.ring.concat(f.ring)};
          }
        } else {
          delete fragmentByStart[f.start];
          f.ring.unshift(start);
          fragmentByStart[f.start = startIndex] = f;
        }
      } else {
        fragmentByStart[startIndex] = fragmentByEnd[endIndex] = {start: startIndex, end: endIndex, ring: [start, end]};
      }
    }
  }

  function index(point) {
    return point[0] * 2 + point[1] * (dx + 1) * 4;
  }

  function smoothLinear(ring, values, value) {
    ring.forEach(function(point) {
      var x = point[0],
          y = point[1],
          xt = x | 0,
          yt = y | 0,
          v0,
          v1 = values[yt * dx + xt];
      if (x > 0 && x < dx && xt === x) {
        v0 = values[yt * dx + xt - 1];
        point[0] = x + (value - v0) / (v1 - v0) - 0.5;
      }
      if (y > 0 && y < dy && yt === y) {
        v0 = values[(yt - 1) * dx + xt];
        point[1] = y + (value - v0) / (v1 - v0) - 0.5;
      }
    });
  }

  contours.contour = contour;

  contours.size = function(_) {
    if (!arguments.length) return [dx, dy];
    var _0 = Math.floor(_[0]), _1 = Math.floor(_[1]);
    if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
    return dx = _0, dy = _1, contours;
  };

  contours.thresholds = function(_) {
    return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), contours) : threshold;
  };

  contours.smooth = function(_) {
    return arguments.length ? (smooth = _ ? smoothLinear : noop, contours) : smooth === smoothLinear;
  };

  return contours;
}
Example #8
Source File: contours.js    From cs-wiki with GNU General Public License v3.0 4 votes vote down vote up
export default function() {
  var dx = 1,
      dy = 1,
      threshold = thresholdSturges,
      smooth = smoothLinear;

  function contours(values) {
    var tz = threshold(values);

    // Convert number of thresholds into uniform thresholds.
    if (!Array.isArray(tz)) {
      var domain = extent(values), start = domain[0], stop = domain[1];
      tz = tickStep(start, stop, tz);
      tz = range(Math.floor(start / tz) * tz, Math.floor(stop / tz) * tz, tz);
    } else {
      tz = tz.slice().sort(ascending);
    }

    return tz.map(function(value) {
      return contour(values, value);
    });
  }

  // Accumulate, smooth contour rings, assign holes to exterior rings.
  // Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js
  function contour(values, value) {
    var polygons = [],
        holes = [];

    isorings(values, value, function(ring) {
      smooth(ring, values, value);
      if (area(ring) > 0) polygons.push([ring]);
      else holes.push(ring);
    });

    holes.forEach(function(hole) {
      for (var i = 0, n = polygons.length, polygon; i < n; ++i) {
        if (contains((polygon = polygons[i])[0], hole) !== -1) {
          polygon.push(hole);
          return;
        }
      }
    });

    return {
      type: "MultiPolygon",
      value: value,
      coordinates: polygons
    };
  }

  // Marching squares with isolines stitched into rings.
  // Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js
  function isorings(values, value, callback) {
    var fragmentByStart = new Array,
        fragmentByEnd = new Array,
        x, y, t0, t1, t2, t3;

    // Special case for the first row (y = -1, t2 = t3 = 0).
    x = y = -1;
    t1 = values[0] >= value;
    cases[t1 << 1].forEach(stitch);
    while (++x < dx - 1) {
      t0 = t1, t1 = values[x + 1] >= value;
      cases[t0 | t1 << 1].forEach(stitch);
    }
    cases[t1 << 0].forEach(stitch);

    // General case for the intermediate rows.
    while (++y < dy - 1) {
      x = -1;
      t1 = values[y * dx + dx] >= value;
      t2 = values[y * dx] >= value;
      cases[t1 << 1 | t2 << 2].forEach(stitch);
      while (++x < dx - 1) {
        t0 = t1, t1 = values[y * dx + dx + x + 1] >= value;
        t3 = t2, t2 = values[y * dx + x + 1] >= value;
        cases[t0 | t1 << 1 | t2 << 2 | t3 << 3].forEach(stitch);
      }
      cases[t1 | t2 << 3].forEach(stitch);
    }

    // Special case for the last row (y = dy - 1, t0 = t1 = 0).
    x = -1;
    t2 = values[y * dx] >= value;
    cases[t2 << 2].forEach(stitch);
    while (++x < dx - 1) {
      t3 = t2, t2 = values[y * dx + x + 1] >= value;
      cases[t2 << 2 | t3 << 3].forEach(stitch);
    }
    cases[t2 << 3].forEach(stitch);

    function stitch(line) {
      var start = [line[0][0] + x, line[0][1] + y],
          end = [line[1][0] + x, line[1][1] + y],
          startIndex = index(start),
          endIndex = index(end),
          f, g;
      if (f = fragmentByEnd[startIndex]) {
        if (g = fragmentByStart[endIndex]) {
          delete fragmentByEnd[f.end];
          delete fragmentByStart[g.start];
          if (f === g) {
            f.ring.push(end);
            callback(f.ring);
          } else {
            fragmentByStart[f.start] = fragmentByEnd[g.end] = {start: f.start, end: g.end, ring: f.ring.concat(g.ring)};
          }
        } else {
          delete fragmentByEnd[f.end];
          f.ring.push(end);
          fragmentByEnd[f.end = endIndex] = f;
        }
      } else if (f = fragmentByStart[endIndex]) {
        if (g = fragmentByEnd[startIndex]) {
          delete fragmentByStart[f.start];
          delete fragmentByEnd[g.end];
          if (f === g) {
            f.ring.push(end);
            callback(f.ring);
          } else {
            fragmentByStart[g.start] = fragmentByEnd[f.end] = {start: g.start, end: f.end, ring: g.ring.concat(f.ring)};
          }
        } else {
          delete fragmentByStart[f.start];
          f.ring.unshift(start);
          fragmentByStart[f.start = startIndex] = f;
        }
      } else {
        fragmentByStart[startIndex] = fragmentByEnd[endIndex] = {start: startIndex, end: endIndex, ring: [start, end]};
      }
    }
  }

  function index(point) {
    return point[0] * 2 + point[1] * (dx + 1) * 4;
  }

  function smoothLinear(ring, values, value) {
    ring.forEach(function(point) {
      var x = point[0],
          y = point[1],
          xt = x | 0,
          yt = y | 0,
          v0,
          v1 = values[yt * dx + xt];
      if (x > 0 && x < dx && xt === x) {
        v0 = values[yt * dx + xt - 1];
        point[0] = x + (value - v0) / (v1 - v0) - 0.5;
      }
      if (y > 0 && y < dy && yt === y) {
        v0 = values[(yt - 1) * dx + xt];
        point[1] = y + (value - v0) / (v1 - v0) - 0.5;
      }
    });
  }

  contours.contour = contour;

  contours.size = function(_) {
    if (!arguments.length) return [dx, dy];
    var _0 = Math.ceil(_[0]), _1 = Math.ceil(_[1]);
    if (!(_0 > 0) || !(_1 > 0)) throw new Error("invalid size");
    return dx = _0, dy = _1, contours;
  };

  contours.thresholds = function(_) {
    return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), contours) : threshold;
  };

  contours.smooth = function(_) {
    return arguments.length ? (smooth = _ ? smoothLinear : noop, contours) : smooth === smoothLinear;
  };

  return contours;
}
Example #9
Source File: DeltaBarGraph.js    From covid19india-react with MIT License 4 votes vote down vote up
function DeltaBarGraph({timeseries, statistic, lookback}) {
  const svgRef = useRef();
  const [wrapperRef, {width, height}] = useMeasure();

  const pastDates = Object.keys(timeseries || {}).filter(
    (date) => date <= getIndiaDateYesterdayISO()
  );
  const dates = pastDates.slice(-lookback);

  const getDeltaStatistic = useCallback(
    (date, statistic) => {
      return getStatistic(timeseries?.[date], 'delta', statistic);
    },
    [timeseries]
  );

  useEffect(() => {
    if (!width) return;
    const svg = select(svgRef.current);

    const chartRight = width - margin.right;
    const chartBottom = height - margin.bottom;
    const r = 5;

    // const formatTime = timeFormat('%e %b');
    const xScale = scaleBand()
      .domain(dates)
      .range([margin.left, chartRight])
      .paddingInner(width / 1000);

    const [statisticMin, statisticMax] = extent(dates, (date) =>
      getDeltaStatistic(date, statistic)
    );

    const yScale = scaleLinear()
      .domain([Math.min(0, statisticMin || 0), Math.max(1, statisticMax || 0)])
      .range([chartBottom, margin.top]);

    const xAxis = axisBottom(xScale)
      .tickSize(0)
      .tickFormat((date) => formatDate(date, 'dd MMM'));

    const t = svg.transition().duration(D3_TRANSITION_DURATION);

    const statisticConfig = STATISTIC_CONFIGS[statistic];

    svg
      .select('.x-axis')
      .transition(t)
      .style('transform', `translate3d(0, ${yScale(0)}px, 0)`)
      .call(xAxis)
      .on('start', () => svg.select('.domain').remove())
      .selectAll('text')
      .attr('y', 0)
      .attr('dy', (date, i) =>
        getDeltaStatistic(date, statistic) < 0 ? '-1em' : '1.5em'
      )
      .style('text-anchor', 'middle')
      .attr('fill', statisticConfig.color);

    svg
      .selectAll('.bar')
      .data(dates)
      .join((enter) =>
        enter
          .append('path')
          .attr('class', 'bar')
          .attr('d', (date) =>
            roundedBar(xScale(date), yScale(0), xScale.bandwidth(), 0, r)
          )
      )
      .transition(t)
      .attr('d', (date) =>
        roundedBar(
          xScale(date),
          yScale(0),
          xScale.bandwidth(),
          yScale(0) - yScale(getDeltaStatistic(date, statistic)),
          r
        )
      )
      .attr('fill', (date, i) => {
        return i < dates.length - 1
          ? statisticConfig.color + '90'
          : statisticConfig.color;
      });

    const textSelection = svg
      .selectAll('.label')
      .data(dates)
      .join('text')
      .attr('class', 'label')
      .attr('x', (date) => xScale(date) + xScale.bandwidth() / 2)
      .text((date) =>
        formatNumber(
          getDeltaStatistic(date, statistic),
          statisticConfig?.showDelta || statisticConfig?.nonLinear
            ? statisticConfig.format
            : 'short'
        )
      );

    textSelection
      .transition(t)
      .attr('fill', statisticConfig.color)
      .attr('y', (date) => {
        const val = getDeltaStatistic(date, statistic);
        return yScale(val) + (val < 0 ? 15 : -6);
      });

    textSelection
      .append('tspan')
      .attr(
        'dy',
        (date) => `${getDeltaStatistic(date, statistic) < 0 ? 1.2 : -1.2}em`
      )
      .attr('x', (date) => xScale(date) + xScale.bandwidth() / 2)
      .text((date, i) => {
        if (i === 0) return '';
        const prevVal = getDeltaStatistic(dates[i - 1], statistic);
        if (!prevVal) return '';
        const delta = getDeltaStatistic(date, statistic) - prevVal;
        return `${delta > 0 ? '+' : ''}${formatNumber(
          (100 * delta) / Math.abs(prevVal),
          '%'
        )}`;
      })
      .transition(t)
      .attr('fill', statisticConfig.color + '90');
  }, [dates, height, statistic, width, getDeltaStatistic]);

  return (
    <div className="DeltaBarGraph" ref={wrapperRef}>
      <svg
        ref={svgRef}
        width={width}
        height={250}
        viewBox={`0 0 ${width} ${height}`}
        preserveAspectRatio="xMidYMid meet"
      >
        <g className="x-axis" />
        <g className="y-axis" />
      </svg>
    </div>
  );
}
Example #10
Source File: Timeseries.js    From covid19india-react with MIT License 4 votes vote down vote up
function Timeseries({
  statistics,
  timeseries,
  dates,
  endDate,
  chartType,
  isUniform,
  isLog,
  isMovingAverage,
  noRegionHighlightedDistrictData,
}) {
  const {t} = useTranslation();
  const refs = useRef([]);
  const [wrapperRef, {width, height}] = useMeasure();

  const [highlightedDate, setHighlightedDate] = useState(
    dates[dates.length - 1]
  );
  useEffect(() => {
    setHighlightedDate(dates[dates.length - 1]);
  }, [dates]);

  const getTimeseriesStatistic = useCallback(
    (date, statistic, movingAverage = isMovingAverage) => {
      return getStatistic(timeseries?.[date], chartType, statistic, {
        movingAverage,
      });
    },
    [chartType, isMovingAverage, timeseries]
  );

  const condenseChart = useMemo(() => {
    const T = dates.length;
    const days = differenceInDays(
      parseIndiaDate(dates[T - 1]),
      parseIndiaDate(dates[0])
    );
    // Chart extremes
    const chartRight = width - margin.right;
    // Bar widths
    const axisWidth = Math.max(0, chartRight - margin.left) / (1.25 * days);
    return axisWidth < 4;
  }, [width, dates]);

  const xScale = useMemo(() => {
    const T = dates.length;
    const chartRight = width - margin.right;

    return scaleTime()
      .clamp(true)
      .domain([
        parseIndiaDate(dates[0] || endDate),
        parseIndiaDate(dates[T - 1] || endDate),
      ])
      .range([margin.left, chartRight]);
  }, [width, endDate, dates]);

  const yScales = useMemo(() => {
    const chartBottom = height - margin.bottom;

    const addScaleBuffer = (scale, log = false) => {
      const domain = scale.domain();
      if (log) {
        scale.domain([
          domain[0],
          domain[0] * Math.pow(domain[1] / domain[0], 1 / yScaleShrinkFactor),
        ]);
      } else {
        scale.domain([
          domain[0],
          domain[0] + (domain[1] - domain[0]) / yScaleShrinkFactor,
        ]);
      }
      return scale;
    };

    const [uniformScaleMin, uniformScaleMax] = extent(
      dates.reduce((res, date) => {
        res.push(
          ...PRIMARY_STATISTICS.map((statistic) =>
            getTimeseriesStatistic(date, statistic)
          )
        );
        return res;
      }, [])
    );

    const yScaleUniformLinear = addScaleBuffer(
      scaleLinear()
        .clamp(true)
        .domain([Math.min(0, uniformScaleMin), Math.max(1, uniformScaleMax)])
        .range([chartBottom, margin.top])
        .nice(4)
    );

    const yScaleUniformLog = addScaleBuffer(
      scaleLog()
        .clamp(true)
        .domain([1, Math.max(10, uniformScaleMax)])
        .range([chartBottom, margin.top])
        .nice(4),
      true
    );

    return statistics.map((statistic) => {
      if (isUniform) {
        if (
          chartType === 'total' &&
          isLog &&
          PRIMARY_STATISTICS.includes(statistic)
        ) {
          return yScaleUniformLog;
        } else if (PRIMARY_STATISTICS.includes(statistic)) {
          return yScaleUniformLinear;
        }
      }

      const [scaleMin, scaleMax] = extent(dates, (date) =>
        getTimeseriesStatistic(date, statistic)
      );

      if (chartType === 'total' && isLog) {
        return addScaleBuffer(
          scaleLog()
            .clamp(true)
            .domain([1, Math.max(10, scaleMax)])
            .range([chartBottom, margin.top])
            .nice(4),
          true
        );
      } else {
        return addScaleBuffer(
          scaleLinear()
            .clamp(true)
            .domain([
              Math.min(0, scaleMin),
              STATISTIC_CONFIGS[statistic].format === '%'
                ? Math.min(100, Math.max(1, scaleMax))
                : Math.max(1, scaleMax),
            ])
            .range([chartBottom, margin.top])
            .nice(4)
        );
      }
    });
  }, [
    height,
    chartType,
    isUniform,
    isLog,
    statistics,
    dates,
    getTimeseriesStatistic,
  ]);

  useEffect(() => {
    if (!width || !height) return;

    const T = dates.length;
    // Chart extremes
    const chartRight = width - margin.right;
    const chartBottom = height - margin.bottom;

    const isDiscrete = chartType === 'delta' && !isMovingAverage;

    const xAxis = (g) =>
      g
        .attr('class', 'x-axis')
        .call(axisBottom(xScale).ticks(numTicksX(width)));

    const xAxis2 = (g, yScale) => {
      g.attr('class', 'x-axis2')
        .call(axisBottom(xScale).tickValues([]).tickSize(0))
        .select('.domain')
        .style('transform', `translate3d(0, ${yScale(0)}px, 0)`);

      if (yScale(0) !== chartBottom) g.select('.domain').attr('opacity', 0.4);
      else g.select('.domain').attr('opacity', 0);
    };

    const yAxis = (g, yScale, format) =>
      g.attr('class', 'y-axis').call(
        axisRight(yScale)
          .ticks(4)
          .tickFormat((num) => formatNumber(num, format))
          .tickPadding(4)
      );

    function mousemove(event) {
      const xm = pointer(event)[0];
      const date = xScale.invert(xm);
      if (!isNaN(date)) {
        const bisectDate = bisector((date) => parseIndiaDate(date)).left;
        const index = bisectDate(dates, date, 1);
        const dateLeft = dates[index - 1];
        const dateRight = dates[index];
        setHighlightedDate(
          date - parseIndiaDate(dateLeft) < parseIndiaDate(dateRight) - date
            ? dateLeft
            : dateRight
        );
      }
    }

    function mouseout(event) {
      setHighlightedDate(dates[T - 1]);
    }

    /* Begin drawing charts */
    statistics.forEach((statistic, i) => {
      const ref = refs.current[i];
      const svg = select(ref);
      const t = svg.transition().duration(D3_TRANSITION_DURATION);

      const yScale = yScales[i];
      const statisticConfig = STATISTIC_CONFIGS[statistic];
      const format =
        STATISTIC_CONFIGS[statistic].format === '%' ? '%' : 'short';
      const isNonLinear = !!STATISTIC_CONFIGS[statistic]?.nonLinear;

      /* X axis */
      svg
        .select('.x-axis')
        .style('transform', `translate3d(0, ${chartBottom}px, 0)`)
        .transition(t)
        .call(xAxis);

      svg.select('.x-axis2').transition(t).call(xAxis2, yScale);

      /* Y axis */
      svg
        .select('.y-axis')
        .style('transform', `translate3d(${chartRight}px, 0, 0)`)
        .transition(t)
        .call(yAxis, yScale, format);

      /* Path dots */
      svg
        .selectAll('circle.normal')
        .data(condenseChart ? [] : dates, (date) => date)
        .join((enter) =>
          enter
            .append('circle')
            .attr('class', 'normal')
            .attr('fill', statisticConfig?.color)
            .attr('stroke', statisticConfig?.color)
            .attr('cx', (date) => xScale(parseIndiaDate(date)))
            .attr('cy', (date) =>
              yScale(isDiscrete ? 0 : getTimeseriesStatistic(date, statistic))
            )
            .attr('r', 2)
        )
        .transition(t)
        .attr('cx', (date) => xScale(parseIndiaDate(date)))
        .attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));

      const areaPath = (dates, allZero = false) =>
        area()
          .curve(curveStep)
          .x((date) => xScale(parseIndiaDate(date)))
          .y0(yScale(0))
          .y1(
            allZero
              ? yScale(0)
              : (date) => yScale(getTimeseriesStatistic(date, statistic, false))
          )(dates);

      svg
        .selectAll('.trend-area')
        .data(
          T && chartType === 'delta' && !isNonLinear && condenseChart
            ? [dates]
            : []
        )
        .join(
          (enter) =>
            enter
              .append('path')
              .attr('class', 'trend-area')
              .call((enter) =>
                enter
                  .attr('d', (dates) => areaPath(dates, true))
                  .transition(t)
                  .attr('d', areaPath)
              ),
          (update) =>
            update.call((update) =>
              update.transition(t).attrTween('d', function (dates) {
                const previous = select(this).attr('d');
                const current = areaPath(dates);
                return interpolatePath(previous, current);
              })
            ),
          (exit) =>
            exit.call((exit) =>
              exit
                .transition(t)
                .attr('d', (dates) => areaPath(dates, true))
                .remove()
            )
        )
        .transition(t)
        .attr('opacity', isMovingAverage ? 0.3 : 1);

      svg
        .selectAll('.stem')
        .data(
          T && chartType === 'delta' && !isNonLinear && !condenseChart
            ? dates
            : [],
          (date) => date
        )
        .join(
          (enter) =>
            enter
              .append('line')
              .attr('class', 'stem')
              .attr('stroke-width', 4)
              .attr('x1', (date) => xScale(parseIndiaDate(date)))
              .attr('y1', yScale(0))
              .attr('x2', (date) => xScale(parseIndiaDate(date)))
              .attr('y2', yScale(0)),
          (update) => update,
          (exit) =>
            exit.call((exit) =>
              exit
                .transition(t)
                .attr('x1', (date) => xScale(parseIndiaDate(date)))
                .attr('x2', (date) => xScale(parseIndiaDate(date)))
                .attr('y1', yScale(0))
                .attr('y2', yScale(0))
                .remove()
            )
        )
        .transition(t)
        .attr('x1', (date) => xScale(parseIndiaDate(date)))
        .attr('y1', yScale(0))
        .attr('x2', (date) => xScale(parseIndiaDate(date)))
        .attr('y2', (date) =>
          yScale(getTimeseriesStatistic(date, statistic, false))
        )
        .attr('opacity', isMovingAverage ? 0.2 : 1);

      const linePath = (
        movingAverage = isMovingAverage,
        curve = curveMonotoneX
      ) =>
        line()
          .curve(curve)
          .x((date) => xScale(parseIndiaDate(date)))
          .y((date) =>
            yScale(getTimeseriesStatistic(date, statistic, movingAverage))
          );

      svg
        .select('.trend')
        .selectAll('path')
        .data(
          T && (chartType === 'total' || isNonLinear || isMovingAverage)
            ? [dates]
            : []
        )
        .join(
          (enter) =>
            enter
              .append('path')
              .attr('class', 'trend')
              .attr('fill', 'none')
              .attr('stroke-width', 4)
              .attr('d', linePath())
              .call((enter) => enter.transition(t).attr('opacity', 1)),
          (update) =>
            update.call((update) =>
              update
                .transition(t)
                .attrTween('d', function (date) {
                  const previous = select(this).attr('d');
                  const current = linePath()(date);
                  return interpolatePath(previous, current);
                })
                .attr('opacity', 1)
            ),
          (exit) =>
            exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
        )
        .attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));

      svg
        .select('.trend-background')
        .selectAll('path')
        .data(
          T && chartType === 'delta' && isNonLinear && isMovingAverage
            ? [dates]
            : []
        )
        .join(
          (enter) =>
            enter
              .append('path')
              .attr('class', 'trend-background')
              .attr('fill', 'none')
              .attr('stroke-width', 4)
              .attr('d', linePath(false, curveLinear))
              .call((enter) => enter.transition(t).attr('opacity', 0.2)),
          (update) =>
            update.call((update) =>
              update
                .transition(t)
                .attrTween('d', function (date) {
                  const previous = select(this).attr('d');
                  const current = linePath(false, curveLinear)(date);
                  return interpolatePath(previous, current);
                })
                .attr('opacity', 0.2)
            ),
          (exit) =>
            exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
        )
        .attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));

      svg.selectAll('*').attr('pointer-events', 'none');
      svg
        .on('mousemove', mousemove)
        .on('touchmove', (event) => mousemove(event.touches[0]))
        .on('mouseout touchend', mouseout);
    });
  }, [
    width,
    height,
    chartType,
    isMovingAverage,
    condenseChart,
    xScale,
    yScales,
    statistics,
    getTimeseriesStatistic,
    dates,
  ]);

  useEffect(() => {
    statistics.forEach((statistic, i) => {
      const ref = refs.current[i];
      const svg = select(ref);
      const statisticConfig = STATISTIC_CONFIGS[statistic];
      const yScale = yScales[i];
      const t = svg.transition().duration(D3_TRANSITION_DURATION);

      svg
        .selectAll('circle.condensed')
        .data(
          condenseChart && highlightedDate ? [highlightedDate] : [],
          (date) => date
        )
        .join((enter) =>
          enter
            .append('circle')
            .attr('class', 'condensed')
            .attr('fill', statisticConfig?.color)
            .attr('stroke', statisticConfig?.color)
            .attr('pointer-events', 'none')
            .attr('cx', (date) => xScale(parseIndiaDate(date)))
            .attr('cy', (date) =>
              yScale(getTimeseriesStatistic(date, statistic))
            )
            .attr('r', 4)
        )
        .transition(t)
        .attr('cx', (date) => xScale(parseIndiaDate(date)))
        .attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));

      if (!condenseChart) {
        svg
          .selectAll('circle')
          .attr('r', (date) => (date === highlightedDate ? 4 : 2));
      }
    });
  }, [
    condenseChart,
    highlightedDate,
    xScale,
    yScales,
    statistics,
    getTimeseriesStatistic,
  ]);

  const getTimeseriesStatisticDelta = useCallback(
    (statistic) => {
      if (!highlightedDate) return;

      const currCount = getTimeseriesStatistic(highlightedDate, statistic);
      if (STATISTIC_CONFIGS[statistic]?.hideZero && currCount === 0) return;

      const prevDate =
        dates[dates.findIndex((date) => date === highlightedDate) - 1];

      const prevCount = getTimeseriesStatistic(prevDate, statistic);
      return currCount - prevCount;
    },
    [dates, highlightedDate, getTimeseriesStatistic]
  );

  const trail = useMemo(
    () =>
      statistics.map((statistic, index) => ({
        animationDelay: `${index * 250}ms`,
      })),
    [statistics]
  );

  return (
    <div className="Timeseries">
      {statistics.map((statistic, index) => {
        const total = getTimeseriesStatistic(highlightedDate, statistic);
        const delta = getTimeseriesStatisticDelta(statistic, index);
        const statisticConfig = STATISTIC_CONFIGS[statistic];
        return (
          <div
            key={statistic}
            className={classnames('svg-parent fadeInUp', `is-${statistic}`)}
            style={trail[index]}
            ref={index === 0 ? wrapperRef : null}
          >
            {highlightedDate && (
              <div className={classnames('stats', `is-${statistic}`)}>
                <h5 className="title">
                  {t(capitalize(statisticConfig.displayName))}
                </h5>
                <h5>{formatDate(highlightedDate, 'dd MMMM')}</h5>
                <div className="stats-bottom">
                  <h2>
                    {!noRegionHighlightedDistrictData ||
                    !statisticConfig?.hasPrimary
                      ? formatNumber(
                          total,
                          statisticConfig.format !== 'short'
                            ? statisticConfig.format
                            : 'long',
                          statistic
                        )
                      : '-'}
                  </h2>
                  <h6>
                    {!noRegionHighlightedDistrictData ||
                    !statisticConfig?.hasPrimary
                      ? `${delta > 0 ? '+' : ''}${formatNumber(
                          delta,
                          statisticConfig.format !== 'short'
                            ? statisticConfig.format
                            : 'long',
                          statistic
                        )}`
                      : ''}
                  </h6>
                </div>
              </div>
            )}
            <svg
              ref={(element) => {
                refs.current[index] = element;
              }}
              preserveAspectRatio="xMidYMid meet"
            >
              <g className="x-axis" />
              <g className="x-axis2" />
              <g className="y-axis" />
              <g className="trend-background" />
              <g className="trend" />
            </svg>
          </div>
        );
      })}
    </div>
  );
}
Example #11
Source File: index.js    From here-covid-19-tracker with MIT License 4 votes vote down vote up
Map = ({ points }) => {

  const currentDataType = useDataTypeStore(state => state.currentDataType)

  const updateCenter = useStore(state => state.updateCenter)
  const mapFocus = useMapFocus(state => state.mapFocus)
  const mapZoom = useMapFocus(state => state.mapZoom)
  const updateMapFocus = useMapFocus(state => state.updateMapFocus)
  const currentDate = useDataDate(state => state.currentDate)
  const [loadedMap, setLoadedMap] = useState(false)

  const mapRef = useRef()
  const Leaflet = useRef()
  const sceneRef = useRef()
  const leafletMap = useRef()

  useEffect(() => {
    if (typeof window === undefined || !points.length) return
    const L = require("leaflet")
    const Tangram = require("tangram")
    Leaflet.current = L

    if (leafletMap.current) {
      leafletMap.current.remove()
      leafletMap.current = null
      sceneRef.current = null
    }

    const popup = L.popup({ autoPan: false })

    const map = L.map(mapRef.current, {
      zoomControl: false,
      minZoom: 3,
      maxZoom: 8,
      scrollWheelZoom: true,
    })

    const layer = Tangram.leafletLayer({
      scene,
      attribution: "Loading map",
      webGLContextOptions: {
        antialias: false,
      },
      events: {
        click: selection => {
          if (!selection.feature || (!selection.feature && !selection.feature.properties)) {
            return
          }
          const { lat, long } = selection.feature.properties
          updateMapFocus([lat, long], selection.feature.properties)
        },
        hover: selection => {
          if (!selection.feature || (!selection.feature && !selection.feature.properties)) {
            mapRef.current.style.cursor = "default"
            map.closePopup()
            return
          }

          mapRef.current.style.cursor = "pointer"

          const coords = selection.leaflet_event.latlng
          const sceneDate = sceneRef.current.config.global.currentDate
          const sceneDataType = sceneRef.current.config.global.currentDataType
          const { prefix } = sceneRef.current.config.global

          const num = formatThousand(selection.feature.properties[prefix + sceneDate])

          const isChina = ["Mainland China", "Macau", "Hong Kong", "Taiwan"].includes(selection.feature.properties.countryregion)

          const label = sceneDataType === 0
            ? "Confirmed cases" : sceneDataType === 1 ? "Deaths" : "Recoveries"

          const value = num
            ? `
              <div>
                <div class="tt-address">
                  <p class="tt-zip-code-value">
                    ${selection.feature.properties.provincestate || selection.feature.properties.countryregion}
                  </p>
                  ${
                    selection.feature.properties.provincestate ? `
                      <p class="tt-zip-code-title">
                        ${isChina ? "China" : selection.feature.properties.countryregion}
                      </p>` : ""
                  }
                </div>
                <p class="tt-median-rent-title">${label}</p>
                <p class="tt-median-rent-value">
                  <span>${num}</span>
                </p>
              </div>
            `
            : ""

          popup
            .setLatLng([coords.lat, coords.lng])
            .setContent(`${value}`)
            .openOn(map)

        }
      },
    })

    map.on("move", () => {
      const { lat, lng } = map.getCenter()
      updateCenter([-lng, -lat])
    })

    layer.addTo(map)
    sceneRef.current = layer.scene    
    map.setView([30, 110], 4)
    leafletMap.current = map

    const geojsonData = {
      type: "FeatureCollection",
      features: points,
    }

    const ext = extent(points, d => {
      const headers = d.properties.headers.split(";;")
      const lastDate = headers[headers.length - 1]
      return parseInt(d.properties[lastDate])
    })

    layer.scene.subscribe({
      load: () => {
        layer.scene.config.global.max = ext[1]
        layer.scene.updateConfig()
        layer.scene.setDataSource("dynamic_data", {
          type: "GeoJSON",
          data: geojsonData,
        })
        setLoadedMap(true)
      },
    })

  }, [points])

  const cf = sceneRef.current && sceneRef.current.config

  useEffect(() => {
    if (!mapFocus) return
    leafletMap.current.flyTo(mapFocus, mapZoom || 5, { easeLinearity: 0.01, duration: 1.5 })
  }, [mapFocus, mapZoom])

  useEffect(() => {
    if (!currentDate || !sceneRef.current || !sceneRef.current.config) return
    if (currentDate === sceneRef.current.config.global.currentDate) return
    sceneRef.current.config.global.currentDate = currentDate
    sceneRef.current.updateConfig()
  }, [currentDate, cf])

  useEffect(() => {
    if (!sceneRef.current || !sceneRef.current.config) return
    sceneRef.current.config.global.currentDataType = currentDataType || 0
    sceneRef.current.config.global.prefix = currentDataType === 0
      ? ""
      : currentDataType === 1
        ? "deaths_"
        : "recoveries_"
    sceneRef.current.updateConfig()
  }, [currentDataType, cf])

  const handleZoomIn = () => {
    leafletMap.current.zoomIn()
  }

  const handleZoomOut = () => {
    leafletMap.current.zoomOut()
  }

  return (
    <>
      <Box
        ref={mapRef}
        top="0"
        left={[0, null, "25rem", "30rem"]}
        right="0"
        bottom="0"
        style={{ position: "fixed", transition: "opacity 500ms", opacity: loadedMap ? 1 : 0 }}
      />

      {
        !loadedMap ? (
          <Box
            position="fixed"
            top="50%"
            left={["50%", null, "calc((100% + 25rem) / 2)", "calc((100% + 30rem) / 2)"]}
            zIndex={9999}
            transform="translateX(-50%)"
          >
            <Icon
              name="spinner"
              size="2.5rem"
              color="gray.500"
              animation={`${rotate} 1s linear infinite`}
              mx="auto"
              display="block"
            />
            <Text textAlign="center" color="gray.500" mx="auto" fontSize="sm" mt="0.75rem">
              { "Loading map" }
            </Text>
          </Box>
        ) : null
      }
      
      <Box
        position="fixed"
        bottom={["6rem", "8rem", "5rem"]}
        left={["auto", null, "26.5rem", "32.5rem"]}
        right={["2.75rem", "3.75rem", "auto"]}
        zIndex={2}
        pointerEvents={["none", null, "all"]}
      >

        <Stack spacing="0.5rem" alignItems="center" mb={["6.5rem", null, "1.25rem"]}>
          <Text color="gray.600" width="2.5rem" textAlign="center" fontWeight={700} fontSize="xs" lineHeight="shorter">
            { "more cases" }
          </Text>
          <Box width="2.5rem" height="2.5rem" border="0.125rem solid" borderColor="gray.500" borderRadius="100%" />
          <Box width="1.25rem" height="1.25rem" border="0.125rem solid" borderColor="gray.500" borderRadius="100%" />
          <Box width="0.625rem" height="0.625rem" border="0.125rem solid" borderColor="gray.500" borderRadius="100%" />
          <Text color="gray.600" width="2.5rem" textAlign="center" fontWeight={700} fontSize="xs" lineHeight="shorter">
            { "fewer cases" }
          </Text>
        </Stack>

        <Stack display={["none", null, "block"]} spacing="1.25rem">
          <Flex justifyContent="center">
            <Flex alignItems="center" direction="column" shadow="lg" borderRadius="md">
              <IconButton 
                onClick={handleZoomIn}
                icon="add" bg="white"
                borderRadius="0.25rem 0.25rem 0 0"
                border="0.0625rem solid"
                borderColor="transparent"
                  _hover={{ borderColor: "transparent" }}
                  _focus={{ borderColor: "rgba(236,97,14, 1)", boxShadow: `0 0 0 0.0625rem rgba(236,97,14, 1), 0 0 0 0.25rem rgba(236,97,14, 0.25)` }}
                  _placeholder={{ color: "gray.500" }}
              />
              <Box height="1px" width="100%" bg="gray.100" />
              <IconButton 
                onClick={handleZoomOut}
                icon="minus"
                bg="white"
                borderRadius="0 0 0.25rem 0.25rem"
                border="0.0625rem solid"
                borderColor="transparent"
                  _hover={{ borderColor: "transparent" }}
                  _focus={{ borderColor: "rgba(236,97,14, 1)", boxShadow: `0 0 0 0.0625rem rgba(236,97,14, 1), 0 0 0 0.25rem rgba(236,97,14, 0.25)` }}
                  _placeholder={{ color: "gray.500" }}
              />
            </Flex>
          </Flex>
        </Stack>
      </Box>
    </>
  )
}
Example #12
Source File: DateValueLineGraph.jsx    From v3-ui with MIT License 4 votes vote down vote up
export function DateValueLineGraph(props) {
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
    useTooltip()

  const { containerRef, TooltipInPortal } = useTooltipInPortal({
    detectBounds: true,
    scroll: true
  })

  const { theme } = useContext(ThemeContext)

  const circleColor = theme === 'light' ? '#401C94' : '#ffffff'

  const id = props.id
  const series = props.data
  const allData = series.reduce((rec, d) => rec.concat(d), [])

  return (
    <div className='h-36 mt-6'>
      <ParentSize className='max-chart-width mx-auto'>
        {({ height, width }) => {
          const maxWidth = 1100
          const w = Math.min(width, maxWidth)

          const maxHeight = 400
          const h = Math.min(height, maxHeight)

          const xMax = w - margin.left - margin.right
          const yMax = h - margin.top - margin.bottom

          // scales
          const xScale = scaleTime({
            range: [0, xMax],
            domain: extent(allData, getX)
          })
          const yScale = scaleLinear({
            range: [yMax, 0],
            domain: [0, max(allData, getY)]
          })

          return (
            <Fragment key={`${id}-fragment`}>
              {tooltipOpen && tooltipData && (
                <>
                  <TooltipInPortal
                    key={Math.random()}
                    top={tooltipTop}
                    left={tooltipLeft}
                    className='vx-chart-tooltip'
                  >
                    {props.valueLabel || 'Value'}:{' '}
                    <strong>{numberWithCommas(tooltipData.value)}</strong>
                    <span className='block mt-2'>
                      Date:{' '}
                      <strong>
                        {formatDate(tooltipData.date / 1000, {
                          short: true,
                          noTimezone: true
                        })}
                      </strong>
                    </span>
                  </TooltipInPortal>
                </>
              )}

              <svg ref={containerRef} width={w} height={h}>
                {w > 8 &&
                  series.map((lineData, i) => (
                    <Group
                      key={`${id}-group-lines-${i}`}
                      left={margin.left}
                      right={margin.right}
                      top={margin.top}
                    >
                      <LinearGradient id='vx-gradient' vertical={false}>
                        <stop offset='0%' stopColor='#ff9304'></stop>
                        <stop offset='10%' stopColor='#ff04ea'></stop>
                        <stop offset='20%' stopColor='#9b4beb'></stop>
                        <stop offset='30%' stopColor='#0e8dd6'></stop>
                        <stop offset='40%' stopColor='#3be8ff'></stop>
                        <stop offset='50%' stopColor='#07d464'></stop>
                        <stop offset='60%' stopColor='#ebf831'></stop>
                        <stop offset='78%' stopColor='#ff04ab'></stop>
                        <stop offset='90%' stopColor='#8933eb'></stop>
                        <stop offset='100%' stopColor='#3b89ff'></stop>
                      </LinearGradient>

                      {lineData?.map((data, j) => {
                        return (
                          <LinePath
                            key={`${id}-group-line-path-${i}-${j}`}
                            data={lineData}
                            x={(d) => xScale(getX(d))}
                            y={(d) => yScale(getY(d))}
                            stroke={'url(#vx-gradient)'}
                            strokeWidth={3}
                          />
                        )
                      })}

                      {lineData?.map((data, j) => {
                        return (
                          <circle
                            key={`${id}-circle-${i}-${j}`}
                            r={4}
                            cx={xScale(getX(data))}
                            cy={yScale(getY(data))}
                            stroke={circleColor}
                            fill={circleColor}
                            className='cursor-pointer'
                            onMouseLeave={hideTooltip}
                            onTouchMove={(event) => {
                              const coords = localPoint(event.target.ownerSVGElement, event)

                              showTooltip({
                                tooltipLeft: coords.x,
                                tooltipTop: coords.y,
                                tooltipData: data
                              })
                            }}
                            onMouseMove={(event) => {
                              const coords = localPoint(event.target.ownerSVGElement, event)

                              showTooltip({
                                tooltipLeft: coords.x,
                                tooltipTop: coords.y,
                                tooltipData: data
                              })
                            }}
                          />
                        )
                      })}
                    </Group>
                  ))}
              </svg>
            </Fragment>
          )
        }}
      </ParentSize>
    </div>
  )
}
Example #13
Source File: index.js    From plant-3d-explorer with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function Panels () {
  const [scan] = useScan()
  const [panels, setPanels] = usePanels()

  const panelsData = useMemo(() => {
    const tempFruitPoints = get(scan, 'data.angles.fruit_points')
    const fruitPoints = tempFruitPoints
    // ? tempFruitPoints.slice(0, tempFruitPoints.length - 1)
    // : undefined
    const tempAutomatedAngles = get(scan, 'data.angles.angles')
    const automatedAngles = tempAutomatedAngles
    // ? tempAutomatedAngles.slice(0, tempAutomatedAngles.length - 1)
    // : undefined

    const tempAutomatedInternodes = get(scan, 'data.angles.internodes')
    const automatedInternodes = tempAutomatedInternodes
    // ? tempAutomatedInternodes.slice(0, tempAutomatedInternodes.length - 1)
    // : undefined
    const internodes = [
      ...(get(scan, 'data.angles.measured_internodes') || []),
      ...(get(scan, 'data.angles.internodes') || [])
    ]
    const interNodesBounds = extent(internodes)

    return {
      'panels-angles': {
        isBarChart: false,
        tooltipId: 'angles-tooltip',
        automated: automatedAngles,
        manual: get(scan, 'data.angles.measured_angles'),
        fruitPoints: fruitPoints,
        unit: '°',
        bounds: [0, 360],
        valueTransform: radianToDegree,
        goal: 137.5,
        ifGraph: true
      },
      'panels-distances': {
        isBarChart: false,
        tooltipId: 'internodes-tooltip',
        automated: automatedInternodes,
        manual: get(scan, 'data.angles.measured_internodes'),
        fruitPoints: fruitPoints,
        unit: 'mm',
        bounds: [
          Math.floor(interNodesBounds[0] / 5) * 5,
          Math.round(interNodesBounds[0] + interNodesBounds[1]) * 0.5,
          Math.ceil(interNodesBounds[1] / 5) * 5
        ],
        valueTransform: valueToValue,
        ifGraph: true
      },
      'panels-evaluation': {
        isBarChart: true,
        tooltipId: 'evaluation-tooltip'
      }
    }
  }, [scan])

  return <Container>
    {
      Object.keys(panels)
        .filter((d) => panels[d])
        .map((d) => {
          if (!panelsData[d].isBarChart) {
            return <GraphPanel
              key={d}
              id={d}
              tooltipId={panelsData[d].tooltipId}
              ifGraph={panelsData[d].ifGraph}
              data={panelsData[d]}
              onClose={() => {
                setPanels({
                  ...panels,
                  [d]: false
                })
              }}
            />
          } else {
            return <div>
              <EvalGraph
                id={d}
                ifGraph={false}
                tooltipId={panelsData[d].tooltipId}
                data={panelsData[d]}
                onClose={() => {
                  setPanels({
                    ...panels,
                    [d]: false
                  })
                }}
              />
            </div>
          }
        })
    }
  </Container>
}