html 自定义简单的时间轴 timeline 并与 table 图表和 echarts 进度甘特图联动

2023-11-18

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: relative;
}


/*图片:cg-完*/

.img_list_item span.img_wan {
	position: absolute;
	right: 12px;
	bottom: 12px;
}


/*顶部表格中的文本列表*/

.text_list {
	width: 100%;
}


/*文本条目左侧的 圆点 图片*/

.dot_in_text_item {
	display: inline-block;
	height: 12px;
	width: 12px;
	background: url(../img/icon_dot_new.png) no-repeat center;
}


/*顶部表格中的文本列表条目*/

.text_list_item {
	width: 100%;
	font-weight: 600;
	color: #1C243199;
}


/*文本条目内容*/

.text_list_item span {
	text-align: center;
	margin-left: 10px;
}


/*文本条目上的小圆点*/

.text_list_item img {
	margin-left: 10px;
}


/*顶部表格中标识月份的文本区域*/

.month_num_text_area {
	position: absolute;
	bottom: 1px;
	right: 10px;
	color: #becbcd;
	font-weight: bold;
}

.moth_number {
	font-size: 30px;
}

/**
 *顶部table的事件窗格td
 */
.month_td{
	cursor: pointer;
}

js

/*顶部列表的数据源,和底部图表的数据源应分开,无论每个月份是否有事件,月份仍要显示,时间坐标轴也是如此*/
var topChartData = {
	events: [{
			/*事件时间*/
			eventTime: '2019-01-01',

			/*事件类型:
			 *因为现在有图片和文本两大类事件,
			 * 0:文本类型事件;
			 * 1:图片类型事件
			 */
			eventType: 1,

			/*图片类型事件中的具体事件类型,包括
			 * 0:cgfssp
			 * 1:ht
			 * 2:tqps
			 * 3:phgg
			 * 4:cggd
			 * 
			 * 文本类型事件可不传该字段
			 * */
			specificEventType: 0,

			/*事件标题
			 * 在事件为文本类型时,该值必填,
			 * 在事件为图片类型时,若传入空字符串,那么将自动按类型填充填充默认标题
			 */
			eventTitle: '采购方式审批',
		},
		{
			eventTime: '2019-01-10',
			eventType: 0,
			eventTitle: 'zb',
		},
		{
			eventTime: '2019-02-28',
			eventType: 1,
			specificEventType: 1,
			eventTitle: 'ht',
		},
		{
			eventTime: '2019-02-15',
			eventType: 0,
			eventTitle: 'zb',
		},
	]
};

/**
 * 加载顶部的图表数据
 */
function inflateTopTableChart() {
	// 获取事件数组
	var events = topChartData.events;
	for(var i = 0; i < timeLineDataArr.length; i++) {
		// 获取时间轴节点的年月
		var timeLineNodeItem = timeLineDataArr[i];
		var timeLineNodeItemDate = convertHanziDateToJSDate(timeLineNodeItem);
		var timeLineNodeMonth = timeLineNodeItemDate.getMonth() + 1;
		var timeLineNodeYear = timeLineNodeItemDate.getFullYear();

		// 将时间轴上的时间作为对应td的id,生成td时先查看该td是否已存在,不存在先生成
		if($('#' + timeLineNodeItem).length == 0) {
			// 需要先生成一个 td
			$('#tr_events').append('<td id="' +
				timeLineNodeItem +
				'"  class="month_td" title="' +
				timeLineNodeYear + "年" + timeLineNodeMonth + "月事件" +
				'"></td>');
		}
		// 顶部 table 中的 td右下角的月份
		$('#' + timeLineNodeItem).append('<div class="month_num_text_area"><span class="moth_number">' +
			timeLineNodeMonth +
			'</span><span>月</span></div>');

		// 获取事件的年月并对比
		for(var j = 0; j < events.length; j++) {
			var event = events[j];
			var eventTimeStr = event.eventTime;
			var eventTime = new Date(eventTimeStr);
			var eventYear = eventTime.getFullYear();
			var eventMonth = eventTime.getMonth() + 1;
			var eventDay = eventTime.getDate();

			// 同年同月
			if(timeLineNodeYear == eventYear && timeLineNodeMonth == eventMonth) {

				// 保证图片类型消息的的ul在上,文本类型的在下
				// 先看该td下是否已存在对应类型的 ul
				if($('#' + timeLineNodeItem + ' ul.img_list').length == 0) {
					//不存在的话,需要先生成一个,
					$('#' + timeLineNodeItem).append('<ul class="img_list"></ul>');
				}

				// 文本类型事件 ul
				// 先看该td下是否已存在对应类型的额 ul
				if($('#' + timeLineNodeItem + ' ul.text_list').length == 0) {
					//不存在的话,需要先生成一个,
					$('#' + timeLineNodeItem).append('<ul class="text_list"></ul>');
				}

				if(event.eventType == 1) {
					// 图片类型事件
					//在图片类型 ul 下拼接 图片类型消息li
					$('#' + timeLineNodeItem + ' ul.img_list').append('<li title="' +
						event.eventTitle +
						'" class="img_list_item ' +
						getTopTableImgTypeClassName(event.specificEventType) +
						'"><p class="case_type">' +
						event.eventTitle +
						'</p><p class="case_time">' +
						eventYear + '.' + eventMonth + '.' + eventDay +
						'</p><span class="img_wan"></span></li>');
				}

				if(event.eventType == 0) {
					// 文本类型事件
					//在文本类型 ul 下拼接 文本类型
					$('#' + timeLineNodeItem + ' ul.text_list').append('<li class="text_list_item" title="' +
						event.eventTitle +
						'"><span class="dot_in_text_item"></span><span>' +
						eventMonth + '.' + eventDay + ' ' + event.eventTitle + '</span></li>');
				}
			}
		}
	}
}

顶部 table 与 时间轴的联动代码可在 时间轴的 js 中找到。

5.下半部分 echarts 柱图的具体实现

这里需要注意的一点是,echarts 在频繁的 给 chart setOption( ) 时,很有可能会残留数据,造成切换数据后显示混乱。

跟踪代码排除是数据传错的情况后,可以选择不通过 setOption() 来刷新柱图数据了,而是在重新 echarts.init() 后,构建好新数据(通过时间轴的最值时间过滤好的 series)setOption()之前,使用 echart.clear() 方法来清除缓存,最后再调用echart.setOption(newOption) 来初始化图表数据。

html

<!--底部图表容器-->
<div id="bottom_chart_container">
    <span id="bottom_chart_title">计<br>划<br>时<br>间</span>
	<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
	<div id="chart_bottom"></div>
</div>

css

/*底部图表*/

#chart_bottom {
	float: left;
	margin: 0 auto;
	padding: 0;
	width: 98%;
	height: 100%;
}

#sjzxjd {
	background-color: '#2f92ff';
}

/*底部图表标题*/

#bottom_chart_title {
	font-weight: bold;
	color: #FFFFFF;
	background-color: #a384e5;
	padding-top: 100px;
	text-align: center;
	width: 2%;
	height: 100%;
	float: left;
}


/*盛放底部图表的容器*/

#bottom_chart_container {
	border: solid #e9e9ea 1px;
	overflow: hidden;
	height: 300px;
	width: 100%;
}

js

// 传底部柱图阶段数据
var stages = [{
		"stageName": 'ghqd阶段',
		"stageTimes": ['2019-01-01', '2019-02-28']
	},
	{
		"stageName": 'ghdyzb阶段',
		"stageTimes": ['2019-02-28', '2019-05-01']
	},
	{
		"stageName": 'ghbz阶段',
		"stageTimes": ['2019-05-01', '2019-10-31']
	},
	{
		"stageName": 'ghsw阶段',
		"stageTimes": ['2019-10-31', '2020-03-31']
	}
];

