散點圖 scatter chart
基本散點圖
// html
<div class="basicScatter"></div>
// JS-建立 svg
const basicScatter = async()=>{
const svgWidth = parseInt(d3.select('.basicScatter').style('width')),
svgHeight = 500
margin = 50
const svg = d3.select('.basicScatter')
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
// 取資料
const cors = "https://cors-anywhere.herokuapp.com/";
const url = "https://raw.githubusercontent.com/holtzy/data_to_viz/
master/Example_dataset/2_TwoNum.csv"
const data = await d3.csv(`${cors}${url}`);
// 建立X軸線
const xScale = d3.scaleLinear()
.domain([0,4000])
.range([0, (svgWidth - margin*2)])
const xAxisGenerator = d3.axisBottom(xScale)
svg.append('g')
.attr('transform', `translate(${margin}, ${svgHeight - margin/2})`)
.call(xAxisGenerator)
// 建立Y軸線
console.log(data)
const yScale = d3
.scaleLinear()
.domain([0,500000])
.range([(svgHeight - margin), 0])
const yAxisGenerator = d3.axisLeft(yScale).tickFormat(d=>'$'+d)
svg.append('g')
.attr('transform', `translate(${margin}, ${margin/2})`)
.call(yAxisGenerator)
// 加上點點
svg.append('g')
.selectAll('dot')
.data(data)
.join('circle')
.attr('cx', d => xScale(d.GrLivArea))
.attr('cy', d => yScale(d.SalePrice))
.attr('r', 1.5)
.style('fill', d => d.SalePrice > 129000? 'pink':'#69b3a2')
}
互動散點圖-hover
// html
<div class="hoverScatter"></div>
// js-建立svg
const width = parseInt(d3.select(".hoverScatter").style("width")),
height = 500,
margin = {
top: 40,
right: 20,
bottom: 20,
left: 40
},
radius = 5;
const svg = d3.select(".hoverScatter")
.append("svg")
.attr('width', width)
.attr('height', height);
const dataset = [
{ x: 100,y: 110 },{ x: 83,y: 43 },{ x: 92,y: 28 },
{ x: 49,y: 74 },{ x: 51,y: 10 },{ x: 25,y: 98 },
{ x: 77,y: 30},{ x: 20,y: 83 },{ x: 11,y: 63 },
{ x: 4,y: 55 },{ x: 0,y: 0 },{ x: 85,y: 100 },
{ x: 60,y: 40 },{ x: 70,y: 80 },{ x: 10,y: 20 },
{ x: 40,y: 50 },{ x: 25,y: 31 }
];
// 建立X比例尺與軸線
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d.x + 10)])
.range([margin.left, width - margin.right]);
const xAxisGenerator = d3
.axisBottom()
.scale(xScale)
const xAxis = svg.append('g')
.attr('class', 'xAxis')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(xAxisGenerator)
// 建立Y比例尺與軸線
const yScale = d3
.scaleLinear()
.domain([d3.max(dataset, (d)=> d.y + 10), 0])
.range([margin.top, height - margin.bottom])
const yAxisGenerator = d3
.axisLeft()
.scale(yScale)
const yAxis = svg
.append('g')
.attr('class', 'yAxis')
.attr('transform', `translate(${margin.left}, 0)`)
.call(yAxisGenerator)
// 綁定資料並建立圓點
svg.selectAll("circle")
.data(dataset)
.join("circle")
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.attr('r', 5)
.attr('fill', '#000')
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);
// mouseover 時點點變色+tooltip
function handleMouseOver(d, i) {
// 選定this的元素,改變hover過去的顏色跟形狀
d3.select(this)
.attr('fill', 'orange')
.attr('r', radius * 2)
.style('cursor', 'pointer')
// 加上tooltips
let pt = d3.pointer(event)
svg.append("text")
.attr('class', 'hoverTextInfo')
.attr('x', pt[0] + 10)
.attr('y', pt[1] - 10)
.style('fill', 'red')
.text([`x:${event.target.__data__.x},
y:${event.target.__data__.y}`])
}
// mouseleave 時變回原樣
function handleMouseOut(d, i) {
d3.selectAll('.hoverTextInfo').remove()
d3.select(this)
.attr('fill', 'black')
.attr('r', radius)
}
互動散點圖-畫布上新增點點
// html
<div class="addPointScatter"></div>
// 建立svg
const width = parseInt(d3.select(".addPointScatter").style("width")),
height = 500,
margin = {
top: 40,
right: 20,
bottom: 20,
left: 40
},
radius = 5;
const svg = d3.select(".addPointScatter")
.append("svg")
.attr('width', width)
.attr('height', height)
.style('cursor', 'pointer');
const dataset = [
{ x: 100,y: 110 },{ x: 83,y: 43 },{ x: 92,y: 28 },
{ x: 49,y: 74 },{ x: 51,y: 10 },{ x: 25,y: 98 },
{ x: 77,y: 30},{ x: 20,y: 83 },{ x: 11,y: 63 },
{ x: 4,y: 55 },{ x: 0,y: 0 },{ x: 85,y: 100 },
{ x: 60,y: 40 },{ x: 70,y: 80 },{ x: 10,y: 20 },
{ x: 40,y: 50 },{ x: 25,y: 31 }
];
// 建立X比例尺與軸線
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d.x + 10)])
.range([margin.left, width - margin.right]);
const xAxisGenerator = d3
.axisBottom()
.scale(xScale)
const xAxis = svg.append('g')
.attr('class', 'xAxis')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(xAxisGenerator)
// 建立Y比例尺與軸線
const yScale = d3
.scaleLinear()
.domain([d3.max(dataset, (d)=> d.y + 10), 0])
.range([margin.top, height - margin.bottom])
const yAxisGenerator = d3
.axisLeft()
.scale(yScale)
const yAxis = svg
.append('g')
.attr('class', 'yAxis')
.attr('transform', `translate(${margin.left}, 0)`)
.call(yAxisGenerator)
// 綁定資料並建立圓點
svg.selectAll("circle")
.data(dataset)
.join("circle")
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.attr('r', 5)
.attr('fill', '#000')
// 滑鼠click的時候增加一個點
svg.on("click", (e) => {
const coords = d3.pointer(e);
// 把XY座標軸轉換成資料
const newData = {
x: Math.round(xScale.invert(coords[0])),
y: Math.round(yScale.invert(coords[1]))
};
// 將增加的資料座標推入原本的data
dataset.push(newData);
// 將新的資料綁定上circle
svg.selectAll("circle")
.data(dataset)
.join("circle")
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.attr('r', 5)
.attr('fill', '#000')
})