Skip to content

Commit

Permalink
Merge branch 'main' into 188710376-breakup-dependency-tree-part1
Browse files Browse the repository at this point in the history
  • Loading branch information
scytacki committed Jan 9, 2025
2 parents 69e4b12 + cccbdb3 commit af6d94f
Show file tree
Hide file tree
Showing 24 changed files with 594 additions and 22 deletions.
2 changes: 1 addition & 1 deletion v3/build_number.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"buildNumber":2086}
{"buildNumber":2087}
15 changes: 14 additions & 1 deletion v3/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,26 @@ export default defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {// promisified fs module

function getConfigurationByFile(file) {
const pathToConfigFile = path.resolve('.', 'cypress/config', `cypress.${file}.json`)

return fs.readJson(pathToConfigFile)
}

// Tasks for checking, clearing downloaded files.
on("task", {
fileExists(filePath) {
return fs.existsSync(filePath)
}
})
on("task", {
clearFolder(folderPath) {
fs.rmdirSync(folderPath, { recursive: true });
fs.mkdirSync(folderPath);
return null;
}
})

const env = config.env.testEnv || 'local'

return getConfigurationByFile(env)
Expand Down
20 changes: 20 additions & 0 deletions v3/cypress/e2e/graph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,26 @@ context("Graph UI", () => {
cy.get('input[type="checkbox"]').should('be.checked')
})
})
it("leads to a file download when the 'Export PNG Image' button is clicked", () => {
const fileName = "Untitled Document.png"
const downloadsFolder = Cypress.config("downloadsFolder")
graph.getCameraButton().click()
cy.get("[data-testid=export-png-image]").should("be.visible")
cy.get("[data-testid=export-png-image]").click()
cy.get("[data-testid=export-png-image]").should("not.exist")
cy.get("[data-testid=modal-dialog]").should("be.visible")
cy.get("[data-testid=modal-dialog-title]").should("have.text", "Export File As ...")
cy.get("[data-testid=modal-dialog-workspace]").find("li").contains("Local File").click()
cy.get("[data-testid=modal-dialog-workspace]").find(".buttons a[download]").contains("Download")
.should("be.visible").click()
cy.get("[data-testid=modal-dialog]").should("not.exist")

cy.task("fileExists", `${downloadsFolder}/${fileName}`).then((exists) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(exists).to.be.true
cy.task("clearFolder", downloadsFolder)
})
})
})
describe("graph bin configuration", () => {
it("disables Point Size control when display type is bars", () => {
Expand Down
13 changes: 8 additions & 5 deletions v3/src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { urlParams } from "../utilities/url-params"
import { kWebViewTileType } from "./web-view/web-view-defs"
import { isWebViewModel } from "./web-view/web-view-model"
import { logStringifiedObjectMessage } from "../lib/log-message"
import { CfmContext } from "../hooks/use-cfm-context"

import "../models/shared/shared-case-metadata-registration"
import "../models/shared/shared-data-set-registration"
Expand Down Expand Up @@ -123,11 +124,13 @@ export const App = observer(function App() {
return (
<CodapDndContext>
<DocumentContentContext.Provider value={appState.document.content}>
<div className="codap-app" data-testid="codap-app">
<MenuBar/>
<ToolShelf document={appState.document}/>
<Container/>
</div>
<CfmContext.Provider value={cfm}>
<div className="codap-app" data-testid="codap-app">
<MenuBar/>
<ToolShelf document={appState.document}/>
<Container/>
</div>
</CfmContext.Provider>
</DocumentContentContext.Provider>
</CodapDndContext>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ export const CategoricalLegend = observer(
}, [setDesiredExtent, layerIndex])

return (
<svg className='legend-categories' ref={keysElt} data-testid='legend-categories'></svg>
<g className='legend-categories' ref={keysElt} data-testid='legend-categories'></g>
)
})
CategoricalLegend.displayName = "CategoricalLegend"
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ export const ColorLegend = observer(function ColorLegend({layerIndex, setDesired
return () => setDesiredExtent(layerIndex, 0)
}, [setDesiredExtent, layerIndex])

return <svg className='legend-categories' data-testid='legend-categories' />
return <g className='legend-categories' data-testid='legend-categories' />
})
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,6 @@ export const NumericLegend =
}
}, [setDesiredExtent, layerIndex])

