Merge pull request #13466 from dataease/pr@dev-v2@chart-line-add-condition-style

feat(图表): 折线图支持条件样式
This commit is contained in:
jianneng-fit2cloud 2024-11-21 16:05:30 +08:00 committed by GitHub
commit 3563dcf625
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 797 additions and 8 deletions

View File

@ -147,6 +147,10 @@ declare interface ChartThreshold {
* 文本卡阈值
*/
textLabelThreshold: Threshold[]
/**
* 折线阈值
*/
lineThreshold: TableThreshold[]
}
declare interface TableThreshold {
/**

View File

@ -7,6 +7,7 @@ import { DEFAULT_THRESHOLD } from '@/views/chart/components/editor/util/chart'
import TableThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/TableThresholdEdit.vue'
import TextLabelThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/TextLabelThresholdEdit.vue'
import TextThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/TextThresholdEdit.vue'
import LineThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/LineThresholdEdit.vue'
import { fieldType } from '@/utils/attr'
import { defaultsDeep } from 'lodash-es'
import { iconFieldMap } from '@/components/icon-group/field-list'
@ -50,7 +51,9 @@ const state = reactive({
editLabelThresholdDialog: false,
thresholdArr: [],
editTableThresholdDialog: false,
tableThresholdArr: []
tableThresholdArr: [],
editLineThresholdDialog: false,
lineThresholdArr: []
})
const init = () => {
@ -63,6 +66,7 @@ const init = () => {
state.textThresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.textLabelThreshold))
state.thresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.labelThreshold))
state.tableThresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.tableThreshold))
state.lineThresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.lineThreshold ?? []))
}
}
const changeThreshold = () => {
@ -257,6 +261,77 @@ const changeTableThreshold = () => {
changeThreshold()
closeTableThreshold()
}
const lineThresholdChange = val => {
state.lineThresholdArr = val
}
const editLineThreshold = () => {
state.editLineThresholdDialog = true
}
const closeLineThreshold = () => {
state.editLineThresholdDialog = false
}
const changeLineThreshold = () => {
// check line config
for (let i = 0; i < state.lineThresholdArr?.length; i++) {
const field = state.lineThresholdArr[i]
if (!field.fieldId) {
ElMessage.error(t('chart.field_can_not_empty'))
return
}
if (!field.conditions || field.conditions.length === 0) {
ElMessage.error(t('chart.conditions_can_not_empty'))
return
}
for (let j = 0; j < field.conditions.length; j++) {
const ele = field.conditions[j]
if (!ele.term || ele.term === '') {
ElMessage.error(t('chart.exp_can_not_empty'))
return
}
if (ele.term === 'between') {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
(ele.min === '' || ele.max === '')
) {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
(parseFloat(ele.min).toString() === 'NaN' || parseFloat(ele.max).toString() === 'NaN')
) {
ElMessage.error(t('chart.value_error'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.min) > parseFloat(ele.max)
) {
ElMessage.error(t('chart.value_min_max_invalid'))
return
}
} else {
if (!ele.term.includes('null') && !ele.term.includes('empty') && ele.value === '') {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.value).toString() === 'NaN'
) {
ElMessage.error(t('chart.value_error'))
return
}
}
}
}
state.thresholdForm.lineThreshold = JSON.parse(JSON.stringify(state.lineThresholdArr ?? []))
changeThreshold()
closeLineThreshold()
}
const getFieldName = field => (field.chartShowName ? field.chartShowName : field.name)
const getDynamicStyleLabel = (item, fieldObj) => {
@ -713,6 +788,129 @@ init()
</div>
</el-col>
</el-col>
<!--折线-->
<el-col v-show="showProperty('lineThreshold')">
<el-col>
<div class="inner-container">
<span class="label" :class="'label-' + props.themes">条件样式设置</span>
<span class="right-btns">
<span
class="set-text-info"
:class="{ 'set-text-info-dark': themes === 'dark' }"
v-if="state.thresholdForm?.tableThreshold?.length > 0"
>
已设置
</span>
<el-button
:title="t('chart.edit')"
:class="'label-' + props.themes"
:style="{ width: '24px', marginLeft: '6px' }"
:disabled="!state.thresholdForm.enable"
class="circle-button"
text
size="small"
@click="editLineThreshold"
>
<template #icon>
<el-icon size="14px">
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
</el-icon>
</template>
</el-button>
</span>
</div>
<div
class="threshold-container"
:class="{ 'threshold-container-dark': themes === 'dark' }"
v-if="state.thresholdForm.lineThreshold?.length > 0"
>
<el-row
v-for="(fieldItem, fieldIndex) in state.thresholdForm.lineThreshold"
:key="fieldIndex"
style="flex-direction: column"
>
<div class="field-style" :class="{ 'field-style-dark': themes === 'dark' }">
<el-icon>
<Icon :className="`field-icon-${fieldType[fieldItem.field.deType]}`"
><component
class="svg-icon"
:class="`field-icon-${fieldType[fieldItem.field.deType]}`"
:is="iconFieldMap[fieldType[fieldItem.field.deType]]"
></component
></Icon>
</el-icon>
<span :title="fieldItem.field.name" class="field-text">{{
fieldItem.field.name
}}</span>
</div>
<div v-for="(item, index) in fieldItem.conditions" :key="index" class="line-style">
<div style="flex: 1">
<span v-if="item.term === 'lt'" :title="t('chart.filter_lt')">
{{ t('chart.filter_lt') }}
</span>
<span v-else-if="item.term === 'gt'" :title="t('chart.filter_gt')">
{{ t('chart.filter_gt') }}
</span>
<span v-else-if="item.term === 'le'" :title="t('chart.filter_le')">
{{ t('chart.filter_le') }}
</span>
<span v-else-if="item.term === 'ge'" :title="t('chart.filter_ge')">
{{ t('chart.filter_ge') }}
</span>
<span v-else-if="item.term === 'between'" :title="t('chart.filter_between')">
{{ t('chart.filter_between') }}
</span>
<span v-else-if="item.term === 'default'" title="默认"> 默认 </span>
</div>
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.fix') }}
</span>
</div>
<div v-else style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.dynamic') }}
</span>
</div>
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span
v-if="
!item.term.includes('null') &&
!item.term.includes('default') &&
!item.term.includes('empty') &&
item.term !== 'between'
"
:title="item.value + ''"
>{{ item.value }}</span
>
<span
v-else-if="
!item.term.includes('null') &&
!item.term.includes('empty') &&
item.term === 'between'
"
:title="item.min + ' ≤= ' + t('chart.drag_block_label_value') + ' ≤ ' + item.max"
>
{{ item.min }}&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;{{ item.max }}
</span>
<span v-else>&nbsp;</span>
</div>
<template v-if="chart.type !== 'picture-group'">
<div
:title="t('chart.color')"
:style="{
backgroundColor: item.color
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
</template>
</div>
</el-row>
</div>
</el-col>
</el-col>
<!--编辑文本卡阈值-->
<el-dialog
@ -794,6 +992,30 @@ init()
</div>
</template>
</el-dialog>
<!--编辑折线阈值-->
<el-dialog
v-if="state.editLineThresholdDialog"
v-model="state.editLineThresholdDialog"
:title="t('chart.threshold')"
:visible="state.editLineThresholdDialog"
width="1050px"
class="dialog-css"
append-to-body
>
<line-threshold-edit
:threshold="state.thresholdForm.lineThreshold"
:chart="chart"
@onLineThresholdChange="lineThresholdChange"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeLineThreshold">{{ t('chart.cancel') }}</el-button>
<el-button type="primary" @click="changeLineThreshold">{{
t('chart.confirm')
}}</el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@ -0,0 +1,499 @@
<script lang="tsx" setup>
import icon_info_filled from '@/assets/svg/icon_info_filled.svg'
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
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'
const { t } = useI18n()
const props = defineProps({
chart: {
type: Object as PropType<ChartObj>,
required: true
},
threshold: {
type: Array,
required: true,
default: () => []
}
})
const emit = defineEmits(['onLineThresholdChange'])
const thresholdCondition = {
term: 'lt',
field: '0',
value: '0',
color: '#ff0000ff',
backgroundColor: '#ffffff00',
min: '0',
max: '1',
type: 'fixed'
}
const valueOptions = [
{
label: '',
options: [
{
value: 'lt',
label: t('chart.filter_lt')
},
{
value: 'gt',
label: t('chart.filter_gt')
}
]
},
{
label: '',
options: [
{
value: 'between',
label: t('chart.filter_between')
}
]
}
]
const predefineColors = COLOR_PANEL
const state = reactive({
thresholdArr: [] as LineThreshold[],
fields: [],
thresholdObj: {
fieldId: '',
field: {},
conditions: []
} as LineThreshold
})
const init = () => {
state.thresholdArr = JSON.parse(JSON.stringify(props.threshold)) as LineThreshold[]
initFields()
}
const initOptions = (item, fieldObj) => {
if (fieldObj) {
item.options = JSON.parse(JSON.stringify(valueOptions))
item.conditions &&
item.conditions.forEach(ele => {
ele.term = ''
})
}
}
const initFields = () => {
let fields = []
const yAxis = JSON.parse(JSON.stringify(props.chart.yAxis))
fields = [...yAxis]
state.fields.splice(0, state.fields.length, ...fields)
//
let change = false
state.thresholdArr.forEach(item => {
const fieldItemObj = state.fields.filter(ele => ele.id === item.fieldId)
if (fieldItemObj.length === 0) {
change = true
item.fieldId = null
}
})
if (change) {
changeThreshold()
}
}
const addThreshold = () => {
state.thresholdArr.push(JSON.parse(JSON.stringify(state.thresholdObj)))
changeThreshold()
}
const removeThreshold = index => {
state.thresholdArr.splice(index, 1)
changeThreshold()
}
const changeThreshold = () => {
emit('onLineThresholdChange', state.thresholdArr)
}
const addConditions = item => {
const newCondition = JSON.parse(JSON.stringify(thresholdCondition))
item.conditions.push(newCondition)
changeThreshold()
}
const removeCondition = (item, index) => {
item.conditions.splice(index, 1)
changeThreshold()
}
const addField = item => {
// get field
if (state.fields && state.fields.length > 0) {
state.fields.forEach(ele => {
if (item.fieldId === ele.id) {
item.field = JSON.parse(JSON.stringify(ele))
initOptions(item, item.field)
}
})
}
changeThreshold()
}
const fieldOptions = [{ label: t('chart.field_fixed'), value: 'fixed' }]
const isNotEmptyAndNull = item => {
return !item.term.includes('null') && !item.term.includes('empty')
}
const isBetween = item => {
return item.term === 'between'
}
const isDynamic = item => {
return item.type === 'dynamic'
}
const getFieldOptions = fieldItem => {
const deType = state.fields.filter(ele => ele.id === fieldItem.fieldId)?.[0]?.deType
if (deType === 1) {
return fieldOptions.filter(ele => ele.value === 'fixed')
} else {
return fieldOptions
}
}
init()
</script>
<template>
<el-col>
<div class="tip">
<Icon name="icon_info_filled" class="icon-style"
><icon_info_filled class="svg-icon icon-style"
/></Icon>
<span style="padding-left: 10px">{{ t('chart.table_threshold_tip') }}</span>
</div>
<div @keydown.stop @keyup.stop style="max-height: 50vh; overflow-y: auto">
<div
v-for="(fieldItem, fieldIndex) in state.thresholdArr"
:key="fieldIndex"
class="field-item"
>
<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-option
class="series-select-option"
v-for="fieldOption in state.fields"
:key="fieldOption.id"
:label="fieldOption.name"
:value="fieldOption.id"
:disabled="chart.type === 'table-info' && fieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(fieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[fieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ fieldOption.name }}
</el-option>
</el-select>
</el-form-item>
<el-button
class="circle-button m-icon-btn"
text
:style="{ float: 'right' }"
@click="removeThreshold(fieldIndex)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</el-row>
<el-row :style="{ marginTop: '16px', borderTop: '1px solid #d5d6d8' }">
<el-row
v-for="(item, index) in fieldItem.conditions"
:key="index"
class="line-item"
:gutter="12"
>
<el-col :span="4">
<el-form-item class="form-item">
<el-select v-model="item.term" @change="changeThreshold">
<el-option-group
v-for="(group, idx) in fieldItem.options"
:key="idx"
:label="group.label"
>
<el-option
v-for="opt in group.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-option-group>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4" v-if="isNotEmptyAndNull(item)" style="padding-left: 0 !important">
<el-form-item class="form-item">
<el-select v-model="item.type" class="select-item" style="width: 100%">
<el-option
v-for="opt in getFieldOptions(fieldItem)"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
</el-col>
<!--不是between 不是动态值-->
<el-col
v-if="isNotEmptyAndNull(item) && !isBetween(item) && !isDynamic(item)"
:span="12"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.value"
v-if="[2, 3].includes(fieldItem.field.deType)"
:placeholder="t('chart.drag_block_label_value')"
controls-position="right"
class="value-item"
clearable
@change="changeThreshold"
/>
<el-input
v-model="item.value"
v-else
:placeholder="t('chart.drag_block_label_value')"
controls-position="right"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<!--between 开始值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.min"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_min')"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<el-col
v-if="isBetween(item) && !isDynamic(item)"
:span="2"
style="margin-top: 4px; text-align: center"
>
<span style="margin: 0 -5px">
&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;
</span>
</el-col>
<!--between 结束值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.max"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_max')"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item class="form-item" :label="t('chart.textColor')">
<el-color-picker
is-custom
size="large"
v-model="item.color"
show-alpha
class="color-picker-style"
:predefine="predefineColors"
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<el-col :span="1">
<div style="display: flex; align-items: center; justify-content: center">
<el-button
class="circle-button m-icon-btn"
text
@click="removeCondition(fieldItem, index)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-col>
</el-row>
</el-row>
<el-button
style="margin-top: 10px"
class="circle-button"
type="primary"
text
@click="addConditions(fieldItem)"
>
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('chart.add_style') }}
</el-button>
</div>
</div>
<el-button
class="circle-button"
text
type="primary"
style="margin-top: 10px"
@click="addThreshold"
>
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('chart.add_condition') }}
</el-button>
</el-col>
</template>
<style lang="less" scoped>
.field-item {
width: 100%;
border-radius: 4px;
padding: 10px 16px;
margin-top: 10px;
background: #f5f6f7;
}
.line-item {
width: 100%;
display: flex;
justify-content: left;
align-items: center;
margin-top: 16px;
}
.form-item {
height: 28px !important;
:deep(.el-form-item__label) {
font-size: 12px;
}
}
span {
font-size: 12px;
}
.value-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.between-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.select-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.el-select-dropdown__item {
padding: 0 20px;
font-size: 12px;
}
.color-picker-style {
cursor: pointer;
z-index: 1003;
width: 28px;
height: 28px;
}
.color-picker-style :deep(.el-color-picker__trigger) {
width: 28px;
height: 28px;
}
.color-title {
color: #646a73;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
padding: 0 8px;
}
.tip {
font-size: 12px;
background: #d6e2ff;
border-radius: 4px;
padding: 10px 20px;
display: flex;
align-items: center;
}
:deep(.ed-form-item) {
margin-bottom: 0 !important;
}
.icon-style {
width: 14px;
height: 14px;
color: var(--ed-color-primary);
}
.m-icon-btn {
&:hover {
background: rgba(31, 35, 41, 0.1) !important;
}
&:focus {
background: rgba(31, 35, 41, 0.1) !important;
}
&:active {
background: rgba(31, 35, 41, 0.2) !important;
}
}
.series-select-option {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 11px;
}
</style>

View File

@ -761,7 +761,8 @@ export const DEFAULT_THRESHOLD: ChartThreshold = {
liquidThreshold: '',
labelThreshold: [],
tableThreshold: [],
textLabelThreshold: []
textLabelThreshold: [],
lineLabelThreshold: []
}
export const DEFAULT_SCROLL: ScrollCfg = {
open: false,

View File

@ -281,7 +281,8 @@ export class Area extends G2PlotChartView<AreaOptions, G2Area> {
this.configXAxis,
this.configYAxis,
this.configSlider,
this.configAnalyse
this.configAnalyse,
this.configConditions
)(chart, options, {}, this)
}

View File

@ -11,7 +11,8 @@ export const LINE_EDITOR_PROPERTY: EditorProperty[] = [
'assist-line',
'function-cfg',
'jump-set',
'linkage'
'linkage',
'threshold'
]
export const LINE_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
'background-overall-component': ['all'],
@ -59,7 +60,8 @@ export const LINE_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
'fontShadow'
],
'legend-selector': ['icon', 'orient', 'fontSize', 'color', 'hPosition', 'vPosition'],
'function-cfg': ['slider', 'emptyDataStrategy']
'function-cfg': ['slider', 'emptyDataStrategy'],
threshold: ['lineThreshold']
}
export const LINE_AXIS_TYPE: AxisType[] = [

View File

@ -358,7 +358,8 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
this.configXAxis,
this.configYAxis,
this.configSlider,
this.configAnalyse
this.configAnalyse,
this.configConditions
)(chart, options)
}

View File

@ -1,4 +1,4 @@
import { hexColorToRGBA, parseJson } from '../../util'
import { hexColorToRGBA, isAlphaColor, isTransparent, parseJson } from '../../util'
import {
DEFAULT_BASIC_STYLE,
DEFAULT_XAXIS_STYLE,
@ -1328,3 +1328,53 @@ export const TOOLTIP_TPL =
'<span class="g2-tooltip-name">{name}</span>:' +
'<span class="g2-tooltip-value">{value}</span>' +
'</li>'
export function getConditions(chart: Chart) {
const { threshold } = parseJson(chart.senior)
const annotations = []
if (!threshold.enable) return annotations
const conditions = threshold.lineThreshold ?? []
for (const field of conditions) {
for (const t of field.conditions) {
if ([2, 3, 4].includes(field.field.deType)) {
const annotation = {
type: 'regionFilter',
start: ['start', 'median'],
end: ['end', 'min'],
color: t.color
}
// 加中线
const annotationLine = {
type: 'line',
start: ['start', t.value],
end: ['end', t.value],
style: {
stroke: t.color,
lineDash: [2, 2]
}
}
if (t.term === 'between') {
annotation.start = ['start', parseFloat(t.min)]
annotation.end = ['end', parseFloat(t.max)]
annotationLine.start = ['start', parseFloat(t.min)]
annotationLine.end = ['end', parseFloat(t.min)]
annotations.push(JSON.parse(JSON.stringify(annotationLine)))
annotationLine.start = ['start', parseFloat(t.max)]
annotationLine.end = ['end', parseFloat(t.max)]
annotations.push(annotationLine)
} else if (['lt', 'le'].includes(t.term)) {
annotation.start = ['start', t.value]
annotation.end = ['end', 'min']
annotations.push(annotationLine)
} else if (['gt', 'ge'].includes(t.term)) {
annotation.start = ['start', t.value]
annotation.end = ['end', 'max']
annotations.push(annotationLine)
}
annotations.push(annotation)
}
}
}
return annotations
}

View File

@ -10,7 +10,8 @@ import {
getTheme,
getTooltip,
getXAxis,
getYAxis
getYAxis,
getConditions
} from '@/views/chart/components/js/panel/common/common_antv'
import {
AntVAbstractChartView,
@ -171,6 +172,14 @@ export abstract class G2PlotChartView<
return undefined
}
protected configConditions(chart: Chart, options: O) {
const annotations = getConditions(chart)
return {
...options,
annotations: [...annotations, ...((options as unknown as Options).annotations || [])]
}
}
/**
* 流式配置公共参数处理常用的配置后续如果有其他通用配置也可以放进来需要单独配置的属性在各个图表自行实现
* @param chart 数据库图表对象