| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 | /* * << * Davinci * == * Copyright (C) 2016 - 2017 EDP * == * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * >> */import { IChartProps } from '../../components/Chart'import { EChartOption } from 'echarts'import {  decodeMetricName,  getTextWidth,  getSizeRate} from '../../components/util'import {  getLegendOption,  getLabelOption,  getSymbolSize} from './util'import {  safeAddition} from 'utils/util'import {  DEFAULT_ECHARTS_THEME} from 'app/globalConstants'import geoData from 'assets/js/geo.js'import { getFormattedValue } from '../../components/Config/Format'const provinceSuffix = ['省', '自治区', '市']const citySuffix = ['自治州', '市', '区', '县', '旗', '盟', '镇']export default function (chartProps: IChartProps) {  const {    chartStyles,    data,    cols,    metrics,    model  } = chartProps  const {    label,    spec  } = chartStyles  const {    labelColor,    labelFontFamily,    labelFontSize,    labelPosition,    showLabel  } = label  const {    layerType,    roam,    linesSpeed,    symbolType  } = spec  const tooltip: EChartOption.Tooltip = {    trigger: 'item',    formatter: (params: EChartOption.Tooltip.Format) => {      const { name, data, color } = params      const tooltipLabels = []      if (color) {        tooltipLabels.push(`<span class="widget-tooltip-circle" style="background: ${color}"></span>`)      }      tooltipLabels.push(name)      if (data) {        tooltipLabels.push(': ')        tooltipLabels.push(getFormattedValue(data.value[2], metrics[0].format))      }      return tooltipLabels.join('')    }  }  const labelOption = {    label: {      normal: {        formatter: '{b}',        position: labelPosition,        show: showLabel,        color: labelColor,        fontFamily: labelFontFamily,        fontSize: labelFontSize      }    }  }  const labelOptionLines = {    label: getLabelOption('lines', label, metrics, true)  }  let metricOptions  let visualMapOptions  const dataTree = {}  let min = 0  let max = 0  const agg = metrics[0].agg  const metricName = decodeMetricName(metrics[0].name)  data.forEach((record) => {    let areaVal    const group = []    const value = record[`${agg}(${metricName})`]    min = Math.min(min, value)    max = Math.max(max, value)    cols.forEach((col) => {      const { visualType } = model[col.name]      if (visualType === 'geoProvince') {        areaVal = record[col.name]        const area = getProvinceArea(areaVal)        const provinceName = getProvinceName(areaVal)        if (area) {          if (!dataTree[provinceName]) {            dataTree[provinceName] = {              lon: area.lon,              lat: area.lat,              value,              children: {}            }          }        }      } else if (visualType === 'geoCity') {        areaVal = record[col.name]        const area = getCityArea(areaVal)        if (area) {          if (layerType === 'map') {            const provinceParent = getProvinceParent(area)            const parentName = getProvinceName(provinceParent.name)            if (!dataTree[parentName]) {              dataTree[parentName] = {                lon: area.lon,                lat: area.lat,                value: 0,                children: {}              }            }            dataTree[parentName].value += value          } else {            if (!dataTree[areaVal]) {              dataTree[areaVal] = {                lon: area.lon,                lat: area.lat,                value,                children: {}              }            }          }        }      }      // todo: 除去显示城市/省的      // const group = ['name', 'sex']      // if (group.length) {      //   group.forEach((g) => {      //     if (!dataTree[areaVal].children[record[g]]) {      //       dataTree[areaVal].children[record[g]] = 0      //     }      //     dataTree[areaVal].children[record[g]] = safeAddition(dataTree[areaVal].children[record[g]], Number(value))      //   })      // }    })  })  // series 数据项  const metricArr = []  const sizeRate = getSizeRate(min, max)  const optionsType = layerType === 'scatter' ? {} : {    blurSize: 40  }  let serieObj  switch (layerType) {    case 'map':      serieObj = {        name: '地图',        type: 'map',        mapType: 'china',        roam,        data: Object.keys(dataTree).map((key, index) => {          const { lon, lat, value } = dataTree[key]          return {            name: key,            value: [lon, lat, value]          }        }),        ...labelOption      }      break    case 'scatter':      serieObj = {        name: '气泡图',        type: 'scatter',        coordinateSystem: 'geo',        data: Object.keys(dataTree).map((key, index) => {          const { lon, lat, value } = dataTree[key]          return {            name: key,            value: [lon, lat, value],            symbolSize: getSymbolSize(sizeRate, value) / 2          }        }),        ...labelOption,        ...optionsType      }      break    case 'heatmap':      serieObj = {        name: '热力图',        type: 'heatmap',        coordinateSystem: 'geo',        data: Object.keys(dataTree).map((key, index) => {          const { lon, lat, value } = dataTree[key]          return {            name: key,            value: [lon, lat, value],            symbolSize: getSymbolSize(sizeRate, value) / 2          }        }),        ...labelOption,        ...optionsType      }      break  }  metricArr.push(serieObj)  metricOptions = {    series: metricArr  }  if (chartStyles.visualMap) {    const {      showVisualMap,      visualMapPosition,      fontFamily,      fontSize,      visualMapDirection,      visualMapWidth,      visualMapHeight,      startColor,      endColor    } = chartStyles.visualMap    visualMapOptions = {      visualMap: {        show: layerType === 'lines' ? false : showVisualMap,        min,        max,        calculable: true,        inRange: {          color: [startColor, endColor]        },        ...getPosition(visualMapPosition),        itemWidth: visualMapWidth,        itemHeight: visualMapHeight,        textStyle: {          fontFamily,          fontSize        },        orient: visualMapDirection      }    }  } else {    visualMapOptions = {      visualMap: {        show: false,        min,        max,        calculable: true,        inRange: {          color: DEFAULT_ECHARTS_THEME.visualMapColor        },        left: 10,        bottom: 20,        itemWidth: 20,        itemHeight: 50,        textStyle: {          fontFamily: 'PingFang SC',          fontSize: 12        },        orient: 'vertical'      }    }  }  const getGeoCity = cols.filter((c) => model[c.name].visualType === 'geoCity')  const getGeoProvince = cols.filter((c) => model[c.name].visualType === 'geoProvince')  const linesSeries = []  const legendData = []  let effectScatterType  let linesType  data.forEach((d, index) => {    let linesSeriesData = []    let scatterData = []    const value = d[`${agg}(${metricName})`]    if (getGeoCity.length > 1 && d[getGeoCity[0].name] && d[getGeoCity[1].name]) {      const fromCityInfo = getCityArea(d[getGeoCity[0].name])      const toCityInfo = getCityArea(d[getGeoCity[1].name])      if (fromCityInfo && toCityInfo) {        legendData.push(d[getGeoCity[0].name])        linesSeriesData = [{          fromName: d[getGeoCity[0].name],          toName: d[getGeoCity[1].name],          coords: [[fromCityInfo.lon, fromCityInfo.lat], [toCityInfo.lon, toCityInfo.lat]]        }]        scatterData = [{          name: d[getGeoCity[1].name],          value: [toCityInfo.lon, toCityInfo.lat, value]        }]      }    } else if (getGeoProvince.length > 1 && d[getGeoProvince[0].name] && d[getGeoProvince[1].name]) {      const fromProvinceInfo = getProvinceArea(d[getGeoProvince[0].name])      const toProvinceInfo = getProvinceArea(d[getGeoProvince[1].name])      if (fromProvinceInfo && toProvinceInfo) {        legendData.push(d[getGeoProvince[0].name])        linesSeriesData = [{          fromName: d[getGeoProvince[0].name],          toName: d[getGeoProvince[1].name],          coords: [[fromProvinceInfo.lon, fromProvinceInfo.lat], [toProvinceInfo.lon, toProvinceInfo.lat]]        }]        scatterData = [{          name: d[getGeoProvince[1].name],          value: [toProvinceInfo.lon, toProvinceInfo.lat, value]        }]      }    } else {      return    }    effectScatterType = {      name: '',      type: 'effectScatter',      coordinateSystem: 'geo',      zlevel: index,      rippleEffect: {          brushType: 'stroke'      },      ...labelOptionLines,      symbolSize: (val) => {          return 12      },      data: scatterData    }    linesType = {      name: '',      type: 'lines',      zlevel: index,      symbol: ['none', 'arrow'],      symbolSize: 10,      effect: {          show: true,          // period: 600,          trailLength: 0,          symbol: symbolType,          symbolSize: 15,          constantSpeed: linesSpeed      },      lineStyle: {          normal: {              width: 2,              opacity: 0.6,              curveness: 0.2          }      },      data: linesSeriesData    }    linesSeries.push(linesType, effectScatterType)  })  let legendOption  if (chartStyles.legend) {    const {      color,      fontFamily,      fontSize,      legendPosition,      selectAll,      showLegend    } = chartStyles.legend    legendOption = {      legend: getLegendOption(chartStyles.legend, legendData)    }  } else {    legendOption = null  }  let mapOptions  switch (layerType) {    case 'map':      mapOptions = {        ...metricOptions,        ...visualMapOptions,        tooltip      }      break    case 'lines':      mapOptions = {        ...legendOption,        geo: {          map: 'china',          roam        },        series: linesSeries,        ...visualMapOptions      }      break    case 'scatter':      mapOptions = {        geo: {          map: 'china',          itemStyle: {            normal: {              areaColor: '#cccccc',              borderColor: '#ffffff',              borderWidth: 1            },            emphasis: {              areaColor: '#bbbbbb'            }          },          roam        },        ...metricOptions,        ...visualMapOptions,        tooltip      }      break    case 'heatmap':      mapOptions = {        geo: {          map: 'china',          itemStyle: {            normal: {              areaColor: '#cccccc',              borderColor: '#ffffff',              borderWidth: 1            },            emphasis: {              areaColor: '#bbbbbb'            }          },          label: {            emphasis: {                show: true            }          },          roam        },        ...metricOptions,        ...visualMapOptions        // ...tooltipOptions      }      break  }  return mapOptions}function getProvinceParent (area) {  if (!area.parent) {    return area  }  const parent = geoData.find((g) => g.id === area.parent)  return !parent.parent ? parent : getProvinceParent(parent)}function getProvinceName (name) {  provinceSuffix.forEach((ps) => {    if (name.includes(ps)) {      name = name.replace(ps, '')    }  })  return name}function getCityArea (name) {  const hasSuffix = citySuffix.some((p) => name.includes(p))  const area = hasSuffix    ? geoData.find((d) => d.name === name)    : geoData.find((d) => d.name.includes(name))  return area}function getProvinceArea (name) {  const hasSuffix = provinceSuffix.some((p) => name.includes(p))  const area = hasSuffix    ? geoData.find((d) => d.name === name && !d.parent)    : geoData.find((d) => d.name.includes(name) && !d.parent)  return area}function getPosition (position) {  let positionValue  switch (position) {    case 'leftBottom':      positionValue = {        left: 'left',        top: 'bottom'      }      break    case 'leftTop':      positionValue = {        left: 'left',        top: 'top'      }      break    case 'rightTop':      positionValue = {        left: 'right',        top: 'top'      }      break    case 'rightBottom':      positionValue = {        left: 'right',        top: 'bottom'      }      break  }  return positionValue}
 |