美文网首页
echart的封装

echart的封装

作者: kingLeft7 | 来源:发表于2022-11-10 14:03 被阅读0次

    第一种方式

    // myCharts.js
    /**
     * 各种画echarts图表的方法都封装在这里
     * 注意:这里echarts没有采用按需引入的方式,只是为了方便学习
     */
    import * as echarts from 'echarts';
    const install = function(Vue) {
      Object.defineProperties(Vue.prototype, {
        $chart: {
          get() {
            return {
              //线形图
              line: function(id, xdata, ydata, data3, sdata, that, isno) {
                this.chart = echarts.init(document.getElementById(id));
                this.chart.clear();
                const optionData = {
                  tooltip: {
                    trigger: 'axis'
                  },
                  legend: {
                    orient: 'horizontal',
                    itemGap: 40,
                    textStyle: {
                      color: '#333'
                      // ...
                    },
                    top: '0px',
                    right: "10%",
                    itemHeight: 16,
    
                    fontSize: 12,
                    padding: [0, 0, -3, 0], // 修改文字和图标距离
                    // ...
                  },
                  grid: {
                    // show:false,
                    top: '20%',
                    right: '10%',
                    bottom: '25%',
                    left: '20%'
                  },
                  toolbox: {
                    // feature: {
                    //   saveAsImage: {}
                    // }
                  },
                  xAxis: {
                    data: xdata,
                    splitLine: {
                      show: false
                    },
                    axisLine: {
                      show: false
                    },
                    axisTick: {
                      show: false
                    },
                  },
                  yAxis: {
                    // splitLine: {
                    //   show: false
                    // },
                    axisLine: {
                      show: false
                    },
                    axisTick: {
                      show: false
                    },
                    // data: [0, 500, 1000, 1500,1800]
                  },
                  series: [{
                    data: ydata,
                    smooth: true,
                    type: 'line',
                    symbol: 'none',
                    lineStyle: {
                      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                          offset: 0,
                          color: '#FBC95C'
                        },
    
                        {
                          offset: 1,
                          color: '#FF6550'
                        }
                      ])
                    },
                    areaStyle: {},
                    itemStyle: {
                      normal: {
                        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                            offset: 0,
                            color: '#FFDDCB'
                          },
    
                          {
                            offset: 1,
                            color: '#FAFAFC'
                          }
                        ])
                      },
    
                    },
                  }]
    
                }
                this.chart.setOption(optionData)
                let _this = this
                window.addEventListener('resize', () => {
                  if (_this.chart) {
                    _this.chart.resize()
                  }
                })
                this.chart.on('click', function(params) {
                  console.log(params.name)
                  // that.$router.push({
                  //    path: '/EventSearch',
                  //    query: {
                  //        language: params.seriesName,
                  //        times: params.name
                  //    }
                  // });
                })
    
              },
              crile: function(id, xdata, that, isno) {
                this.chart = echarts.init(document.getElementById(id));
                this.chart.clear();
                const optionData = {
                  tooltip: {
                    trigger: 'item'
                  },
                  legend: {
                    itemHeight: 10,
                    itemWidth: 10,
                    icon: "circle",
                    orient: 'vertical',
                    left: 'center',
                    bottom: 30,
                    orient: 'horizontal',
                    textStyle: {
                      fontSize: 12,
                      color: '#333333'
                    }
                  },
                  series: [{
                    name: '',
                    type: 'pie',
                    radius: ['35%', '50%'],
                    center: ["50%", "40%"],
                    avoidLabelOverlap: false,
                    label: {
                      show: false,
                      position: 'center'
                    },
                    emphasis: {
                      label: {
                        show: true,
                        fontSize: '20',
                        fontWeight: 'bold'
                      }
                    },
                    labelLine: {
                      show: false
                    },
                    data: xdata,
                    itemStyle: {
                      normal: {
                        color: function(colors) {
                          var colorList = [
                            '#558DFF',
                            '#59D7FF',
                            '#9ADA70',
                            '#FFBF5C',
                            '#B8B8B8',
                            '#F8746C'
                          ];
                          return colorList[colors.dataIndex];
                        }
                      },
                    },
                  }]
                };
                this.chart.setOption(optionData)
                let _this = this
                window.addEventListener('resize', () => {
                  if (_this.chart) {
                    _this.chart.resize()
                  }
                })
                this.chart.on('click', function(params) {
                })
    
              },
              hcolumnar: function(id, xdata, ydata, data3, sdata, that, isno) {
                this.chart = echarts.init(document.getElementById(id));
                this.chart.clear();
                console.log('sdfsfsdfsdf')
                const optionData = {
                  tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                      type: 'shadow'
                    }
                  },
                  grid: {
                    left: '3%',
                    right: '4%',
                    bottom: '3%',
                    containLabel: true
                  },
                  xAxis: [{
                    type: 'value',
                    // splitLine: {
                    //   show: false
                    // },
                    // axisTick: {
                    //    show: false
                    // },
                    show: false,
                  }],
                  yAxis: [{
                    type: 'category',
                    splitLine: {
                      show: false
                    },
                    axisLine: {
                      show: false
                    },
                    axisTick: {
                      show: false
                    },
                    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
                  }],
                  series: [{
                    name: 'Direct',
                    type: 'bar',
                    barWidth: '8',
                    backgroundStyle: {
                      color: 'rgba(111, 162, 135, 0.2)'
                    },
                    itemStyle: {
                      normal: {
                        //这里设置柱形图圆角 [左上角,右上角,右下角,左下角]
                        barBorderRadius: [10, 10, 10, 10],
                        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                            offset: 0,
                            color: '#FEC949'
                          },
                          {
                            offset: 1,
                            color: '#FEC949'
                          }
                        ])
                      },
                    },
                    data: [10, 52, 200, 334, 390, 330, 220]
                  }]
                };
                this.chart.setOption(optionData)
                let _this = this
                window.addEventListener('resize', () => {
                  if (_this.chart) {
                    _this.chart.resize()
                  }
    
                })
                this.chart.on('click', function(params) {
                  // 控制台打印数据的名称'chart1'
                  console.log(params)
                  console.log(params.name)
                  // that.$router.push({
                  //    path: '/EventSearch',
                  //    query: {
                  //        language: params.seriesName,
                  //        times: params.name
                  //    }
                  // });
                })
    
              },
              hcake: function(id, xdata, ydata, data3, sdata, that, isno) {
                console.log(id, '大饼图')
                this.chart = echarts.init(document.getElementById(id));
                this.chart.clear();
                const optionData = {
                  title: {
                    // text: 'Referer of a Website',
                    // subtext: 'Fake Data',
                    left: 'center'
                  },
                  tooltip: {
                    trigger: 'item'
                  },
                  legend: {
                    itemHeight: 10,
                    itemWidth: 10,
                    icon: "circle",
                    orient: 'vertical',
                    left: 'center',
                    bottom: 30,
                    orient: 'horizontal',
                    textStyle: {
                      fontSize: 12,
                      color: '#333333'
                    }
                  },
                  series: [{
                    name: 'Access From',
                    type: 'pie',
                    center: ["50%", "40%"],
                    radius: '50%',
                    data: [{
                        value: 1048,
                        name: '按时填写'
                      },
                      {
                        value: 735,
                        name: '未按时填写'
                      },
                      {
                        value: 580,
                        name: '未填写'
                      }
                    ],
                    itemStyle: {
                      normal: {
                        color: function(colors) {
                          var colorList = [
                            '#92CE6B',
                            '#FFBF5C',
                            '#C0C6CE'
                          ];
                          return colorList[colors.dataIndex];
                        }
                      },
                    },
                    emphasis: {
                      itemStyle: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                      }
                    }
                  }]
                };
                this.chart.setOption(optionData)
                let _this = this
                window.addEventListener('resize', () => {
                  if (_this.chart) {
                    _this.chart.resize()
                  }
    
                })
                this.chart.on('click', function(params) {
                  // 控制台打印数据的名称'chart1'
                  console.log(params)
                  console.log(params.name)
                  // that.$router.push({
                  //    path: '/EventSearch',
                  //    query: {
                  //        language: params.seriesName,
                  //        times: params.name
                  //    }
                  // });
                })
    
              },
              supercolumn: function(id, xdata, ydata, that, isno) {
                console.log(id, '大饼图')
                this.chart = echarts.init(document.getElementById(id));
                this.chart.clear();
                const optionData = {
                  grid: {
                    left: "10%",
                    right: "10%",
                    top: "10%",
                    bottom: "20%",
                    containLable: true,
                  },
                  tooltip: {
                    trigger: "axis",
                    axisPointer: {
                      type: "line",
                      lineStyle: {
                        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                            offset: 0,
                            color: 'rgba(255,255,255,0.33)'
                          },
                          {
                            offset: 1,
                            color: 'rgba(75,139,253,0.02)'
                          }
                        ]),
                        width: 40,
                        type: "solid",
                      },
                      z: 0, //注意要设置层级,不然会在覆盖在柱子前面,设置为0就在柱子后面显示了。
                    },
                  },
                  xAxis: {
                    type: "category",
                    data: xdata,
                    splitLine: {
                      show: false
                    },
                    axisLine: {
                      show: false
                    },
                    axisTick: {
                      show: false
                    },
                  },
                  yAxis: {
                    type: "value",
                    show: false,
    
                  },
                  series: [{
                    data: ydata,
                    type: "bar",
                    // name: "留存",
                    // stack: "用户",
                    showBackground: true,
                    backgroundStyle: {
                      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                          offset: 0,
                          color: 'rgba(255,255,255,0.33)'
                        },
                        {
                          offset: 1,
                          color: 'rgba(75,139,253,0.02)'
                        }
                      ]),
                      width: 40,
                      type: "solid"
                    },
                    emphasis: {
                      focus: "series",
                    },
                    barWidth: 24,
                    itemStyle: {
                      normal: {
                        //这里设置柱形图圆角 [左上角,右上角,右下角,左下角]
                        barBorderRadius: [10, 10, 10, 10],
                        color: '#E9EBF1',
                        // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                        //     offset: 0,
                        //     color: '#FEC949'
                        //   },
                        //   {
                        //     offset: 1,
                        //     color: '#FEC949'
                        //   }
                        // ])
                      },
                      emphasis: {
                                  color: '#4B8BFD',
                              }
                    },
                  }, ],
                };
                this.chart.setOption(optionData)
                let _this = this
                window.addEventListener('resize', () => {
                  if (_this.chart) {
                    _this.chart.resize()
                  }
    
                })
                this.chart.on('click', function(params) {
                  // 控制台打印数据的名称'chart1'
                  console.log(params)
                  console.log(params.name)
                  // that.$router.push({
                  //    path: '/EventSearch',
                  //    query: {
                  //        language: params.seriesName,
                  //        times: params.name
                  //    }
                  // });
                })
    
              },
            }
          }
        }
      })
    }
    export default {
      install
    }
    
    
    // main.js
    import myCharts from "./myCharts.js"
    Vue.use(myCharts)
    
    // index.vue
      <div id="line" />
    
      this.$chart.line('line', xdata, ydata, '', xAxisData, this, true)
    
    #line {
      width: 100%;
      height: 3.5rem;
    }
    

    第二种 单个

    image.png
    //ChartsCircle.vue
    <template>
      <div class="visualization-charts-common">
        <div ref="charts" class="visualization-charts"></div>
        <Select v-if="selectOptions && selectOptions.length&&showSelect" :options="selectOptions" @change="select">
        </Select>
        <!-- 返回上一级 -->
        <p class="back" v-show="showBack" @click="back">返回</p>
        <!-- 中心数据展示 -->
        <div class="center-data" v-if="total || total == 0">
          <p>总数</p>
          <p>{{total}}</p>
        </div>
      </div>
    </template>
    
    <script setup>
    import { ref, toRefs, onMounted, onUnmounted, watch } from 'vue'
    import Store from '@/store'
    import Select from '../components/Select.vue'
    import * as echarts from 'echarts/core';
    
    let props = defineProps({
      data: Object,
      selectOptions: Array,
      total: [String, Number]
    })
    let emit = defineEmits(['select'])
    let { data, selectOptions, total } = toRefs(props)
    
    let charts = ref(null)
    let chart = ref(null)
    let showBack = ref(false)
    let showSelect = ref(true)
    let clickData = ref(null)
    // let loading = ref(true)
    
    onMounted(() => {
      chart.value = echarts.init(charts.value)
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      window.addEventListener('resize', resize)
      // chart.value.on('click', function (params) {
      //   if (params.data.list.length !== 0) {
      //     emit('clickChart', params)
      //     // chart.value.setOption(initOption(params.data.list))
      //   }
      // })
      chart.value.on('click', ({ data: newData }) => {
    
        if (newData.list) {
          let newList = []
          newData.list.forEach(item => {
            newList.push({
              count: item.amount,
              name: item.name,
              ratio: item.ratio,
            })
          })
          // newData.list = [{
          //   amount: "280000",
          //   count: 280000,
          //   list: null,
          //   name: "基于药物代谢动力学特性的复方依达拉奉注射液立题依据研究",
          //   ratio: "0.48%",
          //   type: "普通合同"
          // }]
    
          showSelect.value = false
          showBack.value = true
          clickData.value = {
            ...data.value,
            formatter: '{b}\n\n{d}%',
    
            tipFormat: ({ data: { name, count, ratio } }) => {
              console.log(data, "data")
              return `${name}:<br/>${count}  |  ${ratio}`
            },
            colors: ['#73DEB3', '#F2D459', '#4DC2FF', '#E6645D', '#A285D2'],
            source: newList
          }
          chart.value.setOption(initOption(clickData.value))
        }
      })
    })
    
    onUnmounted(() => {
      window.removeEventListener('resize', resize)
    })
    
    watch(() => data.value, () => {
    
      chart.value.setOption(initOption())
      // loading.value = false
      // chart.value.hideLoading()
    })
    
    // 适配
    const resize = () => {
    
      if (chart.value) {
        if (clickData.value) {
          chart.value.setOption(initOption(clickData.value))
          chart.value.resize()
        } else {
          chart.value.setOption(initOption())
          chart.value.resize()
        }
    
      }
    }
    
    const loadingConfig = () => {
      const rate = Store.state.defaultData.width
      return {
        text: '数据加载中...',
        color: '#fff',
        textColor: '#FFF',
        maskColor: 'rgba(255, 255, 255, 0.1)',
        zlevel: 0,
        // 字体大小。从 `v4.8.0` 开始支持。
        fontSize: 12 * rate,
        // 是否显示旋转动画(spinner)。从 `v4.8.0` 开始支持。
        showSpinner: true,
        // 旋转动画(spinner)的半径。从 `v4.8.0` 开始支持。
        spinnerRadius: 10 * rate,
        // 旋转动画(spinner)的线宽。从 `v4.8.0` 开始支持。
        lineWidth: 2 * rate,
        // 字体粗细。从 `v5.0.1` 开始支持。
        fontWeight: 'normal',
        // 字体风格。从 `v5.0.1` 开始支持。
        fontStyle: 'normal',
      }
    }
    
    const initOption = (newData) => {
    
      const rate = Store.state.defaultData.width
      let tipFormat, formatter, source, colors
      console.log(newData, data.value)
      // const { dimensions, formatter, source, colors, tipFormat } = data.value
      if (newData) {
        tipFormat = newData.tipFormat
        formatter = newData.formatter
        source = newData.source
        colors = newData.colors
        // dimensions = newData.dimensions
      } else {
        tipFormat = data.value.tipFormat
        formatter = data.value.formatter
        source = data.value.source
        colors = data.value.colors
        // dimensions = data.value.dimensions
      }
      console.log(source, "source")
      // console.log(source, "source")
      return {
    
        // 数据集
        dataset: { source },
        tooltip: {
          show: true,
          formatter: tipFormat || formatter
        },
        legend: {
          type: 'scroll', // type 普通模式,滚动模式
          icon: 'circle',
          bottom: 12 * rate,
          left: 'center',
          padding: [0, 10 * rate], // 与 left top 类似
          itemGap: 36 * rate, // 图例间距
          itemWidth: 9 * rate, // 图标宽度
          itemHeight: 9 * rate, // 图标高度
          pageIconSize: 12 * rate, // 滚动icon大小
          pageIconInactiveColor: '#0933AA', // 默认颜色
          pageIconColor: '#4DC2FF', // 激活颜色
          pageTextStyle: {
            color: '#D0E0FF'
          },
          textStyle: {
            color: '#D0E0FF',
            fontSize: 12 * rate,
            lineHeight: 14 * rate
          }
        },
        series: [
          {
            type: 'pie', // 图表类型
            radius: [100 / 2 * rate, 140 / 2 * rate], // 圆环半径
            left: 'center',
            top: 9 * rate, // 中心偏移
            height: 210 * rate,
            color: colors || ['#73DEB3', '#F2D459', '#4DC2FF', '#E6645D', '#A285D2', '#b2fcb6', '#f09278', '#eb5ac8', '#8ec55e', '#5bb3f9', '#eda93b', '#c028ec', '#ec633f', '#7246be', '#5cbb7f', '#3672f6', '#e9752e', '#9c1ff5'],
            clockwise: false, // 顺时针 true 混乱 false
            minAngle: 5, // 最小占比
            itemStyle: {
              borderWidth: 2 * rate,
              borderColor: '#273989'
            },
            label: {
              show: true,
              fontSize: 12 * rate,
              color: '#D0E0FF',
              formatter,
              padding: [0, -60 * rate, 0, -60 * rate]
            },
            labelLine: {
              show: true,
              length: 16 * rate,
              length2: 96 * rate,
              lineStyle: {
                color: '#D0E0FF',
                width: 0.5
              }
            },
            emphasis: {
              scaleSize: 6 * rate
            },
          }
        ]
      }
    
    }
    
    const select = val => {
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      showBack.value = false
    
      emit('select', val)
    }
    
    const back = () => {
      showBack.value = false
      showSelect.value = true
      chart.value.setOption(initOption())
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style lang="scss" scoped>
    .visualization-charts-common {
      width: 100%;
      height: 100%;
      position: relative;
      background: url('~img/visualization/img-chart-bg.png') no-repeat center 20px/185px 185px;
    
      .visualization-charts {
        width: 100%;
        height: 100%;
        position: relative;
      }
    
      .center-data {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        margin-top: -10px;
    
        p {
          font-size: 14px;
          color: #d0e0ff;
          text-align: center;
        }
      }
    }
    .back {
      position: absolute;
      right: 10px;
      top: 10px;
      width: 80px;
      height: 26px;
      border-radius: 4px;
      background: #33539a;
      font-size: 14px;
      font-weight: 400;
      color: #91b3f3;
      line-height: 26px;
      text-align: center;
    
      cursor: pointer;
    }
    </style>
    
    
    //ChartsColumn
    <template>
      <div class="visualization-charts-common">
        <div ref="charts" class="visualization-charts"></div>
        <Select
          v-if="selectOptions && selectOptions.length"
          :options="selectOptions"
          @change="select">
        </Select>
        <Radio
          v-if="radioOptions && radioOptions.length"
          :options="radioOptions"
          :value="radioValue"
          @change="change">
        </Radio>
      </div>
    </template>
    
    <script setup>
    import { ref, toRefs, onMounted, onUnmounted, watch } from 'vue'
    import Select from '../components/Select.vue'
    import Radio from '../components/Radio.vue'
    import Store from '@/store'
    import * as echarts from 'echarts/core';
    
    let props = defineProps({
      data: Object,
      selectOptions: Array,
      radioOptions: Array,
      radioValue: [String, Number]
    })
    let emit = defineEmits(['select', 'change'])
    let { data, selectOptions, radioOptions } = toRefs(props)
    
    let charts = ref(null)
    let chart = ref(null)
    let loading = ref(true)
    
    onMounted(() => {
      chart.value = echarts.init(charts.value)
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      window.addEventListener('resize', resize)
    })
    
    onUnmounted(() => {
      window.removeEventListener('resize', resize)
    })
    
    watch(() => data.value, () => {
      chart.value.setOption(initOption())
      // loading.value = false
      // chart.value.hideLoading()
    })
    
    const resize = () => {
      if (chart.value) {
        chart.value.setOption(initOption())
        chart.value.resize()
      }
    }
    
    const loadingConfig = () => {
      const rate = Store.state.defaultData.width
      return {
        text: '数据加载中...',
        color: '#fff',
        textColor: '#FFF',
        maskColor: 'rgba(255, 255, 255, 0.1)',
        zlevel: 0,
    
        // 字体大小。从 `v4.8.0` 开始支持。
        fontSize: 12 * rate,
        // 是否显示旋转动画(spinner)。从 `v4.8.0` 开始支持。
        showSpinner: true,
        // 旋转动画(spinner)的半径。从 `v4.8.0` 开始支持。
        spinnerRadius: 10 * rate,
        // 旋转动画(spinner)的线宽。从 `v4.8.0` 开始支持。
        lineWidth: 2 * rate,
        // 字体粗细。从 `v5.0.1` 开始支持。
        fontWeight: 'normal',
        // 字体风格。从 `v5.0.1` 开始支持。
        fontStyle: 'normal',
      }
    }
    
    const initOption = () => {
      const rate = Store.state.defaultData.width
      const { line, inverse, legend, rotate, dimensions, source, colors, formatter } = data.value
      // 数据
      const series = []
      colors.forEach((color) => {
        const firstColor = color[0], secondColor = color[1]
        series.push({
          type: 'bar',
          barWidth: 8 * rate,
          itemStyle: {
            barBorderRadius: [10 * rate, 10 * rate, 0, 0],
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: firstColor },
              { offset: 1, color: secondColor }
            ])
          },
          // hover 效果
          // emphasis: {
          //   focus: 'series',
          //   scaleSize: 6 * rate
          // },
        })
      })
      // 如果有线图
      if (line) {
        // 线
        series.push(
          {
            type: 'line',
            yAxisIndex: 1,
            // 标记
            showSymbol: false, // 显示标记
            symbol: 'circle',
            symbolSize: 6 * rate,
            itemStyle: {
              width: 0 * rate,
              color: line.color
            },
            // 线
            lineStyle: {
              color: line.color,
              width: 2 * rate
            },
            smooth: true, // 平滑曲线
            axisLine: {
              show: true,
            },
          }
        )
      }
      //配置项
      const rorateNum = rotate || 0 // 控制 X轴文字过多的旋转
      let height = 86, top = 10
      if (legend) height -= 10
      const hasTop =
        (selectOptions.value && selectOptions.value.length) ||
        (radioOptions.value && radioOptions.value.length)
      if (hasTop) {
        height -= 10
        top = 20
      }
      return {
        color: data.value.colors,
        // 数据集
        dataset: { dimensions, source },
        // 图例配置
        legend: {
          show: legend,
          type: 'scroll', // type 普通模式,滚动模式
          icon: 'rect',
          bottom: 12 * rate,
          left: 'center',
          padding: [0, 10 * rate], // 与 left top 类似
          itemGap: 36 * rate, // 图例间距
          itemWidth: 12 * rate, // 图标宽度
          itemHeight: 2 * rate, // 图标高度
          pageIconSize: 12 * rate, // 滚动icon大小
          pageIconInactiveColor: '#0933AA', // 默认颜色
          pageIconColor: '#4DC2FF', // 激活颜色
          pageTextStyle: {
            color: '#D0E0FF'
          },
          textStyle: {
            color: '#D0E0FF',
            fontSize: 12 * rate
          },
        },
        tooltip: {
          trigger: 'axis',
          formatter,
          axisPointer: {
            type: 'line',
            label: {
              backgroundColor: '#6a7985'
            },
            lineStyle: {
              type: 'solid',
              width: 1,
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: 'rgba(77,194,255,1)'
                },
                {
                  offset: 1,
                  color: 'rgba(25,104,255,0)'
                }
              ])
            }
          }
        },
        xAxis: [
          {
            inverse,
            axisLabel: {
              fontSize: 12 * rate, // 更改坐标轴文字大小
              color: '#D0E0FF', // '#5A6382',
              interval: 0,
              rotate: rorateNum,
              formatter: params => getChar(params, 8)
            },
            type: 'category',
            // inverse: false, // 反转坐标轴
            boundaryGap: true, // 是否留白
            // min: 0, // 最小刻度个数
            // max: 10, // 最大刻度个数
            scale: false,
            // 刻度线设置
            axisLine: {
              lineStyle: {
                color: 'rgba(90,99,130,0.6)',
                width: 1
              }
            },
            // 刻度
            axisTick: {
              show: false,
              // alignWithLabel: false, // 刻度对齐 line 不生效
              inside: true, // 刻度是否向上
              length: 5 * rate, // 刻度长度
              lineStyle: {
                color: 'rgba(90,99,130,0.6)',
                width: 1
              }
            },
            // 背景网格线
            splitLine: {
              show: false
            }
          }
        ],
        yAxis: [
          {
            minInterval: 1,
            axisLabel: {
              fontSize: 12 * rate, // 更改坐标轴文字大小
              color: '#D0E0FF' // '#5A6382'
            },
            type: 'value',
            splitLine: {
              show: false
            }
          },
          {
            show: true,
            type: 'value',
            axisLabel: { show: false, formatter: '{value}' },
            splitLine: { show: false },
            axisTick: { show: false }
          }
        ],
        grid: {
          left: '3%',
          top: `${top}%`,
          width: '90%',
          height: `${height}%`,
          containLabel: true
        },
        series
      }
    }
    
    const select = val => {
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      emit('select', val)
    }
    
    const change = val => {
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      emit('change', val)
    }
    
    // 工具函数
    const getChar = (str, limit) => {
      let text = ''
      let bytesCount = 0;
      for (let i = 0; i < str.length; i++) {
        let char = str.charAt(i);
        let c = char
        text += char
        //匹配双字节
        if (/^[u0000-u00ff]$/.test(c)) {
          bytesCount += 1;
        } else {
          bytesCount += 2;
        }
        if (bytesCount >= limit) return `${text}...`
      }
      return str
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style lang="scss" scoped>
    .visualization-charts-common {
      width: 100%;
      height: 100%;
      position: relative;
    
      .visualization-charts {
        width: 100%;
        height: 100%;
      }
    
      .empty-tip {
        position: absolute;
        left: 50%;
        top: 50%;
        font-size: 14px;
        color: white;
      }
    }
    </style>
    
    
    //ChartsKeyword
    <template>
      <div class="visualization-charts-common">
        <div ref="charts" class="visualization-charts"></div>
      </div>
    </template>
    
    <script setup>
    import { ref, toRefs, onMounted, onUnmounted, watch } from 'vue'
    import Store from '@/store'
    import * as echarts from 'echarts/core';
    
    // type 0 科研项目关键字 1 研究方向 2 因子影响
    let props = defineProps({ data: Object, })
    let { data } = toRefs(props)
    
    let charts = ref(null)
    let chart = ref(null)
    
    onMounted(() => {
      chart.value = echarts.init(charts.value)
      // chart.value.showLoading(loadingConfig())
      window.addEventListener('resize', resize)
    })
    
    onUnmounted(() => {
      window.removeEventListener('resize', resize)
    })
    
    watch(() => data.value, () => {
      chart.value.setOption(initOption())
      // chart.value.hideLoading()
    })
    
    const resize = () => {
      if (chart.value) {
        chart.value.setOption(initOption())
        chart.value.resize()
      }
    }
    
    
    const loadingConfig = () => {
      const rate = Store.state.defaultData.width
      return {
        text: '数据加载中...',
        color: '#fff',
        textColor: '#FFF',
        maskColor: 'rgba(255, 255, 255, 0.1)',
        zlevel: 0,
    
        // 字体大小。从 `v4.8.0` 开始支持。
        fontSize: 12 * rate,
        // 是否显示旋转动画(spinner)。从 `v4.8.0` 开始支持。
        showSpinner: true,
        // 旋转动画(spinner)的半径。从 `v4.8.0` 开始支持。
        spinnerRadius: 10 * rate,
        // 旋转动画(spinner)的线宽。从 `v4.8.0` 开始支持。
        lineWidth: 2 * rate,
        // 字体粗细。从 `v5.0.1` 开始支持。
        fontWeight: 'normal',
        // 字体风格。从 `v5.0.1` 开始支持。
        fontStyle: 'normal',
      }
    }
    
    const color = ['#FF7F82', '#FFD867', '#38D8E4', '#D982E0', '#F74B4B', '#4DC2FF']
    const initOption = () => {
      const rate = Store.state.defaultData.width
    
      return {
        tooltip: {},
        series: [{
          type: 'wordCloud',
          shape: 'pentagon', // 形状
          // 位置
          left: 'center',
          top: 'center',
          // 宽高
          width: '90%',
          height: '85%',
          sizeRange: [14 * rate, 32 * rate], // 文本大小范围
          rotationRange: [-0, 0], // 旋转范围
          rotationStep: 2, // 旋转单位
          gridSize: 10 * rate, // 网格(单词间距)
          drawOutOfBound: false, // 是否可以在画布外绘制
          textStyle: {
            color: () => {
              const index = Math.round(Math.random() * 5)
              return color[index]
            }
          },
          data: data.value.source
        }]
      }
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style lang="scss" scoped>
    .visualization-charts-common {
      width: 100%;
      height: 100%;
      position: relative;
    
      .visualization-charts {
        width: 100%;
        height: 100%;
      }
    }
    </style>
    
    
    //ChartsLevel
    <template>
      <div class="visualization-charts-common">
        <div ref="charts" class="visualization-charts"></div>
        <Select
          v-if="selectOptions && selectOptions.length"
          :options="selectOptions"
          @change="select">
        </Select>
        <Radio
          v-if="radioOptions && radioOptions.length"
          :options="radioOptions"
          @change="change">
        </Radio>
        <div class="button-more" v-if="showMore" @click="clickMore">
          <p class="button-text">更多</p>
          <i class="el-icon-arrow-right"></i>
        </div>
      </div>
    </template>
    
    <script setup>
    import { ref, toRefs, onMounted, onUnmounted, watch } from 'vue'
    import Select from '../components/Select.vue'
    import Radio from '../components/Radio.vue'
    import Store from '@/store'
    import * as echarts from 'echarts/core';
    
    let props = defineProps({
      data: Object,
      selectOptions: Array,
      radioOptions: Array,
      showMore: Boolean,
    })
    let emit = defineEmits(['select', 'change', 'more'])
    let { data, selectOptions, radioOptions, showMore } = toRefs(props)
    
    let charts = ref(null)
    let chart = ref(null)
    let loading = ref(true)
    
    onMounted(() => {
      chart.value = echarts.init(charts.value)
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      window.addEventListener('resize', resize)
    })
    
    onUnmounted(() => {
      window.removeEventListener('resize', resize)
    })
    
    watch(() => data.value, () => {
      chart.value.setOption(initOption())
      // loading.value = false
      // chart.value.hideLoading()
    })
    
    const resize = () => {
      if (chart.value) {
        chart.value.setOption(initOption())
        chart.value.resize()
      }
    }
    
    const loadingConfig = () => {
      const rate = Store.state.defaultData.width
      return {
        text: '数据加载中...',
        color: '#fff',
        textColor: '#FFF',
        maskColor: 'rgba(255, 255, 255, 0.1)',
        zlevel: 0,
    
        // 字体大小。从 `v4.8.0` 开始支持。
        fontSize: 12 * rate,
        // 是否显示旋转动画(spinner)。从 `v4.8.0` 开始支持。
        showSpinner: true,
        // 旋转动画(spinner)的半径。从 `v4.8.0` 开始支持。
        spinnerRadius: 10 * rate,
        // 旋转动画(spinner)的线宽。从 `v4.8.0` 开始支持。
        lineWidth: 2 * rate,
        // 字体粗细。从 `v5.0.1` 开始支持。
        fontWeight: 'normal',
        // 字体风格。从 `v5.0.1` 开始支持。
        fontStyle: 'normal',
      }
    }
    
    const initOption = () => {
      const rate = Store.state.defaultData.width
      const { legend, rotate, dimensions, source, colors, formatter, dot, xFormatter, yInverse } = data.value
      // 数据
      const series = []
      colors.forEach((color) => {
        const firstColor = color[0], secondColor = color[1]
        series.push({
          type: 'bar',
          barWidth: 4 * rate,
          itemStyle: {
            barBorderRadius: [0, 4 * rate, 4 * rate, 0],
            color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
              { offset: 0, color: firstColor },
              { offset: 1, color: secondColor }
            ])
          },
          // hover 效果
          // emphasis: {
          //   focus: 'series',
          //   scaleSize: 6 * rate
          // },
        })
      })
      // 顶部光点
      series.push({
        type: 'pictorialBar',
        symbol: () => (circleList[dot]),
        symbolPosition: 'end',
        symbolSize: [14 * rate, 14 * rate],
        symbolOffset: [7 * rate, 0],
        z: 14,
      })
      //配置项
      const rorateNum = rotate || 0 // 控制 X轴文字过多的旋转
      let height = 86, top = 10
      if (legend) height -= 10
      const hasTop =
        (selectOptions.value && selectOptions.value.length) ||
        (radioOptions.value && radioOptions.value.length) ||
        showMore.value
      if (hasTop) {
        height -= 10
        top = 20
      }
      return {
        color: data.value.colors,
        // 数据集
        dataset: { dimensions, source },
        // 图例配置
        legend: {
          show: legend,
          type: 'scroll', // type 普通模式,滚动模式
          icon: 'rect',
          bottom: 12 * rate,
          left: 'center',
          padding: [0, 10 * rate], // 与 left top 类似
          itemGap: 36 * rate, // 图例间距
          itemWidth: 12 * rate, // 图标宽度
          itemHeight: 2 * rate, // 图标高度
          pageIconSize: 12 * rate, // 滚动icon大小
          pageIconInactiveColor: '#0933AA', // 默认颜色
          pageIconColor: '#4DC2FF', // 激活颜色
          pageTextStyle: {
            color: '#D0E0FF'
          },
          textStyle: {
            color: '#D0E0FF',
            fontSize: 12 * rate
          },
        },
        tooltip: {
          trigger: 'axis',
          formatter,
          axisPointer: {
            type: 'cross',
            label: {
              backgroundColor: '#6a7985'
            },
            lineStyle: {
              type: 'solid',
              width: 1,
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: 'rgba(77,194,255,1)'
                },
                {
                  offset: 1,
                  color: 'rgba(25,104,255,0)'
                }
              ])
            },
            crossStyle: {
              type: 'solid',
              width: 1,
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: 'rgba(77,194,255,1)'
                },
                {
                  offset: 1,
                  color: 'rgba(25,104,255,0)'
                }
              ])
            }
          }
        },
        yAxis: [
          {
            inverse: yInverse,
            axisLabel: {
              fontSize: 12 * rate, // 更改坐标轴文字大小
              color: '#D0E0FF', // '#5A6382',
              interval: 0,
              rotate: rorateNum,
              formatter: params => getChar(params, 8)
            },
            type: 'category',
            // inverse: false, // 反转坐标轴
            boundaryGap: true, // 是否留白
            // min: 0, // 最小刻度个数
            // max: 10, // 最大刻度个数
            scale: false,
            // 刻度线设置
            axisLine: {
              lineStyle: {
                color: 'rgba(90,99,130,0.6)',
                width: 1
              }
            },
            // 刻度
            axisTick: {
              show: false,
              // alignWithLabel: false, // 刻度对齐 line 不生效
              inside: true, // 刻度是否向上
              length: 5 * rate, // 刻度长度
              lineStyle: {
                color: 'rgba(90,99,130,0.6)',
                width: 1
              }
            },
            // 背景网格线
            splitLine: {
              show: false
            }
          }
        ],
        xAxis: [
          {
            axisLabel: {
              fontSize: 12 * rate, // 更改坐标轴文字大小
              color: '#D0E0FF', // '#5A6382'
              formatter: xFormatter
            },
            type: 'value',
            splitLine: {
              show: false
            }
          }
        ],
        grid: {
          left: '3%',
          top: `${top}%`,
          width: '90%',
          height: `${height}%`,
          containLabel: true
        },
        series
      }
    }
    
    const select = val => {
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      emit('select', val)
    }
    
    const change = val => {
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      emit('change', val)
    }
    
    const clickMore = () => {
      emit('more')
    }
    
    // 工具函数
    const getChar = (str, limit) => {
      let text = ''
      let bytesCount = 0;
      for (let i = 0; i < str.length; i++) {
        let char = str.charAt(i);
        let c = char
        text += char
        //匹配双字节
        if (/^[u0000-u00ff]$/.test(c)) {
          bytesCount += 1;
        } else {
          bytesCount += 2;
        }
        if (bytesCount >= limit) return `${text}...`
      }
      return str
    }
    
    let circleList = [
      'image://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAACXBIWXMAAAsTAAALEwEAmpwYAAACFElEQVRIia3Wv24TQRDH8Y9PSZBwCmMrIY2hwDWIPwUNbfgnUfIC8BwgeAtEXgMRAh10iCCo08RpgiWCIiUUxLIpdg8cs2snZ77SNXM389ubnZ2d2sN3352AFpbRRB3z0X6EQ/zAN0wNNjfhXQ1tdKJIijPxaeJSFN/CDoanETyL62hMW/EYdVzBRXzCz/EPioRTC7cqiI3SiDFa0wSXcBMLM4iVLMRYSznBRdxILGIWihhzsTSUe1jDVZOLqI1VYW/PR9suNrEhFEqKuRj7A4alQFt+z+bwCPfiwsYX0cYDvMEafiViNOJ33TJ9nQliT3E/ITZKDXfwRD5LHUKOW/Ln7LFQ5iflspCNFHW0CqGDpLggrPq03I2+KZYLoUukWFWtYovom6JZyKfzWgWxab71wt9GPM7KDII53/n/echH6edeFMIVk2J3BsFexn5UCFdKis0ZBL9k7IeFcHmmeItBBbGB0OpS7BXCTZ1iG+sVBNejb4peIYwFubSuyacnxdfok+JQ/EPCWJCij2d4JTMyRIbCnz2Xr9AtI7fFjjAWNDKiL/Aat4VDvSJUd0/IwIZ8GmE/avzp7EN8FsaCXLfv4uWEoDn6QsUPOd4rD4TBp0pl5hjEmAelYbzT9PDRhE5xCvox1rEmkGptPbwX8l6V/Rjjn46T26+D6DBtEB6n8iAsOnSjc1MYnM6ZPOrv5YRKfgMBHnHO24sLIQAAAABJRU5ErkJggg==',
      'image://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAACXBIWXMAAAsTAAALEwEAmpwYAAACFklEQVRIia3Wv24TQRDH8Y9PSZBwCmMrIY2hwDWIPwUNbfgn8RLwBvQgeAtE3gIhQiihQwRBHSHhNMESQZEcCmLFFLsHjtm1kzNf6Zq5m/ntzc7OTu3hlxeOQQvLaKKO+Wg/wD5+4Bu+Tws0N+FdDW10okiKU/Fp4kIU38I2hicRPI2raExb8Rh1XMJ5fMDP8Q+KhFMLNyqIjdKIMVrTBJdwHQsziJUsxFhLOcFFXEssYhaKGHOxNJR7WMNlk4uojVVhb89G2w42sSEUSoq5GPsdhqVAW37P5nAfd+LCxhfRxj28xhp+JWI04nfdMn2dCWKPcTchNkoNt/BIPksdQo5b8ufsgVDmx+WikI0UdbQKoYOkOCes+qTcjr4plguhS6RYVa1ii+ibolnIp/NKBbFpvvXC30Y8zsoMgjnf+f95yEcZ5F4UwhWTYmcGwV7GflAIV0qKzRkEP2Xs+4VweaZ4g8MKYodCq0uxWwg3dYqvWK8guB59U/QKYSzIpXVNPj0pPkefFPviHxLGghQDPMFLmZEhMhT+7Kl8hW4ZuS22hbGgkRF9hle4KRzqFaG6e0IGNuTTCHtR409nH+KjMBbkun0XzycEzTEQKn7I0V7ZFwafKpWZ4zDG7JeG8U7Tw3sTOsUJGMRYR5pAqrX18FbIe1X2Yox/Ok5uv/rRYdogPE7lQVh06EbnpjA4nTF51N/NCZX8Bj83cdPYyNDpAAAAAElFTkSuQmCC'
    ]
    </script>
    
    is component only -->
    <style lang="scss" scoped>
    .visualization-charts-common {
      width: 100%;
      height: 100%;
      position: relative;
    
      .visualization-charts {
        width: 100%;
        height: 100%;
      }
    
      .button-more {
        @include flexbox;
        justify-content: center;
        position: absolute;
        right: 10px;
        top: 10px;
        height: 26px;
        border-radius: 4px;
        background: #33539a;
        font-size: 14px;
        font-weight: 400;
        color: #91b3f3;
        line-height: 26px;
        padding-left: 10px;
        padding-right: 10px;
        cursor: pointer;
    
        i {
          color: #C0C4CC;
          font-size: 14px;
        }
      }
    }
    </style>
    
    
    //ChartsLine
    <template>
      <div class="visualization-charts-common">
        <div ref="charts" class="visualization-charts"></div>
        <Select
          v-if="selectOptions && selectOptions.length"
          :options="selectOptions"
          @change="select">
        </Select>
        <Radio
          v-if="radioOptions && radioOptions.length"
          :options="radioOptions"
          @change="change">
        </Radio>
      </div>
    </template>
    
    <script setup>
    
    import { ref, toRefs, onMounted, onUnmounted, watch } from 'vue'
    import Select from '../components/Select.vue'
    import Radio from '../components/Radio.vue'
    import Store from '@/store'
    import * as echarts from 'echarts/core'
    
    let props = defineProps({
      data: Object,
      selectOptions: Array,
      radioOptions: Array
    })
    let emit = defineEmits(['select', 'change'])
    let { data, selectOptions, radioOptions } = toRefs(props)
    
    let charts = ref(null)
    let chart = ref(null)
    let loading = ref(true)
    
    onMounted(() => {
      chart.value = echarts.init(charts.value)
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      window.addEventListener('resize', resize)
    })
    
    onUnmounted(() => {
      window.removeEventListener('resize', resize)
    })
    
    watch(() => data.value, () => {
      chart.value.setOption(initOption())
      // loading.value = false
      // chart.value.hideLoading()
    })
    
    // 适配
    const resize = () => {
      if (chart.value) {
        chart.value.setOption(initOption())
        chart.value.resize()
      }
    }
    
    const loadingConfig = () => {
      const rate = Store.state.defaultData.width
      return {
        text: '数据加载中...',
        color: '#fff',
        textColor: '#FFF',
        maskColor: 'rgba(255, 255, 255, 0.1)',
        zlevel: 0,
    
        // 字体大小。从 `v4.8.0` 开始支持。
        fontSize: 12 * rate,
        // 是否显示旋转动画(spinner)。从 `v4.8.0` 开始支持。
        showSpinner: true,
        // 旋转动画(spinner)的半径。从 `v4.8.0` 开始支持。
        spinnerRadius: 10 * rate,
        // 旋转动画(spinner)的线宽。从 `v4.8.0` 开始支持。
        lineWidth: 2 * rate,
        // 字体粗细。从 `v5.0.1` 开始支持。
        fontWeight: 'normal',
        // 字体风格。从 `v5.0.1` 开始支持。
        fontStyle: 'normal',
      }
    }
    
    // 图表逻辑
    const initOption = () => {
      const rate = Store.state.defaultData.width
      const { legend, rotate, dimensions, source, colors, inverse } = data.value
      // 数据
      const series = []
      colors.forEach((color) => {
        const firstColor = color[0], secondColor = color[1]
        series.push({
          type: 'line',
          // seriesLayoutBy: 'row',
          showSymbol: false, // 显示标记
          symbol: 'circle',
          symbolSize: 6 * rate,
          itemStyle: {
            color: firstColor
          },
          // stack: 'Total', // 数据堆叠值
          smooth: true, // 平滑曲线
          lineStyle: {
            color: firstColor,
            width: 2 * rate
          },
          areaStyle: {
            opacity: 0.4,
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: firstColor },
              { offset: 1, color: secondColor }
            ])
          },
          // hover 效果
          emphasis: {
            focus: 'series',
            scaleSize: 6 * rate
          },
        })
      })
      //配置项
      const rorateNum = rotate || 0 // 控制 X轴文字过多的旋转
      let height = 86, top = 10
      if (legend) height -= 10
      const hasTop =
        (selectOptions.value && selectOptions.value.length) ||
        (radioOptions.value && radioOptions.value.length)
      if (hasTop) {
        height -= 10
        top = 20
      }
      return {
        color: data.value.colors,
        // 数据集
        dataset: { dimensions, source },
        // 图例配置
        legend: {
          show: legend,
          type: 'scroll', // type 普通模式,滚动模式
          icon: 'rect',
          bottom: 12 * rate,
          left: 'center',
          padding: [0, 10 * rate], // 与 left top 类似
          itemGap: 36 * rate, // 图例间距
          itemWidth: 12 * rate, // 图标宽度
          itemHeight: 2 * rate, // 图标高度
          pageIconSize: 12 * rate, // 滚动icon大小
          pageIconInactiveColor: '#0933AA', // 默认颜色
          pageIconColor: '#4DC2FF', // 激活颜色
          pageTextStyle: {
            color: '#D0E0FF'
          },
          textStyle: {
            color: '#C6C5DE',
            fontSize: 12 * rate
          },
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'line',
            label: {
              backgroundColor: '#6a7985'
            },
            lineStyle: {
              type: 'solid',
              width: 1,
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: 'rgba(77,194,255,1)'
                },
                {
                  offset: 1,
                  color: 'rgba(25,104,255,0)'
                }
              ])
            }
          }
        },
        xAxis: [
          {
            inverse,
            axisLabel: {
              fontSize: 12 * rate, // 更改坐标轴文字大小
              color: '#D0E0FF', // '#5A6382',
              interval: 0,
              rotate: rorateNum,
              formatter: params => getChar(params, 8)
            },
            type: 'category',
            // inverse: false, // 反转坐标轴
            boundaryGap: true, // 是否留白
            // min: 0, // 最小刻度个数
            // max: 10, // 最大刻度个数
            scale: false,
            // 刻度线设置
            axisLine: {
              lineStyle: {
                color: 'rgba(90,99,130,0.6)',
                width: 1
              }
            },
            // 刻度
            axisTick: {
              alignWithLabel: true, // 搭配 boundaryGap,对齐刻度线
              inside: true, // 刻度是否向上
              length: 5 * rate, // 刻度长度
              lineStyle: {
                color: 'rgba(90,99,130,0.6)',
                width: 1
              }
            },
            // 背景网格线
            splitLine: {
              show: false
            }
          }
        ],
        yAxis: [
          {
            axisLabel: {
              fontSize: 12 * rate, // 更改坐标轴文字大小
              color: '#D0E0FF' // '#5A6382'
            },
            type: 'value',
            splitLine: {
              show: false
            }
          }
        ],
        grid: {
          left: '3%',
          top: `${top}%`,
          width: '90%',
          height: `${height}%`,
          containLabel: true
        },
        series
      }
    }
    
    const select = val => {
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      emit('select', val)
    }
    
    // const change = val = {
    //   // emit('change', val)
    // }
    
    // 工具函数
    const getChar = (str, limit) => {
      let text = ''
      let bytesCount = 0;
      for (let i = 0; i < str.length; i++) {
        let char = str.charAt(i);
        let c = char
        text += char
        //匹配双字节
        if (/^[u0000-u00ff]$/.test(c)) {
          bytesCount += 1;
        } else {
          bytesCount += 2;
        }
        if (bytesCount >= limit) return `${text}...`
      }
      return str
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style lang="scss" scoped>
    .visualization-charts-common {
      width: 100%;
      height: 100%;
      position: relative;
    
      .visualization-charts {
        width: 100%;
        height: 100%;
      }
    }
    </style>
    
    
    //ChartsPie
    <template>
      <div class="visualization-charts-common">
        <div ref="charts" class="visualization-charts"></div>
        <Select v-if="selectOptions && selectOptions.length" :options="selectOptions" @change="select">
        </Select>
        <!-- 返回上一级 -->
        <p class="back" v-show="showBack" @click="back">返回</p>
      </div>
    </template>
    
    <script setup>
    import { ref, toRefs, onMounted, onUnmounted, watch } from 'vue'
    import Store from '@/store'
    import Select from '../components/Select.vue'
    import * as echarts from 'echarts/core';
    
    let props = defineProps({
      data: Object,
      selectOptions: Array,
    })
    let emit = defineEmits(['select'])
    let { data, selectOptions } = toRefs(props)
    
    let charts = ref(null)
    let chart = ref(null)
    let loading = ref(true)
    let showBack = ref(false)
    
    onMounted(() => {
      chart.value = echarts.init(charts.value)
      chart.value.on('click', ({ data }) => {
    
        if (data.cone) {
          showBack.value = true
          chart.value.setOption(initOption({
            ...data.value,
            tipFormat: ({ data: { name, count, ratio } }) => {
              return `${name}:<br/>${count}  |  ${ratio}`
            },
            source: data.cone
          }))
        }
      })
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      window.addEventListener('resize', resize)
    })
    
    onUnmounted(() => {
      window.removeEventListener('resize', resize)
    })
    
    watch(() => data.value, () => {
      chart.value.setOption(initOption())
      // loading.value = false
      // chart.value.hideLoading()
    })
    
    const resize = () => {
      if (chart.value) {
        chart.value.setOption(initOption())
        chart.value.resize()
      }
    }
    
    const loadingConfig = () => {
      const rate = Store.state.defaultData.width
      return {
        text: '数据加载中...',
        color: '#fff',
        textColor: '#FFF',
        maskColor: 'rgba(255, 255, 255, 0.1)',
        zlevel: 0,
    
        // 字体大小。从 `v4.8.0` 开始支持。
        fontSize: 12 * rate,
        // 是否显示旋转动画(spinner)。从 `v4.8.0` 开始支持。
        showSpinner: true,
        // 旋转动画(spinner)的半径。从 `v4.8.0` 开始支持。
        spinnerRadius: 10 * rate,
        // 旋转动画(spinner)的线宽。从 `v4.8.0` 开始支持。
        lineWidth: 2 * rate,
        // 字体粗细。从 `v5.0.1` 开始支持。
        fontWeight: 'normal',
        // 字体风格。从 `v5.0.1` 开始支持。
        fontStyle: 'normal',
      }
    }
    
    const initOption = (newData) => {
      const rate = Store.state.defaultData.width
      let tipFormat, formatter, source, colors
      if (newData) {
        tipFormat = newData.tipFormat
        formatter = newData.formatter
        source = newData.source
        colors = newData.colors || ['#73DEB3', '#F2D459', '#4DC2FF', '#E6645D', '#A285D2', '#b2fcb6', '#f09278', '#eb5ac8', '#8ec55e', '#5bb3f9', '#eda93b', '#c028ec', '#ec633f', '#7246be', '#5cbb7f', '#3672f6', '#e9752e', '#9c1ff5']
      } else {
        tipFormat = data.value.tipFormat
        formatter = data.value.formatter
        source = data.value.source
        colors = data.value.colors || ['#73DEB3', '#F2D459', '#4DC2FF', '#E6645D', '#A285D2', '#b2fcb6', '#f09278', '#eb5ac8', '#8ec55e', '#5bb3f9', '#eda93b', '#c028ec', '#ec633f', '#7246be', '#5cbb7f', '#3672f6', '#e9752e', '#9c1ff5']
      }
      return {
        // 数据集
        dataset: { source },
        tooltip: {
          show: true,
          formatter: tipFormat || formatter
        },
        legend: {
          type: 'scroll', // type 普通模式,滚动模式
          icon: 'circle',
          bottom: 12 * rate,
          left: 'center',
          padding: [0, 10 * rate], // 与 left top 类似
          itemGap: 36 * rate, // 图例间距
          itemWidth: 9 * rate, // 图标宽度
          itemHeight: 9 * rate, // 图标高度
          pageIconSize: 12 * rate, // 滚动icon大小
          pageIconInactiveColor: '#0933AA', // 默认颜色
          pageIconColor: '#4DC2FF', // 激活颜色
          pageTextStyle: {
            color: '#D0E0FF'
          },
          textStyle: {
            color: '#D0E0FF',
            fontSize: 12 * rate,
            lineHeight: 14 * rate
          }
        },
        series: [
          {
            type: 'pie', // 图表类型
            radius: [0, 140 / 2 * rate], // 圆环半径
            left: 'center',
            top: 9 * rate, // 中心偏移
            color: colors,
            clockwise: false, // 顺时针 true 混乱 false
            minAngle: 5, // 最小占比
            height: 210 * rate,
            label: {
              show: true,
              fontSize: 12 * rate,
              color: '#D0E0FF',
              formatter,
              padding: [0, -60 * rate, 0, -60 * rate],
              // textStyle: {
              //   color: '#D0E0FF',
              //   fontSize: 12 * rate,
              //   lineHeight: 14 * rate
              // }
            },
            labelLine: {
              show: true,
              length: 16 * rate,
              length2: 96 * rate,
              lineStyle: {
                color: '#D0E0FF',
                width: 0.5
              }
            },
            emphasis: {
              scaleSize: 6 * rate
            },
          }
        ]
      }
    }
    
    const select = val => {
      // chart.value.showLoading(loadingConfig())
      // loading.value = true
      showBack.value = false
      emit('select', val)
    }
    
    const back = () => {
      showBack.value = false
      chart.value.setOption(initOption())
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style lang="scss" scoped>
    .visualization-charts-common {
      width: 100%;
      height: 100%;
      position: relative;
      background: url('~img/visualization/img-chart-bg.png') no-repeat center 20px/185px 185px;
    
      .visualization-charts {
        width: 100%;
        height: 100%;
      }
    
      .back {
        position: absolute;
        left: 10px;
        top: 10px;
        width: 70px;
        height: 26px;
        border-radius: 4px;
        background: #33539a;
        font-size: 14px;
        font-weight: 400;
        color: #91b3f3;
        line-height: 26px;
        text-align: center;
        padding-left: 10px;
        padding-right: 26px;
        cursor: pointer;
      }
    }
    </style>
    
    
    // 使用
    <template>
      <div class="visualization-page-container">
        <div class="visualization-content">
          <!-- 上 -->
          <div class="section col-3">
            <!-- 左 -->
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 曲线图 -->
                <Title title="科研成果近五年趋势"></Title>
                <Wrapper
                  :loading="trend5YearLoading"
                  :data="trend5YearData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <ChartsLine :data="trend5YearData"></ChartsLine>
                  </div>
                </Wrapper>
              </div>
            </div>
            <!-- 中 -->
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 环形图 -->
                <Title title="科研成果总体概况"></Title>
                <Wrapper
                  :loading="trendOverviewLoading"
                  :data="trendOverviewData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <ChartsCircle
                      :data="trendOverviewData"
                      :selectOptions="trendOverviewSelectOptions"
                      @select="trendOverviewSelect">
                    </ChartsCircle>
                  </div>
                </Wrapper>
              </div>
            </div>
            <!-- 右 -->
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 词云图 -->
                <Title title="研究方向"></Title>
                <Wrapper
                  :loading="keywordLoading"
                  :data="keywordData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <ChartsKeyword :data="keywordData"></ChartsKeyword>
                  </div>
                </Wrapper>
              </div>
            </div>
          </div>
          <!-- 中 -->
          <div class="section col-3">
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 曲线图 -->
                <Title title="各院系科研成果情况"></Title>
                <Wrapper
                  :loading="departmentAchievLoading"
                  :data="departmentAchievData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <ChartsLine
                      :data="departmentAchievData"
                      :selectOptions="departmentAchievSelectOptions"
                      @select="departmentAchievSelect">
                    </ChartsLine>
                  </div>
                </Wrapper>
              </div>
            </div>
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 饼状图 -->
                <Title title="科研论文语种情况"></Title>
                <Wrapper
                  :loading="langLoading"
                  :data="langData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <ChartsPie
                      :data="langData"
                      :selectOptions="langSelectOptions"
                      @select="langSelect">
                    </ChartsPie>
                  </div>
                </Wrapper>
              </div>
            </div>
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 柱状图 -->
                <Title title="研究方向负责人情况"></Title>
                <Wrapper
                  :loading="directionLoading"
                  :data="directionData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <!-- :radioOptions="directionRadioOptions" -->
                    <ChartsColumn
                      :data="directionData"
                      :selectOptions="directionSelectOptions"
                      @select="directionSelect"
                      @change="directionChange">
                    </ChartsColumn>
                  </div>
                </Wrapper>
              </div>
            </div>
          </div>
          <!-- 下 -->
          <div class="section col-2">
            <!-- 左 -->
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 柱状图 -->
                <Title title="科研专利类型情况TOP"></Title>
                <Wrapper
                  :loading="categoryLoading"
                  :data="categoryData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <ChartsLevel
                      :data="categoryData"
                      :selectOptions="categorySelectOptions"
                      :radioOptions="categoryRadioOptions"
                      @select="categorySelect"
                      @change="categoryChange">
                    </ChartsLevel>
                  </div>
                </Wrapper>
              </div>
            </div>
            <!-- 右 -->
            <div class="section-wrapper">
              <div class="section-block">
                <!-- 柱状图 -->
                <Title title="著作类别情况TOP"></Title>
                <Wrapper
                  :loading="bookCategoryLoading"
                  :data="bookCategoryData"
                  showEmpty>
                  <div class="charts-line-wrapper">
                    <ChartsLevel
                      :data="bookCategoryData"
                      :selectOptions="bookCategorySelectOptions"
                      :radioOptions="bookCategoryRadioOptions"
                      @select="bookCategorySelect"
                      @change="bookCategoryChange">
                    </ChartsLevel>
                  </div>
                </Wrapper>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    import { ref, onBeforeMount } from 'vue'
    import Router from '@/router'
    import { countDate, dict } from 'utils'
    import {
      trend5Year, // 5年趋势
      departmentAchiev, // 各科成果
      trendOverview, // 科研成果总体
      articleLang, // 论文语种
      directionKeyword, // 研究方向词云
      directionPerson, // 研究方向负责人
      patentCategory, // 专利类型
      bookCategory // 著作类别
    } from '../service'
    import Title from '../components/Title.vue'
    import Wrapper from '../components/Wrapper.vue';
    import ChartsLine from '../charts/ChartsLine.vue';
    import ChartsCircle from '../charts/ChartsCircle';
    import ChartsKeyword from '../charts/ChartsKeyword.vue';
    import ChartsPie from '../charts/ChartsPie.vue';
    import ChartsColumn from '../charts/ChartsColumn.vue';
    import ChartsLevel from '../charts/ChartsLevel.vue'
    
    let id = ref('')
    // 5年趋势
    let trend5YearData = ref({})
    let trend5YearLoading = ref(true)
    // 各科成果
    let departmentAchievData = ref({})
    let departmentAchievSelectOptions = ref([])
    let departmentAchievSelectAct = ref('')
    let departmentAchievLoading = ref(true)
    // 科研总体
    let trendOverviewData = ref({})
    let trendOverviewSelectOptions = ref([])
    let trendOverviewSelectAct = ref('')
    let trendOverviewLoading = ref(true)
    // 论文语种
    let langData = ref({})
    let langSelectOptions = ref([])
    let langSelectAct = ref('')
    let langLoading = ref(true)
    // 研究方向词云
    let keywordData = ref({})
    let keywordLoading = ref(true)
    // 研究方向
    let directionData = ref({})
    let directionSelectOptions = ref([])
    let directionSelectAct = ref('')
    let directionRadioOptions = ref([]) // 暂时弃用
    let directionRadioAct = ref('') // 暂时弃用
    let directionLoading = ref(true)
    // 专利类型
    let categoryData = ref({})
    let categorySelectOptions = ref([])
    let categorySelectAct = ref('')
    let categoryRadioOptions = ref([])
    let categoryRadioAct = ref('')
    let categoryLoading = ref(true)
    // 著作类别
    let bookCategoryData = ref()
    let bookCategorySelectOptions = ref([])
    let bookCategorySelectAct = ref('')
    let bookCategoryRadioOptions = ref([])
    let bookCategoryRadioAct = ref('')
    let bookCategoryLoading = ref(true)
    
    onBeforeMount(() => {
      if (Router.mode === 'history') {
        id.value = Router.history.current.params.id
      } else {
        id.value = Router.history.current.query.id
      }
    
      // 获取数据
      getTrend5Year() // 5年趋势
      getDepartmentAchiev() // 各科成果
      getTrendOverview() // 科研成果总体概况
      getLang() // 论文语种
      getKeyword() // 研究方向词云
      getDirection() // 研究方向负责人
      getCategory() // 专利类型
      getBookCategory() // 著作类别
    })
    
    // 5年趋势
    const getTrend5Year = () => {
      trend5YearLoading.value = true
      trend5Year({ collegeCode: id.value }).then(({ data }) => {
        if (data) {
          const source = data.map(item => ({
            year: item.year,
            '论文数量': item.lw,
            '专利数量': item.zl,
            '著作数量': item.zz
          }))
          trend5YearData.value = {
            legend: true, // 是否显示图例
            dimensions: ['year', '论文数量', '专利数量', '著作数量'],
            source,
            colors: [
              ['rgba(77,194,255,1)', 'rgba(25,104,255,0)'],
              ['rgba(252,193,72,1)', 'rgba(252,193,72,0)'],
              ['rgba(115,222,179,1)', 'rgba(115,222,179,0)']
            ]
          }
        }
        trend5YearLoading.value = false
        // console.log(trend5YearData.value)
      })
    }
    // 各科成果
    const getDepartmentAchiev = () => {
      departmentAchievLoading.value = true
      // 筛选项
      departmentAchievSelectOptions.value = countDate()
      const select =
        departmentAchievSelectAct.value ||
        departmentAchievSelectOptions.value[0].value
      // 数据
      departmentAchiev({ collegeCode: id.value, time: select }).then(({ data }) => {
        if (data) {
          const source = data.map(item => ({
            name: item.name,
            '获奖成果': item.hjCount,
            '论文数量': item.lwCount,
            '专利数量': item.zlCount,
            '著作数量': item.zzCount
          }))
          departmentAchievData.value = {
            // 配置项
            legend: true, // 是否显示图例
            rotate: 45,
            // 数据
            dimensions: ['name', '获奖成果', '论文数量', '专利数量', '著作数量'],
            source,
            colors: [
              ['rgba(232,104,74,1)', 'rgba(232,104,74,0)'],
              ['rgba(77,194,255,1)', 'rgba(25,104,255,0)'],
              ['rgba(252,193,72,1)', 'rgba(252,193,72,0)'],
              ['rgba(115,222,179,1)', 'rgba(115,222,179,0)']
            ]
          }
        }
        departmentAchievLoading.value = false
        // console.log(trend5YearData.value)
      })
    }
    const departmentAchievSelect = val => {
      departmentAchievSelectAct.value = val
      getDepartmentAchiev()
    }
    // 成果总体概况
    const getTrendOverview = () => {
      trendOverviewLoading.value = true
      // 筛选项
      trendOverviewSelectOptions.value = countDate()
      const select =
        trendOverviewSelectAct.value ||
        trendOverviewSelectOptions.value[0].value
      // 数据
      trendOverview({ collegeCode: id.value, time: select }).then(({ data }) => {
        if (data) {
          trendOverviewData.value = {
            tipFormat: ({ data: { name, count, ratio } }) => {
              return `${name}:<br/>${count}  |  ${ratio}`
            },
            formatter: '{b}\n\n{d}%',
            source: data,
            colors: ['#73DEB3', '#F2D459', '#4DC2FF', '#E6645D', '#A285D2']
          }
        }
        trendOverviewLoading.value = false
        // console.log('trendoverview', trendOverviewData.value)
      })
    }
    const trendOverviewSelect = val => {
      trendOverviewSelectAct.value = val
      getTrendOverview()
    }
    // 论文语种
    const getLang = () => {
      langLoading.value = true
      // 筛选项
      langSelectOptions.value = countDate()
      const select =
        langSelectAct.value ||
        langSelectOptions.value[0].value
      // 数据
      articleLang({ collegeCode: id.value, time: select }).then(({ data }) => {
        if (data) {
          langData.value = {
            tipFormat: ({ data: { name, count, ratio } }) => {
              return `${name}:<br/>${count}  |  ${ratio}`
            },
            formatter: ({ data }) => {
              return `${data.name}\n\n${data.ratio}`
            },
            source: data
          }
          // console.log('langData', langData.value)
        }
        langLoading.value = false
      })
    }
    const langSelect = val => {
      langSelectAct.value = val
      getLang()
    }
    // 研究词云
    const getKeyword = () => {
      keywordLoading.value = true
      directionKeyword({ collegeCode: id.value, }).then(({ data }) => {
        if (data) {
          keywordData.value = { source: data }
        }
        keywordLoading.value = false
        // console.log('keyword', keywordData.value)
      })
    }
    // 研究负责人
    const getDirection = () => {
      directionLoading.value = true
      // 筛选项
      directionSelectOptions.value = countDate()
      const select =
        directionSelectAct.value ||
        directionSelectOptions.value[0].value
      directionRadioOptions.value = dict.top
      let radio =
        directionRadioAct.value ||
        directionRadioOptions.value[0].value
    
      // 数据
      directionPerson({ collegeCode: id.value, time: select, sort: radio }).then(({ data }) => {
        if (data) {
          directionData.value = {
            legend: false,
            rotate: 45,
            formatter: params => {
              let temp = params && params[0]
              if (temp) {
                let { data: { name: label, value } } = temp
                return `${label} ${value}`
              }
              return ''
            },
            source: data,
            colors: [
              ['rgba(77,194,255,1)', 'rgba(25,104,255,0.3)'],
            ]
          }
          // console.log('directionData', directionData.value)
        } else {
          directionData.value = {
            legend: false,
            rotate: 45,
            formatter: params => {
              let temp = params && params[0]
              if (temp) {
                let { data: { name: label, value } } = temp
                return `${label} ${value}`
              }
              return ''
            },
            source: [],
            colors: [
              ['rgba(77,194,255,1)', 'rgba(25,104,255,0.3)'],
            ]
          }
        }
        directionLoading.value = false
      })
    }
    const directionSelect = val => {
      directionSelectAct.value = val
      getDirection()
    }
    const directionChange = val => {
      directionRadioAct.value = val
      getDirection()
    }
    // 专利情况
    const getCategory = () => {
      categoryLoading.value = true
      // 筛选项
      categorySelectOptions.value = countDate()
      const select =
        categorySelectAct.value ||
        categorySelectOptions.value[0].value
      categoryRadioOptions.value = dict.radio
      const radio =
        categoryRadioAct.value ||
        categoryRadioOptions.value[0].value
      // 数据
      patentCategory({ collegeCode: id.value, time: select, sort: radio }).then(({ data }) => {
        if (data) {
          categoryData.value = {
            legend: false,
            dot: 0,
            formatter: params => {
              let temp = params && params[0]
              if (temp) {
                let { data: { name: label, count: value } } = temp
                return `${label} ${value}`
              }
              return ''
            },
            source: data,
            colors: [
              ['rgba(86,188,237,1)', 'rgba(74,87,186,1)'],
            ]
          }
          // console.log('categoryData', categoryData.value)
        }
        categoryLoading.value = false
      })
    }
    const categorySelect = val => {
      categorySelectAct.value = val
      getCategory()
    }
    const categoryChange = val => {
      categoryRadioAct.value = val
      getCategory()
    }
    // 著作类别
    const getBookCategory = () => {
      bookCategoryLoading.value = true
      // 筛选项
      bookCategorySelectOptions.value = countDate()
      const select =
        bookCategorySelectAct.value ||
        bookCategorySelectOptions.value[0].value
      bookCategoryRadioOptions.value = dict.radio
      let radio =
        bookCategoryRadioAct.value ||
        bookCategoryRadioOptions.value[0].value
      // 数据
      bookCategory({ collegeCode: id.value, time: select, sort: radio }).then(({ data }) => {
        if (data) {
          bookCategoryData.value = {
            legend: false,
            dot: 1,
            formatter: params => {
              let temp = params && params[0]
              if (temp) {
                let { data: { name: label, count: value } } = temp
                return `${label} ${value}`
              }
              return ''
            },
            source: data,
            colors: [
              ['rgba(115,222,179,1)', 'rgba(77,194,255,1)'],
            ]
          }
          // console.log('bookCategoryData', bookCategoryData.value)
        } else {
          bookCategoryData.value = {
            legend: false,
            dot: 1,
            formatter: params => {
              let temp = params && params[0]
              if (temp) {
                let { data: { name: label, count: value } } = temp
                return `${label} ${value}`
              }
              return ''
            },
            source: [],
            colors: [
              ['rgba(115,222,179,1)', 'rgba(77,194,255,1)'],
            ]
          }
        }
        bookCategoryLoading.value = false
      })
    }
    const bookCategorySelect = val => {
      bookCategorySelectAct.value = val
      getBookCategory()
    }
    const bookCategoryChange = val => {
      bookCategoryRadioAct.value = val
      getBookCategory()
    }
    </script>
    
    <style lang="scss" scoped>
    .visualization-page-container {
    
      .visualization-content {
        padding-left: 120px;
        padding-right: 0; // 24px;
    
        .section {
          @include flexbox;
          flex-wrap: wrap;
    
          .section-block {
            height: 294px;
            margin-top: 24px;
          }
    
          &.col-2 {
            .section-wrapper {
              width: 50%;
            }
    
            .section-block {
              width: 874px;
            }
          }
    
          &.col-3 {
            .section-wrapper {
              width: 33.33%;
            }
    
            .section-block {
              width: 576px;
            }
          }
        }
    
        .charts-line-wrapper {
          width: 100%;
          height: 246px;
        }
      }
    }
    </style>
    
    

    相关文章

      网友评论

          本文标题:echart的封装

          本文链接:https://www.haomeiwen.com/subject/rltatdtx.html