diff --git a/e2e-tests/elementView.spec.ts b/e2e-tests/elementView.spec.ts index 3636d2cc..c5f889c2 100644 --- a/e2e-tests/elementView.spec.ts +++ b/e2e-tests/elementView.spec.ts @@ -32,14 +32,32 @@ async function dragElement(element: Locator, xOffset: number, yOffset: number, p test('Element View', async ({ page, browserName }) => { await page.goto('http://localhost:3000/?workspace=Upset+Examples&table=simpsons&sessionId=193'); - // Make selection - const row = await page.locator('g > circle').first(); // row - await row.dispatchEvent('click'); - // Open element view const elementViewToggle = await page.getByLabel('Element View Sidebar Toggle'); await elementViewToggle.click(); + // Make sure the query table has results by default + const lisaCell = page.getByRole('cell', { name: 'Lisa' }); + const cell8 = page.getByRole('cell', { name: '8', exact: true }); + await expect(cell8).toBeVisible(); + await expect(lisaCell).toBeVisible(); + + // Make a selection on the vis of all data + await dragElement(page.locator('canvas'), 20, 0, page); + await expect(page.locator('#Subset_Male polygon').nth(1)).toBeVisible(); + await expect(page.getByLabel('Selected elements Atts: Age')).toBeVisible(); + await expect(page.getByRole('cell', { name: 'Homer' })).toBeVisible(); + await expect(page.getByRole('cell', { name: '40' })).toBeVisible(); + await expect(lisaCell).not.toBeVisible(); + await expect(cell8).not.toBeVisible(); + + // Deselect + await page.locator('canvas').click(); + + // Make selection + const row = await page.locator('g > circle').first(); // row + await row.dispatchEvent('click'); + // test expansion buttons await page.getByLabel('Expand the sidebar in full').click(); await page.getByLabel('Reduce the sidebar to normal').click(); diff --git a/packages/upset/src/atoms/elementsSelectors.ts b/packages/upset/src/atoms/elementsSelectors.ts index 374c273c..3ff889fd 100644 --- a/packages/upset/src/atoms/elementsSelectors.ts +++ b/packages/upset/src/atoms/elementsSelectors.ts @@ -117,6 +117,22 @@ export const elementItemMapSelector = selectorFamily({ }, }); +/** + * Gets all elements in the bookmarked intersections and the currently selected intersection. + * If no intersections are bookmarked, returns all elements + * @returns The elements in the bookmarked intersections + */ +export const elementsInBookmarkSelector = selector({ + key: 'bookmarked-elements', + get: ({ get }) => { + const bookmarks = get(bookmarkSelector); + const items: Item[] = get(elementItemMapSelector(bookmarks.map((b) => b.id))); + + if (items.length === 0) return Object.values(get(itemsAtom)).map((item) => ({ ...item, color: '#444' })); + return items; + }, +}); + /** * Gets the current selection of elements * @returns The current selection of elements @@ -155,14 +171,14 @@ export const currentElementQuery = selector({ /** * Returns all items that are in a bookmarked intersection OR the currently selected intersection * AND are within the bounds of the current element selection. + * If no selections are active and no rows are bookmarked, returns all items. */ export const selectedItemsSelector = selector({ key: 'selected-elements', get: ({ get }) => { - const bookmarks = get(bookmarkSelector); - const items: Item[] = get(elementItemMapSelector(bookmarks.map((b) => b.id))); + const items: Item[] = get(elementsInBookmarkSelector); const selection = get(selectedElementSelector); - if (!selection) return []; + if (!selection) return items; return filterItems(items, selection); }, diff --git a/packages/upset/src/components/ElementView/ElementSidebar.tsx b/packages/upset/src/components/ElementView/ElementSidebar.tsx index 10e0ddd0..a3b82305 100644 --- a/packages/upset/src/components/ElementView/ElementSidebar.tsx +++ b/packages/upset/src/components/ElementView/ElementSidebar.tsx @@ -3,18 +3,17 @@ import CloseFullscreen from '@mui/icons-material/CloseFullscreen'; import DownloadIcon from '@mui/icons-material/Download'; import CloseIcon from '@mui/icons-material/Close'; import { - Alert, Box, Divider, Drawer, IconButton, Tooltip, Typography, css, + Box, Divider, Drawer, IconButton, Tooltip, Typography, css, } from '@mui/material'; import { Item } from '@visdesignlab/upset2-core'; import React, { - useCallback, useContext, useEffect, useMemo, useState, + useCallback, useContext, useEffect, useState, } from 'react'; import { useRecoilValue } from 'recoil'; import { columnsAtom } from '../../atoms/columnAtom'; -import { bookmarkSelector, currentIntersectionSelector } from '../../atoms/config/currentIntersectionAtom'; import { - elementSelector, intersectionCountSelector, selectedElementSelector, selectedItemsCounter, + selectedElementSelector, selectedItemsCounter, selectedItemsSelector, } from '../../atoms/elementsSelectors'; import { BookmarkChips } from './BookmarkChips'; @@ -80,30 +79,14 @@ function downloadElementsAsCSV(items: Item[], columns: string[], name: string) { */ export const ElementSidebar = ({ open, close }: Props) => { const [fullWidth, setFullWidth] = useState(false); - const currentIntersection = useRecoilValue(currentIntersectionSelector); const [drawerWidth, setDrawerWidth] = useState(initialDrawerWidth); const currentSelection = useRecoilValue(selectedElementSelector); const selectedItems = useRecoilValue(selectedItemsSelector); - const itemCount = currentSelection - ? useRecoilValue(selectedItemsCounter) - : useRecoilValue(intersectionCountSelector(currentIntersection?.id)); - const currentIntersectionElements = useRecoilValue( - elementSelector(currentIntersection?.id), - ); - const bookmarks = useRecoilValue(bookmarkSelector); + const itemCount = useRecoilValue(selectedItemsCounter); const columns = useRecoilValue(columnsAtom); const [hideElementSidebar, setHideElementSidebar] = useState(!open); const { actions }: {actions: UpsetActions} = useContext(ProvenanceContext); - /** - * Vars - */ - - const queryDownloadable = useMemo( - () => currentIntersection || (currentSelection && bookmarks.length > 0), - [currentIntersection, currentSelection, bookmarks], - ); - /** * Effects */ @@ -237,31 +220,14 @@ export const ElementSidebar = ({ open, close }: Props) => { Query Result - + { - if (queryDownloadable) { - if (currentSelection) { - downloadElementsAsCSV( - selectedItems, - columns, - currentSelection.label, - ); - } else if (currentIntersection) { - downloadElementsAsCSV( - currentIntersectionElements, - columns, - currentIntersection.elementName, - ); - } - } + downloadElementsAsCSV( + selectedItems, + columns, + currentSelection?.label ?? 'upset_elements', + ); }} > @@ -269,20 +235,7 @@ export const ElementSidebar = ({ open, close }: Props) => { - {queryDownloadable ? ( - - ) : ( - - Please select a query to view the elements. - - )} + ); }; diff --git a/packages/upset/src/components/ElementView/ElementTable.tsx b/packages/upset/src/components/ElementView/ElementTable.tsx index 062d2e38..3af6df8b 100644 --- a/packages/upset/src/components/ElementView/ElementTable.tsx +++ b/packages/upset/src/components/ElementView/ElementTable.tsx @@ -5,8 +5,7 @@ import { FC, useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { attributeAtom } from '../../atoms/attributeAtom'; -import { elementSelector, selectedElementSelector, selectedItemsSelector } from '../../atoms/elementsSelectors'; -import { currentIntersectionSelector } from '../../atoms/config/currentIntersectionAtom'; +import { selectedItemsSelector } from '../../atoms/elementsSelectors'; /** * Hook to generate rows for the DataGrid @@ -41,12 +40,8 @@ function useColumns(columns: string[]) { * Table to display elements */ export const ElementTable: FC = () => { - const currentIntersection = useRecoilValue(currentIntersectionSelector); const attributeColumns = useRecoilValue(attributeAtom); - const elementSelection = useRecoilValue(selectedElementSelector); - const elements = elementSelection - ? useRecoilValue(selectedItemsSelector) - : useRecoilValue(elementSelector(currentIntersection?.id)); + const elements = useRecoilValue(selectedItemsSelector); const rows = useRows(elements); const columns = useColumns(['_label', ...attributeColumns]); diff --git a/packages/upset/src/components/ElementView/ElementVisualization.tsx b/packages/upset/src/components/ElementView/ElementVisualization.tsx index 0ec47357..07d94478 100644 --- a/packages/upset/src/components/ElementView/ElementVisualization.tsx +++ b/packages/upset/src/components/ElementView/ElementVisualization.tsx @@ -10,7 +10,7 @@ import { numericalQueryToBookmark, numericalQueriesEqual, isNumericalQuery } fro import { Alert, Button } from '@mui/material'; import { bookmarkSelector, currentIntersectionSelector, elementColorSelector } from '../../atoms/config/currentIntersectionAtom'; import { histogramSelector, scatterplotsSelector } from '../../atoms/config/plotAtoms'; -import { elementItemMapSelector, currentNumericalQuery } from '../../atoms/elementsSelectors'; +import { currentNumericalQuery, elementsInBookmarkSelector } from '../../atoms/elementsSelectors'; import { AddPlotDialog } from './AddPlotDialog'; import { generateVega } from './generatePlotSpec'; import { ProvenanceContext } from '../Root'; @@ -29,7 +29,7 @@ export const ElementVisualization = () => { const scatterplots = useRecoilValue(scatterplotsSelector); const histograms = useRecoilValue(histogramSelector); const bookmarked = useRecoilValue(bookmarkSelector); - const items = useRecoilValue(elementItemMapSelector(bookmarked.map((b) => b.id))); + const items = useRecoilValue(elementsInBookmarkSelector); const numericalQuery = useRecoilValue(currentNumericalQuery); const selectColor = useRecoilValue(elementColorSelector); const currentIntersection = useRecoilValue(currentIntersectionSelector); @@ -88,19 +88,19 @@ export const ElementVisualization = () => { draftSelection.current = undefined; }} > + {!currentIntersection && bookmarked.length === 0 && ( - Please click on an intersection to visualize its attributes. + Currently visualizing all elements. Clicking on an intersection will visualize only its elements. )} - {(scatterplots.length > 0 || histograms.length > 0) && (