feat(图表-流向地图): 支持配置起止点名称以及样式配置优化

This commit is contained in:
jianneng-fit2cloud 2024-07-23 20:10:45 +08:00
parent fa3b7403e1
commit ab950c57e7
11 changed files with 1079 additions and 278 deletions

View File

@ -529,34 +529,6 @@ declare interface ChartMiscAttr {
* 地图倾角
*/
mapPitch: number
/**
* 地图线条类型
*/
mapLineType: string
/**
* 地图线条宽度
*/
mapLineWidth: number
/**
* 流向地图动画
*/
mapLineAnimate?: boolean
/**
* 流向地图动画间隔
*/
mapLineAnimateDuration: number
/**
* 地图线条渐变
*/
mapLineGradient: boolean
/**
* 地图线条渐变起始颜色
*/
mapLineSourceColor: string
/**
* 地图线条渐变结束颜色
*/
mapLineTargetColor: string
/**
* 指标/文本卡值字体
*/
@ -653,6 +625,54 @@ declare interface ChartMiscAttr {
* 显示图例个数
*/
mapLegendNumber: number
/**
* 流向地图配置
*/
flowMapConfig: {
lineConfig: {
/**
* 地图线条类型
*/
mapLineType: string
/**
* 地图线条宽度
*/
mapLineWidth: number
/**
* 流向地图动画
*/
mapLineAnimate?: boolean
/**
* 流向地图动画间隔
*/
mapLineAnimateDuration: number
/**
* 地图线条渐变
*/
mapLineGradient: boolean
/**
* 地图线条渐变起始颜色
*/
mapLineSourceColor: string
/**
* 地图线条渐变结束颜色
*/
mapLineTargetColor: string
alpha: number
}
pointConfig: {
text: {
color: string
fontSize: number
}
point: {
color: string
size: number
animate: boolean
speed: number
}
}
}
}
/**
* 动态极值配置

View File

@ -65,6 +65,8 @@ declare interface Chart {
* 针对不是序列字段的图表通过获取分类字段的值作为序列字段
*/
seriesFieldObjs?: any[]
flowMapStartName?: Axis[]
flowMapEndName?: Axis[]
}
declare type CustomAttr = DeepPartial<ChartAttr> | JSONString<DeepPartial<ChartAttr>>
declare type CustomStyle = DeepPartial<ChartStyle> | JSONString<DeepPartial<ChartStyle>>

View File

@ -25,6 +25,8 @@ declare type EditorProperty =
| 'indicator-name-selector'
| 'quadrant-selector'
| 'map-symbolic-selector'
| 'flow-map-line-selector'
| 'flow-map-point-selector'
declare type EditorPropertyInner = {
[key in EditorProperty]?: string[]
}
@ -50,6 +52,9 @@ declare type AxisType =
| 'extLabel'
| 'extTooltip'
| 'area'
| 'flowMapStartName'
| 'flowMapEndName'
| 'flowMapColor'
/**
* 轴配置
*/

View File

