D3.js从源码分析到精通(三)

data

 let fruits = [
      {name: "orange", value: 200},
      {name: "apple", value: 124},
      {name: "banana", value: 32}
    ];

    let d = d3.select('#bbb').selectAll('div')
      .data(fruits)
      .enter().append('div');
	d.style('color', function(d) {
                     if (d % 2 === 0) {
                         return "green";
                     } else {
                         return "red";
                     }
                 })
      d.text(function(d) { return d.name + ": " + d.value; });
// enter() 是输出的意思

d3.select('#bbb')

选择一个节点

.selectAll('span')

没有这个元素,返回一个空数组

.data(data)

有三个元素,会循环三次

enter()

将为5个元素中的每个元素创建一个跨度

.append('span')

添加到body元素上

text()

打印出来

我们知道text在函数中

.text(function (d, i,groups) {
                 console.log("d: " + d); // 每一样
                 console.log("i: " + i); // 索引
                 console.log("this: " + this);// 当前dom的引用
					groups // 全部引用的dom
                 return d;
             });
.data(data,function(v,i,g){
     // 每一项, 索引,所有数据,绑定的全部#bbb节点
     console.log(v, i, g,this);
     return i
   })

通过查询大量资料,我们了解了如果提供了第二个参数(成为键函数),告诉d3的插入位置

无论键功能是否存在,数据都将是对象,键功能不会更改基础数据

我们会发现data实际存在在每一个dom的__data__ 属性上

join()

上一个案例

join里面有三个参数函数

分别是 enter,update,exit

d3.select('.aaa').selectAll('div').data([1,2,3,4])
      // .join('p').text(v=>v)
      .join(v=>v.append('p')).text(v=>v)
两者的效果类似

enter()

有点理解输入的用法了

<div id="bbb">
<div class="aaa"></div>
<div class="aaa"></div>
<div class="aaa"></div>
</div>

d3.select('#bbb').selectAll(".aaa")
   .data([1,2,3,5,6]).text(v=> {
   return v;
 }).enter().append('h1').text(v=>v)

结果是前三个 .aaa
后面两个是  h1
============
// 有元素会直接更新上去
<div class="aaa">
  <div class="bbb"></div>
  <div class="bbb"></div>
  <div class="bbb"></div>
  <div class="bbb"></div>
</div>
d3.select('.aaa').selectAll('.bbb').data([1,2])
      .text(v=>v)
// 如果多了可以再添加其他
 d3.select('.aaa').selectAll('.bbb').data([1,2,4,5,67,7])
      .text(v=>v).enter().append('h1').text(v=>v)

exit()

删除多余的元素

<div class="aaa">
  <div class="bbb"></div>
  <div class="bbb"></div>
  <div class="bbb"></div>
  <div class="bbb"></div>
</div>

   d3.select('.aaa').selectAll('.bbb').data([1,2])
      .text(v=>v).exit().remove()
结果为
<div class="aaa">
  <div class="bbb">1</div>
  <div class="bbb">2</div>
</div>

datum

data() 添加元素组

datum() 分配给各个元素

document.body.__data__ = 42;
类似
d3.select("body").datum(42);

源码

export default function(value) {
  return arguments.length
      ? this.property("__data__", value)
      : this.node().__data__;
}

实践中

   let a = d3.select('.aaa').selectAll('div').append('p').datum(10).text(v => v);
    // 查询这个值
	console.log(a.datum());
    // 10
    // 我们会发现修改了这个值
    a.datum(12).text(v=>v)
<ul id="list">
  <li data-username="shawnbot">Shawn Allen</li>
  <li data-username="mbostock">Mike Bostock</li>
</ul>

 d3.select('#list').selectAll('li').datum(function(){
      // 可以拿到自定义属性
      return (this as HTMLElement).dataset
    }).text(v=>v.username)

on()

类似于addEventListener()

d3.select('.input').on('change', function(e) {
      console.log(e);
    });

源码分析

function contextListener(listener) {
  return function(event) {
    listener.call(this, event, this.__data__);
  };
}

我们会发现数据会传给函数的第二个参数,写一个案例

  d3.select('button').datum(333).on('click',function(e,data){
            console.log(e);
            console.log(data);// 第二个参数拿到了数据 333
        })

当只写入一个参数,我们会发现可以拿到返回的值或者函数

aaa.on('clicks', function (d) {
                return 3
            })
        console.log(aaa.on('clicks')());// 3
===
     aaa.on('clicks', 3)
     console.log(aaa.on('clicks'));// 3

源码分析

