-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathTimeSeries.tsx
More file actions
234 lines (211 loc) · 9.06 KB
/
TimeSeries.tsx
File metadata and controls
234 lines (211 loc) · 9.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import * as THREE from 'three'
// ! don't import things that are not used in the code, build will fail
// import { Canvas, createPortal, useThree } from '@react-three/fiber';
// import { Center, Html, Text } from '@react-three/drei'
import { Hud, OrthographicCamera, Text } from '@react-three/drei'
import { useMemo, useState, useEffect } from 'react';
import { GetTimeSeries } from './ZarrLoaderLRU';
import { useControls } from 'leva';
import vertexShader from '@/utils/shaders/LineVert.glsl'
interface timeSeriesLocs{
uv:THREE.Vector2;
normal:THREE.Vector3
}
interface DSInfo{
variable:string,
storePath:string
}
interface scaling{
maxVal:number,
minVal:number,
colormap:THREE.DataTexture
}
interface AxisLabels{
labels:number[]
positions:number[]
}
export function TimeSeries({timeSeriesLocs,DSInfo,scaling} : {timeSeriesLocs:timeSeriesLocs, DSInfo:DSInfo,scaling:scaling}){
//This function will take in some coords, get the timeseries from zarr loader and create a new THREE scene with a static camera. Need to create a graph basically
const {uv,normal} = timeSeriesLocs;
const {variable, storePath} = DSInfo;
const {maxVal,minVal,colormap} = scaling;
const [timeSeries, setTimeSeries] = useState<number[]>([0]);
// const [xLabls,setXLabels] = useState();
const [yLabels, setYLabels] = useState<AxisLabels>();
const verticalScale = 2;
const horizontalScale = 5;
const {width} = useControls({
width:{value:5,
min:1,
max:15,
step:1,
label:"Line Width"
}
})
const splineResolution = 3;
useEffect(() => {
if (uv && normal ) {
GetTimeSeries({ TimeSeriesObject: { uv, normal, variable, storePath } })
.then((data) => setTimeSeries(data.data as number[]));
}
}, [timeSeriesLocs, variable]);
const material = new THREE.ShaderMaterial({
glslVersion: THREE.GLSL3,
uniforms: {
cmap:{value: colormap},
width: { value: width},
aspect: {value : window.innerWidth / window.innerHeight},
thickness:{value:width/100},
miter:{value:1},
},
vertexShader,
fragmentShader:`
out vec4 Color;
uniform sampler2D cmap;
varying float vNormed;
void main() {
vec4 texColor = texture(cmap, vec2(vNormed, 0.1));
texColor.a = 1.;
Color = texColor;
}
`,
depthWrite: false,
});
const lineObj = useMemo(() => {
//Need to convert whatever timeseries is into vectors. Depends on the camera and scene zoom.
//Currently this creates a new one each time coords changes. Will need to fix later
const normed = timeSeries.map((i)=>(i-minVal)/(maxVal-minVal))
const size = timeSeries.length;
const vecs = [];
for (let i=0 ; i<size ; i++){
const x = i/size*horizontalScale-horizontalScale/2;
const y = (normed[i]-.5)*verticalScale;
vecs.push(new THREE.Vector2(x,y))
}
const curve = new THREE.SplineCurve(vecs)
const points = curve.getPoints(vecs.length*splineResolution)
const geometry = new THREE.BufferGeometry().setFromPoints( points );
const material = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth:5 });
const obj = new THREE.Line(geometry,material);
return obj;
},[timeSeries])
const geometry = useMemo(() => {
//Need to convert whatever timeseries is into vectors. Depends on the camera and scene zoom.
//Currently this creates a new one each time coords changes. Will need to fix later
const normed = timeSeries.map((i) => (i - minVal) / (maxVal - minVal));
const size = timeSeries.length;
const vecs = []
for (let i = 0; i < size; i++) {
const x = (i / (size - 1)) * horizontalScale - (horizontalScale /2);
const y = (normed[i] - 0.5) * verticalScale;
vecs.push(new THREE.Vector2(x,y)) // 3D points (z = 0 for 2D)
}
const curve = new THREE.SplineCurve(vecs)
const points = curve.getPoints(vecs.length*splineResolution)
const path = points.map((i)=>[i.x,i.y,0])
if (path.length < 2) return new THREE.BufferGeometry(); // Need at least 2 points
// Step 2: Duplicate vertices and compute attributes
const numPoints = path.length;
const positions = [];
const directions = [];
const previous = [];
const next = [];
const normValues = []
const indices = [];
for (let i = 0; i < numPoints; i++) {
const point = path[i];
const prevPoint = path[Math.max(0, i - 1)];
const nextPoint = path[Math.min(numPoints - 1, i + 1)];
// Duplicate vertices
positions.push(...point, ...point); // [x, y, z, x, y, z]
directions.push(1.0, -1.0);
previous.push(...prevPoint, ...prevPoint);
next.push(...nextPoint, ...nextPoint);
normValues.push(...Array(splineResolution*2).fill(normed[i]));
}
// Step 3: Create triangle indices
for (let i = 0; i < numPoints - 1; i++) {
const i0 = i * 2; // First vertex of current point (+1)
const i1 = i0 + 1; // Second vertex of current point (-1)
const i2 = i0 + 2; // First vertex of next point (+1)
const i3 = i0 + 3; // Second vertex of next point (-1)
indices.push(i0, i1, i2); // First triangle
indices.push(i1, i3, i2); // Second triangle
}
// Step 4: Create geometry
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('direction', new THREE.Float32BufferAttribute(directions, 1));
geometry.setAttribute('previous', new THREE.Float32BufferAttribute(previous, 3));
geometry.setAttribute('next', new THREE.Float32BufferAttribute(next, 3));
geometry.setAttribute('normed', new THREE.Float32BufferAttribute(normValues, 1));
geometry.setIndex(new THREE.Uint16BufferAttribute(indices, 1));
return geometry
},[timeSeries])
const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 5;
useEffect(()=>{
const xMin = 0;
const xMax = timeSeries.length;
const xPos = [-horizontalScale/2,-horizontalScale/4,0,horizontalScale/4,horizontalScale/2];
let yMin = (Math.ceil(minVal)-minVal)/(maxVal-minVal);
yMin = (yMin-0.5)*verticalScale
let yMax = (Math.floor(maxVal)-minVal)/(maxVal-minVal);
yMax = (yMax-0.5)*verticalScale
const step = (maxVal-minVal)/3;
let yLabels = Array.from({ length: 3 }, (_, i) => Math.round(minVal + step * i))
let yPos = yLabels.map(val => {
return (((val-minVal)/(maxVal-minVal))-0.5)*verticalScale
})
yPos = [yMin, ...yPos.slice(1), yMax]
yLabels = [Math.ceil(minVal),...yLabels.slice(1), Math.floor(maxVal)]
// console unused variables
console.log(xPos,xMin,xMax,yPos,yMin,yMax)
setYLabels({
positions:yPos,
labels:yLabels
})
},[maxVal,minVal,verticalScale])
return (
<>
<Hud renderPriority={1}>
<OrthographicCamera
makeDefault
left={(frustumSize * aspect) / -2}
right={(frustumSize * aspect) / 2}
top={frustumSize / 2}
bottom={frustumSize / -2}
near={0.1}
far={10}
position={[0, 0, 5]}
zoom={1}
/>
<group position={[0,-1,0]}>
<mesh geometry={geometry} material={material} />
<primitive object={lineObj} />
<mesh position={[-2.5,0,0]}>
<boxGeometry args={[.02,2,1]} />
<meshBasicMaterial color={'black'} />
</mesh>
<mesh position={[0,-1,0]}>
<boxGeometry args={[5,.02,1]} />
<meshBasicMaterial color={'black'} />
</mesh>
{yLabels && yLabels.labels.map((value,index)=>(
<Text
key={`y-${index}`}
position={[-2.6,yLabels.positions[index],0]}
color="black"
fontSize={.1}
anchorX="right"
anchorY="middle"
>
{/* Will need to revist this as labels may not always be numbers */}
{Math.round(value*100)/100}
</Text>
))}
</group>
</Hud>
</>
)
}