D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
leeziwong
Full window
Github gist
泰坦尼克号事故乘客遇难生还分类统计
你在这个数据可视化中注意到什么?
你对这个数据有什么问题吗?
你是否注意到数据关系?
你觉得这个数据可视化主要表达了什么?
这个图形中你有什么不明白的地方吗?
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>泰坦尼克号船难事故乘客人口统计</title> <style> body { width: 100%; max-width: 800px; margin: 20px auto; text-align: center; } h1 { text-align: center; font-size: 22px; margin: 40px auto 10px; } label { margin-right: 10px; } .options { margin: 30px 0; } .x.axis { font-size: 16px; } </style> </head> <body> <div class="options"> <label>分组选项:</label> <label><input type="radio" name="groupName" value="AllPassengers" checked />所有乘客(AllPassengers)</label> <label><input type="radio" name="groupName" value="Sex" />性别(Sex)</label> <label><input type="radio" name="groupName" value="Pclass" />船票等级(Pclass)</label> <label><input type="radio" name="groupName" value="HasFamily" />是否有家庭关系(HasFamily)</label> </div> <h3 class="caption">所有乘客(未分类)</h3> <script src="https://d3js.org/d3.v4.min.js"></script> <script> // 分组的常量 const GROUP_ALL = 'AllPassengers' const GROUP_SEX = 'Sex' const GROUP_PCLASS = 'Pclass' const GROUP_HASFAMILY = 'HasFamily' // 遇难生存变量 const SURVIVED_ALIVE = '生还' const SURVIVED_DEAD = '遇难' // 加载数据时转换数据 function dataConverter(d) { d.Age = parseInt(d.Age) d.Fare = parseFloat(d.Fare) d.Pclass = parseInt(d.Pclass) d.Survived = d.Survived === '0'? SURVIVED_DEAD : SURVIVED_ALIVE d.HasFamily = (parseInt(d.SibSp) + parseInt(d.Parch)) > 0 ? 'Yes' : 'No' return d } // 根据人口特征分类格式化加载后的数据供图表显示使用 // groupName 为分组字段 Sex, Pclass, HasFamily, AllPassengers function formatDataForSurvivedGroup(data, groupName) { let survivedByKeyGroup let formatedData = [] let groupKeys = [] let GROUP_ALL_KEY = 'All passengers' if (groupName === GROUP_ALL) { survivedByKeyGroup = d3.nest() .key(d => d.Survived) .rollup(values => values.length) .entries(data) // properties SURVIVED_ALIVE and SURVIVED_DEAD let survivedCount = {} survivedByKeyGroup.forEach(element => { survivedCount[element.key] = element.value survivedCount[GROUP_ALL] = GROUP_ALL_KEY }); formatedData.push(survivedCount) groupKeys.push(GROUP_ALL_KEY) } if (groupName === GROUP_SEX || groupName === GROUP_PCLASS || groupName === GROUP_HASFAMILY) { survivedByKeyGroup = d3.nest() .key(d => d[groupName]) .key(d => d.Survived) .rollup(values => values.length) .entries(data) // if you are confused by the entries data structure // console log survivedByKeyGroup or search it on google survivedByKeyGroup.forEach(keyGroup => { groupKeys.push(keyGroup['key']) let survivedCount = {} keyGroup['values'].forEach(survived => { survivedCount[groupName] = keyGroup['key'] survivedCount[survived['key']] = survived['value'] }); formatedData.push(survivedCount) }); } const groupKeyCompare = function(a, b) { const sortedGroupKeys = [GROUP_ALL_KEY, 'male', 'female', '3', '2', '1', 'No', 'Yes'] return sortedGroupKeys.indexOf(a) - sortedGroupKeys.indexOf(b) } groupKeys.sort(groupKeyCompare) return { formatedData, groupKeys } } // 图表区域尺寸设置 const outerWidth = 600 const outerHeight = 400 const margin = { top: 20, right: 20, bottom: 30, left: 40 } const width = outerWidth - margin.left - margin.right const height = outerHeight - margin.top - margin.bottom const svg = d3.select('body') .append('svg') .attr('width', outerWidth) .attr('height', outerHeight) // 图表区域 const chart = svg.append('g') .attr('transform', `translate(${margin.left}, ${margin.right})`) // 动态缩放 const xScale = d3.scaleBand() .rangeRound([0, width]) .paddingInner(0.1) const yScale = d3.scaleLinear() .rangeRound([height, 0]) // 颜色设置对应到第三个变量的值 const zColor = d3.scaleOrdinal() .range(['#8a89a6', '#d0743c']) // 图表里出去 X 轴, y 轴的第三个变量 - 遇难生还情况 [SURVIVED_ALIVE, SURVIVED_DEAD] const keys = [SURVIVED_DEAD, SURVIVED_ALIVE] zColor.domain(keys) // 加载数据绘图 d3.csv('titanic_data.csv', dataConverter, data => { // 默认显示乘客未分类的遇难生还情况 let groupData = formatDataForSurvivedGroup(data, GROUP_ALL) xScale.domain(groupData.groupKeys) yScale.domain([0, d3.max(groupData.formatedData, d => d[SURVIVED_ALIVE] + d[SURVIVED_DEAD])]).nice() const stack = d3.stack().keys(keys); const series = stack(groupData.formatedData) // stack bars let stackBars = chart.append('g') let keyGroups = stackBars.selectAll("g") .data(series) .enter() .append('g') .attr("fill", d => zColor(d.key)) let groupsItems = keyGroups.selectAll('rect') .data(d => d) .enter() .append('rect') .attr('x', d => xScale(d.data[GROUP_ALL]) + xScale.bandwidth() / 2 - 50) .attr('y', d => yScale(d[1])) .attr('height', d => yScale(d[0]) - yScale(d[1])) .attr('width', 100) // 数据标签 let dataLabels = keyGroups.selectAll('text') .data(d => d) .enter() .append('text') .text(d => d[1] - d[0]) .attr("text-anchor", "middle") .attr("x", d => xScale(d.data[GROUP_ALL]) + xScale.bandwidth() / 2) .attr("y", d => (yScale(d[0]) + yScale(d[1])) / 2) .attr("font-family", "sans-serif") .attr("font-size", "12px") .attr("fill", "white"); // x axis const xAxis = chart.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(xScale)) // y axis const yAxis = chart.append("g") .attr("class", "y axis") .call(d3.axisLeft(yScale).ticks(null, "s")) .append("text") .attr("x", 2) .attr("y", yScale(yScale.ticks().pop()) + 0.5) .attr("dy", "0.32em") .attr("fill", "#000") .attr("font-size", "12px") .attr("text-anchor", "start") .text("人数"); // legends const legend = chart.append("g") .attr("font-family", "SimSun") .attr("font-size", "12px") .attr("text-anchor", "end") .selectAll("g") .data(keys.slice().reverse()) .enter().append("g") .attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 19) .attr("width", 19) .attr("height", 19) .attr("fill", zColor); legend.append("text") .attr("x", width - 24) .attr("y", 9.5) .attr("dy", "0.32em") .text(function (d) { return d; }); // 选择选项的事件处理 d3.selectAll('[name="groupName"]') .on('click', () => { let groupData = undefined const caption = d3.select('.caption')._groups[0][0] const selectedGroup = d3.event.target.value switch(selectedGroup) { case GROUP_ALL: groupData = formatDataForSurvivedGroup(data, GROUP_ALL) caption.innerText = '所有乘客(未分类)' break case GROUP_SEX: groupData = formatDataForSurvivedGroup(data, GROUP_SEX) caption.innerText = '按性别分类' break case GROUP_PCLASS: groupData = formatDataForSurvivedGroup(data, GROUP_PCLASS) caption.innerText = '按船票等级分类(3级 < 2级 < 1级)' break case GROUP_HASFAMILY: groupData = formatDataForSurvivedGroup(data, GROUP_HASFAMILY) caption.innerText = '按是否有家庭成员同行分类' break default: break } if (groupData) { const series = stack(groupData.formatedData) // update xScale and yScale xScale.domain(groupData.groupKeys) d3.select('.x.axis') .call(d3.axisBottom(xScale)) let yMax = d3.max(groupData.formatedData, d => d[SURVIVED_ALIVE] + d[SURVIVED_DEAD]) yScale.domain([0, yMax]).nice() d3.select('.y.axis') .transition() .call(d3.axisLeft(yScale).ticks(null, "s")) // select keyGroup let keyGroups = stackBars.selectAll('g').data(series) // keyGroups enter keyGroups.enter() .append('g') .attr("fill", d => zColor(d.key)) .merge(keyGroups) // update // keyGroups exit keyGroups.exit().remove() // select groupsItems let groupsItems = keyGroups.selectAll('rect').data(d => d) // groupsItems enter groupsItems.enter() .append('rect') .merge(groupsItems) // update .attr('x', d => xScale(d.data[selectedGroup]) + xScale.bandwidth() / 2 - 50) .attr('y', d => yScale(d[1])) .attr('height', 0) .attr('width', 100) .transition() .ease(d3.easeCircleIn) .attr('height', d => yScale(d[0]) - yScale(d[1])) // groupsItems exit groupsItems.exit().remove() // 数据标签 let dataLabels = keyGroups.selectAll('text').data(d => d) // 数据标签 enter dataLabels.enter() .append('text') .text(d => d[1] - d[0]) .merge(dataLabels) .text(d => d[1] - d[0]) .attr("text-anchor", "middle") .attr("x", d => xScale(d.data[selectedGroup]) + xScale.bandwidth() / 2) .attr("y", d => yScale(d[1])) .attr("font-family", "sans-serif") .attr("font-size", "12px") .attr("fill", "white") .transition() .ease(d3.easeCircleIn) .attr("y", d => (yScale(d[0]) + yScale(d[1])) / 2) // 数据标签 exit dataLabels.exit().remove() } }) }) </script> </body> </html>
https://d3js.org/d3.v4.min.js