65.9K
CodeProject 正在变化。 阅读更多。
Home

仪表盘图 - 分步指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (4投票s)

2016年12月1日

CPOL

4分钟阅读

viewsIcon

24344

downloadIcon

321

这是一个 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

© . All rights reserved.