1.需求
最近有需求需要实现 <table> 图表与 eharts 柱状图的联动。
完整的效果图如下所示
这里时间轴要实现的效果要基本如下图所示
该时间轴并不是要实现选中单独的某一个月份并查看单月的数据,而是要将当前数据的时间跨度控制在时间轴的跨度之间,在点击后退一个月或前进一个月的时候,对应的图表数据需要同步过滤并更新。
2.分析
echarts 本身是有时间轴的,但是这个自带的 timeline 并不能实现 后退一个月、前进一个月 这样的类似固定时间跨度的数据切换,而我们需要的是一个可以改变时间区间的时间轴,并不是要切换到某个月份。其实 echarts 中的 datazoom 是可以实现这个效果的,但是 datazoom 控制条的样式又不能像 timeline 时间轴那样标识每个月,所以决定自己用 html 控件做一个简单的时间轴,然后每当前进或者后退月份的时候,自己再过滤图表数据并更新图表。
图表数据可以选择一次获取全部(如果你数据量很大还是建议分段下载数据),但是展示在图表上的数据必须在时间轴的可见时间区间之内。
时间轴将页面控制页面上下两个图表,上半部分的窗格图表(类似日历,但这个是月历)可以选择 <table> 来实现,每个td中其实有包含两种类型的事件:图片类型事件和纯文本类型事件,这里要求图片类型事件始终在纯文本事件之上。td 右下角虽然现实月份,但是事件必须是年月都匹配上才可以。可以在拿到事件数据的时候动态生成所有td,然后 append 到 table 的 tr 下,在点击时间轴的向前、向后 arrows 时,可以整体左右移动 table 以符合时间轴当前时间区间。至于下半部分的柱状图,还是选择用 echarts 来实现,时间轴变动时主要需要更新 y轴 类目 category 和 系列 series 数据来更新柱图。
3.时间轴具体实现
html
<!--时间轴容器-->
<div id="timeLineContainer">
<!--时间轴-->
<div id="timeline">
<!--后退一个月-->
<span id="prev_month" class="prev_enabled" title="后退一个月"></span>
<!--横向时间轴日期列表-->
<ul id="dates">
<!--<li>
<span >2019年1月</span>
</li>
<li>
<span >2019年2月</span>
</li>
<li>
<span >2019年3月</span>
</li>
<li>
<span >2019年4月</span>
</li>
<li>
<span >2019年5月</span>
</li>
<li>
<span >2019年6月</span>
</li>
<li>
<span >2019年7月</span>
</li>
<li>
<span >2019年8月</span>
</li>
<li>
<span >2019年9月</span>
</li>
<li>
<span >2019年10月</span>
</li>
<li>
<span >2019年11月</span>
</li>
<li>
<span >2019年12月</span>
</li>-->
</ul>
<!--前进一个月-->
<span id="next_month" class="next_enabled" title="前进一个月"></span>
</div>
</div>
css
/*时间轴相关*/
/*时间轴时间节点*/
.timeLineNode{
cursor: pointer;
}
/*回退一个月*/
#prev_month {
z-index: 5;
cursor: pointer;
left: 0px;
top: 15px;
position: absolute;
display: inline-block;
width: 29px;
height: 29px;
}
/*前进一个月*/
#next_month {
z-index: 5;
cursor: pointer;
top: 15px;
right: 0px;
position: absolute;
display: inline-block;
width: 29px;
height: 29px;
}
/*可以回退时(即除了当前界面上的所有月份,在不可见的左侧,仍有月份)*/
.prev_enabled {
background: url(../img/left-on.png) center no-repeat;
}
/*不可以回退时(即除了当前界面上的所有月份,在不可见的左侧,已无月份)*/
.prev_disabled {
background: url(../img/left-off.png) center no-repeat;
}
/*可以前进时(即除了当前界面上的所有月份,在不可见的右侧,仍有月份)*/
.next_enabled {
disabled: false;
background: url(../img/right-on.png) center no-repeat;
}
/*不可以前进时(即除了当前界面上的所有月份,在不可见的右侧,已无月份)*/
.next_disabled {
disabled: true;
background: url(../img/right-off.png) center no-repeat;
}
/*时间轴容器*/
#timeLineContainer {
margin-top: -8px;
margin-left: 2%;
width: 98%;
height: 65px;
}
/*时间轴*/
#timeline {
width: 100%;
height: 60px;
overflow: hidden;
margin: 0px auto;
position: relative;
/*background: url(../img/over_time_red_point.png) left center repeat-x;*/
}
/*时间轴上的横线*/
#timeline:before {
content: "";
width: 100%;
height: 1px;
background: #bfc7ce;
position: absolute;
top: 30px;
left: 0;
}
/*日期列表*/
#dates {
position: absolute;
left: 0;
top: 0;
height: 60px;
overflow: hidden;
}
/*日期节点*/
#dates li {
list-style: none;
float: left;
width: 152px;
height: 60px;
font-size: 16px;
text-align: center;
background: url(../img/icon_dot_new.png) center no-repeat;
position: relative;
z-index: 3;
}
/*日期文字*/
#dates li span {
padding-top: 35px;
display: inline-block;
width: 152px;
font-size: 16px;
}
js
/**
* 时间轴数据源
*/
var timeLineDataArr = generateTimeLineDataArr();
// 这两个索引用来记录当前显示在页面上的时间轴上的起始月和结束月
var nextIndex = 11;
var preIndex = 0;
/*初始化 向前、向后 的图标*/
$('#prev_month').removeClass("prev_enabled").addClass('prev_disabled');
if(nextIndex == timeLineDataArr.length - 1) {
// 不能再前进了
$('#next_month').removeClass("next_enabled").addClass('next_disabled');
}
$(document).ready(function() {
// 初始化顶部普通文本表格
inflateNormalTableData();
// 设置时间轴数据和点击事件
var stringContent = "";
for(var i = 0; i < timeLineDataArr.length; i++) {
stringContent += "<li class='timeLineNode' title='" + timeLineDataArr[i] + "'>" + "<span>" + timeLineDataArr[i] + "</span> " + "</li>";
}
var $lis = $(stringContent);
var $parent = $('#dates');
$parent.append($lis);
// 后退一个月
$('#prev_month').click(function() {
if(!$("#dates").is(":animated")) {
if(preIndex > 0) {
// 前进按钮置为可用的背景图片
if($("#next_month").prop("className") == "next_disabled") {
$('#next_month').removeClass('next_disabled').addClass("next_enabled");
}
var timeLineItemLength = parseInt($("#dates li").css('width'));
var currentDatesLeft = parseInt($("#dates").css('left'));
$("#dates").animate({
left: (currentDatesLeft + timeLineItemLength)
}, 200);
var topTableItemLength = parseInt($("#table_sjzxjd td").css('width'));
var currentTableLeft = parseInt($("#table_sjzxjd").css('left'));
$("#table_sjzxjd").animate({
left: (topTableItemLength + currentTableLeft)
}, 200);
preIndex--;
if(nextIndex > 0) {
nextIndex--
}
if(preIndex == 0) {
// 不能再后退了
$('#prev_month').removeClass("prev_enabled").addClass('prev_disabled');
}
// 刷新底部柱图
refreshBottomBarChart(timeLineDataArr[preIndex], timeLineDataArr[nextIndex]);
}
}
});
// 向前一个月
$('#next_month').click(function() {
if(!$("#dates").is(":animated")) {
if(nextIndex < timeLineDataArr.length - 1) {
// 后退按钮置为可用的背景图片
if($("#prev_month").prop("className") == "prev_disabled") {
$('#prev_month').removeClass('prev_disabled').addClass("prev_enabled");
}
var timeLineItemLength = parseInt($("#dates li").css('width'));
var currentDatesLeft = parseInt($("#dates").css('left'));
$("#dates").animate({
left: (-timeLineItemLength + currentDatesLeft)
}, 200);
var topTableItemLength = parseInt($("#table_sjzxjd td").css('width'));
var currentTableLeft = parseInt($("#table_sjzxjd").css('left'));
$("#table_sjzxjd").animate({
left: (-topTableItemLength + currentTableLeft)
}, 200);
nextIndex++;
preIndex++;
if(nextIndex == timeLineDataArr.length - 1) {
// 不能再前进了
$('#next_month').removeClass("next_enabled").addClass('next_disabled');
}
// 刷新底部柱图
refreshBottomBarChart(timeLineDataArr[preIndex], timeLineDataArr[nextIndex]);
}
}
});
因为页面默认只可以显示 12 个月份的数据,所以选择使用当前时间轴可见时间的最小和最大时间节点,在时间轴数组中对应数据的索引(preIndex、nextIndex)来控制前进、后退月份,这样做的好处是可以方便的获取当前的可见时间区间的最值,并以此来过滤图表数据。
4.上半部分 table 图表具体实现
html
<!--顶部图表容器-->
<div id="top_chart_container">
<!--顶部图表标题-->
<span id="top_chart_title">实<br/>际<br/>执<br/>行<br/>进<br/>度</span>
<div id="top_table_cantainer">
<!--顶部时间事件表-->
<table id="table_sjzxjd">
<tr id="tr_events">
</tr>
</table>
</div>
</div>
css
/*顶部图表容器*/
#top_chart_container {
overflow: hidden;
margin: 0 auto;
padding: 0;
width: 100%;
height: 300px;
border: solid #e9e9ea 1px;
border-right: none;
}
/*顶部图表左侧标题*/
#top_chart_title {
font-weight: bold;
background-color: #2F92FF;
color: #FFFFFF;
padding-top: 80px;
text-align: center;
width: 2%;
height: 100%;
float: left;
}
/*顶部table容器*/
#top_table_cantainer {
position: relative;
width: 98%;
height: 100%;
overflow: hidden;
}
/*顶部table*/
#table_sjzxjd {
position: absolute;
left: 0;
border-collapse: collapse;
border: none;
table-layout: fixed;
float: left;
margin: 0 auto;
padding: 0;
width: 100%;
height: 100%;
}
/*顶部表格中的单元格 td*/
#table_sjzxjd td {
width: 151px;
position: relative;
border: solid #e9e9ea 1px;
border-top: none;
vertical-align: top;
}
/*顶部某一时间内的图片类型事件列表*/
.img_list {
width: 100%%;
}
/*顶部表格中的图片条目*/
.img_list li {
width: 100%;
height: 70px;
}
/*图片 “完”*/
.img_wan {
height: 28px;
width: 28px;
}
/*cg图片类型条目*/
.img_item_cg {
background: url(../img/top_chart_img_list_item_cg_bg.png) no-repeat center;
}
.img_item_cg span.img_wan {
background: url(../img/text_cai_gou_wan.png) no-repeat center;
}
/*ht图片类型条目*/
.img_item_ht {
background: url(../img/top_chart_img_list_item_ht_bg.png) no-repeat center;
}
/*ht——完*/
.img_item_ht span.img_wan {
background: url(../img/text_he_tong_wan.png) no-repeat center;
}
/*phgg图片类型条目*/
.img_item_phgg {
background: url(../img/top_chart_img_list_item_phgg_bg.png) no-repeat center;
}
/*phgg——完*/
.img_item_phgg span.img_wan {
background: url(../img/text_phgg_wan.png) no-repeat center;
}
/*cggd图片类型条目*/
.img_item_cggd {
background: url(../img/top_chart_img_list_item_cggd_bg.png) no-repeat center;
}
/*cggd——完*/
.img_item_cggd span.img_wan {
background: url(../img/text_cggd_wan.png) no-repeat center;
}
/*tqps*/
.img_item_tqps {
background: url(../img/top_chart_img_list_item_tqps_bg.png) no-repeat center;
}
/*tqps——完*/
.img_item_tqps span.img_wan {
background: url(../img/text_ti_qian_pi_shi_wan.png) no-repeat center;
}
/*设置图片条目的文本weight 和 颜色*/
.img_list li p {
font-weight: 400;
color: #FFFFFF;
}
/*cg 图片上的事件时间*/
.img_list_item p.case_time {
padding-left: 15px;
}
/*cg 图片上的事件类型*/
.img_list_item p.case_type {
padding-top: 12px;
padding-left: 15px;
}
/*上半部分图表,”cg“、”ht“、”tqps等“*/
.img_list_item {
position: relat