Commit a4c0de0c871bc816600accd89f0a682cfd24e122
1 parent
4ee311d4
feat:新增动画组件和组合组件下的对应子项,大部分是复制原作者已有的,git追踪记录了,其他未做改动
Showing
53 changed files
with
2558 additions
and
0 deletions
| 1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
| 2 | +import { WordCloudConfig } from './index' | |
| 3 | +import { CreateComponentType } from '@/packages/index.d' | |
| 4 | +import cloneDeep from 'lodash/cloneDeep' | |
| 5 | +import dataJson from './data.json' | |
| 6 | + | |
| 7 | +export const includes = [] | |
| 8 | + | |
| 9 | +export const ShapeEnumList = [ | |
| 10 | + { label: '圆形', value: 'circle' }, | |
| 11 | + { label: '心形', value: 'cardioid' }, | |
| 12 | + { label: '钻石', value: 'diamond' }, | |
| 13 | + { label: '右三角形', value: 'triangle-forward' }, | |
| 14 | + { label: '三角形', value: 'triangle' }, | |
| 15 | + { label: '五边形', value: 'pentagon' }, | |
| 16 | + { label: '星星', value: 'star' } | |
| 17 | +] | |
| 18 | + | |
| 19 | +export const option = { | |
| 20 | + dataset: [...dataJson], | |
| 21 | + tooltip: {}, | |
| 22 | + series: [ | |
| 23 | + { | |
| 24 | + type: 'wordCloud', | |
| 25 | + | |
| 26 | + // “云”绘制的形状,可以是表示为回调函数,也可以是固定关键字。 | |
| 27 | + // 可用值有:circle|cardioid|diamond|triangle-forward|triangle|pentagon|star | |
| 28 | + shape: 'circle', | |
| 29 | + | |
| 30 | + // 白色区域将被排除在绘制文本之外的剪影图像。 | |
| 31 | + // 随着云的形状生长,形状选项将继续应用。 | |
| 32 | + // maskImage: maskImage, | |
| 33 | + | |
| 34 | + // Folllowing left/top/width/height/right/bottom are used for positioning the word cloud | |
| 35 | + // Default to be put in the center and has 75% x 80% size. | |
| 36 | + left: 'center', | |
| 37 | + top: 'center', | |
| 38 | + width: '70%', | |
| 39 | + height: '80%', | |
| 40 | + right: null, | |
| 41 | + bottom: null, | |
| 42 | + | |
| 43 | + // 文本大小范围,默认 [12,60] | |
| 44 | + sizeRange: [12, 60], | |
| 45 | + | |
| 46 | + // 文本旋转范围和程度的步骤。 文本将通过旋转步骤45在[-90,90]中随机旋转 | |
| 47 | + rotationRange: [0, 0], | |
| 48 | + rotationStep: 0, | |
| 49 | + | |
| 50 | + // size of the grid in pixels for marking the availability of the canvas | |
| 51 | + // 网格大小越大,单词之间的差距就越大。 | |
| 52 | + gridSize: 8, | |
| 53 | + | |
| 54 | + // 设置为true,以允许单词在画布之外部分地绘制。允许绘制大于画布的大小 | |
| 55 | + drawOutOfBound: false, | |
| 56 | + | |
| 57 | + // If perform layout animation. | |
| 58 | + // NOTE disable it will lead to UI blocking when there is lots of words. | |
| 59 | + layoutAnimation: true, | |
| 60 | + | |
| 61 | + // Global text style | |
| 62 | + textStyle: { | |
| 63 | + fontFamily: 'sans-serif', | |
| 64 | + fontWeight: 'bold' | |
| 65 | + // 颜色可以是回调功能或颜色字符串 | |
| 66 | + // color: function () { | |
| 67 | + // // 随机颜色 | |
| 68 | + // return ( | |
| 69 | + // 'rgb(' + | |
| 70 | + // [Math.round(Math.random() * 160), Math.round(Math.random() * 160), Math.round(Math.random() * 160)].join( | |
| 71 | + // ',' | |
| 72 | + // ) + | |
| 73 | + // ')' | |
| 74 | + // ) | |
| 75 | + // } | |
| 76 | + }, | |
| 77 | + emphasis: { | |
| 78 | + focus: 'self', | |
| 79 | + | |
| 80 | + textStyle: { | |
| 81 | + shadowBlur: 10, | |
| 82 | + shadowColor: '#333' | |
| 83 | + } | |
| 84 | + }, | |
| 85 | + data: [...dataJson] | |
| 86 | + } | |
| 87 | + ] | |
| 88 | +} | |
| 89 | + | |
| 90 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
| 91 | + public key = WordCloudConfig.key | |
| 92 | + public chartConfig = cloneDeep(WordCloudConfig) | |
| 93 | + // 图表配置项 | |
| 94 | + public option = echartOptionProfixHandle(option, includes) | |
| 95 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <collapse-item name="词云" expanded> | |
| 3 | + <setting-item-box name="形状"> | |
| 4 | + <setting-item> | |
| 5 | + <n-select v-model:value="optionData.series[0].shape" size="small" :options="ShapeEnumList" /> | |
| 6 | + </setting-item> | |
| 7 | + <setting-item> | |
| 8 | + <n-checkbox v-model:checked="optionData.series[0].drawOutOfBound" size="small">允许出边</n-checkbox> | |
| 9 | + </setting-item> | |
| 10 | + </setting-item-box> | |
| 11 | + | |
| 12 | + <setting-item-box name="布局"> | |
| 13 | + <setting-item name="宽度"> | |
| 14 | + <n-slider | |
| 15 | + v-model:value="series.width" | |
| 16 | + :min="0" | |
| 17 | + :max="100" | |
| 18 | + :format-tooltip="sliderFormatTooltip" | |
| 19 | + @update:value="updateWidth" | |
| 20 | + ></n-slider> | |
| 21 | + </setting-item> | |
| 22 | + <setting-item name="高度"> | |
| 23 | + <n-slider | |
| 24 | + v-model:value="series.height" | |
| 25 | + :min="0" | |
| 26 | + :max="100" | |
| 27 | + :format-tooltip="sliderFormatTooltip" | |
| 28 | + @update:value="updateHeight" | |
| 29 | + ></n-slider> | |
| 30 | + </setting-item> | |
| 31 | + </setting-item-box> | |
| 32 | + | |
| 33 | + <setting-item-box name="样式" alone> | |
| 34 | + <setting-item name="字体区间(最小/最大字体)"> | |
| 35 | + <n-slider v-model:value="optionData.series[0].sizeRange" range :step="1" :min="6" :max="100" /> | |
| 36 | + </setting-item> | |
| 37 | + <setting-item name="旋转角度"> | |
| 38 | + <n-slider v-model:value="series.rotationStep" :step="15" :min="0" :max="45" @update:value="updateRotation" /> | |
| 39 | + </setting-item> | |
| 40 | + </setting-item-box> | |
| 41 | + </collapse-item> | |
| 42 | +</template> | |
| 43 | + | |
| 44 | +<script setup lang="ts"> | |
| 45 | +import { PropType, computed } from 'vue' | |
| 46 | +import { option, ShapeEnumList } from './config' | |
| 47 | +// eslint-disable-next-line no-unused-vars | |
| 48 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
| 49 | + | |
| 50 | +const props = defineProps({ | |
| 51 | + optionData: { | |
| 52 | + type: Object as PropType<typeof option>, | |
| 53 | + required: true | |
| 54 | + } | |
| 55 | +}) | |
| 56 | + | |
| 57 | +const series = computed(() => { | |
| 58 | + const { width, height, rotationStep } = props.optionData.series[0] | |
| 59 | + return { | |
| 60 | + width: +width.replace('%', ''), | |
| 61 | + height: +height.replace('%', ''), | |
| 62 | + rotationStep | |
| 63 | + } | |
| 64 | +}) | |
| 65 | + | |
| 66 | +const sliderFormatTooltip = (v: number) => { | |
| 67 | + return `${v}%` | |
| 68 | +} | |
| 69 | + | |
| 70 | +const updateWidth = (value: number) => { | |
| 71 | + props.optionData.series[0].width = `${value}%` | |
| 72 | +} | |
| 73 | + | |
| 74 | +const updateHeight = (value: number) => { | |
| 75 | + props.optionData.series[0].height = `${value}%` | |
| 76 | +} | |
| 77 | + | |
| 78 | +const updateRotation = (value: number) => { | |
| 79 | + props.optionData.series[0].rotationStep = value | |
| 80 | + props.optionData.series[0].rotationRange = value === 0 ? [0, 0] : [-90, 90] | |
| 81 | +} | |
| 82 | +</script> | ... | ... |
| 1 | +[ | |
| 2 | + { | |
| 3 | + "name": "数据可视化", | |
| 4 | + "value": 8000, | |
| 5 | + "textStyle": { | |
| 6 | + "color": "#78fbb2" | |
| 7 | + }, | |
| 8 | + "emphasis": { | |
| 9 | + "textStyle": { | |
| 10 | + "color": "red" | |
| 11 | + } | |
| 12 | + } | |
| 13 | + }, | |
| 14 | + { | |
| 15 | + "name": "GO VIEW", | |
| 16 | + "value": 6181 | |
| 17 | + }, | |
| 18 | + { | |
| 19 | + "name": "低代码", | |
| 20 | + "value": 4386 | |
| 21 | + }, | |
| 22 | + { | |
| 23 | + "name": "Vue3", | |
| 24 | + "value": 4055 | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "TypeScript4", | |
| 28 | + "value": 2467 | |
| 29 | + }, | |
| 30 | + { | |
| 31 | + "name": "Vite2", | |
| 32 | + "value": 2244 | |
| 33 | + }, | |
| 34 | + { | |
| 35 | + "name": "NaiveUI", | |
| 36 | + "value": 1898 | |
| 37 | + }, | |
| 38 | + { | |
| 39 | + "name": "ECharts5", | |
| 40 | + "value": 1484 | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + "name": "Axios", | |
| 44 | + "value": 1112 | |
| 45 | + }, | |
| 46 | + { | |
| 47 | + "name": "Pinia2", | |
| 48 | + "value": 965 | |
| 49 | + }, | |
| 50 | + { | |
| 51 | + "name": "PlopJS", | |
| 52 | + "value": 847 | |
| 53 | + }, | |
| 54 | + { | |
| 55 | + "name": "sfc", | |
| 56 | + "value": 582 | |
| 57 | + }, | |
| 58 | + { | |
| 59 | + "name": "SCSS", | |
| 60 | + "value": 555 | |
| 61 | + }, | |
| 62 | + { | |
| 63 | + "name": "pnpm", | |
| 64 | + "value": 550 | |
| 65 | + }, | |
| 66 | + { | |
| 67 | + "name": "eslint", | |
| 68 | + "value": 462 | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + "name": "json", | |
| 72 | + "value": 366 | |
| 73 | + }, | |
| 74 | + { | |
| 75 | + "name": "图表", | |
| 76 | + "value": 360 | |
| 77 | + }, | |
| 78 | + { | |
| 79 | + "name": "地图", | |
| 80 | + "value": 282 | |
| 81 | + }, | |
| 82 | + { | |
| 83 | + "name": "时钟", | |
| 84 | + "value": 273 | |
| 85 | + }, | |
| 86 | + { | |
| 87 | + "name": "标题", | |
| 88 | + "value": 265 | |
| 89 | + } | |
| 90 | +] | ... | ... |
| 1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
| 2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
| 3 | + | |
| 4 | +export const WordCloudConfig: ConfigType = { | |
| 5 | + key: 'WordCloud', | |
| 6 | + chartKey: 'VWordCloud', | |
| 7 | + conKey: 'VCWordCloud', | |
| 8 | + title: '词云', | |
| 9 | + category: ChatCategoryEnum.MORE, | |
| 10 | + categoryName: ChatCategoryEnumName.MORE, | |
| 11 | + package: PackagesCategoryEnum.INFORMATIONS, | |
| 12 | + chartFrame: ChartFrameEnum.COMMON, | |
| 13 | + image: 'words_cloud.png' | |
| 14 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <v-chart | |
| 3 | + ref="vChartRef" | |
| 4 | + :theme="themeColor" | |
| 5 | + :option="option" | |
| 6 | + :manual-update="isPreview()" | |
| 7 | + :update-options="{ replaceMerge: replaceMergeArr }" | |
| 8 | + autoresize | |
| 9 | + ></v-chart> | |
| 10 | +</template> | |
| 11 | + | |
| 12 | +<script setup lang="ts"> | |
| 13 | +import { ref, computed, watch, PropType } from 'vue' | |
| 14 | +import VChart from 'vue-echarts' | |
| 15 | +import 'echarts-wordcloud' | |
| 16 | +import { use } from 'echarts/core' | |
| 17 | +import { CanvasRenderer } from 'echarts/renderers' | |
| 18 | +import config, { includes } from './config' | |
| 19 | +import { mergeTheme, setOption } from '@/packages/public/chart' | |
| 20 | +import { useChartDataFetch } from '@/hooks' | |
| 21 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
| 22 | +import { isPreview } from '@/utils' | |
| 23 | +import { GridComponent, TooltipComponent } from 'echarts/components' | |
| 24 | +import dataJson from './data.json' | |
| 25 | + | |
| 26 | +const props = defineProps({ | |
| 27 | + themeSetting: { | |
| 28 | + type: Object, | |
| 29 | + required: true | |
| 30 | + }, | |
| 31 | + themeColor: { | |
| 32 | + type: Object, | |
| 33 | + required: true | |
| 34 | + }, | |
| 35 | + chartConfig: { | |
| 36 | + type: Object as PropType<config>, | |
| 37 | + required: true | |
| 38 | + } | |
| 39 | +}) | |
| 40 | + | |
| 41 | +use([CanvasRenderer, GridComponent, TooltipComponent]) | |
| 42 | + | |
| 43 | +const replaceMergeArr = ref<string[]>() | |
| 44 | + | |
| 45 | +const option = computed(() => { | |
| 46 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | |
| 47 | +}) | |
| 48 | + | |
| 49 | +const dataSetHandle = (dataset: typeof dataJson) => { | |
| 50 | + try { | |
| 51 | + dataset && (props.chartConfig.option.series[0].data = dataset) | |
| 52 | + vChartRef.value && isPreview() && setOption(vChartRef.value, props.chartConfig.option) | |
| 53 | + } catch (error) { | |
| 54 | + console.log(error) | |
| 55 | + } | |
| 56 | +} | |
| 57 | + | |
| 58 | +// dataset 无法变更条数的补丁 | |
| 59 | +watch( | |
| 60 | + () => props.chartConfig.option.dataset, | |
| 61 | + newData => { | |
| 62 | + dataSetHandle(newData) | |
| 63 | + }, | |
| 64 | + { | |
| 65 | + deep: false | |
| 66 | + } | |
| 67 | +) | |
| 68 | + | |
| 69 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => { | |
| 70 | + dataSetHandle(newData) | |
| 71 | +}) | |
| 72 | +</script> | ... | ... |
| 1 | +// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
| 2 | +// @ts-nocheck | |
| 3 | +import { | |
| 4 | + ArcCurve, | |
| 5 | + BufferAttribute, | |
| 6 | + BufferGeometry, | |
| 7 | + Color, | |
| 8 | + Line, | |
| 9 | + LineBasicMaterial, | |
| 10 | + Points, | |
| 11 | + PointsMaterial, | |
| 12 | + Quaternion, | |
| 13 | + Vector3 | |
| 14 | +} from 'three' | |
| 15 | +import { lon2xyz } from './common' | |
| 16 | + | |
| 17 | +/* | |
| 18 | + * 绘制一条圆弧飞线 | |
| 19 | + * 5个参数含义:( 飞线圆弧轨迹半径, 开始角度, 结束角度) | |
| 20 | + */ | |
| 21 | +function createFlyLine(radius, startAngle, endAngle, color) { | |
| 22 | + const geometry = new BufferGeometry() //声明一个几何体对象BufferGeometry | |
| 23 | + // ArcCurve创建圆弧曲线 | |
| 24 | + const arc = new ArcCurve(0, 0, radius, startAngle, endAngle, false) | |
| 25 | + //getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组 | |
| 26 | + const pointsArr = arc.getSpacedPoints(100) //分段数80,返回81个顶点 | |
| 27 | + geometry.setFromPoints(pointsArr) // setFromPoints方法从pointsArr中提取数据改变几何体的顶点属性vertices | |
| 28 | + // 每个顶点对应一个百分比数据attributes.percent 用于控制点的渲染大小 | |
| 29 | + const percentArr = [] //attributes.percent的数据 | |
| 30 | + for (let i = 0; i < pointsArr.length; i++) { | |
| 31 | + percentArr.push(i / pointsArr.length) | |
| 32 | + } | |
| 33 | + const percentAttribue = new BufferAttribute(new Float32Array(percentArr), 1) | |
| 34 | + // 通过顶点数据percent点模型从大到小变化,产生小蝌蚪形状飞线 | |
| 35 | + geometry.attributes.percent = percentAttribue | |
| 36 | + // 批量计算所有顶点颜色数据 | |
| 37 | + const colorArr = [] | |
| 38 | + for (let i = 0; i < pointsArr.length; i++) { | |
| 39 | + const color1 = new Color(0xec8f43) //轨迹线颜色 青色 | |
| 40 | + const color2 = new Color(0xf3ae76) //黄色 | |
| 41 | + const color = color1.lerp(color2, i / pointsArr.length) | |
| 42 | + colorArr.push(color.r, color.g, color.b) | |
| 43 | + } | |
| 44 | + // 设置几何体顶点颜色数据 | |
| 45 | + geometry.attributes.color = new BufferAttribute(new Float32Array(colorArr), 3) | |
| 46 | + const size = 1.3 | |
| 47 | + // 点模型渲染几何体每个顶点 | |
| 48 | + const material = new PointsMaterial({ | |
| 49 | + size, //点大小 | |
| 50 | + // vertexColors: VertexColors, //使用顶点颜色渲染 | |
| 51 | + transparent: true, | |
| 52 | + depthWrite: false | |
| 53 | + }) | |
| 54 | + // 修改点材质的着色器源码(注意:不同版本细节可能会稍微会有区别,不过整体思路是一样的) | |
| 55 | + material.onBeforeCompile = function (shader) { | |
| 56 | + // 顶点着色器中声明一个attribute变量:百分比 | |
| 57 | + shader.vertexShader = shader.vertexShader.replace( | |
| 58 | + 'void main() {', | |
| 59 | + [ | |
| 60 | + 'attribute float percent;', //顶点大小百分比变量,控制点渲染大小 | |
| 61 | + 'void main() {' | |
| 62 | + ].join('\n') // .join()把数组元素合成字符串 | |
| 63 | + ) | |
| 64 | + // 调整点渲染大小计算方式 | |
| 65 | + shader.vertexShader = shader.vertexShader.replace( | |
| 66 | + 'gl_PointSize = size;', | |
| 67 | + ['gl_PointSize = percent * size;'].join('\n') // .join()把数组元素合成字符串 | |
| 68 | + ) | |
| 69 | + } | |
| 70 | + const FlyLine = new Points(geometry, material) | |
| 71 | + material.color = new Color(color) | |
| 72 | + FlyLine.name = '飞行线' | |
| 73 | + | |
| 74 | + return FlyLine | |
| 75 | +} | |
| 76 | + | |
| 77 | +/**输入地球上任意两点的经纬度坐标,通过函数flyArc可以绘制一个飞线圆弧轨迹 | |
| 78 | + * lon1,lat1:轨迹线起点经纬度坐标 | |
| 79 | + * lon2,lat2:轨迹线结束点经纬度坐标 | |
| 80 | + */ | |
| 81 | +function flyArc(radius, lon1, lat1, lon2, lat2, options) { | |
| 82 | + const sphereCoord1 = lon2xyz(radius, lon1, lat1) //经纬度坐标转球面坐标 | |
| 83 | + // startSphereCoord:轨迹线起点球面坐标 | |
| 84 | + const startSphereCoord = new Vector3(sphereCoord1.x, sphereCoord1.y, sphereCoord1.z) | |
| 85 | + const sphereCoord2 = lon2xyz(radius, lon2, lat2) | |
| 86 | + // startSphereCoord:轨迹线结束点球面坐标 | |
| 87 | + const endSphereCoord = new Vector3(sphereCoord2.x, sphereCoord2.y, sphereCoord2.z) | |
| 88 | + | |
| 89 | + //计算绘制圆弧需要的关于y轴对称的起点、结束点和旋转四元数 | |
| 90 | + const startEndQua = _3Dto2D(startSphereCoord, endSphereCoord) | |
| 91 | + // 调用arcXOY函数绘制一条圆弧飞线轨迹 | |
| 92 | + const arcline = arcXOY(radius, startEndQua.startPoint, startEndQua.endPoint, options) | |
| 93 | + arcline.quaternion.multiply(startEndQua.quaternion) | |
| 94 | + return arcline | |
| 95 | +} | |
| 96 | +/* | |
| 97 | + * 把3D球面上任意的两个飞线起点和结束点绕球心旋转到到XOY平面上, | |
| 98 | + * 同时保持关于y轴对称,借助旋转得到的新起点和新结束点绘制 | |
| 99 | + * 一个圆弧,最后把绘制的圆弧反向旋转到原来的起点和结束点即可 | |
| 100 | + */ | |
| 101 | +function _3Dto2D(startSphere, endSphere) { | |
| 102 | + /*计算第一次旋转的四元数:表示从一个平面如何旋转到另一个平面*/ | |
| 103 | + const origin = new Vector3(0, 0, 0) //球心坐标 | |
| 104 | + const startDir = startSphere.clone().sub(origin) //飞线起点与球心构成方向向量 | |
| 105 | + const endDir = endSphere.clone().sub(origin) //飞线结束点与球心构成方向向量 | |
| 106 | + // dir1和dir2构成一个三角形,.cross()叉乘计算该三角形法线normal | |
| 107 | + const normal = startDir.clone().cross(endDir).normalize() | |
| 108 | + const xoyNormal = new Vector3(0, 0, 1) //XOY平面的法线 | |
| 109 | + //.setFromUnitVectors()计算从normal向量旋转达到xoyNormal向量所需要的四元数 | |
| 110 | + // quaternion表示把球面飞线旋转到XOY平面上需要的四元数 | |
| 111 | + const quaternion3D_XOY = new Quaternion().setFromUnitVectors(normal, xoyNormal) | |
| 112 | + /*第一次旋转:飞线起点、结束点从3D空间第一次旋转到XOY平面*/ | |
| 113 | + const startSphereXOY = startSphere.clone().applyQuaternion(quaternion3D_XOY) | |
| 114 | + const endSphereXOY = endSphere.clone().applyQuaternion(quaternion3D_XOY) | |
| 115 | + | |
| 116 | + /*计算第二次旋转的四元数*/ | |
| 117 | + // middleV3:startSphereXOY和endSphereXOY的中点 | |
| 118 | + const middleV3 = startSphereXOY.clone().add(endSphereXOY).multiplyScalar(0.5) | |
| 119 | + const midDir = middleV3.clone().sub(origin).normalize() // 旋转前向量midDir,中点middleV3和球心构成的方向向量 | |
| 120 | + const yDir = new Vector3(0, 1, 0) // 旋转后向量yDir,即y轴 | |
| 121 | + // .setFromUnitVectors()计算从midDir向量旋转达到yDir向量所需要的四元数 | |
| 122 | + // quaternion2表示让第一次旋转到XOY平面的起点和结束点关于y轴对称需要的四元数 | |
| 123 | + const quaternionXOY_Y = new Quaternion().setFromUnitVectors(midDir, yDir) | |
| 124 | + | |
| 125 | + /*第二次旋转:使旋转到XOY平面的点再次旋转,实现关于Y轴对称*/ | |
| 126 | + const startSpherXOY_Y = startSphereXOY.clone().applyQuaternion(quaternionXOY_Y) | |
| 127 | + const endSphereXOY_Y = endSphereXOY.clone().applyQuaternion(quaternionXOY_Y) | |
| 128 | + | |
| 129 | + /**一个四元数表示一个旋转过程 | |
| 130 | + *.invert()方法表示四元数的逆,简单说就是把旋转过程倒过来 | |
| 131 | + * 两次旋转的四元数执行.invert()求逆,然后执行.multiply()相乘 | |
| 132 | + *新版本.invert()对应旧版本.invert() | |
| 133 | + */ | |
| 134 | + const quaternionInverse = quaternion3D_XOY.clone().invert().multiply(quaternionXOY_Y.clone().invert()) | |
| 135 | + return { | |
| 136 | + // 返回两次旋转四元数的逆四元数 | |
| 137 | + quaternion: quaternionInverse, | |
| 138 | + // 范围两次旋转后在XOY平面上关于y轴对称的圆弧起点和结束点坐标 | |
| 139 | + startPoint: startSpherXOY_Y, | |
| 140 | + endPoint: endSphereXOY_Y | |
| 141 | + } | |
| 142 | +} | |
| 143 | +/**通过函数arcXOY()可以在XOY平面上绘制一个关于y轴对称的圆弧曲线 | |
| 144 | + * startPoint, endPoint:表示圆弧曲线的起点和结束点坐标值,起点和结束点关于y轴对称 | |
| 145 | + * 同时在圆弧轨迹的基础上绘制一段飞线*/ | |
| 146 | +function arcXOY(radius, startPoint, endPoint, options) { | |
| 147 | + // 计算两点的中点 | |
| 148 | + const middleV3 = new Vector3().addVectors(startPoint, endPoint).multiplyScalar(0.5) | |
| 149 | + // 弦垂线的方向dir(弦的中点和圆心构成的向量) | |
| 150 | + const dir = middleV3.clone().normalize() | |
| 151 | + // 计算球面飞线的起点、结束点和球心构成夹角的弧度值 | |
| 152 | + const earthRadianAngle = radianAOB(startPoint, endPoint, new Vector3(0, 0, 0)) | |
| 153 | + /*设置飞线轨迹圆弧的中间点坐标 | |
| 154 | + 弧度值 * radius * 0.2:表示飞线轨迹圆弧顶部距离地球球面的距离 | |
| 155 | + 起点、结束点相聚越远,构成的弧线顶部距离球面越高*/ | |
| 156 | + const arcTopCoord = dir.multiplyScalar(radius + earthRadianAngle * radius * 0.2) // 黄色飞行线的高度 | |
| 157 | + //求三个点的外接圆圆心(飞线圆弧轨迹的圆心坐标) | |
| 158 | + const flyArcCenter = threePointCenter(startPoint, endPoint, arcTopCoord) | |
| 159 | + // 飞线圆弧轨迹半径flyArcR | |
| 160 | + const flyArcR = Math.abs(flyArcCenter.y - arcTopCoord.y) | |
| 161 | + /*坐标原点和飞线起点构成直线和y轴负半轴夹角弧度值 | |
| 162 | + 参数分别是:飞线圆弧起点、y轴负半轴上一点、飞线圆弧圆心*/ | |
| 163 | + const flyRadianAngle = radianAOB(startPoint, new Vector3(0, -1, 0), flyArcCenter) | |
| 164 | + const startAngle = -Math.PI / 2 + flyRadianAngle //飞线圆弧开始角度 | |
| 165 | + const endAngle = Math.PI - startAngle //飞线圆弧结束角度 | |
| 166 | + // 调用圆弧线模型的绘制函数 | |
| 167 | + const arcline = circleLine(flyArcCenter.x, flyArcCenter.y, flyArcR, startAngle, endAngle, options.color) | |
| 168 | + // const arcline = new Group();// 不绘制轨迹线,使用 Group替换circleLine()即可 | |
| 169 | + arcline.center = flyArcCenter //飞线圆弧自定一个属性表示飞线圆弧的圆心 | |
| 170 | + arcline.topCoord = arcTopCoord //飞线圆弧自定一个属性表示飞线圆弧中间也就是顶部坐标 | |
| 171 | + | |
| 172 | + // const flyAngle = Math.PI/ 10; //飞线圆弧固定弧度 | |
| 173 | + const flyAngle = (endAngle - startAngle) / 7 //飞线圆弧的弧度和轨迹线弧度相关 | |
| 174 | + // 绘制一段飞线,圆心做坐标原点 | |
| 175 | + const flyLine = createFlyLine(flyArcR, startAngle, startAngle + flyAngle, options.flyLineColor) | |
| 176 | + flyLine.position.y = flyArcCenter.y //平移飞线圆弧和飞线轨迹圆弧重合 | |
| 177 | + //飞线段flyLine作为飞线轨迹arcLine子对象,继承飞线轨迹平移旋转等变换 | |
| 178 | + arcline.add(flyLine) | |
| 179 | + //飞线段运动范围startAngle~flyEndAngle | |
| 180 | + flyLine.flyEndAngle = endAngle - startAngle - flyAngle | |
| 181 | + flyLine.startAngle = startAngle | |
| 182 | + // arcline.flyEndAngle:飞线段当前角度位置,这里设置了一个随机值用于演示 | |
| 183 | + flyLine.AngleZ = arcline.flyEndAngle * Math.random() | |
| 184 | + // flyLine.rotation.z = arcline.AngleZ; | |
| 185 | + // arcline.flyLine指向飞线段,便于设置动画是访问飞线段 | |
| 186 | + arcline.userData['flyLine'] = flyLine | |
| 187 | + | |
| 188 | + return arcline | |
| 189 | +} | |
| 190 | +/*计算球面上两点和球心构成夹角的弧度值 | |
| 191 | +参数point1, point2:表示地球球面上两点坐标Vector3 | |
| 192 | +计算A、B两点和顶点O构成的AOB夹角弧度值*/ | |
| 193 | +function radianAOB(A, B, O) { | |
| 194 | + // dir1、dir2:球面上两个点和球心构成的方向向量 | |
| 195 | + const dir1 = A.clone().sub(O).normalize() | |
| 196 | + const dir2 = B.clone().sub(O).normalize() | |
| 197 | + //点乘.dot()计算夹角余弦值 | |
| 198 | + const cosAngle = dir1.clone().dot(dir2) | |
| 199 | + const radianAngle = Math.acos(cosAngle) //余弦值转夹角弧度值,通过余弦值可以计算夹角范围是0~180度 | |
| 200 | + return radianAngle | |
| 201 | +} | |
| 202 | +/*绘制一条圆弧曲线模型Line | |
| 203 | +5个参数含义:(圆心横坐标, 圆心纵坐标, 飞线圆弧轨迹半径, 开始角度, 结束角度)*/ | |
| 204 | +function circleLine(x, y, r, startAngle, endAngle, color) { | |
| 205 | + const geometry = new BufferGeometry() //声明一个几何体对象Geometry | |
| 206 | + // ArcCurve创建圆弧曲线 | |
| 207 | + const arc = new ArcCurve(x, y, r, startAngle, endAngle, false) | |
| 208 | + //getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组 | |
| 209 | + const points = arc.getSpacedPoints(80) //分段数50,返回51个顶点 | |
| 210 | + geometry.setFromPoints(points) // setFromPoints方法从points中提取数据改变几何体的顶点属性vertices | |
| 211 | + const material = new LineBasicMaterial({ | |
| 212 | + color: color || 0xd18547 | |
| 213 | + }) //线条材质 | |
| 214 | + const line = new Line(geometry, material) //线条模型对象 | |
| 215 | + return line | |
| 216 | +} | |
| 217 | +//求三个点的外接圆圆心,p1, p2, p3表示三个点的坐标Vector3。 | |
| 218 | +function threePointCenter(p1, p2, p3) { | |
| 219 | + const L1 = p1.lengthSq() //p1到坐标原点距离的平方 | |
| 220 | + const L2 = p2.lengthSq() | |
| 221 | + const L3 = p3.lengthSq() | |
| 222 | + const x1 = p1.x, | |
| 223 | + y1 = p1.y, | |
| 224 | + x2 = p2.x, | |
| 225 | + y2 = p2.y, | |
| 226 | + x3 = p3.x, | |
| 227 | + y3 = p3.y | |
| 228 | + const S = x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2 | |
| 229 | + const x = (L2 * y3 + L1 * y2 + L3 * y1 - L2 * y1 - L3 * y2 - L1 * y3) / S / 2 | |
| 230 | + const y = (L3 * x2 + L2 * x1 + L1 * x3 - L1 * x2 - L2 * x3 - L3 * x1) / S / 2 | |
| 231 | + // 三点外接圆圆心坐标 | |
| 232 | + const center = new Vector3(x, y, 0) | |
| 233 | + return center | |
| 234 | +} | |
| 235 | + | |
| 236 | +export { arcXOY, flyArc } | ... | ... |
| 1 | +import { | |
| 2 | + CatmullRomCurve3, | |
| 3 | + DoubleSide, | |
| 4 | + Group, | |
| 5 | + Mesh, | |
| 6 | + MeshBasicMaterial, | |
| 7 | + PlaneGeometry, | |
| 8 | + Texture, | |
| 9 | + TubeGeometry, | |
| 10 | + Vector3 | |
| 11 | +} from 'three' | |
| 12 | +import { punctuation } from '../world/Earth' | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 经纬度坐标转球面坐标 | |
| 16 | + * @param {地球半径} R | |
| 17 | + * @param {经度(角度值)} longitude | |
| 18 | + * @param {维度(角度值)} latitude | |
| 19 | + */ | |
| 20 | +export const lon2xyz = (R: number, longitude: number, latitude: number): Vector3 => { | |
| 21 | + let lon = (longitude * Math.PI) / 180 // 转弧度值 | |
| 22 | + const lat = (latitude * Math.PI) / 180 // 转弧度值 | |
| 23 | + lon = -lon // js坐标系z坐标轴对应经度-90度,而不是90度 | |
| 24 | + | |
| 25 | + // 经纬度坐标转球面坐标计算公式 | |
| 26 | + const x = R * Math.cos(lat) * Math.cos(lon) | |
| 27 | + const y = R * Math.sin(lat) | |
| 28 | + const z = R * Math.cos(lat) * Math.sin(lon) | |
| 29 | + // 返回球面坐标 | |
| 30 | + return new Vector3(x, y, z) | |
| 31 | +} | |
| 32 | + | |
| 33 | +// 创建波动光圈 | |
| 34 | +export const createWaveMesh = (options: { radius: number; lon: number; lat: number; textures: any }) => { | |
| 35 | + const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上 | |
| 36 | + const texture = options.textures.aperture | |
| 37 | + | |
| 38 | + const material = new MeshBasicMaterial({ | |
| 39 | + color: 0xe99f68, | |
| 40 | + map: texture, | |
| 41 | + transparent: true, //使用背景透明的png贴图,注意开启透明计算 | |
| 42 | + opacity: 1.0, | |
| 43 | + depthWrite: false //禁止写入深度缓冲区数据 | |
| 44 | + }) | |
| 45 | + const mesh = new Mesh(geometry, material) | |
| 46 | + // 经纬度转球面坐标 | |
| 47 | + const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat) | |
| 48 | + const size = options.radius * 0.12 //矩形平面Mesh的尺寸 | |
| 49 | + mesh.scale.set(size, size, size) //设置mesh大小 | |
| 50 | + mesh.userData['size'] = size //自顶一个属性,表示mesh静态大小 | |
| 51 | + mesh.userData['scale'] = Math.random() * 1.0 //自定义属性._s表示mesh在原始大小基础上放大倍数 光圈在原来mesh.size基础上1~2倍之间变化 | |
| 52 | + mesh.position.set(coord.x, coord.y, coord.z) | |
| 53 | + const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize() | |
| 54 | + const meshNormal = new Vector3(0, 0, 1) | |
| 55 | + mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3) | |
| 56 | + return mesh | |
| 57 | +} | |
| 58 | + | |
| 59 | +// 创建柱状 | |
| 60 | +export const createLightPillar = (options: { | |
| 61 | + radius: number | |
| 62 | + lon: number | |
| 63 | + lat: number | |
| 64 | + index: number | |
| 65 | + textures: Record<string, Texture> | |
| 66 | + punctuation: punctuation | |
| 67 | +}) => { | |
| 68 | + const height = options.radius * 0.3 | |
| 69 | + const geometry = new PlaneGeometry(options.radius * 0.05, height) | |
| 70 | + geometry.rotateX(Math.PI / 2) | |
| 71 | + geometry.translate(0, 0, height / 2) | |
| 72 | + const material = new MeshBasicMaterial({ | |
| 73 | + map: options.textures.light_column, | |
| 74 | + color: options.index == 0 ? options.punctuation.lightColumn.startColor : options.punctuation.lightColumn.endColor, | |
| 75 | + transparent: true, | |
| 76 | + side: DoubleSide, | |
| 77 | + depthWrite: false //是否对深度缓冲区有任何的影响 | |
| 78 | + }) | |
| 79 | + const mesh = new Mesh(geometry, material) | |
| 80 | + const group = new Group() | |
| 81 | + // 两个光柱交叉叠加 | |
| 82 | + group.add(mesh, mesh.clone().rotateZ(Math.PI / 2)) //几何体绕x轴旋转了,所以mesh旋转轴变为z | |
| 83 | + // 经纬度转球面坐标 | |
| 84 | + const SphereCoord = lon2xyz(options.radius, options.lon, options.lat) //SphereCoord球面坐标 | |
| 85 | + group.position.set(SphereCoord.x, SphereCoord.y, SphereCoord.z) //设置mesh位置 | |
| 86 | + const coordVec3 = new Vector3(SphereCoord.x, SphereCoord.y, SphereCoord.z).normalize() | |
| 87 | + const meshNormal = new Vector3(0, 0, 1) | |
| 88 | + group.quaternion.setFromUnitVectors(meshNormal, coordVec3) | |
| 89 | + return group | |
| 90 | +} | |
| 91 | + | |
| 92 | +// 光柱底座矩形平面 | |
| 93 | +export const createPointMesh = (options: { radius: number; lon: number; lat: number; material: MeshBasicMaterial }) => { | |
| 94 | + const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上 | |
| 95 | + const mesh = new Mesh(geometry, options.material) | |
| 96 | + // 经纬度转球面坐标 | |
| 97 | + const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat) | |
| 98 | + const size = options.radius * 0.05 // 矩形平面Mesh的尺寸 | |
| 99 | + mesh.scale.set(size, size, size) // 设置mesh大小 | |
| 100 | + | |
| 101 | + // 设置mesh位置 | |
| 102 | + mesh.position.set(coord.x, coord.y, coord.z) | |
| 103 | + const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize() | |
| 104 | + const meshNormal = new Vector3(0, 0, 1) | |
| 105 | + mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3) | |
| 106 | + return mesh | |
| 107 | +} | |
| 108 | + | |
| 109 | +// 获取点 | |
| 110 | +export const getCirclePoints = (option: any) => { | |
| 111 | + const list = [] | |
| 112 | + for (let j = 0; j < 2 * Math.PI - 0.1; j += (2 * Math.PI) / (option.number || 100)) { | |
| 113 | + list.push([ | |
| 114 | + parseFloat((Math.cos(j) * (option.radius || 10)).toFixed(2)), | |
| 115 | + 0, | |
| 116 | + parseFloat((Math.sin(j) * (option.radius || 10)).toFixed(2)) | |
| 117 | + ]) | |
| 118 | + } | |
| 119 | + if (option.closed) list.push(list[0]) | |
| 120 | + return list | |
| 121 | +} | |
| 122 | + | |
| 123 | +// 创建线 | |
| 124 | + | |
| 125 | +/** | |
| 126 | + * 创建动态的线 | |
| 127 | + */ | |
| 128 | +export const createAnimateLine = (option: any) => { | |
| 129 | + // 由多个点数组构成的曲线 通常用于道路 | |
| 130 | + const l: Array<any> = [] | |
| 131 | + option.pointList.forEach((e: Array<any>) => l.push(new Vector3(e[0], e[1], e[2]))) | |
| 132 | + const curve = new CatmullRomCurve3(l) // 曲线路径 | |
| 133 | + | |
| 134 | + // 管道体 | |
| 135 | + const tubeGeometry = new TubeGeometry(curve, option.number || 50, option.radius || 1, option.radialSegments) | |
| 136 | + return new Mesh(tubeGeometry, option.material) | |
| 137 | +} | ... | ... |
src/packages/components/Artoons/ThreeDAnimations/ThreeEarth01/code/shaders/earth/fragment.fs
0 → 100644
| 1 | +uniform vec3 glowColor; | |
| 2 | +uniform float bias; | |
| 3 | +uniform float power; | |
| 4 | +uniform float time; | |
| 5 | +varying vec3 vp; | |
| 6 | +varying vec3 vNormal; | |
| 7 | +varying vec3 vPositionNormal; | |
| 8 | +uniform float scale; | |
| 9 | +// 获取纹理 | |
| 10 | +uniform sampler2D map; | |
| 11 | +// 纹理坐标 | |
| 12 | +varying vec2 vUv; | |
| 13 | + | |
| 14 | +void main(void){ | |
| 15 | + float a = pow( bias + scale * abs(dot(vNormal, vPositionNormal)), power ); | |
| 16 | + if(vp.y > time && vp.y < time + 20.0) { | |
| 17 | + float t = smoothstep(0.0, 0.8, (1.0 - abs(0.5 - (vp.y - time) / 20.0)) / 3.0 ); | |
| 18 | + gl_FragColor = mix(gl_FragColor, vec4(glowColor, 1.0), t * t ); | |
| 19 | + } | |
| 20 | + gl_FragColor = mix(gl_FragColor, vec4( glowColor, 1.0 ), a); | |
| 21 | + float b = 0.8; | |
| 22 | + gl_FragColor = gl_FragColor + texture2D( map, vUv ); | |
| 23 | +} | |
| \ No newline at end of file | ... | ... |
src/packages/components/Artoons/ThreeDAnimations/ThreeEarth01/code/shaders/earth/vertex.vs
0 → 100644
| 1 | + | |
| 2 | +varying vec2 vUv; | |
| 3 | +varying vec3 vNormal; | |
| 4 | +varying vec3 vp; | |
| 5 | +varying vec3 vPositionNormal; | |
| 6 | +void main(void){ | |
| 7 | + vUv = uv; | |
| 8 | + vNormal = normalize( normalMatrix * normal ); // 转换到视图空间 | |
| 9 | + vp = position; | |
| 10 | + vPositionNormal = normalize(( modelViewMatrix * vec4(position, 1.0) ).xyz); | |
| 11 | + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); | |
| 12 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +/** | |
| 2 | + * 资源文件 | |
| 3 | + * 把模型和图片分开进行加载 | |
| 4 | + */ | |
| 5 | + | |
| 6 | +interface ITextures { | |
| 7 | + name: string | |
| 8 | + url: string | |
| 9 | +} | |
| 10 | + | |
| 11 | +export interface IResources { | |
| 12 | + textures?: ITextures[] | |
| 13 | +} | |
| 14 | + | |
| 15 | +const fileSuffix = ['earth', 'gradient', 'redCircle', 'label', 'aperture', 'glow', 'light_column', 'aircraft'] | |
| 16 | +const textures: ITextures[] = [] | |
| 17 | + | |
| 18 | +const modules = import.meta.globEager("../../images/earth/*"); | |
| 19 | + | |
| 20 | +for(let item in modules) { | |
| 21 | + const n = item.split('/').pop() | |
| 22 | + if(n) { | |
| 23 | + textures.push({ | |
| 24 | + name: n.split('.')[0], | |
| 25 | + url: modules[item].default | |
| 26 | + }) | |
| 27 | + } | |
| 28 | +} | |
| 29 | + | |
| 30 | +const resources: IResources = { | |
| 31 | + textures | |
| 32 | +} | |
| 33 | + | |
| 34 | +export { resources } | ... | ... |
| 1 | +/** | |
| 2 | + * 创建 threejs 四大天王 | |
| 3 | + * 场景、相机、渲染器、控制器 | |
| 4 | + */ | |
| 5 | + | |
| 6 | +import * as THREE from 'three' | |
| 7 | +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' | |
| 8 | + | |
| 9 | +export class Basic { | |
| 10 | + public scene!: THREE.Scene | |
| 11 | + public camera!: THREE.PerspectiveCamera | |
| 12 | + public renderer!: THREE.WebGLRenderer | |
| 13 | + public controls!: OrbitControls | |
| 14 | + public dom: HTMLElement | |
| 15 | + | |
| 16 | + constructor(dom: HTMLElement) { | |
| 17 | + this.dom = dom | |
| 18 | + this.initScenes() | |
| 19 | + this.setControls() | |
| 20 | + } | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 初始化场景 | |
| 24 | + */ | |
| 25 | + initScenes() { | |
| 26 | + this.scene = new THREE.Scene() | |
| 27 | + | |
| 28 | + this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000) | |
| 29 | + this.camera.position.set(0, 30, -250) | |
| 30 | + | |
| 31 | + this.renderer = new THREE.WebGLRenderer({ | |
| 32 | + // canvas: this.dom, | |
| 33 | + alpha: true, // 透明 | |
| 34 | + antialias: true // 抗锯齿 | |
| 35 | + }) | |
| 36 | + this.renderer.setPixelRatio(window.devicePixelRatio) // 设置屏幕像素比 | |
| 37 | + this.renderer.setSize(window.innerWidth, window.innerHeight) // 设置渲染器宽高 | |
| 38 | + this.dom.appendChild(this.renderer.domElement) // 添加到dom中 | |
| 39 | + } | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 设置控制器 | |
| 43 | + */ | |
| 44 | + setControls() { | |
| 45 | + // 鼠标控制 相机,渲染dom | |
| 46 | + this.controls = new OrbitControls(this.camera, this.renderer.domElement) | |
| 47 | + | |
| 48 | + this.controls.autoRotateSpeed = 3 | |
| 49 | + // 使动画循环使用时阻尼或自转 意思是否有惯性 | |
| 50 | + this.controls.enableDamping = true | |
| 51 | + // 动态阻尼系数 就是鼠标拖拽旋转灵敏度 | |
| 52 | + this.controls.dampingFactor = 0.05 | |
| 53 | + // 是否可以缩放 | |
| 54 | + this.controls.enableZoom = true | |
| 55 | + // 设置相机距离原点的最远距离 | |
| 56 | + this.controls.minDistance = 100 | |
| 57 | + // 设置相机距离原点的最远距离 | |
| 58 | + this.controls.maxDistance = 300 | |
| 59 | + // 是否开启右键拖拽 | |
| 60 | + this.controls.enablePan = false | |
| 61 | + } | |
| 62 | +} | ... | ... |
| 1 | +import { | |
| 2 | + BufferAttribute, | |
| 3 | + BufferGeometry, | |
| 4 | + Color, | |
| 5 | + DoubleSide, | |
| 6 | + Group, | |
| 7 | + Material, | |
| 8 | + Mesh, | |
| 9 | + MeshBasicMaterial, | |
| 10 | + NormalBlending, | |
| 11 | + Object3D, | |
| 12 | + Points, | |
| 13 | + PointsMaterial, | |
| 14 | + ShaderMaterial, | |
| 15 | + SphereGeometry, | |
| 16 | + Sprite, | |
| 17 | + SpriteMaterial, | |
| 18 | + Texture, | |
| 19 | + TextureLoader, | |
| 20 | + Vector3 | |
| 21 | +} from 'three' | |
| 22 | + | |
| 23 | +import { | |
| 24 | + createAnimateLine, | |
| 25 | + createLightPillar, | |
| 26 | + createPointMesh, | |
| 27 | + createWaveMesh, | |
| 28 | + getCirclePoints, | |
| 29 | + lon2xyz | |
| 30 | +} from '../Utils/common' | |
| 31 | +import gsap from 'gsap' | |
| 32 | +import { flyArc } from '../Utils/arc' | |
| 33 | +import earthVertex from '../shaders/earth/vertex.vs?raw' | |
| 34 | +import earthFragment from '../shaders/earth/fragment.fs?raw' | |
| 35 | + | |
| 36 | +export type punctuation = { | |
| 37 | + circleColor: number | |
| 38 | + lightColumn: { | |
| 39 | + startColor: number // 起点颜色 | |
| 40 | + endColor: number // 终点颜色 | |
| 41 | + } | |
| 42 | +} | |
| 43 | + | |
| 44 | +type options = { | |
| 45 | + data: { | |
| 46 | + startArray: { | |
| 47 | + name: string | |
| 48 | + E: number // 经度 | |
| 49 | + N: number // 维度 | |
| 50 | + } | |
| 51 | + endArray: { | |
| 52 | + name: string | |
| 53 | + E: number // 经度 | |
| 54 | + N: number // 维度 | |
| 55 | + }[] | |
| 56 | + }[] | |
| 57 | + dom: HTMLElement | |
| 58 | + textures: Record<string, Texture> // 贴图 | |
| 59 | + earth: { | |
| 60 | + radius: number // 地球半径 | |
| 61 | + rotateSpeed: number // 地球旋转速度 | |
| 62 | + isRotation: boolean // 地球组是否自转 | |
| 63 | + } | |
| 64 | + satellite: { | |
| 65 | + show: boolean // 是否显示卫星 | |
| 66 | + rotateSpeed: number // 旋转速度 | |
| 67 | + size: number // 卫星大小 | |
| 68 | + number: number // 一个圆环几个球 | |
| 69 | + } | |
| 70 | + punctuation: punctuation | |
| 71 | + flyLine: { | |
| 72 | + color: number // 飞线的颜色 | |
| 73 | + speed: number // 飞机拖尾线速度 | |
| 74 | + flyLineColor: number // 飞行线的颜色 | |
| 75 | + } | |
| 76 | +} | |
| 77 | +type uniforms = { | |
| 78 | + glowColor: { value: Color } | |
| 79 | + scale: { type: string; value: number } | |
| 80 | + bias: { type: string; value: number } | |
| 81 | + power: { type: string; value: number } | |
| 82 | + time: { type: string; value: any } | |
| 83 | + isHover: { value: boolean } | |
| 84 | + map: { value?: Texture } | |
| 85 | +} | |
| 86 | + | |
| 87 | +export default class earth { | |
| 88 | + public group: Group | |
| 89 | + public earthGroup: Group | |
| 90 | + | |
| 91 | + public around!: BufferGeometry | |
| 92 | + public aroundPoints!: Points<BufferGeometry, PointsMaterial> | |
| 93 | + | |
| 94 | + public options: options | |
| 95 | + public uniforms: uniforms | |
| 96 | + public timeValue: number | |
| 97 | + | |
| 98 | + public earth!: Mesh<SphereGeometry, ShaderMaterial> | |
| 99 | + public punctuationMaterial!: MeshBasicMaterial | |
| 100 | + public markupPoint: Group | |
| 101 | + public waveMeshArr: Object3D[] | |
| 102 | + | |
| 103 | + public circleLineList: any[] | |
| 104 | + public circleList: any[] | |
| 105 | + public x: number | |
| 106 | + public n: number | |
| 107 | + public isRotation: boolean | |
| 108 | + public flyLineArcGroup!: Group | |
| 109 | + | |
| 110 | + constructor(options: options) { | |
| 111 | + this.options = options | |
| 112 | + | |
| 113 | + this.group = new Group() | |
| 114 | + this.group.name = 'group' | |
| 115 | + this.group.scale.set(0, 0, 0) | |
| 116 | + this.earthGroup = new Group() | |
| 117 | + this.group.add(this.earthGroup) | |
| 118 | + this.earthGroup.name = 'EarthGroup' | |
| 119 | + | |
| 120 | + // 标注点效果 | |
| 121 | + this.markupPoint = new Group() | |
| 122 | + this.markupPoint.name = 'markupPoint' | |
| 123 | + this.waveMeshArr = [] | |
| 124 | + | |
| 125 | + // 卫星和标签 | |
| 126 | + this.circleLineList = [] | |
| 127 | + this.circleList = [] | |
| 128 | + this.x = 0 | |
| 129 | + this.n = 0 | |
| 130 | + | |
| 131 | + // 地球自转 | |
| 132 | + this.isRotation = this.options.earth.isRotation | |
| 133 | + | |
| 134 | + // 扫光动画 shader | |
| 135 | + this.timeValue = 200 | |
| 136 | + | |
| 137 | + this.uniforms = { | |
| 138 | + glowColor: { | |
| 139 | + value: new Color(0x0cd1eb) | |
| 140 | + }, | |
| 141 | + scale: { | |
| 142 | + type: 'f', | |
| 143 | + value: -1.0 | |
| 144 | + }, | |
| 145 | + bias: { | |
| 146 | + type: 'f', | |
| 147 | + value: 1.0 | |
| 148 | + }, | |
| 149 | + power: { | |
| 150 | + type: 'f', | |
| 151 | + value: 3.3 | |
| 152 | + }, | |
| 153 | + time: { | |
| 154 | + type: 'f', | |
| 155 | + value: this.timeValue | |
| 156 | + }, | |
| 157 | + isHover: { | |
| 158 | + value: false | |
| 159 | + }, | |
| 160 | + map: { | |
| 161 | + value: undefined | |
| 162 | + } | |
| 163 | + } | |
| 164 | + } | |
| 165 | + | |
| 166 | + async init(): Promise<void> { | |
| 167 | + return new Promise(resolve => { | |
| 168 | + const init = async () => { | |
| 169 | + this.createEarth() // 创建地球 | |
| 170 | + this.createEarthGlow() // 创建地球辉光 | |
| 171 | + this.createEarthAperture() // 创建地球的大气层 | |
| 172 | + await this.createMarkupPoint() // 创建柱状点位 | |
| 173 | + this.createAnimateCircle() // 创建环绕卫星 | |
| 174 | + this.createFlyLine() // 创建飞线 | |
| 175 | + this.show() | |
| 176 | + resolve() | |
| 177 | + } | |
| 178 | + init() | |
| 179 | + }) | |
| 180 | + } | |
| 181 | + | |
| 182 | + createEarth() { | |
| 183 | + const earth_geometry = new SphereGeometry(this.options.earth.radius, 50, 50) | |
| 184 | + const earth_border = new SphereGeometry(this.options.earth.radius + 10, 60, 60) | |
| 185 | + | |
| 186 | + const pointMaterial = new PointsMaterial({ | |
| 187 | + color: 0x81ffff, //设置颜色,默认 0xFFFFFF | |
| 188 | + transparent: true, | |
| 189 | + sizeAttenuation: true, | |
| 190 | + opacity: 0.1, | |
| 191 | + vertexColors: false, //定义材料是否使用顶点颜色,默认false ---如果该选项设置为true,则color属性失效 | |
| 192 | + size: 0.2 //定义粒子的大小。默认为1.0 | |
| 193 | + }) | |
| 194 | + const points = new Points(earth_border, pointMaterial) //将模型添加到场景 | |
| 195 | + | |
| 196 | + this.earthGroup.add(points) | |
| 197 | + | |
| 198 | + this.uniforms.map.value = this.options.textures.earth | |
| 199 | + | |
| 200 | + const earth_material = new ShaderMaterial({ | |
| 201 | + // wireframe:true, // 显示模型线条 | |
| 202 | + uniforms: this.uniforms as any, | |
| 203 | + vertexShader: earthVertex, | |
| 204 | + fragmentShader: earthFragment | |
| 205 | + }) | |
| 206 | + | |
| 207 | + earth_material.needsUpdate = true | |
| 208 | + this.earth = new Mesh(earth_geometry, earth_material) | |
| 209 | + this.earth.name = 'earth' | |
| 210 | + this.earthGroup.add(this.earth) | |
| 211 | + } | |
| 212 | + | |
| 213 | + createEarthGlow() { | |
| 214 | + const R = this.options.earth.radius //地球半径 | |
| 215 | + | |
| 216 | + // TextureLoader创建一个纹理加载器对象,可以加载图片作为纹理贴图 | |
| 217 | + const texture = this.options.textures.glow // 加载纹理贴图 | |
| 218 | + | |
| 219 | + // 创建精灵材质对象SpriteMaterial | |
| 220 | + const spriteMaterial = new SpriteMaterial({ | |
| 221 | + map: texture, // 设置精灵纹理贴图 | |
| 222 | + color: 0x4390d1, | |
| 223 | + transparent: true, //开启透明 | |
| 224 | + opacity: 0.7, // 可以通过透明度整体调节光圈 | |
| 225 | + depthWrite: false //禁止写入深度缓冲区数据 | |
| 226 | + }) | |
| 227 | + | |
| 228 | + // 创建表示地球光圈的精灵模型 | |
| 229 | + const sprite = new Sprite(spriteMaterial) | |
| 230 | + sprite.scale.set(R * 3.0, R * 3.0, 1) //适当缩放精灵 | |
| 231 | + this.earthGroup.add(sprite) | |
| 232 | + } | |
| 233 | + | |
| 234 | + createEarthAperture() { | |
| 235 | + const vertexShader = [ | |
| 236 | + 'varying vec3 vVertexWorldPosition;', | |
| 237 | + 'varying vec3 vVertexNormal;', | |
| 238 | + 'varying vec4 vFragColor;', | |
| 239 | + 'void main(){', | |
| 240 | + ' vVertexNormal = normalize(normalMatrix * normal);', //将法线转换到视图坐标系中 | |
| 241 | + ' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;', //将顶点转换到世界坐标系中 | |
| 242 | + ' // set gl_Position', | |
| 243 | + ' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);', | |
| 244 | + '}' | |
| 245 | + ].join('\n') | |
| 246 | + | |
| 247 | + //大气层效果 | |
| 248 | + const AeroSphere = { | |
| 249 | + uniforms: { | |
| 250 | + coeficient: { | |
| 251 | + type: 'f', | |
| 252 | + value: 1.0 | |
| 253 | + }, | |
| 254 | + power: { | |
| 255 | + type: 'f', | |
| 256 | + value: 3 | |
| 257 | + }, | |
| 258 | + glowColor: { | |
| 259 | + type: 'c', | |
| 260 | + value: new Color(0x4390d1) | |
| 261 | + } | |
| 262 | + }, | |
| 263 | + vertexShader: vertexShader, | |
| 264 | + fragmentShader: [ | |
| 265 | + 'uniform vec3 glowColor;', | |
| 266 | + 'uniform float coeficient;', | |
| 267 | + 'uniform float power;', | |
| 268 | + | |
| 269 | + 'varying vec3 vVertexNormal;', | |
| 270 | + 'varying vec3 vVertexWorldPosition;', | |
| 271 | + | |
| 272 | + 'varying vec4 vFragColor;', | |
| 273 | + | |
| 274 | + 'void main(){', | |
| 275 | + ' vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;', //世界坐标系中从相机位置到顶点位置的距离 | |
| 276 | + ' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;', //视图坐标系中从相机位置到顶点位置的距离 | |
| 277 | + ' viewCameraToVertex= normalize(viewCameraToVertex);', //规一化 | |
| 278 | + ' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);', | |
| 279 | + ' gl_FragColor = vec4(glowColor, intensity);', | |
| 280 | + '}' | |
| 281 | + ].join('\n') | |
| 282 | + } | |
| 283 | + //球体 辉光 大气层 | |
| 284 | + const material1 = new ShaderMaterial({ | |
| 285 | + uniforms: AeroSphere.uniforms, | |
| 286 | + vertexShader: AeroSphere.vertexShader, | |
| 287 | + fragmentShader: AeroSphere.fragmentShader, | |
| 288 | + blending: NormalBlending, | |
| 289 | + transparent: true, | |
| 290 | + depthWrite: false | |
| 291 | + }) | |
| 292 | + const sphere = new SphereGeometry(this.options.earth.radius + 0, 50, 50) | |
| 293 | + const mesh = new Mesh(sphere, material1) | |
| 294 | + this.earthGroup.add(mesh) | |
| 295 | + } | |
| 296 | + | |
| 297 | + async createMarkupPoint() { | |
| 298 | + await Promise.all( | |
| 299 | + this.options.data.map(async item => { | |
| 300 | + const radius = this.options.earth.radius | |
| 301 | + const lon = item.startArray.E //经度 | |
| 302 | + const lat = item.startArray.N //纬度 | |
| 303 | + | |
| 304 | + this.punctuationMaterial = new MeshBasicMaterial({ | |
| 305 | + color: this.options.punctuation.circleColor, | |
| 306 | + map: this.options.textures.label, | |
| 307 | + transparent: true, //使用背景透明的png贴图,注意开启透明计算 | |
| 308 | + depthWrite: false //禁止写入深度缓冲区数据 | |
| 309 | + }) | |
| 310 | + | |
| 311 | + const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面 | |
| 312 | + this.markupPoint.add(mesh) | |
| 313 | + const LightPillar = createLightPillar({ | |
| 314 | + radius: this.options.earth.radius, | |
| 315 | + lon, | |
| 316 | + lat, | |
| 317 | + index: 0, | |
| 318 | + textures: this.options.textures, | |
| 319 | + punctuation: this.options.punctuation | |
| 320 | + }) //光柱 | |
| 321 | + this.markupPoint.add(LightPillar) | |
| 322 | + const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈 | |
| 323 | + this.markupPoint.add(WaveMesh) | |
| 324 | + this.waveMeshArr.push(WaveMesh) | |
| 325 | + | |
| 326 | + await Promise.all( | |
| 327 | + item.endArray.map(obj => { | |
| 328 | + const lon = obj.E //经度 | |
| 329 | + const lat = obj.N //纬度 | |
| 330 | + const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面 | |
| 331 | + this.markupPoint.add(mesh) | |
| 332 | + const LightPillar = createLightPillar({ | |
| 333 | + radius: this.options.earth.radius, | |
| 334 | + lon, | |
| 335 | + lat, | |
| 336 | + index: 1, | |
| 337 | + textures: this.options.textures, | |
| 338 | + punctuation: this.options.punctuation | |
| 339 | + }) //光柱 | |
| 340 | + this.markupPoint.add(LightPillar) | |
| 341 | + const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈 | |
| 342 | + this.markupPoint.add(WaveMesh) | |
| 343 | + this.waveMeshArr.push(WaveMesh) | |
| 344 | + }) | |
| 345 | + ) | |
| 346 | + this.earthGroup.add(this.markupPoint) | |
| 347 | + }) | |
| 348 | + ) | |
| 349 | + } | |
| 350 | + | |
| 351 | + createAnimateCircle() { | |
| 352 | + // 创建 圆环 点 | |
| 353 | + const list = getCirclePoints({ | |
| 354 | + radius: this.options.earth.radius + 15, | |
| 355 | + number: 150, //切割数 | |
| 356 | + closed: true // 闭合 | |
| 357 | + }) | |
| 358 | + const mat = new MeshBasicMaterial({ | |
| 359 | + color: '#0c3172', | |
| 360 | + transparent: true, | |
| 361 | + opacity: 0.4, | |
| 362 | + side: DoubleSide | |
| 363 | + }) | |
| 364 | + const line = createAnimateLine({ | |
| 365 | + pointList: list, | |
| 366 | + material: mat, | |
| 367 | + number: 100, | |
| 368 | + radius: 0.1 | |
| 369 | + }) | |
| 370 | + this.earthGroup.add(line) | |
| 371 | + | |
| 372 | + // 在clone两条线出来 | |
| 373 | + const l2 = line.clone() | |
| 374 | + l2.scale.set(1.2, 1.2, 1.2) | |
| 375 | + l2.rotateZ(Math.PI / 6) | |
| 376 | + this.earthGroup.add(l2) | |
| 377 | + | |
| 378 | + const l3 = line.clone() | |
| 379 | + l3.scale.set(0.8, 0.8, 0.8) | |
| 380 | + l3.rotateZ(-Math.PI / 6) | |
| 381 | + this.earthGroup.add(l3) | |
| 382 | + | |
| 383 | + /** | |
| 384 | + * 旋转的球 | |
| 385 | + */ | |
| 386 | + const ball = new Mesh( | |
| 387 | + new SphereGeometry(this.options.satellite.size, 32, 32), | |
| 388 | + new MeshBasicMaterial({ | |
| 389 | + color: '#e0b187' // 745F4D | |
| 390 | + }) | |
| 391 | + ) | |
| 392 | + | |
| 393 | + const ball2 = new Mesh( | |
| 394 | + new SphereGeometry(this.options.satellite.size, 32, 32), | |
| 395 | + new MeshBasicMaterial({ | |
| 396 | + color: '#628fbb' // 324A62 | |
| 397 | + }) | |
| 398 | + ) | |
| 399 | + | |
| 400 | + const ball3 = new Mesh( | |
| 401 | + new SphereGeometry(this.options.satellite.size, 32, 32), | |
| 402 | + new MeshBasicMaterial({ | |
| 403 | + color: '#806bdf' //6D5AC4 | |
| 404 | + }) | |
| 405 | + ) | |
| 406 | + | |
| 407 | + this.circleLineList.push(line, l2, l3) | |
| 408 | + ball.name = ball2.name = ball3.name = '卫星' | |
| 409 | + | |
| 410 | + for (let i = 0; i < this.options.satellite.number; i++) { | |
| 411 | + const ball01 = ball.clone() | |
| 412 | + // 一根线上总共有几个球,根据数量平均分布一下 | |
| 413 | + const num = Math.floor(list.length / this.options.satellite.number) | |
| 414 | + ball01.position.set(list[num * (i + 1)][0] * 1, list[num * (i + 1)][1] * 1, list[num * (i + 1)][2] * 1) | |
| 415 | + line.add(ball01) | |
| 416 | + | |
| 417 | + const ball02 = ball2.clone() | |
| 418 | + const num02 = Math.floor(list.length / this.options.satellite.number) | |
| 419 | + ball02.position.set(list[num02 * (i + 1)][0] * 1, list[num02 * (i + 1)][1] * 1, list[num02 * (i + 1)][2] * 1) | |
| 420 | + l2.add(ball02) | |
| 421 | + | |
| 422 | + const ball03 = ball2.clone() | |
| 423 | + const num03 = Math.floor(list.length / this.options.satellite.number) | |
| 424 | + ball03.position.set(list[num03 * (i + 1)][0] * 1, list[num03 * (i + 1)][1] * 1, list[num03 * (i + 1)][2] * 1) | |
| 425 | + l3.add(ball03) | |
| 426 | + } | |
| 427 | + } | |
| 428 | + | |
| 429 | + createFlyLine() { | |
| 430 | + this.flyLineArcGroup = new Group() | |
| 431 | + this.flyLineArcGroup.userData['flyLineArray'] = [] | |
| 432 | + this.earthGroup.add(this.flyLineArcGroup) | |
| 433 | + this.options.data.forEach(cities => { | |
| 434 | + cities.endArray.forEach(item => { | |
| 435 | + // 调用函数flyArc绘制球面上任意两点之间飞线圆弧轨迹 | |
| 436 | + const arcline = flyArc( | |
| 437 | + this.options.earth.radius, | |
| 438 | + cities.startArray.E, | |
| 439 | + cities.startArray.N, | |
| 440 | + item.E, | |
| 441 | + item.N, | |
| 442 | + this.options.flyLine | |
| 443 | + ) | |
| 444 | + | |
| 445 | + this.flyLineArcGroup.add(arcline) // 飞线插入flyArcGroup中 | |
| 446 | + this.flyLineArcGroup.userData['flyLineArray'].push(arcline.userData['flyLine']) | |
| 447 | + }) | |
| 448 | + }) | |
| 449 | + } | |
| 450 | + | |
| 451 | + show() { | |
| 452 | + gsap.to(this.group.scale, { | |
| 453 | + x: 1, | |
| 454 | + y: 1, | |
| 455 | + z: 1, | |
| 456 | + duration: 2, | |
| 457 | + ease: 'Quadratic' | |
| 458 | + }) | |
| 459 | + } | |
| 460 | + | |
| 461 | + render() { | |
| 462 | + this.flyLineArcGroup?.userData['flyLineArray']?.forEach((fly: any) => { | |
| 463 | + fly.rotation.z += this.options.flyLine.speed // 调节飞线速度 | |
| 464 | + if (fly.rotation.z >= fly.flyEndAngle) fly.rotation.z = 0 | |
| 465 | + }) | |
| 466 | + | |
| 467 | + if (this.isRotation) { | |
| 468 | + this.earthGroup.rotation.y += this.options.earth.rotateSpeed | |
| 469 | + } | |
| 470 | + | |
| 471 | + this.circleLineList.forEach(e => { | |
| 472 | + e.rotateY(this.options.satellite.rotateSpeed) | |
| 473 | + }) | |
| 474 | + | |
| 475 | + this.uniforms.time.value = | |
| 476 | + this.uniforms.time.value < -this.timeValue ? this.timeValue : this.uniforms.time.value - 1 | |
| 477 | + | |
| 478 | + if (this.waveMeshArr.length) { | |
| 479 | + this.waveMeshArr.forEach((mesh: any) => { | |
| 480 | + mesh.userData['scale'] += 0.007 | |
| 481 | + mesh.scale.set( | |
| 482 | + mesh.userData['size'] * mesh.userData['scale'], | |
| 483 | + mesh.userData['size'] * mesh.userData['scale'], | |
| 484 | + mesh.userData['size'] * mesh.userData['scale'] | |
| 485 | + ) | |
| 486 | + if (mesh.userData['scale'] <= 1.5) { | |
| 487 | + (mesh.material as Material).opacity = (mesh.userData['scale'] - 1) * 2 //2等于1/(1.5-1.0),保证透明度在0~1之间变化 | |
| 488 | + } else if (mesh.userData['scale'] > 1.5 && mesh.userData['scale'] <= 2) { | |
| 489 | + (mesh.material as Material).opacity = 1 - (mesh.userData['scale'] - 1.5) * 2 //2等于1/(2.0-1.5) mesh缩放2倍对应0 缩放1.5被对应1 | |
| 490 | + } else { | |
| 491 | + mesh.userData['scale'] = 1 | |
| 492 | + } | |
| 493 | + }) | |
| 494 | + } | |
| 495 | + } | |
| 496 | +} | ... | ... |
| 1 | +/** | |
| 2 | + * 资源管理和加载 | |
| 3 | + */ | |
| 4 | +import { LoadingManager, Texture, TextureLoader } from 'three' | |
| 5 | +import { loadingStart, loadingFinish, loadingError } from '@/utils' | |
| 6 | +import { resources } from './Assets' | |
| 7 | +export class Resources { | |
| 8 | + private manager!: LoadingManager | |
| 9 | + private callback: () => void | |
| 10 | + private textureLoader!: InstanceType<typeof TextureLoader> | |
| 11 | + public textures: Record<string, Texture> | |
| 12 | + constructor(callback: () => void) { | |
| 13 | + this.callback = callback // 资源加载完成的回调 | |
| 14 | + this.textures = {} // 贴图对象 | |
| 15 | + this.setLoadingManager() | |
| 16 | + this.loadResources() | |
| 17 | + } | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * 管理加载状态 | |
| 21 | + */ | |
| 22 | + private setLoadingManager() { | |
| 23 | + this.manager = new LoadingManager() | |
| 24 | + // 开始加载 | |
| 25 | + this.manager.onStart = () => { | |
| 26 | + loadingStart() | |
| 27 | + } | |
| 28 | + // 加载完成 | |
| 29 | + this.manager.onLoad = () => { | |
| 30 | + this.callback() | |
| 31 | + } | |
| 32 | + // 正在进行中 | |
| 33 | + this.manager.onProgress = url => { | |
| 34 | + loadingFinish() | |
| 35 | + } | |
| 36 | + | |
| 37 | + this.manager.onError = url => { | |
| 38 | + loadingError() | |
| 39 | + window['$message'].error('数据加载失败,请刷新重试!') | |
| 40 | + } | |
| 41 | + } | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * 加载资源 | |
| 45 | + */ | |
| 46 | + private loadResources(): void { | |
| 47 | + this.textureLoader = new TextureLoader(this.manager) | |
| 48 | + resources.textures?.forEach(item => { | |
| 49 | + this.textureLoader.load(item.url, t => { | |
| 50 | + this.textures[item.name] = t | |
| 51 | + }) | |
| 52 | + }) | |
| 53 | + } | |
| 54 | +} | ... | ... |
| 1 | +import { MeshBasicMaterial, PerspectiveCamera, Scene, ShaderMaterial, WebGLRenderer } from 'three' | |
| 2 | +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' | |
| 3 | +// interfaces | |
| 4 | +import { IWord } from '../interfaces/IWord' | |
| 5 | +import { Basic } from './Basic' | |
| 6 | +import { Resources } from './Resources' | |
| 7 | +// earth | |
| 8 | +import Earth from './Earth' | |
| 9 | + | |
| 10 | +export default class World { | |
| 11 | + public basic: Basic | |
| 12 | + public scene: Scene | |
| 13 | + public camera: PerspectiveCamera | |
| 14 | + public renderer: WebGLRenderer | |
| 15 | + public controls: OrbitControls | |
| 16 | + public material!: ShaderMaterial | MeshBasicMaterial | |
| 17 | + public resources: Resources | |
| 18 | + public option: IWord | |
| 19 | + public earth!: Earth | |
| 20 | + | |
| 21 | + constructor(option: IWord) { | |
| 22 | + /** | |
| 23 | + * 加载资源 | |
| 24 | + */ | |
| 25 | + this.option = option | |
| 26 | + this.basic = new Basic(option.dom) | |
| 27 | + this.scene = this.basic.scene | |
| 28 | + this.renderer = this.basic.renderer | |
| 29 | + this.controls = this.basic.controls | |
| 30 | + this.camera = this.basic.camera | |
| 31 | + this.updateSize() | |
| 32 | + this.resources = new Resources(async () => { | |
| 33 | + await this.createEarth() | |
| 34 | + // 开始渲染 | |
| 35 | + this.render() | |
| 36 | + }) | |
| 37 | + } | |
| 38 | + | |
| 39 | + async createEarth(data?: any) { | |
| 40 | + // 资源加载完成,开始制作地球,注释在new Earth()类型里面 | |
| 41 | + this.earth = new Earth({ | |
| 42 | + data: data || this.option.data, | |
| 43 | + dom: this.option.dom, | |
| 44 | + textures: this.resources.textures, | |
| 45 | + earth: { | |
| 46 | + radius: 50, | |
| 47 | + rotateSpeed: 0.002, | |
| 48 | + isRotation: true | |
| 49 | + }, | |
| 50 | + satellite: { | |
| 51 | + show: true, | |
| 52 | + rotateSpeed: -0.01, | |
| 53 | + size: 1, | |
| 54 | + number: 2 | |
| 55 | + }, | |
| 56 | + punctuation: { | |
| 57 | + circleColor: 0x3892ff, | |
| 58 | + lightColumn: { | |
| 59 | + startColor: 0xe4007f, // 起点颜色 | |
| 60 | + endColor: 0xffffff // 终点颜色 | |
| 61 | + } | |
| 62 | + }, | |
| 63 | + flyLine: { | |
| 64 | + color: 0xf3ae76, // 飞线的颜色 | |
| 65 | + flyLineColor: 0xff7714, // 飞行线的颜色 | |
| 66 | + speed: 0.004 // 拖尾飞线的速度 | |
| 67 | + } | |
| 68 | + }) | |
| 69 | + | |
| 70 | + this.scene.add(this.earth.group) | |
| 71 | + await this.earth.init() | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * 渲染函数 | |
| 76 | + */ | |
| 77 | + public render() { | |
| 78 | + requestAnimationFrame(this.render.bind(this)) | |
| 79 | + this.renderer.render(this.scene, this.camera) | |
| 80 | + this.controls && this.controls.update() | |
| 81 | + this.earth && this.earth.render() | |
| 82 | + } | |
| 83 | + | |
| 84 | + // 更新 | |
| 85 | + public updateSize(width?: number, height?: number) { | |
| 86 | + let w = width || this.option.width | |
| 87 | + let h = height || this.option.height | |
| 88 | + // 取小值 | |
| 89 | + if (w < h) h = w | |
| 90 | + else w = h | |
| 91 | + | |
| 92 | + this.renderer.setSize(w, h) | |
| 93 | + this.camera.aspect = w / h | |
| 94 | + this.camera.updateProjectionMatrix() | |
| 95 | + } | |
| 96 | + | |
| 97 | + // 数据更新重新渲染 | |
| 98 | + public updateData(data?: any) { | |
| 99 | + if (!this.earth.group) return | |
| 100 | + // 先删除旧的 | |
| 101 | + this.scene.remove(this.earth.group) | |
| 102 | + // 递归遍历组对象group释放所有后代网格模型绑定几何体占用内存 | |
| 103 | + this.earth.group.traverse((obj: any) => { | |
| 104 | + if (obj.type === 'Mesh') { | |
| 105 | + obj.geometry.dispose() | |
| 106 | + obj.material.dispose() | |
| 107 | + } | |
| 108 | + }) | |
| 109 | + // 重新创建 | |
| 110 | + this.createEarth(data) | |
| 111 | + } | |
| 112 | +} | ... | ... |
| 1 | +import { PublicConfigClass } from '@/packages/public' | |
| 2 | +import { CreateComponentType } from '@/packages/index.d' | |
| 3 | +import { chartInitConfig } from '@/settings/designSetting' | |
| 4 | +import { ThreeEarth01Config } from './index' | |
| 5 | +import dataJson from './data.json' | |
| 6 | +import cloneDeep from 'lodash/cloneDeep' | |
| 7 | + | |
| 8 | +export const option = { | |
| 9 | + dataset: dataJson | |
| 10 | +} | |
| 11 | + | |
| 12 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
| 13 | + public key = ThreeEarth01Config.key | |
| 14 | + public attr = { ...chartInitConfig, w: 800, h: 800, zIndex: -1 } | |
| 15 | + public chartConfig = cloneDeep(ThreeEarth01Config) | |
| 16 | + public option = cloneDeep(option) | |
| 17 | +} | ... | ... |
| 1 | +<template></template> | |
| 2 | + | |
| 3 | +<script setup lang="ts"> | |
| 4 | +import { PropType } from 'vue' | |
| 5 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
| 6 | +import { option } from './config.vue' | |
| 7 | + | |
| 8 | +const props = defineProps({ | |
| 9 | + optionData: { | |
| 10 | + type: Object as PropType<typeof option>, | |
| 11 | + required: true | |
| 12 | + } | |
| 13 | +}) | |
| 14 | +</script> | ... | ... |
| 1 | +[ | |
| 2 | + { | |
| 3 | + "startArray": { | |
| 4 | + "name": "杭州", | |
| 5 | + "N": 30.246026, | |
| 6 | + "E": 120.210792 | |
| 7 | + }, | |
| 8 | + "endArray": [ | |
| 9 | + { | |
| 10 | + "name": "曼谷", | |
| 11 | + "N": 22, | |
| 12 | + "E": 100.49074172973633 | |
| 13 | + }, | |
| 14 | + { | |
| 15 | + "name": "澳大利亚", | |
| 16 | + "N": -23.68477416688374, | |
| 17 | + "E": 133.857421875 | |
| 18 | + }, | |
| 19 | + | |
| 20 | + { | |
| 21 | + "name": "新疆维吾尔自治区", | |
| 22 | + "N": 41.748, | |
| 23 | + "E": 84.9023 | |
| 24 | + }, | |
| 25 | + | |
| 26 | + { | |
| 27 | + "name": "德黑兰", | |
| 28 | + "N": 35, | |
| 29 | + "E": 51 | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "德黑兰", | |
| 33 | + "N": 35, | |
| 34 | + "E": 51 | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "美国", | |
| 38 | + "N": 34.125447565116126, | |
| 39 | + "E": 241.7431640625 | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "英国", | |
| 43 | + "N": 51.508742458803326, | |
| 44 | + "E": 359.82421875 | |
| 45 | + }, | |
| 46 | + { | |
| 47 | + "name": "巴西", | |
| 48 | + "N": -9.96885060854611, | |
| 49 | + "E": 668.1445312499999 | |
| 50 | + } | |
| 51 | + ] | |
| 52 | + }, | |
| 53 | + { | |
| 54 | + "startArray": { | |
| 55 | + "name": "北京", | |
| 56 | + "N": 39.89491, | |
| 57 | + "E": 116.322056 | |
| 58 | + }, | |
| 59 | + "endArray": [ | |
| 60 | + { | |
| 61 | + "name": "西藏", | |
| 62 | + "N": 29.660361, | |
| 63 | + "E": 91.132212 | |
| 64 | + }, | |
| 65 | + { | |
| 66 | + "name": "广西", | |
| 67 | + "N": 22.830824, | |
| 68 | + "E": 108.30616 | |
| 69 | + }, | |
| 70 | + | |
| 71 | + { | |
| 72 | + "name": "江西", | |
| 73 | + "N": 28.676493, | |
| 74 | + "E": 115.892151 | |
| 75 | + }, | |
| 76 | + | |
| 77 | + { | |
| 78 | + "name": "贵阳", | |
| 79 | + "N": 26.647661, | |
| 80 | + "E": 106.630153 | |
| 81 | + } | |
| 82 | + ] | |
| 83 | + } | |
| 84 | +] | ... | ... |
3.58 KB
12.7 KB
566 KB
29.3 KB
5.46 KB
504 KB
12.7 KB
src/packages/components/Artoons/ThreeDAnimations/ThreeEarth01/images/earth/light_column.png
0 → 100644
4.56 KB
42.1 KB
| 1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
| 2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
| 3 | + | |
| 4 | +export const ThreeEarth01Config: ConfigType = { | |
| 5 | + key: 'ThreeEarth01', | |
| 6 | + chartKey: 'VThreeEarth01', | |
| 7 | + conKey: 'VCThreeEarth01', | |
| 8 | + title: '三维地球', | |
| 9 | + category: ChatCategoryEnum.THREEDANIMATION, | |
| 10 | + categoryName: ChatCategoryEnumName.THREEDANIMATION, | |
| 11 | + package: PackagesCategoryEnum.ARTOONS, | |
| 12 | + chartFrame: ChartFrameEnum.STATIC, | |
| 13 | + image: 'threeEarth01.png' | |
| 14 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <div ref="chartRef"></div> | |
| 3 | +</template> | |
| 4 | + | |
| 5 | +<script setup lang="ts"> | |
| 6 | +import { onMounted, PropType, ref, toRefs, watch } from 'vue' | |
| 7 | +import { CreateComponentType } from '@/packages' | |
| 8 | +import { useChartDataFetch } from '@/hooks' | |
| 9 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
| 10 | +import { option } from './config.vue' | |
| 11 | +import World from './code/world/Word' | |
| 12 | +import throttle from 'lodash/throttle' | |
| 13 | + | |
| 14 | +const props = defineProps({ | |
| 15 | + chartConfig: { | |
| 16 | + type: Object as PropType<CreateComponentType & typeof option>, | |
| 17 | + required: true | |
| 18 | + } | |
| 19 | +}) | |
| 20 | + | |
| 21 | +const chartEditStore = useChartEditStore() | |
| 22 | + | |
| 23 | +const chartRef = ref<HTMLElement>() | |
| 24 | +const { w, h } = toRefs(props.chartConfig.attr) | |
| 25 | +let threeClassInstance: World | |
| 26 | + | |
| 27 | +// 初始化 | |
| 28 | +const init = () => { | |
| 29 | + const dom: HTMLElement | undefined = chartRef.value | |
| 30 | + if (dom) { | |
| 31 | + threeClassInstance = new World({ | |
| 32 | + dom: dom, | |
| 33 | + data: props.chartConfig.option.dataset, | |
| 34 | + width: w.value, | |
| 35 | + height: h.value | |
| 36 | + }) | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +const updateData = (data: any) => { | |
| 41 | + try { | |
| 42 | + threeClassInstance.updateData(data) | |
| 43 | + } catch (error) { | |
| 44 | + console.log(error) | |
| 45 | + } | |
| 46 | +} | |
| 47 | + | |
| 48 | +// 改变大小 | |
| 49 | +watch( | |
| 50 | + () => [w.value, h.value], | |
| 51 | + throttle(([newWidth], [newHeight]) => { | |
| 52 | + threeClassInstance.updateSize(newWidth, newHeight) | |
| 53 | + }, 100) | |
| 54 | +) | |
| 55 | + | |
| 56 | +watch( | |
| 57 | + () => props.chartConfig.option.dataset, | |
| 58 | + (newData: any) => { | |
| 59 | + updateData(newData) | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + deep: false | |
| 63 | + } | |
| 64 | +) | |
| 65 | + | |
| 66 | +// DOM 渲染之后进行初始化 | |
| 67 | +onMounted(() => { | |
| 68 | + try { | |
| 69 | + if (navigator.userAgent.indexOf('Chrome') < -1 || navigator.userAgent.indexOf('Edg') < -1) { | |
| 70 | + window['$message'].error('三维地图组件仅在【谷歌】浏览器上能正常展示!') | |
| 71 | + chartEditStore.removeComponentList(undefined, false) | |
| 72 | + return | |
| 73 | + } | |
| 74 | + init() | |
| 75 | + } catch (error) { | |
| 76 | + console.log(error) | |
| 77 | + } | |
| 78 | +}) | |
| 79 | + | |
| 80 | +useChartDataFetch(props.chartConfig, useChartEditStore, updateData) | |
| 81 | +</script> | ... | ... |
| 1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
| 2 | +import { CreateComponentType } from '@/packages/index.d' | |
| 3 | +import { WaterPoloConfig } from './index' | |
| 4 | +import cloneDeep from 'lodash/cloneDeep' | |
| 5 | + | |
| 6 | +export const shapes = [ | |
| 7 | + { | |
| 8 | + label: '圆形', | |
| 9 | + value: 'circle' | |
| 10 | + }, | |
| 11 | + { | |
| 12 | + label: '正方形', | |
| 13 | + value: 'rect' | |
| 14 | + }, | |
| 15 | + { | |
| 16 | + label: '带圆角的正方形', | |
| 17 | + value: 'roundRect' | |
| 18 | + }, | |
| 19 | + { | |
| 20 | + label: '正三角形', | |
| 21 | + value: 'triangle' | |
| 22 | + }, | |
| 23 | + { | |
| 24 | + label: '菱形', | |
| 25 | + value: 'diamond' | |
| 26 | + }, | |
| 27 | + { | |
| 28 | + label: '水滴', | |
| 29 | + value: 'pin' | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + label: '箭头', | |
| 33 | + value: 'arrow' | |
| 34 | + }, | |
| 35 | +] | |
| 36 | + | |
| 37 | +export const includes = [] | |
| 38 | + | |
| 39 | +export const option = { | |
| 40 | + dataset: 0.5, | |
| 41 | + series: [ | |
| 42 | + { | |
| 43 | + type: 'liquidFill', | |
| 44 | + shape: shapes[0].value, | |
| 45 | + radius: '90%', | |
| 46 | + data: [0], | |
| 47 | + center: ['50%', '50%'], | |
| 48 | + color: [ | |
| 49 | + { | |
| 50 | + type: 'linear', | |
| 51 | + x: 0, | |
| 52 | + y: 0, | |
| 53 | + x2: 0, | |
| 54 | + y2: 1, | |
| 55 | + colorStops: [ | |
| 56 | + { | |
| 57 | + offset: 0, | |
| 58 | + color: '#446bf5', | |
| 59 | + }, | |
| 60 | + { | |
| 61 | + offset: 1, | |
| 62 | + color: '#2ca3e2', | |
| 63 | + }, | |
| 64 | + ], | |
| 65 | + globalCoord: false, | |
| 66 | + }, | |
| 67 | + ], | |
| 68 | + backgroundStyle: { | |
| 69 | + borderWidth: 1, | |
| 70 | + color: 'rgba(51, 66, 127, 0.7)', | |
| 71 | + }, | |
| 72 | + label: { | |
| 73 | + normal: { | |
| 74 | + textStyle: { | |
| 75 | + fontSize: 50, | |
| 76 | + color: '#fff', | |
| 77 | + }, | |
| 78 | + }, | |
| 79 | + }, | |
| 80 | + outline: { | |
| 81 | + show: false, | |
| 82 | + borderDistance: 10, | |
| 83 | + itemStyle: { | |
| 84 | + borderWidth: 2, | |
| 85 | + borderColor: '#112165' | |
| 86 | + } | |
| 87 | + } | |
| 88 | + } | |
| 89 | + ] | |
| 90 | +} | |
| 91 | + | |
| 92 | +export default class Config extends PublicConfigClass implements CreateComponentType | |
| 93 | +{ | |
| 94 | + public key = WaterPoloConfig.key | |
| 95 | + public chartConfig = cloneDeep(WaterPoloConfig) | |
| 96 | + public option = echartOptionProfixHandle(option, includes) | |
| 97 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <CollapseItem | |
| 3 | + v-for="(item, index) in seriesList" | |
| 4 | + :key="index" | |
| 5 | + name="水球" | |
| 6 | + :expanded="true" | |
| 7 | + > | |
| 8 | + <SettingItemBox name="内容"> | |
| 9 | + <SettingItem name="数值"> | |
| 10 | + <n-input-number | |
| 11 | + v-model:value="item.data[0]" | |
| 12 | + :min="0" | |
| 13 | + :step="0.01" | |
| 14 | + size="small" | |
| 15 | + placeholder="水球数值" | |
| 16 | + ></n-input-number> | |
| 17 | + </SettingItem> | |
| 18 | + <SettingItem name="形状"> | |
| 19 | + <n-select v-model:value="item.shape" :options="shapes" placeholder="选择形状" /> | |
| 20 | + </SettingItem> | |
| 21 | + <SettingItem name="文本"> | |
| 22 | + <n-input-number v-model:value="item.label.normal.textStyle.fontSize" :min="0" :step="1" size="small" placeholder="文字大小"> | |
| 23 | + </n-input-number> | |
| 24 | + </SettingItem> | |
| 25 | + <SettingItem name="颜色1"> | |
| 26 | + <n-color-picker | |
| 27 | + size="small" | |
| 28 | + :modes="['hex']" | |
| 29 | + v-model:value="item.color[0].colorStops[0].color" | |
| 30 | + ></n-color-picker> | |
| 31 | + </SettingItem> | |
| 32 | + <SettingItem name="颜色2"> | |
| 33 | + <n-color-picker | |
| 34 | + size="small" | |
| 35 | + :modes="['hex']" | |
| 36 | + v-model:value="item.color[0].colorStops[1].color" | |
| 37 | + ></n-color-picker> | |
| 38 | + </SettingItem> | |
| 39 | + </SettingItemBox> | |
| 40 | + <SettingItemBox name="背景" :alone="true"> | |
| 41 | + <SettingItem> | |
| 42 | + <n-color-picker | |
| 43 | + size="small" | |
| 44 | + :modes="['hex']" | |
| 45 | + v-model:value="item.backgroundStyle.color" | |
| 46 | + ></n-color-picker> | |
| 47 | + </SettingItem> | |
| 48 | + </SettingItemBox> | |
| 49 | + </CollapseItem> | |
| 50 | +</template> | |
| 51 | + | |
| 52 | +<script setup lang="ts"> | |
| 53 | +import { PropType, computed } from 'vue' | |
| 54 | +import { option, shapes } from './config' | |
| 55 | +import { | |
| 56 | + CollapseItem, | |
| 57 | + SettingItemBox, | |
| 58 | + SettingItem, | |
| 59 | +} from '@/components/Pages/ChartItemSetting' | |
| 60 | + | |
| 61 | +const props = defineProps({ | |
| 62 | + optionData: { | |
| 63 | + type: Object as PropType<typeof option>, | |
| 64 | + required: true, | |
| 65 | + }, | |
| 66 | +}) | |
| 67 | + | |
| 68 | +const seriesList = computed(() => { | |
| 69 | + return props.optionData.series | |
| 70 | +}) | |
| 71 | +</script> | ... | ... |
| 1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
| 2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
| 3 | + | |
| 4 | +export const WaterPoloConfig: ConfigType = { | |
| 5 | + key: 'WaterPolo', | |
| 6 | + chartKey: 'VWaterPolo', | |
| 7 | + conKey: 'VCWaterPolo', | |
| 8 | + title: '水球图', | |
| 9 | + category: ChatCategoryEnum.TWODANIMATION, | |
| 10 | + categoryName: ChatCategoryEnumName.TWODANIMATION, | |
| 11 | + package: PackagesCategoryEnum.ARTOONS, | |
| 12 | + chartFrame: ChartFrameEnum.COMMON, | |
| 13 | + image: 'water_WaterPolo.png' | |
| 14 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <v-chart :theme="themeColor" :option="option.value" autoresize></v-chart> | |
| 3 | +</template> | |
| 4 | + | |
| 5 | +<script setup lang="ts"> | |
| 6 | +import { PropType, watch, reactive } from 'vue' | |
| 7 | +import VChart from 'vue-echarts' | |
| 8 | +import { use } from 'echarts/core' | |
| 9 | +import 'echarts-liquidfill/src/liquidFill.js' | |
| 10 | +import { CanvasRenderer } from 'echarts/renderers' | |
| 11 | +import { GridComponent } from 'echarts/components' | |
| 12 | +import config from './config' | |
| 13 | +import { isPreview, isString, isNumber } from '@/utils' | |
| 14 | +import { chartColorsSearch, defaultTheme } from '@/settings/chartThemes/index' | |
| 15 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
| 16 | +import { useChartDataFetch } from '@/hooks' | |
| 17 | + | |
| 18 | +const props = defineProps({ | |
| 19 | + themeSetting: { | |
| 20 | + type: Object, | |
| 21 | + required: true | |
| 22 | + }, | |
| 23 | + themeColor: { | |
| 24 | + type: Object, | |
| 25 | + required: true | |
| 26 | + }, | |
| 27 | + chartConfig: { | |
| 28 | + type: Object as PropType<config>, | |
| 29 | + required: true | |
| 30 | + } | |
| 31 | +}) | |
| 32 | + | |
| 33 | +use([CanvasRenderer, GridComponent]) | |
| 34 | + | |
| 35 | +const chartEditStore = useChartEditStore() | |
| 36 | + | |
| 37 | +const option = reactive({ | |
| 38 | + value: {} | |
| 39 | +}) | |
| 40 | + | |
| 41 | +// 渐变色处理 | |
| 42 | +watch( | |
| 43 | + () => chartEditStore.getEditCanvasConfig.chartThemeColor, | |
| 44 | + (newColor: keyof typeof chartColorsSearch) => { | |
| 45 | + try { | |
| 46 | + if (!isPreview()) { | |
| 47 | + const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme] | |
| 48 | + // 背景颜色 | |
| 49 | + props.chartConfig.option.series[0].backgroundStyle.color = themeColor[2] | |
| 50 | + // 水球颜色 | |
| 51 | + props.chartConfig.option.series[0].color[0].colorStops = [ | |
| 52 | + { | |
| 53 | + offset: 0, | |
| 54 | + color: themeColor[0] | |
| 55 | + }, | |
| 56 | + { | |
| 57 | + offset: 1, | |
| 58 | + color: themeColor[1] | |
| 59 | + } | |
| 60 | + ] | |
| 61 | + } | |
| 62 | + option.value = props.chartConfig.option | |
| 63 | + } catch (error) { | |
| 64 | + console.log(error) | |
| 65 | + } | |
| 66 | + }, | |
| 67 | + { | |
| 68 | + immediate: true | |
| 69 | + } | |
| 70 | +) | |
| 71 | + | |
| 72 | +// 数据处理 | |
| 73 | +const dataHandle = (newData: number | string) => { | |
| 74 | + newData = isString(newData) ? parseFloat(newData) : newData | |
| 75 | + return parseFloat(newData.toFixed(2)) | |
| 76 | +} | |
| 77 | + | |
| 78 | +// 编辑 | |
| 79 | +watch( | |
| 80 | + () => props.chartConfig.option.dataset, | |
| 81 | + newData => { | |
| 82 | + if (!isString(newData) && !isNumber(newData)) return | |
| 83 | + props.chartConfig.option.series[0].data = [dataHandle(newData)] | |
| 84 | + option.value = props.chartConfig.option | |
| 85 | + }, | |
| 86 | + { | |
| 87 | + immediate: true, | |
| 88 | + deep: false | |
| 89 | + } | |
| 90 | +) | |
| 91 | + | |
| 92 | +// 预览 | |
| 93 | +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: number) => { | |
| 94 | + // @ts-ignore | |
| 95 | + option.value.series[0].data = [dataHandle(newData)] | |
| 96 | +}) | |
| 97 | +</script> | ... | ... |
src/packages/components/Artoons/index.d.ts
0 → 100644
| 1 | +export enum ChatCategoryEnum { | |
| 2 | + TWODANIMATION = 'TwoDAnimations', | |
| 3 | + | |
| 4 | + THREEDANIMATION = 'ThreeDAnimations', | |
| 5 | + TITLE = 'Titles', | |
| 6 | + MORE = 'Mores', | |
| 7 | + | |
| 8 | +} | |
| 9 | + | |
| 10 | +export enum ChatCategoryEnumName { | |
| 11 | + TITLE = '标题', | |
| 12 | + TWODANIMATION = '2D动画', | |
| 13 | + THREEDANIMATION = '3D动画', | |
| 14 | + | |
| 15 | + MORE = '更多', | |
| 16 | +} | ... | ... |
src/packages/components/Artoons/index.ts
0 → 100644
| 1 | +import { PublicConfigClass } from '@/packages/public' | |
| 2 | +import { CreateComponentType } from '@/packages/index.d' | |
| 3 | +import { LeftCenterRightHeadConfig } from './index' | |
| 4 | +import cloneDeep from 'lodash/cloneDeep' | |
| 5 | + | |
| 6 | +export const option = { | |
| 7 | + dataset: '我是渐变文本', | |
| 8 | + size: 20, | |
| 9 | + gradient: { | |
| 10 | + from: '#0000FFFF', | |
| 11 | + to: '#00FF00FF', | |
| 12 | + deg: 45 | |
| 13 | + } | |
| 14 | +} | |
| 15 | + | |
| 16 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
| 17 | + public key = LeftCenterRightHeadConfig.key | |
| 18 | + public chartConfig = cloneDeep(LeftCenterRightHeadConfig) | |
| 19 | + public option = cloneDeep(option) | |
| 20 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <collapse-item name="信息" :expanded="true"> | |
| 3 | + <setting-item-box name="文字" :alone="true"> | |
| 4 | + <setting-item> | |
| 5 | + <n-input v-model:value="optionData.dataset" size="small"></n-input> | |
| 6 | + </setting-item> | |
| 7 | + </setting-item-box> | |
| 8 | + </collapse-item> | |
| 9 | + <collapse-item name="样式" :expanded="true"> | |
| 10 | + <setting-item-box name="文字"> | |
| 11 | + <setting-item name="字体大小"> | |
| 12 | + <n-input-number v-model:value="optionData.size" size="small" placeholder="字体大小"></n-input-number> | |
| 13 | + </setting-item> | |
| 14 | + </setting-item-box> | |
| 15 | + <setting-item-box name="渐变色参数"> | |
| 16 | + <setting-item name="起始值"> | |
| 17 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.gradient.from"></n-color-picker> | |
| 18 | + </setting-item> | |
| 19 | + <setting-item name="结束值"> | |
| 20 | + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.gradient.to"></n-color-picker> | |
| 21 | + </setting-item> | |
| 22 | + <setting-item name="偏移角度"> | |
| 23 | + <n-input-number v-model:value="optionData.gradient.deg" size="small" placeholder="颜色旋转"></n-input-number> | |
| 24 | + </setting-item> | |
| 25 | + </setting-item-box> | |
| 26 | + | |
| 27 | + </collapse-item> | |
| 28 | +</template> | |
| 29 | + | |
| 30 | +<script setup lang="ts"> | |
| 31 | +import { PropType } from 'vue' | |
| 32 | +import { option } from './config' | |
| 33 | +import { | |
| 34 | + CollapseItem, | |
| 35 | + SettingItemBox, | |
| 36 | + SettingItem | |
| 37 | +} from '@/components/Pages/ChartItemSetting' | |
| 38 | + | |
| 39 | +const props = defineProps({ | |
| 40 | + optionData: { | |
| 41 | + type: Object as PropType<typeof option>, | |
| 42 | + required: true | |
| 43 | + } | |
| 44 | +}) | |
| 45 | +</script> | ... | ... |
| 1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
| 2 | +import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d' | |
| 3 | + | |
| 4 | +export const LeftCenterRightHeadConfig: ConfigType = { | |
| 5 | + key: 'LeftCenterRightHead', | |
| 6 | + chartKey: 'VLeftCenterRightHead', | |
| 7 | + conKey: 'VCLeftCenterRightHead', | |
| 8 | + title: '左中右头部', | |
| 9 | + category: ChatCategoryEnum.HEADCOMBINATION, | |
| 10 | + categoryName: ChatCategoryEnumName.HEADCOMBINATION, | |
| 11 | + package: PackagesCategoryEnum.COMPOSES, | |
| 12 | + chartFrame: ChartFrameEnum.ECHARTS, | |
| 13 | + image: 'text_gradient.png' | |
| 14 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <div class="go-text-box"> | |
| 3 | + <n-gradient-text :size="size" :gradient="gradient"> | |
| 4 | + {{ option.dataset }} | |
| 5 | + </n-gradient-text> | |
| 6 | + </div> | |
| 7 | +</template> | |
| 8 | +<script setup lang="ts"> | |
| 9 | +import { PropType, toRefs, shallowReactive, watch } from 'vue' | |
| 10 | +import { CreateComponentType } from '@/packages/index.d' | |
| 11 | +import { useChartDataFetch } from '@/hooks' | |
| 12 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
| 13 | +import { option as configOption } from './config' | |
| 14 | + | |
| 15 | +const props = defineProps({ | |
| 16 | + chartConfig: { | |
| 17 | + type: Object as PropType<CreateComponentType>, | |
| 18 | + required: true | |
| 19 | + } | |
| 20 | +}) | |
| 21 | + | |
| 22 | +const option = shallowReactive({ | |
| 23 | + dataset: configOption.dataset | |
| 24 | +}) | |
| 25 | + | |
| 26 | +const { w, h } = toRefs(props.chartConfig.attr) | |
| 27 | +const { size, gradient } = toRefs(props.chartConfig.option) | |
| 28 | + | |
| 29 | +watch( | |
| 30 | + () => props.chartConfig.option.dataset, | |
| 31 | + (newData: any) => { | |
| 32 | + option.dataset = newData | |
| 33 | + }, | |
| 34 | + { | |
| 35 | + immediate: true, | |
| 36 | + deep: false | |
| 37 | + } | |
| 38 | +) | |
| 39 | + | |
| 40 | +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | |
| 41 | + option.dataset = newData | |
| 42 | +}) | |
| 43 | +</script> | |
| 44 | + | |
| 45 | +<style lang="scss" scoped> | |
| 46 | +@include go('text-box') { | |
| 47 | + display: flex; | |
| 48 | + align-items: center; | |
| 49 | + justify-content: center; | |
| 50 | + .n-gradient-text { | |
| 51 | + white-space: initial; | |
| 52 | + } | |
| 53 | +} | |
| 54 | +</style> | ... | ... |
| 1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | |
| 2 | +import { WordCloudConfig } from './index' | |
| 3 | +import { CreateComponentType } from '@/packages/index.d' | |
| 4 | +import cloneDeep from 'lodash/cloneDeep' | |
| 5 | +import dataJson from './data.json' | |
| 6 | + | |
| 7 | +export const includes = [] | |
| 8 | + | |
| 9 | +export const ShapeEnumList = [ | |
| 10 | + { label: '圆形', value: 'circle' }, | |
| 11 | + { label: '心形', value: 'cardioid' }, | |
| 12 | + { label: '钻石', value: 'diamond' }, | |
| 13 | + { label: '右三角形', value: 'triangle-forward' }, | |
| 14 | + { label: '三角形', value: 'triangle' }, | |
| 15 | + { label: '五边形', value: 'pentagon' }, | |
| 16 | + { label: '星星', value: 'star' } | |
| 17 | +] | |
| 18 | + | |
| 19 | +export const option = { | |
| 20 | + dataset: [...dataJson], | |
| 21 | + tooltip: {}, | |
| 22 | + series: [ | |
| 23 | + { | |
| 24 | + type: 'wordCloud', | |
| 25 | + | |
| 26 | + // “云”绘制的形状,可以是表示为回调函数,也可以是固定关键字。 | |
| 27 | + // 可用值有:circle|cardioid|diamond|triangle-forward|triangle|pentagon|star | |
| 28 | + shape: 'circle', | |
| 29 | + | |
| 30 | + // 白色区域将被排除在绘制文本之外的剪影图像。 | |
| 31 | + // 随着云的形状生长,形状选项将继续应用。 | |
| 32 | + // maskImage: maskImage, | |
| 33 | + | |
| 34 | + // Folllowing left/top/width/height/right/bottom are used for positioning the word cloud | |
| 35 | + // Default to be put in the center and has 75% x 80% size. | |
| 36 | + left: 'center', | |
| 37 | + top: 'center', | |
| 38 | + width: '70%', | |
| 39 | + height: '80%', | |
| 40 | + right: null, | |
| 41 | + bottom: null, | |
| 42 | + | |
| 43 | + // 文本大小范围,默认 [12,60] | |
| 44 | + sizeRange: [12, 60], | |
| 45 | + | |
| 46 | + // 文本旋转范围和程度的步骤。 文本将通过旋转步骤45在[-90,90]中随机旋转 | |
| 47 | + rotationRange: [0, 0], | |
| 48 | + rotationStep: 0, | |
| 49 | + | |
| 50 | + // size of the grid in pixels for marking the availability of the canvas | |
| 51 | + // 网格大小越大,单词之间的差距就越大。 | |
| 52 | + gridSize: 8, | |
| 53 | + | |
| 54 | + // 设置为true,以允许单词在画布之外部分地绘制。允许绘制大于画布的大小 | |
| 55 | + drawOutOfBound: false, | |
| 56 | + | |
| 57 | + // If perform layout animation. | |
| 58 | + // NOTE disable it will lead to UI blocking when there is lots of words. | |
| 59 | + layoutAnimation: true, | |
| 60 | + | |
| 61 | + // Global text style | |
| 62 | + textStyle: { | |
| 63 | + fontFamily: 'sans-serif', | |
| 64 | + fontWeight: 'bold' | |
| 65 | + // 颜色可以是回调功能或颜色字符串 | |
| 66 | + // color: function () { | |
| 67 | + // // 随机颜色 | |
| 68 | + // return ( | |
| 69 | + // 'rgb(' + | |
| 70 | + // [Math.round(Math.random() * 160), Math.round(Math.random() * 160), Math.round(Math.random() * 160)].join( | |
| 71 | + // ',' | |
| 72 | + // ) + | |
| 73 | + // ')' | |
| 74 | + // ) | |
| 75 | + // } | |
| 76 | + }, | |
| 77 | + emphasis: { | |
| 78 | + focus: 'self', | |
| 79 | + | |
| 80 | + textStyle: { | |
| 81 | + shadowBlur: 10, | |
| 82 | + shadowColor: '#333' | |
| 83 | + } | |
| 84 | + }, | |
| 85 | + data: [...dataJson] | |
| 86 | + } | |
| 87 | + ] | |
| 88 | +} | |
| 89 | + | |
| 90 | +export default class Config extends PublicConfigClass implements CreateComponentType { | |
| 91 | + public key = WordCloudConfig.key | |
| 92 | + public chartConfig = cloneDeep(WordCloudConfig) | |
| 93 | + // 图表配置项 | |
| 94 | + public option = echartOptionProfixHandle(option, includes) | |
| 95 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <collapse-item name="词云" expanded> | |
| 3 | + <setting-item-box name="形状"> | |
| 4 | + <setting-item> | |
| 5 | + <n-select v-model:value="optionData.series[0].shape" size="small" :options="ShapeEnumList" /> | |
| 6 | + </setting-item> | |
| 7 | + <setting-item> | |
| 8 | + <n-checkbox v-model:checked="optionData.series[0].drawOutOfBound" size="small">允许出边</n-checkbox> | |
| 9 | + </setting-item> | |
| 10 | + </setting-item-box> | |
| 11 | + | |
| 12 | + <setting-item-box name="布局"> | |
| 13 | + <setting-item name="宽度"> | |
| 14 | + <n-slider | |
| 15 | + v-model:value="series.width" | |
| 16 | + :min="0" | |
| 17 | + :max="100" | |
| 18 | + :format-tooltip="sliderFormatTooltip" | |
| 19 | + @update:value="updateWidth" | |
| 20 | + ></n-slider> | |
| 21 | + </setting-item> | |
| 22 | + <setting-item name="高度"> | |
| 23 | + <n-slider | |
| 24 | + v-model:value="series.height" | |
| 25 | + :min="0" | |
| 26 | + :max="100" | |
| 27 | + :format-tooltip="sliderFormatTooltip" | |
| 28 | + @update:value="updateHeight" | |
| 29 | + ></n-slider> | |
| 30 | + </setting-item> | |
| 31 | + </setting-item-box> | |
| 32 | + | |
| 33 | + <setting-item-box name="样式" alone> | |
| 34 | + <setting-item name="字体区间(最小/最大字体)"> | |
| 35 | + <n-slider v-model:value="optionData.series[0].sizeRange" range :step="1" :min="6" :max="100" /> | |
| 36 | + </setting-item> | |
| 37 | + <setting-item name="旋转角度"> | |
| 38 | + <n-slider v-model:value="series.rotationStep" :step="15" :min="0" :max="45" @update:value="updateRotation" /> | |
| 39 | + </setting-item> | |
| 40 | + </setting-item-box> | |
| 41 | + </collapse-item> | |
| 42 | +</template> | |
| 43 | + | |
| 44 | +<script setup lang="ts"> | |
| 45 | +import { PropType, computed } from 'vue' | |
| 46 | +import { option, ShapeEnumList } from './config' | |
| 47 | +// eslint-disable-next-line no-unused-vars | |
| 48 | +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | |
| 49 | + | |
| 50 | +const props = defineProps({ | |
| 51 | + optionData: { | |
| 52 | + type: Object as PropType<typeof option>, | |
| 53 | + required: true | |
| 54 | + } | |
| 55 | +}) | |
| 56 | + | |
| 57 | +const series = computed(() => { | |
| 58 | + const { width, height, rotationStep } = props.optionData.series[0] | |
| 59 | + return { | |
| 60 | + width: +width.replace('%', ''), | |
| 61 | + height: +height.replace('%', ''), | |
| 62 | + rotationStep | |
| 63 | + } | |
| 64 | +}) | |
| 65 | + | |
| 66 | +const sliderFormatTooltip = (v: number) => { | |
| 67 | + return `${v}%` | |
| 68 | +} | |
| 69 | + | |
| 70 | +const updateWidth = (value: number) => { | |
| 71 | + props.optionData.series[0].width = `${value}%` | |
| 72 | +} | |
| 73 | + | |
| 74 | +const updateHeight = (value: number) => { | |
| 75 | + props.optionData.series[0].height = `${value}%` | |
| 76 | +} | |
| 77 | + | |
| 78 | +const updateRotation = (value: number) => { | |
| 79 | + props.optionData.series[0].rotationStep = value | |
| 80 | + props.optionData.series[0].rotationRange = value === 0 ? [0, 0] : [-90, 90] | |
| 81 | +} | |
| 82 | +</script> | ... | ... |
| 1 | +[ | |
| 2 | + { | |
| 3 | + "name": "数据可视化", | |
| 4 | + "value": 8000, | |
| 5 | + "textStyle": { | |
| 6 | + "color": "#78fbb2" | |
| 7 | + }, | |
| 8 | + "emphasis": { | |
| 9 | + "textStyle": { | |
| 10 | + "color": "red" | |
| 11 | + } | |
| 12 | + } | |
| 13 | + }, | |
| 14 | + { | |
| 15 | + "name": "GO VIEW", | |
| 16 | + "value": 6181 | |
| 17 | + }, | |
| 18 | + { | |
| 19 | + "name": "低代码", | |
| 20 | + "value": 4386 | |
| 21 | + }, | |
| 22 | + { | |
| 23 | + "name": "Vue3", | |
| 24 | + "value": 4055 | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "TypeScript4", | |
| 28 | + "value": 2467 | |
| 29 | + }, | |
| 30 | + { | |
| 31 | + "name": "Vite2", | |
| 32 | + "value": 2244 | |
| 33 | + }, | |
| 34 | + { | |
| 35 | + "name": "NaiveUI", | |
| 36 | + "value": 1898 | |
| 37 | + }, | |
| 38 | + { | |
| 39 | + "name": "ECharts5", | |
| 40 | + "value": 1484 | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + "name": "Axios", | |
| 44 | + "value": 1112 | |
| 45 | + }, | |
| 46 | + { | |
| 47 | + "name": "Pinia2", | |
| 48 | + "value": 965 | |
| 49 | + }, | |
| 50 | + { | |
| 51 | + "name": "PlopJS", | |
| 52 | + "value": 847 | |
| 53 | + }, | |
| 54 | + { | |
| 55 | + "name": "sfc", | |
| 56 | + "value": 582 | |
| 57 | + }, | |
| 58 | + { | |
| 59 | + "name": "SCSS", | |
| 60 | + "value": 555 | |
| 61 | + }, | |
| 62 | + { | |
| 63 | + "name": "pnpm", | |
| 64 | + "value": 550 | |
| 65 | + }, | |
| 66 | + { | |
| 67 | + "name": "eslint", | |
| 68 | + "value": 462 | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + "name": "json", | |
| 72 | + "value": 366 | |
| 73 | + }, | |
| 74 | + { | |
| 75 | + "name": "图表", | |
| 76 | + "value": 360 | |
| 77 | + }, | |
| 78 | + { | |
| 79 | + "name": "地图", | |
| 80 | + "value": 282 | |
| 81 | + }, | |
| 82 | + { | |
| 83 | + "name": "时钟", | |
| 84 | + "value": 273 | |
| 85 | + }, | |
| 86 | + { | |
| 87 | + "name": "标题", | |
| 88 | + "value": 265 | |
| 89 | + } | |
| 90 | +] | ... | ... |
| 1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | |
| 2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' | |
| 3 | + | |
| 4 | +export const WordCloudConfig: ConfigType = { | |
| 5 | + key: 'WordCloud', | |
| 6 | + chartKey: 'VWordCloud', | |
| 7 | + conKey: 'VCWordCloud', | |
| 8 | + title: '词云', | |
| 9 | + category: ChatCategoryEnum.MORE, | |
| 10 | + categoryName: ChatCategoryEnumName.MORE, | |
| 11 | + package: PackagesCategoryEnum.INFORMATIONS, | |
| 12 | + chartFrame: ChartFrameEnum.COMMON, | |
| 13 | + image: 'words_cloud.png' | |
| 14 | +} | ... | ... |
| 1 | +<template> | |
| 2 | + <v-chart | |
| 3 | + ref="vChartRef" | |
| 4 | + :theme="themeColor" | |
| 5 | + :option="option" | |
| 6 | + :manual-update="isPreview()" | |
| 7 | + :update-options="{ replaceMerge: replaceMergeArr }" | |
| 8 | + autoresize | |
| 9 | + ></v-chart> | |
| 10 | +</template> | |
| 11 | + | |
| 12 | +<script setup lang="ts"> | |
| 13 | +import { ref, computed, watch, PropType } from 'vue' | |
| 14 | +import VChart from 'vue-echarts' | |
| 15 | +import 'echarts-wordcloud' | |
| 16 | +import { use } from 'echarts/core' | |
| 17 | +import { CanvasRenderer } from 'echarts/renderers' | |
| 18 | +import config, { includes } from './config' | |
| 19 | +import { mergeTheme, setOption } from '@/packages/public/chart' | |
| 20 | +import { useChartDataFetch } from '@/hooks' | |
| 21 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | |
| 22 | +import { isPreview } from '@/utils' | |
| 23 | +import { GridComponent, TooltipComponent } from 'echarts/components' | |
| 24 | +import dataJson from './data.json' | |
| 25 | + | |
| 26 | +const props = defineProps({ | |
| 27 | + themeSetting: { | |
| 28 | + type: Object, | |
| 29 | + required: true | |
| 30 | + }, | |
| 31 | + themeColor: { | |
| 32 | + type: Object, | |
| 33 | + required: true | |
| 34 | + }, | |
| 35 | + chartConfig: { | |
| 36 | + type: Object as PropType<config>, | |
| 37 | + required: true | |
| 38 | + } | |
| 39 | +}) | |
| 40 | + | |
| 41 | +use([CanvasRenderer, GridComponent, TooltipComponent]) | |
| 42 | + | |
| 43 | +const replaceMergeArr = ref<string[]>() | |
| 44 | + | |
| 45 | +const option = computed(() => { | |
| 46 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | |
| 47 | +}) | |
| 48 | + | |
| 49 | +const dataSetHandle = (dataset: typeof dataJson) => { | |
| 50 | + try { | |
| 51 | + dataset && (props.chartConfig.option.series[0].data = dataset) | |
| 52 | + vChartRef.value && isPreview() && setOption(vChartRef.value, props.chartConfig.option) | |
| 53 | + } catch (error) { | |
| 54 | + console.log(error) | |
| 55 | + } | |
| 56 | +} | |
| 57 | + | |
| 58 | +// dataset 无法变更条数的补丁 | |
| 59 | +watch( | |
| 60 | + () => props.chartConfig.option.dataset, | |
| 61 | + newData => { | |
| 62 | + dataSetHandle(newData) | |
| 63 | + }, | |
| 64 | + { | |
| 65 | + deep: false | |
| 66 | + } | |
| 67 | +) | |
| 68 | + | |
| 69 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => { | |
| 70 | + dataSetHandle(newData) | |
| 71 | +}) | |
| 72 | +</script> | ... | ... |
src/packages/components/Composes/index.d.ts
0 → 100644
src/packages/components/Composes/index.ts
0 → 100644