Skip to content

Commit

Permalink
Highlight node with ring on hover/click
Browse files Browse the repository at this point in the history
  • Loading branch information
Stukova committed Jan 12, 2023
1 parent c5ace56 commit a5dae7a
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 6 deletions.
7 changes: 7 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ export interface GraphConfigInterface<N extends InputNode, L extends InputLink>
*/
nodeSizeScale?: number;

/**
* Highlighted node ring color hex value.
* Default value: undefined
*/
highlightedNodeRingColor?: string;

/**
* Turns link rendering on / off.
* Default value: `true`
Expand Down Expand Up @@ -269,6 +275,7 @@ export class GraphConfig<N extends InputNode, L extends InputLink> implements Gr
public nodeGreyoutOpacity = defaultGreyoutNodeOpacity
public nodeSize = defaultNodeSize
public nodeSizeScale = defaultConfigValues.nodeSizeScale
public highlightedNodeRingColor = undefined
public linkColor = defaultLinkColor
public linkGreyoutOpacity = defaultGreyoutLinkOpacity
public linkWidth = defaultLinkWidth
Expand Down
51 changes: 50 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Graph<N extends InputNode, L extends InputLink> {
private isRightClickMouse = false

private graph = new GraphData<N, L>()
private store = new Store()
private store = new Store<N>()
private points: Points<N, L>
private lines: Lines<N, L>
private forceGravity: ForceGravity<N, L>
Expand Down Expand Up @@ -92,6 +92,7 @@ export class Graph<N extends InputNode, L extends InputLink> {
this.forceMouse = new ForceMouse(this.reglInstance, this.config, this.store, this.graph, this.points)

this.store.backgroundColor = getRgbaColor(this.config.backgroundColor)
if (this.config.highlightedNodeRingColor) this.store.setHighlightedNodeRingColor(this.config.highlightedNodeRingColor)

if (this.config.showFPSMonitor) this.fpsMonitor = new FPSMonitor(this.canvas)

Expand Down Expand Up @@ -129,6 +130,9 @@ export class Graph<N extends InputNode, L extends InputLink> {
if (prevConfig.nodeSize !== this.config.nodeSize) this.points.updateSize()
if (prevConfig.linkWidth !== this.config.linkWidth) this.lines.updateWidth()
if (prevConfig.backgroundColor !== this.config.backgroundColor) this.store.backgroundColor = getRgbaColor(this.config.backgroundColor)
if (prevConfig.highlightedNodeRingColor !== this.config.highlightedNodeRingColor) {
this.store.setHighlightedNodeRingColor(this.config.highlightedNodeRingColor)
}
if (prevConfig.spaceSize !== this.config.spaceSize ||
prevConfig.simulation.repulsionQuadtreeLevels !== this.config.simulation.repulsionQuadtreeLevels) this.update(this.store.isSimulationRunning)
if (prevConfig.showFPSMonitor !== this.config.showFPSMonitor) {
Expand Down Expand Up @@ -397,6 +401,25 @@ export class Graph<N extends InputNode, L extends InputLink> {
return this.graph.getAdjacentNodes(id)
}

/**
* Converts x and y coordinates from space to the screen coordinate system.
* @param spacePosition Array of x and y coordinates in the space coordinate system.
* @returns Array of x and y coordinates in the screen coordinate system.
*/

public spaceToScreenPosition (spacePosition: [number, number]): [number, number] {
return this.zoomInstance.convertSpaceToScreenPosition(spacePosition)
}

/**
* Converts node radius from space to the screen coordinate system.
* @param spaceRadius Radius of Node in the space coordinate system.
* @returns Radius of Node in the screen coordinate system.
*/
public spaceToScreenRadius (spaceRadius: number): number {
return this.zoomInstance.convertSpaceToScreenRadius(spaceRadius)
}

