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 | +} |
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 | +} |
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
@@ -5,6 +5,7 @@ | @@ -5,6 +5,7 @@ | ||
5 | v-show="hidePackageOneCategory" | 5 | v-show="hidePackageOneCategory" |
6 | class="chart-menu-width" | 6 | class="chart-menu-width" |
7 | v-model:value="selectValue" | 7 | v-model:value="selectValue" |
8 | + style="width: 80px;" | ||
8 | :options="packages.menuOptions" | 9 | :options="packages.menuOptions" |
9 | :icon-size="16" | 10 | :icon-size="16" |
10 | :indent="18" | 11 | :indent="18" |