echarts绘制世界地图及动态航线图

794 阅读1分钟

效果图

24 00_00_00-00_00_30.gif

完整代码

<template>
  <div class='city_box'>
    <div class='map_dv'>
      <div id='mapdv' class="map_box"></div>
      <div :class="[!mapInfo.isShow ? 'bm_dr' : 'bm_dl']" @click="mapInfo.isShow = !mapInfo.isShow">
        <img v-if="!mapInfo.isShow" class="img_l" src="@/assets/images/arr_l.svg">
        <img v-else class="img_l" src="@/assets/images/arr_r.svg"/>
      </div>
      <div :class="['databox', mapInfo.isShow ? 'show_data' : 'hide_data']">
        <table>
          <thead>
            <tr class="tb_head">
              <td width="40px">
                <div class="tb_line"></div>
                <div class="tb_line line_dm"></div>
                <div class="pont_t"></div>
                <div class="pont_r"></div>
                <div class="pont_b"></div>
                <div class="pont_l"></div>
              </td>
              <td width="82px;">口岸</td>
              <td>终点/起点</td>
              <td width="55px;">耗时</td>
              <td width="90px;">时间</td>
              <td width="82px;">频次</td>
            </tr>
          </thead>
          <tbody>
            <tr class="tb_body" v-for="(v, i) in mapInfo.lineDatas" :key="i">
              <td class="tb_h1"><br/></td>
              <td class="tb_y" width="82px;">{{ v.name }}</td>
              <td class="tb_g">{{ v.line }}</td>
              <td width="55px;">{{ v.day }}</td>
              <td width="90px;">{{ v.week }}</td>
              <td width="82px;">{{ v.time }}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    <div class='rt_num'>
      <div class='s_box'>
        <h3 class='inrer_tile'>国际通道</h3>
        <div class='quar_box'>
          <div class='row_box' v-for='(v, i) in dataInfo' :key='i'>
            <img class='icon_img' :src='v.src' alt='火车'>
            <div class='info_box'>
              <div class='info_tile'>{{ v.name }}</div>
              <div class='info_sbox'><span>{{ v.num }}</span></div>
            </div>
          </div>
        </div>
      </div>
      <div class='line_bg'></div>
      <div class='s_box'>
        <h3 class='inrer_tile'>趟次</h3>
        <div class='quar_box rot_box'>
          <div class='rotate_box l1'>
            <img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
            <div class='txt_box'>
              <div class='g_wh'>铁路</div>
              <div class='g_yel'><span style='font-weight: 600;'>445</span></div>
            </div>
          </div>
          <div class='rotate_box l2'>
            <img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
            <div class='txt_box'>
              <div class='g_wh'>航线</div>
              <div class='g_yel'><span style='font-weight: 600;'>4600</span></div>
            </div>
          </div>
          <div class='rotate_box l3'>
            <img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
            <div class='txt_box'>
              <div class='g_wh'>水运</div>
              <div class='g_yel'><span style='font-weight: 600;'>84</span></div>
            </div>
          </div>
          <div class='rotate_box l4'>
            <img class='rotate_img gif' src='http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2577.png' alt='旋转圆'>
            <div class='txt_box'>
              <div class='g_wh'>多式</div>
              <div class='g_yel'><span style='font-weight: 600;'>128</span></div>
            </div>
          </div>
        </div>
      </div>
      <div class='line_bg'></div>
      <div class='s_box'>
        <h3 class='inrer_tile'>箱量</h3>
        <div class='quar_box' style="padding-left: 15px;">
          <Echarts :options='mapInfo.options' style='height: 100%;'/>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang='ts' setup>
import { reactive, onMounted } from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/charts/index.vue'

