diff --git a/packages/upset/src/components/AggregateRow.tsx b/packages/upset/src/components/AggregateRow.tsx index 8a944a81..d72f6e0e 100644 --- a/packages/upset/src/components/AggregateRow.tsx +++ b/packages/upset/src/components/AggregateRow.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/react'; -import { Aggregate } from '@visdesignlab/upset2-core'; +import { Aggregate, getDegreeFromSetMembership } from '@visdesignlab/upset2-core'; import { FC, useContext } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; @@ -17,6 +17,7 @@ import { BookmarkStar } from './custom/BookmarkStar'; import { collapsedSelector } from '../atoms/collapsedAtom'; import { ProvenanceContext } from './Root'; import { AttributeBars } from './AttributeBars'; +import { Degree } from './Degree'; /** @jsxImportSource @emotion/react */ type Props = { @@ -119,6 +120,7 @@ export const AggregateRow: FC = ({ aggregateRow }) => { { bookmarkedIntersections.find((b) => b.id === aggregateRow.id) && } + diff --git a/packages/upset/src/components/AttributeBars.tsx b/packages/upset/src/components/AttributeBars.tsx index 2ec85ed0..ca48bd52 100644 --- a/packages/upset/src/components/AttributeBars.tsx +++ b/packages/upset/src/components/AttributeBars.tsx @@ -23,6 +23,8 @@ export const AttributeBars: FC = ({ attributes, row }) => { dimensions.bookmarkStar.gap + dimensions.bookmarkStar.width + dimensions.bookmarkStar.gap + + dimensions.degreeColumn.width + + dimensions.degreeColumn.gap + dimensions.size.width + dimensions.gap + dimensions.attribute.width + diff --git a/packages/upset/src/components/Degree.tsx b/packages/upset/src/components/Degree.tsx new file mode 100644 index 00000000..cd7b06ba --- /dev/null +++ b/packages/upset/src/components/Degree.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react'; +import { useRecoilValue } from 'recoil'; +import { dimensionsSelector } from '../atoms/dimensionsAtom'; +import translate from '../utils/transform'; + +type Props = { + degree: number; +} + +export const Degree: FC = ({ degree }) => { + const dimensions = useRecoilValue(dimensionsSelector); + return ( + + + {degree} + + + ); +}; diff --git a/packages/upset/src/components/DeviationBar.tsx b/packages/upset/src/components/DeviationBar.tsx index 5036758e..9c44b79d 100644 --- a/packages/upset/src/components/DeviationBar.tsx +++ b/packages/upset/src/components/DeviationBar.tsx @@ -28,6 +28,8 @@ export const DeviationBar: FC = ({ deviation }) => { dimensions.bookmarkStar.gap + dimensions.bookmarkStar.width + dimensions.bookmarkStar.gap + + dimensions.degreeColumn.width + + dimensions.degreeColumn.gap + dimensions.size.width + dimensions.gap + dimensions.attribute.width / 2, diff --git a/packages/upset/src/components/Header/AttributeButton.tsx b/packages/upset/src/components/Header/AttributeButton.tsx index 5a12b7d8..ec9a11a8 100644 --- a/packages/upset/src/components/Header/AttributeButton.tsx +++ b/packages/upset/src/components/Header/AttributeButton.tsx @@ -12,10 +12,10 @@ import { HeaderSortArrow } from '../custom/HeaderSortArrow'; /** @jsxImportSource @emotion/react */ type Props = { label: string; - sort?: boolean; + sortable?: boolean; }; -export const AttributeButton: FC = ({ label, sort = false }) => { +export const AttributeButton: FC = ({ label, sortable = false }) => { const dimensions = useRecoilValue(dimensionsSelector); const { actions } = useContext( ProvenanceContext, @@ -24,17 +24,27 @@ export const AttributeButton: FC = ({ label, sort = false }) => { const sortByOrder = useRecoilValue(sortByOrderSelector); const setContextMenu = useSetRecoilState(contextMenuAtom); - const handleContextMenuClose = () => { - setContextMenu(null); - }; - const sortByHeader = (order: SortByOrder) => { actions.sortBy(label as SortBy, order); }; + const handleOnClick = () => { + if (sortable) { + if (sortBy !== label) { + sortByHeader('Ascending'); + } else { + sortByHeader(sortByOrder === 'Ascending' ? 'Descending' : 'Ascending'); + } + } + }; + + const handleContextMenuClose = () => { + setContextMenu(null); + }; + const getMenuItems = () => { const items = []; - if (sort) { + if (sortable) { items.push( { label: `Sort by ${label} - Ascending`, @@ -82,7 +92,7 @@ export const AttributeButton: FC = ({ label, sort = false }) => { '&:hover': { opacity: 0.7, }, - cursor: (sort ? 'context-menu' : 'default'), + cursor: 'context-menu', }} onContextMenu={(e) => { e.preventDefault(); @@ -90,6 +100,7 @@ export const AttributeButton: FC = ({ label, sort = false }) => { openContextMenu(e); }} transform={translate(0, 6)} + onClick={handleOnClick} > = ({ label, sort = false }) => { > {label} - {(sort && sortBy === label) && - + {(sortable && sortBy === label) && + } diff --git a/packages/upset/src/components/Header/AttributeHeaders.tsx b/packages/upset/src/components/Header/AttributeHeaders.tsx index 2750704e..0fb94875 100644 --- a/packages/upset/src/components/Header/AttributeHeaders.tsx +++ b/packages/upset/src/components/Header/AttributeHeaders.tsx @@ -13,13 +13,15 @@ export const AttributeHeaders = () => { diff --git a/packages/upset/src/components/Header/DegreeHeader.tsx b/packages/upset/src/components/Header/DegreeHeader.tsx new file mode 100644 index 00000000..a03dae46 --- /dev/null +++ b/packages/upset/src/components/Header/DegreeHeader.tsx @@ -0,0 +1,128 @@ +import { css } from '@emotion/react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useContext } from 'react'; +import { Tooltip } from '@mui/material'; +import { sortByOrderSelector, sortBySelector } from '../../atoms/config/sortByAtom'; +import translate from '../../utils/transform'; +import { HeaderSortArrow } from '../custom/HeaderSortArrow'; +import { dimensionsSelector } from '../../atoms/dimensionsAtom'; +import { contextMenuAtom } from '../../atoms/contextMenuAtom'; +import { ProvenanceContext } from '../Root'; + +export const DegreeHeader = () => { + const { actions } = useContext(ProvenanceContext); + const sortBy = useRecoilValue(sortBySelector); + const sortByOrder = useRecoilValue(sortByOrderSelector); + const dimensions = useRecoilValue(dimensionsSelector); + + const setContextMenu = useSetRecoilState(contextMenuAtom); + + const sortByDegree = (order: string) => { + actions.sortBy('Degree', order); + }; + + const handleOnClick = () => { + if (sortBy !== 'Degree') { + sortByDegree('Ascending'); + } else { + sortByDegree(sortByOrder === 'Ascending' ? 'Descending' : 'Ascending'); + } + }; + + const handleContextMenuClose = () => { + setContextMenu(null); + }; + + const getMenuItems = () => [ + { + label: 'Sort by Degree - Ascending', + onClick: () => { + sortByDegree('Ascending'); + handleContextMenuClose(); + }, + disabled: sortBy === 'Degree' && sortByOrder === 'Ascending', + }, + { + label: 'Sort by Degree - Descending', + onClick: () => { + sortByDegree('Descending'); + handleContextMenuClose(); + }, + disabled: sortBy === 'Degree' && sortByOrder === 'Descending', + }, + ]; + + const openContextMenu = (e: MouseEvent) => { + setContextMenu( + { + mouseX: e.clientX, + mouseY: e.clientY, + id: 'header-menu-degree', + items: getMenuItems(), + }, + ); + }; + + return ( + + + { + e.preventDefault(); + e.stopPropagation(); + openContextMenu(e); + }} + onClick={handleOnClick} + > + + + + # + + { sortBy === 'Degree' && + } + + + + + ); +}; diff --git a/packages/upset/src/components/Header/DeviationHeader.tsx b/packages/upset/src/components/Header/DeviationHeader.tsx index b62270c0..497b8fc2 100644 --- a/packages/upset/src/components/Header/DeviationHeader.tsx +++ b/packages/upset/src/components/Header/DeviationHeader.tsx @@ -18,12 +18,14 @@ export const DeviationHeader = () => { dimensions.bookmarkStar.gap + dimensions.bookmarkStar.width + dimensions.bookmarkStar.gap + + dimensions.degreeColumn.width + + dimensions.degreeColumn.gap + dimensions.size.width + dimensions.gap, dimensions.header.totalHeight - dimensions.attribute.height, )} > - + ( <> + diff --git a/packages/upset/src/components/Header/SizeHeader.tsx b/packages/upset/src/components/Header/SizeHeader.tsx index 7fc7e153..29e74bfc 100644 --- a/packages/upset/src/components/Header/SizeHeader.tsx +++ b/packages/upset/src/components/Header/SizeHeader.tsx @@ -45,14 +45,22 @@ export const SizeHeader: FC = () => { const setContextMenu = useSetRecoilState(contextMenuAtom); - const handleContextMenuClose = () => { - setContextMenu(null); - }; - const sortBySize = (order: string) => { actions.sortBy('Size', order); }; + const handleOnClick = () => { + if (sortBy !== 'Size') { + sortBySize('Ascending'); + } else { + sortBySize(sortByOrder === 'Ascending' ? 'Descending' : 'Ascending'); + } + }; + + const handleContextMenuClose = () => { + setContextMenu(null); + }; + const getMenuItems = () => [ { label: 'Sort by Size - Ascending', @@ -148,7 +156,9 @@ export const SizeHeader: FC = () => { dimensions.matrixColumn.width + dimensions.bookmarkStar.gap + dimensions.bookmarkStar.width + - dimensions.bookmarkStar.gap, + dimensions.bookmarkStar.gap + + dimensions.degreeColumn.width + + dimensions.degreeColumn.gap, dimensions.header.totalHeight - dimensions.size.height, )} > @@ -232,6 +242,7 @@ export const SizeHeader: FC = () => { e.stopPropagation(); openContextMenu(e); }} + onClick={handleOnClick} > { strokeWidth="0.3px" height={dimensions.size.buttonHeight} width={dimensions.attribute.width} - onClick={() => { - if (sortBy !== 'Size') actions.sortBy('Size'); - }} /> { Size { sortBy === 'Size' && - + } diff --git a/packages/upset/src/components/Matrix.tsx b/packages/upset/src/components/Matrix.tsx index 2f6fb8fc..6a42f301 100644 --- a/packages/upset/src/components/Matrix.tsx +++ b/packages/upset/src/components/Matrix.tsx @@ -74,6 +74,7 @@ export const Matrix: FC = ({ fillOpacity="0.0" /> = ({ rows }) => { e.stopPropagation()}> {rowTransitions(({ transform }, item) => ( shouldRender(item.row) && - {rowRenderer(item.row)} + {rowRenderer(item.row)} ))} ); diff --git a/packages/upset/src/components/Sidebar.tsx b/packages/upset/src/components/Sidebar.tsx index f95b0fe9..907c19c7 100644 --- a/packages/upset/src/components/Sidebar.tsx +++ b/packages/upset/src/components/Sidebar.tsx @@ -4,7 +4,6 @@ import { Accordion, AccordionDetails, AccordionSummary, - Alert, Box, FormControl, FormControlLabel, @@ -18,14 +17,13 @@ import { Typography, } from '@mui/material'; import { - AggregateBy, aggregateByList, SortBy, + AggregateBy, aggregateByList, } from '@visdesignlab/upset2-core'; import { Fragment, useContext, useEffect, useState, } from 'react'; import { useRecoilValue } from 'recoil'; -import { ArrowUpward } from '@mui/icons-material'; import { firstAggregateSelector, firstOvelapDegreeSelector, @@ -35,13 +33,11 @@ import { import { hideEmptySelector, hideNoSetSelector, maxVisibleSelector, minVisibleSelector, } from '../atoms/config/filterAtoms'; -import { sortByOrderSelector, sortBySelector } from '../atoms/config/sortByAtom'; import { visibleSetSelector } from '../atoms/config/visibleSetsAtoms'; import { ProvenanceContext } from './Root'; import { HelpCircle, defaultMargin } from './custom/HelpCircle'; import { helpText } from '../utils/helpText'; import { dimensionsSelector } from '../atoms/dimensionsAtom'; -import { arrowIconCSS } from '../utils/styles'; const itemDivCSS = css` display: flex; @@ -64,9 +60,6 @@ export const Sidebar = () => { const firstOverlapDegree = useRecoilValue(firstOvelapDegreeSelector); const secondAggregateBy = useRecoilValue(secondAggregateSelector); const secondOverlapDegree = useRecoilValue(secondOverlapDegreeSelector); - - const sortBy = useRecoilValue(sortBySelector); - const sortByOrder = useRecoilValue(sortByOrderSelector); const maxVisible = useRecoilValue(maxVisibleSelector); const minVisible = useRecoilValue(minVisibleSelector); const hideEmpty = useRecoilValue(hideEmptySelector); @@ -95,95 +88,6 @@ export const Sidebar = () => { Settings - }> - Sorting - - - - { - actions.sortBy(ev.target.value as SortBy, 'Descending'); - }} - > - { - if (e.key === 'Enter') { - actions.sortBy('Degree', 'Descending'); - } - }} - > - { - if (sortBy === 'Degree') { - if (sortByOrder === 'Ascending') { - actions.sortBy('Degree', 'Descending'); - } else { - actions.sortBy('Degree', 'Ascending'); - } - } - }} - > - Degree - { sortBy === 'Degree' && - - } - - } - control={} - /> - - - { - if (e.key === 'Enter') { - actions.sortBy('Size', 'Descending'); - } - }} - > - { - if (sortBy === 'Size') { - if (sortByOrder === 'Ascending') { - actions.sortBy('Size', 'Descending'); - } else { - actions.sortBy('Size', 'Ascending'); - } - } - }} - > - Size - { sortBy === 'Size' && - - } - - } - control={} - /> - - - Use column headers for custom sorting - - - - - }> Aggregation @@ -214,7 +118,7 @@ export const Sidebar = () => { label={agg} control={} /> - {agg !== 'None' && } + {agg !== 'None' && } {agg === 'Overlaps' && firstAggregateBy === agg && ( { label={agg} control={} /> - {agg !== 'None' && } + {agg !== 'None' && } {agg === 'Overlaps' && secondAggregateBy === agg && ( = ({ row, size }) => { dimensions.matrixColumn.width + dimensions.bookmarkStar.gap + dimensions.bookmarkStar.width + - dimensions.bookmarkStar.gap, + dimensions.bookmarkStar.gap + + dimensions.degreeColumn.width + + dimensions.degreeColumn.gap, (dimensions.body.rowHeight - dimensions.size.plotHeight) / 2, )} > diff --git a/packages/upset/src/components/SubsetRow.tsx b/packages/upset/src/components/SubsetRow.tsx index de6bc1cd..fc9eaddb 100644 --- a/packages/upset/src/components/SubsetRow.tsx +++ b/packages/upset/src/components/SubsetRow.tsx @@ -1,4 +1,4 @@ -import { Subset, getBelongingSetsFromSetMembership } from '@visdesignlab/upset2-core'; +import { Subset, getBelongingSetsFromSetMembership, getDegreeFromSetMembership } from '@visdesignlab/upset2-core'; import React, { FC, useState } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; @@ -14,6 +14,7 @@ import { } from '../utils/styles'; import { BookmarkStar } from './custom/BookmarkStar'; import { columnHoverAtom, columnSelectAtom } from '../atoms/highlightAtom'; +import { Degree } from './Degree'; type Props = { subset: Subset; @@ -72,6 +73,7 @@ export const SubsetRow: FC = ({ subset }) => { {bookmarkedIntersections.find((b) => b.id === subset.id) && } + diff --git a/packages/upset/src/components/custom/BookmarkStar.tsx b/packages/upset/src/components/custom/BookmarkStar.tsx index a01e19e5..78dd8cb7 100644 --- a/packages/upset/src/components/custom/BookmarkStar.tsx +++ b/packages/upset/src/components/custom/BookmarkStar.tsx @@ -18,7 +18,7 @@ export const BookmarkStar: FC = ({ row }) => { diff --git a/packages/upset/src/components/custom/HeaderSortArrow.tsx b/packages/upset/src/components/custom/HeaderSortArrow.tsx index 4ab00a26..dc4d393c 100644 --- a/packages/upset/src/components/custom/HeaderSortArrow.tsx +++ b/packages/upset/src/components/custom/HeaderSortArrow.tsx @@ -4,20 +4,34 @@ import { FC } from 'react'; import { useRecoilValue } from 'recoil'; import { sortByOrderSelector } from '../../atoms/config/sortByAtom'; import translate from '../../utils/transform'; +import { dimensionsSelector } from '../../atoms/dimensionsAtom'; type Props = { - translateX: number; - translateY: number; + translateX?: number; + translateY?: number; } export const HeaderSortArrow: FC = ({ translateX, translateY }) => { const sortByOrder = useRecoilValue(sortByOrderSelector); + const dimensions = useRecoilValue(dimensionsSelector); + + const height = 16; + const width = 16; + + if (translateX === undefined) { + translateX = dimensions.attribute.width / 2 - width; + } + + if (translateY === undefined) { + translateY = -(height / 2); + } + return ( ); diff --git a/packages/upset/src/dimensions.ts b/packages/upset/src/dimensions.ts index 4ec2b23a..71853bd3 100644 --- a/packages/upset/src/dimensions.ts +++ b/packages/upset/src/dimensions.ts @@ -65,6 +65,11 @@ export function calculateDimensions( gap: 10, }; + const degreeColumn = { + width: 40, + gap: 10, + }; + const sidebar = { width: 250, }; @@ -78,6 +83,9 @@ export function calculateDimensions( bookmarkStar.gap + bookmarkStar.width + // Bookmark Star bookmarkStar.gap + + degreeColumn.gap + + degreeColumn.width + // Degree Column + degreeColumn.gap + // Add margin attribute.width + // Deviation attribute.vGap + (attribute.vGap + attribute.width) * nAttributes, // Show all attributes @@ -104,6 +112,7 @@ export function calculateDimensions( xOffset, size, bookmarkStar, + degreeColumn, body, matrixColumn, margin,