@ -12,7 +12,7 @@ import LegendSelector from '@/views/chart/components/editor/editor-style/compone
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue'
import { ElCollapseItem } from 'element-plus-secondary'
import { ElCollapse, ElCollapseItem } from 'element-plus-secondary'
import BasicStyleSelector from '@/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue'
import ComponentPosition from '@/components/visualization/common/ComponentPosition.vue'
import BackgroundOverallCommon from '@/components/visualization/component-background/BackgroundOverallCommon.vue'
@ -23,6 +23,8 @@ import MiscStyleSelector from '@/views/chart/components/editor/editor-style/comp
import IndicatorValueSelector from '@/views/chart/components/editor/editor-style/components/IndicatorValueSelector.vue'
import IndicatorNameSelector from '@/views/chart/components/editor/editor-style/components/IndicatorNameSelector.vue'
import QuadrantSelector from '@/views/chart/components/editor/editor-style/components/QuadrantSelector.vue'
import FlowMapLineSelector from '@/views/chart/components/editor/editor-style/components/FlowMapLineSelector.vue'
import FlowMapPointSelector from '@/views/chart/components/editor/editor-style/components/FlowMapPointSelector.vue'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -103,7 +105,9 @@ const emit = defineEmits([
'onExtTooltipChange',
'onIndicatorChange',
'onIndicatorNameChange',
'onChangeQuadrantForm'
'onChangeQuadrantForm',
'onChangeFlowMapLineForm',
'onChangeFlowMapPointForm'
])
const indicatorValueRef = ref()
@ -189,6 +193,12 @@ const onExtTooltipChange = val => {
const onChangeQuadrantForm = val => {
emit('onChangeQuadrantForm', val)
}
const onChangeFlowMapLineForm = val => {
emit('onChangeFlowMapLineForm', val)
}
const onChangeFlowMapPointForm = val => {
emit('onChangeFlowMapPointForm', val)
}
watch(
() => props.chart.id,
() => {
@ -436,6 +446,35 @@ watch(
@onChangeQuadrantForm="onChangeQuadrantForm"
/>
</el-collapse-item>
<el-collapse-item
:effect="themes"
name="flowMapLineSelector"
title="线条"
v-if="showProperties('flow-map-line-selector')"
>
<flow-map-line-selector
class="attr-selector"
:property-inner="propertyInnerAll['flow-map-line-selector']"
:themes="themes"
:chart="chart"
@onChangeFlowMapLineForm="onChangeFlowMapLineForm"
@onBasicStyleChange="onBasicStyleChange"
/>
</el-collapse-item>
<el-collapse-item
:effect="themes"
name="flowMapPointSelector"
title="标注"
v-if="showProperties('flow-map-point-selector')"
>
<flow-map-point-selector
class="attr-selector"
:property-inner="propertyInnerAll['flow-map-point-selector']"
:themes="themes"
:chart="chart"
@onChangeFlowMapPointForm="onChangeFlowMapPointForm"
/>
</el-collapse-item>
</el-collapse>
<el-collapse v-model="state.styleActiveNames" class="style-collapse">

View File

@ -55,16 +55,6 @@ watch(
)
const emit = defineEmits(['onBasicStyleChange', 'onMiscChange'])
const changeBasicStyle = (prop?: string, requestData = false) => {
const mapLineColorStyle = prop?.split('@')
if (mapLineColorStyle.length === 2) {
if (mapLineColorStyle[1].toLowerCase() === 'SourceColor'.toLowerCase()) {
state.basicStyleForm.colors[0] = state.miscForm.mapLineSourceColor
}
if (mapLineColorStyle[1].toLowerCase() === 'TargetColor'.toLowerCase()) {
state.basicStyleForm.colors[1] = state.miscForm.mapLineTargetColor
}
changeMisc(state.basicStyleForm.colors[0] + state.basicStyleForm.colors[1])
}
emit('onBasicStyleChange', { data: state.basicStyleForm, requestData }, prop)
}
const onAlphaChange = v => {
@ -92,10 +82,6 @@ const init = () => {
configCompat(basicStyle)
state.basicStyleForm = defaultsDeep(basicStyle, cloneDeep(DEFAULT_BASIC_STYLE)) as ChartBasicStyle
state.miscForm = defaultsDeep(miscStyle, cloneDeep(DEFAULT_MISC)) as ChartMiscAttr
if (props.chart.type === 'flow-map') {
state.miscForm.mapLineSourceColor = state.basicStyleForm.colors[0]
state.miscForm.mapLineTargetColor = state.basicStyleForm.colors[1]
}
if (!state.customColor) {
state.customColor = state.basicStyleForm.colors[0]
state.colorIndex = 0
@ -222,12 +208,6 @@ const heatMapTypeOptions = [
{ name: t('chart.heatmap3D'), value: 'heatmap3D' }
]
const flowLineTypeOptions = [
{ name: t('chart.map_line_type_line'), value: 'line' },
{ name: t('chart.map_line_type_arc'), value: 'arc' },
{ name: t('chart.map_line_type_arc_3d'), value: 'arc3d' }
]
const mapSymbolOptions = [
{ name: t('chart.line_symbol_circle'), value: 'circle' },
{ name: t('chart.line_symbol_rect'), value: 'square' },
@ -410,190 +390,6 @@ onMounted(() => {
</el-row>
</div>
</div>
<div class="map-flow-style" v-if="showProperty('mapLineStyle')">
<el-row style="flex: 1">
<el-col>
<el-form-item
:label="t('chart.line') + t('chart.map_line_type')"
class="form-item"
:class="'form-item-' + themes"
>
<el-select
:effect="themes"
v-model="state.miscForm.mapLineType"
@change="changeMisc('mapLineType')"
>
<el-option
v-for="item in flowLineTypeOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.map_line_width') }}
</label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="1"
:max="10"
v-model="state.miscForm.mapLineWidth"
@change="changeMisc('mapLineWidth')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.miscForm.mapLineGradient"
:predefine="predefineColors"
@change="changeMisc('mapLineGradient')"
>
{{ t('chart.line') + t('chart.map_line_linear') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<div v-if="state.miscForm.mapLineGradient">
<el-row style="flex: 1" :gutter="8">
<el-col :span="13">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.map_line_color_source_color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.miscForm.mapLineSourceColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeBasicStyle('mapLine@SourceColor')"
/>
</el-form-item>
</el-col>
<el-col :span="13">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.map_line_color_target_color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.miscForm.mapLineTargetColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeBasicStyle('mapLine@TargetColor')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div v-if="!state.miscForm.mapLineGradient">
<el-row style="flex: 1" :gutter="8">
<el-col>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.miscForm.mapLineSourceColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeBasicStyle('mapLine@SourceColor')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.not_alpha') }}
</label>
<el-row style="flex: 1" :gutter="8">
<el-col :span="13">
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
v-model="state.basicStyleForm.alpha"
@change="changeBasicStyle('alpha')"
/>
</el-form-item>
</el-col>
<el-col :span="11" style="padding-top: 2px">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-input
type="number"
:effect="themes"
v-model="state.basicStyleForm.alpha"
:min="0"
:max="100"
class="basic-input-number"
:controls="false"
@change="onAlphaChange"
>
<template #suffix> % </template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.miscForm.mapLineAnimate"
:predefine="predefineColors"
@change="changeMisc('mapLineAnimate')"
>
{{ t('chart.line') + t('chart.map_line_animate') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting" v-if="state.miscForm.mapLineAnimate">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.map_line_animate_duration') }}
</label>
<el-row style="flex: 1" :gutter="8">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="0"
:max="20"
v-model="state.miscForm.mapLineAnimateDuration"
@change="changeMisc('mapLineAnimateDuration')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
<div class="alpha-setting" v-if="showProperty('heatMapStyle')">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.heatMapIntensity') }}

View File

@ -0,0 +1,326 @@
<script lang="tsx" setup>
import { computed, onMounted, PropType, reactive, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import {
COLOR_PANEL,
DEFAULT_BASIC_STYLE,
DEFAULT_MISC
} from '@/views/chart/components/editor/util/chart'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { cloneDeep, defaultsDeep } from 'lodash-es'
const { t } = useI18n()
const dvMainStore = dvMainStoreWithOut()
const props = defineProps({
chart: {
type: Object,
required: true
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
propertyInner: {
type: Array<string>
}
})
const predefineColors = COLOR_PANEL
const flowLineTypeOptions = [
{ name: t('chart.map_line_type_line'), value: 'line' },
{ name: t('chart.map_line_type_arc'), value: 'arc' },
{ name: t('chart.map_line_type_arc_3d'), value: 'arc3d' }
]
const state = reactive({
lineForm: {
...JSON.parse(JSON.stringify(DEFAULT_MISC.flowMapConfig.lineConfig))
},
basicStyleForm: {
...JSON.parse(JSON.stringify(DEFAULT_BASIC_STYLE))
}
})
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
const emit = defineEmits(['onChangeFlowMapLineForm', 'onBasicStyleChange'])
watch(
() => props.chart.customAttr.misc.flowMapConfig.lineConfig,
() => {
init()
},
{ deep: true }
)
const changeStyle = () => {
state.basicStyleForm.colors[0] = state.lineForm.mapLineSourceColor
state.basicStyleForm.colors[1] = state.lineForm.mapLineTargetColor
emit('onBasicStyleChange', { data: state.basicStyleForm, requestData: false }, 'colors')
emit('onChangeFlowMapLineForm', state.lineForm)
}
const onAlphaChange = v => {
const _v = parseInt(v)
if (_v >= 0 && _v <= 100) {
state.lineForm.alpha = _v
} else if (_v < 0) {
state.lineForm.alpha = 0
} else if (_v > 100) {
state.lineForm.alpha = 100
} else {
const lineConfig = cloneDeep(props.chart.customAttr.misc.flowMapConfig.lineConfig)
const oldForm = defaultsDeep(
lineConfig,
cloneDeep(DEFAULT_MISC.flowMapConfig)
) as ChartBasicStyle
state.lineForm.alpha = oldForm.alpha
}
changeStyle()
}
const init = () => {
const chart = JSON.parse(JSON.stringify(props.chart))
if (chart.customAttr) {
let customAttr = null
if (Object.prototype.toString.call(chart.customAttr) === '[object Object]') {
customAttr = JSON.parse(JSON.stringify(chart.customAttr))
} else {
customAttr = JSON.parse(chart.customAttr)
}
const basicStyle = customAttr.basicStyle
state.basicStyleForm = defaultsDeep(
basicStyle,
cloneDeep(DEFAULT_BASIC_STYLE)
) as ChartBasicStyle
configCompat(basicStyle)
if (customAttr.misc.flowMapConfig.lineConfig) {
state.lineForm = customAttr.misc.flowMapConfig.lineConfig
state.lineForm.mapLineSourceColor = state.basicStyleForm.colors[0]
state.lineForm.mapLineTargetColor = state.basicStyleForm.colors[1]
} else {
//
state.lineForm = {
...JSON.parse(JSON.stringify(DEFAULT_MISC.flowMapConfig.lineConfig))
}
changeStyle()
}
}
}
const configCompat = (basicStyle: ChartBasicStyle) => {
//
if (basicStyle.suspension === false && basicStyle.showZoom === undefined) {
basicStyle.showZoom = false
}
}
onMounted(() => {
init()
})
</script>
<template>
<el-form ref="lineForm" :model="state.lineForm" size="small" label-position="top">
<el-row style="flex: 1">
<el-col>
<el-form-item
:label="t('chart.line') + t('chart.map_line_type')"
class="form-item"
:class="'form-item-' + themes"
>
<el-select :effect="themes" v-model="state.lineForm.mapLineType" @change="changeStyle()">
<el-option
v-for="item in flowLineTypeOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.map_line_width') }}
</label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="1"
:max="10"
v-model="state.lineForm.mapLineWidth"
@change="changeStyle()"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.lineForm.mapLineGradient"
:predefine="predefineColors"
@change="changeStyle()"
>
{{ t('chart.line') + t('chart.map_line_linear') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<div v-if="state.lineForm.mapLineGradient">
<el-row style="flex: 1" :gutter="8">
<el-col :span="13">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.map_line_color_source_color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.lineForm.mapLineSourceColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeStyle()"
/>
</el-form-item>
</el-col>
<el-col :span="13">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.map_line_color_target_color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.lineForm.mapLineTargetColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeStyle()"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div v-if="!state.lineForm.mapLineGradient">
<el-row style="flex: 1" :gutter="8">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes" :label="t('chart.color')">
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.lineForm.mapLineSourceColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeStyle()"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.not_alpha') }}
</label>
<el-row style="flex: 1" :gutter="8">
<el-col :span="13">
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider :effect="themes" v-model="state.lineForm.alpha" @change="changeStyle()" />
</el-form-item>
</el-col>
<el-col :span="11" style="padding-top: 2px">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-input
type="number"
:effect="themes"
v-model="state.lineForm.alpha"
:min="0"
:max="100"
class="basic-input-number"
:controls="false"
@change="onAlphaChange"
>
<template #suffix> % </template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.lineForm.mapLineAnimate"
:predefine="predefineColors"
@change="changeStyle()"
>
{{ t('chart.line') + t('chart.map_line_animate') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting" v-if="state.lineForm.mapLineAnimate">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.map_line_animate_duration') }}
</label>
<el-row style="flex: 1" :gutter="8">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="0"
:max="20"
v-model="state.lineForm.mapLineAnimateDuration"
@change="changeStyle()"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</template>
<style lang="less" scoped>
:deep(.ed-input .ed-select__prefix--light) {
padding-right: 6px;
}
.alpha-setting {
display: flex;
width: 100%;
.alpha-slider {
padding: 4px 8px;
:deep(.ed-slider__button-wrapper) {
--ed-slider-button-wrapper-size: 36px;
--ed-slider-button-size: 16px;
}
}
.alpha-label {
padding-right: 8px;
font-size: 12px;
font-style: normal;
font-weight: 400;
height: 32px;
line-height: 32px;
display: inline-flex;
align-items: flex-start;
min-width: 56px;
&.dark {
color: #a6a6a6;
}
}
}
</style>

View File

@ -0,0 +1,210 @@
<script lang="tsx" setup>
import { computed, onMounted, PropType, reactive, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import {
COLOR_PANEL,
DEFAULT_BASIC_STYLE,
DEFAULT_MISC
} from '@/views/chart/components/editor/util/chart'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { cloneDeep, defaultsDeep } from 'lodash-es'
import { ElSpace } from 'element-plus-secondary'
const { t } = useI18n()
const dvMainStore = dvMainStoreWithOut()
const props = defineProps({
chart: {
type: Object,
required: true
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
propertyInner: {
type: Array<string>
}
})
const predefineColors = COLOR_PANEL
const fontSizeList = computed(() => {
const arr = []
for (let i = 10; i <= 40; i = i + 2) {
arr.push({
name: i + '',
value: i
})
}
return arr
})
const state = reactive({
pointForm: {
...JSON.parse(JSON.stringify(DEFAULT_MISC.flowMapConfig.pointConfig))
}
})
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
const emit = defineEmits(['onChangeFlowMapPointForm'])
watch(
() => props.chart.customAttr.misc.flowMapConfig.pointConfig,
() => {
init()
},
{ deep: true }
)
const changeStyle = () => {
emit('onChangeFlowMapPointForm', state.pointForm)
}
const init = () => {
const chart = JSON.parse(JSON.stringify(props.chart))
if (chart.customAttr) {
let customAttr = null
if (Object.prototype.toString.call(chart.customAttr) === '[object Object]') {
customAttr = JSON.parse(JSON.stringify(chart.customAttr))
} else {
customAttr = JSON.parse(chart.customAttr)
}
if (customAttr.misc.flowMapConfig.lineConfig) {
state.pointForm = customAttr.misc.flowMapConfig.pointConfig
} else {
//
state.pointForm = {
...JSON.parse(JSON.stringify(DEFAULT_MISC.flowMapConfig.pointConfig))
}
changeStyle()
}
}
}
const showProperty = prop => props.propertyInner?.includes(prop)
onMounted(() => {
init()
})
</script>
<template>
<el-form ref="pointForm" :model="state.pointForm" size="small" label-position="top">
<el-space>
<el-form-item class="form-item" :class="'form-item-' + themes" :label="t('chart.text')">
<el-color-picker
:effect="themes"
v-model="state.pointForm.text.color"
class="color-picker-style"
:predefine="predefineColors"
@change="changeStyle()"
is-custom
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<template #label>&nbsp;</template>
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
size="small"
style="width: 108px"
:effect="themes"
v-model.number="state.pointForm.text.fontSize"
:placeholder="t('chart.text_fontsize')"
@change="changeStyle()"
>
<el-option
v-for="option in fontSizeList"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-tooltip>
</el-form-item>
</el-space>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }"> 标注点大小 </label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="0"
:max="5"
v-model="state.pointForm.point.size"
@change="changeStyle()"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
@change="changeStyle()"
v-model="state.pointForm.point.animate"
>
标注点动画
</el-checkbox>
</el-form-item>
</el-col>
<el-col>
<div class="alpha-setting" v-if="state.pointForm.point.animate">
<label class="alpha-label" :class="{ dark: 'dark' === themes }"> 闪烁频率 </label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="1"
:max="5"
v-model="state.pointForm.point.speed"
@change="changeStyle()"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</el-form>
</template>
<style lang="less" scoped>
:deep(.ed-input .ed-select__prefix--light) {
padding-right: 6px;
}
.alpha-setting {
display: flex;
width: 100%;
.alpha-slider {
padding: 4px 8px;
:deep(.ed-slider__button-wrapper) {
--ed-slider-button-wrapper-size: 36px;
--ed-slider-button-size: 16px;
}
}
.alpha-label {
padding-right: 8px;
font-size: 12px;
font-style: normal;
font-weight: 400;
height: 32px;
line-height: 32px;
display: inline-flex;
align-items: flex-start;
min-width: 56px;
&.dark {
color: #a6a6a6;
}
}
}
</style>

View File

@ -396,6 +396,10 @@ const dimensionItemRemove = item => {
view.value.yAxisExt.splice(item.index, 1)
} else if (item.removeType === 'xAxisExtRight') {
view.value.extBubble.splice(item.index, 1)
} else if (item.removeType === 'flowMapStartName') {
view.value.flowMapStartName.splice(item.index, 1)
} else if (item.removeType === 'flowMapEndName') {
view.value.flowMapEndName.splice(item.index, 1)
}
}
@ -494,6 +498,20 @@ const onExtCustomRightSort = item => {
customSort()
}
const onCustomFlowMapStartNameSort = item => {
recordSnapshotInfo('render')
state.customSortField = view.value.flowMapStartName[item.index]
customSortAxis.value = 'flowMapStartName'
customSort()
}
const onCustomFlowMapEndNameSort = item => {
recordSnapshotInfo('render')
state.customSortField = view.value.flowMapEndName[item.index]
customSortAxis.value = 'flowMapEndName'
customSort()
}
const onMove = e => {
recordSnapshotInfo('calcData')
state.moveId = e.draggedContext.element.id
@ -759,6 +777,14 @@ const addDrill = e => {
dragRemoveAggField(view.value.drillFields, e)
}
const addFlowMapStartName = e => {
addAxis(e, 'flowMapStartName')
}
const addFlowMapEndName = e => {
addAxis(e, 'flowMapEndName')
}
const onAxisChange = (e, axis: AxisType) => {
if (e.removed) {
const { element } = e.removed
@ -1045,6 +1071,14 @@ const onChangeQuadrantForm = val => {
view.value.customAttr.quadrant = val
renderChart(view.value)
}
const onChangeFlowMapLineForm = val => {
view.value.customAttr.misc.flowMapConfig.lineConfig = val
renderChart(view.value)
}
const onChangeFlowMapPointForm = val => {
view.value.customAttr.misc.flowMapConfig.pointConfig = val
renderChart(view.value)
}
const showRename = val => {
recordSnapshotInfo('render')
@ -1069,6 +1103,8 @@ const removeItems = (
| 'extBubble'
| 'customFilter'
| 'drillFields'
| 'flowMapStartName'
| 'flowMapEndName'
) => {
recordSnapshotInfo('calcData')
let axis = []
@ -1098,6 +1134,12 @@ const removeItems = (
case 'drillFields':
axis = view.value.drillFields?.splice(0)
break
case 'flowMapStartName':
axis = view.value.flowMapStartName?.splice(0)
break
case 'flowMapEndName':
axis = view.value.flowMapEndName?.splice(0)
break
}
axis?.length && emitter.emit('removeAxis', { axisType: _type, axis, editType: 'remove' })
}
@ -1141,6 +1183,15 @@ const saveRename = ref => {
break
case 'extTooltip':
view.value.extTooltip[index].chartShowName = chartShowName
case 'flowMapStartName':
axisType = 'flowMapStartName'
axis = view.value.flowMapStartName[index]
view.value.flowMapStartName[index].chartShowName = chartShowName
break
case 'flowMapEndName':
axisType = 'flowMapEndName'
axis = view.value.flowMapEndName[index]
view.value.flowMapEndName[index].chartShowName = chartShowName
break
default:
break
@ -1870,6 +1921,122 @@ const deleteChartFieldItem = id => {
</div>
</el-row>
<!--flowMapStartName-->
<el-row v-if="showAxis('flowMapStartName')" class="padding-lr drag-data">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.flowMapStartName.name }}
</span>
<el-tooltip
:effect="toolTip"
placement="top"
:content="t('common.delete')"
>
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('flowMapStartName')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
class="qw"
@drop="$event => drop($event, 'flowMapStartName')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<draggable
:list="view.flowMapStartName"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addFlowMapStartName"
>
<template #item="{ element, index }">
<dimension-item
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
:themes="props.themes"
type="flowMapStartName"
@onDimensionItemChange="dimensionItemChange"
@onDimensionItemRemove="dimensionItemRemove"
@onNameEdit="showRename"
@onCustomSort="onCustomFlowMapStartNameSort"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :themes="themes" :drag-list="view.flowMapStartName" />
</div>
</el-row>
<!--flowMapEndName-->
<el-row v-if="showAxis('flowMapEndName')" class="padding-lr drag-data">
<div class="form-draggable-title">
<span>
{{ chartViewInstance.axisConfig.flowMapEndName.name }}
</span>
<el-tooltip
:effect="toolTip"
placement="top"
:content="t('common.delete')"
>
<el-icon
class="remove-icon"
:class="{ 'remove-icon--dark': themes === 'dark' }"
size="14px"
@click="removeItems('flowMapEndName')"
>
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
</el-icon>
</el-tooltip>
</div>
<div
class="qw"
@drop="$event => drop($event, 'flowMapEndName')"
@dragenter="dragEnter"
@dragover="$event => dragOver($event)"
>
<draggable
:list="view.flowMapEndName"
:move="onMove"
item-key="id"
group="drag"
animation="300"
class="drag-block-style"
:class="{ dark: themes === 'dark' }"
@add="addFlowMapEndName"
>
<template #item="{ element, index }">
<dimension-item
:dimension-data="state.dimension"
:quota-data="state.quota"
:chart="view"
:item="element"
:index="index"
:themes="props.themes"
type="flowMapEndName"
@onDimensionItemChange="dimensionItemChange"
@onDimensionItemRemove="dimensionItemRemove"
@onNameEdit="showRename"
@onCustomSort="onCustomFlowMapEndNameSort"
@valueFormatter="valueFormatter"
/>
</template>
</draggable>
<drag-placeholder :themes="themes" :drag-list="view.flowMapEndName" />
</div>
</el-row>
<!--extStack-->
<el-row v-if="showAxis('extStack')" class="padding-lr drag-data">
<div class="form-draggable-title">
@ -2599,6 +2766,8 @@ const deleteChartFieldItem = id => {
@onChangeMiscStyleForm="onChangeMiscStyleForm"
@onExtTooltipChange="onExtTooltipChange"
@onChangeQuadrantForm="onChangeQuadrantForm"
@onChangeFlowMapLineForm="onChangeFlowMapLineForm"
@onChangeFlowMapPointForm="onChangeFlowMapPointForm"
/>
</template>
</el-scrollbar>

View File

@ -29,9 +29,14 @@ export const DEFAULT_COLOR_CASE: DeepPartial<ChartAttr> = {
zoomBackground: '#fff'
},
misc: {
mapLineGradient: false,
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC',
flowMapConfig: {
lineConfig: {
mapLineAnimate: true,
mapLineGradient: false,
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC'
}
},
nameFontColor: '#000000',
valueFontColor: '#5470c6'
},
@ -72,9 +77,14 @@ export const DEFAULT_COLOR_CASE_LIGHT: DeepPartial<ChartAttr> = {
zoomBackground: '#fff'
},
misc: {
mapLineGradient: false,
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC',
flowMapConfig: {
lineConfig: {
mapLineAnimate: true,
mapLineGradient: false,
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC'
}
},
nameFontColor: '#000000',
valueFontColor: '#5470c6'
},
@ -115,9 +125,13 @@ export const DEFAULT_COLOR_CASE_DARK: DeepPartial<ChartAttr> = {
zoomBackground: '#000'
},
misc: {
mapLineGradient: false,
mapLineSourceColor: '#2F58CD',
mapLineTargetColor: '#3795BD',
flowMapConfig: {
lineConfig: {
mapLineGradient: false,
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC'
}
},
nameFontColor: '#ffffff',
valueFontColor: '#5470c6'
},
@ -255,18 +269,36 @@ export const DEFAULT_MISC: ChartMiscAttr = {
hPosition: 'center',
vPosition: 'center',
mapPitch: 0,
mapLineType: 'arc',
mapLineWidth: 1,
mapLineAnimateDuration: 3,
mapLineGradient: false,
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC',
wordSizeRange: [8, 32],
wordSpacing: 6,
mapAutoLegend: true,
mapLegendMax: 0,
mapLegendMin: 0,
mapLegendNumber: 9
mapLegendNumber: 9,
flowMapConfig: {
lineConfig: {
mapLineAnimate: true,
mapLineType: 'arc',
mapLineWidth: 1,
mapLineAnimateDuration: 3,
mapLineGradient: false,
mapLineSourceColor: '#146C94',
mapLineTargetColor: '#576CBC',
alpha: 100
},
pointConfig: {
text: {
color: '#146C94',
fontSize: 10
},
point: {
color: 'red',
size: 4,
animate: false,
speed: 0.01
}
}
}
}
export const DEFAULT_MARK = {
@ -1541,7 +1573,9 @@ export const BASE_VIEW_CONFIG = {
threshold: DEFAULT_THRESHOLD,
scrollCfg: DEFAULT_SCROLL,
areaMapping: {}
}
},
flowMapStartName: [],
flowMapEndName: []
}
export function getScaleValue(propValue, scale) {

View File

@ -11,6 +11,7 @@ import { deepCopy } from '@/utils/utils'
import { GaodeMap } from '@antv/l7-maps'
import { Scene } from '@antv/l7-scene'
import { LineLayer } from '@antv/l7-layers'
import { PointLayer } from '@antv/l7-layers'
import { queryMapKeyApi } from '@/api/setting/sysParameter'
import { mapRendered, mapRendering } from '@/views/chart/components/js/panel/common/common_antv'
const { t } = useI18n()
@ -22,13 +23,15 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
properties: EditorProperty[] = [
'background-overall-component',
'basic-style-selector',
'title-selector'
'title-selector',
'flow-map-line-selector',
'flow-map-point-selector'
]
propertyInner: EditorPropertyInner = {
...MAP_EDITOR_PROPERTY_INNER,
'basic-style-selector': ['mapBaseStyle', 'mapLineStyle', 'zoom']
}
axis: AxisType[] = ['xAxis', 'xAxisExt', 'filter']
axis: AxisType[] = ['xAxis', 'xAxisExt', 'filter', 'flowMapStartName', 'flowMapEndName', 'yAxis']
axisConfig: AxisConfig = {
xAxis: {
name: `起点经纬度 / ${t('chart.dimension')}`,
@ -39,6 +42,21 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
name: `终点经纬度 / ${t('chart.dimension')}`,
type: 'd',
limit: 2
},
flowMapStartName: {
name: `起点名称 / ${t('chart.dimension')}`,
type: 'd',
limit: 1
},
flowMapEndName: {
name: `终点名称 / ${t('chart.dimension')}`,
type: 'd',
limit: 1
},
yAxis: {
name: `线条粗细 / ${t('chart.quota')}`,
type: 'q',
limit: 1
}
}
constructor() {
@ -50,19 +68,6 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
const xAxis = deepCopy(chart.xAxis)
const xAxisExt = deepCopy(chart.xAxisExt)
const { basicStyle, misc } = deepCopy(parseJson(chart.customAttr))
const flowLineStyle = {
type: misc.mapLineType,
size: misc.mapLineType === 'line' ? misc.mapLineWidth / 2 : misc.mapLineWidth,
animate: misc.mapLineAnimate,
animateDuration: misc.mapLineAnimateDuration,
gradient: misc.mapLineGradient,
sourceColor: misc.mapLineSourceColor,
targetColor: misc.mapLineTargetColor,
alpha: basicStyle.alpha
}
const colorsWithAlpha = basicStyle.colors.map(color => hexColorToRGBA(color, basicStyle.alpha))
flowLineStyle.sourceColor = colorsWithAlpha[0]
flowLineStyle.targetColor = colorsWithAlpha[1]
const mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}`
const key = await this.getMapKey()
@ -84,6 +89,48 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
if (xAxis?.length < 2 || xAxisExt?.length < 2) {
return new L7Wrapper(scene, undefined)
}
const configList = []
configList.push(this.lineConfig(chart, xAxis, xAxisExt, basicStyle, misc))
this.startAndEndNameConfig(chart, xAxis, xAxisExt, misc, configList)
this.pointConfig(chart, xAxis, xAxisExt, misc, configList)
configList[0].once('inited', () => {
mapRendered(container)
})
this.configZoomButton(chart, scene)
return new L7Wrapper(scene, configList)
}
lineConfig = (chart, xAxis, xAxisExt, basicStyle, misc) => {
const flowLineStyle = {
type: misc.flowMapConfig.lineConfig.mapLineType,
size:
misc.flowMapConfig.lineConfig.mapLineType === 'line'
? misc.flowMapConfig.lineConfig.mapLineWidth / 2
: misc.flowMapConfig.lineConfig.mapLineWidth,
animate: misc.flowMapConfig.lineConfig.mapLineAnimate,
animateDuration: misc.flowMapConfig.lineConfig.mapLineAnimateDuration,
gradient: misc.flowMapConfig.lineConfig.mapLineGradient,
sourceColor: misc.flowMapConfig.lineConfig.mapLineSourceColor,
targetColor: misc.flowMapConfig.lineConfig.mapLineTargetColor,
alpha: misc.flowMapConfig.lineConfig.alpha
}
const colorsWithAlpha = basicStyle.colors.map(color =>
hexColorToRGBA(color, misc.flowMapConfig.lineConfig.alpha)
)
flowLineStyle.sourceColor = colorsWithAlpha[0]
flowLineStyle.targetColor = colorsWithAlpha[1]
// 线条粗细
let lineWidthField = null
const yAxis = deepCopy(chart.yAxis)
if (yAxis.length > 0) {
lineWidthField = yAxis[0].dataeaseName
}
// 线条颜色
let lineColorField = null
const yAxisExt = deepCopy(chart.yAxisExt)
if (yAxisExt.length > 0) {
lineColorField = yAxisExt[0].dataeaseName
}
const config: L7Config = new LineLayer({
name: 'line',
blend: 'normal',
@ -106,24 +153,167 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
interval: 1,
trailLength: 1
})
if (flowLineStyle.gradient) {
if (lineWidthField) {
config.size(lineWidthField, [1, 10])
}
if (lineColorField) {
config.style({
sourceColor: flowLineStyle.sourceColor,
targetColor: flowLineStyle.targetColor,
opacity: flowLineStyle.alpha / 100
})
config.color(lineColorField)
} else {
config
.style({
if (flowLineStyle.gradient) {
config.style({
sourceColor: flowLineStyle.sourceColor,
targetColor: flowLineStyle.targetColor,
opacity: flowLineStyle.alpha / 100
})
.color(flowLineStyle.sourceColor)
} else {
config
.style({
opacity: flowLineStyle.alpha / 100
})
.color(flowLineStyle.sourceColor)
}
}
return config
}
startAndEndNameConfig = (chart, xAxis, xAxisExt, misc, configList) => {
const flowMapStartName = deepCopy(chart.flowMapStartName)
const flowMapEndName = deepCopy(chart.flowMapEndName)
const textColor = misc.flowMapConfig.pointConfig.text.color
const textFontSize = misc.flowMapConfig.pointConfig.text.fontSize
const has = new Map()
if (flowMapStartName?.length > 0) {
const startTextLayer = new PointLayer()
.source(chart.data?.tableRow, {
parser: {
type: 'json',
x: xAxis[0].dataeaseName,
y: xAxis[1].dataeaseName
}
})
.shape(flowMapStartName[0].dataeaseName, args => {
if (has.has('from-' + args)) {
return ''
}
has.set('from-' + args, args)
return args
})
.size(textFontSize)
.color(textColor)
.style({
textAnchor: 'top', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
spacing: 2, // 字符间距
padding: [1, 1], // 文本包围盒 padding [水平垂直]影响碰撞检测结果避免相邻文本靠的太近
textAllowOverlap: true
})
configList.push(startTextLayer)
}
if (flowMapEndName?.length > 0) {
const endTextLayer = new PointLayer()
.source(chart.data?.tableRow, {
parser: {
type: 'json',
x: xAxisExt[0].dataeaseName,
y: xAxisExt[1].dataeaseName
}
})
.shape(flowMapEndName[0].dataeaseName, args => {
if (has.has('from-' + args) || has.has('to-' + args)) {
return ''
}
has.set('to-' + args, args)
return args
})
.size(textFontSize)
.color(textColor)
.style({
textAnchor: 'top', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
spacing: 2, // 字符间距
padding: [1, 1], // 文本包围盒 padding [水平垂直]影响碰撞检测结果避免相邻文本靠的太近
textAllowOverlap: true
})
configList.push(endTextLayer)
}
}
pointConfig = (chart, xAxis, xAxisExt, misc, configList) => {
const color = misc.flowMapConfig.pointConfig.text.color
const size = misc.flowMapConfig.pointConfig.point.size
const animate = misc.flowMapConfig.pointConfig.point.animate
const speed = misc.flowMapConfig.pointConfig.point.speed
const fromDefaultPointLayer = new PointLayer({ zIndex: -1 })
.source(chart.data?.tableRow, {
parser: {
type: 'json',
x: xAxis[0].dataeaseName,
y: xAxis[1].dataeaseName
}
})
.shape('circle')
.size(size)
.color(color)
.style({
blur: 0.6
})
configList.push(fromDefaultPointLayer)
const fromAnimatePointLayer = new PointLayer({ zIndex: -1 })
.source(chart.data?.tableRow, {
parser: {
type: 'json',
x: xAxis[0].dataeaseName,
y: xAxis[1].dataeaseName
}
})
.shape('circle')
.size(20)
.color(color)
.animate({
enable: true,
speed: speed,
rings: 0.01
})
const toDefaultPointLayer = new PointLayer({ zIndex: -1 })
.source(chart.data?.tableRow, {
parser: {
type: 'json',
x: xAxisExt[0].dataeaseName,
y: xAxisExt[1].dataeaseName
}
})
.shape('circle')
.size(size)
.color(color)
.style({
blur: 0.6
})
configList.push(toDefaultPointLayer)
const toAnimatePointLayer = new PointLayer({ zIndex: -1 })
.source(chart.data?.tableRow, {
parser: {
type: 'json',
x: xAxisExt[0].dataeaseName,
y: xAxisExt[1].dataeaseName
}
})
.shape('circle')
.size(20)
.color(color)
.animate({
enable: true,
speed: speed,
rings: 0.01
})
if (animate) {
configList.push(fromAnimatePointLayer)
configList.push(toAnimatePointLayer)
}
config.once('inited', () => {
mapRendered(container)
})
this.configZoomButton(chart, scene)
return new L7Wrapper(scene, config)
}
getMapKey = async () => {
@ -135,7 +325,7 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
}
setupDefaultOptions(chart: ChartObj): ChartObj {
chart.customAttr.misc.mapLineAnimate = true
chart.customAttr.misc.flowMapConfig.lineConfig.mapLineAnimate = true
return chart
}

View File

@ -208,4 +208,14 @@ public class ChartViewBaseDTO implements Serializable {
*/
private Boolean aggregate;
/**
* 流向地图起点名称
*/
private List<ChartViewFieldDTO> flowMapStartName;
/**
* 流向地图终点名称
*/
private List<ChartViewFieldDTO> flowMapEndName;
}