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'
}
}
})
决定自己的高度的是你的态度,而不是你的才能
记得我们是终身初学者和学习者
总有一天我也能成为大佬