@cosmos.gl/graph 2.0.0 → 2.2.0

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.
Files changed (32) hide show
  1. package/dist/config.d.ts +15 -1
  2. package/dist/index.d.ts +12 -0
  3. package/dist/index.js +2825 -2635
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.min.js +165 -54
  6. package/dist/index.min.js.map +1 -1
  7. package/dist/modules/Points/index.d.ts +6 -0
  8. package/dist/stories/clusters/lasso-selection/index.d.ts +6 -0
  9. package/dist/stories/clusters/lasso-selection/lasso.d.ts +20 -0
  10. package/dist/stories/clusters.stories.d.ts +1 -0
  11. package/dist/variables.d.ts +2 -0
  12. package/package.json +1 -1
  13. package/src/config.ts +15 -1
  14. package/src/index.ts +104 -5
  15. package/src/modules/Lines/draw-curve-line.frag +7 -8
  16. package/src/modules/Lines/draw-curve-line.vert +65 -15
  17. package/src/modules/Lines/index.ts +2 -3
  18. package/src/modules/Points/find-points-on-lasso-selection.frag +65 -0
  19. package/src/modules/Points/index.ts +69 -0
  20. package/src/stories/2. configuration.mdx +3 -1
  21. package/src/stories/3. api-reference.mdx +19 -0
  22. package/src/stories/beginners/basic-set-up/index.ts +2 -1
  23. package/src/stories/beginners/point-labels/index.ts +2 -1
  24. package/src/stories/beginners/quick-start.ts +1 -0
  25. package/src/stories/beginners/remove-points/config.ts +2 -1
  26. package/src/stories/clusters/lasso-selection/index.ts +53 -0
  27. package/src/stories/clusters/lasso-selection/lasso.ts +143 -0
  28. package/src/stories/clusters/lasso-selection/style.css +8 -0
  29. package/src/stories/clusters.stories.ts +16 -0
  30. package/src/stories/create-cosmos.ts +2 -1
  31. package/src/stories/generate-mesh-data.ts +1 -1
  32. package/src/variables.ts +3 -1
@@ -24,6 +24,7 @@ import { Meta } from "@storybook/blocks";
24
24
  | linkGreyoutOpacity | Greyed out link opacity value when the selection is active | `0.1` |
25
25
  | linkWidth | The default width value to use for links when no link widths are provided or if the width value in the array is `undefined` or `null` | `1` |
26
26
  | linkWidthScale | Scale factor for the link width | `1` |
27
+ | scaleLinksOnZoom | Increase/decrease link width when zooming | `false` |
27
28
  | curvedLinks | If set to true, links are rendered as curved lines. Otherwise as straight lines | `false` |
28
29
  | curvedLinkSegments | Number of segments in a curved line | `19` |
29
30
  | curvedLinkWeight | Weight affects the shape of the curve | `0.8` |
@@ -35,7 +36,7 @@ import { Meta } from "@storybook/blocks";
35
36
  | useClassicQuadtree | Use the classic [quadtree algorithm](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation) for the Many-Body force. This property will be applied only on component initialization and it can't be changed using the `setConfig` method.<br /><br /> ⚠ The algorithm might not work on some GPUs (e.g. Nvidia) and on Windows (unless you disable ANGLE in the browser settings). | `false` |
36
37
  | showFPSMonitor | Show WebGL performance monitor | `false` |
37
38
  | pixelRatio | Canvas pixel ratio | `2` |
38
- | scalePointsOnZoom | Increase/decrease point size when zooming | `true` |
39
+ | scalePointsOnZoom | Increase/decrease point size when zooming | `false` |
39
40
  | initialZoomLevel | Initial zoom level (set once during initialization) | `undefined` |
40
41
  | enableZoom | Enables zooming interactions | `true` |
41
42
  | enableSimulationDuringZoom | Keep simulation running during zoom operations | `false` |
