forked from github/dataease
parent
464b2523f0
commit
37855f034d
@ -595,6 +595,8 @@ export default {
|
||||
formatter_plc: '内容格式为空时,显示默认格式',
|
||||
xAxis: '横轴',
|
||||
yAxis: '纵轴',
|
||||
yAxisLeft: '左纵轴',
|
||||
yAxisRight: '右纵轴',
|
||||
position: '位置',
|
||||
rotate: '角度',
|
||||
name: '名称',
|
||||
@ -752,6 +754,8 @@ export default {
|
||||
chart_style: '样式',
|
||||
drag_block_type_axis: '类别轴',
|
||||
drag_block_value_axis: '值轴',
|
||||
drag_block_value_axis_left: '左值轴',
|
||||
drag_block_value_axis_right: '右值轴',
|
||||
drag_block_table_data_column: '数据列',
|
||||
drag_block_pie_angel: '扇区角度',
|
||||
drag_block_pie_label: '扇区标签',
|
||||
@ -782,7 +786,7 @@ export default {
|
||||
custom_case: '自定义',
|
||||
last_layer: '当前已经是最后一级',
|
||||
radar_size: '大小',
|
||||
chart_mix: '组合图',
|
||||
chart_mix: '柱线组合图',
|
||||
axis_value: '轴值',
|
||||
axis_value_min: '最小值',
|
||||
axis_value_max: '最大值',
|
||||
@ -831,6 +835,7 @@ export default {
|
||||
chart_type_compare: '柱形图',
|
||||
chart_type_distribute: '分布图',
|
||||
chart_type_relation: '关系图',
|
||||
chart_type_dual_axes: '双轴图',
|
||||
chart_type_space: '地图',
|
||||
preview: '上一步',
|
||||
next: '下一步',
|
||||
|
@ -5,6 +5,7 @@ declare type EditorProperty =
|
||||
| 'tooltip-selector'
|
||||
| 'x-axis-selector'
|
||||
| 'y-axis-selector'
|
||||
| 'dual-y-axis-selector'
|
||||
| 'title-selector'
|
||||
| 'legend-selector'
|
||||
| 'table-header-selector'
|
||||
|
@ -178,6 +178,11 @@ 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)
|
||||
@ -189,6 +194,12 @@ const beforeSummary = type => {
|
||||
}
|
||||
}
|
||||
|
||||
const beforeSwitchType = type => {
|
||||
return {
|
||||
type: type
|
||||
}
|
||||
}
|
||||
|
||||
const showRename = () => {
|
||||
item.value.index = props.index
|
||||
item.value.renameType = props.type
|
||||
@ -334,6 +345,56 @@ onMounted(() => {
|
||||
class="drop-style"
|
||||
:class="themes === 'dark' ? 'dark-dimension-quota' : ''"
|
||||
>
|
||||
<el-dropdown-item @click.prevent v-if="chart.type === 'chart-mix'">
|
||||
<el-dropdown
|
||||
:effect="themes"
|
||||
placement="right-start"
|
||||
style="width: 100%"
|
||||
@command="switchChartType"
|
||||
>
|
||||
<span class="el-dropdown-link inner-dropdown-menu menu-item-padding">
|
||||
<span class="menu-item-content">
|
||||
<el-icon>
|
||||
<Icon name="icon_functions_outlined" />
|
||||
</el-icon>
|
||||
<span>{{ t('chart.chart_type') }}</span>
|
||||
</span>
|
||||
<el-icon>
|
||||
<Icon name="icon_right_outlined"></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu
|
||||
:effect="themes"
|
||||
class="drop-style sub"
|
||||
:class="themes === 'dark' ? 'dark-dimension-quota' : ''"
|
||||
>
|
||||
<el-dropdown-item class="menu-item-padding" :command="beforeSwitchType('bar')">
|
||||
<span
|
||||
class="sub-menu-content"
|
||||
:class="'bar' === item.chartType ? 'content-active' : ''"
|
||||
>
|
||||
{{ t('chart.chart_bar') }}
|
||||
<el-icon class="sub-menu-content--icon">
|
||||
<Icon name="icon_done_outlined" v-if="'bar' === item.chartType" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item class="menu-item-padding" :command="beforeSwitchType('line')">
|
||||
<span
|
||||
class="sub-menu-content"
|
||||
:class="'line' === item.chartType ? 'content-active' : ''"
|
||||
>
|
||||
{{ t('chart.chart_line') }}
|
||||
<el-icon class="sub-menu-content--icon">
|
||||
<Icon name="icon_done_outlined" v-if="'line' === item.chartType" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.prevent
|
||||
v-if="!item.chartId && chart.type !== 'table-info' && item.summary !== ''"
|
||||
|
@ -6,6 +6,7 @@ import LabelSelector from '@/views/chart/components/editor/editor-style/componen
|
||||
import TooltipSelector from '@/views/chart/components/editor/editor-style/components/TooltipSelector.vue'
|
||||
import XAxisSelector from '@/views/chart/components/editor/editor-style/components/XAxisSelector.vue'
|
||||
import YAxisSelector from '@/views/chart/components/editor/editor-style/components/YAxisSelector.vue'
|
||||
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 { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
@ -78,6 +79,7 @@ const emit = defineEmits([
|
||||
'onTooltipChange',
|
||||
'onChangeXAxisForm',
|
||||
'onChangeYAxisForm',
|
||||
'onChangeYAxisExtForm',
|
||||
'onTextChange',
|
||||
'onLegendChange',
|
||||
'onBasicStyleChange',
|
||||
@ -117,6 +119,11 @@ const onChangeYAxisForm = (val, prop) => {
|
||||
state.initReady && emit('onChangeYAxisForm', val, prop)
|
||||
}
|
||||
|
||||
const onChangeYAxisExtForm = (val, prop) => {
|
||||
console.log(val, prop)
|
||||
state.initReady && emit('onChangeYAxisExtForm', val, prop)
|
||||
}
|
||||
|
||||
const onTextChange = (val, prop) => {
|
||||
state.initReady && emit('onTextChange', val, prop)
|
||||
}
|
||||
@ -442,6 +449,25 @@ watch(
|
||||
@onChangeYAxisForm="onChangeYAxisForm"
|
||||
/>
|
||||
</collapse-switch-item>
|
||||
|
||||
<collapse-switch-item
|
||||
:themes="themes"
|
||||
v-if="showProperties('dual-y-axis-selector')"
|
||||
v-model="chart.customStyle.yAxis.show"
|
||||
:change-model="chart.customStyle.yAxis"
|
||||
@modelChange="val => onChangeYAxisForm(val, 'show')"
|
||||
name="yAxis"
|
||||
:title="$t('chart.yAxis')"
|
||||
>
|
||||
<dual-y-axis-selector
|
||||
class="attr-selector"
|
||||
:property-inner="propertyInnerAll['y-axis-selector']"
|
||||
:themes="themes"
|
||||
:chart="chart"
|
||||
@onChangeYAxisForm="onChangeYAxisForm"
|
||||
@onChangeYAxisExtForm="onChangeYAxisExtForm"
|
||||
/>
|
||||
</collapse-switch-item>
|
||||
</el-collapse>
|
||||
</el-row>
|
||||
</div>
|
||||
|
@ -0,0 +1,144 @@
|
||||
<script lang="tsx" setup>
|
||||
import { computed, 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'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
themes: {
|
||||
type: String as PropType<EditorTheme>,
|
||||
default: 'dark'
|
||||
},
|
||||
chart: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
propertyInner: {
|
||||
type: Array<string>
|
||||
}
|
||||
})
|
||||
|
||||
const activeName = ref('left')
|
||||
|
||||
const state = reactive<any>({
|
||||
axisForm: JSON.parse(JSON.stringify(DEFAULT_YAXIS_STYLE)),
|
||||
subAxisForm: JSON.parse(JSON.stringify(DEFAULT_YAXIS_EXT_STYLE))
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onChangeYAxisForm', 'onChangeYAxisExtForm'])
|
||||
|
||||
watch(
|
||||
() => props.chart.customStyle.yAxis,
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const changeAxisStyle = (val, prop) => {
|
||||
emit('onChangeYAxisForm', val, prop)
|
||||
}
|
||||
|
||||
const changeSubAxisStyle = (val, prop) => {
|
||||
console.log(val, prop)
|
||||
emit('onChangeYAxisExtForm', val, prop)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
if (chart.customStyle) {
|
||||
let customStyle = null
|
||||
if (Object.prototype.toString.call(chart.customStyle) === '[object Object]') {
|
||||
customStyle = JSON.parse(JSON.stringify(chart.customStyle))
|
||||
} else {
|
||||
customStyle = JSON.parse(chart.customStyle)
|
||||
}
|
||||
if (customStyle.yAxis) {
|
||||
state.axisForm = cloneDeep(customStyle.yAxis)
|
||||
state.axisForm.position = 'left'
|
||||
}
|
||||
|
||||
if (customStyle.yAxisExt) {
|
||||
state.subAxisForm = cloneDeep(customStyle.yAxisExt)
|
||||
}
|
||||
state.subAxisForm.position = 'right'
|
||||
state.subAxisForm.show = state.axisForm.show
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-tabs v-model="activeName" id="axis-tabs" stretch>
|
||||
<el-tab-pane :label="t('chart.yAxisLeft')" name="left">
|
||||
<dual-y-axis-selector-inner
|
||||
style="margin-top: 8px"
|
||||
v-if="state.axisForm"
|
||||
:form="state.axisForm"
|
||||
:property-inner="propertyInner"
|
||||
:themes="themes"
|
||||
type="left"
|
||||
@on-change-y-axis-form="changeAxisStyle"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('chart.yAxisRight')" name="right">
|
||||
<dual-y-axis-selector-inner
|
||||
style="margin-top: 8px"
|
||||
v-if="state.subAxisForm"
|
||||
:form="state.subAxisForm"
|
||||
:property-inner="propertyInner"
|
||||
:themes="themes"
|
||||
type="right"
|
||||
@on-change-y-axis-form="changeSubAxisStyle"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#axis-tabs {
|
||||
margin-top: -16px;
|
||||
--ed-tabs-header-height: 34px;
|
||||
|
||||
:deep(.ed-tabs__header) {
|
||||
border-top: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-form-item-label {
|
||||
margin-bottom: 4px;
|
||||
line-height: 20px;
|
||||
color: #646a73;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
padding: 2px 12px 0 0;
|
||||
|
||||
&.custom-form-item-label--dark {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
}
|
||||
.form-item-checkbox {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
.m-divider {
|
||||
border-color: rgba(31, 35, 41, 0.15);
|
||||
margin: 0 0 16px;
|
||||
|
||||
&.m-divider--dark {
|
||||
border-color: rgba(235, 235, 235, 0.15);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,495 @@
|
||||
<script lang="tsx" setup>
|
||||
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'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
themes?: EditorTheme
|
||||
form: any
|
||||
propertyInner?: Array<string>
|
||||
type?: 'left' | 'right'
|
||||
}>(),
|
||||
{
|
||||
themes: 'dark',
|
||||
type: 'left'
|
||||
}
|
||||
)
|
||||
|
||||
const predefineColors = COLOR_PANEL
|
||||
const typeList = formatterType
|
||||
const unitList = unitType
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
})
|
||||
const state = reactive({
|
||||
axisForm: JSON.parse(JSON.stringify(DEFAULT_YAXIS_STYLE))
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onChangeYAxisForm'])
|
||||
|
||||
watch(
|
||||
() => props.form,
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const fontSizeList = computed(() => {
|
||||
const arr = []
|
||||
for (let i = 10; i <= 40; i = i + 2) {
|
||||
arr.push({
|
||||
name: i + '',
|
||||
value: i
|
||||
})
|
||||
}
|
||||
return arr
|
||||
})
|
||||
|
||||
const changeAxisStyle = prop => {
|
||||
if (
|
||||
state.axisForm.axisValue.splitCount &&
|
||||
(state.axisForm.axisValue.splitCount > 100 || state.axisForm.axisValue.splitCount < 0)
|
||||
) {
|
||||
ElMessage.error(t('chart.splitCount_less_100'))
|
||||
return
|
||||
}
|
||||
emit('onChangeYAxisForm', state.axisForm, prop)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
state.axisForm = JSON.parse(JSON.stringify(props.form))
|
||||
}
|
||||
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="axisForm"
|
||||
:disabled="!state.axisForm.show"
|
||||
:model="state.axisForm"
|
||||
size="small"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.position')"
|
||||
v-if="showProperty('position')"
|
||||
>
|
||||
<el-radio-group
|
||||
v-model="state.axisForm.position"
|
||||
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>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.name')"
|
||||
v-if="showProperty('name')"
|
||||
>
|
||||
<el-input
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.name"
|
||||
size="small"
|
||||
maxlength="50"
|
||||
@blur="changeAxisStyle('name')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes"
|
||||
>{{ t('chart.name') }}{{ t('chart.text') }}</label
|
||||
>
|
||||
<div style="display: flex">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
v-if="showProperty('color')"
|
||||
style="padding-right: 4px"
|
||||
>
|
||||
<el-color-picker
|
||||
v-model="state.axisForm.color"
|
||||
class="color-picker-style"
|
||||
:predefine="predefineColors"
|
||||
@change="changeAxisStyle('color')"
|
||||
:effect="themes"
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
v-if="showProperty('fontSize')"
|
||||
style="padding-left: 4px"
|
||||
>
|
||||
<el-tooltip content="字号" :effect="toolTip" placement="top">
|
||||
<el-select
|
||||
style="width: 108px"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.fontSize"
|
||||
:placeholder="t('chart.axis_name_fontsize')"
|
||||
@change="changeAxisStyle('fontSize')"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in fontSizeList"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<template v-if="showProperty('axisValue')">
|
||||
<el-divider class="m-divider" :class="'m-divider--' + themes" />
|
||||
|
||||
<div style="display: flex; flex-direction: row; justify-content: space-between">
|
||||
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">
|
||||
{{ t('chart.axis_value') }}
|
||||
<el-tooltip class="item" :effect="toolTip" placement="top">
|
||||
<template #content><span v-html="t('chart.axis_tip')"></span></template>
|
||||
<span style="vertical-align: middle">
|
||||
<el-icon style="cursor: pointer">
|
||||
<Icon name="icon_info_outlined" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</label>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisValue.auto"
|
||||
@change="changeAxisStyle('axisValue.auto')"
|
||||
>
|
||||
{{ t('chart.axis_auto') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<template v-if="showProperty('axisValue') && !state.axisForm.axisValue.auto">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.axis_value_max')"
|
||||
>
|
||||
<el-input-number
|
||||
controls-position="right"
|
||||
:effect="props.themes"
|
||||
v-model.number="state.axisForm.axisValue.max"
|
||||
@change="changeAxisStyle('axisValue.max')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.axis_value_min')"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="props.themes"
|
||||
controls-position="right"
|
||||
v-model.number="state.axisForm.axisValue.min"
|
||||
@change="changeAxisStyle('axisValue.min')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">
|
||||
{{ t('chart.axis_value_split_count') }}
|
||||
<el-tooltip class="item" :effect="toolTip" placement="top">
|
||||
<template #content>期望的坐标轴刻度数量,非最终结果。</template>
|
||||
<span style="vertical-align: middle">
|
||||
<el-icon style="cursor: pointer">
|
||||
<Icon name="icon_info_outlined" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</label>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||
<el-input-number
|
||||
style="width: 100%"
|
||||
:effect="props.themes"
|
||||
controls-position="right"
|
||||
v-model.number="state.axisForm.axisValue.splitCount"
|
||||
@change="changeAxisStyle('axisValue.splitCount')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
<el-divider class="m-divider" :class="'m-divider--' + themes" />
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" v-if="showProperty('axisLine')">
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLine.show"
|
||||
@change="changeAxisStyle('axisLine.show')"
|
||||
>
|
||||
{{ t('chart.axis_show') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
class="form-item form-item-checkbox"
|
||||
:class="{
|
||||
'form-item-dark': themes === 'dark'
|
||||
}"
|
||||
v-if="showProperty('splitLine')"
|
||||
>
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.splitLine.show"
|
||||
@change="changeAxisStyle('splitLine.show')"
|
||||
>
|
||||
{{ t('chart.grid_show') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<div style="padding-left: 22px" v-if="showProperty('splitLine')">
|
||||
<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.splitLine.show"
|
||||
v-model="state.axisForm.splitLine.lineStyle.color"
|
||||
:predefine="predefineColors"
|
||||
@change="changeAxisStyle('splitLine.lineStyle.color')"
|
||||
:effect="themes"
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<el-input-number
|
||||
:disabled="!state.axisForm.splitLine.show"
|
||||
style="width: 108px"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.splitLine.lineStyle.width"
|
||||
:min="1"
|
||||
:max="10"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeAxisStyle('splitLine.lineStyle.width')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider class="m-divider" :class="'m-divider--' + themes" />
|
||||
<el-form-item
|
||||
class="form-item form-item-checkbox"
|
||||
:class="{
|
||||
'form-item-dark': themes === 'dark'
|
||||
}"
|
||||
v-if="showProperty('axisLabel')"
|
||||
>
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabel.show"
|
||||
@change="changeAxisStyle('axisLabel.show')"
|
||||
>
|
||||
{{ t('chart.axis_label_show') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<div style="padding-left: 22px" v-if="showProperty('axisLabel')">
|
||||
<div style="flex: 1">
|
||||
<div style="display: flex">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
style="padding-right: 4px"
|
||||
:label="t('chart.text')"
|
||||
>
|
||||
<el-color-picker
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
v-model="state.axisForm.axisLabel.color"
|
||||
:predefine="predefineColors"
|
||||
@change="changeAxisStyle('axisLabel.color')"
|
||||
:effect="themes"
|
||||
is-custom
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
|
||||
<template #label> </template>
|
||||
<el-tooltip content="字号" :effect="toolTip" placement="top">
|
||||
<el-select
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
style="width: 108px"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabel.fontSize"
|
||||
:placeholder="t('chart.axis_label_fontsize')"
|
||||
@change="changeAxisStyle('axisLabel.fontSize')"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in fontSizeList"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item class="form-item" :class="'form-item-' + themes" :label="t('chart.rotate')">
|
||||
<el-input-number
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
style="width: 100%"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabel.rotate"
|
||||
:min="-90"
|
||||
:max="90"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeAxisStyle('axisLabel.rotate')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="showProperty('axisLabelFormatter')">
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.value_formatter_type')"
|
||||
>
|
||||
<el-select
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
style="width: 100%"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.type"
|
||||
@change="changeAxisStyle('axisLabelFormatter.type')"
|
||||
>
|
||||
<el-option
|
||||
v-for="type in typeList"
|
||||
:key="type.value"
|
||||
:label="t('chart.' + type.name)"
|
||||
:value="type.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="state.axisForm.axisLabelFormatter.type !== 'auto'"
|
||||
:label="t('chart.value_formatter_decimal_count')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input-number
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
style="width: 100%"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.decimalCount"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
:max="10"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeAxisStyle('axisLabelFormatter.decimalCount')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row
|
||||
:gutter="8"
|
||||
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-option
|
||||
v-for="item in unitList"
|
||||
:key="item.value"
|
||||
:label="t('chart.' + item.name)"
|
||||
:value="item.value"
|
||||
/>
|
||||
</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 class="form-item" :class="'form-item-' + themes">
|
||||
<el-checkbox
|
||||
:disabled="!state.axisForm.axisLabel.show"
|
||||
size="small"
|
||||
:effect="props.themes"
|
||||
v-model="state.axisForm.axisLabelFormatter.thousandSeparator"
|
||||
@change="changeAxisStyle('axisLabelFormatter.thousandSeparator')"
|
||||
:label="t('chart.value_formatter_thousand_separator')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.custom-form-item-label {
|
||||
margin-bottom: 4px;
|
||||
line-height: 20px;
|
||||
color: #646a73;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
padding: 2px 12px 0 0;
|
||||
|
||||
&.custom-form-item-label--dark {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
}
|
||||
.form-item-checkbox {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
.m-divider {
|
||||
border-color: rgba(31, 35, 41, 0.15);
|
||||
margin: 0 0 16px;
|
||||
|
||||
&.m-divider--dark {
|
||||
border-color: rgba(235, 235, 235, 0.15);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,7 +4,7 @@ import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart'
|
||||
import { ElSpace } from 'element-plus-secondary'
|
||||
import { formatterType, unitType } from '../../../js/formatter'
|
||||
import { defaultsDeep, cloneDeep, intersection } from 'lodash-es'
|
||||
import { defaultsDeep, cloneDeep, intersection, union, defaultTo } from 'lodash-es'
|
||||
import { includesAny } from '../../util/StringUtils'
|
||||
import { fieldType } from '@/utils/attr'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
@ -37,8 +37,12 @@ watch(
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
const yAxis = computed(() => {
|
||||
return union(defaultTo(props.chart.yAxis, []), defaultTo(props.chart.yAxisExt, []))
|
||||
})
|
||||
|
||||
watch(
|
||||
[() => props.chart.yAxis, () => props.chart.type],
|
||||
[() => yAxis.value, () => props.chart.type],
|
||||
() => {
|
||||
initSeriesLabel()
|
||||
},
|
||||
@ -46,10 +50,7 @@ watch(
|
||||
)
|
||||
const curSeriesFormatter = ref<Partial<SeriesFormatter>>({})
|
||||
const formatterEditable = computed(() => {
|
||||
return (
|
||||
showProperty('seriesLabelFormatter') &&
|
||||
(props.chart.yAxis?.length || props.chart.yAxisExt?.length)
|
||||
)
|
||||
return showProperty('seriesLabelFormatter') && yAxis.value?.length
|
||||
})
|
||||
const formatterSelector = ref()
|
||||
// 初始化系列标签
|
||||
@ -59,17 +60,17 @@ const initSeriesLabel = () => {
|
||||
return
|
||||
}
|
||||
const formatter = state.labelForm.seriesLabelFormatter
|
||||
const yAxis = props.chart.yAxis
|
||||
|
||||
const seriesAxisMap = formatter.reduce((pre, next) => {
|
||||
pre[next.id] = next
|
||||
return pre
|
||||
}, {})
|
||||
formatter.splice(0, formatter.length)
|
||||
if (!yAxis.length) {
|
||||
if (!yAxis.value.length) {
|
||||
curSeriesFormatter.value = {}
|
||||
return
|
||||
}
|
||||
const axisMap = yAxis.reduce((pre, next) => {
|
||||
const axisMap = yAxis.value.reduce((pre, next) => {
|
||||
let tmp = {
|
||||
...next,
|
||||
show: true,
|
||||
|
@ -42,7 +42,7 @@ import CustomSortEdit from '@/views/chart/components/editor/drag-item/components
|
||||
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, get } from 'lodash-es'
|
||||
import { cloneDeep, forEach, get } from 'lodash-es'
|
||||
import { deleteField, saveField } from '@/api/dataset'
|
||||
import { getWorldTree } from '@/api/map'
|
||||
import chartViewManager from '@/views/chart/components/js/panel'
|
||||
@ -494,12 +494,44 @@ const addAxis = (e, axis: AxisType) => {
|
||||
emitter.emit('removeAxis', { axisType: 'yAxis', axis, editType: 'remove' })
|
||||
}
|
||||
}
|
||||
if (view.value.type === 'indicator') {
|
||||
if (view.value.type === 'chart-mix') {
|
||||
if (axis === 'yAxis') {
|
||||
if (view.value.yAxisExt.length > 0) {
|
||||
const chartType = view.value.yAxisExt[0].chartType
|
||||
forEach(view.value.yAxis, axis => {
|
||||
if (chartType === 'bar') {
|
||||
axis.chartType = 'line'
|
||||
} else if (chartType === 'line') {
|
||||
axis.chartType = 'bar'
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (axis === 'yAxisExt') {
|
||||
if (view.value.yAxis.length > 0) {
|
||||
const chartType = view.value.yAxis[0].chartType
|
||||
forEach(view.value.yAxisExt, axis => {
|
||||
if (chartType === 'bar') {
|
||||
axis.chartType = 'line'
|
||||
} else if (chartType === 'line') {
|
||||
axis.chartType = 'bar'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (view.value.type === 'indicator' || view.value.type === 'chart-mix') {
|
||||
if (view.value?.yAxis?.length > 1) {
|
||||
const axis = view.value.yAxis.splice(1)
|
||||
emitter.emit('removeAxis', { axisType: 'yAxis', axis, editType: 'remove' })
|
||||
}
|
||||
}
|
||||
if (view.value.type === 'chart-mix') {
|
||||
if (view.value?.yAxisExt?.length > 1) {
|
||||
const axis = view.value.yAxisExt.splice(1)
|
||||
emitter.emit('removeAxis', { axisType: 'yAxisExt', axis, editType: 'remove' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addXaxis = e => {
|
||||
@ -518,6 +550,10 @@ const addYaxis = e => {
|
||||
addAxis(e, 'yAxis')
|
||||
}
|
||||
|
||||
const addYaxisExt = e => {
|
||||
addAxis(e, 'yAxisExt')
|
||||
}
|
||||
|
||||
const addExtBubble = e => {
|
||||
addAxis(e, 'extBubble')
|
||||
}
|
||||
@ -748,6 +784,12 @@ const onChangeYAxisForm = val => {
|
||||
renderChart(view.value)
|
||||
}
|
||||
|
||||
const onChangeYAxisExtForm = val => {
|
||||
console.log('onChangeYAxisExtForm', val)
|
||||
view.value.customStyle.yAxisExt = val
|
||||
renderChart(view.value)
|
||||
}
|
||||
|
||||
const onChangeMiscStyleForm = val => {
|
||||
view.value.customStyle.misc = val
|
||||
renderChart(view.value)
|
||||
@ -822,7 +864,15 @@ const closeRename = () => {
|
||||
}
|
||||
|
||||
const removeItems = (
|
||||
_type: 'xAxis' | 'xAxisExt' | 'extStack' | 'yAxis' | 'extBubble' | 'customFilter' | 'drillFields'
|
||||
_type:
|
||||
| 'xAxis'
|
||||
| 'xAxisExt'
|
||||
| 'extStack'
|
||||
| 'yAxis'
|
||||
| 'yAxisExt'
|
||||
| 'extBubble'
|
||||
| 'customFilter'
|
||||
| 'drillFields'
|
||||
) => {
|
||||
recordSnapshotInfo('calcData')
|
||||
let axis = []
|
||||
@ -839,6 +889,9 @@ const removeItems = (
|
||||
case 'yAxis':
|
||||
axis = view.value.yAxis?.splice(0)
|
||||
break
|
||||
case 'yAxisExt':
|
||||
axis = view.value.yAxisExt?.splice(0)
|
||||
break
|
||||
case 'extBubble':
|
||||
axis = view.value.extBubble?.splice(0)
|
||||
break
|
||||
@ -1516,6 +1569,54 @@ const onRefreshChange = val => {
|
||||
</draggable>
|
||||
<drag-placeholder :drag-list="view.yAxis" />
|
||||
</el-row>
|
||||
<!--yAxisExt-->
|
||||
<el-row class="padding-lr drag-data" v-if="showAxis('yAxisExt')">
|
||||
<div class="form-draggable-title">
|
||||
<span>
|
||||
{{ chartViewInstance.axisConfig.yAxisExt.name }}
|
||||
</span>
|
||||
<el-tooltip :effect="toolTip" placement="top" :content="t('common.delete')">
|
||||
<el-icon
|
||||
class="remove-icon"
|
||||
:class="{ 'remove-icon--dark': themes === 'dark' }"
|
||||
size="14px"
|
||||
@click="removeItems('yAxisExt')"
|
||||
>
|
||||
<Icon class-name="inner-class" name="icon_delete-trash_outlined" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<draggable
|
||||
:list="view.yAxisExt"
|
||||
:move="onMove"
|
||||
item-key="id"
|
||||
group="drag"
|
||||
animation="300"
|
||||
class="drag-block-style"
|
||||
:class="{ dark: themes === 'dark' }"
|
||||
@add="addYaxisExt"
|
||||
@change="e => onAxisChange(e, 'yAxisExt')"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<quota-item
|
||||
:dimension-data="state.dimension"
|
||||
:quota-data="state.quota"
|
||||
:chart="view"
|
||||
:item="element"
|
||||
:index="index"
|
||||
type="quotaExt"
|
||||
:themes="props.themes"
|
||||
@onQuotaItemChange="item => quotaItemChange(item, 'yAxisExt')"
|
||||
@onQuotaItemRemove="quotaItemRemove"
|
||||
@onNameEdit="showRename"
|
||||
@editItemFilter="showQuotaEditFilter"
|
||||
@editItemCompare="showQuotaEditCompare"
|
||||
@valueFormatter="valueFormatter"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
<drag-placeholder :drag-list="view.yAxisExt" />
|
||||
</el-row>
|
||||
<!-- extBubble -->
|
||||
<el-row class="padding-lr drag-data" v-if="showAxis('extBubble')">
|
||||
<div class="form-draggable-title">
|
||||
@ -1792,6 +1893,7 @@ const onRefreshChange = val => {
|
||||
@onTooltipChange="onTooltipChange"
|
||||
@onChangeXAxisForm="onChangeXAxisForm"
|
||||
@onChangeYAxisForm="onChangeYAxisForm"
|
||||
@onChangeYAxisExtForm="onChangeYAxisExtForm"
|
||||
@onTextChange="onTextChange"
|
||||
@onIndicatorChange="onIndicatorChange"
|
||||
@onIndicatorNameChange="onIndicatorNameChange"
|
||||
|
@ -568,7 +568,7 @@ export const DEFAULT_YAXIS_EXT_STYLE: ChartAxisStyle = {
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
show: false,
|
||||
lineStyle: {
|
||||
color: '#cccccc',
|
||||
width: 1,
|
||||
@ -577,10 +577,10 @@ export const DEFAULT_YAXIS_EXT_STYLE: ChartAxisStyle = {
|
||||
},
|
||||
axisValue: {
|
||||
auto: true,
|
||||
min: null,
|
||||
max: null,
|
||||
split: null,
|
||||
splitCount: null
|
||||
min: 10,
|
||||
max: 100,
|
||||
split: 10,
|
||||
splitCount: 10
|
||||
},
|
||||
axisLabelFormatter: {
|
||||
type: 'auto',
|
||||
@ -1297,6 +1297,20 @@ export const CHART_TYPE_CONFIGS = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'dual_axes',
|
||||
title: t('chart.chart_type_dual_axes'),
|
||||
display: 'show',
|
||||
details: [
|
||||
{
|
||||
render: 'antv',
|
||||
category: 'dual_axes',
|
||||
value: 'chart-mix',
|
||||
title: t('chart.chart_mix'),
|
||||
icon: 'chart-mix'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'other',
|
||||
title: '富文本',
|
||||
|
@ -0,0 +1,69 @@
|
||||
export const CHART_MIX_EDITOR_PROPERTY: EditorProperty[] = [
|
||||
'background-overall-component',
|
||||
'basic-style-selector',
|
||||
'x-axis-selector',
|
||||
'dual-y-axis-selector',
|
||||
'title-selector',
|
||||
'legend-selector',
|
||||
'label-selector',
|
||||
'tooltip-selector',
|
||||
'assist-line',
|
||||
'function-cfg',
|
||||
'jump-set',
|
||||
'linkage'
|
||||
]
|
||||
export const CHART_MIX_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
|
||||
'background-overall-component': ['all'],
|
||||
'label-selector': ['fontSize', 'color'],
|
||||
'tooltip-selector': ['fontSize', 'color', 'backgroundColor'],
|
||||
'basic-style-selector': [
|
||||
'colors',
|
||||
'alpha',
|
||||
'lineWidth',
|
||||
'lineSymbol',
|
||||
'lineSymbolSize',
|
||||
'lineSmooth'
|
||||
],
|
||||
'x-axis-selector': [
|
||||
'name',
|
||||
'color',
|
||||
'fontSize',
|
||||
'position',
|
||||
'axisLabel',
|
||||
'axisLine',
|
||||
'splitLine'
|
||||
],
|
||||
'y-axis-selector': [
|
||||
'name',
|
||||
'color',
|
||||
'fontSize',
|
||||
'axisLabel',
|
||||
'axisLine',
|
||||
'splitLine',
|
||||
'axisValue',
|
||||
'axisLabelFormatter'
|
||||
],
|
||||
'title-selector': [
|
||||
'title',
|
||||
'fontSize',
|
||||
'color',
|
||||
'hPosition',
|
||||
'isItalic',
|
||||
'isBolder',
|
||||
'remarkShow',
|
||||
'fontFamily',
|
||||
'letterSpace',
|
||||
'fontShadow'
|
||||
],
|
||||
'legend-selector': ['icon', 'orient', 'fontSize', 'color', 'hPosition', 'vPosition'],
|
||||
'function-cfg': ['slider', 'emptyDataStrategy']
|
||||
}
|
||||
|
||||
export const CHART_MIX_AXIS_TYPE: AxisType[] = [
|
||||
'xAxis',
|
||||
'yAxis',
|
||||
'drill',
|
||||
'filter',
|
||||
'extLabel',
|
||||
'extTooltip'
|
||||
]
|
@ -0,0 +1,389 @@
|
||||
import {
|
||||
G2PlotChartView,
|
||||
G2PlotDrawOptions
|
||||
} from '@/views/chart/components/js/panel/types/impl/g2plot'
|
||||
import { DualAxes, DualAxesOptions } from '@antv/g2plot/esm/plots/dual-axes'
|
||||
import { getLabel, getPadding, getYAxis, getYAxisExt } from '../../common/common_antv'
|
||||
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
|
||||
import { cloneDeep, isEmpty, defaultTo, map } from 'lodash-es'
|
||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
import {
|
||||
CHART_MIX_AXIS_TYPE,
|
||||
CHART_MIX_EDITOR_PROPERTY,
|
||||
CHART_MIX_EDITOR_PROPERTY_INNER
|
||||
} from './chart-mix-common'
|
||||
import { Datum } from '@antv/g2plot/esm/types/common'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart'
|
||||
|
||||
const { t } = useI18n()
|
||||
const DEFAULT_DATA = []
|
||||
|
||||
/**
|
||||
* 柱线混合图
|
||||
*/
|
||||
export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
|
||||
properties = CHART_MIX_EDITOR_PROPERTY
|
||||
propertyInner = {
|
||||
...CHART_MIX_EDITOR_PROPERTY_INNER,
|
||||
'label-selector': ['vPosition', 'seriesLabelFormatter'],
|
||||
'tooltip-selector': [
|
||||
...CHART_MIX_EDITOR_PROPERTY_INNER['tooltip-selector'],
|
||||
'seriesTooltipFormatter'
|
||||
]
|
||||
}
|
||||
axis: AxisType[] = [...CHART_MIX_AXIS_TYPE, 'yAxisExt']
|
||||
axisConfig = {
|
||||
...this['axisConfig'],
|
||||
yAxis: {
|
||||
name: `${t('chart.drag_block_value_axis_left')} / ${t('chart.quota')}`,
|
||||
type: 'q'
|
||||
},
|
||||
yAxisExt: {
|
||||
name: `${t('chart.drag_block_value_axis_right')} / ${t('chart.quota')}`,
|
||||
type: 'q'
|
||||
}
|
||||
}
|
||||
drawChart(drawOptions: G2PlotDrawOptions<DualAxes>): DualAxes {
|
||||
const { chart, action, container } = drawOptions
|
||||
if (!chart.data.data?.length) {
|
||||
return
|
||||
}
|
||||
const data = cloneDeep(chart.data.data)
|
||||
|
||||
const data1Type = data[0]?.type === 'bar' ? 'column' : data[0]?.type
|
||||
const data2Type = data[1]?.type === 'bar' ? 'column' : data[1]?.type
|
||||
|
||||
const data1 = defaultTo(data[0]?.data, [])
|
||||
const data2 = map(defaultTo(data[1]?.data, []), d => {
|
||||
return {
|
||||
...d,
|
||||
valueExt: d.value
|
||||
}
|
||||
})
|
||||
// custom color
|
||||
const customAttr = parseJson(chart.customAttr)
|
||||
const color = customAttr.basicStyle.colors
|
||||
// options
|
||||
const initOptions: DualAxesOptions = {
|
||||
data: [data1, data2],
|
||||
xField: 'field',
|
||||
yField: ['value', 'valueExt'], //这里不能设置成一样的
|
||||
appendPadding: getPadding(chart),
|
||||
color,
|
||||
geometryOptions: [
|
||||
{
|
||||
geometry: data1Type
|
||||
},
|
||||
{
|
||||
geometry: data2Type
|
||||
}
|
||||
],
|
||||
interactions: [
|
||||
{
|
||||
type: 'legend-active',
|
||||
cfg: {
|
||||
start: [{ trigger: 'legend-item:mouseenter', action: ['element-active:reset'] }],
|
||||
end: [{ trigger: 'legend-item:mouseleave', action: ['element-active:reset'] }]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'legend-filter',
|
||||
cfg: {
|
||||
start: [
|
||||
{
|
||||
trigger: 'legend-item:click',
|
||||
action: [
|
||||
'list-unchecked:toggle',
|
||||
'data-filter:filter',
|
||||
'element-active:reset',
|
||||
'element-highlight:reset'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'tooltip',
|
||||
cfg: {
|
||||
start: [{ trigger: 'point:mousemove', action: 'tooltip:show' }],
|
||||
end: [{ trigger: 'point:mouseleave', action: 'tooltip:hide' }]
|
||||
}
|
||||
},
|
||||
{
|
||||
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, initOptions)
|
||||
// 开始渲染
|
||||
const newChart = new DualAxes(container, options)
|
||||
|
||||
newChart.on('point:click', action)
|
||||
|
||||
return newChart
|
||||
}
|
||||
|
||||
protected configLabel(chart: Chart, options: DualAxesOptions): DualAxesOptions {
|
||||
const tempLabel = getLabel(chart)
|
||||
const tmpOption = { ...options }
|
||||
if (!tempLabel) {
|
||||
if (tmpOption.geometryOptions) {
|
||||
tmpOption.geometryOptions[0].label = false
|
||||
tmpOption.geometryOptions[1].label = false
|
||||
}
|
||||
return tmpOption
|
||||
}
|
||||
|
||||
const labelAttr = parseJson(chart.customAttr).label
|
||||
const formatterMap = labelAttr.seriesLabelFormatter?.reduce((pre, next) => {
|
||||
pre[next.id] = next
|
||||
return pre
|
||||
}, {})
|
||||
tempLabel.style.fill = DEFAULT_LABEL.color
|
||||
const label = {
|
||||
fields: [],
|
||||
...tempLabel,
|
||||
offsetY: -8,
|
||||
formatter: (data: Datum) => {
|
||||
if (!labelAttr.seriesLabelFormatter?.length) {
|
||||
return data.value
|
||||
}
|
||||
const labelCfg = formatterMap?.[data.quotaList[0].id] as SeriesFormatter
|
||||
if (!labelCfg) {
|
||||
return data.value
|
||||
}
|
||||
if (!labelCfg.show) {
|
||||
return
|
||||
}
|
||||
const value = valueFormatter(data.value, labelCfg.formatterCfg)
|
||||
const group = new G2PlotChartView.engine.Group({})
|
||||
group.addShape({
|
||||
type: 'text',
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
text: value,
|
||||
textAlign: 'start',
|
||||
textBaseline: 'top',
|
||||
fontSize: labelCfg.fontSize,
|
||||
fill: labelCfg.color
|
||||
}
|
||||
})
|
||||
return group
|
||||
}
|
||||
}
|
||||
if (tmpOption.geometryOptions) {
|
||||
tmpOption.geometryOptions[0].label = label
|
||||
tmpOption.geometryOptions[1].label = label
|
||||
}
|
||||
return tmpOption
|
||||
}
|
||||
|
||||
protected configBasicStyle(chart: Chart, options: DualAxesOptions): DualAxesOptions {
|
||||
// size
|
||||
const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
|
||||
const s = JSON.parse(JSON.stringify(customAttr.basicStyle))
|
||||
const smooth = s.lineSmooth
|
||||
const point = {
|
||||
size: s.lineSymbolSize,
|
||||
shape: s.lineSymbol
|
||||
}
|
||||
const lineStyle = {
|
||||
lineWidth: s.lineWidth
|
||||
}
|
||||
const tempOption = {
|
||||
...options,
|
||||
smooth,
|
||||
point,
|
||||
lineStyle
|
||||
}
|
||||
if (tempOption.geometryOptions) {
|
||||
tempOption.geometryOptions[0].smooth = smooth
|
||||
tempOption.geometryOptions[0].point = point
|
||||
tempOption.geometryOptions[0].lineStyle = lineStyle
|
||||
|
||||
tempOption.geometryOptions[1].smooth = smooth
|
||||
tempOption.geometryOptions[1].point = point
|
||||
tempOption.geometryOptions[1].lineStyle = lineStyle
|
||||
}
|
||||
return tempOption
|
||||
}
|
||||
|
||||
protected configCustomColors(chart: Chart, options: DualAxesOptions): DualAxesOptions {
|
||||
const basicStyle = parseJson(chart.customAttr).basicStyle
|
||||
const color = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
|
||||
return {
|
||||
...options,
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
protected configYAxis(chart: Chart, options: DualAxesOptions): DualAxesOptions {
|
||||
const yAxis = getYAxis(chart)
|
||||
const yAxisExt = getYAxisExt(chart)
|
||||
|
||||
const tempOption = {
|
||||
...options
|
||||
}
|
||||
|
||||
if (!yAxis) {
|
||||
//左右轴都要隐藏
|
||||
tempOption.yAxis = {
|
||||
value: false,
|
||||
valueExt: false
|
||||
}
|
||||
} else {
|
||||
tempOption.yAxis = {
|
||||
value: undefined,
|
||||
valueExt: undefined
|
||||
}
|
||||
}
|
||||
|
||||
const yAxisTmp = parseJson(chart.customStyle).yAxis
|
||||
if (yAxis.label) {
|
||||
yAxis.label.formatter = value => {
|
||||
return valueFormatter(value, yAxisTmp.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
const axisValue = yAxisTmp.axisValue
|
||||
if (!axisValue?.auto) {
|
||||
tempOption.yAxis.value = {
|
||||
...yAxis,
|
||||
min: axisValue.min,
|
||||
max: axisValue.max,
|
||||
minLimit: axisValue.min,
|
||||
maxLimit: axisValue.max,
|
||||
tickCount: axisValue.splitCount
|
||||
}
|
||||
} else {
|
||||
tempOption.yAxis.value = yAxis
|
||||
}
|
||||
|
||||
const yAxisExtTmp = parseJson(chart.customStyle).yAxisExt
|
||||
if (yAxisExt.label) {
|
||||
yAxisExt.label.formatter = value => {
|
||||
return valueFormatter(value, yAxisExtTmp.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
const axisExtValue = yAxisExtTmp.axisValue
|
||||
if (!axisExtValue?.auto) {
|
||||
tempOption.yAxis.valueExt = {
|
||||
...yAxisExt,
|
||||
min: axisExtValue.min,
|
||||
max: axisExtValue.max,
|
||||
minLimit: axisExtValue.min,
|
||||
maxLimit: axisExtValue.max,
|
||||
tickCount: axisExtValue.splitCount
|
||||
}
|
||||
} else {
|
||||
tempOption.yAxis.valueExt = yAxisExt
|
||||
}
|
||||
|
||||
return tempOption
|
||||
}
|
||||
|
||||
protected configTooltip(chart: Chart, options: DualAxesOptions): DualAxesOptions {
|
||||
const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
|
||||
const tooltipAttr = customAttr.tooltip
|
||||
if (!tooltipAttr.show) {
|
||||
return {
|
||||
...options,
|
||||
tooltip: false
|
||||
}
|
||||
}
|
||||
const xAxisExt = chart.xAxisExt
|
||||
const formatterMap = tooltipAttr.seriesTooltipFormatter
|
||||
?.filter(i => i.show)
|
||||
.reduce((pre, next) => {
|
||||
pre[next.id] = next
|
||||
return pre
|
||||
}, {}) as Record<string, SeriesFormatter>
|
||||
const tooltip: DualAxesOptions['tooltip'] = {
|
||||
shared: true,
|
||||
showTitle: true,
|
||||
customItems(originalItems) {
|
||||
if (!tooltipAttr.seriesTooltipFormatter?.length) {
|
||||
return originalItems
|
||||
}
|
||||
const head = originalItems[0]
|
||||
// 非原始数据
|
||||
if (!head.data.quotaList) {
|
||||
return originalItems
|
||||
}
|
||||
const result = []
|
||||
originalItems
|
||||
.filter(item => formatterMap[item.data.quotaList[0].id])
|
||||
.forEach(item => {
|
||||
const formatter = formatterMap[item.data.quotaList[0].id]
|
||||
const value = valueFormatter(parseFloat(item.value as string), formatter.formatterCfg)
|
||||
let name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
|
||||
if (xAxisExt?.length > 0) {
|
||||
name = item.data.category
|
||||
}
|
||||
result.push({ ...item, name, value })
|
||||
})
|
||||
head.data.dynamicTooltipValue?.forEach(item => {
|
||||
const formatter = formatterMap[item.fieldId]
|
||||
if (formatter) {
|
||||
const value = valueFormatter(parseFloat(item.value), formatter.formatterCfg)
|
||||
const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
|
||||
result.push({ color: 'grey', name, value })
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
return {
|
||||
...options,
|
||||
tooltip
|
||||
}
|
||||
}
|
||||
|
||||
protected configLegend(chart: Chart, options: DualAxesOptions): DualAxesOptions {
|
||||
const o = super.configLegend(chart, options)
|
||||
if (o.legend) {
|
||||
const data = chart.data.data
|
||||
o.legend.itemName = {
|
||||
formatter: (text: string, item: any, index: number) => {
|
||||
let name = undefined
|
||||
if (index === 0 && text === 'value') {
|
||||
name = data[0]?.name
|
||||
} else if (index === 1 && text === 'valueExt') {
|
||||
name = data[1]?.name
|
||||
}
|
||||
if (name === undefined) {
|
||||
return text
|
||||
} else {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
protected setupOptions(chart: Chart, options: DualAxesOptions): DualAxesOptions {
|
||||
return flow(
|
||||
this.configTheme,
|
||||
this.configLabel,
|
||||
this.configTooltip,
|
||||
this.configBasicStyle,
|
||||
this.configCustomColors,
|
||||
this.configLegend,
|
||||
this.configXAxis,
|
||||
this.configYAxis,
|
||||
this.configSlider,
|
||||
this.configAnalyse,
|
||||
this.configEmptyDataStrategy
|
||||
)(chart, options)
|
||||
}
|
||||
|
||||
constructor(name = 'chart-mix') {
|
||||
super(name, DEFAULT_DATA)
|
||||
}
|
||||
}
|
@ -506,6 +506,98 @@ export function getYAxis(chart: Chart) {
|
||||
return axis
|
||||
}
|
||||
|
||||
export function getYAxisExt(chart: Chart) {
|
||||
let axis: Record<string, any> | boolean = {}
|
||||
const yAxis = parseJson(chart.customStyle).yAxisExt
|
||||
if (!yAxis.show) {
|
||||
return false
|
||||
}
|
||||
const title =
|
||||
yAxis.name && yAxis.name !== ''
|
||||
? {
|
||||
text: yAxis.name,
|
||||
style: {
|
||||
fill: yAxis.color,
|
||||
fontSize: yAxis.fontSize
|
||||
},
|
||||
spacing: 8
|
||||
}
|
||||
: null
|
||||
const grid = yAxis.splitLine.show
|
||||
? {
|
||||
line: {
|
||||
style: {
|
||||
stroke: yAxis.splitLine.lineStyle.color,
|
||||
lineWidth: yAxis.splitLine.lineStyle.width
|
||||
}
|
||||
}
|
||||
}
|
||||
: null
|
||||
const axisCfg = yAxis.axisLine ? yAxis.axisLine : DEFAULT_YAXIS_STYLE.axisLine
|
||||
const line = axisCfg.show
|
||||
? {
|
||||
style: {
|
||||
stroke: axisCfg.lineStyle.color,
|
||||
lineWidth: axisCfg.lineStyle.width
|
||||
}
|
||||
}
|
||||
: null
|
||||
const tickLine = axisCfg.show
|
||||
? {
|
||||
style: {
|
||||
stroke: axisCfg.lineStyle.color
|
||||
}
|
||||
}
|
||||
: null
|
||||
const rotate = yAxis.axisLabel.rotate
|
||||
let textAlign = 'end'
|
||||
let textBaseline = 'middle'
|
||||
if (yAxis.position === 'right') {
|
||||
textAlign = 'start'
|
||||
if (Math.abs(rotate) > 75) {
|
||||
textAlign = 'center'
|
||||
}
|
||||
if (rotate > 75) {
|
||||
textBaseline = 'bottom'
|
||||
}
|
||||
if (rotate < -75) {
|
||||
textBaseline = 'top'
|
||||
}
|
||||
}
|
||||
if (yAxis.position === 'left') {
|
||||
if (Math.abs(rotate) > 75) {
|
||||
textAlign = 'center'
|
||||
}
|
||||
if (rotate > 75) {
|
||||
textBaseline = 'top'
|
||||
}
|
||||
if (rotate < -75) {
|
||||
textBaseline = 'bottom'
|
||||
}
|
||||
}
|
||||
const label = yAxis.axisLabel.show
|
||||
? {
|
||||
rotate: (rotate * Math.PI) / 180,
|
||||
style: {
|
||||
fill: yAxis.axisLabel.color,
|
||||
fontSize: yAxis.axisLabel.fontSize,
|
||||
textBaseline,
|
||||
textAlign
|
||||
}
|
||||
}
|
||||
: null
|
||||
|
||||
axis = {
|
||||
position: yAxis.position,
|
||||
title,
|
||||
grid,
|
||||
label,
|
||||
line,
|
||||
tickLine
|
||||
}
|
||||
return axis
|
||||
}
|
||||
|
||||
export function getSlider(chart: Chart) {
|
||||
let cfg
|
||||
const senior = parseJson(chart.senior)
|
||||
|
Loading…
Reference in New Issue
Block a user