更新前端src/views(chart和template)目录文件

This commit is contained in:
limengnan 2025-06-24 09:17:57 +08:00
parent 1357172783
commit f2582cb0d0
106 changed files with 7041 additions and 2204 deletions

View File

@ -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(() => {

View File

@ -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>

View File

@ -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>

View File

@ -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 />&nbsp;
</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>

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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)
})

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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>

View File

@ -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>

View File

@ -62,7 +62,7 @@ const state = reactive({
quotaData: []
})
const props = withDefaults(
withDefaults(
defineProps<{
themes?: EditorTheme
}>(),

View File

@ -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"

View File

@ -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>

View File

@ -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%;
}

View File

@ -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>

View File

@ -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'

View File

@ -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

View File

@ -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(

View File

@ -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()
})

View File

@ -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%;
}

View File

@ -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"

View File

@ -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"

View File

@ -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) {

View File

@ -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>&nbsp;</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>

View File

@ -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 = () => {
// yAxisID
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')"

View File

@ -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"

View File

@ -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'])

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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"

View File

@ -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) => {
//cellparent
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 {

View File

@ -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

View File

@ -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')"

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -246,5 +246,4 @@ const del = (index, child) => {
}
}
}
</style>

View File

@ -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;

View File

@ -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>

View File

@ -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 = {

View File

@ -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 仪表板自定义

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -72,7 +72,8 @@ export class Waterfall extends G2PlotChartView<WaterfallOptions, G2Waterfall> {
'axisForm',
'axisLabel',
'axisLabelFormatter',
'showLengthLimit'
'showLengthLimit',
'axisLine'
],
threshold: ['lineThreshold']
}

View File

@ -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'],

View File

@ -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 {

View File

@ -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 => {

View File

@ -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
}

View File

@ -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')
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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]

View File

@ -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

View File

@ -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 = {

View File

@ -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 : ''
}
}

View File

@ -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
}

View File

@ -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'],

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -6,6 +6,7 @@ export const TABLE_EDITOR_PROPERTY: EditorProperty[] = [
'table-cell-selector',
'title-selector',
'tooltip-selector',
'summary-selector',
'function-cfg',
'threshold',
'scroll-cfg',

View File

@ -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 => {

View File

@ -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
})
)
}

View File

@ -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', [])
}

View File

@ -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) {

View File

@ -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) : {}) }
}
}
}

View File

@ -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
}
})
}
}

View File

@ -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)
}
/**

View File

@ -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 {

View File

@ -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) {

View File

@ -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()
}

View File

@ -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>

View File

@ -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