six-degrees 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +3 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +41 -13
- package/.next/standalone/.next/routes-manifest.json +12 -0
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +18 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/route.js +8 -0
- package/.next/standalone/.next/server/app/api/social/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +22 -21
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +20 -19
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +20 -19
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch/page.js +1 -1
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +22 -21
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +22 -21
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/profile/page.js +1 -1
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +22 -21
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/queue/page.js +1 -1
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +22 -21
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/scores/page.js +1 -1
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +22 -21
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +22 -21
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/setup/page.js +1 -1
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +22 -21
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/social/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/social/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/social/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/social/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/social/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/social/page.js +13 -0
- package/.next/standalone/.next/server/app/social/page.js.map +5 -0
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/social.html +1 -0
- package/.next/standalone/.next/server/app/social.meta +26 -0
- package/.next/standalone/.next/server/app/social.rsc +26 -0
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +26 -0
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +30 -0
- package/.next/standalone/.next/server/app-paths-manifest.json +3 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17kn1mk._.js +8 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0uag-k5._.js → [root-of-the-server]__1dmc7-t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_route_actions_1_3c6qn.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16hs8kw._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1feicqq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jeztd-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_005q9n1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0hw_qnj._.js → _0wxbq78._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1cyxocj._.js → _1ap1tu_._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1mj739k._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_217nddo._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_social_page_actions_176i1kl.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_0i3fw_m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +17 -4
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +4 -0
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/prefetch-hints.json +14 -0
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/04a3zockahetx.js +1 -0
- package/.next/standalone/.next/static/chunks/{2wwmljnf7yn_5.js → 0a5iadnmt9u9k.js} +1 -1
- package/.next/standalone/.next/static/chunks/0g_p4p7oci18u.js +1 -0
- package/.next/standalone/.next/static/chunks/0y1yms5kez2f3.js +1 -0
- package/.next/standalone/.next/static/chunks/14m1zu-8warte.js +1 -0
- package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +1 -0
- package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +1 -0
- package/.next/standalone/.next/static/chunks/{1zwjy1mmpkdrg.js → 24m745q_85u34.js} +2 -2
- package/.next/standalone/.next/static/chunks/2_zu7rmb8flzl.js +2 -0
- package/.next/standalone/.next/static/chunks/38wwtni5e1ec1.js +31 -0
- package/.next/standalone/.next/static/chunks/3p_pwu5gx2i9h.js +1 -0
- package/.next/{static/chunks/0_3ubivay59bq.js → standalone/.next/static/chunks/3zw8bv7-mc4n6.js} +1 -1
- package/.next/standalone/CHANGELOG.md +58 -0
- package/.next/standalone/app/api/network/route.js +11 -0
- package/.next/standalone/app/api/scraper/route.js +2 -0
- package/.next/standalone/app/api/social/route.js +96 -0
- package/.next/standalone/app/components/AutoScanButton.js +41 -0
- package/.next/standalone/app/components/ChainView.js +7 -0
- package/.next/standalone/app/components/FilterPanel.js +6 -0
- package/.next/standalone/app/components/ForceGraph.js +257 -39
- package/.next/standalone/app/components/GalaxyLab.js +275 -0
- package/.next/standalone/app/components/ScanStatusBar.js +22 -8
- package/.next/standalone/app/components/SeparationView.js +9 -1
- package/.next/standalone/app/components/Sidebar.js +88 -26
- package/.next/standalone/app/components/SocialAutoSync.js +34 -0
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +85 -0
- package/.next/standalone/app/layout.js +3 -0
- package/.next/standalone/app/page.js +29 -0
- package/.next/standalone/app/scores/page.js +3 -0
- package/.next/standalone/app/setup/page.js +1 -1
- package/.next/standalone/app/social/page.js +281 -0
- package/.next/standalone/lib/experimental-client.js +38 -0
- package/.next/standalone/lib/galaxy-export.js +152 -0
- package/.next/standalone/lib/galaxy-lab.js +296 -0
- package/.next/standalone/lib/insights.js +54 -0
- package/.next/standalone/lib/linkedin-export.js +156 -0
- package/.next/standalone/lib/network-health.js +42 -0
- package/.next/standalone/lib/network.js +1 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +118 -2
- package/.next/static/chunks/04a3zockahetx.js +1 -0
- package/.next/static/chunks/{2wwmljnf7yn_5.js → 0a5iadnmt9u9k.js} +1 -1
- package/.next/static/chunks/0g_p4p7oci18u.js +1 -0
- package/.next/static/chunks/0y1yms5kez2f3.js +1 -0
- package/.next/static/chunks/14m1zu-8warte.js +1 -0
- package/.next/static/chunks/1e1n0kkz-psdb.js +1 -0
- package/.next/static/chunks/236f0jtp68j_h.js +1 -0
- package/.next/static/chunks/{1zwjy1mmpkdrg.js → 24m745q_85u34.js} +2 -2
- package/.next/static/chunks/2_zu7rmb8flzl.js +2 -0
- package/.next/static/chunks/38wwtni5e1ec1.js +31 -0
- package/.next/static/chunks/3p_pwu5gx2i9h.js +1 -0
- package/.next/{standalone/.next/static/chunks/0_3ubivay59bq.js → static/chunks/3zw8bv7-mc4n6.js} +1 -1
- package/CHANGELOG.md +58 -0
- package/lib/experimental-client.js +38 -0
- package/lib/galaxy-export.js +152 -0
- package/lib/galaxy-lab.js +296 -0
- package/lib/insights.js +54 -0
- package/lib/linkedin-export.js +156 -0
- package/lib/network-health.js +42 -0
- package/lib/network.js +1 -0
- package/package.json +1 -1
- package/scripts/scrape.py +118 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d3vjsq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0m4vk0b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_012mop3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1163x49._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1av11ws._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1ig7em7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +0 -3
- package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +0 -1
- package/.next/standalone/.next/static/chunks/0f4ugd6v3ph0-.js +0 -1
- package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +0 -1
- package/.next/standalone/.next/static/chunks/10ogurbcuf3nq.js +0 -1
- package/.next/standalone/.next/static/chunks/1489t7jrjojk_.js +0 -1
- package/.next/standalone/.next/static/chunks/1zunq4tk-u3ng.js +0 -1
- package/.next/standalone/.next/static/chunks/2p5yt1o9tnxjn.js +0 -2
- package/.next/standalone/.next/static/chunks/2pyo4j84prgp6.js +0 -18
- package/.next/standalone/.next/static/chunks/3kllnkb3hof1s.js +0 -1
- package/.next/static/chunks/0cuuo2jwc9h94.js +0 -1
- package/.next/static/chunks/0f4ugd6v3ph0-.js +0 -1
- package/.next/static/chunks/0l1568qff0efj.js +0 -1
- package/.next/static/chunks/10ogurbcuf3nq.js +0 -1
- package/.next/static/chunks/1489t7jrjojk_.js +0 -1
- package/.next/static/chunks/1zunq4tk-u3ng.js +0 -1
- package/.next/static/chunks/2p5yt1o9tnxjn.js +0 -2
- package/.next/static/chunks/2pyo4j84prgp6.js +0 -18
- package/.next/static/chunks/3kllnkb3hof1s.js +0 -1
- /package/.next/standalone/.next/static/{814ehZBeFO2rUKle7mzX- → 6LINKa6nyesvIEUnLDSck}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{814ehZBeFO2rUKle7mzX- → 6LINKa6nyesvIEUnLDSck}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{814ehZBeFO2rUKle7mzX- → 6LINKa6nyesvIEUnLDSck}/_ssgManifest.js +0 -0
- /package/.next/static/{814ehZBeFO2rUKle7mzX- → 6LINKa6nyesvIEUnLDSck}/_buildManifest.js +0 -0
- /package/.next/static/{814ehZBeFO2rUKle7mzX- → 6LINKa6nyesvIEUnLDSck}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{814ehZBeFO2rUKle7mzX- → 6LINKa6nyesvIEUnLDSck}/_ssgManifest.js +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
4
4
|
import * as d3 from 'd3';
|
|
5
5
|
import { localPhoto } from '../../lib/photos';
|
|
6
6
|
import { recentre } from '../../lib/galaxy';
|
|
7
7
|
import { reachIndex, readyByCircle, scanBars } from '../../lib/reach';
|
|
8
8
|
import { ringSegments, RING } from '../../lib/dot-rings';
|
|
9
|
+
import { LAB_DEFAULTS, labNow, watchLab, effectiveLab, clockNow, watchClock, setClock, stopReplay, bornTimes, reachCounts, colourScheme, findMatches, loadSocial, chapterAt } from '../../lib/galaxy-lab';
|
|
10
|
+
import { registerGalaxy } from '../../lib/galaxy-export';
|
|
9
11
|
|
|
10
12
|
// Connection fields are attacker-reachable: /api/ingest and /api/update-images
|
|
11
13
|
// accept writes, and a page on any other site can POST to this app on localhost.
|
|
@@ -55,6 +57,13 @@ const RING_CSS = `
|
|
|
55
57
|
50% { opacity: 1; transform: scale(1.12); }
|
|
56
58
|
}
|
|
57
59
|
.galaxy-ring-pulse { animation: galaxy-ring-pulse 1.8s ease-in-out infinite; }
|
|
60
|
+
.lab-focus .gn:not(.lit), .lab-focus .gl:not(.lit) { opacity: 0.06; }
|
|
61
|
+
.lab-focus .gl.lit { stroke-opacity: 0.7; }
|
|
62
|
+
.lab-focus .dot-rings, .lab-focus .catalyst-ring { opacity: 0.15; }
|
|
63
|
+
.lab-find .gn:not(.found), .lab-find .gl:not(.found) { opacity: 0.07; }
|
|
64
|
+
.lab-find .gn.found { stroke: #fff; stroke-width: 2px; }
|
|
65
|
+
@keyframes lab-pop { from { opacity: 0; } }
|
|
66
|
+
@media (prefers-reduced-motion: no-preference) { .lab-pop .gn, .lab-pop .gl { animation: lab-pop 0.5s ease-out; } }
|
|
58
67
|
@media (prefers-reduced-motion: reduce) {
|
|
59
68
|
.galaxy-ring-pulse { animation: none; opacity: 0.8; }
|
|
60
69
|
}`;
|
|
@@ -81,6 +90,36 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
81
90
|
const viewRef = useRef({ transform: null, size: null });
|
|
82
91
|
const sizeRef = useRef(null);
|
|
83
92
|
const [dimensions, setDimensions] = useState(null);
|
|
93
|
+
const stampRef = useRef(null);
|
|
94
|
+
|
|
95
|
+
// The physics lab (lib/galaxy-lab.js): a slider moves the layout in place,
|
|
96
|
+
// and the replay's clock hides whoever wasn't there yet. Neither rebuilds.
|
|
97
|
+
const lab = useSyncExternalStore(watchLab, labNow, () => LAB_DEFAULTS);
|
|
98
|
+
const labRef = useRef(lab);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
labRef.current = lab;
|
|
101
|
+
sceneRef.current?.setLab(effectiveLab(lab));
|
|
102
|
+
const c = clockNow();
|
|
103
|
+
sceneRef.current?.setFind(c.find, c.fly);
|
|
104
|
+
if (lab.on) loadSocial();
|
|
105
|
+
}, [lab]);
|
|
106
|
+
useEffect(() => watchClock(() => {
|
|
107
|
+
const c = clockNow();
|
|
108
|
+
sceneRef.current?.setTime(c.at);
|
|
109
|
+
sceneRef.current?.setFind(c.find, c.fly);
|
|
110
|
+
}), []);
|
|
111
|
+
useEffect(() => () => stopReplay(), []);
|
|
112
|
+
|
|
113
|
+
// The dots' colours (Colour by, in the lab): tier as always, or degree,
|
|
114
|
+
// company or warmth. A new scheme recolours in place.
|
|
115
|
+
const social = useSyncExternalStore(watchClock, () => clockNow().social, () => undefined);
|
|
116
|
+
const colourBy = effectiveLab(lab).colourBy;
|
|
117
|
+
const scheme = useMemo(() => colourScheme(colourBy, connections, tierColors, social), [colourBy, connections, tierColors, social]);
|
|
118
|
+
const schemeRef = useRef(scheme);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
schemeRef.current = scheme;
|
|
121
|
+
sceneRef.current?.setColours(scheme);
|
|
122
|
+
}, [scheme]);
|
|
84
123
|
|
|
85
124
|
// The scene is rebuilt from scratch whenever its inputs change, so only real
|
|
86
125
|
// changes should count. A parent re-rendering hands over a new onSelect every
|
|
@@ -139,9 +178,18 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
139
178
|
const saved = transform ? recentre(transform, setFor, size) : null;
|
|
140
179
|
|
|
141
180
|
const select = (d) => onSelectRef.current?.(d);
|
|
142
|
-
const scene = renderNetworkMode(svg, ringRef.current, size, connections, select, tierColors, focusNodeRef, saved, viewRef, userName, marks);
|
|
181
|
+
const scene = renderNetworkMode(svg, ringRef.current, size, connections, select, tierColors, focusNodeRef, saved, viewRef, userName, marks, effectiveLab(labRef.current), stampRef.current, schemeRef.current);
|
|
143
182
|
sceneRef.current = scene;
|
|
144
183
|
scene.select(selectedIdRef.current);
|
|
184
|
+
setClock({ min: scene.range.min, max: scene.range.max, of: scene.range.of });
|
|
185
|
+
scene.setTime(clockNow().at);
|
|
186
|
+
scene.setFind(clockNow().find, clockNow().fly);
|
|
187
|
+
// Save picture and Record replay (lib/galaxy-export.js) draw from this <svg>.
|
|
188
|
+
const unregister = registerGalaxy({
|
|
189
|
+
svg: svgRef.current,
|
|
190
|
+
stamp: () => (stampRef.current?.style.display === 'block' ? stampRef.current.textContent : null),
|
|
191
|
+
legend: () => schemeRef.current.legend,
|
|
192
|
+
});
|
|
145
193
|
|
|
146
194
|
return () => {
|
|
147
195
|
// Stop the force simulation this render started.
|
|
@@ -151,6 +199,7 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
151
199
|
// the same nodes, so they fought each other and the graph shook.
|
|
152
200
|
// Refreshing made it worse because nothing ever stopped the old ones.
|
|
153
201
|
scene.simulation.stop();
|
|
202
|
+
unregister();
|
|
154
203
|
// And any glide or fly-to still under way, which would otherwise go on
|
|
155
204
|
// moving the next scene's view. viewRef already holds where it was going.
|
|
156
205
|
svg.interrupt();
|
|
@@ -174,11 +223,17 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
174
223
|
}}>
|
|
175
224
|
<div className="galaxy-ring-pulse" style={{ boxSizing: 'border-box', border: '2.5px solid', borderRadius: '50%' }} />
|
|
176
225
|
</div>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
226
|
+
{/* The replay's date, written by the scene as it plays. */}
|
|
227
|
+
<div ref={stampRef} style={{
|
|
228
|
+
position: 'absolute', bottom: 56, left: '50%', transform: 'translateX(-50%)', display: 'none',
|
|
229
|
+
padding: '6px 14px', borderRadius: 16, background: 'rgba(10,15,30,0.8)', border: '1px solid rgba(255,255,255,0.12)',
|
|
230
|
+
color: '#e6edf5', fontSize: 13, fontWeight: 600, pointerEvents: 'none', fontVariantNumeric: 'tabular-nums',
|
|
231
|
+
}} />
|
|
232
|
+
<div style={{ position: 'absolute', bottom: 20, left: 20, right: 20, display: 'flex', flexWrap: 'wrap', gap: '4px 12px', fontSize: 11, color: '#888', pointerEvents: 'none' }}>
|
|
233
|
+
{scheme.legend.map(([label, color]) => (
|
|
234
|
+
<span key={label} style={{ display: 'flex', alignItems: 'center', gap: 4, whiteSpace: 'nowrap' }}>
|
|
180
235
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block' }} />
|
|
181
|
-
{
|
|
236
|
+
{label}
|
|
182
237
|
</span>
|
|
183
238
|
))}
|
|
184
239
|
</div>
|
|
@@ -216,7 +271,8 @@ function easeRadius(el, r) {
|
|
|
216
271
|
// Graph coordinates put you at 0,0, whatever the size of the box; the zoom
|
|
217
272
|
// transform places that in the box. So a new size only moves the view, and a
|
|
218
273
|
// rebuild can start from the view as it was.
|
|
219
|
-
function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, focusNodeRef, savedTransform, viewRef, userName, marks = new Map()) {
|
|
274
|
+
function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, focusNodeRef, savedTransform, viewRef, userName, marks = new Map(), lab = LAB_DEFAULTS, stamp = null, scheme = null) {
|
|
275
|
+
let colourOf = scheme?.of ?? ((d) => tierColors[d.tier] || '#666');
|
|
220
276
|
const centerNode = {
|
|
221
277
|
id: CENTER_ID, name: userName || 'You', tier: 'center', degree: 0,
|
|
222
278
|
power_score: 10, fx: 0, fy: 0,
|
|
@@ -285,16 +341,24 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
285
341
|
return SLOTS[slot][t < 0 ? 4 : t];
|
|
286
342
|
};
|
|
287
343
|
|
|
344
|
+
// A phone. Judged by the window, as the page judges it, not by the graph's
|
|
345
|
+
// own box: that narrowed whenever a panel opened on a laptop.
|
|
346
|
+
const isMobileGraph = window.innerWidth < 768;
|
|
347
|
+
|
|
348
|
+
// The physics lab's settings (lib/galaxy-lab.js); today's layout when it's
|
|
349
|
+
// off, and always on a phone, whose layout has no physics.
|
|
350
|
+
let L = isMobileGraph ? { ...LAB_DEFAULTS, labels: lab.labels } : lab;
|
|
351
|
+
const reach = reachCounts(nodes, parentOf);
|
|
352
|
+
|
|
288
353
|
const nodeRadius = (d) => {
|
|
289
354
|
if (d.id === CENTER_ID) return 18;
|
|
355
|
+
// Sized by reach, a dot grows with everyone who hangs off it: a 2nd-degree
|
|
356
|
+
// person with a full circle behind them next to one with nobody.
|
|
357
|
+
if (L.sizeBy === 'reach') return Math.min(28, 2.5 + 1.6 * Math.sqrt(reach.get(d.id) || 0)) * L.dotSize;
|
|
290
358
|
const r = Math.max(3, Math.min(12, (d.power_score || 1) * 1.3));
|
|
291
|
-
return d.degree > 1 ? Math.max(2.5, r * 0.75) : r; // further out, a little smaller
|
|
359
|
+
return (d.degree > 1 ? Math.max(2.5, r * 0.75) : r) * L.dotSize; // further out, a little smaller
|
|
292
360
|
};
|
|
293
361
|
|
|
294
|
-
// A phone. Judged by the window, as the page judges it, not by the graph's
|
|
295
|
-
// own box: that narrowed whenever a panel opened on a laptop.
|
|
296
|
-
const isMobileGraph = window.innerWidth < 768;
|
|
297
|
-
|
|
298
362
|
const g = svg.append('g');
|
|
299
363
|
|
|
300
364
|
// Faint S–D guide rings where each tier settles (the radial force below), so
|
|
@@ -302,9 +366,10 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
302
366
|
// Once the layout settles they move to where each tier's dots actually sit
|
|
303
367
|
// (the median distance out), which the other forces push past that radius.
|
|
304
368
|
const guideRings = new Map();
|
|
369
|
+
let guidesG = null;
|
|
305
370
|
if (!isMobileGraph && layered) {
|
|
306
371
|
// In bands: a faint ring and a label for each step along the chain instead.
|
|
307
|
-
const guides = g.append('g').attr('class', 'degree-guides').style('pointer-events', 'none');
|
|
372
|
+
const guides = guidesG = g.append('g').attr('class', 'degree-guides').style('pointer-events', 'none');
|
|
308
373
|
const names = ['', '1st', '2nd', '3rd', '4th', '5th', '6th'];
|
|
309
374
|
bands.slice(0, SLOTS.length).forEach((b, slot) => {
|
|
310
375
|
const r = SLOTS[slot][2];
|
|
@@ -315,7 +380,7 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
315
380
|
.text(names[b] || `${b}th`);
|
|
316
381
|
});
|
|
317
382
|
} else if (!isMobileGraph) {
|
|
318
|
-
const guides = g.append('g').attr('class', 'tier-guides').style('pointer-events', 'none');
|
|
383
|
+
const guides = guidesG = g.append('g').attr('class', 'tier-guides').style('pointer-events', 'none');
|
|
319
384
|
for (const tier of ['S', 'A', 'B', 'C', 'D']) {
|
|
320
385
|
const r = tierRadius(tier);
|
|
321
386
|
const ring = guides.append('circle').attr('r', r).attr('fill', 'none')
|
|
@@ -371,7 +436,7 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
371
436
|
const r = nodeRadius(ringNode) + 8;
|
|
372
437
|
pulse.style.width = pulse.style.height = `${2 * r + 2.5}px`;
|
|
373
438
|
pulse.style.margin = `${-(r + 1.25)}px`;
|
|
374
|
-
pulse.style.borderColor =
|
|
439
|
+
pulse.style.borderColor = colourOf(ringNode) || '#FFD700';
|
|
375
440
|
placeRing();
|
|
376
441
|
};
|
|
377
442
|
|
|
@@ -421,12 +486,20 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
421
486
|
});
|
|
422
487
|
}
|
|
423
488
|
|
|
489
|
+
// Each force is today's value times its slider in the lab (1 when it's off).
|
|
490
|
+
const linkBase = isMobileGraph ? 0 : layered ? 0.06 : 0.1;
|
|
491
|
+
const radialBase = layered ? 0.45 : 0.3;
|
|
492
|
+
const linkDistance = d => (d.near ? Math.max(40, radiusOf(d.target) - radiusOf(d.source)) : radiusOf(d.target)) * L.distance;
|
|
493
|
+
const charge = d => (d.id === CENTER_ID ? -20 : -1) * L.push;
|
|
424
494
|
const simulation = d3.forceSimulation(nodes)
|
|
425
|
-
.force('link', d3.forceLink(links).id(d => d.id).distance(
|
|
426
|
-
.force('charge', d3.forceManyBody().strength(isMobileGraph ? 0 :
|
|
495
|
+
.force('link', d3.forceLink(links).id(d => d.id).distance(linkDistance).strength(Math.min(1, linkBase * L.pull)))
|
|
496
|
+
.force('charge', d3.forceManyBody().strength(isMobileGraph ? 0 : charge))
|
|
427
497
|
.force('center', isMobileGraph ? null : d3.forceCenter(0, 0))
|
|
498
|
+
.force('gravityX', isMobileGraph ? null : d3.forceX(0).strength(0.15 * L.gravity))
|
|
499
|
+
.force('gravityY', isMobileGraph ? null : d3.forceY(0).strength(0.15 * L.gravity))
|
|
428
500
|
.force('collision', isMobileGraph ? null : d3.forceCollide().radius(d => nodeRadius(d) + 2))
|
|
429
|
-
.force('radial', isMobileGraph ? null : d3.forceRadial(d => d.id === CENTER_ID ? 0 : radiusOf(d), 0, 0).strength(
|
|
501
|
+
.force('radial', isMobileGraph ? null : d3.forceRadial(d => d.id === CENTER_ID ? 0 : radiusOf(d), 0, 0).strength(radialBase * L.rings));
|
|
502
|
+
guidesG?.attr('opacity', Math.min(1, L.rings));
|
|
430
503
|
simulation.on('end.guides', () => {
|
|
431
504
|
for (const [tier, { ring, label }] of guideRings) {
|
|
432
505
|
const out = nodes.filter((n) => n.tier === tier && n.id !== CENTER_ID).map((n) => Math.hypot(n.x || 0, n.y || 0)).sort((x, y) => x - y);
|
|
@@ -441,8 +514,9 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
441
514
|
// the colour is one step away. It used to search every node for every link:
|
|
442
515
|
// about 1.8 s at 30,000 people.
|
|
443
516
|
const link = g.append('g').selectAll('line').data(links).join('line')
|
|
444
|
-
.attr('
|
|
445
|
-
.attr('stroke
|
|
517
|
+
.attr('class', 'gl')
|
|
518
|
+
.attr('stroke', d => colourOf(d.target) || '#333')
|
|
519
|
+
.attr('stroke-opacity', 0.15).attr('stroke-width', 0.5 * L.lines);
|
|
446
520
|
|
|
447
521
|
// Catalyst outer glow rings (rendered behind the nodes)
|
|
448
522
|
// A catalyst is the green outline on their dot (design C, with the scan
|
|
@@ -471,10 +545,11 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
471
545
|
const dotRings = g.append('g').attr('class', 'dot-rings').style('pointer-events', 'none')
|
|
472
546
|
.selectAll('g').data(ringed).join('g')
|
|
473
547
|
.attr('transform', d => `translate(${d.x || 0},${d.y || 0})`);
|
|
474
|
-
|
|
548
|
+
function drawDotRing(d) {
|
|
475
549
|
const { bars, ready } = marks.get(d.id);
|
|
476
550
|
const r = nodeRadius(d) + 3.5;
|
|
477
551
|
const el = d3.select(this);
|
|
552
|
+
el.selectAll('*').remove();
|
|
478
553
|
if (bars != null) {
|
|
479
554
|
for (const seg of ringSegments(r)) {
|
|
480
555
|
el.append('path').attr('d', seg.d).attr('fill', 'none').attr('stroke-linecap', 'round')
|
|
@@ -487,11 +562,13 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
487
562
|
el.append('text').attr('x', at).attr('y', at + 2.5).attr('text-anchor', 'middle')
|
|
488
563
|
.attr('font-size', 7).attr('font-weight', 700).attr('fill', '#fff').text(ready > 9 ? '9+' : ready);
|
|
489
564
|
}
|
|
490
|
-
}
|
|
565
|
+
}
|
|
566
|
+
dotRings.each(drawDotRing);
|
|
491
567
|
|
|
492
568
|
const node = g.append('g').selectAll('circle').data(nodes).join('circle')
|
|
569
|
+
.attr('class', 'gn')
|
|
493
570
|
.attr('r', nodeRadius)
|
|
494
|
-
.attr('fill', d => d.id === CENTER_ID ? '#fff' :
|
|
571
|
+
.attr('fill', d => d.id === CENTER_ID ? '#fff' : colourOf(d))
|
|
495
572
|
.attr('fill-opacity', d => (d.degree >= 3 ? 0.55 : d.degree === 2 ? 0.75 : 1))
|
|
496
573
|
.attr('stroke', d => {
|
|
497
574
|
if (d.id === CENTER_ID) return '#FFD700';
|
|
@@ -518,8 +595,26 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
518
595
|
.style('pointer-events', 'none').style('opacity', 0)
|
|
519
596
|
.style('z-index', 1001).style('transition', 'opacity 0.15s');
|
|
520
597
|
|
|
598
|
+
// Their branch: everyone who hangs off them, all the way down, and the chain
|
|
599
|
+
// back to you. Hovering lights it up in the lab, like Obsidian's graph.
|
|
600
|
+
const kids = new Map();
|
|
601
|
+
for (const [child, parent] of parentOf) {
|
|
602
|
+
if (!kids.has(parent)) kids.set(parent, []);
|
|
603
|
+
kids.get(parent).push(child);
|
|
604
|
+
}
|
|
605
|
+
const lightBranch = (d) => {
|
|
606
|
+
const lit = new Set([CENTER_ID, d.id]);
|
|
607
|
+
const stack = [d.id];
|
|
608
|
+
while (stack.length) for (const k of kids.get(stack.pop()) || []) if (!lit.has(k)) { lit.add(k); stack.push(k); }
|
|
609
|
+
for (let p = parentOf.get(d.id), hops = 0; p != null && hops < 7; p = parentOf.get(p), hops++) lit.add(p);
|
|
610
|
+
node.classed('lit', n => lit.has(n.id));
|
|
611
|
+
link.classed('lit', l => lit.has(l.source.id) && lit.has(l.target.id));
|
|
612
|
+
g.classed('lab-focus', true);
|
|
613
|
+
};
|
|
614
|
+
|
|
521
615
|
node.on('mouseover', function (event, d) {
|
|
522
616
|
easeRadius(this, nodeRadius(d) * 1.5);
|
|
617
|
+
if (L.on && L.branch && d.id !== CENTER_ID) lightBranch(d);
|
|
523
618
|
if (d.profile_image_url && d.id !== CENTER_ID) {
|
|
524
619
|
const size = Math.max(48, nodeRadius(d) * 5);
|
|
525
620
|
photoTooltip.style('opacity', 1);
|
|
@@ -547,27 +642,41 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
547
642
|
}
|
|
548
643
|
}).on('mouseout', function (event, d) {
|
|
549
644
|
easeRadius(this, nodeRadius(d));
|
|
645
|
+
g.classed('lab-focus', false);
|
|
550
646
|
tooltip.style('opacity', 0);
|
|
551
647
|
photoTooltip.style('opacity', 0);
|
|
552
648
|
});
|
|
553
649
|
|
|
554
650
|
setupDrag(node, simulation, CENTER_ID);
|
|
555
651
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
.
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
.
|
|
652
|
+
// Names for you, your S-tier connections and catalysts; further out (the
|
|
653
|
+
// Degree filter) only on hover, or hundreds of S-tier names pile up. The lab
|
|
654
|
+
// can show all your connections' names, or none.
|
|
655
|
+
// Filter → Names turns them all off; whoever Find matches (40 or fewer) is
|
|
656
|
+
// named either way.
|
|
657
|
+
let findHit = null;
|
|
658
|
+
const labelled = (n) => {
|
|
659
|
+
if (findHit && findHit.size <= 40 && findHit.has(n.id)) return true;
|
|
660
|
+
if (!L.labels) return false;
|
|
661
|
+
return n.id === CENTER_ID || (n.degree === 1 && (L.names === 'all' || n.tier === 'S' || n.is_catalyst));
|
|
662
|
+
};
|
|
663
|
+
const labelsG = g.append('g');
|
|
664
|
+
let labels = labelsG.selectAll('text');
|
|
665
|
+
const drawLabels = () => {
|
|
666
|
+
labels = labelsG.selectAll('text').data(nodes.filter(labelled), d => d.id).join('text')
|
|
667
|
+
.text(d => d.is_catalyst && d.tier !== 'S' ? d.name + ' ⚡' : d.name)
|
|
668
|
+
.attr('font-size', d => d.id === CENTER_ID ? 14 : 10)
|
|
669
|
+
.attr('font-weight', d => d.id === CENTER_ID ? 700 : 500)
|
|
670
|
+
.attr('fill', d => {
|
|
671
|
+
if (d.id === CENTER_ID) return '#fff';
|
|
672
|
+
if (d.is_catalyst) return '#00ff88';
|
|
673
|
+
return colourOf(d);
|
|
674
|
+
})
|
|
675
|
+
.attr('text-anchor', 'middle').attr('dy', d => nodeRadius(d) + 14)
|
|
676
|
+
.attr('x', d => d.x || 0).attr('y', d => d.y || 0)
|
|
677
|
+
.style('pointer-events', 'none');
|
|
678
|
+
};
|
|
679
|
+
drawLabels();
|
|
571
680
|
|
|
572
681
|
const catalystRings = g.selectAll('.catalyst-ring');
|
|
573
682
|
|
|
@@ -580,7 +689,116 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
580
689
|
placeRing();
|
|
581
690
|
});
|
|
582
691
|
|
|
583
|
-
|
|
692
|
+
// A slider moved: change the running layout in place and let it settle again.
|
|
693
|
+
const setLab = (next) => {
|
|
694
|
+
if (isMobileGraph) {
|
|
695
|
+
if (next.labels !== L.labels) { L = { ...L, labels: next.labels }; drawLabels(); showBorn(); }
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const prev = L;
|
|
699
|
+
L = next;
|
|
700
|
+
const sized = prev.dotSize !== L.dotSize || prev.sizeBy !== L.sizeBy;
|
|
701
|
+
if (sized) {
|
|
702
|
+
node.attr('r', nodeRadius);
|
|
703
|
+
catalystRings.attr('r', d => nodeRadius(d) + 6);
|
|
704
|
+
dotRings.each(drawDotRing);
|
|
705
|
+
simulation.force('collision').radius(d => nodeRadius(d) + 2);
|
|
706
|
+
if (ringNode) select(ringNode.id);
|
|
707
|
+
}
|
|
708
|
+
if (prev.lines !== L.lines) link.attr('stroke-width', 0.5 * L.lines);
|
|
709
|
+
if (sized || prev.names !== L.names || prev.labels !== L.labels) { drawLabels(); showBorn(); }
|
|
710
|
+
if (!L.on || !L.branch) g.classed('lab-focus', false);
|
|
711
|
+
g.classed('lab-pop', L.on && nodes.length < 5000);
|
|
712
|
+
const forces = ['gravity', 'rings', 'push', 'pull', 'distance'].some(k => prev[k] !== L[k]);
|
|
713
|
+
if (!forces && !sized) return;
|
|
714
|
+
simulation.force('link').distance(linkDistance).strength(Math.min(1, linkBase * L.pull));
|
|
715
|
+
simulation.force('charge').strength(charge);
|
|
716
|
+
simulation.force('gravityX').strength(0.15 * L.gravity);
|
|
717
|
+
simulation.force('gravityY').strength(0.15 * L.gravity);
|
|
718
|
+
simulation.force('radial').strength(radialBase * L.rings);
|
|
719
|
+
guidesG?.attr('opacity', Math.min(1, L.rings));
|
|
720
|
+
simulation.alpha(Math.max(simulation.alpha(), 0.5)).restart();
|
|
721
|
+
};
|
|
722
|
+
g.classed('lab-pop', L.on && nodes.length < 5000);
|
|
723
|
+
|
|
724
|
+
// The replay: whoever wasn't there yet at the clock's time is hidden. Only the
|
|
725
|
+
// dots that change are touched, so a frame costs little at 30,000 people.
|
|
726
|
+
const born = bornTimes(nodes, parentOf);
|
|
727
|
+
const bornById = new Map(nodes.map((n, i) => [n.id, born[i]]));
|
|
728
|
+
const nodeEls = node.nodes();
|
|
729
|
+
const linkEls = link.nodes(); // links[i] is connections[i], which is nodes[i + 1]
|
|
730
|
+
const extraEls = new Map();
|
|
731
|
+
dotRings.each(function (d) { extraEls.set(d.id, [this]); });
|
|
732
|
+
catalystRings.each(function (d) { extraEls.set(d.id, [...(extraEls.get(d.id) || []), this]); });
|
|
733
|
+
const shown = new Uint8Array(nodes.length).fill(1);
|
|
734
|
+
const d1 = nodes.filter(n => n.degree === 1);
|
|
735
|
+
const dated = d1.map(n => bornById.get(n.id)).filter(Number.isFinite);
|
|
736
|
+
const range = { min: dated.length ? dated.reduce((m, t) => Math.min(m, t)) : null, max: dated.length ? dated.reduce((m, t) => Math.max(m, t)) : null, of: d1.length };
|
|
737
|
+
let clockAt = null;
|
|
738
|
+
const showBorn = () => labels.style('display', d => (clockAt == null || bornById.get(d.id) <= clockAt ? null : 'none'));
|
|
739
|
+
const setTime = (at) => {
|
|
740
|
+
if (!L.on) at = null;
|
|
741
|
+
clockAt = at;
|
|
742
|
+
let n1 = 0;
|
|
743
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
744
|
+
const vis = at == null || born[i] <= at ? 1 : 0;
|
|
745
|
+
if (vis && nodes[i].degree === 1) n1++;
|
|
746
|
+
if (vis === shown[i]) continue;
|
|
747
|
+
shown[i] = vis;
|
|
748
|
+
const display = vis ? '' : 'none';
|
|
749
|
+
nodeEls[i].style.display = display;
|
|
750
|
+
if (linkEls[i - 1]) linkEls[i - 1].style.display = display;
|
|
751
|
+
for (const el of extraEls.get(nodes[i].id) || []) el.style.display = display;
|
|
752
|
+
}
|
|
753
|
+
showBorn();
|
|
754
|
+
if (!stamp) return;
|
|
755
|
+
stamp.style.display = at == null ? 'none' : 'block';
|
|
756
|
+
if (at != null) {
|
|
757
|
+
const when = new Date(at).toLocaleDateString(undefined, { month: 'short', year: 'numeric' });
|
|
758
|
+
const job = chapterAt(clockNow().social, at);
|
|
759
|
+
stamp.textContent = `${when} · ${n1.toLocaleString()} of ${range.of.toLocaleString()} connections${job ? ` · at ${job.company}` : ''}`;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
// A new colour scheme (Colour by): recolour the dots, lines and names in place.
|
|
764
|
+
const setColours = (next) => {
|
|
765
|
+
colourOf = next.of;
|
|
766
|
+
node.attr('fill', d => d.id === CENTER_ID ? '#fff' : colourOf(d));
|
|
767
|
+
link.attr('stroke', d => colourOf(d.target) || '#333');
|
|
768
|
+
drawLabels();
|
|
769
|
+
showBorn();
|
|
770
|
+
if (ringNode) select(ringNode.id);
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
// Find: whoever matches stays lit and the rest dim; each Enter (a new `fly`)
|
|
774
|
+
// flies to the best-scored match and opens their card.
|
|
775
|
+
let lastFind = '';
|
|
776
|
+
let lastFly = clockNow().fly;
|
|
777
|
+
const setFind = (query, fly) => {
|
|
778
|
+
const q = L.on ? String(query || '') : '';
|
|
779
|
+
if (q !== lastFind) {
|
|
780
|
+
lastFind = q;
|
|
781
|
+
findHit = findMatches(nodes.filter(n => n.id !== CENTER_ID), q);
|
|
782
|
+
node.classed('found', n => !!findHit?.has(n.id));
|
|
783
|
+
link.classed('found', l => !!findHit?.has(l.target.id));
|
|
784
|
+
g.classed('lab-find', !!findHit);
|
|
785
|
+
drawLabels();
|
|
786
|
+
showBorn();
|
|
787
|
+
setClock({ found: findHit ? findHit.size : 0 });
|
|
788
|
+
}
|
|
789
|
+
if (fly !== lastFly) {
|
|
790
|
+
lastFly = fly;
|
|
791
|
+
const best = findHit && nodes.filter(n => findHit.has(n.id)).sort((a, b) => (b.power_score || 0) - (a.power_score || 0))[0];
|
|
792
|
+
if (best) {
|
|
793
|
+
const k = 2.5;
|
|
794
|
+
moveView(d3.zoomIdentity.translate(box.width / 2 - best.x * k, box.height / 2 - best.y * k).scale(k), 750);
|
|
795
|
+
select(best.id);
|
|
796
|
+
onSelect(best);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
return { simulation, resize, select, setLab, setTime, setColours, setFind, range };
|
|
584
802
|
}
|
|
585
803
|
|
|
586
804
|
function setupDrag(node, simulation, fixedId) {
|