仿照git馬賽克牆用JS&SVG實現的數據可視化熱力圖工具

仿照git馬賽克牆實現的JS原生熱力圖數據可視化工具,支持自定義日期、支持自定義橫縱座標等javascript

先上效果圖css

  • 日曆型熱點圖

日曆型

  • 自定義型熱點圖,能夠本身定義橫縱座標

自定義型

說一說實現的心理歷程,原本想像碼雲那個同樣用div來實現的,可是腦子靈光一閃,我怎麼不用canvas試一試呢,以前沒什麼機會接觸過,恰好來練練手。 因而就實現了一波:java

this.init = (dom) => {
    var section = this.option.size + this.option.gap
    dom.width = section * this.option.xAxis.length
    dom.height = section * this.option.yAxis.length
    // 調用canvas對象的getContext()方法,c變量就包含了指向2d渲染環境的引用
    var context = dom.getContext('2d')
    var levelGap = (this.option.max - this.option.min) / 4
    this.option.data.forEach((item) => {
      // [x, y, value]
      var differ = this.option.max - item[2]
      switch(true) {
        case (item[2] >= this.option.max) :
          color = 'rgb(25, 97, 39)'
          break
        case (differ < levelGap) :
          color = 'rgb(35, 154, 59)'
          break
        case (differ < levelGap * 2) :
          color = 'rgb(123, 201, 111)'
          break
        case (differ < levelGap * 3) :
          color = 'rgb(198, 228, 139)'
          break
        default:
          color = 'rgb(235, 237, 240)'
      }
      context.fillStyle = color
      context.fillRect(item[0] * section, item[1] * section, this.option.size, this.option.size)
    })
  }
複製代碼

寫到一半,發現不行啊,人家canvas是一整張畫布,可是我是須要一個個小方格能夠實現鼠標懸浮顯示詳細信息的呀,不妥不妥。因而我又想到了svg這個好傢伙,F12看了下git,正好人家也是使用svg實現的。git

實現過程不復雜,就是比較繞,感興趣怎麼實現的小夥伴,我把項目地址貼出來了噢。若是個人組件是你須要用到的,那歡迎使用哈哈哈哈~~,下面說一說怎麼使用,還有一些思考 github

1、項目地址

仿照git馬賽克牆實現了個JS原生小工具,支持自定義日期、支持自定義橫縱座標npm

2、使用方法

使用起來很簡單,只要引入css文件和js文件便可canvas

2.1 引入css和js

引入css:bash

<link rel="stylesheet" href="./heatMap.css" />
複製代碼

引入js:數據結構

<script src="./heatMap.js"></script>
複製代碼

2.2 初始化heatMap

<script>
  var heatMap = new HeatMapDate()
  var option = {
    gap: 6,
    type: 'custom',
    xAxis: ['11', '22', '33', '44'],
    yAxis: ['aa', 'bb'],
    data: [
      [0, 0, 0],
      // ...
      [3, 1, 1]
    ],
    min: 0,
    max: 6,
    tip: {
      show: true,
      formatter: '第{y}月的第{x}天有{b}個提交'
    }
  }
  heatMap.setOption(option)
  heatMap.init(document.getElementById('mySvg'))
<\script>
複製代碼

3、配置參數說明

3.1 初始化參數,需傳入配置參數option:

heatMap.setOption(option)
複製代碼

3.2 初始化熱力圖,需傳入須要掛載的dom節點:

heatMap.init(dom)
複製代碼

3.3 option參數說明

參數 說明 類型 可選值 默認值
type 熱力圖類型,分兩種,date-日曆型和custom-自定義型 String date/custom date
xAxis 橫座標的label,當type爲custom類型時須要傳遞該參數 Array - -
yAxis 縱座標的label,當type爲custom類型時須要傳遞該參數 Array - -
gap 方格之間的間隔 Number - 3
data 數據,若是type是date,那data是Object類型,格式{yyyy-MM-dd: value ...};若是type是custom,那data是Array類型,格式[[x,y,value]...] Object/Array - -
dateStart 當type爲date類型時起做用,表示起始日期,日期格式:yyyy-MM-dd String - 去年的今天
rect 方格的相關屬性 Object - -
dateEnd 當type爲date類型時起做用,表示結束日期,日期格式:yyyy-MM-dd String - 今天
min 分級的最低值,默認總共五個等級 Number - 0
max 分級的最高值,默認總共五個等級 Number - data裏頭的值的最大值
tip 方格頂部鼠標懸浮小氣泡的相關屬性 Object - -
tip.show 鼠標懸浮是否顯示小氣泡 Boolean true/false true
tip.formatter 小氣泡的文本內容。type爲date的時候{a}表示日期,{b}表示數值;type爲custom的時候{x}表示x軸對應的值,{y}對應y軸的值,{b}表示數值;若是在替換字符串前加反斜槓(例如/{b}),則不會替換該字符串 String - -
rect 方格的相關屬性 Object - -
rect.stroke 方格邊框的相關屬性 Object - -
rect.stroke.show 是否顯示方格邊框 Boolean true/false false
rect.stroke.background 方格邊框顏色 String - #333333
rect.stroke.opacity 方格邊框透明度 Float 0~1 0.6
rect.colourMatching 方格配色方案,能夠自定義(custom)也可使用現有的配色方案 String custom/green/pink/blue/orange/gray green
rect.stroke.backgroundArr 當type爲custom類型時起做用,方格配色方案具體顏色,多少個顏色就表示多少個等級,等級由重到輕,第一個顏色表示等級最重 Array - -