function parseTypenames(typenames) {
  return typenames.trim().split(/^|\s+/).map(function(t) {
    var name = "", i = t.indexOf(".");
    if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
    return {type: t, name: name};
  });
}
console.log(parseTypenames$1('aaa.bbb ccc.ddd'));
// [ { type: 'aaa', name: 'bbb' }, { type: 'ccc', name: 'ddd' } ]

从中我们可以分析到可以多个鼠标事件作用于一个函数

我们会发现鼠标进入和点击同时作用于这个函数
let aaa=d3.select('.aaa');
 aaa.on('click mouseenter', function(){
            console.log(1);
        })
其中我感觉name类似于标识的意思
 aaa.on('click.name1 mouseenter.name2', function(){
            console.log(1);
        })

在源码中,我们得到了一些分析

aaa.on('clicks')
由于没有第二个参数,源码中会通过 removeEventListener 删除这个事件
let aaa=d3.select('.aaa');
        aaa.on('click.name1 mouseenter.name2', function(){
            console.log(1);
        })
        aaa.on('click.name1',function(){
            console.log(2);
        })
如果出现这个点击事件重复的情况下
我们会发现点击的时候一直打印的是 2
源码中通过removeEventListener删除原有的,然后addEventListener 进行添加
如果使用的selectAll 源码中也会通过 this.eath 进行多个添加事件

合成事件

创建和分派DOM事件 通常称为合成事件,而不是浏览器本身出发的事件

customEvent

event= new VustomEvent(typeArg,customEventInit)
typeArg  事件名称
customEventInit 可选
   里面的字段有
   datail 默认null
   可以添加一个对象进行分配
   这里面其实有个属性
    cancelable: true   是否被清除

demo

<form action="">
  <input type="text" class="input">
</form>

 let form = document.querySelector('form')
    let input = document.querySelector('input')

// 创建一个自定义时间,通过冒泡进行传递
    const eventAwesome = new CustomEvent('awesome', {
      bubbles: true,
      detail: { text: ()=>input.value }
    });

// 父元素接受子元素传递的值
    form.addEventListener('awesome', e => console.log(e.detail.text()));

// 子元素监听值,传递给父亲, 自己写的时候容易出错的是
    input.addEventListener('input', e => e.target.dispatchEvent(eventAwesome));

event.preventDefault()

默认行为,这个 cancelable: true 一起使用,一直纠结这个属性有什么用,查阅了大量知道,写一个简单的案例

我们会发现调用 event.preventDefault() 指令取消这些操作, 对dom.dispatchEvent(event) 的调用为false

  let event = new CustomEvent('aaa',
      // 通过时间冒泡,父元素拿到子元素的数据
      {
        bubbles: true, cancelable: true,
        detail: {text: 333}
      });
    document.querySelector('.input').addEventListener('change', function() {
      console.log(this.dispatchEvent(event));  // false
    })
    document.querySelector('form').addEventListener('aaa', e => {
      e.preventDefault();
    });

结论: cancelable: true的时候event.preventDefault()才能使用

dispatch

上面的事件合成是为了派发任务的原生

功能,点击按钮调用原生自带的 mouseenter 事件随机修改颜色
由于我喜欢用angular这里就用angular 代码了

<div class="aaa"></div>
<button (click)="clickMount()">Click</button>

export class HelloComponent implements OnInit, AfterViewInit {

  randomHexColorCode() {
    let n = (Math.random() * 0xfffff * 1000000).toString(16);
    return '#' + n.slice(0, 6);
  };

  ngAfterViewInit() {
    let that = this;
    d3.select('.aaa').on('mouseenter', function(e) {
      (this as HTMLElement).style.background = that.randomHexColorCode();
    });
  }

  clickMount() {
    d3.select('.aaa').dispatch('mouseenter');
  }
}

我们从源码的基础上去简化demo

<div className="bbb">
                <div className="aaa">
                </div>
 </div>

 	    let aaa=d3.select('.aaa');
        aaa.on('click',function(e){
            aaa.dispatch('clicks',{
                bubbles:true,
                cancelable:true,
                detail:{
                    test:12
                }
            })
        })
	   // 通过冒泡,传给父元素
        d3.select('.bbb').on('clicks', function (e) {
            e.preventDefault();
            console.log(e.detail);
        });

dispatch 第二个参数可以是函数的形式
aaa.dispatch('clicks',function(){
                return {
                    bubbles:true,
                    detail:{
                        text:'xxxx'
                    }
                }
            })
posted @ 2020-09-24 16:01  猫神甜辣酱  阅读(464)  评论(0编辑  收藏  举报