@@ -70,6 +71,7 @@ cosmos.gl layout algorithm was inspired by the [d3-force](https://github.com/d3/
70
71
  | simulationLinkDistance | Minimum link distance | 1 – 20 | `10` |
71
72
  | simulationLinkDistRandomVariationRange | Link distance randomness multiplier range | [0.8 – 1.2,<br/> 1.2 – 2.0] | `[1.0, 1.2]` |
72
73
  | simulationRepulsionFromMouse | Repulsion from the mouse pointer force coefficient. The repulsion force is activated by pressing the right mouse button. | 0.0 – 5.0 | `2.0`
74
+ | enableRightClickRepulsion | Enable or disable the repulsion force from mouse when right-clicking. When set to `true`, holding the right mouse button will activate the mouse repulsion force. When set to `false`, right-clicking will not trigger any repulsion force. | - | `false` |
73
75
  | simulationFriction | Friction coefficient. Values range from `0` (high friction, stops quickly) to `1` (no friction, keeps moving). | 0.0 – 1.0 | `0.85` |
74
76
  | simulationCluster | Cluster coefficient | 0.0 – 1.0 | `0.1` |
75
77
 
@@ -249,6 +249,25 @@ Select points within a rectangular area defined by two corner points `[[left, to
249
249
 
250
250
  The `top` and `bottom` values represent the vertical position in pixels, relative to the top edge of the canvas, with `0` being the topmost position and the height of the canvas being the bottommost position.
251
251
 
252
+ ### <a name="get_points_in_lasso" href="#get_points_in_lasso">#</a> graph.<b>getPointsInLasso</b>(<i>lassoPath</i>)
253
+
254
+ Get points as a Float32Array within a lasso (polygon) area defined by an array of coordinate points.
255
+
256
+ * **`lassoPath`** (Array): An array of coordinate points in the format `[[x1, y1], [x2, y2], ..., [xN, yN]]` that defines the lasso polygon. The coordinates should be in pixels relative to the canvas, where:
257
+ - **`x`**: Horizontal position from 0 to the width of the canvas
258
+ - **`y`**: Vertical position from 0 to the height of the canvas
259
+ - The polygon requires at least 3 points to form a valid selection area
260
+
261
+ **Returns:** A Float32Array containing the indices of points inside the lasso area.
262
+
263
+ ### <a name="select_points_in_lasso" href="#select_points_in_lasso">#</a> graph.<b>selectPointsInLasso</b>(<i>lassoPath</i>)
264
+
265
+ Select points within a lasso (polygon) area defined by an array of coordinate points. This method combines the functionality of `getPointsInLasso` with point selection, making the identified points visually selected in the graph.
266
+
267
+ * **`lassoPath`** (Array | null): An array of coordinate points in the format `[[x1, y1], [x2, y2], ..., [xN, yN]]` that defines the lasso polygon, or `null` to clear the current selection. The coordinates should be in pixels relative to the canvas, where:
268
+ - **`x`**: Horizontal position from 0 to the width of the canvas
269
+ - **`y`**: Vertical position from 0 to the height of the canvas
270
+ - The polygon requires at least 3 points to form a valid selection area
252
271
 
253
272
  ### <a name="select_point_by_index" href="#select_point_by_index">#</a> graph.<b>selectPointByIndex</b>(<i>index</i>, [<i>selectAdjacentPoints</i>])
254
273
 
@@ -24,7 +24,8 @@ export const basicSetUp = (): { graph: Graph; div: HTMLDivElement} => {
24
24
  backgroundColor: '#2d313a',
25
25
  pointSize: 4,
26
26
  pointColor: '#4B5BBF',
27
- linkWidth: 0.1,
27
+ linkWidth: 0.6,
28
+ scalePointsOnZoom: true,
28
29
  linkColor: '#5F74C2',
29
30
  linkArrows: false,
30
31
  linkGreyoutOpacity: 0,
@@ -30,7 +30,8 @@ export const pointLabels = (
30
30
  const graph = new Graph(graphDiv, {
31
31
  spaceSize: 4096,
32
32
  backgroundColor: '#2d313a',
33
- linkWidth: 0.1,
33
+ scalePointsOnZoom: true,
34
+ linkWidth: 0.6,
34
35
  linkColor: '#5F74C2',
35
36
  linkArrows: false,
36
37
  fitViewOnInit: false,
@@ -9,6 +9,7 @@ export const quickStart = (): { graph: Graph; div: HTMLDivElement} => {
9
9
  spaceSize: 4096,
10
10
  backgroundColor: '#2d313a',
11
11
  pointColor: '#F069B4',
12
+ scalePointsOnZoom: true,
12
13
  simulationFriction: 0.1, // keeps the graph inert
13
14
  simulationGravity: 0, // disables gravity
14
15
  simulationRepulsion: 0.5, // increases repulsion between points
@@ -5,8 +5,9 @@ export const config: GraphConfigInterface = {
5
5
  backgroundColor: '#2d313a',
6
6
  pointSize: 4,
7
7
  pointColor: '#4B5BBF',
8
+ scalePointsOnZoom: true,
8
9
  pointGreyoutOpacity: 0.1,
9
- linkWidth: 0.1,
10
+ linkWidth: 0.6,
10
11
  linkColor: '#5F74C2',
11
12
  linkArrows: false,
12
13
  linkGreyoutOpacity: 0,
@@ -0,0 +1,53 @@
1
+ import { Graph } from '@cosmos.gl/graph'
2
+ import { createCosmos } from '../../create-cosmos'
3
+ import { generateMeshData } from '../../generate-mesh-data'
4
+ import { LassoSelection } from './lasso'
5
+
6
+ export const lassoSelection = (): {div: HTMLDivElement; graph: Graph; destroy: () => void } => {
7
+ const nClusters = 25
8
+ const { pointPositions, pointColors, pointClusters } = generateMeshData(150, 150, nClusters, 1.0)
9
+
10
+ const { div, graph } = createCosmos({
11
+ pointPositions,
12
+ pointColors,
13
+ pointClusters,
14
+ simulationGravity: 1.5,
15
+ simulationCluster: 0.3,
16
+ simulationRepulsion: 8,
17
+ pointSize: 8,
18
+ backgroundColor: '#1a1a2e',
19
+ pointGreyoutOpacity: 0.2,
20
+ onClick: (index: number | undefined): void => {
21
+ if (index === undefined) {
22
+ graph.unselectPoints()
23
+ }
24
+ },
25
+ })
26
+
27
+ graph.setZoomLevel(0.4)
28
+
29
+ const lassoSelection = new LassoSelection(div, (lassoPoints) => {
30
+ graph.selectPointsInLasso(lassoPoints)
31
+ })
32
+
33
+ const actionsDiv = document.createElement('div')
34
+ actionsDiv.className = 'actions'
35
+ div.appendChild(actionsDiv)
36
+
37
+ const lassoButton = document.createElement('div')
38
+ lassoButton.className = 'action'
39
+ lassoButton.textContent = 'Enable Lasso Selection'
40
+ lassoButton.addEventListener('click', () => {
41
+ lassoSelection.enableLassoMode()
42
+ })
43
+ actionsDiv.appendChild(lassoButton)
44
+
45
+ const destroy = (): void => {
46
+ lassoSelection.destroy()
47
+ if (actionsDiv.parentNode) {
48
+ actionsDiv.parentNode.removeChild(actionsDiv)
49
+ }
50
+ }
51
+
52
+ return { div, graph, destroy }
53
+ }
@@ -0,0 +1,143 @@
1
+ import './style.css'
2
+
3
+ export class LassoSelection {
4
+ private canvas: HTMLCanvasElement
5
+ private ctx: CanvasRenderingContext2D
6
+ private isDrawing = false
7
+ private points: Array<{ x: number; y: number }> = []
8
+ private graphDiv: HTMLElement
9
+ private onLassoComplete?: (points: [number, number][]) => void
10
+ private boundStartDrawing: (e: MouseEvent) => void
11
+ private boundDraw: (e: MouseEvent) => void
12
+ private boundStopDrawing: () => void
13
+ private resizeObserver: ResizeObserver
14
+
15
+ public constructor (graphDiv: HTMLElement, onLassoComplete?: (points: [number, number][]) => void) {
16
+ this.graphDiv = graphDiv
17
+ this.onLassoComplete = onLassoComplete
18
+
19
+ // Bind event handlers
20
+ this.boundStartDrawing = this.startDrawing.bind(this)
21
+ this.boundDraw = this.draw.bind(this)
22
+ this.boundStopDrawing = this.stopDrawing.bind(this)
23
+
24
+ // Create canvas
25
+ this.canvas = document.createElement('canvas')
26
+ this.canvas.className = 'lasso-canvas'
27
+
28
+ const ctx = this.canvas.getContext('2d')
29
+ if (!ctx) throw new Error('Could not get canvas context')
30
+ this.ctx = ctx
31
+
32
+ this.graphDiv.appendChild(this.canvas)
33
+
34
+ this.resizeObserver = new ResizeObserver(() => {
35
+ this.resizeCanvas()
36
+ })
37
+ this.resizeObserver.observe(this.graphDiv)
38
+ }
39
+
40
+ public enableLassoMode (): void {
41
+ this.canvas.style.pointerEvents = 'auto'
42
+ this.canvas.style.cursor = 'crosshair'
43
+
44
+ // Add event listeners
45
+ this.canvas.addEventListener('mousedown', this.boundStartDrawing)
46
+ this.canvas.addEventListener('mousemove', this.boundDraw)
47
+ this.canvas.addEventListener('mouseup', this.boundStopDrawing)
48
+ }
49
+
50
+ public disableLassoMode (): void {
51
+ this.canvas.style.pointerEvents = 'none'
52
+ this.canvas.style.cursor = 'default'
53
+
54
+ // Remove event listeners
55
+ this.canvas.removeEventListener('mousedown', this.boundStartDrawing)
56
+ this.canvas.removeEventListener('mousemove', this.boundDraw)
57
+ this.canvas.removeEventListener('mouseup', this.boundStopDrawing)
58
+
59
+ // Clear canvas
60
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
61
+ }
62
+
63
+ public destroy (): void {
64
+ this.disableLassoMode()
65
+ this.resizeObserver.disconnect()
66
+ if (this.canvas.parentNode) {
67
+ this.canvas.parentNode.removeChild(this.canvas)
68
+ }
69
+ }
70
+
71
+ private resizeCanvas (): void {
72
+ const rect = this.graphDiv.getBoundingClientRect()
73
+
74
+ // Apply pixel ratio for crisp rendering
75
+ const pixelRatio = window.devicePixelRatio || 1
76
+ this.canvas.width = rect.width * pixelRatio
77
+ this.canvas.height = rect.height * pixelRatio
78
+
79
+ // Reset transform and scale the context to match the pixel ratio
80
+ this.ctx.resetTransform()
81
+ this.ctx.scale(pixelRatio, pixelRatio)
82
+ }
83
+
84
+ private startDrawing (e: MouseEvent): void {
85
+ this.isDrawing = true
86
+ this.points = []
87
+ const rect = this.canvas.getBoundingClientRect()
88
+ this.points.push({ x: e.clientX - rect.left, y: e.clientY - rect.top })
89
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
90
+ }
91
+
92
+ private draw (e: MouseEvent): void {
93
+ if (!this.isDrawing) return
94
+
95
+ const rect = this.canvas.getBoundingClientRect()
96
+ this.points.push({ x: e.clientX - rect.left, y: e.clientY - rect.top })
97
+
98
+ // Clear the entire canvas accounting for pixel ratio
99
+ const pixelRatio = window.devicePixelRatio || 1
100
+ this.ctx.clearRect(0, 0, this.canvas.width / pixelRatio, this.canvas.height / pixelRatio)
101
+
102
+ this.ctx.beginPath()
103
+ if (this.points.length > 0 && this.points[0]) {
104
+ this.ctx.moveTo(this.points[0].x, this.points[0].y)
105
+ }
106
+
107
+ for (let i = 1; i < this.points.length; i++) {
108
+ const point = this.points[i]
109
+ if (point) {
110
+ this.ctx.lineTo(point.x, point.y)
111
+ }
112
+ }
113
+
114
+ this.ctx.strokeStyle = '#ffffff'
115
+ this.ctx.lineWidth = 2
116
+ this.ctx.stroke()
117
+ }
118
+
119
+ private stopDrawing (): void {
120
+ if (!this.isDrawing) return
121
+ this.isDrawing = false
122
+
123
+ if (this.points.length > 2) {
124
+ this.ctx.closePath()
125
+ this.ctx.stroke()
126
+
127
+ const lassoPoints: [number, number][] = this.points.map(p => [p.x, p.y])
128
+ const firstLassoPoint = lassoPoints[0]
129
+ const lastLassoPoint = lassoPoints[lassoPoints.length - 1]
130
+ if (firstLassoPoint && lastLassoPoint && (firstLassoPoint[0] !== lastLassoPoint[0] || firstLassoPoint[1] !== lastLassoPoint[1])) {
131
+ lassoPoints.push(firstLassoPoint)
132
+ }
133
+
134
+ if (this.onLassoComplete) {
135
+ this.onLassoComplete(lassoPoints)
136
+ }
137
+ }
138
+
139
+ const pixelRatio = window.devicePixelRatio || 1
140
+ this.ctx.clearRect(0, 0, this.canvas.width / pixelRatio, this.canvas.height / pixelRatio)
141
+ this.disableLassoMode()
142
+ }
143
+ }
@@ -0,0 +1,8 @@
1
+ .lasso-canvas {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ pointer-events: none;
6
+ width: 100%;
7
+ height: 100%;
8
+ }
@@ -4,6 +4,7 @@ import { createStory, Story } from '@/graph/stories/create-story'
4
4
  import { withLabels } from './clusters/with-labels'
5
5
  import { worm } from './clusters/worm'
6
6
  import { radial } from './clusters/radial'
7
+ import { lassoSelection } from './clusters/lasso-selection'
7
8
 
8
9
  import createCosmosRaw from './create-cosmos?raw'
9
10
  import generateMeshDataRaw from './generate-mesh-data?raw'
@@ -11,6 +12,9 @@ import withLabelsStoryRaw from './clusters/with-labels?raw'
11
12
  import createClusterLabelsRaw from './create-cluster-labels?raw'
12
13
  import wormStory from './clusters/worm?raw'
13
14
  import radialStory from './clusters/radial?raw'
15
+ import lassoSelectionStory from './clusters/lasso-selection?raw'
16
+ import lassoSelectionStyleRaw from './clusters/lasso-selection/style.css?raw'
17
+ import lassoSelectionLassoRaw from './clusters/lasso-selection/lasso.ts?raw'
14
18
 
15
19
  const meta: Meta<CosmosStoryProps> = {
16
20
  title: 'Examples/Clusters',
@@ -57,5 +61,17 @@ export const WithLabels: Story = {
57
61
  },
58
62
  }
59
63
 
64
+ export const LassoSelection: Story = {
65
+ ...createStory(lassoSelection),
66
+ parameters: {
67
+ sourceCode: [
68
+ { name: 'Story', code: lassoSelectionStory },
69
+ { name: 'lasso.ts', code: lassoSelectionLassoRaw },
70
+ ...sourceCodeAddonParams,
71
+ { name: 'style.css', code: lassoSelectionStyleRaw },
72
+ ],
73
+ },
74
+ }
75
+
60
76
  // eslint-disable-next-line import/no-default-export
61
77
  export default meta
@@ -25,7 +25,8 @@ export const createCosmos = (props: CosmosStoryProps): { div: HTMLDivElement; gr
25
25
  pointSize: 3,
26
26
  pointColor: '#4B5BBF',
27
27
  pointGreyoutOpacity: 0.1,
28
- linkWidth: 0.2,
28
+ scalePointsOnZoom: true,
29
+ linkWidth: 0.8,
29
30
  linkColor: '#5F74C2',
30
31
  linkArrows: false,
31
32
  linkGreyoutOpacity: 0,
@@ -104,7 +104,7 @@ export function generateMeshData (
104
104
  linkColors[i * 4 + 2] = rgba[2]
105
105
  linkColors[i * 4 + 3] = 0.9
106
106
 
107
- linkWidths[i] = getRandom(0.1, 0.5)
107
+ linkWidths[i] = getRandom(0.4, 0.8)
108
108
  // linkStrength[i] = (n * m - sourcePointIndex) / (n * m)
109
109
  }
110
110
 
package/src/variables.ts CHANGED
@@ -43,7 +43,8 @@ export const defaultConfigValues = {
43
43
  },
44
44
  showFPSMonitor: false,
45
45
  pixelRatio: 2,
46
- scalePointsOnZoom: true,
46
+ scalePointsOnZoom: false,
47
+ scaleLinksOnZoom: false,
47
48
  enableZoom: true,
48
49
  enableSimulationDuringZoom: false,
49
50
  enableDrag: false,
@@ -54,6 +55,7 @@ export const defaultConfigValues = {
54
55
  pointSamplingDistance: 150,
55
56
  attribution: '',
56
57
  rescalePositions: undefined,
58
+ enableRightClickRepulsion: false,
57
59
  }
58
60
 
59
61
  export const hoveredPointRingOpacity = 0.7