123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- <template>
- <div
- v-loading="config.loading"
- element-loading-text="图表加载中"
- :element-loading-background="loadingBackground"
- style="width: 100%;height: 100%"
- class="bs-design-wrap bs-custom-component"
- :class="{'light-theme':customTheme === 'light','auto-theme':customTheme !=='light'}"
- >
- <div
- :id="chatId"
- style="width: 100%;height: 100%"
- />
- </div>
- </template>
- <script>
- import 'insert-css'
- import cloneDeep from 'lodash/cloneDeep'
- import linkageMixins from 'data-room-ui/js/mixins/linkageMixins'
- import commonMixins from 'data-room-ui/js/mixins/commonMixins'
- import { mapState, mapMutations } from 'vuex'
- import { settingToTheme } from 'data-room-ui/js/utils/themeFormatting'
- import _ from 'lodash'
- import * as echarts from 'echarts'
- import CloneDeep from 'lodash-es/cloneDeep'
- export default {
- name: 'EchartsCustomComponent',
- mixins: [commonMixins, linkageMixins],
- props: {
- config: {
- type: Object,
- default: () => ({})
- }
- },
- data () {
- return {
- chart: null,
- hasData: false
- }
- },
- computed: {
- ...mapState('bigScreen', {
- pageInfo: state => state.pageInfo,
- customTheme: state => state.pageInfo.pageConfig.customTheme,
- activeCode: state => state.activeCode
- }),
- chatId () {
- let prefix = 'chart_'
- if (this.$route.path === window?.BS_CONFIG?.routers?.previewUrl) {
- prefix = 'preview_chart_'
- }
- if (this.$route.path === window?.BS_CONFIG?.routers?.designUrl) {
- prefix = 'design_chart_'
- }
- if (this.$route.path === window?.BS_CONFIG?.routers?.pageListUrl) {
- prefix = 'management_chart_'
- }
- return prefix + this.config.code
- }
- },
- created () {
- },
- watch: {
- // 监听主题变化手动触发组件配置更新
- 'config.option.theme': {
- handler (val) {
- if (val) {
- this.changeStyle(this.config, true)
- }
- }
- }
- },
- mounted () {
- const dragSelect = document.querySelector('#' + this.chatId)
- const resizeObserver = new ResizeObserver(entries => {
- if (this.chart) {
- this.chart.resize()
- let config = this.observeChart(entries)
- config = this.seriesStyle(config)
- config.option && this.chart.setOption(config.option)
- }
- })
- resizeObserver.observe(dragSelect)
- },
- beforeDestroy () {
- if (this.chart) {
- this.chart.dispose()
- }
- },
- methods: {
- ...mapMutations('bigScreen', ['changeChartConfig', 'changeActiveItemConfig', 'changeChartLoading']),
- chartInit () {
- let config = this.config
- // key和code相等,说明是一进来刷新,调用list接口
- if (this.config.code === this.config.key || this.isPreview) {
- // 改变样式
- config = this.changeStyle(config)
- // 改变数据
- config.loading = true
- this.changeChartLoading(config)
- this.changeDataByCode(config).then((res) => {
- // 初始化图表
- config.loading = false
- this.changeChartLoading(config)
- this.newChart(res)
- }).catch(() => {
- })
- } else {
- config.loading = true
- this.changeChartLoading(config)
- // 否则说明是更新,这里的更新只指更新数据(改变样式时是直接调取changeStyle方法),因为更新数据会改变key,调用chart接口
- this.changeData(config).then((res) => {
- config.loading = false
- this.changeChartLoading(config)
- // 初始化图表
- this.newChart(res)
- })
- }
- },
- /**
- * 构造chart
- */
- newChart (config) {
- const chartDom = document.getElementById(this.chatId)
- this.chart = echarts.init(chartDom)
- config.option && this.chart.setOption(config.option)
- },
- /**
- * 控制底部阴影大小
- */
- observeChart (entries) {
- const width = entries[0].contentRect.width
- const height = entries[0].contentRect.height
- const option = this.config.option
- // 调整长方形的大小
- option.graphic.children[0].shape.width = width * 0.9
- // 调整多边形的大小
- option.graphic.children[1].shape.points = [[width / 10, -height / 6], [width - width / 6, -height / 6], [width * 0.9, 0], [0, 0]]
- return this.config
- },
- /**
- * 注册事件
- */
- registerEvent () {
- // 图表添加事件进行数据联动
- let formData = {}
- // eslint-disable-next-line no-unused-vars
- this.chart.on('tooltip:change', (...args) => {
- formData = {}
- formData = cloneDeep(args[0].data.items[0].data)
- })
- // eslint-disable-next-line no-unused-vars
- this.chart.on('plot:click', (...args) => {
- this.linkage(formData)
- })
- },
- // 将config.setting的配置转化为option里的配置,这里之所以将转化的方法提出来,是因为在改变维度指标和样式的时候都需要转化
- transformSettingToOption (config, type) {
- let option = null
- config.setting.forEach(set => {
- if (set.optionField) {
- const optionField = set.optionField.split('.')
- option = config.option
- // 判断是不是关于x轴的相关配置,x轴叠加了两层坐标轴,如果是x轴相关配置则作用于xAxis[0]
- if (optionField[0] === 'xAxis') {
- optionField.forEach((field, index) => {
- if (index === 0) {
- option = option.xAxis[0]
- } else if (index === optionField.length - 1) {
- // 数据配置时,必须有值才更新
- if ((set.tabName === type && type === 'data' && set.value) || (set.tabName === type && type === 'custom')) {
- option[field] = set.value
- }
- } else {
- option = option[field]
- }
- })
- } else if (optionField[0] === 'series') {
- let changeObject = []
- let beforeChange = []
- // 如果要配置数据标签相关信息
- optionField.forEach((field, index) => {
- if (index === 0) {
- option = option[field]
- } else if (index === 1) {
- // 筛选出需要修改的series对象
- changeObject = option.filter(item => item.id.includes(field))
- beforeChange = [...changeObject]
- option = option.filter(item => !(item.id.includes(field)))
- } else if (index === optionField.length - 1) {
- if ((set.tabName === type && type === 'data' && set.value) || (set.tabName === type && type === 'custom')) {
- changeObject.map(item => {
- item[field] = set.value
- })
- }
- } else {
- const changeResult = []
- changeObject.forEach(item => {
- const result = { ...item[field] }
- changeResult.push(result)
- })
- changeObject = [...changeResult]
- }
- })
- // 合并修改后的series对象
- changeObject.forEach(
- (item, index) => {
- beforeChange[index].label = _.cloneDeep(item)
- option.push(beforeChange[index])
- }
- )
- } else if (optionField[0] === 'graphic') {
- // 配置底部阴影颜色
- option.graphic.children.forEach(item => {
- item.style.fill = set.value
- })
- } else {
- optionField.forEach((field, index) => {
- if (index === optionField.length - 1) {
- // 数据配置时,必须有值才更新
- if ((set.tabName === type && type === 'data' && set.value) || (set.tabName === type && type === 'custom')) {
- option[field] = set.value
- }
- } else {
- option = option[field]
- }
- })
- }
- }
- })
- return config
- },
- dataFormatting (config, data) {
- // config = this.config
- // 数据返回成功则赋值
- if (data.success) {
- data = data.data
- // 获取到后端返回的数据,有则赋值
- const option = config.option
- const setting = config.setting
- if (config.dataHandler) {
- try {
- // 此处函数处理data
- eval(config.dataHandler)
- } catch (e) {
- console.error(e)
- }
- }
- config.option = this.echartsOptionFormatting(config, data)
- config = this.transformSettingToOption(config, 'data')
- } else {
- // 数据返回失败则赋前端的模拟数据
- // config.option.data = this.plotList?.find(plot => plot.name === config.name)?.option?.data || config?.option?.data
- }
- config = this.seriesStyle(config)
- return config
- },
- getxDataAndYData (xField, yField, data) {
- let xData = []
- let yData = []
- // 获取所有x轴的分类
- data.forEach(item => {
- xData.push(item[xField])
- })
- xData = [...new Set(xData)]
- xData.forEach(x => {
- let max = 0
- data.forEach(item => {
- if (item[xField] === x) {
- max = item[yField] > max ? item[yField] : max
- }
- })
- yData.push(max)
- })
- return { xData, yData }
- },
- // 格式化echarts的配置
- echartsOptionFormatting (config, data) {
- const option = config.option
- // 分组字段
- const xField = config.setting.find(item => item.optionField === 'xField')?.value
- const yField = config.setting.find(item => item.optionField === 'yField')?.value
- // 判断是否存在分组
- const hasSeries = config.setting.find(item => item.optionField === 'seriesField' && item.value !== '')
- const { xData, yData } = this.getxDataAndYData(xField, yField, data)
- const maxY = Math.max(...yData) + Math.max(...yData) * 0.2
- // 生成阴影柱子的值
- const shadowData = Array.from({ length: xData.length }, () => maxY)
- option.xAxis = option.xAxis.map(item => {
- return {
- ...item,
- data: xData
- }
- })
- // 存在分组字段
- if (hasSeries) {
- const seriesField = config.setting.find(item => item.optionField === 'seriesField')?.value
- const seriesFieldList = [...new Set(data.map(item => item[seriesField]))]
- option.series = []
- const barWidth = option.seriesCustom.barWidth
- // 获取数据标签展示情况
- let labelShow = 0
- let labelPosition = 'inside'
- let labelColor = '#fff'
- let labelSize = 12
- config.setting.forEach(set => {
- if (set.field === 'series_barColor_label_show') {
- labelShow = set.value
- } else if (set.field === 'series_barColor_label_position') {
- labelPosition = set.value
- } else if (set.field === 'series_barColor_label_color') {
- labelColor = set.value
- } else if (set.field === 'series_barColor_label_fontSize') {
- labelSize = set.value
- }
- })
- // 偏移量数组
- const offsetArr = []
- let index = 0
- if (seriesFieldList.length % 2 === 0) {
- const length = seriesFieldList.length / 2
- for (let i = 0; i < length; i++) {
- const offsetX = (parseInt('10%') + parseInt('50%')) * (2 * i + 1)
- offsetArr.push(offsetX)
- offsetArr.unshift(-offsetX)
- }
- } else {
- const length = Math.ceil(seriesFieldList.length / 2)
- for (let i = 0; i < length; i++) {
- if (i === 0) {
- offsetArr.push(0)
- } else {
- const offsetX = (parseInt('20%') + parseInt('100%')) * i
- offsetArr.push(offsetX)
- offsetArr.unshift(-offsetX)
- }
- }
- }
- for (const seriesFieldItem of seriesFieldList) {
- const seriesData = (data.filter(item => item[seriesField] === seriesFieldItem))?.map(item => item[yField])
- const seriesItem = [
- {
- id: 'barTopColor' + seriesFieldItem,
- type: 'pictorialBar',
- tooltip: { show: false },
- symbol: 'diamond',
- symbolSize: [barWidth, barWidth / 2],
- symbolOffset: [offsetArr[index] + '%', -barWidth / 4],
- symbolPosition: 'end',
- z: 15,
- zlevel: 2,
- color: '#ffff33',
- data: seriesData
- },
- {
- id: 'barColor' + seriesFieldItem,
- type: 'bar',
- barGap: '20%',
- barWidth: barWidth,
- color: '#115ba6',
- label: {
- fontStyle: 'normal',
- show: labelShow,
- position: labelPosition,
- color: labelColor,
- fontSize: labelSize
- },
- zlevel: 2,
- z: 12,
- data: seriesData
- },
- {
- id: 'barBottomColor' + seriesFieldItem,
- type: 'pictorialBar',
- tooltip: { show: false },
- symbol: 'diamond',
- symbolSize: [barWidth, barWidth / 2],
- symbolOffset: [offsetArr[index] + '%', barWidth / 4],
- zlevel: 2,
- z: 15,
- color: 'rgb(2, 192, 255)',
- data: seriesData
- },
- {
- id: 'shadowColor' + seriesFieldItem,
- type: 'bar',
- tooltip: { show: false },
- xAxisIndex: 1,
- barGap: '20%',
- data: shadowData,
- zlevel: 1,
- barWidth: barWidth,
- color: 'rgba(9, 44, 76,.8)'
- },
- {
- id: 'shadowTopColor' + seriesFieldItem,
- type: 'pictorialBar',
- tooltip: { show: false },
- symbol: 'diamond',
- symbolSize: [barWidth, barWidth / 2],
- symbolOffset: [offsetArr[index] + '%', -barWidth / 4],
- symbolPosition: 'end',
- z: 15,
- color: 'rgb(15, 69, 133)',
- zlevel: 1,
- data: shadowData
- }
- ]
- index++
- option.series.push(...seriesItem)
- }
- } else {
- // 没有分组,不需要修改配置,直接修改series中每个对象的series
- option.series.forEach(item => {
- if (item.id.includes('shadow')) {
- item.data = shadowData
- } else {
- item.data = yData
- }
- })
- }
- return option
- },
- // 对series里面的样式进行配置
- seriesStyle (config) {
- const _config = CloneDeep(config)
- const seriesCustom = _config.option.seriesCustom
- const ids = Object.keys(config.option.seriesCustom)
- // 判断是否是分组柱状图
- const isGroup = _config.option.series.length !== 5
- // 宽度配置
- _config.option.series.forEach(item => {
- if (item.type === 'pictorialBar') {
- item.symbolSize = [seriesCustom.barWidth, seriesCustom.barWidth / 2]
- } else if (item.type === 'bar') {
- item.barWidth = seriesCustom.barWidth
- }
- })
- // 如果是基础柱状图
- if (!isGroup) {
- _config.option.series.forEach(item => {
- // 配置颜色
- if (ids.includes(item.id)) {
- item.color = seriesCustom[item.id]
- }
- })
- } else {
- // 如果是分组柱状图
- ids.forEach(id => {
- if (id !== 'barWidth') {
- let index = 0
- _config.option.series.forEach(item => {
- if (item.id.includes(id)) {
- item.color = _config.option.seriesCustom[id][index]
- index++
- }
- })
- }
- })
- }
- return _config
- },
- // 组件的样式改变,返回改变后的config
- changeStyle (config, isUpdateTheme) {
- config = { ...this.config, ...config }
- config = this.transformSettingToOption(config, 'custom')
- // 这里定义了option和setting是为了保证在执行eval时,optionHandler、dataHandler里面可能会用到,
- const option = config.option
- const setting = config.setting
- if (this.config.optionHandler) {
- try {
- // 此处函数处理config
- eval(this.config.optionHandler)
- } catch (e) {
- console.error(e)
- }
- }
- // 此时,setting中的部分变量映射到了option.seriesCustom中,但未映射到series的具体配置中
- config = this.seriesStyle(config)
- // 只有样式改变时更新主题配置,切换主题时不需要保存
- if (!isUpdateTheme) {
- config.theme = settingToTheme(_.cloneDeep(config), this.customTheme)
- }
- this.changeChartConfig(config)
- if (config.code === this.activeCode) {
- this.changeActiveItemConfig(config)
- }
- if (this.chart) {
- this.chart.setOption(config.option)
- }
- return config
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- @import '../assets/style/echartStyle';
- .light-theme{
- background-color: #FFFFFF;
- color: #000000;
- }
- .auto-theme{
- background-color: transparent;
- }
- </style>
|