306 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
			
		
		
	
	
			306 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
<template>
 | 
						|
  <svg class="md-chart" :viewBox="`0 0 ${width} ${height}`">
 | 
						|
    <defs>
 | 
						|
      <linearGradient
 | 
						|
        v-for="color in colors"
 | 
						|
        :key="color"
 | 
						|
        :id="`path-fill-gradient-${color}`" x1="0" x2="0" y1="0" y2="1">
 | 
						|
        <stop :style="`stop-color: ${color}`" offset="0%" stop-opacity="0.4"></stop>
 | 
						|
        <stop :style="`stop-color: ${color}`" offset="50%" stop-opacity="0.3"></stop>
 | 
						|
        <stop :style="`stop-color: ${color}`" offset="100%" stop-opacity="0.1"></stop>
 | 
						|
      </linearGradient>
 | 
						|
    </defs>
 | 
						|
    <g class="md-chart-graph" :transform="`translate(${offset.left}, ${offset.top})`">
 | 
						|
      <g class="md-chart-axis-y">
 | 
						|
        <g
 | 
						|
          v-for="(item, index) in yaxis"
 | 
						|
          :key="index"
 | 
						|
          :transform="`translate(0, ${item.offset})`"
 | 
						|
        >
 | 
						|
          <line x1="0" :x2="innerWidth" y1="0" y2="0"></line>
 | 
						|
          <text v-text="item.label" x="0" y="0" dx="-0.5em" dy="0.32em"></text>
 | 
						|
        </g>
 | 
						|
      </g>
 | 
						|
      <g class="md-chart-axis-x" :transform="`translate(0, ${innerHeight})`">
 | 
						|
        <g
 | 
						|
          v-for="(item, index) in xaxis"
 | 
						|
          :key="index"
 | 
						|
          :transform="`translate(${item.offset}, 0)`"
 | 
						|
        >
 | 
						|
          <line x1="0" x2="0" y1="0" y2="6"></line>
 | 
						|
          <text v-text="item.label" x="0" y="0" dy="2em"></text>
 | 
						|
        </g>
 | 
						|
      </g>
 | 
						|
      <g class="md-chart-paths">
 | 
						|
        <template v-for="(path, index) in paths">
 | 
						|
          <path
 | 
						|
            class="md-chart-path"
 | 
						|
            :key="`line-${index}`"
 | 
						|
            :style="path.style"
 | 
						|
            :d="path.value"
 | 
						|
          ></path>
 | 
						|
          <path
 | 
						|
            v-if="path.area"
 | 
						|
            :key="`area-${index}`"
 | 
						|
            class="md-chart-path-area"
 | 
						|
            :style="path.area.style"
 | 
						|
            :d="path.area.value"
 | 
						|
          ></path>
 | 
						|
        </template>
 | 
						|
      </g>
 | 
						|
    </g>
 | 
						|
  </svg>
 | 
						|
</template>
 | 
						|
 | 
						|
<script>
 | 
						|
