<template>
  <div class="line-metrics-view">
    <div class="line-metrics-view__tip" ref="tip">
      <div
        class="line-metrics-view__tip-item"
        v-for="(item, index) in dataList"
        ref="tipItem"
        :style="item.tipLeft"
        :key="index">
        <div class="line-metrics-view__tip-item-label">{{ item.label }}</div>
        <div class="line-metrics-view__tip-item-num">
          {{ item.text }}
          <span
            v-show="item.percentText !== false"
            class="line-metrics-view__tip-item-num-percent">
            ({{ item.percentText }})
          </span>
        </div>
      </div>
    </div>
    <div class="line-metrics-view__arrow" ref="arrow">
      <svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
        <line
          v-for="(item, index) in dataList"
          :key="index"
          :x1="item.x1"
          :y1="lineGap"
          :x2="item.x2"
          :y2="y2"/>
      </svg>
    </div>
    <div class="line-metrics-view__line" ref="line">
      <div
        class="line-metrics-view__line-item"
        v-for="(item, index) in dataList"
        ref="lineItem"
        :style="item.style"
        :key="index">
      </div>
    </div>
    <div class="line-metrics-view__data-empty" v-show="!data.length">
      暂无数据
    </div>
  </div>
</template>

<script>
import {
  COLOR
} from './constants'

export default {
  name: 'LineMetricsView',

  props: {
    data: {
      required: true,
      type: Array
    },
    color: {
      type: Array,
      default () {
        return COLOR
      }
    },
    tipMargin: {
      type: Number,
      default: 10
    },
    lineGap: {
      type: Number,
      default: 2
    },
    percentDigit: {
      type: Number,
      default: 2
    },
    innerPercent: Boolean,
    alias: {
      type: Object,
      default () {
        return {
          labelName: 'label',
          valueName: 'value',
          percentName: 'percent'
        }
      }
    },
    showSum: Boolean,
    nullText: {
      type: String,
      default: '暂无'
    }
  },

  data () {
    return {
      dataList: [],
      y2: 0,
      sum: 0
    }
  },

  methods: {
    dataHandler () {
      const {
        data,
        color,
        setTipPosition,
        percentDigit,
        innerPercent,
        alias,
        getThousandFormat,
        nullText
      } = this
      let sum = 0
      const { labelName = 'label', valueName = 'value', percentName = 'percent' } = alias

      data.forEach(item => { sum += item[valueName] })

      this.dataList = data.map((d, index) => {
        const [label, value, percent] = [d[labelName], d[valueName], d[percentName]]
        const emptyValue = value == null
        const percentNum = sum && !emptyValue
          ? `${parseFloat((value / sum * 100).toFixed(percentDigit))}%`
          : 0
        const percentText = emptyValue
          ? false
          : (percent != null && !innerPercent)
            ? percent
            : percentNum
        const text = emptyValue
          ? nullText
          : getThousandFormat(value)
        return {
          text,
          percent: percentNum,
          percentText,
          label,
          value,
          color: color[index],
          left: 0,
          x1: 0,
          x2: 0,
          style: {
            width: percentNum,
            backgroundColor: color[index]
          }
        }
      })
      this.sum = sum
      this.$nextTick(setTipPosition)
    },

    setTipPosition () {
      const {
        $refs: {
          tipItem,
          lineItem,
          line: {
            clientWidth: lineSumWidth
          }
        },
        dataList,
        tipMargin,
        recalculateWidth
      } = this
      const centerPointList = []
      const tipWidthList = []
      const tipLeftList = []
      const lineLeftList = [0]
      const tips = Array.isArray(tipItem) ? tipItem : []
      tips.forEach((item, index) => {
        const lineItemWidth = lineItem[index].clientWidth
        tipWidthList.push(item.clientWidth)
        dataList[index].x2 = lineLeftList[index] + lineItemWidth / 2
        centerPointList.push(dataList[index].x2)
        lineLeftList.push(lineItemWidth + lineLeftList[index])
      })
      dataList.forEach((d, i) => {
        const leftDistance = i ? tipLeftList[i - 1] : 0
        // 处理左侧数值过小的问题
        d.left = leftDistance < centerPointList[i] - tipWidthList[i] / 2 - tipMargin
          ? centerPointList[i] - tipWidthList[i] / 2
          : leftDistance + tipMargin
        d.x1 = d.left + tipWidthList[i] / 2
        // 处理右侧数值过小的问题
        if (d.left + tipWidthList[i] + tipMargin > lineSumWidth) {
          recalculateWidth({
            dataList,
            i,
            d,
            rightLimit: lineSumWidth,
            tipWidthList,
            tipMargin
          })
        }
        d.tipLeft = { left: `${d.left}px` }
        tipLeftList.push(d.left + tipWidthList[i])
      })
    },

    recalculateWidth ({ dataList, i, d, rightLimit, tipWidthList, tipMargin }) {
      d.left = rightLimit - tipWidthList[i] - tipMargin
      d.tipLeft = { left: `${d.left}px` }
      d.x1 = d.left + tipWidthList[i] / 2
      if (!i) return
      const pre = i - 1
      if (dataList[pre].left + tipWidthList[pre] + tipMargin > d.left) {
        this.recalculateWidth({
          dataList,
          i: pre,
          d: dataList[pre],
          rightLimit: d.left,
          tipWidthList,
          tipMargin
        })
      }
    },

    resize () {
      this.dataHandler()
    },

    getYAxisValue () {
      const {
        $refs: {
          arrow
        },
        lineGap
      } = this
      this.y2 = arrow.clientHeight - lineGap
    },

    getThousandFormat (value) {
      return String(value).replace(/^(\s+|-)?\d+(?=.?\d*($|\s))/g, m => {
        return m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')
      })
    }
  },

  watch: {
    data: {
      immediate: true,
      handler: 'dataHandler'
    }
  },

  mounted () {
    this.getYAxisValue()
  }
}
</script>

<style lang="scss">
$border-color: #ccc;

.line-metrics-view {
  font-size: 12px;
  position: relative;

  .line-metrics-view__data-empty {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #ccc;
    font-size: 16px;
    border: 1px solid $border-color;
    background-color: #fff;
  }

  .line-metrics-view__sum {
    position: absolute;
    width: 110px;
    right: 0;
    bottom: 7px;
  }

  .line-metrics-view__tip {
    height: 50px;
    position: relative;

    .line-metrics-view__tip-item {
      position: absolute;
      bottom: 0;
      padding: 4px;
      border-radius: 4px;
      border: 1px solid $border-color;

      .line-metrics-view__tip-item-num {
        white-space: nowrap;
        margin-top: 3px;

        .line-metrics-view__tip-item-num-percent {
          color: #888;
        }
      }

      .line-metrics-view__tip-item-label {
        position: relative;
        padding-bottom: 3px;

        &::after {
          content: '';
          position: absolute;
          width: 95%;
          border-bottom: 1px dotted $border-color;
          left: 50%;
          bottom: 0;
          transform: translateX(-50%);
        }
      }
    }
  }

  .line-metrics-view__arrow {
    height: 20px;

    line {
      stroke: rgb(99,99,99);
      stroke-width: 1px;
    }
  }

  .line-metrics-view__line {
    height: 30px;
    width: 100%;
    display: flex;
    background-color: $border-color;

    .line-metrics-view__line-item {
      background-clip: padding-box;

      &:not(:last-child) {
        box-shadow: 1px 0 0 2px #fff;
      }
    }
  }
}
</style>
