Линейный график с завершением, сделанным пользователем (как игра) - PullRequest
2 голосов
/ 17 марта 2019

Я пытаюсь воспроизвести этот пример , используя React.

Вот мой код:

import * as React from 'react'
import { mean, maxBy } from 'lodash'
import { line } from 'd3-shape'
import { scaleTime, ScaleTime, scaleLinear, ScaleLinear } from 'd3-scale'
import { extent } from 'd3-array'
import { select } from 'd3-selection'
import Draggable, { DraggableCore } from 'react-draggable'

type YouDrawItDatasetType = {
  year: number
  value: number
  defined?: boolean
}

const YEAR_THRESHOLD = 2012

function youDrawItData(): YouDrawItDatasetType[] {
  const years = range(2001, 2019)
  const dataset = years.map(year => ({ year: year, value: random(0, 100, true) }))
  return dataset
}

function clamp(a: number, b: number, c: number) {
  return Math.max(a, Math.min(b, c))
}

export class YouDrawIt extends React.Component {
  completed = false
  userLinePathRef = React.createRef<SVGPathElement>()
  rectRef = React.createRef<SVGRectElement>()

  handleDragStart() {
  }

  handleDrag = (
    xScale: ScaleTime<number, number>,
    yScale: ScaleLinear<number, number>,
    dataGreaterThanTreshold: any,
    linePathGenerator: any,
  ) => (e: any, ui: any) => {
    console.log('\nhandleDrag')
    const x = ui.x
    const y = ui.y
    const pos = [x, y]
    const year = clamp(
      YEAR_THRESHOLD + 1,
      xScale.domain()[1].getFullYear() + 1,
      xScale.invert(pos[0]).getFullYear(),
    )
    const value = clamp(0, yScale.domain()[1], yScale.invert(pos[1]))
    dataGreaterThanTreshold.forEach((d: any) => {
      if (Math.abs(d.year - year) < 0.5) {
        d.value = value
        d.defined = true
      }

      const dataGreaterThanTresholdDefined = dataGreaterThanTreshold.filter((d: any) => d.defined)
      const dPath = linePathGenerator(dataGreaterThanTresholdDefined)
      this.userLinePathRef.current.setAttribute('d', dPath)
      const meanDataGreaterThanTresholdDefined = mean(dataGreaterThanTresholdDefined)
      const lastYear = new Date(maxBy(dataGreaterThanTreshold, 'year')['year'])
      console.log('lastYear: ', lastYear)
      if (!this.completed && meanDataGreaterThanTresholdDefined === 1) {
        this.completed = true
        select(this.rectRef.current)
          .transition()
          .duration(1000)
          .attr('width', xScale(lastYear))
      }
    })
  }

  handleDragStop() {
  }

  render() {
    const width = window.innerWidth / 1.1
    const height = window.innerHeight / 2
    const dataset = youDrawItData()

    const xScale = scaleTime<number, number>()
      .domain(extent(dataset, d => d.year))
      .range([0, width])

    const yScale = scaleLinear<number, number>()
      .domain(extent(dataset, d => d.value))
      .range([0, height])

    const linePathGenerator = line<YouDrawItDatasetType>()
      .x(d => xScale(d.year))
      .y(d => yScale(d.value))

    const dPath = linePathGenerator(dataset)

    const dataGreaterThanTreshold = dataset
      .map(function(d) {
        return { year: d.year, value: d.value, defined: false }
      })
      .filter(function(d) {
        if (d.year == YEAR_THRESHOLD) d.defined = true
        return d.year >= YEAR_THRESHOLD
      })

    return (
      <div className="black">
        <Draggable
          onStart={this.handleDragStart}
          onDrag={this.handleDrag(xScale, yScale, dataGreaterThanTreshold, linePathGenerator)}
          onStop={this.handleDragStop}
        >
          <svg className="ba b--black" width={width} height={height}>
            <rect className="ba b--black" width={width} height={height} opacity={0} />

            <clipPath id="clip">
              <rect className="ba b--black" width={width / 2} height={height} ref={this.rectRef} />
            </clipPath>

            <g clipPath="url(#clip)">
              <path d={dPath} fill="none" stroke="tomato" strokeWidth={3} />
            </g>

            <path
              className=""
              ref={this.userLinePathRef}
              fill="none"
              stroke="lime"
              strokeWidth={3}
            />
          </svg>
        </Draggable>
      </div>
    )
  }
}

Результат не тот, который я хочу.В результате я могу перетащить все SVG, но мне бы это не понравилось.Я хотел бы, чтобы результат был связанным: пользователь перемещает мышь и создает новые точки.

Когда я делаю:

const year = clamp(
      YEAR_THRESHOLD + 1,
      xScale.domain()[1].getFullYear() + 1,
      xScale.invert(pos[0]).getFullYear(),
    )

, итоговый год всегда равен 2013, почему?

Мне нужна рука для отладки этого кода.Я пытался скопировать логику, найденную в коде Адама Пирса, но я делаю что-то не так.

...