统计了八周的运动步数数据,实现了运动步数的可视化,数据来源于手机APP的记录,分别以柱状图和折线图的形式展示。
效果图:
数据来源:
分别制作了柱状图及折线图,可通过按钮来互相切换。
柱状图有八张,分别对应八周的运动步数统计。
有排序功能,可实现当周每日的运动步数增序排序。
以7000步为界限,当周平均步数高于7000步的柱形为黄色,少于7000步的柱形为红色,并在图标上方有文字说明当周的运动情况。
柱形上方有当日的运动步数数值,有tooltip提示工具,当鼠标置于柱形上时可显示当日的运动步数。
折线图中有八条折线,分别代表每周的数据,可通过图例中的颜色加以区分。
代码如下:
HTML:
<!DOCTYPE html>
<html >
<head>
<title>MySports</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="MySports.css">
</head>
<body >
<script src="http://d3js.org/d3.v5.min.js"></script>
<div id="headline" class="title is-1" >每周运动步数统计图</div>
<div class='tool'>
<button id="btn-sort" class="button is-primary is-rounded">排序</button>
<button id="btn-convert1" class="button is-link is-rounded" >第一周</button>
<button id="btn-convert2" class="button is-link is-rounded" >第二周</button>
<button id="btn-convert3" class="button is-link is-rounded" >第三周</button>
<button id="btn-convert4" class="button is-link is-rounded" >第四周</button>
<button id="btn-convert5" class="button is-link is-rounded" >第五周</button>
<button id="btn-convert6" class="button is-link is-rounded" >第六周</button>
<button id="btn-convert7" class="button is-link is-rounded" >第七周</button>
<button id="btn-convert8" class="button is-link is-rounded" >第八周</button>
<button id="btn-line" class="button is-rounded">折线图</button>
</div>
<script type="text/javascript" src="MySports.js"></script>
</body>
</html>
JS:
var padding = {top:100,bottom:60,left:120,right:60}
var width = 1200;
var height = 620;
var dataset=[
{
week:"1",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[10961,10882,11976,9935,6075,954,8794]
},
{
week:"2",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[7654,12617,5041,5823,6506,2257,7206]
},
{
week:"3",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[11526,11506,8935,8853,3251,4438,4248]
},
{
week:"4",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[7598,7576,10236,8450,3333,6673,6666]
},
{
week:"5",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[8376,11217,9790,6657,6302,7166,7127]
},
{
week:"6",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[10678,4177,5798,12920,7501,7320,2140]
},
{
week:"7",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[9133,7829,10438,6067,2138,7458,5758]
},
{
week:"8",
datax:['周一','周二','周三','周四','周五','周六','周日'],
datay:[8563,9621,5311,10406,10864,9637,4021]
},
];
//柱状图函数
function drawbar(datay,datax,week){
//设置画布
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var g = svg.append("g")
.attr("transform","translate("+padding.top+","+padding.left+")");
//绘制坐标轴
var xScale = d3.scaleBand()
.domain(datax)
.range([0,790])
var xAxis = d3.axisBottom(xScale);
var yScale = d3.scaleLinear()
.domain([0,d3.max(datay)])
.range([height-padding.top-padding.bottom,0]);
var yAxis = d3.axisLeft(yScale);
g.append("g")
.attr('class','axis')
.attr("transform","translate("+0+","+(height-padding.top-padding.bottom)+")")
.call(xAxis);
g.append("g")
.attr('class','axis')
.attr("transform","translate(0,0)")
.call(yAxis);
//求每周步数的平均值
var avg0 =(datay[0]+datay[1]+datay[2]+datay[3]+datay[4]+datay[5]+datay[6])/7
var avg=avg0.toFixed(0) //将平均值去小数点
//绘制矩形和文字
var bar = g.selectAll(".rect")
.data(datay)
.enter()
.append("g");
//绘制矩形
var rectPadding = 10;//矩形之间的间隙
bar.append("rect")
.attr("class",'rects')
.attr("x",function(d,i){
return xScale(datax[i])+rectPadding/2;
})
.attr("y",function (d) {
var min=yScale.domain()[0];
return yScale(min);
})
.attr("width",function(){
return xScale.step()-rectPadding;
})
.attr("height",function(d,i){
return 0;
})
.attr('fill',function(){ //根据每周的平均步数给矩形上色
if(avg>7000){
return 'cornsilk';
}else{
return 'tomato';
}
})
.transition() //为这个元素添加过渡;
.duration(800) //设定元素从起始状态到终止状态的过渡时间;
.delay(function(d,i){ //设定元素执行过渡效果的时间间隔;
return i*50;
})
.ease(d3.easeBackOut) //设定过渡的动画效果
.attr("y",function (d,i) {
return yScale(d)
})
.attr("height",function (d,i) {
return height-padding.top-padding.bottom-yScale(d);
});
//设置tooltip提示框
var tooltip = d3.select("body")
.append("div")
.attr("class","tooltip")
.attr("opacity",0);
//响应事件
//-鼠标移入事件
bar.on("mouseover",function(d,i)
{
//设置tooltip文字
tooltip.html(d)
//设置tooltip的位置(left,top 相对于页面的距离)
.style("left",(d3.event.pageX)+"px")
.style("top",(d3.event.pageY+20)+"px")
.style("opacity",1); //淡入效果
})
//--鼠标移出事件
.on("mouseout",function(d)
{
tooltip.style("opacity",0); //鼠标移出时,将透明度设定为0.0,完全透明
});
//绘制文字
bar.append("text")
.attr('class','texts')
.attr('font-size','2em')
.attr('fill','maroon')
.attr("x",function(d,i){
return (xScale(datax[i])+rectPadding/2);
})
.attr("y",function(d){
var min=yScale.domain()[0];
return yScale(min);
})
.attr("dx",function(){
(xScale.step()-rectPadding)/2;
})
.attr("dy",0)
.text(function(d){
return d;
})
.transition() //为这个元素添加过渡;
.duration(800) //设定元素从起始状态到终止状态的过渡时间;
.delay(function(d,i){ //设定元素执行过渡效果的时间间隔;
return i*50;
})
.ease(d3.easeBackOut) //设定过渡的动画效果
.attr("y",function (d,i) {
return yScale(d)-10;
})
.attr("height",function (d,i) {
return height-padding.top-padding.bottom-yScale(d);
});
//根据每周平均步数添加不同文字
if(avg>7000){
svg.append('text')
.text('当前为第'+week+'周,本周平均步数为'+avg+',继续保持!')
.style('font-style','italic') //斜体
.attr('fill','teal')
.attr('font-size','2em')
.attr('x',100)
.attr('y',50)
}else{
svg.append('text')
.text('当前为第'+week+'周,本周平均步数为'+avg+',需要多运动!')
.style('font-style','italic') //斜体
.attr('fill','navy')
.attr('font-size','2em')
.attr('x',100)
.attr('y',50)
}
//给坐标轴添加文字
svg.append('text')
.text('步数')
.attr('font-size', '1.5em')
.attr('x',55)
.attr('y',100 )
.attr('fill','maroon')
svg.append('text')
.text('日期')
.attr('font-size', '1.5em')
.attr('x',900)
.attr('y',600 )
.attr('fill','maroon')
//添加图例
svg.append('rect')
.attr('x',1000)
.attr('y',100)
.attr('width',50)
.attr('height',20)
.attr('fill','cornsilk');
svg.append('text')
.text('当周平均步数多于7000步')
.attr('font-size', '1em')
.attr('fill','cornsilk')
.attr('x',1000)
.attr('y',140 )
.style()
svg.append('rect')
.attr('x',1000)
.attr('y',160)
.attr('width',50)
.attr('height',20)
.attr('fill','tomato');
svg.append('text')
.text('当周平均步数少于7000步')
.attr('font-size', '1em')
.attr('fill','tomato')
.attr('x',1000)
.attr('y',200);
//排序按钮功能
d3.select('#btn-sort').on('click', ()=>{
svg.selectAll(".rects")
.sort()
.attr("x",function(d,i){
return xScale(datax[i])+rectPadding/2;
})
.attr("y",function (d) {
var min=yScale.domain()[0];
return yScale(min);
})
.attr("width",function(){
return xScale.step()-rectPadding;
})
.attr("height",function(d,i){
return 0;
})
.transition() //为这个元素添加过渡;
.duration(800) //设定元素从起始状态到终止状态的过渡时间;
.delay(function(d,i){ //设定元素执行过渡效果的时间间隔;
return i*50;
})
.ease(d3.easeBackOut) //设定过渡的动画效果
.attr("y",function (d,i) {
return yScale(d)
})
.attr("height",function (d,i) {
return height-padding.top-padding.bottom-yScale(d);
})
svg.selectAll(".texts")
.sort()
.attr("x",function(d,i){
return (xScale(datax[i])+rectPadding/2);
})
.attr("y",function(d){
var min=yScale.domain()[0];
return yScale(min);
})
.attr("dx",function(){
(xScale.step()-rectPadding)/2;
})
.attr("dy",0)
.text(function(d){
return d;
})
.transition() //为这个元素添加过渡;
.duration(800) //设定元素从起始状态到终止状态的过渡时间;
.delay(function(d,i){ //设定元素执行过渡效果的时间间隔;
return i*50;
})
.ease(d3.easeBackOut) //设定过渡的动画效果
.attr("y",function (d,i) {
return yScale(d)-10;
})
.attr("height",function (d,i) {
return height-padding.top-padding.bottom-yScale(d);
})
})
//根据按钮显示每周数据
d3.select('#btn-convert1').on('click', function(){
svg.remove();
drawbar(dataset[0].datay,dataset[0].datax,dataset[0].week);
}) ;
d3.select('#btn-convert2').on('click', function(){
svg.remove();
drawbar(dataset[1].datay,dataset[1].datax,dataset[1].week);
}) ;
d3.select('#btn-convert3').on('click', function(){
svg.remove();
drawbar(dataset[2].datay,dataset[2].datax,dataset[2].week);
}) ;
d3.select('#btn-convert4').on('click', function(){
svg.remove();
drawbar(dataset[3].datay,dataset[3].datax,dataset[3].week);
}) ;
d3.select('#btn-convert5').on('click', function(){
svg.remove();
drawbar(dataset[4].datay,dataset[4].datax,dataset[4].week);
}) ;
d3.select('#btn-convert6').on('click', function(){
svg.remove();
drawbar(dataset[5].datay,dataset[5].datax,dataset[5].week);
}) ;
d3.select('#btn-convert7').on('click', function(){
svg.remove();
drawbar(dataset[6].datay,dataset[6].datax,dataset[6].week);
}) ;
d3.select('#btn-convert8').on('click', function(){
svg.remove();
drawbar(dataset[7].datay,dataset[7].datax,dataset[7].week);
}) ;
d3.select('#btn-line').on('click', function(){
svg.remove();
drawline();
}) ;
}
var dataset2=[
{
Week:"OneWeek",
gdp:[[1,10961],[2,10882],[3,11976],[4,9935],[5,6075],[6,954],[7,8794],]
},
{
Week:"TwoWeek",
gdp:[[1,7654],[2,12617],[3,5041],[4,5823],[5,6506],[6,2257],[7,7206],]
},
{
Week:"ThreeWeek",
gdp:[[1,11526],[2,11506],[3,8935],[4,8853],[5,3251],[6,4438],[7,4248],]
},
{
Week:"FourWeek",
gdp:[[1,7598],[2,7576],[3,10236],[4,8450],[5,3333],[6,6673],[7,6666],]
},
{
Week:"FiveWeek",
gdp:[[1,8376],[2,11217],[3,9790],[4,6657],[5,6302],[6,7166],[7,7127],]
},
{
Week:"SixWeek",
gdp:[[1,10678],[2,4177],[3,5798],[4,12920],[5,7501],[6,7320],[7,2140],]
},
{
Week:"SevenWeek",
gdp:[[1,9133],[2,7829],[3,10438],[4,6067],[5,2138],[6,7458],[7,5758],]
},
{
Week:"EightWeek",
gdp:[[1,8563],[2,9621],[3,5311],[4,10406],[5,10864],[6,9637],[7,4021],]
},
];
//折线图函数
function drawline(){
//定义颜色
var colors=[d3.rgb(0,0,255),d3.rgb(0,255,0),d3.rgb(255,0,0),d3.rgb(0,255,255),d3.rgb(255,0,255),
d3.rgb(255,255,0),d3.rgb(0,0,125),d3.rgb(125,0,0)];
//绘制坐标刻度
var xScale=d3.scaleLinear()
.domain([1,7])
.range([100,850]);
var yScale=d3.scaleLinear()
.domain([0,15000])
.range([height - padding.bottom, padding.top]);
var gdpmax=0;
for(var i=0;i<dataset2.length;i++){
var currGdp=d3.max(dataset2[i].gdp,function(d){
return d[1];
});
if(currGdp>gdpmax)
gdpmax=currGdp;
}
console.log(gdpmax);
//创建一个svg画图区域
var svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
//创建一个直线生成器
var linePath=d3.line()
.x(function(d){
return xScale(d[0]);
})
//获取每个节点的x坐标
.y(function(d){
return yScale(d[1]);
})
//获取每个节点的y坐标
//添加路径,将path添加进svg容器
svg.selectAll("path")
.data(dataset2)
.enter()
.append("path")
.attr("transform","translate(" + 0 + ",0)")
//将直线的左端置于y轴坐标轴上
.attr("d",function(d){
return linePath(d.gdp);
//返回线段生成器得到的路径
})
.attr("fill","none")//填充颜色为none
.attr("stroke-width",3)//线的宽度
.attr("stroke",function(d,i){
return colors[i];
});//使用线段颜色
var xAxis = d3.axisBottom(xScale)
.scale(xScale)
.ticks(5)
.tickFormat(d3.format("d"))
var yAxis = d3.axisLeft(yScale);
//将坐标轴添加进svg容器
//x轴
svg.append("g")
.attr("class","axis")
.attr("transform","translate(0," + (height - padding.bottom) + ")")
.call(xAxis)
//y轴
svg.append("g")
.attr("class","axis")
.attr("transform","translate(" + 100 + ",0)")
.call(yAxis)
//给坐标轴添加文字
svg.append('text')
.text('步数')
.attr('font-size', '1.5em')
.attr('x',60)
.attr('y',80 )
.attr('fill','maroon')
svg.append('text')
.text('日期')
.attr('font-size', '1.5em')
.attr('x',860)
.attr('y',570 )
.attr('fill','maroon')
//图例数组,格式可自定义
var data_legend = [
{
"name":"第一周",
"color":"#0000FF"
},
{
"name":"第二周",
"color":"#00FF00"
},
{
"name":"第三周",
"color":"#FF0000"
},
{
"name":"第四周",
"color":"#00FFFF"
},
{
"name":"第五周",
"color": "#FF00FF"
},
{
"name":"第六周",
"color":"#FFFF00"
},
{
"name":"第七周",
"color":"#00007D"
},
{
"name":"第八周",
"color":"#7D0000"
},
];
//初始化图例,将data_legend与图例绑定
var legend = svg.selectAll(".legend")
.data(data_legend)
.enter()
.append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(-200," + (i * 20 + 80) + ")";
});
//transform属性便是整个图例的坐标
//绘制文字后方的颜色框
legend.append("rect")
.attr("x", width - 25) //width是svg的宽度,x,y属性用来调整位置
.attr("y", -12)
.attr("width", 50) //颜色框宽度
.attr("height", 15) //颜色框高度
.style("fill", function(d){
return d.color
});
//绘制图例文字
legend.append("text")
.attr('fill','maroon')
.attr("x", width - 30)
.attr("y", 0)
.style("text-anchor", "end") //样式对齐
.text(function(d) {
return d.name;
});
//按钮切换到柱状图
d3.select('#btn-convert1').on('click', function(){
svg.remove();
drawbar(dataset[0].datay,dataset[0].datax,dataset[0].week);
}) ;
d3.select('#btn-convert2').on('click', function(){
svg.remove();
drawbar(dataset[1].datay,dataset[1].datax,dataset[1].week);
}) ;
d3.select('#btn-convert3').on('click', function(){
svg.remove();
drawbar(dataset[2].datay,dataset[2].datax,dataset[2].week);
}) ;
d3.select('#btn-convert4').on('click', function(){
svg.remove();
drawbar(dataset[3].datay,dataset[3].datax,dataset[3].week);
}) ;
d3.select('#btn-convert5').on('click', function(){
svg.remove();
drawbar(dataset[4].datay,dataset[4].datax,dataset[4].week);
}) ;
d3.select('#btn-convert6').on('click', function(){
svg.remove();
drawbar(dataset[5].datay,dataset[5].datax,dataset[5].week);
}) ;
d3.select('#btn-convert7').on('click', function(){
svg.remove();
drawbar(dataset[6].datay,dataset[6].datax,dataset[6].week);
}) ;
d3.select('#btn-convert8').on('click', function(){
svg.remove();
drawbar(dataset[7].datay,dataset[7].datax,dataset[7].week);
}) ;
d3.select('#btn-line').on('click', function(){
svg.remove();
drawline();
}) ;
}
drawbar(dataset[0].datay,dataset[0].datax,dataset[0].week);
CSS:
.tooltip{
font-family:simsun;
font-size:30px;
width:120;
height:auto;
position:absolute;
text-align:center;
border-style:solid;
border-width:1px;
background-color:white;
border-radius:5px;
}
#headline {
font-size: 30px;
width: 100%;
margin-bottom: 10px;
text-align: center;
}
.tool {
margin-top: 20px;
text-align: left;
}
body{
background:url(bcg.jpg);
background-size:100%;
}
背景图片:
转载请申明