/* * << * 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(``) } 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 }