4、總結與思考

說一說實現過程當中的痛點:app

  1. 日曆型和自定義型的數據結構必須不一樣。邏輯得分開寫。

日曆型我知道橫軸就是月份,縱軸就是星期,因此我new新的熱力圖的時候,傳的數據裏頭只要有日期和值就行。但自定義就不同了,自定義的橫縱座標都是後來定義的,因此爲了知道具體的哪一個小方格的值,傳的數據裏頭必須像[x, y, value]這樣的結構。

  1. 日期的輪詢

日曆型熱力圖默認是當前時間往前推一年這樣一個時間跨度,因此我必須輪詢出這一年裏的全部日期,再拼裝組合起來。後期考慮到了靈活性,因此這個時間跨度也是能夠自定義的。

  1. 小方格的size的肯定。

小方格的size關乎到左邊區域的大小。總的來講就是當前容器的寬度減去左邊區域大小,再除以列數,寫的時候的邏輯比較繞,跟寫高數題似的,想起了當年被支配的恐懼。。

  1. 頂部懸浮氣泡的位置肯定。

頂部懸浮氣泡實際上我只定義了一個容器,每次調用的時候,改變定位和文字內容。

  1. 配色自定義

配色這一塊,有多少個配色就有多少個等級。我不但願把顏色定死,因此本身組合定義了五種配色方案:green、pink、blue、orange、gray。考慮到有改變等級和配色的須要,配置項能夠自定義。

總的來講,實現的思路並不難,就是實現的過程比較蛋疼。後期我打算增長一些自定義功能,而後作成根據屏幕大小來動態改變佈局。有想法將它封裝成npm工具,也算是個人第一個正兒八經開源小工具啦,源碼我已經貼出了來,有須要一塊兒學習的小夥伴自行clone~~筆芯

5、主要代碼heatMap.js

這就是我實現這個小工具的最核心代碼:

/** * 對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:100%;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 // 縱軸方向的偏移值
    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(/(?<!\/){a}/g, e.target.id).replace(/(?<!\/){b}/g, this.option.data[e.target.id])
                  tip.innerHTML = tipText
                } else {
                  tip.innerHTML = e.target.id + ':' + this.option.data[e.target.id]
                }
                tip.style.display = 'block'
                tip.style.top = (e.target.attributes.week.value * section + dom.querySelector('.svg-tip').offsetHeight / 2 - translateY / 2 - labelPadding) + 'px'
                tip.style.left = ((e.target.attributes.column.value - 1) * section + size / 2 + translateX / 2 + maxStrLength + labelPadding - dom.querySelector('.svg-tip').offsetWidth / 2) + 'px'
              }
              // 鼠標移出
              rect.onmouseout = (e) => {
                if (this.option.rect.stroke.show) {
                  e.srcElement.setAttribute('stroke-width', 0)
                }
                tip.style.display = 'none'
              }
            }
          }
          xGroup.appendChild(rect) //掛載矩形元素添加到小分組元素內
        }
        index++
      }

      // 設置svg元素的寬高
      svg.style.width = Math.ceil(section * columnCount + translateX + 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(/(?<!\/){x}/g, this.option.xAxis[xyArr[0]]).replace(/(?<!\/){y}/g, this.option.yAxis[xyArr[1]]).replace(/(?<!\/){b}/g, elem[2])
                    tip.innerHTML = tipText
                  } else {
                    tip.innerHTML = e.target.id + ':' + elem[2]
                  }
                  tip.style.display = 'block'
                  tip.style.top = (e.target.attributes.row.value * section + dom.querySelector('.svg-tip').offsetHeight / 2 - translateY / 2 - labelPadding) + 'px'
                  tip.style.left = (e.target.attributes.column.value * section + size / 2 + translateX / 2 + maxStrLength + labelPadding - dom.querySelector('.svg-tip').offsetWidth / 2) + 'px'
                }
                // 鼠標移出
                rect.onmouseout = (e) => {
                  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
    }
  }
}
複製代碼
相關文章
相關標籤/搜索