/**
 * 阶段开始时间数组
 */
var stagesBeginTimeArr = new Array();
var stagesBeginTimeMillisArr = new Array();

/**
 * 阶段结束时间数组
 */
var stagesEndTimeArr = new Array();
var stagesEndTimeMillisArr = new Array();

/**
 * 阶段名称
 */
var stagesNameArr = new Array();

/**
 * 填充柱图数据
 * @param {Object} inputStages 输入阶段
 */
function fillBarChartData(inputStages) {
	stagesNameArr = [];
	stagesBeginTimeArr = [];
	stagesBeginTimeMillisArr = [];
	stagesEndTimeArr = [];
	stagesEndTimeMillisArr = [];
	for(var i = 0; i < inputStages.length; i++) {
		var stage = inputStages[i];
		stagesNameArr[i] = stage.stageName;

		stagesBeginTimeArr[i] = stage.stageTimes[0];
		stagesBeginTimeMillisArr[i] = getTimeMilliseconds(stage.stageTimes[0]);

		stagesEndTimeArr[i] = stage.stageTimes[1];
		stagesEndTimeMillisArr[i] = getTimeMilliseconds(stage.stageTimes[1]);
	}
}


/**
 * 获取随机16进制颜色
 */
function getRandomColor() {
	var color = '#';
	for(var i = 0; i < 6; i++) {
		color += (Math.random() * 16 | 0).toString(16);
	}
	if(color.toUpperCase() != '#FFFFF') {
		console.log(color);
		return color;
	} else {
		return getRandomColor();
	}
}

/**
 * 获取柱图单独系列的数据
 * @param {Object} inputStages 输入阶段
 * @param {Object} seriesRank 阶段在阶段数组中的索引
 */
function getSingleSeriesData(inputStages, seriesRank) {
	var dataArr = [];
	for(var j = 0; j < inputStages.length; j++) {
		if(seriesRank == j) {
			dataArr[j] = getTimeMilliseconds(inputStages[j].stageTimes[1]);
		} else {
			dataArr[j] = "-";
		}
	}

	return dataArr;
}

/**
 * 生成系列数据 
 * @param {Object} inputStages 输入阶段
 */
var generatedSeries = function(inputStages) {
	var series = new Array();
	// 占位柱图1
	series[0] = {
		name: '开始时间',
		type: 'bar',
		stack: '时间',
		itemStyle: {
			normal: {
				barBorderColor: 'rgba(0,0,0,0)',
				color: 'rgba(0,0,0,0)'
			},
			emphasis: {
				barBorderColor: 'rgba(0,0,0,0)',
				color: 'rgba(0,0,0,0)'
			}
		},
		label: {
			normal: {
				formatter: function(params) {
					var stageName = "";
					if(params != null) {
						stageName = params.name;
					}
					if(stageName == stagesNameArr[0]) {
						return getSeriesDateStr(params.value);
					}
					return "";

				},
				show: true,
				position: 'insideRight',
				fontSize: 16,
				color: '#1c2431',
				fontFamily: 'Microsoft YaHei',
				offset: [65, -20],
			}
		},
		data: stagesBeginTimeMillisArr,
	};

	// 占位柱图2
	series[1] = {
		name: '开始时间1',
		type: 'bar',
		stack: '时间',
		itemStyle: {
			normal: {
				barBorderColor: 'rgba(0,0,0,0)',
				color: 'rgba(0,0,0,0)'
			},
			emphasis: {
				barBorderColor: 'rgba(0,0,0,0)',
				color: 'rgba(0,0,0,0)'
			}
		},
		label: {
			normal: {
				formatter: function(params) {
					var stageName = "";
					if(params != null) {
						stageName = params.name;
					}
					return stageName;

				},
				show: true,
				position: 'Right',
				fontSize: 16,
				color: '#1c2431',
				fontFamily: 'Microsoft YaHei',
				offset: [0, 25],
			}
		},
		data: stagesBeginTimeMillisArr,
	};

	// 循环填充数据
	for(var i = 0; i < inputStages.length; i++) {
		series[i + 2] = {
			name: stagesNameArr[i],
			type: 'bar',
			stack: '时间',
			itemStyle: {
				normal: {
					color: getRandomColor(),
				}
			},
			label: {
				normal: {
					formatter: function(params) {
						return getSeriesDateStr(params.value);
					},
					show: true,
					fontSize: 16,
					color: '#1c2431',
					position: 'insideRight',
					fontFamily: 'Microsoft YaHei',
					offset: [5, -20],
				}
			},
			data: getSingleSeriesData(inputStages, i),
			/**
			 * 柱状图宽度
			 */
			barWidth: 20,
		};
	}
	return series;
};

