main
reng 4 weeks ago
parent fc0bf216e1
commit 4093e6b056
  1. 2
      v2/app/src/components/graph.jsx
  2. 91
      v2/app/src/components/point.jsx
  3. 4
      v2/app/src/utils/draw.js
  4. 27
      v2/app/src/utils/parsing.js
  5. 2
      v2/scrapper/main.js

@ -68,7 +68,7 @@ export default function Graph({results}){
console.log("3D Points:", newPoints);
setPoints(newPoints);
generateLinesFromPoints(newPoints).then(newLines=>{
generateLinesFromPoints(newPoints, 10).then(newLines=>{
console.log("Lines:", newLines);
setLines(newLines);
});

@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { useFrame, useThree } from '@react-three/fiber';
import gsap from 'gsap';
import { normalize } from 'umap-js/dist/matrix';
export const PointScale=200;
@ -10,11 +11,12 @@ const PointSize=1;
const PointDuration=3;
const KeywordOffset=0;
const SceneDuration=10; //
const DEPTH_CONFIG = {
NEAR: 600, //
FAR: 1000, //
FADE_START: 400
NEAR: 1200, //
FAR: 1500, //
FADE_START: 800
};
@ -35,13 +37,14 @@ export default function Point({point, index, totalPoints, result, showContent, s
const meshRef=useRef();
const refText=useRef();
const { camera } = useThree();
const refAnimation=useRef({t:0});
function getRandomText(text){
if(contentType==PointContentType.Blur) return text.slice(0, textToShow) + '...';
const length=text.length;
const characters='░▒▓█';
const characters='*@#$%^&';
let result='';
for(let i=0; i<length; i++){
if(Math.random() < 0.8)
@ -101,7 +104,7 @@ export default function Point({point, index, totalPoints, result, showContent, s
// HTML
if (refText.current) {
refText.current.style.opacity = alpha;
refText.current.style.opacity *= alpha;
//
const finalHtmlScale = depthScaleFactor * (currentMeshScale / PointSize);
refText.current.style.transform = `translate(-50%, -50%) scale(${finalHtmlScale})`;
@ -114,51 +117,54 @@ export default function Point({point, index, totalPoints, result, showContent, s
refText.current.style.display = alpha <= 0.01 ? 'none' : 'block';
}
});
function setTextStyle(t){
if(refText.current){
refText.current.style.opacity=t;
refText.current.style.transform=`translate(-50%, -50%) scale(${t})`;
}
}
useEffect(()=>{
return;
// return;
if(!payload) return;
const lifeTime=payload.number/payload.total;
console.log("Point lifeTime:", lifeTime);
const lifeTime=payload.normalized_time || 0;
// console.log("Point lifeTime:", lifeTime);
// animate point size based on lifeTime
const targetSize=1;
const initialSize=targetSize;
const timeline=gsap.timeline({ repeat:-1});
timeline.set(meshRef.current.scale, {
x: initialSize, y: initialSize, z: initialSize,
});
timeline.set(refText.current, {
scale: initialSize,
opacity: 0,
});
timeline.fromTo(meshRef.current.scale, {
x: initialSize, y: initialSize, z: initialSize,
}, {
x: PointSize, y: PointSize, z: PointSize,
duration:PointDuration,
delay: lifeTime *PointDuration,
ease: "power3.in",
onUpdate:()=>{
if(refText.current){
refText.current.style.transform=`scale(${meshRef.current.scale.x / PointSize})`;
refText.current.style.opacity=`${meshRef.current.scale.x / PointSize}`;
}
timeline.fromTo(refAnimation.current,{t:0}, {
t:0,
onUpdate: ()=>{
setTextStyle(refAnimation.current.t);
}
});
timeline.to(meshRef.current.scale, {
x: targetSize, y: targetSize, z: targetSize,
duration:PointDuration,
ease: "power3.out",
onUpdate:()=>{
if(refText.current){
refText.current.style.transform=`scale(${meshRef.current.scale.x / PointSize})`;
refText.current.style.opacity=`${meshRef.current.scale.x / PointSize}`;
}
}, lifeTime * SceneDuration);
timeline.to(refAnimation.current,{
t: 1,
duration: PointDuration,
ease: 'power1.inOut',
onComplete: ()=>{
console.log("Show text for point:", index);
},
onUpdate: ()=>{
setTextStyle(refAnimation.current.t);
}
}); // lifeTime
timeline.to(refAnimation.current,{
t: 0,
duration: PointDuration,
onComplete: ()=>{
console.log("Hide text for point:", index);
},
onUpdate: ()=>{
setTextStyle(refAnimation.current.t);
}
});
});
// timeline.to(refAnimation.current,{t:0}, {
// duration: SceneDuration - (lifeTime * SceneDuration + PointDuration * 2),
// }); //
timeline.play();
@ -177,6 +183,7 @@ export default function Point({point, index, totalPoints, result, showContent, s
total: result.payload.total || '1',
content: JSON.parse(result.payload.metadata).text || '',
teaser: result.payload.teaser || '',
normalized_time: result.payload.normalized_time || 0,
});
},[point]);
@ -191,13 +198,13 @@ export default function Point({point, index, totalPoints, result, showContent, s
emissiveIntensity={0.5}
/>
<Html>
<div ref={refText} className='text-white p-2 w-[20vw] select-none translate-x-[-50%] translate-y-[-50%] text-center opacity-0'>
<div ref={refText} className='text-white p-2 select-none text-center opacity-0'>
{showKeyword && <div className='flex flex-row justify-center flex-wrap text-[1rem]'>
{payload?.keywords.map((el, index)=><span key={index} className='px-2' style={{transform: `translate(${Math.random() * KeywordOffset}px,${Math.random() * KeywordOffset}px)`}}>{el}</span>)}
</div>}
{showContent && <div
className='text-[2rem] whitespace-pre-wrap text-white rounded p-2 text-left'
style={{width: `${Math.random()*(payload?.content.length/5 || 10) + 20}vw`}}
className='text-[2rem] text-white rounded p-2 text-center w-[100vw]'
// style={{width: `${Math.random()*(payload?.content.length/5 || 10) + 20}vw`}}
dangerouslySetInnerHTML={{__html: getContent()}}
></div>}
</div>

@ -18,7 +18,7 @@ export async function get3dCoordinates(rawPoints) {
}
export async function generateLinesFromPoints(points){
export async function generateLinesFromPoints(points, maxDistance=300) {
if(!Array.isArray(points) || points.length<2){
return [];
}
@ -34,7 +34,7 @@ export async function generateLinesFromPoints(points){
Math.pow(end[1]-start[1], 2) +
Math.pow(end[2]-start[2], 2)
);
if(distance > 20) continue; // skip if too far apart
if(distance > maxDistance) continue; // skip if too far apart
lines.push([start.map(coord => coord * PointScale), end.map(coord => coord * PointScale)]);
}

@ -130,7 +130,10 @@ export async function searchByTheme(themeId, limit){
const data = await res.json();
console.log(data);
return await searchQdrant(data.result.vector, limit);
const allResults=await searchQdrant(data.result.vector, limit);
normalizeResultTime(allResults);
return allResults;
}
export async function searchByKeywords(keywordIds, limit){
@ -155,6 +158,8 @@ export async function searchByKeywords(keywordIds, limit){
allResults.push(...results);
}
normalizeResultTime(allResults);
return allResults;
}
@ -185,3 +190,23 @@ async function searchQdrant(query_embeddings, limit=10){
return data.result;
}
function normalizeResultTime(results){
let min, max;
results.forEach(item=>{
const time = JSON.parse(item.payload.metadata).published_on;
if(!min || time<min) min=time;
if(!max || time>max) max=time;
});
console.log("Time range:", min, max);
results.forEach(item=>{
const time = JSON.parse(item.payload.metadata).published_on;
item.payload.normalized_time = (time - min) / (max - min);
});
return results;
}

@ -53,7 +53,7 @@ const Keywords=[
const Version="v2-1";
const DEBUG_MODE=false;
const SCRAP_TYPE='TAG'; // 'KEYWORD' or 'TAG'
const SCRAP_TYPE='KEYWORD'; // 'KEYWORD' or 'TAG'
async function step1(){
// const chooseKeywords=['人生 妥協','邊界 被侵犯','卡住 職涯','尷尬 年齡','厭世 創作'];

Loading…
Cancel
Save