From 84f2f058dcbea3f49b96c8ea8664af00e94166e2 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Wed, 21 Jul 2021 06:34:25 +0200 Subject: [PATCH] make people a variable size based on how many people know them also show who knows them in ProfileCard also draw lines only until the edge of a node --- README.md | 7 +++- src/components/DataContainer.tsx | 9 ++++ src/components/PersonCard.tsx | 21 +++++++++- src/components/Visualization.tsx | 50 +++++++++++++++-------- src/components/VisualizationContainer.tsx | 39 ++++++++++++------ src/simulation/index.ts | 31 +++++++------- src/simulation/types.ts | 5 +-- 7 files changed, 109 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 1793124..c64a0c7 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,12 @@ ## TODO - [x] add people to history of browser (will allow browsing) -- [ ] make people a variable size based on how many people know them +- [x] make people a variable size based on how many people know them - [ ] show clearly what are the directions of :knows -- [ ] show also who knows this person +- [x] show also who knows this person - [ ] login for different pod providers - [ ] faster (parallel) crawling - [ ] search people +- [ ] highlight also people who know the person +- [ ] highlight people whose button is crawled in PersonList +- [ ] add custom starting point for crawling diff --git a/src/components/DataContainer.tsx b/src/components/DataContainer.tsx index 4acfb31..c680a88 100644 --- a/src/components/DataContainer.tsx +++ b/src/components/DataContainer.tsx @@ -15,6 +15,7 @@ export type Person = { photo: string status: 'success' | 'error' | 'pending' knows: Set + known?: Set } const DataContainer = ({ children }: Props) => { @@ -42,6 +43,14 @@ const DataContainer = ({ children }: Props) => { return BFSFriends([timbl, ...(info?.isLoggedIn ? [me] : [])], setPeople) }, [info]) + Object.values(people).forEach(({ uri, knows }) => { + knows.forEach(k => { + const person = people[k] + person.known = person.known ?? new Set() + person.known.add(uri) + }) + }) + return ( {children} ) diff --git a/src/components/PersonCard.tsx b/src/components/PersonCard.tsx index 9858781..0e760ea 100644 --- a/src/components/PersonCard.tsx +++ b/src/components/PersonCard.tsx @@ -4,10 +4,11 @@ import { Person } from './DataContainer' interface Props { person: Person knows: Person[] + known: Person[] onSelectPerson: (uri: string) => void } -const Statement = ({ person, knows, onSelectPerson }: Props) => { +const PersonCard = ({ person, knows, known, onSelectPerson }: Props) => { return (
{ ))} +
+

known by: {known.length}

+
+
+
    + {known.map(friend => ( +
  • onSelectPerson(friend.uri)} + key={friend.uri} + className="button is-link" + > + {friend.name} +
  • + ))} +
