d3.js 带有轴线或/和圆圈的趋势线图

问题描述 投票:0回答:1

我有一个折线图 - 想要调整它以采用不同的样式/创建趋势线。用轴线显示当前时间并添加趋势线/圆点。

在顶部生成遵循相同比例的图标/警报,并合并到顶部的图表。

https://codesandbox.io/s/strange-surf-wyd6cj

import React from 'react'
import * as d3 from 'd3'
import './LineChart.scss'

class LineChart extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
    this.state = {
      data: this.props.data ? this.props.data : [],
      theme: this.props.theme
        ? this.props.theme
        : ['#bde0fe', '#2698f9', '#71bcfd', '#f1f8fe'],
    }
  }

  componentWillReceiveProps(nextProps) {
    // You don't have to do this check first, but it can help prevent an unneeded render
    if (nextProps.data !== this.state.data) {
      //console.log("PROPS HAVE CHANGED FOR CHART");
      this.setState({data: nextProps.data})
      this.buildChart()
    }
  }

  componentDidMount() {
    this.buildChart()
  }

  buildChart() {
    var $this = this.myRef.current

    d3.select($this).selectAll('svg').remove()

    var data = this.state.data

    //console.log("DATA", data)

    var hasLabels = false

    data.forEach((item, index) => {
      if (item.id.length > 0) {
        hasLabels = true
      }
    })

    var yAxisLabel = this.props.yAxisLabel
    //console.log("hasLabels", hasLabels)

    var width = parseInt(this.props.width, 10),
      height = parseInt(this.props.height, 10)

    var color = d3.scaleOrdinal().range(this.state.theme)

    var parseTime = d3.timeParse('%Y%m%d')

    var margin = {
        top: 20,
        right: hasLabels ? 95 : 25,
        bottom: 30,
        left: 45,
      },
      w = width - margin.left - margin.right,
      h = height - margin.top - margin.bottom

    var x = d3.scaleTime().range([0, w])
    var y = d3.scaleLinear().range([h, 0])

    var valueline = d3
      .line()
      .curve(d3.curveBasis)
      .x(function (d) {
        return x(new Date(d.date))
      })
      .y(function (d) {
        return y(d.value)
      })

    //valueline.curve(d3.curveBasis)

    x.domain([
      d3.min(data, function (c) {
        return d3.min(c.values, function (d) {
          return new Date(d.date)
        })
      }),
      d3.max(data, function (c) {
        return d3.max(c.values, function (d) {
          return new Date(d.date)
        })
      }),
    ])

    y.domain([
      d3.min(data, function (c) {
        return d3.min(c.values, function (d) {
          return d.value
        })
      }),
      d3.max(data, function (c) {
        return d3.max(c.values, function (d) {
          return d.value
        })
      }),
    ])

    var svg = d3
      .select($this)
      .append('svg')
      .attr('width', w + margin.left + margin.right)
      .attr('height', h + margin.top + margin.bottom)
      .append('g')
      .attr('class', 'linechart')
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

    var lines = svg.append('g').attr('class', 'lines')

    var line = lines
      .selectAll('.line')
      .data(data)
      .enter()
      .append('g')
      .attr('class', 'line')

    line
      .append('path')
      .attr('d', function (d) {
        return valueline(d.values)
      })
      .attr('stroke', function (d, i) {
        return color(i)
      })

    line
      .append('text')
      .datum(function (d) {
        return {
          id: d.id,
          value: d.values[d.values.length - 1],
        }
      })
      .attr('transform', function (d) {
        return (
          'translate(' +
          x(new Date(d.value.date)) +
          ',' +
          y(d.value.value) +
          ')'
        )
      })
      .attr('x', 3)
      .attr('dy', '0.35em')
      .text(function (d) {
        return d.id
      })

    ////return c.date;
    var minDate = d3.min(data, function (c) {
      return d3.min(c.values, function (d) {
        return d.date
      })
    })
    var maxDate = d3.max(data, function (c) {
      return d3.max(c.values, function (d) {
        return d.date
      })
    })

    //console.log("minDate", minDate);
    //console.log("maxDate", maxDate);

    var difference = dateDiffInDays(
      new Date(minDate.toString()),
      new Date(maxDate.toString()),
    )
    //console.log(difference + ' days')

    let tick = d3.timeDay.every(1)
    if (difference < 1) {
      tick = d3.timeHour.every(2)
    }
    if (difference > 1 && difference < 7) {
      tick = d3.timeDay.every(1)
    }
    if (difference > 7 && difference < 30) {
      tick = d3.timeWeek.every(1)
    }
    if (difference >= 30 && difference < 90) {
      //console.log("jackpot")
      tick = d3.timeMonth.every(1)
    }

    lines
      .append('g')
      .attr('transform', 'translate(0,' + h + ')')
      .call(d3.axisBottom(x).ticks(tick))

    lines
      .append('g')
      .call(d3.axisLeft(y))
      .append('text')
      .attr('class', 'ylabel')
      .attr('transform', 'rotate(-90)')
      .attr('y', 6)
      .attr('dy', '0.71em')
      .text(yAxisLabel)

    function dateDiffInDays(a, b) {
      const _MS_PER_DAY = 1000 * 60 * 60 * 24
      // Discard the time and time-zone information.
      const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())
      const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate())

      return Math.floor((utc2 - utc1) / _MS_PER_DAY)
    }
  }

  render() {
    return <div ref={this.myRef} className="LineChart" />
  }
}
export default LineChart
d3.js
1个回答
0
投票

添加热门图表

这是一项非常简单的任务。我将列出实施步骤,而不是详细解释。

1.扁平化数据

分组不是必需的,因为图标(点)将显示在同一层上。它们被展平为一维数据。

const flatData = []
data.forEach(({id: groupId, values}) => {
  values.forEach((v) => {
    flatData.push({groupId, ...v})
  })
})

2.将它们全部绘制出来,并带有值标签

标签和图标将共享相同的位置。我将它们与

<g/>
元素分组在一起,并使用
.call()
语法轻松访问它们。

    gDots
      .selectAll('.dot-info')
      .data(flatData, (d) => d.date)
      .join((enter) =>
        enter
          .append('g')
          .attr('class', 'dot-info')
          .attr(
            'transform',
            (d) =>
              // some data were placed in the same X position
              // gave different Y per group to avoid collision
              `translate(${x(d.date)},${d.groupId === 'New York' ? -30 : -60})`,
          )
          .call((g) =>
            g
              .append('circle')
              .attr('r', 3)
              // I've provided dots with colors, but it can be replaced with icon object if necessary.
              .attr('fill', (d) => (d.groupId === 'New York' ? 'blue' : 'red')),
          )
          .call((g) =>
            g
              .append('text')
              .text((d) => d.value)
              .attr('transform', 'translate(0,10)')
              .attr('dominant-baseline', 'hanging')
              .attr('text-anchor', 'middle'),
          ),
      )

3.给光标

最后,我添加了一个目标光标来模仿给定示例中的用户界面。由于其限制,它在 Codesandbox 上相当慢,但在裸项目上可能工作正常。

   const lineCursor = svg
      .append('g')
      .call((g) =>
        g
          .append('line')
          .attr('x1', 0)
          .attr('y1', -60)
          .attr('x2', 0)
          .attr('y2', h)
          .attr('stroke', 'black')
          .attr('stroke-width', '1px'),
      )
    svg.on('mousemove', (ev) => {
      // gave the same x margin as the chart
      lineCursor.attr('transform', `translate(${ev.clientX - 45})`)
    })

这是 codesandbox 上的演示。

© www.soinside.com 2019 - 2024. All rights reserved.