From f909a01d6264762b38d330ff6a57f86dfa8a0e7f Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Fri, 6 Sep 2024 15:57:41 +0200 Subject: [PATCH] Improve initial force-graph node positions Also increase performance of generating color map --- bun.lockb | Bin 440680 -> 404552 bytes .../routes/public/force-graph-client-lazy.tsx | 85 ++++++------------ web/package.json | 3 +- 3 files changed, 29 insertions(+), 59 deletions(-) diff --git a/bun.lockb b/bun.lockb index b6cbd8eb66f5749367737e4e1e1417b61d5b4a68..033042b27ffa78e3a4fca159ab68d26fae342e74 100755 GIT binary patch delta 23606 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 r+`kpcsG delta 45926 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 KcDU}Z`QhrhPH(=#OkG*;dAPSi;eGvozcDR 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 diff --git a/web/components/routes/public/force-graph-client-lazy.tsx b/web/components/routes/public/force-graph-client-lazy.tsx index 924b003b..21fc1f6c 100644 --- a/web/components/routes/public/force-graph-client-lazy.tsx +++ b/web/components/routes/public/force-graph-client-lazy.tsx @@ -2,7 +2,7 @@ import * as react from "react" import * as fg from "@nothing-but/force-graph" -import { ease, trig, raf } from "@nothing-but/utils" +import { ease, trig, raf, color } from "@nothing-but/utils" import * as schedule from "@/lib/utils/schedule" import * as canvas from "@/lib/utils/canvas" @@ -13,70 +13,37 @@ export type RawGraphNode = { connectedTopics: string[] } -type HSL = [hue: number, saturation: number, lightness: number] - -const COLORS: readonly HSL[] = [ +const COLORS: readonly color.HSL[] = [ [3, 86, 64], + [15, 87, 66], [31, 90, 69], - [15, 87, 66] + [15, 87, 66], + [31, 90, 69], + [344, 87, 70], ] -/* use a plain object instead of Map for faster lookups */ -type ColorMap = {[key: string]: string} -type HSLMap = Map +type ColorMap = Record -const MAX_COLOR_ITERATIONS = 10 +function generateColorMap(g: fg.graph.Graph): ColorMap { + const hsl_map: ColorMap = {} -/** - * Add a color to a node and all its connected nodes. - */ -function visitColorNode( - g: fg.graph.Graph, - prev: fg.graph.Node, - node: fg.graph.Node, - hsl_map: HSLMap, - add: HSL, - iteration: number = 1 -): void { - if (iteration > MAX_COLOR_ITERATIONS) return - - const color = hsl_map.get(node) - - if (!color) { - hsl_map.set(node, [...add]) - } else { - const add_strength = MAX_COLOR_ITERATIONS / iteration - color[0] = (color[0] + add[0] * add_strength) / (1 + add_strength) - color[1] = (color[1] + add[1] * add_strength) / (1 + add_strength) - color[2] = (color[2] + add[2] * add_strength) / (1 + add_strength) + for (let i = 0; i < g.nodes.length; i++) { + hsl_map[g.nodes[i].key as string] = COLORS[i % COLORS.length] } - for (let edge of g.edges) { - let b: fg.graph.Node - if (edge.a === node) b = edge.b - else if (edge.b === node) b = edge.a - else continue - if (b !== prev) { - visitColorNode(g, node, b, hsl_map, add, iteration + 1) - } - } -} + for (let {a, b} of g.edges) { -function generateColorMap(g: fg.graph.Graph, nodes: readonly fg.graph.Node[]): ColorMap { - const hls_map: HSLMap = new Map() + let a_hsl = hsl_map[a.key as string] + let b_hsl = hsl_map[b.key as string] - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]! - const color = COLORS[i % COLORS.length]! - visitColorNode(g, node, node, hls_map, color) + let am = a.mass-1 + let bm = b.mass-1 + + hsl_map[a.key as string] = color.mix(a_hsl, b_hsl, am*am*am, bm) + hsl_map[b.key as string] = color.mix(a_hsl, b_hsl, am, bm*bm*bm) } - const color_map: ColorMap = {} - for (const [node, [hue, saturation, lightness]] of hls_map.entries()) { - color_map[node.key as string] = `${hue} ${saturation}% ${lightness}%` - } - - return color_map + return hsl_map } function generateNodesFromRawData(g: fg.graph.Graph, raw_data: RawGraphNode[]): void { @@ -107,8 +74,6 @@ function generateNodesFromRawData(g: fg.graph.Graph, raw_data: RawGraphNode[]): let edges = fg.graph.get_node_edges(g, node) node.mass = fg.graph.node_mass_from_edges(edges.length) } - - fg.graph.randomize_positions(g) } function filterNodes( @@ -135,7 +100,7 @@ const GRAPH_OPTIONS: fg.graph.Options = { origin_strength: 0.01, repel_distance: 40, repel_strength: 2, - link_strength: 0.015, + link_strength: 0.03, grid_size: 500 } @@ -209,7 +174,7 @@ const drawGraph = (c: fg.canvas.CanvasState, color_map: ColorMap): void => { c.ctx.fillStyle = node.anchor || c.hovered_node === node ? `rgba(129, 140, 248, ${opacity})` - : `hsl(${color_map[node.key as string]} / ${opacity})` + : color.hsl_to_hsla_string(color_map[node.key as string], opacity) c.ctx.fillText(node.label, x, y) } @@ -250,10 +215,12 @@ function init( if (s.ctx == null) return generateNodesFromRawData(s.graph, raw_nodes) + fg.graph.set_positions_smart(s.graph) + s.nodes = s.graph.nodes.slice() s.edges = s.graph.edges.slice() - let color_map = generateColorMap(s.graph, s.nodes) + let color_map = generateColorMap(s.graph) let canvas_state = fg.canvas.canvasState({ ctx: s.ctx, @@ -270,6 +237,8 @@ function init( }) s.ro.observe(canvas_el) + simulateGraph(6, s.graph, canvas_state, window.innerWidth, window.innerHeight) + function loop(time: number) { let is_active = gestures.mode.type === fg.canvas.Mode.DraggingNode let iterations = Math.min(2, raf.calcIterations(s.frame_iter_limit, time)) diff --git a/web/package.json b/web/package.json index 9ddd9b66..70983899 100644 --- a/web/package.json +++ b/web/package.json @@ -13,7 +13,8 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@hookform/resolvers": "^3.9.0", - "@nothing-but/force-graph": "^0.8.3", + "@nothing-but/force-graph": "^0.9.3", + "@nothing-but/utils": "^0.16.0", "@omit/react-confirm-dialog": "^1.1.5", "@omit/react-fancy-switch": "^0.1.1", "@radix-ui/react-avatar": "^1.1.0",