/**
	 * 过滤底部柱图阶段数据
	 * @param {Object} currentBarMinDate
	 * @param {Object} currentBarMaxDate
	 */
	function filterStagesAccordingTimeLineMinAndMaxVisibleTimeNode(currentBarMinDate, currentBarMaxDate) {
		// 过滤底部柱图的数据
		var barCharMinTime = convertHanziDateToJSDate(currentBarMinDate);
		var barCharMaxTime = convertHanziDateToJSDate(currentBarMaxDate);
		//		// 当前时间轴上显示的最大、最小年月
		var barCharMinYear = barCharMinTime.getFullYear();
		var barCharMinMonth = barCharMinTime.getMonth() + 1;
		var barCharMaxYear = barCharMaxTime.getFullYear();
		var barCharMaxMonth = barCharMaxTime.getMonth() + 1;

		var newStages = new Array();

		for(var i = 0; i < stages.length; i++) {
			var stage = stages[i];
			// 阶段开始时间年月
			var stageBeginTimeStr = stage.stageTimes[0];
			var stageEndTimeStr = stage.stageTimes[1];
			var stageBeginTime = new Date(stageBeginTimeStr);
			var stageEndTime = new Date(stageEndTimeStr);

			// 阶段结束时间年月
			var stageBeginYear = stageBeginTime.getFullYear();
			var stageBeginMonth = stageBeginTime.getMonth() + 1;
			var stageEndYear = stageEndTime.getFullYear();
			var stageEndMonth = stageEndTime.getMonth() + 1;

			if(stageBeginTime.getTime() >= barCharMinTime.getTime() &&
				stageEndTime.getTime() <= barCharMaxTime.getTime() ||
				(stageBeginYear == barCharMinYear && stageBeginMonth >= barCharMinMonth &&
					stageEndYear == barCharMaxYear && stageEndMonth <= barCharMaxMonth) ||
				(stageBeginYear > barCharMinYear && stageEndYear < barCharMaxYear)) {
				newStages.push(stage);
			}
		}
		console.log('newStages-->' + newStages);
		return newStages;
	}

	/**
	 * 当后退或前进时刷新底部的柱图
	 * @param {Object} currentBarMinDate 当前时间轴可见最小年月
	 * @param {Object} currentBarMaxDate 当前时间轴可见最大年月
	 */
	function refreshBottomBarChart(currentBarMinDate, currentBarMaxDate) {
		//		$("#chart_bottom").html(' ');
		var newStages = filterStagesAccordingTimeLineMinAndMaxVisibleTimeNode(currentBarMinDate, currentBarMaxDate);

		// 重新填充柱图数据
		fillBarChartData(newStages);

		// 初始化底部柱图图表
		var myBottomChart = echarts.init(document.getElementById('chart_bottom'));

		// 构建图表配置项
		bottomChartOption = {
			tooltip: {
				trigger: 'axis',
				axisPointer: {
					type: 'shadow',
				},

				/**
				 * 也可以使用 formatter: '{b0}:<br />{a0}: {c0}<br />{a1}: {c1}<br />{a2}: {c2}',
				 * 但是这样当鼠标指向纵坐标的三个阶段中的某一个时,即使该阶段 没有按时完成,或者 没有超时,
				 * 也会显示 按时 或 超时 的 tooltip
				 */
				formatter: function(params) {
					console.log("params111" + JSON.stringify(params));
					var info = params[0].axisValue + ":<br />";
					info += params[0].seriesName + ":" + getSeriesDateStr(params[0].value) + "<br />";
					for(var i = 0; i < params.length; i++) {
						if(params[i].seriesName == params[0].axisValue) {
							info += "结束时间:" + getSeriesDateStr(params[i].value);
						}
					}
					return info;
				},
			},

			/**
			 * 右上角工具栏
			 */
			toolbox: {
				right: '3%',
				show: false,
				feature: {
					saveAsImage: {
						show: true
					}
				}
			},

			/**
			 * 直角坐标系内绘图网格
			 */
			grid: {
				top: '0%',
				left: '0%',
				right: '3%',
				bottom: '0%',
				/**
				 * grid 区域是否包含坐标轴的刻度标签。
				 */
				containLabel: false,
			},

			/**
			 * 横坐标
			 */
			xAxis: {
				/**
				 * 时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,
				 * 在刻度计算上也有所不同,例如会根据跨度的范围来决定使用月,星期,日还是小时范围的刻度。
				 */
				type: 'time',

				/**
				 * value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数应该返回坐标轴的最大值。
				 *
				 * 坐标轴刻度最小值。
				 */
				min: getTimeMilliseconds(getProperTimeAxisBeginAndEndTime()[0]),

				/**
				 * value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数应该返回坐标轴的最大值。
				 *
				 * 坐标轴刻度最大值。
				 */
				max: getTimeMilliseconds(getProperTimeAxisBeginAndEndTime()[1]),

				/**
				 * 设置坐标轴分割间隔
				 */
				interval: getProperTimeAxisInterval(),
				axisLine: {
					lineStyle: {
						color: '#BDC8CD',
						width: 1,
					},
				},

				axisTick: {
					show: false,
				},

				/**
				 * 坐标轴刻度标签的相关设置。
				 */
				axisLabel: {
					show: false,
					showMinLabel: true,
					showMaxLabel: true,
					margin: 12,
					fontSize: 16,
					color: '#1c2431',
					formatter: function(value, index) {
						var date = new Date(value);
						// var time = date.getFullYear() + "." + (date.getMonth() + 1) + "." + date.getDate();
						var time = date.getFullYear();
						if(xAxisLabelUnit.month) {
							time += "." + (date.getMonth() + 1);
						}
						if(xAxisLabelUnit.day) {
							time += "." + (date.getMonth() + 1) + '.' + date.getDate();
						}
						return time;
					},
				},

				/**
				 * 坐标轴刻度分割线
				 */
				splitLine: {
					show: false,
				},

			},

			/**
			 * 纵坐标
			 */
			yAxis: {
				type: 'category',
				data: stagesNameArr,
				//		show: false,
				axisTick: {
					show: false,
				},
				axisLine: {
					lineStyle: {
						color: '#e9e9ea',
						width: 1,
					},
				},
				axisLabel: {
					show: false,
					fontWeight: 'bold',
					fontSize: 16,
					color: '#1c2431',
					fontFamily: 'Microsoft YaHei',
				},
				splitLine: {
					show: false,
					lineStyle: {
						color: '#eaeae9',
						width: 1,
					},
				},
			},
			series: generatedSeries(newStages),
		};

		// 清除一下 echart 的缓存数据
		myBottomChart.clear();

		// 将构建好的配置项传入echarts
		myBottomChart.setOption(bottomChartOption);
	}

6.完整js(较长)

最后贴上完整js

// 传底部柱图阶段数据
var stages = [{
		"stageName": 'ghqd阶段',
		"stageTimes": ['2019-01-01', '2019-02-28']
	},
	{
		"stageName": 'ghdyzb阶段',
		"stageTimes": ['2019-02-28', '2019-05-01']
	},
	{
		"stageName": 'ghbz阶段',
		"stageTimes": ['2019-05-01', '2019-10-31']
	},
	{
		"stageName": 'ghsw阶段',
		"stageTimes": ['2019-10-31', '2020-03-31']
	}
];

/**
 * 横坐标轴时间刻度可选值
 * 这里 month和year 没有考虑平闰年之分
 */
var timeInterval = {
	day: 3600 * 1000 * 24,
	month: 3600 * 1000 * 24 * 31,
	year: 3600 * 1000 * 24 * 31 * 12,
};

/**
 * 阶段开始时间数组
 */
var stagesBeginTimeArr = new Array();
var stagesBeginTimeMillisArr = new Array();

/**
 * 阶段结束时间数组
 */
var stagesEndTimeArr = new Array();
var stagesEndTimeMillisArr = new Array();

/**
 * 阶段名称
 */
var stagesNameArr = new Array();

/**
 * 填充柱图数据
 * @param {Object} inputStages 输入阶段
 */
function fillBarChartData(inputStages) {
	stagesNameArr = [];
	stagesBeginTimeArr = [];
	stagesBeginTimeMillisArr = [];
	stagesEndTimeArr = [];
	stagesEndTimeMillisArr = [];
	for(var i = 0; i < inputStages.length; i++) {
		var stage = inputStages[i];
		stagesNameArr[i] = stage.stageName;

		stagesBeginTimeArr[i] = stage.stageTimes[0];
		stagesBeginTimeMillisArr[i] = getTimeMilliseconds(stage.stageTimes[0]);

		stagesEndTimeArr[i] = stage.stageTimes[1];
		stagesEndTimeMillisArr[i] = getTimeMilliseconds(stage.stageTimes[1]);
	}
}

/**
 * 时间对象转日期字符串 yyyy.MM.dd
 * @param {Object} timeObject 毫秒值或时间字符串
 */
function getSeriesDateStr(timeObject) {
	if(timeObject == "-") {
		return timeObject;
	}
	var date = new Date(timeObject);
	var dateStr = '';
	dateStr += date.getFullYear() + '.';
	dateStr += date.getMonth() + 1 + '.';
	dateStr += date.getDate();
	return dateStr;
};

/**
 * 获取随机16进制颜色
 */
function getRandomColor() {
	var color = '#';
	for(var i = 0; i < 6; i++) {
		color += (Math.random() * 16 | 0).toString(16);
	}
	if(color.toUpperCase() != '#FFFFF') {
		console.log(color);
		return color;
	} else {
		return getRandomColor();
	}
}

/**
 * 获取柱图单独系列的数据
 * @param {Object} inputStages 输入阶段
 * @param {Object} seriesRank 阶段在阶段数组中的索引
 */
function getSingleSeriesData(inputStages, seriesRank) {
	var dataArr = [];
	for(var j = 0; j < inputStages.length; j++) {
		if(seriesRank == j) {
			dataArr[j] = getTimeMilliseconds(inputStages[j].stageTimes[1]);
		} else {
			dataArr[j] = "-";
		}
	}
	console.log("dataArr--> " + dataArr);
	console.log("dataArr.length--> " + dataArr.length);

	return dataArr;
}

/**
 * 生成系列数据 
 * @param {Object} inputStages 输入阶段
 */