+
@@ -61,4 +78,4 @@ const Statement = ({ person, knows, onSelectPerson }: Props) => { ) } -export default Statement +export default PersonCard diff --git a/src/components/Visualization.tsx b/src/components/Visualization.tsx index 1505b4d..c6912cf 100644 --- a/src/components/Visualization.tsx +++ b/src/components/Visualization.tsx @@ -48,10 +48,22 @@ const Visualization: React.FC = ({ context.clearRect(-offset[0], -offset[1], width, height) drawGrid(context, grid, width, height, offset) graph.links.forEach(link => { + // we're counting a unit vector to make links that don't overlap nodes + // source point + const s = [link.source.x, link.source.y] + // target point + const t = [link.target.x, link.target.y] + // vector + const v = numeric.sub(t, s) + // vector size + const size = Math.sqrt(v[0] ** 2 + v[1] ** 2) + // unit vector + const i = numeric.div(v, size) drawLine( context, - [link.source.x, link.source.y], - [link.target.x, link.target.y], + // links don't overlap circles + numeric.add(s, numeric.mul(i, link.source.r)) as Vector, + numeric.sub(t, numeric.mul(i, link.target.r)) as Vector, { strokeStyle: 'white', lineWidth: 0.5, @@ -70,41 +82,43 @@ const Visualization: React.FC = ({ const rest = graph.nodes.filter(({ style }) => !style) // draw all the nodes which are not special - rest.forEach(({ x, y }) => - drawCircle(context, [x, y], 10, { fillStyle: '#fff8' }), + rest.forEach(({ x, y, r }) => + drawCircle(context, [x, y], r, { fillStyle: '#fff8' }), ) // draw errored nodes - errored.forEach(({ x, y }) => - drawCircle(context, [x, y], 10, { fillStyle: erroredColor }), + errored.forEach(({ x, y, r }) => + drawCircle(context, [x, y], r, { fillStyle: erroredColor }), ) // draw successed nodes - successed.forEach(({ x, y }) => - drawCircle(context, [x, y], 10, { fillStyle: successedColor }), + successed.forEach(({ x, y, r }) => + drawCircle(context, [x, y], r, { fillStyle: successedColor }), ) // draw text of all the above nodes - ;[...errored, ...successed, ...rest].forEach(({ x, y, label }) => - drawText(context, [x + 15, y], label, { fillStyle: '#fff4' }), + ;[...errored, ...successed, ...rest].forEach(({ x, y, r, label }) => + drawText(context, [x + r + 5, y], label, { fillStyle: '#fff4' }), ) // draw accented nodes - accented.forEach(({ x, y }) => - drawCircle(context, [x, y], 10, { fillStyle: accentedColor }), + accented.forEach(({ x, y, r }) => + drawCircle(context, [x, y], r, { fillStyle: accentedColor }), ) - accented.forEach(({ x, y, label }) => - drawText(context, [x + 15, y], label, { fillStyle: accentedColor }), + accented.forEach(({ x, y, r, label }) => + drawText(context, [x + r + 5, y], label, { + fillStyle: accentedColor, + }), ) // draw focused nodes - focused.forEach(({ x, y }) => - drawCircle(context, [x, y], 10, { fillStyle: focusedColor }), + focused.forEach(({ x, y, r }) => + drawCircle(context, [x, y], r, { fillStyle: focusedColor }), ) - focused.forEach(({ x, y, label }) => - drawText(context, [x + 15, y], label, { fillStyle: focusedColor }), + focused.forEach(({ x, y, r, label }) => + drawText(context, [x + r + 5, y], label, { fillStyle: focusedColor }), ) return () => context.restore() diff --git a/src/components/VisualizationContainer.tsx b/src/components/VisualizationContainer.tsx index cf2866a..1ed86f0 100644 --- a/src/components/VisualizationContainer.tsx +++ b/src/components/VisualizationContainer.tsx @@ -77,6 +77,7 @@ const transformLayout = ( const transformedNodesDict = Object.fromEntries( graph.nodes.map(node => { const [x, y] = transform(matrix, [node.x, node.y]) + const r = matrix[0][0] * node.r const status = people[node.uri]?.status ?? '' const style = status === 'success' ? 'success' : status === 'error' ? 'error' : '' @@ -86,9 +87,10 @@ const transformLayout = ( ...node, x, y, + r, style, label: people[node.uri]?.name ?? '', - } as VisualizationNode, + }, ] }), ) @@ -126,6 +128,12 @@ const transformLayout = ( return { nodes: Object.values(transformedNodesDict), links } } +function nodeRadius(person: Person) { + let count = person.known?.size ?? 0 + count = count < 1 ? 1 : count + return count ** 0.42 * 5 +} + const selectNodeDependencies = ( selectedNodeUri: string | undefined, graph: PeopleGraph, @@ -178,16 +186,13 @@ const VisualizationContainer: React.FC = ({ // when graph changes, update simulation useEffect(() => { - const prunedOrFullGraph = people - - const nodes = Object.values(prunedOrFullGraph).map( - ({ name: label, uri }) => ({ - label, - uri, - }), - ) + const nodes = Object.values(people).map(node => ({ + label: node.name, + uri: node.uri, + r: nodeRadius(node), + })) - const links = Object.values(prunedOrFullGraph).reduce( + const links = Object.values(people).reduce( (nodes, { uri: source, knows }) => { knows.forEach(target => nodes.push({ source, target })) return nodes @@ -233,12 +238,15 @@ const VisualizationContainer: React.FC = ({ const grid = transformGrid(matrix, basicGrid) - let person, knows + let person, knows, known if (selectedNode) { person = people[selectedNode] if (person) { knows = Array.from(person.knows).map(f => people[f]) + if (person.known) { + known = Array.from(person.known).map(f => people[f]) + } } } @@ -260,8 +268,13 @@ const VisualizationContainer: React.FC = ({ - {person && knows && ( - + {person && knows && known && ( + )} ) diff --git a/src/simulation/index.ts b/src/simulation/index.ts index e694220..83bc620 100644 --- a/src/simulation/index.ts +++ b/src/simulation/index.ts @@ -14,6 +14,7 @@ import { Coords, Uri, SimulationNode, SimulationLink, Node } from './types' interface SimulationNodeExt extends SimulationNodeDatum { uri: Uri + r: number } export type SimulationLinkExt = SimulationLinkDatum @@ -36,7 +37,10 @@ export default class Simulation { .force('charge', forceManyBody().strength(-150).distanceMax(500)) .force('gravityX', forceX(0).strength(0.01)) .force('gravityY', forceY(0).strength(0.01)) - .force('collide', forceCollide(15)) + .force( + 'collide', + forceCollide(({ r }: SimulationNodeExt) => r + 5), + ) .force('center', forceCenter(0, 0)) .stop() @@ -81,20 +85,17 @@ export default class Simulation { update = ({ nodes, links }: { nodes: Node[]; links: SimulationLink[] }) => { this.simulation.stop() - // combine current visualization and + // combine current nodes and the old nodes const thisNodeDict: { [uri: string]: SimulationNodeExt } = Object.fromEntries(this.nodes.map(node => [node.uri, node])) - const nodeDict: { [uri: string]: SimulationNodeExt } = Object.fromEntries( - nodes.map(node => [ - node.uri, - { - ...node, - x: Math.random() * 400, - y: Math.random() * 400, - }, - ]), - ) - this.nodes = Object.values({ ...nodeDict, ...thisNodeDict }) + const updatedNodes: SimulationNodeExt[] = nodes.map(node => ({ + ...node, + x: Math.random() * 400, + y: Math.random() * 400, + ...thisNodeDict[node.uri], + r: node.r, + })) + this.nodes = updatedNodes this.links = links.map(link => ({ ...link })) as SimulationLinkExt[] this.simulation.nodes(this.nodes) @@ -105,9 +106,9 @@ export default class Simulation { > ).links(this.links) - this.simulation.alpha(0.5).restart() + this.simulation.alpha(1).restart() } selectNode = ({ x, y }: Coords) => - this.simulation.find(x, y, 32) as SimulationNodeExt + this.simulation.find(x, y, 40) as SimulationNodeExt } diff --git a/src/simulation/types.ts b/src/simulation/types.ts index 51ea627..c994e3f 100644 --- a/src/simulation/types.ts +++ b/src/simulation/types.ts @@ -35,6 +35,7 @@ export interface DependencyExtended { export interface Node { label: string uri: Uri + r: number } export interface Coords { @@ -42,9 +43,7 @@ export interface Coords { y: number } -export interface SimulationNode extends Node, Coords { - radius?: number -} +export interface SimulationNode extends Node, Coords {} export interface SimulationLink { source: Uri