const dataInfo = [
  {
    name: '铁路货运班列',
    num: 34,
    src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2619.png'
  },
  {
    name: '水运航线',
    num: 4,
    src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2620.png'
  },
  {
    name: '航空航线',
    num: 16,
    src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2621.png'
  },
  {
    name: '多式联运',
    num: 3,
    src: 'http://basic.tms.fineyun.cn:10008/images/%E9%80%9A%E8%BE%BE%E8%83%BD%E5%8A%9B-%E5%9B%BD%E9%99%85/u2622.png'
  }
]
const nGs = []
const chartInfo = reactive({
  geo: {
    map: 'china',
    roam: false,
    emphasis: {
      disabled: true
    },
    label: {
      show: true,
      color: '#fff',
      formatter: (res) => {
        let wname = ''
        switch (res.name) {
          case ('美国' || '墨西哥'):
            wname = res.name
            break
          case '墨西哥':
            wname = res.name
            break
          case '英国':
            wname = res.name
            break
          case '芬兰':
            wname = res.name
            break
          case '意大利':
            wname = res.name
            break
          case '印度':
            wname = res.name
            break
          case '中国':
            wname = res.name
            break
          case '日本':
            wname = res.name
            break
          case '越南':
            wname = res.name
            break
          case '澳大利亚':
            wname = res.name
            break
        }
        return wname
      }
    },
    top: 'center',
    left: 'center',
    zoom: 1,
    itemStyle: {
      areaColor: '#093551',
      opacity: 0.8,
      borderColor: '#0398C2',
      borderWidth: 1
    }
  },
  legend: {
    orient: 'vertical',
    top: 15,
    left: 15,
    data: [
      {
        name: '国际铁路货运班列',
        icon: 'circle',
        itemStyle: {
          color: '#2E4F85',
          borderColor: '#fff'
        }
      },
      {
        name: '国际水运航线',
        icon: 'circle',
        itemStyle: {
          color: '#506E45',
          borderColor: '#fff'
        }
      },
      {
        name: '国际航空航线',
        icon: 'circle',
        itemStyle: {
          color: '#846C36',
          borderColor: '#fff'
        }
      },
      {
        name: '国际多式联运',
        icon: 'circle',
        itemStyle: {
          color: '#7E3B3D',
          borderColor: '#fff'
        }
      }
    ],
    textStyle: {
      color: '#fff',
      fontSize: 14,
      height: 13,
      rich: {
        a: {
          verticalAlign: 'middle'
        }
      }
    },
    itemWidth: 10,
    itemHeight: 10,
    itemGap: 15,
    inactiveBorderColor: '#fff',
    backgroundColor: '#062462',
    padding: [10, 100, 10, 10],
    selectedMode: 'multiple'
  },
  series: nGs,
  tooltip: {
    trigger: 'item',
    triggerOn: 'mousemove',
    formatter: (params) => {
      // console.log(params)
      if (params.seriesType === 'effectScatter') {
        return '线路:' + params.data.fromName + '=>' + params.data.toName
      } else if (params.seriesType === 'lines') {
        return (
          // '线路:' + params.data.fromName + '=>' + params.data.toName + '<br />'
          '线路:' + params.data.fromName + '=>' + params.data.toName + '<br />'
        )
      } else {
        return params.name
      }
    }
  }
})
const mapInfo = reactive({
  mychart: null,
  area: null,
  options: null,
  isShow: false,
  lineDatas: [
    {
      name: '二连浩特',
      line: '马拉舍维奇/杜伊斯堡/汉堡',
      day: '18天',
      week: '每周周五',
      time: '一周一班'
    },
    {
      name: '阿拉山口',
      line: '马拉舍维奇/汉堡/杜伊斯堡/里昂/伦敦',
      day: '16天',
      week: '每周周三',
      time: '一周一班'
    },
    {
      name: '阿拉山口',
      line: '马拉舍维奇/杜伊斯堡/布达佩斯/布拉格',
      day: '16天',
      week: '每周周五',
      time: '一周一班'
    },
    {
      name: '阿拉山口',
      line: '马拉舍维奇/杜伊斯堡/汉堡/里昂',
      day: '18天',
      week: '每周周六、周日',
      time: '一周二班'
    }
  ]
})