var generatedSeries = function(inputStages) {
	var series = new Array();
	// 占位白色柱图
	series[0] = {
		name: '开始时间',
		type: 'bar',
		stack: '时间',
		itemStyle: {
			normal: {
				barBorderColor: 'rgba(0,0,0,0)',
				color: 'rgba(0,0,0,0)'
			},
			emphasis: {
				barBorderColor: 'rgba(0,0,0,0)',
				color: 'rgba(0,0,0,0)'
			}
		},
		label: {
			normal: {
				formatter: function(params) {
					//					console.log("占位:" + JSON.stringify(params));
					var stageName = "";
					if(params != null) {
						stageName = params.name;
					}
					if(stageName == stagesNameArr[0]) {
						return getSeriesDateStr(params.value);
					}
					return "";

				},
				show: true,
				position: 'insideRight',
				fontSize: 16,
				color: '#1c2431',
				fontFamily: 'Microsoft YaHei',
				offset: [65, -20],
			}
		},
		data: stagesBeginTimeMillisArr,
	};

	// 占位白色柱图
	series[1] = {
		name: '开始时间1',
		type: 'bar',
		stack: '时间',
		itemStyle: {
			normal: {
				//				barBorderColor: 'rgba(0,0,0,0)',
				//				color: 'rgba(0,0,0,0)'
				color: '#4BB429'
			},
			emphasis: {
				barBorderColor: 'rgba(0,0,0,0)',
				color: 'rgba(0,0,0,0)'
			}
		},
		label: {
			normal: {
				formatter: function(params) {
					//					console.log("占位:" + JSON.stringify(params));
					var stageName = "";
					if(params != null) {
						stageName = params.name;
					}
					return stageName;

				},
				show: true,
				position: 'Right',
				fontSize: 16,
				color: '#1c2431',
				fontFamily: 'Microsoft YaHei',
				offset: [0, 25],
			}
		},
		data: stagesBeginTimeMillisArr,
	};

	// 循环填充数据
	for(var i = 0; i < inputStages.length; i++) {
		series[i + 2] = {
			name: stagesNameArr[i],
			type: 'bar',
			stack: '时间',
			itemStyle: {
				normal: {
					color: getRandomColor(),
				}
			},
			label: {
				normal: {
					formatter: function(params) {
						return getSeriesDateStr(params.value);
					},
					show: true,
					fontSize: 16,
					color: '#1c2431',
					position: 'insideRight',
					fontFamily: 'Microsoft YaHei',
					offset: [5, -20],
				}
			},
			data: getSingleSeriesData(inputStages, i),
			/**
			 * 柱状图宽度
			 */
			barWidth: 20,
		};
	}
	return series;
};

/**
 * 时间坐标轴标签单位应该精确到哪一位
 */
var xAxisLabelUnit = {
	year: false,
	month: false,
	day: false
}

/**
 * 获取横轴坐标数据源,这里横坐标只显示年月
 * 获取的是当前柱图横坐标轴上,对应时间轴上可见时间的数组
 * 最小值取传入数据最小的时间再减小一个月
 * 最大值取传入数据最小的时间再增加一个月
 */
function getXAxisData() {
	var arr = new Array();
	arr = arr.concat(stagesBeginTimeArr)
		.concat(stagesEndTimeArr)
		.filter(function(item) {
			return item != "-";
		}).sort();
	console.log(arr);
	return arr;
}

/**
 * 获取所有阶段的时间数组,这是过滤之前的原始数据
 */
function getTotalStagesTimeArr() {
	var arr = new Array();
	for(var i = 0; i < stages.length; i++) {
		arr.push(stages[i].stageTimes[0]);
		arr.push(stages[i].stageTimes[1]);
	}
	return arr.filter(function(item) {
		return item != "-";
	}).sort();
}

/**
 *获取时间坐标轴的起始和结束值
 */
function getProperTimeAxisBeginAndEndTime() {
	var xAxis = getXAxisData();
	var begin = xAxis[0];
	var end = xAxis[xAxis.length - 1];
	var beginDate = new Date(begin);
	var endDate = new Date(end);

	if(xAxisLabelUnit.month) {
		//		if(beginDate.getDate()<15){
		//			beginDate.setMonth(beginDate.getMonth()-1);	
		//		}
		beginDate.setDate(1);
		endDate.setMonth(endDate.getMonth() + 1);
		endDate.setDate(1);
	} else {
		var daysCount = getProperTimeAxisInterval() / timeInterval.day;
		console.log("daysCount " + daysCount);
		beginDate.setDate(beginDate.getDate() - daysCount);
		endDate.setDate(endDate.getDate() + daysCount);
	}

	var beArr = [formatDateToStr(beginDate), formatDateToStr(endDate)];
	console.log("beArr " + beArr);

	return beArr;
}

/**
 * 获取格式化的日期 YYYY-MM-dd
 */
function formatDateToStr(date) {
	var inputMonth = date.getMonth();
	var inputDate = date.getDate();
	var result = date.getFullYear() +
		"-" + (inputMonth >= 9 ? inputMonth + 1 : "0" + (inputMonth + 1)) +
		"-" + (inputDate >= 9 ? inputDate : "0" + (inputDate));
	return result;
}

/**
 * 根据时间字符串获取对应的毫秒值
 * @param {Object} timeStr 时间字符串
 */
function getTimeMilliseconds(timeStr) {
	return(new Date(timeStr)).getTime();
}

/**
 * 获取合适的横坐标时间刻度间隔
 */
function getProperTimeAxisInterval() {
	xAxisLabelUnit.year = false;
	xAxisLabelUnit.month = false;
	xAxisLabelUnit.day = false;

	var timeDataArray = getXAxisData();
	var begin = getTimeMilliseconds(timeDataArray[timeDataArray.length - 1]);
	console.log("begin " + begin);
	var periodMillis = getTimeMilliseconds(timeDataArray[timeDataArray.length - 1]) - getTimeMilliseconds(timeDataArray[0]);
	console.log("periodMillis " + periodMillis);
	var years = periodMillis / timeInterval.year;
	console.log("years " + years);
	var months = periodMillis / timeInterval.month;
	console.log("months " + months);
	var days = periodMillis / timeInterval.day;
	console.log("days " + days);

	if(months <= 1) {
		xAxisLabelUnit.day = true;
		return timeInterval.day * 2;
	} else if(months <= 16) {
		xAxisLabelUnit.month = true;
		return timeInterval.month;
	} else if(months <= 24) {
		xAxisLabelUnit.month = true;
		return timeInterval.month * 2;
	} else if(years <= 16) {
		xAxisLabelUnit.year = true;
		return timeInterval.year;
	}
}

/*顶部列表的数据源,和底部图表的数据源应分开,无论每个月份是否有事件,月份仍要显示,时间坐标轴也是如此*/
var topChartData = {
	events: [{
			/*事件时间*/
			eventTime: '2019-01-01',

			/*事件类型:
			 *因为现在有图片和文本两大类事件,
			 * 0:文本类型事件;
			 * 1:图片类型事件
			 */
			eventType: 1,

			/*图片类型事件中的具体事件类型,包括
			 * 0:cgfssp
			 * 1:ht
			 * 2:tqps
			 * 3:phgg
			 * 4:cggd
			 * 
			 * 文本类型事件可不传该字段
			 * */
			specificEventType: 0,

			/*事件标题
			 * 在事件为文本类型时,该值必填,
			 * 在事件为图片类型时,若传入空字符串,那么将自动按类型填充填充默认标题
			 */
			eventTitle: 'cgfssp',
		},
		{
			eventTime: '2019-01-10',
			eventType: 0,
			eventTitle: 'zb',
		},
		{
			eventTime: '2019-02-28',
			eventType: 1,
			specificEventType: 1,
			eventTitle: 'ht',
		},
		{
			eventTime: '2019-02-15',
			eventType: 0,
			eventTitle: 'zb',
		},
	]
};

