feat(图表): 地图支持自定义图例区间

This commit is contained in:
jianneng-fit2cloud 2024-10-15 18:38:22 +08:00
parent 8d15eebe02
commit 3b3cef670c
5 changed files with 263 additions and 43 deletions

View File

@ -681,6 +681,15 @@ declare interface ChartMiscAttr {
* 显示图例个数
*/
mapLegendNumber: number
/**
* 自定义区间类型等间距自定义
*/
mapLegendRangeType: 'quantize' | 'custom'
/**
* 自定义区间类型为自定义(custom)时生效
* 自定义区间值
*/
mapLegendCustomRange: number[]
/**
* 流向地图配置
*/

View File

@ -15,6 +15,7 @@ import {
import { ElCol, ElRow, ElSpace } from 'element-plus-secondary'
import { cloneDeep } from 'lodash-es'
import { useEmitt } from '@/hooks/web/useEmitt'
import { getDynamicColorScale } from '@/views/chart/components/js/util'
const { t } = useI18n()
@ -99,18 +100,114 @@ const init = () => {
if (!state.legendForm.miscForm.hasOwnProperty('mapAutoLegend')) {
state.legendForm.miscForm.mapAutoLegend = true
}
if (!state.legendForm.miscForm.hasOwnProperty('mapLegendRangeType')) {
state.legendForm.miscForm.mapLegendRangeType = 'quantize'
}
if (!state.legendForm.miscForm.hasOwnProperty('mapLegendCustomRange')) {
state.legendForm.miscForm.mapLegendCustomRange = []
}
initMapCustomRange()
}
}
}
}
//
const mapLegendDefaultRange = {
max: 0,
min: 0
}
//
let mapLegendCustomRangeCacheList = []
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
const rangeCustom = state.legendForm.miscForm.mapLegendRangeType === 'custom'
if (!rangeCustom) {
state.legendForm.miscForm.mapLegendMax = cloneDeep(args.data.max)
state.legendForm.miscForm.mapLegendMin = cloneDeep(args.data.min)
}
state.legendForm.miscForm.mapLegendNumber = cloneDeep(args.data.legendNumber)
mapLegendCustomRangeCacheList = []
mapLegendDefaultRange.max = cloneDeep(args.data.max)
mapLegendDefaultRange.min = cloneDeep(args.data.min)
const customRange = getDynamicColorScale(
mapLegendDefaultRange.min,
mapLegendDefaultRange.max,
args.data.legendNumber
)
customRange.forEach((item, index) => {
if (index === 0) {
mapLegendCustomRangeCacheList.push(...item.value)
} else {
mapLegendCustomRangeCacheList.push(item.value[1])
}
})
}
}
const initMapCustomRange = () => {
const legendCustom = state.legendForm.miscForm.mapAutoLegend
const rangeCustom = state.legendForm.miscForm.mapLegendRangeType === 'custom'
const rangeCustomValue = state.legendForm.miscForm.mapLegendCustomRange
// rangeCustomValue0
if (legendCustom && rangeCustom && rangeCustomValue.length === 0) {
calcMapCustomRange()
}
}
/**
* 计算自定义区间
*/
const calcMapCustomRange = () => {
const customRange = getDynamicColorScale(
mapLegendDefaultRange.min,
mapLegendDefaultRange.max,
state.legendForm.miscForm.mapLegendNumber
)
state.legendForm.miscForm.mapLegendCustomRange = []
customRange.forEach((item, index) => {
if (index === 0) {
state.legendForm.miscForm.mapLegendCustomRange.push(...item.value)
} else {
state.legendForm.miscForm.mapLegendCustomRange.push(item.value[1])
}
})
}
/**
* 改变自定义区间类型
* @param prop
*/
const changeLegendCustomType = (prop?) => {
const type = state.legendForm.miscForm.mapLegendRangeType
if (type === 'custom') {
state.legendForm.miscForm.mapLegendCustomRange = cloneDeep(
mapLegendCustomRangeCacheList.slice(0, state.legendForm.miscForm.mapLegendNumber + 1)
)
} else {
state.legendForm.miscForm.mapLegendCustomRange = []
}
prop ? changeMisc(prop) : ''
}
/**
* 改变自定义区间个数
* @param prop
*/
const changeLegendNumber = (prop?) => {
if (!state.legendForm.miscForm.mapLegendNumber) {
return
}
calcMapCustomRange()
prop ? changeMisc(prop) : ''
}
const changeRangeItem = (prop, index) => {
console.log(state.legendForm.miscForm.mapLegendCustomRange[index])
console.log(mapLegendCustomRangeCacheList[index])
if (state.legendForm.miscForm.mapLegendCustomRange[index] === null) {
state.legendForm.miscForm.mapLegendCustomRange[index] = cloneDeep(
mapLegendCustomRangeCacheList[index]
)
console.log(state.legendForm.miscForm.mapLegendCustomRange[index])
}
changeMisc(prop)
}
onMounted(() => {
init()
})
@ -194,48 +291,58 @@ onMounted(() => {
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.legend')"
prop="miscForm.mapAutoLegend"
>
<el-checkbox
<el-radio
size="small"
:effect="themes"
v-model="state.legendForm.miscForm.mapAutoLegend"
:label="true"
@change="changeMisc('mapAutoLegend')"
style="width: 80px"
>
{{ t('chart.margin_model_auto') }}
</el-checkbox>
</el-radio>
<el-radio
size="small"
:effect="themes"
v-model="state.legendForm.miscForm.mapAutoLegend"
:label="false"
@change="changeMisc('mapAutoLegend')"
>
自定义
</el-radio>
</el-form-item>
</el-col>
</el-row>
<div v-if="!state.legendForm.miscForm.mapAutoLegend">
<el-row :gutter="8">
<el-col :span="12">
<el-row>
<el-col>
<el-form-item
:label="t('chart.max')"
class="form-item"
:class="'form-item-' + themes"
label="图例区间划分"
prop="miscForm.mapLegendRangeType"
>
<el-input-number
:effect="themes"
v-model="state.legendForm.miscForm.mapLegendMax"
<el-radio
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"
v-model="state.legendForm.miscForm.mapLegendRangeType"
:label="'quantize'"
@change="changeLegendCustomType('mapLegendRangeType')"
style="width: 80px"
>
等分区间
</el-radio>
<el-radio
size="small"
controls-position="right"
@change="changeMisc('mapLegendMin')"
/>
:effect="themes"
v-model="state.legendForm.miscForm.mapLegendRangeType"
:label="'custom'"
@change="changeLegendCustomType('mapLegendRangeType')"
>
自定义区间
</el-radio>
</el-form-item>
</el-col>
</el-row>
@ -254,8 +361,70 @@ onMounted(() => {
:min="1"
:max="9"
:step="1"
:controls="true"
controls-position="right"
@change="changeMisc('mapLegendNumber')"
@change="changeLegendNumber('mapLegendNumber')"
/>
</el-form-item>
</el-col>
</el-row>
<div v-if="state.legendForm.miscForm.mapLegendRangeType === 'custom'">
<el-row
:gutter="8"
:key="index"
v-for="(_value, index) in state.legendForm.miscForm.mapLegendCustomRange"
>
<el-col :span="8">
<lable class="ed-form-item__label">
{{ index === 0 ? '最小值' : '' }}
{{ index === state.legendForm.miscForm.mapLegendNumber ? '最大值' : '' }}
</lable>
</el-col>
<el-col :span="16">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-input-number
:effect="themes"
v-model="state.legendForm.miscForm.mapLegendCustomRange[index]"
size="small"
clearable
:value-on-clear="mapLegendCustomRangeCacheList[index]"
controls-position="right"
@change="changeRangeItem('mapLegendCustomRange', index)"
style="margin-bottom: 4px"
:step="1"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row :gutter="8" v-if="state.legendForm.miscForm.mapLegendRangeType === 'quantize'">
<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-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>

View File

@ -275,6 +275,8 @@ export const DEFAULT_MISC: ChartMiscAttr = {
mapLegendMax: 0,
mapLegendMin: 0,
mapLegendNumber: 9,
mapLegendRangeType: 'quantize',
mapLegendCustomRange: [],
flowMapConfig: {
lineConfig: {
mapLineAnimate: true,

View File

@ -88,6 +88,12 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
if (!misc.mapAutoLegend && legend.show) {
let minValue = misc.mapLegendMin
let maxValue = misc.mapLegendMax
let legendNumber = 9
if (misc.mapLegendRangeType === 'custom') {
maxValue = 0
minValue = 0
legendNumber = misc.mapLegendNumber
}
getMaxAndMinValueByData(sourceData, 'value', maxValue, minValue, (max, min) => {
maxValue = max
minValue = min
@ -96,7 +102,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
data: {
max: maxValue,
min: minValue,
legendNumber: 9
legendNumber: legendNumber
}
})
})
@ -257,7 +263,8 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
options: ChoroplethOptions,
_context: Record<string, any>
): ChoroplethOptions {
const { basicStyle } = parseJson(chart.customAttr)
const { basicStyle, misc } = parseJson(chart.customAttr)
const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
if (basicStyle.suspension === false && basicStyle.showZoom === undefined) {
return options
}
@ -282,7 +289,49 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
}
const customLegend = {
position: 'bottomleft',
customContent: (_: string, items: CategoryLegendListItem[]) => {
domStyles: {
'l7plot-legend__category-value': {
fontSize: legend.fontSize + 'px',
color: legend.color
},
'l7plot-legend__category-marker': {
...LEGEND_SHAPE_STYLE_MAP[legend.icon]
}
}
}
if (!misc.mapAutoLegend && misc.mapLegendRangeType === 'custom') {
// 获取图例区间数据
const items = []
// 区间数组
const ranges = misc.mapLegendCustomRange
.slice(0, -1)
.map((item, index) => [item, misc.mapLegendCustomRange[index + 1]])
ranges.forEach((range, index) => {
const tmpRange = [range[0]?.toFixed(0), range[1]?.toFixed(0)]
const colorIndex = index % colors.length
items.push({
value: tmpRange,
color: colors[colorIndex]
})
})
customLegend['items'] = items
const findColorByValue = (value, intervals) => {
if (value) {
for (const interval of intervals) {
if (value >= interval.value[0] && value <= interval.value[1]) {
return interval.color
}
}
}
// 或者可以返回 undefined
return null
}
options.color.value = t => {
const c = findColorByValue(t.value, items)
return c ? c : null
}
} else {
customLegend['customContent'] = (_: string, items: CategoryLegendListItem[]) => {
const showItems = items?.length > 30 ? items.slice(0, 30) : items
if (showItems?.length) {
const containerDom = createDom(CONTAINER_TPL) as HTMLElement
@ -311,15 +360,6 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
return listDom
}
return ''
},
domStyles: {
'l7plot-legend__category-value': {
fontSize: legend.fontSize + 'px',
color: legend.color
},
'l7plot-legend__category-marker': {
...LEGEND_SHAPE_STYLE_MAP[legend.icon]
}
}
}
defaultsDeep(options, { legend: customLegend })

View File

@ -585,7 +585,7 @@ export const getDynamicColorScale = (
minValue: number,
maxValue: number,
intervals: number,
colors: string[]
colors?: string[]
) => {
const step = (maxValue - minValue) / intervals
@ -593,8 +593,8 @@ export const getDynamicColorScale = (
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)}`
color: colors?.[i],
label: `${(minValue + i * step).toFixed(0)} - ${(minValue + (i + 1) * step).toFixed(0)}`
})
}