mirror of
https://github.com/dataease/dataease.git
synced 2025-02-24 11:32:57 +08:00
feat(图表): 支持圆形填充图
This commit is contained in:
parent
7aff0c0dbe
commit
7f5e2174f4
16
core/core-frontend/src/assets/svg/circle-packing-dark.svg
Normal file
16
core/core-frontend/src/assets/svg/circle-packing-dark.svg
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<svg width="80" height="56" viewBox="0 0 80 56" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="40" cy="28" r="25"/>
|
||||||
|
<circle cx="40" cy="14" r="8" fill="#26DCC3"/>
|
||||||
|
<circle cx="43" cy="42" r="5" fill="#00D6B9"/>
|
||||||
|
<circle cx="31" cy="43" r="5" fill="#3370FF"/>
|
||||||
|
<circle cx="28" cy="19" r="3" fill="#00D6B9"/>
|
||||||
|
<circle cx="53" cy="29" r="10" fill="#3370FF"/>
|
||||||
|
<circle cx="34" cy="29" r="7" fill="#3370FF"/>
|
||||||
|
<circle cx="38.5" cy="11.5" r="3.5" fill="#99EFE3"/>
|
||||||
|
<circle cx="43.5" cy="16.5" r="2.5" fill="#99EFE3"/>
|
||||||
|
<circle cx="49.5" cy="27.5" r="4.5" fill="#ADC6FF"/>
|
||||||
|
<circle cx="58" cy="27" r="3" fill="#ADC6FF"/>
|
||||||
|
<circle cx="58" cy="27" r="3" fill="#ADC6FF"/>
|
||||||
|
<circle cx="55" cy="34" r="3" fill="#ADC6FF"/>
|
||||||
|
</svg>
|
||||||
|
|
After Width: | Height: | Size: 735 B |
15
core/core-frontend/src/assets/svg/circle-packing-origin.svg
Normal file
15
core/core-frontend/src/assets/svg/circle-packing-origin.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<svg width="80" height="56" viewBox="0 0 80 56" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="40" cy="28" r="25"/>
|
||||||
|
<circle cx="40" cy="14" r="8" fill="#26DCC3"/>
|
||||||
|
<circle cx="43" cy="42" r="5" fill="#00D6B9"/>
|
||||||
|
<circle cx="31" cy="43" r="5" fill="#3370FF"/>
|
||||||
|
<circle cx="28" cy="19" r="3" fill="#00D6B9"/>
|
||||||
|
<circle cx="53" cy="29" r="10" fill="#3370FF"/>
|
||||||
|
<circle cx="34" cy="29" r="7" fill="#3370FF"/>
|
||||||
|
<circle cx="38.5" cy="11.5" r="3.5" fill="#99EFE3"/>
|
||||||
|
<circle cx="43.5" cy="16.5" r="2.5" fill="#99EFE3"/>
|
||||||
|
<circle cx="49.5" cy="27.5" r="4.5" fill="#ADC6FF"/>
|
||||||
|
<circle cx="58" cy="27" r="3" fill="#ADC6FF"/>
|
||||||
|
<circle cx="58" cy="27" r="3" fill="#ADC6FF"/>
|
||||||
|
<circle cx="55" cy="34" r="3" fill="#ADC6FF"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 734 B |
15
core/core-frontend/src/assets/svg/circle-packing.svg
Normal file
15
core/core-frontend/src/assets/svg/circle-packing.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="40" cy="28" r="25" fill="#D6E2FF"/>
|
||||||
|
<circle cx="40" cy="14" r="8" fill="#26DCC3"/>
|
||||||
|
<circle cx="43" cy="42" r="5" fill="#00D6B9"/>
|
||||||
|
<circle cx="31" cy="43" r="5" fill="#3370FF"/>
|
||||||
|
<circle cx="28" cy="19" r="3" fill="#00D6B9"/>
|
||||||
|
<circle cx="53" cy="29" r="10" fill="#3370FF"/>
|
||||||
|
<circle cx="34" cy="29" r="7" fill="#3370FF"/>
|
||||||
|
<circle cx="38.5" cy="11.5" r="3.5" fill="#99EFE3"/>
|
||||||
|
<circle cx="43.5" cy="16.5" r="2.5" fill="#99EFE3"/>
|
||||||
|
<circle cx="49.5" cy="27.5" r="4.5" fill="#ADC6FF"/>
|
||||||
|
<circle cx="58" cy="27" r="3" fill="#ADC6FF"/>
|
||||||
|
<circle cx="58" cy="27" r="3" fill="#ADC6FF"/>
|
||||||
|
<circle cx="55" cy="34" r="3" fill="#ADC6FF"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 760 B |
@ -73,6 +73,7 @@ import { lockStoreWithOut } from '@/store/modules/data-visualization/lock'
|
|||||||
import ContextMenuAsideDetails from '@/components/data-visualization/canvas/ContextMenuAsideDetails.vue'
|
import ContextMenuAsideDetails from '@/components/data-visualization/canvas/ContextMenuAsideDetails.vue'
|
||||||
import ComposeShow from '@/components/data-visualization/canvas/ComposeShow.vue'
|
import ComposeShow from '@/components/data-visualization/canvas/ComposeShow.vue'
|
||||||
import { composeStoreWithOut } from '@/store/modules/data-visualization/compose'
|
import { composeStoreWithOut } from '@/store/modules/data-visualization/compose'
|
||||||
|
import circlePackingOrigin from '@/assets/svg/circle-packing-origin.svg'
|
||||||
const dropdownMore = ref(null)
|
const dropdownMore = ref(null)
|
||||||
const lockStore = lockStoreWithOut()
|
const lockStore = lockStoreWithOut()
|
||||||
|
|
||||||
@ -233,7 +234,8 @@ const iconMap = {
|
|||||||
'waterfall-origin': waterfallOrigin,
|
'waterfall-origin': waterfallOrigin,
|
||||||
'word-cloud-origin': wordCloudOrigin,
|
'word-cloud-origin': wordCloudOrigin,
|
||||||
't-heatmap-origin': tHeatmapOrigin,
|
't-heatmap-origin': tHeatmapOrigin,
|
||||||
group: group
|
group: group,
|
||||||
|
'circle-packing-origin': circlePackingOrigin
|
||||||
}
|
}
|
||||||
const getIconName = item => {
|
const getIconName = item => {
|
||||||
if (item.component === 'UserView') {
|
if (item.component === 'UserView') {
|
||||||
|
@ -80,6 +80,7 @@ import RealTimeGroup from '@/components/data-visualization/RealTimeGroup.vue'
|
|||||||
import { contextmenuStoreWithOut } from '@/store/modules/data-visualization/contextmenu'
|
import { contextmenuStoreWithOut } from '@/store/modules/data-visualization/contextmenu'
|
||||||
import RealTimeTab from '@/components/data-visualization/RealTimeTab.vue'
|
import RealTimeTab from '@/components/data-visualization/RealTimeTab.vue'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import circlePackingOrigin from '@/assets/svg/circle-packing-origin.svg'
|
||||||
const dropdownMore = ref(null)
|
const dropdownMore = ref(null)
|
||||||
const lockStore = lockStoreWithOut()
|
const lockStore = lockStoreWithOut()
|
||||||
|
|
||||||
@ -329,7 +330,8 @@ const iconMap = {
|
|||||||
'word-cloud-origin': wordCloudOrigin,
|
'word-cloud-origin': wordCloudOrigin,
|
||||||
't-heatmap-origin': tHeatmapOrigin,
|
't-heatmap-origin': tHeatmapOrigin,
|
||||||
'picture-group-origin': pictureGroupOrigin,
|
'picture-group-origin': pictureGroupOrigin,
|
||||||
group: group
|
group: group,
|
||||||
|
'circle-packing-origin': circlePackingOrigin
|
||||||
}
|
}
|
||||||
const getIconName = item => {
|
const getIconName = item => {
|
||||||
if (item.component === 'UserView') {
|
if (item.component === 'UserView') {
|
||||||
|
@ -344,7 +344,8 @@ const boardMoveActive = computed(() => {
|
|||||||
'table-pivot',
|
'table-pivot',
|
||||||
'symbolic-map',
|
'symbolic-map',
|
||||||
'heat-map',
|
'heat-map',
|
||||||
't-heatmap'
|
't-heatmap',
|
||||||
|
'circle-packing'
|
||||||
]
|
]
|
||||||
return element.value.isPlugin || CHARTS.includes(element.value.innerType)
|
return element.value.isPlugin || CHARTS.includes(element.value.innerType)
|
||||||
})
|
})
|
||||||
|
@ -42,6 +42,7 @@ import treemapDark from '@/assets/svg/treemap-dark.svg'
|
|||||||
import waterfallDark from '@/assets/svg/waterfall-dark.svg'
|
import waterfallDark from '@/assets/svg/waterfall-dark.svg'
|
||||||
import wordCloudDark from '@/assets/svg/word-cloud-dark.svg'
|
import wordCloudDark from '@/assets/svg/word-cloud-dark.svg'
|
||||||
import tHeatmapDark from '@/assets/svg/t-heatmap-dark.svg'
|
import tHeatmapDark from '@/assets/svg/t-heatmap-dark.svg'
|
||||||
|
import circlePackingDark from '@/assets/svg/circle-packing-dark.svg'
|
||||||
|
|
||||||
const iconChartDarkMap = {
|
const iconChartDarkMap = {
|
||||||
'area-dark': areaDark,
|
'area-dark': areaDark,
|
||||||
@ -87,7 +88,8 @@ const iconChartDarkMap = {
|
|||||||
'treemap-dark': treemapDark,
|
'treemap-dark': treemapDark,
|
||||||
'waterfall-dark': waterfallDark,
|
'waterfall-dark': waterfallDark,
|
||||||
'word-cloud-dark': wordCloudDark,
|
'word-cloud-dark': wordCloudDark,
|
||||||
't-heatmap-dark': tHeatmapDark
|
't-heatmap-dark': tHeatmapDark,
|
||||||
|
'circle-packing-dark': circlePackingDark
|
||||||
}
|
}
|
||||||
|
|
||||||
export { iconChartDarkMap }
|
export { iconChartDarkMap }
|
||||||
|
@ -45,6 +45,7 @@ import tHeatmap from '@/assets/svg/t-heatmap.svg'
|
|||||||
import pictureGroup from '@/assets/svg/picture-group.svg'
|
import pictureGroup from '@/assets/svg/picture-group.svg'
|
||||||
import filter from '@/assets/svg/filter.svg'
|
import filter from '@/assets/svg/filter.svg'
|
||||||
import outerParams from '@/assets/svg/icon_params_setting.svg'
|
import outerParams from '@/assets/svg/icon_params_setting.svg'
|
||||||
|
import circlePacking from '@/assets/svg/circle-packing.svg'
|
||||||
|
|
||||||
const iconChartMap = {
|
const iconChartMap = {
|
||||||
'area-stack': areaStack,
|
'area-stack': areaStack,
|
||||||
@ -93,7 +94,8 @@ const iconChartMap = {
|
|||||||
't-heatmap': tHeatmap,
|
't-heatmap': tHeatmap,
|
||||||
'picture-group': pictureGroup,
|
'picture-group': pictureGroup,
|
||||||
filter: filter,
|
filter: filter,
|
||||||
outerParams: outerParams
|
outerParams: outerParams,
|
||||||
|
'circle-packing': circlePacking
|
||||||
}
|
}
|
||||||
|
|
||||||
export { iconChartMap }
|
export { iconChartMap }
|
||||||
|
@ -1919,7 +1919,13 @@ export default {
|
|||||||
'When Customizing, Supports SVG, JPG, JPEG, and PNG files up to 1MB',
|
'When Customizing, Supports SVG, JPG, JPEG, and PNG files up to 1MB',
|
||||||
size_range: 'Size Range',
|
size_range: 'Size Range',
|
||||||
x_axis_constant_line: 'X-axis Constant Line',
|
x_axis_constant_line: 'X-axis Constant Line',
|
||||||
y_axis_constant_line: 'Y-axis Constant Line'
|
y_axis_constant_line: 'Y-axis Constant Line',
|
||||||
|
chart_circle_packing: 'Circle Packing Chart',
|
||||||
|
circle_packing_name: 'Circle Name',
|
||||||
|
circle_packing_value: 'Circle Size',
|
||||||
|
circle_packing_border_color: 'Border color',
|
||||||
|
circle_packing_border_width: 'Border width',
|
||||||
|
circle_packing_padding: 'Circle padding'
|
||||||
},
|
},
|
||||||
dataset: {
|
dataset: {
|
||||||
scope_edit: 'Only effective when editing',
|
scope_edit: 'Only effective when editing',
|
||||||
|
@ -1919,7 +1919,13 @@ Scatter chart (bubble) chart: {a} (series name), {b} (data name), {c} (value arr
|
|||||||
x_axis_constant_line: 'X-axis Constant Line',
|
x_axis_constant_line: 'X-axis Constant Line',
|
||||||
y_axis_constant_line: 'Y-axis Constant Line',
|
y_axis_constant_line: 'Y-axis Constant Line',
|
||||||
sort_priority: 'Sort Priority Setting',
|
sort_priority: 'Sort Priority Setting',
|
||||||
sort_priority_tip: 'Top-down, sorting priority from highest to lowest'
|
sort_priority_tip: 'Top-down, sorting priority from highest to lowest',
|
||||||
|
chart_circle_packing: 'Circle packing chart',
|
||||||
|
circle_packing_name: 'Circle name',
|
||||||
|
circle_packing_value: 'Circle size',
|
||||||
|
circle_packing_border_color: 'Border color',
|
||||||
|
circle_packing_border_width: 'Border width',
|
||||||
|
circle_packing_padding: 'Circle padding'
|
||||||
},
|
},
|
||||||
dataset: {
|
dataset: {
|
||||||
scope_edit: 'Only effective when editing',
|
scope_edit: 'Only effective when editing',
|
||||||
|
@ -1877,7 +1877,13 @@ export default {
|
|||||||
x_axis_constant_line: 'X 軸恆線',
|
x_axis_constant_line: 'X 軸恆線',
|
||||||
y_axis_constant_line: 'Y 軸恆線',
|
y_axis_constant_line: 'Y 軸恆線',
|
||||||
sort_priority: '排序優先級設置',
|
sort_priority: '排序優先級設置',
|
||||||
sort_priority_tip: '自上而下,排序優先級從高到低'
|
sort_priority_tip: '自上而下,排序優先級從高到低',
|
||||||
|
chart_circle_packing: '圓形填充圖',
|
||||||
|
circle_packing_name: '圓形名稱',
|
||||||
|
circle_packing_value: '圓形大小',
|
||||||
|
circle_packing_border_color: '邊線顏色',
|
||||||
|
circle_packing_border_width: '邊線寬度',
|
||||||
|
circle_packing_padding: 'Circle padding'
|
||||||
},
|
},
|
||||||
dataset: {
|
dataset: {
|
||||||
scope_edit: '僅編輯時生效',
|
scope_edit: '僅編輯時生效',
|
||||||
|
@ -1879,7 +1879,13 @@ export default {
|
|||||||
x_axis_constant_line: 'X 轴恒线',
|
x_axis_constant_line: 'X 轴恒线',
|
||||||
y_axis_constant_line: 'Y 轴恒线',
|
y_axis_constant_line: 'Y 轴恒线',
|
||||||
sort_priority: '排序优先级设置',
|
sort_priority: '排序优先级设置',
|
||||||
sort_priority_tip: '自上而下,排序优先级从高到低'
|
sort_priority_tip: '自上而下,排序优先级从高到低',
|
||||||
|
chart_circle_packing: '圆形填充图',
|
||||||
|
circle_packing_name: '圆形名称',
|
||||||
|
circle_packing_value: '圆形大小',
|
||||||
|
circle_packing_border_color: '边线颜色',
|
||||||
|
circle_packing_border_width: '边线宽度',
|
||||||
|
circle_packing_padding: '圓形間距'
|
||||||
},
|
},
|
||||||
dataset: {
|
dataset: {
|
||||||
scope_edit: '仅编辑时生效',
|
scope_edit: '仅编辑时生效',
|
||||||
|
@ -350,6 +350,18 @@ declare interface ChartBasicStyle {
|
|||||||
* 雷达图面积颜色开关
|
* 雷达图面积颜色开关
|
||||||
*/
|
*/
|
||||||
radarAreaColor: boolean
|
radarAreaColor: boolean
|
||||||
|
/**
|
||||||
|
* 圆形填充图边线颜色
|
||||||
|
*/
|
||||||
|
circleBorderColor: string
|
||||||
|
/**
|
||||||
|
* 圆形填充图边线宽度
|
||||||
|
*/
|
||||||
|
circleBorderWidth: number
|
||||||
|
/**
|
||||||
|
* 圆形填充图间距
|
||||||
|
*/
|
||||||
|
circlePadding: number
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 表头属性
|
* 表头属性
|
||||||
|
@ -93,6 +93,9 @@ const initFieldCtrl = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const isCirclePacking = computed(() => {
|
||||||
|
return equalsAny(props.chart.type, 'circle-packing')
|
||||||
|
})
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
init()
|
init()
|
||||||
})
|
})
|
||||||
@ -196,7 +199,9 @@ onMounted(() => {
|
|||||||
</el-radio>
|
</el-radio>
|
||||||
<el-radio v-if="isRichText" :effect="themes" :label="'custom'"> 自定义 </el-radio>
|
<el-radio v-if="isRichText" :effect="themes" :label="'custom'"> 自定义 </el-radio>
|
||||||
<template v-if="!isRichText">
|
<template v-if="!isRichText">
|
||||||
<el-radio :effect="themes" :label="'setZero'">{{ t('chart.set_zero') }}</el-radio>
|
<el-radio v-if="!isCirclePacking" :effect="themes" :label="'setZero'">{{
|
||||||
|
t('chart.set_zero')
|
||||||
|
}}</el-radio>
|
||||||
<el-radio v-if="showIgnoreOption" :effect="themes" :label="'ignoreData'">
|
<el-radio v-if="showIgnoreOption" :effect="themes" :label="'ignoreData'">
|
||||||
{{ t('chart.ignore_data') }}
|
{{ t('chart.ignore_data') }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
@ -13,7 +13,7 @@ import { SERIES_NUMBER_FIELD } from '@antv/s2'
|
|||||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { isNumber } from 'mathjs'
|
import { isNumber } from 'mathjs'
|
||||||
import { ElMessage, UploadProps } from 'element-plus-secondary'
|
import { ElFormItem, ElInputNumber, ElMessage, UploadProps } from 'element-plus-secondary'
|
||||||
import { svgStrToUrl } from '../../../js/util'
|
import { svgStrToUrl } from '../../../js/util'
|
||||||
|
|
||||||
const dvMainStore = dvMainStoreWithOut()
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
@ -1389,6 +1389,69 @@ onMounted(() => {
|
|||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
<!-- pie/rose end -->
|
<!-- pie/rose end -->
|
||||||
|
<!-- circle-packing start -->
|
||||||
|
<div v-if="showProperty('circleBorderStyle')">
|
||||||
|
<div class="alpha-setting">
|
||||||
|
<el-row style="display: flex; width: 100%">
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.circle_packing_border_color')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-color-picker
|
||||||
|
v-model="state.basicStyleForm.circleBorderColor"
|
||||||
|
class="color-picker-style"
|
||||||
|
:triggerWidth="65"
|
||||||
|
is-custom
|
||||||
|
show-alpha
|
||||||
|
:predefine="state.predefineColors"
|
||||||
|
@change="changeBasicStyle('circleBorderColor')"
|
||||||
|
>
|
||||||
|
</el-color-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14">
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.circle_packing_border_width')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:max="50"
|
||||||
|
:effect="themes"
|
||||||
|
controls-position="right"
|
||||||
|
v-model="state.basicStyleForm.circleBorderWidth"
|
||||||
|
class="color-picker-style"
|
||||||
|
@change="changeBasicStyle('circleBorderWidth')"
|
||||||
|
>
|
||||||
|
</el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<el-row>
|
||||||
|
<el-form-item
|
||||||
|
style="width: 150px"
|
||||||
|
:label="t('chart.circle_packing_padding')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:max="10"
|
||||||
|
:effect="themes"
|
||||||
|
controls-position="right"
|
||||||
|
v-model="state.basicStyleForm.circlePadding"
|
||||||
|
class="color-picker-style"
|
||||||
|
@change="changeBasicStyle('circlePadding')"
|
||||||
|
>
|
||||||
|
</el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<!-- circle-packing end -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
@ -1520,6 +1520,13 @@ export const CHART_TYPE_CONFIGS = [
|
|||||||
value: 'sankey',
|
value: 'sankey',
|
||||||
title: t('chart.chart_sankey'),
|
title: t('chart.chart_sankey'),
|
||||||
icon: 'sankey'
|
icon: 'sankey'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
render: 'antv',
|
||||||
|
category: 'distribute',
|
||||||
|
value: 'circle-packing',
|
||||||
|
title: t('chart.chart_circle_packing'),
|
||||||
|
icon: 'circle-packing'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,298 @@
|
|||||||
|
import {
|
||||||
|
G2PlotChartView,
|
||||||
|
G2PlotDrawOptions
|
||||||
|
} from '@/views/chart/components/js/panel/types/impl/g2plot'
|
||||||
|
import type {
|
||||||
|
CirclePacking as G2CirclePacking,
|
||||||
|
CirclePackingOptions
|
||||||
|
} from '@antv/g2plot/esm/plots/circle-packing'
|
||||||
|
import { flow, parseJson } from '@/views/chart/components/js/util'
|
||||||
|
import { getPadding } from '@/views/chart/components/js/panel/common/common_antv'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import type { Datum } from '@antv/g2plot/esm/types/common'
|
||||||
|
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const DEFAULT_DATA = []
|
||||||
|
/**
|
||||||
|
* 圆形填充图
|
||||||
|
*/
|
||||||
|
export class CirclePacking extends G2PlotChartView<CirclePackingOptions, G2CirclePacking> {
|
||||||
|
properties: EditorProperty[] = [
|
||||||
|
'basic-style-selector',
|
||||||
|
'background-overall-component',
|
||||||
|
'border-style',
|
||||||
|
'label-selector',
|
||||||
|
'legend-selector',
|
||||||
|
'title-selector',
|
||||||
|
'tooltip-selector',
|
||||||
|
'jump-set',
|
||||||
|
'linkage'
|
||||||
|
]
|
||||||
|
propertyInner: EditorPropertyInner = {
|
||||||
|
'background-overall-component': ['all'],
|
||||||
|
'border-style': ['all'],
|
||||||
|
'basic-style-selector': ['colors', 'alpha', 'circleBorderStyle'],
|
||||||
|
'title-selector': [
|
||||||
|
'title',
|
||||||
|
'fontSize',
|
||||||
|
'color',
|
||||||
|
'hPosition',
|
||||||
|
'isItalic',
|
||||||
|
'isBolder',
|
||||||
|
'remarkShow',
|
||||||
|
'fontFamily',
|
||||||
|
'letterSpace',
|
||||||
|
'fontShadow'
|
||||||
|
],
|
||||||
|
'function-cfg': ['emptyDataStrategy'],
|
||||||
|
'label-selector': ['color', 'fontSize'],
|
||||||
|
'legend-selector': ['icon', 'orient', 'fontSize', 'color', 'hPosition', 'vPosition'],
|
||||||
|
'tooltip-selector': ['color', 'fontSize', 'backgroundColor', 'tooltipFormatter', 'show']
|
||||||
|
}
|
||||||
|
axis: AxisType[] = ['xAxis', 'yAxis', 'filter', 'drill']
|
||||||
|
axisConfig: AxisConfig = {
|
||||||
|
xAxis: {
|
||||||
|
name: `${t('chart.circle_packing_name')} / ${t('chart.dimension')}`,
|
||||||
|
type: 'd'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: `${t('chart.circle_packing_value')} / ${t('chart.quota')}`,
|
||||||
|
type: 'q',
|
||||||
|
limit: 1,
|
||||||
|
allowEmpty: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async drawChart(drawOptions: G2PlotDrawOptions<G2CirclePacking>): Promise<G2CirclePacking> {
|
||||||
|
const { chart, container, action } = drawOptions
|
||||||
|
if (chart?.data?.tableRow?.length) {
|
||||||
|
// data
|
||||||
|
const data = chart.data.tableRow
|
||||||
|
const { xAxis, yAxis } = chart
|
||||||
|
const ySort = yAxis[0]?.sort ?? 'none'
|
||||||
|
const sort = {
|
||||||
|
sort: (a, b) =>
|
||||||
|
ySort === 'asc' ? a.value - b.value : ySort === 'desc' ? b.value - a.value : 0
|
||||||
|
}
|
||||||
|
// 根据配置获取节点的key,用于构建节点树,拖入字段顺序即为节点的层级
|
||||||
|
const nodeKeys = xAxis.map(item => item.dataeaseName)
|
||||||
|
// 将数据转为圆形填充图数据格式
|
||||||
|
const getCirclePackingData = () => {
|
||||||
|
const result = [{ name: t('commons.all'), children: [] }]
|
||||||
|
const addNode = (nodes, item, level) => {
|
||||||
|
if (level >= nodeKeys.length) return
|
||||||
|
const key = nodeKeys[level]
|
||||||
|
const value = item[key]
|
||||||
|
let node = nodes.find(n => n.name === value)
|
||||||
|
if (!node) {
|
||||||
|
node = { name: value, field: xAxis.find(f => f.dataeaseName === key), children: [] }
|
||||||
|
nodes.push(node)
|
||||||
|
}
|
||||||
|
if (level === nodeKeys.length - 1) {
|
||||||
|
node.value = yAxis.length ? item[yAxis[0].dataeaseName] : 1
|
||||||
|
} else {
|
||||||
|
addNode(node.children, item, level + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.forEach(item => addNode(result[0].children, item, 0))
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
// options
|
||||||
|
const initOptions: CirclePackingOptions = {
|
||||||
|
data: getCirclePackingData(),
|
||||||
|
appendPadding: getPadding(chart),
|
||||||
|
hierarchyConfig: {
|
||||||
|
...(ySort === 'none' ? {} : sort)
|
||||||
|
},
|
||||||
|
interactions: [
|
||||||
|
{
|
||||||
|
type: 'legend-active',
|
||||||
|
cfg: {
|
||||||
|
start: [{ trigger: 'legend-item:mouseenter', action: ['element-active:reset'] }],
|
||||||
|
end: [{ trigger: 'legend-item:mouseleave', action: ['element-active:reset'] }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'legend-filter',
|
||||||
|
cfg: {
|
||||||
|
start: [
|
||||||
|
{
|
||||||
|
trigger: 'legend-item:click',
|
||||||
|
action: [
|
||||||
|
'list-unchecked:toggle',
|
||||||
|
'data-filter:filter',
|
||||||
|
'element-active:reset',
|
||||||
|
'element-highlight:reset'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const options = this.setupOptions(chart, initOptions)
|
||||||
|
const { CirclePacking: G2CirclePacking } = await import(
|
||||||
|
'@antv/g2plot/esm/plots/circle-packing'
|
||||||
|
)
|
||||||
|
const newChart = new G2CirclePacking(container, options)
|
||||||
|
newChart.on('point:click', param => {
|
||||||
|
const data = param?.data?.data
|
||||||
|
if (data?.name === t('commons.all')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
action({
|
||||||
|
x: param.x,
|
||||||
|
y: param.y,
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
dimensionList: [
|
||||||
|
{
|
||||||
|
id: data?.field?.id,
|
||||||
|
value: data.name,
|
||||||
|
name: data.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return newChart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected configBasicStyle(chart: Chart, options: CirclePackingOptions): CirclePackingOptions {
|
||||||
|
// size
|
||||||
|
const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
|
||||||
|
const s = JSON.parse(JSON.stringify(customAttr.basicStyle))
|
||||||
|
// 圆形边框样式
|
||||||
|
const pointStyle = {
|
||||||
|
stroke: s.circleBorderColor,
|
||||||
|
lineWidth: s.circleBorderWidth ?? 0
|
||||||
|
}
|
||||||
|
const padding = s.circlePadding
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
hierarchyConfig: {
|
||||||
|
...options.hierarchyConfig,
|
||||||
|
padding: padding / 100 ?? 0
|
||||||
|
},
|
||||||
|
pointStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected configLabel(chart: Chart, options: CirclePackingOptions): CirclePackingOptions {
|
||||||
|
const tmpOptions = super.configLabel(chart, options)
|
||||||
|
if (!tmpOptions.label) {
|
||||||
|
return {
|
||||||
|
...tmpOptions,
|
||||||
|
label: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { label: labelAttr } = parseJson(chart.customAttr)
|
||||||
|
const label = {
|
||||||
|
...tmpOptions.label,
|
||||||
|
textAlign: 'center',
|
||||||
|
offsetY: 5,
|
||||||
|
layout: labelAttr.fullDisplay ? [{ type: 'limit-in-plot' }] : tmpOptions.label.layout,
|
||||||
|
formatter: (d: Datum, _point) => {
|
||||||
|
return d.children.length === 0 ? d.name : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...tmpOptions,
|
||||||
|
label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected configTooltip(chart: Chart, options: CirclePackingOptions): CirclePackingOptions {
|
||||||
|
const temOptions = super.configTooltip(chart, options)
|
||||||
|
if (!temOptions.tooltip) {
|
||||||
|
return temOptions
|
||||||
|
}
|
||||||
|
const tooltipAttr = parseJson(chart.customAttr).tooltip
|
||||||
|
return {
|
||||||
|
...temOptions,
|
||||||
|
tooltip: {
|
||||||
|
...temOptions,
|
||||||
|
fields: ['name', 'value'],
|
||||||
|
formatter: d => {
|
||||||
|
let value = d.value
|
||||||
|
if (tooltipAttr.tooltipFormatter) {
|
||||||
|
value = valueFormatter(value, tooltipAttr.tooltipFormatter)
|
||||||
|
}
|
||||||
|
return { name: d.name, value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configEmptyDataStrategy(chart: Chart, options: CirclePackingOptions): CirclePackingOptions {
|
||||||
|
const { functionCfg } = parseJson(chart.senior)
|
||||||
|
const emptyDataStrategy = functionCfg.emptyDataStrategy
|
||||||
|
const setChildren = children => {
|
||||||
|
if (emptyDataStrategy === 'ignoreData') {
|
||||||
|
for (let i = children.length - 1; i >= 0; i--) {
|
||||||
|
let isNotNullChildren = []
|
||||||
|
if (children[i].children?.length) {
|
||||||
|
isNotNullChildren = children[i].children.filter(item => item.value !== null)
|
||||||
|
}
|
||||||
|
if (children[i].children?.length && isNotNullChildren.length) {
|
||||||
|
setChildren(children[i].children)
|
||||||
|
}
|
||||||
|
if (children[i]?.hasOwnProperty('value') && children[i].value === null) {
|
||||||
|
children.splice(i, 1)
|
||||||
|
}
|
||||||
|
if (!children[i]?.hasOwnProperty('value') && isNotNullChildren.length === 0) {
|
||||||
|
children.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = children.length - 1; i >= 0; i--) {
|
||||||
|
let isNotNullChildren = []
|
||||||
|
if (children[i].children?.length) {
|
||||||
|
isNotNullChildren = children[i].children.filter(item => item.value !== null)
|
||||||
|
if (!isNotNullChildren.length) {
|
||||||
|
children[i].children = []
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setChildren(children[i].children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = cloneDeep(options.data.children)
|
||||||
|
setChildren(data)
|
||||||
|
options.data.children = data
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
setupDefaultOptions(chart: ChartObj): ChartObj {
|
||||||
|
const { customAttr, customStyle, senior } = chart
|
||||||
|
const { label, basicStyle } = customAttr
|
||||||
|
const { legend } = customStyle
|
||||||
|
senior.functionCfg.emptyDataStrategy = 'ignoreData'
|
||||||
|
customAttr.label = {
|
||||||
|
...label,
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
legend.show = false
|
||||||
|
basicStyle.circleBorderWidth = 0
|
||||||
|
basicStyle.circleBorderColor = '#fff'
|
||||||
|
basicStyle.circlePadding = 0
|
||||||
|
return chart
|
||||||
|
}
|
||||||
|
protected setupOptions(chart: Chart, options: CirclePackingOptions): CirclePackingOptions {
|
||||||
|
return flow(
|
||||||
|
this.configTheme,
|
||||||
|
this.configEmptyDataStrategy,
|
||||||
|
this.configBasicStyle,
|
||||||
|
this.configLabel,
|
||||||
|
this.configTooltip,
|
||||||
|
this.configLegend
|
||||||
|
)(chart, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('circle-packing', DEFAULT_DATA)
|
||||||
|
}
|
||||||
|
}
|
@ -185,7 +185,6 @@ export function getLabel(chart: Chart) {
|
|||||||
layout.push({ type: 'hide-overlap' })
|
layout.push({ type: 'hide-overlap' })
|
||||||
} else {
|
} else {
|
||||||
layout.push({ type: 'limit-in-plot' })
|
layout.push({ type: 'limit-in-plot' })
|
||||||
layout.push({ type: 'fixed-overlap' })
|
|
||||||
layout.push({ type: 'hide-overlap' })
|
layout.push({ type: 'hide-overlap' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ import { deepCopy, isMobile } from '@/utils/utils'
|
|||||||
import { isDashboard, trackBarStyleCheck } from '@/utils/canvasUtils'
|
import { isDashboard, trackBarStyleCheck } from '@/utils/canvasUtils'
|
||||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||||
import { L7ChartView } from '@/views/chart/components/js/panel/types/impl/l7'
|
import { L7ChartView } from '@/views/chart/components/js/panel/types/impl/l7'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
const { t } = useI18n()
|
||||||
const dvMainStore = dvMainStoreWithOut()
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack, inMobile } =
|
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack, inMobile } =
|
||||||
storeToRefs(dvMainStore)
|
storeToRefs(dvMainStore)
|
||||||
@ -86,6 +87,7 @@ const emit = defineEmits([
|
|||||||
|
|
||||||
const g2TypeSeries1 = ['bidirectional-bar']
|
const g2TypeSeries1 = ['bidirectional-bar']
|
||||||
const g2TypeSeries0 = ['bar-range']
|
const g2TypeSeries0 = ['bar-range']
|
||||||
|
const g2TypeTree = ['circle-packing']
|
||||||
|
|
||||||
const { view, showPosition, scale, terminal, suffixId } = toRefs(props)
|
const { view, showPosition, scale, terminal, suffixId } = toRefs(props)
|
||||||
|
|
||||||
@ -165,6 +167,14 @@ const checkSelected = param => {
|
|||||||
return state.linkageActiveParam.name === param.field
|
return state.linkageActiveParam.name === param.field
|
||||||
} else if (g2TypeSeries0.includes(view.value.type)) {
|
} else if (g2TypeSeries0.includes(view.value.type)) {
|
||||||
return state.linkageActiveParam.category === param.category
|
return state.linkageActiveParam.category === param.category
|
||||||
|
} else if (g2TypeTree.includes(view.value.type)) {
|
||||||
|
if (
|
||||||
|
param.path?.startsWith(state.linkageActiveParam.name) ||
|
||||||
|
state.linkageActiveParam.name === t('commons.all')
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return state.linkageActiveParam.name === param.name
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
(state.linkageActiveParam.name === param.name ||
|
(state.linkageActiveParam.name === param.name ||
|
||||||
@ -444,7 +454,7 @@ const trackClick = trackAction => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let quotaList = state.pointParam.data.quotaList
|
let quotaList = state.pointParam.data.quotaList
|
||||||
if (curView.type === 'bar-range') {
|
if (['bar-range', 'circle-packing'].includes(curView.type)) {
|
||||||
quotaList = state.pointParam.data.dimensionList
|
quotaList = state.pointParam.data.dimensionList
|
||||||
} else {
|
} else {
|
||||||
quotaList[0]['value'] = state.pointParam.data.value
|
quotaList[0]['value'] = state.pointParam.data.value
|
||||||
|
@ -352,6 +352,13 @@ const chartClick = param => {
|
|||||||
ElMessage.error(t('chart.drill_field_error'))
|
ElMessage.error(t('chart.drill_field_error'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
view.value.type === 'circle-packing' &&
|
||||||
|
(param.data?.childNodeCount === 0 || param.data.name === t('commons.all'))
|
||||||
|
) {
|
||||||
|
ElMessage.error(t('chart.last_layer'))
|
||||||
|
return
|
||||||
|
}
|
||||||
if (state.drillClickDimensionList.length < props.view.drillFields.length - 1) {
|
if (state.drillClickDimensionList.length < props.view.drillFields.length - 1) {
|
||||||
state.drillClickDimensionList.push({
|
state.drillClickDimensionList.push({
|
||||||
dimensionList: param.data.dimensionList,
|
dimensionList: param.data.dimensionList,
|
||||||
|
Loading…
Reference in New Issue
Block a user