使用angular-nvd3构造圆形分数

目标简介

一个漂亮的分数显示,从0-100,就像下面几个图那样:

../../../_images/40.png ../../../_images/80.png ../../../_images/95.png

所用到的技术

angularjs [1] 是非常著名的前端MVC框架,由google支持的开源项目。

d3 [2] 是基于SVG的数据可视化技术,是教底层的可扩展的库。

nvd3 [3] 是对d3的封装,实现了柱状图、折线图、饼图等常用的统计图形的定制库,增加了d3的易用性。

angular-nvd3 [4] 是nvd3在angular框架中的进一步封装,使得其在angular用户中更加易用。

要使用angular-nvd3,html页面中需要包含如下片段(假设各个模块已经下载到本地,使用cdn的读者自行替换):

<link href="bower_components/nvd3/build/nv.d3.min.css" rel="stylesheet">
<script src="bower_components/d3/d3.min.js"></script>
<script src="bower_components/nvd3/build/nv.d3.min.js"></script>
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/angular-nvd3/dist/angular-nvd3.min.js"></script>

实现思路

在nvd3中,由于饼图(pieChart)可以定制成中间掏空环形图(donut),我们可以以此为基础来构造整个图形:

  1. 外圈绿色和灰色构成的环形图;
  2. 中圈白色和黄色构成的环形图;
  3. 内圈直接使用div

外圈环形图需要两个值, score和100-score; 中圈一样; 内圈通过css设置border-radius为50%来构成一个整圆形,使用line-height来居中。

不过,怎么使这两个环与中间的圆都是同心圆呢?这需要如下两个条件:

  • 容器为正方形,绝对定位
  • 两个nvd3图的选项,四边的border都是0

参考源代码

index.html
<html>
  <head>
    <title>漂亮的分数</title>
    <link href="bower_components/nvd3/build/nv.d3.min.css" rel="stylesheet">
    <script src="bower_components/d3/d3.min.js"></script>
    <script src="bower_components/nvd3/build/nv.d3.min.js"></script>
    <script src="bower_components/angular/angular.min.js"></script>
    <script src="bower_components/angular-nvd3/dist/angular-nvd3.min.js"></script>

    <link href="index.css" rel="stylesheet" >
  </head>
<body ng-app="demoApp">
<h1>漂亮的分数</h1>

<div style="width: 400px; height: 400px; position:absolute;" ng-controller="DemoCtrl" >
  <nvd3 options="chart.options" data="scoreData" class="full" ></nvd3>
  <div style="width: 75%; height: 75%; margin-left: 12.5%; margin-top: 12.5%; position:absolute;">
    <nvd3 options="chart.options_inner" data="scoreData" class="full" ></nvd3>
  </div>
  <div style="width: 80%; height: 80%; margin-left: 10%; margin-top: 10%; position:absolute;">
    <div style="position:absolute; width: 50%; height: 50%; left: 25%; top: 25%; background: black; border-radius: 50%; text-align: center; color: white; line-height: 45px;">

      <span style="font-size:2.5em; line-height: 160px;" >{{score}}分</span>
    </div>
  </div>
</div>

<script type="text/javascript">

  angular.module("demoApp", ['nvd3'])
  .controller("DemoCtrl", function($scope){

        $scope.xFunction = function(){
            return function(d){
                return d.key;
            };

        };
        $scope.yFunction = function(){
            return function(d){
                return d.value;
            };

        };

        function init(){
            $scope.score = 95;
            $scope.chart = {};

            $scope.chart.options = {
                chart:{
                    type: 'pieChart',
                    margin: { left: 0, top: 0, right: 0, bottom: 0},
                    donut: true,
                    donutRatio: 0.60,
                    x: $scope.xFunction(),
                    y: $scope.yFunction(),
                    showLabels: false,
                    showLegend: false,
                    color: ['rgb(130,201,63)', 'lightgray']

                }
            };
            $scope.chart.options_inner = {
                chart:{
                    type: 'pieChart',
                    margin: { left: 0, top: 0, right: 0, bottom: 0},
                    donut: true,
                    donutRatio: 0.5,
                    x: $scope.xFunction(),
                    y: $scope.yFunction(),
                    showLabels: false,
                    showLegend: false,
                    color: ['transparent', 'rgb(252,225,11)']

                }

            };

        }

        init();

        $scope.$watch('score', function(nv, ov){
            if( nv ){

                $scope.scoreData = [
                    {key: 'score', value: nv },
                    {key: 'other', value: 100-nv }
                ];
                $scope.chart.data = {
                    key: 'thekey',
                    data: $scope.scoreData

                };

            }

        });

  });


</script>


</body>
</html>
index.css
.full{
    position:absolute;
    width: 100%;
    height: 100%;
}