Skip to content

Commit

Permalink
Merge pull request #416 from visdesignlab/360-in-plot-bookmarking
Browse files Browse the repository at this point in the history
In-plot bookmarking
  • Loading branch information
JakeWags authored Nov 7, 2024
2 parents da522b9 + 98768d6 commit 02914f9
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 17 deletions.
4 changes: 2 additions & 2 deletions e2e-tests/provenance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ test('Selection History', async ({ page }) => {
await page.getByLabel('Additional options menu').click();
await page.getByLabel('History tree sidebar').click();

const schoolIntersection = page.locator('#Subset_School > g:nth-child(3)');
const duffFanIntersection = page.locator('[id="Subset_Duff_Fan\\~\\&\\~Male"] > g:nth-child(3)');
const schoolIntersection = page.locator('#Subset_School > g:nth-child(4)');
const duffFanIntersection = page.locator('[id="Subset_Duff_Fan\\~\\&\\~Male"] > g:nth-child(4)');

// Testing history for a subset selection & deselection
await page.locator('g > circle').first().click();
Expand Down
37 changes: 36 additions & 1 deletion packages/upset/src/atoms/config/currentIntersectionAtom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Bookmark, Row } from '@visdesignlab/upset2-core';
import { selector } from 'recoil';
import { selector, selectorFamily } from 'recoil';

import { queryColorPalette } from '../../utils/styles';
import { upsetConfigAtom } from './upsetConfigAtoms';
Expand Down Expand Up @@ -64,6 +64,41 @@ export const nextColorSelector = selector<string>({
},
});

/**
* Selector to determine if a row is bookmarked.
*
* This selector uses the `bookmarkSelector` to get the list of bookmarks and checks if the given row
* is present in that list by comparing the row's ID with the IDs of the bookmarks.
*
* @param row - The row to check for being bookmarked.
* @returns A boolean indicating whether the row is bookmarked.
*/
export const isRowBookmarkedSelector = selectorFamily<boolean, Row>({
key: 'is-row-bookmarked',
get: (row: Row) => ({ get }) => {
const bookmarks = get(bookmarkSelector);
return bookmarks.some(b => b.id === row.id);
},
});

/**
* Selector to get the color associated with a bookmarked row OR to get the next color if the row is not bookmarked.
*
* This selector uses the `bookmarkedColorPalette` to find the color for the given row.
* If the row does not have a color in the palette, it falls back to the `nextColorSelector`.
*
* @param row - The row for which the color is being selected.
* @returns The color associated with the given row.
*/
export const bookmarkedColorSelector = selectorFamily<string, Row>({
key: 'bookmarked-color-selector',
get: (row: Row) => ({ get }) => {
const palette = get(bookmarkedColorPalette);
const nextColor = get(nextColorSelector);
return palette[row.id] || nextColor;
},
});

/**
* The color to use for the current element selection stored in the config
*/
Expand Down
112 changes: 103 additions & 9 deletions packages/upset/src/components/Columns/BookmarkStar.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,122 @@
import { Row } from '@visdesignlab/upset2-core';
import { FC } from 'react';
import { useRecoilValue } from 'recoil';
import StarIcon from '@mui/icons-material/Star';
import { FC, MouseEvent, useContext, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { ProvenanceContext } from '../Root';
import { dimensionsSelector } from '../../atoms/dimensionsAtom';
import translate from '../../utils/transform';
import { bookmarkedColorPalette } from '../../atoms/config/currentIntersectionAtom';
import {
bookmarkedColorSelector,
isRowBookmarkedSelector,
} from '../../atoms/config/currentIntersectionAtom';

type Props = {
row: Row;
}
row: Row;
};

const BASE_OPACITY = 0.0;
const HOVERED_OPACITY = 0.5;
const BOOKMARKED_OPACITY = 1.0;

