【D3.js数据可视化系列教程】(十七)--通过键联结数据

作者:zhang__tianxu

最后效果:点击删除会从左侧消失。数值和条是对应的,即添加一条时更新位置后对应条上的数值还在这条上。

  1. 键值对数据集

var dataset = [ 
    {key:0,value:5},
    {key:1,value:10},
    {key:2,value:13},
    {key:3,value:19},
    {key:4,value:21},
    {key:5,value:25},
    {key:6,value:22},
    {key:7,value:18},
    {key:8,value:15},
    {key:9,value:13},
    ...
];

  2. 更新数据引用,包含下面所有关于要使用到d.value的地方

.domain([0,d3.max(dataset,function(d){
   return d.value;
})])
.range([0,h]);

  3. 定义键函数(简洁),以备数据绑定到元素的时候使用

var key=function(d){
   return d.key;
};

  4. 从左侧退出

bars.exit().transition().duration(500)
.attr("x", -xScale.rangeBand())//w-xScale.rangeBand()间隙宽其实其他负数也行
.remove();});

  5. 实现键联结数据

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>testD3-18-update.html</title>
        <script type="text/javascript" src="http://localhost:8080/spring/js/d3.v3.js"></script>
    <style type="text/css">
    /* 鼠标悬停时变色*/
    rect:hover{
        fill :orange;
    }
    /* 过渡效果*/
    rect{
    -moz-transiton:all 0.3s;
    -o-transiton:all 0.3s;
    -webkit-transition:all 0.3s;
    transition:all 0.3s
    }
    /*(2)给提示条加上样式*/
    #tooltip {
        position: absolute;
        width: 200px;
        height: auto;
        padding: 10px;
        background-color: white;
        -webkit-border-radius: 10px;
        -moz-border-radius: 10px;
        border-radius: 10px;
        -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        pointer-events: none;
    }

    #tooltip.hidden {
        display: none;
    }

    #tooltip p {
        margin: 0;
        font-family: sans-serif;
        font-size: 16px;
        line-height: 20px;
    }

    </style>
    </head>
    <body>
    <button>单击更新</button>
    <br>
    <p id="remove" class="click">单击删除</p><div id="add" class="click">单击添加</div>
    <br>

    <!-- (1)创建div提示层 -->
    <div id="tooltip" class="hildden">
        <p><strong>提示:</strong></p>
        <p><span id="value">100</span>%</p>
    </div>
    <br>
        <script type="text/javascript">

            //键值对数据集
            var dataset = [ {
                key : 0,
                value : 5
            }, {
                key : 1,
                value : 10
            }, {
                key : 2,
                value : 13
            }, {
                key : 3,
                value : 19
            }, {
                key : 4,
                value : 21
            }, {
                key : 5,
                value : 25
            }, {
                key : 6,
                value : 22
            }, {
                key : 7,
                value : 18
            }, {
                key : 8,
                value : 15
            }, {
                key : 9,
                value : 13
            }, {
                key : 10,
                value : 11
            }, {
                key : 11,
                value : 12
            }, {
                key : 12,
                value : 15
            }, {
                key : 13,
                value : 20
            }, {
                key : 14,
                value : 18
            }, {
                key : 15,
                value : 17
            }, {
                key : 16,
                value : 16
            }, {
                key : 17,
                value : 18
            }, {
                key : 18,
                value : 23
            }, {
                key : 19,
                value : 25
            } ];

            //设置SVG的高宽
            var w = 600;
            var h = 250;
            var barPadding = 1;

            //定义序数比例尺
            var xScale = d3.scale.ordinal()//序数比例尺
                .domain(d3.range(dataset.length))
                .rangeRoundBands([ 0, w ], 0.05);

            // 更新数据引用,包含下面所有关于要使用到d.value的地方             
            var yScale = d3.scale.linear()//y仍然是线性比例尺
                .domain([ 0, d3.max(dataset, function(d) {return d.value;}) ])
                .range([ 0, h ]);

            // 定义键函数(简洁),以备数据绑定到元素的时候使用
            //把所有.data(dataset)改成.data(dataset,key)
            var key = function(d) {
                return d.key;
            };

            //值函数 
            var value = function(d) {
                return d.value;
            };

            //条排序函数
            var sortOrders = false;
            var sortBars = function() {
                sortOrders = !sortOrders;//(3)每点击一次排序方向改变
                svg.selectAll("rect")
                    .sort(function(a, b) {
                        if (sortOrders) {
                            //对数据集升序排序
                            return d3.ascending(a.value, b.value);//这个地方注意是键值对所以要加上值的引用b.value
                        } else {
                            //对数据集降序排序
                            return d3.descending(a.value, b.value);
                        }
                    })
                    .transition()
                    .duration(1000)
                    .attr("x", function(d, i) {//对排序之后的横坐标重排
                        return xScale(i);
                    });

                svg.selectAll("text")
                    .sort(function(a, b) {
                        if (sortOrders) {
                            //对数据集升序排序
                            return d3.ascending(a.value, b.value);//这个地方注意是键值对所以要加上值的引用b.value
                        } else {
                            //对数据集降序排序
                            return d3.descending(a.value, b.value);
                        }
                    })
                    .transition()
                    .duration(1000)
                    .attr("x", function(d, i) {//对排序之后的横坐标重排
                        return xScale(i)+ xScale.rangeBand() / 2;
                    });
            };

            d3.select("#tooltip").classed("hidden", true);

            //创建SVG元素
            var svg = d3.select("body")//选中DOM中的目标元素
                .append("svg")//为目标元素附加上一个svg子元素
                .attr("width", w)//设置这个svg的宽
                .attr("height", h);//设置这个svg的高

            //为SVG添加条形
            svg.selectAll("rect")//选中空元素,表示即将创建这样的元素
                .data(dataset, key)//对此后的方法都执行dataset.length遍
                .enter()//数据元素值比前面选中的DOM元素多就创建一个新的DOM元素
                .append("rect")//取得enter的占位元素,并把rect追加到对应的DOM中
                .attr("x", function(d, i) {//设置横坐标,从0开始每次右移元素宽那么长(w / dataset.length)
                    //return i * (w / dataset.length);
                    return xScale(i);//这里使用序数比例尺,自己去找刚才划分好的档位
                }).attr("y", function(d) {//设置纵坐标,纵坐标正方向是从上往下的,所以条有多长就要设置起点是相对于h再向上移动条长
                    return h - yScale(d.value);
                })
                //.attr("width", w / dataset.length - barPadding)//设置元素宽,留出间隙宽barPadding。
                .attr("width", xScale.rangeBand())//这里xScale比例尺已经设置间距了所以直接用
                .attr("height", function(d) {
                    return yScale(d.value);
                }).attr("fill", function(d) {//设置RGB颜色与数值的关系
                    return "rgb(0, 0, " + (d.value * 10) + ")";
                })
                //点击排序
                .on("click", function() {
                    sortBars();
                })
                //(3)更新提示条的值和位置
                .on("mouseover",function(d) {
                    //取得提示显示的位置
                    var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.rangeBand() / 2;
                    var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + h / 2;
                    d3.select("#tooltip")
                        .style("left", xPosition + "px")
                        .style("top", yPosition + "px")                     
                        .select("#value")
                        .text(d.value);
                    d3.select("#tooltip").classed("hidden", false);
                })
                //移除提示条 
                .on("mouseout", function() {
                    //(4)添加隐藏类
                    d3.select("#tooltip").classed("hidden", true);//ID 选择的语法:"#tooltip"
                });

            //为条加上数值
            svg.selectAll("text").data(dataset, key).enter().append("text")
                .text(function(d) {
                    return d.value;
                })
                .attr("text-anchor", "middle").attr("x", function(d, i) {
                    return xScale(i) + xScale.rangeBand() / 2;
                })
                .attr("y", function(d) {
                    return h - yScale(d.value) + 14;
                })
                .attr("font-family", "sans-serif")
                .attr("font-size",function(d) {
                    return xScale.rangeBand() / 2;
                })
                .attr("fill", "white");

            //删除一条、添加一条
            d3.selectAll(".click")
              .on("click",function() {
                //根据ID确定点击的是哪个标签
                var paragraphID = d3.select(this).attr("id");
                //添加删除组合起来
                if (paragraphID == "add") {
                    //数据集最后添加数值
                    var maxValue = 75;
                    var newNumber = Math.floor(Math.random() * maxValue);//0-24的整数

                    //根据最后一个key添加一个值
                    var lastKeyValue = dataset[dataset.length - 1].key;
                    dataset.push({
                        key : lastKeyValue + 1,
                        value : newNumber
                    });

                    //更新X轴比例尺
                    xScale.domain(d3.range(dataset.length));
                    //选择所有条
                    var bars = svg.selectAll("rect").data(dataset, key); //绑定数据到元素集,返回更新的元素集

                    var texts = svg.selectAll("text").data(dataset, key);
                    //添加条形元素到最右边
                    bars.enter().append("rect").attr("x", w);//在SVG最右边,不可见

                    texts.enter().append("text");

                    //更新新矩形到可见范围内
                    //并在这个时候根据数据集为每个条设置对应的属性
                    bars.transition()
                        .duration(500)
                        .attr("x",function(d, i) {
                            return xScale(i);
                        })//每个X对应到它相应的档位上
                        .attr("y", function(d) {
                            return h - yScale(d.value);
                        }).attr("width", xScale.rangeBand())//这里xScale比例尺已经设置间距了所以直接用
                        .attr("height", function(d) {
                            return yScale(d.value);
                        }).attr("fill",function(d) {//设置RGB颜色与数值的关系
                            return "rgb(0, 0, " + (d.value * 10) + ")";
                        });

                    texts.transition()
                        .text(function(d) {
                            return d.value;
                        })
                        .attr("text-anchor", "middle")
                        .attr("x",function(d, i) {
                            return xScale(i)+ xScale.rangeBand()/ 2;
                        })
                        .attr("y",function(d) {return h- yScale(d.value)+ 14;})
                        .attr("font-family", "sans-serif")
                        .attr("font-size", "12px")
                        .attr("fill", "red");
                } else {
                    //删除的操作
                    //选择所有条
                    dataset.shift();
                    //更新X轴比例尺
                    xScale.domain(d3.range(dataset.length));

                    var bars = svg.selectAll("rect").data(dataset, key);
                    var texts = svg.selectAll("text").data(dataset, key);
                    //从左侧退出
                    bars.exit().transition().duration(500)
                        .attr("x", -xScale.rangeBand())//w-xScale.rangeBand()间隙宽其实其他负数也行
                        .remove();
                    //从左侧退出
                    texts.exit().transition().duration(500)
                        .attr("x", -xScale.rangeBand())//w-xScale.rangeBand()间隙宽其实其他负数也行
                        .remove();
                }

            });

            // 更新条形数长短的代码,需要一个button标签配合
            //特别注意:这里选中的元素必须在d3选择器之前,或许要先加载完了元素才能被选中
            d3.select("button").on("click",function() {
                // 新数据集,随机数组
                var numValues = dataset.length;
                dataset = [];
                var maxValue = 75;
                var newNumber;
                for ( var i = 0; i < numValues; i++) {
                    newNumber = Math.floor(Math.random() * maxValue);//0-24的整数
                    //根据i添加一个值
                    dataset.push({
                        key : i,
                        value : newNumber
                    });
                }
                // 更新比例尺,免使纵坐标超出范围
                yScale.domain([ 0, d3.max(dataset, value) ]);//只要更新定义域就行了,映射到的值域不变
                //更新所有的矩形
                svg.selectAll("rect").data(dataset, key).transition()// 加上过渡动画 
                .delay(function(d, i) {
                    return i / dataset.length * 1000;
                })//指定过度什么时间开始,可以用函数控制每一条的动画时间,这样就可得到钢琴版的效果
                .duration(2000)// 加上动画的持续时间,以毫秒计算
                .ease("linear")// 缓动函数:有circle(加速)elastic(伸缩),linear(匀速),bounce(弹跳)
                .attr("y", function(d) {
                    return h - yScale(d.value);
                }).attr("height", function(d) {
                    return yScale(d.value);
                });

            // 更新条上的数值
            svg.selectAll("text")
                .data(dataset, key)
                .text(function(d) {
                    return d.value;
                })
                .attr("text-anchor", "middle")
                .attr("x",function(d, i) {
                    return xScale(i) + xScale.rangeBand() / 2;
                })
                .attr("y", function(d) {
                    return h - yScale(d.value) + 14;
                })
                .attr("font-family", "sans-serif").attr("font-size","12px")
                .attr("fill", "red");
            });
        </script>
    </body>
</html>

发表评论

10个评论

  • DCX_abc

    同3楼

    2017-10-17 09:10:48回复

  • lolicon_ming

    191行写成yScale.domain([0,d3.max(dataset,function(d){return d.value;})]);吧

    2015-01-14 14:59:19回复

  • zhang__tianxu

    回复lolicon_ming: 是的,谢谢指正!

    2015-01-19 22:04:50回复

  • lolicon_ming

    126行貌似不能直接push一个数,应该push一个{key,value}数组的样子

    2015-01-14 14:45:18回复

  • zhang__tianxu

    回复lolicon_ming: 已更改 dataset.push({ key : lastKeyValue + 1, value : newNumber });

    2015-01-19 22:04:26回复

加载更多
我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

知识工程知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?