From f971f77a984001a1bf5ff2896d7d238515e45329 Mon Sep 17 00:00:00 2001 From: asizemore Date: Mon, 23 Oct 2023 06:44:31 -0400 Subject: [PATCH 01/14] Update volcano legend text --- .../implementations/VolcanoPlotVisualization.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx index 3200e9d4e8..190a2a17c7 100755 --- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx +++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx @@ -590,7 +590,7 @@ function VolcanoPlotViz(props: VisualizationProps) { markerColor: significanceColors['inconclusive'], }, { - label: `Up regulated in ${computationConfiguration.comparator.groupB + label: `Up in ${computationConfiguration.comparator.groupB ?.map((entry) => entry.label) .join(',')} (${countsData[significanceColors['high']]})`, marker: 'circle', @@ -598,7 +598,7 @@ function VolcanoPlotViz(props: VisualizationProps) { markerColor: significanceColors['high'], }, { - label: `Up regulated in ${computationConfiguration.comparator.groupA + label: `Up in ${computationConfiguration.comparator.groupA ?.map((entry) => entry.label) .join(',')} (${countsData[significanceColors['low']]})`, marker: 'circle', From 362445ee914a5fa7da4f5be5612318c4f9382dd1 Mon Sep 17 00:00:00 2001 From: asizemore Date: Mon, 23 Oct 2023 10:03:58 -0400 Subject: [PATCH 02/14] styling tweaks for volcano x axis annotations --- .../libs/components/src/plots/VolcanoPlot.css | 11 + .../libs/components/src/plots/VolcanoPlot.tsx | 7 +- .../src/stories/plots/VolcanoPlot.stories.tsx | 26 ++ .../VolcanoPlotVisualization.tsx | 8 +- .../visualizations/relaxMicrobe.tsx | 396 ------------------ 5 files changed, 45 insertions(+), 403 deletions(-) delete mode 100644 packages/libs/eda/src/lib/core/components/visualizations/relaxMicrobe.tsx diff --git a/packages/libs/components/src/plots/VolcanoPlot.css b/packages/libs/components/src/plots/VolcanoPlot.css index 3316c0cb7f..7dc36bcd8e 100644 --- a/packages/libs/components/src/plots/VolcanoPlot.css +++ b/packages/libs/components/src/plots/VolcanoPlot.css @@ -26,3 +26,14 @@ .VolcanoPlotTooltip > ul > li > span { font-weight: bold; } + +.visx-annotationlabel { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 10px; + width: 10px; + color: red; + opacity: 1; + /* font-weight: 900; WhY DOES THIS NOT WORK */ +} diff --git a/packages/libs/components/src/plots/VolcanoPlot.tsx b/packages/libs/components/src/plots/VolcanoPlot.tsx index 2cf096f82c..7a24f1a768 100755 --- a/packages/libs/components/src/plots/VolcanoPlot.tsx +++ b/packages/libs/components/src/plots/VolcanoPlot.tsx @@ -266,9 +266,9 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref) { findNearestDatumOverride={findNearestDatumXY} margin={{ top: MARGIN_DEFAULT, - right: showCappedDataAnnotation ? 150 : MARGIN_DEFAULT, - left: MARGIN_DEFAULT, - bottom: MARGIN_DEFAULT, + right: showCappedDataAnnotation ? 150 : MARGIN_DEFAULT + 10, + left: MARGIN_DEFAULT + 10, // Bottom annotatiions get wide (for right margin, too) + bottom: MARGIN_DEFAULT + 20, // Bottom annotations can get long }} > {/* Set up the axes and grid lines. XYChart magically lays them out correctly */} @@ -295,6 +295,7 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref) { verticalAnchor="start" showAnchorLine={false} showBackground={false} + maxWidth={100} /> ); diff --git a/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx b/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx index 5010f1eb8c..893caf4e9c 100755 --- a/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx +++ b/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx @@ -7,6 +7,7 @@ import { NumberRange } from '../../types/general'; import { yellow } from '@veupathdb/coreui/lib/definitions/colors'; import { assignSignificanceColor } from '../../plots/VolcanoPlot'; import { significanceColors } from '../../types/plots'; +import { CSSProperties } from 'react'; export default { title: 'Plots/VolcanoPlot', @@ -123,6 +124,7 @@ interface TemplateProps { comparisonLabels?: string[]; truncationBarFill?: string; showSpinner?: boolean; + containerStyles?: CSSProperties; } const Template: Story = (args) => { @@ -190,6 +192,7 @@ const Template: Story = (args) => { dependentAxisRange: args.dependentAxisRange, truncationBarFill: args.truncationBarFill, showSpinner: args.showSpinner, + containerStyles: args.containerStyles, rawDataMinMaxValues, }; @@ -268,3 +271,26 @@ Empty.args = { independentAxisRange: { min: -9, max: 9 }, dependentAxisRange: { min: -1, max: 9 }, }; + +// With visualization plot container styles +const plotContainerStyles = { + width: 750, + height: 450, + marginLeft: '0.75rem', + border: '1px solid #dedede', + boxShadow: '1px 1px 4px #00000066', +}; +export const WithStyle = Template.bind({}); +WithStyle.args = { + data: dataSetVolcano, + markerBodyOpacity: 0.8, + effectSizeThreshold: 1, + significanceThreshold: 0.01, + comparisonLabels: [ + 'Up in group a, b, c, d, e, f, g, and h', + 'Up in group i, j, k, l, m, n, o, pqrs', + ], + independentAxisRange: { min: -9, max: 9 }, + dependentAxisRange: { min: 0, max: 9 }, + containerStyles: plotContainerStyles, +}; diff --git a/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx b/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx index 190a2a17c7..6bb9bf3d37 100755 --- a/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx +++ b/packages/libs/eda/src/lib/core/components/visualizations/implementations/VolcanoPlotVisualization.tsx @@ -407,11 +407,11 @@ function VolcanoPlotViz(props: VisualizationProps) { 'Up in ' + computationConfiguration.comparator.groupA .map((entry) => entry.label) - .join(','), + .join(', '), 'Up in ' + computationConfiguration.comparator.groupB .map((entry) => entry.label) - .join(','), + .join(', '), ] : []; @@ -592,7 +592,7 @@ function VolcanoPlotViz(props: VisualizationProps) { { label: `Up in ${computationConfiguration.comparator.groupB ?.map((entry) => entry.label) - .join(',')} (${countsData[significanceColors['high']]})`, + .join(', ')} (${countsData[significanceColors['high']]})`, marker: 'circle', hasData: true, markerColor: significanceColors['high'], @@ -600,7 +600,7 @@ function VolcanoPlotViz(props: VisualizationProps) { { label: `Up in ${computationConfiguration.comparator.groupA ?.map((entry) => entry.label) - .join(',')} (${countsData[significanceColors['low']]})`, + .join(', ')} (${countsData[significanceColors['low']]})`, marker: 'circle', hasData: true, markerColor: significanceColors['low'], diff --git a/packages/libs/eda/src/lib/core/components/visualizations/relaxMicrobe.tsx b/packages/libs/eda/src/lib/core/components/visualizations/relaxMicrobe.tsx deleted file mode 100644 index e924d1d7a7..0000000000 --- a/packages/libs/eda/src/lib/core/components/visualizations/relaxMicrobe.tsx +++ /dev/null @@ -1,396 +0,0 @@ -export default function RelaxMicrobeSVG() { - return ( - // {/*?xml version="1.0" encoding="utf-8"?*/} - // {/* Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - R - - - elax,{' '} - - - w - - - e - - - {' '} - h - - - av - - - e it c - - - ov - - - e - - - r - - - ed!{' '} - - - Y - - - our compu - - - t - - - e job will continue in{' '} - - - t - - - he{' '} - - - bac - - - k - - - g - - - r - - - ound. - - - F - - - eel f - - - r - - - ee{' '} - - - t - - - o change tabs, sta - - - r - - - t a new{' '} - - - visualization, or get a cup of cof - - - f - - - ee.{' '} - - - Y - - - our{' '} - - - r - - - esul - - - t - - - s will be{' '} - - - r - - - eady soon. - - - - - - - - - ); -} From a925140005182342550ff31935dcc65339951b78 Mon Sep 17 00:00:00 2001 From: asizemore Date: Mon, 23 Oct 2023 10:17:51 -0400 Subject: [PATCH 03/14] fix empty volcano story --- .../src/stories/plots/VolcanoPlot.stories.tsx | 109 ++++++++++-------- 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx b/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx index 893caf4e9c..d94c4dace9 100755 --- a/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx +++ b/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx @@ -130,57 +130,66 @@ interface TemplateProps { const Template: Story = (args) => { // Process input data. Take the object of arrays and turn it into // an array of data points. Note the backend will do this for us! - const volcanoDataPoints: VolcanoPlotData | undefined = { - effectSizeLabel: args.data?.volcanoplot.effectSizeLabel ?? '', - statistics: - args.data?.volcanoplot.statistics.effectSize.map((effectSize, index) => { - return { - effectSize: effectSize, - pValue: args.data?.volcanoplot.statistics.pValue[index], - adjustedPValue: - args.data?.volcanoplot.statistics.adjustedPValue[index], - pointID: args.data?.volcanoplot.statistics.pointID[index], - significanceColor: assignSignificanceColor( - Number(effectSize), - Number(args.data?.volcanoplot.statistics.pValue[index]), - args.significanceThreshold, - args.effectSizeThreshold, - significanceColors - ), - }; - }) ?? [], - }; + const volcanoDataPoints: VolcanoPlotData | undefined = args.data + ? { + effectSizeLabel: args.data?.volcanoplot.effectSizeLabel ?? '', + statistics: + args.data?.volcanoplot.statistics.effectSize.map( + (effectSize, index) => { + return { + effectSize: effectSize, + pValue: args.data?.volcanoplot.statistics.pValue[index], + adjustedPValue: + args.data?.volcanoplot.statistics.adjustedPValue[index], + pointID: args.data?.volcanoplot.statistics.pointID[index], + significanceColor: assignSignificanceColor( + Number(effectSize), + Number(args.data?.volcanoplot.statistics.pValue[index]), + args.significanceThreshold, + args.effectSizeThreshold, + significanceColors + ), + }; + } + ) ?? [], + } + : undefined; - const rawDataMinMaxValues = { - x: { - min: - (volcanoDataPoints && - Math.min( - ...volcanoDataPoints.statistics.map((d) => Number(d.effectSize)) - )) ?? - 0, - max: - (volcanoDataPoints && - Math.max( - ...volcanoDataPoints.statistics.map((d) => Number(d.effectSize)) - )) ?? - 0, - }, - y: { - min: - (volcanoDataPoints && - Math.min( - ...volcanoDataPoints.statistics.map((d) => Number(d.pValue)) - )) ?? - 1, - max: - (volcanoDataPoints && - Math.max( - ...volcanoDataPoints.statistics.map((d) => Number(d.pValue)) - )) ?? - 1, - }, - }; + const rawDataMinMaxValues = args.data + ? { + x: { + min: + (volcanoDataPoints && + Math.min( + ...volcanoDataPoints.statistics.map((d) => Number(d.effectSize)) + )) ?? + 0, + max: + (volcanoDataPoints && + Math.max( + ...volcanoDataPoints.statistics.map((d) => Number(d.effectSize)) + )) ?? + 0, + }, + y: { + min: + (volcanoDataPoints && + Math.min( + ...volcanoDataPoints.statistics.map((d) => Number(d.pValue)) + )) ?? + 1, + max: + (volcanoDataPoints && + Math.max( + ...volcanoDataPoints.statistics.map((d) => Number(d.pValue)) + )) ?? + 1, + }, + } + : { + x: { min: 0, max: 0 }, + y: { min: 1, max: 1 }, + }; const volcanoPlotProps: VolcanoPlotProps = { data: volcanoDataPoints, From 1134f282968f1089fa451f1f50974c4e6f5670e4 Mon Sep 17 00:00:00 2001 From: asizemore Date: Tue, 24 Oct 2023 06:45:26 -0400 Subject: [PATCH 04/14] add isClickable prop to legends to control cursor --- .../src/components/plotControls/PlotBubbleLegend.tsx | 1 + .../src/components/plotControls/PlotGradientLegend.tsx | 4 +++- .../components/src/components/plotControls/PlotLegend.tsx | 5 ++++- .../src/components/plotControls/PlotListLegend.tsx | 5 ++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx b/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx index d52a84e216..086a92eee1 100644 --- a/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx +++ b/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx @@ -6,6 +6,7 @@ import _ from 'lodash'; export interface PlotLegendBubbleProps { legendMax: number; valueToDiameterMapper: ((value: number) => number) | undefined; + isClickable?: boolean; // controls the cursor type } // legend ellipsis function for legend title and legend items (from custom legend work) diff --git a/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx b/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx index ef84ace1b7..63e14972cb 100755 --- a/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx +++ b/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx @@ -8,6 +8,7 @@ export interface PlotLegendGradientProps { valueToColorMapper: (a: number) => string; nTicks?: number; // MUST be odd! showMissingness?: boolean; + isClickable?: boolean; // controls the cursor type } // legend ellipsis function for legend title and legend items (from custom legend work) @@ -24,6 +25,7 @@ export default function PlotGradientLegend({ valueToColorMapper, nTicks = 5, showMissingness, + isClickable = true, }: PlotLegendGradientProps) { // Declare constants const gradientBoxHeight = 150; @@ -119,7 +121,7 @@ export default function PlotGradientLegend({ )} diff --git a/packages/libs/components/src/plots/VolcanoPlot.tsx b/packages/libs/components/src/plots/VolcanoPlot.tsx index 7a24f1a768..9bcf5809f7 100755 --- a/packages/libs/components/src/plots/VolcanoPlot.tsx +++ b/packages/libs/components/src/plots/VolcanoPlot.tsx @@ -40,6 +40,7 @@ import { ToImgopts } from 'plotly.js'; import { DEFAULT_CONTAINER_HEIGHT } from './PlotlyPlot'; import domToImage from 'dom-to-image'; import './VolcanoPlot.css'; +import { truncateWithEllipsis } from '../utils/axis-tick-label-ellipsis'; export interface RawDataMinMaxValues { x: NumberRange; @@ -290,7 +291,7 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref) { {...xyAccessors} > { + return (label || '').length > maxLabelLength + ? (label || '').substring(0, maxLabelLength - 2) + '...' + : label; +}; + export const axisTickLableEllipsis = ( categoryOrder: string[], maxIndependentTickLabelLength: number ) => { // make array for tick label with ellipsis const categoryOrderEllipsis = categoryOrder.map((element) => { - return (element || '').length > maxIndependentTickLabelLength - ? (element || '').substring(0, maxIndependentTickLabelLength - 2) + '...' - : element; + return truncateWithEllipsis(element, maxIndependentTickLabelLength); }); // identify duplicate element and duplicate indices in the array From d56358967df0ba44d3f0b8eb2d8a2e3b1f8105c0 Mon Sep 17 00:00:00 2001 From: Jeremy Myers Date: Mon, 6 Nov 2023 14:53:21 -0500 Subject: [PATCH 08/14] Add collapsible section to studies menu --- .../src/App/SiteMenu/SiteMenuItem.scss | 2 +- .../DIYStudyMenuCollapsibleSection.tsx | 43 +++++++++++ .../src/App/Studies/DIYStudyMenuItem.tsx | 15 +++- .../web-common/src/App/Studies/StudyMenu.scss | 3 + .../src/App/Studies/StudyMenuItem.jsx | 12 ++- .../webapp/js/client/data/headerMenuItems.jsx | 75 +++++++++++-------- 6 files changed, 112 insertions(+), 38 deletions(-) create mode 100644 packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx diff --git a/packages/libs/web-common/src/App/SiteMenu/SiteMenuItem.scss b/packages/libs/web-common/src/App/SiteMenu/SiteMenuItem.scss index e9ab0cc8d6..aadd4d21a9 100644 --- a/packages/libs/web-common/src/App/SiteMenu/SiteMenuItem.scss +++ b/packages/libs/web-common/src/App/SiteMenu/SiteMenuItem.scss @@ -84,7 +84,7 @@ $white: #e0e0e0; opacity: 1; width: 100%; display: block; - max-height: 200px; + // max-height: 200px; border-bottom: 1px solid #fff; font-weight: 400; font-size: 0.8em; diff --git a/packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx b/packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx new file mode 100644 index 0000000000..f1664e5969 --- /dev/null +++ b/packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx @@ -0,0 +1,43 @@ +import React, { ReactNode, useState } from 'react'; +import { ArrowDown, ArrowRight } from '@veupathdb/coreui'; + +interface Props { + sectionLabel: string; + children: ReactNode; + sectionKey: string; +} + +const styles = { + fill: '#555', + height: '0.8em', + width: '0.8em', +}; + +export function DIYStudyMenuCollapsibleSection({ + sectionLabel, + children, +}: Props) { + const [isExpanded, setIsExpanded] = useState(true); + return ( +
+
setIsExpanded(!isExpanded)} + style={{ + display: 'flex', + alignItems: 'center', + gap: '0.5em', + cursor: 'pointer', + }} + title={`Click to ${isExpanded ? 'hide' : 'show'} this section`} + > + {isExpanded ? : } + {sectionLabel} +
+ {isExpanded && ( +
+ {children} +
+ )} +
+ ); +} diff --git a/packages/libs/web-common/src/App/Studies/DIYStudyMenuItem.tsx b/packages/libs/web-common/src/App/Studies/DIYStudyMenuItem.tsx index 30f348063f..67b96ff103 100644 --- a/packages/libs/web-common/src/App/Studies/DIYStudyMenuItem.tsx +++ b/packages/libs/web-common/src/App/Studies/DIYStudyMenuItem.tsx @@ -5,12 +5,21 @@ import { Link } from '@veupathdb/wdk-client/lib/Components'; interface Props { name: ReactNode; link: string; + isChildOfCollapsibleSection: boolean; } -export function DIYStudyMenuItem({ name, link }: Props) { +export function DIYStudyMenuItem({ + name, + link, + isChildOfCollapsibleSection = false, +}: Props) { return ( -
-
+
+
{name} diff --git a/packages/libs/web-common/src/App/Studies/StudyMenu.scss b/packages/libs/web-common/src/App/Studies/StudyMenu.scss index e6de6414ea..c39a546b38 100644 --- a/packages/libs/web-common/src/App/Studies/StudyMenu.scss +++ b/packages/libs/web-common/src/App/Studies/StudyMenu.scss @@ -38,6 +38,9 @@ $red: #dd314e; color: $red !important; } } + .CollapsibleSectionChild { + padding: 5px; + } } span:has(> div.StudyMenuItem--disabled) { diff --git a/packages/libs/web-common/src/App/Studies/StudyMenuItem.jsx b/packages/libs/web-common/src/App/Studies/StudyMenuItem.jsx index e16544260a..9e579e5edf 100644 --- a/packages/libs/web-common/src/App/Studies/StudyMenuItem.jsx +++ b/packages/libs/web-common/src/App/Studies/StudyMenuItem.jsx @@ -85,7 +85,11 @@ class StudyMenuItem extends React.Component { } renderEdaMenuItem() { - const { study, permissions } = this.props; + const { + study, + permissions, + isChildOfCollapsibleSection = false, + } = this.props; const { name, id, disabled } = study; const edaRoute = makeEdaRoute(study.id) + '/new'; @@ -95,7 +99,11 @@ class StudyMenuItem extends React.Component { 'row StudyMenuItem' + (disabled ? ' StudyMenuItem--disabled' : '') } > -
+
{safeHtml(name)} diff --git a/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx b/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx index 0f258323b9..ffbfd8284c 100755 --- a/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx +++ b/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx @@ -5,6 +5,7 @@ import { StudyMenuSearch, } from '@veupathdb/web-common/lib/App/Studies'; import { DIYStudyMenuItem } from '@veupathdb/web-common/lib/App/Studies/DIYStudyMenuItem'; +import { DIYStudyMenuCollapsibleSection } from '@veupathdb/web-common/lib/App/Studies/DIYStudyMenuCollapsibleSection'; import { menuItemsFromSocials, iconMenuItemsFromSocials, @@ -105,42 +106,52 @@ export default function makeHeaderMenuItemsFactory( studies.entities?.length > 0 ? [ { - text: My studies, + text: ( + ( + + ))} + /> + ), }, ] : [] + ).concat( + filteredCuratedStudies.length > 0 && diyDatasets?.length > 0 + ? [ + { + text: ( + ( + + ) + )} + /> + ), + }, + ] + : filteredCuratedStudies.map((study) => ({ + text: ( + + ), + })) ) - .concat( - filteredUserStudies.map((study) => ({ - text: ( - - ), - })) - ) - .concat( - filteredCuratedStudies.length > 0 && - diyDatasets?.length > 0 - ? [ - { - text: Curated studies, - }, - ] - : [] - ) - .concat( - filteredCuratedStudies.map((study) => ({ - text: ( - - ), - })) - ) : [ { text: ( From 6be2766c242ab2650b048c806efaf9914f107321 Mon Sep 17 00:00:00 2001 From: Jeremy Myers Date: Wed, 8 Nov 2023 15:32:18 -0500 Subject: [PATCH 09/14] Refactor collapsible sections for ClinEpi datasets menu --- packages/libs/wdk-client/src/Components.ts | 2 + .../Display/CollapsibleDetailsSection.tsx | 24 +++++++++++ .../DIYStudyMenuCollapsibleSection.tsx | 43 ------------------- .../webapp/js/client/data/headerMenuItems.jsx | 28 ++++++------ 4 files changed, 41 insertions(+), 56 deletions(-) create mode 100644 packages/libs/wdk-client/src/Components/Display/CollapsibleDetailsSection.tsx delete mode 100644 packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx diff --git a/packages/libs/wdk-client/src/Components.ts b/packages/libs/wdk-client/src/Components.ts index 246c9571b8..20022aa9d2 100644 --- a/packages/libs/wdk-client/src/Components.ts +++ b/packages/libs/wdk-client/src/Components.ts @@ -69,6 +69,7 @@ import SliderInput from './Components/InputControls/SliderInput'; import UnhandledErrors from './Views/UnhandledErrors/UnhandledErrors'; import RecordNavigationSection from './Views/Records/RecordNavigation/RecordNavigationSection'; import { SearchInputSelector } from './Views/Strategy/SearchInputSelector'; +import CollapsibleDetailsSection from './Components/Display/CollapsibleDetailsSection'; export { AccordionButton, @@ -79,6 +80,7 @@ export { Checkbox, CheckboxList, CollapsibleSection, + CollapsibleDetailsSection, CommonModal, DataTable, DateRangeSelector, diff --git a/packages/libs/wdk-client/src/Components/Display/CollapsibleDetailsSection.tsx b/packages/libs/wdk-client/src/Components/Display/CollapsibleDetailsSection.tsx new file mode 100644 index 0000000000..2466774645 --- /dev/null +++ b/packages/libs/wdk-client/src/Components/Display/CollapsibleDetailsSection.tsx @@ -0,0 +1,24 @@ +import React, { ReactNode, useState } from 'react'; + +interface Props { + summary: ReactNode; + collapsibleDetails: ReactNode; + initialShowDetailsState?: boolean; +} + +export default function CollapsibleDetailsSection({ + summary, + collapsibleDetails, + initialShowDetailsState = false, +}: Props) { + const [showDetails, setShowDetails] = useState(initialShowDetailsState); + return ( +
setShowDetails(e.currentTarget.open)} + > + {summary} + {collapsibleDetails} +
+ ); +} diff --git a/packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx b/packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx deleted file mode 100644 index f1664e5969..0000000000 --- a/packages/libs/web-common/src/App/Studies/DIYStudyMenuCollapsibleSection.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { ReactNode, useState } from 'react'; -import { ArrowDown, ArrowRight } from '@veupathdb/coreui'; - -interface Props { - sectionLabel: string; - children: ReactNode; - sectionKey: string; -} - -const styles = { - fill: '#555', - height: '0.8em', - width: '0.8em', -}; - -export function DIYStudyMenuCollapsibleSection({ - sectionLabel, - children, -}: Props) { - const [isExpanded, setIsExpanded] = useState(true); - return ( -
-
setIsExpanded(!isExpanded)} - style={{ - display: 'flex', - alignItems: 'center', - gap: '0.5em', - cursor: 'pointer', - }} - title={`Click to ${isExpanded ? 'hide' : 'show'} this section`} - > - {isExpanded ? : } - {sectionLabel} -
- {isExpanded && ( -
- {children} -
- )} -
- ); -} diff --git a/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx b/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx index ffbfd8284c..229b57d718 100755 --- a/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx +++ b/packages/sites/clinepi-site/webapp/js/client/data/headerMenuItems.jsx @@ -5,7 +5,7 @@ import { StudyMenuSearch, } from '@veupathdb/web-common/lib/App/Studies'; import { DIYStudyMenuItem } from '@veupathdb/web-common/lib/App/Studies/DIYStudyMenuItem'; -import { DIYStudyMenuCollapsibleSection } from '@veupathdb/web-common/lib/App/Studies/DIYStudyMenuCollapsibleSection'; +import { CollapsibleDetailsSection } from '@veupathdb/wdk-client/lib/Components'; import { menuItemsFromSocials, iconMenuItemsFromSocials, @@ -107,15 +107,17 @@ export default function makeHeaderMenuItemsFactory( ? [ { text: ( - ( - - ))} + ( + + ) + )} /> ), }, @@ -126,9 +128,9 @@ export default function makeHeaderMenuItemsFactory( ? [ { text: ( - ( Date: Thu, 9 Nov 2023 15:37:02 -0500 Subject: [PATCH 10/14] Use correct mapType for supporting plots (#623) --- .../src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx | 2 +- .../lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx index 840c6b4c15..48b3888ce3 100644 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/BarMarkerMapType.tsx @@ -292,7 +292,7 @@ function ConfigPanelComponent(props: MapTypeConfigPanelProps) { activeVisualizationId={configuration.activeVisualizationId} plugins={plugins} geoConfigs={geoConfigs} - mapType="bubble" + mapType="barplot" setHideVizInputsAndControls={props.setHideVizInputsAndControls} /> ), diff --git a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx index e3a5cfb95a..c86cb7d385 100644 --- a/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx +++ b/packages/libs/eda/src/lib/map/analysis/mapTypes/plugins/DonutMarkerMapType.tsx @@ -264,7 +264,7 @@ function ConfigPanelComponent(props: MapTypeConfigPanelProps) { activeVisualizationId={configuration.activeVisualizationId} plugins={plugins} geoConfigs={geoConfigs} - mapType="bubble" + mapType="pie" setHideVizInputsAndControls={props.setHideVizInputsAndControls} /> ), From b2820ec73f63164761a3219d084d90c9211a5958 Mon Sep 17 00:00:00 2001 From: Dave Falke Date: Thu, 9 Nov 2023 15:46:15 -0500 Subject: [PATCH 11/14] User datasets: Add link to standalone map for ISA user datasets (#622) * Ignore intellij files * Add optional `hasMap` property * Remove unused request for wdk dataset record * Add link to standalone map in isa user datasets --- .gitignore | 3 ++ packages/libs/eda/src/lib/core/hooks/study.ts | 53 +++---------------- packages/libs/eda/src/lib/core/types/study.ts | 11 ++-- .../Components/Detail/IsaDatasetDetail.jsx | 32 ++++++++--- .../src/controllers/UserDatasetRouter.ts | 1 + packages/libs/web-common/src/routes.jsx | 10 ++-- .../js/client/routes/userDatasetRoutes.tsx | 23 ++++++-- 7 files changed, 71 insertions(+), 62 deletions(-) create mode 100644 packages/libs/web-common/src/controllers/UserDatasetRouter.ts diff --git a/.gitignore b/.gitignore index 529cfe1c88..a326802f62 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,9 @@ dist # VSCode config files .vscode +# IntelliJ config files +.idea + .editorconfig .pnp.* diff --git a/packages/libs/eda/src/lib/core/hooks/study.ts b/packages/libs/eda/src/lib/core/hooks/study.ts index 8cfd3cf0c6..8b26b73e76 100644 --- a/packages/libs/eda/src/lib/core/hooks/study.ts +++ b/packages/libs/eda/src/lib/core/hooks/study.ts @@ -1,4 +1,4 @@ -import { createContext } from 'react'; +import { createContext, useCallback } from 'react'; import { useWdkService, useWdkServiceWithRefresh, @@ -9,10 +9,7 @@ import { getScopes, getNodeId, } from '@veupathdb/wdk-client/lib/Utils/CategoryUtils'; -import { - AnswerJsonFormatConfig, - RecordInstance, -} from '@veupathdb/wdk-client/lib/Utils/WdkModel'; +import { AnswerJsonFormatConfig } from '@veupathdb/wdk-client/lib/Utils/WdkModel'; // Definitions import { @@ -26,7 +23,7 @@ import { import SubsettingClient from '../api/SubsettingClient'; // Hooks -import { useStudyRecord } from '..'; +import { usePromise, useStudyRecord } from '..'; import { useStudyAccessApi } from '@veupathdb/study-data-access/lib/study-access/studyAccessHooks'; import { getWdkStudyRecords } from '../utils/study-records'; import { useDeepValue } from './immutability'; @@ -189,44 +186,11 @@ export function isStubEntity(entity: StudyEntity) { export function useStudyMetadata(datasetId: string, client: SubsettingClient) { const permissionsResponse = usePermissions(); - return useWdkServiceWithRefresh( - async (wdkService) => { + return usePromise( + useCallback(async () => { if (permissionsResponse.loading) return; const { permissions } = permissionsResponse; - const recordClass = await wdkService.findRecordClass('dataset'); - const attributes = ['dataset_id', 'study_access'].filter( - (attribute) => attribute in recordClass.attributesMap - ); - const studyRecord = await wdkService - .getRecord( - STUDY_RECORD_CLASS_NAME, - [{ name: 'dataset_id', value: datasetId }], - { attributes } - ) - .catch((error) => { - console.warn( - 'Unable to load study dataset record. See error below. Using stub record.' - ); - console.error(error); - const attrs = attributes.reduce( - (attrs, name) => - Object.assign(attrs, { - [name]: '######', - }), - {} - ); - return { - displayName: 'Fake Study', - id: [{ name: 'dataset_id', value: datasetId }], - recordClassName: STUDY_RECORD_CLASS_NAME, - attributes: attrs, - tables: {}, - tableErrors: [], - } as RecordInstance; - }); - const studyId = - permissions.perDataset[studyRecord.attributes.dataset_id as string] - ?.studyId; + const studyId = permissions.perDataset[datasetId]?.studyId; if (studyId == null) throw new Error('Not an eda study'); try { return await client.getStudyMetadata(studyId); @@ -240,7 +204,6 @@ export function useStudyMetadata(datasetId: string, client: SubsettingClient) { } throw error; } - }, - [datasetId, client, permissionsResponse] - ); + }, [client, datasetId, permissionsResponse]) + ).value; } diff --git a/packages/libs/eda/src/lib/core/types/study.ts b/packages/libs/eda/src/lib/core/types/study.ts index 77cb85a28b..b7b4b76077 100644 --- a/packages/libs/eda/src/lib/core/types/study.ts +++ b/packages/libs/eda/src/lib/core/types/study.ts @@ -273,9 +273,14 @@ export const StudyEntity: t.Type = t.recursion('StudyEntity', () => // ------------- export type StudyOverview = t.TypeOf; -export const StudyOverview = t.type({ - id: t.string, -}); +export const StudyOverview = t.intersection([ + t.type({ + id: t.string, + }), + t.partial({ + hasMap: t.boolean, + }), +]); export type StudyMetadata = t.TypeOf; export const StudyMetadata = t.intersection([ diff --git a/packages/libs/user-datasets/src/lib/Components/Detail/IsaDatasetDetail.jsx b/packages/libs/user-datasets/src/lib/Components/Detail/IsaDatasetDetail.jsx index 62029a05ae..6206d36569 100644 --- a/packages/libs/user-datasets/src/lib/Components/Detail/IsaDatasetDetail.jsx +++ b/packages/libs/user-datasets/src/lib/Components/Detail/IsaDatasetDetail.jsx @@ -13,16 +13,32 @@ class IsaDatasetDetail extends UserDatasetDetail { config: { displayName }, userDataset: { isInstalled }, edaWorkspaceUrl, + edaMapUrl, } = this.props; - return !isInstalled || !edaWorkspaceUrl ? null : ( -
-

- - Explore in {displayName} - -

-
+ if (!isInstalled) return null; + + return ( + <> + {!edaWorkspaceUrl ? null : ( +
+

+ + Explore in {displayName} + +

+
+ )} + {!edaMapUrl ? null : ( +
+

+ + Explore in MapVEu + +

+
+ )} + ); } diff --git a/packages/libs/web-common/src/controllers/UserDatasetRouter.ts b/packages/libs/web-common/src/controllers/UserDatasetRouter.ts new file mode 100644 index 0000000000..161b669cc0 --- /dev/null +++ b/packages/libs/web-common/src/controllers/UserDatasetRouter.ts @@ -0,0 +1 @@ +export { UserDatasetRouter as default } from '@veupathdb/user-datasets/lib/Controllers/UserDatasetRouter'; diff --git a/packages/libs/web-common/src/routes.jsx b/packages/libs/web-common/src/routes.jsx index d4a5e308c3..31306e1743 100644 --- a/packages/libs/web-common/src/routes.jsx +++ b/packages/libs/web-common/src/routes.jsx @@ -30,6 +30,10 @@ export function makeEdaRoute(studyId) { return '/workspace/analyses' + (studyId ? `/${studyId}` : ''); } +export function makeMapRoute(studyId) { + return '/workspace/maps' + (studyId ? `/${studyId}` : ''); +} + const EdaWorkspace = React.lazy(() => import('@veupathdb/eda/lib/workspace')); /** @@ -70,13 +74,13 @@ export const wrapRoutes = (wdkRoutes) => [ }, { - path: '/workspace/maps', + path: makeMapRoute(), exact: true, component: EdaMapController, }, { - path: '/workspace/maps', + path: makeMapRoute(), exact: false, isFullscreen: true, rootClassNameModifier: 'MapVEu', @@ -88,7 +92,7 @@ export const wrapRoutes = (wdkRoutes) => [ exact: false, isFullscreen: true, rootClassNameModifier: 'MapVEu', - component: () => , + component: () => , }, { diff --git a/packages/sites/clinepi-site/webapp/js/client/routes/userDatasetRoutes.tsx b/packages/sites/clinepi-site/webapp/js/client/routes/userDatasetRoutes.tsx index e10ce2a035..f454777835 100644 --- a/packages/sites/clinepi-site/webapp/js/client/routes/userDatasetRoutes.tsx +++ b/packages/sites/clinepi-site/webapp/js/client/routes/userDatasetRoutes.tsx @@ -5,17 +5,24 @@ import { useLocation } from 'react-router-dom'; import { Loading } from '@veupathdb/wdk-client/lib/Components'; import { RouteEntry } from '@veupathdb/wdk-client/lib/Core/RouteEntry'; -import { makeEdaRoute } from '@veupathdb/web-common/lib/routes'; +import { makeEdaRoute, makeMapRoute } from '@veupathdb/web-common/lib/routes'; import { diyUserDatasetIdToWdkRecordId } from '@veupathdb/wdk-client/lib/Utils/diyDatasets'; import { UserDatasetDetailProps } from '@veupathdb/user-datasets/lib/Controllers/UserDatasetDetailController'; import { uploadTypeConfig } from '@veupathdb/user-datasets/lib/Utils/upload-config'; -import { communitySite, projectId } from '@veupathdb/web-common/lib/config'; +import { + communitySite, + edaServiceUrl, + projectId, +} from '@veupathdb/web-common/lib/config'; import ExternalContentController from '@veupathdb/web-common/lib/controllers/ExternalContentController'; +import { useConfiguredSubsettingClient } from '@veupathdb/eda/lib/core/hooks/client'; +import { useStudyMetadata } from '@veupathdb/eda/lib/core/hooks/study'; + const IsaDatasetDetail = React.lazy( () => import('@veupathdb/user-datasets/lib/Components/Detail/IsaDatasetDetail') @@ -53,11 +60,16 @@ export const userDatasetRoutes: RouteEntry[] = [ const wdkDatasetId = diyUserDatasetIdToWdkRecordId( props.userDataset.id ); - + const edaStudyMetadata = useEdaStudyMetadata(wdkDatasetId); return ( ); }, @@ -84,3 +96,8 @@ export const userDatasetRoutes: RouteEntry[] = [ }, }, ]; + +function useEdaStudyMetadata(wdkDatasetId: string) { + const subsettingClient = useConfiguredSubsettingClient(edaServiceUrl); + return useStudyMetadata(wdkDatasetId, subsettingClient); +} From 066f3364a9ec2bc7630454c8bf200a8cf5e53442 Mon Sep 17 00:00:00 2001 From: asizemore Date: Fri, 10 Nov 2023 11:14:55 -0500 Subject: [PATCH 12/14] replace isClickable with simpler cursor logic --- .../src/components/plotControls/PlotBubbleLegend.tsx | 8 -------- .../src/components/plotControls/PlotGradientLegend.tsx | 4 +--- .../components/src/components/plotControls/PlotLegend.tsx | 6 ++---- .../src/components/plotControls/PlotListLegend.tsx | 7 +++---- .../implementations/VolcanoPlotVisualization.tsx | 1 - 5 files changed, 6 insertions(+), 20 deletions(-) diff --git a/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx b/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx index 086a92eee1..24a7d0f6ed 100644 --- a/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx +++ b/packages/libs/components/src/components/plotControls/PlotBubbleLegend.tsx @@ -6,16 +6,8 @@ import _ from 'lodash'; export interface PlotLegendBubbleProps { legendMax: number; valueToDiameterMapper: ((value: number) => number) | undefined; - isClickable?: boolean; // controls the cursor type } -// legend ellipsis function for legend title and legend items (from custom legend work) -// const legendEllipsis = (label: string, ellipsisLength: number) => { -// return (label || '').length > ellipsisLength -// ? (label || '').substring(0, ellipsisLength) + '...' -// : label; -// }; - export default function PlotBubbleLegend({ legendMax, valueToDiameterMapper, diff --git a/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx b/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx index 6629edc9c1..6770feaca0 100755 --- a/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx +++ b/packages/libs/components/src/components/plotControls/PlotGradientLegend.tsx @@ -8,7 +8,6 @@ export interface PlotLegendGradientProps { valueToColorMapper: (a: number) => string; nTicks?: number; // MUST be odd! showMissingness?: boolean; - isClickable?: boolean; // controls the cursor type } // make gradient colorscale legend into a component so it can be more easily incorporated into DK's custom legend if we need @@ -18,7 +17,6 @@ export default function PlotGradientLegend({ valueToColorMapper, nTicks = 5, showMissingness, - isClickable = true, }: PlotLegendGradientProps) { // Declare constants const gradientBoxHeight = 150; @@ -114,7 +112,7 @@ export default function PlotGradientLegend({