Skip to content

Commit

Permalink
Merge pull request #277 from PrefectHQ/add-animation
Browse files Browse the repository at this point in the history
add animation to nodes and edges
  • Loading branch information
brandonreid authored Oct 27, 2023
2 parents dd7b426 + af6939f commit 48f33fd
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 32 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"d3": "7.8.5",
"date-fns": "2.30.0",
"fontfaceobserver": "^2.3.0",
"gsap": "^3.12.2",
"lodash.isequal": "4.5.0",
"lodash.merge": "4.6.2",
"pixi-viewport": "5.0.2",
Expand Down
22 changes: 22 additions & 0 deletions src/factories/animation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import gsap from 'gsap'
import { waitForViewport } from '@/objects'
import { waitForConfig } from '@/objects/config'

export async function animate(el: gsap.TweenTarget, vars: gsap.TweenVars, skipAnimation?: boolean): Promise<gsap.core.Tween> {
const config = await waitForConfig()
const viewport = await waitForViewport()
const duration = skipAnimation ? 0 : config.animationDuration / 1000

return gsap.to(el, {
...vars,
duration,
ease: 'power1.out',
}).then(() => {
if (!skipAnimation) {
// setting the viewport dirty here will allow culling to take place after any
// number of animations that may have been called at once have completed, rather
// than calling cull() n number of times all at once.
viewport.dirty = true
}
})
}
59 changes: 43 additions & 16 deletions src/factories/edge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Container, Point, SimpleRope } from 'pixi.js'
import { DEFAULT_EDGE_CONTAINER_NAME, DEFAULT_EDGE_MINIMUM_BEZIER, DEFAULT_EDGE_POINTS } from '@/consts'
import { animate } from '@/factories/animation'
import { ArrowDirection, arrowFactory } from '@/factories/arrow'
import { Pixels } from '@/models/layout'
import { waitForConfig } from '@/objects/config'
Expand All @@ -10,6 +11,9 @@ import { repeat } from '@/utilities/repeat'

export type EdgeFactory = Awaited<ReturnType<typeof edgeFactory>>

