d3.js散点图 - 剪裁迷你图

时间:2018-03-07 15:56:13

标签: javascript d3.js clipping

我创建了一个动态d3.js图表​​,用于绘制时间序列。 每一秒我都会添加一个新样本并删除旧样本。 它给人的印象是迷你线从右向左移动,这很好。 但是,我对添加和删除路径的新旧段的方式不太满意。

看到这张图: enter image description here

在此图像上,您可以看到右侧y轴与迷你图之间有一点间隙。随着火花线向左移动,这个间隙变得更大,一旦足够大,就会增加新的迷你线段。这看起来不是很顺利。 我希望绘制新的线段,因为迷你线向左移动(就像手工绘制时一样)。

我正在使用剪辑路径来隐藏我不想要的路径部分(在情节之外),但这似乎并没有给我正确的行为。

clip-path

的定义
 this.container.append('defs')
  .append('clipPath')
    .attr('id', 'chart-content')
  .append('rect')
    .attr('height', this.height)
    .attr('width', this.width);

使用clip-path

   group.path = this.paths
    .append('g')
      .attr('clip-path', 'url(#chart-content)')
    .append('path')
      .data([group.data])

另一方面,剪辑路径似乎在我平移和缩放时工作..这使我更加困惑!我希望有人可以帮助我!

1 个答案:

答案 0 :(得分:0)

我找到了问题的答案.. 这是一个动态折线图的片段!



<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .line {
    fill: none;
    stroke: #000;
    stroke-width: 1.5px;
  }
  #chart{
    width: 100%;
    height: 500px;
  }
</style>
<div id='chart'></div>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
<script>
  var margin = { top: 20, right: 20, bottom: 20, left: 20};
  var padding = { top: 20, right: 40, bottom: 20, left: 40};

  let width;
  let height;
  let svg;
  let container;
  let data;
  let xScale;
  let yScale;
  let now;
  let path;
  let gX;
  let xAxis;
  let lineGenerator;
  const duration = 500;

  render = () => {
    this.hostElement = d3.select('#chart');
    this.width = this.hostElement.node().getBoundingClientRect().width - this.margin.left - this.margin.right - this.padding.left - this.padding.right;
    this.height = this.hostElement.node().getBoundingClientRect().height - this.margin.top - this.margin.bottom - this.padding.bottom - this.padding.top;

    this.createSvg();
    this.createAxis();
    this.createLine();
    this.defineBounds();
    this.tick();
  }

  createSvg = () => {
    this.svg = this.hostElement
      .append('svg')
        .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')')
        .attr('width', this.width + this.padding.right + this.padding.left)
        .attr('height', this.height + this.padding.top + this.padding.bottom)

    this.container = this.svg
      .append('g')
        .attr('class', 'chart')
        .attr('transform', 'translate(' + this.padding.left + ',' + this.padding.top + ')')
        .attr('width', this.width)
        .attr('height', this.height);
  }

  createAxis = () => {
    // maxY
    const maxY = d3.max(this.data, d => d.value);

    // minX & maxX
    const minX = d3.min(this.data, d => d.date);
    const maxX = d3.max(this.data, d => d.date);
    this.now = maxX;
    const maxXToDisplay = moment(maxX).subtract(1, 's').toDate();
    const minXToDisplay = moment(minX).add(2, 's').toDate();

    // Update scales
    this.xScale = d3.scaleTime()
      .domain([minXToDisplay, maxXToDisplay])
      .range([0, this.width]);

    this.yScale = d3.scaleLinear()
      .domain([0, maxY])
      .range([this.height, 0]);

    // Update axis
    this.xAxis = d3.axisBottom(this.xScale);
    const yAxis = d3.axisLeft(this.yScale);

    // Draws the axis
    this.gX = this.container.append('g')
      .attr('class', 'x axis')
      .attr('transform', 'translate(0,' + this.height + ')')
      .call(this.xAxis);

    const gY = this.container.append('g')
      .attr('class', 'y axis')
      .call(yAxis);
  }

  createLine = () => {
    this.lineGenerator = d3.line()
      .x(d => this.xScale(d.date))
      .y(d => this.yScale(d.value))
      .curve(d3.curveMonotoneX);

    this.path = this.container
      .append('g')
        .attr('class', 'path-container')
        .attr('clip-path', 'url(#chart-content)')
      .append('path')
        .datum(this.data)
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-width", 1.5)
        .attr('d', this.lineGenerator);
  }

  tick = () => {
    this.now = moment(this.now).add(1, 's').toDate();

    // Add new values
    this.data.push({
      value: Math.floor(10 + Math.random() * 15),
      date: this.now
    });

    // Remove old values
    this.data.shift();

    this.path.attr('d', this.lineGenerator);

    const numberSamplesToDisplay = this.data.length - 2;
    const minX = moment(this.now).subtract(numberSamplesToDisplay, 's').toDate();
    const maxX = this.now;
    const maxXToDisplay = moment(maxX).subtract(1, 's').toDate();
    const minXToDisplay = moment(minX).add(1, 's').toDate();

    // Shift domain
    this.xScale.domain([minXToDisplay, maxXToDisplay]);

    // Slide x-axis left
    const xTransition = this.gX.transition()
      .duration(1000)
      .ease(d3.easeLinear)
      .call(this.xAxis);

    // Slide paths left
    this.path.attr('transform', null)
      .transition()
      .duration(1000)
      .ease(d3.easeLinear)
      .attr('transform', 'translate(' + this.xScale(minX) + ', 0)')
      .on('end', this.tick);
  }

  defineBounds = () => {
    this.container.append('defs')
      .append('clipPath')
        .attr('id', 'chart-content')
      .append('rect')
        .attr('height', this.height)
        .attr('width', this.width);
  }

  generateData = () => {
    const dataset = [
      {
        value: Math.floor(10 + Math.random() * 15),
        date: moment().subtract(60, 'seconds').toDate()
      }
    ];

    for (let i = 0; i < 59; i ++) {
      dataset.push({
        value: Math.floor(10 + Math.random() * 15),
        date: moment(dataset[dataset.length - 1].date).add(1, 'seconds').toDate()
      })
    }
    return dataset;
  }

  this.data = generateData();
  render();
</script>
&#13;
&#13;
&#13;