效果图     完整代码在最下面

 基本效果是,左侧默认显示最大占比的区域,旋转到右侧,同时两条线的起点是左侧扇形区域

上下两点,两条线的终点是右侧扇形的上下两点。

实现思路,两条线是div定位实现的,这里利用了css的属性 transform-origin  设置基准点

两条线都是设置右侧为基准点,同时设置了旋转transform: rotate(0deg);

最终只需要更改偏移角度,就可以实现线条的移动。

这里需要注意

因为线条是根据左侧扇形区域的占比大小不同而移动,所以线条的宽度也不一定,除了需要计算

偏移角度,线条的长度也是需要动态计算的。

目前已知条件有:

左侧扇形,半径90

右侧扇形,半径75

左侧扇形图到右侧扇形图圆心的距离,330

两根线条默认长度,330

 完整代码


<template>
  <div
  class="chart_content"
  :style="`width:100%;height:${chart1.domHeight}px;`"
  >
    <RelationPieChart
      :data1="chart1.data1"
      :data2="chart1.data2"
      :domHeight="chart1.domHeight"
      :distanceValue="chart1.distanceValue"
      @loadedChart="loadedChart"
    ></RelationPieChart>
  </div>
</template>
<script>
export default {
   name: 'example1',
   data () {
    return {
      chart1 : {
        domHeight: 350,
        distanceValue: 260, 
        data1: [
          {
            value: 40,
            name: "火影"
          },
          {
            value: 15,
            name: "海贼"
          },
          {
            value: 20,
            name: "死神"
          },
          {
            value: 5,
            name: "龙珠"
          },
          {
            value: 7,
            name: "幽游白书"
          },
          {
            value: 3,
            name: "猫和老鼠"
          }
        ],
        data2: [
          {
            value: 40,
            name: "海贼王1"
          },
          {
            value: 15,
            name: "海贼王2"
          },
          {
            value: 20,
            name: "海贼王3"
          },
          {
            value: 5,
            name: "海贼王4"
          },
          {
            value: 7,
            name: "海贼王5"
          },
          {
            value: 3,
            name: "海贼王6"
          }
        ]
      }
    }
  },
  methods: {
    loadedChart(dom) {
      dom.on("click", (e) => {
        this.chart1.data1.forEach((val, index) => {
          if (val.name === e.name) {
            this.chart1.data2 = this.randSort(this.chart1.data2);
          }
        });
      });
    },
    randSort(arr) {
      for (let i = 0, len = arr.length; i < len; i++) {
        const rand = parseInt(Math.random() * len);
        const temp = arr[rand];
        arr[rand] = arr[i];
        arr[i] = temp;
      }
      return arr;
    }
  },
}
</script>
<style scoped>
.squareOne {
  width: 420px;
  height: 1px;
  background-color: red;
  position: absolute;
  z-index: 3;
}
.squareTwo {
  width: 420px;
  height: 1px;
  background-color: red;
  position: absolute;
  z-index: 3;
}

.squareOne {
  border-color: #000000;
  border-bottom-color: red;
  transform-origin: right bottom;
}

.squareTwo {
  border-color: #000000;
  border-top-color: red;
  transform-origin: right bottom;
}

</style>

 组件代码

<template>
  <div class="chartView" v-if="!isEmpty">
    <div class="chart" v-resize="resizeCharts" ref="barChart"></div>
    <div
      class="squareOne"
      :style="`transform: rotate(${topRotete}deg;width: ${shadowWidth2}px;background-color: ${lineColor};right: ${positionRight};top:${topValue1}px`"
    ></div>
    <div
      class="squareTwo"
      :style="`transform: rotate(${endRotete}deg;width: ${shadowWidth2}px;background-color: ${lineColor};right:${positionRight};top:${topValue2}px`"
    ></div>
  </div>
  <EmptyData v-else :urlImg="urlImg" :text="text"></EmptyData>