/**
* BookmarkStar component renders a star icon that can be bookmarked.
* It changes its color and opacity based on the bookmark and hover states.
*
* @component
* @param {Props} props - The properties object.
* @param {Object} props.row - The row data for the current item.
* @returns {JSX.Element} The rendered BookmarkStar component.
*
* @example
* <BookmarkStar row={row} />
*/
export const BookmarkStar: FC<Props> = ({ row }) => {
const dimensions = useRecoilValue(dimensionsSelector);
const colorPallete = useRecoilValue(bookmarkedColorPalette);
const bookmarked = useRecoilValue(isRowBookmarkedSelector(row));
const color = useRecoilValue(bookmarkedColorSelector(row));
const { actions } = useContext(ProvenanceContext);

const rowDisplayName = row.elementName.replaceAll('~&~', ' & ') || '';

const [hovered, setHovered] = useState(false);

/**
* Calculates the opacity value based on the bookmark and hover states.
*
* @returns {number} The opacity value which can be one of the following:
* - `BOOKMARKED_OPACITY` if the item is bookmarked.
* - `HOVERED_OPACITY` if the item is hovered.
* - `BASE_OPACITY` if neither condition is met.
*
* @param {boolean} bookmarked - Indicates if the item is bookmarked.
* @param {boolean} hovered - Indicates if the item is hovered.
*/
const opacity = useMemo(() => {
if (bookmarked) {
return BOOKMARKED_OPACITY;
}

if (hovered) {
return HOVERED_OPACITY;
}

return BASE_OPACITY;
}, [bookmarked, hovered]);

const handleMouseEnter = () => {
setHovered(true);
};

const handleMouseLeave = () => {
setHovered(false);
};

/**
* Handles the click event on the bookmark star icon.
* Stops the event from propagating and adds a bookmark with the specified details.
*
* @param event - The mouse event triggered by clicking the bookmark star icon.
*/
const handleClick = (event: MouseEvent<SVGGElement, MouseEvent>) => {
event.stopPropagation();
if (bookmarked) {
actions.removeBookmark(bookmarked);
} else {
actions.addBookmark({
id: row.id,
label: rowDisplayName,
size: row.size,
type: 'intersection',
});
}
};

return (
<g
transform={translate(
dimensions.matrixColumn.width +
dimensions.bookmarkStar.gap,
dimensions.matrixColumn.width + dimensions.bookmarkStar.gap,
0,
)}
height={dimensions.body.rowHeight}
width={dimensions.set.width}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={(e: any) => handleClick(e)}
>
<StarIcon height={dimensions.body.rowHeight} width={dimensions.set.width} fontSize={'1em' as any} sx={{ color: colorPallete[row.id] }} />
<rect
height={dimensions.body.rowHeight}
width={dimensions.set.width}
fill="transparent"
/>
<StarIcon
height={dimensions.body.rowHeight}
width={dimensions.set.width}
fontSize={'1em' as any}
sx={{
color,
fillOpacity: opacity,
}}
/>
</g>
);
};
4 changes: 1 addition & 3 deletions packages/upset/src/components/Rows/AggregateRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export const AggregateRow: FC<Props> = ({ aggregateRow }) => {
const visibleSets = useRecoilValue(visibleSetSelector);
const dimensions = useRecoilValue(dimensionsSelector);
const currentIntersection = useRecoilValue(currentIntersectionSelector);
const bookmarks = useRecoilValue(bookmarkSelector);
const collapsedIds = useRecoilValue(collapsedSelector);
const { actions } = useContext(ProvenanceContext);
const selected = useRecoilValue(aggregateSelectedCount(aggregateRow));
Expand Down Expand Up @@ -140,8 +139,7 @@ export const AggregateRow: FC<Props> = ({ aggregateRow }) => {
</g>
)}
<g transform={translate(0, (['Sets', 'Overlaps'].includes(aggregateRow.aggregateBy)) ? dimensions.body.rowHeight - 5 : 0)}>
{ bookmarks.find((b) => b.id === aggregateRow.id) &&
<BookmarkStar row={aggregateRow} />}
<BookmarkStar row={aggregateRow} />
<SizeBar
row={aggregateRow}
size={aggregateRow.size}
Expand Down
3 changes: 1 addition & 2 deletions packages/upset/src/components/Rows/SubsetRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ export const SubsetRow: FC<Props> = ({ subset }) => {
fillOpacity="0.0"
/>
<Matrix sets={visibleSets} subset={subset} />
{bookmarks.find((b) => b.id === subset.id) &&
<BookmarkStar row={subset} />}
<BookmarkStar row={subset} />
<SizeBar size={subset.size} row={subset} selected={selected} />
<AttributeBars attributes={subset.attributes} row={subset} />
</g>
Expand Down

0 comments on commit 02914f9

Please sign in to comment.