Invent the third dimension. Update homepage screenshots.

This commit is contained in:
William Jeynes
2026-04-28 23:33:52 +01:00
parent 60aa4119ba
commit b872fbc5ec
9 changed files with 294 additions and 4 deletions
+3
View File
@@ -24,6 +24,9 @@ export function Home() {
<p className="text-center">
A great introduction to the dataset on a curated set of examples
</p>
<p className="text-center">
Also <a className="underline" href="#3d">available in 3D</a>
</p>
</div>
<div className="m-2 rounded-3xl w-full sm:w-[48%] bg-gray-200 p-10 flex flex-col items-center">
+111
View File
@@ -0,0 +1,111 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import ForceGraph3D from "react-force-graph-3d";
import data from "./data/data.json";
import titlesData from "./data/titles.json";
import HomeButton from "./utils/HomeButton";
import { FloatingPanel } from "./utils/FloatingPanel";
import { DetailsPanel } from "./utils/DetailsPanel";
import { FloatingPanelStack } from "./utils/FloatingPanelStack";
import { drawRoundedRect, getConnectedComponents } from "./graph/common";
import { buildGraph } from "./VizSmallConnected";
import * as THREE from 'three';
import SpriteText from 'three-spritetext';
export function VizSmall3D() {
const fgRef = useRef();
const [selectedNode, setSelectedNode] = useState(null);
const [minGraphSize, setMinGraphSize] = useState(10);
const [showLabel, setShowLabel] = useState(false);
const graphData = useMemo(() => {
const full = buildGraph(data);
const components = getConnectedComponents(full.nodes, full.links);
// keep only components large enough
const validIds = new Set(
components
.filter(comp => comp.length >= minGraphSize && comp.length < 50)
.flat()
);
const filteredNodes = full.nodes.filter(n => validIds.has(n.id));
const filteredLinks = full.links.filter(
l => validIds.has(l.source) && validIds.has(l.target)
);
return {
nodes: filteredNodes,
links: filteredLinks
};
}, [minGraphSize]);
return (
<div>
<HomeButton />
<ForceGraph3D
ref={fgRef}
graphData={graphData}
nodeLabel={(node) => node.label}
nodeAutoColorBy="type"
linkColor={() => "black"}
linkWidth={2.5}
onNodeClick={(node) => setSelectedNode(node)}
backgroundColor="white"
nodeThreeObject={(node) => {
const circle = new THREE.Mesh(
new THREE.SphereGeometry(node.members.length * 2),
new THREE.MeshLambertMaterial({
color: node.type.includes("claim") ? "DarkMagenta" : "green",
transparent: true,
opacity: 0.75
}));
if (!showLabel) {
return circle;
}
const group = new THREE.Group();
group.add(circle);
const text = new SpriteText(node.label);
text.textHeight = 8;
text.offsetY = -12;
text.color = "black";
group.add(text);
return group;
}
}
/>
<FloatingPanelStack>
<DetailsPanel selectedNode={selectedNode} data={data} />
<FloatingPanel title={"Config"}>
<label className="flex">
<span className="mr-1 grow">Show Labels</span>
<input
type="checkbox"
checked={showLabel}
onChange={() => setShowLabel(!showLabel)}
/>
</label>
<label>
Min connected graph size: <strong>{minGraphSize}</strong>
</label>
<br />
<input
type="range"
min="9"
max="20"
value={minGraphSize}
onChange={(e) => setMinGraphSize(Number(e.target.value))}
/>
</FloatingPanel>
</FloatingPanelStack>
</div>
);
}
+1 -1
View File
@@ -10,7 +10,7 @@ import { DetailsPanel } from "./utils/DetailsPanel";
import { FloatingPanelStack } from "./utils/FloatingPanelStack";
import { drawRoundedRect, getConnectedComponents } from "./graph/common";
function buildGraph(data) {
export function buildGraph(data) {
const nodes = [];
const links = [];
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="./index.css" type="text/css" rel="stylesheet" />
<title>Parcel React App</title>
<title>LLMs For Disinformation Analysis</title>
</head>
<body>
<div id="app"></div>
+2
View File
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { VizSmallConnected } from "./VizSmallConnected";
import { VizTimeFilter } from "./VizTimeFilter";
import { Home } from "./Home";
import { VizSmall3D } from "./VizSmall3D";
export function AppRouter() {
const [route, setRoute] = useState(() => window.location.hash);
@@ -19,6 +20,7 @@ export function AppRouter() {
if (route === "#small") return <VizSmallConnected />;
if (route === "#time") return <VizTimeFilter />;
if (route === "#3d") return <VizSmall3D />;
return <Home />;
}