还可以继续去改造options,类似echarts的配置图。代码仅提供结构参考。
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
<title>4</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div class="control-group">
<button onclick="update()">Update</button>
</div>
</body>
<script type='text/javascript'>
const { ceil, round, sqrt, random: _random } = Math;
const range = (min, max) => (max - min) * _random() + min;
const random = (n = 10) => round(range(1, n));
const randomColor = () => `#${(~~(Math.random() * 0x1000000)).toString(16)}`;
class Chart {
constructor(options) {
this.data = Array.from({ length: 31 }, (_, i) => ({ x: i, y: random(10) }));
this.width = 600;
this.height = 300;
this.margin = { top: 30, left: 30, right: 30, bottom: 30 };
this.colors = d3.scaleOrdinal(d3.schemeCategory10);
this.scaleX = d3.scaleLinear().domain([-1, 32])
this.scaleY = d3.scaleLinear().domain([0, 10])
this.xStart = this.margin.left;
this.yStart = this.height - this.margin.bottom;
this.xEnd = this.width - this.margin.right;
this.yEnd = this.margin.top;
this.quadrantWidth = this.xEnd - this.xStart;
this.quadrantHeight = this.yStart - this.yEnd;
this.render();
}
render(data) {
const { width, height, svg, xStart, yEnd } = this;
if (!svg) {
this.svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
this.bodyG = this.svg.append("g")
.attr("class", "body")
.attr("transform", `translate(${xStart}, ${yEnd})`)
.attr("clip-path", "url(#body-clip)");
this.renderAxes();
this.defineBodyClip();
}
data && this.update(data);
this.renderBody();
}
update(data) {
this.data = data;
}
renderAxes() {
const { svg } = this;
var axesG = svg.append("g").attr("class", "axes");
this.renderXAxis(axesG);
this.renderYAxis(axesG);
}
renderXAxis(axes) {
const { scaleX, quadrantWidth, quadrantHeight, xStart, yStart } = this;
const xAxis = d3.axisBottom().scale(scaleX.range([0, quadrantWidth]));
axes.append("g")
.attr("class", "x axis")
.attr("transform", `translate(${xStart},${yStart})`)
.call(xAxis);
d3.selectAll("g.x g.tick")
.append("line")
.attr("stroke", "black")
.attr("shape-rendering", "crispEdges")
.attr("stroke-opacity", .2)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", - quadrantHeight);
}
renderYAxis(axes) {
const { scaleY, quadrantWidth, quadrantHeight, xStart, yEnd } = this;
const yAxis = d3.axisLeft().scale(scaleY.range([quadrantHeight, 0]));
axes.append("g")
.attr("class", "y axis")
.attr("transform", `translate(${xStart},${yEnd})`)
.call(yAxis);
d3.selectAll("g.y g.tick")
.append("line")
.attr("stroke", "black")
.attr("shape-rendering", "crispEdges")
.attr("stroke-opacity", .2)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", quadrantWidth)
.attr("y2", 0);
}
defineBodyClip() {
const { svg, quadrantWidth, quadrantHeight } = this;
const padding = 5;
svg.append("defs")
.append("clipPath")
.attr("id", "body-clip")
.append("rect")
.attr("x", 0 - padding)
.attr("y", 0)
.attr("width", quadrantWidth + 2 * padding)
.attr("height", quadrantHeight);
}
renderBody() {
this.renderBars();
}
renderBars() {
const padding = 2;
const { bodyG, data, scaleX, scaleY, quadrantWidth, yStart, colors } = this;
const bars = bodyG.selectAll("rect.bar").data(data);
bars.enter()
.append("rect")
.merge(bars)
.attr("class", "bar")
.transition()
.attr("x", (d) => scaleX(d.x))
.attr("y", (d) => scaleY(d.y))
.attr("height", (d) => yStart - scaleY(d.y))
.attr("fill", (d) => colors(d.x))
.attr("width", (d) => Math.floor(quadrantWidth / data.length) - padding);
}
}
const numberOfDataPoint = 31;
const data = Array.from({ length: numberOfDataPoint }, (_, i) => ({ x: i, y: random(10) }));
const chart = new Chart();
function update() {
data.forEach(item => item.y = random(10));
chart.render(data);
}
</script>
</html>