return <svg className='legend-categories' ref={elt => setChoroplethElt(elt)} data-testid='legend-categories'>
</svg>
return <g className='legend-categories' ref={elt => setChoroplethElt(elt)} data-testid='legend-categories'>
</g>
})
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { IDataConfigurationModel } from "./data-configuration-model"
import {DataDisplayLayerModelUnion} from "./data-display-layer-union"
import {DisplayItemDescriptionModel} from "./display-item-description-model"
import { IBaseDataDisplayModel } from "./base-data-display-content-model"
import { DataDisplayRenderState } from "./data-display-render-state"

export const DataDisplayContentModel = TileContentModel
.named("DataDisplayContentModel")
Expand All @@ -38,6 +39,7 @@ export const DataDisplayContentModel = TileContentModel
.volatile(() => ({
animationTimerId: 0,
marqueeMode: 'unclicked' as MarqueeMode,
renderState: undefined as DataDisplayRenderState | undefined,
}))
.views(self => ({
placeCanAcceptAttributeIDDrop(place: GraphPlace,
Expand Down Expand Up @@ -145,6 +147,9 @@ export const DataDisplayContentModel = TileContentModel
},
setMarqueeMode(mode: MarqueeMode) {
self.marqueeMode = mode
},
setRenderState(renderState: DataDisplayRenderState) {
self.renderState = renderState
}
}))
export interface IDataDisplayContentModel extends Instance<typeof DataDisplayContentModel> {}
Expand Down
47 changes: 47 additions & 0 deletions v3/src/components/data-display/models/data-display-render-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { graphSnapshot } from "../../graph/utilities/image-utils"
import { PixiPointsArray } from "../pixi/pixi-points"

export class DataDisplayRenderState {
pixiPointsArray: PixiPointsArray
displayElement: HTMLElement
getTitle?: (() => string | undefined)
dataUri?: string

constructor(
pixiPointsArray: PixiPointsArray,
displayElement: HTMLElement,
getTitle?: () => string,
dataUri?: string
) {
this.pixiPointsArray = pixiPointsArray
this.displayElement = displayElement
this.getTitle = getTitle
this.dataUri = dataUri
}

setDataUri(dataUri: string) {
this.dataUri = dataUri
}

async updateSnapshot() {
const title = this.getTitle?.() || ""
const pixiPoints = this.pixiPointsArray?.[0]
if (!this.displayElement || !pixiPoints) return

const width = this.displayElement.getBoundingClientRect().width ?? 0
const height = this.displayElement.getBoundingClientRect().height ?? 0
const svgElementsToImageOptions = {
rootEl: this.displayElement,
graphWidth: width,
graphHeight: height,
graphTitle: title,
asDataURL: true,
pixiPoints
}
const graphImage = await graphSnapshot(svgElementsToImageOptions)
const dataUri = typeof graphImage === "string" ? graphImage : undefined
if (dataUri) {
this.setDataUri(dataUri)
}
}
}
19 changes: 17 additions & 2 deletions v3/src/components/graph/components/camera-menu-list.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React, { useState } from "react"
import { MenuItem, MenuList, useToast } from "@chakra-ui/react"
import { t } from "../../../utilities/translation/translate"
import { useTileModelContext } from "../../../hooks/use-tile-model-context"
import { isGraphContentModel } from "../models/graph-content-model"
import { useCfmContext } from "../../../hooks/use-cfm-context"

