Skip to content

Commit

Permalink
Merge pull request #419 from visdesignlab/398-view-all-elements
Browse files Browse the repository at this point in the history
Default to viewing all elements instead of none when no selection is active
  • Loading branch information
NateLanza authored Nov 8, 2024
2 parents 02914f9 + 2b57602 commit 0548dcc
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 77 deletions.
26 changes: 22 additions & 4 deletions e2e-tests/elementView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
22 changes: 19 additions & 3 deletions packages/upset/src/atoms/elementsSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ export const elementItemMapSelector = selectorFamily<Item[], string[]>({
},
});

/**
* 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<Item[]>({
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
Expand Down Expand Up @@ -155,14 +171,14 @@ export const currentElementQuery = selector<ElementQuery | undefined>({
/**
* 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<Item[]>({
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);
},
Expand Down
69 changes: 11 additions & 58 deletions packages/upset/src/components/ElementView/ElementSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -237,52 +220,22 @@ export const ElementSidebar = ({ open, close }: Props) => {
<QueryInterface />
<Typography variant="h3" fontSize="1.2em">
Query Result
<Tooltip
title={
queryDownloadable
? `Download ${itemCount} elements`
: ''
}
>
<Tooltip title={`Download ${itemCount} elements`}>
<IconButton
disabled={!queryDownloadable}
onClick={() => {
if (queryDownloadable) {
if (currentSelection) {
downloadElementsAsCSV(
selectedItems,
columns,
currentSelection.label,
);
} else if (currentIntersection) {
downloadElementsAsCSV(
currentIntersectionElements,
columns,
currentIntersection.elementName,
);
}
}
downloadElementsAsCSV(
selectedItems,
columns,
currentSelection?.label ?? 'upset_elements',
);
}}
>
<DownloadIcon />
</IconButton>
</Tooltip>
</Typography>
<Divider />
{queryDownloadable ? (
<ElementTable />
) : (
<Alert
severity="info"
variant="outlined"
role="generic"
sx={{
alignItems: 'center', marginTop: '0.5em', marginBottom: '100px', border: 'none', color: '#777777',
}}
>
Please select a query to view the elements.
</Alert>
)}
<ElementTable />
</Drawer>
);
};
9 changes: 2 additions & 7 deletions packages/upset/src/components/ElementView/ElementTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -88,19 +88,19 @@ export const ElementVisualization = () => {
draftSelection.current = undefined;
}}
>
<Button style={{ marginTop: '0.5em' }} onClick={() => setOpenAddPlot(true)}>Add Plot</Button>
{!currentIntersection && bookmarked.length === 0 && (
<Alert
severity="info"
variant="outlined"
role="generic"
sx={{
alignItems: 'center', margin: '0.5em 0', border: 'none', color: '#777777',
alignItems: 'center', marginBottom: '0.5em', border: 'none', color: '#777777',
}}
>
Please click on an intersection to visualize its attributes.
Currently visualizing all elements. Clicking on an intersection will visualize only its elements.
</Alert>
)}
<Button onClick={() => setOpenAddPlot(true)}>Add Plot</Button>
<AddPlotDialog open={openAddPlot} onClose={onClose} />
<Box sx={{ overflowX: 'auto' }}>
{(scatterplots.length > 0 || histograms.length > 0) && (
Expand Down

0 comments on commit 0548dcc

Please sign in to comment.