数据结构:
[
{
“name”: ‘’,
“x”: ‘’,
“y”:’’
},
]
import * as d3 from 'd3';
export default function scatter(id, data) {
const width = 600;
const height = 400;
const margin = { top: 20, right: 30, bottom: 30, left: 60 };
const svg = d3
.select(id)
.append('svg')
.attr('class', 'mainBubbleSVG')
.attr('width', width)
.attr('height', height);
//比例尺
const x = d3
.scaleLinear()
.domain(d3.extent(data, d => d.x))
.nice()
.range([margin.left, width - margin.right]);
const y = d3
.scaleLinear()
.domain(d3.extent(data, d => d.y))
.nice()
.range([height - margin.bottom, margin.top]);
//坐标轴
const xAxis = g =>
g
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
.call(() => g.select('.domain').remove())
.call(() =>
g
.append('text')
.attr('x', width - margin.right)
.attr('y', -4)
.attr('fill', '#000')
.attr('font-weight', 'bold')
.attr('text-anchor', 'end')
.text(data.x)
);
const yAxis = g =>
g
.attr('transform', `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(() => g.select('.domain').remove())
.call(() =>
g
.select('.tick:last-of-type text')
.clone()
.attr('x', 4)
.attr('text-anchor', 'start')
.attr('font-weight', 'bold')
.text(data.y)
);
//添加坐标轴
svg.append('g').call(xAxis);
svg.append('g').call(yAxis);
//打点
svg
.append('g')
.attr('stroke', 'steelblue')
.attr('stroke-width', 1.5)
.attr('fill', 'none')
.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', d => x(d.x))
.attr('cy', d => y(d.y))
.attr('r', 2);
//标注文字
svg
.append('g')
.attr('font-family', 'sans-serif')
.attr('font-size', 10)
.selectAll('text')
.data(data)
.join('text')
.attr('x', d => x(d.x))
.attr('y', d => y(d.y))
.text(d => d.name)
.call(dodge);
function dodge(text, iterations = 300) {
const nodes = text.nodes();
const left = () => text.attr('text-anchor', 'start').attr('dy', '0.32em');
const right = () => text.attr('text-anchor', 'end').attr('dy', '0.32em');
const top = () => text.attr('text-anchor', 'middle').attr('dy', '0.0em');
const bottom = () => text.attr('text-anchor', 'middle').attr('dy', '0.8em');
const points = nodes.map(node => ({
fx: +node.getAttribute('x'),
fy: +node.getAttribute('y'),
}));
const labels = points.map(({ fx, fy }) => ({ x: fx, y: fy }));
const links = points.map((source, i) => ({ source, target: labels[i] }));
const simulation = d3
.forceSimulation(points.concat(labels))
.force('charge', d3.forceManyBody().distanceMax(80))
.force(
'link',
d3
.forceLink(links)
.distance(4)
.iterations(4)
)
.stop();
for (let i = 0; i < iterations; i+=1) simulation.tick();
text
.attr('x', (_, i) => labels[i].x)
.attr('y', (_, i) => labels[i].y)
.each(function(_, i) {
const a = Math.atan2(labels[i].y - points[i].fy, labels[i].x - points[i].fx);
d3.select(this).call(
a > Math.PI / 4 && a <= (Math.PI * 3) / 4
? bottom
: a > -Math.PI / 4 && a <= Math.PI / 4
? left
: a > (-Math.PI * 3) / 4 && a <= (Math.PI * 3) / 4
? top
: right
);
});
}
}