D3.js(2) Data-Join

什么是Data-Join?

本质上是将数据与图元绑定

可以省去大量根据数据设置图元属性的代码量,对动态变化的数据提供统一接口

D3.js绑定数据的三个状态

Enter

数据数量>图元数量,d3.js会根据新增的数据生成相应的图元

给不存在数据绑定的图元提供占位符

const p = maingroup.selectAll('.class').data(data).enter().append('').attr(...)
//data(data) 数据绑定

Update

图元数量与数据数量一致 

const p = maingroup.selectAll('.class').data(data).append('').attr(...)

在update状态时可以配合transition().duration(time)实现动画的过渡效果

Exit

数据数量<图元数量,d3.js会自动删除没有绑定数据的图元

const p = maingroup.selectAll('.class').data(data).exit().remove()

 读取CSV

d3.csv('./data.csv').then(data => {})
//返回值是一个Promise对象,JS中的异步编程
//不能用等号接受它的返回值

如果d3.csv读取的是主机本地的路径,那么会报跨域错误 

案例 

运行这次代码的时候的报错:Uncaught TypeError TypeError: Failed to fetch

按照这个文章里的第一个方法解决:(80条消息) d3.csv()读取本地文件失败_报错Access to XMLHttpRequest at ‘file:‘ from origin ‘null‘ has been blocked_替换和解决方法_Jude_zhai的博客-CSDN博客

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Scatter-Simple</title>
  </head>
  <body style="text-align: center">
    <svg width="1650" height="920" id="mainsvg" class="svgs" style="background-color: #ffffff;"></svg>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script src="d3.min.js"></script>
    <script>
        const svg = d3.select('#mainsvg');
        const width = +svg.attr('width');
        const height = +svg.attr('height');
        const margin = {top: 100, right: 120, bottom: 100, left: 120};
        const innerWidth = width - margin.left - margin.right;
        const innerHeight = height - margin.top - margin.bottom;
        let xScale, yScale;
        const xAxisLabel = '累计确诊人数(对数)';
        const yAxisLabel = '新增人数(对数)';
        let alldates;
        let sequantial;
        let aduration = 1000;
        let xValue = d => Math.log(d['确诊人数'] + 1);
        let yValue = d => Math.log(d['新增确诊'] + 1); 

        var color = {
            "武汉":"#ff1c12",
            "黄石": "#de5991",
            "十堰": "#759AA0",
            "荆州": "#E69D87",
            "宜昌": "#be3259",
            "襄阳": "#EA7E53",
            "鄂州": "#EEDD78",
            "荆门": "#9359b1",
            "孝感": "#47c0d4",
            "黄冈": "#F49F42",
            "咸宁": "#AA312C",
            "恩施州": "#B35E45",
            "随州": "#4B8E6F",
            "仙桃": "#ff8603",
            "天门": "#ffde1d",
            "潜江": "#1e9d95",
            "神农架": "#7289AB"
        }

        const renderinit = function(data){
            // Linear Scale: Data Space -> Screen Space; 
            xScale = d3.scaleLinear()
            .domain([d3.min(data, xValue), d3.max(data, xValue)]) 
            .range([0, innerWidth])
            .nice();

            // Introducing y-Scale; 
            yScale = d3.scaleLinear()
            // d3.extent()计算数组的最小值和最大值 返回的是数组 纵轴起始点原本是左上角 需要reverse一下
            .domain(d3.extent(data, yValue).reverse()) 
            .range([0, innerHeight])
            .nice(); //将定义域的刻度扩展整齐

            // 可作图区域的容器
            const g = svg.append('g')
            .attr('transform', `translate(${margin.left}, ${margin.top})`)
            .attr('id', 'maingroup');

            // Adding axes; 
            const yAxis = d3.axisLeft(yScale)
            .tickSize(-innerWidth) //设置刻度线长度
            //.tickPadding(10); // .tickPadding is used to prevend intersection of ticks; 
            const xAxis = d3.axisBottom(xScale)
            .tickSize(-innerHeight)
            //.tickPadding(10);

            let yAxisGroup = g.append('g').call(yAxis)
            .attr('id', 'yaxis');
            yAxisGroup.append('text')
            .attr('font-size', '2em')
            .attr('transform', `rotate(-90)`)
            .attr('x', -innerHeight / 2)
            .attr('y', -60)
            .attr('fill', '#333333')
            .text(yAxisLabel)
            .attr('text-anchor', 'middle') // Make label at the middle of axis. 
            //yAxisGroup.selectAll('.domain').remove(); // we can select multiple tags using comma to seperate them and we can use space to signify nesting; 
            
            let xAxisGroup = g.append('g').call(xAxis)
            .attr('transform', `translate(${0}, ${innerHeight})`)
            .attr('id', 'xaxis');
            xAxisGroup.append('text')
            .attr('font-size', '2em')
            .attr('y', 60)
            .attr('x', innerWidth / 2)
            .attr('fill', '#333333')
            .text(xAxisLabel);
            //xAxisGroup.selectAll('.domain').remove();
        };
        

        const renderUpdate = function(seq){
            const g = d3.select('#maingroup');
            let circleupdates = g.selectAll('circle').data(seq, d => d['地区']);
            

            let circleenter = circleupdates.enter().append('circle')
            .attr('cx', d => xScale(xValue(d))) //横坐标
            .attr('cy', d => yScale(yValue(d))) //纵坐标
            .attr('r', 30) //半径
            .attr('fill', d => color[d['地区']]) //颜色
            .attr('opacity', 0.8) 


            circleupdates.merge(circleenter)
            .transition().ease(d3.easeLinear).duration(aduration)
            .attr('cx', d => xScale(xValue(d)))
            .attr('cy', d => yScale(yValue(d)));

            //日期修改
            time = seq[0]['日期'];
            g.selectAll('.date_text').remove();
            g.append("text")
            .data(['seq']) 
            .attr('class', 'date_text')
            .attr("x", innerWidth / 4 + 150)
            .attr("y", - 20)
            .style("text-anchor", "end")
            .attr("fill", "#504f4f")
            .attr('font-size', '6em')
            .attr('font-weight', 'bold')
            .text(time);

            textupdates = g.selectAll('.province_text').data(seq);
            textenter = textupdates.enter().append('text')
            .attr("class", "province_text")
            .attr("x", (datum) => { return xScale(xValue(datum)); })
            .attr("y", (datum) => { return yScale(yValue(datum)); })
            .attr("dy", "1em")
            .style("text-anchor", "middle")
            .attr("fill", "#333333")
            //.attr('opacity', 0)
            .text(function(d,i){ 
                return d['地区']
            })

            textupdates.merge(textenter).transition().ease(d3.easeLinear).duration(aduration)
            .attr('x', (datum) => { 
                return xScale(xValue(datum)); })
            .attr('y', (datum) => { return yScale(yValue(datum)); });
        }

        /*--------------------------------------------数据处理--------------------------------------------------*/
        d3.csv("hubeinxt.csv").then( data => {
            //数据过滤
            data = data.filter( d => d['地区'] !== '总计' );
            data.forEach( d => {
                //将目标数据转换成数值
                d['确诊人数'] = +(d['确诊人数']);
                d['新增确诊'] = +(d['新增确诊']);
                //异常数据处理
                if( d['新增确诊'] < 0 ){
                    d['新增确诊'] = 0;
                }
            } )

            //set去掉重复日期
            alldates = Array.from( new Set(data.map( d => d['日期']) )); 

            //日期的排序 从早到晚排序
            alldates = alldates.sort( (a,b) => {
                return new Date(a) - new Date(b);
            } );

            //构造每一天数据的二维数组
            sequantial = []; 
            alldates.forEach( d => {
                sequantial.push([])
            } );
            data.forEach( d => {
                //根据alldates中日期的索引,将data中日期相同的数据放在sequantial同一个数组
                sequantial[ alldates.indexOf(d['日期'])].push(d);
            } );
            renderinit(data);

            let c = 0;
            let intervalId = setInterval( () => {
                if( c >= alldates.length ){
                    clearInterval(intervalId);
                }else{
                    renderUpdate(sequantial[c]);
                    c = c + 1;
                }
            }, aduration)

            console.log(sequantial);
        } )

    </script>
  </body>
</html>

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值