export const CameraMenuList = () => {
const tile = useTileModelContext().tile
const graphModel = isGraphContentModel(tile?.content) ? tile?.content : undefined
const cfm = useCfmContext()
const [hasBackgroundImage, setHasBackgroundImage] = useState(false)
const [imageLocked, setImageLocked] = useState(false)
const toast = useToast()
Expand Down Expand Up @@ -40,8 +46,17 @@ export const CameraMenuList = () => {
handleMenuItemClick("Copy Image clicked")
}

const handleExportPNG = () => {
handleMenuItemClick("Export PNG Image clicked")
const handleExportPNG = async () => {
if (!graphModel?.renderState) return

await graphModel.renderState.updateSnapshot()

if (graphModel.renderState.dataUri) {
const imageString = graphModel.renderState.dataUri.replace("data:image/png;base64,", "")
cfm?.client.saveSecondaryFileAsDialog(imageString, "png", "image/png", () => null)
} else {
console.error("Error exporting PNG image.")
}
}

const handleExportSVG = () => {
Expand Down
16 changes: 14 additions & 2 deletions v3/src/components/graph/components/graph-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import { graphCollisionDetection, kGraphIdBase } from './graph-drag-drop'
import { kTitleBarHeight } from "../../constants"
import {AttributeDragOverlay} from "../../drag-drop/attribute-drag-overlay"
import "../register-adornment-types"
import { getTitle } from '../../../models/tiles/tile-content-info'
import { DataDisplayRenderState } from '../../data-display/models/data-display-render-state'

registerTileCollisionDetection(kGraphIdBase, graphCollisionDetection)

export const GraphComponent = observer(function GraphComponent({tile}: ITileBaseProps) {
const graphModel = isGraphContentModel(tile?.content) ? tile?.content : undefined

const title = (tile && getTitle?.(tile)) || tile?.title || ""
const instanceId = useNextInstanceId("graph")
const {data} = useDataSet(graphModel?.dataset)
const layout = useInitGraphLayout(graphModel)
Expand All @@ -49,6 +51,16 @@ export const GraphComponent = observer(function GraphComponent({tile}: ITileBase

useGraphController({graphController, graphModel, pixiPointsArray})

const setGraphRef = (ref: HTMLDivElement | null) => {
graphRef.current = ref
const elementParent = ref?.parentElement
const dataUri = graphModel?.renderState?.dataUri ?? undefined
if (elementParent) {
const renderState = new DataDisplayRenderState(pixiPointsArray, elementParent, () => title, dataUri)
graphModel?.setRenderState(renderState)
}
}

useEffect(() => {
(width != null) && width >= 0 && (height != null) &&
height >= kTitleBarHeight && layout.setTileExtent(width, height)
Expand Down Expand Up @@ -78,7 +90,7 @@ export const GraphComponent = observer(function GraphComponent({tile}: ITileBase
<AxisProviderContext.Provider value={graphModel}>
<Graph
graphController={graphController}
graphRef={graphRef}
setGraphRef={setGraphRef}
pixiPointsArray={pixiPointsArray}
/>
</AxisProviderContext.Provider>
Expand Down
16 changes: 11 additions & 5 deletions v3/src/components/graph/components/graph.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {comparer} from "mobx"
import {observer} from "mobx-react-lite"
import {IDisposer, isAlive} from "mobx-state-tree"
import React, {MutableRefObject, useCallback, useEffect, useMemo, useRef} from "react"
import React, {useCallback, useEffect, useMemo, useRef} from "react"
import {select} from "d3"
import {clsx} from "clsx"
import { logStringifiedObjectMessage } from "../../../lib/log-message"
Expand Down Expand Up @@ -45,11 +45,11 @@ import "./graph.scss"

interface IProps {
graphController: GraphController
graphRef: MutableRefObject<HTMLDivElement | null>
setGraphRef: (ref: HTMLDivElement | null) => void
pixiPointsArray: PixiPointsArray
}

export const Graph = observer(function Graph({graphController, graphRef, pixiPointsArray}: IProps) {
export const Graph = observer(function Graph({graphController, setGraphRef, pixiPointsArray}: IProps) {
const graphModel = useGraphContentModelContext(),
{plotType} = graphModel,
pixiPoints = pixiPointsArray[0],
Expand All @@ -65,7 +65,8 @@ export const Graph = observer(function Graph({graphController, graphRef, pixiPoi
pixiContainerRef = useRef<SVGForeignObjectElement>(null),
prevAttrCollectionsMapRef = useRef<Record<string, string>>({}),
xAttrID = graphModel.getAttributeID('x'),
yAttrID = graphModel.getAttributeID('y')
yAttrID = graphModel.getAttributeID('y'),
graphRef = useRef<HTMLDivElement | null>(null)

if (pixiPoints?.canvas && pixiContainerRef.current && pixiContainerRef.current.children.length === 0) {
pixiContainerRef.current.appendChild(pixiPoints.canvas)
Expand All @@ -74,6 +75,11 @@ export const Graph = observer(function Graph({graphController, graphRef, pixiPoi
})
}

const mySetGraphRef = (ref: HTMLDivElement | null) => {
graphRef.current = ref
setGraphRef(ref)
}

useEffect(function handleFilteredCasesChange() {
return mstReaction(
() => graphModel.dataConfiguration.filteredCases.map(({ id }) => id),
Expand Down Expand Up @@ -358,7 +364,7 @@ export const Graph = observer(function Graph({graphController, graphRef, pixiPoi

return (
<GraphDataConfigurationContext.Provider value={graphModel.dataConfiguration}>
<div className={clsx(kGraphClass, kPortalClass)} ref={graphRef} data-testid="graph">
<div className={clsx(kGraphClass, kPortalClass)} ref={mySetGraphRef} data-testid="graph">
{graphModel.showParentToggles && <ParentToggles/>}
<svg className='graph-svg' ref={svgRef}>
<Background
Expand Down
23 changes: 23 additions & 0 deletions v3/src/components/graph/graph-data-display-handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { graphDataDisplayHandler } from "./graph-data-display-handler"
import { ITileContentModel } from "../../models/tiles/tile-content"
import { kGraphTileType } from "./graph-defs"

describe("graphDataDisplayHandler", () => {
it("should return an exportDataUri for a graph", async () => {
const graphContent = {
renderState: {
updateSnapshot: jest.fn(),
dataUri: "data:image/png;base64,"
},
type: kGraphTileType
} as Partial<ITileContentModel>
const result = await graphDataDisplayHandler.get(graphContent as ITileContentModel)
expect(result).toEqual({ exportDataUri: "data:image/png;base64,", success: true })
})

it("should return success as false for other types", async () => {
const content = { type: "table" } as ITileContentModel
const result = await graphDataDisplayHandler.get(content)
expect(result).toEqual({ success: false })
})
})
16 changes: 16 additions & 0 deletions v3/src/components/graph/graph-data-display-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DIDataDisplayHandler } from "../../data-interactive/handlers/data-display-handler"
import { ITileContentModel } from "../../models/tiles/tile-content"
import { isGraphContentModel } from "./models/graph-content-model"

export const graphDataDisplayHandler: DIDataDisplayHandler = {
async get(content: ITileContentModel) {

if (isGraphContentModel(content)) {
await content?.renderState?.updateSnapshot()
const exportDataUri = content?.renderState?.dataUri
return { exportDataUri, success: !!exportDataUri }
} else {
return { success: false }
}
}
}
3 changes: 3 additions & 0 deletions v3/src/components/graph/graph-registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { kGraphDataConfigurationType } from "./models/graph-data-configuration-m
import { GraphFilterFormulaAdapter } from "./models/graph-filter-formula-adapter"
import { kGraphPointLayerType } from "./models/graph-point-layer-model"
import { v2GraphImporter } from "./v2-graph-importer"
import { registerDataDisplayHandler } from "../../data-interactive/handlers/data-display-handler"
import { graphDataDisplayHandler } from "./graph-data-display-handler"

GraphFilterFormulaAdapter.register()

Expand Down Expand Up @@ -79,3 +81,4 @@ registerTileComponentInfo({
registerV2TileImporter("DG.GraphView", v2GraphImporter)

registerComponentHandler(kV2GraphType, graphComponentHandler)
registerDataDisplayHandler(kV2GraphType, graphDataDisplayHandler)
Loading

0 comments on commit af6d94f

Please sign in to comment.