/**
* Start the simulation.
* @param alpha Value from 0 to 1. The higher the value, the more initial energy the simulation will get.
Expand Down Expand Up @@ -497,6 +520,7 @@ export class Graph<N extends InputNode, L extends InputLink> {
this.requestAnimationFrameId = window.requestAnimationFrame((now) => {
this.fpsMonitor?.begin()
this.resizeCanvas()
this.findHoveredPoint()

if (this.isRightClickMouse) {
if (!isSimulationRunning) this.start(0.1)
Expand Down Expand Up @@ -558,6 +582,7 @@ export class Graph<N extends InputNode, L extends InputLink> {
}

private onClick (event: MouseEvent): void {
this.store.setClickedNode()
this.points.findPointsOnMouseClick()
const pixels = readPixels(this.reglInstance, this.points.selectedFbo as regl.Framebuffer2D)
let position: [number, number] | undefined
Expand Down Expand Up @@ -642,6 +667,30 @@ export class Graph<N extends InputNode, L extends InputLink> {
.call(this.zoomInstance.behavior.transform, transform)
}
}

private findHoveredPoint (): void {
this.points.findHoveredPoint()
const pixels = readPixels(this.reglInstance, this.points.hoveredFbo as regl.Framebuffer2D)
const nodeSize = pixels[1] as number
if (nodeSize) {
const index = pixels[0] as number
const i = index % this.store.pointsTextureSize
const j = Math.floor(index / this.store.pointsTextureSize)
const inputIndex = this.graph.getInputIndexBySortedIndex(index)
const hovered = inputIndex ? this.graph.getNodeByIndex(inputIndex) : undefined
this.store.hoveredNode.node = hovered
this.store.hoveredNode.indicesFromFbo = [i, j]
const pointX = pixels[2] as number
const pointY = pixels[3] as number
this.store.hoveredNode.position = [pointX, pointY]
this.store.hoveredNode.radius = nodeSize / 2
} else {
this.store.hoveredNode.node = undefined
this.store.hoveredNode.indicesFromFbo = [-1, -1]
this.store.hoveredNode.position = undefined
this.store.hoveredNode.radius = undefined
}
}
}

export type { InputLink, InputNode } from './types'
Expand Down
19 changes: 19 additions & 0 deletions src/modules/Points/draw-highlighted.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
precision mediump float;

uniform vec4 color;
uniform float width;

varying vec2 pos;
varying float isHighlighted;

const float smoothing = 1.05;

void main () {
if (isHighlighted == 0.0) discard;

vec2 cxy = pos;
float r = dot(cxy, cxy);
float opacity = smoothstep(r, r * smoothing, 1.0);
float stroke = smoothstep(width, width * smoothing, r);
gl_FragColor = vec4(color.rgb, opacity * stroke * color.a);
}
50 changes: 50 additions & 0 deletions src/modules/Points/draw-highlighted.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
precision mediump float;

attribute vec2 quad;

uniform sampler2D positions;
uniform sampler2D particleSize;
uniform mat3 transform;
uniform float pointsTextureSize;
uniform float sizeScale;
uniform float spaceSize;
uniform vec2 screenSize;
uniform bool scaleNodesOnZoom;
uniform vec2 hoveredPointIndices;
uniform float maxPointSize;
uniform vec4 color;

varying float isHighlighted;
varying vec2 pos;

float pointSize(float size) {
float pSize;
if (scaleNodesOnZoom) {
pSize = size * transform[0][0];
} else {
pSize = size * min(5.0, max(1.0, transform[0][0] * 0.01));
}
return min(pSize, maxPointSize);
}

const float relativeRingRadius = 1.3;

void main () {
if (hoveredPointIndices.r < 0.0) isHighlighted = 0.0;
else isHighlighted = 1.0;
pos = quad;
vec4 pointPosition = texture2D(positions, (hoveredPointIndices + 0.5) / pointsTextureSize);
vec4 pSize = texture2D(particleSize, (hoveredPointIndices + 0.5) / pointsTextureSize);
float size = (pointSize(pSize.r * sizeScale) * relativeRingRadius) / transform[0][0];
float radius = size * 0.5;
vec2 a = pointPosition.xy;
vec2 b = pointPosition.xy + vec2(0.0, radius);
vec2 xBasis = b - a;
vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));
vec2 point = a + xBasis * quad.x + yBasis * radius * quad.y;
vec2 p = 2.0 * point / spaceSize - 1.0;
p *= spaceSize / screenSize;
vec3 final = transform * vec3(p, 1);

gl_Position = vec4(final.rg, 0, 1);
}
9 changes: 9 additions & 0 deletions src/modules/Points/find-hovered-point.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifdef GL_ES
precision highp float;
#endif

varying vec4 rgba;

void main() {
gl_FragColor = rgba;
}
55 changes: 55 additions & 0 deletions src/modules/Points/find-hovered-point.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifdef GL_ES
precision highp float;
#endif

uniform sampler2D position;
uniform float pointsTextureSize;
uniform sampler2D particleSize;
uniform float sizeScale;
uniform float spaceSize;
uniform vec2 screenSize;
uniform float ratio;
uniform mat3 transform;
uniform vec2 mousePosition;
uniform bool scaleNodesOnZoom;
uniform float maxPointSize;

attribute vec2 indexes;

varying vec4 rgba;

float pointSize(float size) {
float pSize;
if (scaleNodesOnZoom) {
pSize = size * ratio * transform[0][0];
} else {
pSize = size * ratio * min(5.0, max(1.0, transform[0][0] * 0.01));
}
return min(pSize, maxPointSize);
}

float euclideanDistance (float x1, float x2, float y1, float y2) {
return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}

void main() {
vec4 pointPosition = texture2D(position, (indexes + 0.5) / pointsTextureSize);
vec2 p = 2.0 * pointPosition.rg / spaceSize - 1.0;
p *= spaceSize / screenSize;
vec3 final = transform * vec3(p, 1);

vec4 pSize = texture2D(particleSize, indexes / pointsTextureSize);
float size = pSize.r * sizeScale;
float pointRadius = 0.5 * pointSize(size);

vec2 pointScreenPosition = (final.xy + 1.0) * screenSize / 2.0;
rgba = vec4(0.0);
gl_Position = vec4(0.5, 0.5, 0.0, 1.0);
if (euclideanDistance(pointScreenPosition.x, mousePosition.x, pointScreenPosition.y, mousePosition.y) < pointRadius) {
float index = indexes.g * pointsTextureSize + indexes.r;
rgba = vec4(index, pSize.r, pointPosition.xy);
gl_Position = vec4(-0.5, -0.5, 0.0, 1.0);
}

gl_PointSize = 1.0;
}
Loading

0 comments on commit a5dae7a

Please sign in to comment.