export default {
 | 
						|
  name: 'md-chart',
 | 
						|
 | 
						|
  props: {
 | 
						|
    labels: {
 | 
						|
      type: Array,
 | 
						|
      default() {
 | 
						|
        /* istanbul ignore next */
 | 
						|
        return []
 | 
						|
      },
 | 
						|
    },
 | 
						|
    datasets: {
 | 
						|
      type: Array,
 | 
						|
      default() {
 | 
						|
        /* istanbul ignore next */
 | 
						|
        return []
 | 
						|
      },
 | 
						|
    },
 | 
						|
    size: {
 | 
						|
      type: Array,
 | 
						|
      default: () => [480, 320],
 | 
						|
    },
 | 
						|
    max: {
 | 
						|
      type: Number,
 | 
						|
      default() {
 | 
						|
        let max = Math.max.apply(
 | 
						|
          Math,
 | 
						|
          this.datasets.map(data => {
 | 
						|
            return Math.max.apply(Math, data.values)
 | 
						|
          }),
 | 
						|
        )
 | 
						|
        let multiple = 1
 | 
						|
        while (max > 10) {
 | 
						|
          multiple *= 10
 | 
						|
          max /= 10
 | 
						|
        }
 | 
						|
        max = Math.ceil(max) * multiple
 | 
						|
 | 
						|
        return max
 | 
						|
      },
 | 
						|
    },
 | 
						|
    min: {
 | 
						|
      type: Number,
 | 
						|
      default() {
 | 
						|
        let min = Math.min.apply(
 | 
						|
          Math,
 | 
						|
          this.datasets.map(data => {
 | 
						|
            return Math.min.apply(Math, data.values)
 | 
						|
          }),
 | 
						|
        )
 | 
						|
        let multiple = 1
 | 
						|
        while (min > 10) {
 | 
						|
          multiple *= 10
 | 
						|
          min = min / 10
 | 
						|
        }
 | 
						|
        min = Math.floor(min) * multiple
 | 
						|
 | 
						|
        return min
 | 
						|
      },
 | 
						|
    },
 | 
						|
    lines: {
 | 
						|
      type: Number,
 | 
						|
      default: 5,
 | 
						|
    },
 | 
						|
    step: {
 | 
						|
      type: Number,
 | 
						|
      default() {
 | 
						|
        return (this.max - this.min) / this.lines
 | 
						|
      },
 | 
						|
    },
 | 
						|
    shift: {
 | 
						|
      type: Number,
 | 
						|
      default: 0.6,
 | 
						|
    },
 | 
						|
    format: {
 | 
						|
      type: Function,
 | 
						|
      default: val => val,
 | 
						|
    },
 | 
						|
  },
 | 
						|
 | 
						|
  data() {
 | 
						|
    return {
 | 
						|
      unit: 16,
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  computed: {
 | 
						|
    offset() {
 | 
						|
      return {
 | 
						|
        top: 0.2 * this.unit,
 | 
						|
        bottom: 0.5 * this.unit,
 | 
						|
        left: this.shift * this.unit,
 | 
						|
        right: 0.2 * this.unit,
 | 
						|
      }
 | 
						|
    },
 | 
						|
    width() {
 | 
						|
      if (typeof this.size[0] === 'string' && this.size[0].indexOf('rem') !== -1) {
 | 
						|
        return parseFloat(this.size[0]) * this.unit
 | 
						|
      } else {
 | 
						|
        return parseFloat(this.size[0])
 | 
						|
      }
 | 
						|
    },
 | 
						|
    height() {
 | 
						|
      if (typeof this.size[1] === 'string' && this.size[1].indexOf('rem') !== -1) {
 | 
						|
        return parseFloat(this.size[1]) * this.unit
 | 
						|
      } else {
 | 
						|
        return parseFloat(this.size[1])
 | 
						|
      }
 | 
						|
    },
 | 
						|
    innerWidth() {
 | 
						|
      return this.width - this.offset.left - this.offset.right
 | 
						|
    },
 | 
						|
    innerHeight() {
 | 
						|
      return this.height - this.offset.top - this.offset.bottom
 | 
						|
    },
 | 
						|
    xaxis() {
 | 
						|
      const deltaX = this.innerWidth / (this.labels.length - 1)
 | 
						|
      const items = this.labels.map((label, index) => {
 | 
						|
        return {
 | 
						|
          offset: index * deltaX,
 | 
						|
          label: label,
 | 
						|
        }
 | 
						|
      })
 | 
						|
 | 
						|
      return items
 | 
						|
    },
 | 
						|
    yaxis() {
 | 
						|
      const items = []
 | 
						|
      const deltaY = this.innerHeight / this.lines
 | 
						|
 | 
						|
      for (let i = 0; i < this.lines; i++) {
 | 
						|
        items.push({
 | 
						|
          offset: i * deltaY,
 | 
						|
          label: this.format(this.max - i * this.step),
 | 
						|
        })
 | 
						|
      }
 | 
						|
 | 
						|
      items.push({
 | 
						|
        offset: this.innerHeight,
 | 
						|
        label: this.format(this.min),
 | 
						|
      })
 | 
						|
 | 
						|
      return items
 | 
						|
    },
 | 
						|
    lower() {
 | 
						|
      return this.max - (this.lines - 1) * this.step
 | 
						|
    },
 | 
						|
    paths() {
 | 
						|
      return this.datasets.map(data => {
 | 
						|
        const deltaX = this.innerWidth / (data.values.length - 1)
 | 
						|
        const deltaY = this.innerHeight / this.lines
 | 
						|
        const points = data.values.map((value, index) => {
 | 
						|
          if (value < this.lower) {
 | 
						|
            return {
 | 
						|
              x: index * deltaX,
 | 
						|
              y: this.innerHeight - (1 - (this.lower - value) / (this.lower - this.min)) * deltaY,
 | 
						|
            }
 | 
						|
          } else {
 | 
						|
            return {
 | 
						|
              x: index * deltaX,
 | 
						|
              y: (1 - (value - this.lower) / (this.max - this.lower)) * (this.innerHeight - deltaY),
 | 
						|
            }
 | 
						|
          }
 | 
						|
        })
 | 
						|
 | 
						|
        const ret = {
 | 
						|
          style: {
 | 
						|
            fill: 'none',
 | 
						|
            stroke: data.color || '#fa8919',
 | 
						|
            strokeWidth: data.width || 1,
 | 
						|
          },
 | 
						|
        }
 | 
						|
 | 
						|
        if (data.theme === 'heat') {
 | 
						|
          ret.style.stroke = `url(#path-fill-gradient-${data.color})`
 | 
						|
        } else if (data.theme === 'region') {
 | 
						|
          ret.area = {
 | 
						|
            value:
 | 
						|
              `M0,${this.innerHeight} ` +
 | 
						|
              points.map(point => `L${point.x},${point.y}`).join(' ') +
 | 
						|
              ` L${points[points.length - 1].x},${this.innerHeight}`,
 | 
						|
            style: {
 | 
						|
              fill: `url(#path-fill-gradient-${data.color})`,
 | 
						|
              stroke: 'none',
 | 
						|
            },
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        ret.value = `M0,${points.shift().y} ` + points.map(point => `L${point.x},${point.y}`).join(' ')
 | 
						|
 | 
						|
        return ret
 | 
						|
      })
 | 
						|
    },
 | 
						|
    colors() {
 | 
						|
      const uniqueColors = []
 | 
						|
      this.datasets.map(data => {
 | 
						|
        if (data.color && uniqueColors.indexOf(data.color) === -1) {
 | 
						|
          uniqueColors.push(data.color)
 | 
						|
        }
 | 
						|
      })
 | 
						|
      return uniqueColors
 | 
						|
    },
 | 
						|
  },
 | 
						|
 | 
						|
  // LiftCircle Hook
 | 
						|
  mounted() {
 | 
						|
    if (document.readyState !== 'loading') {
 | 
						|
      this.$_resize()
 | 
						|
    }
 | 
						|
    document.addEventListener('DOMContentLoaded', this.$_resize)
 | 
						|
    window.addEventListener('resize', this.$_resize)
 | 
						|
  },
 | 
						|
  beforeDestroy() {
 | 
						|
    document.removeEventListener('DOMContentLoaded', this.$_resize)
 | 
						|
    window.removeEventListener('resize', this.$_resize)
 | 
						|
  },
 | 
						|
 | 
						|
  methods: {
 | 
						|
    // MARK: private methods
 | 
						|
    $_resize() {
 | 
						|
      this.unit = parseFloat(
 | 
						|
        window.getComputedStyle(document.getElementsByTagName('html')[0]).getPropertyValue('font-size'),
 | 
						|
      )
 | 
						|
    },
 | 
						|
  },
 | 
						|
}
 | 
						|
 | 
						|
</script>
 | 
						|
 | 
						|
<style lang="stylus">
 | 
						|
.md-chart
 | 
						|
  line
 | 
						|
    stroke chart-line-color
 | 
						|
    stroke-width 0.5
 | 
						|
    stroke-linecap square
 | 
						|
  path
 | 
						|
    stroke chart-path-color
 | 
						|
    stroke-width 1
 | 
						|
    stroke-linecap butt
 | 
						|
 | 
						|
.md-chart-axis-y
 | 
						|
  text
 | 
						|
    fill chart-text-color
 | 
						|
    font-size chart-value-size
 | 
						|
    text-anchor end
 | 
						|
 | 
						|
.md-chart-axis-x
 | 
						|
  text
 | 
						|
    fill chart-text-color
 | 
						|
    font-size chart-label-size
 | 
						|
    text-anchor middle
 | 
						|
</style>
 |