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
This is the graph that i am able to make which doesnt look like the image above
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);
}
}