2022年1-8月各觀測站降雨量
// html
<div class="multiLineChart"></div>
// js
const multiLineChart = async () => {
// SVG
const width = parseInt(
d3.select('.multiLineChart').style('width')
),
height = 500,
margin = 60
const svg = d3.select('.multiLineChart')
.append('svg')
.attr('width', width)
.attr('height', height);
//取資料集
const res = await d3.json('https://data.coa.gov.tw/Service/
OpenData/TransService.aspx?UnitId=5n9c3AlEJ2DH')
// 只取2022年的資料
const data = res.filter(d=>d.observeDate.substr(0,4)==='2022')
const xData = data.map((i) => i.observeDate.substr(5,6));
const yData = data.map((i)=>{
let rainfall = parseFloat(i.rainfall)
return rainfall = rainfall || 0
})
// 設定要給 X 軸用的 scale 跟 axis
const xScale = d3.scaleLinear()
.domain(d3.extent(xData))
.range([margin, width - margin])
.nice()
// X軸的刻度
const xAxisGenerator = d3.axisBottom(xScale)
.ticks(8)
.tickFormat(d => d + '月')
// 呼叫繪製x軸、調整x軸位置
const xAxis = svg
.append("g")
.call(xAxisGenerator)
.attr("transform", `translate(0,${height - margin})`)
// 設定要給 Y 軸用的 scale 跟 axis
const yScale = d3.scaleLinear()
.domain(d3.extent(yData))
.range([height - margin, margin])
.nice()
const yAxisGenerator = d3.axisLeft(yScale)
// 呼叫繪製y軸、調整y軸位置
const yAxis = svg
.append("g")
.call(yAxisGenerator)
.attr("transform", `translate(${margin},0)`)
// 把資料按照 name 分組
const sumName = d3.group(data, d => d.observatory);
const color = d3.scaleOrdinal()
.domain(data.map(d=>d.item))
.range(d3.schemeCategory10);
// 建立tooltip
const nameTag = d3.select('.multiLineChart')
.style('position', 'relative')
.append('div')
.attr('class', 'nameTag')
.style('position', 'absolute')
.style("display",'none')
.style("background-color", "black")
.style("border-radius", "5px")
.style("padding", "10px")
.style("color", "white")
// 開始建立折線圖
svg.selectAll('.line')
.data(sumName)
.join('path')
.attr("fill", "none")
.attr("stroke", d => color(d))
.attr("stroke-width", 1.5)
.attr("d", d => {
return d3.line()
.x((d) => xScale(d.observeDate.substr(5,6)))
.y((d) => {
let rainfall = parseFloat(d.rainfall)
rainfall = rainfall || 0
return yScale(rainfall)
})
(d[1])
})
.style('cursor', 'pointer')
.on('mouseover', handleMouseover)
.on('mouseleave', handleMouseleave)
// 滑鼠事件
function handleMouseover(d){
const pt = d3.pointer(event, this)
d3.select(this).style('stroke-width', '5')
nameTag.style("display",'block')
.html(d.target.__data__[0])
.style('left', (pt[0]+10) + 'px')
.style('top', (pt[1]+ 10) + 'px')
}
function handleMouseleave(d){
d3.select(this).style('stroke-width', '1')
nameTag.style("display",'none')
}
};
multiLineChart();
2010-2022年,每月各觀測站降雨量
const brushMultiLineChart = async () => {
// SVG
const width = parseInt(d3.select('.brushMultiLineChart').style('width')),
height = 500,
margin = 60
const svg = d3.select('.brushMultiLineChart')
.append('svg')
.attr('width', width)
.attr('height', height);
const data = await d3.json('https://data.coa.gov.tw/Service/OpenData/TransService.aspx?UnitId=5n9c3AlEJ2DH');
// map 資料集
const xData = data.map((i) => i.observeDate);
const yData = data.map((i)=>{
let rainfall = parseFloat(i.rainfall)
return rainfall = rainfall || 0 // 沒有的資料換成0
})
// 設定 format 時間的方法
const parseTime = (d)=>d3.timeParse("%Y-%m")(d)
// 設定要給 X 軸用的 scale 跟 axis
const xScale = d3.scaleTime()
.domain(d3.extent(data, d => parseTime(d.observeDate)))
.range([margin, width - margin])
// rwd X軸
const xAxisGenerator = d3.axisBottom(xScale)
const xAxis = svg
.append("g")
.call(xAxisGenerator)
.style('font-size', '16px')
.attr("transform", `translate(0,${height - margin})`)
// 設定要給 Y 軸用的 scale 跟 axis
const yScale = d3.scaleLinear()
.domain(d3.extent(yData))
.range([height - margin, margin])
.nice()
const yAxisGenerator = d3.axisLeft(yScale)
// 呼叫繪製y軸、調整y軸位置
const yAxis = svg
.append("g")
.call(yAxisGenerator)
.style('font-size', '16px')
.attr("transform", `translate(${margin},0)`)
// 把資料按照 name 分組
const sumName = d3.group(data, d => d.observatory);
const color = d3.scaleOrdinal()
.domain(data.map(d=>d.item))
.range(d3.schemeCategory10);
// 建立一個畫布範圍,超過此畫布的畫面都不會被渲染,這樣才能控制縮放的大小
const clip = svg.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", margin)
.attr("y", margin)
.attr("width", width-margin*2)
.attr("height", height-margin*2)
// 加上brush
const brush = d3
.brushX()
.extent([[margin, margin], [width-margin, height-margin]])
.on("end", updateChart)
// 開始建立折線圖
const line = svg.append('g')
.attr("clip-path", "url(#clip)");
line.selectAll('.line')
.data(sumName)
.join('path')
.attr('class', 'line')
.attr("fill", "none")
.attr("stroke", d => color(d))
.attr("stroke-width", 1.5)
.attr("d", d => {
return d3.line()
.x((d) => xScale(parseTime(d.observeDate)))
.y((d) => {
let rainfall = parseFloat(d.rainfall)
rainfall = rainfall || 0
return yScale(rainfall)
})
(d[1])
})
// add brush
line.append('g')
.attr('class', 'brush')
.call(brush)
// brush end function
function updateChart(event, d){
// brush 的範圍,會返還一個[x0, x1]的陣列
extent = event.selection
if(extent){
// xScale.invert 是把返還的x0跟x1變成xscale接受的數值
xScale.domain([xScale.invert(extent[0]), xScale.invert(extent[1]) ])
// 移除brush的灰色區塊
line.select(".brush").call(brush.move, null)
}
// 按照更新的domain範圍值重新渲染圖表
xAxis.transition().duration(1000).call(d3.axisBottom(xScale))
line
.selectAll('.line')
.transition()
.duration(1000)
.attr("d", d => {
return d3.line()
.x((d) => xScale(parseTime(d.observeDate)))
.y((d) => {
let rainfall = parseFloat(d.rainfall)
rainfall = rainfall || 0
return yScale(rainfall)
})
(d[1]) // 只取資料的部分帶入
})
}
//雙擊 svg 縮回原本大小
svg.on('dblclick', function(){
// 回到原本的大小
xScale.domain(d3.extent(data, d => parseTime(d.observeDate)))
// 重新呼叫渲染軸線跟折線
xAxis.transition().duration(1000).call(d3.axisBottom(xScale))
line
.selectAll('.line')
.transition()
.duration(1000)
.attr("d", d => {
return d3.line()
.x((d) => xScale(parseTime(d.observeDate)))
.y((d) => {
let rainfall = parseFloat(d.rainfall)
rainfall = rainfall || 0
return yScale(rainfall)
})
(d[1])
})
})
};
brushMultiLineChart();