Skip to content

Commit

Permalink
Merge pull request #1515 from concord-consortium/187950454-pixi-v3
Browse files Browse the repository at this point in the history
Update Pixi to v8
  • Loading branch information
pjanik authored Sep 25, 2024
2 parents a08f665 + 8fc3e11 commit 5c1ca74
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 783 deletions.
772 changes: 104 additions & 668 deletions v3/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion v3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
"mobx-react-lite": "^4.0.7",
"nanoid": "^5.0.7",
"papaparse": "^5.4.1",
"pixi.js": "~7.4.2",
"pixi.js": "~8.4.1",
"pluralize": "^8.0.0",
"prop-types": "^15.8.1",
"query-string": "^9.1.0",
Expand Down
11 changes: 5 additions & 6 deletions v3/src/components/data-display/components/background.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ import {rectangleSubtract, rectNormalize} from "../data-display-utils"
import {useDataDisplayLayout} from "../hooks/use-data-display-layout"
import {useDataDisplayModelContext} from "../hooks/use-data-display-model"
import {MarqueeState} from "../models/marquee-state"
import {IPixiPointMetadata, IPixiPointsArrayRef, PixiBackgroundPassThroughEvent, PixiPoints}
from "../pixi/pixi-points"
import {IPixiPointMetadata, IPixiPointsArray, PixiBackgroundPassThroughEvent, PixiPoints} from "../pixi/pixi-points"

interface IProps {
marqueeState: MarqueeState
pixiPointsArrayRef: IPixiPointsArrayRef
pixiPointsArray: IPixiPointsArray
}

type RTree = ReturnType<typeof RTreeLib>
Expand Down Expand Up @@ -64,7 +63,7 @@ const prepareTree = (pixiPointsArray: PixiPoints[]): RTree => {
}

