Skip to content

Commit

Permalink
Merge pull request #78 from sayari-analytics/feature/label-styles
Browse files Browse the repository at this point in the history
Label styles, LabelBackground
  • Loading branch information
mggower authored Nov 6, 2023
2 parents b135b82 + 83d44c9 commit e31e727
Show file tree
Hide file tree
Showing 14 changed files with 840 additions and 234 deletions.
5 changes: 4 additions & 1 deletion examples/native/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
<h1>Native Examples</h1>
<ul>
<li>
<a href="/src/simple/simple.html">Simple Trellis Example</a>
<a href="/src/perf/perf.html">Perf</a>
</li>
<li>
<a href="/src/labels/labels.html">Label Styles</a>
</li>
</ul>
</div>
Expand Down
120 changes: 120 additions & 0 deletions examples/native/src/labels/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as Renderer from '@trellis/renderers/webgl'
import * as Graph from '@trellis/index'
import * as Collide from '@trellis/layout/collide'

const GREEN = '#91AD49'
const GREEN_LIGHT = '#C6D336'
const DARK_GREEN = '#607330'

const TEXT_ICON: Graph.TextIcon = {
type: 'textIcon',
family: 'sans-serif',
color: '#fff',
weight: '400',
text: '!',
size: 14
}

const NODE_STYLE: Graph.NodeStyle = {
color: GREEN,
icon: TEXT_ICON,
stroke: [{ width: 2, color: GREEN_LIGHT }],
label: {
position: 'right',
fontName: 'NodeLabel',
fontFamily: 'Arial, sans-serif',
background: { color: GREEN_LIGHT },
margin: 4
}
}

const NODE_HOVER_STYLE: Graph.NodeStyle = {
color: DARK_GREEN,
icon: TEXT_ICON,
stroke: [{ width: 2, color: GREEN_LIGHT }],
label: {
position: 'right',
fontName: 'NodeLabelHover',
fontFamily: 'Arial, sans-serif',
background: { color: DARK_GREEN },
color: '#FFF',
margin: 4
}
}

const data = [
'Myriel',
'Napoleon',
'Mlle.Baptistine',
'Mme.Magloire',
'CountessdeLo',
'Geborand',
'Champtercier',
'Cravatte',
'Count',
'OldMan',
'Labarre',
'Valjean'
]

const collide = Collide.Layout()

const edges: Graph.Edge[] = []
let nodes = data.map<Graph.Node>((label, index) => ({
radius: 10,
label: index % 2 === 0 ? label + ' 北京' : label,
id: `${index}-${label}`,
style: NODE_STYLE
}))

const layout = collide({ nodes, edges, options: { nodePadding: 50 } })
nodes = layout.nodes

const size = { width: 1250, height: 650 }
const bounds = Graph.getSelectionBounds(nodes, 100)
const viewport = Graph.boundsToViewport(bounds, size)
const container = document.querySelector('#graph') as HTMLDivElement

const options: Renderer.Options = {
...viewport,
...size,
minZoom: 0.025,
onViewportDrag: (event: Renderer.ViewportDragEvent | Renderer.ViewportDragDecelerateEvent) => {
// console.log('viewport drag', `x: ${event.dx}, y: ${event.dy}`)
options.x! += event.dx
options.y! += event.dy
renderer.update({ nodes, edges, options })
},
onViewportWheel: ({ dx, dy, dz }) => {
options.x! += dx
options.y! += dy
options.zoom! += dz
renderer.update({ nodes, edges, options })
},
onNodePointerEnter: (event: Renderer.NodePointerEvent) => {
// console.log('node pointer enter', `x: ${event.x}, y: ${event.y}`)
nodes = nodes.map((node) => (node.id === event.target.id ? { ...node, label: node.label + ' 北京', style: NODE_HOVER_STYLE } : node))
renderer.update({ nodes, edges, options })
},
onNodeDrag: (event: Renderer.NodeDragEvent) => {
// console.log('node drag', `x: ${event.x}, y: ${event.y}`)
nodes = nodes.map((node) =>
node.id === event.target.id ? { ...node, x: (node.x ?? 0) + event.dx, y: (node.y ?? 0) + event.dy } : node
)
renderer.update({ nodes, edges, options })
},
onNodePointerLeave: (event: Renderer.NodePointerEvent) => {
// console.log('node pointer leave', `x: ${event.x}, y: ${event.y}`)
nodes = nodes.map((node) =>
node.id === event.target.id ? { ...node, label: node.label?.slice(0, node.label.length - 3), style: NODE_STYLE } : node
)
renderer.update({ nodes, edges, options })
}
}