/**
 * 获取 顶部图表月份数组
 */
function getTopCharMonthsTimeArr() {
	var arr = new Array;
	for(var i = 0; i < topChartData.events.length; i++) {
		arr[i] = topChartData.events[i].eventTime;
	}
	arr = arr.sort();
	console.log('topChartMonthsArr:' + arr);
	return arr;
}

/**
 * 获取顶部table中的图片类型消息的名称文本描述
 * 
 * 0:cgffsp
 * 1:ht
 * 2:tqps
 * 3:phgg
 * 4:cggd
 * 
 * @param {Object} specificEventType 图片类型事件中的具体事件类型
 */
function getTopTableImgTypeClassName(specificEventType) {
	switch(specificEventType) {
		case 0:
			return 'img_item_cg';
		case 1:
			return 'img_item_ht';
		case 2:
			return 'img_item_tqps';
		case 3:
			return 'img_item_phgg';
		case 4:
			return 'img_item_cggd';
		default:
			return '';
	}
}

/**
 * 加载顶部的图表数据
 */
function inflateTopTableChart() {
	// 获取事件数组
	var events = topChartData.events;
	for(var i = 0; i < timeLineDataArr.length; i++) {
		// 获取时间轴节点的年月
		var timeLineNodeItem = timeLineDataArr[i];
		var timeLineNodeItemDate = convertHanziDateToJSDate(timeLineNodeItem);
		var timeLineNodeMonth = timeLineNodeItemDate.getMonth() + 1;
		var timeLineNodeYear = timeLineNodeItemDate.getFullYear();

		// 将时间轴上的时间作为对应td的id,生成td时先查看该td是否已存在,不存在先生成
		if($('#' + timeLineNodeItem).length == 0) {
			// 需要先生成一个 td
			$('#tr_events').append('<td id="' +
				timeLineNodeItem +
				'"  class="month_td" title="' +
				timeLineNodeYear + "年" + timeLineNodeMonth + "月事件" +
				'"></td>');
		}
		// 顶部 table 中的 td右下角的月份
		$('#' + timeLineNodeItem).append('<div class="month_num_text_area"><span class="moth_number">' +
			timeLineNodeMonth +
			'</span><span>月</span></div>');

		// 获取事件的年月并对比
		for(var j = 0; j < events.length; j++) {
			var event = events[j];
			var eventTimeStr = event.eventTime;
			var eventTime = new Date(eventTimeStr);
			var eventYear = eventTime.getFullYear();
			var eventMonth = eventTime.getMonth() + 1;
			var eventDay = eventTime.getDate();

			// 同年同月
			if(timeLineNodeYear == eventYear && timeLineNodeMonth == eventMonth) {

				// 保证图片类型消息的的ul在上,文本类型的在下
				// 先看该td下是否已存在对应类型的 ul
				if($('#' + timeLineNodeItem + ' ul.img_list').length == 0) {
					//不存在的话,需要先生成一个,
					$('#' + timeLineNodeItem).append('<ul class="img_list"></ul>');
				}

				// 文本类型事件 ul
				// 先看该td下是否已存在对应类型的额 ul
				if($('#' + timeLineNodeItem + ' ul.text_list').length == 0) {
					//不存在的话,需要先生成一个,
					$('#' + timeLineNodeItem).append('<ul class="text_list"></ul>');
				}

				if(event.eventType == 1) {
					// 图片类型事件
					//在图片类型 ul 下拼接 图片类型消息li
					$('#' + timeLineNodeItem + ' ul.img_list').append('<li title="' +
						event.eventTitle +
						'" class="img_list_item ' +
						getTopTableImgTypeClassName(event.specificEventType) +
						'"><p class="case_type">' +
						event.eventTitle +
						'</p><p class="case_time">' +
						eventYear + '.' + eventMonth + '.' + eventDay +
						'</p><span class="img_wan"></span></li>');
				}

				if(event.eventType == 0) {
					// 文本类型事件
					//在文本类型 ul 下拼接 文本类型
					$('#' + timeLineNodeItem + ' ul.text_list').append('<li class="text_list_item" title="' +
						event.eventTitle +
						'"><span class="dot_in_text_item"></span><span>' +
						eventMonth + '.' + eventDay + ' ' + event.eventTitle + '</span></li>');
				}
			}
		}
	}
}

/**
 * 年月日中文格式日期转换成Date
 * @param {Object} timeStr 日期字符串
 */
function convertHanziDateToJSDate(timeStr) {
	return new Date(Date.parse(timeStr.replace('年', '-').replace('月', '-').replace('日', '')));
}

/**
 * 获取 顶部图表月份数组
 */
var topChartMonthsArr = getTopCharMonthsTimeArr();

/**
 * 获取指定时间的后十二个月份
 * @param {Object} timeStr
 */
function generateTimeLineDataArr() {
	// 底部所有阶段月份数组
	var bottomCharMonthsArr = getTotalStagesTimeArr();

	var arr = new Array();
	arr = arr.concat(topChartMonthsArr)
		.concat(bottomCharMonthsArr)
		.filter(function(item) {
			return item != "-";
		}).sort();
	console.log(arr);

	// 最小日期
	var minDate = new Date(arr[0]);
	// 最大日期
	var maxDate = new Date(arr[arr.length - 1]);
	// 循环填充时间轴数据应该到多少月份
	var loopEnd = 0;

	var diffyear = maxDate.getFullYear() - minDate.getFullYear();
	console.log("diffyear:" + diffyear);
	var diffmonth = diffyear * 12 + maxDate.getMonth() - minDate.getMonth();

	console.log("diffmonth->" + diffmonth);

	if(diffmonth <= 11) {
		loopEnd = 11;
	} else {
		loopEnd = diffmonth;
	}

	var result = [];
	result.push(minDate.getFullYear() + "年" + (minDate.getMonth() + 1) + "月");
	for(var i = 0; i < loopEnd; i++) {
		minDate.setMonth(minDate.getMonth() + 1); //每次循环一次 月份值+1
		var m = minDate.getMonth() + 1;
		result.push(minDate.getFullYear() + "年" + (m) + "月");
	}
	console.log("result-->" + result);
	return result;
}

