更新前端src/views(chart和template)目录文件
This commit is contained in:
parent
1357172783
commit
f2582cb0d0
@ -29,6 +29,7 @@ const DashboardPanel = defineAsyncComponent(
|
||||
() => import('@/views/dashboard/DashboardPreviewShow.vue')
|
||||
)
|
||||
const Copilot = defineAsyncComponent(() => import('@/views/copilot/index.vue'))
|
||||
const TemplateManage = defineAsyncComponent(() => import('@/views/template/indexInject.vue'))
|
||||
|
||||
const AsyncXpackComponent = defineAsyncComponent(() => import('@/components/plugin/src/index.vue'))
|
||||
|
||||
@ -42,7 +43,8 @@ const componentMap = {
|
||||
Datasource,
|
||||
ScreenPanel,
|
||||
DashboardPanel,
|
||||
Copilot
|
||||
Copilot,
|
||||
TemplateManage
|
||||
}
|
||||
const iframeStyle = ref(null)
|
||||
const setStyle = debounce(() => {
|
||||
|
@ -127,19 +127,19 @@ const groupActiveChange = category => {
|
||||
.chart-light {
|
||||
color: #646a73 !important;
|
||||
:deep(.group-right) {
|
||||
border-left: 1px solid @side-outline-border-color-light;
|
||||
border-left: 1px solid @side-outline-border-color-light!important;
|
||||
}
|
||||
:deep(.item-top) {
|
||||
background-color: #f5f6f7;
|
||||
background-color: #f5f6f7 !important;
|
||||
}
|
||||
:deep(.ul-custom) {
|
||||
color: @chart-change-font-color-light!important;
|
||||
}
|
||||
:deep(.item-bottom) {
|
||||
color: @chart-change-font-color-light;
|
||||
color: @chart-change-font-color-light!important;
|
||||
}
|
||||
:deep(.item-top-icon) {
|
||||
color: @chart-change-font-color-light;
|
||||
color: @chart-change-font-color-light!important;
|
||||
}
|
||||
}
|
||||
.group {
|
||||
@ -250,8 +250,8 @@ const groupActiveChange = category => {
|
||||
}
|
||||
.group .group-left .ul-custom{
|
||||
color: #a6a6a6 !important;
|
||||
}
|
||||
}
|
||||
.chart-light .item-top{
|
||||
background-color: #1a1a1a !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,45 +1,98 @@
|
||||
<script lang="tsx" setup>
|
||||
import dvUpArrow from '@/assets/svg/dv-up-arrow.svg'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n()
|
||||
defineProps({
|
||||
themes: {
|
||||
type: String,
|
||||
default: 'light'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="view-panel-Mask">
|
||||
<Icon class-name="item-icon" name="dv-up-arrow"><dvUpArrow class="svg-icon item-icon" /></Icon>
|
||||
<div>
|
||||
<el-button style="opacity: 1 !important" type="warning" size="mini" round>
|
||||
<span style="font-weight: bold; opacity: 1">{{
|
||||
t('visualization.template_view_tips')
|
||||
}}</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="view-panel-mask-left"></div>
|
||||
<div class="view-panel-mask">
|
||||
<el-popover
|
||||
:visible="true"
|
||||
placement="bottom"
|
||||
popper-class="template-popper-tips"
|
||||
:width="256"
|
||||
show-arrow
|
||||
>
|
||||
<div class="template-popper-tips-content">
|
||||
<p class="constant">{{ t('visualization.template_view_tips') }}</p>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div
|
||||
class="view-panel-mask-inner"
|
||||
:class="{ 'view-panel-mask-inner-dark': themes === 'dark' }"
|
||||
></div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.view-panel-Mask {
|
||||
display: flex;
|
||||
height: calc(100vh - 148px);
|
||||
width: 100%;
|
||||
background-color: rgba(92, 94, 97, 0.7);
|
||||
.view-panel-mask-left {
|
||||
height: 100%;
|
||||
width: 240px;
|
||||
position: absolute;
|
||||
top: 85px;
|
||||
left: 0px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: not-allowed;
|
||||
background-color: rgba(31, 35, 41);
|
||||
opacity: 0.4;
|
||||
z-index: 2;
|
||||
}
|
||||
.view-panel-mask {
|
||||
height: 100%;
|
||||
width: 178px;
|
||||
background-color: rgba(31, 35, 41);
|
||||
opacity: 0.4;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 240px;
|
||||
z-index: 2;
|
||||
cursor: not-allowed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.view-panel-mask-inner {
|
||||
top: 51px;
|
||||
left: 6px;
|
||||
height: 34px;
|
||||
width: 170px;
|
||||
background: white;
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 300px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
opacity: 1;
|
||||
color: #ff8800;
|
||||
.view-panel-mask-inner-dark {
|
||||
background: rgba(31, 35, 41);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.template-popper-tips {
|
||||
z-index: 1000 !important;
|
||||
padding: 24px !important;
|
||||
box-shadow: none !important;
|
||||
border: 0 !important;
|
||||
background: var(--ed-color-primary) !important;
|
||||
inset: 0 auto auto -5px !important;
|
||||
.ed-popper__arrow::before {
|
||||
border: 1px solid var(--ed-color-primary) !important;
|
||||
background: var(--ed-color-primary) !important;
|
||||
}
|
||||
}
|
||||
.template-popper-tips-content {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
.content {
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,76 @@
|
||||
<script lang="ts" setup>
|
||||
import dvAi from '@/assets/svg/dv-ai.svg'
|
||||
import { ref } from 'vue'
|
||||
const visible = ref(true)
|
||||
</script>
|
||||
<template>
|
||||
<el-popover
|
||||
:visible="visible"
|
||||
placement="bottom"
|
||||
popper-class="ai-popper-tips"
|
||||
:width="288"
|
||||
show-arrow
|
||||
>
|
||||
<div class="ai-popper-tips-content">
|
||||
<p class="constant">
|
||||
你好,我是 DataEase 智能客服<br />点击一下,开启高效解答模式~<br />
|
||||
</p>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="ai-popper-tips-icon">
|
||||
<el-icon style="margin: 2px" class="ai-icon">
|
||||
<Icon name="dv-ai"><dvAi class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.ai-popper-tips {
|
||||
z-index: 10001 !important;
|
||||
padding: 24px !important;
|
||||
box-shadow: none !important;
|
||||
border: 0px !important;
|
||||
background: var(--ed-color-primary) !important;
|
||||
.ed-popper__arrow::before {
|
||||
border: 1px solid var(--ed-color-primary) !important;
|
||||
background: var(--ed-color-primary) !important;
|
||||
}
|
||||
}
|
||||
.ai-popper-tips-content {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
.title {
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
}
|
||||
.content {
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
}
|
||||
.bottom {
|
||||
line-height: 22px;
|
||||
text-align: right;
|
||||
button {
|
||||
border: 0px !important;
|
||||
border-color: #ffffff !important;
|
||||
font-weight: 500;
|
||||
color: rgba(51, 112, 255, 1) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-popper-tips-icon {
|
||||
margin: 0 8px;
|
||||
z-index: 10003;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
</style>
|
@ -373,13 +373,13 @@ onMounted(() => {
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
<!-- <el-footer v-if="!isDataEaseBi">
|
||||
<el-footer v-if="!isDataEaseBi">
|
||||
<div class="footer-container">
|
||||
<el-button type="primary" :icon="Plus" link class="add-btn" @click="addDataset">
|
||||
{{ newSource }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-footer> -->
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</template>
|
||||
</el-popover>
|
||||
@ -395,7 +395,7 @@ onMounted(() => {
|
||||
:deep(.ed-input__wrapper) {
|
||||
cursor: pointer;
|
||||
padding: 1px 11px;
|
||||
|
||||
|
||||
.ed-input__inner {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
@ -405,7 +405,6 @@ onMounted(() => {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
:deep(.ed-form-item.is-error .ed-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
input {
|
||||
color: var(--ed-color-danger);
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ const emit = defineEmits([
|
||||
|
||||
const { item } = toRefs(props)
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes || 'dark'
|
||||
})
|
||||
const showValueFormatter = computed<boolean>(() => {
|
||||
return (
|
||||
@ -192,17 +192,25 @@ const showCustomSort = item => {
|
||||
}
|
||||
return !item.chartId && (item.deType === 0 || item.deType === 5)
|
||||
}
|
||||
const showSort = () => {
|
||||
|
||||
const NOT_SUPPORT_SORT = ['word-cloud', 'stock-line', 'treemap', 'circle-packing']
|
||||
const showSort = computed(() => {
|
||||
const { type: chartType } = props.chart
|
||||
const { type: propType } = props
|
||||
const notShowSort = ['word-cloud', 'stock-line'].includes(chartType)
|
||||
const notShowSort = NOT_SUPPORT_SORT.includes(chartType)
|
||||
if (notShowSort || propType === 'extColor') {
|
||||
return false
|
||||
}
|
||||
const isChartMix = chartType.includes('chart-mix')
|
||||
const isDimensionType = ['dimension', 'dimensionStack', 'dimensionExt'].includes(propType)
|
||||
return !isChartMix || isDimensionType
|
||||
}
|
||||
})
|
||||
const showSortPriority = computed(() => {
|
||||
if (props.chart.type.includes('chart-mix')) {
|
||||
return false
|
||||
}
|
||||
return showSort.value
|
||||
})
|
||||
const toggleHide = () => {
|
||||
item.value.index = props.index
|
||||
item.value.hide = !item.value.hide
|
||||
@ -212,6 +220,7 @@ const toggleHide = () => {
|
||||
const showHideIcon = computed(() => {
|
||||
return ['table-info', 'table-normal'].includes(props.chart.type) && item.value.hide
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getItemTagType()
|
||||
})
|
||||
@ -226,17 +235,17 @@ onMounted(() => {
|
||||
:style="{ backgroundColor: tagType + '0a', border: '1px solid ' + tagType }"
|
||||
>
|
||||
<span v-if="type !== 'extColor'" style="display: flex; color: #646a73">
|
||||
<el-icon v-if="'asc' === item.sort && showSort()">
|
||||
<el-icon v-if="'asc' === item.sort && showSort">
|
||||
<Icon name="icon_sort-a-to-z_outlined"
|
||||
><icon_sortAToZ_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</el-icon>
|
||||
<el-icon v-if="'desc' === item.sort && showSort()">
|
||||
<el-icon v-if="'desc' === item.sort && showSort">
|
||||
<Icon name="icon_sort-z-to-a_outlined"
|
||||
><icon_sortZToA_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</el-icon>
|
||||
<el-icon v-if="'custom_sort' === item.sort && showSort()">
|
||||
<el-icon v-if="'custom_sort' === item.sort && showSort">
|
||||
<Icon name="icon_sort_outlined"><icon_sort_outlined class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
<el-icon>
|
||||
@ -283,7 +292,7 @@ onMounted(() => {
|
||||
class="item-span-style"
|
||||
:class="{
|
||||
'hidden-status': showHideIcon,
|
||||
'sort-status': showSort() && item.sort !== 'none'
|
||||
'sort-status': showSort && item.sort !== 'none'
|
||||
}"
|
||||
>
|
||||
<span class="item-name">{{ item.chartShowName ? item.chartShowName : item.name }}</span>
|
||||
@ -312,7 +321,7 @@ onMounted(() => {
|
||||
class="drop-style"
|
||||
:class="themes === 'dark' ? 'dark-dimension-quota' : ''"
|
||||
>
|
||||
<el-dropdown-item @click.prevent v-if="showSort()">
|
||||
<el-dropdown-item @click.prevent v-if="showSort">
|
||||
<el-dropdown
|
||||
:effect="themes"
|
||||
popper-class="data-dropdown_popper_mr9"
|
||||
@ -398,14 +407,14 @@ onMounted(() => {
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-dropdown-item>
|
||||
<!-- <el-dropdown-item
|
||||
v-if="showSort()"
|
||||
<el-dropdown-item
|
||||
v-if="showSortPriority"
|
||||
:command="beforeClickItem('sortPriority')"
|
||||
class="menu-item-padding"
|
||||
>
|
||||
<el-icon />
|
||||
<span>{{ t('chart.sort_priority') }}</span>
|
||||
</el-dropdown-item> -->
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.prevent
|
||||
v-if="item.deType === 1"
|
||||
@ -531,6 +540,25 @@ onMounted(() => {
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
class="menu-item-padding"
|
||||
:command="beforeDateStyle('y_M_d_H')"
|
||||
:divided="
|
||||
chart.type.includes('bar-range') && ['quota', 'quotaExt'].includes(type)
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="sub-menu-content"
|
||||
:class="'y_M_d_H' === item.dateStyle ? 'content-active' : ''"
|
||||
>
|
||||
{{ t('chart.y_M_d_H') }}
|
||||
<el-icon class="sub-menu-content--icon">
|
||||
<Icon name="icon_done_outlined" v-if="'y_M_d_H' === item.dateStyle"
|
||||
><icon_done_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
class="menu-item-padding"
|
||||
:command="beforeDateStyle('y_M_d_H_m')"
|
||||
@ -817,9 +845,17 @@ span {
|
||||
background-color: rgba(31, 35, 41, 0.1);
|
||||
}
|
||||
&.dark-dimension-quota {
|
||||
background-color: #292929;
|
||||
border: 1px solid #434343;
|
||||
:deep(.ed-dropdown-menu__item--divided) {
|
||||
border-color: #ebebeb26;
|
||||
}
|
||||
.inner-dropdown-menu {
|
||||
color: rgba(235, 235, 235, 1);
|
||||
}
|
||||
:deep(.ed-dropdown-menu__item:not(.is-disabled):hover) {
|
||||
background-color: #ebebeb1a;
|
||||
}
|
||||
:deep(.ed-dropdown-menu__item) {
|
||||
color: rgba(235, 235, 235, 1);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import icon_sort_outlined from '@/assets/svg/icon_sort_outlined.svg'
|
||||
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import icon_sort_priority from '@/assets/svg/icon_sort_priority.svg'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { onMounted, ref, toRefs, watch } from 'vue'
|
||||
import { getItemType } from '@/views/chart/components/editor/drag-item/utils'
|
||||
@ -165,7 +164,7 @@ onMounted(() => {
|
||||
></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<el-tooltip :effect="themes === 'dark' ? 'ndark' : 'dark'" placement="top">
|
||||
<el-tooltip :effect="themes || 'dark'" placement="top">
|
||||
<template #content>
|
||||
<table>
|
||||
<tbody>
|
||||
@ -450,9 +449,18 @@ span {
|
||||
background-color: rgba(31, 35, 41, 0.1);
|
||||
}
|
||||
&.dark-dimension-quota {
|
||||
background-color: #292929;
|
||||
border: 1px solid #434343;
|
||||
:deep(.ed-dropdown-menu__item--divided) {
|
||||
border-color: #ebebeb26;
|
||||
}
|
||||
.inner-dropdown-menu {
|
||||
color: rgba(235, 235, 235, 1);
|
||||
}
|
||||
:deep(.ed-dropdown-menu__item:not(.is-disabled):hover) {
|
||||
background-color: #ebebeb1a;
|
||||
}
|
||||
|
||||
:deep(.ed-dropdown-menu__item) {
|
||||
color: rgba(235, 235, 235, 1);
|
||||
}
|
||||
@ -531,6 +539,7 @@ span {
|
||||
}
|
||||
}
|
||||
.dark-dimension-quota {
|
||||
background-color: #292929;
|
||||
span {
|
||||
color: #ebebeb;
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ const emit = defineEmits([
|
||||
|
||||
const { item, chart } = toRefs(props)
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes || 'dark'
|
||||
})
|
||||
watch(
|
||||
[() => props.quotaData, () => props.item, () => props.chart.type],
|
||||
@ -210,11 +210,6 @@ const beforeSort = type => {
|
||||
}
|
||||
}
|
||||
|
||||
const switchChartType = param => {
|
||||
item.value.chartType = param.type
|
||||
emit('onQuotaItemChange', item.value)
|
||||
}
|
||||
|
||||
const summary = param => {
|
||||
item.value.summary = param.type
|
||||
emit('onQuotaItemChange', item.value)
|
||||
@ -226,12 +221,6 @@ const beforeSummary = type => {
|
||||
}
|
||||
}
|
||||
|
||||
const beforeSwitchType = type => {
|
||||
return {
|
||||
type: type
|
||||
}
|
||||
}
|
||||
|
||||
const showRename = () => {
|
||||
item.value.index = props.index
|
||||
item.value.renameType = props.type
|
||||
@ -315,14 +304,23 @@ const showHideIcon = computed(() => {
|
||||
return ['tale-info', 'table-normal'].includes(props.chart.type) && item.value.hide
|
||||
})
|
||||
|
||||
const NOT_SUPPORT_SORT = [
|
||||
'circle-packing',
|
||||
'indicator',
|
||||
'liquid',
|
||||
'gauge',
|
||||
'word-cloud',
|
||||
'stock-line',
|
||||
'treemap'
|
||||
]
|
||||
|
||||
const showSort = computed(() => {
|
||||
return (
|
||||
props.type !== 'extLabel' &&
|
||||
props.type !== 'extTooltip' &&
|
||||
props.type !== 'extBubble' &&
|
||||
!['chart-mix', 'indicator', 'liquid', 'gauge', 'word-cloud', 'stock-line'].includes(
|
||||
chart.value.type
|
||||
)
|
||||
!NOT_SUPPORT_SORT.includes(chart.value.type) &&
|
||||
!chart.value.type.includes('chart-mix')
|
||||
)
|
||||
})
|
||||
|
||||
@ -613,7 +611,9 @@ onMounted(() => {
|
||||
<!--同比/环比等快速计算-->
|
||||
<el-dropdown-item
|
||||
@click.prevent
|
||||
v-if="chart.type !== 'table-info' && props.type !== 'extBubble'"
|
||||
v-if="
|
||||
!['table-info', 'bullet-graph'].includes(chart.type) && props.type !== 'extBubble'
|
||||
"
|
||||
>
|
||||
<el-dropdown
|
||||
placement="right-start"
|
||||
@ -661,6 +661,7 @@ onMounted(() => {
|
||||
class="menu-item-padding"
|
||||
:disabled="state.disableEditCompare"
|
||||
:command="beforeQuickCalc('setting')"
|
||||
v-if="!(chart.type.includes('chart-mix') && type === 'quotaExt')"
|
||||
>
|
||||
<div
|
||||
class="sub-menu-content"
|
||||
@ -790,14 +791,14 @@ onMounted(() => {
|
||||
</el-dropdown>
|
||||
</el-dropdown-item>
|
||||
|
||||
<!-- <el-dropdown-item
|
||||
<el-dropdown-item
|
||||
v-if="showSort"
|
||||
class="menu-item-padding"
|
||||
:command="beforeClickItem('sortPriority')"
|
||||
>
|
||||
<el-icon />
|
||||
<span>{{ t('chart.sort_priority') }}</span>
|
||||
</el-dropdown-item> -->
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item
|
||||
class="menu-item-padding"
|
||||
@ -998,6 +999,14 @@ span {
|
||||
background-color: rgba(31, 35, 41, 0.1);
|
||||
}
|
||||
&.dark-dimension-quota {
|
||||
background-color: #292929;
|
||||
border: 1px solid #434343;
|
||||
:deep(.ed-dropdown-menu__item--divided) {
|
||||
border-color: #ebebeb26;
|
||||
}
|
||||
:deep(.ed-dropdown-menu__item:not(.is-disabled):hover) {
|
||||
background-color: #ebebeb1a;
|
||||
}
|
||||
.inner-dropdown-menu {
|
||||
color: rgba(235, 235, 235, 1);
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ const props = defineProps({
|
||||
fieldType: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
originSortList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: false
|
||||
}
|
||||
})
|
||||
|
||||
@ -54,6 +59,17 @@ const init = () => {
|
||||
reqMethod(param)
|
||||
.then(response => {
|
||||
const strArr = response.data
|
||||
if (props.originSortList?.length) {
|
||||
const tmp = []
|
||||
props.originSortList.forEach(ele => {
|
||||
const index = strArr.findIndex(item => item === ele)
|
||||
if (index !== -1) {
|
||||
tmp.push(strArr[index])
|
||||
strArr.splice(index, 1)
|
||||
}
|
||||
})
|
||||
strArr.unshift(...tmp)
|
||||
}
|
||||
state.sortList = strArr.map(ele => {
|
||||
return transStr2Obj(ele)
|
||||
})
|
||||
|
@ -1,7 +1,14 @@
|
||||
<script lang="tsx" setup>
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { reactive, toRefs } from 'vue'
|
||||
import { formatterType, unitType, valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
import {
|
||||
isEnLocal,
|
||||
formatterType,
|
||||
getUnitTypeList,
|
||||
onChangeFormatCfgUnitLanguage,
|
||||
valueFormatter,
|
||||
initFormatCfgUnit
|
||||
} from '@/views/chart/components/js/formatter'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -20,13 +27,19 @@ const { formatterItem } = toRefs(props)
|
||||
|
||||
const state = reactive({
|
||||
typeList: formatterType,
|
||||
unitList: unitType,
|
||||
exampleResult: '20000000'
|
||||
})
|
||||
|
||||
function changeUnitLanguage(cfg: BaseFormatter, lang) {
|
||||
onChangeFormatCfgUnitLanguage(cfg, lang)
|
||||
getExampleValue()
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
if (!formatterItem.value.formatterCfg) {
|
||||
formatterItem.value.formatterCfg = formatterItem
|
||||
|
||||
initFormatCfgUnit(formatterItem.value.formatterCfg)
|
||||
}
|
||||
}
|
||||
const getExampleValue = () => {
|
||||
@ -66,24 +79,39 @@ getExampleValue()
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="formatterItem.formatterCfg.type !== 'percent'"
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
>
|
||||
<el-select
|
||||
v-model="formatterItem.formatterCfg.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
@change="getExampleValue"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in state.unitList"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="formatterItem.formatterCfg.type !== 'percent'">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item :label="t('chart.value_formatter_unit_language')">
|
||||
<el-select
|
||||
v-model="formatterItem.formatterCfg.unitLanguage"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
@change="v => changeUnitLanguage(formatterItem.formatterCfg, v)"
|
||||
>
|
||||
<el-option :label="t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item :label="t('chart.value_formatter_unit')">
|
||||
<el-select
|
||||
v-model="formatterItem.formatterCfg.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
@change="getExampleValue"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(formatterItem.formatterCfg.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item :label="t('chart.value_formatter_suffix')">
|
||||
<el-input
|
||||
|
@ -22,6 +22,7 @@ export function getItemType(dimensionData, quotaData, item) {
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
ele.id === item.id &&
|
||||
ele.originName === item.originName &&
|
||||
ele.deType === item.deType &&
|
||||
ele.groupType === item.groupType
|
||||
|
@ -36,7 +36,7 @@ import { Icon } from 'vant'
|
||||
import CommonEvent from '@/custom-component/common/CommonEvent.vue'
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
|
||||
const { nowPanelTrackInfo, nowPanelJumpInfo, dvInfo, componentData, curComponent, batchOptStatus } =
|
||||
const { nowPanelTrackInfo, nowPanelJumpInfo, dvInfo, curComponent, batchOptStatus } =
|
||||
storeToRefs(dvMainStore)
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -223,7 +223,7 @@ const appStore = useAppStoreWithOut()
|
||||
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
|
||||
|
||||
const removeLinkageSenior = () => {
|
||||
removeLinkage({ dvId: dvInfo.value.id, sourceViewId: chart.value.id }).then(rsp => {
|
||||
removeLinkage({ dvId: dvInfo.value.id, sourceViewId: chart.value.id }).then(() => {
|
||||
// 刷新联动信息
|
||||
getPanelAllLinkageInfo(dvInfo.value.id).then(rsp => {
|
||||
dvMainStore.setNowPanelTrackInfo(rsp.data)
|
||||
@ -232,7 +232,7 @@ const removeLinkageSenior = () => {
|
||||
}
|
||||
|
||||
const removeJumpSenior = () => {
|
||||
removeJumpSet({ sourceDvId: dvInfo.value.id, sourceViewId: chart.value.id }).then(rspCur => {
|
||||
removeJumpSet({ sourceDvId: dvInfo.value.id, sourceViewId: chart.value.id }).then(() => {
|
||||
// 刷新跳转信息
|
||||
queryVisualizationJumpInfo(dvInfo.value.id).then(rsp => {
|
||||
dvMainStore.setNowPanelJumpInfo(rsp.data)
|
||||
@ -334,6 +334,7 @@ const removeJumpSenior = () => {
|
||||
:chart="chart"
|
||||
:themes="themes"
|
||||
:is-screen="dvInfo.type === 'dataV'"
|
||||
:resource-table="'snapshot'"
|
||||
jsname="L2NvbXBvbmVudC90aHJlc2hvbGQtd2FybmluZy9TZW5pb3JIYW5kbGVy"
|
||||
/>
|
||||
|
||||
@ -524,6 +525,9 @@ span {
|
||||
:deep(.ed-form-item__label) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
:deep(.style-collapse) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.label-dark {
|
||||
@ -576,3 +580,11 @@ span {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.senior-dark {
|
||||
.label-dark {
|
||||
color: #a6a6a6 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -224,7 +224,7 @@ onMounted(() => {
|
||||
<template #header>
|
||||
<div class="assist-line-cfg-header">
|
||||
<span class="ed-dialog__title">{{ t('chart.assist_line') }}</span>
|
||||
<el-tooltip class="item" effect="ndark" placement="top">
|
||||
<el-tooltip class="item" effect="light" placement="top">
|
||||
<template #content>
|
||||
<span> {{ t('chart.assist_line_tip') }}</span>
|
||||
</template>
|
||||
@ -392,12 +392,4 @@ span {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
}
|
||||
.ed-button{
|
||||
color: #F2F4F5;
|
||||
background-color: #212121;
|
||||
border: 1px solid #434343;
|
||||
}
|
||||
.ed-button--primary{
|
||||
background-color:#0089FF;
|
||||
}
|
||||
</style>
|
||||
|
@ -799,7 +799,7 @@ init()
|
||||
<span
|
||||
class="set-text-info"
|
||||
:class="{ 'set-text-info-dark': themes === 'dark' }"
|
||||
v-if="state.thresholdForm?.tableThreshold?.length > 0"
|
||||
v-if="state.thresholdForm?.lineThresholdArr?.length > 0"
|
||||
>
|
||||
$t('visualization.already_setting')
|
||||
</span>
|
||||
@ -1143,7 +1143,7 @@ init()
|
||||
/>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeLineThreshold">{{ t('chart.cancel') }}</el-button>
|
||||
<el-button @click="closeLineThreshold">{{ t('chart.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="changeLineThreshold">{{
|
||||
t('chart.confirm')
|
||||
}}</el-button>
|
||||
@ -1347,13 +1347,4 @@ span {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
.dialog-footer .ed-button{
|
||||
color: #F2F4F5;
|
||||
background-color: #212121;
|
||||
border: 1px solid #434343;
|
||||
}
|
||||
.dialog-footer .ed-button--primary{
|
||||
background-color:#0089FF;
|
||||
border: 1px solid #0089FF;
|
||||
}
|
||||
</style>
|
||||
|
@ -269,6 +269,7 @@ onMounted(() => {
|
||||
<el-option key="avg" value="avg" label="平均值" />
|
||||
<el-option key="max" value="max" :label="t('chart.max')" />
|
||||
<el-option key="min" value="min" :label="t('chart.min')" />
|
||||
<el-option key="last_item" value="last_item" :label="t('chart.last_item')" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="useQuotaExt ? 2 : 3">
|
||||
@ -298,10 +299,10 @@ onMounted(() => {
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="2" style="text-align: center">
|
||||
<el-col :span="2">
|
||||
<el-color-picker
|
||||
is-custom
|
||||
size="large"
|
||||
:trigger-width="60"
|
||||
v-model="item.color"
|
||||
class="color-picker-style"
|
||||
:predefine="state.predefineColors"
|
||||
@ -377,45 +378,4 @@ span {
|
||||
padding: 0 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
:deep(.ed-input__wrapper){
|
||||
background-color: #292929 ;
|
||||
box-shadow: none;
|
||||
border: 1px solid #636363;
|
||||
}
|
||||
:deep(.ed-input__inner){
|
||||
color: #fff;
|
||||
}
|
||||
:deep(.ed-input-number__decrease){
|
||||
background-color: #434343 !important;
|
||||
color:#ffffff;
|
||||
}
|
||||
:deep(.ed-input-number__increase){
|
||||
background-color: #434343 !important;
|
||||
color:#ffffff !important;
|
||||
}
|
||||
:deep(.ed-input-number.is-controls-right .ed-input-number__increase){
|
||||
border-bottom: 1px solid #434343 !important;
|
||||
border-left: 1px solid #5f5f5f !important;
|
||||
}
|
||||
:deep(.ed-input-number.is-controls-right .ed-input-number__decrease){
|
||||
border-left: 1px solid #5f5f5f !important;
|
||||
}
|
||||
:deep(.ed-select-dropdown__item){
|
||||
color: #ffffff !important;
|
||||
}
|
||||
:deep(.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover){
|
||||
background: rgb(61,61,61) !important;
|
||||
}
|
||||
.ed-select-dropdown__item{
|
||||
color: #ffffff;
|
||||
}
|
||||
.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover{
|
||||
background: rgb(61,61,61);
|
||||
}
|
||||
.ed-select-dropdown__item.selected{
|
||||
color: #0089ff;
|
||||
}
|
||||
:deep(.ed-color-picker__trigger){
|
||||
border: 1px solid #5f5f5f ;
|
||||
}
|
||||
</style>
|
||||
|
@ -269,7 +269,11 @@ init()
|
||||
>
|
||||
<el-row style="margin-top: 6px; align-items: center; justify-content: space-between">
|
||||
<el-form-item class="form-item">
|
||||
<el-select v-model="fieldItem.fieldId" @change="addField(fieldItem)">
|
||||
<el-select
|
||||
style="width: 181px"
|
||||
v-model="fieldItem.fieldId"
|
||||
@change="addField(fieldItem)"
|
||||
>
|
||||
<el-option
|
||||
class="series-select-option"
|
||||
v-for="fieldOption in state.fields"
|
||||
@ -424,7 +428,7 @@ init()
|
||||
>
|
||||
<el-color-picker
|
||||
is-custom
|
||||
size="large"
|
||||
:trigger-width="60"
|
||||
v-model="item.color"
|
||||
show-alpha
|
||||
class="color-picker-style"
|
||||
@ -452,7 +456,7 @@ init()
|
||||
</el-row>
|
||||
|
||||
<el-button
|
||||
style="margin-top: 10px;"
|
||||
style="margin-top: 10px"
|
||||
class="circle-button"
|
||||
type="primary"
|
||||
text
|
||||
@ -604,7 +608,7 @@ span {
|
||||
:deep(.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover){
|
||||
background: rgb(61,61,61) !important;
|
||||
}
|
||||
.ed-select-dropdown__item{
|
||||
.ed-select-dropdown__item{
|
||||
color: #ffffff;
|
||||
}
|
||||
.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover{
|
||||
|
@ -4,7 +4,6 @@ import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.s
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import { PropType, reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL } from '../../../util/chart'
|
||||
import { fieldType } from '@/utils/attr'
|
||||
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||
import PictureItem from '@/custom-component/picture-group/PictureItem.vue'
|
||||
@ -201,7 +200,6 @@ const valueOptions = [
|
||||
]
|
||||
}
|
||||
]
|
||||
const predefineColors = COLOR_PANEL
|
||||
|
||||
const state = reactive({
|
||||
thresholdArr: [] as TableThreshold[],
|
||||
@ -303,7 +301,11 @@ init()
|
||||
>
|
||||
<el-row style="margin-top: 6px; align-items: center; justify-content: space-between">
|
||||
<el-form-item class="form-item">
|
||||
<el-select v-model="fieldItem.fieldId" @change="addField(fieldItem)">
|
||||
<el-select
|
||||
style="width: 181px"
|
||||
v-model="fieldItem.fieldId"
|
||||
@change="addField(fieldItem)"
|
||||
>
|
||||
<el-option
|
||||
class="series-select-option"
|
||||
v-for="fieldOption in state.fields"
|
||||
@ -334,7 +336,7 @@ init()
|
||||
:style="{ float: 'right' }"
|
||||
@click="removeThreshold(fieldIndex)"
|
||||
>
|
||||
<el-icon size="20px" style="color: #979797">
|
||||
<el-icon size="20px" style="color: #646a73">
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
@ -342,7 +344,7 @@ init()
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-row :style="{ marginTop: '16px', borderTop: '1px solid #5f5f5f' }">
|
||||
<el-row :style="{ marginTop: '16px', borderTop: '1px solid #d5d6d8' }">
|
||||
<el-row
|
||||
v-for="(item, index) in fieldItem.conditions"
|
||||
:key="index"
|
||||
@ -437,6 +439,7 @@ init()
|
||||
<el-select
|
||||
v-model="item.url"
|
||||
@change="changeThreshold"
|
||||
style="width: 181px"
|
||||
popper-class="picture-group-select"
|
||||
>
|
||||
<template v-if="item.url" #prefix>
|
||||
@ -464,7 +467,7 @@ init()
|
||||
text
|
||||
@click="removeCondition(fieldItem, index)"
|
||||
>
|
||||
<el-icon size="20px" style="color: #979797">
|
||||
<el-icon size="20px" style="color: #646a73">
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
@ -627,7 +630,7 @@ span {
|
||||
:deep(.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover){
|
||||
background: rgb(61,61,61) !important;
|
||||
}
|
||||
.ed-select-dropdown__item{
|
||||
.ed-select-dropdown__item{
|
||||
color: #ffffff;
|
||||
}
|
||||
.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover{
|
||||
|
@ -25,7 +25,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['onTableThresholdChange'])
|
||||
|
||||
const thresholdCondition = {
|
||||
term: 'eq',
|
||||
term: '',
|
||||
field: '0',
|
||||
value: '0',
|
||||
color: '#ff0000ff',
|
||||
@ -373,7 +373,7 @@ const changeConditionItemType = item => {
|
||||
item.dynamicMaxField.summary = 'value'
|
||||
}
|
||||
}
|
||||
const getFieldOptions = fieldItem => {
|
||||
const getFieldOptions = () => {
|
||||
return fieldOptions
|
||||
}
|
||||
|
||||
@ -397,7 +397,11 @@ init()
|
||||
>
|
||||
<el-row style="margin-top: 6px; align-items: center; justify-content: space-between">
|
||||
<el-form-item class="form-item">
|
||||
<el-select v-model="fieldItem.fieldId" @change="addField(fieldItem)">
|
||||
<el-select
|
||||
style="width: 181px"
|
||||
v-model="fieldItem.fieldId"
|
||||
@change="addField(fieldItem)"
|
||||
>
|
||||
<el-option
|
||||
class="series-select-option"
|
||||
v-for="fieldOption in state.fields"
|
||||
@ -428,7 +432,7 @@ init()
|
||||
:style="{ float: 'right' }"
|
||||
@click="removeThreshold(fieldIndex)"
|
||||
>
|
||||
<el-icon size="20px" style="color: #979797">
|
||||
<el-icon size="20px" style="color: #646a73">
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
@ -436,7 +440,7 @@ init()
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-row :style="{ marginTop: '16px', borderTop: '1px solid #5f5f5f !important' }">
|
||||
<el-row :style="{ marginTop: '16px', borderTop: '1px solid #d5d6d8' }">
|
||||
<el-row
|
||||
v-for="(item, index) in fieldItem.conditions"
|
||||
:key="index"
|
||||
@ -735,9 +739,9 @@ init()
|
||||
<el-form-item class="form-item" :label="t('chart.textColor')">
|
||||
<el-color-picker
|
||||
is-custom
|
||||
size="large"
|
||||
v-model="item.color"
|
||||
show-alpha
|
||||
:trigger-width="68"
|
||||
class="color-picker-style"
|
||||
:predefine="predefineColors"
|
||||
@change="changeThreshold"
|
||||
@ -748,8 +752,9 @@ init()
|
||||
<el-form-item class="form-item" :label="t('chart.backgroundColor')">
|
||||
<el-color-picker
|
||||
is-custom
|
||||
size="large"
|
||||
size="default"
|
||||
v-model="item.backgroundColor"
|
||||
:trigger-width="68"
|
||||
show-alpha
|
||||
class="color-picker-style"
|
||||
:predefine="predefineColors"
|
||||
@ -764,7 +769,7 @@ init()
|
||||
text
|
||||
@click="removeCondition(fieldItem, index)"
|
||||
>
|
||||
<el-icon size="20px" style="color: #979797">
|
||||
<el-icon size="20px" style="color: #646a73">
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
@ -856,18 +861,12 @@ span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.color-picker-style {
|
||||
:deep(.color-picker-style) {
|
||||
cursor: pointer;
|
||||
z-index: 1003;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.color-picker-style :deep(.el-color-picker__trigger) {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.color-title {
|
||||
color: #646a73;
|
||||
font-size: 14px;
|
||||
@ -928,7 +927,7 @@ span {
|
||||
:deep(.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover){
|
||||
background: rgb(61,61,61) !important;
|
||||
}
|
||||
.ed-select-dropdown__item{
|
||||
.ed-select-dropdown__item{
|
||||
color: #ffffff;
|
||||
}
|
||||
.ed-select-dropdown__item.hover, .ed-select-dropdown__item:hover{
|
||||
|
@ -166,11 +166,11 @@ init()
|
||||
</el-form-item>
|
||||
</el-space>
|
||||
</el-col>
|
||||
<div style="display: flex; align-items: center; justify-content: center; margin-left: 15px">
|
||||
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
|
||||
<div class="color-title">{{ t('chart.textColor') }}</div>
|
||||
<el-color-picker
|
||||
is-custom
|
||||
size="large"
|
||||
:trigger-width="60"
|
||||
v-model="item.color"
|
||||
show-alpha
|
||||
class="color-picker-style"
|
||||
@ -192,14 +192,14 @@ init()
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
|
||||
<el-button
|
||||
class="circle-button circle-buttons"
|
||||
class="circle-button"
|
||||
type="text"
|
||||
circle
|
||||
style="float: right"
|
||||
@click="removeThreshold(index)"
|
||||
>
|
||||
<template #icon>
|
||||
<Icon name="icon_delete-trash_outlined" class=""
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</template>
|
||||
@ -326,4 +326,4 @@ color:#ffffff;
|
||||
.ed-dialog__headerbtn{
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -9,6 +9,7 @@ import YAxisSelector from '@/views/chart/components/editor/editor-style/componen
|
||||
import DualYAxisSelector from '@/views/chart/components/editor/editor-style/components/DualYAxisSelector.vue'
|
||||
import TitleSelector from '@/views/chart/components/editor/editor-style/components/TitleSelector.vue'
|
||||
import LegendSelector from '@/views/chart/components/editor/editor-style/components/LegendSelector.vue'
|
||||
import SummarySelector from '@/views/chart/components/editor/editor-style/components/SummarySelector.vue'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue'
|
||||
@ -29,6 +30,10 @@ import FlowMapLineSelector from '@/views/chart/components/editor/editor-style/co
|
||||
import FlowMapPointSelector from '@/views/chart/components/editor/editor-style/components/FlowMapPointSelector.vue'
|
||||
import CommonBorderSetting from '@/custom-component/common/CommonBorderSetting.vue'
|
||||
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||
import BulletTargetSelector from '@/views/chart/components/editor/editor-style/components/bullet/BulletTargetSelector.vue'
|
||||
import BulletMeasureSelector from '@/views/chart/components/editor/editor-style/components/bullet/BulletMeasureSelector.vue'
|
||||
import BulletRangeSelector from '@/views/chart/components/editor/editor-style/components/bullet/BulletRangeSelector.vue'
|
||||
|
||||
const snapshotStore = snapshotStoreWithOut()
|
||||
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
@ -196,7 +201,7 @@ const onBackgroundChange = (val, prop) => {
|
||||
state.initReady && emit('onBackgroundChange', val, prop)
|
||||
}
|
||||
|
||||
const onActiveChange = val => {
|
||||
const onActiveChange = () => {
|
||||
snapshotStore.recordSnapshotCache('onActiveChange')
|
||||
state.initReady &&
|
||||
emit('onStyleAttrChange', {
|
||||
@ -288,6 +293,40 @@ watch(
|
||||
@onMiscChange="onMiscChange"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
<div v-if="showProperties('bullet-graph-selector')">
|
||||
<el-collapse-item :effect="themes" name="bullet" :title="t('chart.progress_target')">
|
||||
<bullet-target-selector
|
||||
:themes="themes"
|
||||
:chart="chart"
|
||||
:selector-type="'target'"
|
||||
@onBasicStyleChange="onBasicStyleChange"
|
||||
@onMiscChange="onMiscChange"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item :effect="themes" name="measure" :title="t('chart.progress_current')">
|
||||
<bullet-measure-selector
|
||||
:themes="themes"
|
||||
:chart="chart"
|
||||
:selector-type="'measure'"
|
||||
@onBasicStyleChange="onBasicStyleChange"
|
||||
@onMiscChange="onMiscChange"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
style="margin-bottom: 0 !important"
|
||||
:effect="themes"
|
||||
name="range"
|
||||
:title="t('chart.range_bg')"
|
||||
>
|
||||
<bullet-range-selector
|
||||
:themes="themes"
|
||||
:chart="chart"
|
||||
:selector-type="'range'"
|
||||
@onBasicStyleChange="onBasicStyleChange"
|
||||
@onMiscChange="onMiscChange"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</div>
|
||||
<collapse-switch-item
|
||||
:themes="themes"
|
||||
v-model="chart.customStyle.text.show"
|
||||
@ -450,12 +489,8 @@ watch(
|
||||
@onLabelChange="onLabelChange"
|
||||
/>
|
||||
</collapse-switch-item>
|
||||
<!-- tooltip 为鼠标悬停 移动端table看不到效果 不再单独配置 -->
|
||||
<collapse-switch-item
|
||||
v-if="
|
||||
showProperties('tooltip-selector') &&
|
||||
(!mobileInPc || (mobileInPc && chart.type.indexOf('table') === -1))
|
||||
"
|
||||
v-if="showProperties('tooltip-selector')"
|
||||
v-model="chart.customAttr.tooltip.show"
|
||||
:themes="themes"
|
||||
:change-model="chart.customAttr.tooltip"
|
||||
@ -616,6 +651,23 @@ watch(
|
||||
@onChangeYAxisExtForm="onChangeYAxisExtForm"
|
||||
/>
|
||||
</collapse-switch-item>
|
||||
|
||||
<collapse-switch-item
|
||||
:themes="themes"
|
||||
v-if="showProperties('summary-selector')"
|
||||
v-model="chart.customAttr.basicStyle.showSummary"
|
||||
:change-model="chart.customAttr.basicStyle"
|
||||
@modelChange="val => onBasicStyleChange({ data: val }, 'showSummary')"
|
||||
:title="t('chart.table_summary')"
|
||||
name="summary"
|
||||
>
|
||||
<summary-selector
|
||||
:property-inner="propertyInnerAll['summary-selector']"
|
||||
:themes="themes"
|
||||
:chart="chart"
|
||||
@onBasicStyleChange="onBasicStyleChange"
|
||||
/>
|
||||
</collapse-switch-item>
|
||||
</el-collapse>
|
||||
</el-row>
|
||||
</div>
|
||||
@ -671,6 +723,6 @@ span {
|
||||
}
|
||||
|
||||
:deep(.ed-collapse-item__content) {
|
||||
padding: 16px 8px 0 8px !important;
|
||||
padding: 16px 8px 8px 8px !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -62,7 +62,7 @@ const state = reactive({
|
||||
quotaData: []
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
themes?: EditorTheme
|
||||
}>(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import icon_bold_outlined from '@/assets/svg/icon_bold_outlined.svg'
|
||||
import { uploadFileResult } from '@/api/staticResource'
|
||||
import icon_italic_outlined from '@/assets/svg/icon_italic_outlined.svg'
|
||||
@ -15,6 +15,7 @@ import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import BackgroundOverallCommon from '@/components/visualization/component-background/BackgroundOverallCommon.vue'
|
||||
import { isDashboard, isMainCanvas } from '@/utils/canvasUtils'
|
||||
const { t } = useI18n()
|
||||
const styleActiveNames = ref(['basicStyle'])
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
@ -40,7 +41,7 @@ const props = defineProps({
|
||||
})
|
||||
const { chart, commonBackgroundPop, element } = toRefs(props)
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const predefineColors = COLOR_PANEL
|
||||
const fontSizeList = []
|
||||
@ -117,6 +118,47 @@ const currentSearch = ref({
|
||||
queryConditionWidth: 227
|
||||
})
|
||||
|
||||
const onFreezeChange = () => {
|
||||
if (element.value.freeze) {
|
||||
let historyFreezeCount = 0
|
||||
dvMainStore.componentData.forEach(item => {
|
||||
if (item.innerType === 'VQuery' && item.id !== element.value.id && item.freeze) {
|
||||
historyFreezeCount++
|
||||
}
|
||||
})
|
||||
if (historyFreezeCount) {
|
||||
ElMessageBox.confirm(t('visualization.filter_freeze_tips'), {
|
||||
confirmButtonType: 'primary',
|
||||
type: 'warning',
|
||||
confirmButtonText: t('common.sure'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
autofocus: false,
|
||||
showClose: false
|
||||
})
|
||||
.then(() => {
|
||||
dvMainStore.componentData.forEach(item => {
|
||||
if (item.innerType === 'VQuery' && item.id !== element.value.id && item.freeze) {
|
||||
item.freeze = false
|
||||
}
|
||||
})
|
||||
snapshotStore.recordSnapshotCache('onFreezeChange')
|
||||
})
|
||||
.catch(() => {
|
||||
element.value.freeze = false
|
||||
})
|
||||
} else {
|
||||
dvMainStore.componentData.forEach(item => {
|
||||
if (item.innerType === 'VQuery' && item.id !== element.value.id && item.freeze) {
|
||||
item.freeze = false
|
||||
}
|
||||
})
|
||||
snapshotStore.recordSnapshotCache('onFreezeChange')
|
||||
}
|
||||
} else {
|
||||
snapshotStore.recordSnapshotCache('onFreezeChange')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCurrentPlaceholder = val => {
|
||||
const obj = props.element.propValue.find(ele => {
|
||||
return ele.id === val
|
||||
@ -158,7 +200,7 @@ onMounted(() => {
|
||||
const reUpload = e => {
|
||||
const file = e.target.files[0]
|
||||
if (file.size > 15000000) {
|
||||
ElMessage.success('图片大小不符合')
|
||||
ElMessage.success('图片大小不能超过15M')
|
||||
return
|
||||
}
|
||||
uploadFileResult(file, fileUrl => {
|
||||
@ -194,6 +236,12 @@ const checkItalic = type => {
|
||||
chart.value.customStyle.component[type] = chart.value.customStyle.component[type] ? '' : 'italic'
|
||||
}
|
||||
const initParams = () => {
|
||||
if (!chart.value.customStyle.component.hasOwnProperty('queryConditionHeight')) {
|
||||
chart.value.customStyle.component = {
|
||||
...chart.value.customStyle.component,
|
||||
queryConditionHeight: 32
|
||||
}
|
||||
}
|
||||
if (!chart.value.customStyle.component.hasOwnProperty('labelShow')) {
|
||||
chart.value.customStyle.component = {
|
||||
...chart.value.customStyle.component,
|
||||
@ -251,7 +299,7 @@ const onTitleChange = () => {
|
||||
<el-row class="de-collapse-style">
|
||||
<el-collapse v-model="styleActiveNames" class="style-collapse">
|
||||
<el-collapse-item :effect="themes" name="basicStyle" :title="t('chart.basic_style')">
|
||||
<el-form @keydown.stop.prevent.enter label-position="top">
|
||||
<el-form size="small" @keydown.stop.prevent.enter label-position="top">
|
||||
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
:effect="themes"
|
||||
@ -289,6 +337,26 @@ const onTitleChange = () => {
|
||||
:predefine="COLOR_PANEL"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="!mobileInPc && isDashboard() && isMainCanvas(element.canvasId)"
|
||||
class="form-item margin-bottom-8"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('visualization.query_position')"
|
||||
>
|
||||
<el-radio-group
|
||||
v-model="element.freeze"
|
||||
:effect="themes"
|
||||
size="small"
|
||||
@change="onFreezeChange"
|
||||
>
|
||||
<el-radio :effect="themes" style="min-width: 80px" :label="true">{{
|
||||
t('visualization.to_top')
|
||||
}}</el-radio>
|
||||
<el-radio :effect="themes" style="min-width: 80px" :label="false">{{
|
||||
t('visualization.default')
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<background-overall-common
|
||||
:common-background-pop="commonBackgroundPop"
|
||||
:themes="themes"
|
||||
@ -298,7 +366,12 @@ const onTitleChange = () => {
|
||||
</el-form>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item :effect="themes" name="addition" :title="t('v_query.query_condition')">
|
||||
<el-form @keydown.stop.prevent.enter label-position="top" style="padding-bottom: 8px">
|
||||
<el-form
|
||||
size="small"
|
||||
@keydown.stop.prevent.enter
|
||||
label-position="top"
|
||||
style="padding-bottom: 8px"
|
||||
>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
@ -346,6 +419,19 @@ const onTitleChange = () => {
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:effect="themes"
|
||||
class="form-item"
|
||||
:label="t('visualization.query_condition_height')"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="chart.customStyle.component.queryConditionHeight"
|
||||
:min="32"
|
||||
:effect="themes"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
:effect="themes"
|
||||
@ -366,6 +452,7 @@ const onTitleChange = () => {
|
||||
<el-color-picker
|
||||
:effect="themes"
|
||||
:trigger-width="56"
|
||||
style="max-width: 56px; min-width: 56px"
|
||||
is-custom
|
||||
show-alpha
|
||||
v-model="chart.customStyle.component.text"
|
||||
@ -423,6 +510,7 @@ const onTitleChange = () => {
|
||||
>
|
||||
<el-input-number
|
||||
:effect="themes"
|
||||
:min="100"
|
||||
controls-position="right"
|
||||
@change="handleCurrentPlaceholderChange"
|
||||
:disabled="!chart.customStyle.component.placeholderShow || !currentPlaceholder"
|
||||
@ -441,6 +529,7 @@ const onTitleChange = () => {
|
||||
:class="!chart.customStyle.component.labelShow && 'is-disabled'"
|
||||
:disabled="!chart.customStyle.component.labelShow"
|
||||
label-position="top"
|
||||
size="small"
|
||||
style="padding-bottom: 8px"
|
||||
>
|
||||
<el-form-item
|
||||
@ -467,6 +556,7 @@ const onTitleChange = () => {
|
||||
:effect="themes"
|
||||
is-custom
|
||||
show-alpha
|
||||
style="width: 50px"
|
||||
v-model="chart.customStyle.component.labelColor"
|
||||
:predefine="predefineColors"
|
||||
/><el-tooltip
|
||||
@ -545,7 +635,12 @@ const onTitleChange = () => {
|
||||
</el-form>
|
||||
</collapse-switch-item>
|
||||
<el-collapse-item :effect="themes" name="button" :title="t('commons.button')">
|
||||
<el-form @keydown.stop.prevent.enter label-position="top" style="padding-bottom: 8px">
|
||||
<el-form
|
||||
size="small"
|
||||
@keydown.stop.prevent.enter
|
||||
label-position="top"
|
||||
style="padding-bottom: 8px"
|
||||
>
|
||||
<el-form-item
|
||||
:effect="themes"
|
||||
class="form-item"
|
||||
|
@ -12,11 +12,22 @@ import { cloneDeep, debounce, defaultsDeep } from 'lodash-es'
|
||||
import { SERIES_NUMBER_FIELD } from '@antv/s2'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { isNumber } from 'mathjs'
|
||||
import { isNumber } from 'lodash-es'
|
||||
import { ElFormItem, ElInputNumber, ElMessage } from 'element-plus-secondary'
|
||||
import { svgStrToUrl } from '../../../js/util'
|
||||
import { numberToChineseUnderHundred } from '../../../js/panel/common/common_antv'
|
||||
import { useLocaleStoreWithOut } from '@/store/modules/locale'
|
||||
import { useMapStoreWithOut } from '@/store/modules/map'
|
||||
import { queryMapKeyApi } from '@/api/setting/sysParameter'
|
||||
import {
|
||||
gaodeMapStyleOptions,
|
||||
qqMapStyleOptions,
|
||||
tdtMapStyleOptions
|
||||
} from '@/views/chart/components/js/panel/charts/map/common'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const localeStore = useLocaleStoreWithOut()
|
||||
const { batchOptStatus, mobileInPc } = storeToRefs(dvMainStore)
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
@ -32,7 +43,17 @@ const props = defineProps({
|
||||
type: Array<string>
|
||||
}
|
||||
})
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
const showProperty = prop => {
|
||||
const has = props.propertyInner?.includes(prop)
|
||||
if (!has) {
|
||||
return false
|
||||
}
|
||||
if (props.chart.type.includes('map') && mapType.value === 'tianditu' && prop === 'showLabel') {
|
||||
return false
|
||||
}
|
||||
return has
|
||||
}
|
||||
const tableExpandLevelOptions = reactive([{ name: t('chart.expand_all'), value: 'all' }])
|
||||
const predefineColors = COLOR_PANEL
|
||||
const state = reactive({
|
||||
basicStyleForm: JSON.parse(JSON.stringify(DEFAULT_BASIC_STYLE)) as ChartBasicStyle,
|
||||
@ -46,8 +67,8 @@ const state = reactive({
|
||||
fileList: []
|
||||
})
|
||||
const emit = defineEmits(['onBasicStyleChange', 'onMiscChange'])
|
||||
const changeBasicStyle = (prop?: string, requestData = false) => {
|
||||
emit('onBasicStyleChange', { data: state.basicStyleForm, requestData }, prop)
|
||||
const changeBasicStyle = (prop?: string, requestData = false, render = true) => {
|
||||
emit('onBasicStyleChange', { data: state.basicStyleForm, requestData, render }, prop)
|
||||
}
|
||||
const onAlphaChange = v => {
|
||||
const _v = parseInt(v)
|
||||
@ -106,6 +127,28 @@ const init = () => {
|
||||
state.customColor = state.basicStyleForm.colors[0]
|
||||
state.colorIndex = 0
|
||||
}
|
||||
if (basicStyle.tableLayoutMode === 'tree') {
|
||||
tableExpandLevelOptions.splice(1)
|
||||
let maxLevel = props.chart.xAxis?.length
|
||||
if (isNumber(basicStyle.defaultExpandLevel)) {
|
||||
maxLevel = Math.max(maxLevel, basicStyle.defaultExpandLevel)
|
||||
}
|
||||
for (let i = 1; i <= maxLevel; i++) {
|
||||
let name = t('chart.level_label', { num: i })
|
||||
if (localeStore.getCurrentLocale.lang !== 'en') {
|
||||
name = t('chart.level_label', { num: numberToChineseUnderHundred(i) })
|
||||
}
|
||||
tableExpandLevelOptions.push({ name, value: i })
|
||||
}
|
||||
}
|
||||
const lastPageInfo = dvMainStore.getViewPageInfo(props.chart.id)
|
||||
if (lastPageInfo) {
|
||||
if (lastPageInfo.pageSize && lastPageInfo.pageSize !== state.basicStyleForm.tablePageSize) {
|
||||
state.basicStyleForm.tablePageSize = lastPageInfo.pageSize
|
||||
changeBasicStyle('tablePageSize', false, false)
|
||||
return
|
||||
}
|
||||
}
|
||||
initTableColumnWidth()
|
||||
}
|
||||
const debouncedInit = debounce(init, 500)
|
||||
@ -226,6 +269,8 @@ const changeFieldColumnWidth = () => {
|
||||
const pageSizeOptions = [
|
||||
{ name: '10' + t('chart.table_page_size_unit'), value: 10 },
|
||||
{ name: '20' + t('chart.table_page_size_unit'), value: 20 },
|
||||
{ name: '30' + t('chart.table_page_size_unit'), value: 30 },
|
||||
{ name: '40' + t('chart.table_page_size_unit'), value: 40 },
|
||||
{ name: '50' + t('chart.table_page_size_unit'), value: 50 },
|
||||
{ name: '100' + t('chart.table_page_size_unit'), value: 100 }
|
||||
]
|
||||
@ -238,17 +283,34 @@ const symbolOptions = [
|
||||
{ name: t('chart.line_symbol_triangle'), value: 'triangle' },
|
||||
{ name: t('chart.line_symbol_diamond'), value: 'diamond' }
|
||||
]
|
||||
const mapStyleOptions = [
|
||||
{ name: t('chart.map_style_normal'), value: 'normal' },
|
||||
{ name: t('chart.map_style_darkblue'), value: 'darkblue' },
|
||||
{ name: t('chart.map_style_light'), value: 'light' },
|
||||
{ name: t('chart.map_style_dark'), value: 'dark' },
|
||||
{ name: t('chart.map_style_fresh'), value: 'fresh' },
|
||||
{ name: t('chart.map_style_grey'), value: 'grey' },
|
||||
{ name: t('chart.map_style_blue'), value: 'blue' },
|
||||
{ name: '卫星地图', value: 'Satellite' },
|
||||
{ name: t('commons.custom'), value: 'custom' }
|
||||
]
|
||||
|
||||
const mapStore = useMapStoreWithOut()
|
||||
|
||||
const getMapKey = async () => {
|
||||
if (!mapStore.mapKey.key) {
|
||||
await queryMapKeyApi().then(res => mapStore.setKey(res.data))
|
||||
}
|
||||
if (mapStore.mapKey.securityCode) {
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: mapStore.mapKey.securityCode
|
||||
}
|
||||
}
|
||||
return mapStore.mapKey
|
||||
}
|
||||
|
||||
const mapType = ref<string>(undefined)
|
||||
|
||||
const mapStyleOptions = computed(() => {
|
||||
switch (mapType.value) {
|
||||
case 'tianditu':
|
||||
return tdtMapStyleOptions
|
||||
case 'qq':
|
||||
return qqMapStyleOptions
|
||||
default:
|
||||
return gaodeMapStyleOptions
|
||||
}
|
||||
})
|
||||
|
||||
const heatMapTypeOptions = [
|
||||
{ name: t('chart.heatmap_classics'), value: 'heatmap' },
|
||||
{ name: t('chart.heatmap3D'), value: 'heatmap3D' }
|
||||
@ -265,12 +327,51 @@ const mergeCell = computed(() => {
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
const preventInvalidKeydown = event => {
|
||||
const invalidKeys = ['e', 'E', '+', '-', '.']
|
||||
if (invalidKeys.includes(event.key)) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
// 验证输入值
|
||||
const validateInput = (value, field) => {
|
||||
if (value === '') {
|
||||
state.basicStyleForm[field] = 1
|
||||
return
|
||||
}
|
||||
|
||||
let num = parseInt(value, 10)
|
||||
|
||||
if (isNaN(num)) {
|
||||
num = 1
|
||||
} else if (num < 1) {
|
||||
num = 1
|
||||
} else if (num > 100) {
|
||||
num = 100
|
||||
}
|
||||
state.basicStyleForm[field] = num
|
||||
}
|
||||
onMounted(() => {
|
||||
init()
|
||||
getMapKey().then(res => {
|
||||
if (res) {
|
||||
mapType.value = res.mapType
|
||||
}
|
||||
})
|
||||
useEmitt({
|
||||
name: 'chart-type-change',
|
||||
callback: () => {
|
||||
if (['topRoundAngle', 'roundAngle'].includes(state.basicStyleForm.radiusColumnBar)) {
|
||||
state.basicStyleForm.radiusColumnBar = 'roundAngle'
|
||||
changeBasicStyle('radiusColumnBar')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div style="width: 100%">
|
||||
<el-form size="small" style="width: 100%">
|
||||
<template v-if="showProperty('colors')">
|
||||
<custom-color-style-select
|
||||
v-model="state"
|
||||
@ -308,7 +409,53 @@ onMounted(() => {
|
||||
<el-radio label="tree" :effect="themes">{{ t('chart.table_layout_tree') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
v-if="showProperty('tableLayoutMode') && state.basicStyleForm.tableLayoutMode === 'tree'"
|
||||
:label="t('chart.default_expand_level')"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:effect="themes"
|
||||
v-model="state.basicStyleForm.defaultExpandLevel"
|
||||
@change="changeBasicStyle('defaultExpandLevel')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tableExpandLevelOptions"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
v-if="showProperty('quotaPosition')"
|
||||
:label="t('chart.quota_position')"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-radio-group
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.basicStyleForm.quotaPosition"
|
||||
@change="changeBasicStyle('quotaPosition')"
|
||||
>
|
||||
<el-radio label="col" :effect="themes">{{ t('chart.quota_position_col') }}</el-radio>
|
||||
<el-radio label="row" :effect="themes">{{ t('chart.quota_position_row') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="showProperty('quotaColLabel') && state.basicStyleForm.quotaPosition === 'row'"
|
||||
class="form-item"
|
||||
:label="t('chart.quota_col_label')"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
v-model="state.basicStyleForm.quotaColLabel"
|
||||
@change="changeBasicStyle('quotaColLabel')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="alpha-setting" v-if="showProperty('alpha')">
|
||||
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
|
||||
{{ t('chart.not_alpha') }}
|
||||
@ -353,9 +500,16 @@ onMounted(() => {
|
||||
:effect="themes"
|
||||
v-model="state.basicStyleForm.radiusColumnBar"
|
||||
@change="changeBasicStyle('radiusColumnBar')"
|
||||
class="radius-class"
|
||||
>
|
||||
<el-radio label="rightAngle" :effect="themes">{{ t('chart.rightAngle') }}</el-radio>
|
||||
<el-radio label="roundAngle" :effect="themes">{{ t('chart.roundAngle') }}</el-radio>
|
||||
<el-radio
|
||||
v-if="!props.chart.type.includes('-stack')"
|
||||
label="topRoundAngle"
|
||||
:effect="themes"
|
||||
>{{ t('chart.topRoundAngle') }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
@ -434,7 +588,7 @@ onMounted(() => {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="alpha-setting">
|
||||
<div class="alpha-setting" v-if="mapType !== 'tianditu'">
|
||||
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
|
||||
{{ t('chart.chart_map') + ' ' + t('chart.map_pitch') }}
|
||||
</label>
|
||||
@ -726,7 +880,7 @@ onMounted(() => {
|
||||
<el-radio-group
|
||||
:effect="themes"
|
||||
v-model="state.basicStyleForm.tablePageStyle"
|
||||
@change="changeBasicStyle('tablePageStyle', true)"
|
||||
@change="changeBasicStyle('tablePageStyle', false)"
|
||||
>
|
||||
<el-radio :effect="themes" label="simple">{{ t('chart.page_pager_simple') }}</el-radio>
|
||||
<el-radio :effect="themes" label="general">{{ t('chart.page_pager_general') }}</el-radio>
|
||||
@ -824,34 +978,6 @@ onMounted(() => {
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="showProperty('showSummary')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.basicStyleForm.showSummary"
|
||||
@change="changeBasicStyle('showSummary')"
|
||||
>
|
||||
{{ t('chart.table_show_summary') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="showProperty('summaryLabel') && state.basicStyleForm.showSummary"
|
||||
:label="t('chart.table_summary_label')"
|
||||
:class="'form-item-' + themes"
|
||||
class="form-item"
|
||||
>
|
||||
<el-input
|
||||
v-model="state.basicStyleForm.summaryLabel"
|
||||
type="text"
|
||||
:effect="themes"
|
||||
:max-length="10"
|
||||
@blur="changeBasicStyle('summaryLabel')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showProperty('autoWrap')" class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
size="small"
|
||||
@ -1347,7 +1473,9 @@ onMounted(() => {
|
||||
:max="100"
|
||||
class="basic-input-number"
|
||||
:controls="false"
|
||||
@input="validateInput($event, 'innerRadius')"
|
||||
@change="changeBasicStyle('innerRadius')"
|
||||
@keydown="preventInvalidKeydown"
|
||||
>
|
||||
<template #suffix> % </template>
|
||||
</el-input>
|
||||
@ -1382,7 +1510,9 @@ onMounted(() => {
|
||||
:max="100"
|
||||
class="basic-input-number"
|
||||
:controls="false"
|
||||
@input="validateInput($event, 'radius')"
|
||||
@change="changeBasicStyle('radius')"
|
||||
@keydown="preventInvalidKeydown"
|
||||
>
|
||||
<template #suffix> % </template>
|
||||
</el-input>
|
||||
@ -1454,7 +1584,7 @@ onMounted(() => {
|
||||
</el-row>
|
||||
</div>
|
||||
<!-- circle-packing end -->
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
<style scoped lang="less">
|
||||
.color-picker-style {
|
||||
@ -1628,4 +1758,12 @@ onMounted(() => {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.radius-class {
|
||||
:deep(.ed-radio) {
|
||||
margin-right: 30px !important;
|
||||
}
|
||||
.ed-radio:last-child {
|
||||
margin-right: 0px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -234,11 +234,18 @@ const changeColorOption = (option?) => {
|
||||
}
|
||||
}
|
||||
const resetCustomColor = () => {
|
||||
if (props.chart.type.includes('map')) {
|
||||
const { type } = props.chart
|
||||
const { basicStyleForm } = state.value
|
||||
|
||||
if (type.includes('map')) {
|
||||
changeColorOption()
|
||||
} else {
|
||||
state.value.basicStyleForm[seriesColorName.value] = []
|
||||
basicStyleForm[seriesColorName.value] = []
|
||||
changeBasicStyle(seriesColorName.value)
|
||||
const colorScheme = basicStyleForm[colorSchemeName.value]
|
||||
basicStyleForm[colorsName.value] =
|
||||
colorCases.find(ele => ele.value === colorScheme)?.colors ?? colorCases[0].colors
|
||||
changeBasicStyle(colorsName.value)
|
||||
setupSeriesColor()
|
||||
}
|
||||
}
|
||||
@ -300,7 +307,8 @@ const colorItemBorderColor = (index, state) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<el-form
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
:style="{ 'margin-bottom': customColorExtendSettingOpened ? '16px' : 0 }"
|
||||
>
|
||||
@ -510,7 +518,7 @@ const colorItemBorderColor = (index, state) => {
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@ -523,6 +531,7 @@ const colorItemBorderColor = (index, state) => {
|
||||
.custom-color-selector {
|
||||
:deep(.ed-input__prefix) {
|
||||
width: calc(100% - 22px);
|
||||
.ed-input__prefix,
|
||||
.ed-input__prefix-inner {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -1,22 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, PropType, reactive, watch, ref } from 'vue'
|
||||
import {
|
||||
COLOR_PANEL,
|
||||
DEFAULT_BASIC_STYLE,
|
||||
DEFAULT_MISC
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import { DEFAULT_BASIC_STYLE, DEFAULT_MISC } from '@/views/chart/components/editor/util/chart'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import CustomColorStyleSelect from '@/views/chart/components/editor/editor-style/components/CustomColorStyleSelect.vue'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import {
|
||||
CHART_MIX_DEFAULT_BASIC_STYLE,
|
||||
MixChartBasicStyle
|
||||
} from '@/views/chart/components/js/panel/charts/others/chart-mix-common'
|
||||
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { batchOptStatus } = storeToRefs(dvMainStore)
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
chart: {
|
||||
@ -33,7 +25,6 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
const predefineColors = COLOR_PANEL
|
||||
const state = reactive({
|
||||
basicStyleForm: JSON.parse(JSON.stringify(CHART_MIX_DEFAULT_BASIC_STYLE)) as MixChartBasicStyle,
|
||||
miscForm: JSON.parse(JSON.stringify(DEFAULT_MISC)) as ChartMiscAttr,
|
||||
@ -128,6 +119,12 @@ const init = () => {
|
||||
state.customColor = state.basicStyleForm.colors[0]
|
||||
state.colorIndex = 0
|
||||
}
|
||||
if (
|
||||
props.chart.type.includes('-stack') &&
|
||||
state.basicStyleForm.radiusColumnBar === 'topRoundAngle'
|
||||
) {
|
||||
state.basicStyleForm.radiusColumnBar = 'roundAngle'
|
||||
}
|
||||
}
|
||||
const configCompat = (basicStyle: ChartBasicStyle) => {
|
||||
// 悬浮改为图例和缩放按钮
|
||||
@ -149,7 +146,7 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div style="width: 100%">
|
||||
<el-form size="small" style="width: 100%">
|
||||
<el-tabs v-model="activeName" id="axis-tabs" stretch>
|
||||
<el-tab-pane :label="t('chart.yAxisLeft')" name="left">
|
||||
<template v-if="showProperty('colors')">
|
||||
@ -224,9 +221,17 @@ onMounted(() => {
|
||||
:effect="themes"
|
||||
v-model="state.basicStyleForm.radiusColumnBar"
|
||||
@change="changeBasicStyle('radiusColumnBar')"
|
||||
class="radius-class"
|
||||
>
|
||||
<el-radio label="rightAngle" :effect="themes">{{ t('chart.rightAngle') }}</el-radio>
|
||||
<el-radio label="roundAngle" :effect="themes">{{ t('chart.roundAngle') }}</el-radio>
|
||||
<el-radio
|
||||
v-if="!props.chart.type.includes('-stack')"
|
||||
label="topRoundAngle"
|
||||
:effect="themes"
|
||||
>
|
||||
{{ t('chart.topRoundAngle') }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div class="alpha-setting" v-if="showProperty('columnWidthRatio')">
|
||||
@ -462,7 +467,7 @@ onMounted(() => {
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
<style scoped lang="less">
|
||||
.form-item {
|
||||
@ -552,4 +557,12 @@ onMounted(() => {
|
||||
border-top: none !important;
|
||||
}
|
||||
}
|
||||
.radius-class {
|
||||
:deep(.ed-radio) {
|
||||
margin-right: 30px !important;
|
||||
}
|
||||
.ed-radio:last-child {
|
||||
margin-right: 0px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,13 +1,10 @@
|
||||
<script lang="tsx" setup>
|
||||
import { computed, onMounted, PropType, reactive, ref, watch } from 'vue'
|
||||
import { onMounted, PropType, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import {
|
||||
COLOR_PANEL,
|
||||
DEFAULT_YAXIS_EXT_STYLE,
|
||||
DEFAULT_YAXIS_STYLE
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import { formatterType, unitType } from '@/views/chart/components/js/formatter'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import DualYAxisSelectorInner from './DualYAxisSelectorInner.vue'
|
||||
|
||||
|
@ -3,8 +3,14 @@ import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
|
||||
import { computed, onMounted, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_YAXIS_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { formatterType, unitType } from '@/views/chart/components/js/formatter'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import {
|
||||
isEnLocal,
|
||||
formatterType,
|
||||
getUnitTypeList,
|
||||
initFormatCfgUnit,
|
||||
onChangeFormatCfgUnitLanguage
|
||||
} from '@/views/chart/components/js/formatter'
|
||||
import { ElFormItem, ElMessage } from 'element-plus-secondary'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -25,9 +31,8 @@ const props = withDefaults(
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
const typeList = formatterType
|
||||
const unitList = unitType
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const state = reactive({
|
||||
axisForm: JSON.parse(JSON.stringify(DEFAULT_YAXIS_STYLE))
|
||||
@ -77,8 +82,14 @@ const changeAxisStyle = prop => {
|
||||
emit('onChangeYAxisForm', state.axisForm, prop)
|
||||
}
|
||||
|
||||
function changeUnitLanguage(cfg: BaseFormatter, lang, prop: string) {
|
||||
onChangeFormatCfgUnitLanguage(cfg, lang)
|
||||
changeAxisStyle(prop)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
state.axisForm = JSON.parse(JSON.stringify(props.form))
|
||||
initFormatCfgUnit(state.axisForm.axisLabelFormatter)
|
||||
}
|
||||
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
@ -302,6 +313,49 @@ onMounted(() => {
|
||||
{{ t('chart.axis_show') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<div style="padding-left: 22px" v-if="showProperty('axisLine')">
|
||||
<div style="flex: 1; display: flex">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
|
||||
<el-color-picker
|
||||
:disabled="!state.axisForm.axisLine.show"
|
||||
v-model="state.axisForm.axisLine.lineStyle.color"
|
||||
:predefine="predefineColors"
|
||||
:effect="themes"
|
||||
@change="changeAxisStyle('axisLine.lineStyle.color')"
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding: 0 4px">
|
||||
<el-select
|
||||
:disabled="!state.axisForm.axisLine.show"
|
||||
style="width: 62px"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLine.lineStyle.style"
|
||||
@change="changeAxisStyle('axisLine.lineStyle.style')"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in splitLineStyle"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:label="option.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-input-number
|
||||
:disabled="!state.axisForm.axisLine.show"
|
||||
style="width: 70px"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLine.lineStyle.width"
|
||||
:min="1"
|
||||
:max="10"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeAxisStyle('axisLine.lineStyle.width')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item
|
||||
class="form-item form-item-checkbox"
|
||||
:class="{
|
||||
@ -473,52 +527,82 @@ onMounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="
|
||||
state.axisForm.axisLabel.show && state.axisForm.axisLabelFormatter.type !== 'percent'
|
||||
"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
>
|
||||
<el-select
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeAxisStyle('axisLabelFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitList"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unitLanguage"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeUnitLanguage(
|
||||
state.axisForm.axisLabelFormatter,
|
||||
v,
|
||||
'axisLabelFormatter'
|
||||
)
|
||||
"
|
||||
>
|
||||
<el-option :label="t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
>
|
||||
<el-select
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeAxisStyle('axisLabelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(
|
||||
state.axisForm.axisLabelFormatter.unitLanguage
|
||||
)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeAxisStyle('axisLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeAxisStyle('axisLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
|
@ -1,16 +1,14 @@
|
||||
<script lang="tsx" setup>
|
||||
import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { 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,
|
||||
@ -34,9 +32,6 @@ const state = reactive({
|
||||
lineForm: {} as DeepPartial<ChartMiscAttr['flowMapConfig']['lineConfig']>,
|
||||
basicStyleForm: {}
|
||||
})
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
})
|
||||
const emit = defineEmits(['onChangeFlowMapLineForm', 'onBasicStyleChange'])
|
||||
|
||||
watch(
|
||||
|
@ -2,12 +2,10 @@
|
||||
import { computed, nextTick, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_MISC } from '@/views/chart/components/editor/util/chart'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { ElSpace } from 'element-plus-secondary'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const props = defineProps({
|
||||
chart: {
|
||||
type: Object,
|
||||
@ -44,7 +42,7 @@ const state = reactive({
|
||||
}
|
||||
})
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const emit = defineEmits(['onChangeFlowMapPointForm'])
|
||||
|
||||
@ -85,7 +83,6 @@ const init = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="tsx" setup>
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, COLOR_CASES } from '@/views/chart/components/editor/util/chart'
|
||||
import { COLOR_CASES } from '@/views/chart/components/editor/util/chart'
|
||||
import { ElPopover } from 'element-plus-secondary'
|
||||
import { getMapColorCases } from '@/views/chart/components/js/util'
|
||||
|
||||
@ -22,7 +22,6 @@ const props = withDefaults(
|
||||
}
|
||||
)
|
||||
const colorCases = JSON.parse(JSON.stringify(COLOR_CASES))
|
||||
const predefineColors = JSON.parse(JSON.stringify(COLOR_PANEL))
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'selectColorCase'])
|
||||
const state = computed({
|
||||
@ -185,6 +184,7 @@ onMounted(() => {
|
||||
.custom-color-selector {
|
||||
:deep(.ed-input__prefix) {
|
||||
width: calc(100% - 22px);
|
||||
.ed-input__prefix,
|
||||
.ed-input__prefix-inner {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import { PropType, computed, onMounted, reactive, watch, nextTick } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import {
|
||||
COLOR_PANEL,
|
||||
CHART_FONT_FAMILY,
|
||||
CHART_FONT_LETTER_SPACE,
|
||||
DEFAULT_INDICATOR_NAME_STYLE,
|
||||
DEFAULT_BASIC_STYLE,
|
||||
@ -14,14 +13,9 @@ import {
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
import Icon from '@/components/icon-custom/src/Icon.vue'
|
||||
import { hexColorToRGBA } from '@/views/chart/components/js/util'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { batchOptStatus } = storeToRefs(dvMainStore)
|
||||
const appearanceStore = useAppearanceStoreWithOut()
|
||||
|
||||
const props = defineProps({
|
||||
@ -40,7 +34,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['onIndicatorNameChange'])
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const predefineColors = COLOR_PANEL
|
||||
const fontFamily = CHART_FONT_FAMILY_ORIGIN.concat(
|
||||
@ -64,6 +58,12 @@ const fontSizeList = computed(() => {
|
||||
value: i
|
||||
})
|
||||
}
|
||||
for (let i = 70; i <= 210; i += 10) {
|
||||
arr.push({
|
||||
name: i + '',
|
||||
value: i
|
||||
})
|
||||
}
|
||||
return arr
|
||||
})
|
||||
|
||||
@ -119,6 +119,7 @@ defineExpose({ getFormData })
|
||||
:disabled="!state.indicatorNameForm.show"
|
||||
:model="state.indicatorNameForm"
|
||||
label-position="top"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
|
@ -20,12 +20,7 @@ import {
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
import { ElIcon, ElInput } from 'element-plus-secondary'
|
||||
import Icon from '@/components/icon-custom/src/Icon.vue'
|
||||
import { hexColorToRGBA } from '@/views/chart/components/js/util'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { batchOptStatus } = storeToRefs(dvMainStore)
|
||||
const appearanceStore = useAppearanceStoreWithOut()
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -46,7 +41,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['onIndicatorChange', 'onBasicStyleChange'])
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const predefineColors = COLOR_PANEL
|
||||
const fontFamily = CHART_FONT_FAMILY_ORIGIN.concat(
|
||||
@ -61,7 +56,6 @@ const state = reactive({
|
||||
indicatorValueForm: JSON.parse(JSON.stringify(DEFAULT_INDICATOR_STYLE)),
|
||||
basicStyleForm: {} as ChartBasicStyle
|
||||
})
|
||||
|
||||
const fontSizeList = computed(() => {
|
||||
const arr = []
|
||||
for (let i = 10; i <= 60; i = i + 2) {
|
||||
@ -70,6 +64,12 @@ const fontSizeList = computed(() => {
|
||||
value: i
|
||||
})
|
||||
}
|
||||
for (let i = 70; i <= 210; i += 10) {
|
||||
arr.push({
|
||||
name: i + '',
|
||||
value: i
|
||||
})
|
||||
}
|
||||
return arr
|
||||
})
|
||||
|
||||
@ -126,6 +126,7 @@ defineExpose({ getFormData })
|
||||
:disabled="!state.indicatorValueForm.show"
|
||||
:model="state.indicatorValueForm"
|
||||
label-position="top"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
|
@ -4,7 +4,13 @@ import { computed, onMounted, PropType, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart'
|
||||
import { ElFormItem, ElIcon, ElInput, ElSpace } from 'element-plus-secondary'
|
||||
import { formatterType, unitType } from '../../../js/formatter'
|
||||
import {
|
||||
isEnLocal,
|
||||
formatterType,
|
||||
getUnitTypeList,
|
||||
initFormatCfgUnit,
|
||||
onChangeFormatCfgUnitLanguage
|
||||
} from '@/views/chart/components/js/formatter'
|
||||
import { defaultsDeep, cloneDeep, intersection, union, defaultTo, map, isEmpty } from 'lodash-es'
|
||||
import { includesAny } from '../../util/StringUtils'
|
||||
import { fieldType } from '@/utils/attr'
|
||||
@ -42,7 +48,7 @@ const props = defineProps({
|
||||
})
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const { batchOptStatus } = storeToRefs(dvMainStore)
|
||||
watch(
|
||||
@ -152,6 +158,7 @@ const initSeriesLabel = () => {
|
||||
position: 'top'
|
||||
} as SeriesFormatter
|
||||
if (seriesAxisMap[next[computedIdKey.value]]) {
|
||||
initFormatCfgUnit(seriesAxisMap[next[computedIdKey.value]].formatterCfg)
|
||||
tmp = {
|
||||
...tmp,
|
||||
formatterCfg: seriesAxisMap[next[computedIdKey.value]].formatterCfg,
|
||||
@ -253,6 +260,11 @@ const changeLabelAttr = (prop: string, render = true) => {
|
||||
emit('onLabelChange', { data: state.labelForm, render }, prop)
|
||||
}
|
||||
|
||||
function changeLabelUnitLanguage(cfg: BaseFormatter, lang, prop: string, render = true) {
|
||||
onChangeFormatCfgUnitLanguage(cfg, lang)
|
||||
changeLabelAttr(prop, render)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
if (chart.customAttr) {
|
||||
@ -260,6 +272,10 @@ const init = () => {
|
||||
if (customAttr.label) {
|
||||
configCompat(customAttr.label)
|
||||
state.labelForm = defaultsDeep(customAttr.label, cloneDeep(COMPUTED_DEFAULT_LABEL.value))
|
||||
//初始化format单位语言
|
||||
initFormatCfgUnit(state.labelForm.labelFormatter)
|
||||
initFormatCfgUnit(state.labelForm.quotaLabelFormatter)
|
||||
initFormatCfgUnit(state.labelForm.totalFormatter)
|
||||
if (chartType.value === 'liquid' && state.labelForm.fontSize < fontSizeList.value[0].value) {
|
||||
state.labelForm.fontSize = fontSizeList.value[0].value
|
||||
}
|
||||
@ -486,6 +502,7 @@ const isProgressBar = computed(() => {
|
||||
:disabled="!state.labelForm.show"
|
||||
:model="state.labelForm"
|
||||
label-position="top"
|
||||
size="small"
|
||||
>
|
||||
<el-row v-show="showEmpty" style="margin-bottom: 12px">
|
||||
{{ t('chart.no_other_configurable_properties') }}</el-row
|
||||
@ -738,48 +755,71 @@ const isProgressBar = computed(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="state.labelForm.labelFormatter && state.labelForm.labelFormatter.type !== 'percent'"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('labelFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="$t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v => changeLabelUnitLanguage(state.labelForm.labelFormatter, v, 'labelFormatter')
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('labelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(state.labelForm.labelFormatter.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('labelFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('labelFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -871,48 +911,72 @@ const isProgressBar = computed(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="state.labelForm.totalFormatter && state.labelForm.totalFormatter.type !== 'percent'"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.totalFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('totalFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="$t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.totalFormatter.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeLabelUnitLanguage(state.labelForm.totalFormatter, v, 'totalFormatter')
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.totalFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('totalFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(state.labelForm.totalFormatter.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.totalFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('totalFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.totalFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('totalFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -1001,54 +1065,85 @@ const isProgressBar = computed(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="
|
||||
state.labelForm.quotaLabelFormatter &&
|
||||
state.labelForm.quotaLabelFormatter.type !== 'percent'
|
||||
"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!state.labelForm.showQuota"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.quotaLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeLabelAttr('quotaLabelFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
:disabled="!state.labelForm.showQuota"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.quotaLabelFormatter.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeLabelUnitLanguage(
|
||||
state.labelForm.quotaLabelFormatter,
|
||||
v,
|
||||
'quotaLabelFormatter'
|
||||
)
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!state.labelForm.showQuota"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.quotaLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeLabelAttr('quotaLabelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(
|
||||
state.labelForm.quotaLabelFormatter.unitLanguage
|
||||
)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.labelForm.showQuota"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.quotaLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeLabelAttr('quotaLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.labelForm.showQuota"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.quotaLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeLabelAttr('quotaLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -1267,55 +1362,84 @@ const isProgressBar = computed(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="
|
||||
curSeriesFormatter.show &&
|
||||
curSeriesFormatter.formatterCfg &&
|
||||
curSeriesFormatter.formatterCfg.type !== 'percent'
|
||||
"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!curSeriesFormatter.show"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeLabelAttr('seriesLabelFormatter')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
:disabled="!curSeriesFormatter.show"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeLabelUnitLanguage(
|
||||
curSeriesFormatter.formatterCfg,
|
||||
v,
|
||||
'seriesLabelFormatter'
|
||||
)
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!curSeriesFormatter.show"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeLabelAttr('seriesLabelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(curSeriesFormatter.formatterCfg.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!curSeriesFormatter.show"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeLabelAttr('seriesLabelFormatter')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!curSeriesFormatter.show"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeLabelAttr('seriesLabelFormatter')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -1478,50 +1602,75 @@ const isProgressBar = computed(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="state.labelForm.labelFormatter && state.labelForm.labelFormatter.type !== 'percent'"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('labelFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="$t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeLabelUnitLanguage(state.labelForm.labelFormatter, v, 'labelFormatter')
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('labelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(state.labelForm.labelFormatter.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('labelFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('labelFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -1711,50 +1860,76 @@ const isProgressBar = computed(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="state.labelForm.labelFormatter && state.labelForm.labelFormatter.type !== 'percent'"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('labelFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="$t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeLabelUnitLanguage(state.labelForm.labelFormatter, v, 'labelFormatter')
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.unit"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="changeLabelAttr('labelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(state.labelForm.labelFormatter.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('labelFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.labelForm.childrenShow"
|
||||
:effect="themes"
|
||||
v-model="state.labelForm.labelFormatter.suffix"
|
||||
clearable
|
||||
:placeholder="$t('commons.input_content')"
|
||||
@change="changeLabelAttr('labelFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -1850,9 +2025,8 @@ const isProgressBar = computed(() => {
|
||||
}
|
||||
|
||||
.series-select {
|
||||
:deep(.ed-select__prefix--light) {
|
||||
padding-right: unset;
|
||||
border-right: unset;
|
||||
:deep(.ed-select__prefix::after) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.ed-select__prefix--dark) {
|
||||
|
@ -5,17 +5,18 @@ import icon_rightAlign_outlined from '@/assets/svg/icon_right-align_outlined.svg
|
||||
import icon_topAlign_outlined from '@/assets/svg/icon_top-align_outlined.svg'
|
||||
import icon_verticalAlign_outlined from '@/assets/svg/icon_vertical-align_outlined.svg'
|
||||
import icon_bottomAlign_outlined from '@/assets/svg/icon_bottom-align_outlined.svg'
|
||||
import { computed, onMounted, reactive, watch } from 'vue'
|
||||
import { computed, onMounted, reactive, watch, ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import {
|
||||
COLOR_PANEL,
|
||||
DEFAULT_LEGEND_STYLE,
|
||||
DEFAULT_MISC
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import { ElCol, ElRow, ElSpace } from 'element-plus-secondary'
|
||||
import { ElCol, ElFormItem, 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'
|
||||
import CustomSortEdit from '@/views/chart/components/editor/drag-item/components/CustomSortEdit.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -33,7 +34,7 @@ useEmitt({
|
||||
})
|
||||
const emit = defineEmits(['onLegendChange', 'onMiscChange'])
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
watch(
|
||||
() => props.chart.customStyle,
|
||||
@ -55,7 +56,9 @@ const state = reactive({
|
||||
legendForm: {
|
||||
...JSON.parse(JSON.stringify(DEFAULT_LEGEND_STYLE)),
|
||||
miscForm: JSON.parse(JSON.stringify(DEFAULT_MISC)) as ChartMiscAttr
|
||||
}
|
||||
},
|
||||
showCustomSort: false,
|
||||
customSortField: null
|
||||
})
|
||||
|
||||
const chartType = computed(() => {
|
||||
@ -99,7 +102,9 @@ const changeMisc = prop => {
|
||||
emit('onMiscChange', { data: state.legendForm.miscForm, requestData: true }, prop)
|
||||
}
|
||||
|
||||
const legendSort = ref()
|
||||
const init = () => {
|
||||
legendSort.value?.blur()
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
if (chart.customStyle) {
|
||||
let customStyle = null
|
||||
@ -226,6 +231,31 @@ const getMapCustomRange = index => {
|
||||
if (index === state.legendForm.miscForm.mapLegendNumber) return t('chart.max')
|
||||
return ''
|
||||
}
|
||||
const customSort = []
|
||||
const changeLegendSort = sort => {
|
||||
if (sort === 'custom') {
|
||||
state.customSortField = cloneDeep(props.chart.xAxisExt?.[0])
|
||||
if (!state.customSortField) {
|
||||
return
|
||||
}
|
||||
state.showCustomSort = true
|
||||
} else {
|
||||
state.showCustomSort = false
|
||||
state.legendForm.sort = sort
|
||||
}
|
||||
changeLegendStyle('sort')
|
||||
}
|
||||
const closeCustomSort = () => {
|
||||
state.showCustomSort = false
|
||||
}
|
||||
const saveCustomSort = () => {
|
||||
state.showCustomSort = false
|
||||
state.legendForm.customSort = customSort
|
||||
changeLegendStyle('customSort')
|
||||
}
|
||||
const customSortChange = list => {
|
||||
customSort.splice(0, customSort.length, ...list)
|
||||
}
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
@ -237,6 +267,7 @@ onMounted(() => {
|
||||
:disabled="!state.legendForm.show"
|
||||
:model="state.legendForm"
|
||||
label-position="top"
|
||||
size="small"
|
||||
>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12">
|
||||
@ -281,7 +312,51 @@ onMounted(() => {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item v-if="showProperty('showRange')" class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.legendForm.showRange"
|
||||
@change="changeLegendStyle('showRange')"
|
||||
:label="t('chart.show_range_bg')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div
|
||||
style="flex: 1; display: flex"
|
||||
v-if="showProperty('showRange') && state.legendForm.showRange"
|
||||
>
|
||||
<el-form-item :label="t('chart.icon')" class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
:effect="themes"
|
||||
v-model="state.legendForm.miscForm.bullet.bar.ranges.symbol"
|
||||
:placeholder="t('chart.icon')"
|
||||
@change="changeMisc('bullet.bar.ranges.symbol')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in iconSymbolOptions"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 8px">
|
||||
<template #label> </template>
|
||||
<el-select
|
||||
:effect="themes"
|
||||
v-model="state.legendForm.miscForm.bullet.bar.ranges.symbolSize"
|
||||
size="small"
|
||||
@change="changeMisc('bullet.bar.ranges.symbolSize')"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in sizeList"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-space>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
@ -633,7 +708,55 @@ onMounted(() => {
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-space>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
v-if="showProperty('legendSort')"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.legend_sort')"
|
||||
>
|
||||
<el-select
|
||||
v-model="state.legendForm.sort"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
:disabled="!chart.xAxisExt?.length"
|
||||
ref="legendSort"
|
||||
@change="changeLegendSort"
|
||||
>
|
||||
<el-option :label="t('chart.none')" value="none" />
|
||||
<el-option :label="t('chart.asc')" value="asc" />
|
||||
<el-option :label="t('chart.desc')" value="desc" />
|
||||
<el-option
|
||||
value="custom"
|
||||
:label="t('visualization.custom_sort')"
|
||||
@click="changeLegendSort('custom')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-dialog
|
||||
v-if="state.showCustomSort"
|
||||
v-model="state.showCustomSort"
|
||||
:title="t('chart.custom_sort') + t('chart.sort')"
|
||||
:visible="state.showCustomSort"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
width="372px"
|
||||
class="dialog-css custom_sort_dialog"
|
||||
>
|
||||
<custom-sort-edit
|
||||
field-type="xAxisExt"
|
||||
:chart="chart"
|
||||
:field="state.customSortField"
|
||||
:origin-sort-list="state.legendForm.customSort"
|
||||
@on-sort-change="customSortChange"
|
||||
/>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeCustomSort">{{ t('chart.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="saveCustomSort">{{ t('chart.confirm') }} </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -5,7 +5,7 @@ import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { DEFAULT_MISC } from '@/views/chart/components/editor/util/chart'
|
||||
import { ElRow } from 'element-plus-secondary'
|
||||
import { fieldType } from '@/utils/attr'
|
||||
import { cloneDeep, defaultsDeep, isEmpty } from 'lodash-es'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||
|
||||
@ -27,13 +27,42 @@ useEmitt({
|
||||
callback: args => wordCloudDefaultDataRange(args)
|
||||
})
|
||||
useEmitt({
|
||||
name: 'gauge-default-data',
|
||||
callback: args => gaugeOrLiquidDefaultRangeData(args)
|
||||
name: 'gauge-liquid-y-value',
|
||||
callback: args => gaugeLiquidYaxisValue(args)
|
||||
})
|
||||
useEmitt({
|
||||
name: 'liquid-default-data',
|
||||
callback: args => gaugeOrLiquidDefaultRangeData(args)
|
||||
name: 'chart-type-change',
|
||||
callback: () => {
|
||||
if (isLiquid.value || isGauge.value) {
|
||||
init()
|
||||
initField()
|
||||
initAxis(props.chart.yAxis[0]?.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
const addAxis = (form: AxisEditForm) => {
|
||||
initAxis(form.axis[0]?.id)
|
||||
}
|
||||
useEmitt({ name: 'addAxis', callback: addAxis })
|
||||
const wordCloudDefaultDataRange = ({ data: { max, min } }) => {
|
||||
Object.assign(state.miscForm.wordCloudAxisValueRange, {
|
||||
max,
|
||||
min,
|
||||
fieldId: props.chart.yAxis?.[0]?.id
|
||||
})
|
||||
}
|
||||
const gaugeLiquidYaxisDefaultValue = { gaugeMax: undefined, liquidMax: undefined }
|
||||
const gaugeLiquidYaxisValue = args => {
|
||||
const { type, max } = args.data
|
||||
const key = type === 'gauge' ? 'gaugeMax' : type === 'liquid' ? 'liquidMax' : null
|
||||
if (key) {
|
||||
gaugeLiquidYaxisDefaultValue[key] = cloneDeep(max)
|
||||
if (!state.miscForm[key]) {
|
||||
state.miscForm[key] = gaugeLiquidYaxisDefaultValue[key]
|
||||
changeMisc()
|
||||
}
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['onMiscChange'])
|
||||
|
||||
watch(
|
||||
@ -46,24 +75,21 @@ watch(
|
||||
)
|
||||
|
||||
const validLiquidMaxField = computed(() => {
|
||||
return isValidField(state.liquidMaxField)
|
||||
return isValidField(state.miscForm.liquidMaxField)
|
||||
})
|
||||
const validMinField = computed(() => {
|
||||
return isValidField(state.minField)
|
||||
return isValidField(state.miscForm.gaugeMinField)
|
||||
})
|
||||
const validMaxField = computed(() => {
|
||||
return isValidField(state.maxField)
|
||||
return isValidField(state.miscForm.gaugeMaxField)
|
||||
})
|
||||
const isValidField = field => {
|
||||
return field.id !== '-1' && quotaData.value.findIndex(ele => ele.id === field.id) !== -1
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
miscForm: JSON.parse(JSON.stringify(DEFAULT_MISC)),
|
||||
minField: {},
|
||||
maxField: {},
|
||||
liquidMaxField: {},
|
||||
quotaData: [],
|
||||
// 是否已处理没有 y 轴字段的情况
|
||||
liquidProcessedNoYAxis: false,
|
||||
gaugeProcessedNoYAxis: false
|
||||
quotaData: []
|
||||
})
|
||||
|
||||
const liquidShapeOptions = [
|
||||
@ -81,12 +107,6 @@ const changeMisc = (prop = '', refresh = false) => {
|
||||
const init = () => {
|
||||
const misc = cloneDeep(props.chart.customAttr.misc)
|
||||
state.miscForm = defaultsDeep(misc, cloneDeep(DEFAULT_MISC)) as ChartMiscAttr
|
||||
const maxTypeKey = props.chart.type === 'liquid' ? 'liquidMaxType' : 'gaugeMaxType'
|
||||
const maxValueKey = props.chart.type === 'liquid' ? 'liquidMax' : 'gaugeMax'
|
||||
if (!props.chart.yAxis.length) {
|
||||
state.miscForm[maxTypeKey] = 'fix'
|
||||
state.miscForm[maxValueKey] = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const initField = () => {
|
||||
@ -95,96 +115,57 @@ const initField = () => {
|
||||
if (!yAxisInDataset) {
|
||||
return
|
||||
}
|
||||
// 过滤掉记录数字段以及计算字段
|
||||
state.quotaData = props.quotaFields.filter(ele => ele.id !== '-1' && ele.extField !== 2)
|
||||
if (!isEmpty(state.miscForm.gaugeMinField.id)) {
|
||||
state.minField = getQuotaField(state.miscForm.gaugeMinField.id)
|
||||
}
|
||||
if (!isEmpty(state.miscForm.gaugeMaxField.id)) {
|
||||
state.maxField = getQuotaField(state.miscForm.gaugeMaxField.id)
|
||||
}
|
||||
if (!isEmpty(state.miscForm.liquidMaxField.id)) {
|
||||
state.liquidMaxField = getQuotaField(state.miscForm.liquidMaxField.id)
|
||||
}
|
||||
}
|
||||
const COUNT_DE_TYPE = [0, 1, 5]
|
||||
const NUMBER_DE_TYPE = [1, 2, 3]
|
||||
const getFieldSummaryByDeType = (deType: number) => {
|
||||
return COUNT_DE_TYPE.includes(deType) || !deType ? 'count' : 'sum'
|
||||
// 过滤掉记录数字段
|
||||
state.quotaData = props.quotaFields.filter(ele => ele.id !== '-1')
|
||||
}
|
||||
const NUMBER_DE_TYPE = [2, 3]
|
||||
|
||||
const getDynamicFieldId = () => {
|
||||
// 返回yAxis字段ID
|
||||
const curFieldObj = quotaData.value?.find(item => item.id === props.chart.yAxis?.[0]?.id)
|
||||
if (curFieldObj) return curFieldObj.id
|
||||
// 返回第一个数字类型字段ID
|
||||
return quotaData.value?.filter(item => NUMBER_DE_TYPE.includes(item.deType))?.[0]?.id
|
||||
const getDynamicField = () => {
|
||||
return (
|
||||
quotaData.value?.find(item => item.id === props.chart.yAxis?.[0]?.id) || quotaData.value?.[0]
|
||||
)
|
||||
}
|
||||
|
||||
const changeQuotaField = (type: string, resetSummary?: boolean) => {
|
||||
if (isGauge.value) {
|
||||
if (type === 'max') {
|
||||
const quotaField = getQuotaField(state.miscForm.gaugeMaxField.id || getDynamicField()?.id)
|
||||
state.miscForm.gaugeMaxField.id = quotaField.id
|
||||
const isDynamic = state.miscForm.gaugeMaxType === 'dynamic'
|
||||
if (isDynamic) {
|
||||
if (!state.miscForm.gaugeMaxField.id) {
|
||||
setDynamicFieldId(state.miscForm.gaugeMaxField)
|
||||
}
|
||||
if (!state.miscForm.gaugeMaxField.summary || resetSummary) {
|
||||
state.miscForm.gaugeMaxField.summary = 'sum'
|
||||
}
|
||||
if (state.miscForm.gaugeMaxField.id && state.miscForm.gaugeMaxField.summary) {
|
||||
state.maxField = getQuotaField(state.miscForm.gaugeMaxField.id)
|
||||
}
|
||||
} else {
|
||||
state.miscForm.gaugeMax = state.miscForm.gaugeMax || cloneDeep(defaultMaxValue.gaugeMax)
|
||||
if (isDynamic && resetSummary) {
|
||||
state.miscForm.gaugeMaxField.summary = quotaField.summary
|
||||
}
|
||||
if (!isDynamic) {
|
||||
state.miscForm.gaugeMax = cloneDeep(gaugeLiquidYaxisDefaultValue.gaugeMax)
|
||||
state.miscForm.gaugeMaxField.id = ''
|
||||
}
|
||||
changeMisc('gaugeMaxField', true)
|
||||
}
|
||||
if (type === 'min') {
|
||||
const quotaField = getQuotaField(state.miscForm.gaugeMinField.id || getDynamicField()?.id)
|
||||
state.miscForm.gaugeMinField.id = quotaField.id
|
||||
const isDynamic = state.miscForm.gaugeMinType === 'dynamic'
|
||||
if (isDynamic) {
|
||||
if (!state.miscForm.gaugeMinField.id) {
|
||||
setDynamicFieldId(state.miscForm.gaugeMinField)
|
||||
}
|
||||
if (!state.miscForm.gaugeMinField.summary || resetSummary) {
|
||||
state.miscForm.gaugeMinField.summary = 'sum'
|
||||
}
|
||||
if (state.miscForm.gaugeMinField.id && state.miscForm.gaugeMinField.summary) {
|
||||
state.minField = getQuotaField(state.miscForm.gaugeMinField.id)
|
||||
}
|
||||
} else {
|
||||
state.miscForm.gaugeMin = state.miscForm.gaugeMin ?? 0
|
||||
if (isDynamic && resetSummary) {
|
||||
state.miscForm.gaugeMinField.summary = quotaField.summary
|
||||
}
|
||||
if (!isDynamic) {
|
||||
state.miscForm.gaugeMin = state.miscForm.gaugeMin || 0
|
||||
state.miscForm.gaugeMinField.id = ''
|
||||
}
|
||||
changeMisc('gaugeMinField', true)
|
||||
}
|
||||
}
|
||||
if (isLiquid.value) {
|
||||
const field = state.miscForm.liquidMaxField
|
||||
const maxValueKey = 'liquidMax'
|
||||
const quotaField = getQuotaField(state.miscForm.liquidMaxField.id || getDynamicField()?.id)
|
||||
state.miscForm.liquidMaxField.id = quotaField.id
|
||||
const isDynamic = state.miscForm.liquidMaxType === 'dynamic'
|
||||
if (isDynamic) {
|
||||
if (!field.id) setDynamicFieldId(field)
|
||||
if (!field.summary || resetSummary) field.summary = 'count'
|
||||
if (field.id && field.summary) {
|
||||
state.liquidMaxField = getQuotaField(field.id)
|
||||
}
|
||||
} else {
|
||||
state.miscForm.liquidMax = state.miscForm.liquidMax || cloneDeep(defaultMaxValue.liquidMax)
|
||||
if (isDynamic && resetSummary) {
|
||||
state.miscForm.liquidMaxField.summary = quotaField.summary
|
||||
}
|
||||
changeMisc(`${maxValueKey}Field`, true)
|
||||
}
|
||||
}
|
||||
|
||||
const setDynamicFieldId = fieldObj => {
|
||||
const yAxisField = props.chart.yAxis?.[0]
|
||||
if (
|
||||
yAxisField?.extField === 2 ||
|
||||
yAxisField?.id === '-1' ||
|
||||
!NUMBER_DE_TYPE.includes(yAxisField?.deType)
|
||||
) {
|
||||
fieldObj.id = getDynamicFieldId()
|
||||
} else {
|
||||
fieldObj.id = yAxisField?.id
|
||||
if (!isDynamic) {
|
||||
state.miscForm.liquidMax = cloneDeep(gaugeLiquidYaxisDefaultValue.liquidMax)
|
||||
state.miscForm.liquidMaxField.id = ''
|
||||
}
|
||||
changeMisc('liquidMaxField', true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,222 +173,139 @@ const getQuotaField = id => {
|
||||
return quotaData.value.find(ele => ele.id === id) || {}
|
||||
}
|
||||
|
||||
const isValidField = field => {
|
||||
return field.id !== '-1' && quotaData.value.findIndex(ele => ele.id === field.id) !== -1
|
||||
}
|
||||
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
const wordCloudDefaultDataRange = args => {
|
||||
state.miscForm.wordCloudAxisValueRange.max = args.data.max
|
||||
state.miscForm.wordCloudAxisValueRange.min = args.data.min
|
||||
state.miscForm.wordCloudAxisValueRange.fieldId = props.chart.yAxis?.[0]?.id
|
||||
}
|
||||
const defaultMaxValue = {
|
||||
gaugeMax: undefined,
|
||||
liquidMax: undefined
|
||||
}
|
||||
const gaugeOrLiquidDefaultRangeData = args => {
|
||||
if (args.data.type === 'gauge') {
|
||||
defaultMaxValue.gaugeMax = cloneDeep(args.data.max)
|
||||
if (!state.miscForm.gaugeMax) {
|
||||
state.miscForm.gaugeMax = cloneDeep(defaultMaxValue.gaugeMax)
|
||||
changeMisc('gaugeMaxField', true)
|
||||
}
|
||||
}
|
||||
if (args.data.type === 'liquid') {
|
||||
defaultMaxValue.liquidMax = cloneDeep(args.data.max)
|
||||
if (!state.miscForm.liquidMax) {
|
||||
state.miscForm.liquidMax = cloneDeep(defaultMaxValue.liquidMax)
|
||||
changeMisc('liquidMaxField', true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验最大值的输入
|
||||
*/
|
||||
const changeMaxValidate = prop => {
|
||||
const changeFixedValidate = prop => {
|
||||
if (prop === 'gaugeMax' && !state.miscForm.gaugeMax) {
|
||||
state.miscForm.gaugeMax = cloneDeep(defaultMaxValue.gaugeMax)
|
||||
state.miscForm.gaugeMax = cloneDeep(gaugeLiquidYaxisDefaultValue.gaugeMax)
|
||||
}
|
||||
if (prop === 'liquidMax' && !state.miscForm.liquidMax) {
|
||||
state.miscForm.liquidMax = cloneDeep(defaultMaxValue.liquidMax)
|
||||
state.miscForm.liquidMax = cloneDeep(gaugeLiquidYaxisDefaultValue.liquidMax)
|
||||
}
|
||||
if (prop === 'gaugeMin' && !state.miscForm.gaugeMin) {
|
||||
state.miscForm.gaugeMin = 0
|
||||
}
|
||||
changeMisc(prop, true)
|
||||
}
|
||||
const addAxis = (form: AxisEditForm) => {
|
||||
initAxis(form.axis[0]?.id)
|
||||
}
|
||||
const initAxis = yAxisId => {
|
||||
state.quotaData = []
|
||||
if (yAxisId) {
|
||||
const uniqueIds = new Set(state.quotaData.map(item => item.id))
|
||||
state.quotaData = [
|
||||
...props.quotaFields.filter(
|
||||
ele => ele.id !== '-1' && ele.extField !== 2 && !uniqueIds.has(ele.id)
|
||||
)
|
||||
...props.quotaFields.filter(ele => ele.id !== '-1' && !uniqueIds.has(ele.id))
|
||||
]
|
||||
const maxTypeKey = isLiquid.value ? 'liquidMaxType' : 'gaugeMaxType'
|
||||
const maxValueKey = isLiquid.value ? 'liquidMax' : 'gaugeMax'
|
||||
if (state.quotaData.length) {
|
||||
if (isLiquid.value) {
|
||||
state.miscForm[maxTypeKey] = 'dynamic'
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = getDynamicFieldId() ?? state.quotaData[0]?.id
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = 'sum'
|
||||
state.liquidMaxField = getQuotaField(state.miscForm[maxValueKey + 'Field']['id'])
|
||||
changeMisc(`${maxValueKey}Field`, true)
|
||||
state.miscForm.liquidMaxType = 'dynamic'
|
||||
state.miscForm.liquidMaxField.id = getDynamicField()?.id || state.quotaData[0]?.id
|
||||
const quotaField = getQuotaField(state.miscForm.liquidMaxField.id)
|
||||
state.miscForm.liquidMaxField.summary = quotaField.summary
|
||||
}
|
||||
if (isGauge.value) {
|
||||
// max
|
||||
state.miscForm[maxTypeKey] = 'dynamic'
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = getDynamicFieldId() ?? state.quotaData[0]?.id
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = 'sum'
|
||||
state.maxField = getQuotaField(state.miscForm[maxValueKey + 'Field']['id'])
|
||||
changeMisc(`${maxValueKey}Field`, true)
|
||||
state.miscForm.gaugeMaxType = 'dynamic'
|
||||
state.miscForm.gaugeMaxField.id = getDynamicField()?.id || state.quotaData[0]?.id
|
||||
const quotaField = getQuotaField(state.miscForm.gaugeMaxField.id)
|
||||
state.miscForm.gaugeMaxField.summary = quotaField.summary
|
||||
// min
|
||||
state.miscForm.gaugeMinType = 'fix'
|
||||
state.miscForm.gaugeMin = 0
|
||||
changeMisc('gaugeMinField', true)
|
||||
state.miscForm.gaugeMinField.summary = quotaField.summary
|
||||
}
|
||||
changeMisc()
|
||||
} else {
|
||||
if (isLiquid.value) {
|
||||
state.miscForm[maxTypeKey] = 'fix'
|
||||
state.miscForm[maxValueKey] = cloneDeep(defaultMaxValue[maxValueKey]) ?? 0
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = ''
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = ''
|
||||
state.miscForm.liquidMaxType = 'fix'
|
||||
state.miscForm.liquidMax = cloneDeep(gaugeLiquidYaxisDefaultValue.liquidMax) || 0
|
||||
state.miscForm.liquidMaxField.id = ''
|
||||
state.miscForm.liquidMaxField.summary = ''
|
||||
changeMisc('liquidMax', true)
|
||||
}
|
||||
if (isGauge.value) {
|
||||
// max
|
||||
state.miscForm[maxTypeKey] = 'fix'
|
||||
state.miscForm[maxValueKey] = cloneDeep(defaultMaxValue[maxValueKey]) ?? 0
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = ''
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = ''
|
||||
state.miscForm.gaugeMaxType = 'fix'
|
||||
state.miscForm.gaugeMax = gaugeLiquidYaxisDefaultValue.gaugeMax || 0
|
||||
state.miscForm.liquidMaxField.id = ''
|
||||
state.miscForm.liquidMaxField.summary = ''
|
||||
changeMisc('gaugeMax', true)
|
||||
// min
|
||||
state.miscForm.gaugeMinType = 'fix'
|
||||
state.miscForm.gaugeMin = 0
|
||||
state.miscForm.gaugeMinField.id = ''
|
||||
state.miscForm.gaugeMinField.summary = ''
|
||||
}
|
||||
changeMisc('', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
const initStateForm = () => {
|
||||
state.quotaData = []
|
||||
if (props.chart.yAxis?.[0]?.id) {
|
||||
const uniqueIds = new Set(state.quotaData.map(item => item.id))
|
||||
state.quotaData = [
|
||||
...props.quotaFields.filter(
|
||||
ele => ele.id !== '-1' && ele.extField !== 2 && !uniqueIds.has(ele.id)
|
||||
)
|
||||
]
|
||||
}
|
||||
const maxTypeKey = isLiquid.value ? 'liquidMaxType' : 'gaugeMaxType'
|
||||
const maxValueKey = isLiquid.value ? 'liquidMax' : 'gaugeMax'
|
||||
if (quotaData.value.length) {
|
||||
if (isLiquid.value) {
|
||||
const hasDynamicValue = props.quotaFields.find(
|
||||
ele => ele.id === state.miscForm[maxValueKey + 'Field']['id']
|
||||
)
|
||||
const hasFixValue = state.miscForm[maxValueKey]
|
||||
if (state.miscForm[maxTypeKey] === 'dynamic' && !hasDynamicValue) {
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = state.quotaData[0]?.id ?? ''
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = 'sum'
|
||||
state.liquidMaxField = getQuotaField(state.miscForm[maxValueKey + 'Field']['id'])
|
||||
changeMisc(`${maxValueKey}Field`, true)
|
||||
} else if (state.miscForm[maxTypeKey] === 'fix' && !hasFixValue && hasFixValue !== 0) {
|
||||
state.miscForm[maxValueKey] = cloneDeep(defaultMaxValue[maxValueKey]) ?? 0
|
||||
changeMisc(`${maxValueKey}Field`, true)
|
||||
changeMisc('gaugeMin', true)
|
||||
}
|
||||
}
|
||||
if (isGauge.value) {
|
||||
// max
|
||||
const hasDynamicValue = props.quotaFields.find(
|
||||
ele => ele.id === state.miscForm[maxValueKey + 'Field']['id']
|
||||
)
|
||||
const hasFixValue = state.miscForm[maxValueKey]
|
||||
if (state.miscForm[maxTypeKey] === 'dynamic' && !hasDynamicValue) {
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = state.quotaData[0]?.id ?? ''
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = 'sum'
|
||||
state.maxField = getQuotaField(state.miscForm[maxValueKey + 'Field']['id'])
|
||||
changeMisc(`${maxValueKey}Field`, true)
|
||||
} else if (state.miscForm[maxTypeKey] === 'fix' && !hasFixValue && hasFixValue !== 0) {
|
||||
state.miscForm[maxValueKey] = cloneDeep(defaultMaxValue[maxValueKey]) ?? 0
|
||||
changeMisc(`${maxValueKey}Field`, true)
|
||||
}
|
||||
// min
|
||||
const hasDynamicMinValue = props.quotaFields.find(
|
||||
ele => ele.id === state.miscForm.gaugeMinField.id
|
||||
)
|
||||
if (state.miscForm.gaugeMinType === 'dynamic' && !hasDynamicMinValue) {
|
||||
state.miscForm.gaugeMin = 0
|
||||
state.miscForm.gaugeMinField.id = state.quotaData[0]?.id ?? ''
|
||||
state.miscForm.gaugeMinField.summary = 'sum'
|
||||
state.minField = getQuotaField(state.miscForm.gaugeMinField.id)
|
||||
changeMisc('gaugeMinField', true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const hasFixValue = state.miscForm[maxValueKey]
|
||||
if (isLiquid.value) {
|
||||
state.miscForm[maxTypeKey] = 'fix'
|
||||
state.miscForm[maxValueKey] = hasFixValue
|
||||
? hasFixValue
|
||||
: cloneDeep(defaultMaxValue[maxValueKey]) ?? 0
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = ''
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = ''
|
||||
}
|
||||
if (isGauge.value) {
|
||||
// max
|
||||
state.miscForm[maxTypeKey] = 'fix'
|
||||
state.miscForm[maxValueKey] = hasFixValue
|
||||
? hasFixValue
|
||||
: cloneDeep(defaultMaxValue[maxValueKey]) ?? 0
|
||||
state.miscForm[maxValueKey + 'Field']['id'] = ''
|
||||
state.miscForm[maxValueKey + 'Field']['summary'] = ''
|
||||
// min
|
||||
state.miscForm.gaugeMinType = 'fix'
|
||||
state.miscForm.gaugeMin = 0
|
||||
state.miscForm.gaugeMinField.id = ''
|
||||
state.miscForm.gaugeMinField.summary = ''
|
||||
}
|
||||
changeMisc('', false)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
initField()
|
||||
useEmitt({ name: 'addAxis', callback: addAxis })
|
||||
useEmitt({
|
||||
name: 'chart-data-change',
|
||||
callback: () => {
|
||||
initStateForm()
|
||||
}
|
||||
})
|
||||
useEmitt({
|
||||
name: 'chart-type-change',
|
||||
callback: () => {
|
||||
if (isLiquid.value || isGauge.value) {
|
||||
init()
|
||||
initField()
|
||||
initAxis(props.chart.yAxis[0]?.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
// 校验聚合函数
|
||||
const validLiquidMaxFieldAgg = computed(() => {
|
||||
return isAggField(state.miscForm.liquidMaxField)
|
||||
})
|
||||
const validMinFieldAgg = computed(() => {
|
||||
return isAggField(state.miscForm.gaugeMinField)
|
||||
})
|
||||
const validMaxFieldAgg = computed(() => {
|
||||
return isAggField(state.miscForm.gaugeMaxField)
|
||||
})
|
||||
const isAggField = field => {
|
||||
return quotaData.value.find(ele => ele.id === field.id)?.agg
|
||||
}
|
||||
// 校验计算字段和聚合函数
|
||||
const validLiquidMaxFieldCalcAndAgg = computed(() => {
|
||||
return isCalcFieldAndAgg(state.miscForm.liquidMaxField)
|
||||
})
|
||||
const validMinFieldCalcAndAgg = computed(() => {
|
||||
return isCalcFieldAndAgg(state.miscForm.gaugeMinField)
|
||||
})
|
||||
const validMaxFieldCalcAndAgg = computed(() => {
|
||||
return isCalcFieldAndAgg(state.miscForm.gaugeMaxField)
|
||||
})
|
||||
const isCalcFieldAndAgg = field => {
|
||||
return quotaData.value.find(ele => ele.id === field.id && ele.extField === 2 && ele.agg)
|
||||
}
|
||||
|
||||
// 校验数值类型
|
||||
const validLiquidMaxFieldNum = computed(() => {
|
||||
return isNumType(state.miscForm.liquidMaxField)
|
||||
})
|
||||
const validMinFieldNum = computed(() => {
|
||||
return isNumType(state.miscForm.gaugeMinField)
|
||||
})
|
||||
const validMaxFieldNum = computed(() => {
|
||||
return isNumType(state.miscForm.gaugeMaxField)
|
||||
})
|
||||
|
||||
const isNumType = field => {
|
||||
return quotaData.value.find(ele => ele.id === field.id && NUMBER_DE_TYPE.includes(ele.deType))
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含记录数字段以及计算字段
|
||||
* 计算属性
|
||||
*/
|
||||
const quotaData = computed(() => {
|
||||
return state.quotaData.filter(item => NUMBER_DE_TYPE.includes(item.deType))
|
||||
return state.quotaData
|
||||
})
|
||||
const isLiquid = computed(() => props.chart.type === 'liquid')
|
||||
const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
onMounted(() => {
|
||||
init()
|
||||
initField()
|
||||
if (
|
||||
(isGauge.value && !state.miscForm.gaugeMaxField.id && !state.miscForm.gaugeMax) ||
|
||||
(isLiquid.value && !state.miscForm.liquidMaxField.id && !state.miscForm.liquidMax)
|
||||
) {
|
||||
initAxis(props.chart.yAxis[0]?.id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form :model="state.miscForm">
|
||||
<el-form size="small" :model="state.miscForm">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-show="showProperty('gaugeStartAngle')">
|
||||
<el-form-item
|
||||
@ -474,14 +372,14 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
v-model="state.miscForm.gaugeMin"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeMisc('gaugeMin')"
|
||||
@blur="changeFixedValidate('gaugeMin')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-row
|
||||
:gutter="8"
|
||||
v-if="showProperty('gaugeMinField') && state.miscForm.gaugeMinType === 'dynamic'"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-col :span="validMinFieldCalcAndAgg ? 24 : 12">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
:effect="themes"
|
||||
@ -511,7 +409,7 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="!validMinFieldCalcAndAgg">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
:effect="themes"
|
||||
@ -519,25 +417,27 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
v-model="state.miscForm.gaugeMinField.summary"
|
||||
@change="changeQuotaField('min')"
|
||||
>
|
||||
<el-option v-if="validMinField" key="sum" value="sum" :label="t('chart.sum')" />
|
||||
<el-option v-if="validMinField" key="avg" value="avg" :label="t('chart.avg')" />
|
||||
<el-option v-if="validMinField" key="max" value="max" :label="t('chart.max')" />
|
||||
<el-option v-if="validMinField" key="min" value="min" :label="t('chart.min')" />
|
||||
<el-option
|
||||
v-if="validMinField"
|
||||
key="stddev_pop"
|
||||
value="stddev_pop"
|
||||
:label="t('chart.stddev_pop')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="validMinField"
|
||||
key="var_pop"
|
||||
value="var_pop"
|
||||
:label="t('chart.var_pop')"
|
||||
/>
|
||||
<div v-if="!validMinFieldAgg && validMinFieldNum">
|
||||
<el-option v-if="validMinField" key="sum" value="sum" :label="t('chart.sum')" />
|
||||
<el-option v-if="validMinField" key="avg" value="avg" :label="t('chart.avg')" />
|
||||
<el-option v-if="validMinField" key="max" value="max" :label="t('chart.max')" />
|
||||
<el-option v-if="validMinField" key="min" value="min" :label="t('chart.min')" />
|
||||
<el-option
|
||||
v-if="validMinField"
|
||||
key="stddev_pop"
|
||||
value="stddev_pop"
|
||||
:label="t('chart.stddev_pop')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="validMinField"
|
||||
key="var_pop"
|
||||
value="var_pop"
|
||||
:label="t('chart.var_pop')"
|
||||
/>
|
||||
</div>
|
||||
<el-option key="count" value="count" :label="t('chart.count')" />
|
||||
<el-option
|
||||
v-if="state.minField.id !== '-1'"
|
||||
v-if="state.miscForm.gaugeMinField.id !== '-1'"
|
||||
key="count_distinct"
|
||||
value="count_distinct"
|
||||
:label="t('chart.count_distinct')"
|
||||
@ -573,14 +473,15 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
v-model="state.miscForm.gaugeMax"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeMaxValidate('gaugeMax')"
|
||||
value-on-clear="gaugeLiquidYaxisDefaultValue.gaugeMax"
|
||||
@blur="changeFixedValidate('gaugeMax')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-row
|
||||
:gutter="8"
|
||||
v-if="showProperty('gaugeMaxField') && state.miscForm.gaugeMaxType === 'dynamic'"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-col :span="validMaxFieldCalcAndAgg ? 24 : 12">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
:effect="themes"
|
||||
@ -610,7 +511,7 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="!validMaxFieldCalcAndAgg">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
:effect="themes"
|
||||
@ -618,25 +519,27 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
:placeholder="t('chart.summary')"
|
||||
@change="changeQuotaField('max')"
|
||||
>
|
||||
<el-option v-if="validMaxField" key="sum" value="sum" :label="t('chart.sum')" />
|
||||
<el-option v-if="validMaxField" key="avg" value="avg" :label="t('chart.avg')" />
|
||||
<el-option v-if="validMaxField" key="max" value="max" :label="t('chart.max')" />
|
||||
<el-option v-if="validMaxField" key="min" value="min" :label="t('chart.min')" />
|
||||
<el-option
|
||||
v-if="validMaxField"
|
||||
key="stddev_pop"
|
||||
value="stddev_pop"
|
||||
:label="t('chart.stddev_pop')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="validMaxField"
|
||||
key="var_pop"
|
||||
value="var_pop"
|
||||
:label="t('chart.var_pop')"
|
||||
/>
|
||||
<div v-if="!validMaxFieldAgg && validMaxFieldNum">
|
||||
<el-option v-if="validMaxField" key="sum" value="sum" :label="t('chart.sum')" />
|
||||
<el-option v-if="validMaxField" key="avg" value="avg" :label="t('chart.avg')" />
|
||||
<el-option v-if="validMaxField" key="max" value="max" :label="t('chart.max')" />
|
||||
<el-option v-if="validMaxField" key="min" value="min" :label="t('chart.min')" />
|
||||
<el-option
|
||||
v-if="validMaxField"
|
||||
key="stddev_pop"
|
||||
value="stddev_pop"
|
||||
:label="t('chart.stddev_pop')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="validMaxField"
|
||||
key="var_pop"
|
||||
value="var_pop"
|
||||
:label="t('chart.var_pop')"
|
||||
/>
|
||||
</div>
|
||||
<el-option key="count" value="count" :label="t('chart.count')" />
|
||||
<el-option
|
||||
v-if="state.maxField.id !== '-1'"
|
||||
v-if="state.miscForm.gaugeMaxField.id !== '-1'"
|
||||
key="count_distinct"
|
||||
value="count_distinct"
|
||||
:label="t('chart.count_distinct')"
|
||||
@ -723,7 +626,7 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
v-model="state.miscForm.liquidMax"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@blur="changeMaxValidate('liquidMax')"
|
||||
@blur="changeFixedValidate('liquidMax')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@ -731,7 +634,7 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
:gutter="8"
|
||||
v-if="showProperty('liquidMaxField') && state.miscForm.liquidMaxType === 'dynamic'"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-col :span="validLiquidMaxFieldCalcAndAgg ? 24 : 12">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
:effect="themes"
|
||||
@ -761,7 +664,7 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="!validLiquidMaxFieldCalcAndAgg">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
:effect="themes"
|
||||
@ -769,25 +672,27 @@ const isGauge = computed(() => props.chart.type === 'gauge')
|
||||
:placeholder="t('chart.summary')"
|
||||
@change="changeQuotaField('max')"
|
||||
>
|
||||
<el-option v-if="validLiquidMaxField" key="sum" value="sum" :label="t('chart.sum')" />
|
||||
<el-option v-if="validLiquidMaxField" key="avg" value="avg" :label="t('chart.avg')" />
|
||||
<el-option v-if="validLiquidMaxField" key="max" value="max" :label="t('chart.max')" />
|
||||
<el-option v-if="validLiquidMaxField" key="min" value="min" :label="t('chart.min')" />
|
||||
<el-option
|
||||
v-if="validLiquidMaxField"
|
||||
key="stddev_pop"
|
||||
value="stddev_pop"
|
||||
:label="t('chart.stddev_pop')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="validLiquidMaxField"
|
||||
key="var_pop"
|
||||
value="var_pop"
|
||||
:label="t('chart.var_pop')"
|
||||
/>
|
||||
<div v-if="!validLiquidMaxFieldAgg && validLiquidMaxFieldNum">
|
||||
<el-option v-if="validLiquidMaxField" key="sum" value="sum" :label="t('chart.sum')" />
|
||||
<el-option v-if="validLiquidMaxField" key="avg" value="avg" :label="t('chart.avg')" />
|
||||
<el-option v-if="validLiquidMaxField" key="max" value="max" :label="t('chart.max')" />
|
||||
<el-option v-if="validLiquidMaxField" key="min" value="min" :label="t('chart.min')" />
|
||||
<el-option
|
||||
v-if="validLiquidMaxField"
|
||||
key="stddev_pop"
|
||||
value="stddev_pop"
|
||||
:label="t('chart.stddev_pop')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="validLiquidMaxField"
|
||||
key="var_pop"
|
||||
value="var_pop"
|
||||
:label="t('chart.var_pop')"
|
||||
/>
|
||||
</div>
|
||||
<el-option key="count" value="count" :label="t('chart.count')" />
|
||||
<el-option
|
||||
v-if="state.liquidMaxField.id !== '-1'"
|
||||
v-if="state.miscForm.liquidMaxField.id !== '-1'"
|
||||
key="count_distinct"
|
||||
value="count_distinct"
|
||||
:label="t('chart.count_distinct')"
|
||||
|
@ -16,7 +16,7 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
@ -83,7 +83,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form ref="miscForm" :model="state.miscForm">
|
||||
<el-form size="small" ref="miscForm" :model="state.miscForm">
|
||||
<el-form-item
|
||||
v-if="showProperty('showName')"
|
||||
class="form-item form-item-checkbox"
|
||||
|
@ -50,7 +50,7 @@ const state = reactive({
|
||||
}
|
||||
})
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const emit = defineEmits(['onChangeQuadrantForm'])
|
||||
|
||||
|
@ -0,0 +1,194 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { cloneDeep, defaultsDeep, filter, find } from 'lodash-es'
|
||||
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
chart: {
|
||||
type: Object as PropType<ChartObj>,
|
||||
required: true
|
||||
},
|
||||
themes: {
|
||||
type: String as PropType<EditorTheme>,
|
||||
default: 'dark'
|
||||
},
|
||||
propertyInner: {
|
||||
type: Array<string>
|
||||
}
|
||||
})
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
|
||||
const state = reactive({
|
||||
basicStyleForm: JSON.parse(JSON.stringify(DEFAULT_BASIC_STYLE)) as ChartBasicStyle,
|
||||
currentAxis: undefined as string,
|
||||
currentAxisSummary: undefined as {
|
||||
show: boolean
|
||||
field: string
|
||||
summary: string
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onBasicStyleChange'])
|
||||
const changeBasicStyle = (prop?: string, requestData = false) => {
|
||||
emit('onBasicStyleChange', { data: state.basicStyleForm, requestData }, prop)
|
||||
}
|
||||
|
||||
watch(
|
||||
[
|
||||
() => props.chart.customAttr.basicStyle.showSummary,
|
||||
() => props.chart.xAxis,
|
||||
() => props.chart.yAxis
|
||||
],
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
function getAxisList() {
|
||||
return props.chart.type === 'table-info'
|
||||
? filter(props.chart.xAxis, axis => [2, 3, 4].includes(axis.deType))
|
||||
: props.chart.yAxis
|
||||
}
|
||||
|
||||
const computedAxis = computed(() => {
|
||||
return getAxisList()
|
||||
})
|
||||
const summaryTypes = [
|
||||
{ key: 'sum', name: t('chart.sum') },
|
||||
{ key: 'avg', name: t('chart.avg') },
|
||||
{ key: 'max', name: t('chart.max') },
|
||||
{ key: 'min', name: t('chart.min') }
|
||||
// { key: 'stddev_pop', name: t('chart.stddev_pop') },
|
||||
// { key: 'var_pop', name: t('chart.var_pop') }
|
||||
]
|
||||
|
||||
function onSelectAxis(value) {
|
||||
state.currentAxisSummary = find(state.basicStyleForm.seriesSummary, s => s.field === value)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const basicStyle = cloneDeep(props.chart.customAttr.basicStyle)
|
||||
|
||||
state.basicStyleForm = defaultsDeep(basicStyle, cloneDeep(DEFAULT_BASIC_STYLE)) as ChartBasicStyle
|
||||
|
||||
const axisList = getAxisList()
|
||||
|
||||
const tempList = []
|
||||
for (let i = 0; i < axisList.length; i++) {
|
||||
const axis = axisList[i]
|
||||
let savedAxis = find(state.basicStyleForm.seriesSummary, s => s.field === axis.dataeaseName)
|
||||
if (savedAxis) {
|
||||
if (savedAxis.summary == undefined) {
|
||||
savedAxis.summary = 'sum'
|
||||
}
|
||||
if (savedAxis.show == undefined) {
|
||||
savedAxis.show = true
|
||||
}
|
||||
} else {
|
||||
savedAxis = {
|
||||
field: axis.dataeaseName,
|
||||
summary: 'sum',
|
||||
show: true
|
||||
}
|
||||
}
|
||||
tempList.push(savedAxis)
|
||||
}
|
||||
|
||||
state.basicStyleForm.seriesSummary = tempList
|
||||
|
||||
if (state.basicStyleForm.seriesSummary.length > 0 && state.basicStyleForm.showSummary) {
|
||||
state.currentAxis = state.basicStyleForm.seriesSummary[0].field
|
||||
onSelectAxis(state.currentAxis)
|
||||
} else {
|
||||
state.currentAxis = undefined
|
||||
state.currentAxisSummary = undefined
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 100%">
|
||||
<el-form
|
||||
ref="summaryForm"
|
||||
:disabled="!state.basicStyleForm.showSummary"
|
||||
:model="state.basicStyleForm"
|
||||
size="small"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item
|
||||
v-if="showProperty('summaryLabel')"
|
||||
:label="t('chart.table_summary_label')"
|
||||
:class="'form-item-' + themes"
|
||||
class="form-item"
|
||||
>
|
||||
<el-input
|
||||
v-model="state.basicStyleForm.summaryLabel"
|
||||
type="text"
|
||||
:effect="themes"
|
||||
:max-length="10"
|
||||
@blur="changeBasicStyle('summaryLabel')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
v-model="state.currentAxis"
|
||||
:class="'form-item-' + themes"
|
||||
class="form-item"
|
||||
@change="onSelectAxis"
|
||||
>
|
||||
<el-option
|
||||
v-for="c in computedAxis"
|
||||
:key="c.dataeaseName"
|
||||
:value="c.dataeaseName"
|
||||
:label="c.chartShowName ?? c.name"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="state.currentAxis && state.currentAxisSummary">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.currentAxisSummary.show"
|
||||
@change="changeBasicStyle('seriesSummary')"
|
||||
>
|
||||
{{ t('chart.table_show_summary') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<div class="indented-container">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-select
|
||||
v-model="state.currentAxisSummary.summary"
|
||||
:class="'form-item-' + themes"
|
||||
class="form-item"
|
||||
:disabled="!state.currentAxisSummary.show"
|
||||
@change="changeBasicStyle('seriesSummary')"
|
||||
>
|
||||
<el-option v-for="c in summaryTypes" :key="c.key" :value="c.key" :label="c.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.indented-container {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
padding-left: 22px;
|
||||
}
|
||||
</style>
|
@ -154,7 +154,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 100%">
|
||||
<el-form size="small" style="width: 100%">
|
||||
<div class="map-flow-style" v-if="showProperty('symbolicMapStyle')">
|
||||
<el-row style="flex: 1">
|
||||
<el-col>
|
||||
@ -303,7 +303,7 @@ onMounted(() => {
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
@ -41,7 +41,7 @@ const props = defineProps({
|
||||
const appearanceStore = useAppearanceStoreWithOut()
|
||||
const emit = defineEmits(['onTextChange'])
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const predefineColors = COLOR_PANEL
|
||||
const fontFamily = CHART_FONT_FAMILY.concat(
|
||||
@ -132,6 +132,7 @@ watch(
|
||||
:disabled="!state.titleForm.show"
|
||||
:model="state.titleForm"
|
||||
label-position="top"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="t('chart.title')"
|
||||
@ -202,7 +203,11 @@ watch(
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="width: 106px; padding-left: 4px"
|
||||
>
|
||||
<el-select
|
||||
:effect="themes"
|
||||
v-model="state.titleForm.letterSpace"
|
||||
@ -210,7 +215,7 @@ watch(
|
||||
@change="changeTitleStyle('letterSpace')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<el-icon size="16">
|
||||
<Icon name="icon_letter-spacing_outlined"
|
||||
><icon_letterSpacing_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
|
@ -3,10 +3,16 @@ import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
|
||||
import { PropType, computed, onMounted, reactive, watch, ref, inject } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_TOOLTIP } from '@/views/chart/components/editor/util/chart'
|
||||
import { ElIcon, ElSpace } from 'element-plus-secondary'
|
||||
import { ElFormItem, ElIcon, ElSpace } from 'element-plus-secondary'
|
||||
import cloneDeep from 'lodash-es/cloneDeep'
|
||||
import defaultsDeep from 'lodash-es/defaultsDeep'
|
||||
import { formatterType, unitType } from '../../../js/formatter'
|
||||
import {
|
||||
isEnLocal,
|
||||
formatterType,
|
||||
getUnitTypeList,
|
||||
initFormatCfgUnit,
|
||||
onChangeFormatCfgUnitLanguage
|
||||
} from '@/views/chart/components/js/formatter'
|
||||
import { fieldType } from '@/utils/attr'
|
||||
import { defaultTo, partition, map, includes, isEmpty } from 'lodash-es'
|
||||
import chartViewManager from '../../../js/panel'
|
||||
@ -39,7 +45,7 @@ const dvMainStore = dvMainStoreWithOut()
|
||||
const { batchOptStatus, mobileInPc } = storeToRefs(dvMainStore)
|
||||
const predefineColors = COLOR_PANEL
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const emit = defineEmits(['onTooltipChange', 'onExtTooltipChange'])
|
||||
const curSeriesFormatter = ref<DeepPartial<SeriesFormatter>>({})
|
||||
@ -63,6 +69,7 @@ const changeChartType = () => {
|
||||
formatter.splice(0, formatter.length)
|
||||
const axisIds = []
|
||||
quotaAxis.value.forEach(axis => {
|
||||
initFormatCfgUnit(axis.formatterCfg)
|
||||
formatter.push({
|
||||
...axis,
|
||||
show: true
|
||||
@ -71,6 +78,7 @@ const changeChartType = () => {
|
||||
})
|
||||
quotaData.value.forEach(quotaAxis => {
|
||||
if (!axisIds.includes(quotaAxis.id)) {
|
||||
initFormatCfgUnit(quotaAxis.formatterCfg)
|
||||
formatter.push({
|
||||
...quotaAxis,
|
||||
seriesId: quotaAxis.id,
|
||||
@ -78,6 +86,9 @@ const changeChartType = () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
if (formatter[0]) {
|
||||
curSeriesFormatter.value = formatter[0]
|
||||
}
|
||||
emit('onTooltipChange', { data: state.tooltipForm, render: false }, 'seriesTooltipFormatter')
|
||||
emit('onExtTooltipChange', extTooltip.value)
|
||||
}
|
||||
@ -101,6 +112,9 @@ const changeDataset = () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
if (formatter[0]) {
|
||||
curSeriesFormatter.value = formatter[0]
|
||||
}
|
||||
}
|
||||
|
||||
const AXIS_PROP: AxisType[] = ['yAxis', 'yAxisExt', 'extBubble']
|
||||
@ -229,6 +243,12 @@ const changeTooltipAttr = (prop: string, requestData = false, render = true) =>
|
||||
}
|
||||
emit('onTooltipChange', { data: state.tooltipForm, requestData, render }, prop)
|
||||
}
|
||||
|
||||
function changeUnitLanguage(cfg: BaseFormatter, lang, prop: string) {
|
||||
onChangeFormatCfgUnitLanguage(cfg, lang)
|
||||
changeTooltipAttr(prop)
|
||||
}
|
||||
|
||||
const formatterSelector = ref()
|
||||
const init = () => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
@ -236,6 +256,9 @@ const init = () => {
|
||||
const customAttr = JSON.parse(JSON.stringify(chart.customAttr))
|
||||
if (customAttr.tooltip) {
|
||||
state.tooltipForm = defaultsDeep(customAttr.tooltip, cloneDeep(DEFAULT_TOOLTIP))
|
||||
|
||||
initFormatCfgUnit(state.tooltipForm.tooltipFormatter)
|
||||
|
||||
formatterSelector.value?.blur()
|
||||
// 新增图表
|
||||
const formatter = state.tooltipForm.seriesTooltipFormatter
|
||||
@ -244,6 +267,9 @@ const init = () => {
|
||||
curSeriesFormatter.value = {}
|
||||
return
|
||||
}
|
||||
formatter.forEach(f => {
|
||||
initFormatCfgUnit(f.formatterCfg)
|
||||
})
|
||||
const seriesAxisMap = formatter.reduce((pre, next) => {
|
||||
next.seriesId = next.seriesId ?? next.id
|
||||
pre[next.seriesId] = next
|
||||
@ -251,6 +277,9 @@ const init = () => {
|
||||
}, {})
|
||||
if (!curSeriesFormatter?.value || !seriesAxisMap[curSeriesFormatter.value?.seriesId]) {
|
||||
curSeriesFormatter.value = {}
|
||||
if (formatter[0]) {
|
||||
curSeriesFormatter.value = formatter[0]
|
||||
}
|
||||
} else {
|
||||
curSeriesFormatter.value = seriesAxisMap[curSeriesFormatter.value?.seriesId]
|
||||
}
|
||||
@ -443,6 +472,7 @@ onMounted(() => {
|
||||
:disabled="!state.tooltipForm.show"
|
||||
:model="state.tooltipForm"
|
||||
label-position="top"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="t('chart.background') + t('chart.color')"
|
||||
@ -590,47 +620,72 @@ onMounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="8" v-if="state.tooltipForm.tooltipFormatter.type !== 'percent'">
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="state.tooltipForm.tooltipFormatter.type === 'percent'"
|
||||
:effect="props.themes"
|
||||
v-model="state.tooltipForm.tooltipFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeTooltipAttr('tooltipFormatter.unit')"
|
||||
<template v-if="state.tooltipForm.tooltipFormatter.type !== 'percent'">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
:disabled="state.tooltipForm.tooltipFormatter.type === 'percent'"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.tooltipForm.tooltipFormatter.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v => changeUnitLanguage(state.tooltipForm.tooltipFormatter, v, 'tooltipFormatter')
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="state.tooltipForm.tooltipFormatter.type === 'percent'"
|
||||
:effect="props.themes"
|
||||
v-model="state.tooltipForm.tooltipFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeTooltipAttr('tooltipFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(state.tooltipForm.tooltipFormatter.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="props.themes"
|
||||
v-model="state.tooltipForm.tooltipFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeTooltipAttr('tooltipFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="props.themes"
|
||||
v-model="state.tooltipForm.tooltipFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeTooltipAttr('tooltipFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -781,50 +836,82 @@ onMounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="8" v-if="curSeriesFormatter.formatterCfg.type !== 'percent'">
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="
|
||||
!curSeriesFormatter.show || curSeriesFormatter.formatterCfg.type == 'percent'
|
||||
"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeTooltipAttr('seriesTooltipFormatter')"
|
||||
<template v-if="curSeriesFormatter.formatterCfg.type !== 'percent'">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="$t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitType"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
:disabled="
|
||||
!curSeriesFormatter.show || curSeriesFormatter.formatterCfg.type == 'percent'
|
||||
"
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.unitLanguage"
|
||||
:placeholder="$t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeUnitLanguage(
|
||||
curSeriesFormatter.formatterCfg,
|
||||
v,
|
||||
'seriesTooltipFormatter'
|
||||
)
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="$t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-select
|
||||
:disabled="
|
||||
!curSeriesFormatter.show || curSeriesFormatter.formatterCfg.type == 'percent'
|
||||
"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeTooltipAttr('seriesTooltipFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(curSeriesFormatter.formatterCfg.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!curSeriesFormatter.show"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeTooltipAttr('seriesTooltipFormatter')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!curSeriesFormatter.show"
|
||||
:effect="props.themes"
|
||||
v-model="curSeriesFormatter.formatterCfg.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeTooltipAttr('seriesTooltipFormatter')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
@ -872,7 +959,8 @@ onMounted(() => {
|
||||
:effect="themes"
|
||||
controls-position="right"
|
||||
size="middle"
|
||||
:min="0"
|
||||
precision="0"
|
||||
:min="1"
|
||||
:max="600"
|
||||
:disabled="!state.tooltipForm.carousel.enable"
|
||||
@change="changeTooltipAttr('carousel')"
|
||||
@ -891,7 +979,8 @@ onMounted(() => {
|
||||
:effect="themes"
|
||||
controls-position="right"
|
||||
size="middle"
|
||||
:min="0"
|
||||
precision="0"
|
||||
:min="1"
|
||||
:max="600"
|
||||
:disabled="!state.tooltipForm.carousel.enable"
|
||||
@change="changeTooltipAttr('carousel')"
|
||||
@ -906,13 +995,8 @@ onMounted(() => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.series-select {
|
||||
:deep(.ed-select__prefix--light) {
|
||||
padding-right: unset;
|
||||
border-right: unset;
|
||||
}
|
||||
:deep(.ed-select__prefix--dark) {
|
||||
padding-right: unset;
|
||||
border-right: unset;
|
||||
:deep(.ed-select__prefix::after) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,14 @@ import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
|
||||
import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_XAXIS_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { formatterType, unitType } from '@/views/chart/components/js/formatter'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import {
|
||||
isEnLocal,
|
||||
formatterType,
|
||||
getUnitTypeList,
|
||||
initFormatCfgUnit,
|
||||
onChangeFormatCfgUnitLanguage
|
||||
} from '@/views/chart/components/js/formatter'
|
||||
import { ElFormItem, ElMessage } from 'element-plus-secondary'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -24,13 +30,12 @@ const props = defineProps({
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
const typeList = formatterType
|
||||
const unitList = unitType
|
||||
|
||||
const state = reactive({
|
||||
axisForm: JSON.parse(JSON.stringify(DEFAULT_XAXIS_STYLE))
|
||||
})
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const emit = defineEmits(['onChangeXAxisForm'])
|
||||
|
||||
@ -90,6 +95,11 @@ const changeAxisStyle = prop => {
|
||||
emit('onChangeXAxisForm', state.axisForm, prop)
|
||||
}
|
||||
|
||||
function changeUnitLanguage(cfg: BaseFormatter, lang, prop: string) {
|
||||
onChangeFormatCfgUnitLanguage(cfg, lang)
|
||||
changeAxisStyle(prop)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
if (chart.customStyle) {
|
||||
@ -101,6 +111,7 @@ const init = () => {
|
||||
}
|
||||
if (customStyle.xAxis) {
|
||||
state.axisForm = customStyle.xAxis
|
||||
initFormatCfgUnit(state.axisForm.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,6 +122,10 @@ const isBidirectionalBar = computed(() => {
|
||||
return props.chart.type === 'bidirectional-bar'
|
||||
})
|
||||
|
||||
const isBulletGraph = computed(() => {
|
||||
return ['bullet-graph'].includes(props.chart.type)
|
||||
})
|
||||
|
||||
const isHorizontalLayout = computed(() => {
|
||||
return props.chart.customAttr.basicStyle.layout === 'horizontal'
|
||||
})
|
||||
@ -147,6 +162,20 @@ onMounted(() => {
|
||||
t('chart.text_pos_center')
|
||||
}}</el-radio>
|
||||
</div>
|
||||
<div v-else-if="isBulletGraph">
|
||||
<div v-if="isHorizontalLayout">
|
||||
<el-radio :effect="props.themes" label="bottom">{{
|
||||
t('chart.text_pos_left')
|
||||
}}</el-radio>
|
||||
<el-radio :effect="props.themes" label="top">{{ t('chart.text_pos_right') }}</el-radio>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-radio :effect="props.themes" label="top">{{ t('chart.text_pos_top') }}</el-radio>
|
||||
<el-radio :effect="props.themes" label="bottom">{{
|
||||
t('chart.text_pos_bottom')
|
||||
}}</el-radio>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-radio :effect="props.themes" label="top">{{ t('chart.text_pos_top') }}</el-radio>
|
||||
<el-radio :effect="props.themes" label="bottom">{{
|
||||
@ -552,52 +581,76 @@ onMounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="
|
||||
state.axisForm.axisLabel.show && state.axisForm.axisLabelFormatter.type !== 'percent'
|
||||
"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
>
|
||||
<el-select
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeAxisStyle('axisLabelFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitList"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unitLanguage"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeUnitLanguage(state.axisForm.axisLabelFormatter, v, 'axisLabelFormatter')
|
||||
"
|
||||
>
|
||||
<el-option :label="t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
>
|
||||
<el-select
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeAxisStyle('axisLabelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(state.axisForm.axisLabelFormatter.unitLanguage)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeAxisStyle('axisLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeAxisStyle('axisLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
|
@ -3,8 +3,14 @@ import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
|
||||
import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_YAXIS_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { formatterType, unitType } from '@/views/chart/components/js/formatter'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import {
|
||||
isEnLocal,
|
||||
formatterType,
|
||||
getUnitTypeList,
|
||||
initFormatCfgUnit,
|
||||
onChangeFormatCfgUnitLanguage
|
||||
} from '@/views/chart/components/js/formatter'
|
||||
import { ElFormItem, ElMessage } from 'element-plus-secondary'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -24,9 +30,8 @@ const props = defineProps({
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
const typeList = formatterType
|
||||
const unitList = unitType
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes === 'dark' ? 'light' : 'dark'
|
||||
})
|
||||
const state = reactive({
|
||||
axisForm: JSON.parse(JSON.stringify(DEFAULT_YAXIS_STYLE))
|
||||
@ -76,6 +81,11 @@ const changeAxisStyle = prop => {
|
||||
emit('onChangeYAxisForm', state.axisForm, prop)
|
||||
}
|
||||
|
||||
function changeUnitLanguage(cfg: BaseFormatter, lang, prop: string) {
|
||||
onChangeFormatCfgUnitLanguage(cfg, lang)
|
||||
changeAxisStyle(prop)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
if (chart.customStyle) {
|
||||
@ -87,11 +97,19 @@ const init = () => {
|
||||
}
|
||||
if (customStyle.yAxis) {
|
||||
state.axisForm = customStyle.yAxis
|
||||
initFormatCfgUnit(state.axisForm.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
const isBulletGraph = computed(() => {
|
||||
return ['bullet-graph'].includes(props.chart.type)
|
||||
})
|
||||
|
||||
const isHorizontalLayout = computed(() => {
|
||||
return props.chart.customAttr.basicStyle.layout === 'horizontal'
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
@ -117,8 +135,24 @@ onMounted(() => {
|
||||
size="small"
|
||||
@change="changeAxisStyle('position')"
|
||||
>
|
||||
<el-radio :effect="props.themes" label="left">{{ t('chart.text_pos_left') }}</el-radio>
|
||||
<el-radio :effect="props.themes" label="right">{{ t('chart.text_pos_right') }}</el-radio>
|
||||
<div v-if="isBulletGraph">
|
||||
<div v-if="isHorizontalLayout">
|
||||
<el-radio :effect="props.themes" label="right">{{ t('chart.text_pos_top') }}</el-radio>
|
||||
<el-radio :effect="props.themes" label="left">{{
|
||||
t('chart.text_pos_bottom')
|
||||
}}</el-radio>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-radio :effect="props.themes" label="left">{{ t('chart.text_pos_left') }}</el-radio>
|
||||
<el-radio :effect="props.themes" label="right">{{
|
||||
t('chart.text_pos_right')
|
||||
}}</el-radio>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-radio :effect="props.themes" label="left">{{ t('chart.text_pos_left') }}</el-radio>
|
||||
<el-radio :effect="props.themes" label="right">{{ t('chart.text_pos_right') }}</el-radio>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
@ -528,52 +562,82 @@ onMounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
<template
|
||||
v-if="
|
||||
state.axisForm.axisLabel.show && state.axisForm.axisLabelFormatter.type !== 'percent'
|
||||
"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
>
|
||||
<el-select
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeAxisStyle('axisLabelFormatter.unit')"
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12" v-if="!isEnLocal">
|
||||
<el-form-item
|
||||
:label="t('chart.value_formatter_unit_language')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitList"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
<el-select
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unitLanguage"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
@change="
|
||||
v =>
|
||||
changeUnitLanguage(
|
||||
state.axisForm.axisLabelFormatter,
|
||||
v,
|
||||
'axisLabelFormatter'
|
||||
)
|
||||
"
|
||||
>
|
||||
<el-option :label="t('chart.value_formatter_unit_language_ch')" value="ch" />
|
||||
<el-option :label="t('chart.value_formatter_unit_language_en')" value="en" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isEnLocal ? 24 : 12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_unit')"
|
||||
>
|
||||
<el-select
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.unit"
|
||||
:placeholder="t('chart.pls_select_field')"
|
||||
size="small"
|
||||
@change="changeAxisStyle('axisLabelFormatter')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in getUnitTypeList(
|
||||
state.axisForm.axisLabelFormatter.unitLanguage
|
||||
)"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeAxisStyle('axisLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_suffix')"
|
||||
>
|
||||
<el-input
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.suffix"
|
||||
size="small"
|
||||
clearable
|
||||
:placeholder="t('commons.input_content')"
|
||||
@change="changeAxisStyle('axisLabelFormatter.suffix')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
|
@ -0,0 +1,118 @@
|
||||
<script lang="tsx" setup>
|
||||
import { onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_MISC } from '@/views/chart/components/editor/util/chart'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
chart: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
themes: {
|
||||
type: String as PropType<EditorTheme>,
|
||||
default: 'dark'
|
||||
},
|
||||
propertyInner: {
|
||||
type: Array<string>
|
||||
},
|
||||
selectorType: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
|
||||
const state = reactive({
|
||||
bulletMeasureForm: {
|
||||
bar: {
|
||||
measures: {
|
||||
fill: 'rgba(0,128,255,1)',
|
||||
size: 15,
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['onBasicStyleChange', 'onMiscChange'])
|
||||
watch(
|
||||
() => props.chart.customAttr.misc,
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
const changeStyle = (prop?) => {
|
||||
emit(
|
||||
'onMiscChange',
|
||||
{ data: { bullet: { ...state.bulletMeasureForm } }, requestData: true },
|
||||
prop
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
state.bulletMeasureForm = defaultsDeep(customAttr.misc.bullet, cloneDeep(DEFAULT_MISC.bullet))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="bulletMeasureForm"
|
||||
:model="state.bulletMeasureForm"
|
||||
size="small"
|
||||
label-position="top"
|
||||
@submit.prevent
|
||||
>
|
||||
<div v-if="selectorType === 'measure'">
|
||||
<div style="flex: 1; display: flex">
|
||||
<el-form-item
|
||||
:label="t('visualization.backgroundColor')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-right: 4px"
|
||||
>
|
||||
<el-color-picker
|
||||
v-model="state.bulletMeasureForm.bar.measures.fill"
|
||||
:predefine="predefineColors"
|
||||
:effect="themes"
|
||||
@change="changeStyle('bar.measures.fill')"
|
||||
show-alpha
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="t('chart.radar_size')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-left: 4px; width: 100%"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="props.themes"
|
||||
v-model="state.bulletMeasureForm.bar.measures.size"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeStyle('bar.measures.size')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
@ -0,0 +1,259 @@
|
||||
<script lang="tsx" setup>
|
||||
import { reactive, onMounted, watch } from 'vue'
|
||||
import { COLOR_PANEL, DEFAULT_MISC } from '@/views/chart/components/editor/util/chart'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
chart: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
themes: {
|
||||
type: String,
|
||||
default: 'dark'
|
||||
},
|
||||
propertyInner: {
|
||||
type: Array
|
||||
},
|
||||
selectorType: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
|
||||
const state = reactive({
|
||||
bulletRangeForm: {
|
||||
bar: {
|
||||
ranges: {
|
||||
fill: 'rgba(0,128,255,0.5)',
|
||||
size: 20,
|
||||
showType: 'dynamic',
|
||||
fixedRange: [],
|
||||
fixedRangeNumber: 3,
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
rangeList: []
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onBasicStyleChange', 'onMiscChange'])
|
||||
|
||||
watch(
|
||||
() => props.chart.customAttr.misc,
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const changeStyle = (prop?) => {
|
||||
if (state.bulletRangeForm.bar.ranges.showType === 'fixed' && state.rangeList.length) {
|
||||
state.bulletRangeForm.bar.ranges.fixedRange = cloneDeep(state.rangeList)
|
||||
}
|
||||
emit('onMiscChange', { data: { bullet: { ...state.bulletRangeForm } }, requestData: true }, prop)
|
||||
}
|
||||
const changeRangeNumber = () => {
|
||||
if (state.bulletRangeForm.bar.ranges.fixedRangeNumber === null) {
|
||||
state.bulletRangeForm.bar.ranges.fixedRangeNumber = 1
|
||||
}
|
||||
if (state.rangeList.length > state.bulletRangeForm.bar.ranges.fixedRangeNumber) {
|
||||
state.rangeList = state.rangeList.slice(0, state.bulletRangeForm.bar.ranges.fixedRangeNumber)
|
||||
} else {
|
||||
for (
|
||||
let i = state.rangeList.length;
|
||||
i < state.bulletRangeForm.bar.ranges.fixedRangeNumber;
|
||||
i++
|
||||
) {
|
||||
state.rangeList.push({
|
||||
name: t('chart.symbolic_range') + (i + 1),
|
||||
fixedRangeValue: undefined,
|
||||
fill: 'rgba(0,128,255,0.44)'
|
||||
})
|
||||
}
|
||||
}
|
||||
changeRangeItem()
|
||||
}
|
||||
|
||||
const changeRangeItem = () => {
|
||||
validateRangeList() && changeStyle()
|
||||
}
|
||||
|
||||
const validateRangeList = () => {
|
||||
return state.rangeList.every(
|
||||
item => item.name && item.fixedRangeValue !== null && item.fixedRangeValue !== undefined
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
state.bulletRangeForm = defaultsDeep(customAttr.misc.bullet, cloneDeep(DEFAULT_MISC.bullet))
|
||||
getRangeList()
|
||||
}
|
||||
}
|
||||
|
||||
const getRangeList = () => {
|
||||
const range = []
|
||||
if (state.bulletRangeForm.bar.ranges?.fixedRange?.length) {
|
||||
state.rangeList = state.bulletRangeForm.bar.ranges.fixedRange
|
||||
} else {
|
||||
for (let i = 0; i < state.bulletRangeForm.bar.ranges.fixedRangeNumber; i++) {
|
||||
range.push({
|
||||
name: '区间' + (i + 1),
|
||||
fixedRangeValue: undefined,
|
||||
fill: 'rgba(0,128,255,0)'
|
||||
})
|
||||
}
|
||||
state.rangeList = cloneDeep(range)
|
||||
}
|
||||
}
|
||||
|
||||
const changeShowType = () => {
|
||||
if (state.bulletRangeForm.bar.ranges.showType === 'dynamic') {
|
||||
changeStyle()
|
||||
} else {
|
||||
changeRangeItem()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="bulletRangeForm"
|
||||
:model="state.bulletRangeForm"
|
||||
size="small"
|
||||
label-position="top"
|
||||
@submit.prevent
|
||||
>
|
||||
<div v-if="selectorType === 'range'">
|
||||
<el-form-item
|
||||
:label="t('chart.radar_size')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-left: 4px; width: 100%"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="props.themes"
|
||||
v-model="state.bulletRangeForm.bar.ranges.size"
|
||||
:min="1"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeStyle('bar.ranges.size')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-radio-group
|
||||
:effect="themes"
|
||||
v-model="state.bulletRangeForm.bar.ranges.showType"
|
||||
@change="changeShowType()"
|
||||
>
|
||||
<el-radio :effect="themes" label="dynamic">{{ t('chart.dynamic') }}</el-radio>
|
||||
<el-radio :effect="themes" label="fixed">{{ t('chart.fix') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="state.bulletRangeForm.bar.ranges.showType === 'dynamic'">
|
||||
<div style="flex: 1; display: flex">
|
||||
<el-form-item
|
||||
:label="t('visualization.backgroundColor')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-right: 4px"
|
||||
>
|
||||
<el-color-picker
|
||||
v-model="state.bulletRangeForm.bar.ranges.fill"
|
||||
:predefine="predefineColors"
|
||||
:effect="themes"
|
||||
@change="changeStyle('bar.ranges.fill')"
|
||||
show-alpha
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="state.bulletRangeForm.bar.ranges.showType === 'fixed'">
|
||||
<div style="flex: 1; display: flex">
|
||||
<el-form-item
|
||||
style="width: 100%"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.range_num')"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="themes"
|
||||
v-model="state.bulletRangeForm.bar.ranges.fixedRangeNumber"
|
||||
:precision="0"
|
||||
:min="1"
|
||||
:max="9"
|
||||
:step="1"
|
||||
:controls="true"
|
||||
controls-position="right"
|
||||
@change="changeRangeNumber()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div style="flex: 1; display: flex" v-for="(item, index) in state.rangeList" :key="index">
|
||||
<el-form-item
|
||||
:label="index === 0 ? t('chart.threshold_value') : ' '"
|
||||
style="width: 170px"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="themes"
|
||||
v-model="item.fixedRangeValue"
|
||||
:min="0"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeRangeItem()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="index === 0 ? t('chart.show_name') : ' '"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-left: 4px"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
v-model="item.name"
|
||||
size="small"
|
||||
@change="changeRangeItem()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="index === 0 ? t('visualization.backgroundColor') : ' '"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-left: 4px"
|
||||
>
|
||||
<el-color-picker
|
||||
v-model="item.fill"
|
||||
:predefine="predefineColors"
|
||||
:effect="themes"
|
||||
@change="changeRangeItem()"
|
||||
show-alpha
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
@ -0,0 +1,183 @@
|
||||
<script lang="tsx" setup>
|
||||
import { onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_MISC } from '@/views/chart/components/editor/util/chart'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
chart: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
themes: {
|
||||
type: String as PropType<EditorTheme>,
|
||||
default: 'dark'
|
||||
},
|
||||
propertyInner: {
|
||||
type: Array<string>
|
||||
},
|
||||
selectorType: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
|
||||
const state = reactive({
|
||||
bulletTargetForm: {
|
||||
bar: {
|
||||
target: {
|
||||
fill: 'rgb(0,0,0)',
|
||||
size: 20,
|
||||
showType: 'dynamic',
|
||||
value: 0,
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['onBasicStyleChange', 'onMiscChange'])
|
||||
watch(
|
||||
() => props.chart.customAttr.misc,
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
const changeStyle = (prop?) => {
|
||||
if (state.bulletTargetForm.bar.target.value === null) {
|
||||
state.bulletTargetForm.bar.target.value = 1
|
||||
}
|
||||
emit('onMiscChange', { data: { bullet: { ...state.bulletTargetForm } }, requestData: true }, prop)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
state.bulletTargetForm = defaultsDeep(customAttr.misc.bullet, cloneDeep(DEFAULT_MISC.bullet))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="bulletTargetForm"
|
||||
:model="state.bulletTargetForm"
|
||||
size="small"
|
||||
label-position="top"
|
||||
@submit.prevent
|
||||
>
|
||||
<div v-if="selectorType === 'target'">
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-radio-group
|
||||
:effect="themes"
|
||||
v-model="state.bulletTargetForm.bar.target.showType"
|
||||
@change="changeStyle('bar.target.name')"
|
||||
>
|
||||
<el-radio :effect="themes" label="dynamic">{{ t('chart.dynamic') }}</el-radio>
|
||||
<el-radio :effect="themes" label="fixed">{{ t('chart.fix') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="state.bulletTargetForm.bar.target.showType === 'dynamic'">
|
||||
<div style="flex: 1; display: flex">
|
||||
<el-form-item
|
||||
:label="t('visualization.color')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-right: 4px"
|
||||
>
|
||||
<el-color-picker
|
||||
v-model="state.bulletTargetForm.bar.target.fill"
|
||||
:predefine="predefineColors"
|
||||
:effect="themes"
|
||||
@change="changeStyle('bar.target.fill')"
|
||||
show-alpha
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="t('chart.height')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-left: 4px; width: 100%"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="props.themes"
|
||||
v-model="state.bulletTargetForm.bar.target.size"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeStyle('bar.target.size')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="state.bulletTargetForm.bar.target.showType === 'fixed'">
|
||||
<div style="flex: 1; display: flex">
|
||||
<el-form-item
|
||||
:label="t('chart.progress_target')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="props.themes"
|
||||
v-model="state.bulletTargetForm.bar.target.value"
|
||||
:min="1"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeStyle('bar.target.value')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div style="flex: 1; display: flex">
|
||||
<el-form-item
|
||||
:label="t('visualization.color')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-left: 4px"
|
||||
>
|
||||
<el-color-picker
|
||||
v-model="state.bulletTargetForm.bar.target.fill"
|
||||
:predefine="predefineColors"
|
||||
:effect="themes"
|
||||
@change="changeStyle('bar.target.fill')"
|
||||
show-alpha
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="t('chart.height')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-left: 4px; width: 100%"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="props.themes"
|
||||
v-model="state.bulletTargetForm.bar.target.size"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeStyle('bar.target.size')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
@ -4,13 +4,12 @@ import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlin
|
||||
import icon_adjustment_outlined from '@/assets/svg/icon_adjustment_outlined.svg'
|
||||
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, watch, unref, computed, nextTick } from 'vue'
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import CodeMirror from '@/views/visualized/data/dataset/form/CodeMirror.vue'
|
||||
import { getFunction } from '@/api/dataset'
|
||||
import { fieldType } from '@/utils/attr'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { guid } from '@/views/visualized/data/dataset/form/util'
|
||||
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||
|
||||
export interface CalcFieldType {
|
||||
@ -82,7 +81,7 @@ const setNameIdTrans = (from, to, originName, name2Auto?: string[]) => {
|
||||
pre[next[from]] = next[to]
|
||||
return pre
|
||||
}, {})
|
||||
const on = originName.match(/\[(.+?)\]/g)
|
||||
const on = originName.match(/\[(.+?)\]/g) || []
|
||||
if (on) {
|
||||
on.forEach(itm => {
|
||||
const ele = itm.slice(1, -1)
|
||||
@ -398,10 +397,8 @@ initFunction()
|
||||
.mr0 {
|
||||
margin-right: 0;
|
||||
|
||||
:deep(.ed-select__prefix--light) {
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
:deep(.ed-select__prefix::after) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,12 +75,14 @@ const init = () => {
|
||||
tableCell.mergeCells = tableCell.mergeCells === undefined ? false : tableCell.mergeCells
|
||||
state.tableCellForm = defaultsDeep(cloneDeep(tableCell), cloneDeep(DEFAULT_TABLE_CELL))
|
||||
const alpha = props.chart.customAttr.basicStyle.alpha
|
||||
|
||||
if (!isAlphaColor(state.tableCellForm.tableItemBgColor)) {
|
||||
state.tableCellForm.tableItemBgColor = convertToAlphaColor(
|
||||
state.tableCellForm.tableItemBgColor,
|
||||
alpha
|
||||
)
|
||||
}
|
||||
|
||||
if (!isAlphaColor(state.tableCellForm.tableItemSubBgColor)) {
|
||||
state.tableCellForm.tableItemSubBgColor = convertToAlphaColor(
|
||||
state.tableCellForm.tableItemSubBgColor,
|
||||
@ -97,12 +99,12 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form ref="tableCellForm" :model="state.tableCellForm" label-position="top">
|
||||
<el-form size="small" ref="tableCellForm" :model="state.tableCellForm" label-position="top">
|
||||
<el-form-item
|
||||
:label="t('chart.backgroundColor')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
v-if="showProperty('tableItemBgColor')"
|
||||
v-if="showProperty('tableItemBgColor') && state.tableCellForm.tableItemBgColor"
|
||||
>
|
||||
<el-color-picker
|
||||
:effect="themes"
|
||||
@ -148,7 +150,7 @@ onMounted(() => {
|
||||
:class="'form-item-' + themes"
|
||||
class="form-item"
|
||||
label=""
|
||||
v-if="showProperty('tableItemSubBgColor')"
|
||||
v-if="showProperty('tableItemSubBgColor') && state.tableCellForm.tableItemSubBgColor"
|
||||
>
|
||||
<el-color-picker
|
||||
v-model="state.tableCellForm.tableItemSubBgColor"
|
||||
|
@ -17,7 +17,10 @@ import {
|
||||
S2Event,
|
||||
S2Options,
|
||||
TableSheet,
|
||||
TooltipShowOptions
|
||||
TooltipShowOptions,
|
||||
ColCell,
|
||||
Node,
|
||||
LayoutResult
|
||||
} from '@antv/s2'
|
||||
import { ElMessageBox } from 'element-plus-secondary'
|
||||
import { cloneDeep, debounce, isEqual, isNumber } from 'lodash-es'
|
||||
@ -72,7 +75,7 @@ const init = () => {
|
||||
}
|
||||
if (headerGroupConfig?.columns?.length) {
|
||||
const allAxis = showColumns.map(item => item.key)
|
||||
const leafNodes = getLeafNodes(headerGroupConfig.columns as Array<ColumnNode>)
|
||||
const leafNodes = getLeafNodes(headerGroupConfig.columns)
|
||||
const leafKeys = leafNodes.map(item => item.key)
|
||||
if (!isEqual(allAxis, leafKeys)) {
|
||||
const { columns, meta } = headerGroupConfig
|
||||
@ -166,6 +169,14 @@ const renderTable = (chart: ChartObj) => {
|
||||
position: 'absolute',
|
||||
borderRadius: '4px'
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
rangeSelection: false,
|
||||
resize: {
|
||||
colCellHorizontal: false,
|
||||
colCellVertical: false,
|
||||
rowCellVertical: false
|
||||
}
|
||||
}
|
||||
}
|
||||
s2 = new TableSheet(containerDom, s2DataConfig, s2Options)
|
||||
@ -321,7 +332,7 @@ const renderTable = (chart: ChartObj) => {
|
||||
//如果有多个cell都在同一个层级,并且parent相同,那就是可以进行合并分组操作
|
||||
if (activeColumns?.length > 1) {
|
||||
const sameParent = activeCells.every(
|
||||
cell => cell.getMeta().parent === curCell.getMeta().parent
|
||||
cell => cell.getMeta().parent.id === curCell.getMeta().parent.id
|
||||
)
|
||||
if (!sameParent) {
|
||||
return
|
||||
@ -443,6 +454,57 @@ const renderTable = (chart: ChartObj) => {
|
||||
return
|
||||
}
|
||||
})
|
||||
s2.on(S2Event.COL_CELL_CLICK, e => {
|
||||
const lastCell = s2.store.get('lastClickedCell') as ColCell
|
||||
const originEvent = e.originalEvent as MouseEvent
|
||||
if (!lastCell || !(originEvent?.ctrlKey || originEvent?.metaKey || originEvent?.shiftKey)) {
|
||||
const cell = s2.getCell(e.target)
|
||||
s2.store.set('lastClickedCell', cell)
|
||||
return
|
||||
}
|
||||
if (originEvent?.shiftKey) {
|
||||
if (!lastCell) {
|
||||
const cell = s2.getCell(e.target)
|
||||
s2.store.set('lastClickedCell', cell)
|
||||
return
|
||||
}
|
||||
const curCell = s2.getCell(e.target)
|
||||
const lastMeta = lastCell.getMeta()
|
||||
const curMeta = curCell.getMeta()
|
||||
if (
|
||||
lastMeta.key === curMeta.key ||
|
||||
lastMeta.level !== curMeta.level ||
|
||||
lastMeta.parent !== curMeta.parent
|
||||
) {
|
||||
return
|
||||
}
|
||||
const parent = curMeta.parent as Node
|
||||
const lastIndex = parent.children.findIndex(item => item.key === lastMeta.key)
|
||||
const curIndex = parent.children.findIndex(item => item.key === curMeta.key)
|
||||
const startIndex = Math.min(lastIndex, curIndex)
|
||||
const endIndex = Math.max(lastIndex, curIndex)
|
||||
const activeCells = parent.children.slice(startIndex, endIndex + 1)
|
||||
s2.interaction.clearState()
|
||||
activeCells.forEach(cell => {
|
||||
s2.interaction.selectHeaderCell({ cell: cell.belongsCell, isMultiSelection: true })
|
||||
})
|
||||
}
|
||||
})
|
||||
s2.once(S2Event.LAYOUT_AFTER_HEADER_LAYOUT, (e: LayoutResult) => {
|
||||
const initialized = s2.store.get('initialized')
|
||||
if (!initialized) {
|
||||
s2.store.set('initialized', true)
|
||||
s2.changeSheetSize(e.colsHierarchy.width)
|
||||
const length = s2.dataCfg.data?.length || 0
|
||||
const headerHeight = e.colsHierarchy.height
|
||||
const rowHeight = s2.options.style.cellCfg.height
|
||||
const totalHeight = headerHeight + rowHeight * length
|
||||
if (containerDom.offsetHeight > totalHeight) {
|
||||
containerDom.style.height = totalHeight + 'px'
|
||||
}
|
||||
s2.render(false)
|
||||
}
|
||||
})
|
||||
s2.render()
|
||||
}
|
||||
|
||||
@ -486,9 +548,14 @@ const getTreesMaxDepth = (nodes: Array<ColumnNode>): number => {
|
||||
return Math.max(...rootDepths)
|
||||
}
|
||||
|
||||
const resize = debounce((width, height) => {
|
||||
const resize = debounce(height => {
|
||||
if (s2) {
|
||||
s2.changeSheetSize(width, height)
|
||||
const tableHeight = s2.container.cfg.height
|
||||
if (height > tableHeight) {
|
||||
const dom = document.getElementById(containerId.value)
|
||||
dom.style.height = tableHeight + 'px'
|
||||
}
|
||||
s2.changeSheetSize(undefined, height)
|
||||
s2.render(false)
|
||||
}
|
||||
}, 500)
|
||||
@ -504,14 +571,13 @@ onMounted(() => {
|
||||
preSize[0] = size.inlineSize
|
||||
preSize[1] = size.blockSize
|
||||
}
|
||||
const widthOffset = Math.abs(size.inlineSize - preSize[0])
|
||||
const heightOffset = Math.abs(size.blockSize - preSize[1])
|
||||
if (widthOffset < TOLERANCE && heightOffset < TOLERANCE) {
|
||||
if (heightOffset < TOLERANCE) {
|
||||
return
|
||||
}
|
||||
preSize[0] = size.inlineSize
|
||||
preSize[1] = size.blockSize
|
||||
resize(size.inlineSize, Math.round(size.blockSize))
|
||||
resize(Math.round(size.blockSize))
|
||||
})
|
||||
resizeObserver.observe(document.getElementById(containerId.value))
|
||||
})
|
||||
@ -536,6 +602,8 @@ class GroupMenu extends BaseTooltip {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.group-menu {
|
||||
|
@ -9,11 +9,12 @@ import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_TABLE_HEADER } from '@/views/chart/components/editor/util/chart'
|
||||
import { ElDivider, ElSpace } from 'element-plus-secondary'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
import { cloneDeep, defaultsDeep, isEqual } from 'lodash-es'
|
||||
import { convertToAlphaColor, isAlphaColor } from '@/views/chart/components/js/util'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import TableHeaderGroupConfig from './TableHeaderGroupConfig.vue'
|
||||
import { getLeafNodes } from '@/views/chart/components/js/panel/common/common_table'
|
||||
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { batchOptStatus, mobileInPc } = storeToRefs(dvMainStore)
|
||||
@ -86,6 +87,28 @@ const enableGroupConfig = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const groupConfigValid = computed(() => {
|
||||
const columns = props.chart?.customAttr?.tableHeader?.headerGroupConfig?.columns
|
||||
if (!columns?.length) {
|
||||
return false
|
||||
}
|
||||
const noGroup = columns.every(item => !item.children?.length)
|
||||
if (noGroup) {
|
||||
return false
|
||||
}
|
||||
const xAxis = props.chart.xAxis
|
||||
const showColumns = []
|
||||
xAxis?.forEach(axis => {
|
||||
axis.hide !== true && showColumns.push({ key: axis.dataeaseName })
|
||||
})
|
||||
if (!showColumns.length) {
|
||||
return false
|
||||
}
|
||||
const allAxis = showColumns.map(item => item.key)
|
||||
const leafNodes = getLeafNodes(columns as Array<ColumnNode>)
|
||||
const leafKeys = leafNodes.map(item => item.key)
|
||||
return isEqual(allAxis, leafKeys)
|
||||
})
|
||||
const init = () => {
|
||||
const tableHeader = props.chart?.customAttr?.tableHeader
|
||||
if (tableHeader) {
|
||||
@ -128,6 +151,7 @@ onMounted(() => {
|
||||
:disabled="!state.tableHeaderForm.showTableHeader"
|
||||
ref="tableHeaderForm"
|
||||
label-position="top"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="
|
||||
@ -135,7 +159,7 @@ onMounted(() => {
|
||||
"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
v-if="showProperty('tableHeaderBgColor')"
|
||||
v-if="showProperty('tableHeaderBgColor') && state.tableHeaderForm.tableHeaderBgColor"
|
||||
>
|
||||
<el-color-picker
|
||||
:effect="themes"
|
||||
@ -742,6 +766,20 @@ onMounted(() => {
|
||||
{{ t('chart.table_header_show_vertical_border') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
v-if="showProperty('rowHeaderFreeze')"
|
||||
>
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.tableHeaderForm.rowHeaderFreeze"
|
||||
@change="changeTableHeader('rowHeaderFreeze')"
|
||||
>
|
||||
{{ t('chart.table_row_header_freeze') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="!batchOptStatus && showProperty('headerGroup')"
|
||||
class="form-item"
|
||||
@ -761,7 +799,7 @@ onMounted(() => {
|
||||
<div class="header-group-config">
|
||||
<span>{{ t('chart.table_header_group_config') }}</span>
|
||||
<div class="group-icon">
|
||||
<span v-if="state.tableHeaderForm.headerGroupConfig?.columns?.length">
|
||||
<span v-if="groupConfigValid">
|
||||
{{ t('visualization.already_setting') }}
|
||||
</span>
|
||||
<div
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, PropType, reactive, watch, ref, inject, nextTick } from 'vue'
|
||||
import { onMounted, PropType, reactive, watch, ref, inject, nextTick, computed } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import {
|
||||
DEFAULT_BASIC_STYLE,
|
||||
@ -32,6 +32,7 @@ watch(
|
||||
)
|
||||
|
||||
const aggregations = [
|
||||
{ name: t('chart.none'), value: 'NONE' },
|
||||
{ name: t('chart.sum'), value: 'SUM' },
|
||||
{ name: t('chart.avg'), value: 'AVG' },
|
||||
{ name: t('chart.max'), value: 'MAX' },
|
||||
@ -53,6 +54,26 @@ const state = reactive({
|
||||
basicStyleForm: JSON.parse(JSON.stringify(DEFAULT_BASIC_STYLE)) as ChartBasicStyle
|
||||
})
|
||||
|
||||
const showColFieldTotalLabel = computed(() => {
|
||||
const chart = props.chart
|
||||
return (
|
||||
chart.customAttr.basicStyle.quotaPosition !== 'row' &&
|
||||
chart.xAxisExt.length &&
|
||||
chart.yAxis.length > 1
|
||||
)
|
||||
})
|
||||
|
||||
const showRowFieldTotalLabel = computed(() => {
|
||||
const chart = props.chart
|
||||
return (
|
||||
chart.customAttr.basicStyle.quotaPosition === 'row' &&
|
||||
chart.customAttr.basicStyle.tableLayoutMode !== 'tree' &&
|
||||
chart.xAxis.length &&
|
||||
chart.xAxisExt.length &&
|
||||
chart.yAxis.length > 1
|
||||
)
|
||||
})
|
||||
|
||||
function onSelectedSubTotalDimensionNameChange(name) {
|
||||
state.selectedSubTotalDimension = find(state.subTotalDimensionList, d => d.name === name)
|
||||
}
|
||||
@ -159,6 +180,7 @@ const init = () => {
|
||||
total.dataeaseName = totalCfg[0].dataeaseName
|
||||
total.aggregation = totalCfg[0].aggregation
|
||||
total.originName = totalCfg[0].originName
|
||||
total.label = totalCfg[0].label
|
||||
}
|
||||
})
|
||||
|
||||
@ -174,6 +196,7 @@ const changeTotal = (totalItem, totals) => {
|
||||
if (item.dataeaseName === totalItem.dataeaseName) {
|
||||
totalItem.aggregation = item.aggregation
|
||||
totalItem.originName = item.originName
|
||||
totalItem.label = item.label
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -183,6 +206,7 @@ const changeTotalAggr = (totalItem, totals, colOrNum) => {
|
||||
const item = totals[i]
|
||||
if (item.dataeaseName === totalItem.dataeaseName) {
|
||||
item.aggregation = totalItem.aggregation
|
||||
item.label = totalItem.label
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -196,7 +220,8 @@ const setupTotalCfg = (totalCfg, axis) => {
|
||||
axis.forEach(i => {
|
||||
totalCfg.push({
|
||||
dataeaseName: i.dataeaseName,
|
||||
aggregation: 'SUM'
|
||||
aggregation: 'SUM',
|
||||
label: i.chartShowName ?? i.name
|
||||
})
|
||||
})
|
||||
return
|
||||
@ -214,7 +239,10 @@ const setupTotalCfg = (totalCfg, axis) => {
|
||||
totalCfg.push({
|
||||
dataeaseName: i.dataeaseName,
|
||||
aggregation: cfgMap[i.dataeaseName] ? cfgMap[i.dataeaseName].aggregation : 'SUM',
|
||||
originName: cfgMap[i.dataeaseName] ? cfgMap[i.dataeaseName].originName : ''
|
||||
originName: cfgMap[i.dataeaseName] ? cfgMap[i.dataeaseName].originName : '',
|
||||
label: cfgMap[i.dataeaseName]?.label
|
||||
? cfgMap[i.dataeaseName].label
|
||||
: i.chartShowName ?? i.name
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -264,7 +292,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form ref="tableTotalForm" :model="state.tableTotalForm" label-position="top">
|
||||
<el-form size="small" ref="tableTotalForm" :model="state.tableTotalForm" label-position="top">
|
||||
<el-divider
|
||||
v-if="showProperty('row')"
|
||||
content-position="center"
|
||||
@ -302,13 +330,13 @@ onMounted(() => {
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="t('chart.total_label')"
|
||||
:label="t('chart.table_grand_total_label')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
:placeholder="t('chart.total_label')"
|
||||
:placeholder="t('chart.table_grand_total_label')"
|
||||
size="small"
|
||||
maxlength="20"
|
||||
v-model="state.tableTotalForm.row.label"
|
||||
@ -371,6 +399,28 @@ onMounted(() => {
|
||||
</el-icon>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="showRowFieldTotalLabel"
|
||||
class="form-item"
|
||||
:label="t('chart.table_field_total_label')"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
:placeholder="t('chart.table_field_total_label')"
|
||||
size="small"
|
||||
maxlength="20"
|
||||
v-model="state.rowTotalItem.label"
|
||||
clearable
|
||||
@change="
|
||||
changeTotalAggr(
|
||||
state.rowTotalItem,
|
||||
state.tableTotalForm.row.calcTotals.cfg,
|
||||
'row.calcTotals.cfg'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="chart.type === 'table-pivot'"
|
||||
:label="t('chart.total_sort')"
|
||||
@ -469,7 +519,7 @@ onMounted(() => {
|
||||
<el-radio-group
|
||||
:effect="themes"
|
||||
v-model="state.tableTotalForm.row.reverseSubLayout"
|
||||
:disabled="chart.xAxis.length < 2"
|
||||
:disabled="chart.xAxis.length < 2 || state.basicStyleForm.tableLayoutMode === 'tree'"
|
||||
@change="changeTableTotal('row')"
|
||||
>
|
||||
<el-radio :effect="themes" :label="true">{{ t('chart.total_pos_top') }}</el-radio>
|
||||
@ -587,13 +637,13 @@ onMounted(() => {
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="t('chart.total_label')"
|
||||
:label="t('chart.table_grand_total_label')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
:placeholder="t('chart.total_label')"
|
||||
:placeholder="t('chart.table_grand_total_label')"
|
||||
size="small"
|
||||
maxlength="20"
|
||||
v-model="state.tableTotalForm.col.label"
|
||||
@ -656,6 +706,28 @@ onMounted(() => {
|
||||
</el-icon>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="showColFieldTotalLabel"
|
||||
class="form-item"
|
||||
:label="t('chart.table_field_total_label')"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input
|
||||
:effect="themes"
|
||||
:placeholder="t('chart.table_field_total_label')"
|
||||
size="small"
|
||||
maxlength="20"
|
||||
v-model="state.colTotalItem.label"
|
||||
clearable
|
||||
@change="
|
||||
changeTotalAggr(
|
||||
state.colTotalItem,
|
||||
state.tableTotalForm.col.calcTotals.cfg,
|
||||
'col.calcTotals.cfg'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="chart.type === 'table-pivot'"
|
||||
:label="t('chart.total_sort')"
|
||||
|
@ -100,7 +100,7 @@ defineExpose({
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
<style lang="less">
|
||||
.filter-tree-cont {
|
||||
.tree-cont {
|
||||
min-height: 67px;
|
||||
@ -123,6 +123,6 @@ defineExpose({
|
||||
.ed-button--primary{
|
||||
background-color:#0089FF;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
@ -191,7 +191,7 @@ const del = (index, child) => {
|
||||
.operate-title{
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
|
||||
.operate-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
@ -254,7 +254,7 @@ const del = (index, child) => {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.ed-dropdown__popper .ed-dropdown-menu{
|
||||
@ -266,10 +266,10 @@ const del = (index, child) => {
|
||||
}
|
||||
:deep(.ed-popper.is-light){
|
||||
border:1px solid #434343;
|
||||
background: rgba(41, 41, 41, 1);
|
||||
background: rgba(41, 41, 41, 1);
|
||||
}
|
||||
:deep(.ed-dropdown__popper.ed-popper){
|
||||
border-color: rgba(70, 70, 70, 1);
|
||||
border-color: rgba(70, 70, 70, 1);
|
||||
}
|
||||
:deep(.ed-dropdown-menu__item:not(.is-disabled):hover){
|
||||
background-color: rgba(41,41,41, 1);
|
||||
@ -278,11 +278,11 @@ const del = (index, child) => {
|
||||
<style>
|
||||
.ed-popper.is-light{
|
||||
border:1px solid #434343;
|
||||
background: rgba(41, 41, 41, 1);
|
||||
background: rgba(41, 41, 41, 1);
|
||||
}
|
||||
.ed-popper.is-light .ed-popper__arrow::before{
|
||||
background:rgba(41, 41, 41, 1);
|
||||
background:rgba(41, 41, 41, 1);
|
||||
border: 1px solid rgba(70, 70, 70, 1);
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
@ -493,6 +493,7 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
<span class="filed-title">{{ t('auth.screen_method') }}</span>
|
||||
<el-select
|
||||
size="small"
|
||||
class="w181"
|
||||
@change="filterTypeChange"
|
||||
v-model="item.filterType"
|
||||
:placeholder="t('auth.select')"
|
||||
@ -748,7 +749,6 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
font-size: 14px;
|
||||
margin: 0 10px;
|
||||
cursor: pointer;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.ed-input {
|
||||
@ -756,11 +756,15 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
}
|
||||
|
||||
.w100.ed-select {
|
||||
width: 100px;
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
.w181.ed-select {
|
||||
width: 181px !important;
|
||||
}
|
||||
|
||||
.w70 {
|
||||
width: 70px;
|
||||
width: 70px !important;
|
||||
}
|
||||
|
||||
.mar5 {
|
||||
@ -849,7 +853,7 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
background-color: #212121;
|
||||
border: 1px solid #5f5f5f;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
box-shadow: none !important;
|
||||
height: 26px;
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
word-wrap: break-word;
|
||||
|
@ -246,5 +246,4 @@ const del = (index, child) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -375,6 +375,7 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
size="small"
|
||||
@change="filterTypeChange"
|
||||
v-model="item.filterType"
|
||||
class="w181"
|
||||
:placeholder="t('auth.select')"
|
||||
>
|
||||
<el-option
|
||||
@ -586,11 +587,15 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
}
|
||||
|
||||
.w100.ed-select {
|
||||
width: 100px;
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
.w181.ed-select {
|
||||
width: 181px !important;
|
||||
}
|
||||
|
||||
.w70 {
|
||||
width: 70px;
|
||||
width: 70px !important;
|
||||
}
|
||||
|
||||
.mar5 {
|
||||
@ -676,11 +681,12 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ed-input__wrapper) {
|
||||
:deep(.ed-input__wrapper),
|
||||
:deep(.ed-select__wrapper) {
|
||||
background-color: #f8f8fa;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
box-shadow: none !important;
|
||||
height: 26px;
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
word-wrap: break-word;
|
||||
@ -785,7 +791,7 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
.ed-input__wrapper {
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
|
||||
|
||||
&:focus {
|
||||
border: 1px solid #0089ff;
|
||||
border-right-width: 1px !important;
|
||||
|
@ -55,13 +55,12 @@ import SortPriorityEdit from '@/views/chart/components/editor/drag-item/componen
|
||||
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||
import CalcFieldEdit from '@/views/visualized/data/dataset/form/CalcFieldEdit.vue'
|
||||
import { getFieldName, guid } from '@/views/visualized/data/dataset/form/util'
|
||||
import { cloneDeep, forEach, get } from 'lodash-es'
|
||||
import { cloneDeep, forEach, get, debounce, set, concat, keys } from 'lodash-es'
|
||||
import { deleteField, saveField } from '@/api/dataset'
|
||||
import { getWorldTree, listCustomGeoArea } from '@/api/map'
|
||||
import chartViewManager from '@/views/chart/components/js/panel'
|
||||
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
|
||||
import { useDraggable } from '@vueuse/core'
|
||||
import { set, concat, keys } from 'lodash-es'
|
||||
import { PluginComponent } from '@/components/plugin'
|
||||
import { Field, getFieldByDQ, copyChartField, deleteChartField } from '@/api/chart'
|
||||
import ChartTemplateInfo from '@/views/chart/components/editor/common/ChartTemplateInfo.vue'
|
||||
@ -104,6 +103,10 @@ const { emitter } = useEmitt({
|
||||
name: 'set-table-column-width',
|
||||
callback: args => onTableColumnWidthChange(args)
|
||||
})
|
||||
useEmitt({
|
||||
name: 'set-page-size',
|
||||
callback: args => onTablePageSizeChange(args)
|
||||
})
|
||||
const props = defineProps({
|
||||
view: {
|
||||
type: Object as PropType<ChartObj>,
|
||||
@ -171,11 +174,15 @@ const editComponentName = () => {
|
||||
})
|
||||
}
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
return props.themes || 'dark'
|
||||
})
|
||||
|
||||
const templateStatusShow = computed(() => {
|
||||
return view.value['dataFrom'] === 'template' && !mobileInPc.value
|
||||
return (
|
||||
view.value['dataFrom'] === 'template' &&
|
||||
view.value.type !== 'picture-group' &&
|
||||
!mobileInPc.value
|
||||
)
|
||||
})
|
||||
|
||||
const { view } = toRefs(props)
|
||||
@ -208,7 +215,7 @@ const isDataEaseBi = computed(() => appStore.getIsDataEaseBi || appStore.getIsIf
|
||||
const itemFormRules = reactive<FormRules>({
|
||||
chartShowName: [
|
||||
{ required: true, message: t('commons.input_content'), trigger: 'change' },
|
||||
{ max: 50, message: t('commons.char_can_not_more_50'), trigger: 'change' }
|
||||
{ max: 200, message: t('commons.char_count_limit', { count: 200 }), trigger: 'change' }
|
||||
]
|
||||
})
|
||||
|
||||
@ -916,7 +923,16 @@ const calcData = (view, resetDrill = false, updateQuery = '') => {
|
||||
if (resetDrill) {
|
||||
useEmitt().emitter.emit('resetDrill-' + view.id, 0)
|
||||
} else {
|
||||
useEmitt().emitter.emit('calcData-' + view.id, view)
|
||||
if (mobileInPc.value) {
|
||||
//移动端设计
|
||||
useEmitt().emitter.emit('onMobileStatusChange', {
|
||||
type: 'componentStyleChange',
|
||||
value: { type: 'calcData', component: JSON.parse(JSON.stringify(view)) }
|
||||
})
|
||||
} else {
|
||||
useEmitt().emitter.emit('calcData-' + view.id, view)
|
||||
snapshotStore.recordSnapshotCache('renderChart', view.id)
|
||||
}
|
||||
}
|
||||
snapshotStore.recordSnapshotCache('calcData', view.id)
|
||||
if (updateQuery === 'updateQuery') {
|
||||
@ -1026,12 +1042,13 @@ const onTypeChange = (render, type) => {
|
||||
}
|
||||
|
||||
const onBasicStyleChange = (chartForm: ChartEditorForm<ChartBasicStyle>, prop: string) => {
|
||||
const { data, requestData } = chartForm
|
||||
const { data, requestData, render } = chartForm
|
||||
const val = get(data, prop)
|
||||
set(view.value.customAttr.basicStyle, prop, val)
|
||||
if (requestData) {
|
||||
calcData(view.value)
|
||||
} else {
|
||||
}
|
||||
if (render !== false) {
|
||||
renderChart(view.value)
|
||||
}
|
||||
}
|
||||
@ -1064,7 +1081,7 @@ const onMiscChange = val => {
|
||||
}
|
||||
|
||||
const onLabelChange = (chartForm: ChartEditorForm<ChartLabelAttr>, prop: string) => {
|
||||
const { data, requestData, render } = chartForm
|
||||
const { data, render } = chartForm
|
||||
let labelObj = data
|
||||
if (!data) {
|
||||
labelObj = chartForm as unknown as ChartLabelAttr
|
||||
@ -1250,6 +1267,14 @@ const onTableColumnWidthChange = val => {
|
||||
snapshotStore.recordSnapshotCache('renderChart', view.value.id)
|
||||
}
|
||||
|
||||
const onTablePageSizeChange = val => {
|
||||
if (editMode.value !== 'edit') {
|
||||
return
|
||||
}
|
||||
view.value.customAttr.basicStyle.tablePageSize = val
|
||||
snapshotStore.recordSnapshotCache('renderChart', view.value.id)
|
||||
}
|
||||
|
||||
const onExtTooltipChange = val => {
|
||||
view.value.extTooltip = val
|
||||
}
|
||||
@ -1504,13 +1529,18 @@ const addDsWindow = () => {
|
||||
const editDs = () => {
|
||||
const path =
|
||||
embeddedStore.getToken && appStore.getIsIframe ? 'dataset-embedded-form' : '/dataset-form'
|
||||
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
|
||||
// 此处校验提前 防止router返回时找到错误的路径
|
||||
if (openType === '_self' && !dvInfo.value.id) {
|
||||
ElMessage.warning(t('visualization.save_page_tips'))
|
||||
return
|
||||
}
|
||||
let routeData = router.resolve({
|
||||
path: path,
|
||||
query: {
|
||||
id: view.value.tableId
|
||||
}
|
||||
})
|
||||
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
|
||||
// 检查是否保存
|
||||
if (openType === '_self') {
|
||||
if (!dvInfo.value.id) {
|
||||
@ -1518,6 +1548,7 @@ const editDs = () => {
|
||||
return
|
||||
}
|
||||
canvasSave(() => {
|
||||
wsCache.delete('DE-DV-CATCH-' + dvInfo.value.id)
|
||||
const newWindow = window.open(routeData.href, openType)
|
||||
initOpenHandler(newWindow)
|
||||
})
|
||||
@ -1575,6 +1606,7 @@ const closeSortPriority = () => {
|
||||
}
|
||||
const saveSortPriority = () => {
|
||||
view.value.sortPriority = state.sortPriority as ChartViewField[]
|
||||
recordSnapshotInfo('render')
|
||||
closeSortPriority()
|
||||
}
|
||||
const onPriorityChange = val => {
|
||||
@ -1710,14 +1742,14 @@ const { y, isDragging } = useDraggable(el, {
|
||||
draggingElement: elDrag
|
||||
})
|
||||
const previewHeight = ref(0)
|
||||
const calcEle = () => {
|
||||
const calcEle = debounce(() => {
|
||||
nextTick(() => {
|
||||
previewHeight.value = (elDrag.value as HTMLDivElement).offsetHeight
|
||||
y.value = previewHeight.value / 2 + 200
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
|
||||
const setCacheId = () => {
|
||||
const setCacheId = debounce(() => {
|
||||
nextTick(() => {
|
||||
// 富文本不使用cacheId
|
||||
if (
|
||||
@ -1729,7 +1761,7 @@ const setCacheId = () => {
|
||||
return
|
||||
view.value.tableId = cacheId as unknown as number
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
watch(
|
||||
() => curComponent.value,
|
||||
val => {
|
||||
@ -1831,7 +1863,7 @@ const setActiveShift = (ele, type = 'dimension') => {
|
||||
|
||||
const isDrag = ref(false)
|
||||
|
||||
const dragStartD = (e: DragEvent) => {
|
||||
const dragStartD = () => {
|
||||
isDrag.value = true
|
||||
setTimeout(() => {
|
||||
isDraggingItem.value = true
|
||||
@ -1848,7 +1880,7 @@ const singleDragStartD = (e: DragEvent, ele, type) => {
|
||||
startToMove(e, unref(activeDimension.value))
|
||||
}
|
||||
|
||||
const dragStart = (e: DragEvent) => {
|
||||
const dragStart = () => {
|
||||
isDrag.value = true
|
||||
setTimeout(() => {
|
||||
isDraggingItem.value = true
|
||||
@ -2040,10 +2072,10 @@ const deleteChartFieldItem = id => {
|
||||
><Icon><dvInfoSvg class="svg-icon" /></Icon
|
||||
></el-icon>
|
||||
</template>
|
||||
<div style="margin-bottom: 4px; font-size: 14px; color: #fff">
|
||||
<div style="margin-bottom: 4px; font-size: 14px; color: #646a73">
|
||||
{{ t('visualization.view_id') }}
|
||||
</div>
|
||||
<div style="font-size: 14px; color: #fff">
|
||||
<div style="font-size: 14px; color: #1f2329">
|
||||
{{ view.id }}
|
||||
</div>
|
||||
</el-popover>
|
||||
@ -3828,7 +3860,7 @@ const deleteChartFieldItem = id => {
|
||||
</div>
|
||||
</el-row>
|
||||
</template>
|
||||
<chart-template-info v-if="templateStatusShow"></chart-template-info>
|
||||
<chart-template-info v-if="templateStatusShow" :themes="themes"></chart-template-info>
|
||||
<!--显示名修改-->
|
||||
<el-dialog
|
||||
v-model="state.renameItem"
|
||||
@ -4111,11 +4143,11 @@ const deleteChartFieldItem = id => {
|
||||
}
|
||||
|
||||
.editor-light {
|
||||
border-left: solid 1px @side-outline-border-color-light;
|
||||
color: @canvas-main-font-color-light;
|
||||
background-color: @side-area-background-light;
|
||||
border-left: solid 1px @side-outline-border-color-light !important;
|
||||
color: @canvas-main-font-color-light!important;
|
||||
background-color: @side-area-background-light!important;
|
||||
:deep(.ed-tabs__header) {
|
||||
border-top: solid 1px @side-outline-border-color-light;
|
||||
border-top: solid 1px @side-outline-border-color-light !important;
|
||||
}
|
||||
:deep(.drag_main_area) {
|
||||
border-top: solid 1px @side-outline-border-color-light !important;
|
||||
@ -4130,7 +4162,7 @@ const deleteChartFieldItem = id => {
|
||||
border-top: 1px solid @side-outline-border-color-light !important;
|
||||
}
|
||||
:deep(.dataset-main) {
|
||||
border-left: 1px solid @side-outline-border-color-light;
|
||||
border-left: 1px solid @side-outline-border-color-light !important;
|
||||
}
|
||||
:deep(input) {
|
||||
font-size: 12px;
|
||||
@ -4140,14 +4172,14 @@ const deleteChartFieldItem = id => {
|
||||
background-color: @side-outline-border-color-light !important;
|
||||
}
|
||||
:deep(.item-span-style) {
|
||||
color: @canvas-main-font-color-light;
|
||||
color: @canvas-main-font-color-light!important;
|
||||
}
|
||||
|
||||
:deep(.editor-title) {
|
||||
color: #1f2329;
|
||||
color: #1f2329 !important;
|
||||
}
|
||||
:deep(.collapse-title) {
|
||||
color: #1f2329;
|
||||
color: #1f2329 !important;
|
||||
}
|
||||
:deep(.collapse-icon) {
|
||||
color: #646a73 !important;
|
||||
@ -4214,7 +4246,7 @@ const deleteChartFieldItem = id => {
|
||||
|
||||
// editor form 全局样式
|
||||
.editor-dark {
|
||||
border-left: solid 1px @main-collapse-border-dark;
|
||||
border-left: solid 1px @main-collapse-border-dark !important;
|
||||
.dataset-selector {
|
||||
:deep(.ed-input__inner),
|
||||
:deep(.ed-input__wrapper),
|
||||
@ -4227,12 +4259,10 @@ const deleteChartFieldItem = id => {
|
||||
border: none;
|
||||
}
|
||||
:deep(.ed-input__wrapper) {
|
||||
box-shadow: none;
|
||||
border: 1px solid #636363;
|
||||
box-shadow: 0 0 0 1px hsla(0, 0%, 100%, 0.15) inset !important;
|
||||
}
|
||||
:deep(.ed-input__wrapper:hover) {
|
||||
box-shadow: none;
|
||||
border: 1px solid #3370ff;
|
||||
box-shadow: 0 0 0 1px var(--ed-color-primary, #3370ff) inset !important;
|
||||
}
|
||||
}
|
||||
.query-style-tab {
|
||||
@ -4316,63 +4346,8 @@ span {
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
|
||||
:deep(.ed-collapse-item__header) {
|
||||
height: 36px !important;
|
||||
line-height: 36px !important;
|
||||
font-size: 12px !important;
|
||||
padding: 0 !important;
|
||||
font-weight: 500 !important;
|
||||
border-top: unset;
|
||||
|
||||
&.is-active {
|
||||
border-bottom-color: var(--ed-collapse-border-color);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.ed-collapse-item__arrow {
|
||||
margin: 0 6px 0 8px;
|
||||
|
||||
&.is-active {
|
||||
color: #A6A6A6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ed-collapse-item__content) {
|
||||
padding: 16px 10px 0;
|
||||
border: none;
|
||||
:deep(.ed-checkbox) {
|
||||
height: 20px;
|
||||
}
|
||||
.ed-checkbox {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.style-dark) {
|
||||
.ed-collapse-item__header {
|
||||
&.is-active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ed-collapse-item__arrow {
|
||||
&.is-active {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ed-collapse-item.ed-collapse--dark .ed-collapse-item__header) {
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
|
||||
&.is-active {
|
||||
color: #fff;
|
||||
}
|
||||
.ed-collapse-item__arrow {
|
||||
&.is-active {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4397,11 +4372,14 @@ span {
|
||||
font-size: 12px;
|
||||
padding: 0 8px !important;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
:deep(.ed-tabs__item:not(.is-active)) {
|
||||
color: var(--custom-tab-color);
|
||||
}
|
||||
:deep(.is-active) {
|
||||
|
||||
:deep(.ed-tabs__item.is-active) {
|
||||
font-weight: 500;
|
||||
color: var(--ed-color-primary, #3370ff);
|
||||
}
|
||||
|
||||
:deep(.ed-tabs__nav-scroll) {
|
||||
@ -4736,6 +4714,7 @@ span {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8px;
|
||||
line-height: 22px;
|
||||
|
||||
span {
|
||||
width: calc(100% - 24px);
|
||||
@ -4771,7 +4750,7 @@ span {
|
||||
.result-style-dark {
|
||||
:deep(.ed-button) {
|
||||
color: #ffffff;
|
||||
background-color: #0089ff !important;
|
||||
background-color: var(--ed-color-primary, #3370ff);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
@ -4807,7 +4786,6 @@ span {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
background-color: #0089ff;
|
||||
}
|
||||
|
||||
.switch-chart-dark {
|
||||
@ -4831,7 +4809,7 @@ span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #fff;
|
||||
color: #1f2329;
|
||||
font-weight: 500;
|
||||
|
||||
&.dark {
|
||||
@ -5085,6 +5063,7 @@ span {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.style-collapse {
|
||||
@ -5121,7 +5100,7 @@ span {
|
||||
.field-setting {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
color: #a6a6a6;
|
||||
color: #646a73;
|
||||
&.remove-icon--dark {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
@ -5160,10 +5139,13 @@ span {
|
||||
.chart-type-select {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
:deep(.ed-input__prefix-inner > div) {
|
||||
:deep(.ed-select__prefix) {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
height: 20px;
|
||||
.chart-type-select-icon {
|
||||
width: 23px;
|
||||
height: 16px;
|
||||
@ -5211,6 +5193,9 @@ span {
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.ed-dropdown__popper.ed-popper.is-dark:has(.dark-dimension-quota) {
|
||||
border: none;
|
||||
}
|
||||
:deep(.ed-select-dropdown__item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -5253,7 +5238,7 @@ span {
|
||||
position: relative;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
font-size: 14px;
|
||||
font-size: 14px !important;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
input {
|
||||
@ -5320,7 +5305,7 @@ span {
|
||||
.ed-input--dark .ed-input__wrapper.is-focus{
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.ed-select .ed-input__wrapper:hover{
|
||||
.ed-select .ed-input__wrapper:hover{
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.ed-select .ed-input__wrapper.is-focus{
|
||||
@ -5403,4 +5388,4 @@ span {
|
||||
.config-hidden .wrapper-inner-adaptor:hover{
|
||||
border-color: #0089ff !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { deepCopy } from '@/utils/utils'
|
||||
import { formatterItem } from '@/views/chart/components/js/formatter'
|
||||
import { formatterItem, isEnLocal } from '@/views/chart/components/js/formatter'
|
||||
const { t } = useI18n()
|
||||
|
||||
export const DEFAULT_COLOR_CASE: DeepPartial<ChartAttr> = {
|
||||
@ -318,6 +318,32 @@ export const DEFAULT_MISC: ChartMiscAttr = {
|
||||
min: 0,
|
||||
max: 0,
|
||||
fieldId: undefined
|
||||
},
|
||||
bullet: {
|
||||
bar: {
|
||||
ranges: {
|
||||
fill: ['rgba(0,128,255,0.3)'],
|
||||
size: 20,
|
||||
showType: 'dynamic',
|
||||
fixedRangeNumber: 3,
|
||||
symbol: 'circle',
|
||||
symbolSize: 4
|
||||
},
|
||||
measures: {
|
||||
fill: ['rgba(0,128,255,1)'],
|
||||
size: 15,
|
||||
symbol: 'circle',
|
||||
symbolSize: 4
|
||||
},
|
||||
target: {
|
||||
fill: 'rgb(0,0,0)',
|
||||
size: 20,
|
||||
showType: 'dynamic',
|
||||
value: 0,
|
||||
symbol: 'line',
|
||||
symbolSize: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,7 +479,8 @@ export const DEFAULT_TABLE_HEADER: ChartTableHeaderAttr = {
|
||||
headerGroupConfig: {
|
||||
columns: [],
|
||||
meta: []
|
||||
}
|
||||
},
|
||||
rowHeaderFreeze: true
|
||||
}
|
||||
export const DEFAULT_TABLE_CELL: ChartTableCellAttr = {
|
||||
tableFontColor: '#000000',
|
||||
@ -552,17 +579,6 @@ export const DEFAULT_TITLE_STYLE_DARK = {
|
||||
remarkBackgroundColor: '#5A5C62'
|
||||
}
|
||||
|
||||
export const DEFAULT_LEGEND_STYLE: ChartLegendStyle = {
|
||||
show: true,
|
||||
hPosition: 'center',
|
||||
vPosition: 'bottom',
|
||||
orient: 'horizontal',
|
||||
icon: 'circle',
|
||||
color: '#333333',
|
||||
fontSize: 12,
|
||||
size: 4
|
||||
}
|
||||
|
||||
export const DEFAULT_LEGEND_STYLE_BASE: ChartLegendStyle = {
|
||||
show: true,
|
||||
hPosition: 'center',
|
||||
@ -571,7 +587,24 @@ export const DEFAULT_LEGEND_STYLE_BASE: ChartLegendStyle = {
|
||||
icon: 'circle',
|
||||
color: '#333333',
|
||||
fontSize: 12,
|
||||
size: 4
|
||||
size: 4,
|
||||
showRange: true,
|
||||
sort: 'none',
|
||||
customSort: []
|
||||
}
|
||||
|
||||
export const DEFAULT_LEGEND_STYLE: ChartLegendStyle = {
|
||||
show: true,
|
||||
hPosition: 'center',
|
||||
vPosition: 'bottom',
|
||||
orient: 'horizontal',
|
||||
icon: 'circle',
|
||||
color: '#333333',
|
||||
fontSize: 12,
|
||||
size: 4,
|
||||
showRange: true,
|
||||
sort: 'none',
|
||||
customSort: []
|
||||
}
|
||||
|
||||
export const DEFAULT_LEGEND_STYLE_LIGHT: ChartLegendStyle = {
|
||||
@ -634,6 +667,7 @@ export const DEFAULT_XAXIS_STYLE: ChartAxisStyle = {
|
||||
},
|
||||
axisLabelFormatter: {
|
||||
type: 'auto',
|
||||
unitLanguage: isEnLocal ? 'en' : 'ch',
|
||||
unit: 1,
|
||||
suffix: '',
|
||||
decimalCount: 2,
|
||||
@ -680,6 +714,7 @@ export const DEFAULT_YAXIS_STYLE: ChartAxisStyle = {
|
||||
},
|
||||
axisLabelFormatter: {
|
||||
type: 'auto',
|
||||
unitLanguage: isEnLocal ? 'en' : 'ch',
|
||||
unit: 1,
|
||||
suffix: '',
|
||||
decimalCount: 2,
|
||||
@ -724,6 +759,7 @@ export const DEFAULT_YAXIS_EXT_STYLE: ChartAxisStyle = {
|
||||
},
|
||||
axisLabelFormatter: {
|
||||
type: 'auto',
|
||||
unitLanguage: isEnLocal ? 'en' : 'ch',
|
||||
unit: 1,
|
||||
suffix: '',
|
||||
decimalCount: 2,
|
||||
@ -1395,6 +1431,13 @@ export const CHART_TYPE_CONFIGS = [
|
||||
value: 'stock-line',
|
||||
title: t('chart.chart_stock_line'),
|
||||
icon: 'stock-line'
|
||||
},
|
||||
{
|
||||
render: 'antv',
|
||||
category: 'compare',
|
||||
value: 'bullet-graph',
|
||||
title: t('chart.bullet_chart'),
|
||||
icon: 'bullet-graph'
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1651,6 +1694,7 @@ export const DEFAULT_BASIC_STYLE: ChartBasicStyle = {
|
||||
zoomButtonColor: '#aaa',
|
||||
zoomBackground: '#fff',
|
||||
tableLayoutMode: 'grid',
|
||||
defaultExpandLevel: 1,
|
||||
calcTopN: false,
|
||||
topN: 5,
|
||||
topNLabel: t('datasource.other'),
|
||||
@ -1679,7 +1723,9 @@ export const DEFAULT_BASIC_STYLE: ChartBasicStyle = {
|
||||
radarAreaColor: true,
|
||||
circleBorderColor: '#fff',
|
||||
circleBorderWidth: 0,
|
||||
circlePadding: 0
|
||||
circlePadding: 0,
|
||||
quotaPosition: 'col',
|
||||
quotaColLabel: t('dataset.value')
|
||||
}
|
||||
|
||||
export const BASE_VIEW_CONFIG = {
|
||||
|
@ -61,6 +61,7 @@ export const MOBILE_SETTING_DARK = {
|
||||
export const DEFAULT_DASHBOARD_STYLE_BASE = {
|
||||
gap: 'yes',
|
||||
gapSize: 5,
|
||||
gapMode: 'middle',
|
||||
showGrid: false,
|
||||
matrixBase: 4, // 当前matrix的基数 (是pcMatrixCount的几倍)
|
||||
resultMode: 'all', // 图表结果显示模式 all 图表 custom 仪表板自定义
|
||||
|
@ -79,7 +79,8 @@ function createExtremumDiv(id, value, formatterCfg, chart) {
|
||||
transform: translateX(-50%);
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
white-space:nowrap;`
|
||||
white-space:nowrap;
|
||||
overflow:auto;`
|
||||
)
|
||||
div.textContent = valueFormatter(value, formatterCfg)
|
||||
const span = document.createElement('span')
|
||||
@ -109,7 +110,7 @@ const noChildrenFieldChart = chart => {
|
||||
* 支持最值图表的折线图,面积图,柱状图,分组柱状图
|
||||
* @param chart
|
||||
*/
|
||||
const supportExtremumChartType = chart => {
|
||||
export const supportExtremumChartType = chart => {
|
||||
return ['line', 'area', 'bar', 'bar-group'].includes(chart.type)
|
||||
}
|
||||
|
||||
@ -138,8 +139,8 @@ function removeDivsWithPrefix(parentDivId, prefix) {
|
||||
|
||||
export const extremumEvt = (newChart, chart, _options, container) => {
|
||||
chart.container = container
|
||||
clearExtremum(chart)
|
||||
if (!supportExtremumChartType(chart)) {
|
||||
clearExtremum(chart)
|
||||
return
|
||||
}
|
||||
const { label: labelAttr } = parseJson(chart.customAttr)
|
||||
@ -150,7 +151,9 @@ export const extremumEvt = (newChart, chart, _options, container) => {
|
||||
i.forEach(item => {
|
||||
delete item._origin.EXTREME
|
||||
})
|
||||
const { minItem, maxItem } = findMinMax(i.filter(item => item._origin.value))
|
||||
const { minItem, maxItem } = findMinMax(
|
||||
i.filter(item => item?._origin?.value !== null && item?._origin?.value !== undefined)
|
||||
)
|
||||
if (!minItem || !maxItem) {
|
||||
return
|
||||
}
|
||||
@ -223,6 +226,7 @@ export const createExtremumPoint = (chart, ev) => {
|
||||
divParent.style.zIndex = '1'
|
||||
divParent.style.opacity = '0'
|
||||
divParent.style.transition = 'opacity 0.2s ease-in-out'
|
||||
divParent.style.overflow = 'visible'
|
||||
// 将父标注加入到图表中
|
||||
const containerElement = document.getElementById(chart.container)
|
||||
containerElement.insertBefore(divParent, containerElement.firstChild)
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { Datum } from '@antv/g2plot'
|
||||
import { find } from 'lodash-es'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { getLocale } from '@/utils/utils'
|
||||
const { t } = useI18n()
|
||||
|
||||
export const isEnLocal = !['zh', 'zh-cn', 'zh-CN', 'tw'].includes(getLocale())
|
||||
|
||||
export const formatterItem = {
|
||||
type: 'auto', // auto,value,percent
|
||||
unitLanguage: isEnLocal ? 'en' : 'ch',
|
||||
unit: 1, // 换算单位
|
||||
suffix: '', // 单位后缀
|
||||
decimalCount: 2, // 小数位数
|
||||
@ -10,12 +16,51 @@ export const formatterItem = {
|
||||
|
||||
// 单位list
|
||||
export const unitType = [
|
||||
{ name: 'unit_none', value: 1 },
|
||||
{ name: 'unit_thousand', value: 1000 },
|
||||
{ name: 'unit_ten_thousand', value: 10000 },
|
||||
{ name: 'unit_million', value: 1000000 },
|
||||
{ name: 'unit_hundred_million', value: 100000000 }
|
||||
{ name: t('chart.unit_none'), value: 1 },
|
||||
{ name: t('chart.unit_thousand'), value: 1000 },
|
||||
{ name: t('chart.unit_ten_thousand'), value: 10000 },
|
||||
{ name: t('chart.unit_million'), value: 1000000 },
|
||||
{ name: t('chart.unit_hundred_million'), value: 100000000 }
|
||||
]
|
||||
export const unitEnType = [
|
||||
{ name: 'None', value: 1 },
|
||||
{ name: 'Thousand (K)', value: 1000 },
|
||||
{ name: 'Million (M)', value: 1000000 },
|
||||
{ name: 'Billion (B)', value: 1000000000 }
|
||||
]
|
||||
|
||||
export function getUnitTypeList(lang) {
|
||||
if (isEnLocal) {
|
||||
return unitEnType
|
||||
}
|
||||
if (lang === 'ch') {
|
||||
return unitType
|
||||
}
|
||||
return unitEnType
|
||||
}
|
||||
|
||||
export function getUnitTypeValue(lang, value) {
|
||||
const list = getUnitTypeList(lang)
|
||||
const item = find(list, l => l.value === value)
|
||||
if (item) {
|
||||
return value
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
export function initFormatCfgUnit(cfg) {
|
||||
if (cfg && cfg.unitLanguage === undefined) {
|
||||
cfg.unitLanguage = 'ch'
|
||||
}
|
||||
if (cfg && isEnLocal) {
|
||||
cfg.unitLanguage = 'en'
|
||||
}
|
||||
onChangeFormatCfgUnitLanguage(cfg, cfg.unitLanguage)
|
||||
}
|
||||
|
||||
export function onChangeFormatCfgUnitLanguage(cfg, lang) {
|
||||
cfg.unit = getUnitTypeValue(lang, cfg.unit)
|
||||
}
|
||||
|
||||
// 格式化方式
|
||||
export const formatterType = [
|
||||
@ -47,17 +92,32 @@ export function valueFormatter(value, formatter) {
|
||||
}
|
||||
|
||||
function transUnit(value, formatter) {
|
||||
initFormatCfgUnit(formatter)
|
||||
return value / formatter.unit
|
||||
}
|
||||
|
||||
function transDecimal(value, formatter) {
|
||||
const resultV = value.toFixed(formatter.decimalCount)
|
||||
const resultV = retain(value, formatter.decimalCount) as string
|
||||
if (Object.is(parseFloat(resultV), -0)) {
|
||||
return resultV.slice(1)
|
||||
}
|
||||
return resultV
|
||||
}
|
||||
|
||||
function retain(value, n) {
|
||||
if (!n) return Math.round(value)
|
||||
const tran = Math.round(value * Math.pow(10, n)) / Math.pow(10, n)
|
||||
let tranV = tran.toString()
|
||||
const newVal = tranV.indexOf('.')
|
||||
if (newVal < 0) {
|
||||
tranV += '.'
|
||||
}
|
||||
for (let i = tranV.length - tranV.indexOf('.'); i <= n; i++) {
|
||||
tranV += '0'
|
||||
}
|
||||
return tranV
|
||||
}
|
||||
|
||||
function transSeparatorAndSuffix(value, formatter) {
|
||||
let str = value + ''
|
||||
if (str.match(/^(\d)(\.\d)?e-(\d)/)) {
|
||||
@ -74,34 +134,27 @@ function transSeparatorAndSuffix(value, formatter) {
|
||||
//百分比没有后缀,直接返回
|
||||
return str
|
||||
} else {
|
||||
if (formatter.unit === 1000) {
|
||||
str += '千'
|
||||
} else if (formatter.unit === 10000) {
|
||||
str += '万'
|
||||
} else if (formatter.unit === 1000000) {
|
||||
str += '百万'
|
||||
} else if (formatter.unit === 100000000) {
|
||||
str += '亿'
|
||||
const unit = formatter.unit
|
||||
|
||||
if (formatter.unitLanguage === 'ch') {
|
||||
if (unit === 1000) {
|
||||
str += t('chart.unit_thousand')
|
||||
} else if (unit === 10000) {
|
||||
str += t('chart.unit_ten_thousand')
|
||||
} else if (unit === 1000000) {
|
||||
str += t('chart.unit_million')
|
||||
} else if (unit === 100000000) {
|
||||
str += t('chart.unit_hundred_million')
|
||||
}
|
||||
} else {
|
||||
if (unit === 1000) {
|
||||
str += 'K'
|
||||
} else if (unit === 1000000) {
|
||||
str += 'M'
|
||||
} else if (unit === 1000000000) {
|
||||
str += 'B'
|
||||
}
|
||||
}
|
||||
}
|
||||
return str + formatter.suffix.replace(/(^\s*)|(\s*$)/g, '')
|
||||
}
|
||||
|
||||
export function singleDimensionTooltipFormatter(param: Datum, chart: Chart, prop = 'category') {
|
||||
let res
|
||||
const yAxis = chart.yAxis
|
||||
const obj = { name: param[prop], value: param.value }
|
||||
for (let i = 0; i < yAxis.length; i++) {
|
||||
const f = yAxis[i]
|
||||
if (f.name === param[prop]) {
|
||||
if (f.formatterCfg) {
|
||||
res = valueFormatter(param.value, f.formatterCfg)
|
||||
} else {
|
||||
res = valueFormatter(param.value, formatterItem)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
obj.value = res ?? ''
|
||||
return obj
|
||||
}
|
||||
|
@ -0,0 +1,656 @@
|
||||
import { DualAxes, Plot } from '@antv/g2plot'
|
||||
|
||||
/**
|
||||
* 使用 Map 来存储实例,键为 chart.container 对象
|
||||
*/
|
||||
export const CAROUSEL_MANAGER_INSTANCES = new Map<string, ChartCarouselTooltip>()
|
||||
/**
|
||||
* 支持的图表类型
|
||||
*/
|
||||
const CHART_CATEGORY = {
|
||||
COLUMN: ['bar', 'bar-stack', 'bar-group', 'bar-group-stack', 'percentage-bar-stack'],
|
||||
LINE: ['line', 'area', 'area-stack'],
|
||||
MIX: ['chart-mix', 'chart-mix-group', 'chart-mix-stack', 'chart-mix-dual-line'],
|
||||
PIE: ['pie', 'pie-donut']
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为柱状图
|
||||
* @param chartType
|
||||
*/
|
||||
export function isColumn(chartType: string) {
|
||||
return CHART_CATEGORY.COLUMN.includes(chartType)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为折线图
|
||||
* @param chartType
|
||||
*/
|
||||
export function isLine(chartType: string) {
|
||||
return CHART_CATEGORY.LINE.includes(chartType)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为饼图
|
||||
* @param chartType
|
||||
*/
|
||||
export function isPie(chartType: string) {
|
||||
return CHART_CATEGORY.PIE.includes(chartType)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为组合图
|
||||
* @param chartType
|
||||
*/
|
||||
export function isMix(chartType: string) {
|
||||
return CHART_CATEGORY.MIX.includes(chartType)
|
||||
}
|
||||
|
||||
export function isSupport(chartType: string) {
|
||||
return Object.values(CHART_CATEGORY).some(category => category.includes(chartType))
|
||||
}
|
||||
|
||||
// 轮播配置默认值
|
||||
const DEFAULT_CAROUSEL_CONFIG: Required<CarouselConfig> = {
|
||||
xField: '',
|
||||
duration: 2000,
|
||||
interval: 2000,
|
||||
loop: true
|
||||
}
|
||||
|
||||
type CarouselConfig = {
|
||||
xField: string
|
||||
duration?: number
|
||||
interval?: number
|
||||
loop?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 图表轮播提示管理类
|
||||
* */
|
||||
class ChartCarouselTooltip {
|
||||
private plot: Plot | DualAxes
|
||||
private config: Required<CarouselConfig>
|
||||
private currentIndex = 0
|
||||
private values: string[] = []
|
||||
// 合并定时器管理
|
||||
private timers = { interval: null, carousel: null }
|
||||
private states = { paused: false, destroyed: false }
|
||||
// 图表可视性变化
|
||||
private observers: Map<string, IntersectionObserver> = new Map()
|
||||
// 图表元素大小变化
|
||||
private resizeObservers: Map<string, ResizeObserver> = new Map()
|
||||
// 图表是否在可视范围内
|
||||
private chartIsVisible: boolean
|
||||
|
||||
private constructor(plot: Plot | DualAxes, private chart: Chart, config: CarouselConfig) {
|
||||
this.plot = plot
|
||||
this.config = { ...DEFAULT_CAROUSEL_CONFIG, ...config }
|
||||
this.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新实例
|
||||
* */
|
||||
static manage(plot: Plot | DualAxes, chart: Chart, config: CarouselConfig) {
|
||||
if (!isSupport(chart.type)) return null
|
||||
const container = chart.container
|
||||
let instance = CAROUSEL_MANAGER_INSTANCES.get(container)
|
||||
|
||||
CAROUSEL_MANAGER_INSTANCES.forEach(instance => {
|
||||
if (container.includes('viewDialog')) {
|
||||
instance.paused()
|
||||
}
|
||||
})
|
||||
|
||||
if (instance) {
|
||||
instance.update(plot, chart, config)
|
||||
return instance
|
||||
}
|
||||
if (isSupport(chart.type)) {
|
||||
instance = new this(plot, chart, config)
|
||||
CAROUSEL_MANAGER_INSTANCES.set(container, instance)
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁实例
|
||||
* @param container
|
||||
*/
|
||||
static destroyByContainer(container: string) {
|
||||
const instance = CAROUSEL_MANAGER_INSTANCES.get(container)
|
||||
if (instance) {
|
||||
instance.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过容器DOM获取对应实例
|
||||
* */
|
||||
static getInstanceByContainer(container: string) {
|
||||
const instance = CAROUSEL_MANAGER_INSTANCES.get(container)
|
||||
if (instance) {
|
||||
return instance
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过chart.id销毁对应实例
|
||||
* 关闭放大图表弹窗,销毁对应实例
|
||||
* 重启图表自身轮播
|
||||
* */
|
||||
static closeEnlargeDialogDestroy(id?: string) {
|
||||
// 首先,暂停并删除包含 'viewDialog' 的实例
|
||||
CAROUSEL_MANAGER_INSTANCES?.forEach((instance, key) => {
|
||||
if (instance.chart.id === id && instance.chart.container.includes('viewDialog')) {
|
||||
const dialogInstance = CAROUSEL_MANAGER_INSTANCES.get(key)
|
||||
if (dialogInstance) {
|
||||
dialogInstance.destroy()
|
||||
}
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
// 然后,恢复
|
||||
CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
|
||||
if (instance.chartIsVisible) {
|
||||
instance.resume()
|
||||
}
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停轮播
|
||||
* @param id
|
||||
*/
|
||||
static paused(id?: string) {
|
||||
CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
|
||||
if (id && instance.chart.id === id) {
|
||||
setTimeout(() => instance.paused(), 200)
|
||||
}
|
||||
if (!id) {
|
||||
setTimeout(() => instance.paused(), 200)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id
|
||||
*/
|
||||
static resume(id?: string) {
|
||||
CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
|
||||
if (instance.chart.id === id) {
|
||||
instance.paused()
|
||||
setTimeout(() => instance.resume(), 500)
|
||||
}
|
||||
if (!id) {
|
||||
setTimeout(() => instance.resume(), 200)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化核心逻辑
|
||||
* */
|
||||
private init() {
|
||||
this.values = [].concat(this.getUniqueValues())
|
||||
if (!this.values.length) return
|
||||
this.chartIsVisible = true
|
||||
this.states.paused = false
|
||||
this.states.destroyed = false
|
||||
this.bindEventListeners()
|
||||
this.startCarousel()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图表唯一值集合
|
||||
* */
|
||||
private getUniqueValues() {
|
||||
const data =
|
||||
this.plot instanceof DualAxes
|
||||
? [...this.plot.options.data[0], ...this.plot.options.data[1]]
|
||||
: this.plot.options.data
|
||||
|
||||
return [...new Set(data.map(item => item[this.config.xField]))]
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动轮播
|
||||
* */
|
||||
private startCarousel() {
|
||||
if (!this.shouldStart()) {
|
||||
this.stop()
|
||||
return
|
||||
}
|
||||
// 定义启动嵌套定时器的函数
|
||||
const startNestedTimers = () => {
|
||||
// 重置当前索引
|
||||
this.currentIndex = 0
|
||||
// 定义递归处理数据数组的函数
|
||||
const processArray = () => {
|
||||
if (this.states.paused || this.states.destroyed || !this.isElementFullyVisible()) return
|
||||
// 获取当前需要显示的值
|
||||
const currentValue = this.values[this.currentIndex]
|
||||
// 计算 Tooltip 显示的位置
|
||||
const point = this.calculatePosition(currentValue)
|
||||
// 高亮当前数据点
|
||||
this.highlightElement(currentValue)
|
||||
if (point) {
|
||||
// 显示 Tooltip,并设置其位置为顶部
|
||||
this.plot.chart.showTooltip(point)
|
||||
this.plot.chart.getController('tooltip').update()
|
||||
}
|
||||
// 更新索引,指向下一个数据点
|
||||
this.currentIndex++
|
||||
if (this.currentIndex > this.values.length) {
|
||||
this.currentIndex = 0
|
||||
this.hideTooltip()
|
||||
this.plot.chart.showTooltip({ x: 0, y: 0 })
|
||||
this.plot.chart.getController('tooltip').update()
|
||||
this.unHighlightPoint(currentValue)
|
||||
this.timers.interval = setTimeout(() => processArray(), this.config.interval)
|
||||
} else {
|
||||
// 如果未遍历完,继续处理下一个数据点
|
||||
this.timers.carousel = setTimeout(() => processArray(), this.config.duration)
|
||||
}
|
||||
}
|
||||
processArray()
|
||||
}
|
||||
this.stop()
|
||||
startNestedTimers()
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否满足启动条件' */
|
||||
private shouldStart() {
|
||||
return (
|
||||
this.chart.customAttr?.tooltip?.show &&
|
||||
this.chart.customAttr?.tooltip?.carousel?.enable &&
|
||||
this.values.length > 0 &&
|
||||
this.chartIsVisible
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断图表是否在可视范围内
|
||||
* */
|
||||
private isElementFullyVisible(): boolean {
|
||||
// 全屏
|
||||
const isFullscreen = document.fullscreenElement !== null
|
||||
// 新页面或公共连接
|
||||
const isNewPagePublicLink = document
|
||||
.getElementById('enlarge-inner-content-' + this.chart.id)
|
||||
?.getBoundingClientRect()
|
||||
const isMobileEdit = document.getElementsByClassName('panel-mobile')?.length > 0
|
||||
const isMobileList = document.getElementsByClassName('mobile-com-list')?.length > 0
|
||||
if (isMobileList) {
|
||||
return false
|
||||
}
|
||||
const rect = this.plot.chart.ele.getBoundingClientRect()
|
||||
return (
|
||||
rect.top >= (isFullscreen || isNewPagePublicLink || isMobileEdit ? 0 : 64) &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||
)
|
||||
}
|
||||
/**
|
||||
* 计算元素位置(核心定位逻辑)
|
||||
* */
|
||||
private calculatePosition(value: string) {
|
||||
const view = this.plot.chart.views?.[0] || this.plot.chart
|
||||
// 饼图特殊处理
|
||||
if (CHART_CATEGORY.PIE.includes(this.chart.type)) {
|
||||
return this.getPieTooltipPosition(view, value)
|
||||
}
|
||||
if (this.plot instanceof DualAxes) {
|
||||
return this.getDualAxesTooltipPosition(view, value)
|
||||
}
|
||||
const types = view
|
||||
.scale()
|
||||
.getGeometries()
|
||||
.map(item => item.type)
|
||||
let point = { x: 0, y: 0 }
|
||||
if (!types.length) return point
|
||||
types.forEach(type => {
|
||||
if (type === 'interval' || type === 'point') {
|
||||
point = view
|
||||
.scale()
|
||||
.getGeometries()
|
||||
.find(item => item.type === type)
|
||||
.elements.find(item => item.data.field === value && (item.model.x || item.model.y))?.model
|
||||
}
|
||||
})
|
||||
// 处理柱状图和折线图,柱状图固定y轴位置
|
||||
const y = CHART_CATEGORY.COLUMN.includes(this.chart.type) ? 0 : [].concat(point?.y)?.[0]
|
||||
return { x: [].concat(point?.x)?.[0], y: y }
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算饼图元素位置
|
||||
* */
|
||||
private getPieTooltipPosition(view, value: string) {
|
||||
const piePoint = view
|
||||
.scale()
|
||||
.getGeometries()[0]
|
||||
?.elements.find(item => item.data.field === value)
|
||||
?.getModel()
|
||||
if (!piePoint) {
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
const coordinates = [
|
||||
{ x: [].concat(piePoint.x)[0], y: piePoint.y[0] },
|
||||
{ x: piePoint.x[0], y: piePoint.y[1] },
|
||||
{ x: piePoint.x[1], y: piePoint.y[0] },
|
||||
{ x: piePoint.x[1], y: piePoint.y[1] }
|
||||
]
|
||||
const index = coordinates.findIndex(coord => {
|
||||
const items = this.plot.chart.getTooltipItems(coord)
|
||||
return items.some(item => item.data.field === value)
|
||||
})
|
||||
if (index !== -1) {
|
||||
return coordinates[index]
|
||||
} else {
|
||||
return {
|
||||
x: piePoint.x[0],
|
||||
y: piePoint.y[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取双轴图表的 Tooltip 位置
|
||||
* @param view
|
||||
* @param value
|
||||
* @private
|
||||
*/
|
||||
private getDualAxesTooltipPosition(view, value: string) {
|
||||
const xScale = view.getXScale()
|
||||
if (!xScale) return { x: 0, y: 0 }
|
||||
const values = xScale.values
|
||||
if (values.length < 2) {
|
||||
const point = view
|
||||
.getGeometries()?.[0]
|
||||
.elements[view.getGeometries()?.[0].elements?.length - 1].getModel()
|
||||
return point || { x: 0, y: 0 }
|
||||
}
|
||||
const [rangeStart, rangeEnd] = xScale.range
|
||||
const totalMonths = values.length
|
||||
const bandWidth = (rangeEnd - rangeStart) / totalMonths
|
||||
const index = values.indexOf(value)
|
||||
const xPos = rangeStart + bandWidth * (index + 0.5)
|
||||
return view.getCoordinate().convert({ x: xPos, y: 0 })
|
||||
}
|
||||
|
||||
/**
|
||||
* 高亮指定元素
|
||||
* */
|
||||
private highlightElement(value: string) {
|
||||
if (CHART_CATEGORY.LINE.includes(this.chart.type)) return
|
||||
this.unHighlightPoint(value)
|
||||
this.plot.setState(
|
||||
this.getHighlightType(),
|
||||
(data: any) => data[this.config.xField] === value,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消高亮
|
||||
* **/
|
||||
private unHighlightPoint(value?: string) {
|
||||
if (CHART_CATEGORY.LINE.includes(this.chart.type)) return
|
||||
this.plot.setState(
|
||||
this.getHighlightType(),
|
||||
(data: any) => data[this.config.xField] !== value,
|
||||
false
|
||||
)
|
||||
}
|
||||
private getHighlightType() {
|
||||
return 'active'
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏工具提示
|
||||
* */
|
||||
private hideTooltip() {
|
||||
const container = this.getTooltipContainer()
|
||||
if (container) {
|
||||
container.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具提示容器
|
||||
* */
|
||||
private getTooltipContainer() {
|
||||
const tooltipCtl = this.plot.chart.getController('tooltip')
|
||||
if (!tooltipCtl) {
|
||||
return
|
||||
}
|
||||
return tooltipCtl.tooltip?.cfg?.container
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定事件监听
|
||||
* */
|
||||
private bindEventListeners() {
|
||||
// 定义图表元素ID前缀数组
|
||||
// 图表在不同的显示页面可能有不同的ID前缀
|
||||
const chartElementIds = ['enlarge-inner-content-', 'enlarge-inner-shape-']
|
||||
let chartElement = null
|
||||
|
||||
// 查找图表元素
|
||||
for (const idPrefix of chartElementIds) {
|
||||
chartElement = document.getElementById(idPrefix + this.chart.id)
|
||||
if (chartElement) break
|
||||
}
|
||||
|
||||
// 绑定鼠标进入和离开事件
|
||||
chartElement?.addEventListener('mouseenter', () => this.paused())
|
||||
chartElement?.addEventListener('mouseleave', ev => {
|
||||
setTimeout(() => {
|
||||
// 获取鼠标位置
|
||||
const mouseX = ev.clientX
|
||||
const mouseY = ev.clientY
|
||||
// 获取div的边界信息
|
||||
const rect = chartElement.getBoundingClientRect()
|
||||
// 判断鼠标位置是否在div内
|
||||
const isInside =
|
||||
mouseX >= rect.left + 10 &&
|
||||
mouseX <= rect.right - 10 &&
|
||||
mouseY >= rect.top + 10 &&
|
||||
mouseY <= rect.bottom - 10
|
||||
console.log(isInside)
|
||||
if (!isInside) {
|
||||
this.paused()
|
||||
this.resume()
|
||||
}
|
||||
}, 300)
|
||||
})
|
||||
|
||||
// 定义鼠标滚轮事件处理函数
|
||||
const handleMouseWheel = this.debounce(() => {
|
||||
CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
|
||||
instance.paused()
|
||||
instance.resume()
|
||||
})
|
||||
}, 50)
|
||||
// 定义 touchmove 事件处理函数(移动端)
|
||||
const handleTouchMove = (event: TouchEvent) => {
|
||||
handleMouseWheel(event)
|
||||
}
|
||||
// 获取目标元素,优先全屏预览
|
||||
const targetDiv =
|
||||
document.getElementById('de-preview-content') ||
|
||||
document.getElementById('preview-canvas-main') ||
|
||||
document.getElementById('dv-main-center') ||
|
||||
document.getElementById('edit-canvas-main') ||
|
||||
document.getElementById('canvas-mark-line') ||
|
||||
document.getElementById('de-canvas-canvas-main')
|
||||
// 绑定目标元素的事件
|
||||
if (targetDiv) {
|
||||
targetDiv.removeEventListener('wheel', handleMouseWheel)
|
||||
targetDiv.addEventListener('wheel', handleMouseWheel)
|
||||
//移除和添加 touchmove 事件监听器(移动端)
|
||||
targetDiv.removeEventListener('touchmove', handleTouchMove)
|
||||
targetDiv.addEventListener('touchmove', handleTouchMove)
|
||||
}
|
||||
// 页面可见性控制
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
|
||||
instance.paused()
|
||||
})
|
||||
} else if (this.chartIsVisible) {
|
||||
CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
|
||||
instance.resume()
|
||||
})
|
||||
}
|
||||
})
|
||||
// 元素可视性观察(交叉观察器)
|
||||
this.setupIntersectionObserver()
|
||||
// 元素大小观察(大小观察器)
|
||||
this.setupResizeObserver()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置暂停状态
|
||||
* */
|
||||
private setPaused(state: boolean) {
|
||||
this.states.paused = state
|
||||
state ? this.stop() : this.startCarousel()
|
||||
}
|
||||
/**
|
||||
* 设置交叉观察器
|
||||
* */
|
||||
private setupIntersectionObserver() {
|
||||
setTimeout(() => {
|
||||
// 监听元素可见性变化,全部可见时开始轮播
|
||||
if (!this.observers.get(this.plot.chart.ele.id)) {
|
||||
this.observers.set(
|
||||
this.plot.chart.ele.id,
|
||||
new IntersectionObserver(
|
||||
entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.intersectionRatio < 0.7) {
|
||||
this.paused()
|
||||
this.chartIsVisible = false
|
||||
} else {
|
||||
this.paused()
|
||||
this.chartIsVisible = true
|
||||
this.resume()
|
||||
}
|
||||
})
|
||||
},
|
||||
{ threshold: [0.7] }
|
||||
)
|
||||
)
|
||||
this.observers.get(this.plot.chart.ele.id).observe(this.plot.chart.ele)
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元素大小观察器
|
||||
* 当元素全部可见时
|
||||
* 图表的最外层元素
|
||||
* @private
|
||||
*/
|
||||
private setupResizeObserver() {
|
||||
// 放大图表弹窗不需要监听
|
||||
if (this.plot.chart.ele.id.includes('viewDialog')) return
|
||||
// 创建防抖回调函数
|
||||
const debouncedCallback = (entries: ResizeObserverEntry[]) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.target) {
|
||||
this.debounce(() => {
|
||||
this.paused()
|
||||
this.resume()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 监听元素大小, 发生变化时重新轮播
|
||||
if (!this.resizeObservers.get(this.plot.chart.ele.id)) {
|
||||
this.resizeObservers.set(this.plot.chart.ele.id, new ResizeObserver(debouncedCallback))
|
||||
this.resizeObservers.get(this.plot.chart.ele.id).observe(this.plot.chart.ele)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
* */
|
||||
private update(plot: Plot | DualAxes, chart: Chart, config: CarouselConfig) {
|
||||
this.stop()
|
||||
this.plot = plot
|
||||
this.chart = chart
|
||||
this.config = { ...this.config, ...config }
|
||||
this.currentIndex = 0
|
||||
this.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时器
|
||||
* @private
|
||||
*/
|
||||
private stop() {
|
||||
clearTimeout(this.timers.interval)
|
||||
clearTimeout(this.timers.carousel)
|
||||
this.timers = { interval: null, carousel: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁实例
|
||||
* */
|
||||
destroy() {
|
||||
this.stop()
|
||||
this.clearObserver()
|
||||
this.states.destroyed = true
|
||||
CAROUSEL_MANAGER_INSTANCES.delete(this.chart.container)
|
||||
}
|
||||
/**
|
||||
* 清除观察器
|
||||
* */
|
||||
clearObserver() {
|
||||
const observer = this.observers.get(this.plot.chart.ele.id)
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
this.observers.delete(this.plot.chart.ele.id)
|
||||
}
|
||||
const resizeObservers = this.resizeObservers.get(this.plot.chart.ele.id)
|
||||
if (resizeObservers) {
|
||||
resizeObservers.disconnect()
|
||||
this.resizeObservers.delete(this.plot.chart.ele.id)
|
||||
}
|
||||
}
|
||||
/** 暂停 */
|
||||
paused() {
|
||||
this.hideTooltip()
|
||||
this.unHighlightPoint()
|
||||
this.setPaused(true)
|
||||
}
|
||||
|
||||
/** 恢复 */
|
||||
resume() {
|
||||
this.setPaused(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖
|
||||
*/
|
||||
private debounce(func: (...args: any[]) => void, delay: number): (...args: any[]) => void {
|
||||
let timeout: number | null = null
|
||||
return (...args: any[]) => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
timeout = window.setTimeout(() => {
|
||||
func(...args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ChartCarouselTooltip
|
@ -7,7 +7,6 @@ import {
|
||||
import {
|
||||
flow,
|
||||
hexColorToRGBA,
|
||||
hexToRgba,
|
||||
parseJson,
|
||||
setUpGroupSeriesColor,
|
||||
setUpStackSeriesColor
|
||||
@ -21,6 +20,7 @@ import {
|
||||
} from '@/views/chart/components/js/panel/charts/bar/common'
|
||||
import {
|
||||
configPlotTooltipEvent,
|
||||
configRoundAngle,
|
||||
getLabel,
|
||||
getPadding,
|
||||
getTooltipContainer,
|
||||
@ -43,7 +43,14 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
|
||||
...BAR_EDITOR_PROPERTY_INNER,
|
||||
'basic-style-selector': [...BAR_EDITOR_PROPERTY_INNER['basic-style-selector'], 'seriesColor'],
|
||||
'label-selector': ['vPosition', 'seriesLabelFormatter', 'showExtremum'],
|
||||
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'seriesTooltipFormatter', 'show'],
|
||||
'tooltip-selector': [
|
||||
'fontSize',
|
||||
'color',
|
||||
'backgroundColor',
|
||||
'seriesTooltipFormatter',
|
||||
'show',
|
||||
'carousel'
|
||||
],
|
||||
'y-axis-selector': [...BAR_EDITOR_PROPERTY_INNER['y-axis-selector'], 'axisLabelFormatter']
|
||||
}
|
||||
protected baseOptions: ColumnOptions = {
|
||||
@ -69,11 +76,14 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
|
||||
|
||||
async drawChart(drawOptions: G2PlotDrawOptions<Column>): Promise<Column> {
|
||||
const { chart, container, action } = drawOptions
|
||||
chart.container = container
|
||||
if (!chart?.data?.data?.length) {
|
||||
chart.container = container
|
||||
clearExtremum(chart)
|
||||
return
|
||||
}
|
||||
const isGroup = 'bar-group' === this.name && chart.xAxisExt?.length > 0
|
||||
const isStack =
|
||||
['bar-stack', 'bar-group-stack'].includes(this.name) && chart.extStack?.length > 0
|
||||
const data = cloneDeep(drawOptions.chart.data?.data)
|
||||
const initOptions: ColumnOptions = {
|
||||
...this.baseOptions,
|
||||
@ -108,7 +118,7 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
|
||||
const label = {
|
||||
fields: [],
|
||||
...tmpOptions.label,
|
||||
formatter: (data: Datum, _point) => {
|
||||
formatter: (data: Datum) => {
|
||||
if (data.EXTREME) {
|
||||
return ''
|
||||
}
|
||||
@ -174,19 +184,9 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
|
||||
color
|
||||
}
|
||||
}
|
||||
if (basicStyle.radiusColumnBar === 'roundAngle') {
|
||||
const columnStyle = {
|
||||
radius: [
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius
|
||||
]
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
columnStyle
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
...configRoundAngle(chart, 'columnStyle')
|
||||
}
|
||||
let columnWidthRatio
|
||||
const _v = basicStyle.columnWidthRatio ?? DEFAULT_BASIC_STYLE.columnWidthRatio
|
||||
@ -227,7 +227,10 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
|
||||
tickCount: axisValue.splitCount
|
||||
}
|
||||
}
|
||||
return { ...tmpOptions, ...axis }
|
||||
// 根据axis的最小值,过滤options中的data数据,过滤掉小于最小值的数据
|
||||
const { data } = options
|
||||
const newData = data.filter(item => item.value >= axisValue.min)
|
||||
return { ...tmpOptions, data: newData, ...axis }
|
||||
}
|
||||
return tmpOptions
|
||||
}
|
||||
@ -276,7 +279,14 @@ export class StackBar extends Bar {
|
||||
'totalFormatter',
|
||||
'showStackQuota'
|
||||
],
|
||||
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'show']
|
||||
'tooltip-selector': [
|
||||
'fontSize',
|
||||
'color',
|
||||
'backgroundColor',
|
||||
'tooltipFormatter',
|
||||
'show',
|
||||
'carousel'
|
||||
]
|
||||
}
|
||||
protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
|
||||
let label = getLabel(chart)
|
||||
@ -438,6 +448,74 @@ export class GroupBar extends StackBar {
|
||||
}
|
||||
}
|
||||
|
||||
async drawChart(drawOptions: G2PlotDrawOptions<Column>): Promise<Column> {
|
||||
const plot = await super.drawChart(drawOptions)
|
||||
if (!plot) {
|
||||
return plot
|
||||
}
|
||||
const { chart } = drawOptions
|
||||
const { xAxis, xAxisExt, yAxis } = chart
|
||||
let innerSort = !!(xAxis.length && xAxisExt.length && yAxis.length)
|
||||
if (innerSort && yAxis[0].sort === 'none') {
|
||||
innerSort = false
|
||||
}
|
||||
if (innerSort && xAxisExt[0].sort !== 'none') {
|
||||
const sortPriority = chart.sortPriority ?? []
|
||||
const yAxisIndex = sortPriority?.findIndex(e => e.id === yAxis[0].id)
|
||||
const xAxisExtIndex = sortPriority?.findIndex(e => e.id === xAxisExt[0].id)
|
||||
if (xAxisExtIndex <= yAxisIndex) {
|
||||
innerSort = false
|
||||
}
|
||||
}
|
||||
if (!innerSort) {
|
||||
return plot
|
||||
}
|
||||
plot.chart.once('beforepaint', () => {
|
||||
const geo = plot.chart.geometries[0]
|
||||
const originMapping = geo.beforeMapping.bind(geo)
|
||||
geo.beforeMapping = originData => {
|
||||
const values = geo.getXScale().values
|
||||
const valueMap = values.reduce((p, n) => {
|
||||
if (!p?.[n]) {
|
||||
p[n] = {
|
||||
fieldArr: [],
|
||||
indexArr: [],
|
||||
dataArr: []
|
||||
}
|
||||
}
|
||||
originData.forEach((arr, arrIndex) => {
|
||||
arr.forEach((item, index) => {
|
||||
if (item._origin.field === n) {
|
||||
p[n].fieldArr.push(item.field)
|
||||
p[n].indexArr.push([arrIndex, index])
|
||||
p[n].dataArr.push(item)
|
||||
}
|
||||
})
|
||||
})
|
||||
return p
|
||||
}, {})
|
||||
values.forEach(v => {
|
||||
const item = valueMap[v]
|
||||
item.dataArr.sort((a, b) => {
|
||||
if (yAxis[0].sort === 'asc') {
|
||||
return a.value - b.value
|
||||
}
|
||||
if (yAxis[0].sort === 'desc') {
|
||||
return b.value - a.value
|
||||
}
|
||||
return 0
|
||||
})
|
||||
item.indexArr.forEach((index, i) => {
|
||||
item.dataArr[i].field = item.fieldArr[i]
|
||||
originData[index[0]][index[1]] = item.dataArr[i]
|
||||
})
|
||||
})
|
||||
return originMapping(originData)
|
||||
}
|
||||
})
|
||||
return plot
|
||||
}
|
||||
|
||||
protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
|
||||
const tmpLabel = getLabel(chart)
|
||||
if (!tmpLabel) {
|
||||
@ -448,7 +526,7 @@ export class GroupBar extends StackBar {
|
||||
baseOptions.label.style.fill = labelAttr.color
|
||||
const label = {
|
||||
...baseOptions.label,
|
||||
formatter: function (param: Datum, _point) {
|
||||
formatter: function (param: Datum) {
|
||||
if (param.EXTREME) {
|
||||
return ''
|
||||
}
|
||||
@ -492,6 +570,7 @@ export class GroupBar extends StackBar {
|
||||
super(name)
|
||||
this.baseOptions = {
|
||||
...this.baseOptions,
|
||||
marginRatio: 0,
|
||||
isGroup: true,
|
||||
isStack: false,
|
||||
meta: {
|
||||
@ -606,7 +685,7 @@ export class PercentageStackBar extends GroupStackBar {
|
||||
propertyInner = {
|
||||
...this['propertyInner'],
|
||||
'label-selector': ['color', 'fontSize', 'vPosition', 'reserveDecimalCount'],
|
||||
'tooltip-selector': ['color', 'fontSize', 'backgroundColor', 'show']
|
||||
'tooltip-selector': ['color', 'fontSize', 'backgroundColor', 'show', 'carousel']
|
||||
}
|
||||
protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
|
||||
const baseOptions = super.configLabel(chart, options)
|
||||
|
@ -6,14 +6,14 @@ import { cloneDeep, defaultTo, isEmpty, map } from 'lodash-es'
|
||||
import {
|
||||
configAxisLabelLengthLimit,
|
||||
configPlotTooltipEvent,
|
||||
configRoundAngle,
|
||||
getPadding,
|
||||
getTooltipContainer,
|
||||
getTooltipItemConditionColor,
|
||||
getYAxis,
|
||||
getYAxisExt,
|
||||
setGradientColor,
|
||||
TOOLTIP_TPL,
|
||||
addConditionsStyleColorToData
|
||||
TOOLTIP_TPL
|
||||
} from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import type {
|
||||
BidirectionalBar as G2BidirectionalBar,
|
||||
@ -213,19 +213,9 @@ export class BidirectionalHorizontalBar extends G2PlotChartView<
|
||||
...options,
|
||||
layout: basicStyle.layout
|
||||
}
|
||||
if (basicStyle.radiusColumnBar === 'roundAngle') {
|
||||
const barStyle = {
|
||||
radius: [
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius
|
||||
]
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
barStyle
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
...configRoundAngle(chart, 'barStyle')
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
@ -0,0 +1,507 @@
|
||||
import type {
|
||||
Bullet as G2Bullet,
|
||||
BulletOptions as G2BulletOptions
|
||||
} from '@antv/g2plot/esm/plots/bullet'
|
||||
import {
|
||||
G2PlotChartView,
|
||||
G2PlotDrawOptions
|
||||
} from '@/views/chart/components/js/panel/types/impl/g2plot'
|
||||
import {
|
||||
BAR_AXIS_TYPE,
|
||||
BAR_EDITOR_PROPERTY,
|
||||
BAR_EDITOR_PROPERTY_INNER
|
||||
} from '@/views/chart/components/js/panel/charts/bar/common'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { flow, parseJson } from '@/views/chart/components/js/util'
|
||||
import { BulletOptions } from '@antv/g2plot'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import {
|
||||
configAxisLabelLengthLimit,
|
||||
configPlotTooltipEvent,
|
||||
getPadding,
|
||||
getTooltipContainer,
|
||||
TOOLTIP_TPL
|
||||
} from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
/**
|
||||
* 子弹图
|
||||
*/
|
||||
export class BulletGraph extends G2PlotChartView<G2BulletOptions, G2Bullet> {
|
||||
constructor() {
|
||||
super('bullet-graph', [])
|
||||
}
|
||||
|
||||
axis: AxisType[] = [...BAR_AXIS_TYPE, 'yAxisExt', 'extBubble']
|
||||
axisConfig = {
|
||||
...this['axisConfig'],
|
||||
xAxis: { name: `${t('chart.form_type')} / ${t('chart.dimension')}`, type: 'd', limit: 1 },
|
||||
yAxis: { name: `${t('chart.progress_current')} / ${t('chart.quota')}`, type: 'q', limit: 1 },
|
||||
yAxisExt: { name: `${t('chart.progress_target')} / ${t('chart.quota')}`, type: 'q', limit: 1 },
|
||||
extBubble: {
|
||||
name: `${t('chart.range_bg')} / ${t('chart.quota')}`,
|
||||
type: 'q',
|
||||
allowEmpty: true,
|
||||
limit: 1
|
||||
}
|
||||
}
|
||||
properties: EditorProperty[] = [
|
||||
...BAR_EDITOR_PROPERTY.filter(
|
||||
item => !['function-cfg', 'assist-line', 'threshold'].includes(item)
|
||||
),
|
||||
'bullet-graph-selector'
|
||||
]
|
||||
propertyInner = {
|
||||
'basic-style-selector': ['radiusColumnBar', 'layout'],
|
||||
'label-selector': ['hPosition', 'fontSize', 'color', 'labelFormatter'],
|
||||
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'seriesTooltipFormatter', 'show'],
|
||||
'x-axis-selector': [
|
||||
...BAR_EDITOR_PROPERTY_INNER['x-axis-selector'].filter(item => item != 'position'),
|
||||
'showLengthLimit'
|
||||
],
|
||||
'y-axis-selector': [
|
||||
...BAR_EDITOR_PROPERTY_INNER['y-axis-selector'].filter(
|
||||
item => item !== 'axisValue' && item !== 'position'
|
||||
),
|
||||
'axisLabelFormatter'
|
||||
],
|
||||
'legend-selector': ['showRange', 'orient', 'fontSize', 'color', 'hPosition', 'vPosition']
|
||||
}
|
||||
|
||||
async drawChart(drawOption: G2PlotDrawOptions<G2Bullet>): Promise<G2Bullet> {
|
||||
const { chart, container, action } = drawOption
|
||||
if (!chart.data?.data?.length) return
|
||||
const result = mergeBulletData(chart)
|
||||
// 处理自定义区间
|
||||
const { bullet } = parseJson(chart.customAttr).misc
|
||||
if (bullet.bar.ranges.showType === 'fixed') {
|
||||
const customRange = bullet.bar.ranges.fixedRange?.map(item => item.fixedRangeValue) || [0]
|
||||
result.forEach(item => (item.ranges = customRange))
|
||||
} else {
|
||||
result.forEach(item => (item.ranges = item.originalRanges))
|
||||
}
|
||||
// 处理自定义目标值
|
||||
if (bullet.bar.target.showType === 'fixed') {
|
||||
const customTarget = bullet.bar.target.value || 0
|
||||
result.forEach(item => (item.target = customTarget))
|
||||
} else {
|
||||
result.forEach(item => (item.target = item.originalTarget))
|
||||
}
|
||||
const initialOptions: BulletOptions = {
|
||||
appendPadding: getPadding(chart),
|
||||
data: result.reverse(),
|
||||
measureField: 'measures',
|
||||
rangeField: 'ranges',
|
||||
targetField: 'target',
|
||||
xField: 'title',
|
||||
meta: {
|
||||
title: {
|
||||
type: 'cat'
|
||||
}
|
||||
},
|
||||
interactions: [
|
||||
{
|
||||
type: 'active-region',
|
||||
cfg: {
|
||||
start: [{ trigger: 'element:mousemove', action: 'active-region:show' }],
|
||||
end: [{ trigger: 'element:mouseleave', action: 'active-region:hide' }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
const options = this.setupOptions(chart, initialOptions)
|
||||
let newChart = null
|
||||
const { Bullet: BulletClass } = await import('@antv/g2plot/esm/plots/bullet')
|
||||
newChart = new BulletClass(container, options)
|
||||
newChart.on('element:click', ev => {
|
||||
const pointData = ev?.data?.data
|
||||
const dimensionList = options.data.find(item => item.title === pointData.title)?.dimensionList
|
||||
const actionParams = {
|
||||
x: ev.x,
|
||||
y: ev.y,
|
||||
data: {
|
||||
data: {
|
||||
...pointData,
|
||||
dimensionList
|
||||
}
|
||||
}
|
||||
}
|
||||
action(actionParams)
|
||||
})
|
||||
configPlotTooltipEvent(chart, newChart)
|
||||
configAxisLabelLengthLimit(chart, newChart, null)
|
||||
return newChart
|
||||
}
|
||||
|
||||
protected configBasicStyle(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
const basicStyle = parseJson(chart.customAttr).basicStyle
|
||||
const { radiusColumnBar, columnBarRightAngleRadius, layout } = basicStyle
|
||||
let radiusValue = 0
|
||||
let rangeLength = 1
|
||||
if (radiusColumnBar === 'roundAngle' || radiusColumnBar === 'topRoundAngle') {
|
||||
radiusValue = columnBarRightAngleRadius
|
||||
rangeLength = options.data[0]?.ranges?.length
|
||||
}
|
||||
const barRadiusStyle = { radius: Array(2).fill(radiusValue) }
|
||||
const baseRadius = [...barRadiusStyle.radius, ...barRadiusStyle.radius]
|
||||
options = {
|
||||
...options,
|
||||
bulletStyle: {
|
||||
range: datum => {
|
||||
if (!datum.rKey) return { fill: 'rgba(0, 0, 0, 0)' }
|
||||
if (rangeLength === 1) {
|
||||
return {
|
||||
radius:
|
||||
radiusColumnBar === 'topRoundAngle' ? [...barRadiusStyle.radius, 0, 0] : baseRadius
|
||||
}
|
||||
}
|
||||
if (rangeLength > 1 && datum.rKey === 'ranges_0') {
|
||||
return {
|
||||
radius: radiusColumnBar === 'topRoundAngle' ? [] : [0, 0, ...barRadiusStyle.radius]
|
||||
}
|
||||
}
|
||||
if (rangeLength > 1 && datum.rKey === 'ranges_' + (rangeLength - 1)) {
|
||||
return { radius: [...barRadiusStyle.radius, 0, 0] }
|
||||
}
|
||||
},
|
||||
measure: datum => {
|
||||
if (datum.measures) {
|
||||
return {
|
||||
radius:
|
||||
radiusColumnBar === 'topRoundAngle' ? [...barRadiusStyle.radius, 0, 0] : baseRadius
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
target: datum => (datum.tKey === 'target' ? { lineWidth: 2 } : undefined)
|
||||
}
|
||||
}
|
||||
if (layout === 'vertical') options = { ...options, layout: 'vertical' }
|
||||
return options
|
||||
}
|
||||
|
||||
protected configMisc(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
const { bullet } = parseJson(chart.customAttr).misc
|
||||
const isDynamic = bullet.bar.ranges.showType === 'dynamic'
|
||||
// 动态背景按大小升序
|
||||
const rangeColor = isDynamic
|
||||
? bullet.bar.ranges.fill
|
||||
: bullet.bar.ranges.fixedRange
|
||||
?.sort((a, b) => (a.fixedRangeValue ?? 0) - (b.fixedRangeValue ?? 0))
|
||||
.map(item => item.fill) || []
|
||||
return {
|
||||
...options,
|
||||
color: {
|
||||
measure: [].concat(bullet.bar.measures.fill),
|
||||
range: [].concat(rangeColor),
|
||||
target: [].concat(bullet.bar.target.fill)
|
||||
},
|
||||
size: {
|
||||
measure: bullet.bar.measures.size,
|
||||
range: bullet.bar.ranges.size,
|
||||
target: bullet.bar.target.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected configXAxis(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
const tmpOptions = super.configXAxis(chart, options)
|
||||
if (!tmpOptions.xAxis || !tmpOptions.xAxis.label) return tmpOptions
|
||||
|
||||
const { layout, xAxis } = tmpOptions
|
||||
const position = xAxis.position
|
||||
const style: any = { ...xAxis.label.style }
|
||||
|
||||
if (layout === 'vertical') {
|
||||
style.textAlign = 'center'
|
||||
style.textBaseline = position === 'bottom' ? 'top' : 'bottom'
|
||||
} else {
|
||||
style.textAlign = position === 'bottom' ? 'end' : 'start'
|
||||
style.textBaseline = 'middle'
|
||||
}
|
||||
|
||||
xAxis.label.style = style
|
||||
return tmpOptions
|
||||
}
|
||||
|
||||
protected configYAxis(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
const tmpOptions = super.configYAxis(chart, options)
|
||||
if (!tmpOptions.yAxis || !tmpOptions.yAxis.label) return tmpOptions
|
||||
|
||||
const yAxis = parseJson(chart.customStyle).yAxis
|
||||
tmpOptions.yAxis.label.formatter = value => valueFormatter(value, yAxis.axisLabelFormatter)
|
||||
|
||||
const { layout, yAxis: yAxisConfig } = tmpOptions
|
||||
const position = yAxisConfig.position
|
||||
const style: any = { ...yAxisConfig.label.style }
|
||||
|
||||
if (layout === 'vertical') {
|
||||
style.textAlign = position === 'left' ? 'end' : 'start'
|
||||
style.textBaseline = 'middle'
|
||||
} else {
|
||||
style.textAlign = 'center'
|
||||
style.textBaseline = position === 'left' ? 'top' : 'bottom'
|
||||
}
|
||||
|
||||
yAxisConfig.label.style = style
|
||||
return tmpOptions
|
||||
}
|
||||
|
||||
protected configLabel(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
const tmpOptions = super.configLabel(chart, options)
|
||||
if (!tmpOptions.label) return tmpOptions
|
||||
|
||||
const labelAttr = parseJson(chart.customAttr).label
|
||||
const label: any = {
|
||||
...tmpOptions.label,
|
||||
formatter: param =>
|
||||
param.mKey === 'measures'
|
||||
? valueFormatter(param.measures, labelAttr.labelFormatter)
|
||||
: undefined
|
||||
}
|
||||
return { ...tmpOptions, label: { measure: label } }
|
||||
}
|
||||
|
||||
protected configLegend(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
const baseLegend = super.configLegend(chart, options).legend
|
||||
if (!baseLegend) return options
|
||||
|
||||
const { bullet } = parseJson(chart.customAttr).misc
|
||||
const customStyleLegend = parseJson(chart.customStyle).legend
|
||||
const items = []
|
||||
|
||||
const createLegendItem = (value, name, symbol, fill, size = 4) => ({
|
||||
value,
|
||||
name,
|
||||
marker: { symbol, style: { fill, stroke: value === 'measure' ? '' : fill, r: size } }
|
||||
})
|
||||
|
||||
if (customStyleLegend.showRange) {
|
||||
if (bullet.bar.ranges.showType === 'dynamic') {
|
||||
if (chart.extBubble.length) {
|
||||
const rangeName = chart.extBubble[0]?.chartShowName || bullet.bar.ranges.name
|
||||
items.push(
|
||||
createLegendItem(
|
||||
'dynamic',
|
||||
rangeName || chart.extBubble[0]?.name,
|
||||
bullet.bar.ranges.symbol,
|
||||
[].concat(bullet.bar.ranges.fill)[0],
|
||||
bullet.bar.ranges.symbolSize
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
bullet.bar.ranges.fixedRange?.forEach(item => {
|
||||
items.push(
|
||||
createLegendItem(
|
||||
item.name,
|
||||
item.name,
|
||||
bullet.bar.ranges.symbol,
|
||||
item.fill,
|
||||
bullet.bar.ranges.symbolSize
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const targetName = chart.yAxisExt[0]?.chartShowName || bullet.bar.target.name
|
||||
items.push(
|
||||
createLegendItem(
|
||||
'target',
|
||||
targetName || chart.yAxisExt[0]?.name,
|
||||
'line',
|
||||
[].concat(bullet.bar.target.fill)[0],
|
||||
bullet.bar.ranges.symbolSize
|
||||
)
|
||||
)
|
||||
|
||||
const measureName = chart.yAxis[0]?.chartShowName || bullet.bar.measures.name
|
||||
items.push(
|
||||
createLegendItem(
|
||||
'measure',
|
||||
measureName || chart.yAxis[0]?.name,
|
||||
'square',
|
||||
[].concat(bullet.bar.measures.fill)[0],
|
||||
bullet.bar.ranges.symbolSize
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
...options,
|
||||
legend: { custom: true, position: baseLegend.position, layout: baseLegend.layout, items }
|
||||
}
|
||||
}
|
||||
|
||||
protected configTooltip(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
|
||||
const tooltipAttr = customAttr.tooltip
|
||||
const { bullet } = parseJson(chart.customAttr).misc
|
||||
if (!tooltipAttr.show) return { ...options, tooltip: false }
|
||||
|
||||
const formatterMap = tooltipAttr.seriesTooltipFormatter
|
||||
?.filter(i => i.show)
|
||||
.reduce((pre, next, index) => {
|
||||
const keys = ['measures', 'target', 'ranges']
|
||||
if (keys[index]) pre[keys[index]] = next
|
||||
return pre
|
||||
}, {}) as Record<string, SeriesFormatter>
|
||||
|
||||
const tooltip = {
|
||||
shared: true,
|
||||
showMarkers: true,
|
||||
customItems(originalItems) {
|
||||
if (!tooltipAttr.seriesTooltipFormatter?.length) return originalItems
|
||||
|
||||
const result = []
|
||||
const data = options.data.find(item => item.title === originalItems[0].title)
|
||||
Object.keys(formatterMap).forEach(key => {
|
||||
if (key === '记录数*') return
|
||||
const formatter = formatterMap[key]
|
||||
if (formatter) {
|
||||
if (key !== 'ranges') {
|
||||
let value = 0
|
||||
if (chart.yAxis[0].id === chart.yAxisExt[0].id) {
|
||||
value = valueFormatter(parseFloat(data['target'] as string), formatter.formatterCfg)
|
||||
} else {
|
||||
value = valueFormatter(parseFloat(data[key] as string), formatter.formatterCfg)
|
||||
}
|
||||
const name = isEmpty(formatter.chartShowName)
|
||||
? formatter.name
|
||||
: formatter.chartShowName
|
||||
result.push({ ...originalItems[0], color: bullet.bar[key].fill, name, value })
|
||||
} else {
|
||||
const ranges = data.ranges
|
||||
const isDynamic = bullet.bar.ranges.showType === 'dynamic'
|
||||
ranges.forEach((range, index) => {
|
||||
const value = valueFormatter(
|
||||
parseFloat(isDynamic ? data.minRanges[0] : (range as string)),
|
||||
formatter.formatterCfg
|
||||
)
|
||||
let name = ''
|
||||
let color: string | string[]
|
||||
if (bullet.bar.ranges.showType === 'dynamic') {
|
||||
name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
|
||||
color = bullet.bar[key].fill
|
||||
} else {
|
||||
const customRange = bullet.bar.ranges.fixedRange[index].name
|
||||
name = customRange
|
||||
? customRange
|
||||
: isEmpty(formatter.chartShowName)
|
||||
? formatter.name
|
||||
: formatter.chartShowName
|
||||
color = bullet.bar[key].fixedRange[index].fill
|
||||
}
|
||||
result.push({ ...originalItems[0], color, name, value })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
const dynamicTooltipValue = chart.data.data.find(
|
||||
d => d.field === originalItems[0]['title']
|
||||
)?.dynamicTooltipValue
|
||||
if (dynamicTooltipValue.length > 0) {
|
||||
dynamicTooltipValue.forEach(dy => {
|
||||
const q = tooltipAttr.seriesTooltipFormatter.filter(i => i.id === dy.fieldId)
|
||||
if (q && q.length > 0) {
|
||||
const value = valueFormatter(parseFloat(dy.value as string), q[0].formatterCfg)
|
||||
const name = isEmpty(q[0].chartShowName) ? q[0].name : q[0].chartShowName
|
||||
result.push({ color: 'grey', name, value })
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
container: getTooltipContainer(`tooltip-${chart.id}`),
|
||||
itemTpl: TOOLTIP_TPL,
|
||||
enterable: true
|
||||
}
|
||||
return { ...options, tooltip }
|
||||
}
|
||||
|
||||
setupDefaultOptions(chart: ChartObj): ChartObj {
|
||||
chart.customAttr.label.position = 'middle'
|
||||
chart.customStyle.yAxis.splitLine.show = false
|
||||
return super.setupDefaultOptions(chart)
|
||||
}
|
||||
|
||||
protected setupOptions(chart: Chart, options: BulletOptions): BulletOptions {
|
||||
return flow(
|
||||
this.configTheme,
|
||||
this.configBasicStyle,
|
||||
this.configMisc,
|
||||
this.configXAxis,
|
||||
this.configYAxis,
|
||||
this.configLabel,
|
||||
this.configLegend,
|
||||
this.configTooltip
|
||||
)(chart, options, {}, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装子弹图数据
|
||||
* @param chart
|
||||
*/
|
||||
function mergeBulletData(chart): any[] {
|
||||
// 先根据维度分组,再根据指标字段组装成子弹图的格式
|
||||
const groupedData = chart.data.data.reduce((acc, item) => {
|
||||
const field = item.field
|
||||
if (!acc[field]) {
|
||||
acc[field] = []
|
||||
}
|
||||
acc[field].push(item)
|
||||
return acc
|
||||
}, {})
|
||||
const result = []
|
||||
// 组装子弹图数据,每个维度对应一个子弹图
|
||||
Object.keys(groupedData).forEach(field => {
|
||||
const items = groupedData[field]
|
||||
// 初始化子弹图条目结构
|
||||
const entry = {
|
||||
title: field,
|
||||
ranges: [],
|
||||
measures: [],
|
||||
target: [],
|
||||
dimensionList: items[0].dimensionList,
|
||||
quotaList: []
|
||||
}
|
||||
|
||||
// 防止指标相同时无数据有可能会导致数据不一致
|
||||
items.forEach(item => {
|
||||
const quotaId = item.quotaList[0]?.id
|
||||
const v = item.value || 0
|
||||
if (quotaId === chart.yAxis[0]?.id) {
|
||||
entry.measures.push(v)
|
||||
}
|
||||
if (quotaId === chart.yAxisExt[0]?.id) {
|
||||
entry.target.push(v)
|
||||
}
|
||||
if (quotaId === chart.extBubble[0]?.id) {
|
||||
entry.ranges.push(v)
|
||||
}
|
||||
entry.quotaList.push(item.quotaList[0])
|
||||
})
|
||||
// 对数据进行累加
|
||||
const ranges = chart.extBubble[0]?.id
|
||||
? [].concat(entry.ranges?.reduce((acc, curr) => acc + curr, 0))
|
||||
: []
|
||||
const target = [].concat(entry.target?.reduce((acc, curr) => acc + curr, 0))
|
||||
const measures = [].concat(entry.measures?.reduce((acc, curr) => acc + curr, 0))
|
||||
const bulletData = {
|
||||
...entry,
|
||||
measures: measures,
|
||||
target: target,
|
||||
ranges: ranges,
|
||||
quotaList: [...entry.quotaList],
|
||||
minRanges: ranges,
|
||||
originalRanges: ranges,
|
||||
originalTarget: target
|
||||
}
|
||||
result.push(bulletData)
|
||||
})
|
||||
return result
|
||||
}
|
@ -6,6 +6,7 @@ import type { Bar, BarOptions } from '@antv/g2plot/esm/plots/bar'
|
||||
import {
|
||||
configAxisLabelLengthLimit,
|
||||
configPlotTooltipEvent,
|
||||
configRoundAngle,
|
||||
getPadding,
|
||||
getTooltipContainer,
|
||||
setGradientColor,
|
||||
@ -101,6 +102,17 @@ export class HorizontalBar extends G2PlotChartView<BarOptions, Bar> {
|
||||
const newChart = new Bar(container, options)
|
||||
|
||||
newChart.on('interval:click', action)
|
||||
if (options.label) {
|
||||
newChart.on('label:click', e => {
|
||||
action({
|
||||
x: e.x,
|
||||
y: e.y,
|
||||
data: {
|
||||
data: e.target.attrs.data
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
configPlotTooltipEvent(chart, newChart)
|
||||
configAxisLabelLengthLimit(chart, newChart)
|
||||
return newChart
|
||||
@ -135,7 +147,10 @@ export class HorizontalBar extends G2PlotChartView<BarOptions, Bar> {
|
||||
tickCount: axisValue.splitCount
|
||||
}
|
||||
}
|
||||
return { ...tmpOptions, ...axis }
|
||||
// 根据axis的最小值,过滤options中的data数据,过滤掉小于最小值的数据
|
||||
const { data } = options
|
||||
const newData = data.filter(item => item.value >= axisValue.min)
|
||||
return { ...tmpOptions, data: newData, ...axis }
|
||||
}
|
||||
return tmpOptions
|
||||
}
|
||||
@ -157,19 +172,9 @@ export class HorizontalBar extends G2PlotChartView<BarOptions, Bar> {
|
||||
color
|
||||
}
|
||||
}
|
||||
if (basicStyle.radiusColumnBar === 'roundAngle') {
|
||||
const barStyle = {
|
||||
radius: [
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius
|
||||
]
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
barStyle
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
...configRoundAngle(chart, 'barStyle')
|
||||
}
|
||||
|
||||
let barWidthRatio
|
||||
@ -234,6 +239,7 @@ export class HorizontalBar extends G2PlotChartView<BarOptions, Bar> {
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
data,
|
||||
text: value,
|
||||
textAlign: 'start',
|
||||
textBaseline: 'top',
|
||||
@ -316,8 +322,24 @@ export class HorizontalStackBar extends HorizontalBar {
|
||||
baseOptions.label.style.fill = labelAttr.color
|
||||
const label = {
|
||||
...baseOptions.label,
|
||||
formatter: function (param: Datum) {
|
||||
return valueFormatter(param.value, labelAttr.labelFormatter)
|
||||
formatter: function (data: Datum) {
|
||||
const value = valueFormatter(data.value, labelAttr.labelFormatter)
|
||||
const group = new Group({})
|
||||
group.addShape({
|
||||
type: 'text',
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
data,
|
||||
text: value,
|
||||
textAlign: 'start',
|
||||
textBaseline: 'top',
|
||||
fontSize: labelAttr.fontSize,
|
||||
fontFamily: chart.fontFamily,
|
||||
fill: labelAttr.color
|
||||
}
|
||||
})
|
||||
return group
|
||||
}
|
||||
}
|
||||
return {
|
||||
@ -435,11 +457,29 @@ export class HorizontalPercentageStackBar extends HorizontalStackBar {
|
||||
const l = parseJson(customAttr).label
|
||||
const label = {
|
||||
...baseOptions.label,
|
||||
formatter: function (param: Datum) {
|
||||
if (!param.value) {
|
||||
return '0%'
|
||||
formatter: function (data: Datum) {
|
||||
let value = data.value
|
||||
if (value) {
|
||||
value = (Math.round(value * 10000) / 100).toFixed(l.reserveDecimalCount) + '%'
|
||||
} else {
|
||||
value = '0%'
|
||||
}
|
||||
return (Math.round(param.value * 10000) / 100).toFixed(l.reserveDecimalCount) + '%'
|
||||
const group = new Group({})
|
||||
group.addShape({
|
||||
type: 'text',
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
data,
|
||||
text: value,
|
||||
textAlign: 'start',
|
||||
textBaseline: 'top',
|
||||
fontSize: l.fontSize,
|
||||
fontFamily: chart.fontFamily,
|
||||
fill: l.color
|
||||
}
|
||||
})
|
||||
return group
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
@ -3,8 +3,8 @@ import { flow, hexColorToRGBA, parseJson } from '../../../util'
|
||||
import {
|
||||
configAxisLabelLengthLimit,
|
||||
configPlotTooltipEvent,
|
||||
configRoundAngle,
|
||||
getTooltipContainer,
|
||||
getTooltipItemConditionColor,
|
||||
setGradientColor,
|
||||
TOOLTIP_TPL
|
||||
} from '../../common/common_antv'
|
||||
@ -66,7 +66,7 @@ export class ProgressBar extends G2PlotChartView<BarOptions, G2Progress> {
|
||||
'fontSize',
|
||||
'axisForm',
|
||||
'axisLabel',
|
||||
'position',
|
||||
// 'position',
|
||||
'showLengthLimit'
|
||||
],
|
||||
'function-cfg': ['emptyDataStrategy'],
|
||||
@ -166,6 +166,7 @@ export class ProgressBar extends G2PlotChartView<BarOptions, G2Progress> {
|
||||
}
|
||||
})
|
||||
if (basicStyle.gradient) {
|
||||
// eslint-disable-next-line
|
||||
color1 = color1.map((ele, _index) => {
|
||||
return setGradientColor(ele, true, 0)
|
||||
})
|
||||
@ -184,19 +185,9 @@ export class ProgressBar extends G2PlotChartView<BarOptions, G2Progress> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (basicStyle.radiusColumnBar === 'roundAngle') {
|
||||
const barStyle = {
|
||||
radius: [
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius
|
||||
]
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
barStyle
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
...configRoundAngle(chart, 'barStyle')
|
||||
}
|
||||
|
||||
let barWidthRatio
|
||||
@ -297,12 +288,31 @@ export class ProgressBar extends G2PlotChartView<BarOptions, G2Progress> {
|
||||
if (!baseOption.yAxis) {
|
||||
return baseOption
|
||||
}
|
||||
if (baseOption.yAxis.position === 'left') {
|
||||
baseOption.yAxis.position = 'bottom'
|
||||
const yAxis = parseJson(chart.customStyle).yAxis
|
||||
if (yAxis.axisLabel.show) {
|
||||
const rotate = yAxis.axisLabel.rotate
|
||||
let textAlign = 'end'
|
||||
let textBaseline = 'middle'
|
||||
if (Math.abs(rotate) > 75) {
|
||||
textAlign = 'center'
|
||||
}
|
||||
if (rotate > 75) {
|
||||
textBaseline = 'top'
|
||||
}
|
||||
if (rotate < -75) {
|
||||
textBaseline = 'bottom'
|
||||
}
|
||||
baseOption.yAxis.label.style.textBaseline = textBaseline
|
||||
baseOption.yAxis.label.style.textAlign = textAlign
|
||||
}
|
||||
|
||||
/*if (baseOption.yAxis.position === 'left') {
|
||||
baseOption.yAxis.position = 'bottom'
|
||||
}
|
||||
if (baseOption.yAxis.position === 'right') {
|
||||
baseOption.yAxis.position = 'top'
|
||||
}
|
||||
}*/
|
||||
return baseOption
|
||||
}
|
||||
setupDefaultOptions(chart: ChartObj): ChartObj {
|
||||
|
@ -6,6 +6,7 @@ import type { Bar, BarOptions } from '@antv/g2plot/esm/plots/bar'
|
||||
import {
|
||||
configAxisLabelLengthLimit,
|
||||
configPlotTooltipEvent,
|
||||
configRoundAngle,
|
||||
getPadding,
|
||||
getTooltipContainer,
|
||||
setGradientColor,
|
||||
@ -22,6 +23,7 @@ import {
|
||||
import { Datum } from '@antv/g2plot/esm/types/common'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { Group } from '@antv/g-canvas'
|
||||
|
||||
const { t } = useI18n()
|
||||
const DEFAULT_DATA = []
|
||||
@ -170,6 +172,17 @@ export class RangeBar extends G2PlotChartView<BarOptions, Bar> {
|
||||
const newChart = new BarClass(container, options)
|
||||
|
||||
newChart.on('interval:click', action)
|
||||
if (options.label) {
|
||||
newChart.on('label:click', e => {
|
||||
action({
|
||||
x: e.x,
|
||||
y: e.y,
|
||||
data: {
|
||||
data: e.target.attrs.data
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
configPlotTooltipEvent(chart, newChart)
|
||||
configAxisLabelLengthLimit(chart, newChart)
|
||||
return newChart
|
||||
@ -309,19 +322,10 @@ export class RangeBar extends G2PlotChartView<BarOptions, Bar> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (basicStyle.radiusColumnBar === 'roundAngle') {
|
||||
const barStyle = {
|
||||
radius: [
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius,
|
||||
basicStyle.columnBarRightAngleRadius
|
||||
]
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
barStyle
|
||||
}
|
||||
|
||||
options = {
|
||||
...options,
|
||||
...configRoundAngle(chart, 'barStyle')
|
||||
}
|
||||
let barWidthRatio
|
||||
const _v = basicStyle.columnWidthRatio ?? DEFAULT_BASIC_STYLE.columnWidthRatio
|
||||
@ -391,7 +395,22 @@ export class RangeBar extends G2PlotChartView<BarOptions, Bar> {
|
||||
valueFormatter(param.values[1], labelAttr.labelFormatter)
|
||||
}
|
||||
}
|
||||
return res
|
||||
const group = new Group({})
|
||||
group.addShape({
|
||||
type: 'text',
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
data: param,
|
||||
text: res,
|
||||
textAlign: 'start',
|
||||
textBaseline: 'top',
|
||||
fontSize: labelAttr.fontSize,
|
||||
fontFamily: chart.fontFamily,
|
||||
fill: labelAttr.color
|
||||
}
|
||||
})
|
||||
return group
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
@ -72,7 +72,8 @@ export class Waterfall extends G2PlotChartView<WaterfallOptions, G2Waterfall> {
|
||||
'axisForm',
|
||||
'axisLabel',
|
||||
'axisLabelFormatter',
|
||||
'showLengthLimit'
|
||||
'showLengthLimit',
|
||||
'axisLine'
|
||||
],
|
||||
threshold: ['lineThreshold']
|
||||
}
|
||||
|
@ -46,7 +46,8 @@ export class Area extends G2PlotChartView<AreaOptions, G2Area> {
|
||||
'label-selector': ['seriesLabelVPosition', 'seriesLabelFormatter', 'showExtremum'],
|
||||
'tooltip-selector': [
|
||||
...LINE_EDITOR_PROPERTY_INNER['tooltip-selector'],
|
||||
'seriesTooltipFormatter'
|
||||
'seriesTooltipFormatter',
|
||||
'carousel'
|
||||
]
|
||||
}
|
||||
axis: AxisType[] = [...LINE_AXIS_TYPE]
|
||||
@ -103,8 +104,8 @@ export class Area extends G2PlotChartView<AreaOptions, G2Area> {
|
||||
|
||||
async drawChart(drawOptions: G2PlotDrawOptions<G2Area>): Promise<G2Area> {
|
||||
const { chart, container, action } = drawOptions
|
||||
chart.container = container
|
||||
if (!chart.data?.data?.length) {
|
||||
chart.container = container
|
||||
clearExtremum(chart)
|
||||
return
|
||||
}
|
||||
@ -147,7 +148,7 @@ export class Area extends G2PlotChartView<AreaOptions, G2Area> {
|
||||
fields: [],
|
||||
...tmpOptions.label,
|
||||
layout: labelAttr.fullDisplay ? [{ type: 'limit-in-plot' }] : tmpOptions.label.layout,
|
||||
formatter: (data: Datum, _point) => {
|
||||
formatter: (data: Datum) => {
|
||||
if (data.EXTREME) {
|
||||
return ''
|
||||
}
|
||||
@ -305,7 +306,7 @@ export class StackArea extends Area {
|
||||
propertyInner = {
|
||||
...this['propertyInner'],
|
||||
'label-selector': ['vPosition', 'fontSize', 'color', 'labelFormatter'],
|
||||
'tooltip-selector': ['fontSize', 'color', 'tooltipFormatter', 'show']
|
||||
'tooltip-selector': ['fontSize', 'color', 'tooltipFormatter', 'show', 'carousel']
|
||||
}
|
||||
axisConfig = {
|
||||
...this['axisConfig'],
|
||||
|
@ -10,10 +10,12 @@ import {
|
||||
TOOLTIP_TPL
|
||||
} from '../../common/common_antv'
|
||||
import {
|
||||
convertToAlphaColor,
|
||||
flow,
|
||||
getLineConditions,
|
||||
getLineLabelColorByCondition,
|
||||
hexColorToRGBA,
|
||||
isAlphaColor,
|
||||
parseJson,
|
||||
setUpGroupSeriesColor
|
||||
} from '@/views/chart/components/js/util'
|
||||
@ -43,8 +45,10 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
|
||||
'label-selector': ['seriesLabelVPosition', 'seriesLabelFormatter', 'showExtremum'],
|
||||
'tooltip-selector': [
|
||||
...LINE_EDITOR_PROPERTY_INNER['tooltip-selector'],
|
||||
'seriesTooltipFormatter'
|
||||
]
|
||||
'seriesTooltipFormatter',
|
||||
'carousel'
|
||||
],
|
||||
'legend-selector': [...LINE_EDITOR_PROPERTY_INNER['legend-selector'], 'legendSort']
|
||||
}
|
||||
axis: AxisType[] = [...LINE_AXIS_TYPE, 'xAxisExt']
|
||||
axisConfig = {
|
||||
@ -66,8 +70,8 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
|
||||
}
|
||||
async drawChart(drawOptions: G2PlotDrawOptions<G2Line>): Promise<G2Line> {
|
||||
const { chart, action, container } = drawOptions
|
||||
chart.container = container
|
||||
if (!chart.data?.data?.length) {
|
||||
chart.container = container
|
||||
clearExtremum(chart)
|
||||
return
|
||||
}
|
||||
@ -146,7 +150,7 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
|
||||
fields: [],
|
||||
...tmpOptions.label,
|
||||
layout: labelAttr.fullDisplay ? [{ type: 'limit-in-plot' }] : tmpOptions.label.layout,
|
||||
formatter: (data: Datum, _point) => {
|
||||
formatter: (data: Datum) => {
|
||||
if (data.EXTREME) {
|
||||
return ''
|
||||
}
|
||||
@ -321,17 +325,30 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
|
||||
if (sort?.length) {
|
||||
// 用值域限定排序,有可能出现新数据但是未出现在图表上,所以这边要遍历一下子维度,加到后面,让新数据显示出来
|
||||
const data = optionTmp.data
|
||||
data?.forEach(d => {
|
||||
const cat = d['category']
|
||||
if (cat && !sort.includes(cat)) {
|
||||
sort.push(cat)
|
||||
const cats =
|
||||
data?.reduce((p, n) => {
|
||||
const cat = n['category']
|
||||
if (cat && !p.includes(cat)) {
|
||||
p.push(cat)
|
||||
}
|
||||
return p
|
||||
}, []) || []
|
||||
const values = sort.reduce((p, n) => {
|
||||
if (cats.includes(n)) {
|
||||
const index = cats.indexOf(n)
|
||||
if (index !== -1) {
|
||||
cats.splice(index, 1)
|
||||
}
|
||||
p.push(n)
|
||||
}
|
||||
})
|
||||
return p
|
||||
}, [])
|
||||
cats.length > 0 && values.push(...cats)
|
||||
optionTmp.meta = {
|
||||
...optionTmp.meta,
|
||||
category: {
|
||||
type: 'cat',
|
||||
values: sort
|
||||
values
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -351,6 +368,56 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
|
||||
fill: style.stroke
|
||||
}
|
||||
}
|
||||
const { sort, customSort, icon } = customStyle.legend
|
||||
if (sort && sort !== 'none' && chart.xAxisExt.length) {
|
||||
const customAttr = parseJson(chart.customAttr)
|
||||
const { basicStyle } = customAttr
|
||||
const seriesMap =
|
||||
basicStyle.seriesColor?.reduce((p, n) => {
|
||||
p[n.id] = n
|
||||
return p
|
||||
}, {}) || {}
|
||||
const dupCheck = new Set()
|
||||
const items = optionTmp.data?.reduce((arr, item) => {
|
||||
if (!dupCheck.has(item.category)) {
|
||||
const fill =
|
||||
seriesMap[item.category]?.color ??
|
||||
optionTmp.color[dupCheck.size % optionTmp.color.length]
|
||||
dupCheck.add(item.category)
|
||||
arr.push({
|
||||
name: item.category,
|
||||
value: item.category,
|
||||
marker: {
|
||||
symbol: icon,
|
||||
style: {
|
||||
r: size,
|
||||
fill: isAlphaColor(fill) ? fill : convertToAlphaColor(fill, basicStyle.alpha)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return arr
|
||||
}, [])
|
||||
if (sort !== 'custom') {
|
||||
items.sort((a, b) => {
|
||||
return sort !== 'desc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)
|
||||
})
|
||||
} else {
|
||||
const tmp = []
|
||||
;(customSort || []).forEach(item => {
|
||||
const index = items.findIndex(i => i.name === item)
|
||||
if (index !== -1) {
|
||||
tmp.push(items[index])
|
||||
items.splice(index, 1)
|
||||
}
|
||||
})
|
||||
items.unshift(...tmp)
|
||||
}
|
||||
optionTmp.legend.items = items
|
||||
if (xAxisExt?.customSort?.length > 0) {
|
||||
delete optionTmp.meta?.category.values
|
||||
}
|
||||
}
|
||||
return optionTmp
|
||||
}
|
||||
protected setupOptions(chart: Chart, options: LineOptions): LineOptions {
|
||||
|
@ -440,7 +440,6 @@ export class StockLine extends G2PlotChartView<MixOptions, Mix> {
|
||||
|
||||
protected configTooltip(chart: Chart, options: MixOptions): MixOptions {
|
||||
const tooltipAttr = parseJson(chart.customAttr).tooltip
|
||||
const xAxis = chart.xAxis
|
||||
const newPlots = []
|
||||
const linePlotList = options.plots.filter(item => item.type === 'line')
|
||||
linePlotList.forEach(item => {
|
||||
|
@ -74,7 +74,7 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
|
||||
})
|
||||
// 处理空数据, 只要有一个指标是空数据,就不显示图表
|
||||
const hasNoneData = chart.data?.series.some(s => !s.data?.[0])
|
||||
this.configEmptyDataStyle(newChart, hasNoneData ? [] : [1], container)
|
||||
this.configEmptyDataStyle(hasNoneData ? [] : [1], container, newChart)
|
||||
if (hasNoneData) {
|
||||
return
|
||||
}
|
||||
|
@ -402,7 +402,8 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
content.push(name)
|
||||
}
|
||||
if (label.showQuota) {
|
||||
areaMap[name] && content.push(valueFormatter(areaMap[name], label.quotaLabelFormatter))
|
||||
;(areaMap[name] || areaMap[name] === 0) &&
|
||||
content.push(valueFormatter(areaMap[name], label.quotaLabelFormatter))
|
||||
}
|
||||
item.properties['_DE_LABEL_'] = content.join('\n\n')
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export const MAP_EDITOR_PROPERTY: EditorProperty[] = [
|
||||
'background-overall-component',
|
||||
'border-style',
|
||||
@ -51,6 +55,28 @@ export const MAP_AXIS_TYPE: AxisType[] = [
|
||||
'extTooltip'
|
||||
]
|
||||
|
||||
export const gaodeMapStyleOptions = [
|
||||
{ name: t('chart.map_style_normal'), value: 'normal' },
|
||||
{ name: t('chart.map_style_darkblue'), value: 'darkblue' },
|
||||
{ name: t('chart.map_style_light'), value: 'light' },
|
||||
{ name: t('chart.map_style_dark'), value: 'dark' },
|
||||
{ name: t('chart.map_style_fresh'), value: 'fresh' },
|
||||
{ name: t('chart.map_style_grey'), value: 'grey' },
|
||||
{ name: t('chart.map_style_blue'), value: 'blue' },
|
||||
{ name: t('commons.custom'), value: 'custom' }
|
||||
]
|
||||
|
||||
export const tdtMapStyleOptions = [
|
||||
{ name: t('chart.map_style_normal'), value: 'normal' },
|
||||
{ name: t('chart.map_style_dark'), value: 'black' },
|
||||
{ name: t('chart.map_style_darkblue'), value: 'indigo' }
|
||||
]
|
||||
|
||||
export const qqMapStyleOptions = [
|
||||
{ name: t('chart.map_style_normal'), value: 'normal' },
|
||||
{ name: t('commons.custom'), value: 'custom' }
|
||||
]
|
||||
|
||||
export declare type MapMouseEvent = MouseEvent & {
|
||||
feature: GeoJSON.Feature
|
||||
}
|
||||
|
@ -8,12 +8,16 @@ import {
|
||||
import { MAP_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/charts/map/common'
|
||||
import { hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
|
||||
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 { mapRendered, mapRendering } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import {
|
||||
getMapCenter,
|
||||
getMapScene,
|
||||
getMapStyle,
|
||||
mapRendered,
|
||||
qqMapRendered
|
||||
} from '@/views/chart/components/js/panel/common/common_antv'
|
||||
const { t } = useI18n()
|
||||
|
||||
/**
|
||||
@ -88,103 +92,16 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
|
||||
const xAxisExt = deepCopy(chart.xAxisExt)
|
||||
const { basicStyle, misc } = deepCopy(parseJson(chart.customAttr))
|
||||
|
||||
let center: [number, number] = [
|
||||
DEFAULT_BASIC_STYLE.mapCenter.longitude,
|
||||
DEFAULT_BASIC_STYLE.mapCenter.latitude
|
||||
]
|
||||
if (basicStyle.autoFit === false) {
|
||||
center = [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
|
||||
}
|
||||
let mapStyle = basicStyle.mapStyleUrl
|
||||
if (basicStyle.mapStyle !== 'custom') {
|
||||
mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}`
|
||||
}
|
||||
const mapKey = await this.getMapKey()
|
||||
const mapStyle = getMapStyle(mapKey, basicStyle)
|
||||
// 底层
|
||||
const chartObj = drawOption.chartObj as unknown as L7Wrapper<L7Config, Scene>
|
||||
let scene = chartObj?.getScene()
|
||||
if(scene){
|
||||
if (scene.getLayers()?.length) {
|
||||
await scene.removeAllLayer()
|
||||
scene.setPitch(misc.mapPitch)
|
||||
}
|
||||
}
|
||||
if (mapStyle.indexOf('Satellite') == -1) {
|
||||
scene = new Scene({
|
||||
id: container,
|
||||
logoVisible: false,
|
||||
map: new GaodeMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle,
|
||||
pitch: misc.mapPitch,
|
||||
center,
|
||||
zoom: basicStyle.autoFit === false ? basicStyle.zoomLevel : undefined,
|
||||
showLabel: !(basicStyle.showLabel === false),
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
})
|
||||
}else{
|
||||
scene = new Scene({
|
||||
id: container,
|
||||
logoVisible: false,
|
||||
map: new GaodeMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle,
|
||||
features: ['bg', 'road'], // 必须开启路网层
|
||||
plugin: ['AMap.TileLayer.Satellite'], // 显式声明卫星图层
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
// if (!scene) {
|
||||
// scene = new Scene({
|
||||
// id: container,
|
||||
// logoVisible: false,
|
||||
// map: new GaodeMap({
|
||||
// token: mapKey?.key ?? undefined,
|
||||
// style: mapStyle,
|
||||
// pitch: misc.mapPitch,
|
||||
// center: basicStyle.autoFit === false ? center : undefined,
|
||||
// zoom: basicStyle.autoFit === false ? basicStyle.zoomLevel : undefined,
|
||||
// showLabel: !(basicStyle.showLabel === false),
|
||||
// WebGLParams: {
|
||||
// preserveDrawingBuffer: true
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// } else {
|
||||
// if (scene.getLayers()?.length) {
|
||||
// await scene.removeAllLayer()
|
||||
// scene.setPitch(misc.mapPitch)
|
||||
// scene.setMapStyle(mapStyle)
|
||||
// scene.map.showLabel = !(basicStyle.showLabel === false)
|
||||
// }
|
||||
// if (basicStyle.autoFit === false) {
|
||||
// scene.setZoomAndCenter(basicStyle.zoomLevel, center)
|
||||
// }
|
||||
// }
|
||||
mapRendering(container)
|
||||
if (mapStyle.indexOf('Satellite') == -1) {
|
||||
scene.once('loaded', () => {
|
||||
mapRendered(container)
|
||||
})
|
||||
} else {
|
||||
scene.once('loaded', () => {
|
||||
// 创建卫星图层实例
|
||||
const satelliteLayer = new AMap.TileLayer.Satellite()
|
||||
// 与矢量图层叠加显示
|
||||
satelliteLayer.setMap(scene.map)
|
||||
mapRendered(container)
|
||||
})
|
||||
}
|
||||
// scene.once('loaded', () => {
|
||||
// mapRendered(container)
|
||||
// })
|
||||
this.configZoomButton(chart, scene)
|
||||
|
||||
const center = getMapCenter(basicStyle)
|
||||
scene = await getMapScene(chart, scene, container, mapKey, basicStyle, misc, mapStyle, center)
|
||||
|
||||
this.configZoomButton(chart, scene, mapKey)
|
||||
if (xAxis?.length < 2 || xAxisExt?.length < 2) {
|
||||
return new L7Wrapper(scene, undefined)
|
||||
}
|
||||
@ -195,6 +112,11 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
|
||||
configList[0].once('inited', () => {
|
||||
mapRendered(container)
|
||||
})
|
||||
for (let i = 0; i < configList.length; i++) {
|
||||
configList[i].on('inited', () => {
|
||||
qqMapRendered(scene)
|
||||
})
|
||||
}
|
||||
return new L7Wrapper(scene, configList)
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,16 @@ import {
|
||||
import { MAP_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/charts/map/common'
|
||||
import { flow, parseJson } from '@/views/chart/components/js/util'
|
||||
import { deepCopy } from '@/utils/utils'
|
||||
import { GaodeMap } from '@antv/l7-maps'
|
||||
import { Scene } from '@antv/l7-scene'
|
||||
import { HeatmapLayer } from '@antv/l7-layers'
|
||||
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { mapRendered, mapRendering } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import {
|
||||
getMapCenter,
|
||||
getMapScene,
|
||||
getMapStyle,
|
||||
mapRendered,
|
||||
qqMapRendered
|
||||
} from '@/views/chart/components/js/panel/common/common_antv'
|
||||
const { t } = useI18n()
|
||||
|
||||
/**
|
||||
@ -69,94 +74,31 @@ export class HeatMap extends L7ChartView<Scene, L7Config> {
|
||||
basicStyle = parseJson(chart.customAttr).basicStyle
|
||||
miscStyle = parseJson(chart.customAttr).misc
|
||||
}
|
||||
let center: [number, number] = [
|
||||
DEFAULT_BASIC_STYLE.mapCenter.longitude,
|
||||
DEFAULT_BASIC_STYLE.mapCenter.latitude
|
||||
]
|
||||
if (basicStyle.autoFit === false) {
|
||||
center = [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
|
||||
}
|
||||
let mapStyle = basicStyle.mapStyleUrl
|
||||
if (basicStyle.mapStyle !== 'custom') {
|
||||
mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}`
|
||||
}
|
||||
const mapKey = await this.getMapKey()
|
||||
const mapStyle = getMapStyle(mapKey, basicStyle)
|
||||
// 底层
|
||||
const chartObj = drawOption.chartObj as unknown as L7Wrapper<L7Config, Scene>
|
||||
let scene = chartObj?.getScene()
|
||||
if(scene){
|
||||
if (scene.getLayers()?.length) {
|
||||
await scene.removeAllLayer()
|
||||
scene.setPitch(miscStyle.mapPitch)
|
||||
}
|
||||
}
|
||||
|
||||
if (mapStyle.indexOf('Satellite') == -1) {
|
||||
scene = new Scene({
|
||||
id: container,
|
||||
logoVisible: false,
|
||||
map: new GaodeMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle,
|
||||
pitch: miscStyle.mapPitch,
|
||||
center,
|
||||
zoom: basicStyle.autoFit === false ? basicStyle.zoomLevel : undefined,
|
||||
showLabel: !(basicStyle.showLabel === false),
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
scene = new Scene({
|
||||
id: container,
|
||||
logoVisible: false,
|
||||
map: new GaodeMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle,
|
||||
features: ['bg', 'road'], // 必须开启路网层
|
||||
plugin: ['AMap.TileLayer.Satellite'], // 显式声明卫星图层
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// } else {
|
||||
// // if (scene.getLayers()?.length) {
|
||||
// await scene.removeAllLayer()
|
||||
// scene.setPitch(miscStyle.mapPitch)
|
||||
// scene.setMapStyle(mapStyle)
|
||||
// scene.map.showLabel = !(basicStyle.showLabel === false)
|
||||
// if (basicStyle.autoFit === false) {
|
||||
// scene.setZoomAndCenter(basicStyle.zoomLevel, center)
|
||||
// }
|
||||
// // }
|
||||
// }
|
||||
mapRendering(container)
|
||||
if (mapStyle.indexOf('Satellite') == -1) {
|
||||
scene.once('loaded', () => {
|
||||
mapRendered(container)
|
||||
})
|
||||
} else {
|
||||
scene.once('loaded', () => {
|
||||
// 创建卫星图层实例
|
||||
const satelliteLayer = new AMap.TileLayer.Satellite()
|
||||
// 与矢量图层叠加显示
|
||||
satelliteLayer.setMap(scene.map)
|
||||
mapRendered(container)
|
||||
})
|
||||
}
|
||||
|
||||
this.configZoomButton(chart, scene)
|
||||
const center = getMapCenter(basicStyle)
|
||||
scene = await getMapScene(
|
||||
chart,
|
||||
scene,
|
||||
container,
|
||||
mapKey,
|
||||
basicStyle,
|
||||
miscStyle,
|
||||
mapStyle,
|
||||
center
|
||||
)
|
||||
this.configZoomButton(chart, scene, mapKey)
|
||||
if (xAxis?.length < 2 || yAxis?.length < 1) {
|
||||
return new L7Wrapper(scene, undefined)
|
||||
}
|
||||
const config: L7Config = new HeatmapLayer({
|
||||
name: 'line',
|
||||
blend: 'normal',
|
||||
autoFit: !(basicStyle.autoFit === false)
|
||||
autoFit: !(basicStyle.autoFit === false),
|
||||
zIndex: 10
|
||||
})
|
||||
.source(chart.data?.data, {
|
||||
parser: {
|
||||
@ -177,6 +119,13 @@ export class HeatMap extends L7ChartView<Scene, L7Config> {
|
||||
}
|
||||
})
|
||||
|
||||
config.once('inited', () => {
|
||||
mapRendered(container)
|
||||
})
|
||||
config.on('inited', () => {
|
||||
qqMapRendered(scene)
|
||||
})
|
||||
|
||||
return new L7Wrapper(scene, config)
|
||||
}
|
||||
|
||||
|
@ -157,6 +157,11 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
})
|
||||
})
|
||||
data = filterChartDataByRange(sourceData, maxValue, minValue)
|
||||
if (chart.drill) {
|
||||
getMaxAndMinValueByData(sourceData, 'value', 0, 0, (max, min) => {
|
||||
data = filterChartDataByRange(sourceData, max, min)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
data = sourceData
|
||||
}
|
||||
@ -301,7 +306,8 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
content.push(name)
|
||||
}
|
||||
if (label.showQuota) {
|
||||
areaMap[name] && content.push(valueFormatter(areaMap[name], label.quotaLabelFormatter))
|
||||
;(areaMap[name] || areaMap[name] === 0) &&
|
||||
content.push(valueFormatter(areaMap[name], label.quotaLabelFormatter))
|
||||
}
|
||||
item.properties['_DE_LABEL_'] = content.join('\n\n')
|
||||
}
|
||||
@ -346,11 +352,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
return listDom
|
||||
}
|
||||
|
||||
private customConfigLegend(
|
||||
chart: Chart,
|
||||
options: ChoroplethOptions,
|
||||
context: Record<string, any>
|
||||
): ChoroplethOptions {
|
||||
private customConfigLegend(chart: Chart, options: ChoroplethOptions): ChoroplethOptions {
|
||||
const { basicStyle, misc } = parseJson(chart.customAttr)
|
||||
const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
|
||||
if (basicStyle.suspension === false && basicStyle.showZoom === undefined) {
|
||||
@ -420,14 +422,14 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
const isLessThanMin = range[0] < ranges[0][0] && range[1] < ranges[0][0]
|
||||
let rangeColor = colors[colorIndex]
|
||||
if (isLessThanMin) {
|
||||
rangeColor = hexColorToRGBA(basicStyle.areaBaseColor, basicStyle.alpha)
|
||||
rangeColor = basicStyle.areaBaseColor
|
||||
}
|
||||
items.push({
|
||||
value: tmpRange,
|
||||
color: rangeColor
|
||||
})
|
||||
})
|
||||
customLegend['customContent'] = (_: string, _items: CategoryLegendListItem[]) => {
|
||||
customLegend['customContent'] = () => {
|
||||
if (items?.length) {
|
||||
return this.createLegendCustomContent(items)
|
||||
}
|
||||
@ -435,13 +437,16 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
}
|
||||
options.color['value'] = ({ value }) => {
|
||||
const item = items.find(item => value >= item.value[0] && value <= item.value[1])
|
||||
return item ? item.color : hexColorToRGBA(basicStyle.areaBaseColor, basicStyle.alpha)
|
||||
return item ? item.color : basicStyle.areaBaseColor
|
||||
}
|
||||
options.color.scale.domain = [ranges[0][0], ranges[ranges.length - 1][1]]
|
||||
} else {
|
||||
customLegend['customContent'] = (_: string, items: CategoryLegendListItem[]) => {
|
||||
const showItems = items?.length > 30 ? items.slice(0, 30) : items
|
||||
if (showItems?.length) {
|
||||
if (showItems.length === 1) {
|
||||
showItems[0].value = options.color.scale.domain.slice(0, 2)
|
||||
}
|
||||
return this.createLegendCustomContent(showItems)
|
||||
}
|
||||
return ''
|
||||
@ -508,7 +513,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
content.push(area.name)
|
||||
}
|
||||
if (label.showQuota) {
|
||||
areaMap[area.name] &&
|
||||
;(areaMap[area.name] || areaMap[area.name] === 0) &&
|
||||
content.push(valueFormatter(areaMap[area.name].value, label.quotaLabelFormatter))
|
||||
}
|
||||
labelLocation.push({
|
||||
@ -567,6 +572,9 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
return result
|
||||
}
|
||||
const head = originalItem.properties
|
||||
if (!head) {
|
||||
return result
|
||||
}
|
||||
const { adcode } = head
|
||||
const areaName = subAreaMap['156' + adcode]
|
||||
const valItem = areaMap[areaName]
|
||||
|
@ -13,13 +13,17 @@ import {
|
||||
svgStrToUrl
|
||||
} from '@/views/chart/components/js/util'
|
||||
import { deepCopy } from '@/utils/utils'
|
||||
import { GaodeMap } from '@antv/l7-maps'
|
||||
import { Scene } from '@antv/l7-scene'
|
||||
import { PointLayer } from '@antv/l7-layers'
|
||||
import { LayerPopup, Popup } from '@antv/l7'
|
||||
import { mapRendered, mapRendering } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import {
|
||||
getMapCenter,
|
||||
getMapScene,
|
||||
getMapStyle,
|
||||
mapRendered,
|
||||
qqMapRendered
|
||||
} from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { configCarouselTooltip } from '@/views/chart/components/js/panel/charts/map/tooltip-carousel'
|
||||
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { filter } from 'lodash-es'
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -102,18 +106,10 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
|
||||
miscStyle = parseJson(chart.customAttr).misc
|
||||
}
|
||||
|
||||
let mapStyle = basicStyle.mapStyleUrl
|
||||
if (basicStyle.mapStyle !== 'custom') {
|
||||
mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}`
|
||||
}
|
||||
const mapKey = await this.getMapKey()
|
||||
let center: [number, number] = [
|
||||
DEFAULT_BASIC_STYLE.mapCenter.longitude,
|
||||
DEFAULT_BASIC_STYLE.mapCenter.latitude
|
||||
]
|
||||
if (basicStyle.autoFit === false) {
|
||||
center = [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
|
||||
}
|
||||
const mapStyle = getMapStyle(mapKey, basicStyle)
|
||||
|
||||
let center = getMapCenter(basicStyle)
|
||||
// 联动时,聚焦到数据点,多个取第一个
|
||||
if (
|
||||
chart.chartExtRequest?.linkageFilters?.length &&
|
||||
@ -128,38 +124,18 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
|
||||
}
|
||||
const chartObj = drawOption.chartObj as unknown as L7Wrapper<L7Config, Scene>
|
||||
let scene = chartObj?.getScene()
|
||||
if (!scene) {
|
||||
scene = new Scene({
|
||||
id: container,
|
||||
logoVisible: false,
|
||||
map: new GaodeMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle,
|
||||
pitch: miscStyle.mapPitch,
|
||||
center,
|
||||
zoom: basicStyle.autoFit === false ? basicStyle.zoomLevel : undefined,
|
||||
showLabel: !(basicStyle.showLabel === false),
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
if (scene.getLayers()?.length) {
|
||||
await scene.removeAllLayer()
|
||||
scene.setPitch(miscStyle.mapPitch)
|
||||
scene.setMapStyle(mapStyle)
|
||||
scene.map.showLabel = !(basicStyle.showLabel === false)
|
||||
}
|
||||
if (basicStyle.autoFit === false) {
|
||||
scene.setZoomAndCenter(basicStyle.zoomLevel, center)
|
||||
}
|
||||
}
|
||||
mapRendering(container)
|
||||
scene.once('loaded', () => {
|
||||
mapRendered(container)
|
||||
})
|
||||
this.configZoomButton(chart, scene)
|
||||
scene = await getMapScene(
|
||||
chart,
|
||||
scene,
|
||||
container,
|
||||
mapKey,
|
||||
basicStyle,
|
||||
miscStyle,
|
||||
mapStyle,
|
||||
center
|
||||
)
|
||||
|
||||
this.configZoomButton(chart, scene, mapKey)
|
||||
if (xAxis?.length < 2) {
|
||||
return new L7Wrapper(scene, undefined)
|
||||
}
|
||||
@ -171,9 +147,13 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
|
||||
scene.addPopup(tooltipLayer)
|
||||
}
|
||||
this.buildLabel(chart, configList)
|
||||
symbolicLayer.once('inited', () => {
|
||||
mapRendered(container)
|
||||
})
|
||||
symbolicLayer.on('inited', () => {
|
||||
chart.container = container
|
||||
configCarouselTooltip(chart, symbolicLayer, symbolicLayer.sourceOption.data, scene)
|
||||
qqMapRendered(scene)
|
||||
})
|
||||
symbolicLayer.on('click', ev => {
|
||||
const data = ev.feature
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
} from '@/views/chart/components/js/panel/types/impl/g2plot'
|
||||
import type { DualAxes, DualAxesOptions } from '@antv/g2plot/esm/plots/dual-axes'
|
||||
import {
|
||||
configRoundAngle,
|
||||
configPlotTooltipEvent,
|
||||
getAnalyse,
|
||||
getLabel,
|
||||
@ -42,6 +43,7 @@ import {
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import type { Options } from '@antv/g2plot/esm'
|
||||
import { Group } from '@antv/g-canvas'
|
||||
import { extremumEvt } from '@/views/chart/components/js/extremumUitl'
|
||||
|
||||
const { t } = useI18n()
|
||||
const DEFAULT_DATA = []
|
||||
@ -56,7 +58,8 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
'label-selector': ['vPosition', 'seriesLabelFormatter'],
|
||||
'tooltip-selector': [
|
||||
...CHART_MIX_EDITOR_PROPERTY_INNER['tooltip-selector'],
|
||||
'seriesTooltipFormatter'
|
||||
'seriesTooltipFormatter',
|
||||
'carousel'
|
||||
]
|
||||
}
|
||||
axis: AxisType[] = [...CHART_MIX_AXIS_TYPE, 'xAxisExtRight', 'yAxisExt']
|
||||
@ -94,6 +97,7 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
|
||||
async drawChart(drawOptions: G2PlotDrawOptions<DualAxes>): Promise<DualAxes> {
|
||||
const { chart, action, container } = drawOptions
|
||||
chart.container = container
|
||||
if (!chart.data?.left?.data?.length && !chart.data?.right?.data?.length) {
|
||||
return
|
||||
}
|
||||
@ -117,7 +121,6 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
valueExt: d.value
|
||||
}
|
||||
})
|
||||
|
||||
// options
|
||||
const initOptions: DualAxesOptions = {
|
||||
data: [data1, data2],
|
||||
@ -127,6 +130,7 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
geometryOptions: [
|
||||
{
|
||||
geometry: data1Type,
|
||||
marginRatio: 0,
|
||||
color: [],
|
||||
isGroup: isGroup,
|
||||
isStack: isStack,
|
||||
@ -174,6 +178,7 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
|
||||
newChart.on('point:click', action)
|
||||
newChart.on('interval:click', action)
|
||||
extremumEvt(newChart, chart, options, container)
|
||||
configPlotTooltipEvent(chart, newChart)
|
||||
return newChart
|
||||
}
|
||||
@ -292,18 +297,9 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
tempOption.geometryOptions[1].smooth = smooth
|
||||
tempOption.geometryOptions[1].point = point
|
||||
tempOption.geometryOptions[1].lineStyle = lineStyle
|
||||
|
||||
if (s.radiusColumnBar === 'roundAngle') {
|
||||
const columnStyle = {
|
||||
radius: [
|
||||
s.columnBarRightAngleRadius,
|
||||
s.columnBarRightAngleRadius,
|
||||
s.columnBarRightAngleRadius,
|
||||
s.columnBarRightAngleRadius
|
||||
]
|
||||
}
|
||||
tempOption.geometryOptions[0].columnStyle = columnStyle
|
||||
tempOption.geometryOptions[1].columnStyle = columnStyle
|
||||
tempOption.geometryOptions[0] = {
|
||||
...tempOption.geometryOptions[0],
|
||||
...configRoundAngle(chart, 'columnStyle')
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,7 +324,7 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
}
|
||||
|
||||
setupDefaultOptions(chart: ChartObj): ChartObj {
|
||||
const { customAttr, senior } = chart
|
||||
const { senior } = chart
|
||||
if (
|
||||
senior.functionCfg.emptyDataStrategy == undefined ||
|
||||
senior.functionCfg.emptyDataStrategy === 'ignoreData'
|
||||
@ -670,7 +666,8 @@ export class GroupColumnLineMix extends ColumnLineMix {
|
||||
'label-selector': ['vPosition', 'seriesLabelFormatter'],
|
||||
'tooltip-selector': [
|
||||
...CHART_MIX_EDITOR_PROPERTY_INNER['tooltip-selector'],
|
||||
'seriesTooltipFormatter'
|
||||
'seriesTooltipFormatter',
|
||||
'carousel'
|
||||
]
|
||||
}
|
||||
axisConfig = {
|
||||
@ -782,7 +779,8 @@ export class StackColumnLineMix extends ColumnLineMix {
|
||||
'label-selector': ['vPosition', 'seriesLabelFormatter'],
|
||||
'tooltip-selector': [
|
||||
...CHART_MIX_EDITOR_PROPERTY_INNER['tooltip-selector'],
|
||||
'seriesTooltipFormatter'
|
||||
'seriesTooltipFormatter',
|
||||
'carousel'
|
||||
]
|
||||
}
|
||||
axisConfig = {
|
||||
@ -895,7 +893,8 @@ export class DualLineMix extends ColumnLineMix {
|
||||
'label-selector': ['seriesLabelFormatter'],
|
||||
'tooltip-selector': [
|
||||
...CHART_MIX_EDITOR_PROPERTY_INNER['tooltip-selector'],
|
||||
'seriesTooltipFormatter'
|
||||
'seriesTooltipFormatter',
|
||||
'carousel'
|
||||
]
|
||||
}
|
||||
axisConfig = {
|
||||
|
@ -69,7 +69,7 @@ export class CirclePacking extends G2PlotChartView<CirclePackingOptions, G2Circl
|
||||
if (chart?.data?.data?.length) {
|
||||
// data
|
||||
const data = chart.data.data
|
||||
const { xAxis, yAxis, drillFields } = chart
|
||||
const { yAxis } = chart
|
||||
const ySort = yAxis[0]?.sort ?? 'none'
|
||||
const sort = {
|
||||
sort: (a, b) =>
|
||||
@ -123,7 +123,7 @@ export class CirclePacking extends G2PlotChartView<CirclePackingOptions, G2Circl
|
||||
'@antv/g2plot/esm/plots/circle-packing'
|
||||
)
|
||||
const newChart = new G2CirclePacking(container, options)
|
||||
newChart.on('point:click', param => {
|
||||
newChart.on('element:click', param => {
|
||||
const pointData = param?.data?.data
|
||||
if (pointData?.name === t('commons.all')) {
|
||||
return
|
||||
@ -177,7 +177,7 @@ export class CirclePacking extends G2PlotChartView<CirclePackingOptions, G2Circl
|
||||
textAlign: 'center',
|
||||
offsetY: 5,
|
||||
layout: labelAttr.fullDisplay ? [{ type: 'limit-in-plot' }] : tmpOptions.label.layout,
|
||||
formatter: (d: Datum, _point) => {
|
||||
formatter: (d: Datum) => {
|
||||
return d.children.length === 0 ? d.name : ''
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
getScaleValue
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { merge } from 'lodash-es'
|
||||
|
||||
@ -74,7 +74,7 @@ export class Gauge extends G2PlotChartView<GaugeOptions, G2Gauge> {
|
||||
// options
|
||||
const initOptions: GaugeOptions = {
|
||||
percent: 0,
|
||||
appendPadding: getPadding(chart),
|
||||
appendPadding: [0, 10, 15, 10],
|
||||
axis: {
|
||||
tickInterval: 0.2,
|
||||
label: {
|
||||
@ -109,8 +109,10 @@ export class Gauge extends G2PlotChartView<GaugeOptions, G2Gauge> {
|
||||
}
|
||||
})
|
||||
})
|
||||
const hasNoneData = chart.data?.series.some(s => !s.data?.[0])
|
||||
this.configEmptyDataStyle(newChart, hasNoneData ? [] : [1], container)
|
||||
const hasNoneData = chart.data?.series.some(
|
||||
s => s.data?.[0] === undefined || s.data?.[0] === null
|
||||
)
|
||||
this.configEmptyDataStyle(hasNoneData ? [] : [1], container, newChart)
|
||||
if (hasNoneData) {
|
||||
return
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ export class IndicatorChartView extends AbstractChartView {
|
||||
'indicator-value-selector',
|
||||
'indicator-name-selector',
|
||||
'threshold',
|
||||
'function-cfg'
|
||||
'function-cfg',
|
||||
'linkage'
|
||||
]
|
||||
propertyInner: EditorPropertyInner = {
|
||||
'background-overall-component': ['all'],
|
||||
|
@ -13,7 +13,8 @@ import {
|
||||
configPlotTooltipEvent,
|
||||
configYaxisTitleLengthLimit,
|
||||
getTooltipContainer,
|
||||
TOOLTIP_TPL
|
||||
TOOLTIP_TPL,
|
||||
getPadding
|
||||
} from '../../common/common_antv'
|
||||
import { DEFAULT_LEGEND_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
|
||||
@ -209,7 +210,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
|
||||
data: data,
|
||||
xField: 'yAxis',
|
||||
yField: 'yAxisExt',
|
||||
appendPadding: 30,
|
||||
appendPadding: getPadding(chart),
|
||||
pointStyle: {
|
||||
fillOpacity: 0.8,
|
||||
stroke: '#bbb'
|
||||
@ -476,7 +477,6 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
|
||||
this.configLegend,
|
||||
this.configXAxis,
|
||||
this.configYAxis,
|
||||
this.configAnalyse,
|
||||
this.configSlider,
|
||||
this.configBasicStyle
|
||||
)(chart, options, {}, this)
|
||||
|
@ -14,6 +14,8 @@ import {
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { defaults, isEmpty } from 'lodash-es'
|
||||
import { DEFAULT_LEGEND_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { type Datum } from '@antv/g2plot/esm'
|
||||
import { Group } from '@antv/g-canvas'
|
||||
|
||||
const { t } = useI18n()
|
||||
/**
|
||||
@ -144,6 +146,17 @@ export class Scatter extends G2PlotChartView<ScatterOptions, G2Scatter> {
|
||||
const { Scatter: G2Scatter } = await import('@antv/g2plot/esm/plots/scatter')
|
||||
const newChart = new G2Scatter(container, options)
|
||||
newChart.on('point:click', action)
|
||||
if (options.label) {
|
||||
newChart.on('label:click', e => {
|
||||
action({
|
||||
x: e.x,
|
||||
y: e.y,
|
||||
data: {
|
||||
data: e.target.attrs.data
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
configPlotTooltipEvent(chart, newChart)
|
||||
return newChart
|
||||
}
|
||||
@ -277,6 +290,41 @@ export class Scatter extends G2PlotChartView<ScatterOptions, G2Scatter> {
|
||||
return optionTmp
|
||||
}
|
||||
|
||||
protected configLabel(chart: Chart, options: ScatterOptions): ScatterOptions {
|
||||
const tmpOption = super.configLabel(chart, options)
|
||||
if (!tmpOption.label) {
|
||||
return options
|
||||
}
|
||||
const { label: labelAttr } = parseJson(chart.customAttr)
|
||||
tmpOption.label.style.fill = labelAttr.color
|
||||
const label = {
|
||||
...tmpOption.label,
|
||||
formatter: function (data: Datum) {
|
||||
const value = valueFormatter(data.value, labelAttr.labelFormatter)
|
||||
const group = new Group({})
|
||||
group.addShape({
|
||||
type: 'text',
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
data,
|
||||
text: value,
|
||||
textAlign: 'start',
|
||||
textBaseline: 'top',
|
||||
fontSize: labelAttr.fontSize,
|
||||
fontFamily: chart.fontFamily,
|
||||
fill: labelAttr.color
|
||||
}
|
||||
})
|
||||
return group
|
||||
}
|
||||
}
|
||||
return {
|
||||
...tmpOption,
|
||||
label
|
||||
}
|
||||
}
|
||||
|
||||
protected setupOptions(chart: Chart, options: ScatterOptions) {
|
||||
return flow(
|
||||
this.configTheme,
|
||||
@ -286,7 +334,6 @@ export class Scatter extends G2PlotChartView<ScatterOptions, G2Scatter> {
|
||||
this.configLegend,
|
||||
this.configXAxis,
|
||||
this.configYAxis,
|
||||
this.configAnalyse,
|
||||
this.configSlider,
|
||||
this.configBasicStyle
|
||||
)(chart, options)
|
||||
|
@ -27,19 +27,23 @@ import type { Datum } from '@antv/g2plot/esm/types/common'
|
||||
import { add } from 'mathjs'
|
||||
import isEmpty from 'lodash-es/isEmpty'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n()
|
||||
const DEFAULT_DATA = []
|
||||
export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
|
||||
axis: AxisType[] = PIE_AXIS_TYPE
|
||||
properties = PIE_EDITOR_PROPERTY
|
||||
propertyInner: EditorPropertyInner = {
|
||||
...PIE_EDITOR_PROPERTY_INNER,
|
||||
'basic-style-selector': ['colors', 'alpha', 'radius', 'topN', 'seriesColor']
|
||||
'basic-style-selector': ['colors', 'alpha', 'radius', 'topN', 'seriesColor'],
|
||||
'tooltip-selector': [...PIE_EDITOR_PROPERTY_INNER['tooltip-selector'], 'carousel']
|
||||
}
|
||||
axisConfig = PIE_AXIS_CONFIG
|
||||
|
||||
async drawChart(drawOptions: G2PlotDrawOptions<G2Pie>): Promise<G2Pie> {
|
||||
const { chart, container, action } = drawOptions
|
||||
this.configEmptyDataStyle(chart.data?.data, container, null, t('chart.no_data_or_not_positive'))
|
||||
chart.container = container
|
||||
if (!chart.data?.data?.length) {
|
||||
return
|
||||
}
|
||||
@ -115,12 +119,22 @@ export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
|
||||
field: {
|
||||
type: 'cat'
|
||||
}
|
||||
},
|
||||
state: {
|
||||
active: {
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
fillOpacity: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const options = this.setupOptions(chart, initOptions)
|
||||
const { Pie: G2Pie } = await import('@antv/g2plot/esm/plots/pie')
|
||||
const newChart = new G2Pie(container, options)
|
||||
newChart.on('interval:click', action)
|
||||
newChart.on('interval:click', d => {
|
||||
d.data?.data?.field !== customAttr.basicStyle.topNLabel && action(d)
|
||||
})
|
||||
configPlotTooltipEvent(chart, newChart)
|
||||
return newChart
|
||||
}
|
||||
@ -244,6 +258,7 @@ export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
|
||||
},
|
||||
container: getTooltipContainer(`tooltip-${chart.id}`),
|
||||
itemTpl: TOOLTIP_TPL,
|
||||
shared: true,
|
||||
enterable: true
|
||||
}
|
||||
return {
|
||||
@ -338,7 +353,8 @@ export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
|
||||
export class PieDonut extends Pie {
|
||||
propertyInner: EditorPropertyInner = {
|
||||
...PIE_EDITOR_PROPERTY_INNER,
|
||||
'basic-style-selector': ['colors', 'alpha', 'radius', 'innerRadius', 'topN', 'seriesColor']
|
||||
'basic-style-selector': ['colors', 'alpha', 'radius', 'innerRadius', 'topN', 'seriesColor'],
|
||||
'tooltip-selector': [...PIE_EDITOR_PROPERTY_INNER['tooltip-selector'], 'carousel']
|
||||
}
|
||||
protected configBasicStyle(chart: Chart, options: PieOptions): PieOptions {
|
||||
const tmp = super.configBasicStyle(chart, options)
|
||||
|
@ -40,6 +40,7 @@ export class Rose extends G2PlotChartView<RoseOptions, G2Rose> {
|
||||
|
||||
async drawChart(drawOptions: G2PlotDrawOptions<G2Rose>): Promise<G2Rose> {
|
||||
const { chart, container, action } = drawOptions
|
||||
this.configEmptyDataStyle(chart.data?.data, container, null, t('chart.no_data_or_not_positive'))
|
||||
if (!chart?.data?.data?.length) {
|
||||
return
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ export const TABLE_EDITOR_PROPERTY: EditorProperty[] = [
|
||||
'table-cell-selector',
|
||||
'title-selector',
|
||||
'tooltip-selector',
|
||||
'summary-selector',
|
||||
'function-cfg',
|
||||
'threshold',
|
||||
'scroll-cfg',
|
||||
|
@ -123,7 +123,8 @@ export class TableHeatmap extends G2PlotChartView<HeatmapOptions, Heatmap> {
|
||||
const xFieldExt = xAxisExt[0].dataeaseName
|
||||
const extColorField = extColor[0].dataeaseName
|
||||
// data
|
||||
const data = cloneDeep(chart.data.tableRow)
|
||||
const tmpData = cloneDeep(chart.data.tableRow)
|
||||
const data = tmpData.filter(cell => cell[xField] && cell[xFieldExt] && cell[extColorField])
|
||||
data.forEach(i => {
|
||||
Object.keys(i).forEach(key => {
|
||||
if (key === '*') {
|
||||
@ -207,6 +208,12 @@ export class TableHeatmap extends G2PlotChartView<HeatmapOptions, Heatmap> {
|
||||
return newChart
|
||||
}
|
||||
|
||||
protected configTheme(chart: Chart, options: HeatmapOptions): HeatmapOptions {
|
||||
const tmp = super.configTheme(chart, options)
|
||||
tmp.theme.innerLabels.offset = 0
|
||||
return tmp
|
||||
}
|
||||
|
||||
protected configBasicStyle(chart: Chart, options: HeatmapOptions): HeatmapOptions {
|
||||
const basicStyle = parseJson(chart.customAttr).basicStyle
|
||||
const color = basicStyle.colors?.map(ele => {
|
||||
|
@ -14,7 +14,7 @@ import { hexColorToRGBA, isAlphaColor, parseJson } from '../../../util'
|
||||
import { S2ChartView, S2DrawOptions } from '../../types/impl/s2'
|
||||
import { TABLE_EDITOR_PROPERTY, TABLE_EDITOR_PROPERTY_INNER } from './common'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { isEqual, isNumber, merge } from 'lodash-es'
|
||||
import { filter, isEqual, isNumber, merge } from 'lodash-es'
|
||||
import {
|
||||
copyContent,
|
||||
CustomDataCell,
|
||||
@ -22,37 +22,19 @@ import {
|
||||
getRowIndex,
|
||||
calculateHeaderHeight,
|
||||
SortTooltip,
|
||||
configSummaryRow,
|
||||
summaryRowStyle,
|
||||
configEmptyDataStyle,
|
||||
getLeafNodes,
|
||||
getColumns
|
||||
getColumns,
|
||||
drawImage,
|
||||
getSummaryRow,
|
||||
SummaryCell
|
||||
} from '@/views/chart/components/js/panel/common/common_table'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
class ImageCell extends CustomDataCell {
|
||||
protected drawTextShape(): void {
|
||||
const img = new Image()
|
||||
const { x, y, width, height, fieldValue } = this.meta
|
||||
img.src = fieldValue as string
|
||||
img.setAttribute('crossOrigin', 'anonymous')
|
||||
img.onload = () => {
|
||||
!this.cfg.children && (this.cfg.children = [])
|
||||
const { width: imgWidth, height: imgHeight } = img
|
||||
const ratio = Math.max(imgWidth / width, imgHeight / height)
|
||||
// 不铺满,部分留白
|
||||
const imgShowWidth = (imgWidth / ratio) * 0.8
|
||||
const imgShowHeight = (imgHeight / ratio) * 0.8
|
||||
this.textShape = this.addShape('image', {
|
||||
attrs: {
|
||||
x: x + (imgShowWidth < width ? (width - imgShowWidth) / 2 : 0),
|
||||
y: y + (imgShowHeight < height ? (height - imgShowHeight) / 2 : 0),
|
||||
width: imgShowWidth,
|
||||
height: imgShowHeight,
|
||||
img
|
||||
}
|
||||
})
|
||||
}
|
||||
drawImage.apply(this)
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -75,9 +57,7 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
'alpha',
|
||||
'tablePageMode',
|
||||
'showHoverStyle',
|
||||
'autoWrap',
|
||||
'showSummary',
|
||||
'summaryLabel'
|
||||
'autoWrap'
|
||||
],
|
||||
'table-cell-selector': [
|
||||
...TABLE_EDITOR_PROPERTY_INNER['table-cell-selector'],
|
||||
@ -85,7 +65,8 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
'tableColumnFreezeHead',
|
||||
'tableRowFreezeHead',
|
||||
'mergeCells'
|
||||
]
|
||||
],
|
||||
'summary-selector': ['showSummary', 'summaryLabel']
|
||||
}
|
||||
axis: AxisType[] = ['xAxis', 'filter', 'drill']
|
||||
axisConfig: AxisConfig = {
|
||||
@ -140,7 +121,7 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
if (value === null || value === undefined) {
|
||||
return value
|
||||
}
|
||||
if (![2, 3].includes(f.deType) || !isNumber(value)) {
|
||||
if (![2, 3, 4].includes(f.deType) || !isNumber(value)) {
|
||||
return value
|
||||
}
|
||||
let formatCfg = f.formatterCfg
|
||||
@ -215,37 +196,6 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
s2Options.frozenColCount = tableCell.tableColumnFreezeHead ?? 0
|
||||
s2Options.frozenRowCount = tableCell.tableRowFreezeHead ?? 0
|
||||
}
|
||||
// 开启序号之后,第一列就是序号列,修改 label 即可
|
||||
if (s2Options.showSeriesNumber) {
|
||||
let indexLabel = tableHeader.indexLabel
|
||||
if (!indexLabel) {
|
||||
indexLabel = ''
|
||||
}
|
||||
s2Options.layoutCoordinate = (_, __, col) => {
|
||||
if (col.colIndex === 0 && col.rowIndex === 0) {
|
||||
col.label = indexLabel
|
||||
col.value = indexLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
s2Options.dataCell = viewMeta => {
|
||||
const field = fields.filter(f => f.dataeaseName === viewMeta.valueField)?.[0]
|
||||
if (field?.deType === 7 && chart.showPosition !== 'dialog') {
|
||||
return new ImageCell(viewMeta, viewMeta?.spreadsheet)
|
||||
}
|
||||
if (viewMeta.colIndex === 0 && s2Options.showSeriesNumber) {
|
||||
if (tableCell.mergeCells) {
|
||||
viewMeta.fieldValue = getRowIndex(s2Options.mergedCellsInfo, viewMeta)
|
||||
} else {
|
||||
viewMeta.fieldValue =
|
||||
pageInfo.pageSize * (pageInfo.currentPage - 1) + viewMeta.rowIndex + 1
|
||||
}
|
||||
}
|
||||
// 配置文本自动换行参数
|
||||
viewMeta.autoWrap = tableCell.mergeCells ? false : basicStyle.autoWrap
|
||||
viewMeta.maxLines = basicStyle.maxLines
|
||||
return new CustomDataCell(viewMeta, viewMeta?.spreadsheet)
|
||||
}
|
||||
// tooltip
|
||||
this.configTooltip(chart, s2Options)
|
||||
// 合并单元格
|
||||
@ -274,12 +224,12 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
return new CustomTableColCell(node, sheet, config)
|
||||
}
|
||||
}
|
||||
// 总计
|
||||
configSummaryRow(chart, s2Options, newData, tableHeader, basicStyle, basicStyle.showSummary)
|
||||
// 序列号和总计行
|
||||
this.configSummaryRowAndIndex(chart, pageInfo, s2Options, s2DataConfig)
|
||||
// 开始渲染
|
||||
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
|
||||
// 总计紧贴在单元格后面
|
||||
summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
this.summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
// 开启自动换行
|
||||
if (basicStyle.autoWrap && !tableCell.mergeCells) {
|
||||
// 调整表头宽度时,计算表头高度
|
||||
@ -417,13 +367,13 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
newChart.on(S2Event.COL_CELL_HOVER, event => this.showTooltip(newChart, event, meta))
|
||||
newChart.on(S2Event.DATA_CELL_HOVER, event => this.showTooltip(newChart, event, meta))
|
||||
newChart.on(S2Event.MERGED_CELLS_HOVER, event => this.showTooltip(newChart, event, meta))
|
||||
// touch
|
||||
this.configTouchEvent(newChart, drawOption, meta)
|
||||
}
|
||||
// header resize
|
||||
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, ev => resizeAction(ev))
|
||||
// right click
|
||||
newChart.on(S2Event.GLOBAL_CONTEXT_MENU, event => copyContent(newChart, event, meta))
|
||||
// touch
|
||||
this.configTouchEvent(newChart, drawOption, meta)
|
||||
// theme
|
||||
const customTheme = this.configTheme(chart)
|
||||
newChart.setThemeCfg({ theme: customTheme })
|
||||
@ -444,6 +394,11 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
const fontStyle = tableCell.isItalic ? 'italic' : 'normal'
|
||||
const fontWeight = tableCell.isBolder === false ? 'normal' : 'bold'
|
||||
const mergeCellTheme: S2Theme = {
|
||||
dataCell: {
|
||||
cell: {
|
||||
crossBackgroundColor: tableItemBgColor
|
||||
}
|
||||
},
|
||||
mergedCell: {
|
||||
cell: {
|
||||
backgroundColor: tableItemBgColor,
|
||||
@ -488,6 +443,92 @@ export class TableInfo extends S2ChartView<TableSheet> {
|
||||
return theme
|
||||
}
|
||||
|
||||
protected configSummaryRowAndIndex(
|
||||
chart: Chart,
|
||||
pageInfo: PageInfo,
|
||||
s2Options: S2Options,
|
||||
s2DataConfig: S2DataConfig
|
||||
) {
|
||||
const { tableHeader, basicStyle, tableCell } = parseJson(chart.customAttr)
|
||||
const fields = chart.data?.fields ?? []
|
||||
// 开启序号之后,第一列就是序号列,修改 label 即可
|
||||
if (s2Options.showSeriesNumber) {
|
||||
let indexLabel = tableHeader.indexLabel
|
||||
if (!indexLabel) {
|
||||
indexLabel = ''
|
||||
}
|
||||
s2Options.layoutCoordinate = (_, __, col) => {
|
||||
if (col.colIndex === 0 && col.rowIndex === 0) {
|
||||
col.label = indexLabel
|
||||
col.value = indexLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
const { showSummary, summaryLabel } = basicStyle
|
||||
const data = s2DataConfig.data
|
||||
const xAxis = chart.xAxis
|
||||
if (showSummary && data?.length) {
|
||||
// 设置汇总行高度和表头一致
|
||||
const heightByField = {}
|
||||
heightByField[data.length] = tableHeader.tableTitleHeight
|
||||
s2Options.style.rowCfg = { heightByField }
|
||||
// 计算汇总加入到数据里,冻结最后一行
|
||||
s2Options.frozenTrailingRowCount = 1
|
||||
const axis = filter(xAxis, axis => [2, 3, 4].includes(axis.deType))
|
||||
const summaryObj = getSummaryRow(data, axis, basicStyle.seriesSummary) as any
|
||||
data.push(summaryObj)
|
||||
}
|
||||
s2Options.dataCell = viewMeta => {
|
||||
// 总计行处理
|
||||
if (showSummary && viewMeta.rowIndex === data.length - 1) {
|
||||
if (viewMeta.colIndex === 0) {
|
||||
if (tableHeader.showIndex) {
|
||||
viewMeta.fieldValue = summaryLabel ?? t('chart.total_show')
|
||||
} else {
|
||||
// 第一列不是数值类型的,显示总计
|
||||
if (![2, 3, 4].includes(xAxis?.[0]?.deType)) {
|
||||
viewMeta.fieldValue = summaryLabel ?? t('chart.total_show')
|
||||
}
|
||||
}
|
||||
}
|
||||
return new SummaryCell(viewMeta, viewMeta?.spreadsheet)
|
||||
}
|
||||
const field = fields.find(f => f.dataeaseName === viewMeta.valueField)
|
||||
if (field?.deType === 7 && chart.showPosition !== 'dialog') {
|
||||
return new ImageCell(viewMeta, viewMeta?.spreadsheet)
|
||||
}
|
||||
if (viewMeta.colIndex === 0 && s2Options.showSeriesNumber) {
|
||||
if (tableCell.mergeCells) {
|
||||
viewMeta.fieldValue = getRowIndex(s2Options.mergedCellsInfo, viewMeta)
|
||||
} else {
|
||||
viewMeta.fieldValue =
|
||||
pageInfo.pageSize * (pageInfo.currentPage - 1) + viewMeta.rowIndex + 1
|
||||
}
|
||||
}
|
||||
// 配置文本自动换行参数
|
||||
viewMeta.autoWrap = tableCell.mergeCells ? false : basicStyle.autoWrap
|
||||
viewMeta.maxLines = basicStyle.maxLines
|
||||
return new CustomDataCell(viewMeta, viewMeta?.spreadsheet)
|
||||
}
|
||||
}
|
||||
|
||||
protected summaryRowStyle(newChart: TableSheet, newData, tableCell, tableHeader, showSummary) {
|
||||
if (!showSummary || !newData.length) return
|
||||
const columns = newChart.dataCfg.fields.columns
|
||||
const showHeader = tableHeader.showTableHeader === true
|
||||
// 不显示表头时,减少一个表头的高度
|
||||
const headerAndSummaryHeight = showHeader ? getMaxTreeDepth(columns) + 1 : 1
|
||||
newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
|
||||
const totalHeight =
|
||||
tableHeader.tableTitleHeight * headerAndSummaryHeight +
|
||||
tableCell.tableItemHeight * (newData.length - 1)
|
||||
if (totalHeight < newChart.container.cfg.height) {
|
||||
newChart.options.height =
|
||||
totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super('table-info', [])
|
||||
}
|
||||
@ -508,3 +549,17 @@ function getStartPosition(node) {
|
||||
}
|
||||
return getStartPosition(node.children[0])
|
||||
}
|
||||
|
||||
function getMaxTreeDepth(nodes) {
|
||||
if (!nodes?.length) {
|
||||
return 0
|
||||
}
|
||||
return Math.max(
|
||||
...nodes.map(node => {
|
||||
if (!node.children?.length) {
|
||||
return 1
|
||||
}
|
||||
return getMaxTreeDepth(node.children) + 1
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -2,10 +2,11 @@ import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { formatterItem, valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
import {
|
||||
configEmptyDataStyle,
|
||||
configSummaryRow,
|
||||
copyContent,
|
||||
CustomDataCell,
|
||||
getSummaryRow,
|
||||
SortTooltip,
|
||||
summaryRowStyle
|
||||
SummaryCell
|
||||
} from '@/views/chart/components/js/panel/common/common_table'
|
||||
import { S2ChartView, S2DrawOptions } from '@/views/chart/components/js/panel/types/impl/s2'
|
||||
import { parseJson } from '@/views/chart/components/js/util'
|
||||
@ -19,7 +20,7 @@ import {
|
||||
TableSheet,
|
||||
ViewMeta
|
||||
} from '@antv/s2'
|
||||
import { cloneDeep, isNumber } from 'lodash-es'
|
||||
import { isNumber } from 'lodash-es'
|
||||
import { TABLE_EDITOR_PROPERTY, TABLE_EDITOR_PROPERTY_INNER } from './common'
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -37,8 +38,7 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
],
|
||||
'basic-style-selector': [
|
||||
...TABLE_EDITOR_PROPERTY_INNER['basic-style-selector'],
|
||||
'showSummary',
|
||||
'summaryLabel',
|
||||
'tablePageMode',
|
||||
'showHoverStyle'
|
||||
],
|
||||
'table-cell-selector': [
|
||||
@ -46,7 +46,8 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
'tableFreeze',
|
||||
'tableColumnFreezeHead',
|
||||
'tableRowFreezeHead'
|
||||
]
|
||||
],
|
||||
'summary-selector': ['showSummary', 'summaryLabel']
|
||||
}
|
||||
axis: AxisType[] = ['xAxis', 'yAxis', 'drill', 'filter']
|
||||
axisConfig: AxisConfig = {
|
||||
@ -66,7 +67,7 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
}
|
||||
|
||||
drawChart(drawOption: S2DrawOptions<TableSheet>): TableSheet {
|
||||
const { container, chart, action, resizeAction } = drawOption
|
||||
const { container, chart, action, pageInfo, resizeAction } = drawOption
|
||||
const containerDom = document.getElementById(container)
|
||||
if (!containerDom) return
|
||||
|
||||
@ -112,7 +113,7 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
if (value === null || value === undefined) {
|
||||
return value
|
||||
}
|
||||
if (![2, 3].includes(f.deType) || !isNumber(value)) {
|
||||
if (![2, 3, 4].includes(f.deType) || !isNumber(value)) {
|
||||
return value
|
||||
}
|
||||
let formatCfg = f.formatterCfg
|
||||
@ -160,19 +161,6 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
s2Options.frozenColCount = tableCell.tableColumnFreezeHead ?? 0
|
||||
s2Options.frozenRowCount = tableCell.tableRowFreezeHead ?? 0
|
||||
}
|
||||
// 开启序号之后,第一列就是序号列,修改 label 即可
|
||||
if (s2Options.showSeriesNumber) {
|
||||
let indexLabel = tableHeader.indexLabel
|
||||
if (!indexLabel) {
|
||||
indexLabel = ''
|
||||
}
|
||||
s2Options.layoutCoordinate = (_, __, col) => {
|
||||
if (col.colIndex === 0 && col.rowIndex === 0) {
|
||||
col.label = indexLabel
|
||||
col.value = indexLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
// tooltip
|
||||
this.configTooltip(chart, s2Options)
|
||||
// 隐藏表头,保留顶部的分割线, 禁用表头横向 resize
|
||||
@ -193,13 +181,12 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
chart.container = container
|
||||
this.configHeaderInteraction(chart, s2Options)
|
||||
}
|
||||
|
||||
// 总计
|
||||
configSummaryRow(chart, s2Options, newData, tableHeader, basicStyle, basicStyle.showSummary)
|
||||
// 配置总计和序号列
|
||||
this.configSummaryRowAndIndex(chart, pageInfo, s2Options, s2DataConfig)
|
||||
// 开始渲染
|
||||
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
|
||||
// 总计紧贴在单元格后面
|
||||
summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
this.summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary)
|
||||
// 自适应铺满
|
||||
if (basicStyle.tableColumnMode === 'adapt') {
|
||||
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => {
|
||||
@ -281,19 +268,86 @@ export class TableNormal extends S2ChartView<TableSheet> {
|
||||
if (show) {
|
||||
newChart.on(S2Event.COL_CELL_HOVER, event => this.showTooltip(newChart, event, meta))
|
||||
newChart.on(S2Event.DATA_CELL_HOVER, event => this.showTooltip(newChart, event, meta))
|
||||
// touch
|
||||
this.configTouchEvent(newChart, drawOption, meta)
|
||||
}
|
||||
// header resize
|
||||
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, ev => resizeAction(ev))
|
||||
// right click
|
||||
newChart.on(S2Event.GLOBAL_CONTEXT_MENU, event => copyContent(newChart, event, meta))
|
||||
// touch
|
||||
this.configTouchEvent(newChart, drawOption, meta)
|
||||
// theme
|
||||
const customTheme = this.configTheme(chart)
|
||||
newChart.setThemeCfg({ theme: customTheme })
|
||||
|
||||
return newChart
|
||||
}
|
||||
|
||||
protected configSummaryRowAndIndex(
|
||||
chart: Chart,
|
||||
pageInfo: PageInfo,
|
||||
s2Options: S2Options,
|
||||
s2DataConfig: S2DataConfig
|
||||
) {
|
||||
const { tableHeader, basicStyle } = parseJson(chart.customAttr)
|
||||
// 开启序号之后,第一列就是序号列,修改 label 即可
|
||||
if (s2Options.showSeriesNumber) {
|
||||
let indexLabel = tableHeader.indexLabel
|
||||
if (!indexLabel) {
|
||||
indexLabel = ''
|
||||
}
|
||||
s2Options.layoutCoordinate = (_, __, col) => {
|
||||
if (col.colIndex === 0 && col.rowIndex === 0) {
|
||||
col.label = indexLabel
|
||||
col.value = indexLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
const { showSummary, summaryLabel } = basicStyle
|
||||
const data = s2DataConfig.data
|
||||
const { xAxis, yAxis } = chart
|
||||
if (showSummary && data?.length) {
|
||||
// 设置汇总行高度和表头一致
|
||||
const heightByField = {}
|
||||
heightByField[data.length] = tableHeader.tableTitleHeight
|
||||
s2Options.style.rowCfg = { heightByField }
|
||||
// 计算汇总加入到数据里,冻结最后一行
|
||||
s2Options.frozenTrailingRowCount = 1
|
||||
const summaryObj = getSummaryRow(data, yAxis, basicStyle.seriesSummary) as any
|
||||
data.push(summaryObj)
|
||||
}
|
||||
s2Options.dataCell = viewMeta => {
|
||||
// 总计行处理
|
||||
if (showSummary && viewMeta.rowIndex === data.length - 1) {
|
||||
if (viewMeta.colIndex === 0) {
|
||||
if (tableHeader.showIndex || xAxis?.length) {
|
||||
viewMeta.fieldValue = summaryLabel ?? t('chart.total_show')
|
||||
}
|
||||
}
|
||||
return new SummaryCell(viewMeta, viewMeta?.spreadsheet)
|
||||
}
|
||||
if (viewMeta.colIndex === 0 && s2Options.showSeriesNumber) {
|
||||
viewMeta.fieldValue = pageInfo.pageSize * (pageInfo.currentPage - 1) + viewMeta.rowIndex + 1
|
||||
}
|
||||
return new CustomDataCell(viewMeta, viewMeta?.spreadsheet)
|
||||
}
|
||||
}
|
||||
|
||||
protected summaryRowStyle(newChart, newData, tableCell, tableHeader, showSummary) {
|
||||
if (!showSummary || !newData.length) return
|
||||
newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
|
||||
const showHeader = tableHeader.showTableHeader === true
|
||||
// 不显示表头时,减少一个表头的高度
|
||||
const headerAndSummaryHeight = showHeader ? 2 : 1
|
||||
const totalHeight =
|
||||
tableHeader.tableTitleHeight * headerAndSummaryHeight +
|
||||
tableCell.tableItemHeight * (newData.length - 1)
|
||||
if (totalHeight < newChart.container.cfg.height) {
|
||||
newChart.options.height =
|
||||
totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super('table-normal', [])
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ import {
|
||||
TotalStatus,
|
||||
Aggregation,
|
||||
S2DataConfig,
|
||||
MergedCell
|
||||
MergedCell,
|
||||
LayoutResult
|
||||
} from '@antv/s2'
|
||||
import { formatterItem, valueFormatter } from '../../../formatter'
|
||||
import { hexColorToRGBA, isAlphaColor, parseJson } from '../../../util'
|
||||
@ -91,7 +92,8 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
'showColTooltip',
|
||||
'showRowTooltip',
|
||||
'showHorizonBorder',
|
||||
'showVerticalBorder'
|
||||
'showVerticalBorder',
|
||||
'rowHeaderFreeze'
|
||||
],
|
||||
'table-total-selector': ['row', 'col'],
|
||||
'basic-style-selector': [
|
||||
@ -100,7 +102,9 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
'tableScrollBarColor',
|
||||
'alpha',
|
||||
'tableLayoutMode',
|
||||
'showHoverStyle'
|
||||
'showHoverStyle',
|
||||
'quotaPosition',
|
||||
'quotaColLabel'
|
||||
]
|
||||
}
|
||||
axis: AxisType[] = ['xAxis', 'xAxisExt', 'yAxis', 'filter']
|
||||
@ -162,7 +166,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
if (value === null || value === undefined) {
|
||||
return value
|
||||
}
|
||||
if (![2, 3].includes(f.deType) || !isNumber(value)) {
|
||||
if (![2, 3, 4].includes(f.deType) || !isNumber(value)) {
|
||||
return value
|
||||
}
|
||||
if (f.formatterCfg) {
|
||||
@ -175,7 +179,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
})
|
||||
|
||||
// total config
|
||||
const { basicStyle, tooltip, tableTotal } = parseJson(chart.customAttr)
|
||||
const { basicStyle, tooltip, tableTotal, tableHeader } = parseJson(chart.customAttr)
|
||||
if (!tableTotal.row.subTotalsDimensionsNew || tableTotal.row.subTotalsDimensions == undefined) {
|
||||
tableTotal.row.subTotalsDimensions = r
|
||||
}
|
||||
@ -183,6 +187,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
|
||||
// 解析合计、小计排序
|
||||
const sortParams = []
|
||||
let rowTotalSort = false
|
||||
if (
|
||||
tableTotal.row.totalSort &&
|
||||
tableTotal.row.totalSort !== 'none' &&
|
||||
@ -190,16 +195,20 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
tableTotal.row.showGrandTotals &&
|
||||
v.indexOf(tableTotal.row.totalSortField) > -1
|
||||
) {
|
||||
const sort = {
|
||||
sortFieldId: c[0],
|
||||
sortMethod: tableTotal.row.totalSort.toUpperCase(),
|
||||
sortByMeasure: TOTAL_VALUE,
|
||||
query: {
|
||||
[EXTRA_FIELD]: tableTotal.row.totalSortField
|
||||
c.forEach(i => {
|
||||
const sort = {
|
||||
sortFieldId: i,
|
||||
sortMethod: tableTotal.row.totalSort.toUpperCase(),
|
||||
sortByMeasure: TOTAL_VALUE,
|
||||
query: {
|
||||
[EXTRA_FIELD]: tableTotal.row.totalSortField
|
||||
}
|
||||
}
|
||||
}
|
||||
sortParams.push(sort)
|
||||
sortParams.push(sort)
|
||||
})
|
||||
rowTotalSort = true
|
||||
}
|
||||
let colTotalSort = false
|
||||
if (
|
||||
tableTotal.col.totalSort &&
|
||||
tableTotal.col.totalSort !== 'none' &&
|
||||
@ -207,15 +216,18 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
tableTotal.col.showGrandTotals &&
|
||||
v.indexOf(tableTotal.col.totalSortField) > -1
|
||||
) {
|
||||
const sort = {
|
||||
sortFieldId: r[0],
|
||||
sortMethod: tableTotal.col.totalSort.toUpperCase(),
|
||||
sortByMeasure: TOTAL_VALUE,
|
||||
query: {
|
||||
[EXTRA_FIELD]: tableTotal.col.totalSortField
|
||||
r.forEach(i => {
|
||||
const sort = {
|
||||
sortFieldId: i,
|
||||
sortMethod: tableTotal.col.totalSort.toUpperCase(),
|
||||
sortByMeasure: TOTAL_VALUE,
|
||||
query: {
|
||||
[EXTRA_FIELD]: tableTotal.col.totalSortField
|
||||
}
|
||||
}
|
||||
}
|
||||
sortParams.push(sort)
|
||||
sortParams.push(sort)
|
||||
})
|
||||
colTotalSort = true
|
||||
}
|
||||
//列维度为空,行排序按照指标列来排序,取第一个有排序设置的指标
|
||||
if (!columnFields?.length) {
|
||||
@ -244,9 +256,23 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
col: chart.xAxisExt,
|
||||
quota: chart.yAxis
|
||||
}
|
||||
//树形模式下,列维度为空,行小计会变成列总计,特殊处理下
|
||||
if (basicStyle.tableLayoutMode === 'tree' && !chart.xAxisExt?.length) {
|
||||
tableTotal.col.calcTotals = tableTotal.row.calcSubTotals
|
||||
// 沒有列维度需要特殊处理
|
||||
if (!chart.xAxisExt?.length) {
|
||||
//树形模式下,列维度为空,行小计的配置会变成列总计
|
||||
if (basicStyle.tableLayoutMode === 'tree') {
|
||||
tableTotal.col.calcTotals = tableTotal.row.calcSubTotals
|
||||
if (!tableTotal.col.calcTotals.cfg?.length) {
|
||||
tableTotal.col.calcTotals.cfg = chart.yAxis.map(y => {
|
||||
return {
|
||||
dataeaseName: y.dataeaseName,
|
||||
aggregation: 'SUM'
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 列总计设置为空
|
||||
tableTotal.col.calcTotals.calcFunc = () => '-'
|
||||
}
|
||||
}
|
||||
totals.forEach(total => {
|
||||
if (total.cfg?.length) {
|
||||
@ -262,12 +288,93 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
})
|
||||
// 空值处理
|
||||
const newData = this.configEmptyDataStrategy(chart)
|
||||
// 行列维度排序
|
||||
if (!rowTotalSort) {
|
||||
c?.forEach((f, i) => {
|
||||
if (valueFieldMap[f]?.sort === 'none') {
|
||||
return
|
||||
}
|
||||
const sort = {
|
||||
sortFieldId: f
|
||||
}
|
||||
const sortMethod = valueFieldMap[f]?.sort?.toUpperCase()
|
||||
if (sortMethod === 'CUSTOM_SORT') {
|
||||
sort.sortBy = valueFieldMap[f].customSort
|
||||
} else {
|
||||
if (i === 0) {
|
||||
sort.sortMethod = sortMethod
|
||||
} else {
|
||||
const fieldValues = newData.map(item => item[f])
|
||||
const uniqueValues = [...new Set(fieldValues)]
|
||||
|
||||
// 根据配置动态决定排序顺序
|
||||
uniqueValues.sort((a, b) => {
|
||||
if ([2, 3, 4].includes(valueFieldMap[f]?.deType)) {
|
||||
return sortMethod === 'ASC' ? a - b : b - a
|
||||
}
|
||||
if (!a && !b) {
|
||||
return 0
|
||||
}
|
||||
if (!a) {
|
||||
return sortMethod === 'ASC' ? -1 : 1
|
||||
}
|
||||
if (!b) {
|
||||
return sortMethod === 'ASC' ? 1 : -1
|
||||
}
|
||||
return sortMethod === 'ASC' ? a.localeCompare(b) : b.localeCompare(a)
|
||||
})
|
||||
sort.sortBy = uniqueValues
|
||||
}
|
||||
}
|
||||
sortParams.push(sort)
|
||||
})
|
||||
}
|
||||
if (!colTotalSort) {
|
||||
r?.forEach((f, i) => {
|
||||
if (valueFieldMap[f]?.sort === 'none') {
|
||||
return
|
||||
}
|
||||
const sort = {
|
||||
sortFieldId: f
|
||||
}
|
||||
const sortMethod = valueFieldMap[f]?.sort?.toUpperCase()
|
||||
if (sortMethod === 'CUSTOM_SORT') {
|
||||
sort.sortBy = valueFieldMap[f].customSort
|
||||
} else {
|
||||
if (i === 0) {
|
||||
sort.sortMethod = sortMethod
|
||||
} else {
|
||||
const fieldValues = newData.map(item => item[f])
|
||||
const uniqueValues = [...new Set(fieldValues)]
|
||||
// 根据配置动态决定排序顺序
|
||||
uniqueValues.sort((a, b) => {
|
||||
if ([2, 3, 4].includes(valueFieldMap[f]?.deType)) {
|
||||
return sortMethod === 'ASC' ? a - b : b - a
|
||||
}
|
||||
if (!a && !b) {
|
||||
return 0
|
||||
}
|
||||
if (!a) {
|
||||
return sortMethod === 'ASC' ? -1 : 1
|
||||
}
|
||||
if (!b) {
|
||||
return sortMethod === 'ASC' ? 1 : -1
|
||||
}
|
||||
return sortMethod === 'ASC' ? a.localeCompare(b) : b.localeCompare(a)
|
||||
})
|
||||
sort.sortBy = uniqueValues
|
||||
}
|
||||
}
|
||||
sortParams.push(sort)
|
||||
})
|
||||
}
|
||||
// data config
|
||||
const s2DataConfig: S2DataConfig = {
|
||||
fields: {
|
||||
rows: r,
|
||||
columns: c,
|
||||
values: v
|
||||
values: v,
|
||||
valueInCols: !(basicStyle.quotaPosition === 'row')
|
||||
},
|
||||
meta: meta,
|
||||
data: newData,
|
||||
@ -277,6 +384,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
width: containerDom.offsetWidth,
|
||||
height: containerDom.offsetHeight,
|
||||
totals: tableTotal as Totals,
|
||||
cornerExtraFieldText: basicStyle.quotaColLabel ?? t('dataset.value'),
|
||||
conditions: this.configConditions(chart),
|
||||
tooltip: {
|
||||
getContainer: () => containerDom
|
||||
@ -288,21 +396,204 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
},
|
||||
dataCell: meta => {
|
||||
return new CustomDataCell(meta, meta.spreadsheet)
|
||||
}
|
||||
},
|
||||
frozenRowHeader: !(tableHeader.rowHeaderFreeze === false)
|
||||
}
|
||||
// options
|
||||
s2Options.style = this.configStyle(chart, s2DataConfig)
|
||||
s2Options.style.hierarchyCollapse = true
|
||||
// 默认展开层级
|
||||
if (basicStyle.tableLayoutMode === 'tree') {
|
||||
const { defaultExpandLevel } = basicStyle
|
||||
if (isNumber(defaultExpandLevel)) {
|
||||
if (defaultExpandLevel >= chart.xAxis.length) {
|
||||
s2Options.style.rowExpandDepth = defaultExpandLevel
|
||||
} else {
|
||||
s2Options.style.rowExpandDepth = defaultExpandLevel - 2
|
||||
}
|
||||
}
|
||||
if (defaultExpandLevel === 'all') {
|
||||
s2Options.style.rowExpandDepth = chart.xAxis.length
|
||||
}
|
||||
if (!defaultExpandLevel) {
|
||||
s2Options.style.hierarchyCollapse = true
|
||||
}
|
||||
}
|
||||
// 列汇总别名
|
||||
if (!(basicStyle.quotaPosition === 'row' && basicStyle.tableLayoutMode === 'tree')) {
|
||||
if (
|
||||
basicStyle.quotaPosition !== 'row' &&
|
||||
chart.xAxisExt?.length &&
|
||||
chart.yAxis?.length > 1 &&
|
||||
tableTotal.col.showGrandTotals &&
|
||||
tableTotal.col.calcTotals?.cfg?.length
|
||||
) {
|
||||
const colTotalCfgMap = tableTotal.col.calcTotals.cfg.reduce((p, n) => {
|
||||
p[n.dataeaseName] = n
|
||||
return p
|
||||
}, {})
|
||||
s2Options.layoutCoordinate = (_, __, col) => {
|
||||
if (col?.isGrandTotals) {
|
||||
if (colTotalCfgMap[col.value]?.label) {
|
||||
col.label = colTotalCfgMap[col.value].label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
basicStyle.quotaPosition === 'row' &&
|
||||
chart.xAxisExt?.length &&
|
||||
chart.yAxis?.length > 1 &&
|
||||
tableTotal.row.showGrandTotals &&
|
||||
tableTotal.row.calcTotals?.cfg?.length
|
||||
) {
|
||||
const rowTotalCfgMap = tableTotal.row.calcTotals.cfg.reduce((p, n) => {
|
||||
p[n.dataeaseName] = n
|
||||
return p
|
||||
}, {})
|
||||
// eslint-disable-next-line
|
||||
s2Options.layoutCoordinate = (_, row, __) => {
|
||||
if (row?.isGrandTotals) {
|
||||
if (rowTotalCfgMap[row.value]?.label) {
|
||||
row.label = rowTotalCfgMap[row.value].label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// tooltip
|
||||
this.configTooltip(chart, s2Options)
|
||||
// 开始渲染
|
||||
const s2 = new PivotSheet(containerDom, s2DataConfig, s2Options as unknown as S2Options)
|
||||
// 自适应铺满
|
||||
if (basicStyle.tableColumnMode === 'adapt') {
|
||||
s2.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => {
|
||||
s2.store.set('lastLayoutResult', s2.facet.layoutResult)
|
||||
})
|
||||
// 平铺模式行头resize
|
||||
s2.on(S2Event.LAYOUT_RESIZE_ROW_WIDTH, () => {
|
||||
s2.store.set('lastLayoutResult', s2.facet.layoutResult)
|
||||
})
|
||||
// 树形模式行头resize
|
||||
s2.on(S2Event.LAYOUT_RESIZE_TREE_WIDTH, () => {
|
||||
s2.store.set('lastLayoutResult', s2.facet.layoutResult)
|
||||
})
|
||||
s2.on(S2Event.LAYOUT_AFTER_HEADER_LAYOUT, (ev: LayoutResult) => {
|
||||
const lastLayoutResult = s2.store.get('lastLayoutResult') as LayoutResult
|
||||
if (lastLayoutResult) {
|
||||
// 拖动 col 表头 resize
|
||||
const colWidthByFieldValue = s2.options.style?.colCfg?.widthByFieldValue
|
||||
// 平铺模式拖动 row 表头 resize
|
||||
const rowWidthByField = s2.options.style?.rowCfg?.widthByField
|
||||
// 树形模式拖动 row 表头 resize
|
||||
const treeRowWidth =
|
||||
s2.options.style?.treeRowsWidth || lastLayoutResult.rowsHierarchy.width
|
||||
const colWidthMap =
|
||||
lastLayoutResult.colLeafNodes.reduce((p, n) => {
|
||||
p[n.id] = colWidthByFieldValue?.[n.value] ?? n.width
|
||||
return p
|
||||
}, {}) || {}
|
||||
const totalColWidth = ev.colLeafNodes.reduce((p, n) => {
|
||||
n.width = colWidthMap[n.id] || n.width
|
||||
n.x = p
|
||||
return p + n.width
|
||||
}, 0)
|
||||
ev.colNodes.forEach(n => {
|
||||
if (n.isLeaf) {
|
||||
return
|
||||
}
|
||||
n.width = this.getColWidth(n)
|
||||
n.x = this.getLeftChild(n).x
|
||||
})
|
||||
if (basicStyle.tableLayoutMode === 'tree') {
|
||||
ev.rowNodes.forEach(n => {
|
||||
n.width = treeRowWidth
|
||||
})
|
||||
ev.rowsHierarchy.width = treeRowWidth
|
||||
ev.colsHierarchy.width = totalColWidth
|
||||
} else {
|
||||
const rowWidthMap =
|
||||
lastLayoutResult.rowNodes.reduce((p, n) => {
|
||||
p[n.id] = rowWidthByField?.[n.field] ?? n.width
|
||||
return p
|
||||
}, {}) || {}
|
||||
ev.rowNodes.forEach(n => {
|
||||
n.x = 0
|
||||
n.width = rowWidthMap[n.id] || n.width
|
||||
let tmp = n
|
||||
while (tmp.parent.id !== 'root') {
|
||||
n.x += tmp.parent.width
|
||||
tmp = tmp.parent
|
||||
}
|
||||
})
|
||||
const totlaRowWidth = ev.rowsHierarchy.sampleNodesForAllLevels.reduce((p, n) => {
|
||||
return p + n.width
|
||||
}, 0)
|
||||
const maxRowLevel = ev.rowsHierarchy.maxLevel
|
||||
ev.rowNodes.forEach(n => {
|
||||
// 总计和中间层级的小计需要重新计算宽度
|
||||
if (n.isTotalRoot || (n.isSubTotals && n.level < maxRowLevel)) {
|
||||
let width = 0
|
||||
for (let i = n.level; i <= maxRowLevel; i++) {
|
||||
width += ev.rowsHierarchy.sampleNodesForAllLevels[i].width
|
||||
}
|
||||
n.width = width
|
||||
}
|
||||
})
|
||||
ev.rowsHierarchy.width = totlaRowWidth
|
||||
ev.colsHierarchy.width = totalColWidth
|
||||
}
|
||||
s2.store.set('lastLayoutResult', undefined)
|
||||
return
|
||||
}
|
||||
const containerWidth = containerDom.getBoundingClientRect().width
|
||||
const scale = containerWidth / (ev.colsHierarchy.width + ev.rowsHierarchy.width)
|
||||
if (scale <= 1) {
|
||||
return
|
||||
}
|
||||
const totalRowWidth = Math.round(ev.rowsHierarchy.width * scale)
|
||||
ev.rowNodes.forEach(n => {
|
||||
n.width = Math.round(n.width * scale)
|
||||
})
|
||||
if (basicStyle.tableLayoutMode !== 'tree') {
|
||||
ev.rowNodes.forEach(n => {
|
||||
n.x = 0
|
||||
let tmp = n
|
||||
while (tmp.parent.id !== 'root') {
|
||||
n.x += tmp.parent.width
|
||||
tmp = tmp.parent
|
||||
}
|
||||
})
|
||||
}
|
||||
let totalColWidth = ev.colLeafNodes.reduce((p, n) => {
|
||||
n.width = Math.round(n.width * scale)
|
||||
n.x = p
|
||||
return p + n.width
|
||||
}, 0)
|
||||
ev.colNodes.forEach(n => {
|
||||
if (n.isLeaf) {
|
||||
return
|
||||
}
|
||||
n.width = this.getColWidth(n)
|
||||
n.x = this.getLeftChild(n).x
|
||||
})
|
||||
const totalWidth = totalColWidth + totalRowWidth
|
||||
if (totalWidth > containerWidth) {
|
||||
// 从最后一列减掉
|
||||
ev.colLeafNodes[ev.colLeafNodes.length - 1].width -= totalWidth - containerWidth
|
||||
totalColWidth = totalColWidth - (totalWidth - containerWidth)
|
||||
}
|
||||
ev.colsHierarchy.width = totalColWidth
|
||||
ev.rowsHierarchy.width = totalRowWidth
|
||||
})
|
||||
}
|
||||
// tooltip
|
||||
const { show } = tooltip
|
||||
if (show) {
|
||||
s2.on(S2Event.COL_CELL_HOVER, event => this.showTooltip(s2, event, meta))
|
||||
s2.on(S2Event.ROW_CELL_HOVER, event => this.showTooltip(s2, event, meta))
|
||||
s2.on(S2Event.DATA_CELL_HOVER, event => this.showTooltip(s2, event, meta))
|
||||
// touch
|
||||
this.configTouchEvent(s2, drawOption, meta)
|
||||
}
|
||||
// empty data tip
|
||||
configEmptyDataStyle(s2, newData)
|
||||
@ -312,14 +603,29 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
s2.on(S2Event.COL_CELL_CLICK, ev => this.headerCellClickAction(chart, ev, s2, action))
|
||||
// right click
|
||||
s2.on(S2Event.GLOBAL_CONTEXT_MENU, event => copyContent(s2, event, meta))
|
||||
// touch
|
||||
this.configTouchEvent(s2, drawOption, meta)
|
||||
// theme
|
||||
const customTheme = this.configTheme(chart)
|
||||
s2.setThemeCfg({ theme: customTheme })
|
||||
|
||||
return s2
|
||||
}
|
||||
private getColWidth(node) {
|
||||
let width = 0
|
||||
if (node.children?.length) {
|
||||
node.children.forEach(child => {
|
||||
width += this.getColWidth(child)
|
||||
})
|
||||
} else {
|
||||
width = node.width
|
||||
}
|
||||
return width
|
||||
}
|
||||
private getLeftChild(node) {
|
||||
if (!node.children?.length) {
|
||||
return node
|
||||
}
|
||||
return this.getLeftChild(node.children[0])
|
||||
}
|
||||
private dataCellClickAction(chart: Chart, ev, s2Instance: PivotSheet, callback) {
|
||||
const cell = s2Instance.getCell(ev.target)
|
||||
const meta = cell.getMeta()
|
||||
@ -522,7 +828,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
|
||||
}
|
||||
function customCalcFunc(query, data, status, chart, totalCfgMap, axisMap, customCalc) {
|
||||
if (!data?.length || !query[EXTRA_FIELD]) {
|
||||
return 0
|
||||
return '-'
|
||||
}
|
||||
const aggregation = totalCfgMap[query[EXTRA_FIELD]]?.aggregation || 'SUM'
|
||||
switch (aggregation) {
|
||||
@ -549,10 +855,13 @@ function customCalcFunc(query, data, status, chart, totalCfgMap, axisMap, custom
|
||||
})
|
||||
return result?.[query[EXTRA_FIELD]]
|
||||
}
|
||||
case 'NONE': {
|
||||
return '-'
|
||||
}
|
||||
case 'CUSTOM': {
|
||||
const val = getCustomCalcResult(query, axisMap, chart, status, customCalc || {})
|
||||
if (val === '') {
|
||||
return val
|
||||
if (val === '' || val === undefined) {
|
||||
return '-'
|
||||
}
|
||||
return parseFloat(val)
|
||||
}
|
||||
@ -593,11 +902,17 @@ function getTreeCustomCalcResult(query, axisMap, status: TotalStatus, customCalc
|
||||
// 列小计
|
||||
if (status.isColSubTotal && !status.isRowTotal && !status.isRowSubTotal) {
|
||||
const { colSubTotal } = customCalc
|
||||
const subLevel = getSubLevel(query, col)
|
||||
const subColLevel = getSubLevel(query, col)
|
||||
const subRowLevel = getSubLevel(query, row)
|
||||
const rowPath = getTreePath(query, row)
|
||||
const colPath = getTreePath(query, col)
|
||||
const path = [...rowPath, ...colPath]
|
||||
const data = colSubTotal?.[subLevel]?.data
|
||||
let data = colSubTotal?.[subColLevel]?.data
|
||||
// 列小计里面的行小计
|
||||
if (rowPath.length < row.length) {
|
||||
const { rowSubInColSub } = customCalc
|
||||
data = rowSubInColSub?.[subRowLevel]?.[subColLevel]?.data
|
||||
}
|
||||
let val
|
||||
if (path.length && data) {
|
||||
path.push(quotaField)
|
||||
@ -647,7 +962,7 @@ function getTreeCustomCalcResult(query, axisMap, status: TotalStatus, customCalc
|
||||
if (status.isRowTotal && status.isColSubTotal) {
|
||||
const { colSubInRowTotal } = customCalc
|
||||
const colLevel = getSubLevel(query, col)
|
||||
const { data } = colSubInRowTotal?.[colLevel]
|
||||
const data = colSubInRowTotal?.[colLevel]?.data
|
||||
const colPath = getTreePath(query, col)
|
||||
let val
|
||||
if (colPath.length && colSubInRowTotal) {
|
||||
@ -669,23 +984,7 @@ function getTreeCustomCalcResult(query, axisMap, status: TotalStatus, customCalc
|
||||
}
|
||||
return val
|
||||
}
|
||||
// 列小计里面的行小计
|
||||
if (status.isColSubTotal && status.isRowSubTotal) {
|
||||
const { rowSubInColSub } = customCalc
|
||||
const rowSubLevel = getSubLevel(query, row)
|
||||
const colSubLevel = getSubLevel(query, col)
|
||||
const data = rowSubInColSub?.[rowSubLevel]?.[colSubLevel]?.data
|
||||
const rowPath = getTreePath(query, row)
|
||||
const colPath = getTreePath(query, col)
|
||||
const path = [...rowPath, ...colPath]
|
||||
let val
|
||||
if (path.length && rowSubInColSub) {
|
||||
path.push(quotaField)
|
||||
val = get(data, path)
|
||||
}
|
||||
return val
|
||||
}
|
||||
return NaN
|
||||
return '-'
|
||||
}
|
||||
|
||||
function getGridCustomCalcResult(query, axisMap, status: TotalStatus, customCalc) {
|
||||
@ -759,7 +1058,7 @@ function getGridCustomCalcResult(query, axisMap, status: TotalStatus, customCalc
|
||||
if (status.isRowTotal && status.isColSubTotal) {
|
||||
const { colSubInRowTotal } = customCalc
|
||||
const colLevel = getSubLevel(query, col)
|
||||
const { data } = colSubInRowTotal?.[colLevel]
|
||||
const data = colSubInRowTotal?.[colLevel]?.data
|
||||
const colPath = getTreePath(query, col)
|
||||
let val
|
||||
if (colPath.length && colSubInRowTotal) {
|
||||
|
@ -33,10 +33,23 @@ import { PositionType } from '@antv/l7-core'
|
||||
import { centroid } from '@turf/centroid'
|
||||
import type { Plot } from '@antv/g2plot'
|
||||
import type { PickOptions } from '@antv/g2plot/lib/core/plot'
|
||||
import { defaults } from 'lodash-es'
|
||||
import { defaults, find } from 'lodash-es'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t: tI18n } = useI18n()
|
||||
import { isMobile } from '@/utils/utils'
|
||||
import { GaodeMap, TMap, TencentMap } from '@antv/l7-maps'
|
||||
import {
|
||||
gaodeMapStyleOptions,
|
||||
qqMapStyleOptions,
|
||||
tdtMapStyleOptions
|
||||
} from '@/views/chart/components/js/panel/charts/map/common'
|
||||
import ChartCarouselTooltip, {
|
||||
isPie,
|
||||
isColumn,
|
||||
isMix,
|
||||
isSupport
|
||||
} from '@/views/chart/components/js/g2plot_tooltip_carousel'
|
||||
|
||||
const { t: tI18n } = useI18n()
|
||||
|
||||
export function getPadding(chart: Chart): number[] {
|
||||
if (chart.drill) {
|
||||
@ -137,14 +150,22 @@ export function getTheme(chart: Chart) {
|
||||
},
|
||||
'g2-tooltip-list-item': {
|
||||
display: 'flex',
|
||||
'align-items': 'center'
|
||||
'align-items': 'flex-start',
|
||||
'justify-content': 'space-between',
|
||||
'line-height': tooltipFontsize + 'px'
|
||||
},
|
||||
'g2-tooltip-name': {
|
||||
display: 'inline-block',
|
||||
'line-height': tooltipFontsize + 'px',
|
||||
flex: 1
|
||||
'line-height': tooltipFontsize + 'px'
|
||||
},
|
||||
'g2-tooltip-value': {
|
||||
flex: 1,
|
||||
display: 'inline-block',
|
||||
'text-align': 'end',
|
||||
'line-height': tooltipFontsize + 'px'
|
||||
},
|
||||
'g2-tooltip-marker': {
|
||||
'margin-top': (tooltipFontsize - 8) / 2 + 'px',
|
||||
'min-width': '8px',
|
||||
'min-height': '8px'
|
||||
}
|
||||
@ -469,7 +490,8 @@ export function getXAxis(chart: Chart) {
|
||||
style: {
|
||||
fill: a.axisLabel.color,
|
||||
fontSize: a.axisLabel.fontSize,
|
||||
textAlign: textAlign
|
||||
textAlign: textAlign,
|
||||
fontFamily: chart.fontFamily
|
||||
},
|
||||
formatter: value => {
|
||||
return chart.type === 'bidirectional-bar' && value.length > a.axisLabel.lengthLimit
|
||||
@ -574,7 +596,8 @@ export function getYAxis(chart: Chart) {
|
||||
fill: yAxis.axisLabel.color,
|
||||
fontSize: yAxis.axisLabel.fontSize,
|
||||
textBaseline,
|
||||
textAlign
|
||||
textAlign,
|
||||
fontFamily: chart.fontFamily
|
||||
},
|
||||
formatter: value => {
|
||||
return value.length > yAxis.axisLabel.lengthLimit
|
||||
@ -603,7 +626,7 @@ export function getYAxisExt(chart: Chart) {
|
||||
return false
|
||||
}
|
||||
const title =
|
||||
yAxis.name && yAxis.name !== ''
|
||||
yAxis.nameShow && yAxis.name && yAxis.name !== ''
|
||||
? {
|
||||
text: yAxis.name,
|
||||
style: {
|
||||
@ -629,14 +652,16 @@ export function getYAxisExt(chart: Chart) {
|
||||
? {
|
||||
style: {
|
||||
stroke: axisCfg.lineStyle.color,
|
||||
lineWidth: axisCfg.lineStyle.width
|
||||
lineWidth: axisCfg.lineStyle.width,
|
||||
lineDash: getLineDash(axisCfg.lineStyle.style)
|
||||
}
|
||||
}
|
||||
: null
|
||||
const tickLine = axisCfg.show
|
||||
? {
|
||||
style: {
|
||||
stroke: axisCfg.lineStyle.color
|
||||
stroke: axisCfg.lineStyle.color,
|
||||
lineWidth: axisCfg.lineStyle.width
|
||||
}
|
||||
}
|
||||
: null
|
||||
@ -673,7 +698,8 @@ export function getYAxisExt(chart: Chart) {
|
||||
fill: yAxis.axisLabel.color,
|
||||
fontSize: yAxis.axisLabel.fontSize,
|
||||
textBaseline,
|
||||
textAlign
|
||||
textAlign,
|
||||
fontFamily: chart.fontFamily
|
||||
}
|
||||
}
|
||||
: null
|
||||
@ -821,10 +847,9 @@ export function getAnalyseHorizontal(chart: Chart) {
|
||||
const assistLineArr = senior.assistLineCfg.assistLine
|
||||
if (assistLineArr?.length > 0) {
|
||||
const customStyle = parseJson(chart.customStyle)
|
||||
let xAxisPosition, axisFormatterCfg
|
||||
let axisFormatterCfg
|
||||
if (customStyle.xAxis) {
|
||||
const a = JSON.parse(JSON.stringify(customStyle.xAxis))
|
||||
xAxisPosition = transAxisPosition(a.position)
|
||||
axisFormatterCfg = a.axisLabelFormatter
|
||||
? a.axisLabelFormatter
|
||||
: DEFAULT_XAXIS_STYLE.axisLabelFormatter
|
||||
@ -894,7 +919,9 @@ export function getLineDash(type) {
|
||||
*/
|
||||
export function setGradientColor(rawColor: string, show = false, angle = 0, start = 0) {
|
||||
const item = rawColor.split(',')
|
||||
item.splice(3, 1, '0.3)')
|
||||
const alpha = parseFloat(item[3].replace(')', ''))
|
||||
const startAlpha = alpha * 0.3
|
||||
item.splice(3, 1, `${startAlpha})`)
|
||||
let color: string
|
||||
if (start == 0) {
|
||||
color = `l(${angle}) 0:${item.join(',')} 1:${rawColor}`
|
||||
@ -993,6 +1020,9 @@ export function configL7Tooltip(chart: Chart): TooltipOptions {
|
||||
return result
|
||||
}
|
||||
const head = originalItem.properties
|
||||
if (!head) {
|
||||
return result
|
||||
}
|
||||
const formatter = formatterMap[head.quotaList?.[0]?.id]
|
||||
if (!isEmpty(formatter)) {
|
||||
const originValue = parseFloat(head.value as string)
|
||||
@ -1152,13 +1182,27 @@ export class CustomZoom extends Zoom {
|
||||
'l7-button-control',
|
||||
container,
|
||||
() => {
|
||||
if (this.controlOption['bounds']) {
|
||||
this.mapsService.fitBounds(this.controlOption['bounds'], { animate: true })
|
||||
if (this.mapsService.map?.deMapProvider == 'qq') {
|
||||
if (this.mapsService.map.deMapAutoFit) {
|
||||
this.mapsService.setZoomAndCenter(this.mapsService.map.deMapAutoZoom, [
|
||||
this.mapsService.map.deMapAutoLng,
|
||||
this.mapsService.map.deMapAutoLat
|
||||
])
|
||||
} else {
|
||||
this.mapsService.setZoomAndCenter(
|
||||
this.controlOption['initZoom'],
|
||||
this.controlOption['center']
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this.mapsService.setZoomAndCenter(
|
||||
this.controlOption['initZoom'],
|
||||
this.controlOption['center']
|
||||
)
|
||||
if (this.controlOption['bounds']) {
|
||||
this.mapsService.fitBounds(this.controlOption['bounds'], { animate: true })
|
||||
} else {
|
||||
this.mapsService.setZoomAndCenter(
|
||||
this.controlOption['initZoom'],
|
||||
this.controlOption['center']
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -1208,7 +1252,11 @@ export class CustomZoom extends Zoom {
|
||||
} as IZoomControlOption
|
||||
}
|
||||
}
|
||||
export function configL7Zoom(chart: Chart, scene: Scene) {
|
||||
export function configL7Zoom(
|
||||
chart: Chart,
|
||||
scene: Scene,
|
||||
mapKey?: { key: string; securityCode: string; mapType: string }
|
||||
) {
|
||||
const { basicStyle } = parseJson(chart.customAttr)
|
||||
const zoomOption = scene?.getControlByName('zoom')
|
||||
if (zoomOption) {
|
||||
@ -1220,20 +1268,56 @@ export function configL7Zoom(chart: Chart, scene: Scene) {
|
||||
if (!scene?.getControlByName('zoom')) {
|
||||
if (!scene.map) {
|
||||
scene.once('loaded', () => {
|
||||
scene.map.on('complete', () => {
|
||||
const initZoom = basicStyle.autoFit === false ? basicStyle.zoomLevel : scene.getZoom()
|
||||
const center =
|
||||
basicStyle.autoFit === false
|
||||
? [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
|
||||
: [scene.map.getCenter().lng, scene.map.getCenter().lat]
|
||||
const newZoomOptions = {
|
||||
initZoom: initZoom,
|
||||
center: center,
|
||||
buttonColor: basicStyle.zoomButtonColor,
|
||||
buttonBackground: basicStyle.zoomBackground
|
||||
} as any
|
||||
scene.addControl(new CustomZoom(newZoomOptions))
|
||||
})
|
||||
switch (mapKey?.mapType) {
|
||||
case 'tianditu':
|
||||
//天地图
|
||||
{
|
||||
const initZoom = basicStyle.autoFit === false ? basicStyle.zoomLevel : scene.getZoom()
|
||||
const center =
|
||||
basicStyle.autoFit === false
|
||||
? [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
|
||||
: [scene.map.getCenter().getLng(), scene.map.getCenter().getLat()]
|
||||
const newZoomOptions = {
|
||||
initZoom: initZoom,
|
||||
center: center,
|
||||
buttonColor: basicStyle.zoomButtonColor,
|
||||
buttonBackground: basicStyle.zoomBackground
|
||||
} as any
|
||||
scene.addControl(new CustomZoom(newZoomOptions))
|
||||
}
|
||||
break
|
||||
case 'qq':
|
||||
{
|
||||
const initZoom = basicStyle.autoFit === false ? basicStyle.zoomLevel : scene.getZoom()
|
||||
const center =
|
||||
basicStyle.autoFit === false
|
||||
? [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
|
||||
: [scene.map.getCenter().lng, scene.map.getCenter().lat]
|
||||
const newZoomOptions = {
|
||||
initZoom: initZoom,
|
||||
center: center,
|
||||
buttonColor: basicStyle.zoomButtonColor,
|
||||
buttonBackground: basicStyle.zoomBackground
|
||||
} as any
|
||||
scene.addControl(new CustomZoom(newZoomOptions))
|
||||
}
|
||||
break
|
||||
default:
|
||||
scene.map.on('complete', () => {
|
||||
const initZoom = basicStyle.autoFit === false ? basicStyle.zoomLevel : scene.getZoom()
|
||||
const center =
|
||||
basicStyle.autoFit === false
|
||||
? [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
|
||||
: [scene.map.getCenter().lng, scene.map.getCenter().lat]
|
||||
const newZoomOptions = {
|
||||
initZoom: initZoom,
|
||||
center: center,
|
||||
buttonColor: basicStyle.zoomButtonColor,
|
||||
buttonBackground: basicStyle.zoomBackground
|
||||
} as any
|
||||
scene.addControl(new CustomZoom(newZoomOptions))
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const newZoomOptions = {
|
||||
@ -1335,6 +1419,18 @@ export function mapRendering(dom: HTMLElement | string) {
|
||||
dom.classList.add('de-map-rendering')
|
||||
}
|
||||
|
||||
export function qqMapRendered(scene?: Scene) {
|
||||
if (scene?.map && scene.map.deMapProvider === 'qq') {
|
||||
setTimeout(() => {
|
||||
if (scene.map) {
|
||||
scene.map.deMapAutoZoom = scene.map.getZoom()
|
||||
scene.map.deMapAutoLng = scene.map.getCenter().getLng()
|
||||
scene.map.deMapAutoLat = scene.map.getCenter().getLat()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
export function mapRendered(dom: HTMLElement | string) {
|
||||
if (typeof dom === 'string') {
|
||||
dom = document.getElementById(dom)
|
||||
@ -1342,6 +1438,213 @@ export function mapRendered(dom: HTMLElement | string) {
|
||||
dom.classList.add('de-map-rendered')
|
||||
}
|
||||
|
||||
export function getMapCenter(basicStyle: ChartBasicStyle) {
|
||||
let center: [number, number]
|
||||
if (basicStyle.autoFit === false) {
|
||||
const longitude = basicStyle?.mapCenter?.longitude ?? DEFAULT_BASIC_STYLE.mapCenter.longitude
|
||||
const latitude = basicStyle?.mapCenter?.latitude ?? DEFAULT_BASIC_STYLE.mapCenter.latitude
|
||||
center = [longitude, latitude]
|
||||
} else {
|
||||
center = undefined
|
||||
}
|
||||
return center
|
||||
}
|
||||
|
||||
export function getMapStyle(
|
||||
mapKey: { key: string; securityCode: string; mapType: string },
|
||||
basicStyle: ChartBasicStyle
|
||||
) {
|
||||
let mapStyle: string
|
||||
switch (mapKey.mapType) {
|
||||
case 'tianditu':
|
||||
if (!find(tdtMapStyleOptions, s => s.value === basicStyle.mapStyle)) {
|
||||
mapStyle = 'normal'
|
||||
} else {
|
||||
mapStyle = basicStyle.mapStyle
|
||||
}
|
||||
break
|
||||
case 'qq':
|
||||
if (
|
||||
!find(qqMapStyleOptions, s => s.value === basicStyle.mapStyle) ||
|
||||
basicStyle.mapStyle === 'normal'
|
||||
) {
|
||||
mapStyle = 'normal'
|
||||
} else {
|
||||
mapStyle = basicStyle.mapStyleUrl
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!find(gaodeMapStyleOptions, s => s.value === basicStyle.mapStyle)) {
|
||||
basicStyle.mapStyle = 'normal'
|
||||
}
|
||||
mapStyle = basicStyle.mapStyleUrl
|
||||
if (basicStyle.mapStyle !== 'custom') {
|
||||
mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}`
|
||||
}
|
||||
break
|
||||
}
|
||||
return mapStyle
|
||||
}
|
||||
|
||||
export async function getMapScene(
|
||||
chart: Chart,
|
||||
scene: Scene,
|
||||
container: string,
|
||||
mapKey: { key: string; securityCode: string; mapType: string },
|
||||
basicStyle: ChartBasicStyle,
|
||||
miscStyle: ChartMiscAttr,
|
||||
mapStyle: string,
|
||||
center?: [number, number]
|
||||
) {
|
||||
if (!scene) {
|
||||
scene = new Scene({
|
||||
id: container,
|
||||
logoVisible: false,
|
||||
map: getMapObject(mapKey, basicStyle, miscStyle, mapStyle, center)
|
||||
})
|
||||
} else {
|
||||
if (mapKey.mapType === 'tianditu') {
|
||||
scene.map?.checkResize()
|
||||
}
|
||||
if (scene.getLayers()?.length) {
|
||||
await scene.removeAllLayer()
|
||||
try {
|
||||
scene.setPitch(miscStyle.mapPitch)
|
||||
} catch (e) {}
|
||||
if (mapKey.mapType === 'tianditu') {
|
||||
if (mapStyle === 'normal') {
|
||||
scene.map?.removeStyle()
|
||||
} else {
|
||||
scene.setMapStyle(mapStyle)
|
||||
}
|
||||
} else {
|
||||
scene.setMapStyle(mapStyle)
|
||||
}
|
||||
|
||||
scene.map.showLabel = !(basicStyle.showLabel === false)
|
||||
if (mapKey.mapType === 'qq') {
|
||||
scene.map.setBaseMap({
|
||||
//底图设置(参数为:VectorBaseMap对象)
|
||||
type: 'vector', //类型:失量底图
|
||||
features: basicStyle.showLabel === false ? ['base', 'building2d'] : undefined
|
||||
//仅渲染:道路及底面(base) + 2d建筑物(building2d),以达到隐藏文字的效果
|
||||
})
|
||||
}
|
||||
}
|
||||
if (basicStyle.autoFit === false) {
|
||||
scene.setZoomAndCenter(basicStyle.zoomLevel, center)
|
||||
if (mapKey.mapType === 'qq') {
|
||||
scene.map.deMapAutoFit = false
|
||||
scene.map.deMapZoom = basicStyle.zoomLevel
|
||||
scene.map.deMapCenter = center
|
||||
}
|
||||
}
|
||||
}
|
||||
mapRendering(container)
|
||||
scene.once('loaded', () => {
|
||||
mapRendered(container)
|
||||
if (mapKey.mapType === 'qq') {
|
||||
scene.map.setBaseMap({
|
||||
//底图设置(参数为:VectorBaseMap对象)
|
||||
type: 'vector', //类型:失量底图
|
||||
features: basicStyle.showLabel === false ? ['base', 'building2d'] : undefined
|
||||
//仅渲染:道路及底面(base) + 2d建筑物(building2d),以达到隐藏文字的效果
|
||||
})
|
||||
scene.setMapStyle(mapStyle)
|
||||
|
||||
scene.map.deMapProvider = 'qq'
|
||||
scene.map.deMapAutoFit = !!basicStyle.autoFit
|
||||
// scene.map.deMapAutoZoom = scene.map.getZoom()
|
||||
// scene.map.deMapAutoLng = scene.map.getCenter().getLng()
|
||||
// scene.map.deMapAutoLat = scene.map.getCenter().getLat()
|
||||
}
|
||||
// 去除天地图自己的缩放按钮
|
||||
if (mapKey.mapType === 'tianditu') {
|
||||
if (mapStyle === 'normal') {
|
||||
scene.map?.removeStyle()
|
||||
} else {
|
||||
scene.setMapStyle(mapStyle)
|
||||
}
|
||||
|
||||
const tdtControl = document.querySelector(
|
||||
`#component${chart.id} .tdt-control-zoom.tdt-bar.tdt-control`
|
||||
)
|
||||
if (tdtControl) {
|
||||
tdtControl.style.display = 'none'
|
||||
}
|
||||
const tdtControlOuter = document.querySelectorAll(
|
||||
`#wrapper-outer-id-${chart.id} .tdt-control-zoom.tdt-bar.tdt-control`
|
||||
)
|
||||
if (tdtControlOuter && tdtControlOuter.length > 0) {
|
||||
for (let i = 0; i < tdtControlOuter.length; i++) {
|
||||
tdtControlOuter[i].style.display = 'none'
|
||||
}
|
||||
}
|
||||
const tdtCopyrightControl = document.querySelector(
|
||||
`#component${chart.id} .tdt-control-copyright.tdt-control`
|
||||
)
|
||||
if (tdtCopyrightControl) {
|
||||
tdtCopyrightControl.style.display = 'none'
|
||||
}
|
||||
const tdtCopyrightControlOuter = document.querySelectorAll(
|
||||
`#wrapper-outer-id-${chart.id} .tdt-control-copyright.tdt-control`
|
||||
)
|
||||
if (tdtCopyrightControlOuter && tdtCopyrightControlOuter.length > 0) {
|
||||
for (let i = 0; i < tdtCopyrightControlOuter.length; i++) {
|
||||
tdtCopyrightControlOuter[i].style.display = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return scene
|
||||
}
|
||||
|
||||
export function getMapObject(
|
||||
mapKey: { key: string; securityCode: string; mapType: string },
|
||||
basicStyle: ChartBasicStyle,
|
||||
miscStyle: ChartMiscAttr,
|
||||
mapStyle: string,
|
||||
center?: [number, number]
|
||||
) {
|
||||
switch (mapKey.mapType) {
|
||||
case 'tianditu':
|
||||
return new TMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle, //不生效
|
||||
pitch: undefined, //不支持
|
||||
center,
|
||||
zoom: basicStyle.autoFit === false ? basicStyle.zoomLevel : undefined,
|
||||
showLabel: !(basicStyle.showLabel === false), //不支持
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
case 'qq':
|
||||
return new TencentMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle,
|
||||
pitch: miscStyle.mapPitch,
|
||||
center,
|
||||
zoom: basicStyle.autoFit === false ? basicStyle.zoomLevel : 12,
|
||||
showLabel: !(basicStyle.showLabel === false),
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
default:
|
||||
return new GaodeMap({
|
||||
token: mapKey?.key ?? undefined,
|
||||
style: mapStyle,
|
||||
pitch: miscStyle.mapPitch,
|
||||
center,
|
||||
zoom: basicStyle.autoFit === false ? basicStyle.zoomLevel : undefined,
|
||||
showLabel: !(basicStyle.showLabel === false),
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 隐藏缩放控件
|
||||
* @param basicStyle
|
||||
@ -1358,6 +1661,8 @@ export function getTooltipContainer(id) {
|
||||
let wrapperDom = document.getElementById(G2_TOOLTIP_WRAPPER)
|
||||
if (!wrapperDom) {
|
||||
wrapperDom = document.createElement('div')
|
||||
wrapperDom.style.position = 'absolute'
|
||||
wrapperDom.style.zIndex = '9999'
|
||||
wrapperDom.id = G2_TOOLTIP_WRAPPER
|
||||
document.body.appendChild(wrapperDom)
|
||||
}
|
||||
@ -1391,14 +1696,78 @@ export function getTooltipContainer(id) {
|
||||
}
|
||||
return g2Tooltip
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置提示轮播
|
||||
* @param plot
|
||||
* @param chart
|
||||
*/
|
||||
function configCarouselTooltip(plot, chart) {
|
||||
const start = isSupport(chart.type) && !document.getElementById('multiplexingDrawer')
|
||||
if (start) {
|
||||
// 启用轮播
|
||||
plot.once('afterrender', () => {
|
||||
const carousel = chart.customAttr?.tooltip?.carousel
|
||||
ChartCarouselTooltip.manage(plot, chart, {
|
||||
xField: 'field',
|
||||
duration: carousel.enable ? carousel?.stayTime * 1000 : 2000,
|
||||
interval: carousel.enable ? carousel?.intervalTime * 1000 : 2000
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 计算 Tooltip 的位置
|
||||
* @param {Chart} chart - 图表实例
|
||||
* @param {boolean} isCarousel - 是否为轮播模式
|
||||
* @param {object} tooltipCtl - Tooltip 控制器
|
||||
* @param {HTMLElement} chartElement - 图表元素
|
||||
* @param {Event} event - 事件对象
|
||||
* @param {boolean} enlargeElement - 放大弹窗
|
||||
* @returns {{x: number, y: number}} - 计算后的 x 和 y 坐标
|
||||
*/
|
||||
function calculateTooltipPosition(chart, isCarousel, tooltipCtl, chartElement, event) {
|
||||
// 辅助函数: 根据不同图表类型计算 Tooltip 的y位置
|
||||
const getTooltipY = () => {
|
||||
const top = Number(chartElement.getBoundingClientRect().top)
|
||||
if (isColumn(chart.type)) {
|
||||
return top + chartElement.getBoundingClientRect().height / 2
|
||||
}
|
||||
if (isMix(chart.type) || isPie(chart.type)) {
|
||||
return top + tooltipCtl.point.y
|
||||
}
|
||||
return top + tooltipCtl.point.y + 60
|
||||
}
|
||||
if (isCarousel) {
|
||||
return {
|
||||
x: tooltipCtl.point.x + Number(chartElement.getBoundingClientRect().left),
|
||||
y: getTooltipY()
|
||||
}
|
||||
} else {
|
||||
return { x: event.clientX, y: event.clientY }
|
||||
}
|
||||
}
|
||||
export function configPlotTooltipEvent<O extends PickOptions, P extends Plot<O>>(
|
||||
chart: Chart,
|
||||
plot: P
|
||||
) {
|
||||
const { tooltip } = parseJson(chart.customAttr)
|
||||
if (!tooltip.show) {
|
||||
ChartCarouselTooltip.destroyByContainer(chart.container)
|
||||
return
|
||||
}
|
||||
// 图表容器,用于计算 tooltip 的位置
|
||||
// 获取图表元素,优先顺序:放大 > 预览 > 公共连接页面 > 默认
|
||||
const chartElement =
|
||||
document.getElementById('container-viewDialog-' + chart.id + '-common') ||
|
||||
document.getElementById('container-preview-' + chart.id + '-common') ||
|
||||
document.getElementById('enlarge-inner-content-' + chart.id) ||
|
||||
document.getElementById('shape-id-' + chart.id)
|
||||
// 是否是放大弹窗
|
||||
const enlargeElement = chartElement?.id.includes('viewDialog')
|
||||
// 轮播时tooltip的zIndex
|
||||
const carousel_zIndex = enlargeElement ? '9999' : '1002'
|
||||
configCarouselTooltip(plot, chart)
|
||||
// 鼠标可移入, 移入之后保持显示, 移出之后隐藏
|
||||
plot.options.tooltip.container.addEventListener('mouseenter', e => {
|
||||
e.target.style.visibility = 'visible'
|
||||
@ -1415,10 +1784,25 @@ export function configPlotTooltipEvent<O extends PickOptions, P extends Plot<O>>
|
||||
if (!tooltipCtl) {
|
||||
return
|
||||
}
|
||||
// 处理 tooltip 与下拉菜单的显示冲突问题
|
||||
const viewTrackBarElement = document.getElementById('view-track-bar-' + chart.id)
|
||||
const event = plot.chart.interactions.tooltip?.context?.event
|
||||
// 是否时轮播模式
|
||||
const isCarousel =
|
||||
chart.customAttr?.tooltip?.carousel &&
|
||||
(!event || // 事件触发时,使用event的client坐标
|
||||
['plot:leave', 'plot:mouseleave'].includes(event?.type) || //鼠标离开时,使用tooltipCtl.point
|
||||
['pie', 'pie-rose', 'pie-donut'].includes(chart.type)) // 饼图时,使用tooltipCtl.point
|
||||
plot.options.tooltip.showMarkers = isCarousel ? true : false
|
||||
const wrapperDom = document.getElementById(G2_TOOLTIP_WRAPPER)
|
||||
wrapperDom.style.zIndex = isCarousel && wrapperDom ? carousel_zIndex : '9999'
|
||||
if (tooltipCtl.tooltip) {
|
||||
// 处理视图放大后再关闭 tooltip 的 dom 被清除
|
||||
const container = tooltipCtl.tooltip.cfg.container
|
||||
// 当下拉菜单不显示时,移除tooltip的hidden-tooltip样式
|
||||
if (viewTrackBarElement?.getAttribute('aria-expanded') === 'false') {
|
||||
container.classList.toggle('hidden-tooltip', false)
|
||||
}
|
||||
container.style.display = 'block'
|
||||
const dom = document.getElementById(container.id)
|
||||
if (!dom) {
|
||||
@ -1433,8 +1817,17 @@ export function configPlotTooltipEvent<O extends PickOptions, P extends Plot<O>>
|
||||
}
|
||||
plot.chart.getOptions().tooltip.follow = false
|
||||
tooltipCtl.title = Math.random().toString()
|
||||
plot.chart.getTheme().components.tooltip.x = event.clientX
|
||||
plot.chart.getTheme().components.tooltip.y = event.clientY
|
||||
// 当显示提示为事件触发时,使用event的client坐标,否则使用tooltipCtl.point 数据点的位置,在图表中,需要加上图表在绘制区的位置
|
||||
const { x, y } = calculateTooltipPosition(
|
||||
chart,
|
||||
isCarousel,
|
||||
tooltipCtl,
|
||||
chartElement,
|
||||
event,
|
||||
enlargeElement
|
||||
)
|
||||
plot.chart.getTheme().components.tooltip.x = x
|
||||
plot.chart.getTheme().components.tooltip.y = y
|
||||
})
|
||||
// https://github.com/antvis/G2/blob/master/src/chart/controller/tooltip.ts#hideTooltip
|
||||
plot.on('plot:leave', () => {
|
||||
@ -1457,14 +1850,22 @@ export function configPlotTooltipEvent<O extends PickOptions, P extends Plot<O>>
|
||||
if (!tooltipCtl) {
|
||||
return
|
||||
}
|
||||
const container = tooltipCtl.tooltip.cfg.container
|
||||
const container = tooltipCtl.tooltip?.cfg.container
|
||||
for (const ele of wrapperDom.children) {
|
||||
if (container.id !== ele.id) {
|
||||
if (!container || container.id !== ele.id) {
|
||||
ele.style.display = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
plot.on('tooltip:hidden', () => {
|
||||
const tooltipCtl = plot.chart.getController('tooltip')
|
||||
if (!tooltipCtl) {
|
||||
return
|
||||
}
|
||||
const container = tooltipCtl.tooltip?.cfg.container
|
||||
container && (container.style.display = 'none')
|
||||
})
|
||||
}
|
||||
|
||||
export const TOOLTIP_TPL =
|
||||
@ -1699,10 +2100,12 @@ export function configYaxisTitleLengthLimit(chart, plot) {
|
||||
? wrappedTitle.slice(0, wrappedTitle.length - 2) + '...'
|
||||
: wrappedTitle + '...'
|
||||
}
|
||||
|
||||
// 更新Y轴标题的原始文本和截断后的文本
|
||||
ev.view.options.axes.yAxisExt.title.originalText = yAxis.name
|
||||
ev.view.options.axes.yAxisExt.title.text = wrappedTitle
|
||||
const { title } = ev.view.options.axes.yAxisExt
|
||||
if (title) {
|
||||
title.originalText = yAxis.name
|
||||
title.text = wrappedTitle
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1731,7 +2134,7 @@ export const addConditionsStyleColorToData = (chart: Chart, options) => {
|
||||
})
|
||||
} else if (item.quotaList?.length) {
|
||||
const quotaList = item.quotaList.map(q => q.id) ?? []
|
||||
quotaList.forEach((q, index) => {
|
||||
quotaList.forEach(q => {
|
||||
// 定义后,在 handleConditionsStyle 函数中使用
|
||||
let currentValue = item[valueField]
|
||||
if (chart.type === 'progress-bar') {
|
||||
@ -1798,7 +2201,7 @@ const getColorByConditions = (quotaList: [], values: number | number[], chart) =
|
||||
* @param chart
|
||||
* @param options
|
||||
*/
|
||||
export function handleConditionsStyle(chart: Chart, options: O) {
|
||||
export function handleConditionsStyle(chart: Chart, options) {
|
||||
const { threshold } = parseJson(chart.senior)
|
||||
if (!threshold.enable) return options
|
||||
const { basicStyle } = parseJson(chart.customAttr)
|
||||
@ -1810,8 +2213,6 @@ export function handleConditionsStyle(chart: Chart, options: O) {
|
||||
// 辅助函数:配置柱条样式颜色,条形图为barStyle,柱形图为columnStyle
|
||||
const columnStyle = data => {
|
||||
return {
|
||||
...options.columnStyle,
|
||||
...options.barStyle,
|
||||
...(data[colorField]?.[0] ? { fill: data[colorField][0] } : {})
|
||||
}
|
||||
}
|
||||
@ -1825,8 +2226,8 @@ export function handleConditionsStyle(chart: Chart, options: O) {
|
||||
const tmpOption = {
|
||||
...options,
|
||||
rawFields,
|
||||
columnStyle: columnStyle,
|
||||
barStyle: columnStyle,
|
||||
...configRoundAngle(chart, 'columnStyle', columnStyle),
|
||||
...configRoundAngle(chart, 'barStyle', columnStyle),
|
||||
tooltip: {
|
||||
...options.tooltip,
|
||||
...(options.tooltip['customItems']
|
||||
@ -1934,7 +2335,7 @@ export const getTooltipItemConditionColor = item => {
|
||||
* @param newData
|
||||
* @param container
|
||||
*/
|
||||
export const configEmptyDataStyle = (newChart, newData, container) => {
|
||||
export const configEmptyDataStyle = (newData, container, newChart?, content?) => {
|
||||
/**
|
||||
* 辅助函数:移除空数据dom
|
||||
*/
|
||||
@ -1949,15 +2350,121 @@ export const configEmptyDataStyle = (newChart, newData, container) => {
|
||||
if (!newData.length) {
|
||||
const emptyDom = document.createElement('div')
|
||||
emptyDom.id = container + '_empty'
|
||||
emptyDom.textContent = tI18n('data_set.no_data')
|
||||
emptyDom.textContent = content || tI18n('data_set.no_data')
|
||||
emptyDom.setAttribute(
|
||||
'style',
|
||||
`position: absolute;
|
||||
left: 45%;
|
||||
top: 50%;`
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: darkgray;
|
||||
textAlign: center;`
|
||||
)
|
||||
const parent = document.getElementById(container)
|
||||
parent.insertBefore(emptyDom, parent.firstChild)
|
||||
newChart.destroy()
|
||||
newChart?.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
export const numberToChineseUnderHundred = (num: number): string => {
|
||||
// 合法性检查
|
||||
if (num <= 0 || num > 99 || !Number.isInteger(num)) {
|
||||
throw new Error('请输入1-99之间的整数')
|
||||
}
|
||||
|
||||
const digits = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
|
||||
|
||||
// 处理个位数
|
||||
if (num < 10) return digits[num]
|
||||
|
||||
const tens = Math.floor(num / 10)
|
||||
const ones = num % 10
|
||||
|
||||
// 处理整十
|
||||
if (ones === 0) {
|
||||
return tens === 1 ? '十' : digits[tens] + '十'
|
||||
}
|
||||
|
||||
// 处理其他两位数
|
||||
return tens === 1 ? '十' + digits[ones] : digits[tens] + '十' + digits[ones]
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置柱条图的圆角
|
||||
* @param styleName
|
||||
* @param callBack 自定义其他属性函数
|
||||
*/
|
||||
export const configRoundAngle = (chart: Chart, styleName: string, callBack?: (datum) => {}) => {
|
||||
const { basicStyle } = parseJson(chart.customAttr)
|
||||
if (['roundAngle', 'topRoundAngle'].includes(basicStyle.radiusColumnBar)) {
|
||||
const radius = Array(2).fill(basicStyle.columnBarRightAngleRadius)
|
||||
const topRadius = [0, 0, ...radius]
|
||||
const bottomRadius = [...radius, 0, 0]
|
||||
const finalRadius = [...radius, ...radius]
|
||||
if (chart.type.includes('-stack')) {
|
||||
return {
|
||||
[styleName]: datum => {
|
||||
if (!datum.value) return { radius: [], ...(callBack ? callBack(datum) : {}) }
|
||||
return { radius: finalRadius, ...(callBack ? callBack(datum) : {}) }
|
||||
}
|
||||
}
|
||||
}
|
||||
const isTopRound = basicStyle.radiusColumnBar === 'topRoundAngle'
|
||||
// 对称条形图
|
||||
if (chart.type === 'bidirectional-bar') {
|
||||
const valueField = basicStyle.layout === 'vertical' ? 'valueExt' : 'value'
|
||||
return {
|
||||
[styleName]: datum => ({
|
||||
radius: datum[valueField] && isTopRound ? topRadius : isTopRound ? radius : finalRadius,
|
||||
...(callBack ? callBack(datum) : {})
|
||||
})
|
||||
}
|
||||
}
|
||||
// 进度条
|
||||
if (chart.type === 'progress-bar') {
|
||||
return {
|
||||
[styleName]: datum => {
|
||||
return {
|
||||
radius: isTopRound ? bottomRadius : finalRadius,
|
||||
...(callBack ? callBack(datum) : {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 区间条形图
|
||||
if (chart.type === 'bar-range') {
|
||||
return {
|
||||
[styleName]: datum => {
|
||||
return {
|
||||
radius:
|
||||
datum?.values[0] < datum?.values[1]
|
||||
? isTopRound
|
||||
? bottomRadius
|
||||
: finalRadius
|
||||
: isTopRound
|
||||
? topRadius
|
||||
: finalRadius,
|
||||
...(callBack ? callBack(datum) : {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 配置柱条样式
|
||||
const style = datum => {
|
||||
if (isTopRound) {
|
||||
return { radius, ...(callBack ? callBack(datum) : {}) }
|
||||
}
|
||||
if (!isTopRound) {
|
||||
return { radius: finalRadius, ...(callBack ? callBack(datum) : {}) }
|
||||
}
|
||||
}
|
||||
return {
|
||||
[styleName]: style
|
||||
}
|
||||
}
|
||||
return {
|
||||
[styleName]: datum => {
|
||||
return { ...(callBack ? callBack(datum) : {}) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import {
|
||||
isAlphaColor,
|
||||
isTransparent,
|
||||
parseJson,
|
||||
resetRgbOpacity
|
||||
resetRgbOpacity,
|
||||
safeDecimalSum,
|
||||
safeDecimalMean
|
||||
} from '../..//util'
|
||||
import {
|
||||
DEFAULT_BASIC_STYLE,
|
||||
@ -43,13 +45,31 @@ import {
|
||||
updateShapeAttr,
|
||||
ViewMeta
|
||||
} from '@antv/s2'
|
||||
import { cloneDeep, filter, find, intersection, keys, merge, repeat } from 'lodash-es'
|
||||
import {
|
||||
cloneDeep,
|
||||
filter,
|
||||
find,
|
||||
intersection,
|
||||
keys,
|
||||
map,
|
||||
maxBy,
|
||||
meanBy,
|
||||
merge,
|
||||
minBy,
|
||||
repeat,
|
||||
sumBy,
|
||||
size,
|
||||
sum
|
||||
} from 'lodash-es'
|
||||
import { createVNode, render } from 'vue'
|
||||
import TableTooltip from '@/views/chart/components/editor/common/TableTooltip.vue'
|
||||
import Exceljs from 'exceljs'
|
||||
import { saveAs } from 'file-saver'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
|
||||
const { t: i18nt } = useI18n()
|
||||
|
||||
export function getCustomTheme(chart: Chart): S2Theme {
|
||||
@ -401,8 +421,7 @@ export function getCustomTheme(chart: Chart): S2Theme {
|
||||
},
|
||||
dataCell: {
|
||||
cell: {
|
||||
crossBackgroundColor:
|
||||
enableTableCrossBG && !tableCell.mergeCells ? tableItemSubBgColor : tableItemBgColor,
|
||||
crossBackgroundColor: enableTableCrossBG ? tableItemSubBgColor : tableItemBgColor,
|
||||
backgroundColor: tableItemBgColor
|
||||
},
|
||||
bolderText: {
|
||||
@ -603,7 +622,7 @@ export function getConditions(chart: Chart) {
|
||||
const dimFields = [...chart.xAxis, ...chart.xAxisExt].map(i => i.dataeaseName)
|
||||
if (conditions?.length > 0) {
|
||||
const { tableCell, basicStyle, tableHeader } = parseJson(chart.customAttr)
|
||||
// 合并单元格时,班马纹失效
|
||||
// 合并单元格时斑马纹失效
|
||||
const enableTableCrossBG =
|
||||
chart.type === 'table-info'
|
||||
? tableCell.enableTableCrossBG && !tableCell.mergeCells
|
||||
@ -783,6 +802,9 @@ export function mappingColor(value, defaultColor, field, type, filedValueMap?, r
|
||||
}
|
||||
} else {
|
||||
// time
|
||||
if (!tv || !value) {
|
||||
break
|
||||
}
|
||||
const fc = field.conditions[i]
|
||||
tv = new Date(tv.replace(/-/g, '/') + ' GMT+8').getTime()
|
||||
const v = new Date(value.replace(/-/g, '/') + ' GMT+8').getTime()
|
||||
@ -880,6 +902,7 @@ export function handleTableEmptyStrategy(chart: Chart) {
|
||||
}
|
||||
return newData
|
||||
}
|
||||
|
||||
export class SortTooltip extends BaseTooltip {
|
||||
show(showOptions) {
|
||||
const { iconName } = showOptions
|
||||
@ -934,6 +957,7 @@ export class SortTooltip extends BaseTooltip {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const SORT_DEFAULT =
|
||||
'<svg t="1711681787276" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4355" width="200" height="200"><path d="M922.345786 372.183628l-39.393195 38.687114L676.138314 211.079416l0 683.909301-54.713113 0L621.425202 129.010259l53.320393 0L922.345786 372.183628zM349.254406 894.989741 101.654214 651.815349l39.393195-38.687114 206.814276 199.792349L347.861686 129.010259l54.713113 0 0 765.978459L349.254406 894.988718z" fill="{fill}" p-id="4356"></path></svg>'
|
||||
const SORT_UP =
|
||||
@ -1063,7 +1087,14 @@ export function copyContent(s2Instance: SpreadSheet, event, fieldMeta) {
|
||||
if (cells.length === 1) {
|
||||
const curCell = cells[0]
|
||||
if (cell.getMeta().id === curCell.id) {
|
||||
copyString(cellMeta.value + '', true)
|
||||
const cellMeta = cell.getMeta()
|
||||
const value = cellMeta.data?.[cellMeta.valueField]
|
||||
const metaObj = find(fieldMeta, m => m.field === cellMeta.valueField)
|
||||
let fieldVal = value?.toString()
|
||||
if (metaObj) {
|
||||
fieldVal = metaObj.formatter(value)
|
||||
}
|
||||
copyString(fieldVal, true)
|
||||
}
|
||||
s2Instance.interaction.clearState()
|
||||
return
|
||||
@ -1189,7 +1220,7 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) {
|
||||
const { meta, fields } = instance.dataCfg
|
||||
const rowLength = fields?.rows?.length || 0
|
||||
const colLength = fields?.columns?.length || 0
|
||||
const colNums = layoutResult.colLeafNodes.length + rowLength + 1
|
||||
const colNums = layoutResult.colLeafNodes.length + rowLength
|
||||
if (colNums > 16384) {
|
||||
ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
|
||||
return
|
||||
@ -1346,9 +1377,180 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) {
|
||||
if (fieldValue === 0 || fieldValue) {
|
||||
const meta = metaMap[dataCellMeta.valueField]
|
||||
const cell = worksheet.getCell(rowIndex + maxColHeight + 1, rowLength + colIndex + 1)
|
||||
const value = meta?.formatter?.(fieldValue) || fieldValue.toString()
|
||||
const value = meta?.formatter?.(fieldValue) || fieldValue
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.value = value
|
||||
cell.value = isNumeric(value) ? parseFloat(value) : value
|
||||
}
|
||||
}
|
||||
}
|
||||
const buffer = await workbook.xlsx.writeBuffer()
|
||||
const dataBlob = new Blob([buffer], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
|
||||
})
|
||||
saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`)
|
||||
}
|
||||
|
||||
export async function exportRowQuotaGridPivot(instance: PivotSheet, chart: ChartObj) {
|
||||
const { layoutResult } = instance.facet
|
||||
const { meta, fields } = instance.dataCfg
|
||||
const rowLength = fields?.rows?.length || 0
|
||||
const colLength = fields?.columns?.length || 0
|
||||
const colNums = layoutResult.colLeafNodes.length + rowLength
|
||||
if (colNums > 16384) {
|
||||
ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
|
||||
return
|
||||
}
|
||||
const workbook = new Exceljs.Workbook()
|
||||
const worksheet = workbook.addWorksheet(i18nt('chart.chart_data'))
|
||||
const metaMap: Record<string, Meta> = meta?.reduce((p, n) => {
|
||||
if (n.field) {
|
||||
p[n.field] = n
|
||||
}
|
||||
return p
|
||||
}, {})
|
||||
// 角头
|
||||
if (colLength > 1) {
|
||||
fields.columns.forEach((column: string, index) => {
|
||||
if (index >= colLength - 1) {
|
||||
return
|
||||
}
|
||||
const cell = worksheet.getCell(index + 1, 1)
|
||||
cell.value = metaMap[column]?.name ?? column
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.border = {
|
||||
right: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
worksheet.mergeCells(index + 1, 1, index + 1, rowLength + 1)
|
||||
})
|
||||
}
|
||||
fields?.rows?.forEach((row, index) => {
|
||||
const cell = worksheet.getCell(colLength === 0 ? 1 : colLength, index + 1)
|
||||
cell.value = metaMap[row]?.name ?? row
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.border = { bottom: { style: 'thick', color: { argb: '00000000' } } }
|
||||
})
|
||||
const quotaColLabel = chart.customAttr.basicStyle.quotaColLabel ?? t('dataset.value')
|
||||
const quotaColHeadCell = worksheet.getCell(colLength === 0 ? 1 : colLength, rowLength + 1)
|
||||
quotaColHeadCell.value = quotaColLabel
|
||||
quotaColHeadCell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
quotaColHeadCell.border = {
|
||||
bottom: { style: 'thick', color: { argb: '00000000' } },
|
||||
right: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
// 行头
|
||||
const { rowLeafNodes, rowNodes } = layoutResult
|
||||
const notLeafNodeHeightMap: Record<string, number> = {}
|
||||
rowLeafNodes.forEach(node => {
|
||||
// 行头的高度由子节点相加决定,也就是行头子节点中包含的叶子节点数量
|
||||
let curNode = node.parent
|
||||
while (curNode) {
|
||||
const height = notLeafNodeHeightMap[curNode.id] ?? 0
|
||||
notLeafNodeHeightMap[curNode.id] = height + 1
|
||||
curNode = curNode.parent
|
||||
}
|
||||
const { rowIndex } = node
|
||||
const writeRowIndex = rowIndex + 2 + (colLength === 0 ? 1 : colLength - 1)
|
||||
const writeColIndex = node.level + 1
|
||||
const cell = worksheet.getCell(writeRowIndex, writeColIndex)
|
||||
let value = node.label
|
||||
if (node.field === '$$extra$$' && metaMap[value]?.name) {
|
||||
value = metaMap[value].name
|
||||
}
|
||||
cell.value = value
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.border = {
|
||||
right: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
})
|
||||
|
||||
const getNodeStartRowIndex = (node: Node) => {
|
||||
if (!node.children?.length) {
|
||||
return node.rowIndex + 1
|
||||
} else {
|
||||
return getNodeStartRowIndex(node.children[0])
|
||||
}
|
||||
}
|
||||
rowNodes?.forEach(node => {
|
||||
if (node.isLeaf) {
|
||||
return
|
||||
}
|
||||
const rowIndex = getNodeStartRowIndex(node)
|
||||
const height = notLeafNodeHeightMap[node.id]
|
||||
const writeRowIndex = rowIndex + 1 + (colLength === 0 ? 1 : colLength - 1)
|
||||
const mergeColCount = node.children[0].level - node.level
|
||||
const cell = worksheet.getCell(writeRowIndex, node.level + 1)
|
||||
cell.value = node.label
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
if (mergeColCount > 1 || height > 1) {
|
||||
worksheet.mergeCells(
|
||||
writeRowIndex,
|
||||
node.level + 1,
|
||||
writeRowIndex + height - 1,
|
||||
node.level + mergeColCount
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// 列头
|
||||
const { colLeafNodes, colNodes, colsHierarchy } = layoutResult
|
||||
const maxColHeight = colsHierarchy.maxLevel + 1
|
||||
const notLeafNodeWidthMap: Record<string, number> = {}
|
||||
colLeafNodes.forEach(node => {
|
||||
// 列头的宽度由子节点相加决定,也就是列头子节点中包含的叶子节点数量
|
||||
let curNode = node.parent
|
||||
while (curNode) {
|
||||
const width = notLeafNodeWidthMap[curNode.id] ?? 0
|
||||
notLeafNodeWidthMap[curNode.id] = width + 1
|
||||
curNode = curNode.parent
|
||||
}
|
||||
const { colIndex } = node
|
||||
const writeRowIndex = node.level + 1
|
||||
const writeColIndex = colIndex + rowLength + 2
|
||||
const cell = worksheet.getCell(writeRowIndex, writeColIndex)
|
||||
const value = node.label
|
||||
cell.value = value
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
if (writeRowIndex < maxColHeight) {
|
||||
worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex)
|
||||
}
|
||||
cell.border = {
|
||||
bottom: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
})
|
||||
const getNodeStartColIndex = (node: Node) => {
|
||||
if (!node.children?.length) {
|
||||
return node.colIndex + 1
|
||||
} else {
|
||||
return getNodeStartColIndex(node.children[0])
|
||||
}
|
||||
}
|
||||
colNodes.forEach(node => {
|
||||
if (node.isLeaf) {
|
||||
return
|
||||
}
|
||||
const colIndex = getNodeStartColIndex(node)
|
||||
const width = notLeafNodeWidthMap[node.id]
|
||||
const writeRowIndex = node.level + 1
|
||||
const value = node.label
|
||||
const writeColIndex = colIndex + rowLength + 1
|
||||
const cell = worksheet.getCell(writeRowIndex, writeColIndex)
|
||||
cell.value = value
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
if (width > 1) {
|
||||
worksheet.mergeCells(writeRowIndex, writeColIndex, writeRowIndex, writeColIndex + width - 1)
|
||||
}
|
||||
})
|
||||
// 单元格数据
|
||||
for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) {
|
||||
for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) {
|
||||
const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex)
|
||||
const { fieldValue } = dataCellMeta
|
||||
if (fieldValue === 0 || fieldValue) {
|
||||
const meta = metaMap[dataCellMeta.valueField]
|
||||
const cell = worksheet.getCell(rowIndex + maxColHeight + 1, rowLength + colIndex + 2)
|
||||
const value = meta?.formatter?.(fieldValue) || fieldValue
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.value = isNumeric(value) ? parseFloat(value) : value
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1361,7 +1563,7 @@ export async function exportGridPivot(instance: PivotSheet, chart: ChartObj) {
|
||||
|
||||
export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) {
|
||||
const layoutResult = instance.facet.layoutResult
|
||||
if (layoutResult.colLeafNodes.length + 2 > 16384) {
|
||||
if (layoutResult.colLeafNodes.length + 1 > 16384) {
|
||||
ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
|
||||
return
|
||||
}
|
||||
@ -1468,9 +1670,9 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) {
|
||||
if (fieldValue === 0 || fieldValue) {
|
||||
const meta = metaMap[dataCellMeta.valueField]
|
||||
const cell = worksheet.getCell(rowIndex + maxColHeight + 1, colIndex + 1 + 1)
|
||||
const value = meta?.formatter?.(fieldValue) || fieldValue.toString()
|
||||
const value = meta?.formatter?.(fieldValue) || fieldValue
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.value = value
|
||||
cell.value = isNumeric(value) ? parseFloat(value) : value
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1480,6 +1682,135 @@ export async function exportTreePivot(instance: PivotSheet, chart: ChartObj) {
|
||||
})
|
||||
saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`)
|
||||
}
|
||||
|
||||
export async function exportRowQuotaTreePivot(instance: PivotSheet, chart: ChartObj) {
|
||||
const layoutResult = instance.facet.layoutResult
|
||||
if (layoutResult.colLeafNodes.length + 1 > 16384) {
|
||||
ElMessage.warning(i18nt('chart.pivot_export_invalid_col_exceed'))
|
||||
return
|
||||
}
|
||||
const { meta, fields } = instance.dataCfg
|
||||
const colLength = fields?.columns?.length || 0
|
||||
const workbook = new Exceljs.Workbook()
|
||||
const worksheet = workbook.addWorksheet(i18nt('chart.chart_data'))
|
||||
const metaMap: Record<string, Meta> = meta?.reduce((p, n) => {
|
||||
if (n.field) {
|
||||
p[n.field] = n
|
||||
}
|
||||
return p
|
||||
}, {})
|
||||
|
||||
// 角头
|
||||
fields.columns?.forEach((column, index) => {
|
||||
if (index >= fields.columns.length - 1) {
|
||||
return
|
||||
}
|
||||
const cell = worksheet.getCell(index + 1, 1)
|
||||
cell.value = metaMap[column]?.name ?? column
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.border = {
|
||||
right: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
})
|
||||
const quotaColLabel = chart.customAttr.basicStyle.quotaColLabel ?? t('dataset.value')
|
||||
const maxColHeight = layoutResult.colsHierarchy.maxLevel + 1
|
||||
const rowName = fields?.rows
|
||||
?.map(row => metaMap[row]?.name ?? row)
|
||||
.concat(quotaColLabel)
|
||||
.join('/')
|
||||
const cell = worksheet.getCell(colLength, 1)
|
||||
cell.value = rowName
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.border = {
|
||||
right: { style: 'thick', color: { argb: '00000000' } },
|
||||
bottom: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
//行头
|
||||
const { rowLeafNodes } = layoutResult
|
||||
rowLeafNodes.forEach((node, index) => {
|
||||
const cell = worksheet.getCell(maxColHeight + index + 1, 1)
|
||||
let value = node.label
|
||||
if (node.field === '$$extra$$' && metaMap[value]?.name) {
|
||||
value = metaMap[value].name
|
||||
}
|
||||
cell.value = repeat(' ', node.level) + value
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'left' }
|
||||
cell.border = {
|
||||
right: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
})
|
||||
// 列头
|
||||
const notLeafNodeWidthMap: Record<string, number> = {}
|
||||
const { colLeafNodes } = layoutResult
|
||||
colLeafNodes.forEach(node => {
|
||||
let curNode = node.parent
|
||||
while (curNode) {
|
||||
const width = notLeafNodeWidthMap[curNode.id] ?? 0
|
||||
notLeafNodeWidthMap[curNode.id] = width + 1
|
||||
curNode = curNode.parent
|
||||
}
|
||||
const { colIndex } = node
|
||||
const writeRowIndex = node.level + 1
|
||||
const writeColIndex = colIndex + 2
|
||||
const cell = worksheet.getCell(writeRowIndex, writeColIndex)
|
||||
cell.value = node.label
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
if (writeRowIndex < maxColHeight) {
|
||||
worksheet.mergeCells(writeRowIndex, writeColIndex, maxColHeight, writeColIndex)
|
||||
}
|
||||
cell.border = {
|
||||
bottom: { style: 'thick', color: { argb: '00000000' } }
|
||||
}
|
||||
})
|
||||
const colNodes = layoutResult.colNodes
|
||||
const getNodeStartIndex = (node: Node) => {
|
||||
if (!node.children?.length) {
|
||||
return node.colIndex + 1
|
||||
} else {
|
||||
return getNodeStartIndex(node.children[0])
|
||||
}
|
||||
}
|
||||
colNodes.forEach(node => {
|
||||
if (node.isLeaf) {
|
||||
return
|
||||
}
|
||||
const colIndex = getNodeStartIndex(node)
|
||||
const width = notLeafNodeWidthMap[node.id]
|
||||
const writeRowIndex = node.level + 1
|
||||
const writeColIndex = colIndex + 1
|
||||
const cell = worksheet.getCell(writeRowIndex, writeColIndex)
|
||||
cell.value = node.label
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
if (width > 1) {
|
||||
worksheet.mergeCells(writeRowIndex, writeColIndex, writeRowIndex, writeColIndex + width - 1)
|
||||
}
|
||||
})
|
||||
// 单元格数据
|
||||
for (let rowIndex = 0; rowIndex < rowLeafNodes.length; rowIndex++) {
|
||||
for (let colIndex = 0; colIndex < colLeafNodes.length; colIndex++) {
|
||||
const dataCellMeta = layoutResult.getCellMeta(rowIndex, colIndex)
|
||||
const { fieldValue } = dataCellMeta
|
||||
if (fieldValue === 0 || fieldValue) {
|
||||
const meta = metaMap[dataCellMeta.valueField]
|
||||
const cell = worksheet.getCell(rowIndex + maxColHeight + 1, colIndex + 2)
|
||||
const value = meta?.formatter?.(fieldValue) || fieldValue
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' }
|
||||
cell.value = isNumeric(value) ? parseFloat(value) : value
|
||||
}
|
||||
}
|
||||
}
|
||||
const buffer = await workbook.xlsx.writeBuffer()
|
||||
const dataBlob = new Blob([buffer], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
|
||||
})
|
||||
saveAs(dataBlob, `${chart.title ?? '透视表'}.xlsx`)
|
||||
}
|
||||
|
||||
|
||||
function isNumeric(value: string): boolean {
|
||||
return /^[+-]?\d+(\.\d+)?$/.test(value)
|
||||
}
|
||||
|
||||
export async function exportPivotExcel(instance: PivotSheet, chart: ChartObj) {
|
||||
const { fields } = instance.dataCfg
|
||||
const rowLength = fields?.rows?.length || 0
|
||||
@ -1488,10 +1819,19 @@ export async function exportPivotExcel(instance: PivotSheet, chart: ChartObj) {
|
||||
ElMessage.warning(i18nt('chart.pivot_export_invalid_field'))
|
||||
return
|
||||
}
|
||||
const { quotaPosition } = chart.customAttr.basicStyle
|
||||
if (chart.customAttr.basicStyle.tableLayoutMode !== 'tree') {
|
||||
exportGridPivot(instance, chart)
|
||||
if (quotaPosition === 'row') {
|
||||
exportRowQuotaGridPivot(instance, chart)
|
||||
} else {
|
||||
exportGridPivot(instance, chart)
|
||||
}
|
||||
} else {
|
||||
exportTreePivot(instance, chart)
|
||||
if (quotaPosition === 'row') {
|
||||
exportRowQuotaTreePivot(instance, chart)
|
||||
} else {
|
||||
exportTreePivot(instance, chart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1571,6 +1911,7 @@ export function configMergeCells(chart: Chart, options: S2Options, dataConfig: S
|
||||
if (showIndex && meta.colIndex === 0) {
|
||||
meta.fieldValue = getRowIndex(mergedCellsInfo, meta)
|
||||
}
|
||||
meta.deFieldType = fieldsMap[meta.valueField]?.deType
|
||||
return new CustomMergedCell(sheet, cells, meta)
|
||||
}
|
||||
}
|
||||
@ -1598,12 +1939,13 @@ export function getRowIndex(mergedCellsInfo: MergedCellInfo[][], meta: ViewMeta)
|
||||
}, 0)
|
||||
return curRangeStartIndex - lostCells + 1
|
||||
}
|
||||
|
||||
class CustomMergedCell extends MergedCell {
|
||||
protected drawBackgroundShape() {
|
||||
const allPoints = getPolygonPoints(this.cells)
|
||||
// 处理条件样式,这里没有用透明度
|
||||
// 因为合并的单元格是单独的图层,透明度降低的话会显示底下未合并的单元格,需要单独处理被覆盖的单元格
|
||||
const { backgroundColor: fill, backgroundColorOpacity: fillOpacity } = this.getBackgroundColor()
|
||||
const { backgroundColor: fill } = this.getBackgroundColor()
|
||||
const cellTheme = this.theme.dataCell.cell
|
||||
this.backgroundShape = renderPolygon(this, {
|
||||
points: allPoints,
|
||||
@ -1612,6 +1954,14 @@ class CustomMergedCell extends MergedCell {
|
||||
lineHeight: cellTheme.horizontalBorderWidth
|
||||
})
|
||||
}
|
||||
|
||||
drawTextShape(): void {
|
||||
if (this.meta.deFieldType === 7) {
|
||||
drawImage.apply(this)
|
||||
} else {
|
||||
super.drawTextShape()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomDataCell extends TableDataCell {
|
||||
@ -1847,96 +2197,95 @@ const getWrapTextHeight = (wrapText, textStyle, spreadsheet, maxLines) => {
|
||||
return Math.min(lines, maxLines) * maxHeight
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置汇总行
|
||||
* @param chart
|
||||
* @param s2Options
|
||||
* @param newData
|
||||
* @param tableHeader
|
||||
* @param basicStyle
|
||||
* @param showSummary
|
||||
*/
|
||||
export const configSummaryRow = (
|
||||
chart,
|
||||
s2Options,
|
||||
newData,
|
||||
tableHeader,
|
||||
basicStyle,
|
||||
showSummary
|
||||
) => {
|
||||
if (!showSummary || !newData.length) return
|
||||
// 设置汇总行高度和表头一致
|
||||
const heightByField = {}
|
||||
heightByField[newData.length] = tableHeader.tableTitleHeight
|
||||
s2Options.style.rowCfg = { heightByField }
|
||||
// 计算汇总加入到数据里,冻结最后一行
|
||||
s2Options.frozenTrailingRowCount = 1
|
||||
const yAxis = chart.yAxis
|
||||
const xAxis = chart.xAxis
|
||||
const summaryObj = newData.reduce(
|
||||
(p, n) => {
|
||||
if (chart.type === 'table-info') {
|
||||
xAxis
|
||||
.filter(axis => [2, 3, 4].includes(axis.deType))
|
||||
.forEach(axis => {
|
||||
p[axis.dataeaseName] =
|
||||
(parseFloat(n[axis.dataeaseName]) || 0) + (parseFloat(p[axis.dataeaseName]) || 0)
|
||||
// 导出获取汇总行的函数
|
||||
export function getSummaryRow(data, axis, sumCon = []) {
|
||||
const summaryObj = { SUMMARY: true }
|
||||
for (let i = 0; i < axis.length; i++) {
|
||||
const a = axis[i].dataeaseName
|
||||
let savedAxis = find(sumCon, s => s.field === a)
|
||||
if (savedAxis) {
|
||||
if (savedAxis.summary == undefined) {
|
||||
savedAxis.summary = 'sum' // 默认汇总方式为求和
|
||||
}
|
||||
if (savedAxis.show == undefined) {
|
||||
savedAxis.show = true // 默认显示汇总结果
|
||||
}
|
||||
} else {
|
||||
savedAxis = {
|
||||
field: a,
|
||||
summary: 'sum',
|
||||
show: true
|
||||
}
|
||||
}
|
||||
// 如果配置为不显示,则跳过该字段
|
||||
if (!savedAxis.show) {
|
||||
continue
|
||||
}
|
||||
// 根据汇总方式处理数据
|
||||
switch (savedAxis.summary) {
|
||||
case 'sum':
|
||||
// 计算字段的总和
|
||||
summaryObj[a] = safeDecimalSum(data, a)
|
||||
break
|
||||
case 'avg':
|
||||
// 计算字段的平均值
|
||||
summaryObj[a] = safeDecimalMean(data, a)
|
||||
break
|
||||
case 'max':
|
||||
// 计算字段的最大值
|
||||
summaryObj[a] = maxBy(
|
||||
filter(data, d => parseFloat(d[a]) !== undefined),
|
||||
d => parseFloat(d[a]) // 提取数值
|
||||
)[a]
|
||||
break
|
||||
case 'min':
|
||||
// 计算字段的最小值
|
||||
summaryObj[a] = minBy(
|
||||
filter(data, d => parseFloat(d[a]) !== undefined),
|
||||
d => parseFloat(d[a]) // 提取数值
|
||||
)[a]
|
||||
break
|
||||
case 'var_pop':
|
||||
// 计算总体方差(需要至少2个数据点)
|
||||
if (data.length < 2) {
|
||||
continue
|
||||
} else {
|
||||
const mean = safeDecimalMean(data, a) // 计算平均值
|
||||
// 计算每个数据点与平均值的差的平方
|
||||
const squaredDeviations = map(data, d => {
|
||||
const value = new Decimal(d[a] ?? 0) // 获取字段值,如果不存在则使用0
|
||||
const dev = value.minus(mean) // 计算差值
|
||||
return dev.times(dev) // 计算平方
|
||||
})
|
||||
} else {
|
||||
yAxis.forEach(axis => {
|
||||
p[axis.dataeaseName] =
|
||||
(parseFloat(n[axis.dataeaseName]) || 0) + (parseFloat(p[axis.dataeaseName]) || 0)
|
||||
})
|
||||
}
|
||||
return p
|
||||
},
|
||||
{ SUMMARY: true }
|
||||
)
|
||||
newData.push(summaryObj)
|
||||
s2Options.dataCell = viewMeta => {
|
||||
// 配置文本自动换行参数
|
||||
viewMeta.autoWrap = basicStyle.autoWrap
|
||||
viewMeta.maxLines = basicStyle.maxLines
|
||||
if (viewMeta.rowIndex !== newData.length - 1) {
|
||||
return new CustomDataCell(viewMeta, viewMeta.spreadsheet)
|
||||
}
|
||||
if (viewMeta.colIndex === 0) {
|
||||
if (tableHeader.showIndex) {
|
||||
viewMeta.fieldValue = basicStyle.summaryLabel ?? i18nt('chart.total_show')
|
||||
} else {
|
||||
if (xAxis.length) {
|
||||
viewMeta.fieldValue = basicStyle.summaryLabel ?? i18nt('chart.total_show')
|
||||
// 计算方差(平方差的平均值)
|
||||
const variance = squaredDeviations.reduce((acc, val) => acc.plus(val), new Decimal(0))
|
||||
summaryObj[a] = variance.dividedBy(data.length - 1).toNumber() // 计算总体方差
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'stddev_pop':
|
||||
// 计算总体标准差(需要至少2个数据点)
|
||||
if (data.length < 2) {
|
||||
continue
|
||||
} else {
|
||||
const mean = safeDecimalMean(data, a) // 计算平均值
|
||||
// 计算每个数据点与平均值的差的平方
|
||||
const squaredDeviations = map(data, d => {
|
||||
const value = new Decimal(d[a] ?? 0) // 获取字段值,如果不存在则使用0
|
||||
const dev = value.minus(mean) // 计算差值
|
||||
return dev.times(dev) // 计算平方
|
||||
})
|
||||
// 计算方差(平方差的平均值)
|
||||
const variance = squaredDeviations.reduce((acc, val) => acc.plus(val), new Decimal(0))
|
||||
summaryObj[a] = variance.dividedBy(data.length - 1).sqrt().toNumber() // 计算总体标准差
|
||||
}
|
||||
break
|
||||
}
|
||||
return new SummaryCell(viewMeta, viewMeta.spreadsheet)
|
||||
}
|
||||
|
||||
// 返回汇总结果对象
|
||||
return summaryObj
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总行样式,紧贴在单元格后面
|
||||
* @param newChart
|
||||
* @param newData
|
||||
* @param tableCell
|
||||
* @param tableHeader
|
||||
* @param showSummary
|
||||
*/
|
||||
export const summaryRowStyle = (newChart, newData, tableCell, tableHeader, showSummary) => {
|
||||
if (!showSummary || !newData.length) return
|
||||
newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
|
||||
const showHeader = tableHeader.showTableHeader === true
|
||||
// 不显示表头时,减少一个表头的高度
|
||||
const headerAndSummaryHeight = showHeader ? 2 : 1
|
||||
const totalHeight =
|
||||
tableHeader.tableTitleHeight * headerAndSummaryHeight +
|
||||
tableCell.tableItemHeight * (newData.length - 1)
|
||||
if (totalHeight < newChart.options.height) {
|
||||
// 6 是阴影高度
|
||||
newChart.options.height =
|
||||
totalHeight < newChart.options.height - 6 ? totalHeight + 6 : totalHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export class SummaryCell extends CustomDataCell {
|
||||
getTextStyle() {
|
||||
@ -1944,6 +2293,7 @@ export class SummaryCell extends CustomDataCell {
|
||||
textStyle.textAlign = this.theme.dataCell.text.textAlign
|
||||
return textStyle
|
||||
}
|
||||
|
||||
getBackgroundColor() {
|
||||
const { backgroundColor, backgroundColorOpacity } = this.theme.colCell.cell
|
||||
return { backgroundColor, backgroundColorOpacity }
|
||||
@ -2019,3 +2369,27 @@ export const getColumns = (fields, cols: Array<ColumnNode>) => {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function drawImage() {
|
||||
const img = new Image()
|
||||
const { x, y, width, height, fieldValue } = this.meta
|
||||
img.src = fieldValue as string
|
||||
img.setAttribute('crossOrigin', 'anonymous')
|
||||
img.onload = () => {
|
||||
!this.cfg.children && (this.cfg.children = [])
|
||||
const { width: imgWidth, height: imgHeight } = img
|
||||
const ratio = Math.max(imgWidth / width, imgHeight / height)
|
||||
// 不铺满,部分留白
|
||||
const imgShowWidth = (imgWidth / ratio) * 0.8
|
||||
const imgShowHeight = (imgHeight / ratio) * 0.8
|
||||
this.textShape = this.addShape('image', {
|
||||
attrs: {
|
||||
x: x + (imgShowWidth < width ? (width - imgShowWidth) / 2 : 0),
|
||||
y: y + (imgShowHeight < height ? (height - imgShowHeight) / 2 : 0),
|
||||
width: imgShowWidth,
|
||||
height: imgShowHeight,
|
||||
img
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ export abstract class G2PlotChartView<
|
||||
public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
||||
return setupSeriesColor(chart, data)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
public setupSubSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
||||
return undefined
|
||||
}
|
||||
@ -191,8 +191,8 @@ export abstract class G2PlotChartView<
|
||||
return addConditionsStyleColorToData(chart, data)
|
||||
}
|
||||
|
||||
protected configEmptyDataStyle(newChart, newData: any[], container: string) {
|
||||
configEmptyDataStyle(newChart, newData, container)
|
||||
protected configEmptyDataStyle(newData, container, newChart?, content?) {
|
||||
configEmptyDataStyle(newData, container, newChart, content)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,8 +107,8 @@ export abstract class L7ChartView<
|
||||
return options
|
||||
}
|
||||
|
||||
protected configZoomButton(chart: Chart, plot: S) {
|
||||
configL7Zoom(chart, plot)
|
||||
protected configZoomButton(chart: Chart, plot: S, mapKey?: any) {
|
||||
configL7Zoom(chart, plot, mapKey)
|
||||
}
|
||||
|
||||
protected configLabel(chart: Chart, options: O): O {
|
||||
|
@ -136,18 +136,18 @@ export abstract class S2ChartView<P extends SpreadSheet> extends AntVAbstractCha
|
||||
if (duration > 300) {
|
||||
return
|
||||
}
|
||||
const canvasPosition = canvas.getBoundingClientRect()
|
||||
const touchPosition = [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
|
||||
const relativePosition = [
|
||||
touchPosition[0] - canvasPosition.x,
|
||||
touchPosition[1] - canvasPosition.y
|
||||
]
|
||||
const shape = s2Instance.container.getShape(relativePosition[0], relativePosition[1])
|
||||
// 图片单元格,表头排序图标点击放大图片
|
||||
if (shape.cfg?.type === 'image') {
|
||||
return
|
||||
}
|
||||
const callback = () => {
|
||||
const canvasPosition = canvas.getBoundingClientRect()
|
||||
const touchPosition = [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
|
||||
const relativePosition = [
|
||||
touchPosition[0] - canvasPosition.x,
|
||||
touchPosition[1] - canvasPosition.y
|
||||
]
|
||||
const shape = s2Instance.container.getShape(relativePosition[0], relativePosition[1])
|
||||
// 图片单元格点击放大图片
|
||||
if (shape.cfg?.parent.constructor.name === 'ImageCell') {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (shape) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isEmpty, isNumber } from 'lodash-es'
|
||||
import { isNumber } from 'lodash-es'
|
||||
import { DEFAULT_TITLE_STYLE } from '../editor/util/chart'
|
||||
import { equalsAny, includesAny } from '../editor/util/StringUtils'
|
||||
import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
|
||||
@ -12,8 +12,7 @@ import { ElMessage } from 'element-plus-secondary'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useLinkStoreWithOut } from '@/store/modules/link'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
import { deepCopy } from '@/utils/utils'
|
||||
import { Decimal } from 'decimal.js'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
|
||||
@ -286,17 +285,23 @@ export function handleEmptyDataStrategy<O extends PickOptions>(chart: Chart, opt
|
||||
}
|
||||
return options
|
||||
}
|
||||
const { yAxis, xAxisExt, extStack } = chart
|
||||
const { yAxis, xAxisExt, extStack, extBubble } = chart
|
||||
const multiDimension = yAxis?.length >= 2 || xAxisExt?.length > 0 || extStack?.length > 0
|
||||
switch (strategy) {
|
||||
case 'breakLine': {
|
||||
if (multiDimension) {
|
||||
// 多维度保持空
|
||||
if (isChartMix) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
handleBreakLineMultiDimension(data[i] as Record<string, any>[])
|
||||
if (isChartMix) {
|
||||
if (data[0]) {
|
||||
if (xAxisExt?.length > 0 || extStack?.length > 0) {
|
||||
handleBreakLineMultiDimension(data[0] as Record<string, any>[])
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if (data[1]) {
|
||||
if (extBubble?.length > 0) {
|
||||
handleBreakLineMultiDimension(data[1] as Record<string, any>[])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (multiDimension) {
|
||||
handleBreakLineMultiDimension(data)
|
||||
}
|
||||
}
|
||||
@ -306,22 +311,27 @@ export function handleEmptyDataStrategy<O extends PickOptions>(chart: Chart, opt
|
||||
}
|
||||
}
|
||||
case 'setZero': {
|
||||
if (multiDimension) {
|
||||
// 多维度置0
|
||||
if (isChartMix) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
handleSetZeroMultiDimension(data[i] as Record<string, any>[])
|
||||
if (isChartMix) {
|
||||
if (data[0]) {
|
||||
if (xAxisExt?.length > 0 || extStack?.length > 0) {
|
||||
handleSetZeroMultiDimension(data[0] as Record<string, any>[])
|
||||
} else {
|
||||
handleSetZeroSingleDimension(data[0] as Record<string, any>[])
|
||||
}
|
||||
}
|
||||
if (data[1]) {
|
||||
if (extBubble?.length > 0) {
|
||||
handleSetZeroMultiDimension(data[1] as Record<string, any>[], true)
|
||||
} else {
|
||||
handleSetZeroSingleDimension(data[1] as Record<string, any>[], true)
|
||||
}
|
||||
} else {
|
||||
handleSetZeroMultiDimension(data)
|
||||
}
|
||||
} else {
|
||||
// 单维度置0
|
||||
if (isChartMix) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
handleSetZeroSingleDimension(data[i] as Record<string, any>[])
|
||||
}
|
||||
if (multiDimension) {
|
||||
// 多维度置0
|
||||
handleSetZeroMultiDimension(data)
|
||||
} else {
|
||||
// 单维度置0
|
||||
handleSetZeroSingleDimension(data)
|
||||
}
|
||||
}
|
||||
@ -367,7 +377,7 @@ function handleBreakLineMultiDimension(data) {
|
||||
})
|
||||
}
|
||||
|
||||
function handleSetZeroMultiDimension(data: Record<string, any>[]) {
|
||||
function handleSetZeroMultiDimension(data: Record<string, any>[], isExt = false) {
|
||||
const dimensionInfoMap = new Map()
|
||||
const subDimensionSet = new Set()
|
||||
const quotaMap = new Map<string, { id: string }[]>()
|
||||
@ -375,6 +385,9 @@ function handleSetZeroMultiDimension(data: Record<string, any>[]) {
|
||||
const item = data[i]
|
||||
if (item.value === null) {
|
||||
item.value = 0
|
||||
if (isExt) {
|
||||
item.valueExt = 0
|
||||
}
|
||||
}
|
||||
const dimensionInfo = dimensionInfoMap.get(item.field)
|
||||
if (dimensionInfo) {
|
||||
@ -391,12 +404,17 @@ function handleSetZeroMultiDimension(data: Record<string, any>[]) {
|
||||
let subInsertIndex = 0
|
||||
subDimensionSet.forEach(dimension => {
|
||||
if (!dimensionInfo.set.has(dimension)) {
|
||||
data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, {
|
||||
const _temp = {
|
||||
field,
|
||||
value: 0,
|
||||
category: dimension,
|
||||
quotaList: quotaMap.get(dimension as string)
|
||||
})
|
||||
} as any
|
||||
if (isExt) {
|
||||
_temp.valueExt = 0
|
||||
}
|
||||
|
||||
data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, _temp)
|
||||
}
|
||||
subInsertIndex++
|
||||
})
|
||||
@ -405,10 +423,14 @@ function handleSetZeroMultiDimension(data: Record<string, any>[]) {
|
||||
})
|
||||
}
|
||||
|
||||
function handleSetZeroSingleDimension(data: Record<string, any>[]) {
|
||||
function handleSetZeroSingleDimension(data: Record<string, any>[], isExt = false) {
|
||||
data.forEach(item => {
|
||||
if (item.value === null) {
|
||||
item.value = 0
|
||||
if (!isExt) {
|
||||
item.value = 0
|
||||
} else {
|
||||
item.valueExt = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -525,8 +547,20 @@ const getExcelDownloadRequest = (data, type?) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const exportExcelDownload = (chart, callBack?) => {
|
||||
const excelName = chart.title
|
||||
function getChartExcelTitle(preFix, viewTitle) {
|
||||
const now = new Date()
|
||||
const pad = n => n.toString().padStart(2, '0')
|
||||
const year = now.getFullYear()
|
||||
const month = pad(now.getMonth() + 1) // 月份从 0 开始
|
||||
const day = pad(now.getDate())
|
||||
const hour = pad(now.getHours())
|
||||
const minute = pad(now.getMinutes())
|
||||
const second = pad(now.getSeconds())
|
||||
return `${preFix}_${viewTitle}_${year}${month}${day}_${hour}${minute}${second}`
|
||||
}
|
||||
|
||||
export const exportExcelDownload = (chart, preFix, callBack?) => {
|
||||
const excelName = getChartExcelTitle(preFix, chart.title)
|
||||
let request: any = {
|
||||
proxy: null,
|
||||
dvId: chart.sceneId,
|
||||
@ -589,18 +623,21 @@ export const exportExcelDownload = (chart, callBack?) => {
|
||||
}
|
||||
|
||||
export const copyString = (content: string, notify = false) => {
|
||||
const clipboard = navigator.clipboard || {
|
||||
writeText: data => {
|
||||
return new Promise(resolve => {
|
||||
const textareaDom = document.createElement('textarea')
|
||||
textareaDom.setAttribute('style', 'z-index: -1;position: fixed;opacity: 0;')
|
||||
textareaDom.value = data
|
||||
document.body.appendChild(textareaDom)
|
||||
textareaDom.select()
|
||||
document.execCommand('copy')
|
||||
textareaDom.remove()
|
||||
resolve()
|
||||
})
|
||||
let clipboard = navigator.clipboard as Pick<Clipboard, 'writeText'>
|
||||
if (!clipboard || window.top !== window.self) {
|
||||
clipboard = {
|
||||
writeText: data => {
|
||||
return new Promise<void>(resolve => {
|
||||
const textareaDom = document.createElement('textarea')
|
||||
textareaDom.setAttribute('style', 'z-index: -1;position: fixed;opacity: 0;')
|
||||
textareaDom.value = data
|
||||
document.body.appendChild(textareaDom)
|
||||
textareaDom.select()
|
||||
document.execCommand('copy')
|
||||
textareaDom.remove()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
clipboard.writeText(content).then(() => {
|
||||
@ -782,7 +819,7 @@ export function getColor(chart: Chart) {
|
||||
}
|
||||
}
|
||||
|
||||
export function setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
||||
export function setupSeriesColor(chart: ChartObj): ChartBasicStyle['seriesColor'] {
|
||||
const result: ChartBasicStyle['seriesColor'] = []
|
||||
const seriesSet = new Set<string>()
|
||||
const colors = chart.customAttr.basicStyle.colors
|
||||
@ -1155,8 +1192,10 @@ export function getLineLabelColorByCondition(conditions, value, fieldId) {
|
||||
if (fieldConditions.length) {
|
||||
fieldConditions.some(item => {
|
||||
if (
|
||||
(item.term === 'lt' && value <= item.value) ||
|
||||
(item.term === 'gt' && value >= item.value) ||
|
||||
(item.term === 'lt' && value < item.value) ||
|
||||
(item.term === 'le' && value <= item.value) ||
|
||||
(item.term === 'gt' && value > item.value) ||
|
||||
(item.term === 'ge' && value >= item.value) ||
|
||||
(item.term === 'between' && value >= item.min && value <= item.max)
|
||||
) {
|
||||
color = item.color
|
||||
@ -1210,3 +1249,27 @@ export const hexToRgba = (hex, alpha = 1) => {
|
||||
// 返回 RGBA 格式
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`
|
||||
}
|
||||
|
||||
// 安全计算数值字段的总和,使用 Decimal 避免浮点数精度问题
|
||||
export function safeDecimalSum(data, field) {
|
||||
// 使用 reduce 累加所有行的指定字段值
|
||||
return data
|
||||
.reduce((acc, row) => {
|
||||
// 将字段值转换为 Decimal 类型并累加到累加器
|
||||
return acc.plus(new Decimal(row[field] ?? 0))
|
||||
}, new Decimal(0))
|
||||
.toNumber() // 最终结果转换为普通数字返回
|
||||
}
|
||||
|
||||
// 安全计算数值字段的平均值,使用 Decimal 避免浮点数精度问题
|
||||
export function safeDecimalMean(data, field) {
|
||||
// 如果数据为空,直接返回 0
|
||||
if (!data.length) return 0
|
||||
// 计算所有行的指定字段值的总和
|
||||
const sum = data.reduce((acc, row) => {
|
||||
// 将字段值转换为 Decimal 类型并累加到累加器
|
||||
return acc.plus(new Decimal(row[field] ?? 0))
|
||||
}, new Decimal(0))
|
||||
// 将总和除以数据行数,得到平均值,并转换为普通数字返回
|
||||
return sum.dividedBy(data.length).toNumber()
|
||||
}
|
||||
|
@ -14,12 +14,11 @@ import { ChartLibraryType } from '@/views/chart/components/js/panel/types'
|
||||
import { G2PlotChartView } from '@/views/chart/components/js/panel/types/impl/g2plot'
|
||||
import { L7PlotChartView } from '@/views/chart/components/js/panel/types/impl/l7plot'
|
||||
import chartViewManager from '@/views/chart/components/js/panel'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import ViewTrackBar from '@/components/visualization/ViewTrackBar.vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { parseJson } from '@/views/chart/components/js/util'
|
||||
import { defaultsDeep, cloneDeep } from 'lodash-es'
|
||||
import { defaultsDeep, cloneDeep, concat } from 'lodash-es'
|
||||
import ChartError from '@/views/chart/components/views/components/ChartError.vue'
|
||||
import { BASE_VIEW_CONFIG } from '../../editor/util/chart'
|
||||
import { customAttrTrans, customStyleTrans, recursionTransObj } from '@/utils/canvasStyle'
|
||||
@ -28,7 +27,8 @@ import { isDashboard, trackBarStyleCheck } from '@/utils/canvasUtils'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import { L7ChartView } from '@/views/chart/components/js/panel/types/impl/l7'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ExportImage,Scale } from '@antv/l7'
|
||||
import { ExportImage } from '@antv/l7'
|
||||
import { configEmptyDataStyle } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
const { t } = useI18n()
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack, inMobile } =
|
||||
@ -75,6 +75,11 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'inherit'
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
@ -89,6 +94,14 @@ const emit = defineEmits([
|
||||
const g2TypeSeries1 = ['bidirectional-bar']
|
||||
const g2TypeSeries0 = ['bar-range']
|
||||
const g2TypeTree = ['circle-packing']
|
||||
const g2TypeStack = [
|
||||
'bar-stack',
|
||||
'bar-group-stack',
|
||||
'percentage-bar-stack',
|
||||
'bar-stack-horizontal',
|
||||
'percentage-bar-stack-horizontal'
|
||||
]
|
||||
const g2TypeGroup = ['bar-group']
|
||||
|
||||
const { view, showPosition, scale, terminal, suffixId } = toRefs(props)
|
||||
|
||||
@ -132,7 +145,10 @@ const clearLinkage = () => {
|
||||
}
|
||||
const reDrawView = () => {
|
||||
linkageActiveHistory.value = false
|
||||
myChart?.render()
|
||||
const slider = myChart?.chart?.getController('slider')
|
||||
if (!slider) {
|
||||
myChart?.render()
|
||||
}
|
||||
}
|
||||
const linkageActivePre = () => {
|
||||
if (linkageActiveHistory.value) {
|
||||
@ -144,43 +160,103 @@ const linkageActivePre = () => {
|
||||
}
|
||||
const linkageActive = () => {
|
||||
linkageActiveHistory.value = true
|
||||
myChart?.setState('active', () => true, false)
|
||||
myChart?.setState('inactive', () => true, false)
|
||||
myChart?.setState('selected', () => true, false)
|
||||
myChart?.setState('active', param => {
|
||||
if (Array.isArray(param)) {
|
||||
return false
|
||||
} else {
|
||||
if (checkSelected(param)) {
|
||||
return true
|
||||
}
|
||||
return checkSelected(param)
|
||||
}
|
||||
})
|
||||
myChart?.setState('inactive', param => {
|
||||
if (Array.isArray(param)) {
|
||||
return false
|
||||
} else {
|
||||
if (!checkSelected(param)) {
|
||||
return true
|
||||
}
|
||||
return !checkSelected(param)
|
||||
}
|
||||
})
|
||||
myChart?.setState('selected', param => {
|
||||
if (Array.isArray(param)) {
|
||||
return false
|
||||
} else {
|
||||
return checkSelected(param)
|
||||
}
|
||||
})
|
||||
}
|
||||
const checkSelected = param => {
|
||||
// 获取当前视图的所有联动字段ID
|
||||
const mappingFieldIds = Array.from(
|
||||
new Set(
|
||||
(view.value.type.includes('chart-mix')
|
||||
? concat(chartData.value?.left?.fields, chartData.value?.right?.fields)
|
||||
: chartData.value?.fields
|
||||
)
|
||||
.map(item => item?.id)
|
||||
.filter(id =>
|
||||
Object.keys(nowPanelTrackInfo.value).some(
|
||||
key => key.startsWith(view.value.id) && key.split('#')[1] === id
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// 维度字段匹配
|
||||
const [xAxis, xAxisExt, extStack] = ['xAxis', 'xAxisExt', 'extStack'].map(key =>
|
||||
view.value[key].find(item => mappingFieldIds.includes(item.id))
|
||||
)
|
||||
// 选中字段数据
|
||||
const { group, name, category } = state.linkageActiveParam
|
||||
// 选中字段数据匹配
|
||||
if (g2TypeSeries1.includes(view.value.type)) {
|
||||
return state.linkageActiveParam.name === param.field
|
||||
return name === param.field
|
||||
} else if (g2TypeSeries0.includes(view.value.type)) {
|
||||
return state.linkageActiveParam.category === param.category
|
||||
return category === param.category
|
||||
} else if (g2TypeTree.includes(view.value.type)) {
|
||||
if (
|
||||
param.path?.startsWith(state.linkageActiveParam.name) ||
|
||||
state.linkageActiveParam.name === t('commons.all')
|
||||
) {
|
||||
if (param.path?.startsWith(name) || name === t('commons.all')) {
|
||||
return true
|
||||
}
|
||||
return state.linkageActiveParam.name === param.name
|
||||
return name === param.name
|
||||
} else if (g2TypeGroup.includes(view.value.type)) {
|
||||
const isNameMatch = name === param.name || (name === 'NO_DATA' && !param.name)
|
||||
const isCategoryMatch = category === param.category
|
||||
if (xAxis && xAxisExt) {
|
||||
return isNameMatch && isCategoryMatch
|
||||
}
|
||||
if (xAxis && !xAxisExt) {
|
||||
return isNameMatch
|
||||
}
|
||||
if (!xAxis && xAxisExt) {
|
||||
return isCategoryMatch
|
||||
}
|
||||
return false
|
||||
} else if (g2TypeStack.includes(view.value.type)) {
|
||||
const isGroupMatch = group === param.group || (group === 'NO_DATA' && !param.group)
|
||||
const isNameMatch = name === param.name || (name === 'NO_DATA' && !param.name)
|
||||
const isCategoryMatch = category === param.category
|
||||
// 全部匹配
|
||||
if (xAxis && xAxisExt && extStack) {
|
||||
return isNameMatch && isGroupMatch && isCategoryMatch
|
||||
}
|
||||
// 只匹配到维度
|
||||
if (xAxis && !xAxisExt && !extStack) {
|
||||
return isNameMatch
|
||||
} else if (!xAxis && xAxisExt && !extStack) {
|
||||
return isGroupMatch
|
||||
} else if (!xAxis && !xAxisExt && extStack) {
|
||||
return isCategoryMatch
|
||||
} else if (xAxis && xAxisExt && !extStack) {
|
||||
return isNameMatch && isGroupMatch
|
||||
} else if (xAxis && !xAxisExt && extStack) {
|
||||
return isNameMatch && isCategoryMatch
|
||||
} else if (!xAxis && xAxisExt && extStack) {
|
||||
return isGroupMatch && isCategoryMatch
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
(state.linkageActiveParam.name === param.name ||
|
||||
(state.linkageActiveParam.name === 'NO_DATA' && !param.name)) &&
|
||||
state.linkageActiveParam.category === param.category
|
||||
(name === param.name || (name === 'NO_DATA' && !param.name)) && category === param.category
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -274,6 +350,8 @@ const renderG2Plot = async (chart, chartView: G2PlotChartView<any, any>) => {
|
||||
g2Timer && clearTimeout(g2Timer)
|
||||
g2Timer = setTimeout(async () => {
|
||||
try {
|
||||
// 在这里清理掉之前图表的空dom
|
||||
configEmptyDataStyle([1], containerId)
|
||||
myChart?.destroy()
|
||||
myChart = await chartView.drawChart({
|
||||
chartObj: myChart,
|
||||
@ -295,7 +373,6 @@ const renderG2Plot = async (chart, chartView: G2PlotChartView<any, any>) => {
|
||||
|
||||
const dynamicAreaId = ref('')
|
||||
const country = ref('')
|
||||
const appStore = useAppStoreWithOut()
|
||||
const chartContainer = ref<HTMLElement>(null)
|
||||
let scope
|
||||
let mapTimer: number
|
||||
@ -332,8 +409,6 @@ const renderL7Plot = async (chart: ChartObj, chartView: L7PlotChartView<any, any
|
||||
}
|
||||
|
||||
let mapL7Timer: number
|
||||
let scaleControl: Scale | null = null // 存储比例尺实例
|
||||
|
||||
const renderL7 = async (chart: ChartObj, chartView: L7ChartView<any, any>, callback) => {
|
||||
mapL7Timer && clearTimeout(mapL7Timer)
|
||||
mapL7Timer = setTimeout(async () => {
|
||||
@ -343,25 +418,12 @@ const renderL7 = async (chart: ChartObj, chartView: L7ChartView<any, any>, callb
|
||||
chart: chart,
|
||||
action
|
||||
})
|
||||
|
||||
// 清除已有比例尺
|
||||
if (scaleControl) {
|
||||
myChart.getScene()?.removeControl(scaleControl)
|
||||
scaleControl = null
|
||||
}
|
||||
|
||||
// 创建并添加新比例尺
|
||||
scaleControl = new Scale({
|
||||
position: 'bottomleft',
|
||||
imperial: false
|
||||
})
|
||||
myChart.getScene()?.addControl(scaleControl)
|
||||
|
||||
myChart?.render()
|
||||
callback?.()
|
||||
emit('resetLoading')
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const pointClickTrans = () => {
|
||||
if (embeddedCallBack.value === 'yes') {
|
||||
trackClick('pointClick')
|
||||
@ -375,11 +437,8 @@ const actionDefault = param => {
|
||||
if (param.from === 'word-cloud') {
|
||||
emitter.emit('word-cloud-default-data-range', param)
|
||||
}
|
||||
if (param.from === 'gauge') {
|
||||
emitter.emit('gauge-default-data', param)
|
||||
}
|
||||
if (param.from === 'liquid') {
|
||||
emitter.emit('liquid-default-data', param)
|
||||
if (param.from === 'gauge' || param.from === 'liquid') {
|
||||
emitter.emit('gauge-liquid-y-value', param)
|
||||
}
|
||||
}
|
||||
|
||||
@ -399,7 +458,8 @@ const action = param => {
|
||||
// 下钻 联动 跳转
|
||||
state.linkageActiveParam = {
|
||||
category: state.pointParam.data.category ? state.pointParam.data.category : 'NO_DATA',
|
||||
name: state.pointParam.data.name ? state.pointParam.data.name : 'NO_DATA'
|
||||
name: state.pointParam.data.name ? state.pointParam.data.name : 'NO_DATA',
|
||||
group: state.pointParam.data.group ? state.pointParam.data.group : 'NO_DATA'
|
||||
}
|
||||
if (trackMenu.value.length < 2) {
|
||||
// 只有一个事件直接调用
|
||||
@ -429,7 +489,7 @@ const action = param => {
|
||||
state.trackBarStyle.top = trackBarY + 'px'
|
||||
}
|
||||
|
||||
viewTrack.value.trackButtonClick()
|
||||
viewTrack.value.trackButtonClick(view.value.id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,10 +498,28 @@ const trackClick = trackAction => {
|
||||
if (!param?.data?.dimensionList) {
|
||||
return
|
||||
}
|
||||
let checkName = state.pointParam.data.name
|
||||
// 对多维度的处理 取第一个
|
||||
if (state.pointParam.data.dimensionList.length > 1) {
|
||||
checkName = state.pointParam.data.dimensionList[0].id
|
||||
let checkName = undefined
|
||||
if (param.data.dimensionList.length > 1) {
|
||||
// 分组堆叠处理 去能比较出来值的那个维度
|
||||
if (view.value.type === 'bar-group-stack') {
|
||||
const length = param.data.dimensionList.length
|
||||
// 存在最后一个id
|
||||
if (param.data.dimensionList[length - 1].id === param.data.dimensionList[length - 2].id) {
|
||||
param.data.dimensionList.pop()
|
||||
}
|
||||
param.data.dimensionList.forEach(dimension => {
|
||||
if (dimension.value === param.data.category) {
|
||||
checkName = dimension.id
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!checkName) {
|
||||
// 对多维度的处理 取第一个
|
||||
checkName = param.data.dimensionList[0].id
|
||||
}
|
||||
}
|
||||
if (!checkName) {
|
||||
checkName = param.data.name
|
||||
}
|
||||
// 跳转字段处理
|
||||
let jumpName = state.pointParam.data.name
|
||||
@ -480,7 +558,7 @@ const trackClick = trackAction => {
|
||||
}
|
||||
}
|
||||
let quotaList = state.pointParam.data.quotaList
|
||||
if (['bar-range'].includes(curView.type)) {
|
||||
if (['bar-range', 'bullet-graph'].includes(curView.type)) {
|
||||
quotaList = state.pointParam.data.dimensionList
|
||||
} else {
|
||||
quotaList[0]['value'] = state.pointParam.data.value
|
||||
@ -535,37 +613,38 @@ const trackMenu = computed(() => {
|
||||
let trackMenuInfo = []
|
||||
// 复用、放大状态的仪表板不进行联动、跳转和下钻的动作
|
||||
if (!['multiplexing', 'viewDialog'].includes(showPosition.value)) {
|
||||
let drillFields =
|
||||
curView?.drill && curView?.drillFilters?.length
|
||||
? curView.drillFilters.map(item => item.fieldId)
|
||||
: []
|
||||
let linkageCount = 0
|
||||
let jumpCount = 0
|
||||
if (curView?.type?.includes('chart-mix')) {
|
||||
chartData.value?.left?.fields?.forEach(item => {
|
||||
const sourceInfo = view.value.id + '#' + item.id
|
||||
if (nowPanelTrackInfo.value[sourceInfo]) {
|
||||
linkageCount++
|
||||
}
|
||||
if (nowPanelJumpInfo.value[sourceInfo]) {
|
||||
jumpCount++
|
||||
}
|
||||
})
|
||||
chartData.value?.right?.fields?.forEach(item => {
|
||||
const sourceInfo = view.value.id + '#' + item.id
|
||||
if (nowPanelTrackInfo.value[sourceInfo]) {
|
||||
linkageCount++
|
||||
}
|
||||
if (nowPanelJumpInfo.value[sourceInfo]) {
|
||||
jumpCount++
|
||||
}
|
||||
Array.of('left', 'right').forEach(side => {
|
||||
chartData.value?.[side]?.fields
|
||||
?.filter(item => !drillFields.includes(item.id))
|
||||
.forEach(item => {
|
||||
const sourceInfo = view.value.id + '#' + item.id
|
||||
if (nowPanelTrackInfo.value[sourceInfo]) {
|
||||
linkageCount++
|
||||
}
|
||||
if (nowPanelJumpInfo.value[sourceInfo]) {
|
||||
jumpCount++
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
chartData.value?.fields?.forEach(item => {
|
||||
const sourceInfo = view.value.id + '#' + item.id
|
||||
if (nowPanelTrackInfo.value[sourceInfo]) {
|
||||
linkageCount++
|
||||
}
|
||||
if (nowPanelJumpInfo.value[sourceInfo]) {
|
||||
jumpCount++
|
||||
}
|
||||
})
|
||||
chartData.value?.fields
|
||||
?.filter(item => !drillFields.includes(item.id))
|
||||
.forEach(item => {
|
||||
const sourceInfo = view.value.id + '#' + item.id
|
||||
if (nowPanelTrackInfo.value[sourceInfo]) {
|
||||
linkageCount++
|
||||
}
|
||||
if (nowPanelJumpInfo.value[sourceInfo]) {
|
||||
jumpCount++
|
||||
}
|
||||
})
|
||||
}
|
||||
jumpCount &&
|
||||
view.value?.jumpActive &&
|
||||
@ -615,7 +694,7 @@ const canvas2Picture = (pictureData, online) => {
|
||||
mapDom.appendChild(imgDom)
|
||||
}
|
||||
const preparePicture = id => {
|
||||
if (id !== curView.id) {
|
||||
if (id !== curView?.id) {
|
||||
return
|
||||
}
|
||||
const chartView = chartViewManager.getChartView(curView.render, curView.type)
|
||||
@ -639,7 +718,7 @@ const preparePicture = id => {
|
||||
}
|
||||
}
|
||||
const unPreparePicture = id => {
|
||||
if (id !== curView.id) {
|
||||
if (id !== curView?.id) {
|
||||
return
|
||||
}
|
||||
const chartView = chartViewManager.getChartView(curView.render, curView.type)
|
||||
@ -667,6 +746,7 @@ defineExpose({
|
||||
trackMenu,
|
||||
clearLinkage
|
||||
})
|
||||
let intersectionObserver
|
||||
let resizeObserver
|
||||
const TOLERANCE = 0.01
|
||||
const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map', 'heat-map']
|
||||
@ -691,13 +771,32 @@ onMounted(() => {
|
||||
preSize[1] = size.blockSize
|
||||
})
|
||||
resizeObserver.observe(containerDom)
|
||||
intersectionObserver = new IntersectionObserver(([entry]) => {
|
||||
if (RESIZE_MONITOR_CHARTS.includes(view.value.type)) {
|
||||
return
|
||||
}
|
||||
if (entry.intersectionRatio <= 0) {
|
||||
myChart?.emit('tooltip:hidden')
|
||||
}
|
||||
})
|
||||
intersectionObserver.observe(containerDom)
|
||||
useEmitt({ name: 'l7-prepare-picture', callback: preparePicture })
|
||||
useEmitt({ name: 'l7-unprepare-picture', callback: unPreparePicture })
|
||||
})
|
||||
const MAP_CHARTS = ['map', 'bubble-map', 'flow-map', 'heat-map', 'symbolic-map']
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
if (!MAP_CHARTS.includes(view.value.type)) {
|
||||
return
|
||||
}
|
||||
if (!props.active) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
try {
|
||||
myChart?.destroy()
|
||||
resizeObserver?.disconnect()
|
||||
intersectionObserver?.disconnect()
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
}
|
||||
@ -715,7 +814,13 @@ onBeforeUnmount(() => {
|
||||
:style="state.trackBarStyle"
|
||||
@trackClick="trackClick"
|
||||
/>
|
||||
<div v-if="!isError" ref="chartContainer" class="canvas-content" :id="containerId"></div>
|
||||
<div
|
||||
@wheel.capture="onWheel"
|
||||
v-if="!isError"
|
||||
ref="chartContainer"
|
||||
class="canvas-content"
|
||||
:id="containerId"
|
||||
></div>
|
||||
<chart-error v-else :err-msg="errMsg" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
} from 'vue'
|
||||
import { getData } from '@/api/chart'
|
||||
import chartViewManager from '@/views/chart/components/js/panel'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import ViewTrackBar from '@/components/visualization/ViewTrackBar.vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
@ -125,6 +124,7 @@ const state = reactive({
|
||||
imgEnlarge: false,
|
||||
imgSrc: ''
|
||||
})
|
||||
const PAGE_CHARTS = ['table-info', 'table-normal']
|
||||
// 图表数据不用全响应式
|
||||
let chartData = shallowRef<Partial<Chart['data']>>({
|
||||
fields: []
|
||||
@ -133,17 +133,20 @@ let chartData = shallowRef<Partial<Chart['data']>>({
|
||||
const containerId = 'container-' + showPosition.value + '-' + view.value.id + '-' + suffixId.value
|
||||
const viewTrack = ref(null)
|
||||
|
||||
const calcData = (view: Chart, callback, resetPageInfo = true) => {
|
||||
if (view.customAttr.basicStyle.tablePageStyle === 'general') {
|
||||
const calcData = (viewInfo: Chart, callback, resetPageInfo = true) => {
|
||||
if (viewInfo.customAttr.basicStyle.tablePageStyle === 'general') {
|
||||
if (state.currentPageSize !== 0) {
|
||||
view.chartExtRequest.pageSize = state.currentPageSize
|
||||
viewInfo.chartExtRequest.pageSize = state.currentPageSize
|
||||
state.pageInfo.pageSize = state.currentPageSize
|
||||
} else {
|
||||
viewInfo.chartExtRequest.pageSize = state.pageInfo.pageSize
|
||||
}
|
||||
} else {
|
||||
delete view.chartExtRequest.pageSize
|
||||
delete viewInfo.chartExtRequest?.pageSize
|
||||
}
|
||||
if (view.tableId || view['dataFrom'] === 'template') {
|
||||
if (viewInfo.tableId || viewInfo['dataFrom'] === 'template') {
|
||||
isError.value = false
|
||||
const v = JSON.parse(JSON.stringify(view))
|
||||
const v = JSON.parse(JSON.stringify(viewInfo))
|
||||
getData(v)
|
||||
.then(res => {
|
||||
if (res.code && res.code !== 0) {
|
||||
@ -152,7 +155,7 @@ const calcData = (view: Chart, callback, resetPageInfo = true) => {
|
||||
} else {
|
||||
chartData.value = res?.data as Partial<Chart['data']>
|
||||
state.totalItems = res?.totalItems
|
||||
dvMainStore.setViewDataDetails(view.id, res)
|
||||
dvMainStore.setViewDataDetails(viewInfo.id, res)
|
||||
emit('onDrillFilters', res?.drillFilters)
|
||||
renderChart(res as unknown as Chart, resetPageInfo)
|
||||
}
|
||||
@ -223,7 +226,7 @@ const renderChart = (viewInfo: Chart, resetPageInfo: boolean) => {
|
||||
nextTick(() => debounceRender(resetPageInfo))
|
||||
}
|
||||
|
||||
const debounceRender = debounce(resetPageInfo => {
|
||||
const debounceRender = debounce(() => {
|
||||
myChart?.facet?.timer?.stop()
|
||||
myChart?.facet?.cancelScrollFrame()
|
||||
myChart?.destroy()
|
||||
@ -248,19 +251,13 @@ const debounceRender = debounce(resetPageInfo => {
|
||||
|
||||
const setupPage = (chart: ChartObj, resetPageInfo?: boolean) => {
|
||||
const customAttr = chart.customAttr
|
||||
if (chart.type !== 'table-info' || customAttr.basicStyle.tablePageMode !== 'page') {
|
||||
if (!PAGE_CHARTS.includes(chart.type) || customAttr.basicStyle.tablePageMode !== 'page') {
|
||||
state.showPage = false
|
||||
return
|
||||
}
|
||||
const pageInfo = state.pageInfo
|
||||
state.pageStyle = customAttr.basicStyle.tablePageStyle
|
||||
if (state.pageStyle === 'general') {
|
||||
if (state.currentPageSize === 0) {
|
||||
state.currentPageSize = pageInfo.pageSize
|
||||
} else {
|
||||
pageInfo.pageSize = state.currentPageSize
|
||||
}
|
||||
} else {
|
||||
if (state.pageStyle !== 'general') {
|
||||
pageInfo.pageSize = customAttr.basicStyle.tablePageSize ?? 20
|
||||
}
|
||||
if (state.totalItems > state.pageInfo.pageSize || state.pageStyle === 'general') {
|
||||
@ -272,6 +269,7 @@ const setupPage = (chart: ChartObj, resetPageInfo?: boolean) => {
|
||||
if (resetPageInfo) {
|
||||
state.pageInfo.currentPage = 1
|
||||
}
|
||||
dvMainStore.setViewPageInfo(chart.id, state.pageInfo)
|
||||
}
|
||||
|
||||
const mouseMove = () => {
|
||||
@ -293,7 +291,8 @@ const initScroll = () => {
|
||||
myChart &&
|
||||
senior?.scrollCfg?.open &&
|
||||
chartData.value.tableRow?.length &&
|
||||
(view.value.type === 'table-normal' || (view.value.type === 'table-info' && !state.showPage))
|
||||
PAGE_CHARTS.includes(props.view.type) &&
|
||||
!state.showPage
|
||||
) {
|
||||
// 防止多次渲染
|
||||
myChart.facet.timer?.stop()
|
||||
@ -337,7 +336,7 @@ const initScroll = () => {
|
||||
}
|
||||
|
||||
const showPage = computed(() => {
|
||||
if (view.value.type !== 'table-info') {
|
||||
if (!PAGE_CHARTS.includes(view.value.type)) {
|
||||
return false
|
||||
}
|
||||
return state.showPage
|
||||
@ -355,6 +354,7 @@ const handleCurrentChange = pageNum => {
|
||||
const handlePageSizeChange = pageSize => {
|
||||
if (state.pageStyle === 'general') {
|
||||
state.currentPageSize = pageSize
|
||||
emitter.emit('set-page-size', pageSize)
|
||||
}
|
||||
let extReq = { pageSize: pageSize }
|
||||
if (chartExtRequest.value) {
|
||||
@ -401,10 +401,9 @@ const action = param => {
|
||||
state.trackBarStyle.top = barStyleTemp.top + 'px'
|
||||
}
|
||||
|
||||
viewTrack.value.trackButtonClick()
|
||||
viewTrack.value.trackButtonClick(view.value.id)
|
||||
}
|
||||
}
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const trackClick = trackAction => {
|
||||
const param = state.pointParam
|
||||
@ -681,12 +680,6 @@ const autoStyle = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const autoHeightStyle = computed(() => {
|
||||
return {
|
||||
height: 20 * scale.value + 8 + 'px'
|
||||
}
|
||||
})
|
||||
|
||||
const tabStyle = computed(() => [
|
||||
{ '--de-pager-color': canvasStyleData.value.component.seniorStyleSetting?.pagerColor }
|
||||
])
|
||||
@ -743,7 +736,7 @@ const tablePageClass = computed(() => {
|
||||
v-else
|
||||
class="table-page-content"
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
v-model:page-size="state.currentPageSize"
|
||||
v-model:page-size="state.pageInfo.pageSize"
|
||||
v-model:current-page="state.pageInfo.currentPage"
|
||||
:pager-count="5"
|
||||
:total="state.pageInfo.total"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user