chart = {
let split = false;
const nodes = Array.from({length: 100}, (i) => ({x: i}));
const chartHeight = height / 4
const positions = {
train: {
x: width / 5 + 110,
y: chartHeight
},
test: {
x: width / 1.6,
y: chartHeight
}
}
const simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-9))
.force("center", d3.forceCenter(width / 2, chartHeight))
.force("x", d3.forceX().strength(0.05).x(width / 2))
.force("y", d3.forceY().strength(0.05).y(chartHeight))
.alphaDecay(0)
.on("tick", ticked)
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `${0} ${0} ${width} ${height}`);
const node = svg.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", 7)
.attr('fill', 'whitesmoke')
.attr('stroke', 'black')
const traintLabel = svg.append('text')
.attr('class', 'label')
.attr('x', positions.train.x)
.attr('y', positions.train.y - height / 5 - 10)
.text('TRAIN')
.attr('text-anchor', 'middle')
.attr('opacity', 0);
const testLabel = svg.append('text')
.attr('class', 'label')
.attr('x', positions.test.x + 50)
.attr('y', positions.test.y - height / 5 - 10)
.text('TEST')
.attr('text-anchor', 'middle')
.attr('opacity', 0);
svg.append('circle')
.attr('id', 'category_1')
.attr('class', 'hull')
.attr('r', height / 5.5)
.attr('cx', positions.train.x)
.attr('cy', positions.train.y)
.attr('fill', color1)
.attr('fill-opacity', 0.1)
.attr('stroke', '#3bbc9b')
.attr('stroke-opacity', 0.5)
.attr('opacity', 0)
svg.append('circle')
.attr('id', 'category_2')
.attr('class', 'hull')
.attr('r', height / 5.5)
.attr('cx', positions.test.x + 50)
.attr('cy', positions.test.y)
.attr('fill', color2)
.attr('fill-opacity', 0.1)
.attr('stroke', '#b97ebb')
.attr('stroke-opacity', 0.5)
.attr('opacity', 0);
function ticked() {
// reposition nodes
node
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr('r', 10)
}
d3.select('#updateButton').on('click', () => {
split = !split;
if (split) {
let x1 = positions.train.x
let x2 = positions.test.x
simulation.force("x").x((d, i) => i & 1 ? x1 : x2);
simulation.alpha(1).restart();
d3.selectAll('.label')
.transition()
.delay(500)
.duration(1000)
.attr('opacity', 1);
d3.selectAll('.hull')
.transition()
.delay(500)
.duration(1000)
.attr('opacity', 1);
node.transition().duration(1000).attr('stroke', (d, i) => i & 1 ? color2 : color1)
.attr("fill", (d, i) => i & 1 ? color1 :color2)
} else {
simulation
.force("x", d3.forceX(width / 2).strength(0.05))
.force("y", d3.forceY(chartHeight).strength(0.05))
node.transition().duration(1000).attr('fill', 'whitesmoke')
.attr('stroke', 'black')
d3.selectAll('.label')
.transition()
.duration(1000)
.attr('opacity', 0);
d3.selectAll('.hull')
.transition()
.duration(1000)
.attr('opacity', 0);
}
})
return svg.node();
}