/**
 * 时间轴数据源
 */
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]);
			}
		}
	});

	/**
	 * 过滤底部柱图阶段数据
	 * @param {Object} currentBarMinDate
	 * @param {Object} currentBarMaxDate
	 */
	function filterStagesAccordingTimeLineMinAndMaxVisibleTimeNode(currentBarMinDate, currentBarMaxDate) {
		// 过滤底部柱图的数据
		var barCharMinTime = convertHanziDateToJSDate(currentBarMinDate);
		var barCharMaxTime = convertHanziDateToJSDate(currentBarMaxDate);
		//		// 当前时间轴上显示的最大、最小年月
		var barCharMinYear = barCharMinTime.getFullYear();
		var barCharMinMonth = barCharMinTime.getMonth() + 1;
		var barCharMaxYear = barCharMaxTime.getFullYear();
		var barCharMaxMonth = barCharMaxTime.getMonth() + 1;

		var newStages = new Array();

		for(var i = 0; i < stages.length; i++) {
			var stage = stages[i];
			// 阶段开始时间年月
			var stageBeginTimeStr = stage.stageTimes[0];
			var stageEndTimeStr = stage.stageTimes[1];
			var stageBeginTime = new Date(stageBeginTimeStr);
			var stageEndTime = new Date(stageEndTimeStr);

			// 阶段结束时间年月
			var stageBeginYear = stageBeginTime.getFullYear();
			var stageBeginMonth = stageBeginTime.getMonth() + 1;
			var stageEndYear = stageEndTime.getFullYear();
			var stageEndMonth = stageEndTime.getMonth() + 1;

			if(stageBeginTime.getTime() >= barCharMinTime.getTime() &&
				stageEndTime.getTime() <= barCharMaxTime.getTime() ||
				(stageBeginYear == barCharMinYear && stageBeginMonth >= barCharMinMonth &&
					stageEndYear == barCharMaxYear && stageEndMonth <= barCharMaxMonth) ||
				(stageBeginYear > barCharMinYear && stageEndYear < barCharMaxYear)) {
				newStages.push(stage);
			}
		}
		console.log('newStages-->' + newStages);
		return newStages;
	}

	/**
	 * 当后退或前进时刷新底部的柱图
	 * @param {Object} currentBarMinDate 当前时间轴可见最小年月
	 * @param {Object} currentBarMaxDate 当前时间轴可见最大年月
	 */
	function refreshBottomBarChart(currentBarMinDate, currentBarMaxDate) {
		//		$("#chart_bottom").html(' ');
		var newStages = filterStagesAccordingTimeLineMinAndMaxVisibleTimeNode(currentBarMinDate, currentBarMaxDate);

		// 重新填充柱图数据
		fillBarChartData(newStages);

		// 初始化底部柱图图表
		var myBottomChart = echarts.init(document.getElementById('chart_bottom'));

		// 构建图表配置项
		bottomChartOption = {
			tooltip: {
				trigger: 'axis',
				axisPointer: {
					type: 'shadow',
				},

				/**
				 * 也可以使用 formatter: '{b0}:<br />{a0}: {c0}<br />{a1}: {c1}<br />{a2}: {c2}',
				 * 但是这样当鼠标指向纵坐标的三个阶段中的某一个时,即使该阶段 没有按时完成,或者 没有超时,
				 * 也会显示 按时 或 超时 的 tooltip
				 */
				formatter: function(params) {
					console.log("params111" + JSON.stringify(params));
					var info = params[0].axisValue + ":<br />";
					info += params[0].seriesName + ":" + getSeriesDateStr(params[0].value) + "<br />";
					for(var i = 0; i < params.length; i++) {
						if(params[i].seriesName == params[0].axisValue) {
							info += "结束时间:" + getSeriesDateStr(params[i].value);
						}
					}
					return info;
				},
			},

			/**
			 * 右上角工具栏
			 */
			toolbox: {
				right: '3%',
				show: false,
				feature: {
					/**
					 * 数据视图
					 */
					// 			dataView:{
					// 				show:true,
					// 			},
					saveAsImage: {
						show: true
					}
				}
			},

			/**
			 * 直角坐标系内绘图网格
			 */
			grid: {
				top: '0%',
				left: '0%',
				right: '3%',
				bottom: '0%',
				/**
				 * grid 区域是否包含坐标轴的刻度标签。
				 */
				containLabel: false,
			},

			/**
			 * 横坐标
			 */
			xAxis: {
				/**
				 * 时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,
				 * 在刻度计算上也有所不同,例如会根据跨度的范围来决定使用月,星期,日还是小时范围的刻度。
				 */
				type: 'time',

				/**
				 * value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数应该返回坐标轴的最大值。
				 *
				 * 坐标轴刻度最小值。
				 */
				min: getTimeMilliseconds(getProperTimeAxisBeginAndEndTime()[0]),

				/**
				 * value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数应该返回坐标轴的最大值。
				 *
				 * 坐标轴刻度最大值。
				 */
				max: getTimeMilliseconds(getProperTimeAxisBeginAndEndTime()[1]),

				/**
				 * 设置坐标轴分割间隔
				 */
				interval: getProperTimeAxisInterval(),
				axisLine: {
					lineStyle: {
						color: '#BDC8CD',
						width: 1,
					},
				},

				axisTick: {
					show: false,
				},

				/**
				 * 坐标轴刻度标签的相关设置。
				 */
				axisLabel: {
					show: false,
					showMinLabel: true,
					showMaxLabel: true,
					//			rotate: 35,
					margin: 12,
					fontSize: 16,
					color: '#1c2431',
					formatter: function(value, index) {
						var date = new Date(value);
						// var time = date.getFullYear() + "." + (date.getMonth() + 1) + "." + date.getDate();
						var time = date.getFullYear();
						if(xAxisLabelUnit.month) {
							time += "." + (date.getMonth() + 1);
						}
						if(xAxisLabelUnit.day) {
							time += "." + (date.getMonth() + 1) + '.' + date.getDate();
						}
						return time;
					},
				},

				/**
				 * 坐标轴刻度分割线
				 */
				splitLine: {
					show: false,
				},

			},

			/**
			 * 纵坐标
			 */
			yAxis: {
				type: 'category',
				data: stagesNameArr,
				//		show: false,
				axisTick: {
					show: false,
				},
				axisLine: {
					lineStyle: {
						color: '#e9e9ea',
						width: 1,
					},
				},
				axisLabel: {
					show: false,
					fontWeight: 'bold',
					fontSize: 16,
					color: '#1c2431',
					fontFamily: 'Microsoft YaHei',
				},
				splitLine: {
					show: false,
					lineStyle: {
						color: '#eaeae9',
						width: 1,
					},
				},
			},
			series: generatedSeries(newStages),
		};

		// 清除一下 echart 的缓存数据
		myBottomChart.clear();

		// 将构建好的配置项传入echarts
		myBottomChart.setOption(bottomChartOption);
	}

	// 初始化顶部table图表
	inflateTopTableChart();

	// 初始化时被过滤的stages,若stages时间跨度大于当前时间轴可见的时间区间,则会过滤掉区间之外的数据
	var initialFilterdStages = filterStagesAccordingTimeLineMinAndMaxVisibleTimeNode(timeLineDataArr[preIndex], timeLineDataArr[nextIndex])

	// 初始化柱图数据
	var initBarData = fillBarChartData(initialFilterdStages);

	// 初始化底部柱图图表
	var myBottomChart = echarts.init(document.getElementById('chart_bottom'));

	// 构建图表配置项
	bottomChartOption = {
		tooltip: {
			trigger: 'axis',
			axisPointer: {
				type: 'shadow',
			},

			/**
			 * 也可以使用 formatter: '{b0}:<br />{a0}: {c0}<br />{a1}: {c1}<br />{a2}: {c2}',
			 * 但是这样当鼠标指向纵坐标的三个阶段中的某一个时,即使该阶段 没有按时完成,或者 没有超时,
			 * 也会显示 按时 或 超时 的 tooltip
			 */
			formatter: function(params) {
				console.log("params111" + JSON.stringify(params));
				var info = params[0].axisValue + ":<br />";
				info += params[0].seriesName + ":" + getSeriesDateStr(params[0].value) + "<br />";
				for(var i = 0; i < params.length; i++) {
					if(params[i].seriesName == params[0].axisValue) {
						info += "结束时间:" + getSeriesDateStr(params[i].value);
					}
				}
				return info;
			},
		},

		/**
		 * 右上角工具栏
		 */
		toolbox: {
			right: '3%',
			show: false,
			feature: {
				/**
				 * 数据视图
				 */
				// 			dataView:{
				// 				show:true,
				// 			},
				saveAsImage: {
					show: true
				}
			}
		},

		/**
		 * 直角坐标系内绘图网格
		 */
		grid: {
			top: '0%',
			left: '0%',
			right: '3%',
			bottom: '0%',
			/**
			 * grid 区域是否包含坐标轴的刻度标签。
			 */
			containLabel: false,
		},

		/**
		 * 横坐标
		 */
		xAxis: {
			/**
			 * 时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,
			 * 在刻度计算上也有所不同,例如会根据跨度的范围来决定使用月,星期,日还是小时范围的刻度。
			 */
			type: 'time',

			/**
			 * value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数应该返回坐标轴的最大值。
			 *
			 * 坐标轴刻度最小值。
			 */
			min: getTimeMilliseconds(getProperTimeAxisBeginAndEndTime()[0]),

			/**
			 * value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数应该返回坐标轴的最大值。
			 *
			 * 坐标轴刻度最大值。
			 */
			max: getTimeMilliseconds(getProperTimeAxisBeginAndEndTime()[1]),

			/**
			 * 设置坐标轴分割间隔
			 */
			interval: getProperTimeAxisInterval(),
			axisLine: {
				lineStyle: {
					color: '#BDC8CD',
					width: 1,
				},
			},

			axisTick: {
				show: false,
			},

			/**
			 * 坐标轴刻度标签的相关设置。
			 */
			axisLabel: {
				show: true,
				showMinLabel: true,
				showMaxLabel: true,
				//			rotate: 35,
				margin: 12,
				fontSize: 16,
				color: '#1c2431',
				formatter: function(value, index) {
					var date = new Date(value);
					// var time = date.getFullYear() + "." + (date.getMonth() + 1) + "." + date.getDate();
					var time = date.getFullYear();
					if(xAxisLabelUnit.month) {
						time += "." + (date.getMonth() + 1);
					}
					if(xAxisLabelUnit.day) {
						time += "." + (date.getMonth() + 1) + '.' + date.getDate();
					}
					return time;
				},
			},

			/**
			 * 坐标轴刻度分割线
			 */
			splitLine: {
				show: false,
			},

		},

		/**
		 * 纵坐标
		 */
		yAxis: {
			type: 'category',
			data: stagesNameArr,
			//		show: false,
			axisTick: {
				show: false,
			},
			axisLine: {
				lineStyle: {
					color: '#e9e9ea',
					width: 1,
				},
			},
			axisLabel: {
				show: false,
				fontWeight: 'bold',
				fontSize: 16,
				color: '#1c2431',
				fontFamily: 'Microsoft YaHei',
			},
			splitLine: {
				show: false,
				lineStyle: {
					color: '#eaeae9',
					width: 1,
				},
			},
		},
		series: generatedSeries(initialFilterdStages),
	};

	// 将构建好的配置项传入echarts
	myBottomChart.setOption(bottomChartOption);
});

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

