feat: 优化胶囊图,新增配置

This commit is contained in:
奔跑的面条 2022-10-28 11:13:52 +08:00
parent 505f119efa
commit f6af081806
9 changed files with 295 additions and 279 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,4 +1,4 @@
import { PublicConfigClass } from '@/packages/public' import { PublicConfigClass } from '@/packages/public'
import { CapsuleChartConfig } from './index' import { CapsuleChartConfig } from './index'
import { CreateComponentType } from '@/packages/index.d' import { CreateComponentType } from '@/packages/index.d'
import { chartInitConfig } from '@/settings/designSetting' import { chartInitConfig } from '@/settings/designSetting'
@ -6,18 +6,20 @@ import { chartInitConfig } from '@/settings/designSetting'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json' import dataJson from './data.json'
export const option = { export const option = {
dataset:dataJson, dataset: dataJson,
colors: ['#e062ae', '#fb7293', '#e690d1', '#32c5e9', '#96bfff'], colors: ['#c4ebad', '#6be6c1', '#a0a7e6', '#96dee8', '#3fb1e3' ],
unit: '', unit: '',
itemHeight:10, itemHeight: 10,
valueFontSize: 16,
paddingRight: 50,
paddingLeft: 50,
showValue: true showValue: true
} }
export default class Config extends PublicConfigClass implements CreateComponentType { export default class Config extends PublicConfigClass implements CreateComponentType {
public key: string = CapsuleChartConfig.key public key: string = CapsuleChartConfig.key
public attr = { ...chartInitConfig,w: 300, h: 200 ,zIndex: -1} public attr = { ...chartInitConfig, zIndex: -1 }
public chartConfig = cloneDeep(CapsuleChartConfig) public chartConfig = cloneDeep(CapsuleChartConfig)
public option = cloneDeep(option) public option = cloneDeep(option)
} }

View File

@ -0,0 +1,53 @@
<template>
<!-- Echarts 全局设置 -->
<global-setting :optionData="optionData"> </global-setting>
<!-- 胶囊柱图 -->
<collapse-item name="胶囊柱图" expanded>
<SettingItemBox name="布局">
<setting-item name="左侧边距">
<n-input-number v-model:value="optionData.paddingLeft" :min="10" :step="1" size="small"></n-input-number>
</setting-item>
<setting-item name="右侧边距">
<n-input-number v-model:value="optionData.paddingRight" :min="10" :step="1" size="small"></n-input-number>
</setting-item>
<setting-item name="每块高度(px)">
<n-input-number v-model:value="optionData.itemHeight" :min="0" :step="1" size="small"></n-input-number>
</setting-item>
</SettingItemBox>
<SettingItemBox name="文本">
<setting-item name="所有文字大小">
<n-input-number v-model:value="optionData.valueFontSize" :min="0" :step="1" size="small"></n-input-number>
</setting-item>
<setting-item name="单位">
<n-input v-model:value="optionData.unit" size="small"></n-input>
</setting-item>
<SettingItem>
<n-space>
<n-switch v-model:value="optionData.showValue" size="small"></n-switch>
<n-text>显示数值</n-text>
</n-space>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="颜色">
<setting-item v-for="(item, index) in optionData.colors" :key="index" :name="`颜色${index}`">
<n-color-picker v-model:value="optionData.colors[index]" size="small"></n-color-picker>
</setting-item>
</SettingItemBox>
</collapse-item>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
import { option } from './config'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option & GlobalThemeJsonType>,
required: true
}
})
</script>

View File

@ -3,7 +3,7 @@
"source": [ "source": [
{ "name": "厦门", "value": 20 }, { "name": "厦门", "value": 20 },
{ "name": "南阳", "value": 40 }, { "name": "南阳", "value": 40 },
{ "name": "背景", "value": 60 }, { "name": "北京", "value": 60 },
{ "name": "上海", "value": 80 }, { "name": "上海", "value": 80 },
{ "name": "新疆", "value": 100 } { "name": "新疆", "value": 100 }
] ]

View File

