Skip to content
Merged
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,16 @@ const ArrowVegaLiteChart: FC<Props> = ({
// because the forward message always produces new references, so
// this function will run regularly to update the view.
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- TODO: Fix this
updateView(data, datasets)
}, [data, datasets, updateView])
void updateView(data, datasets)

// We only want to update the view if the data or datasets change.
// updateView isn't stable because its updated via the isCreatingView flag.
// With updateView as dependency, the chart seems to
// expand within the parent container (less left/right padding).

// eslint-disable-next-line react-hooks/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, datasets])
Comment on lines +173 to +182
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having updateView as a dependency is causing the difference in snapshots. I think what happens is that updateView gets called, an additional time -> which recalculates the chart and better fills the available space.
But I'm ignoring updateView here for this PR to keep the status quo on how charts are rendered.


useEffect(() => {
// We only show data if its provided via data or if there
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ import { expressionInterpreter } from "vega-interpreter"
import { Mock, Mocked } from "vitest"

import { useFormClearHelper } from "~lib/components/widgets/Form"
import { Quiver } from "~lib/dataframes/Quiver"
import { WidgetStateManager } from "~lib/WidgetStateManager"

import { getDataArrays, getInlineData } from "./arrowUtils"
import {
getDataArray,
getDataArrays,
getInlineData,
VegaLiteChartElement,
WrappedNamedDataset,
} from "./arrowUtils"
import { useVegaEmbed } from "./useVegaEmbed"
import { useVegaLiteSelections } from "./useVegaLiteSelections"

Expand Down Expand Up @@ -54,6 +61,7 @@ vi.mock("./arrowUtils", async () => {
...actual,
getDataArrays: vi.fn(),
getInlineData: vi.fn(),
getDataArray: vi.fn(),
}
})

Expand Down Expand Up @@ -116,6 +124,7 @@ describe("useVegaEmbed hook", () => {
;(useFormClearHelper as Mock).mockImplementation(() => {})
;(getDataArrays as Mock).mockReturnValue({})
;(getInlineData as Mock).mockReturnValue(null)
;(getDataArray as Mock).mockReturnValue([])
})

afterEach(() => {
Expand Down Expand Up @@ -336,4 +345,150 @@ describe("useVegaEmbed hook", () => {
// 2 from createView, 1 from updateView -> .resize().runAsync()
expect(mockVegaView.runAsync).toHaveBeenCalledTimes(3)
})

it("uses latest props data/datasets on createView after rerender", async () => {
const initialElement: VegaLiteChartElement = {
id: "chartId",
data: null,
datasets: [
{
name: "old",
hasName: true,
data: { dimensions: { numDataRows: 1 } } as Quiver,
},
],
spec: "",
useContainerWidth: false,
vegaLiteTheme: "",
selectionMode: [],
formId: "",
}

const { result, rerender } = renderHook(
({ element }) => useVegaEmbed(element, mockWidgetMgr),
{ initialProps: { element: initialElement } }
)

const updatedDatasets = [
{ name: "new", hasName: true, data: { dimensions: { numDataRows: 1 } } },
] as unknown
const updatedElement = {
...initialElement,
datasets: updatedDatasets,
} as VegaLiteChartElement
;(getInlineData as Mock).mockReturnValue(null)
;(getDataArrays as Mock).mockReturnValue({ new: [{ x: 1 }] })

rerender({ element: updatedElement })

const containerRef = { current: document.createElement("div") }

await act(async () => {
await result.current.createView(containerRef, {})
})

// getDataArrays should have been called with the latest datasets
const lastCallArg = (getDataArrays as Mock).mock.calls.at(-1)?.[0]
expect(lastCallArg).toBe(updatedDatasets)
// Insert should use the dataset name returned by getDataArrays
expect(mockVegaView.insert).toHaveBeenCalledWith("new", [{ x: 1 }])
})

it("uses single dataset name as default for inline data insert", async () => {
const element: VegaLiteChartElement = {
id: "chartId",
data: null,
datasets: [],
spec: "",
useContainerWidth: false,
vegaLiteTheme: "",
selectionMode: [],
formId: "",
}

const { result } = renderHook(() => useVegaEmbed(element, mockWidgetMgr))

const inline = [{ d: "inline" }]
;(getInlineData as Mock).mockReturnValue(inline)
;(getDataArrays as Mock).mockReturnValue({ only: [{ d: "ds" }] })

const containerRef = { current: document.createElement("div") }
await act(async () => {
await result.current.createView(containerRef, {})
})

// First insert should be inline, and it should target the single dataset name
const firstInsertCall = (mockVegaView.insert as Mock).mock.calls[0]
expect(firstInsertCall[0]).toBe("only")
expect(firstInsertCall[1]).toBe(inline)
})

it("uses 'source' as default dataset name when no datasets but vgSpec.data present", async () => {
const element: VegaLiteChartElement = {
id: "chartId",
data: null,
datasets: [],
spec: "",
useContainerWidth: false,
vegaLiteTheme: "",
selectionMode: [],
formId: "",
}

const { result } = renderHook(() => useVegaEmbed(element, mockWidgetMgr))

const inline = [{ d: "inline" }]
;(getInlineData as Mock).mockReturnValue(inline)
;(getDataArrays as Mock).mockReturnValue(null)

const containerRef = { current: document.createElement("div") }
await act(async () => {
await result.current.createView(containerRef, {})
})

// Inline insert should target the default 'source' dataset
expect(mockVegaView.insert).toHaveBeenCalledWith("source", inline)
})

it("updateView removes stale named datasets not present in new input", async () => {
const element: VegaLiteChartElement = {
id: "chartId",
data: null,
datasets: [],
spec: "",
useContainerWidth: false,
vegaLiteTheme: "",
selectionMode: [],
formId: "",
}

const { result } = renderHook(() => useVegaEmbed(element, mockWidgetMgr))

const containerRef = { current: document.createElement("div") }
await act(async () => {
await result.current.createView(containerRef, {})
})

const dsOld = {
name: "old",
hasName: true,
data: { dimensions: { numDataRows: 1 }, hash: "X" },
} as WrappedNamedDataset
;(getDataArray as Mock).mockReturnValue([{ row: 1 }])

// First update with 'old' dataset present
await act(async () => {
await result.current.updateView(null, [dsOld])
})

// Next update with no datasets; 'old' should be removed
await act(async () => {
await result.current.updateView(null, [])
})

expect(mockVegaView.remove).toHaveBeenCalledWith(
"old",
expect.any(Function)
)
})
})
Loading
Loading