const arrowOffset = 8
const edgeTargetOffset = 2

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function edgeFactory() {
const config = await waitForConfig()
Expand All @@ -21,6 +25,7 @@ export async function edgeFactory() {
const pixel = await getPixelTexture()
const points = repeat(DEFAULT_EDGE_POINTS, () => new Point())
const rope = new SimpleRope(pixel, points)
let initialized = false

container.name = DEFAULT_EDGE_CONTAINER_NAME

Expand All @@ -31,31 +36,50 @@ export async function edgeFactory() {

edgeCull.add(container)

async function render(target: Pixels): Promise<Container> {
updatePoints(target)

const arrow = await renderArrow({
async function render(): Promise<Container> {
await renderArrow({
size: 10,
rotate: ArrowDirection.Right,
})

// little magic numbering here to get the arrows point in the right spot
arrow.position = {
x: target.x - 8,
y: target.y,
}

arrow.tint = config.styles.edgeColor
rope.tint = config.styles.edgeColor

return container
}

function updatePoints({ x, y }: Pixels): void {
async function setPosition(source: Pixels, target: Pixels, skipAnimation?: boolean): Promise<void> {
const newPositions = getPointPositions(target)

if (!initialized) {
await render()
initialized = true
}

for (const [index, point] of points.entries()) {
const { x, y } = newPositions[index]
animate(point, {
x,
y,
}, skipAnimation)
}

animate(container, {
x: source.x,
y: source.y,
}, skipAnimation)

animate(arrow, {
x: target.x - arrowOffset,
y: target.y,
}, skipAnimation)
}

function getPointPositions({ x, y }: Pixels): Pixels[] {
const newPoints: Pixels[] = []
const source: Pixels = { x: 0, y: 0 }

// little magic numbering here to get the line to end at the arrows point
const target: Pixels = { x: x - 2, y }
const target: Pixels = { x: x - edgeTargetOffset, y }

const sourceBezier: Pixels = {
x: getXBezier(source.x, { source, target }),
Expand All @@ -67,9 +91,9 @@ export async function edgeFactory() {
y: target.y,
}

for (const [index, point] of points.entries()) {
for (const [index] of points.entries()) {
if (index === points.length - 1) {
point.set(target.x, target.y)
newPoints[index] = target
continue
}

Expand All @@ -80,13 +104,16 @@ export async function edgeFactory() {
targetBezier,
})

point.set(position.x, position.y)
newPoints[index] = position
}

return newPoints
}

return {
container,
render,
setPosition,
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/factories/node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Container, Ticker } from 'pixi.js'
import { animate } from '@/factories/animation'
import { FlowRunContainer, flowRunContainerFactory } from '@/factories/nodeFlowRun'
import { TaskRunContainer, taskRunContainerFactory } from '@/factories/nodeTaskRun'
import { Pixels } from '@/models/layout'
import { RunGraphNode } from '@/models/RunGraph'
import { waitForCull } from '@/objects/culling'

Expand Down Expand Up @@ -55,9 +57,17 @@ export async function nodeContainerFactory(node: RunGraphNode) {
return values.join(',')
}

function setPosition({ x, y }: Pixels, skipAnimation?: boolean): void {
animate(container, {
x,
y,
}, skipAnimation)
}

return {
render,
container,
bar,
setPosition,
}
}
3 changes: 1 addition & 2 deletions src/factories/nodeFlowRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,7 @@ export async function flowRunContainerFactory(node: RunGraphNode) {

const labelMinLeft = Math.max(barRight, buttonRight)
const inside = barWithoutMargin > labelMinLeft + label.width
const yOffset = 0.5
const y = bar.height / 2 - label.height / 2 - yOffset
const y = bar.height / 2 - label.height / 2
const x = inside ? labelMinLeft + margin : arrowButton.x + arrowButton.width + margin

label.position = { x, y }
Expand Down
11 changes: 7 additions & 4 deletions src/factories/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export async function nodesContainerFactory(runId: string) {
const container = new Container()
const config = await waitForConfig()
const rows = await offsetsFactory()
let initialized = false

let data: RunGraphData | null = null
let layout: NodesLayoutResponse = new Map()
Expand Down Expand Up @@ -154,26 +155,28 @@ export async function nodesContainerFactory(runId: string) {
y: childActualPosition.y - parentActualPositionOffset.y + config.styles.nodeHeight / 2,
}

edge.container.position = parentActualPositionOffset
edge.render(childActualPositionOffset)
edge.setPosition(parentActualPositionOffset, childActualPositionOffset, !initialized)
}
}

function setPositions(): void {
for (const [nodeId, { container }] of nodes) {
for (const [nodeId, node] of nodes) {
const position = layout.get(nodeId)

if (!position) {
console.warn(`Could not find node in layout: Skipping ${nodeId}`)
return
}

container.position = getActualPosition(position)
const newPosition = getActualPosition(position)

node.setPosition(newPosition, !initialized)
}

renderEdges()
resized()

initialized = true
container.emit('rendered')
}

Expand Down
2 changes: 1 addition & 1 deletion src/models/RunGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ export type RunGraphConfig = {
styles?: RunGraphStyles,
}

export type RequiredGraphConfig = Omit<RunGraphConfig, 'styles'> & {
export type RequiredGraphConfig = Omit<Required<RunGraphConfig>, 'styles'> & {
styles: Required<RunGraphStyles>,
}
11 changes: 4 additions & 7 deletions src/objects/culling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ export function stopCulling(): void {
callback = null
}

export function pauseCulling(): void {
if (cullInstance) {
cullInstance.uncull()
}
}

export async function cull(): Promise<void> {
if (!cullInstance) {
return
Expand All @@ -54,7 +48,10 @@ export async function cull(): Promise<void> {
cullInstance.cull(application.renderer.screen)
}

export async function resumeCulling(): Promise<void> {
export function uncull(): void {
if (cullInstance) {
cullInstance.uncull()
}
}

export async function waitForCull(): Promise<Cull> {
Expand Down
4 changes: 2 additions & 2 deletions src/objects/viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RunGraphProps } from '@/models/RunGraph'
import { ViewportDateRange } from '@/models/viewport'
import { waitForApplication } from '@/objects/application'
import { waitForConfig } from '@/objects/config'
import { pauseCulling } from '@/objects/culling'
import { uncull } from '@/objects/culling'
import { emitter, waitForEvent } from '@/objects/events'
import { waitForScale } from '@/objects/scale'
import { waitForScope } from '@/objects/scope'
Expand Down Expand Up @@ -63,7 +63,7 @@ export async function centerViewport({ animate }: CenterViewportParameters = {})
throw new Error('Nodes container not found')
}

pauseCulling()
uncull()
const { x, y, width, height } = container.getLocalBounds()
const scale = viewport.findFit(width, height)

Expand Down

0 comments on commit 48f33fd

Please sign in to comment.