案例
Area 圖形可以填充整塊的圖形腰埂,下面是同樣的數(shù)據(jù)(2017年2季度到2018年1季度手機(jī)品牌市場(chǎng)占有情況)分別在 area 和在 stack bar 圖呈現(xiàn)的結(jié)果渊抽。
area-market-share.png
stack-bar-market-share.png
解析
d3.area 可以生成一個(gè)圖形生成器悠反,輸入一個(gè)數(shù)組數(shù)據(jù)锹安,將返回值傳入 path 元素的 d 屬性就可以渲染出對(duì)應(yīng)的 area 圖形党瓮。
首先嘗試一個(gè)比較簡(jiǎn)單的 case玩裙,只生成單一 area 圖形的情況姜挺。
可以構(gòu)造一個(gè) area 生成器:
const areaGenerator = d3.area().x([x]).y0([y]).y1([y])
y0 函數(shù)可以確定 x 點(diǎn)對(duì)應(yīng)的 y 軸投影下限(base)齿税,y1 確定上限(top) 。單一的 area 與折線圖非常類似炊豪,相當(dāng)于現(xiàn)充了 y 點(diǎn)到 x 坐標(biāo)軸的的折線圖凌箕。
single-area.png
要生成 stack 形式的 area,可以拆分為兩個(gè)步驟:
- 先將數(shù)據(jù)轉(zhuǎn)化為 series 結(jié)構(gòu)词渤,每個(gè)數(shù)據(jù)點(diǎn)用 [baseline, topline] 描述上下限牵舱。
- 將 series 數(shù)據(jù)傳遞 area 生成器,生成對(duì)應(yīng)的 path 圖形缺虐。
利用 d3.stack 生成 series 數(shù)據(jù):
const stack = d3.stack().keys([keys]);
const series = stack(data);
生成 path 圖形的步驟如下:
在 x 軸使用 scalePoint:
// scalePoint in x axis
const xAccessor = (d) => `${d.year}-${d.quarter}`;
const xScale = d3.scalePoint()
.domain(data.map(xAccessor))
.range([0, maxWidth]);
在 y 軸使用 scaleLinear:
// scaleLinear in y axis
const stackMin = serie => d3.min(serie, (d) => d[0]);
const stackMax = serie => d3.max(serie, (d) => d[1]);
const yScale = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.range([maxHeight, 0])
構(gòu)造 area 生成器:
// area generator
const area = d3.area()
.x((d) => xScale(xAccessor(d)))
.y0((d) => yScale(d[0]))
.y1((d) => yScale(d[1]))
為每個(gè) serie 添加 area 圖形:
// add path
svg.selectAll('path.area')
.data(series)
.enter()
.append('path')
.attr('class', 'area')
.attr('d', area)
.attr('fill', (d, i) => colorArray[i % colorArray.length])
實(shí)現(xiàn)
完整源碼如下:
<!DOCTYPE html>
<html>
<body>
<style>
svg {
border: 1px solid lightgrey;
}
</style>
<script src="http://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
const maxHeight = 400;
const maxWidth = 600;
const barWidth = 20;
const colorArray = ['#38CCCB', '#0074D9', '#2FCC40', '#FEDC00', '#FF4036', 'lightgrey'];
function singleArea() {
const data = [
{
date: '2019-01-01',
value: 1,
},
{
date: '2019-01-02',
value: 10,
},
{
date: '2019-01-03',
value: 35,
},
];
const xScale = d3.scaleTime()
.domain(d3.extent(data, (d) => new Date(d.date)))
.range([0, maxWidth]);
const yScale = d3.scaleLinear()
.domain(d3.extent(data, (d) => d.value))
.range([maxHeight, 0])
const area = d3.area()
.x((d) => xScale(new Date(d.date)))
.y1((d) => yScale(d.value))
.y0((d) => yScale(0))
const svg = d3.select('body')
.append('svg')
.attr('width', maxWidth)
.attr('height', maxHeight)
svg.append('g')
.selectAll('path.area')
.data([data])
.enter()
.append('path')
.attr('class', 'area')
.attr('d', area)
.attr('fill', colorArray[0])
}
function stackArea() {
const stackData = [
{
year: 2017,
quarter: 2,
samsung: 0.229,
apple: 0.118,
huawei: 0.110,
oppo: 0.08,
xiaomi: 0.062,
others: 0.401,
},
{
year: 2017,
quarter: 3,
samsung: 0.221,
apple: 0.124,
huawei: 0.104,
oppo: 0.081,
xiaomi: 0.075,
others: 0.396,
},
{
year: 2017,
quarter: 4,
samsung: 0.189,
apple: 0.196,
huawei: 0.107,
oppo: 0.069,
xiaomi: 0.071,
others: 0.368,
},
{
year: 2018,
quarter: 1,
samsung: 0.235,
apple: 0.157,
huawei: 0.118,
oppo: 0.074,
xiaomi: 0.084,
others: 0.332,
},
]
const stack = d3.stack()
.keys(['apple', 'samsung', 'huawei', 'oppo', 'xiaomi', 'others']);
const series = stack(stackData);
const stackMin = (serie) => d3.min(serie, (d) => d[0]);
const stackMax = (serie) => d3.max(serie, (d) => d[1]);
const yScale = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.range([maxHeight, 0])
const xAccessor = (d) => `${d.year}-${d.quarter}`;
const xScale = d3.scalePoint()
.domain(stackData.map(xAccessor))
.range([0, maxWidth])
const area = d3.area()
.x((d) => xScale(xAccessor(d.data)))
.y0((d) => yScale(d[0]))
.y1((d) => yScale(d[1]))
const svg = d3.select('body')
.append('svg')
.attr('width', maxWidth)
.attr('height', maxHeight)
svg.append('g')
.selectAll('path')
.data(series)
.enter()
.append('path')
.attr('d', area)
.attr('fill', (d, i) => colorArray[i % colorArray.length])
}
singleArea()
stackArea()
</script>
</body>
</html>