feat(图表-地图): 支持自定义图例区间及图例个数 #9493 #10164

This commit is contained in:
jianneng-fit2cloud 2024-06-21 12:51:53 +08:00
parent ae492053ad
commit 93308d84df
8 changed files with 268 additions and 16 deletions

View File

@ -660,6 +660,7 @@ export default {
horizontal: '水平',
vertical: '垂直',
legend: '图例',
legend_num: '图例数',
shape: '形状',
polygon: '多边形',
circle: '圆形',

View File

@ -612,6 +612,22 @@ declare interface ChartMiscAttr {
* 词云图文字间距
*/
wordSpacing: number
/**
* 自动图例
*/
mapAutoLegend: boolean
/**
* 图例最大值
*/
mapLegendMax: number
/**
* 图例最小值
*/
mapLegendMin: number
/**
* 显示图例个数
*/
mapLegendNumber: number
}
/**
* 动态极值配置

View File

@ -260,6 +260,7 @@ watch(
:themes="themes"
:chart="chart"
@onLegendChange="onLegendChange"
@onMiscChange="onMiscChange"
/>
</collapse-switch-item>
<el-collapse-item

View File

@ -1,8 +1,14 @@
<script lang="tsx" setup>
import { computed, onMounted, reactive, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL, DEFAULT_LEGEND_STYLE } from '@/views/chart/components/editor/util/chart'
import { ElSpace } from 'element-plus-secondary'
import {
COLOR_PANEL,
DEFAULT_LEGEND_STYLE,
DEFAULT_MISC
} from '@/views/chart/components/editor/util/chart'
import { ElCol, ElRow, ElSpace } from 'element-plus-secondary'
import { cloneDeep } from 'lodash-es'
import { useEmitt } from '@/hooks/web/useEmitt'
const { t } = useI18n()
@ -14,8 +20,11 @@ const props = withDefaults(
}>(),
{ themes: 'dark' }
)
const emit = defineEmits(['onLegendChange'])
useEmitt({
name: 'map-default-range',
callback: args => mapDefaultRange(args)
})
const emit = defineEmits(['onLegendChange', 'onMiscChange'])
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
@ -36,7 +45,15 @@ const iconSymbolOptions = [
]
const state = reactive({
legendForm: JSON.parse(JSON.stringify(DEFAULT_LEGEND_STYLE))
legendForm: {
...JSON.parse(JSON.stringify(DEFAULT_LEGEND_STYLE)),
miscForm: JSON.parse(JSON.stringify(DEFAULT_MISC)) as ChartMiscAttr
}
})
const chartType = computed(() => {
const chart = JSON.parse(JSON.stringify(props.chart))
return chart?.type
})
const fontSizeList = computed(() => {
@ -54,6 +71,10 @@ const changeLegendStyle = prop => {
emit('onLegendChange', state.legendForm, prop)
}
const changeMisc = prop => {
emit('onMiscChange', { data: state.legendForm.miscForm, requestData: true }, prop)
}
const init = () => {
const chart = JSON.parse(JSON.stringify(props.chart))
if (chart.customStyle) {
@ -63,13 +84,21 @@ const init = () => {
} else {
customStyle = JSON.parse(chart.customStyle)
}
const miscStyle = cloneDeep(props.chart.customAttr.misc)
if (customStyle.legend) {
state.legendForm = customStyle.legend
state.legendForm.miscForm = miscStyle
}
}
}
const showProperty = prop => props.propertyInner?.includes(prop)
const mapDefaultRange = args => {
if (args.from === 'map') {
state.legendForm.miscForm.mapLegendMax = args.data.max
state.legendForm.miscForm.mapLegendMin = args.data.min
state.legendForm.miscForm.mapLegendNumber = args.data.legendNumber
}
}
onMounted(() => {
init()
})
@ -145,6 +174,82 @@ onMounted(() => {
</el-tooltip>
</el-form-item>
</el-space>
<el-space>
<div v-if="chartType === 'map'">
<el-row>
<el-col>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.legend')"
>
<el-checkbox
size="small"
:effect="themes"
v-model="state.legendForm.miscForm.mapAutoLegend"
@change="changeMisc('mapAutoLegend')"
>
{{ t('chart.margin_model_auto') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<div v-if="!state.legendForm.miscForm.mapAutoLegend">
<el-row :gutter="8">
<el-col :span="12">
<el-form-item
:label="t('chart.max')"
class="form-item"
:class="'form-item-' + themes"
>
<el-input-number
:effect="themes"
v-model="state.legendForm.miscForm.mapLegendMax"
size="small"
controls-position="right"
@change="changeMisc('mapLegendMax')"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
:label="t('chart.min')"
class="form-item"
:class="'form-item-' + themes"
>
<el-input-number
:effect="themes"
v-model="state.legendForm.miscForm.mapLegendMin"
size="small"
controls-position="right"
@change="changeMisc('mapLegendMin')"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.legend_num')"
>
<el-input-number
:effect="themes"
v-model="state.legendForm.miscForm.mapLegendNumber"
size="small"
:min="1"
:max="9"
controls-position="right"
@change="changeMisc('mapLegendNumber')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
</el-space>
<el-form-item
:label="t('chart.orient')"
class="form-item"

View File

@ -262,7 +262,11 @@ export const DEFAULT_MISC: ChartMiscAttr = {
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC',
wordSizeRange: [8, 32],
wordSpacing: 6
wordSpacing: 6,
mapAutoLegend: true,
mapLegendMax: 0,
mapLegendMin: 0,
mapLegendNumber: 9
}
export const DEFAULT_MARK = {

View File

@ -3,7 +3,15 @@ import {
L7PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/l7plot'
import { Choropleth, ChoroplethOptions } from '@antv/l7plot/dist/esm/plots/choropleth'
import { flow, getGeoJsonFile, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import {
filterChartDataByRange,
flow,
getDynamicColorScale,
getGeoJsonFile,
setMapChartDefaultMaxAndMinValueByData,
hexColorToRGBA,
parseJson
} from '@/views/chart/components/js/util'
import { handleGeoJson } from '@/views/chart/components/js/panel/common/common_antv'
import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
import { cloneDeep } from 'lodash-es'
@ -50,6 +58,30 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
if (!areaId) {
return
}
const sourceData = JSON.parse(JSON.stringify(chart.data?.data || []))
let data = []
const { misc } = parseJson(chart.customAttr)
const { legend } = parseJson(chart.customStyle)
// 自定义图例
if (!misc.mapAutoLegend && legend.show) {
let minValue = misc.mapLegendMin
let maxValue = misc.mapLegendMax
setMapChartDefaultMaxAndMinValueByData(sourceData, maxValue, minValue, (max, min) => {
maxValue = max
minValue = min
action({
from: 'map',
data: {
max: maxValue,
min: minValue,
legendNumber: 9
}
})
})
data = filterChartDataByRange(sourceData, maxValue, minValue)
} else {
data = sourceData
}
const geoJson = cloneDeep(await getGeoJsonFile(areaId))
let options: ChoroplethOptions = {
preserveDrawingBuffer: true,
@ -61,7 +93,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
type: 'geojson'
},
source: {
data: chart.data?.data || [],
data: data,
joinBy: {
sourceField: 'name',
geoField: 'name',
@ -125,7 +157,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
): ChoroplethOptions {
const { areaId }: L7PlotDrawOptions<any> = context.drawOption
const geoJson: FeatureCollection = context.geoJson
const { basicStyle, label } = parseJson(chart.customAttr)
const { basicStyle, label, misc } = parseJson(chart.customAttr)
const senior = parseJson(chart.senior)
const curAreaNameMapping = senior.areaMapping?.[areaId]
handleGeoJson(geoJson, curAreaNameMapping)
@ -141,7 +173,32 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
options.label && (options.label.field = 'name')
return options
}
const data = chart.data.data
const sourceData = JSON.parse(JSON.stringify(chart.data.data))
const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
const { legend } = parseJson(chart.customStyle)
let data = []
let colorScale = []
if (legend.show) {
let minValue = misc.mapLegendMin
let maxValue = misc.mapLegendMax
let mapLegendNumber = misc.mapLegendNumber
setMapChartDefaultMaxAndMinValueByData(sourceData, maxValue, minValue, (max, min) => {
maxValue = max
minValue = min
mapLegendNumber = 9
})
// 非自动过滤数据
if (!misc.mapAutoLegend) {
data = filterChartDataByRange(sourceData, maxValue, minValue)
} else {
mapLegendNumber = 9
}
// 定义最大值最小值区间数量和对应的颜色
colorScale = getDynamicColorScale(minValue, maxValue, mapLegendNumber, colors)
} else {
data = sourceData
colorScale = colors
}
const areaMap = data.reduce((obj, value) => {
obj[value['field']] = value.value
return obj
@ -164,12 +221,11 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
item.properties['_DE_LABEL_'] = content.join('\n\n')
}
})
let colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
if (validArea < colors.length) {
colors = colors.slice(0, validArea)
if (validArea < colorScale.length && !misc.mapAutoLegend) {
colorScale = colorScale.map(item => (item.color ? item.color : item)).slice(0, validArea)
}
if (colors.length) {
options.color['value'] = colors
if (colorScale.length) {
options.color['value'] = colorScale.map(item => (item.color ? item.color : item))
}
return options
}

View File

@ -527,3 +527,68 @@ export const copyString = (content: string, notify = false) => {
}
})
}
/**
* 计算动态区间和颜色
* @param minValue
* @param maxValue
* @param intervals
* @param colors
*/
export const getDynamicColorScale = (
minValue: number,
maxValue: number,
intervals: number,
colors: string[]
) => {
const step = (maxValue - minValue) / intervals
const colorScale = []
for (let i = 0; i < intervals; i++) {
colorScale.push({
value: [minValue + i * step, minValue + (i + 1) * step],
color: colors[i],
label: `${(minValue + i * step).toFixed(2)} - ${(minValue + (i + 1) * step).toFixed(2)}`
})
}
return colorScale
}
/**
* 过滤掉不在区间的数据
* @param data
* @param maxValue
* @param minValue
*/
export const filterChartDataByRange = (data: any[], maxValue: number, minValue: number) => {
return data.filter(
item =>
item.value === null ||
item.value === undefined ||
(item.value >= minValue && item.value <= maxValue)
)
}
/**
* 获取地图默认最大最小值根据数据
* @param data
* @param maxValue
* @param minValue
* @param callback
*/
export const setMapChartDefaultMaxAndMinValueByData = (
data: any[],
maxValue: number,
minValue: number,
callback: (max: number, min: number) => void
) => {
if (minValue === 0 && maxValue === 0) {
const maxResult = data.reduce((max, current) => {
return current.value > max ? current.value : max
}, Number.MIN_SAFE_INTEGER)
const minResult = data.reduce((min, current) => {
return current.value < min ? current.value : min
}, Number.MAX_SAFE_INTEGER)
callback(maxResult, minResult)
}
}

View File

@ -287,6 +287,10 @@ const pointClickTrans = () => {
}
const action = param => {
if (param.from === 'map') {
emitter.emit('map-default-range', param)
return
}
state.pointParam = param.data
//
pointClickTrans()