数据可视化【七】 更新模式
Enter
以下面這個簡單的代碼進(jìn)行分析
const svg = d3.select('svg'); // svg.style('background-color', 'red'); testconst height = +svg.attr('height'); //+ equals paresFloat() const width = +svg.attr('width');const makeFruit = type =>( {type} ); //這種寫法好像能夠直接得到一個屬性名為'type',值為變量type值的對象const fruits = d3.range(5).map(() => makeFruit('apple') );// console.log(fruits);svg.selectAll('circle').data(fruits).enter().append('circle').attr('cx', (d,i) => i*100+10).attr('cy', height/2).attr('r', 10).attr('fill', 'red');在選擇selectAll()以后相當(dāng)于選擇了上圖的Elements部分,data(fruits)相當(dāng)于上圖的Data部分,在一起以后就得到了上面的Data Join。然后我們再根據(jù)自己的需求選擇想要的部分進(jìn)行處理。
- Enter 是不在Elements中的Data中的元素的集合
- Update是既在Elments集合中又在Data集合中的元素集合
- Exit是僅在Elements集合中的元素集合
我們得到了集合以后就能使用append函數(shù)和attr函數(shù)進(jìn)行設(shè)置。
Exit
經(jīng)常我們綁定的數(shù)據(jù)發(fā)生變化以后我們想要對已經(jīng)不在數(shù)據(jù)集中的元素進(jìn)行處理。這個時候只需要再綁定一次數(shù)據(jù),然后對其中的exit()部分進(jìn)行設(shè)置即可。例如下面的例子:
const svg = d3.select('svg'); // svg.style('background-color', 'red'); testconst height = +svg.attr('height'); //+ equals paresFloat() const width = +svg.attr('width');const render = (selection, { fruits }) => {const circles = selection.selectAll('circle').data(fruits);circles.enter().append('circle').attr('cx', (d,i) => i*100+10).attr('cy', height/2).attr('r', 10).attr('fill', 'red');circles.exit().remove();// .attr('fill', 'black'); }const makeFruit = type =>( {type} ); //這種寫法好像能夠直接得到一個屬性名為'type',值為變量type值的對象const fruits = d3.range(5).map(() => makeFruit('apple') );render(svg, { fruits } );// console.log(fruits);setTimeout(()=>{fruits.pop();render(svg, { fruits } ); }, 1000);實(shí)現(xiàn)的效果就是原本屏幕上有五個點(diǎn),經(jīng)過一秒鐘以后最后一個點(diǎn)消失了。
Update
如果我們對數(shù)據(jù)中的一部分做了一些改動,又希望能夠在圖形中顯示出來,就可以在update中進(jìn)行修改。需要注意的是:在我們綁定數(shù)據(jù)以后默認(rèn)就是update集合。例如:
const svg = d3.select('svg'); // svg.style('background-color', 'red'); testconst height = +svg.attr('height'); //+ equals paresFloat() const width = +svg.attr('width');const colorScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range(['red', 'yellow']);const radiusScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range([50, 20]);const render = (selection, { fruits }) => {const circles = selection.selectAll('circle').data(fruits);circles.enter().append('circle').attr('cx', (d,i) => i*150+50).attr('cy', height/2).attr('r', d => radiusScale(d.type)).attr('fill', d => colorScale(d.type) );circles.exit().remove();// .attr('fill', 'black');circles.attr('cx', (d,i) => i*150+50).attr('cy', height/2).attr('r', d => radiusScale(d.type)).attr('fill', d => colorScale(d.type) ); }const makeFruit = type =>( {type} ); //這種寫法好像能夠直接得到一個屬性名為'type',值為變量type值的對象const fruits = d3.range(5).map(() => makeFruit('apple') );render(svg, { fruits } );// console.log(fruits);setTimeout(()=>{fruits.pop();render(svg, { fruits } ); }, 1000);setTimeout(()=>{fruits[2].type = 'lemon';render(svg, { fruits } ); }, 2000);最終的效果:
Merge
有時候我們需要對多個部分進(jìn)行相同的操作,當(dāng)然可以對每個部分都寫相同的代碼,只是那樣顯得有些繁瑣。我們可以使用merge()函數(shù)將多個selection進(jìn)行合并,傳入的參數(shù)是需要合并的部分。例如在上面的代碼中我們需要對enter和update都設(shè)置圓的半徑的顏色,我們就可以在enter后面合并update(update即就是綁定數(shù)據(jù)后的變量)。進(jìn)行修改以后就可以得到下面更加簡潔的代碼。
fruitBowl.js
const colorScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range(['red', 'yellow']);const radiusScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range([50, 20]);export const fruitBowl = (selection, props) => {const {fruits, height} = props;const circles = selection.selectAll('circle').data(fruits);circles.enter().append('circle').attr('cx', (d,i) => i*150+50).attr('cy', height/2).merge(circles) //both enter section and update section.attr('r', d => radiusScale(d.type)).attr('fill', d => colorScale(d.type));circles.exit().remove();// .attr('fill', 'black');}index.js
import { fruitBowl } from './fruitBowl.js';const svg = d3.select('svg'); // svg.style('background-color', 'red'); testconst makeFruit = type =>( {type} ); //這種寫法好像能夠直接得到一個屬性名為'type',值為變量type值的對象let fruits = d3.range(5).map(() => makeFruit('apple') );const render = () => {fruitBowl(svg, {fruits, height : +svg.attr('height')}) };render();// console.log(fruits);setTimeout(()=>{fruits.pop();render(); }, 1000);setTimeout(()=>{fruits[2].type = 'lemon';render(); }, 2000);setTimeout(()=>{fruits = fruits.filter((d, i) => i!=1);render(); }, 3000);Animated Transitions(動畫過渡)
為了添加動畫效果我們可以使用transition()函數(shù)設(shè)置屬性,這樣屬性的變化就是漸進(jìn)的,我們可以同時設(shè)置duration(x)來設(shè)置變化需要x ms。例如:
const colorScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range(['red', 'yellow']);const radiusScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range([50, 20]);export const fruitBowl = (selection, props) => {const {fruits, height} = props;const circles = selection.selectAll('circle').data(fruits);circles.enter().append('circle').attr('cx', (d,i) => i*150+50).attr('cy', height/2).attr('r', 0).merge(circles) //both enter section and update section.attr('fill', d => colorScale(d.type)).transition().duration(1000).attr('r', d => radiusScale(d.type));circles.exit().transition().duration(1000).attr('r', 0).remove();// .attr('fill', 'black'); }Object constancy
在觀察上面代碼第三秒效果的時候我們會發(fā)現(xiàn),我們想要刪除第二個圓點(diǎn),實(shí)際上的效果卻是第二個圓點(diǎn)從紅色變成了黃色,然后變小了,第三個圓點(diǎn)變成了紅色然后變大了,第四個圓點(diǎn)消失了。這顯然不是我們想要的結(jié)果,我們想要的應(yīng)該是第二個圓點(diǎn)消失了,然后后面的圓點(diǎn)向前移動。
產(chǎn)生這種現(xiàn)象的原因在于d3中對數(shù)據(jù)唯一性的處理。默認(rèn)情況下d3是通過下標(biāo)唯一標(biāo)識數(shù)據(jù)的。因此產(chǎn)生上面現(xiàn)象的原因是我們修改數(shù)組以后,d3按照新數(shù)組的情況進(jìn)行了處理:第四個數(shù)據(jù)已經(jīng)沒有了,所以在exit里面進(jìn)行刪除,前面的屬性發(fā)生了變化然后直接進(jìn)行處理。
我們需要做的是幫助d3標(biāo)識不同的數(shù)據(jù),方法就是在綁定數(shù)據(jù)的時候使用data函數(shù)的數(shù)組后面再傳入一個函數(shù),用于標(biāo)識數(shù)據(jù)。
在這個實(shí)例中我們給原本的對象數(shù)組的每個元素添加新的屬性id,用來進(jìn)行區(qū)分,然后傳入的函數(shù)就是返回每個元素的id屬性。這樣d3就會將每個元素的id作為區(qū)分的標(biāo)準(zhǔn)。
代碼如下:
fruitBowl.js
index.js
const makeFruit = (type, i) =>( {type,id : i } ); //這種寫法好像能夠直接得到一個屬性名為'type',值為變量type值的對象let fruits = d3.range(5).map((d,i) => makeFruit('apple', i) );進(jìn)行上面的修改以后會發(fā)現(xiàn)效果的確是第二個圓點(diǎn)消失了,但是后面的圓點(diǎn)卻沒有向前移動。為了解決這個問題我們應(yīng)該將對位置的設(shè)置放在merge后的transition函數(shù)中,這樣每次綁定數(shù)據(jù)都會對enter和update中的元素設(shè)置位置。
代碼如下:
fruitBowl.js
vizhub 代碼:https://vizhub.com/Edward-Elric233/1a1bd422d4b349aba1735868ff453b5f
Nested(嵌套的) elements
只有圓圈可能難以理解表達(dá)的是什么意思,我們就需要加上文字text:
fruitBowl.js
styles.css
text {font-size: 1.2em;text-anchor : middle; }注意text元素設(shè)置文字內(nèi)容的函數(shù)是.text()
雖然上面的做法可行,但是當(dāng)有很多元素組合在一起的時候就有點(diǎn)力不從心。因此我們更常用的做法是將一組元素放在g中,然后再對g的位置屬性等進(jìn)行設(shè)置。
大概的做法就是先綁定數(shù)據(jù)以后給每個元素append一個g,然后再對g進(jìn)行操作。
const colorScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range(['red', 'yellow']);const radiusScale = d3.scaleOrdinal().domain(['apple', 'lemon']).range([50, 20]);const xPosition = (d,i) => i*150+50;export const fruitBowl = (selection, props) => {const {fruits, height} = props;const groups = selection.selectAll('g').data(fruits, d => d.id);const groupEnter = groups.enter().append('g').attr('transform', (d,i) => `translate(${i*150+50}, ${height/2})`);;groupEnter.merge(groups) //both enter section and update section.transition().duration(1000).attr('transform', (d,i) => `translate(${i*150+50}, ${height/2})`);groups.exit().select('circle').transition().duration(1000).style('fill', 'white').attr('r',0)setTimeout(() => groups.exit().select('circle').remove(),1000);// .attr('fill', 'black');groups.exit().select('text').transition().duration(1000).attr('fill', 'white');setTimeout(() => groups.exit().select('text').remove(), 1000);groupEnter.append('circle').attr('r', 0).merge(groups.select('circle')) //both enter section and update section.attr('fill', d => colorScale(d.type)).transition().duration(1000).attr('r', d => radiusScale(d.type));const text = groups.select('text');groupEnter.append('text').attr('y', 100).merge(text) //both enter section and update section.text(d => d.type)}vizhub代碼:https://vizhub.com/Edward-Elric233/f33fac2e32134707896521d420d5e255
Singular elements
每次我用對selection操作的時候都會對其中的每個元素進(jìn)行操作。特殊的,我們?nèi)绻胍砑右粋€唯一的元素的話可以綁定一個個數(shù)為一的數(shù)組,內(nèi)容可以隨意選定,一般情況下選擇[null]數(shù)組就比較好。然后在這個selection上進(jìn)行操作。
例如,我們想要給我們的動畫添加一個矩形的背景,就可以使用上面的方法添加一個。
const bowl = selection.selectAll('rect').data([null]).enter().append('rect').attr('y', 110).attr('width', 700).attr('height', 300).attr('rx', 300/2) //圓角矩形.attr('fill', '#ebfbfc');最后的效果圖(實(shí)際上應(yīng)該是動圖,這里沒有制作gif圖,可以在網(wǎng)站進(jìn)行查看)
vizhub代碼:https://vizhub.com/Edward-Elric233/bc54edb3b722482590f498f3a1047a62
總結(jié)
以上是生活随笔為你收集整理的数据可视化【七】 更新模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据可视化【六】Line Chart
- 下一篇: 数据可视化【八】根据数据类型选择可视化方