html 自定义简单的时间轴 timeline 并与 table 图表和 echarts 进度甘特图联动 的相关文章

  • vue2项目实现excel文件导入导出和拖拽上传

    文章目录 一 excle文件导出 二 excel文件导入 三 文件拖拽上传 四 完整代码 文件导入导出实现逻辑图 一 excle文件导出 导出员工接口返回的是二进制流 axios配置responseType为blob接收二进制流文件为Blo
  • vue 点击改变数组中选中的icon颜色(结合拖拽实现)

    1 vue 点击改变数组中选中的icon颜色 在Vue中 可以通过使用v bind指令来动态地修改元素的样式 要根据点击事件来改变数组中选中图标的颜色 首先需要定义一个data属性来存储当前被选中的索引值或者其他相关信息 然后 在模板中使用
  • Vue + Element-ui组件上传图片报错问题解决方案

    在使用Vue和Element ui组件上传图片时 可能会遇到一些报错问题 以下是一些常见的问题及解决方案 报错 TypeError Cannot read property name of undefined 解决方案 这个错误通常是因为在
  • CMAKE_MAKE_PROGRAM is not set 解读

    目录 CMAKE MAKE PROGRAM 未设置 错误原因 解决方案 示例1 GNU Make 示例2 Ninja CMakeLists txt 的结构 示例 CMakeLists txt 文件 总结 CMAKE MAKE PROGRAM
  • 从three.js旋转动画,我了解了requestAnimationFrame

    前言 大家好 我是南木元元 热衷分享有趣实用的文章 希望大家多多支持 一起进步 个人主页 南木元元
  • 拼多多详情API开启运营比价新纪元

    随着互联网的快速发展 电商行业正在迅速崛起 拼多多作为一家新兴的电商平台 凭借其独特的营销策略和创新的商业模式 成为了电商行业的一匹黑马 在拼多多的成功背后 其详情API接口营销起到了至关重要的作用 本文将详细介绍拼多多详情API接口营销的
  • ECMAScript简介及特性介绍

    ECMAScript 简称ES 是JavaScript的规范 同时也是被广泛采用和实现的脚本语言标准 从最初的1996年推出第一版至今 ECMAScript已经经历了数十年的发展和改进 成为了互联网开发中的重要基石之一 本文将对ECMASc
  • Web 安全漏洞之 OS 命令注入

    什么是 OS 命令注入 上周我们分享了一篇 Web 安全漏洞之 SQL 注入 其原理简单来说就是因为 SQL 是一种结构化字符串语言 攻击者利用可以随意构造语句的漏洞构造了开发者意料之外的语句 而今天要讲的 OS 命令注入其实原理和 SQL
  • WEB前端常见受攻击方式及解决办法总结

    一个网址建立后 如果不注意安全问题 就很容易被人攻击 下面讨论一下集中漏洞情况和放置攻击的方法 一 SQL注入 所谓的SQL注入 就是通过把SQL命令插入到web表单提交或输入域名或页面请求的查询字符串 最终达到欺骗服务器执行恶意的SQL命
  • 每天10个前端小知识 <Day 7>

    前端面试基础知识题 1 什么是尾调用优化和尾递归 尾调用的概念非常简单 一句话就能说清楚 就是指某个函数的最后一步是调用另一个函数 function f x return g x 上面代码中 函数f的最后一步是调用函数g 这就叫尾调用 尾调
  • HTML概述、基本语法(表格整理、标签、基本结构)

    一 HTML概述 HTML指的是超文本标记语言 超文本 是指页面内可以包含图片 链接 声音 视频等内容 标记 标签 通过标记符号来告诉浏览器页面该如何显示 我们可以打开浏览器 右击页面 点击 查看网页源代码 来方便了解HTML标签通过浏览器
  • 低代码-详情页组件设计

    效果图 详情页数据结构定义 layout 按钮数据 buttonLayout headButton 页头按钮 footButton 页脚按钮 详情页表单配置 config 配置组件列表 detailLayout 默认行为 进表单初始化 只展
  • Vue 如何使用WebSocket与服务器建立链接 持续保持通信

    WebSocket 浏览器通过JavaScript向服务器发出建立WebSocket链接的请求 链接建立后 客户端和服务器端就可以通过TCP链接直接交互数据 WebSocket链接后可以通过 send 方法来向服务器发送数据 并通过 onn
  • 考虑光伏出力利用率的电动汽车充电站能量调度策略研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 数据
  • Web自动化测试 —— cookie复用

    一 cookie简介 cookie是一些数据 存储于用户电脑的文本文件中 当web服务器想浏览器发送web页面时 在链接关闭后 服务端不会记录用户信息 二 为什么要使用Cookie自动化登录 复用浏览器仍然在每次用例开始都需要人为介入 若用
  • 深入解析 YAML 配置文件:从语法到最佳实践

    一 认识YAML YAML YAML Ain t Markup Language 是一种人类可读的数据序列化语言 它的设计目标是使数据在不同编程语言之间交换和共享变得简单 YAML采用了一种简洁 直观的语法 以易于阅读和编写的方式表示数据结
  • javaFX 中的抖动阶段

    是否可以使用时间轴来摇动初级阶段 从而使用 XTimeline 和 YTimeLine final Timeline Xtimeline new Timeline for Animate On X Stage Xtimeline setCy
  • 为什么 Chrome 需要 12 秒来更新图层树?

    我有一个相对不复杂的 ASP Net 应用程序 它偶尔会在层中显示一条警报消息 带有阴影 并带有一个按钮来消除通知 这段代码多年来一直运行良好 最近 我收到用户抱怨响应速度非常慢 他们的体验是页面完全没有响应 并且当他们单击 确定 按钮时很
  • 如何使用 R 个性化时间线?

    我想用 R 绘制一个时间线 其中的时间段很容易识别 我可以在其中个性化可视化 periods 周期 盒子 的颜色 线条 颜色 位置 文本的位置并将其放入 框 中 轴 大小 颜色 选择重点 与事件相关的日期 etc 我使用时间线库 但是我找不
  • 我可以在 jQuery 中为每个动画步骤实现回调吗?

    我想在我的网站中实现一个动画 需要更新多个 DOM 元素 每个 DOM 元素都有自己的动画路径 取决于它们的位置 并且仍然有缓和的效果 如果我为每个元素调用 jQuery 的 animate 函数 队列 false 它将使每个元素与其余元素

