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
- }
|