diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/3dPie.ts b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/3dPie.ts new file mode 100644 index 0000000..475166e --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/3dPie.ts @@ -0,0 +1,242 @@ +const heightProportion = 0.2 // 柱状扇形的高度比例 + +const colorList = [ + 'rgba(0, 81, 180, 0.5)', + 'rgba(255, 196, 0, 0.5)', + 'rgba(95, 144, 110, 0.5)', + 'rgba(144, 19, 254, 0.5)', + 'rgba(255, 105, 97, 0.5)', + 'rgba(255, 215, 0, 0.5)', + 'rgba(126, 211, 33, 0.5)', + 'rgba(255, 153, 153, 0.5)', + 'rgba(255, 204, 102, 0.5)', + 'rgba(153, 204, 255, 0.5)', + 'rgba(255, 153, 204, 0.5)', + 'rgba(204, 255, 153, 0.5)', + 'rgba(255, 204, 204, 0.5)', +] + +// 生成扇形的曲面参数方程,用于 series-surface.parametricEquation +function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, height) { + + // 计算 + let midRatio = (startRatio + endRatio) / 3; + + let startRadian = startRatio * Math.PI * 2; + let endRadian = endRatio * Math.PI * 2; + let midRadian = midRatio * Math.PI * 2; + + // 如果只有一个扇形,则不实现选中效果。 + if (startRatio === 0 && endRatio === 1) { + isSelected = false; + } + + // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3) + k = typeof k !== 'undefined' ? k : 1 / 3; + + // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0) + let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0; + let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0; + + // 计算高亮效果的放大比例(未高亮,则比例为 1) + let hoverRate = isHovered ? 1.1 : 1; + + // 返回曲面参数方程 + return { + u: { + min: -Math.PI, + max: Math.PI * 3, + step: Math.PI / 32 + }, + + v: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20 + }, + + x: function (u, v) { + if (u < startRadian) { + return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + y: function (u, v) { + if (u < startRadian) { + return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + z: function (u, v) { + if (u < -Math.PI * 0.5) { + return Math.sin(u); + } + if (u > Math.PI * 2.5) { + return Math.sin(u); + } + return Math.sin(v) > 0 ? heightProportion * height : -1; + } + }; +}; + +// 生成模拟 3D 饼图的配置项 +export function getPie3D(pieData, internalDiameterRatio) { + + let series = []; + let sumValue = 0; + let startValue = 0; + let endValue = 0; + let legendData = []; + let linesSeries = []; // line3D模拟label指示线 + let k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3; + + // 为每一个饼图数据,生成一个 series-surface 配置 + for (let i = 0; i < pieData.length; i++) { + + sumValue += pieData[i].value; + + let seriesItem = { + name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name, + type: 'surface', + parametric: true, + wireframe: { + show: false + }, + pieData: pieData[i], + pieStatus: { + selected: false, + hovered: false, + k: k + }, + itemStyle: { + color: colorList[i] + } + }; + + series.push(seriesItem); + } + + // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数, + // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。 + for (let i = 0; i < series.length; i++) { + endValue = startValue + series[i].pieData.value; + // console.log(series[i]); + series[i].pieData.startRatio = startValue / sumValue; + series[i].pieData.endRatio = endValue / sumValue; + series[i].parametricEquation = getParametricEquation(series[i].pieData.startRatio, + series[i].pieData.endRatio, + false, + false, + k, + series[i].pieData.value / sumValue * 100 + ); + + startValue = endValue; + + // 计算label指示线的起始和终点位置 + let midRadian = (series[i].pieData.endRatio + series[i].pieData.startRatio) * Math.PI; + let posX = Math.cos(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posY = Math.sin(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posZ = Math.log(Math.abs(series[i].pieData.value + 1)) * 0.1; + let flag = ((midRadian >= 0 && midRadian <= Math.PI / 2) || (midRadian >= 3 * Math.PI / 2 && midRadian <= Math.PI * 2)) ? 1 : -1; + let color = colorList[i]; + let turningPosArr = [posX * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posY * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posZ * (2)] + let endPosArr = [posX * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posY * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posZ * (6)] + + console.log('endPosArr',endPosArr); + linesSeries.push({ + type: 'line3D', + lineStyle: { + color: color, + }, + data: [[posX, posY, posZ], turningPosArr, endPosArr] + }, + { + type: 'scatter3D', + label: { + show: true, + distance: 0, + position: 'center', + textStyle: { + color: 'rgb(226,236,236)', + borderWidth: 2, + fontSize: 18, + padding: 10, + borderRadius: 4, + }, + formatter: '{b}' + }, + symbolSize: 0, + data: [{ name: series[i].pieData.value, value: endPosArr }] + }, + { + type: 'scatter3D', + label: { + show: true, + distance: 0, + position: 'center', + textStyle: { + color: 'rgb(158,158,158', + borderWidth: 2, + fontSize: 12, + padding: 10, + borderRadius: 4, + paddingTop:50, + }, + formatter: '{b}' + }, + symbolSize: 0, + data: [{ name: series[i].name, value: [endPosArr[0],endPosArr[1],endPosArr[2]*-1] }] + }); + + legendData.push(series[i].name); + } + + series = series.concat(linesSeries) + + // 最底下圆盘 + // 最底下圆盘 + series.push({ + name: 'mouseoutSeries', + type: 'surface', + parametric: true, + wireframe: { + show: false, + }, + itemStyle: { + opacity: 1, + color: 'rgba(25, 93, 176, 1)', + }, + parametricEquation: { + u: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20, + }, + v: { + min: 0, + max: Math.PI, + step: Math.PI / 20, + }, + x: function (u, v) { + return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2; + }, + y: function (u, v) { + return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2; + }, + z: function (u, v) { + return Math.cos(v) > 0 ? -0 : -1.5; + }, + }, + }); + + return series; +} \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/assets/title.svg b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/assets/title.svg new file mode 100644 index 0000000..873f2e3 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/assets/title.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/config.ts b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/config.ts new file mode 100644 index 0000000..7eeb9d7 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/config.ts @@ -0,0 +1,99 @@ +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' +import { ConsumptionProportionConfig } from './index' +import { CreateComponentType } from '@/packages/index.d' +import cloneDeep from 'lodash/cloneDeep' +import dataJson from './data.json' +import { getParametricEquation, getPie3D } from './3dPie' +import { chartInitConfig } from '@/settings/designSetting' +export const includes = ['legend'] + +// 其它配置 +const otherConfig = { + dateTime: { + selectValue: 'day', + dataset: [ + { + label: '当天', + value: 'day' + }, + { + label: '本周', + value: 'week' + }, + { + label: '当月', + value: 'month' + }, + { + label: '本季度', + value: 'quarter' + }, + { + label: '当年', + value: 'year' + } + ] + }, + +} + +let total = 0 +dataJson.source.forEach(item => { + total += item.value; +}) + +const series = getPie3D(dataJson.source, 0.8); + + +const option = { + ...otherConfig, + renderer: 'canvas', + backgroundColor: 'transparent', + legend: { + show:false, + }, + // color: ['rgba(255, 215, 0, 1)', 'rgba(74, 144, 226, 1)', 'rgba(80, 227, 194, 1)', 'rgba(126, 211, 33, 1)', 'rgba(144, 19, 254, 1)'], + dataset: { ...dataJson }, + labelLine: { + show: true, + lineStyle: { + color: '#7BC0CB', + }, + }, + label: { + show: false, + }, + + xAxis3D: { + min: -1.2, + max: 1.2, + }, + yAxis3D: { + min: -1.2, + max: 1.2, + }, + zAxis3D: { + min: -1.2, + max: 1.2, + }, + grid3D: { + show: false, + boxHeight: 6, + top: '20%', + viewControl: { + distance: 180, + alpha: 20, + beta: -60, + autoRotate: false, // 自动旋转 + }, + }, + + series: series, +} + +export default class Config extends PublicConfigClass implements CreateComponentType { + public key: string = ConsumptionProportionConfig.key + public chartConfig = cloneDeep(ConsumptionProportionConfig) + public option = echartOptionProfixHandle(option, includes) + public attr = { ...chartInitConfig, x: 0, y: 0, w: 420, h: 280, zIndex: 1 } +} \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/config.vue b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/config.vue new file mode 100644 index 0000000..8117ad9 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/config.vue @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/data.json b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/data.json new file mode 100644 index 0000000..f9a8305 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/data.json @@ -0,0 +1,18 @@ +{ + "dimensions": [ + "name", + "value", + "itemColor", + "borderColor" + ], + "source": [ + { + "name": "电", + "value": 423 + }, + { + "name": "燃气", + "value": 235 + } + ] +} \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/index.ts b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/index.ts new file mode 100644 index 0000000..da80ac8 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/index.ts @@ -0,0 +1,14 @@ +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' +import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d' + +export const ConsumptionProportionConfig: ConfigType = { + key: 'ConsumptionProportion', + chartKey: 'VConsumptionProportion', + conKey: 'VCConsumptionProportion', + title: '能耗占比', + category: ChatCategoryEnum.IntegratedEnergy, + categoryName: ChatCategoryEnumName.IntegratedEnergy, + package: PackagesCategoryEnum.CHARTS, + chartFrame: ChartFrameEnum.ECHARTS, + image: 'pie_center.png' +} diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/index.vue b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/index.vue new file mode 100644 index 0000000..aa5a93b --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/index.vue @@ -0,0 +1,126 @@ + + + + + 100.00 + /万元 + + + + + + + + + \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/select.vue b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/select.vue new file mode 100644 index 0000000..39e2284 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/select.vue @@ -0,0 +1,159 @@ + + + + {{ getSelectedLabel() }} + ▼ + + + + {{ item.label }} + + + + + + + + \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/test.js b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/test.js new file mode 100644 index 0000000..0a56abc --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/ConsumptionProportion/test.js @@ -0,0 +1,320 @@ +// 传入数据生成 option +const dataList = [ + { + name: '公务用车运行维护费', + val: 1230,//存储数据的地方 + itemStyle: { + color: 'rgba(0, 81, 180, 0.5)', + }, + }, + + { + name: '办公费', + val: 800,//存储数据的地方 + itemStyle: { + color: 'rgba(255, 196, 0, 0.5)', + }, + }, + { + name: '差旅费', + val: 500,//存储数据的地方 + itemStyle: { + color: 'rgba(95, 144, 110, 0.5)', + }, + }, +]; +const heightProportion = 0.2 // 柱状扇形的高度比例 + + +// 生成扇形的曲面参数方程,用于 series-surface.parametricEquation +function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, height) { + + // 计算 + let midRatio = (startRatio + endRatio) / 3; + + let startRadian = startRatio * Math.PI * 2; + let endRadian = endRatio * Math.PI * 2; + let midRadian = midRatio * Math.PI * 2; + + // 如果只有一个扇形,则不实现选中效果。 + if (startRatio === 0 && endRatio === 1) { + isSelected = false; + } + + // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3) + k = typeof k !== 'undefined' ? k : 1 / 3; + + // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0) + let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0; + let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0; + + // 计算高亮效果的放大比例(未高亮,则比例为 1) + let hoverRate = isHovered ? 1.1 : 1; + + // 返回曲面参数方程 + return { + u: { + min: -Math.PI, + max: Math.PI * 3, + step: Math.PI / 32 + }, + + v: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20 + }, + + x: function (u, v) { + if (u < startRadian) { + return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + y: function (u, v) { + if (u < startRadian) { + return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + z: function (u, v) { + if (u < -Math.PI * 0.5) { + return Math.sin(u); + } + if (u > Math.PI * 2.5) { + return Math.sin(u); + } + return Math.sin(v) > 0 ? heightProportion * height : -1; + } + }; +}; + +// 生成模拟 3D 饼图的配置项 +function getPie3D(pieData, internalDiameterRatio) { + + let series = []; + let sumValue = 0; + let startValue = 0; + let endValue = 0; + let legendData = []; + let linesSeries = []; // line3D模拟label指示线 + let k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3; + + // 为每一个饼图数据,生成一个 series-surface 配置 + for (let i = 0; i < pieData.length; i++) { + + sumValue += pieData[i].value; + + let seriesItem = { + name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name, + type: 'surface', + parametric: true, + wireframe: { + show: false + }, + pieData: pieData[i], + pieStatus: { + selected: false, + hovered: false, + k: k + } + }; + + if (typeof pieData[i].itemStyle != 'undefined') { + + let itemStyle = {}; + + typeof pieData[i].itemStyle.color != 'undefined' ? itemStyle.color = pieData[i].itemStyle.color : null; + typeof pieData[i].itemStyle.opacity != 'undefined' ? itemStyle.opacity = pieData[i].itemStyle.opacity : null; + + seriesItem.itemStyle = itemStyle; + } + series.push(seriesItem); + } + + // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数, + // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。 + for (let i = 0; i < series.length; i++) { + endValue = startValue + series[i].pieData.value; + // console.log(series[i]); + series[i].pieData.startRatio = startValue / sumValue; + series[i].pieData.endRatio = endValue / sumValue; + series[i].parametricEquation = getParametricEquation(series[i].pieData.startRatio, + series[i].pieData.endRatio, + false, + false, + k, + series[i].pieData.value + ); + + startValue = endValue; + + // 计算label指示线的起始和终点位置 + let midRadian = (series[i].pieData.endRatio + series[i].pieData.startRatio) * Math.PI; + let posX = Math.cos(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posY = Math.sin(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posZ = Math.log(Math.abs(series[i].pieData.value + 1)) * 0.1; + let flag = ((midRadian >= 0 && midRadian <= Math.PI / 2) || (midRadian >= 3 * Math.PI / 2 && midRadian <= Math.PI * 2)) ? 1 : -1; + let color = pieData[i].itemStyle.color; + let turningPosArr = [posX * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posY * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posZ * (2)] + let endPosArr = [posX * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posY * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posZ * (6)] + + linesSeries.push({ + type: 'line3D', + lineStyle: { + color: color, + }, + data: [[posX, posY, posZ], turningPosArr, endPosArr] + }, + { + type: 'scatter3D', + label: { + show: true, + distance: 0, + position: 'center', + textStyle: { + color: '#ffffff', + backgroundColor: color, + borderWidth: 2, + fontSize: 14, + padding: 10, + borderRadius: 4, + }, + formatter: '{b}' + }, + symbolSize: 0, + data: [{ name: series[i].name + '\n' + series[i].pieData.val, value: endPosArr }] + }); + + legendData.push(series[i].name); + } + series = series.concat(linesSeries) + + // 最底下圆盘 + series.push({ + name: 'mouseoutSeries', + type: 'surface', + parametric: true, + wireframe: { + show: false, + }, + itemStyle: { + opacity: 1, + color: 'rgba(25, 93, 176, 1)', + }, + parametricEquation: { + u: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20, + }, + v: { + min: 0, + max: Math.PI, + step: Math.PI / 20, + }, + x: function (u, v) { + return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2; + }, + y: function (u, v) { + return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2; + }, + z: function (u, v) { + return Math.cos(v) > 0 ? -0 : -1.5; + }, + }, + }); + return series; +} + +let total = 0 +dataList.forEach(item => { + total += item.val +}) +const series = getPie3D(dataList.map(item => { + item.value = Number((item.val / total * 100).toFixed(2)) + return item +}), 0.8, 240, 28, 26, 1); + +// 准备待返回的配置项,把准备好的 legendData、series 传入。 +option = { + legend: { + tooltip: { + show: true, + }, + data: dataList.map(item => item.name), + top: '5%', + left: '5%', + icon: 'circle', + textStyle: { + color: '#fff', + fontSize: 14, + }, + }, + animation: true, + title: [ + { + x: 'center', + top: '40%', + text: total, + textStyle: { + color: '#fff', + fontSize: 42, + fontWeight: 'bold' + }, + }, + { + x: 'center', + top: '48%', + text: '还款总额', + textStyle: { + color: '#fff', + fontSize: 22, + fontWeight: 400 + }, + }, + ], + backgroundColor: '#333', + labelLine: { + show: true, + lineStyle: { + color: '#7BC0CB', + }, + }, + label: { + show: false, + }, + xAxis3D: { + min: -1.5, + max: 1.5, + }, + yAxis3D: { + min: -1.5, + max: 1.5, + }, + zAxis3D: { + min: -1, + max: 1, + }, + grid3D: { + show: false, + boxHeight: 4, + bottom: '50%', + viewControl: { + distance: 180, + alpha: 25, + beta: 60, + autoRotate: true, // 自动旋转 + }, + }, + + series: series, +}; \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/3dPie.ts b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/3dPie.ts new file mode 100644 index 0000000..bad0d94 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/3dPie.ts @@ -0,0 +1,242 @@ +const heightProportion = 0.2 // 柱状扇形的高度比例 + +const colorList = [ + 'rgba(0, 81, 180, 0.5)', + 'rgba(255, 196, 0, 0.5)', + 'rgba(95, 144, 110, 0.5)', + 'rgba(144, 19, 254, 0.5)', + 'rgba(255, 105, 97, 0.5)', + 'rgba(255, 215, 0, 0.5)', + 'rgba(126, 211, 33, 0.5)', + 'rgba(255, 153, 153, 0.5)', + 'rgba(255, 204, 102, 0.5)', + 'rgba(153, 204, 255, 0.5)', + 'rgba(255, 153, 204, 0.5)', + 'rgba(204, 255, 153, 0.5)', + 'rgba(255, 204, 204, 0.5)', +] + +// 生成扇形的曲面参数方程,用于 series-surface.parametricEquation +function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, height) { + + // 计算 + let midRatio = (startRatio + endRatio) / 3; + + let startRadian = startRatio * Math.PI * 2; + let endRadian = endRatio * Math.PI * 2; + let midRadian = midRatio * Math.PI * 2; + + // 如果只有一个扇形,则不实现选中效果。 + if (startRatio === 0 && endRatio === 1) { + isSelected = false; + } + + // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3) + k = typeof k !== 'undefined' ? k : 1 / 3; + + // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0) + let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0; + let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0; + + // 计算高亮效果的放大比例(未高亮,则比例为 1) + let hoverRate = isHovered ? 1.1 : 1; + + // 返回曲面参数方程 + return { + u: { + min: -Math.PI, + max: Math.PI * 3, + step: Math.PI / 32 + }, + + v: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20 + }, + + x: function (u, v) { + if (u < startRadian) { + return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + y: function (u, v) { + if (u < startRadian) { + return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + z: function (u, v) { + if (u < -Math.PI * 0.5) { + return Math.sin(u); + } + if (u > Math.PI * 2.5) { + return Math.sin(u); + } + return Math.sin(v) > 0 ? heightProportion * height : -1; + } + }; +}; + +// 生成模拟 3D 饼图的配置项 +export function getPie3D(pieData, internalDiameterRatio) { + + let series = []; + let sumValue = 0; + let startValue = 0; + let endValue = 0; + let legendData = []; + let linesSeries = []; // line3D模拟label指示线 + let k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3; + + // 为每一个饼图数据,生成一个 series-surface 配置 + for (let i = 0; i < pieData.length; i++) { + + sumValue += pieData[i].value; + + let seriesItem = { + name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name, + type: 'surface', + parametric: true, + wireframe: { + show: false + }, + pieData: pieData[i], + pieStatus: { + selected: false, + hovered: false, + k: k + }, + itemStyle: { + color: colorList[i] + } + }; + + series.push(seriesItem); + } + + // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数, + // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。 + for (let i = 0; i < series.length; i++) { + endValue = startValue + series[i].pieData.value; + // console.log(series[i]); + series[i].pieData.startRatio = startValue / sumValue; + series[i].pieData.endRatio = endValue / sumValue; + series[i].parametricEquation = getParametricEquation(series[i].pieData.startRatio, + series[i].pieData.endRatio, + false, + false, + k, + series[i].pieData.value / sumValue * 100 + ); + + startValue = endValue; + + // 计算label指示线的起始和终点位置 + let midRadian = (series[i].pieData.endRatio + series[i].pieData.startRatio) * Math.PI; + let posX = Math.cos(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posY = Math.sin(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posZ = Math.log(Math.abs(series[i].pieData.value + 1)) * 0.1; + let flag = ((midRadian >= 0 && midRadian <= Math.PI / 2) || (midRadian >= 3 * Math.PI / 2 && midRadian <= Math.PI * 2)) ? 1 : -1; + let color = colorList[i]; + let turningPosArr = [posX * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0)*0.2, posY * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0)*0.2, posZ * (2)] + let endPosArr = [posX * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0)*0.2, posY * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0)*0.2, posZ * (6)] + + console.log('endPosArr',posX,posY,posZ,turningPosArr,endPosArr); + linesSeries.push({ + type: 'line3D', + lineStyle: { + color: color, + }, + data: [[posX, posY, posZ], turningPosArr, endPosArr] + }, + { + type: 'scatter3D', + label: { + show: true, + distance: 0, + position: 'center', + textStyle: { + color: 'rgb(226,236,236)', + borderWidth: 2, + fontSize: 18, + padding: 10, + borderRadius: 4, + }, + formatter: '{b}' + }, + symbolSize: 0, + data: [{ name: series[i].pieData.value, value: endPosArr }] + }, + { + type: 'scatter3D', + label: { + show: true, + distance: 0, + position: 'center', + textStyle: { + color: 'rgb(158,158,158', + borderWidth: 2, + fontSize: 12, + padding: 10, + borderRadius: 4, + paddingTop:50, + }, + formatter: '{b}' + }, + symbolSize: 0, + data: [{ name: series[i].name, value: [endPosArr[0],endPosArr[1],endPosArr[2]*-1] }] + }); + + legendData.push(series[i].name); + } + + series = series.concat(linesSeries) + + // 最底下圆盘 + // 最底下圆盘 + series.push({ + name: 'mouseoutSeries', + type: 'surface', + parametric: true, + wireframe: { + show: false, + }, + itemStyle: { + opacity: 1, + color: 'rgba(25, 93, 176, 1)', + }, + parametricEquation: { + u: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20, + }, + v: { + min: 0, + max: Math.PI, + step: Math.PI / 20, + }, + x: function (u, v) { + return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2; + }, + y: function (u, v) { + return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2; + }, + z: function (u, v) { + return Math.cos(v) > 0 ? -0 : -1.5; + }, + }, + }); + + return series; +} \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/assets/title.svg b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/assets/title.svg new file mode 100644 index 0000000..873f2e3 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/assets/title.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/config.ts b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/config.ts new file mode 100644 index 0000000..1e76a4a --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/config.ts @@ -0,0 +1,99 @@ +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' +import { FeeOverviewConfig } from './index' +import { CreateComponentType } from '@/packages/index.d' +import cloneDeep from 'lodash/cloneDeep' +import dataJson from './data.json' +import { getParametricEquation, getPie3D } from './3dPie' +import { chartInitConfig } from '@/settings/designSetting' +export const includes = ['legend'] + +// 其它配置 +const otherConfig = { + dateTime: { + selectValue: 'day', + dataset: [ + { + label: '当天', + value: 'day' + }, + { + label: '本周', + value: 'week' + }, + { + label: '当月', + value: 'month' + }, + { + label: '本季度', + value: 'quarter' + }, + { + label: '当年', + value: 'year' + } + ] + }, + +} + +let total = 0 +dataJson.source.forEach(item => { + total += item.value; +}) + +const series = getPie3D(dataJson.source, 0.8); + + +const option = { + ...otherConfig, + renderer: 'canvas', + backgroundColor: 'transparent', + legend: { + show:false, + }, + // color: ['rgba(255, 215, 0, 1)', 'rgba(74, 144, 226, 1)', 'rgba(80, 227, 194, 1)', 'rgba(126, 211, 33, 1)', 'rgba(144, 19, 254, 1)'], + dataset: { ...dataJson }, + labelLine: { + show: true, + lineStyle: { + color: '#7BC0CB', + }, + }, + label: { + show: false, + }, + + xAxis3D: { + min: -1.2, + max: 1.2, + }, + yAxis3D: { + min: -1.2, + max: 1.2, + }, + zAxis3D: { + min: -1.2, + max: 1.2, + }, + grid3D: { + show: false, + boxHeight: 6, + top: '15%', + viewControl: { + distance: 180, + alpha: 25, + beta: -80, + autoRotate: false, // 自动旋转 + }, + }, + + series: series, +} + +export default class Config extends PublicConfigClass implements CreateComponentType { + public key: string = FeeOverviewConfig.key + public chartConfig = cloneDeep(FeeOverviewConfig) + public option = echartOptionProfixHandle(option, includes) + public attr = { ...chartInitConfig, x: 0, y: 0, w: 420, h: 280, zIndex: 1 } +} \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/config.vue b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/config.vue new file mode 100644 index 0000000..8117ad9 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/config.vue @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/data.json b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/data.json new file mode 100644 index 0000000..acf87d5 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/data.json @@ -0,0 +1,22 @@ +{ + "dimensions": [ + "name", + "value", + "itemColor", + "borderColor" + ], + "source": [ + { + "name": "水", + "value": 450 + }, + { + "name": "燃气", + "value": 220 + }, + { + "name": "电", + "value": 200 + } + ] +} \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/index.ts b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/index.ts new file mode 100644 index 0000000..7290b85 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/index.ts @@ -0,0 +1,14 @@ +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' +import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d' + +export const FeeOverviewConfig: ConfigType = { + key: 'FeeOverview', + chartKey: 'VFeeOverview', + conKey: 'VCFeeOverview', + title: '费用概况', + category: ChatCategoryEnum.IntegratedEnergy, + categoryName: ChatCategoryEnumName.IntegratedEnergy, + package: PackagesCategoryEnum.CHARTS, + chartFrame: ChartFrameEnum.ECHARTS, + image: 'pie_center.png' +} diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/index.vue b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/index.vue new file mode 100644 index 0000000..aa5a93b --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/index.vue @@ -0,0 +1,126 @@ + + + + + 100.00 + /万元 + + + + + + + + + \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/select.vue b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/select.vue new file mode 100644 index 0000000..39e2284 --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/select.vue @@ -0,0 +1,159 @@ + + + + {{ getSelectedLabel() }} + ▼ + + + + {{ item.label }} + + + + + + + + \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/FeeOverview/test.js b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/test.js new file mode 100644 index 0000000..0a56abc --- /dev/null +++ b/src/packages/components/Charts/IntegratedEnergy/FeeOverview/test.js @@ -0,0 +1,320 @@ +// 传入数据生成 option +const dataList = [ + { + name: '公务用车运行维护费', + val: 1230,//存储数据的地方 + itemStyle: { + color: 'rgba(0, 81, 180, 0.5)', + }, + }, + + { + name: '办公费', + val: 800,//存储数据的地方 + itemStyle: { + color: 'rgba(255, 196, 0, 0.5)', + }, + }, + { + name: '差旅费', + val: 500,//存储数据的地方 + itemStyle: { + color: 'rgba(95, 144, 110, 0.5)', + }, + }, +]; +const heightProportion = 0.2 // 柱状扇形的高度比例 + + +// 生成扇形的曲面参数方程,用于 series-surface.parametricEquation +function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, height) { + + // 计算 + let midRatio = (startRatio + endRatio) / 3; + + let startRadian = startRatio * Math.PI * 2; + let endRadian = endRatio * Math.PI * 2; + let midRadian = midRatio * Math.PI * 2; + + // 如果只有一个扇形,则不实现选中效果。 + if (startRatio === 0 && endRatio === 1) { + isSelected = false; + } + + // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3) + k = typeof k !== 'undefined' ? k : 1 / 3; + + // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0) + let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0; + let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0; + + // 计算高亮效果的放大比例(未高亮,则比例为 1) + let hoverRate = isHovered ? 1.1 : 1; + + // 返回曲面参数方程 + return { + u: { + min: -Math.PI, + max: Math.PI * 3, + step: Math.PI / 32 + }, + + v: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20 + }, + + x: function (u, v) { + if (u < startRadian) { + return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + y: function (u, v) { + if (u < startRadian) { + return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + if (u > endRadian) { + return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate; + } + return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate; + }, + + z: function (u, v) { + if (u < -Math.PI * 0.5) { + return Math.sin(u); + } + if (u > Math.PI * 2.5) { + return Math.sin(u); + } + return Math.sin(v) > 0 ? heightProportion * height : -1; + } + }; +}; + +// 生成模拟 3D 饼图的配置项 +function getPie3D(pieData, internalDiameterRatio) { + + let series = []; + let sumValue = 0; + let startValue = 0; + let endValue = 0; + let legendData = []; + let linesSeries = []; // line3D模拟label指示线 + let k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3; + + // 为每一个饼图数据,生成一个 series-surface 配置 + for (let i = 0; i < pieData.length; i++) { + + sumValue += pieData[i].value; + + let seriesItem = { + name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name, + type: 'surface', + parametric: true, + wireframe: { + show: false + }, + pieData: pieData[i], + pieStatus: { + selected: false, + hovered: false, + k: k + } + }; + + if (typeof pieData[i].itemStyle != 'undefined') { + + let itemStyle = {}; + + typeof pieData[i].itemStyle.color != 'undefined' ? itemStyle.color = pieData[i].itemStyle.color : null; + typeof pieData[i].itemStyle.opacity != 'undefined' ? itemStyle.opacity = pieData[i].itemStyle.opacity : null; + + seriesItem.itemStyle = itemStyle; + } + series.push(seriesItem); + } + + // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数, + // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。 + for (let i = 0; i < series.length; i++) { + endValue = startValue + series[i].pieData.value; + // console.log(series[i]); + series[i].pieData.startRatio = startValue / sumValue; + series[i].pieData.endRatio = endValue / sumValue; + series[i].parametricEquation = getParametricEquation(series[i].pieData.startRatio, + series[i].pieData.endRatio, + false, + false, + k, + series[i].pieData.value + ); + + startValue = endValue; + + // 计算label指示线的起始和终点位置 + let midRadian = (series[i].pieData.endRatio + series[i].pieData.startRatio) * Math.PI; + let posX = Math.cos(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posY = Math.sin(midRadian) * (1 + Math.cos(Math.PI / 2)); + let posZ = Math.log(Math.abs(series[i].pieData.value + 1)) * 0.1; + let flag = ((midRadian >= 0 && midRadian <= Math.PI / 2) || (midRadian >= 3 * Math.PI / 2 && midRadian <= Math.PI * 2)) ? 1 : -1; + let color = pieData[i].itemStyle.color; + let turningPosArr = [posX * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posY * (1.8) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posZ * (2)] + let endPosArr = [posX * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posY * (1.9) + (i * 0.1 * flag) + (flag < 0 ? -0.5 : 0), posZ * (6)] + + linesSeries.push({ + type: 'line3D', + lineStyle: { + color: color, + }, + data: [[posX, posY, posZ], turningPosArr, endPosArr] + }, + { + type: 'scatter3D', + label: { + show: true, + distance: 0, + position: 'center', + textStyle: { + color: '#ffffff', + backgroundColor: color, + borderWidth: 2, + fontSize: 14, + padding: 10, + borderRadius: 4, + }, + formatter: '{b}' + }, + symbolSize: 0, + data: [{ name: series[i].name + '\n' + series[i].pieData.val, value: endPosArr }] + }); + + legendData.push(series[i].name); + } + series = series.concat(linesSeries) + + // 最底下圆盘 + series.push({ + name: 'mouseoutSeries', + type: 'surface', + parametric: true, + wireframe: { + show: false, + }, + itemStyle: { + opacity: 1, + color: 'rgba(25, 93, 176, 1)', + }, + parametricEquation: { + u: { + min: 0, + max: Math.PI * 2, + step: Math.PI / 20, + }, + v: { + min: 0, + max: Math.PI, + step: Math.PI / 20, + }, + x: function (u, v) { + return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2; + }, + y: function (u, v) { + return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2; + }, + z: function (u, v) { + return Math.cos(v) > 0 ? -0 : -1.5; + }, + }, + }); + return series; +} + +let total = 0 +dataList.forEach(item => { + total += item.val +}) +const series = getPie3D(dataList.map(item => { + item.value = Number((item.val / total * 100).toFixed(2)) + return item +}), 0.8, 240, 28, 26, 1); + +// 准备待返回的配置项,把准备好的 legendData、series 传入。 +option = { + legend: { + tooltip: { + show: true, + }, + data: dataList.map(item => item.name), + top: '5%', + left: '5%', + icon: 'circle', + textStyle: { + color: '#fff', + fontSize: 14, + }, + }, + animation: true, + title: [ + { + x: 'center', + top: '40%', + text: total, + textStyle: { + color: '#fff', + fontSize: 42, + fontWeight: 'bold' + }, + }, + { + x: 'center', + top: '48%', + text: '还款总额', + textStyle: { + color: '#fff', + fontSize: 22, + fontWeight: 400 + }, + }, + ], + backgroundColor: '#333', + labelLine: { + show: true, + lineStyle: { + color: '#7BC0CB', + }, + }, + label: { + show: false, + }, + xAxis3D: { + min: -1.5, + max: 1.5, + }, + yAxis3D: { + min: -1.5, + max: 1.5, + }, + zAxis3D: { + min: -1, + max: 1, + }, + grid3D: { + show: false, + boxHeight: 4, + bottom: '50%', + viewControl: { + distance: 180, + alpha: 25, + beta: 60, + autoRotate: true, // 自动旋转 + }, + }, + + series: series, +}; \ No newline at end of file diff --git a/src/packages/components/Charts/IntegratedEnergy/index.ts b/src/packages/components/Charts/IntegratedEnergy/index.ts index 62bb595..7ac17e9 100644 --- a/src/packages/components/Charts/IntegratedEnergy/index.ts +++ b/src/packages/components/Charts/IntegratedEnergy/index.ts @@ -1,5 +1,8 @@ import { EnergyOverviewConfig } from "./EnergyOverview" import { EnergyConsumptionTrendConfig } from "./EnergyConsumptionTrend" +import { ConsumptionProportionConfig } from "./ConsumptionProportion" +import { FeeOverviewConfig } from "./FeeOverview" + export default [ - EnergyOverviewConfig, EnergyConsumptionTrendConfig + EnergyOverviewConfig, EnergyConsumptionTrendConfig, ConsumptionProportionConfig, FeeOverviewConfig ] \ No newline at end of file