仪表盘图 - 分步指南
这是一个 D3 仪表盘图表示例,一步一步讲解。
引言
这是一个 D3 仪表盘图表示例,一步一步讲解。
背景
许多 Javascript 库都支持在网页中创建仪表盘图表,其中最著名的是大家熟知的 C3.js 库。尽管 C3.js 非常灵活,但我发现很难按照我想要的样子来制作图表,所以决定自己实现一个仪表盘图表。
为了简单起见,我使用了 D3.js 来进行 SVG 操作。虽然简单,但记录如何逐步创建仪表盘图表是一件很有意义的事情。
附件是一个 Java Maven 项目。我在 Tomcat 7 上进行了测试。如果您想运行它,不一定非要使用 Tomcat,因为所有文件都只是静态 HTML 文件。但我还是建议您通过 Web 服务器运行这些文件,以避免浏览器安全检查。您还需要互联网连接来加载文件,因为 D3.js 库是从 CDN 链接的。
step-0-start-from-a-half-circle.html
下面的 HTML 内容将用于测试仪表盘。为了简单起见,SVG 仪表盘将直接附加到“body”中。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>step-0-start-from-a-half-circle.html</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.11/d3.min.js"></script>
</head>
<body>
<div><a href="index.html">Go back</a></div>
</body>
</body>
</html>
作为“第 0 步”,唯一的目标是在网页中添加一个弧形(半圆形)。因为我们只需要一个半圆形,所以 SVG 的尺寸设置为 200 x 100。
let d2r = function(d) {
return d * (Math.PI / 180);
};
let Gauge = function() {
let c = {
element: 'body',
width: 200,
height: 100,
thickness: 30,
gaugeColor: '#dde9f7',
backgroundColor: '#f5f5f5'
};
let svg = d3.select(c.element).append('svg')
.attr('width', c.width).attr('height', c.height);
// Add a rect as the background
svg.append('rect').attr('x', 0).attr('y', 0)
.attr('width', c.width).attr('height', c.height).attr('fill', c.backgroundColor);
let r = c.width/2;
let createArc = function(sa, ea) {
return d3.svg.arc()
.outerRadius(r)
.innerRadius(r - c.thickness)
.startAngle(d2r(sa))
.endAngle(d2r(ea));
};
let addArc = function(arc, color) {
return svg.append('path')
.attr('d', arc).attr('fill', color)
.attr('transform', 'translate('
+ r + ',' + r + '), scale(1, 1)');
};
// Create a 1/2 circle arc and put it at the center
addArc(createArc(-90, 90), c.gaugeColor);
};
window.addEventListener('load', function() {
// Test the createGauge function
let gauge = new Gauge();
});
- 在 D3.js 的帮助下,通过调用“d3.svg.arc()”函数可以非常简单地添加一个弧形。
- 除了使用“startAngle = -90 度”和“endAngle = 90 度”添加弧形外,还会向 SVG 添加一个矩形作为图表的背景。
您应该已经注意到 JSON 对象“let c= {...}”。所有可配置的参数都定义在这个对象中。在后续部分,此对象还将用于接收输入的可配置参数。
step-1-add-margin.html
虽然不总是必需的,但为图表添加一些边距会更好。
let c = {
element: 'body',
width: 200,
height: 100,
margin: 4,
thickness: 30,
gaugeColor: '#dde9f7',
backgroundColor: '#f5f5f5'
};
为了使边距可配置,将其添加到配置对象中。然后按照以下方式计算弧形的外半径,以便为边距留出空间。
let r = (c.width - 2*c.margin)/2;
弧形的中心也会被调整,以考虑边距。
let addArc = function(arc, color) {
return svg.append('path')
.attr('d', arc).attr('fill', color)
.attr('transform', 'translate(' + (c.margin + r)
+ ',' + (c.margin + r) + '), scale(1, 1)');
};
您可以看到左侧、顶部和右侧的边距都已成功添加。您也应该看到底部没有边距。这是因为在后续步骤中,我将裁剪并缩放此弧形,并在底部添加标签。
step-2-scale-and-chop-the-arc.html
为了达到期望的外观,我需要裁剪和缩放弧形。
let c = {
element: 'body',
width: 200,
height: 100,
margin: 4,
yscale: 0.75,
chopAngle: 60,
thickness: 30,
gaugeColor: '#dde9f7',
backgroundColor: '#f5f5f5'
};
“yscale”和“chopAngle”已添加到配置对象中。为了使弧形保持相同的左、上、右边距,需要按照以下方式计算弧形的半径,以考虑裁剪角度。
let ir = (c.width - 2*c.margin)/2;
let r = ir/Math.sin(d2r(c.chopAngle));
弧形的中心需要考虑缩放,并且在绘制弧形时需要指定裁剪角度。
let addArc = function(arc, color) {
return svg.append('path')
.attr('d', arc).attr('fill', color)
.attr('transform', 'translate(' + (c.margin + ir)
+ ',' + (c.margin + r*c.yscale) + '), scale(1, ' + c.yscale + ')');
};
addArc(createArc(-1*c.chopAngle, c.chopAngle), c.gaugeColor);
step-3-add-the-indicator.html
为了让仪表盘显示一个值,我们需要添加值指示器。
let c = {
element: 'body',
width: 200,
height: 100,
margin: 4,
yscale: 0.75,
chopAngle: 60,
thickness: 30,
value: {
minValue: 0,
maxValue: 100,
initialvalue: 60
},
gaugeColor: '#dde9f7',
indicatorColor: '#4281a4',
backgroundColor: '#f5f5f5'
};
配置对象现在有一个值对象,其中包含仪表盘的最小值、最大值和初始值。
let getIndicatorData = function(value) {
let min = c.value.minValue;
let max = c.value.maxValue;
return {
sa: c.chopAngle - 2*c.chopAngle*value/(max - min),
ea: c.chopAngle
};
}
// Add the initial indicator
let data = getIndicatorData(c.value.initialvalue);
let indicator = addArc(createArc(data.sa, data.ea), c.indicatorColor);
在这个仪表盘中,值代表“剩余多少”,所以指示器占据仪表盘的右侧。如果您希望仪表盘值代表“已使用/已花费多少”,可以修改“getIndicatorData()”函数来实现您的期望结果。
step-4-add-the-labels.html
通常,仪表盘需要一些标签来以文本形式显示值。
let c = {
element: 'body',
width: 200,
height: 100,
margin: 4,
yscale: 0.75,
chopAngle: 60,
thickness: 30,
value: {
minValue: 0,
maxValue: 100,
initialvalue: 60
},
label: {
yoffset: 10,
unit: 'unit.',
labelText: {
text: 'REMAINING',
yoffset: 12
}
},
gaugeColor: '#dde9f7',
indicatorColor: '#4281a4',
backgroundColor: '#f5f5f5'
};
配置对象中的“label”对象包含了创建图表标签所需的信息。
let createLabel = function(config) {
let c = {
x: config.width/2,
y: config.height/2 + config.label.yoffset,
unit: config.label.unit,
label: {
text: config.label.labelText.text,
yoffset: config.label.labelText.yoffset
},
value: config.value.initialvalue
};
let text = svg.append('text')
.attr("x", c.x)
.attr("y", c.y)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "25px");
let label = text.append('tspan')
.attr("font-size", "25px")
.text(c.value);
text.append('tspan')
.attr('font-size', '10px').text(c.unit)
.attr('fill', '#333333');
text = svg.append('text')
.attr("x", c.x)
.attr("y", c.y + c.label.yoffset)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "central")
.attr("font-family", "sans-serif")
.attr('fill', '#a8a8a8')
.attr("font-size", "12px");
text.text(c.label.text);
return label;
};
// Add the label
let valueText = createLabel(c);
step-5-external-configuration.html
为了让“Gauge()”函数能够接受外部配置参数,我创建了以下方法来合并外部配置对象和默认配置对象。
var jmerge = function(t, s) {
if (t === null || typeof t === 'undefined'
|| s === null || typeof s === 'undefined') { return s;}
if ((typeof t !== 'object') || (typeof s !== 'object')) { return s; }
if ((t instanceof Array) || (s instanceof Array)) { return s; }
if ((t instanceof String) || (s instanceof String)) { return s; }
for (var key in s) {
if (s.hasOwnProperty(key)) {
if (!t.hasOwnProperty(key)) { t[key] = s[key]; }
else { t[key] = jmerge(t[key], s[key]); }
}
}
return t;
}
“jmerge”函数将“s”对象递归地合并到“t”对象中并返回合并后的结果。它不是一个完美的 Javascript 合并函数,但足以满足此示例的目的。借助“jmerge”函数,“Gauge”函数现在可以接受外部配置对象。
let Gauge = function(config) {
let c = {
element: 'body',
width: 200,
height: 100,
margin: 4,
yscale: 0.75,
chopAngle: 60,
thickness: 30,
value: {
minValue: 0,
maxValue: 100,
initialvalue: 60
},
label: {
yoffset: 10,
unit: 'unit.',
labelText: {
text: 'REMAINING',
yoffset: 12
}
},
gaugeColor: '#dde9f7',
indicatorColor: '#4281a4',
backgroundColor: '#f5f5f5'
};
// Merge the external config to the default config
c = config? jmerge(c, config): c;
// Rest of the code ...
};
在显示图表时,我们可以尝试提供一些外部配置参数来覆盖默认配置。
let gauge = new Gauge({
margin: 10,
label: {
unit: 'Hours',
labelText: {
text: 'GOING-ON'
}
}
});
step-6-update-the-value.html
为了能够实时更新仪表盘显示,我在“Gauge”构造函数中添加了“update”方法。
let self = this;
self.update = function(value) {
// Update the indicator
let data = getIndicatorData(value);
indicator.attr('d', createArc(data.sa, data.ea))
// Update the label
valueText.text(value);
};
然后我们可以调用“update”方法来更改仪表盘的值。
let gauge = new Gauge({
margin: 10,
label: {
unit: 'Hours',
labelText: {
text: 'GOING-ON'
}
}
});
// Test the update function
gauge.update(40);
关注点
- 这是一个 D3 仪表盘图表,通过 6 个小步骤构建而成;
- 希望您喜欢我的博文,并希望这篇笔记能以某种方式帮助您。
历史
首次修订 - 2016/1/12