diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index aab0fe5e65..0b7898d796 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -660,6 +660,7 @@ export default { horizontal: '水平', vertical: '垂直', legend: '图例', + legend_num: '图例数', shape: '形状', polygon: '多边形', circle: '圆形', diff --git a/core/core-frontend/src/models/chart/chart-attr.d.ts b/core/core-frontend/src/models/chart/chart-attr.d.ts index 44b6f80a50..b725a79615 100644 --- a/core/core-frontend/src/models/chart/chart-attr.d.ts +++ b/core/core-frontend/src/models/chart/chart-attr.d.ts @@ -612,6 +612,22 @@ declare interface ChartMiscAttr { * 词云图文字间距 */ wordSpacing: number + /** + * 自动图例 + */ + mapAutoLegend: boolean + /** + * 图例最大值 + */ + mapLegendMax: number + /** + * 图例最小值 + */ + mapLegendMin: number + /** + * 显示图例个数 + */ + mapLegendNumber: number } /** * 动态极值配置 diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue index 6fcbbe8dc2..c647d27039 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue @@ -260,6 +260,7 @@ watch( :themes="themes" :chart="chart" @onLegendChange="onLegendChange" + @onMiscChange="onMiscChange" /> 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(() => { + +
+ + + + + {{ t('chart.margin_model_auto') }} + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ { 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 { type: 'geojson' }, source: { - data: chart.data?.data || [], + data: data, joinBy: { sourceField: 'name', geoField: 'name', @@ -125,7 +157,7 @@ export class Map extends L7PlotChartView { ): ChoroplethOptions { const { areaId }: L7PlotDrawOptions = 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 { 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 { 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 } diff --git a/core/core-frontend/src/views/chart/components/js/util.ts b/core/core-frontend/src/views/chart/components/js/util.ts index 9bc8765741..6100f516e1 100644 --- a/core/core-frontend/src/views/chart/components/js/util.ts +++ b/core/core-frontend/src/views/chart/components/js/util.ts @@ -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) + } +} diff --git a/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue b/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue index 6549f29d3a..d0564551d6 100644 --- a/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue +++ b/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue @@ -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()