@andespindola/brainlink 0.1.0-beta.87 → 0.1.0-beta.88
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/README.md +2 -2
- package/dist/application/frontend/client-js.js +179 -62
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
|
|
|
84
84
|
- Graph renderer optimized for large datasets with viewport-driven node culling and edge lookup by visible nodes.
|
|
85
85
|
- Canvas graph rendering uses the same batched node and edge pipeline for every graph size, reducing per-frame draw calls while keeping selected and hovered items highlighted.
|
|
86
86
|
- WebGL acceleration is used when available for dense node and edge drawing, with Canvas 2D preserved as the interaction and fallback layer.
|
|
87
|
-
- Graph zoom-out renders hierarchical ecosystem subgraphs only above 1000 notes:
|
|
87
|
+
- Graph zoom-out renders hierarchical ecosystem subgraphs only above 1000 notes: the memory hub stays centered, 1000-note groups stay as compact sand-like points, and focused groups gradually expand into smaller graph meshes before individual notes are rendered.
|
|
88
88
|
- Large graph layout API automatically uses compact payload encoding with link-coverage-aware edge selection to reduce initial client load without hiding major relationships.
|
|
89
89
|
- Large-segment layout spacing now grows logarithmically to keep initial visual density consistent between medium and very large vaults (for example, ~1k vs ~50k notes).
|
|
90
90
|
- Graph coordinates are visually compacted across graph sizes so reset starts from a stable macro mass and zoom-in progressively expands toward local detail.
|
|
@@ -602,7 +602,7 @@ The graph UI shows:
|
|
|
602
602
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
603
603
|
- compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
|
|
604
604
|
- graph camera treats hub-centered navigation as structural only when the hub is dominant; diffuse stress graphs reset and zoom around the full graph mass
|
|
605
|
-
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use
|
|
605
|
+
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use a compact memory-hub-centered mesh of connected 1000-note points, zoom-in spreads only focused clusters into 250-note and 60-note subgraphs with aggregated real links, then progressively raises the focused node budget so local areas keep nearby notes and links visible
|
|
606
606
|
|
|
607
607
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
608
608
|
|
|
@@ -25,6 +25,7 @@ const ecosystemGroupSize = 1000
|
|
|
25
25
|
const ecosystemActivationNodeThreshold = 1000
|
|
26
26
|
const ecosystemGroupSizes = [1000, 250, 60]
|
|
27
27
|
const ecosystemClusterEdgeLimit = 520
|
|
28
|
+
const ecosystemHubEdgeLimit = 120
|
|
28
29
|
const ecosystemClusterScaleThreshold = 0.32
|
|
29
30
|
const ecosystemSubgraphScaleThreshold = 0.18
|
|
30
31
|
const ecosystemMicroScaleThreshold = 0.08
|
|
@@ -74,6 +75,8 @@ const state = {
|
|
|
74
75
|
visibleEdgeByNode: new Map(),
|
|
75
76
|
ecosystemClusters: [],
|
|
76
77
|
ecosystemClustersBySize: new Map(),
|
|
78
|
+
ecosystemNodeClusterBySize: new Map(),
|
|
79
|
+
ecosystemHubCluster: null,
|
|
77
80
|
macroCenter: { x: 0, y: 0 },
|
|
78
81
|
macroRepresentative: null,
|
|
79
82
|
primaryHub: null,
|
|
@@ -570,11 +573,6 @@ const recomputeVisibility = () => {
|
|
|
570
573
|
state.visibleEdges = limitedEdges
|
|
571
574
|
state.visibleNodeSpatial = createSpatialIndex(nodes)
|
|
572
575
|
state.visibleEdgeByNode = createVisibleEdgeLookup(limitedEdges)
|
|
573
|
-
const ecosystemGraph = nodes.length > ecosystemActivationNodeThreshold
|
|
574
|
-
? buildEcosystemGraph(nodes)
|
|
575
|
-
: { clusters: [], clustersBySize: new Map() }
|
|
576
|
-
state.ecosystemClusters = ecosystemGraph.clusters
|
|
577
|
-
state.ecosystemClustersBySize = ecosystemGraph.clustersBySize
|
|
578
576
|
const primaryHub = rankedHubNodes()[0] ?? null
|
|
579
577
|
state.primaryHub = primaryHub
|
|
580
578
|
state.hubNeighborDistance = nearestHubNeighborDistance(primaryHub, nodes)
|
|
@@ -586,6 +584,13 @@ const recomputeVisibility = () => {
|
|
|
586
584
|
y: macroHub ? macroHub.y : (bounds.minY + bounds.maxY) / 2
|
|
587
585
|
}
|
|
588
586
|
: { x: 0, y: 0 }
|
|
587
|
+
const ecosystemGraph = nodes.length > ecosystemActivationNodeThreshold
|
|
588
|
+
? buildEcosystemGraph(nodes, state.macroCenter, primaryHub)
|
|
589
|
+
: { clusters: [], clustersBySize: new Map(), nodeClusterBySize: new Map(), hubCluster: null }
|
|
590
|
+
state.ecosystemClusters = ecosystemGraph.clusters
|
|
591
|
+
state.ecosystemClustersBySize = ecosystemGraph.clustersBySize
|
|
592
|
+
state.ecosystemNodeClusterBySize = ecosystemGraph.nodeClusterBySize
|
|
593
|
+
state.ecosystemHubCluster = ecosystemGraph.hubCluster
|
|
589
594
|
state.macroRepresentative = resolveMacroRepresentative(nodes)
|
|
590
595
|
markRenderDirty()
|
|
591
596
|
}
|
|
@@ -730,18 +735,32 @@ const selectEcosystemRepresentative = nodes => {
|
|
|
730
735
|
return representative
|
|
731
736
|
}
|
|
732
737
|
|
|
733
|
-
const
|
|
738
|
+
const ecosystemLayoutSpacingForSize = size => {
|
|
739
|
+
if (size >= 1000) return 360
|
|
740
|
+
if (size >= 250) return 92
|
|
741
|
+
return 28
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const ecosystemCompactPoint = (index, total, center, spacing) => {
|
|
745
|
+
if (total <= 1) {
|
|
746
|
+
return { x: center.x, y: center.y }
|
|
747
|
+
}
|
|
748
|
+
const angle = index * 2.399963229728653
|
|
749
|
+
const radius = spacing * Math.sqrt(index + 1)
|
|
750
|
+
return {
|
|
751
|
+
x: center.x + Math.cos(angle) * radius,
|
|
752
|
+
y: center.y + Math.sin(angle) * radius
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const buildEcosystemCluster = (nodes, index, point) => {
|
|
734
757
|
const count = Math.max(nodes.length, 1)
|
|
735
|
-
const sum = nodes.reduce((accumulator, node) => ({
|
|
736
|
-
x: accumulator.x + node.x,
|
|
737
|
-
y: accumulator.y + node.y
|
|
738
|
-
}), { x: 0, y: 0 })
|
|
739
758
|
const representative = selectEcosystemRepresentative(nodes)
|
|
740
759
|
|
|
741
760
|
return {
|
|
742
761
|
id: 'ecosystem-' + index,
|
|
743
|
-
x:
|
|
744
|
-
y:
|
|
762
|
+
x: point.x,
|
|
763
|
+
y: point.y,
|
|
745
764
|
count,
|
|
746
765
|
nodeIds: nodes.map(node => node.id),
|
|
747
766
|
representative,
|
|
@@ -749,15 +768,42 @@ const buildEcosystemCluster = (nodes, index) => {
|
|
|
749
768
|
}
|
|
750
769
|
}
|
|
751
770
|
|
|
752
|
-
const
|
|
771
|
+
const buildEcosystemHubCluster = (hub, center) => hub
|
|
772
|
+
? {
|
|
773
|
+
id: 'ecosystem-hub',
|
|
774
|
+
x: center.x,
|
|
775
|
+
y: center.y,
|
|
776
|
+
count: 1,
|
|
777
|
+
size: 1,
|
|
778
|
+
nodeIds: [hub.id],
|
|
779
|
+
representative: hub,
|
|
780
|
+
label: hub.title || 'Memory Hub',
|
|
781
|
+
parentId: null,
|
|
782
|
+
parentX: null,
|
|
783
|
+
parentY: null,
|
|
784
|
+
isHub: true
|
|
785
|
+
}
|
|
786
|
+
: null
|
|
787
|
+
|
|
788
|
+
const buildEcosystemLevel = (sortedNodes, size, parentLookup, center) => {
|
|
753
789
|
const clusters = []
|
|
754
790
|
const clusterByNodeId = new Map()
|
|
791
|
+
const parentChildIndex = new Map()
|
|
755
792
|
|
|
756
793
|
for (let offset = 0; offset < sortedNodes.length; offset += size) {
|
|
757
794
|
const clusterNodes = sortedNodes.slice(offset, offset + size)
|
|
758
795
|
const parentCluster = parentLookup?.get(clusterNodes[0]?.id)
|
|
796
|
+
const siblingIndex = parentCluster
|
|
797
|
+
? (parentChildIndex.get(parentCluster.id) ?? 0)
|
|
798
|
+
: clusters.length
|
|
799
|
+
if (parentCluster) {
|
|
800
|
+
parentChildIndex.set(parentCluster.id, siblingIndex + 1)
|
|
801
|
+
}
|
|
802
|
+
const point = parentCluster
|
|
803
|
+
? ecosystemCompactPoint(siblingIndex, Math.ceil((parentCluster.count || size) / size), parentCluster, ecosystemLayoutSpacingForSize(size))
|
|
804
|
+
: ecosystemCompactPoint(clusters.length, Math.ceil(sortedNodes.length / size), center, ecosystemLayoutSpacingForSize(size))
|
|
759
805
|
const cluster = {
|
|
760
|
-
...buildEcosystemCluster(clusterNodes, clusters.length),
|
|
806
|
+
...buildEcosystemCluster(clusterNodes, clusters.length, point),
|
|
761
807
|
id: 'ecosystem-' + size + '-' + clusters.length,
|
|
762
808
|
size,
|
|
763
809
|
parentId: parentCluster?.id ?? null,
|
|
@@ -773,25 +819,32 @@ const buildEcosystemLevel = (sortedNodes, size, parentLookup) => {
|
|
|
773
819
|
return { clusters, clusterByNodeId }
|
|
774
820
|
}
|
|
775
821
|
|
|
776
|
-
const buildEcosystemGraph = (nodes) => {
|
|
822
|
+
const buildEcosystemGraph = (nodes, center, hub) => {
|
|
777
823
|
if (nodes.length === 0) {
|
|
778
|
-
return { clusters: [], clustersBySize: new Map() }
|
|
824
|
+
return { clusters: [], clustersBySize: new Map(), nodeClusterBySize: new Map(), hubCluster: null }
|
|
779
825
|
}
|
|
780
826
|
|
|
781
|
-
const
|
|
827
|
+
const hubCluster = buildEcosystemHubCluster(hub, center)
|
|
828
|
+
const sortedNodes = nodes
|
|
829
|
+
.filter(node => node.id !== hub?.id)
|
|
830
|
+
.sort(compareNodesForEcosystem)
|
|
782
831
|
const clustersBySize = new Map()
|
|
832
|
+
const nodeClusterBySize = new Map()
|
|
783
833
|
let parentLookup = null
|
|
784
834
|
|
|
785
835
|
for (let index = 0; index < ecosystemGroupSizes.length; index += 1) {
|
|
786
836
|
const size = ecosystemGroupSizes[index]
|
|
787
|
-
const level = buildEcosystemLevel(sortedNodes, size, parentLookup)
|
|
837
|
+
const level = buildEcosystemLevel(sortedNodes, size, parentLookup, center)
|
|
788
838
|
clustersBySize.set(size, level.clusters)
|
|
839
|
+
nodeClusterBySize.set(size, level.clusterByNodeId)
|
|
789
840
|
parentLookup = level.clusterByNodeId
|
|
790
841
|
}
|
|
791
842
|
|
|
792
843
|
return {
|
|
793
844
|
clusters: clustersBySize.get(ecosystemGroupSize) ?? [],
|
|
794
|
-
clustersBySize
|
|
845
|
+
clustersBySize,
|
|
846
|
+
nodeClusterBySize,
|
|
847
|
+
hubCluster
|
|
795
848
|
}
|
|
796
849
|
}
|
|
797
850
|
|
|
@@ -832,21 +885,22 @@ const smoothStep = value => {
|
|
|
832
885
|
const zoomProgress = (scale, start, end) =>
|
|
833
886
|
smoothStep((scale - start) / Math.max(end - start, 0.0001))
|
|
834
887
|
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
888
|
+
const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
|
|
889
|
+
const focusPoint = ecosystemFocusPoint()
|
|
890
|
+
const expandedParentIds = new Set(nearestEcosystemParentIds(
|
|
891
|
+
parentClusters,
|
|
892
|
+
focusPoint,
|
|
893
|
+
ecosystemFocusedParentLimit
|
|
894
|
+
))
|
|
895
|
+
const childClusters = state.ecosystemClustersBySize.get(childSize) ?? []
|
|
896
|
+
const visibleChildClusters = childClusters
|
|
897
|
+
.filter(cluster => expandedParentIds.has(cluster.parentId))
|
|
898
|
+
.map(cluster => spreadChildClusterFromParent(cluster, spread))
|
|
899
|
+
.filter(cluster => isClusterInViewport(cluster, viewport))
|
|
900
|
+
|
|
846
901
|
return {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
spread: zoomProgress(scale, ecosystemSubgraphScaleThreshold, ecosystemClusterScaleThreshold)
|
|
902
|
+
expandedParentIds,
|
|
903
|
+
childClusters: visibleChildClusters
|
|
850
904
|
}
|
|
851
905
|
}
|
|
852
906
|
|
|
@@ -863,50 +917,84 @@ const spreadChildClusterFromParent = (cluster, spread) => {
|
|
|
863
917
|
}
|
|
864
918
|
|
|
865
919
|
const selectHierarchicalEcosystemClusters = viewport => {
|
|
866
|
-
const
|
|
867
|
-
const baseClusters = state.ecosystemClustersBySize.get(plan.baseSize) ?? state.ecosystemClusters
|
|
920
|
+
const baseClusters = state.ecosystemClustersBySize.get(ecosystemGroupSize) ?? state.ecosystemClusters
|
|
868
921
|
const visibleBaseClusters = filterEcosystemClustersByViewport(baseClusters, viewport)
|
|
922
|
+
const hubClusters = state.ecosystemHubCluster ? [state.ecosystemHubCluster] : []
|
|
869
923
|
|
|
870
|
-
if (
|
|
871
|
-
return visibleBaseClusters
|
|
924
|
+
if (state.transform.scale <= ecosystemMicroScaleThreshold) {
|
|
925
|
+
return [...hubClusters, ...visibleBaseClusters]
|
|
872
926
|
}
|
|
873
927
|
|
|
874
|
-
const
|
|
875
|
-
const expandedParentIds = new Set(nearestEcosystemParentIds(
|
|
928
|
+
const midExpansion = expandFocusedClusters(
|
|
876
929
|
visibleBaseClusters,
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
930
|
+
250,
|
|
931
|
+
state.transform.scale <= ecosystemSubgraphScaleThreshold
|
|
932
|
+
? zoomProgress(state.transform.scale, ecosystemMicroScaleThreshold, ecosystemSubgraphScaleThreshold)
|
|
933
|
+
: 1,
|
|
934
|
+
viewport
|
|
935
|
+
)
|
|
936
|
+
if (midExpansion.childClusters.length === 0) {
|
|
937
|
+
return [...hubClusters, ...visibleBaseClusters]
|
|
938
|
+
}
|
|
885
939
|
|
|
886
|
-
|
|
887
|
-
|
|
940
|
+
const remainingBaseClusters = visibleBaseClusters.filter(cluster => !midExpansion.expandedParentIds.has(cluster.id))
|
|
941
|
+
if (state.transform.scale <= ecosystemSubgraphScaleThreshold) {
|
|
942
|
+
return [
|
|
943
|
+
...hubClusters,
|
|
944
|
+
...remainingBaseClusters,
|
|
945
|
+
...midExpansion.childClusters
|
|
946
|
+
]
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const microExpansion = expandFocusedClusters(
|
|
950
|
+
midExpansion.childClusters,
|
|
951
|
+
60,
|
|
952
|
+
zoomProgress(state.transform.scale, ecosystemSubgraphScaleThreshold, ecosystemClusterScaleThreshold),
|
|
953
|
+
viewport
|
|
954
|
+
)
|
|
955
|
+
if (microExpansion.childClusters.length === 0) {
|
|
956
|
+
return [
|
|
957
|
+
...hubClusters,
|
|
958
|
+
...remainingBaseClusters,
|
|
959
|
+
...midExpansion.childClusters
|
|
960
|
+
]
|
|
888
961
|
}
|
|
889
962
|
|
|
890
963
|
return [
|
|
891
|
-
...
|
|
892
|
-
...
|
|
964
|
+
...hubClusters,
|
|
965
|
+
...remainingBaseClusters,
|
|
966
|
+
...midExpansion.childClusters.filter(cluster => !microExpansion.expandedParentIds.has(cluster.id)),
|
|
967
|
+
...microExpansion.childClusters
|
|
893
968
|
]
|
|
894
969
|
}
|
|
895
970
|
|
|
896
971
|
const ecosystemEdgesForClusters = clusters => {
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
972
|
+
const clusterById = new Map(clusters.map(cluster => [cluster.id, cluster]))
|
|
973
|
+
const clusterIds = new Set(clusterById.keys())
|
|
974
|
+
const levelBySize = new Map()
|
|
975
|
+
for (let index = 0; index < clusters.length; index += 1) {
|
|
976
|
+
const cluster = clusters[index]
|
|
977
|
+
if (!cluster.size || cluster.isHub) continue
|
|
978
|
+
levelBySize.set(cluster.size, state.ecosystemNodeClusterBySize.get(cluster.size) ?? new Map())
|
|
979
|
+
}
|
|
980
|
+
const resolveClusterForNode = nodeId => {
|
|
981
|
+
if (state.ecosystemHubCluster?.nodeIds.includes(nodeId) && clusterIds.has(state.ecosystemHubCluster.id)) {
|
|
982
|
+
return state.ecosystemHubCluster
|
|
983
|
+
}
|
|
984
|
+
for (const [size, lookup] of levelBySize) {
|
|
985
|
+
const cluster = lookup.get(nodeId)
|
|
986
|
+
if (cluster && clusterIds.has(cluster.id)) {
|
|
987
|
+
return clusterById.get(cluster.id) ?? cluster
|
|
988
|
+
}
|
|
902
989
|
}
|
|
990
|
+
return null
|
|
903
991
|
}
|
|
904
992
|
|
|
905
993
|
const edgeByClusterPair = new Map()
|
|
906
994
|
for (let index = 0; index < state.visibleEdges.length; index += 1) {
|
|
907
995
|
const edge = state.visibleEdges[index]
|
|
908
|
-
const sourceCluster =
|
|
909
|
-
const targetCluster =
|
|
996
|
+
const sourceCluster = resolveClusterForNode(edge.source)
|
|
997
|
+
const targetCluster = resolveClusterForNode(edge.target)
|
|
910
998
|
if (!sourceCluster || !targetCluster || sourceCluster.id === targetCluster.id) {
|
|
911
999
|
continue
|
|
912
1000
|
}
|
|
@@ -929,9 +1017,34 @@ const ecosystemEdgesForClusters = clusters => {
|
|
|
929
1017
|
})
|
|
930
1018
|
}
|
|
931
1019
|
|
|
932
|
-
|
|
1020
|
+
const edges = Array.from(edgeByClusterPair.values())
|
|
933
1021
|
.sort((left, right) => right.weight - left.weight)
|
|
934
1022
|
.slice(0, ecosystemClusterEdgeLimit)
|
|
1023
|
+
const hubCluster = state.ecosystemHubCluster && clusterIds.has(state.ecosystemHubCluster.id)
|
|
1024
|
+
? state.ecosystemHubCluster
|
|
1025
|
+
: null
|
|
1026
|
+
if (!hubCluster) {
|
|
1027
|
+
return edges
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const existingHubTargets = new Set(edges.flatMap(edge =>
|
|
1031
|
+
edge.sourceCluster.id === hubCluster.id
|
|
1032
|
+
? [edge.targetCluster.id]
|
|
1033
|
+
: edge.targetCluster.id === hubCluster.id
|
|
1034
|
+
? [edge.sourceCluster.id]
|
|
1035
|
+
: []
|
|
1036
|
+
))
|
|
1037
|
+
const syntheticHubEdges = clusters
|
|
1038
|
+
.filter(cluster => cluster.id !== hubCluster.id && !existingHubTargets.has(cluster.id))
|
|
1039
|
+
.slice(0, ecosystemHubEdgeLimit)
|
|
1040
|
+
.map(cluster => ({
|
|
1041
|
+
id: hubCluster.id + ':' + cluster.id,
|
|
1042
|
+
sourceCluster: hubCluster,
|
|
1043
|
+
targetCluster: cluster,
|
|
1044
|
+
weight: 1,
|
|
1045
|
+
inferred: true
|
|
1046
|
+
}))
|
|
1047
|
+
return edges.concat(syntheticHubEdges)
|
|
935
1048
|
}
|
|
936
1049
|
|
|
937
1050
|
const edgeBudgetForCurrentFrame = () => {
|
|
@@ -2372,6 +2485,9 @@ const clusterRadiusPx = cluster => {
|
|
|
2372
2485
|
if (cluster.id === 'macro-galaxy') {
|
|
2373
2486
|
return 10
|
|
2374
2487
|
}
|
|
2488
|
+
if (cluster.isHub) {
|
|
2489
|
+
return 5.2
|
|
2490
|
+
}
|
|
2375
2491
|
if (String(cluster.id).startsWith('ecosystem-')) {
|
|
2376
2492
|
const base = cluster.size >= 1000 ? 2.4 : cluster.size >= 250 ? 2.1 : 1.8
|
|
2377
2493
|
return Math.max(1.8, Math.min(4.2, base + Math.log10(cluster.count + 1) * 0.28))
|
|
@@ -2807,10 +2923,11 @@ const render = now => {
|
|
|
2807
2923
|
state.renderClusters.forEach(cluster => {
|
|
2808
2924
|
const isMacro = cluster.id === 'macro-galaxy'
|
|
2809
2925
|
const isEcosystem = String(cluster.id).startsWith('ecosystem-')
|
|
2926
|
+
const isHub = Boolean(cluster.isHub)
|
|
2810
2927
|
const radiusPx = clusterRadiusPx(cluster)
|
|
2811
2928
|
const radius = radiusPx / safeScale
|
|
2812
|
-
const haloRadius = (radiusPx + (isMacro ? 8 : isEcosystem ? 1.1 : 4)) / safeScale
|
|
2813
|
-
if (!isEcosystem || state.transform.scale >= ecosystemSubgraphScaleThreshold) {
|
|
2929
|
+
const haloRadius = (radiusPx + (isMacro ? 8 : isHub ? 4 : isEcosystem ? 1.1 : 4)) / safeScale
|
|
2930
|
+
if (isHub || !isEcosystem || state.transform.scale >= ecosystemSubgraphScaleThreshold) {
|
|
2814
2931
|
ctx.beginPath()
|
|
2815
2932
|
ctx.arc(cluster.x, cluster.y, haloRadius, 0, Math.PI * 2)
|
|
2816
2933
|
ctx.fillStyle = isMacro ? 'rgba(243, 247, 251, 0.28)' : graphTheme.nodeHalo
|
|
@@ -2820,7 +2937,7 @@ const render = now => {
|
|
|
2820
2937
|
ctx.arc(cluster.x, cluster.y, radius, 0, Math.PI * 2)
|
|
2821
2938
|
ctx.fillStyle = isMacro ? '#f3f7fb' : graphTheme.node
|
|
2822
2939
|
ctx.fill()
|
|
2823
|
-
ctx.lineWidth = (isEcosystem ? 0.7 : 1.4) / safeScale
|
|
2940
|
+
ctx.lineWidth = (isEcosystem && !isHub ? 0.7 : 1.4) / safeScale
|
|
2824
2941
|
ctx.strokeStyle = isMacro ? '#ffffff' : graphTheme.nodeStroke
|
|
2825
2942
|
ctx.stroke()
|
|
2826
2943
|
if (isMacro && cluster.representative?.title) {
|
package/package.json
CHANGED