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
120 changes: 70 additions & 50 deletions site/components/InteractiveGraphics/InteractiveGraphics.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import useResizeObserver from "@react-hook/resize-observer"
import { useCallback, useEffect, useMemo, useState } from "react"
import { SuperGrid } from "react-supergrid"
import { applyObjectLimit } from "site/utils/applyObjectLimit"
import { getGraphicsBounds } from "site/utils/getGraphicsBounds"
import { getMaxStep } from "site/utils/getMaxStep"
import { sortRectsByArea } from "site/utils/sortRectsByArea"
Expand Down Expand Up @@ -421,65 +422,78 @@ 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(
() =>
filterAndLimit(graphics.lines, filterLines).sort(
(a, b) =>
(a.zIndex ?? 0) - (b.zIndex ?? 0) ||
a.originalIndex - b.originalIndex,
),
[graphics.lines, filterLines, objectLimit],
)
const filteredInfiniteLines = useMemo(
() => filterAndLimit(graphics.infiniteLines, filterLayerAndStep),
[graphics.infiniteLines, filterLayerAndStep, objectLimit],
)
const filteredRects = useMemo(
() => sortRectsByArea(filterAndLimit(graphics.rects, filterRects)),
[graphics.rects, filterRects, objectLimit],
)
const filteredPolygons = useMemo(
() => filterAndLimit(graphics.polygons, filterPolygons),
[graphics.polygons, filterPolygons, objectLimit],
)
const filteredPoints = useMemo(
() => filterAndLimit(graphics.points, filterPoints),
[graphics.points, filterPoints, objectLimit],
)
const filteredCircles = useMemo(
() => filterAndLimit(graphics.circles, filterCircles),
[graphics.circles, filterCircles, objectLimit],
)
const filteredTexts = useMemo(
() => filterAndLimit(graphics.texts, filterTexts),
[graphics.texts, filterTexts, objectLimit],
)
const filteredArrows = useMemo(
() => filterAndLimit(graphics.arrows, filterArrows),
[graphics.arrows, filterArrows, objectLimit],
)
const visibleObjects = useMemo(() => {
const lines = filterObjects(graphics.lines, filterLines).sort(
(a, b) =>
(a.zIndex ?? 0) - (b.zIndex ?? 0) || a.originalIndex - b.originalIndex,
)
const infiniteLines = filterObjects(
graphics.infiniteLines,
filterLayerAndStep,
)
const rects = sortRectsByArea(filterObjects(graphics.rects, filterRects))
const polygons = filterObjects(graphics.polygons, filterPolygons)
const points = filterObjects(graphics.points, filterPoints)
const circles = filterObjects(graphics.circles, filterCircles)
const texts = filterObjects(graphics.texts, filterTexts)
const arrows = filterObjects(graphics.arrows, filterArrows)

return applyObjectLimit(
{
arrows,
infiniteLines,
lines,
rects,
polygons,
circles,
texts,
points,
},
objectLimit,
)
}, [
graphics.lines,
graphics.infiniteLines,
graphics.rects,
graphics.polygons,
graphics.points,
graphics.circles,
graphics.texts,
graphics.arrows,
filterLines,
filterLayerAndStep,
filterRects,
filterPolygons,
filterPoints,
filterCircles,
filterTexts,
filterArrows,
objectLimit,
])

const totalFilteredObjects =
filteredInfiniteLines.length +
filteredLines.length +
filteredRects.length +
filteredPolygons.length +
filteredPoints.length +
filteredCircles.length +
filteredTexts.length +
filteredArrows.length
const isLimitReached = objectLimit && totalFilteredObjects > objectLimit
const {
arrows: filteredArrows,
infiniteLines: filteredInfiniteLines,
lines: filteredLines,
rects: filteredRects,
polygons: filteredPolygons,
circles: filteredCircles,
texts: filteredTexts,
points: filteredPoints,
} = visibleObjects.limitedGroups

const totalFilteredObjects = visibleObjects.totalObjectCount
const isLimitReached = visibleObjects.isLimited

return (
<div>
Expand Down Expand Up @@ -560,6 +574,12 @@ export const InteractiveGraphics = ({
)}
</div>
)}
{maxStep <= 0 && isLimitReached && (
<span style={{ color: "red", fontSize: "12px" }}>
Display limited to {objectLimit} objects. Received:{" "}
{totalFilteredObjects}.
</span>
)}

<label>
<input
Expand Down
39 changes: 39 additions & 0 deletions site/utils/applyObjectLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
type ObjectGroups = Record<string, unknown[]>

export function applyObjectLimit<TGroups extends ObjectGroups>(
groups: TGroups,
objectLimit?: number,
): {
limitedGroups: TGroups
totalObjectCount: number
isLimited: boolean
} {
const totalObjectCount = Object.values(groups).reduce(
(total, objects) => total + objects.length,
0,
)
const shouldLimit = objectLimit !== undefined && objectLimit >= 0

if (!shouldLimit || totalObjectCount <= objectLimit) {
return {
limitedGroups: groups,
totalObjectCount,
isLimited: false,
}
}

let remainingObjects = objectLimit
const limitedGroups = {} as TGroups

for (const [key, objects] of Object.entries(groups)) {
const objectCount = Math.min(objects.length, remainingObjects)
;(limitedGroups as ObjectGroups)[key] = objects.slice(0, objectCount)
remainingObjects -= objectCount
}

return {
limitedGroups,
totalObjectCount,
isLimited: true,
}
}
48 changes: 48 additions & 0 deletions tests/apply-object-limit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, expect, test } from "bun:test"
import { applyObjectLimit } from "../site/utils/applyObjectLimit"

describe("applyObjectLimit", () => {
test("does not limit when objectLimit is undefined", () => {
const result = applyObjectLimit({
lines: [1, 2],
points: [3, 4],
})

expect(result.totalObjectCount).toBe(4)
expect(result.isLimited).toBe(false)
expect(result.limitedGroups.lines).toEqual([1, 2])
expect(result.limitedGroups.points).toEqual([3, 4])
})

test("applies a global limit across object groups", () => {
const result = applyObjectLimit(
{
lines: ["line-1", "line-2"],
points: ["point-1", "point-2"],
rects: ["rect-1"],
},
3,
)

expect(result.totalObjectCount).toBe(5)
expect(result.isLimited).toBe(true)
expect(result.limitedGroups.lines).toEqual(["line-1", "line-2"])
expect(result.limitedGroups.points).toEqual(["point-1"])
expect(result.limitedGroups.rects).toEqual([])
})

test("supports an objectLimit of zero", () => {
const result = applyObjectLimit(
{
lines: ["line-1"],
points: ["point-1"],
},
0,
)

expect(result.totalObjectCount).toBe(2)
expect(result.isLimited).toBe(true)
expect(result.limitedGroups.lines).toEqual([])
expect(result.limitedGroups.points).toEqual([])
})
})
Loading