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