本篇大纲:开放资料下载、本次范例的画面与互动效果、复数长条图的绘制关键、绘制复数长条图
今天的一天一图表,我们要来画图表长条图第二部曲 — 复数长条图!
复数长条图主要是用来比较 某项目在不同时间的变化
,或是 同一时间不同项目的差异
。由於有多项资料要比较,因此除了XY轴之外,最好还要有资料的标签,才能让看图表的人更了解这张图想比较什麽东西。
为了更贴近真实,我们这次一样使用政府提供的公开资料:台南市劳动人口-依年龄别区分 来当作此次图表的资料!这次的资料一样是csv档案,打开档案的结构如下
由於是 csv 档,所以我们不用进行其他处理,直接拿来用就可以了 (耶比~)
这次我们要做的范例画面与互动效果有:
绘制复数长条图的关键是 多加一条X轴的比例尺
。一般的长条图只有一条X轴跟一个X轴的比例尺,所有的资料会根据这条X轴去排列;但复数长条图的关键是另外整理一个新的资料阵列放要比较的资料,接着用这个新的资料阵列再多设定一个新的X轴比例尺。这样讲可能很模糊,我们直接来看程序码比较清楚。
首先,我们先将资料取回来,并且建立svg画面
// css
.chart{
width: 100%;
min-width: 300px;
margin: auto;
}
// html
<div class="chart"></div>
let data = []
async function getData() {
// 取资料
dataGet = await d3.csv('./data/tainan_labor_force_population.csv')
data = dataGet
console.log(data)
drawBarChart()
};
getData()
// RWD
function drawBarChart(){
// 删除原本的svg.charts,重新渲染改变宽度的svg
d3.select('.chart svg').remove();
// RWD 的svg 宽高
const rwdSvgWidth = parseInt(d3.select('.chart').style('width')),
rwdSvgHeight = rwdSvgWidth,
margin = 20,
marginBottom = 100
const svg = d3.select('.chart')
.append('svg')
.attr('width', rwdSvgWidth)
.attr('height', rwdSvgHeight);
// 接下来的程序码放这边...
// 接下来的程序码放这边...
// 接下来的程序码放这边...
}
d3.select(window).on('resize', drawBarChart);
接着,我们先把基本的XY轴、以及XY轴的比例尺建立出来
// map 资料集
const xData = data.map((i) => i['年度']);
// 设定要给 X 轴用的 scale 跟 axis
const xScale = d3.scaleBand()
.domain(xData)
.range([margin*2, rwdSvgWidth - margin]) // 宽度
.padding(0.2)
const xAxis = d3.axisBottom(xScale)
// 呼叫绘制x轴、调整x轴位置
const xAxisGroup = svg.append("g")
.call(xAxis)
.attr("transform", `translate(0,${rwdSvgHeight - marginBottom})`)
// 设定要给 Y 轴用的 scale 跟 axis
const yScale = d3.scaleLinear()
.domain([0, 600])
.range([rwdSvgHeight - marginBottom, margin]) // 数值要颠倒,才会从低往高排
.nice() // 补上终点值
const yAxis = d3.axisLeft(yScale)
.ticks(5)
.tickSize(3)
// 呼叫绘制y轴、调整y轴位置
const yAxisGroup = svg.append("g")
.call(yAxis)
.attr("transform", `translate(${margin*2},0)`)
再来!最重要的部分来了!我们把要比较的资料抓出来建立新的阵列,并且建立新的X轴比例尺
const subgroups = Object.keys(data[0]).slice(1)
// 第二条X轴的比例尺,用来设定多条bar的位置
const xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, xScale.bandwidth()])
.padding([0.05])
我们可以把新的资料阵列 console 出来看看
这个就是我们要分组的阵列啦~~接着,我们来设定一下每个组别的颜色
// 设定不同 subgorup bar的颜色
const color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#ff2d85','#4a4ae0','#4daf4a', '#f29909'])
然後就是建立长条图啦!
// 开始建立长条图
const bar = svg.append('g')
.selectAll('g')
.data(data)
.join('g')
.attr('transform', d => `translate(${xScale(d['年度'])}, 0)`)
.selectAll('rect')
.data(d => {
return subgroups.map(key=>{
return {key:key, value:d[key]};})
})
.join('rect')
.attr('x', d => xSubgroup(d.key))
.attr("y", d => yScale(d.value))
.attr("width", xSubgroup.bandwidth())
.attr("height", d =>{
return (rwdSvgHeight-marginBottom) - yScale(d.value)})
.attr("fill", d => color(d.key))
.style('cursor', 'pointer')
这样就建立好长条图罗~刚刚有说过,复数长条图的标签很重要,因此我们还要加上最下方的标签
// 加上下方分类标签
const tagsWrap = svg.append('g')
.selectAll('g')
.attr('class', 'tags')
.data(subgroups)
.enter()
.append('g')
.attr('transform', "translate(-70,0)")
tagsWrap.append('rect')
.attr('x', (d,i)=> (i+1)*marginBottom*1.3)
.attr('y', rwdSvgHeight-marginBottom/2)
.attr('width', 20)
.attr('height', 20)
.attr('fill', d => color(d))
tagsWrap.append('text')
.attr('x', (d,i)=> (i+1)*marginBottom*1.3)
.attr('y', rwdSvgHeight-10)
.style('fill', '#000')
.style('font-size', '12px')
.style('font-weight', 'bold')
.style("text-anchor", 'middle')
.text(d=>d)
这样基本的图表就好罗
再来我们来绑定滑鼠的事件,同时使用 d3.pointer( )
的方法来建立水平轴线跟资料的标示
bar.on("mouseover", handleMouseOver)
.on("mouseleave", handleMouseLeave)
function handleMouseOver(d, i){
// console.log(d.target.__data__)
const pt = d3.pointer(event, svg.node())
// 加上文字标签
svg.append('text')
.attr('class', 'infoText')
.attr('y', yScale(d.target.__data__['value']))
.attr("x", margin*2)
.style('fill', '#000')
.style('font-size', '18px')
.style('font-weight', 'bold')
.style("text-anchor", 'middle')
.text(d.target.__data__['value'] + '千人')
// 加上轴线
svg.append('line')
.attr('class', 'dashed-Y')
.attr('x1', margin*2)
.attr('y1', yScale(d.target.__data__['value']))
.attr('x2', pt[0])
.attr('y2', yScale(d.target.__data__['value']))
.style('stroke', 'black')
.style('stroke-dasharray', '3' )
}
function handleMouseLeave(){
// 移除文字、轴线标签
svg.select('.infoText').remove()
svg.select('.dashed-Y').remove()
}
大功告成~
最後附上本章的程序码:想看完整程序码的请上 Github,想直接操作图表的则去 Github Page 吧!请自行取用~
<<: Day_23: 让 Vite 来开启你的Vue 之 <script setup>
Debian/Ubuntu Linux都使用apt,升级时都是: apt-get update ap...
FB登入 第一步:到FB官网并创建帐号 https://developers.facebook.co...
五年前,我从资管系毕业。当时的我告诉自己,未来不会踏上跟写程序相关的工作。往餐饮业、服务业发展什麽...
Install OpenProject with DEB/RPM packages Import t...
接着来讲讲资料库连线的部分.... Mysql 类别Class public class Categ...