【问题标题】:d3.js scatter plot connecting dots with lined3.js 散点图用线连接点
【发布时间】:2021-07-02 07:39:00
【问题描述】:

我是 d3.js 的新手,我的任务是创建如下图所示的脚。我有它的 X 和 Y 坐标,我正在使用散点图和线图来创建它,但我无法在连接点的边界上画一条线。这是Stackblitz Link

这是我能够制作的图表,它看起来不像上面的图片

下面是我的代码

export class ScatterPlotComponent implements OnChanges {
  @Input() data = [];
  private width = 350;
  private height = 500;
  private margin = 30;
  public svg;
  public svgInner;
  public yScale;
  public xScale;
  public xAxis;
  public yAxis;
  public tooltip;
  public lineGroup;
  constructor() {}

  public ngOnChanges(changes): void {
    if (changes.hasOwnProperty('data') && this.data) {
      console.log(this.data);
      this.data.forEach((element: { insoleX: number; insoleY: number }) => {
        element.insoleX = +element.insoleX.toFixed(2);
        element.insoleY = +element.insoleY.toFixed(2);
      });
      this.initializeChart();
    }
  }

  createDomain() {
    const minY = d3.min(
      this.data,
      (d: { insoleX: number; insoleY: number }) => {
        return d.insoleY;
      }
    );
    const maxY = d3.max(
      this.data,
      (d: { insoleX: number; insoleY: number }) => {
        return d.insoleY;
      }
    );
    const nonNegativeAxis = minY >= 0 && maxY >= 0;
    const positiveAndNegativeAxis = minY < 0 && maxY > 0;

    let yScaleDomain: any[], xScaleDomain;

    if (nonNegativeAxis) {
      yScaleDomain = [
        0,
        d3.max(this.data, (d: { insoleX: number; insoleY: number }) => {
          return d.insoleY;
        }),
      ];
    } else {
      yScaleDomain = d3.extent(
        this.data,
        (d: { insoleX: number; insoleY: number }) => {
          return d.insoleY;
        }
      );
    }

    xScaleDomain = d3.extent(
      this.data,
      function (d: { insoleX: number; insoleY: number }) {
        return d.insoleX;
      }
    );
    xScaleDomain[1] = xScaleDomain[1] + 2;
    return { xScaleDomain, yScaleDomain };
  }

  initializeChart() {
    const { xScaleDomain, yScaleDomain } = this.createDomain();

    this.svg = d3
      .select('#my_dataviz')
      .append('svg')
      .attr('width', this.width + this.margin + this.margin)
      .attr('height', this.height + this.margin + this.margin)
      .append('g')
      .attr('transform', 'translate(' + this.margin + ',' + this.margin + ')');

    this.xScale = d3
      .scaleLinear()
      .domain(xScaleDomain)
      .rangeRound([0, this.width - 2 * this.margin]);

    this.xAxis = this.svg
      .append('g')
      .attr('transform', 'translate(0,' + this.height + ')');

    this.yScale = d3
      .scaleLinear()
      .domain([yScaleDomain[1] + 2, yScaleDomain[0]])
      .rangeRound([0, this.height - 2 * this.margin]);

    this.yAxis = this.svg.append('g').attr('id', 'y-axis');

    const xAxis = d3.axisBottom(this.xScale);
    this.xAxis.call(xAxis);
    const yAxis = d3.axisLeft(this.yScale);
    this.yAxis.call(yAxis);

    const line = d3
      .line()
      .x((d) => d[0])
      .y((d) => d[1])
      .curve(d3.curveMonotoneX);

    const points: [number, number][] = this.data.map((d) => [
      this.xScale(d.insoleX),
      this.yScale(d.insoleY),
    ]);

    this.svg
      .append('path')
      .datum(points)
      .attr('fill', 'none')
      .attr('stroke', '#69b3a2')
      .attr('stroke-width', 1.5)
      .attr(
        'd',
        d3
          .line()
          .x((d) => d[0])
          .y((d) => d[1])
      );
    // Add the points
    this.svg
      .append('g')
      .selectAll('dot')
      .data(this.data)
      .enter()
      .append('circle')
      .attr('class', 'dot')
      .classed('filled', true)
      .attr('fill', '#83B9EE')
      .attr('data-xvalue', (d: { insoleX: any }) => d.insoleX)
      .attr('data-yvalue', (d: { insoleY: any }) => d.insoleY)
      .attr('cx', (d: { insoleX: any }) => this.xScale(d.insoleX))
      .attr('cy', (d: { insoleY: any }) => this.yScale(d.insoleY))
      .attr('r', 4);
  }

还有其他方法可以做到这一点吗?或者我怎样才能折射这个?请帮忙。

【问题讨论】:

  • 您的积分顺序错误。您必须对它们进行排序以确保序列中的前一个点和下一个点最接近当前点
  • @MichaelRovinsky 我按升序对 y 轴进行了排序,图形得到了改善,但有些点仍然没有形成正确的线 [![在此处输入图像描述][1]][1] [1 ]:i.stack.imgur.com/Y57hD.png
  • @MichaelRovinsky 我设法在 stackblitz 上复制了代码,你能看一下吗? stackblitz.com/edit/angular-ni3mp4?file=src/app/scatter-plot/…

标签: javascript html reactjs angular d3.js


【解决方案1】:

刚刚修改了 orderSequence 以向上/向下移动而不是向左/向右移动。有帮助吗?

const orderSequence = seq => {
    let goUp = false;
    let input = [...seq];
  let output = [input.shift()];
  while(input.length > 0) {
    const last = output[output.length - 1];
    const points = [...input].filter(({y}) => 
        goUp ? y < last.y : y >= last.y);
    if (points.length === 0) {
        goUp = !goUp;
    } else {
      const closest = findClosestPoint(last, points);
      output.push(closest);
      const index = input.findIndex(p => 
        p.x === closest.x && p.y === closest.y);
      input.splice(index, 1);  
    }  
  }

【讨论】:

  • 它确实有助于这条线,但它仍然指向错误的方向 [1]:i.stack.imgur.com/XdvWg.png
  • 您可以通过调用reverse()来反转序列。此外,您可以将序列中的第一项推到尾部以绘制闭合路径。请标记并支持它有帮助的正确答案
  • 我能够绘制封闭路径,但是当我使用 const ordered = orderSequence(randomPoints).reverse() 反转序列时,由于某种原因它仍然保持不变
  • 对不起,我不明白生成的顺序如何影响整个情节。无论起点和方向,似乎都一样
【解决方案2】:

orderSequence 例程是如何对点序列进行排序以形成循环的示例。

sn-p中左边的序列是无序的,右边是有序的:

let i;
const randomPoints = [];
for (i = 0; i < 50; i++) {
    const angle = Math.random() * 2 * Math.PI;
  const x = 100 + Math.sin(angle) * 50;
  const y = 75 + Math.cos(angle) * 50;
  randomPoints.push({x, y});
}

const distance = (from, to) => 
    Math.hypot(from.x - to.x, from.y - to.y);
  
const findClosestPoint = (point, seq) => 
    seq.reduce((closest, current) => 
        closest === null ? 
        current : 
        (distance(current, point) < distance(closest, point) ? 
        current : closest), 
      null);

const orderSequence = seq => {
    let goLeft = false;
    let input = [...seq];
  let output = [input.shift()];
  while(input.length > 0) {
    const last = output[output.length - 1];
    const points = [...input].filter(({x}) => 
        goLeft ? x < last.x : x >= last.x);
    if (points.length === 0) {
        goLeft = !goLeft;
    } else {
      const closest = findClosestPoint(last, points);
      output.push(closest);
      const index = input.findIndex(p => 
        p.x === closest.x && p.y === closest.y);
      input.splice(index, 1);  
    }  
  }
  return output;
}

const ordered = orderSequence(randomPoints);

const svg = d3.select('svg')

const drawSequence = (seq, container) => 
    seq.forEach(({x, y}, index) => {
        container.append('circle')
      .attr('cx', x).attr('cy', y)
      .attr('r', 3).style('fill', 'red');
    
  if (index > 0) {
    const prev = seq[index - 1];
    container.append('line')
        .attr('x1', prev.x).attr('y1', prev.y)
      .attr('x2', x).attr('y2', y)
      .style('stroke', 'blue');
  }  
});

const c1 = svg.append('g')
    .attr('transform', 'translate(0, 0)');
drawSequence(randomPoints, c1);

const c2 = svg.append('g')
    .attr('transform', 'translate(200, 0)');
drawSequence(ordered, c2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="150">
  
</svg>

【讨论】:

  • 我建议修改排序程序并使用上/下方向而不是左/右方向
  • 这确实对我有用,但在某些时候,线条并没有像我附上的图片那样正确放置。是否有可能产生不正确的点? [1]:i.stack.imgur.com/AeFyM.png
猜你喜欢
  • 2013-05-18
  • 2013-12-06
  • 1970-01-01
  • 2018-08-15
  • 1970-01-01
  • 2017-04-19
  • 1970-01-01
  • 2015-02-18
  • 1970-01-01
相关资源
最近更新 更多