</template>
<script>
import * as echarts from "echarts";
import EmptyData from "_com_c/BigScrollComponents/EmptyData";
export default {
  name: "barChart",
  props: {
    data1: {
      type: Array,
      default() {
        return [];
      },
    },
    data2: {
      type: Array,
      default() {
        return [];
      },
    },
    colors: {
      type: Array,
      default() {
        return [
          ["#54cc34", "#9fe27d", "#debdf7", "#6b92f6", "#ffb33b", "#fcdc5d"],
          ["#a9dbfc", "#c2e953", "#fcdc5d", "#5ecebe", "#fa8f88", "#a3bcfc"],
        ];
      },
    },
    legend: {
      type: Object,
      default() {
        return {};
      },
    },
    tooltip: {
      type: Object,
      default() {
        return {};
      },
    },
    radius: {
      type: Array,
      default() {
        return ["50%", "30%"];
      },
    },
    center: {
      type: Array,
      default() {
        return [
          ["30%", "50%"],
          ["75%", "50%"],
        ];
      },
    },
    domHeight: {
      type: Number,
      default() {
        return 350;
      },
    },
    //  默认线条颜色  当传入这个值后、线条颜色就会固定
    defaultLineColor: {
      type: String,
      default() {
        return "";
      },
    },
    //两个扇形之间的距离 、左侧扇形距离右侧扇形圆心
    distanceValue: {
      type: Number,
      default() {
        return 200;
      },
    },
  },
  data() {
    return {
      charts: null,
      topRotete: 0, //  和底部偏移值一样、取反
      endRotete: 0, // 最终两根线条偏移的角度
      lineColor: "#9fe27d", //线条默认颜色
      startAngleRotate: 90, //左侧扇形的起始角度 和计算无关
      topFixed: -1, //  上方线条top 差异值
      endFixed: 0, //  下方线条top 差异值
      centerDistance: 0, //  左侧圆心距  左侧扇形右边边界距离内部距离(内部距离指的是高亮区域顶部点向下延长的线条构成的三角形底边长度)、和 distanceValue相加、就是最终线条的长度
      shadowWidth2: 220, //默认线的长度
    };
  },
  computed: {
    isEmpty() {
      return (
        !this.data1 ||
        this.data1.length === 0 ||
        !this.data2 ||
        this.data2.length === 0
      );
    },
    //   左侧默认颜色补齐
    defaultColor() {
      const defaultColor = [];
      for (let k = 0; k < this.data1.length; k++) {
        if (this.colors[k]) {
          defaultColor.push(this.colors[k]);
        } else {
          defaultColor.push("red");
        }
      }
      return defaultColor;
    },
    topValue1() {
      const leftTopValue =
        (this.domHeight - this.domHeight * (parseInt(this.radius[1]) / 100)) /
          2 +
        this.topFixed;
      return leftTopValue;
    },
    topValue2() {
      const leftTopValue =
        (this.domHeight - this.domHeight * (parseInt(this.radius[1]) / 100)) /
          2 +
        this.domHeight * (parseInt(this.radius[1]) / 100) -
        this.endFixed;
      return leftTopValue;
    },
    positionRight() {
      const rightVal = 100 - parseInt(this.center[1]);
      return rightVal + "%";
    },
    // distanceValue() {
    //   // 计算左侧扇形右边界距离右侧圆心之间的距离  动态计算的不准确、目前是自己传参
    //   const widthValue = this.$refs.barChart.clientWidth;
    //   const leftWidthValue = widthValue * (parseInt(this.radius[0]) / 100) / 2;
    //   const offsetValue = widthValue * ((parseInt(this.center[1][0]) - parseInt(this.center[0][0])) / 100) - leftWidthValue / 2;
    //   return offsetValue;
    // },
  },
  methods: {
    resizeCharts() {
      this.charts.resize();
    },
    initCharts() {
      this.$nextTick(() => {
        this.charts = echarts.init(this.$refs.barChart);
        this.charts.clear();
        this.setOption();
        this.$emit("loadedChart", this.charts);
        // 获取最大值并设置为默认展示区域
        this.firstRotate(this.charts);
        // 点击切换
        this.clickMethods(this.charts);
      });
    },
    firstRotate(dom) {
      // 拿到第一个扇形区域的角度  用当前的值/整个扇形区域的总值
      let val_num = 0;
      const numArray = [];
      this.data1.forEach((val, index) => {
        val_num += val.value;
        numArray.push(val.value);
      });
      // 默认展示占比最大的值
      let max = numArray[0];
      let maxIndex = 0;
      if (numArray.length === this.data1.length) {
        for (var i = 1; i < numArray.length; i++) {
          if (numArray[i] > max) {
            maxIndex = i;
            max = numArray[i];
          }
        }
      }
      const valAll = this.data1;
      const spliceVal = valAll.splice(
        valAll.findIndex((item) => item.name === valAll[maxIndex].name),
        1
      );
      valAll.unshift(spliceVal[0]);
      this.data1 = valAll;
      // 加载后显示默认高亮
      dom.dispatchAction({
        type: "highlight",
        seriesIndex: 0,
        dataIndex: maxIndex,
      });
      // 默认计算占比最大的扇形区域
      const percent = ((this.data1[0].value * 100) / val_num).toFixed(2);
      let color_val = "";
      if (this.colors[0]) {
        if (this.colors[0][0]) {
          color_val = this.colors[0][0];
        } else {
          color_val = "red";
        }
      } else {
        color_val = "red";
      }
      this.lineColor = this.defaultLineColor
        ? this.defaultLineColor
        : color_val;
      this.startAngleRotate = ((percent / 100) * 360) / 2;
      this.publicRotate(percent);
    },
    clickMethods(dom) {
      dom.on("click", (e) => {
        if (e.seriesIndex === 0) {
          dom.dispatchAction({
            type: "downplay",
          });
          this.data1.forEach((val, index) => {
            if (val.name === e.name) {
              let colorData = [];
              if (this.colors.length >= 1) {
                colorData = this.colors[0] ? this.colors[0] : this.defaultColor;
              } else {
                colorData = this.defaultColor;
              }
              const valAll = this.data1;
              // 点击的数据默认放第一个 更改子组件数据
              valAll.splice(
                valAll.findIndex((item) => item.name === val.name),
                1
              );
              valAll.unshift(val);
              this.data1 = valAll;
              const addColor = colorData.splice(
                colorData.findIndex((item) => item === colorData[index]),
                1
              );
              colorData.unshift(addColor[0]);
              this.colorData = colorData;
              this.lineColor = this.defaultLineColor
                ? this.defaultLineColor
                : colorData[0];
              // 计算后的角度
              let val_num = 0;
              this.data1.forEach((val, index) => {
                val_num += val.value;
              });
              const percent = ((e.value * 100) / val_num).toFixed(2);
              this.startAngleRotate = ((percent / 100) * 360) / 2;
              this.publicRotate(percent);
              setTimeout(() => {
                dom.dispatchAction({
                  type: "highlight",
                  seriesIndex: 0,
                  dataIndex: 0,
                });
              }, 300);
            }
          });
        }
      });
    },
    // 计算便宜角度和线条长度
    publicRotate(percent) {
      // 计算左半边扇形弦
      const xian =
        Math.sin((this.startAngleRotate * Math.PI) / 180) *
        ((this.domHeight * (parseInt(this.radius[0]) / 100)) / 2); //90是左边扇形的半径

      //中间位置距离差形成的长方形高的一半  75是右边扇形的半径
      const centerHeight =
        (this.domHeight * (parseInt(this.radius[1]) / 100)) / 2 - xian;
      const w =
        ((this.domHeight * (parseInt(this.radius[1]) / 100)) / 2) *
        Math.cos((((percent / 100) * 360) / 2) * (Math.PI / 180));
      // 圆心距  左侧扇形右边边界距离内部距离(内部距离指的是高亮区域顶部点向下延长的线条构成的三角形底边长度)
      this.centerDistance =
        (this.domHeight * (parseInt(this.radius[1]) / 100)) / 2 - w; //(parseInt(this.radius[1]) / 100))就是半径
      const lineReg =
        (Math.atan(centerHeight / (this.centerDistance + this.distanceValue)) *
          180) /
        Math.PI;

      this.endRotete = lineReg;
      this.topRotete = -lineReg;
      //   线条总长度 = 左边善心圆心距 + 左边扇形右侧距离右边扇形圆心距离(distanceValue)
      const linb = this.centerDistance + this.distanceValue;
      // 已知直角边和高,求直角三角形的斜边 斜边=根号下的a平方+b平方
      const hypotenuse = Math.sqrt(
        Math.pow(centerHeight, 2) + Math.pow(linb, 2)
      );
      this.shadowWidth2 = hypotenuse;
    },
    setOption() {
      const option = this.getOption();
      if (this.charts) {
        this.charts.setOption(option, true);
      }
    },
    /**
     * 更新图表
     */
    updateOption(option, replaceMerge) {
      this.$nextTick(() => {
        if (this.charts) {
          this.charts.setOption(option, replaceMerge);
          this.resizeCharts();
        } else {
          this.initCharts();
        }
      });
    },
    getTooltip(tooltip) {
      const tempTooltip = {
        trigger: "item",
        show: true,
        ...tooltip,
      };
      return tempTooltip;
    },
    getLegend(data1, data2, legend) {
      const data1val = data1.map((item) => {
        return item.name;
      });
      const data2val = data2.map((item) => {
        return item.name;
      });
      const tempLegend = [
        {
          show: true,
          type: "scroll",
          orient: "vertical",
          height: 110,
          top: "middle",
          left: 10,
          data: data1val,
          pageIcons: {
            vertical: [
              "path://M472.064 272.448l-399.232 399.232c-22.08 22.08-22.08 57.792 0 79.872 22.016 22.016 57.792 22.08 79.872 0L512 392.256l359.296 359.296c22.016 22.016 57.792 22.08 79.872 0 22.08-22.08 22.016-57.792 0-79.872L551.936 272.448C529.856 250.432 494.144 250.432 472.064 272.448z",
              "path://M472.064 751.552 72.832 352.32c-22.08-22.08-22.08-57.792 0-79.872 22.016-22.016 57.792-22.08 79.872 0L512 631.744l359.296-359.296c22.016-22.016 57.792-22.08 79.872 0 22.08 22.08 22.016 57.792 0 79.872l-399.232 399.232C529.856 773.568 494.144 773.568 472.064 751.552z",
            ],
          },
          pageIconColor: "#29bca8", // 可以点击的翻页按钮颜色
          pageIconInactiveColor: "#7f7f7f", // 禁用的按钮颜色
          pageIconSize: 15, //这当然就是按钮的大小
          ...legend[0],
        },
        {
          show: true,
          type: "scroll",
          orient: "vertical",
          height: 110,
          top: "middle",
          right: 10,
          data: data2val,
          pageIcons: {
            vertical: [
              "path://M472.064 272.448l-399.232 399.232c-22.08 22.08-22.08 57.792 0 79.872 22.016 22.016 57.792 22.08 79.872 0L512 392.256l359.296 359.296c22.016 22.016 57.792 22.08 79.872 0 22.08-22.08 22.016-57.792 0-79.872L551.936 272.448C529.856 250.432 494.144 250.432 472.064 272.448z",
              "path://M472.064 751.552 72.832 352.32c-22.08-22.08-22.08-57.792 0-79.872 22.016-22.016 57.792-22.08 79.872 0L512 631.744l359.296-359.296c22.016-22.016 57.792-22.08 79.872 0 22.08 22.08 22.016 57.792 0 79.872l-399.232 399.232C529.856 773.568 494.144 773.568 472.064 751.552z",
            ],
          },
          pageIconColor: "#29bca8", // 可以点击的翻页按钮颜色
          pageIconInactiveColor: "#7f7f7f", // 禁用的按钮颜色
          pageIconSize: 15, //这当然就是按钮的大小
          ...legend[1],
        },
      ];
      return tempLegend;
    },
    getOption() {
      const option = {
        tooltip: this.getTooltip(this.tooltip),
        legend: this.getLegend(this.data1, this.data2, this.legend),
        series: this.getSeries(
          this.data1,
          this.data2,
          this.radius,
          this.center
        ),
      };
      return option;
    },
    getSeries(data1, data2, radius, center) {
      const dataValue1 = data1.map((item, index) => {
        const colorData = this.colors[0] ? this.colors[0] : this.defaultColor;
        item.itemStyle = {
          color: colorData[index] ? colorData[index] : "red",
        };
        return item;
      });
      const dataValue2 = data2.map((item, index) => {
        let colorData = [];
        if (this.colors.length >= 1) {
          colorData = this.colors[1] ? this.colors[1] : this.defaultColor;
        } else {
          colorData = this.defaultColor;
        }
        item.itemStyle = {
          color: colorData[index] ? colorData[index] : "red",
        };
        return item;
      });
      const seriesVal = [
        {
          name: "",
          type: "pie",
          radius: radius[0], //圆环的大小
          center: center[0], //圆环的位置
          startAngle: this.startAngleRotate, //起始角度
          data: dataValue1,
          label: {
            show: false,
          },
        },
        {
          name: "",
          type: "pie",
          radius: radius[1], //圆环的大小
          center: center[1], //圆环的位置
          startAngle: 90, //起始角度
          data: dataValue2,
          label: {
            show: false,
          },
        },
      ];
      return seriesVal;
    },
  },
  mounted() {
    this.initCharts();
  },
  beforeDestroy() {
    this.charts && this.charts.dispose();
    this.charts = null;
  },
  watch: {
    data1: {
      handler() {
        const option = {
          legend: this.getLegend(this.data1, this.data2, this.legend),
          series: this.getSeries(
            this.data1,
            this.data2,
            this.radius,
            this.center
          ),
        };
        this.updateOption(option);
      },
      deep: true,
    },
    data2: {
      handler() {
        const option = {
          legend: this.getLegend(this.data1, this.data2, this.legend),
          series: this.getSeries(
            this.data1,
            this.data2,
            this.radius,
            this.center
          ),
        };
        this.updateOption(option, {
          replaceMerge: ["series", "legend"],
        });
      },
      deep: true,
    },
    colors: {
      handler() {
        this.setOption();
      },
    },
    domHeight: {
      handler() {
        this.setOption();
      },
    },
    radius: {
      handler() {
        this.setOption();
      },
    },
    center: {
      handler() {
        this.setOption();
      },
    },
  },
  components: {},
};
</script>

<style scoped>
.chartView {
  width: 100%;
  height: 100%;
  position: relative;
}
.chart {
  width: 100%;
  height: 100%;
}
.squareOne,
.squareTwo {
  width: 420px;
  height: 1px;
  background-color: red;
  position: absolute;
  z-index: 3;
}

.squareOne {
  border-color: #000000;
  border-bottom-color: red;
  transform-origin: right bottom;
}

.squareTwo {
  border-color: #000000;
  border-top-color: red;
  transform-origin: right bottom;
}
</style>

Logo

前往低代码交流专区

更多推荐