123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- /*
- * <<
- * 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 {
- decodeMetricName,
- getTextWidth
- } from '../../components/util'
- import { getLegendOption, getLabelOption } from './util'
- import { EChartOption } from 'echarts'
- import { getFormattedValue } from '../../components/Config/Format'
- import defaultTheme from 'assets/json/echartsThemes/default.project.json'
- const defaultThemeColors = defaultTheme.theme.color
- export default function (chartProps: IChartProps, drillOptions?: any) {
- const {
- width,
- height,
- data,
- cols,
- metrics,
- chartStyles,
- color,
- tip
- } = chartProps
- const { label, legend, spec, toolbox } = chartStyles
- const { legendPosition, fontSize } = legend
- const { alignmentMode, gapNumber, sortMode } = spec
- const labelOption = {
- label: getLabelOption('funnel', label, metrics)
- }
- const { selectedItems } = drillOptions
- let seriesObj = {}
- const seriesArr = []
- const legendData = []
- let grouped: { [key: string]: object[] } = {}
- if (metrics.length <= 1) {
- const groupColumns = color.items
- .map((c) => c.name)
- .concat(cols.map((c) => c.name))
- .reduce((distinctColumns, col) => {
- if (!distinctColumns.includes(col)) {
- distinctColumns.push(col)
- }
- return distinctColumns
- }, [])
- grouped = data.reduce<{ [key: string]: object[] }>((obj, val) => {
- const groupingKey = groupColumns
- .reduce((keyArr, col) => keyArr.concat(val[col]), [])
- .join(String.fromCharCode(0))
- if (!obj[groupingKey]) {
- obj[groupingKey] = []
- }
- obj[groupingKey].push(val)
- return obj
- }, {})
- metrics.forEach((metric) => {
- const decodedMetricName = decodeMetricName(metric.name)
- const metricNameWithAgg = `${metric.agg}(${decodedMetricName})`
- const seriesData = []
- Object.entries(grouped).forEach(([key, value]) => {
- const legendStr = key.replace(String.fromCharCode(0), ' ')
- legendData.push(legendStr)
- value.forEach((v) => {
- const obj = {
- name: legendStr,
- value: v[metricNameWithAgg]
- }
- seriesData.push(obj)
- })
- })
- const maxValue = Math.max(
- ...data.map((s) => s[metricNameWithAgg])
- )
- const minValue = Math.min(
- ...data.map((s) => s[metricNameWithAgg])
- )
- const numValueArr = data.map(
- (d) => d[metricNameWithAgg] >= 0
- )
- const minSizePer = (minValue / maxValue) * 100
- const minSizeValue =
- numValueArr.indexOf(false) === -1 ? `${minSizePer}%` : '0%'
- const funnelLeft =
- 56 +
- Math.max(...legendData.map((s) => getTextWidth(s, '', `${fontSize}px`)))
- const leftValue =
- legendPosition === 'left' ? width * 0.15 + funnelLeft : width * 0.15
- const topValue =
- legendPosition === 'top' ? height * 0.12 + 32 : height * 0.12
- const heightValue =
- legendPosition === 'left' || legendPosition === 'right'
- ? height - height * 0.12 * 2
- : height - 32 - height * 0.12 * 2
- const widthValue =
- legendPosition === 'left' || legendPosition === 'right'
- ? width - funnelLeft - width * 0.15 * 2
- : width - width * 0.15 * 2
- seriesObj = {
- name: '',
- type: 'funnel',
- min: minValue,
- max: maxValue,
- minSize: minSizeValue,
- maxSize: '100%',
- sort: sortMode,
- funnelAlign: alignmentMode,
- gap: gapNumber || 0,
- left: leftValue,
- top: topValue,
- width: widthValue,
- height: heightValue,
- data: getFunnelSeriesData(seriesData)
- .map((data, index) => {
- return {
- ...data,
- itemStyle: {
- normal: {
- ...color.items.length && {
- color: color.items[0].config.values[data.name]
- },
- opacity: selectedItems && selectedItems.length
- ? selectedItems.includes(index) ? 1 : 0.25
- : 1
- }
- }
- }
- }),
- itemStyle: {
- emphasis: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- },
- ...labelOption
- }
- seriesArr.push(seriesObj)
- })
- } else {
- const seriesData = []
- metrics.forEach((metric) => {
- const decodedMetricName = decodeMetricName(metric.name)
- legendData.push(decodedMetricName)
- seriesData.push({
- name: decodedMetricName,
- metricName: metric.name,
- value: data.reduce((sum, record) => sum + record[`${metric.agg}(${decodedMetricName})`], 0)
- })
- })
- seriesObj = {
- type: 'funnel',
- sort: sortMode,
- funnelAlign: alignmentMode,
- gap: gapNumber || 0,
- left: width * 0.15,
- top: height * 0.12,
- width: width - width * 0.15 * 2,
- height: height - height * 0.12 * 2,
- data: getFunnelSeriesData(seriesData)
- .map((data, index) => ({
- ...data,
- itemStyle: {
- normal: {
- color: color.value[data.metricName] || defaultThemeColors[index % defaultThemeColors.length],
- opacity: selectedItems && selectedItems.length
- ? selectedItems.includes(index) ? 1 : 0.25
- : 1
- }
- }
- })),
- itemStyle: {
- emphasis: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- },
- ...labelOption
- }
- seriesArr.push(seriesObj)
- }
- const tooltip: EChartOption.Tooltip = {
- trigger: 'item',
- formatter (params: EChartOption.Tooltip.Format) {
- const { color, name, value, percent, dataIndex, data } = params
- const formattedValue = getFormattedValue(
- value as number,
- metrics[metrics.length > 1 ? dataIndex : 0].format
- )
- const tooltipLabels = []
- let basicInfo = `${name}: ${formattedValue}`
- if (color) {
- basicInfo = `<span class="widget-tooltip-circle" style="background: ${color}"></span> ${basicInfo}`
- }
- tooltipLabels.push(basicInfo)
- if (data.conversion) {
- tooltipLabels.push(`转化率: ${data.conversion}%`)
- }
- if (data.arrival) {
- tooltipLabels.push(`到达率: ${data.arrival}%`)
- }
- tooltipLabels.push(`百分比: ${percent}%`)
- return tooltipLabels.join('<br/>')
- }
- }
- return {
- tooltip,
- legend: getLegendOption(legend, legendData),
- series: seriesArr
- }
- }
- function getFunnelSeriesData (seriesData) {
- return seriesData
- .sort((d1, d2) => d2.value - d1.value)
- .map((d, index) => {
- if (index) {
- d.conversion = formatPercent(d.value / seriesData[index - 1].value * 100)
- d.arrival = formatPercent(d.value / seriesData[0].value * 100)
- }
- return d
- })
- }
- function formatPercent (per) {
- const perStr = per + ''
- return perStr.length - (perStr.indexOf('.') + 1) > 2
- ? per.toFixed(2)
- : perStr
- }
|