export const Background = forwardRef<SVGGElement | HTMLDivElement, IProps>((props, ref) => {
const {marqueeState, pixiPointsArrayRef} = props,
const {marqueeState, pixiPointsArray} = props,
dataDisplayModel = useDataDisplayModelContext(),
datasetsArray = dataDisplayModel.datasetsArray,
datasetsMap: SelectionMap = useMemo(() => {
Expand Down Expand Up @@ -92,7 +91,7 @@ export const Background = forwardRef<SVGGElement | HTMLDivElement, IProps>((prop

onDragStart = useCallback((event: PointerEvent) => {
appState.beginPerformance()
selectionTree.current = prepareTree(pixiPointsArrayRef.current)
selectionTree.current = prepareTree(pixiPointsArray)
// Event coordinates are window coordinates. To convert them to SVG coordinates, we need to subtract the
// bounding rect of the SVG element.
const bgRect = (bgRef.current as SVGGElement).getBoundingClientRect()
Expand All @@ -108,7 +107,7 @@ export const Background = forwardRef<SVGGElement | HTMLDivElement, IProps>((prop
})
}
marqueeState.setMarqueeRect({x: startX.current, y: startY.current, width: 0, height: 0})
}, [bgRef, datasetsArray, marqueeState, pixiPointsArrayRef]),
}, [bgRef, datasetsArray, marqueeState, pixiPointsArray]),

onDrag = useCallback((event: { dx: number; dy: number }) => {
if (event.dx !== 0 || event.dy !== 0 && datasetsArray.length) {
Expand Down
27 changes: 21 additions & 6 deletions v3/src/components/data-display/hooks/use-pixi-points-array.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef } from "react"
import { useCallback, useEffect, useState } from "react"
import { PixiPoints } from "../pixi/pixi-points"

interface IProps {
Expand All @@ -7,18 +7,33 @@ interface IProps {

export function usePixiPointsArray(props?: IProps) {
const { addInitialPixiPoints = false } = props || {}
const pixiPointsRef = useRef<PixiPoints[]>([])
const [ pixiPointsArray, setPixiPointsArray ] = useState<PixiPoints[]>([])

useEffect(() => {
const pixiPointsArray = pixiPointsRef.current
const initialPixiPoints = addInitialPixiPoints ? new PixiPoints() : undefined
initialPixiPoints && pixiPointsArray.push(initialPixiPoints)

async function initPixiPoints() {
if (initialPixiPoints) {
await initialPixiPoints.init()
setPixiPointsArray([initialPixiPoints])
}
}
initPixiPoints()

return () => {
// if we created it, we destroy it
initialPixiPoints?.dispose()
pixiPointsArray.length = 0
setPixiPointsArray([])
}
}, [addInitialPixiPoints])

return pixiPointsRef
const setPixiPointsLayer = useCallback((pixiPoints: PixiPoints, layerIndex: number) => {
setPixiPointsArray((prev) => {
const newPixiPointsArray = [...prev]
newPixiPointsArray[layerIndex] = pixiPoints
return newPixiPointsArray
})
}, [])

return {pixiPointsArray, setPixiPointsLayer}
}
72 changes: 40 additions & 32 deletions v3/src/components/data-display/pixi/pixi-points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export enum PixiBackgroundPassThroughEvent {
PointerDown = "pointerdown",
}

export type IPixiPointsArrayRef = React.MutableRefObject<PixiPoints[]>
export type IPixiPointsArray = PixiPoints[]

export type PixiPointEventHandler = (event: PointerEvent, point: PIXI.Sprite, metadata: IPixiPointMetadata) => void

Expand Down Expand Up @@ -72,21 +72,7 @@ interface IPointTransitionState {
const caseDataKey = ({ plotNum, caseID }: CaseData) => `${plotNum}:${caseID}`

export class PixiPoints {
renderer: PIXI.Renderer = new PIXI.Renderer({
resolution: window.devicePixelRatio,
autoDensity: true,
backgroundAlpha: 0,
antialias: true,
// `passive` is more performant and will be used by default in the future Pixi.JS versions
eventMode: "passive",
eventFeatures: {
move: true,
click: true,
// disables the global move events which can be very expensive in large scenes
globalMove: false,
wheel: false
}
})
renderer?: PIXI.Renderer
stage = new PIXI.Container()
pointsContainer = new PIXI.Container()
background = new PIXI.Sprite(PIXI.Texture.EMPTY)
Expand Down Expand Up @@ -118,7 +104,27 @@ export class PixiPoints {
onPointDrag?: PixiPointEventHandler
onPointDragEnd?: PixiPointEventHandler

constructor(options?: IPixiPointsOptions) {
async init(options?: IPixiPointsOptions) {
// Automatically determines the most appropriate renderer for the current environment.
// The function will prioritize the WebGL renderer as it is the most tested safe API to use. In the near future as
// WebGPU becomes more stable and ubiquitous, it will be prioritized over WebGL.
// See: https://pixijs.download/release/docs/rendering.html#autoDetectRenderer
this.renderer = await PIXI.autoDetectRenderer({
resolution: window.devicePixelRatio,
autoDensity: true,
backgroundAlpha: 0,
antialias: true,
// `passive` is more performant and will be used by default in the future Pixi.JS versions
eventMode: "passive",
eventFeatures: {
move: true,
click: true,
// disables the global move events which can be very expensive in large scenes
globalMove: false,
wheel: false
}
})

this.ticker.add(this.tick.bind(this))
this.stage.addChild(this.background)
this.stage.addChild(this.pointsContainer)
Expand All @@ -141,7 +147,7 @@ export class PixiPoints {
}

get canvas() {
return this.renderer.view as HTMLCanvasElement
return this.renderer?.view.canvas as HTMLCanvasElement
}

get points() {
Expand All @@ -163,14 +169,14 @@ export class PixiPoints {
// The only reason for ticker to run is to handle ongoing transitions. If there are no transitions, we can stop.
this.ticker.stop()
}
this.renderer.render(this.stage)
this.renderer?.render(this.stage)
}

resize(width: number, height: number) {
// We only set the background size if the width and height are valid. If we ever set width/height of background to
// negative values, the background won't be able to detect pointer events.
if (width > 0 && height > 0) {
this.renderer.resize(width, height)
this.renderer?.resize(width, height)
this.background.width = width
this.background.height = height
this.startRendering()
Expand Down Expand Up @@ -420,7 +426,11 @@ export class PixiPoints {
}

generateTexture(graphics: PIXI.Graphics, key: string): PIXI.Texture {
const texture = this.renderer.generateTexture(graphics, {
if (!this.renderer) {
throw new Error("PixiPoints renderer not initialized")
}
const texture = this.renderer.generateTexture({
target: graphics,
// A trick to make sprites/textures look still sharp when they're scaled up (e.g. during hover effect).
// The default resolution is `devicePixelRatio`, so if we multiply it by `MAX_SPRITE_SCALE`, we can scale
// sprites up to `MAX_SPRITE_SCALE` without losing sharpness.
Expand All @@ -439,9 +449,6 @@ export class PixiPoints {
return this.textures.get(key) as PIXI.Texture
}

const graphics = new PIXI.Graphics()
graphics.beginFill(fill)

const shouldDrawStroke = (dimension: number | undefined) => {
// Do not draw the stroke when either:
// 1. a transition from points to bars is active -- the stroke would be distorted by the scale change
Expand All @@ -451,7 +458,6 @@ export class PixiPoints {
}

const textureStrokeWidth = shouldDrawStroke(width) || shouldDrawStroke(height) ? strokeWidth : 0
graphics.lineStyle(textureStrokeWidth, stroke, strokeOpacity ?? 0.4)

// When the option to display bars is first selected, the width and height of the bars are first set to two times
// the radius value specified in `style`. This is so the bars are initially drawn as squares that are the same size
Expand All @@ -464,8 +470,11 @@ export class PixiPoints {
? width : radius * 2
const rectHeight = isFiniteNumber(height) && (!this.displayTypeTransitionState.isActive || includeDimensions)
? height : radius * 2
graphics.drawRect(0, 0, rectWidth, rectHeight)
graphics.endFill()

const graphics = new PIXI.Graphics()
.rect(0, 0, rectWidth, rectHeight)
.fill(fill)
.stroke({ color: stroke, width: textureStrokeWidth, alpha: strokeOpacity ?? 0.4 })

return this.generateTexture(graphics, key)
}
Expand All @@ -479,10 +488,9 @@ export class PixiPoints {
}

const graphics = new PIXI.Graphics()
graphics.beginFill(fill)
graphics.lineStyle(strokeWidth, stroke, strokeOpacity ?? 0.4)
graphics.drawCircle(0, 0, radius)
graphics.endFill()
.circle(0, 0, radius)
.fill(fill)
.stroke({ color: stroke, width: strokeWidth, alpha: strokeOpacity ?? 0.4 })

return this.generateTexture(graphics, key)
}
Expand Down Expand Up @@ -704,7 +712,7 @@ export class PixiPoints {

dispose() {
this.ticker.destroy()
this.renderer.destroy()
this.renderer?.destroy()
this.stage.destroy()
this.textures.forEach(texture => texture.destroy())
this.resizeObserver?.disconnect()
Expand Down
6 changes: 3 additions & 3 deletions v3/src/components/graph/components/graph-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export const GraphComponent = observer(function GraphComponent({tile}: ITileBase
const layout = useInitGraphLayout(graphModel)
const graphRef = useRef<HTMLDivElement | null>(null)
const {width, height} = useResizeDetector<HTMLDivElement>({targetRef: graphRef})
const pixiPointsArrayRef = usePixiPointsArray({ addInitialPixiPoints: true })
const {pixiPointsArray} = usePixiPointsArray({ addInitialPixiPoints: true })
const graphController = useMemo(
() => new GraphController({layout, instanceId}),
[layout, instanceId]
)

useGraphController({graphController, graphModel, pixiPointsArrayRef})
useGraphController({graphController, graphModel, pixiPointsArray})

useEffect(() => {
(width != null) && width >= 0 && (height != null) &&
Expand Down Expand Up @@ -69,7 +69,7 @@ export const GraphComponent = observer(function GraphComponent({tile}: ITileBase
<Graph
graphController={graphController}
graphRef={graphRef}
pixiPointsArrayRef={pixiPointsArrayRef}
pixiPointsArray={pixiPointsArray}
/>
</AxisProviderContext.Provider>
<AttributeDragOverlay activeDragId={overlayDragId}/>
Expand Down
10 changes: 5 additions & 5 deletions v3/src/components/graph/components/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {clsx} from "clsx"
import { logStringifiedObjectMessage } from "../../../lib/log-message"
import {mstReaction} from "../../../utilities/mst-reaction"
import {onAnyAction} from "../../../utilities/mst-utils"
import {IPixiPointsArrayRef} from "../../data-display/pixi/pixi-points"
import {IPixiPointsArray} from "../../data-display/pixi/pixi-points"
import {GraphAttrRole, graphPlaceToAttrRole, kPortalClass} from "../../data-display/data-display-types"
import {AxisPlace, AxisPlaces} from "../../axis/axis-types"
import {GraphAxis} from "./graph-axis"
Expand Down Expand Up @@ -47,13 +47,13 @@ import "./graph.scss"
interface IProps {
graphController: GraphController
graphRef: MutableRefObject<HTMLDivElement | null>
pixiPointsArrayRef: IPixiPointsArrayRef
pixiPointsArray: IPixiPointsArray
}

export const Graph = observer(function Graph({graphController, graphRef, pixiPointsArrayRef}: IProps) {
export const Graph = observer(function Graph({graphController, graphRef, pixiPointsArray}: IProps) {
const graphModel = useGraphContentModelContext(),
{plotType} = graphModel,
pixiPoints = pixiPointsArrayRef.current?.[0],
pixiPoints = pixiPointsArray[0],
{startAnimation} = useDataDisplayAnimation(),
instanceId = useInstanceIdContext(),
marqueeState = useMemo<MarqueeState>(() => new MarqueeState(), []),
Expand Down Expand Up @@ -350,7 +350,7 @@ export const Graph = observer(function Graph({graphController, graphRef, pixiPoi
<Background
ref={backgroundSvgRef}
marqueeState={marqueeState}
pixiPointsArrayRef={pixiPointsArrayRef}
pixiPointsArray={pixiPointsArray}
/>

{renderGraphAxes()}
Expand Down
11 changes: 6 additions & 5 deletions v3/src/components/graph/hooks/use-graph-controller.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {useEffect} from "react"
import {GraphController} from "../models/graph-controller"
import {IGraphContentModel} from "../models/graph-content-model"
import {IPixiPointsArrayRef} from "../../data-display/pixi/pixi-points"
import {IPixiPointsArray} from "../../data-display/pixi/pixi-points"

export interface IUseGraphControllerProps {
graphController: GraphController,
graphModel?: IGraphContentModel,
pixiPointsArrayRef: IPixiPointsArrayRef
pixiPointsArray: IPixiPointsArray
}

export const useGraphController = ({graphController, graphModel, pixiPointsArrayRef}: IUseGraphControllerProps) => {
export const useGraphController = ({graphController, graphModel, pixiPointsArray}: IUseGraphControllerProps) => {
useEffect(() => {
graphModel && graphController.setProperties(graphModel, pixiPointsArrayRef.current?.[0])
}, [graphController, graphModel, pixiPointsArrayRef])
const pixiPoints = pixiPointsArray[0]
graphModel && pixiPoints && graphController.setProperties(graphModel, pixiPoints)
}, [graphController, graphModel, pixiPointsArray])
}
6 changes: 3 additions & 3 deletions v3/src/components/map/components/codap-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const CodapMap = observer(function CodapMap({mapRef}: IProps) {
interiorDivRef = useRef<HTMLDivElement>(null),
prevMapSize = useRef<{ width: number, height: number, legend: number }>({width: 0, height: 0, legend: 0}),
forceUpdate = useForceUpdate(),
pixiPointsArrayRef = usePixiPointsArray()
{pixiPointsArray, setPixiPointsLayer} = usePixiPointsArray()

// trigger an additional render once references have been fulfilled
useEffect(() => forceUpdate(), [forceUpdate])
Expand Down Expand Up @@ -92,9 +92,9 @@ export const CodapMap = observer(function CodapMap({mapRef}: IProps) {
})
}
</>
<MapInterior pixiPointsArrayRef={pixiPointsArrayRef}/>
<MapInterior setPixiPointsLayer={setPixiPointsLayer}/>
</MapContainer>
<MapBackground mapModel={mapModel} pixiPointsArrayRef={pixiPointsArrayRef}/>
<MapBackground mapModel={mapModel} pixiPointsArray={pixiPointsArray}/>
</div>
{renderSliderIfAppropriate()}
<DroppableMapArea
Expand Down
8 changes: 4 additions & 4 deletions v3/src/components/map/components/map-background.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { useMemo } from "use-memo-one"
import { Background } from "../../data-display/components/background"
import { Marquee } from "../../data-display/components/marquee"
import { MarqueeState } from "../../data-display/models/marquee-state"
import { IPixiPointsArrayRef } from "../../data-display/pixi/pixi-points"
import { IPixiPointsArray } from "../../data-display/pixi/pixi-points"
import { IMapContentModel } from "../models/map-content-model"
import { mstReaction } from "../../../utilities/mst-reaction"

interface IProps {
mapModel: IMapContentModel
pixiPointsArrayRef: IPixiPointsArrayRef
pixiPointsArray: IPixiPointsArray
}

export const MapBackground = observer(function MapBackground({ mapModel, pixiPointsArrayRef }: IProps) {
export const MapBackground = observer(function MapBackground({ mapModel, pixiPointsArray }: IProps) {
const backgroundSvgRef = useRef<SVGGElement>(null)
const marqueeState = useMemo<MarqueeState>(() => new MarqueeState(), [])

Expand Down Expand Up @@ -42,7 +42,7 @@ export const MapBackground = observer(function MapBackground({ mapModel, pixiPoi
<Background
ref={backgroundSvgRef}
marqueeState={marqueeState}
pixiPointsArrayRef={pixiPointsArrayRef}
pixiPointsArray={pixiPointsArray}
/>
<Marquee marqueeState={marqueeState}/>
</svg>
Expand Down
Loading

0 comments on commit 5c1ca74

Please sign in to comment.