/** * 对Date的扩展,将 Date 转化为指定格式的String * 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符, * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) * 例子: * (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 * (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18 * @param fmt 日期格式 */ Date.prototype.format = function (fmt) { var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 "h+": this.getHours(), //小时 "m+": this.getMinutes(), //分 "s+": this.getSeconds(), //秒 "q+": Math.floor((this.getMonth() + 3) / 3), //季度 "S": this.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; } /** * 字符串转换为日期对象 * @param dateStr Date 格式为yyyy-MM-dd HH:mm:ss,必须按年月日时分秒的顺序,中间分隔符不限制 */ Date.prototype.strToDate = function (dateStr) { var data = dateStr var reCat = /(\d{1,4})/gm var t = data.match(reCat) t[1] = t[1] - 1 eval('var d = new Date(' + t.join(',') + ')') return d } /** * 获取日期列表,不传参默认当前时间为截止日期,去年的今天为起始日期 * @param dateStart 起始日期 格式为yyyy-MM-dd,必须按年月日的顺序,中间分隔符不限制 * @param dateEnd 截止日期 格式为yyyy-MM-dd,必须按年月日的顺序,中间分隔符不限制 */ Date.prototype.getDateList = function (dateStart, dateEnd) { try { var date, diff, list = {} // 缺一不可,缺任何一个都采取默认时间 if (!dateStart || !dateEnd) { // 当前时间 date = new Date() // 当前时间往前推一年 date.setFullYear(date.getFullYear() - 1) // 如果去年的今天不是星期天的话,补充天数直到起始日期是星期天为止 while (date.getDay() > 0) { date.setDate(date.getDate() - 1) } // 计算相差的天数 diff = parseInt((new Date().getTime() - date.getTime()) / (1000 * 60 * 60 * 24)) } else { // 转换为日期对象 var start = this.strToDate(dateStart) var end = this.strToDate(dateEnd) // 补充天数直到起始日期是星期天为止 while (start.getDay() > 0) { start.setDate(start.getDate() - 1) } // 计算相差的天数 diff = parseInt((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) date = start } // 实际上连最后一天也要算上,所以diff加2 for (i = 1; i < diff + 2; i++) { list[date.format("yyyy-MM-dd")] = date.getDay() date.setDate(date.getDate() + 1) } return list } catch(e) { return {} } } var weekMap = ['周日','周一', '周二', '周三', '周四', '周五', '周六'] var monthMap = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'] var colorMap = { 'blue': ['#003C9D', '#409EFF', '#87CEFA', '#E0FFFF', '#EBEDF0'], 'pink': ['#990099', '#CC00CC', '#FF88C2', '#FFB7DD', '#EBEDF0'], 'green': ['#196127', '#239A3B', '#7BC96F', '#C6E48B', '#EBEDF0'], 'orange': ['#A42D00', '#CC6600', '#EE7700', '#FFAA33', '#EBEDF0'], 'gray': ['#303133', '#444444', '#808080', '#C0C0C0', '#EBEDF0'] } // 合并两个object,以mainObj为基准 function matchObj(mainObj, obj) { var resultObj = {} for (var key in mainObj) { if (!obj.hasOwnProperty(key)) { resultObj[key] = mainObj[key] } else if (Object.prototype.toString.call(mainObj[key]) === '[Object Object]' && key !== 'data') { resultObj[key] = matchObj(mainObj[key], obj[key]) } else { resultObj[key] = obj[key] } } return resultObj } function HeatMapDate() { this.option = { type: 'date', // 类型:date-日历型,custom-自定义型 xAxis: [], // 横坐标的label,type=custom起作用 yAxis: [], // 纵坐标的label,type=custom起作用 gap: 3, // 方格之间的间隔 /* 数据 * 如果type是date。那data是Object类型,格式是{ 'yyyy-MM-dd' : value } * 如果type是custom。那data是Array类型,格式是[[x, y, value], ..., [x, y, value]] */ data: {}, rect: { stroke: { show: false, background: '#333', // 正方形的边框颜色 opacity: 0.6 // 正方形的边框透明度 }, colourMatching: '', //配色方案,有custom-自定义和reen、pink、blue、orange、gray五种渐变色 backgroundArr: [] // 自定义配色方案,程度由重到轻 }, dateStart: '', dateEnd: '', min: 0, // 分级最低值,总共五个等级,不传默认值是0 max: 0, // 分级最高值,总共五个等级,不传默认值是 tip: { // 顶部鼠标悬浮小气泡 show: true, // 是否展示 /** 文本内容 * type为date的时候{a}表示日期,{b}表示数值 * type为custom的时候表示{x}x轴对应的值,{y}y轴对应的值,{b}表示数值 * 如果在替换字符串前加反斜杠(例如/{b}),则不会替换该字符串 */ formatter: '' } } // 初始化参数,没传的就使用默认值 this.setOption = (obj) => { this.option = matchObj(this.option, obj) if (!this.option.max) { this.option.max = Object.values(obj.data).sort(function(a, b) { return b - a })[0] || 0 } } this.init = (dom) => { // 初始化dom的样式 dom.setAttribute('style', 'width:170%;height:100%;position:relative;') // 获取父级dom的宽度 var parentWidth = dom.offsetWidth // 通过createElementNS创建svg元素并设置属性 var svg = document.createElementNS('http://www.w3.org/2000/svg','svg') svg.setAttribute('version', '1.1') svg.setAttribute('class', 'svg-container') dom.appendChild(svg) // 挂载元素。SVG元素添加到页面内显示 // 显示顶部提示小气泡 if (this.option.tip.show) { // 创建tip容器并设置属性 var tip = document.createElement('div') tip.setAttribute('class', 'svg-tip svg-tip-one-line') var title = document.createElement('strong') tip.appendChild(title) // 挂载到父节点上 dom.appendChild(tip) // 挂载元素。挂载顶部提示气泡 } // 创建svg的group元素并设置属性 var group = document.createElementNS('http://www.w3.org/2000/svg', 'g') var translateX = 20 // 横轴方向的偏移值 var translateY = 40 // 纵轴方向的偏移值 // var translateX = 10 // 横轴方向的偏移值 // var translateY = 20 // 纵轴方向的偏移值 group.setAttribute('transform', 'translate(' + translateX + ',' + translateY + ')') svg.appendChild(group) // 挂载到父节点上 var maxYLabelFontSize = 12 var labelPadding = 10 var maxStrLength = 0 if (this.option.type === 'date') { // 获取全部天数列表 var dateList = (new Date()).getDateList(this.option.dateStart, this.option.dateEnd) var columnCount = Math.ceil(Object.keys(dateList).length / 7) var size = Math.floor((parentWidth - translateX / 2 - maxYLabelFontSize * 2 - labelPadding - this.option.gap * columnCount) / columnCount) var section = size + this.option.gap // 纵轴的label值,这里是星期值 for (var w = 0; w < 7; w++) { //创建矩形元素并设置属性 var yText = document.createElementNS('http://www.w3.org/2000/svg', 'text') yText.style.fontSize = section * 0.7 > maxYLabelFontSize ? maxYLabelFontSize : section * 0.7 // 字体大小响应,最大是12px yText.setAttribute('dx', -labelPadding) yText.setAttribute('dy', w * section + size / 2 + 4) yText.setAttribute('class', 'wday') yText.innerHTML = weekMap[w] group.appendChild(yText) if (maxStrLength < yText.getBBox().width) { maxStrLength = yText.getBBox().width } } var index = 0 // 天数列表索引 var column = 0 // 分组索引,完整的一周为一组 for (var dateKey in dateList) { // 完整的一周为一组 if (index === 0 || index % 7 === 0) { // 创建svg的group元素并设置属性,一周为一组 var xGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g') // 设置偏移值 xGroup.setAttribute('transform', 'translate(' + (column * section + maxStrLength) + ', 0)') group.appendChild(xGroup) // 挂载到父节点 column++ // 递增组数索引 // 判断在哪个分组的上方增加横轴的label值,在这里是月份 if (index > 0) { var startMonth = dateKey.split('-')[1] var preStartMonth = Object.keys(dateList)[index - 7].split('-')[1] if (Math.abs(Number(startMonth) - Number(preStartMonth)) > 0) { //创建text元素并设置属性 var fontSize = section * 0.8 > maxYLabelFontSize ? maxYLabelFontSize : section * 0.8 // 字体大小响应,最大是14px var xText = document.createElementNS('http://www.w3.org/2000/svg', 'text') xText.style.fontSize = fontSize xText.setAttribute('x', column * section) xText.setAttribute('y', -labelPadding) xText.setAttribute('class', 'month') xText.innerHTML = monthMap[Number(startMonth) - 1] group.appendChild(xText) } } } if(this.option.data.hasOwnProperty(dateKey)) { // 开始画正方形啦~创建矩形元素并设置属性 var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') rect.setAttribute('x', 0) rect.setAttribute('y', dateList[dateKey] * section) rect.setAttribute('id', dateKey) // 设置日期为id值 rect.setAttribute('week', dateList[dateKey]) // 设置星期几属性 rect.setAttribute('column', column) // 设置分组的组索引属性 rect.setAttribute('width', size) rect.setAttribute('height', size) color = '#fff' // 默认颜色是白色,就是啥也没有时候的颜色,以你的背景色为准 var colorSelect = '' // 选择的颜色系列 if (this.option.rect.colourMatching === 'custom') { colorSelect = this.option.rect.backgroundArr } else { colorSelect = colorMap[this.option.rect.colourMatching] } // 默认绿色为基本配色方案 colorSelect = colorSelect ? colorSelect : colorMap['green'] // 分为五个等级,小于最小值最低等级,大于最大值最高等级。中间还应该有三个等级(可自定义) var levelGap = (this.option.max - this.option.min) / (colorSelect.length - 2) if (this.option.data.hasOwnProperty(dateKey)) { var differ = this.option.max - this.option.data[dateKey] // 小正方形的颜色决定于他的值处在哪个level里头 switch(true) { case (this.option.data[dateKey] >= this.option.max) : color = colorSelect[0] break case (differ < levelGap) : color = colorSelect[1] break case (differ < levelGap * 2) : color = colorSelect[2] break case (differ < levelGap * 3) : color = colorSelect[3] break default: color = colorSelect[4] } // 设置小矩形的颜色 rect.setAttribute('style', 'fill:' + color) // 显示顶部提示小气泡 if (this.option.tip.show) { //矩形元素绑定鼠标事件实现动态效果 // 鼠标移入 rect.onmouseover = (e) => { if (this.option.rect.stroke.show) { e.srcElement.setAttribute('stroke-width', 1) e.srcElement.setAttribute('stroke', this.option.rect.stroke.background) e.srcElement.setAttribute('stroke-opacity', this.option.rect.stroke.opacity) } // 提示小气泡的文本内容,默认==>日期:值 if (this.option.tip.formatter) { var tipText = this.option.tip.formatter.replace(/(? { if (this.option.rect.stroke.show) { e.srcElement.setAttribute('stroke-width', 0) } tip.style.display = 'none' } } } xGroup.appendChild(rect) //挂载矩形元素添加到小分组元素内 } index++ } // 设置svg元素的宽高 svg.style.width = section * columnCount + translateX / 2 + maxStrLength + labelPadding svg.style.height = section * 7 + translateY } if (this.option.type === 'custom') { var size = parseInt(parentWidth / this.option.xAxis.length) - this.option.gap * this.option.xAxis.length var section = size + this.option.gap var fontSize = section * 0.7 > maxYLabelFontSize ? maxYLabelFontSize : section * 0.7 // 字体大小响应,最大是12px this.option.xAxis.forEach((xItem,xIndex) => { var xGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g') group.appendChild(xGroup) this.option.yAxis.forEach((yItem,yIndex) => { if (xIndex === 0) { //创建矩形元素并设置属性 var yText = document.createElementNS('http://www.w3.org/2000/svg', 'text') yText.style.fontSize = fontSize yText.setAttribute('dx', -labelPadding) yText.setAttribute('dy', yIndex * section + size / 2 + 4) yText.setAttribute('class', 'wday') yText.innerHTML = yItem group.appendChild(yText) if (maxStrLength < yText.getBBox().width) { maxStrLength = yText.getBBox().width } } this.option.data.some((elem, i) => { if (elem[0] === xIndex && elem[1] === yIndex) { color = 'rgb(255, 255, 255)' // 默认颜色是白色,就是啥也没有时候的颜色,以你的背景色为准 var colorSelect = '' // 选择的颜色系列 if (this.option.rect.colourMatching === 'custom') { colorSelect = this.option.rect.backgroundArr } else { colorSelect = colorMap[this.option.rect.colourMatching] } // 默认绿色为基本配色方案 colorSelect = colorSelect ? colorSelect : colorMap['green'] // 分为五个等级,小于最小值最低等级,大于最大值最高等级。中间还应该有三个等级(可自定义) var levelGap = (this.option.max - this.option.min) / (colorSelect.length - 2) var differ = this.option.max - elem[2] // 小正方形的颜色决定于他的值处在哪个level里头 switch(true) { case (elem[2] >= this.option.max) : color = colorSelect[0] break case (differ < levelGap) : color = colorSelect[1] break case (differ < levelGap * 2) : color = colorSelect[2] break case (differ < levelGap * 3) : color = colorSelect[3] break default: color = colorSelect[4] } //创建矩形元素并设置属性 var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') rect.setAttribute('x', 0) rect.setAttribute('y', elem[1] * section) rect.setAttribute('id', elem[0] + '_' + elem[1]) // 设置日期为id值 rect.setAttribute('row', yIndex) rect.setAttribute('column', xIndex) rect.setAttribute('width', size) rect.setAttribute('height', size) rect.setAttribute('style', 'fill:' + color) // 显示顶部提示小气泡 if (this.option.tip.show) { //矩形元素绑定鼠标事件实现动态效果 // 鼠标移入 rect.onmouseover = (e) => { if (this.option.rect.stroke.show) { e.srcElement.setAttribute('stroke-width', 1) e.srcElement.setAttribute('stroke', this.option.rect.stroke.background) e.srcElement.setAttribute('stroke-opacity', this.option.rect.stroke.opacity) } var xyArr = e.target.id.split('_') // 提示小气泡的文本内容,默认==>x_y:值 if (this.option.tip.formatter) { var tipText = this.option.tip.formatter.replace(/(? { if (this.option.rect.stroke.show) { e.srcElement.setAttribute('stroke-width', 0) } tip.style.display = 'none' } } //将矩形元素添加到SVG元素内 xGroup.appendChild(rect) return true } else { return false } }) }) xGroup.setAttribute('transform', 'translate(' + (xIndex * section + maxStrLength) + ', 0)') //创建text元素并设置属性 var xText = document.createElementNS('http://www.w3.org/2000/svg', 'text') xText.style.fontSize = fontSize xText.setAttribute('x', xIndex * section + maxStrLength) xText.setAttribute('y', -labelPadding) xText.setAttribute('class', 'month') xText.innerHTML = xItem group.appendChild(xText) xText.setAttribute('textLength', size < xText.getBBox().width ? size : xText.getBBox().width) }) // 设置svg元素的宽高 svg.style.width = section * this.option.xAxis.length + translateX / 2 + labelPadding + maxStrLength svg.style.height = section * this.option.yAxis.length + translateY } } }