function loadData () {
  const flays = [
    {
      name: '北京',
      fromName: '中国',
      toName: '芬兰',
      coords: [[116.4551, 40.2539], [24.939374, 60.176573]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '日本',
      coords: [[116.4551, 40.2539], [139.771786, 35.724056]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '英国',
      coords: [[116.4551, 40.2539], [-0.128333, 51.513269]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '意大利',
      coords: [[116.4551, 40.2539], [12.491766, 41.904716]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '美国',
      coords: [[116.4551, 40.2539], [-73.865402, 40.847183]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '印度',
      coords: [[116.4551, 40.2539], [77.102253, 28.711647]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '墨西哥',
      coords: [[116.4551, 40.2539], [-99.139532, 19.445971]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '越南',
      coords: [[116.4551, 40.2539], [105.698818, 20.971959]]
    },
    {
      name: '北京',
      fromName: '中国',
      toName: '澳大利亚',
      coords: [[116.4551, 40.2539], [149.131687, -35.261253]]
    }
  ]
  // 航线数据
  const linesData = [
    {
      name: '国际铁路货运班列',
      type: 'train',
      data: flays.slice(0, 3)
    },
    {
      name: '国际水运航线',
      type: 'ship',
      data: flays.slice(3, 5)
    },
    {
      name: '国际航空航线',
      type: 'plane',
      data: flays.slice(5, 7)
    },
    {
      name: '国际多式联运',
      type: '',
      data: flays.slice(7, 9)
    }
  ]
  const planePath = 'image://' + require('@/assets/images/plane.svg')
  const trainPath = 'image://' + require('@/assets/images/train.svg')
  const shipPath = 'image://' + require('@/assets/images/ship.svg')
  const otherPath = 'image://' + require('@/assets/images/ufo.svg')
  const pointPath = 'image://' + require('@/assets/images/piont.svg')
  const ypointPath = 'image://' + require('@/assets/images/u311.svg')
  const bpointPath = 'image://' + require('@/assets/images/u2536.svg')
  const rpointPath = 'image://' + require('@/assets/images/u2533.svg')
  const mpointPath = 'image://' + require('@/assets/images/u2539.svg')
  const s = []
  // 线路图
  linesData.forEach(v => {
    s.push(
      {
        type: 'lines',
        name: v.name,
        coordinateSystem: 'geo',
        effect: {
          show: true,
          period: 5,
          symbol: v.type === 'plane' ? planePath : v.type === 'ship' ? shipPath : v.type === 'train' ? trainPath : otherPath, // 飞机/轮船/火车/ufo
          symbolSize: 35,
          trailLength: 0,
          // color: '#f00',
          loop: true
        },
        lineStyle: {
          width: 2,
          curveness: 0.2,
          type: 'dashed'
        },
        data: v.data
      }
    )
  })
  // 水波图
  flays.forEach(item => {
    s.push({
      type: 'effectScatter',
      name: '水波图',
      coordinateSystem: 'geo',
      symbol: item.toName === '芬兰' ? pointPath : item.toName === '日本' ? bpointPath : item.toName === '英国' ? mpointPath : ypointPath,
      symbolSize: 60,
      rippleEffect: {
        color: '#f00',
        number: 2,
        scale: 4,
        brushType: 'stroke'
      },
      data: [
        // {
        //   fromName: item.fromName,
        //   toName: item.toName,
        //   value: item.coords[0]
        // },
        {
          fromName: item.fromName,
          toName: item.toName,
          value: item.coords[1]
        }
      ]
    })
  })
  // 插入起始点
  s.push({
    type: 'effectScatter',
    name: '水波图',
    coordinateSystem: 'geo',
    symbol: rpointPath,
    symbolSize: 60,
    rippleEffect: {
      number: 2,
      scale: 4,
      brushType: 'stroke'
    },
    data: [
      {
        fromName: flays[0].fromName,
        toName: flays[0].toName,
        value: flays[0].coords[0]
      }
    ]
  })
  return new Promise((resolve: any, reject: any) => {
    resolve(s)
  })
}
function initMap (area, zoom) {
  mapInfo.mychart = null
  const ct = echarts.init(document.querySelector('#mapdv'))
  echarts.registerMap('china', area)
  chartInfo.geo.zoom = zoom
  ct.setOption(chartInfo, true)
  mapInfo.mychart = ct
}
// 箱量
function initChat2 () {
  const data = [
    { code: '600519', stock: '铁路', fundPost: '21.987691' },
    { code: '000858', stock: '水运', fundPost: '20.377176' },
    { code: '002475', stock: '航空', fundPost: '19.127404' },
    { code: '600276', stock: '多式联运', fundPost: '18.40882' }
  ]
  const attaData = []
  const attaName = []
  const topName = []
  data.forEach((it, index) => {
    attaData[index] = parseFloat(it.fundPost).toFixed(2)
    attaName[index] = it.stock
    topName[index] = it.fundPost
  })
  const salvProMax = [] // 背景按最大值
  for (let i = 0; i < attaData.length; i++) {
    salvProMax.push(attaData[0])
  }
  const option = {
    grid: {
      top: '8%',
      bottom: 0,
      left: 0,
      right: 0,
      width: 'auto',
      containLabel: true
    },
    xAxis: {
      type: 'value',
      splitLine: {
        show: false
      },
      axisLabel: {
        show: false
      },
      axisTick: {
        show: false
      },
      axisLine: {
        show: false
      }
    },
    yAxis: [
      {
        type: 'category',
        inverse: true,
        axisLine: {
          show: false
        },
        axisTick: {
          show: false
        },
        axisLabel: {
          inside: true,
          color: '#000'
        }
      },
      {
        inverse: true,
        type: 'category',
        offset: 6,
        position: 'left',
        axisLine: {
          show: false
        },
        axisTick: {
          show: false
        },
        axisLabel: {
          inside: true,
          color: '#fff',
          align: 'left',
          verticalAlign: 'bottom',
          lineHeight: 25,
          fontSize: 10,
          formatter: (val, index) => {
            return val
          }
        },
        data: attaName
      },
      {
        inverse: true,
        type: 'category',
        offset: -25,
        position: 'right',
        axisLine: {
          show: false
        },
        axisTick: {
          show: false
        },
        axisLabel: {
          inside: true,
          color: '#fff',
          align: 'right',
          verticalAlign: 'bottom',
          lineHeight: 25,
          fontSize: 10,
          formatter: (val, index) => {
            return val
          }
        },
        data: topName
      }
    ],
    series: [
      {
        zlevel: 1,
        name: '个人所得(亿元)',
        type: 'bar',
        barWidth: '10px',
        animationDuration: 1500,
        data: attaData,
        align: 'center',
        itemStyle: {
          // barBorderRadius: 10
          color: '#00DEDA'
        },
        label: {
          show: false,
          fontSize: 10,
          color: '#fff',
          textBorderWidth: 2
        }
      },
      {
        name: '个人所得(亿元)',
        type: 'bar',
        barWidth: '10px',
        barGap: '-100%',
        margin: '20',
        data: salvProMax,
        itemStyle: {
          color: '#0B2C43'
          // width: '100%',
          // barBorderRadius: 30,
          // fontSize: 10
        }
      }
    ]
  }
  return new Promise((resolve, reject) => {
    resolve(option)
  })
}

onMounted(() => {
  loadData().then((res: any) => {
    chartInfo.series = res
    const world = require('@/assets/geoworld.json')
    initMap(world, 1)
  })
  initChat2().then(res => {
    mapInfo.options = res
  })
})
</script>

<style lang='scss' scoped>
.city_box {
  background-color: #0E1014;
  width: 100vw;
  height: 100vh;
  display: flex;
  .map_dv {
    position: relative;
    flex: 1;
    overflow: hidden;
    .map_box {
      width: 100%;
      height: 100%;
    }
  }
  .rt_num {
    width: 300px;
    height: 100%;
    display: flex;
    flex-direction: column;
    border-left: 20px solid #1C1F27;
    .line_bg {
      width: 100;
      height: 20px;
      background-color: #1C1F27;
    }
    .s_box {
      padding: 15px;
      flex: 1;
    }
    .inrer_tile {
      color: #fff;
      font-size: 18px;
      position: relative;
      padding-left: 15px;
      &:before {
        position: absolute;
        content: '';
        width: 5px;
        height: 100%;
        background-color: #66BBF9;
        top: 0;
        left: 0;
      }
    }
    .quar_box {
      position: relative;
      height: calc(100% - 30px);
      .row_box {
        display: flex;
        align-items: center;
        background-color: #0A213D;
        padding: 5px 0 5px 18px;
        margin-bottom: 10px;
        &:last-of-type {
          margin-bottom: 0;
        }
        position: relative;
        &::before {
          position: absolute;
          content: '';
          width: 5px;
          height: 100%;
          background-color: #66BBF9;
          top: 0;
          left: 0;
        }
        .icon_img {
          width: 35px;
          margin-right: 10px;
        }
        .info_box {
          .info_tile {
            color: #fff;
          }
          .info_sbox {
            color: #66BBF9;
            > span {
              font-size: 35px;
              font-weight: 600;
              padding: 0;
              height: initial;
              line-height: normal;
            }
          }
        }
      }
      .l1 {
        left: 5px;
        top: 10px;
      }
      .l2 {
        left: 100px;
        top: 30px;
      }
      .l3 {
        left: 50px;
        top: 95px;
      }
      .l4 {
        left: 155px;
        top: 100px;
      }
      .rotate_box {
        width: max-content;
        position: absolute;
        padding: 20px;
        .rotate_img {
          position: absolute;
          width: 100%;
          top: 0;
          left: 0;
          z-index: 1;
        }
        .txt_box {
          position: relative;
          z-index: 2;
          text-align: center;
          font-size: 18px;
          .g_wh {
            color: #fff;
          }
          .g_yel {
            color: #FFCC00;
          }
        }
        .gif {
          animation: rotate 5s linear infinite;
        }
      }
    }
    .rot_box {
      position: relative;
      height: 200px;
    }
  }
  .bm_dl {
    border-width: 0px;
    position: absolute;
    right: 0;
    bottom: 50px;
    width: 22px;
    height: 67px;
    background: inherit;
    background-color: rgba(1, 84, 120, 0.364705882352941);
    border: none;
    border-radius: 10px;
    border-top-left-radius: 0px;
    border-bottom-left-radius: 0px;
    -moz-box-shadow: none;
    -webkit-box-shadow: none;
    box-shadow: none;
    z-index: 1;
    cursor: pointer;
    .img_l {
      position: absolute;
      width: 100%;
      top: 50%;
      left: 2px;
      z-index: 2;
      transform: translateY(-50%);
    }
  }
  .bm_dr {
    border-width: 0px;
    position: absolute;
    right: 0;
    bottom: 50px;
    width: 22px;
    height: 67px;
    background: inherit;
    background-color: rgba(1, 84, 120, 0.364705882352941);
    border: none;
    border-radius: 10px;
    border-top-right-radius: 0px;
    border-bottom-right-radius: 0px;
    -moz-box-shadow: none;
    -webkit-box-shadow: none;
    box-shadow: none;
    z-index: 1;
    cursor: pointer;
    .img_l {
      position: absolute;
      width: 100%;
      top: 50%;
      left: 2px;
      z-index: 2;
      transform: translateY(-50%);
    }
  }
  .show_data {
    animation: showTb 0.5s linear forwards;
  }
  .hide_data {
    animation: hideTb 0.5s linear forwards;
  }
  .databox {
    position: absolute;
    width: 570px;
    bottom: 0;
    transition: all 0.5s linear;
    right: -323px;
    color: #fff;
    font-size: 14px;
    table thead, tbody tr {
      display: table;
      width: 100%;
      table-layout: fixed;
      margin-bottom: 5px;
    }
    // table thead {
    //   width: calc(100% - 1em);
    // }
    tbody {
      display: block;
      max-height: 250px;
      overflow-y: auto;
      &:before {
        content: "-";
        display: block;
        line-height: 1px;
        color: transparent;
      }
    }
    td {
      padding: 5px 10px;
      .bg_td {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
    }
    .tb_h1 {
      width: 40px;
      background: url('@/assets/images/bg_img.svg') no-repeat top center;
      background-size: 100% 100%;
    }
    .tb_head {
      position: relative;
      background-color: rgba(0, 150, 255, 0.298039215686275);
      .tb_line {
        position: absolute;
        height: 1px;
        width: 100%;
        background-color: #0A2C5A;
        top: -5px;
        left: 0;
      }
      .line_dm {
        bottom: -5px;
        top: inherit;
      }
      .pont_t {
        background-color: #00AEFF;
        width: 5px;
        height: 3px;
        position: absolute;
        top: -6px;
        left: -6px;
      }
      .pont_r {
        background-color: #00AEFF;
        width: 5px;
        height: 3px;
        position: absolute;
        top: -6px;
        right: -6px;
      }
      .pont_b {
        background-color: #00AEFF;
        width: 5px;
        height: 3px;
        position: absolute;
        bottom: -6px;
        right: -6px;
      }
      .pont_l {
        background-color: #00AEFF;
        width: 5px;
        height: 3px;
        position: absolute;
        bottom: -6px;
        left: -6px;
      }
    }
    .tb_body {
      cursor: default;
      background: linear-gradient(180deg, rgba(0, 102, 153, 1) 0%, rgba(0, 102, 153, 1) 17%, rgba(9, 60, 106, 1) 100%, rgba(9, 60, 106, 1) 100%);
      &:hover {
        background: #F0AB33;
        td {
          color: #000;
        }
        .tb_h1 {
          background: url('@/assets/images/bg_redimg.svg') no-repeat top center;
          background-size: 100% 100%;
        }
      }
    }
    .tb_y {
      color: #00FFBA;
    }
    .tb_g {
      color: #FFEA00;
    }
  }
}
@keyframes rotate {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
@keyframes showTb {
  0% {
    right: -623px;
  }
  100% {
    right: 23px;
  }
}
@keyframes hideTb {
  0% {
    right: 23px;
  }
  100% {
    right: -623px;
    display: none;
  }
}
</style>