[Fixed] d3.js scatter plot connecting dots with line

Issue

I am new to d3.js and i have been given a task to create a feet like in the image below. I have its X and Y coordinates and I am using scatter plot and line plot to create this but I am unable to draw a line on the boundary connecting the points. Here is the Stackblitz Link
enter image description here

This is the graph that i am able to make which doesnt look like the image above
enter image description here

Below is my code

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);
  }

Is there any other way to do this ? Or how can I refractor this? Please help.

Solution

Just modified orderSequence to move up/down instead of left/right. Does it help?

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);  
    }  
  }

Leave a Reply

(*) Required, Your email will not be published