@ -7,8 +7,8 @@ export const CapsuleChartConfig: ConfigType = {
chartKey: 'VCapsuleChart', chartKey: 'VCapsuleChart',
conKey: 'VCCapsuleChart', conKey: 'VCCapsuleChart',
title: '胶囊柱图', title: '胶囊柱图',
category: ChatCategoryEnum.MORE, category: ChatCategoryEnum.BAR,
categoryName: ChatCategoryEnumName.MORE, categoryName: ChatCategoryEnumName.BAR,
package: PackagesCategoryEnum.CHARTS, package: PackagesCategoryEnum.CHARTS,
chartFrame: ChartFrameEnum.COMMON, chartFrame: ChartFrameEnum.COMMON,
image image

View File

@ -1,223 +1,228 @@
<script setup lang="ts"> <template>
import { onMounted, watch, reactive,PropType } from 'vue' <div
import merge from 'lodash/merge' v-if="state.mergedConfig"
import cloneDeep from 'lodash/cloneDeep' class="go-dv-capsule-chart"
import { useChartDataFetch } from '@/hooks' :style="{
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' fontSize: numberSizeHandle(state.mergedConfig.valueFontSize),
import config from './config' paddingLeft: numberSizeHandle(state.mergedConfig.paddingLeft),
paddingRight: numberSizeHandle(state.mergedConfig.paddingRight)
const props = defineProps({ }"
chartConfig: { >
type: Object as PropType<config>, <div class="label-column">
default: () => ({}) <div
} v-for="item in state.mergedConfig.dataset.source"
}) :key="item[state.mergedConfig.dataset.dimensions[0]]"
type DataProps = { :style="{ height: state.capsuleItemHeight, lineHeight: state.capsuleItemHeight }"
name: string | number >
value: string | number {{ item[state.mergedConfig.dataset.dimensions[0]] }}
[key: string]: string | number </div>
} <div class="laset">&nbsp;</div>
</div>
interface StateProps {
defaultConfig: { <div class="capsule-container">
dataset: { <div
dimensions: Array<string> v-for="(capsule, index) in state.capsuleLength"
source: Array<DataProps> :key="index"
} class="capsule-item"
colors: Array<string> :style="{ height: state.capsuleItemHeight }"
unit: string >
showValue: boolean <div
itemHeight: number class="capsule-item-column"
} :style="`width: ${capsule * 100}%; background-color: ${
mergedConfig: any state.mergedConfig.colors[index % state.mergedConfig.colors.length]
capsuleLength: Array<number> };height:calc(100% - ${2}px);`"
capsuleValue: Array<string | Object> >
labelData: Array<number> <div v-if="state.mergedConfig.showValue" class="capsule-item-value">
capsuleItemHeight: string {{ state.capsuleValue[index] }}
} </div>
</div>
const state = reactive<StateProps>({ </div>
defaultConfig: {
dataset: { dimensions: ['name', 'value'], source: [] }, <div class="unit-label">
colors: ['#37a2da', '#32c5e9', '#67e0e3', '#9fe6b8', '#ffdb5c', '#ff9f7f', '#fb7293'], <div v-for="(label, index) in state.labelData" :key="label + index">
unit: '', {{ label }}
showValue: false, </div>
itemHeight: 10 </div>
}, </div>
mergedConfig: null,
capsuleLength: [], <div v-if="state.mergedConfig.unit" class="unit-text">
capsuleValue: [], {{ state.mergedConfig.unit }}
labelData: [], </div>
capsuleItemHeight: '' </div>
}) </template>
watch( <script setup lang="ts">
() => props.chartConfig.option, import { onMounted, watch, reactive, PropType } from 'vue'
newVal => { import merge from 'lodash/merge'
calcData(newVal) import cloneDeep from 'lodash/cloneDeep'
}, import { useChartDataFetch } from '@/hooks'
{ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
deep: true import config, { option } from './config'
}
) type DataProps = {
name: string | number
function calcData(data:any) { value: string | number
mergeConfig(props.chartConfig.option) [key: string]: string | number
}
calcCapsuleLengthAndLabelData()
} interface StateProps {
defaultConfig: {
function mergeConfig(data:any) { dataset: {
state.mergedConfig = merge(cloneDeep(state.defaultConfig), data || {}) dimensions: Array<string>
} source: Array<DataProps>
}
function calcCapsuleLengthAndLabelData() { colors: Array<string>
const { source } = state.mergedConfig.dataset unit: string
if (!source.length) return showValue: boolean
itemHeight: number
state.capsuleItemHeight = handle(state.mergedConfig.itemHeight) valueFontSize: number
const capsuleValue = source.map((item: DataProps) => item[state.mergedConfig.dataset.dimensions[1]]) paddingLeft: number
paddingRight: number
const maxValue = Math.max(...capsuleValue) }
mergedConfig: any
state.capsuleValue = capsuleValue capsuleLength: Array<number>
capsuleValue: Array<string | Object>
state.capsuleLength = capsuleValue.map((v: any) => (maxValue ? v / maxValue : 0)) labelData: Array<number>
capsuleItemHeight: string
const oneFifth = maxValue / 5 }
const labelData = Array.from(new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth)))) const props = defineProps({
chartConfig: {
state.labelData = labelData type: Object as PropType<config>,
} default: () => ({})
const handle = (val: string | number) => { }
return val + 'px' })
}
onMounted(() => { const state = reactive<StateProps>({
calcData(props.chartConfig.option) defaultConfig: option,
}) mergedConfig: null,
capsuleLength: [],
// capsuleValue: [],
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { labelData: [],
calcData(newData) capsuleItemHeight: ''
}) })
</script>
<template> watch(
<div class="dv-capsule-chart"> () => props.chartConfig.option,
<template v-if="state.mergedConfig"> newVal => {
<div class="label-column"> calcData(newVal)
<div },
v-for="item in state.mergedConfig.dataset.source" {
:key="item[state.mergedConfig.dataset.dimensions[0]]" deep: true
:style="{ height: state.capsuleItemHeight, lineHeight: state.capsuleItemHeight }" }
> )
{{ item[state.mergedConfig.dataset.dimensions[0]] }}
</div> const calcData = (data: any) => {
<div class="laset">&nbsp;</div> mergeConfig(props.chartConfig.option)
</div>
calcCapsuleLengthAndLabelData()
<div class="capsule-container"> }
<div
v-for="(capsule, index) in state.capsuleLength" const mergeConfig = (data: any) => {
:key="index" state.mergedConfig = merge(cloneDeep(state.defaultConfig), data || {})
class="capsule-item" }
:style="{ height: state.capsuleItemHeight }"
> //
<div const calcCapsuleLengthAndLabelData = () => {
class="capsule-item-column" const { source } = state.mergedConfig.dataset
:style="`width: ${capsule * 100}%; background-color: ${ if (!source.length) return
state.mergedConfig.colors[index % state.mergedConfig.colors.length]
};height:calc(100% - ${2}px);`" state.capsuleItemHeight = numberSizeHandle(state.mergedConfig.itemHeight)
> const capsuleValue = source.map((item: DataProps) => item[state.mergedConfig.dataset.dimensions[1]])
<div v-if="state.mergedConfig.showValue" class="capsule-item-value">
{{ state.capsuleValue[index] }} const maxValue = Math.max(...capsuleValue)
</div>
</div> state.capsuleValue = capsuleValue
</div>
state.capsuleLength = capsuleValue.map((v: any) => (maxValue ? v / maxValue : 0))
<div class="unit-label">
<div v-for="(label, index) in state.labelData" :key="label + index"> const oneFifth = maxValue / 5
{{ label }}
</div> const labelData = Array.from(new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth))))
</div>
</div> state.labelData = labelData
}
<div v-if="state.mergedConfig.unit" class="unit-text">
{{ state.mergedConfig.unit }} const numberSizeHandle = (val: string | number) => {
</div> return val + 'px'
</template> }
</div>
</template> onMounted(() => {
calcData(props.chartConfig.option)
<style lang="scss" scoped> })
.dv-capsule-chart {
position: relative; //
display: flex; useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
flex-direction: row; calcData(newData)
box-sizing: border-box; })
padding: 10px 16px 10px 10px; </script>
color: #fff;
<style lang="scss" scoped>
.label-column { @include go('dv-capsule-chart') {
display: flex; position: relative;
flex-direction: column; display: flex;
justify-content: space-between; flex-direction: row;
box-sizing: border-box; box-sizing: border-box;
padding-right: 10px; padding: 20px;
text-align: right; padding-right: 50px;
font-size: 12px; color: #b9b8cc;
>div:not(:last-child){
margin: 5px 0; .label-column {
} display: flex;
flex-direction: column;
} justify-content: space-between;
box-sizing: border-box;
.capsule-container { padding-right: 10px;
flex: 1; text-align: right;
display: flex; > div:not(:last-child) {
flex-direction: column; margin: 5px 0;
justify-content: space-between; }
} }
.capsule-item { .capsule-container {
box-shadow: 0 0 3px #999; flex: 1;
height: 10px; display: flex;
margin: 5px 0px; flex-direction: column;
border-radius: 5px; justify-content: space-between;
}
.capsule-item-column {
position: relative; .capsule-item {
height: 8px; box-shadow: 0 0 3px #999;
margin-top: 1px; height: 10px;
border-radius: 5px; margin: 5px 0px;
transition: all 0.3s; border-radius: 5px;
display: flex;
justify-content: flex-end; .capsule-item-column {
align-items: center; position: relative;
height: 8px;
.capsule-item-value { margin-top: 1px;
font-size: 12px; border-radius: 5px;
transform: translateX(100%); transition: all 0.3s;
} display: flex;
} justify-content: flex-end;
} align-items: center;
.unit-label { .capsule-item-value {
height: 20px; padding-left: 10px;
font-size: 12px; transform: translateX(100%);
position: relative; }
display: flex; }
justify-content: space-between; }
align-items: center;
} .unit-label {
height: 20px;
.unit-text { position: relative;
text-align: right; display: flex;
display: flex; justify-content: space-between;
align-items: flex-end; align-items: center;
font-size: 12px; }
line-height: 20px;
margin-left: 10px; .unit-text {
} text-align: right;
} display: flex;
</style> align-items: flex-end;
line-height: 20px;
margin-left: 10px;
}
}
</style>

View File

@ -1,4 +1,5 @@
import { BarCommonConfig } from './BarCommon/index' import { BarCommonConfig } from './BarCommon/index'
import { BarCrossrangeConfig } from './BarCrossrange/index' import { BarCrossrangeConfig } from './BarCrossrange/index'
import { CapsuleChartConfig } from './CapsuleChart/index'
export default [BarCommonConfig, BarCrossrangeConfig] export default [BarCommonConfig, BarCrossrangeConfig, CapsuleChartConfig]

View File

@ -1,43 +0,0 @@
<template>
<!-- Echarts 全局设置 -->
<global-setting :optionData="optionData"> </global-setting>
<!-- 胶囊柱图 -->
<collapse-item :name="`胶囊柱图`" expanded>
<SettingItemBox name="指标">
<SettingItem name="显示数值">
<n-space>
<n-switch v-model:value="optionData.showValue" size="small"></n-switch>
</n-space>
</SettingItem>
<setting-item name="单位">
<n-input v-model:value="optionData.unit" size="small"></n-input>
</setting-item>
<setting-item name="每块高度(px)">
<n-input-number
v-model:value="optionData.itemHeight"
:min="0"
:step="1"
size="small"
placeholder="水球数值"
></n-input-number>
</setting-item>
</SettingItemBox>
</collapse-item>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
import { option } from './config'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option & GlobalThemeJsonType>,
required: true
}
})
</script>

View File

@ -4,7 +4,5 @@ import { FunnelConfig } from './Funnel/index'
import { HeatmapConfig } from './Heatmap/index' import { HeatmapConfig } from './Heatmap/index'
import { WaterPoloConfig } from './WaterPolo/index' import { WaterPoloConfig } from './WaterPolo/index'
import { TreeMapConfig } from './TreeMap/index' import { TreeMapConfig } from './TreeMap/index'
import { CapsuleChartConfig } from './CapsuleChart'
export default [ProcessConfig, RadarConfig, FunnelConfig, HeatmapConfig, WaterPoloConfig, TreeMapConfig]
export default [ProcessConfig, RadarConfig, FunnelConfig, HeatmapConfig, WaterPoloConfig, TreeMapConfig,CapsuleChartConfig]