Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 64 additions & 28 deletions site/components/InteractiveGraphics/InteractiveGraphics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -421,64 +421,100 @@ export const InteractiveGraphics = ({
filterLayerAndStep,
})

const filterAndLimit = <T,>(
const filterObjects = <T,>(
objects: T[] | undefined,
filterFn: (obj: T) => boolean,
): (T & { originalIndex: number })[] => {
if (!objects) return []
const filtered = objects
return objects
.map((obj, index) => ({ ...obj, originalIndex: index }))
.filter(filterFn)
return objectLimit ? filtered.slice(-objectLimit) : filtered
}

const filteredLines = useMemo(
const limitObjects = <T,>(objects: (T & { originalIndex: number })[]) => {
return objectLimit ? objects.slice(-objectLimit) : objects
}

const allFilteredLines = useMemo(
() =>
filterAndLimit(graphics.lines, filterLines).sort(
filterObjects(graphics.lines, filterLines).sort(
(a, b) =>
(a.zIndex ?? 0) - (b.zIndex ?? 0) ||
a.originalIndex - b.originalIndex,
),
[graphics.lines, filterLines, objectLimit],
[graphics.lines, filterLines],
)
const allFilteredInfiniteLines = useMemo(
() => filterObjects(graphics.infiniteLines, filterLayerAndStep),
[graphics.infiniteLines, filterLayerAndStep],
)
const allFilteredRects = useMemo(
() => sortRectsByArea(filterObjects(graphics.rects, filterRects)),
[graphics.rects, filterRects],
)
const allFilteredPolygons = useMemo(
() => filterObjects(graphics.polygons, filterPolygons),
[graphics.polygons, filterPolygons],
)
const allFilteredPoints = useMemo(
() => filterObjects(graphics.points, filterPoints),
[graphics.points, filterPoints],
)
const allFilteredCircles = useMemo(
() => filterObjects(graphics.circles, filterCircles),
[graphics.circles, filterCircles],
)
const allFilteredTexts = useMemo(
() => filterObjects(graphics.texts, filterTexts),
[graphics.texts, filterTexts],
)
const allFilteredArrows = useMemo(
() => filterObjects(graphics.arrows, filterArrows),
[graphics.arrows, filterArrows],
)

const filteredLines = useMemo(
() => limitObjects(allFilteredLines),
[allFilteredLines, objectLimit],
)
const filteredInfiniteLines = useMemo(
() => filterAndLimit(graphics.infiniteLines, filterLayerAndStep),
[graphics.infiniteLines, filterLayerAndStep, objectLimit],
() => limitObjects(allFilteredInfiniteLines),
[allFilteredInfiniteLines, objectLimit],
)
const filteredRects = useMemo(
() => sortRectsByArea(filterAndLimit(graphics.rects, filterRects)),
[graphics.rects, filterRects, objectLimit],
() => limitObjects(allFilteredRects),
[allFilteredRects, objectLimit],
)
const filteredPolygons = useMemo(
() => filterAndLimit(graphics.polygons, filterPolygons),
[graphics.polygons, filterPolygons, objectLimit],
() => limitObjects(allFilteredPolygons),
[allFilteredPolygons, objectLimit],
)
const filteredPoints = useMemo(
() => filterAndLimit(graphics.points, filterPoints),
[graphics.points, filterPoints, objectLimit],
() => limitObjects(allFilteredPoints),
[allFilteredPoints, objectLimit],
)
const filteredCircles = useMemo(
() => filterAndLimit(graphics.circles, filterCircles),
[graphics.circles, filterCircles, objectLimit],
() => limitObjects(allFilteredCircles),
[allFilteredCircles, objectLimit],
)
const filteredTexts = useMemo(
() => filterAndLimit(graphics.texts, filterTexts),
[graphics.texts, filterTexts, objectLimit],
() => limitObjects(allFilteredTexts),
[allFilteredTexts, objectLimit],
)
const filteredArrows = useMemo(
() => filterAndLimit(graphics.arrows, filterArrows),
[graphics.arrows, filterArrows, objectLimit],
() => limitObjects(allFilteredArrows),
[allFilteredArrows, objectLimit],
)

const totalFilteredObjects =
filteredInfiniteLines.length +
filteredLines.length +
filteredRects.length +
filteredPolygons.length +
filteredPoints.length +
filteredCircles.length +
filteredTexts.length +
filteredArrows.length
allFilteredInfiniteLines.length +
allFilteredLines.length +
allFilteredRects.length +
allFilteredPolygons.length +
allFilteredPoints.length +
allFilteredCircles.length +
allFilteredTexts.length +
allFilteredArrows.length
const isLimitReached = objectLimit && totalFilteredObjects > objectLimit

return (
Expand Down
79 changes: 79 additions & 0 deletions tests/InteractiveGraphics.objectLimit.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { beforeAll, describe, expect, test } from "bun:test"
import { act } from "react"
import { createRoot } from "react-dom/client"
import * as jsdom from "jsdom"
import { InteractiveGraphics } from "../site/components/InteractiveGraphics/InteractiveGraphics"

declare global {
var IS_REACT_ACT_ENVIRONMENT: boolean
}

beforeAll(() => {
const { JSDOM } = jsdom
const dom = new JSDOM("<!doctype html><html><body></body></html>", {
url: "http://localhost/",
})

class ResizeObserverMock {
observe() {}
unobserve() {}
disconnect() {}
}

global.document = dom.window.document
global.window = dom.window as unknown as Window & typeof globalThis
global.navigator = dom.window.navigator
global.localStorage = dom.window.localStorage
global.ResizeObserver = ResizeObserverMock as any
;(global.window as any).ResizeObserver = ResizeObserverMock
;(global.window as any).HTMLCanvasElement.prototype.getContext = () => ({
clearRect() {},
beginPath() {},
moveTo() {},
lineTo() {},
stroke() {},
strokeRect() {},
fillText() {},
measureText() {
return { width: 0 }
},
setLineDash() {},
save() {},
restore() {},
translate() {},
scale() {},
})
globalThis.IS_REACT_ACT_ENVIRONMENT = true
})

describe("InteractiveGraphics objectLimit", () => {
test("shows limit message when filtered objects exceed objectLimit", async () => {
const container = document.createElement("div")
document.body.appendChild(container)
const root = createRoot(container)

const graphics = {
points: [
{ x: 0, y: 0, label: "A", step: 1 },
{ x: 10, y: 10, label: "B", step: 1 },
{ x: 20, y: 20, label: "C", step: 1 },
],
}

try {
await act(async () => {
root.render(
<InteractiveGraphics graphics={graphics as any} objectLimit={2} />,
)
})

expect(container.textContent).toContain("Display limited to 2 objects")
expect(container.textContent).toContain("Received: 3")
} finally {
await act(async () => {
root.unmount()
})
document.body.removeChild(container)
}
})
})
Loading