随机推荐

  • iOS App上传到苹果应用市场构建版本的图文教程

    使用hbuilderx的h5 或uniapp框架写的前端 进行云打包ios应用 会生成一个ipa后缀的应用文件 这个文件是没有办法像安卓应用那样直接安装在手机上面的 需要上架到苹果应用商店 用户才能下载安装使用 因此 我们这篇文章讲详细介绍
  • 5G基础信令

    一 4 5G高层协议规范框架对比 4G 5G 36 300 LTE整体 38 300 NR整体 36 401 E URTAN整体架构 38 401 NG RAN整体架构 36 321 LTE MAC 38 321 NR MAC 36 322
  • Dockerfile部署mysql并初始化

    文件目录结构 Dockerfile FROM centos 7 ADD jdk 8u261 linux x64 tar gz usr local ADD check mysql sh home datasong release bin CO
  • Gogs使用详解

    Gogs使用介绍 Gogs是一款类似Github 国内有码市 的开源文件 代码管理系统 基于Git 目前功能基本介绍 远程代码仓库管理 代码仓库权限分配 管理 团队管理 代码审查 1 注册 2 基本功能介绍 主面板说明 图中 表示自己个人账
  • 【测试入门】测试用例经典设计方法 —— 因果图法

    01 因果图设计测试用例的步骤 1 分析需求 阅读需求文档 如果User Case很复杂 尽量将它分解成若干个简单的部分 这样做的好处是 不必在一次处理过程中考虑所有的原因 没有固定的流程说明究竟分解到何种程度才算简单 需要测试人员根据自己
  • 直线检测方法—LSD论文翻译

    附原文链接 LSD a Line Segment Detector 摘 要 LSD是一个线段检测器 能够在线性时间内得到亚像素级精度的检测结果 它无需调试参数就可以适用于任何数字图像上 并且能够自我控制错误数量的检测 平均来说 一个图像中允
  • Bazel install Tips

    Bazel Fast Correct Choose two Build and test software of any size quickly and reliably Speed up your builds and tests Ba
  • Android接口一般定义格式,Android开发规范

    原标题 Android开发规范 一 书写规范 1 编码方式统一用UTF 8 2 花括号不要单独一行 和它前面的代码同一行 而且 花括号与前面的代码之间用一个空格隔开 3 空格的使用 if else for switch while等逻辑关键
  • 一个不错的关于CPU和GPU(CUDA)的性能比较讨论话题

    http topic csdn net u 20081027 23 67ff3857 3c71 4d5c acf6 095f3497c7a9 html这里是今天的一个论坛的一个帖子 大家可以讨论一下 1 那些程序适合用cpu来做 那些适合用
  • 【Transformer系列】深入浅出理解Tokenization分词技术

    一 参考资料 NLP技术中的Tokenization是什么 核心任务是什么 二 Tokenization相关介绍 1 Tokenization的概念 NLP技术中Tokenization被称作是 word segmentation 直译为分
  • 深入理解工具链-自己搭建STM32编程IDE

    目录 一 前言 二 编译器组成与编译流程 2 1 编译流程概述 2 2 Gcc For Arm编译器 2 3 预编译 2 4 编译 2 5 汇编 2 6 链接 2 7 生成HEX镜像 2 8 通过Makefile编译代码 三 调试流程 3
  • 解决在使用 Ant Design Vue组件库更改Modal对话框样式使用深度作用选择符不生效的问题

    项目场景 在使用 Ant Design Vue组件库更改Modal对话框样式使用深度作用选择符不生效 问题描述 Modal对话框官方样式如下图 现在要修改为下图所示样式 使用深度作用选择符样式没有生效 原因分析 因为modal是直接插入到b
  • 【SQL】10 SQL UPDATE 语句

    UPDATE 语句用于更新表中的记录 SQL UPDATE 语句 UPDATE 语句用于更新表中已存在的记录 SQL UPDATE 语法 UPDATE table name SET column1 value1 column2 value2
  • Flink消费kafka出现空指针异常

    文章目录 出现场景 表现 问题 解决 tombstone Kafka中提供了一个墓碑消息 tombstone 的概念 如果一条消息的key不为null 但是其value为null 那么此消息就是墓碑消息 出现场景 双流join时 采用的是l
  • 此av非彼"AV"

    作者 王亨 R语言中文社区专栏作者 跟着菜鸟一起一步步学习R语言 争做R语言高手 个人公众号 跟着菜鸟一起学R语言 微信ID learn R 最近发现一个特别有意思的包 av 为什么有意思 首先名字有意思吧 其次这个包可以捕获图像 添加背景
  • 华为OD机试真题(Java),最小步骤数(100%通过+复盘思路)

    一 题目描述 一个正整数数组 设为nums 最大为100个成员 求从第一个成员开始正好走到数组最后一个成员所使用的最小步骤数 要求 第一步 必须从第一元素起 且 1 lt 第一步步长
  • Java企业微信开发_01_接收消息服务器配置

    一 准备阶段 需要准备事项 1 一个能在公网上访问的项目 见 Java微信公众平台开发01本地服务器映射外网 http www cnblogs com shirui p 7308856 html 2 一个企业微信账号 去注册 https w
  • 链式存储结构-----栈

    链式存储结构实现栈 上一节我们说了关于线性表的链式存储结构的实现 今天的栈也是建立在线性表的基础上 栈的特性 先进后出 1 删除时 出栈 我们考虑时间复杂度时发现 删除时的头删的复杂度为O 1 而尾删的时间复杂度为O n 故而我们出栈选择从
  • 【操作系统】键盘敲入字母时,操作系统期间发生了什么?

    操作系统 键盘敲入字母时 操作系统期间发生了什么 参考资料 键盘敲入 A 字母时 操作系统期间发生了什么 操作系统 浅谈 Linux 中的中断机制 文章目录 操作系统 键盘敲入字母时 操作系统期间发生了什么 设备控制器 I O 控制方式 设
  • html 自定义简单的时间轴 timeline 并与 table 图表和 echarts 进度甘特图联动

    1 需求 最近有需求需要实现 table 图表与 eharts 柱状图的联动 完整的效果图如下所示 这里时间轴要实现的效果要基本如下图所示 该时间轴并不是要实现选中单独的某一个月份并查看单月的数据 而是要将当前数据的时间跨度控制在时间轴的跨