const renderer = new Renderer.Renderer({ container, width: options.width, height: options.height, debug: true }).update({
nodes,
edges,
options
})
;(window as any).renderer = renderer
14 changes: 14 additions & 0 deletions examples/native/src/labels/labels.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../index.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<title>Trellis Example: Label Style</title>
</head>
<body>
<div id="graph"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,28 @@ const sampleCoordinatePlane = function* (count: number, step: number, sample: nu

const PURPLE = '#7A5DC5'
const LIGHT_PURPLE = '#CAD'
const ARIAL_PINK = 'ArialPink'

const NODE_STYLE: Graph.NodeStyle = {
color: PURPLE,
stroke: [{ width: 2, color: LIGHT_PURPLE }],
icon: { type: 'textIcon', text: 'T', family: 'sans-serif', size: 14, color: '#fff', weight: '400' },
label: {
position: 'bottom',
color: LIGHT_PURPLE
position: 'top',
fontName: ARIAL_PINK,
fontFamily: ['Arial', 'sans-serif'],
margin: 2,
background: {
color: '#f66',
opacity: 0.5
}
}
}

const NODE_HOVER_STYLE: Graph.NodeStyle = {
color: '#f66',
stroke: [{ width: 2, color: '#fcc' }],
label: { position: 'bottom' },
label: { position: 'bottom', color: '#fcc' },
icon: { type: 'textIcon', text: 'L', family: 'sans-serif', size: 14, color: '#fff', weight: '400' }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../index.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<title>Simple Trellis Example</title>
<title>Trellis Example: Perf</title>
</head>
<body>
<div id="graph"></div>
Expand Down
1 change: 1 addition & 0 deletions src/bindings/react/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type Props = {
group?: 'top' | 'middle' | 'bottom'
title?: string
onClick?: () => void
children?: React.ReactNode
}

const STYLE = {
Expand Down
22 changes: 6 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { TWO_PI } from './renderers/webgl/utils'
import type { LabelStyle } from './renderers/webgl/objects/label'
import type { Stroke } from './types'

export type Node = {
id: string
Expand Down Expand Up @@ -43,22 +45,6 @@ export type ImageIcon = {
offsetY?: number
}

export type Stroke = { color: string; width: number }

export type LabelBackground = { color: string; opacity?: number }

export type LabelPosition = 'bottom' | 'left' | 'top' | 'right'

export type LabelStyle = Partial<{
color: string
fontFamily: string
fontSize: number
maxWidth: number
stroke: Stroke
position: LabelPosition
background: LabelBackground
}>

export type NodeStyle = {
color?: string
icon?: TextIcon | ImageIcon
Expand Down Expand Up @@ -392,3 +378,7 @@ export const angle = (x0: number, y0: number, x1: number, y1: number) => {
const angle = Math.atan2(y0 - y1, x0 - x1)
return angle < 0 ? angle + TWO_PI : angle
}

// exports
export type { Stroke } from './types'
export type { LabelStyle, LabelBackgroundStyle, LabelPosition, FontWeight, TextAlign } from './renderers/webgl/objects/label'
22 changes: 10 additions & 12 deletions src/renderers/webgl/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { FederatedPointerEvent } from 'pixi.js'
import { MIN_LABEL_ZOOM, MIN_INTERACTION_ZOOM, MIN_NODE_STROKE_ZOOM, Renderer, MIN_NODE_ICON_ZOOM } from '.'
import * as Graph from '../..'
import { Label } from './objects/label'
import { positionNodeLabel } from './utils'
import { NodeFill } from './objects/nodeFill'
import { NodeStrokes } from './objects/nodeStrokes'
import { Icon } from './objects/icon'
Expand Down Expand Up @@ -44,15 +43,15 @@ export class NodeRenderer {

update(node: Graph.Node) {
if (this.label === undefined) {
if (node.label) {
this.label = new Label(this.renderer.labelsContainer, node.label)
}
} else {
if (node.label === undefined) {
this.renderer.labelObjectManager.delete(this.label)
this.labelMounted = false
this.label = undefined
if (node.label !== undefined) {
this.label = new Label(this.renderer.labelsContainer, node.label, node.style?.label)
}
} else if (node.label === undefined || node.label.trim() === '') {
this.renderer.labelObjectManager.delete(this.label)
this.labelMounted = false
this.label = undefined
} else if (!this.label.equals(node.label, node.style?.label)) {
this.label.update(node.label, node.style?.label)
}

if (this.icon === undefined) {
Expand Down Expand Up @@ -502,9 +501,8 @@ export class NodeRenderer {

this.fill.update(this.x, this.y, radius, node.style)
this.strokes.update(this.x, this.y, radius, node.style)
if (this.label && node.label) {
const labelPosition = positionNodeLabel(this.x, this.y, node.label, this.strokes.radius, node.style?.label?.position)
this.label.update(node.label, labelPosition[0], labelPosition[1], node.style?.label)
if (this.label !== undefined) {
this.label.moveTo(this.x, this.y, this.strokes.radius)
}
if (this.icon && node.style?.icon) {
this.icon.update(this.x, this.y, node.style.icon)
Expand Down
Loading

0 comments on commit e31e727

Please sign in to comment.