diff --git a/src/vis/EagerVis.tsx b/src/vis/EagerVis.tsx index c79aa64e8..9518be019 100644 --- a/src/vis/EagerVis.tsx +++ b/src/vis/EagerVis.tsx @@ -21,42 +21,98 @@ import { import { VisSidebar } from './VisSidebar'; import { VisSidebarOpenButton } from './VisSidebarOpenButton'; -import { BarVis } from './barGood/BarVis'; -import { BarVisSidebar } from './barGood/BarVisSidebar'; -import { EBarDirection, EBarDisplayType, EBarGroupingType, IBarConfig, barMergeDefaultConfig } from './barGood/utils'; -import { ICorrelationConfig, correlationMergeDefaultConfig } from './correlation'; +import { BarVis } from './bar/BarVis'; +import { BarVisSidebar } from './bar/BarVisSidebar'; +import { EBarDirection, EBarDisplayType, EBarGroupingType, IBarConfig } from './bar/interfaces'; +import { barMergeDefaultConfig } from './bar/utils'; +import { correlationMergeDefaultConfig } from './correlation'; import { CorrelationVis } from './correlation/CorrelationVis'; import { CorrelationVisSidebar } from './correlation/CorrelationVisSidebar'; +import { ICorrelationConfig } from './correlation/interfaces'; import { HeatmapVis } from './heatmap/HeatmapVis'; import { HeatmapVisSidebar } from './heatmap/HeatmapVisSidebar'; -import { IHeatmapConfig, heatmapMergeDefaultConfig } from './heatmap/utils'; +import { IHeatmapConfig } from './heatmap/interfaces'; +import { heatmapMergeDefaultConfig } from './heatmap/utils'; import { HexbinVis } from './hexbin/HexbinVis'; import { HexbinVisSidebar } from './hexbin/HexbinVisSidebar'; -import { IHexbinConfig, hexinbMergeDefaultConfig } from './hexbin/utils'; +import { IHexbinConfig } from './hexbin/interfaces'; +import { hexinbMergeDefaultConfig } from './hexbin/utils'; import { RaincloudVis } from './raincloud/RaincloudVis'; import { RaincloudVisSidebar } from './raincloud/RaincloudVisSidebar'; -import { IRaincloudConfig, raincloudMergeDefaultConfig } from './raincloud/utils'; +import { IRaincloudConfig } from './raincloud/interfaces'; +import { raincloudMergeDefaultConfig } from './raincloud/utils'; import { SankeyVis } from './sankey/SankeyVis'; import { SankeyVisSidebar } from './sankey/SankeyVisSidebar'; -import { ISankeyConfig, sankeyMergeDefaultConfig } from './sankey/utils'; -import { IScatterConfig, scatterMergeDefaultConfig } from './scatter'; +import { ISankeyConfig } from './sankey/interfaces'; +import { sankeyMergeDefaultConfig } from './sankey/utils'; +import { scatterMergeDefaultConfig } from './scatter'; import { ScatterVis } from './scatter/ScatterVis'; import { ScatterVisSidebar } from './scatter/ScatterVisSidebar'; -import { IViolinConfig, ViolinVis, violinMergeDefaultConfig } from './violin'; +import { IScatterConfig } from './scatter/interfaces'; +import { ViolinVis, violinMergeDefaultConfig } from './violin'; import { ViolinVisSidebar } from './violin/ViolinVisSidebar'; +import { IViolinConfig } from './violin/interfaces'; const DEFAULT_SHAPES = ['circle', 'square', 'triangle-up', 'star']; function registerAllVis() { return [ - createVis(ESupportedPlotlyVis.SCATTER, ScatterVis, ScatterVisSidebar, scatterMergeDefaultConfig), - createVis(ESupportedPlotlyVis.BAR, BarVis, BarVisSidebar, barMergeDefaultConfig), - createVis(ESupportedPlotlyVis.HEXBIN, HexbinVis, HexbinVisSidebar, hexinbMergeDefaultConfig), - createVis(ESupportedPlotlyVis.SANKEY, SankeyVis, SankeyVisSidebar, sankeyMergeDefaultConfig), - createVis(ESupportedPlotlyVis.HEATMAP, HeatmapVis, HeatmapVisSidebar, heatmapMergeDefaultConfig), - createVis(ESupportedPlotlyVis.VIOLIN, ViolinVis, ViolinVisSidebar, violinMergeDefaultConfig), - createVis(ESupportedPlotlyVis.RAINCLOUD, RaincloudVis, RaincloudVisSidebar, raincloudMergeDefaultConfig), - createVis(ESupportedPlotlyVis.CORRELATION, CorrelationVis, CorrelationVisSidebar, correlationMergeDefaultConfig), + createVis({ + type: ESupportedPlotlyVis.SCATTER, + renderer: ScatterVis, + sidebarRenderer: ScatterVisSidebar, + mergeConfig: scatterMergeDefaultConfig, + description: 'Visualizes two variables as individual data points in two-dimensional space', + }), + createVis({ + type: ESupportedPlotlyVis.BAR, + renderer: BarVis, + sidebarRenderer: BarVisSidebar, + mergeConfig: barMergeDefaultConfig, + description: 'Visualizes categorical data with rectangular bars', + }), + createVis({ + type: ESupportedPlotlyVis.HEXBIN, + renderer: HexbinVis, + sidebarRenderer: HexbinVisSidebar, + mergeConfig: hexinbMergeDefaultConfig, + description: 'Visualizes 2D data points within hexagons', + }), + createVis({ + type: ESupportedPlotlyVis.SANKEY, + renderer: SankeyVis, + sidebarRenderer: SankeyVisSidebar, + mergeConfig: sankeyMergeDefaultConfig, + description: 'Visualizes the flow of data between different categories', + }), + createVis({ + type: ESupportedPlotlyVis.HEATMAP, + renderer: HeatmapVis, + sidebarRenderer: HeatmapVisSidebar, + mergeConfig: heatmapMergeDefaultConfig, + description: 'Visualizes matrix data using color gradients', + }), + createVis({ + type: ESupportedPlotlyVis.VIOLIN, + renderer: ViolinVis, + sidebarRenderer: ViolinVisSidebar, + mergeConfig: violinMergeDefaultConfig, + description: 'Visualizes numerical data distribution by combining a box plot and a kernel density plot', + }), + createVis({ + type: ESupportedPlotlyVis.RAINCLOUD, + renderer: RaincloudVis, + sidebarRenderer: RaincloudVisSidebar, + mergeConfig: raincloudMergeDefaultConfig, + description: 'Visualizes a combination of boxplot, violin plot, and jitter plot', + }), + createVis({ + type: ESupportedPlotlyVis.CORRELATION, + renderer: CorrelationVis, + sidebarRenderer: CorrelationVisSidebar, + mergeConfig: correlationMergeDefaultConfig, + description: 'Visualizes statistical relationships between pairs of numerical variables', + }), ]; } @@ -277,7 +333,7 @@ export function EagerVis({ }, }} > - {enableSidebar ? setShowSidebar(!showSidebar)} isOpen={showSidebar} /> : null} + {enableSidebar && !showSidebar ? setShowSidebar(!showSidebar)} /> : null} {Renderer ? ( @@ -307,7 +363,7 @@ export function EagerVis({ ) : null} {showSidebar ? ( - + setShowSidebar(false)}> ) : null} diff --git a/src/vis/Provider.tsx b/src/vis/Provider.tsx index 8e6fb9225..59dc021df 100644 --- a/src/vis/Provider.tsx +++ b/src/vis/Provider.tsx @@ -9,27 +9,37 @@ export interface GeneralVis { renderer: (props: ICommonVisProps) => React.JSX.Element; sidebarRenderer: (props: ICommonVisSideBarProps) => React.JSX.Element; mergeConfig: (columns: VisColumn[], config: T) => T; + description: string; } /** * Generic utility function for creating a vis object. */ -export function createVis( - type: string, +export function createVis({ + type, + renderer, + sidebarRenderer, + mergeConfig, + description = '', +}: { + type: string; /** The main vis renderer. Required in all visualizations. */ - renderer: (props: ICommonVisProps) => React.JSX.Element, + renderer: (props: ICommonVisProps) => React.JSX.Element; /** The sidebar renderer. Required in all visualizations. */ - sidebarRenderer: (props: ICommonVisSideBarProps) => React.JSX.Element, + sidebarRenderer: (props: ICommonVisSideBarProps) => React.JSX.Element; + + mergeConfig: (columns: VisColumn[], config: T) => T; - mergeConfig: (columns: VisColumn[], config: T) => T, -): GeneralVis { + description: string; +}): GeneralVis { return { type, renderer, sidebarRenderer, mergeConfig, + description, }; } diff --git a/src/vis/VisFilterAndSelectSettings.tsx b/src/vis/VisFilterAndSelectSettings.tsx deleted file mode 100644 index d550636c6..000000000 --- a/src/vis/VisFilterAndSelectSettings.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { ActionIcon, Divider, Group, Tooltip } from '@mantine/core'; -import * as React from 'react'; -import { FilterClear } from '../assets/icons/FilterClear'; -import { FilterEmpty } from '../assets/icons/FilterEmpty'; -import { FilterFilled } from '../assets/icons/FilterFilled'; -import { EFilterOptions, EScatterSelectSettings } from './interfaces'; -import { BrushOptionButtons } from './sidebar/BrushOptionButtons'; - -export function VisFilterAndSelectSettings({ - onBrushOptionsCallback, - onFilterCallback, - dragMode, - showSelect, - selectOptions = [EScatterSelectSettings.RECTANGLE, EScatterSelectSettings.LASSO, EScatterSelectSettings.PAN, EScatterSelectSettings.ZOOM], -}: { - onBrushOptionsCallback: (dragMode: EScatterSelectSettings) => void; - onFilterCallback: (opt: EFilterOptions) => void; - dragMode: EScatterSelectSettings; - showSelect: boolean; - selectOptions?: EScatterSelectSettings[]; -}) { - return ( - - - - onFilterCallback(EFilterOptions.OUT)}> - - - - - - - onFilterCallback(EFilterOptions.IN)}> - - - - - - - onFilterCallback(EFilterOptions.CLEAR)}> - - - - - {showSelect ? : null} - - ); -} diff --git a/src/vis/VisSidebarOpenButton.tsx b/src/vis/VisSidebarOpenButton.tsx index 8933e2430..634eef632 100644 --- a/src/vis/VisSidebarOpenButton.tsx +++ b/src/vis/VisSidebarOpenButton.tsx @@ -1,15 +1,14 @@ -import * as React from 'react'; -import { ActionIcon, Center, Container, Group, Stack, Tooltip } from '@mantine/core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faGear } from '@fortawesome/free-solid-svg-icons/faGear'; -import { faClose } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ActionIcon, Tooltip } from '@mantine/core'; +import * as React from 'react'; import { i18n } from '../i18n'; export function VisSidebarOpenButton({ isOpen, onClick }: { isOpen?: boolean; onClick: () => void }) { return ( - - + + ); diff --git a/src/vis/VisSidebarWrapper.tsx b/src/vis/VisSidebarWrapper.tsx index 2efe3ec60..e3fa3cd0e 100644 --- a/src/vis/VisSidebarWrapper.tsx +++ b/src/vis/VisSidebarWrapper.tsx @@ -1,16 +1,33 @@ -import { Box, Group, ScrollArea } from '@mantine/core'; +import { faClose } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ActionIcon, Box, Divider, Group, ScrollArea, Stack, Text, Tooltip } from '@mantine/core'; import * as React from 'react'; import { ReactNode } from 'react'; +import { i18n } from '../i18n'; +import { VisTypeSelect } from './sidebar/VisTypeSelect'; +import { HelpHoverCard } from '../components/HelpHoverCard'; const sidebarSize = 200; -export function VisSidebarWrapper({ children }: { children: ReactNode }) { +export function VisSidebarWrapper({ children, config, setConfig, onClick }: { children: ReactNode; config; setConfig; onClick }) { return ( - {children} + + + Settings + + + + + + + setConfig({ ...config, type })} currentSelected={config.type} /> + + {children} + diff --git a/src/vis/barGood/BarChart.tsx b/src/vis/bar/BarChart.tsx similarity index 97% rename from src/vis/barGood/BarChart.tsx rename to src/vis/bar/BarChart.tsx index 174f157c0..40ff0ecff 100644 --- a/src/vis/barGood/BarChart.tsx +++ b/src/vis/bar/BarChart.tsx @@ -6,7 +6,8 @@ import { EColumnTypes, VisColumn } from '../interfaces'; import { SingleBarChart } from './SingleBarChart'; import { Legend } from './barComponents/Legend'; import { useGetGroupedBarScales } from './hooks/useGetGroupedBarScales'; -import { IBarConfig, SortTypes, getBarData } from './utils'; +import { getBarData } from './utils'; +import { IBarConfig, SortTypes } from './interfaces'; export function BarChart({ config, diff --git a/src/vis/bar/BarDirectionButtons.tsx b/src/vis/bar/BarDirectionButtons.tsx new file mode 100644 index 000000000..371597b19 --- /dev/null +++ b/src/vis/bar/BarDirectionButtons.tsx @@ -0,0 +1,25 @@ +import { Input, SegmentedControl } from '@mantine/core'; +import * as React from 'react'; +import { EBarDirection } from './interfaces'; + +interface BarDirectionProps { + callback: (s: EBarDirection) => void; + currentSelected: EBarDirection; +} + +export function BarDirectionButtons({ callback, currentSelected }: BarDirectionProps) { + return ( + + + + ); +} diff --git a/src/vis/sidebar/BarDisplayTypeButtons.tsx b/src/vis/bar/BarDisplayTypeButtons.tsx similarity index 94% rename from src/vis/sidebar/BarDisplayTypeButtons.tsx rename to src/vis/bar/BarDisplayTypeButtons.tsx index feda3b14d..54c5a6b2d 100644 --- a/src/vis/sidebar/BarDisplayTypeButtons.tsx +++ b/src/vis/bar/BarDisplayTypeButtons.tsx @@ -1,6 +1,6 @@ import { Container, SegmentedControl, Stack } from '@mantine/core'; import * as React from 'react'; -import { EBarDisplayType } from '../barGood/utils'; +import { EBarDisplayType } from './interfaces'; interface BarDisplayProps { callback: (s: EBarDisplayType) => void; diff --git a/src/vis/sidebar/BarGroupTypeButtons.tsx b/src/vis/bar/BarGroupTypeButtons.tsx similarity index 93% rename from src/vis/sidebar/BarGroupTypeButtons.tsx rename to src/vis/bar/BarGroupTypeButtons.tsx index f439b5a4f..a0c300f17 100644 --- a/src/vis/sidebar/BarGroupTypeButtons.tsx +++ b/src/vis/bar/BarGroupTypeButtons.tsx @@ -1,6 +1,6 @@ import { Container, SegmentedControl, Stack } from '@mantine/core'; import * as React from 'react'; -import { EBarGroupingType } from '../barGood/utils'; +import { EBarGroupingType } from './interfaces'; interface BarGroupTypeProps { callback: (s: EBarGroupingType) => void; diff --git a/src/vis/barGood/BarVis.tsx b/src/vis/bar/BarVis.tsx similarity index 80% rename from src/vis/barGood/BarVis.tsx rename to src/vis/bar/BarVis.tsx index 0f6ff6dfa..31c9b42fb 100644 --- a/src/vis/barGood/BarVis.tsx +++ b/src/vis/bar/BarVis.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { InvalidCols } from '../general/InvalidCols'; import { ICommonVisProps } from '../interfaces'; import { BarChart } from './BarChart'; -import { IBarConfig } from './utils'; +import { IBarConfig } from './interfaces'; export function BarVis({ config, columns, selectionCallback = () => null, selectedMap = {}, selectedList = [] }: ICommonVisProps) { return ( @@ -12,7 +12,7 @@ export function BarVis({ config, columns, selectionCallback = () => null, select {config.catColumnSelected ? ( ) : ( - + )} ); diff --git a/src/vis/bar/BarVisSidebar.tsx b/src/vis/bar/BarVisSidebar.tsx new file mode 100644 index 000000000..755fb983b --- /dev/null +++ b/src/vis/bar/BarVisSidebar.tsx @@ -0,0 +1,120 @@ +import merge from 'lodash/merge'; +import * as React from 'react'; +import { useMemo } from 'react'; +import { ColumnInfo, EAggregateTypes, EColumnTypes, ICommonVisSideBarProps } from '../interfaces'; +import { AggregateTypeSelect } from '../sidebar/AggregateTypeSelect'; +import { FilterButtons } from '../sidebar/FilterButtons'; +import { SingleColumnSelect } from '../sidebar/SingleColumnSelect'; +import { BarDirectionButtons } from './BarDirectionButtons'; +import { GroupSelect } from './GroupSelect'; +import { EBarDirection, EBarDisplayType, EBarGroupingType, IBarConfig } from './interfaces'; + +const defaultConfig = { + group: { + enable: true, + customComponent: null, + }, + multiples: { + enable: true, + customComponent: null, + }, + direction: { + enable: true, + customComponent: null, + }, + filter: { + enable: true, + customComponent: null, + }, + groupType: { + enable: true, + customComponent: null, + }, + display: { + enable: true, + customComponent: null, + }, +}; + +export function BarVisSidebar({ + config, + optionsConfig, + columns, + setConfig, + className = '', + filterCallback, + style: { width = '20em', ...style } = {}, +}: ICommonVisSideBarProps) { + const mergedOptionsConfig = useMemo(() => { + return merge({}, defaultConfig, optionsConfig); + }, [optionsConfig]); + + return ( + <> + + setConfig({ + ...config, + catColumnSelected, + multiples: config.multiples && config.multiples.id === catColumnSelected?.id ? null : config.multiples, + group: config.group && config.group.id === catColumnSelected?.id ? null : config.group, + }) + } + columns={columns} + currentSelected={config.catColumnSelected} + type={[EColumnTypes.CATEGORICAL]} + label="Categorical column" + /> + { + if (config.aggregateColumn === null) { + setConfig({ + ...config, + aggregateType, + aggregateColumn: columns.find((col) => col.type === EColumnTypes.NUMERICAL).info, + display: aggregateType === EAggregateTypes.COUNT ? config.display : EBarDisplayType.ABSOLUTE, + }); + } else { + setConfig({ ...config, aggregateType, display: aggregateType === EAggregateTypes.COUNT ? config.display : EBarDisplayType.ABSOLUTE }); + } + }} + aggregateColumnSelectCallback={(aggregateColumn: ColumnInfo) => setConfig({ ...config, aggregateColumn })} + columns={columns} + currentSelected={config.aggregateType} + aggregateColumn={config.aggregateColumn} + /> + + {mergedOptionsConfig.group.enable + ? mergedOptionsConfig.group.customComponent || ( + setConfig({ ...config, group })} + groupTypeSelectCallback={(groupType: EBarGroupingType) => setConfig({ ...config, groupType })} + groupDisplaySelectCallback={(display: EBarDisplayType) => setConfig({ ...config, display })} + displayType={config.display} + groupType={config.groupType} + columns={columns.filter((c) => config.catColumnSelected && c.info.id !== config.catColumnSelected.id)} + currentSelected={config.group} + /> + ) + : null} + {mergedOptionsConfig.multiples.enable + ? mergedOptionsConfig.multiples.customComponent || ( + setConfig({ ...config, multiples })} + columns={columns.filter((c) => config.catColumnSelected && c.info.id !== config.catColumnSelected.id)} + currentSelected={config.multiples} + label="Multiples" + type={[EColumnTypes.CATEGORICAL]} + /> + ) + : null} + {mergedOptionsConfig.direction.enable + ? mergedOptionsConfig.direction.customComponent || ( + setConfig({ ...config, direction })} currentSelected={config.direction} /> + ) + : null} + {mergedOptionsConfig.filter.enable ? mergedOptionsConfig.filter.customComponent || : null} + + ); +} diff --git a/src/vis/sidebar/GroupSelect.tsx b/src/vis/bar/GroupSelect.tsx similarity index 94% rename from src/vis/sidebar/GroupSelect.tsx rename to src/vis/bar/GroupSelect.tsx index 087f48949..078a123e6 100644 --- a/src/vis/sidebar/GroupSelect.tsx +++ b/src/vis/bar/GroupSelect.tsx @@ -1,10 +1,10 @@ import { Select, Stack } from '@mantine/core'; import * as React from 'react'; -import { EBarDisplayType, EBarGroupingType } from '../barGood/utils'; +import { EBarDisplayType, EBarGroupingType } from './interfaces'; import { ColumnInfo, EAggregateTypes, EColumnTypes, VisColumn } from '../interfaces'; import { BarDisplayButtons } from './BarDisplayTypeButtons'; import { BarGroupTypeButtons } from './BarGroupTypeButtons'; -import { SelectDropdownItem } from './utils'; +import { SelectDropdownItem } from '../sidebar/utils'; interface GroupSelectProps { groupColumnSelectCallback: (c: ColumnInfo) => void; diff --git a/src/vis/barGood/SingleBarChart.tsx b/src/vis/bar/SingleBarChart.tsx similarity index 99% rename from src/vis/barGood/SingleBarChart.tsx rename to src/vis/bar/SingleBarChart.tsx index 49e329e4e..c92cde259 100644 --- a/src/vis/barGood/SingleBarChart.tsx +++ b/src/vis/bar/SingleBarChart.tsx @@ -8,7 +8,8 @@ import { GroupedBars } from './barTypes/GroupedBars'; import { SimpleBars } from './barTypes/SimpleBars'; import { StackedBars } from './barTypes/StackedBars'; import { useGetGroupedBarScales } from './hooks/useGetGroupedBarScales'; -import { EBarDirection, EBarDisplayType, EBarGroupingType, IBarConfig, SortTypes, getBarData } from './utils'; +import { getBarData } from './utils'; +import { EBarDirection, EBarDisplayType, EBarGroupingType, IBarConfig, SortTypes } from './interfaces'; const margin = { top: 30, diff --git a/src/vis/barGood/barComponents/Legend.tsx b/src/vis/bar/barComponents/Legend.tsx similarity index 100% rename from src/vis/barGood/barComponents/Legend.tsx rename to src/vis/bar/barComponents/Legend.tsx diff --git a/src/vis/barGood/barComponents/SingleBar.tsx b/src/vis/bar/barComponents/SingleBar.tsx similarity index 100% rename from src/vis/barGood/barComponents/SingleBar.tsx rename to src/vis/bar/barComponents/SingleBar.tsx diff --git a/src/vis/barGood/barComponents/XAxis.tsx b/src/vis/bar/barComponents/XAxis.tsx similarity index 98% rename from src/vis/barGood/barComponents/XAxis.tsx rename to src/vis/bar/barComponents/XAxis.tsx index 935b5b0dc..c3ff07e53 100644 --- a/src/vis/barGood/barComponents/XAxis.tsx +++ b/src/vis/bar/barComponents/XAxis.tsx @@ -4,7 +4,7 @@ import * as d3 from 'd3v7'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { Center, Group, Text, Tooltip } from '@mantine/core'; -import { SortTypes } from '../utils'; +import { SortTypes } from '../interfaces'; // code taken from https://wattenberger.com/blog/react-and-d3 export function XAxis({ diff --git a/src/vis/barGood/barComponents/YAxis.tsx b/src/vis/bar/barComponents/YAxis.tsx similarity index 98% rename from src/vis/barGood/barComponents/YAxis.tsx rename to src/vis/bar/barComponents/YAxis.tsx index 870cac6b8..56ddb1576 100644 --- a/src/vis/barGood/barComponents/YAxis.tsx +++ b/src/vis/bar/barComponents/YAxis.tsx @@ -4,7 +4,7 @@ import { Center, Group, Text } from '@mantine/core'; import * as d3 from 'd3v7'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons'; -import { SortTypes } from '../utils'; +import { SortTypes } from '../interfaces'; type IsEqual = Type1 | Type2 extends Type1 & Type2 ? true : never; diff --git a/src/vis/barGood/barTypes/GroupedBars.tsx b/src/vis/bar/barTypes/GroupedBars.tsx similarity index 100% rename from src/vis/barGood/barTypes/GroupedBars.tsx rename to src/vis/bar/barTypes/GroupedBars.tsx diff --git a/src/vis/barGood/barTypes/SimpleBars.tsx b/src/vis/bar/barTypes/SimpleBars.tsx similarity index 100% rename from src/vis/barGood/barTypes/SimpleBars.tsx rename to src/vis/bar/barTypes/SimpleBars.tsx diff --git a/src/vis/barGood/barTypes/StackedBars.tsx b/src/vis/bar/barTypes/StackedBars.tsx similarity index 100% rename from src/vis/barGood/barTypes/StackedBars.tsx rename to src/vis/bar/barTypes/StackedBars.tsx diff --git a/src/vis/barGood/hooks/useGetBarScales.ts b/src/vis/bar/hooks/useGetBarScales.ts similarity index 89% rename from src/vis/barGood/hooks/useGetBarScales.ts rename to src/vis/bar/hooks/useGetBarScales.ts index e60908d4e..f7a7dd977 100644 --- a/src/vis/barGood/hooks/useGetBarScales.ts +++ b/src/vis/bar/hooks/useGetBarScales.ts @@ -3,7 +3,8 @@ import ColumnTable from 'arquero/dist/types/table/column-table'; import { desc, op, table, addFunction } from 'arquero'; import { useMemo } from 'react'; import * as d3 from 'd3v7'; -import { SortTypes, getBarData, sortTableBySortType } from '../utils'; +import { getBarData, sortTableBySortType } from '../utils'; +import { SortTypes } from '../interfaces'; import { EAggregateTypes } from '../../interfaces'; export function useGetBarScales( @@ -24,7 +25,7 @@ export function useGetBarScales( group: allColumns?.groupColVals?.resolvedValues.map((val) => val.val), multiples: allColumns?.multiplesColVals?.resolvedValues.map((val) => val.val) || [], selected: allColumns.catColVals.resolvedValues.map((val) => (selectedMap[val.id] ? 1 : 0)), - aggregateValues: allColumns?.aggregateColVals?.resolvedValues.map((val) => val.val) || [], + aggregateVal: allColumns?.aggregateColVals?.resolvedValues.map((val) => val.val) || [], id: allColumns.catColVals.resolvedValues.map((val) => val.id), }); } @@ -37,13 +38,13 @@ export function useGetBarScales( case EAggregateTypes.COUNT: return (d) => op.count(); case EAggregateTypes.AVG: - return (d) => op.average(d.aggregateValues); + return (d) => op.average(d.aggregateVal); case EAggregateTypes.MIN: - return (d) => op.min(d.aggregateValues); + return (d) => op.min(d.aggregateVal); case EAggregateTypes.MED: - return (d) => op.median(d.aggregateValues); + return (d) => op.median(d.aggregateVal); case EAggregateTypes.MAX: - return (d) => op.max(d.aggregateValues); + return (d) => op.max(d.aggregateVal); default: return (d) => op.count(); } diff --git a/src/vis/barGood/hooks/useGetGroupedBarScales.ts b/src/vis/bar/hooks/useGetGroupedBarScales.ts similarity index 82% rename from src/vis/barGood/hooks/useGetGroupedBarScales.ts rename to src/vis/bar/hooks/useGetGroupedBarScales.ts index c92b94c53..7117cd7a4 100644 --- a/src/vis/barGood/hooks/useGetGroupedBarScales.ts +++ b/src/vis/bar/hooks/useGetGroupedBarScales.ts @@ -4,7 +4,8 @@ import ColumnTable from 'arquero/dist/types/table/column-table'; import * as d3 from 'd3v7'; import { useMemo } from 'react'; import { EAggregateTypes, EColumnTypes } from '../../interfaces'; -import { EBarGroupingType, SortTypes, binByAggregateType, getBarData, groupByAggregateType, rollupByAggregateType } from '../utils'; +import { binByAggregateType, getBarData, groupByAggregateType, rollupByAggregateType } from '../utils'; +import { EBarGroupingType, SortTypes } from '../interfaces'; import { useGetBarScales } from './useGetBarScales'; export function useGetGroupedBarScales( @@ -47,7 +48,6 @@ export function useGetGroupedBarScales( if (categoryFilter && allColumns.multiplesColVals) { filteredTable = baseTable.filter(escape((d) => d.multiples === categoryFilter)); } - return allColumns.groupColVals.type === EColumnTypes.NUMERICAL ? binByAggregateType(filteredTable, aggregateType) : groupByAggregateType(filteredTable, aggregateType); @@ -59,16 +59,27 @@ export function useGetGroupedBarScales( const groupColorScale = useMemo(() => { if (!groupedTable) return null; - const newGroup = groupedTable.ungroup().groupby('group').count(); + const colorScale = ['#337ab7', '#ec6836', '#75c4c2', '#e9d36c', '#24b466', '#e891ae', '#db933c', '#b08aa6', '#8a6044', '#7b7b7b']; + let i = -1; - return d3 - .scaleOrdinal() - .domain(newGroup.array('group').sort()) - .range( - allColumns.groupColVals.type === EColumnTypes.NUMERICAL - ? d3.schemeBlues[newGroup.array('group').length > 3 ? newGroup.array('group').length : 3] - : ['#337ab7', '#ec6836', '#75c4c2', '#e9d36c', '#24b466', '#e891ae', '#db933c', '#b08aa6', '#8a6044', '#7b7b7b'], - ); + const newGroup = groupedTable.ungroup().groupby('group').count(); + const categoricalColors = allColumns.groupColVals.color + ? newGroup + .array('group') + .sort() + .map((value) => { + i += 1; + return allColumns.groupColVals.color[value] || colorScale[i % colorScale.length]; + }) + : colorScale; + + const domain = newGroup.array('group').sort(); + const range = + allColumns.groupColVals.type === EColumnTypes.NUMERICAL + ? d3.schemeBlues[newGroup.array('group').length > 3 ? newGroup.array('group').length : 3] + : categoricalColors; + + return d3.scaleOrdinal().domain(domain).range(range); }, [groupedTable, allColumns]); const groupScale = useMemo(() => { diff --git a/src/vis/bar/interfaces.ts b/src/vis/bar/interfaces.ts new file mode 100644 index 000000000..6f2a1f42b --- /dev/null +++ b/src/vis/bar/interfaces.ts @@ -0,0 +1,53 @@ +import { BaseVisConfig, ColumnInfo, EAggregateTypes, ESupportedPlotlyVis } from '../interfaces'; + +export enum SortTypes { + NONE = 'NONE', + CAT_ASC = 'CAT_ASC', + CAT_DESC = 'CAT_DESC', + COUNT_ASC = 'COUNT_ASC', + COUNT_DESC = 'COUNT_DESC', +} + +export enum EBarGroupingType { + STACK = 'Stacked', + GROUP = 'Grouped', +} + +export enum EBarDisplayType { + ABSOLUTE = 'Absolute', + NORMALIZED = 'Normalized', +} +export enum EBarDirection { + VERTICAL = 'Vertical', + HORIZONTAL = 'Horizontal', +} + +export interface IBarConfig extends BaseVisConfig { + type: ESupportedPlotlyVis.BAR; + multiples: ColumnInfo | null; + group: ColumnInfo | null; + direction: EBarDirection; + display: EBarDisplayType; + groupType: EBarGroupingType; + numColumnsSelected: ColumnInfo[]; + catColumnSelected: ColumnInfo; + aggregateType: EAggregateTypes; + aggregateColumn: ColumnInfo | null; +} + +export const defaultConfig: IBarConfig = { + type: ESupportedPlotlyVis.BAR, + numColumnsSelected: [], + catColumnSelected: null, + group: null, + groupType: EBarGroupingType.STACK, + multiples: null, + display: EBarDisplayType.ABSOLUTE, + direction: EBarDirection.HORIZONTAL, + aggregateColumn: null, + aggregateType: EAggregateTypes.COUNT, +}; + +export function isBarConfig(s: BaseVisConfig): s is IBarConfig { + return s.type === ESupportedPlotlyVis.BAR; +} diff --git a/src/vis/barGood/utils.ts b/src/vis/bar/utils.ts similarity index 77% rename from src/vis/barGood/utils.ts rename to src/vis/bar/utils.ts index 4f11ac378..1797eafd6 100644 --- a/src/vis/barGood/utils.ts +++ b/src/vis/bar/utils.ts @@ -2,51 +2,8 @@ import { bin, desc, op } from 'arquero'; import ColumnTable from 'arquero/dist/types/table/column-table'; import merge from 'lodash/merge'; import { resolveSingleColumn } from '../general/layoutUtils'; -import { - BaseVisConfig, - ColumnInfo, - EAggregateTypes, - EColumnTypes, - ESupportedPlotlyVis, - VisCategoricalValue, - VisColumn, - VisNumericalValue, -} from '../interfaces'; - -export enum SortTypes { - NONE = 'NONE', - CAT_ASC = 'CAT_ASC', - CAT_DESC = 'CAT_DESC', - COUNT_ASC = 'COUNT_ASC', - COUNT_DESC = 'COUNT_DESC', -} - -export enum EBarGroupingType { - STACK = 'Stacked', - GROUP = 'Grouped', -} - -export enum EBarDisplayType { - ABSOLUTE = 'Absolute', - NORMALIZED = 'Normalized', -} -export enum EBarDirection { - VERTICAL = 'Vertical', - HORIZONTAL = 'Horizontal', -} - -const defaultConfig: IBarConfig = { - type: ESupportedPlotlyVis.BAR, - numColumnsSelected: [], - catColumnSelected: null, - group: null, - groupType: EBarGroupingType.STACK, - multiples: null, - display: EBarDisplayType.ABSOLUTE, - direction: EBarDirection.HORIZONTAL, - aggregateColumn: null, - aggregateType: EAggregateTypes.COUNT, -}; +import { ColumnInfo, EAggregateTypes, EColumnTypes, VisCategoricalValue, VisColumn, VisNumericalValue } from '../interfaces'; +import { IBarConfig, defaultConfig, SortTypes } from './interfaces'; export function barMergeDefaultConfig(columns: VisColumn[], config: IBarConfig): IBarConfig { const merged = merge({}, defaultConfig, config); @@ -65,19 +22,6 @@ export function barMergeDefaultConfig(columns: VisColumn[], config: IBarConfig): return merged; } -export interface IBarConfig extends BaseVisConfig { - type: ESupportedPlotlyVis.BAR; - multiples: ColumnInfo | null; - group: ColumnInfo | null; - direction: EBarDirection; - display: EBarDisplayType; - groupType: EBarGroupingType; - numColumnsSelected: ColumnInfo[]; - catColumnSelected: ColumnInfo; - aggregateType: EAggregateTypes; - aggregateColumn: ColumnInfo | null; -} - // Helper function for the bar chart which sorts the data depending on the sort type. export function sortTableBySortType(tempTable: ColumnTable, sortType: SortTypes) { switch (sortType) { @@ -108,7 +52,7 @@ export function binByAggregateType(tempTable: ColumnTable, aggregateType: EAggre return tempTable .groupby('category', { group: bin('group', { maxbins: 9 }), group_max: bin('group', { maxbins: 9, offset: 1 }) }) .rollup({ - aggregateVal: (d) => op.average(d.aggregateValues), + aggregateVal: (d) => op.average(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id), @@ -119,7 +63,7 @@ export function binByAggregateType(tempTable: ColumnTable, aggregateType: EAggre case EAggregateTypes.MIN: return tempTable .groupby('category', { group: bin('group', { maxbins: 9 }), group_max: bin('group', { maxbins: 9, offset: 1 }) }) - .rollup({ aggregateVal: (d) => op.min(d.aggregateValues), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) + .rollup({ aggregateVal: (d) => op.min(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) .orderby('group') .groupby('category') .derive({ categoryCount: (d) => op.sum(d.count) }); @@ -127,7 +71,7 @@ export function binByAggregateType(tempTable: ColumnTable, aggregateType: EAggre return tempTable .groupby('category', { group: bin('group', { maxbins: 9 }), group_max: bin('group', { maxbins: 9, offset: 1 }) }) .rollup({ - aggregateVal: (d) => op.median(d.aggregateValues), + aggregateVal: (d) => op.median(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id), @@ -139,7 +83,7 @@ export function binByAggregateType(tempTable: ColumnTable, aggregateType: EAggre case EAggregateTypes.MAX: return tempTable .groupby('category', { group: bin('group', { maxbins: 9 }), group_max: bin('group', { maxbins: 9, offset: 1 }) }) - .rollup({ aggregateVal: (d) => op.max(d.aggregateValues), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) + .rollup({ aggregateVal: (d) => op.max(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) .orderby('group') .groupby('category') .derive({ categoryCount: (d) => op.sum(d.count) }); @@ -162,7 +106,7 @@ export function groupByAggregateType(tempTable: ColumnTable, aggregateType: EAgg return tempTable .groupby('category', 'group') .rollup({ - aggregateVal: (d) => op.average(d.aggregateValues), + aggregateVal: (d) => op.average(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id), @@ -173,7 +117,7 @@ export function groupByAggregateType(tempTable: ColumnTable, aggregateType: EAgg case EAggregateTypes.MIN: return tempTable .groupby('category', 'group') - .rollup({ aggregateVal: (d) => op.min(d.aggregateValues), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) + .rollup({ aggregateVal: (d) => op.min(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) .orderby('category') .groupby('category') .derive({ categoryCount: (d) => op.sum(d.count) }); @@ -181,7 +125,7 @@ export function groupByAggregateType(tempTable: ColumnTable, aggregateType: EAgg return tempTable .groupby('category', 'group') .rollup({ - aggregateVal: (d) => op.median(d.aggregateValues), + aggregateVal: (d) => op.median(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id), @@ -193,7 +137,7 @@ export function groupByAggregateType(tempTable: ColumnTable, aggregateType: EAgg case EAggregateTypes.MAX: return tempTable .groupby('category', 'group') - .rollup({ aggregateVal: (d) => op.max(d.aggregateValues), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) + .rollup({ aggregateVal: (d) => op.max(d.aggregateVal), count: op.count(), selectedCount: (d) => op.sum(d.selected), ids: (d) => op.array_agg(d.id) }) .orderby('category') .groupby('category') .derive({ categoryCount: (d) => op.sum(d.count) }); @@ -209,15 +153,15 @@ export function rollupByAggregateType(tempTable: ColumnTable, aggregateType: EAg case EAggregateTypes.COUNT: return tempTable.rollup({ aggregateVal: () => op.count() }); case EAggregateTypes.AVG: - return tempTable.rollup({ aggregateVal: (d) => op.average(d.aggregateValues) }); + return tempTable.rollup({ aggregateVal: (d) => op.average(d.aggregateVal) }); case EAggregateTypes.MIN: - return tempTable.rollup({ aggregateVal: (d) => op.min(d.aggregateValues) }); + return tempTable.rollup({ aggregateVal: (d) => op.min(d.aggregateVal) }); case EAggregateTypes.MED: - return tempTable.rollup({ aggregateVal: (d) => op.median(d.aggregateValues) }); + return tempTable.rollup({ aggregateVal: (d) => op.median(d.aggregateVal) }); case EAggregateTypes.MAX: - return tempTable.rollup({ aggregateVal: (d) => op.max(d.aggregateValues) }); + return tempTable.rollup({ aggregateVal: (d) => op.max(d.aggregateVal) }); default: return null; @@ -240,6 +184,7 @@ export async function getBarData( resolvedValues: (VisNumericalValue | VisCategoricalValue)[]; type: EColumnTypes.NUMERICAL | EColumnTypes.CATEGORICAL; info: ColumnInfo; + color?: Record; }; multiplesColVals: { resolvedValues: (VisNumericalValue | VisCategoricalValue)[]; diff --git a/src/vis/barGood/BarVisSidebar.tsx b/src/vis/barGood/BarVisSidebar.tsx deleted file mode 100644 index c161f6e0a..000000000 --- a/src/vis/barGood/BarVisSidebar.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Container, Divider, Stack } from '@mantine/core'; -import merge from 'lodash/merge'; -import * as React from 'react'; -import { useMemo } from 'react'; -import { ColumnInfo, EAggregateTypes, EColumnTypes, ICommonVisSideBarProps } from '../interfaces'; -import { AggregateTypeSelect } from '../sidebar/AggregateTypeSelect'; -import { BarDirectionButtons } from '../sidebar/BarDirectionButtons'; -import { FilterButtons } from '../sidebar/FilterButtons'; -import { GroupSelect } from '../sidebar/GroupSelect'; -import { SingleColumnSelect } from '../sidebar/SingleColumnSelect'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { EBarDirection, EBarDisplayType, EBarGroupingType, IBarConfig } from './utils'; - -const defaultConfig = { - group: { - enable: true, - customComponent: null, - }, - multiples: { - enable: true, - customComponent: null, - }, - direction: { - enable: true, - customComponent: null, - }, - filter: { - enable: true, - customComponent: null, - }, - groupType: { - enable: true, - customComponent: null, - }, - display: { - enable: true, - customComponent: null, - }, -}; - -export function BarVisSidebar({ - config, - optionsConfig, - columns, - setConfig, - className = '', - filterCallback, - style: { width = '20em', ...style } = {}, -}: ICommonVisSideBarProps) { - const mergedOptionsConfig = useMemo(() => { - return merge({}, defaultConfig, optionsConfig); - }, [optionsConfig]); - - return ( - - setConfig({ ...(config as any), type })} currentSelected={config.type} /> - - - - setConfig({ - ...config, - catColumnSelected, - multiples: config.multiples && config.multiples.id === catColumnSelected?.id ? null : config.multiples, - group: config.group && config.group.id === catColumnSelected?.id ? null : config.group, - }) - } - columns={columns} - currentSelected={config.catColumnSelected} - type={[EColumnTypes.CATEGORICAL]} - label="Categorical column" - /> - { - if (config.aggregateColumn === null) { - setConfig({ - ...config, - aggregateType, - aggregateColumn: columns.find((col) => col.type === EColumnTypes.NUMERICAL).info, - display: aggregateType === EAggregateTypes.COUNT ? config.display : EBarDisplayType.ABSOLUTE, - }); - } else { - setConfig({ ...config, aggregateType, display: aggregateType === EAggregateTypes.COUNT ? config.display : EBarDisplayType.ABSOLUTE }); - } - }} - aggregateColumnSelectCallback={(aggregateColumn: ColumnInfo) => setConfig({ ...config, aggregateColumn })} - columns={columns} - currentSelected={config.aggregateType} - aggregateColumn={config.aggregateColumn} - /> - - - - - {mergedOptionsConfig.group.enable - ? mergedOptionsConfig.group.customComponent || ( - setConfig({ ...config, group })} - groupTypeSelectCallback={(groupType: EBarGroupingType) => setConfig({ ...config, groupType })} - groupDisplaySelectCallback={(display: EBarDisplayType) => setConfig({ ...config, display })} - displayType={config.display} - groupType={config.groupType} - columns={columns.filter((c) => config.catColumnSelected && c.info.id !== config.catColumnSelected.id)} - currentSelected={config.group} - /> - ) - : null} - {mergedOptionsConfig.multiples.enable - ? mergedOptionsConfig.multiples.customComponent || ( - setConfig({ ...config, multiples })} - columns={columns.filter((c) => config.catColumnSelected && c.info.id !== config.catColumnSelected.id)} - currentSelected={config.multiples} - label="Multiples" - type={[EColumnTypes.CATEGORICAL]} - /> - ) - : null} - - - {mergedOptionsConfig.direction.enable - ? mergedOptionsConfig.direction.customComponent || ( - setConfig({ ...config, direction })} currentSelected={config.direction} /> - ) - : null} - {mergedOptionsConfig.filter.enable ? mergedOptionsConfig.filter.customComponent || : null} - - ); -} diff --git a/src/vis/correlation/CorrelationMatrix.tsx b/src/vis/correlation/CorrelationMatrix.tsx index de8ccd5e0..11cbe75a1 100644 --- a/src/vis/correlation/CorrelationMatrix.tsx +++ b/src/vis/correlation/CorrelationMatrix.tsx @@ -6,10 +6,11 @@ import { corrcoeff, spearmancoeff, tukeyhsd } from 'jstat'; import * as React from 'react'; import { useMemo } from 'react'; import { useAsync } from '../../hooks/useAsync'; -import { ColumnInfo, EColumnTypes, ECorrelationType, EScaleType, VisCategoricalValue, VisColumn, VisNumericalValue } from '../interfaces'; +import { ColumnInfo, EColumnTypes, EScaleType, VisCategoricalValue, VisColumn, VisNumericalValue } from '../interfaces'; import { ColorLegendVert } from '../legend/ColorLegendVert'; import { CorrelationPair, CorrelationPairProps } from './components/CorrelationPair'; -import { ICorrelationConfig, getCorrelationMatrixData } from './utils'; +import { ECorrelationType, ICorrelationConfig } from './interfaces'; +import { getCorrelationMatrixData } from './utils'; const paddingCircle = { top: 5, right: 5, bottom: 5, left: 5 }; const CIRCLE_MIN_SIZE = 4; diff --git a/src/vis/correlation/CorrelationVis.tsx b/src/vis/correlation/CorrelationVis.tsx index f9d3fd576..a1c6cc46a 100644 --- a/src/vis/correlation/CorrelationVis.tsx +++ b/src/vis/correlation/CorrelationVis.tsx @@ -1,8 +1,13 @@ import * as React from 'react'; +import { InvalidCols } from '../general/InvalidCols'; import { ICommonVisProps } from '../interfaces'; import { CorrelationMatrix } from './CorrelationMatrix'; -import { ICorrelationConfig } from './utils'; +import { ICorrelationConfig } from './interfaces'; export function CorrelationVis({ config, columns }: ICommonVisProps) { - return config.numColumnsSelected.length > 1 ? : null; + return config.numColumnsSelected.length > 1 ? ( + + ) : ( + + ); } diff --git a/src/vis/correlation/CorrelationVisSidebar.tsx b/src/vis/correlation/CorrelationVisSidebar.tsx index 0f3afc5f9..06f477ab4 100644 --- a/src/vis/correlation/CorrelationVisSidebar.tsx +++ b/src/vis/correlation/CorrelationVisSidebar.tsx @@ -1,12 +1,11 @@ import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ActionIcon, Container, Divider, Group, NumberInput, SegmentedControl, Stack, Text, Tooltip } from '@mantine/core'; +import { ActionIcon, Group, Input, NumberInput, SegmentedControl, Text, Tooltip } from '@mantine/core'; import * as d3 from 'd3v7'; import * as React from 'react'; -import { ColumnInfo, ECorrelationType, EScaleType, ESupportedPlotlyVis, ICommonVisSideBarProps, VisColumn } from '../interfaces'; +import { ColumnInfo, EScaleType, ICommonVisSideBarProps, VisColumn } from '../interfaces'; import { NumericalColumnSelect } from '../sidebar/NumericalColumnSelect'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { ICorrelationConfig } from './utils'; +import { ECorrelationType, ICorrelationConfig } from './interfaces'; export function CorrelationVisSidebar({ config, @@ -20,98 +19,94 @@ export function CorrelationVisSidebar({ setConfig: (config: ICorrelationConfig) => void; } & ICommonVisSideBarProps) { return ( - - setConfig({ ...config, type })} currentSelected={config.type} /> - - - setConfig({ ...config, numColumnsSelected })} - columns={columns} - currentSelected={config.numColumnsSelected || []} + <> + setConfig({ ...config, numColumnsSelected })} + columns={columns} + currentSelected={config.numColumnsSelected || []} + /> + + + setConfig({ ...config, correlationType: v as ECorrelationType })} + /> + + + + setConfig({ ...config, pScaleType: v as EScaleType })} /> - - - Correlation type - - setConfig({ ...config, correlationType: v as ECorrelationType })} - /> - - P value scale type - - setConfig({ ...config, pScaleType: v as EScaleType })} - /> - { - return d3.format('.3~g')(+value); - }} - onChange={(val) => setConfig({ ...config, pDomain: [+val, config.pDomain[1]] })} - label={ - - Maximum P Value - - Sets the maximum p value for the size scale. Any p value at or above this value will have the smallest possible circle - - } - > - - - - - - } - value={config.pDomain[0]} - /> - { - return d3.format('.3~g')(+value); - }} - onChange={(val) => setConfig({ ...config, pDomain: [config.pDomain[0], +val] })} - label={ - - Minimum P Value - - Sets the minimum p value for the size scale. Any p value at or below this value will have the largest possible circle - - } - > - - - - - - } - value={config.pDomain[1]} - /> - - - + + { + return d3.format('.3~g')(+value); + }} + onChange={(val) => setConfig({ ...config, pDomain: [+val, config.pDomain[1]] })} + label={ + + Maximum p-value + + Sets the maximum p-value for the size scale. Any p-value at or above this value will have the smallest possible circle + + } + > + + + + + + } + value={config.pDomain[0]} + /> + { + return d3.format('.3~g')(+value); + }} + onChange={(val) => setConfig({ ...config, pDomain: [config.pDomain[0], +val] })} + label={ + + Minimum p-value + + Sets the minimum p-value for the size scale. Any p-value at or below this value will have the largest possible circle + + } + > + + + + + + } + value={config.pDomain[1]} + /> + ); } diff --git a/src/vis/correlation/components/CorrelationPair.tsx b/src/vis/correlation/components/CorrelationPair.tsx index a1fbe808f..f10a4bf6a 100644 --- a/src/vis/correlation/components/CorrelationPair.tsx +++ b/src/vis/correlation/components/CorrelationPair.tsx @@ -2,7 +2,7 @@ import { Center, Stack, Text, Tooltip, useMantineTheme } from '@mantine/core'; import * as d3 from 'd3v7'; import * as React from 'react'; import { useMemo } from 'react'; -import { ICorrelationConfig } from '../utils'; +import { ICorrelationConfig } from '../interfaces'; const marginRect = { top: 0, right: 0, bottom: 0, left: 0 }; diff --git a/src/vis/correlation/interfaces.ts b/src/vis/correlation/interfaces.ts new file mode 100644 index 000000000..b92cb914d --- /dev/null +++ b/src/vis/correlation/interfaces.ts @@ -0,0 +1,18 @@ +import { BaseVisConfig, ColumnInfo, EScaleType, ESupportedPlotlyVis } from '../interfaces'; + +export interface ICorrelationConfig extends BaseVisConfig { + type: ESupportedPlotlyVis.CORRELATION; + correlationType: ECorrelationType; + numColumnsSelected: ColumnInfo[]; + pScaleType: EScaleType; + pDomain: [number, number]; +} + +export enum ECorrelationType { + PEARSON = 'Pearson', + SPEARMAN = 'Spearman', +} + +export function isCorrelationConfig(s: BaseVisConfig): s is ICorrelationConfig { + return s.type === ESupportedPlotlyVis.CORRELATION; +} diff --git a/src/vis/correlation/utils.ts b/src/vis/correlation/utils.ts index ba2385bab..5aca9a339 100644 --- a/src/vis/correlation/utils.ts +++ b/src/vis/correlation/utils.ts @@ -1,28 +1,7 @@ import merge from 'lodash/merge'; import { resolveColumnValues } from '../general/layoutUtils'; -import { - BaseVisConfig, - ColumnInfo, - EColumnTypes, - ECorrelationType, - EScaleType, - ESupportedPlotlyVis, - VisCategoricalValue, - VisColumn, - VisNumericalValue, -} from '../interfaces'; - -export interface ICorrelationConfig extends BaseVisConfig { - type: ESupportedPlotlyVis.CORRELATION; - correlationType: ECorrelationType; - numColumnsSelected: ColumnInfo[]; - pScaleType: EScaleType; - pDomain: [number, number]; -} - -export function isCorrelation(s: BaseVisConfig): s is ICorrelationConfig { - return s.type === ESupportedPlotlyVis.CORRELATION; -} +import { ColumnInfo, EColumnTypes, EScaleType, ESupportedPlotlyVis, VisCategoricalValue, VisColumn, VisNumericalValue } from '../interfaces'; +import { ECorrelationType, ICorrelationConfig } from './interfaces'; const defaultConfig: ICorrelationConfig = { type: ESupportedPlotlyVis.CORRELATION, diff --git a/src/vis/general/InvalidCols.tsx b/src/vis/general/InvalidCols.tsx index 30cc12a2e..356a6c11d 100644 --- a/src/vis/general/InvalidCols.tsx +++ b/src/vis/general/InvalidCols.tsx @@ -1,11 +1,11 @@ +import { Alert, Center, Stack, rem } from '@mantine/core'; import * as React from 'react'; -import { Alert, Center, Stack } from '@mantine/core'; export function InvalidCols({ headerMessage, bodyMessage }: { headerMessage: string; bodyMessage: string }) { return (
- + {bodyMessage}
diff --git a/src/vis/heatmap/AnimatedLine.tsx b/src/vis/heatmap/AnimatedLine.tsx index d46f536dc..ca84a230c 100644 --- a/src/vis/heatmap/AnimatedLine.tsx +++ b/src/vis/heatmap/AnimatedLine.tsx @@ -2,10 +2,24 @@ import * as React from 'react'; import { useMemo, useRef } from 'react'; import { useSpring, animated, easings } from 'react-spring'; -export function AnimatedLine({ x1, x2, y1, y2, order = 1 }: { y2: number; y1: number; x2: number; x1: number; order?: number }) { +export function AnimatedLine({ + x1, + x2, + y1, + y2, + order = 1, + setImmediate, +}: { + y2: number; + y1: number; + x2: number; + x1: number; + order?: number; + setImmediate: boolean; +}) { const myOrder = useRef(order); - const isImmediate = myOrder.current === order; + const isImmediate = setImmediate || myOrder.current === order; const spring = useSpring({ x1, y1, diff --git a/src/vis/heatmap/AnimatedText.tsx b/src/vis/heatmap/AnimatedText.tsx index 5f3f8a653..af1e74aa6 100644 --- a/src/vis/heatmap/AnimatedText.tsx +++ b/src/vis/heatmap/AnimatedText.tsx @@ -10,6 +10,7 @@ export function AnimatedText({ width, height, bold = false, + setImmediate = true, }: { x: number; y: number; @@ -18,10 +19,11 @@ export function AnimatedText({ bold?: boolean; width: number; height: number; + setImmediate?: boolean; }) { const myOrder = useRef(order); - const isImmediate = myOrder.current === order; + const isImmediate = setImmediate || myOrder.current === order; const spring = useSpring({ x, y, diff --git a/src/vis/heatmap/Heatmap.tsx b/src/vis/heatmap/Heatmap.tsx index 023f99466..dafe044b1 100644 --- a/src/vis/heatmap/Heatmap.tsx +++ b/src/vis/heatmap/Heatmap.tsx @@ -1,4 +1,4 @@ -import { faArrowUpWideShort, faArrowUpZA } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDownShortWide, faArrowDownWideShort, faArrowDownAZ, faArrowDownZA } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Box, Container, Group, Stack, Text } from '@mantine/core'; import { useResizeObserver } from '@mantine/hooks'; @@ -6,12 +6,12 @@ import { desc, op, table } from 'arquero'; import * as d3 from 'd3v7'; import * as React from 'react'; import { useMemo } from 'react'; -import { rollupByAggregateType } from '../barGood/utils'; -import { ColumnInfo, EAggregateTypes, EColumnTypes, ENumericalColorScaleType, ESortTypes, VisCategoricalValue, VisNumericalValue } from '../interfaces'; +import { rollupByAggregateType } from '../bar/utils'; +import { ColumnInfo, EAggregateTypes, EColumnTypes, ENumericalColorScaleType, VisCategoricalValue, VisNumericalValue } from '../interfaces'; import { ColorLegendVert } from '../legend/ColorLegendVert'; import { HeatmapRect } from './HeatmapRect'; import { HeatmapText } from './HeatmapText'; -import { IHeatmapConfig } from './utils'; +import { ESortTypes, IHeatmapConfig } from './interfaces'; const interRectDistance = 1; @@ -42,19 +42,20 @@ export function Heatmap({ }) { const [ref, { width, height }] = useResizeObserver(); - const aggregatedTable = useMemo(() => { + const baseTable = useMemo(() => { if (!column1 || !column2) return null; - let valueTable = table({ + return table({ xVal: column1.resolvedValues.map(({ val }) => val), yVal: column2.resolvedValues.map(({ val }) => val), - aggregateValues: aggregateColumn?.resolvedValues.map(({ val }) => val) || [], + aggregateVal: aggregateColumn?.resolvedValues.map(({ val }) => val) || [], id: column1.resolvedValues.map(({ id }) => id), }); + }, [aggregateColumn?.resolvedValues, column1, column2]); + const aggregatedTable = useMemo(() => { + if (!baseTable) return null; - valueTable = valueTable.groupby('xVal', 'yVal'); - - valueTable = rollupByAggregateType(valueTable, config.aggregateType); + let valueTable = rollupByAggregateType(baseTable.groupby('xVal', 'yVal'), config.aggregateType); if (config.aggregateType === EAggregateTypes.COUNT) { valueTable = valueTable.impute({ aggregateVal: () => 0 }, { expand: ['xVal', 'yVal'] }); @@ -68,14 +69,43 @@ export function Heatmap({ .groupby('yVal') .derive({ rowTotal: op.sum('aggregateVal') }); - if (config.sortedBy === ESortTypes.COUNT_ASC) { - valueTable = valueTable.orderby('colTotal', desc('rowTotal')); - } else { - valueTable = valueTable.orderby('xVal', 'yVal'); + // default is ESortTypes.CAT_ASC + let xOrder: string | object; + switch (config.xSortedBy) { + case ESortTypes.VAL_ASC: + xOrder = 'colTotal'; + break; + case ESortTypes.CAT_DESC: + xOrder = desc('xVal'); + break; + case ESortTypes.VAL_DESC: + xOrder = desc('colTotal'); + break; + default: + xOrder = 'xVal'; + break; } + // default is ESortTypes.CAT_ASC + let yOrder: string | object; + switch (config.ySortedBy) { + case ESortTypes.VAL_ASC: + yOrder = 'rowTotal'; + break; + case ESortTypes.CAT_DESC: + yOrder = desc('yVal'); + break; + case ESortTypes.VAL_DESC: + yOrder = desc('rowTotal'); + break; + default: + yOrder = 'yVal'; + break; + } + valueTable = valueTable.orderby(xOrder, yOrder); + return valueTable; - }, [aggregateColumn?.resolvedValues, column1, column2, config.aggregateType, config.sortedBy]); + }, [baseTable, config.aggregateType, config.xSortedBy, config.ySortedBy]); const { groupedValues, rectHeight, rectWidth, yScale, xScale, colorScale } = React.useMemo(() => { const groupedVals = aggregatedTable.objects() as { xVal: string; yVal: string; aggregateVal: number; ids: string[] }[]; @@ -149,7 +179,13 @@ export function Heatmap({ const rects = useMemo(() => { if (width === 0 || height === 0) return null; return groupedValues.map((d, i) => { - const { aggregateVal, ids, x, y, xVal, yVal, color } = d; + const { aggregateVal, x, y, xVal, yVal, color } = d; + const ids: string[] = Array.from( + baseTable + .params({ x: xVal, y: yVal }) + .filter((b, $) => b.xVal === $.x && b.yVal === $.y) + .values('id'), + ); return ( selectionCallback(ids)} + isImmediate={!config.isAnimationEnabled} /> ); }); - }, [groupedValues, height, rectHeight, rectWidth, selected, selectionCallback, width, xScale, yScale]); + }, [baseTable, groupedValues, height, rectHeight, rectWidth, selected, selectionCallback, width, xScale, yScale, config.isAnimationEnabled]); const text = useMemo(() => { if (width === 0 || height === 0) return null; - return ; - }, [height, margin, rectHeight, rectWidth, width, xScale, yScale]); + return ( + + ); + }, [height, margin, rectHeight, rectWidth, width, xScale, yScale, config.isAnimationEnabled]); return ( @@ -188,13 +236,33 @@ export function Heatmap({ setExternalConfig({ ...config, sortedBy: config.sortedBy === ESortTypes.CAT_ASC ? ESortTypes.COUNT_ASC : ESortTypes.CAT_ASC })} + onClick={() => + setExternalConfig({ + ...config, + ySortedBy: + config.ySortedBy === ESortTypes.CAT_ASC + ? ESortTypes.CAT_DESC + : config.ySortedBy === ESortTypes.CAT_DESC + ? ESortTypes.VAL_ASC + : config.ySortedBy === ESortTypes.VAL_ASC + ? ESortTypes.VAL_DESC + : ESortTypes.CAT_ASC, + }) + } > {column2.info.name} @@ -219,9 +287,33 @@ export function Heatmap({ setExternalConfig({ ...config, sortedBy: config.sortedBy === ESortTypes.CAT_ASC ? ESortTypes.COUNT_ASC : ESortTypes.CAT_ASC })} + onClick={() => + setExternalConfig({ + ...config, + xSortedBy: + config.xSortedBy === ESortTypes.CAT_ASC + ? ESortTypes.CAT_DESC + : config.xSortedBy === ESortTypes.CAT_DESC + ? ESortTypes.VAL_ASC + : config.xSortedBy === ESortTypes.VAL_ASC + ? ESortTypes.VAL_DESC + : ESortTypes.CAT_ASC, + }) + } > - + {column1.info.name} diff --git a/src/vis/heatmap/HeatmapGrid.tsx b/src/vis/heatmap/HeatmapGrid.tsx index 2d6aae150..05af2e414 100644 --- a/src/vis/heatmap/HeatmapGrid.tsx +++ b/src/vis/heatmap/HeatmapGrid.tsx @@ -1,9 +1,11 @@ -import { Loader, Stack, Text } from '@mantine/core'; +import { Loader, Stack } from '@mantine/core'; import * as React from 'react'; import { useAsync } from '../../hooks/useAsync'; +import { InvalidCols } from '../general/InvalidCols'; import { VisColumn } from '../interfaces'; import { Heatmap } from './Heatmap'; -import { IHeatmapConfig, getHeatmapData } from './utils'; +import { IHeatmapConfig } from './interfaces'; +import { getHeatmapData } from './utils'; export function HeatmapGrid({ config, @@ -35,9 +37,7 @@ export function HeatmapGrid({ {status === 'pending' ? ( ) : !hasAtLeast2CatCols ? ( - - Select at least 2 categorical columns to display heatmap - + ) : ( null, }: { x: number; @@ -28,13 +29,14 @@ export function HeatmapRect({ xOrder?: number; yOrder?: number; isSelected?: boolean; + isImmediate?: boolean; onClick?: (e: any) => void; }) { const [isHovered, setIsHovered] = useState(); const currXOrder = useRef(xOrder); const currYOrder = useRef(yOrder); - const isImmediate = currXOrder.current === xOrder && currYOrder.current === yOrder; + isImmediate = isImmediate || (currXOrder.current === xOrder && currYOrder.current === yOrder); const colorSpring = useSpring({ fill: color, config: { duration: 750, easing: easings.easeInOutSine } }); diff --git a/src/vis/heatmap/HeatmapText.tsx b/src/vis/heatmap/HeatmapText.tsx index cdb26248e..37744f1e7 100644 --- a/src/vis/heatmap/HeatmapText.tsx +++ b/src/vis/heatmap/HeatmapText.tsx @@ -14,6 +14,7 @@ export function HeatmapText({ rectHeight, height, rectWidth, + isImmediate, }: { margin: { top: number; right: number; bottom: number; left: number }; yScale: d3.ScaleBand; @@ -22,6 +23,7 @@ export function HeatmapText({ height: number; rectHeight: number; rectWidth: number; + isImmediate: boolean; }) { const labelSpacing = useMemo(() => { const maxLabelLength = d3.max(yScale.domain().map((m) => m.length)); @@ -39,6 +41,7 @@ export function HeatmapText({ y1={margin.top} y2={height - margin.bottom} order={1 - i / xScale.domain().length} + setImmediate={isImmediate} /> @@ -70,6 +75,7 @@ export function HeatmapText({ y1={yScale(yVal) + rectHeight + margin.top} y2={yScale(yVal) + rectHeight + margin.top} order={i / yScale.domain().length} + setImmediate={isImmediate} /> - + diff --git a/src/vis/heatmap/HeatmapVis.tsx b/src/vis/heatmap/HeatmapVis.tsx index ae9fd1644..f9eb0b73e 100644 --- a/src/vis/heatmap/HeatmapVis.tsx +++ b/src/vis/heatmap/HeatmapVis.tsx @@ -1,9 +1,8 @@ import { Group } from '@mantine/core'; import * as React from 'react'; -import { VisSidebarOpenButton } from '../VisSidebarOpenButton'; import { ICommonVisProps } from '../interfaces'; import { HeatmapGrid } from './HeatmapGrid'; -import { IHeatmapConfig } from './utils'; +import { IHeatmapConfig } from './interfaces'; export function HeatmapVis({ config, @@ -17,7 +16,6 @@ export function HeatmapVis({ }: ICommonVisProps) { return ( - {enableSidebar ? setShowSidebar(!showSidebar)} isOpen={showSidebar} /> : null} ); diff --git a/src/vis/heatmap/HeatmapVisSidebar.tsx b/src/vis/heatmap/HeatmapVisSidebar.tsx index d7083df1f..87c668d13 100644 --- a/src/vis/heatmap/HeatmapVisSidebar.tsx +++ b/src/vis/heatmap/HeatmapVisSidebar.tsx @@ -1,11 +1,11 @@ -import { Container, Stack } from '@mantine/core'; import * as React from 'react'; -import { ColumnInfo, EAggregateTypes, EColumnTypes, ESupportedPlotlyVis, VisColumn } from '../interfaces'; +import { Switch } from '@mantine/core'; +import { ColumnInfo, EAggregateTypes, EColumnTypes, VisColumn } from '../interfaces'; import { AggregateTypeSelect } from '../sidebar/AggregateTypeSelect'; import { CategoricalColumnSelect } from '../sidebar/CategoricalColumnSelect'; import { NumericalColorButtons } from '../sidebar/NumericalColorButtons'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { IHeatmapConfig } from './utils'; +import { IHeatmapConfig } from './interfaces'; +import { i18n } from '../../i18n'; export function HeatmapVisSidebar({ config, @@ -17,35 +17,38 @@ export function HeatmapVisSidebar({ setConfig: (config: IHeatmapConfig) => void; }) { return ( - - - setConfig({ ...(config as any), type })} currentSelected={config.type} /> - setConfig({ ...config, catColumnsSelected })} - columns={columns} - currentSelected={config.catColumnsSelected || []} - /> - {config?.catColumnsSelected?.length > 1 ? ( - setConfig({ ...config, numColorScaleType })} currentSelected={config.numColorScaleType} /> - ) : null} - { - if (config.aggregateColumn === null) { - setConfig({ - ...config, - aggregateType, - aggregateColumn: columns.find((col) => col.type === EColumnTypes.NUMERICAL).info, - }); - } else { - setConfig({ ...config, aggregateType }); - } - }} - aggregateColumnSelectCallback={(aggregateColumn: ColumnInfo) => setConfig({ ...config, aggregateColumn })} - columns={columns} - currentSelected={config.aggregateType} - aggregateColumn={config.aggregateColumn} - /> - - + <> + setConfig({ ...config, catColumnsSelected })} + columns={columns} + currentSelected={config.catColumnsSelected || []} + /> + {config?.catColumnsSelected?.length > 1 ? ( + setConfig({ ...config, numColorScaleType })} currentSelected={config.numColorScaleType} /> + ) : null} + { + if (config.aggregateColumn === null) { + setConfig({ + ...config, + aggregateType, + aggregateColumn: columns.find((col) => col.type === EColumnTypes.NUMERICAL).info, + }); + } else { + setConfig({ ...config, aggregateType }); + } + }} + aggregateColumnSelectCallback={(aggregateColumn: ColumnInfo) => setConfig({ ...config, aggregateColumn })} + columns={columns} + currentSelected={config.aggregateType} + aggregateColumn={config.aggregateColumn} + /> + {/* Disabled until the animations are fixed. By default animations are disabled */} + {/* setConfig({ ...config, isAnimationEnabled: event.currentTarget.checked })} + label={i18n.t('visyn:vis.animation')} + /> */} + ); } diff --git a/src/vis/heatmap/interfaces.ts b/src/vis/heatmap/interfaces.ts new file mode 100644 index 000000000..586e2f738 --- /dev/null +++ b/src/vis/heatmap/interfaces.ts @@ -0,0 +1,24 @@ +import { BaseVisConfig, ColumnInfo, EAggregateTypes, ENumericalColorScaleType, ESupportedPlotlyVis } from '../interfaces'; + +export interface IHeatmapConfig { + type: ESupportedPlotlyVis.HEATMAP; + color: ColumnInfo | null; + catColumnsSelected: ColumnInfo[]; + numColorScaleType: ENumericalColorScaleType; + xSortedBy: ESortTypes; + ySortedBy: ESortTypes; + aggregateType: EAggregateTypes; + aggregateColumn: ColumnInfo | null; + isAnimationEnabled: boolean; +} + +export enum ESortTypes { + CAT_ASC = 'CAT_ASC', + CAT_DESC = 'CAT_DESC', + VAL_ASC = 'VAL_ASC', + VAL_DESC = 'VAL_DESC', +} + +export function isHeatmapConfig(vis: BaseVisConfig): vis is IHeatmapConfig { + return vis.type === ESupportedPlotlyVis.HEATMAP; +} diff --git a/src/vis/heatmap/utils.ts b/src/vis/heatmap/utils.ts index f62543690..3109564a3 100644 --- a/src/vis/heatmap/utils.ts +++ b/src/vis/heatmap/utils.ts @@ -1,40 +1,27 @@ import merge from 'lodash/merge'; import { resolveColumnValues, resolveSingleColumn } from '../general/layoutUtils'; import { - BaseVisConfig, ColumnInfo, EAggregateTypes, EColumnTypes, ENumericalColorScaleType, - ESortTypes, ESupportedPlotlyVis, VisCategoricalValue, VisColumn, VisNumericalValue, } from '../interfaces'; - -export interface IHeatmapConfig { - type: ESupportedPlotlyVis.HEATMAP; - color: ColumnInfo | null; - catColumnsSelected: ColumnInfo[]; - numColorScaleType: ENumericalColorScaleType; - sortedBy: ESortTypes; - aggregateType: EAggregateTypes; - aggregateColumn: ColumnInfo | null; -} - -export function isHeatmap(vis: BaseVisConfig): vis is IHeatmapConfig { - return vis.type === ESupportedPlotlyVis.HEATMAP; -} +import { ESortTypes, IHeatmapConfig } from './interfaces'; const defaultConfig: IHeatmapConfig = { type: ESupportedPlotlyVis.HEATMAP, color: null, catColumnsSelected: [], - numColorScaleType: null, - sortedBy: ESortTypes.CAT_ASC, + numColorScaleType: ENumericalColorScaleType.SEQUENTIAL, + xSortedBy: ESortTypes.CAT_ASC, + ySortedBy: ESortTypes.CAT_ASC, aggregateColumn: null, aggregateType: EAggregateTypes.COUNT, + isAnimationEnabled: false, }; export function heatmapMergeDefaultConfig(columns: VisColumn[], config: IHeatmapConfig): IHeatmapConfig { diff --git a/src/vis/sidebar/HexOpacitySwitch.tsx b/src/vis/hexbin/HexOpacitySwitch.tsx similarity index 100% rename from src/vis/sidebar/HexOpacitySwitch.tsx rename to src/vis/hexbin/HexOpacitySwitch.tsx diff --git a/src/vis/hexbin/HexSizeSlider.tsx b/src/vis/hexbin/HexSizeSlider.tsx new file mode 100644 index 000000000..ba0692af4 --- /dev/null +++ b/src/vis/hexbin/HexSizeSlider.tsx @@ -0,0 +1,37 @@ +import { Input, Slider } from '@mantine/core'; +import debounce from 'lodash/debounce'; +import * as React from 'react'; +import { useMemo } from 'react'; +import { useSyncedRef } from '../../hooks'; + +interface OpacitySliderProps { + callback: (n: number) => void; + currentValue: number; +} + +export function HexSizeSlider({ callback, currentValue }: OpacitySliderProps) { + const syncedCallback = useSyncedRef(callback); + + const debouncedCallback = useMemo(() => { + return debounce((n: number) => syncedCallback.current?.(n), 10); + }, [syncedCallback]); + + return ( + + { + debouncedCallback(n); + }} + /> + + ); +} diff --git a/src/vis/sidebar/HexSizeSwitch.tsx b/src/vis/hexbin/HexSizeSwitch.tsx similarity index 100% rename from src/vis/sidebar/HexSizeSwitch.tsx rename to src/vis/hexbin/HexSizeSwitch.tsx diff --git a/src/vis/sidebar/HexbinOptionSelect.tsx b/src/vis/hexbin/HexbinOptionSelect.tsx similarity index 93% rename from src/vis/sidebar/HexbinOptionSelect.tsx rename to src/vis/hexbin/HexbinOptionSelect.tsx index 5b3d52187..aa9bfeca9 100644 --- a/src/vis/sidebar/HexbinOptionSelect.tsx +++ b/src/vis/hexbin/HexbinOptionSelect.tsx @@ -1,7 +1,7 @@ import { Select } from '@mantine/core'; import * as React from 'react'; import { i18n } from '../../i18n'; -import { EHexbinOptions } from '../interfaces'; +import { EHexbinOptions } from './interfaces'; interface HexbinOptionSelectProps { callback: (c: EHexbinOptions) => void; diff --git a/src/vis/hexbin/HexbinVis.tsx b/src/vis/hexbin/HexbinVis.tsx index 7a37378ef..ddbaf2426 100644 --- a/src/vis/hexbin/HexbinVis.tsx +++ b/src/vis/hexbin/HexbinVis.tsx @@ -1,36 +1,102 @@ -import { Center, Group, SimpleGrid, Stack } from '@mantine/core'; -import merge from 'lodash/merge'; +import { Box, Center, Chip, Group, ScrollArea, Stack, Tooltip, rem } from '@mantine/core'; +import * as d3v7 from 'd3v7'; import * as React from 'react'; -import { useMemo } from 'react'; +import { useAsync } from '../../hooks/useAsync'; import { i18n } from '../../i18n'; import { InvalidCols } from '../general'; import { EScatterSelectSettings, ICommonVisProps } from '../interfaces'; import { BrushOptionButtons } from '../sidebar'; import { Hexplot } from './Hexplot'; -import { IHexbinConfig } from './utils'; +import { IHexbinConfig } from './interfaces'; +import { getHexData } from './utils'; -const defaultExtensions = { - prePlot: null, - postPlot: null, - preSidebar: null, - postSidebar: null, -}; +function Legend({ + categories, + filteredCategories, + colorScale, + onClick, + height, +}: { + categories: string[]; + filteredCategories: string[]; + colorScale: d3v7.ScaleOrdinal; + onClick: (string) => void; + height: number; +}) { + return ( + + + {categories.map((c) => { + return ( + + + onClick(c)} + checked={false} + styles={{ + label: { + width: '100%', + backgroundColor: filteredCategories.includes(c) ? 'lightgrey' : `${colorScale(c)} !important`, + textAlign: 'center', + paddingLeft: '10px', + paddingRight: '10px', + overflow: 'hidden', + color: filteredCategories.includes(c) ? 'black' : 'white', + textOverflow: 'ellipsis', + }, + }} + > + {c} + + + + ); + })} + + + ); +} export function HexbinVis({ config, - extensions, columns, + dimensions, setConfig, selectionCallback = () => null, selectedMap = {}, showDragModeOptions = true, }: ICommonVisProps) { - const mergedExtensions = useMemo(() => { - return merge({}, defaultExtensions, extensions); - }, [extensions]); + const { width, height } = dimensions; + const { value: allColumns, status: colsStatus } = useAsync(getHexData, [columns, config.numColumnsSelected, config.color]); + + const [filteredCategories, setFilteredCategories] = React.useState([]); + + const currentColorColumn = React.useMemo(() => { + if (config.color && allColumns?.colorColVals) { + return { + allValues: allColumns.colorColVals.resolvedValues, + filteredValues: allColumns.colorColVals.resolvedValues.filter((val) => !filteredCategories.includes(val.val as string)), + }; + } + + return null; + }, [allColumns?.colorColVals, config.color, filteredCategories]); + + const colorScale = React.useMemo(() => { + if (!currentColorColumn?.allValues) { + return null; + } + + const colorOptions = currentColorColumn.allValues.map((val) => val.val as string); + + return d3v7 + .scaleOrdinal(allColumns.colorColVals.color ? Object.keys(allColumns.colorColVals.color) : d3v7.schemeCategory10) + .domain(allColumns.colorColVals.color ? Object.values(allColumns.colorColVals.color) : Array.from(new Set(colorOptions))); + }, [currentColorColumn, allColumns]); return ( - + {showDragModeOptions ? (
@@ -42,13 +108,33 @@ export function HexbinVis({
) : null} - 2 ? config.numColumnsSelected.length : 1}> - {config.numColumnsSelected.length < 2 ? ( - - ) : ( - <> - {config.numColumnsSelected.length > 2 ? ( - config.numColumnsSelected.map((xCol) => { + + 2 + ? { gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gridTemplateRows: 'repeat(3, minmax(0, 1fr))', gap: '1rem 1rem' } + : { gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', gridTemplateRows: 'repeat(1, minmax(0, 1fr))', gap: '1rem 1rem' }), + }} + > + {config.numColumnsSelected.length < 2 ? ( + + ) : null} + + {config.numColumnsSelected.length === 2 && allColumns?.numColVals.length === config.numColumnsSelected.length && colsStatus === 'success' ? ( + + ) : null} + {config.numColumnsSelected.length > 2 && allColumns?.numColVals.length === config.numColumnsSelected.length && colsStatus === 'success' + ? config.numColumnsSelected.map((xCol) => { return config.numColumnsSelected.map((yCol) => { if (xCol.id !== yCol.id) { return ( @@ -57,11 +143,14 @@ export function HexbinVis({ selectionCallback={selectionCallback} selected={selectedMap} config={config} - columns={[ - columns.find((col) => col.info.id === yCol.id), - columns.find((col) => col.info.id === xCol.id), - columns.find((col) => col.info.id === config.color?.id), - ]} + filteredCategories={filteredCategories} + allColumns={{ + numColVals: [ + allColumns.numColVals.find((col) => col.info.id === yCol.id), + allColumns.numColVals.find((col) => col.info.id === xCol.id), + ], + colorColVals: allColumns.colorColVals, + }} /> ); } @@ -69,22 +158,24 @@ export function HexbinVis({ return
; }); }) - ) : ( - col.info.id === config.numColumnsSelected[0].id), - columns.find((col) => col.info.id === config.numColumnsSelected[1].id), - columns.find((col) => col.info.id === config.color?.id), - ]} - /> - )} - {mergedExtensions.postPlot} - - )} - + : null} + + {currentColorColumn ? ( +
+ + filteredCategories.includes(s) + ? setFilteredCategories(filteredCategories.filter((f) => f !== s)) + : setFilteredCategories([...filteredCategories, s]) + } + height={height - 100} + /> +
+ ) : null} + ); } diff --git a/src/vis/hexbin/HexbinVisSidebar.tsx b/src/vis/hexbin/HexbinVisSidebar.tsx index 0f50be88f..6d973f726 100644 --- a/src/vis/hexbin/HexbinVisSidebar.tsx +++ b/src/vis/hexbin/HexbinVisSidebar.tsx @@ -1,45 +1,35 @@ -import { Container, Divider, Stack } from '@mantine/core'; import * as React from 'react'; -import { ColumnInfo, EColumnTypes, EHexbinOptions, ESupportedPlotlyVis, ICommonVisSideBarProps } from '../interfaces'; +import { ColumnInfo, EColumnTypes, ICommonVisSideBarProps } from '../interfaces'; import { NumericalColumnSelect } from '../sidebar'; -import { HexOpacitySwitch } from '../sidebar/HexOpacitySwitch'; -import { HexSizeSlider } from '../sidebar/HexSizeSlider'; -import { HexSizeSwitch } from '../sidebar/HexSizeSwitch'; -import { HexbinOptionSelect } from '../sidebar/HexbinOptionSelect'; import { SingleColumnSelect } from '../sidebar/SingleColumnSelect'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { IHexbinConfig } from './utils'; +import { HexOpacitySwitch } from './HexOpacitySwitch'; +import { HexSizeSlider } from './HexSizeSlider'; +import { HexSizeSwitch } from './HexSizeSwitch'; +import { HexbinOptionSelect } from './HexbinOptionSelect'; +import { EHexbinOptions, IHexbinConfig } from './interfaces'; export function HexbinVisSidebar({ config, columns, setConfig }: ICommonVisSideBarProps) { return ( - - - setConfig({ ...(config as any), type })} currentSelected={config.type} /> - - - setConfig({ ...config, numColumnsSelected })} - columns={columns} - currentSelected={config.numColumnsSelected || []} - /> - setConfig({ ...config, color })} - columns={columns} - currentSelected={config.color} - /> - {config.color ? ( - setConfig({ ...config, hexbinOptions })} currentSelected={config.hexbinOptions} /> - ) : null} - - - - setConfig({ ...config, hexRadius })} /> - setConfig({ ...config, isSizeScale })} /> - setConfig({ ...config, isOpacityScale })} /> - - - + <> + setConfig({ ...config, numColumnsSelected })} + columns={columns} + currentSelected={config.numColumnsSelected || []} + /> + setConfig({ ...config, color })} + columns={columns} + currentSelected={config.color} + /> + {config.color ? ( + setConfig({ ...config, hexbinOptions })} currentSelected={config.hexbinOptions} /> + ) : null} + + setConfig({ ...config, hexRadius })} /> + setConfig({ ...config, isSizeScale })} /> + setConfig({ ...config, isOpacityScale })} /> + ); } diff --git a/src/vis/hexbin/Hexplot.tsx b/src/vis/hexbin/Hexplot.tsx index 9100421a5..ed3bfe64b 100644 --- a/src/vis/hexbin/Hexplot.tsx +++ b/src/vis/hexbin/Hexplot.tsx @@ -1,4 +1,5 @@ import { Box, Chip, Container, ScrollArea, Stack, Tooltip } from '@mantine/core'; +import { useElementSize } from '@mantine/hooks'; import * as hex from 'd3-hexbin'; import { HexbinBin } from 'd3-hexbin'; import * as d3v7 from 'd3v7'; @@ -6,87 +7,35 @@ import { D3BrushEvent, D3ZoomEvent } from 'd3v7'; import uniqueId from 'lodash/uniqueId'; import * as React from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; -import { useAsync } from '../../hooks/useAsync'; -import { EScatterSelectSettings, VisColumn } from '../interfaces'; +import { EScatterSelectSettings } from '../interfaces'; import { SingleHex } from './SingleHex'; import { XAxis } from './XAxis'; import { YAxis } from './YAxis'; -import { IHexbinConfig, getHexData } from './utils'; +import { IHexbinConfig } from './interfaces'; +import { ResolvedHexValues } from './utils'; interface HexagonalBinProps { config: IHexbinConfig; - columns: VisColumn[]; + allColumns: ResolvedHexValues; selectionCallback?: (ids: string[]) => void; selected?: { [key: string]: boolean }; -} - -function Legend({ - categories, - filteredCategories, - colorScale, - onClick, - height, -}: { - categories: string[]; filteredCategories: string[]; - colorScale: d3v7.ScaleOrdinal; - onClick: (string) => void; - height: number; -}) { - return ( - - - {categories.map((c) => { - return ( - - - onClick(c)} - checked={false} - styles={{ - label: { - width: '100%', - backgroundColor: filteredCategories.includes(c) ? 'lightgrey' : `${colorScale(c)} !important`, - textAlign: 'center', - paddingLeft: '10px', - paddingRight: '10px', - overflow: 'hidden', - color: filteredCategories.includes(c) ? 'black' : 'white', - textOverflow: 'ellipsis', - }, - }} - > - {c} - - - - ); - })} - - - ); } -export function Hexplot({ config, columns, selectionCallback = () => null, selected = {} }: HexagonalBinProps) { - const ref = useRef(null); - const [height, setHeight] = useState(0); - const [width, setWidth] = useState(0); +export function Hexplot({ config, allColumns, selectionCallback = () => null, selected = {}, filteredCategories }: HexagonalBinProps) { + const { ref: hexRef, width: realWidth, height: realHeight } = useElementSize(); + const xZoomedScale = useRef>(null); const yZoomedScale = useRef>(null); const [xZoomTransform, setXZoomTransform] = useState(0); const [yZoomTransform, setYZoomTransform] = useState(0); const [zoomScale, setZoomScale] = useState(1); - const [filteredCategories, setFilteredCategories] = useState([]); - - const { value: allColumns, status: colsStatus } = useAsync(getHexData, [columns, config.numColumnsSelected, config.color]); - const id = React.useMemo(() => uniqueId('HexPlot'), []); // getting current categorical column values, original and filtered const currentColorColumn = useMemo(() => { - if (colsStatus === 'success' && config.color && allColumns.colorColVals) { + if (config.color && allColumns.colorColVals) { return { allValues: allColumns.colorColVals.resolvedValues, filteredValues: allColumns.colorColVals.resolvedValues.filter((val) => !filteredCategories.includes(val.val as string)), @@ -94,20 +43,23 @@ export function Hexplot({ config, columns, selectionCallback = () => null, selec } return null; - }, [allColumns?.colorColVals, config.color, colsStatus, filteredCategories]); + }, [allColumns?.colorColVals, config.color, filteredCategories]); const margin = useMemo(() => { return { - left: 52, - right: config.color ? 80 : 25, - top: 50, - bottom: 53, + left: 48, + right: 16, + top: 48, + bottom: 48, }; - }, [config.color]); + }, []); + + const height = realHeight - margin.top - margin.bottom; + const width = realWidth - margin.left - margin.right; // getting currentX data values, both original and filtered. const currentX = useMemo(() => { - if (colsStatus === 'success' && allColumns) { + if (allColumns) { if (config.color && allColumns.colorColVals) { return { allValues: allColumns.numColVals[0].resolvedValues, @@ -123,11 +75,11 @@ export function Hexplot({ config, columns, selectionCallback = () => null, selec } return null; - }, [allColumns, config.color, colsStatus, filteredCategories]); + }, [allColumns, config.color, filteredCategories]); // getting currentY data values, both original and filtered. const currentY = useMemo(() => { - if (colsStatus === 'success' && allColumns) { + if (allColumns) { if (config.color && allColumns.colorColVals) { return { allValues: allColumns.numColVals[1].resolvedValues, @@ -143,23 +95,7 @@ export function Hexplot({ config, columns, selectionCallback = () => null, selec } return null; - }, [allColumns, colsStatus, config.color, filteredCategories]); - - // resize observer for setting size of the svg and updating on size change - useEffect(() => { - const ro = new ResizeObserver((entries: ResizeObserverEntry[]) => { - setHeight(entries[0].contentRect.height - margin.top - margin.bottom); - setWidth(entries[0].contentRect.width - margin.left - margin.right); - }); - - if (ref) { - ro.observe(ref.current); - } - - return () => { - ro.disconnect(); - }; - }, [margin]); + }, [allColumns, config.color, filteredCategories]); // create x scale const xScale = useMemo(() => { @@ -230,32 +166,28 @@ export function Hexplot({ config, columns, selectionCallback = () => null, selec // simple radius scale for the hexes const radiusScale = useMemo(() => { - if (colsStatus === 'success') { - const [min, max] = d3v7.extent(hexes, (h) => h.length); + const [min, max] = d3v7.extent(hexes, (h) => h.length); - return d3v7 - .scaleLinear() - .domain([min, max]) - .range([config.hexRadius / 2, config.hexRadius]); - } + return d3v7 + .scaleLinear() + .domain([min, max]) + .range([config.hexRadius / 2, config.hexRadius]); return null; - }, [colsStatus, hexes, config.hexRadius]); + }, [hexes, config.hexRadius]); // simple opacity scale for the hexes const opacityScale = useMemo(() => { - if (colsStatus === 'success') { - const [min, max] = d3v7.extent(hexes, (h) => h.length); + const [min, max] = d3v7.extent(hexes, (h) => h.length); - return d3v7.scaleLinear().domain([min, max]).range([0.1, 1]); - } + return d3v7.scaleLinear().domain([min, max]).range([0.1, 1]); return null; - }, [colsStatus, hexes]); + }, [hexes]); // Create a default color scale const colorScale = useMemo(() => { - if (colsStatus !== 'success' || !currentColorColumn?.allValues) { + if (!currentColorColumn?.allValues) { return null; } @@ -264,7 +196,7 @@ export function Hexplot({ config, columns, selectionCallback = () => null, selec return d3v7 .scaleOrdinal(allColumns.colorColVals.color ? Object.keys(allColumns.colorColVals.color) : d3v7.schemeCategory10) .domain(allColumns.colorColVals.color ? Object.values(allColumns.colorColVals.color) : Array.from(new Set(colorOptions))); - }, [allColumns, colsStatus, currentColorColumn]); + }, [allColumns, currentColorColumn]); // memoize the actual hexes since they do not need to change on zoom/drag const hexObjects = React.useMemo(() => { @@ -381,7 +313,7 @@ export function Hexplot({ config, columns, selectionCallback = () => null, selec }, [width, height, id, hexes, selectionCallback, config.dragMode, xScale, yScale, margin]); return ( - + null, selec pointerEvents={config.dragMode === EScatterSelectSettings.PAN ? 'auto' : 'none'} /> -
- - filteredCategories.includes(s) - ? setFilteredCategories(filteredCategories.filter((f) => f !== s)) - : setFilteredCategories([...filteredCategories, s]) - } - height={200} - /> -
); diff --git a/src/vis/hexbin/SingleHex.tsx b/src/vis/hexbin/SingleHex.tsx index 1fc703023..2fe25fc7a 100644 --- a/src/vis/hexbin/SingleHex.tsx +++ b/src/vis/hexbin/SingleHex.tsx @@ -4,7 +4,7 @@ import * as d3v7 from 'd3v7'; import { useMemo } from 'react'; import { PieChart } from './PieChart'; import { cutHex } from './utils'; -import { EHexbinOptions } from '../interfaces'; +import { EHexbinOptions } from './interfaces'; export interface SingleHexProps { hexbinOption: EHexbinOptions; diff --git a/src/vis/hexbin/interfaces.ts b/src/vis/hexbin/interfaces.ts new file mode 100644 index 000000000..976d29154 --- /dev/null +++ b/src/vis/hexbin/interfaces.ts @@ -0,0 +1,22 @@ +import { BaseVisConfig, ColumnInfo, EScatterSelectSettings, ESupportedPlotlyVis } from '../interfaces'; + +export interface IHexbinConfig extends BaseVisConfig { + type: ESupportedPlotlyVis.HEXBIN; + numColumnsSelected: ColumnInfo[]; + color: ColumnInfo | null; + hexRadius: number; + isOpacityScale: boolean; + isSizeScale: boolean; + dragMode: EScatterSelectSettings; + hexbinOptions: EHexbinOptions; +} + +export enum EHexbinOptions { + COLOR = 'Color', + PIE = 'Pie', + BINS = 'Bins', +} + +export function isHexbinConfig(vis: BaseVisConfig): vis is IHexbinConfig { + return vis.type === ESupportedPlotlyVis.HEXBIN; +} diff --git a/src/vis/hexbin/utils.tsx b/src/vis/hexbin/utils.tsx index 606f9d738..32f73f8e1 100644 --- a/src/vis/hexbin/utils.tsx +++ b/src/vis/hexbin/utils.tsx @@ -1,10 +1,8 @@ import merge from 'lodash/merge'; import { resolveColumnValues, resolveSingleColumn } from '../general/layoutUtils'; import { - BaseVisConfig, ColumnInfo, EColumnTypes, - EHexbinOptions, EScatterSelectSettings, ESupportedPlotlyVis, VisCategoricalValue, @@ -12,17 +10,7 @@ import { VisNumericalColumn, VisNumericalValue, } from '../interfaces'; - -export interface IHexbinConfig extends BaseVisConfig { - type: ESupportedPlotlyVis.HEXBIN; - numColumnsSelected: ColumnInfo[]; - color: ColumnInfo | null; - hexRadius: number; - isOpacityScale: boolean; - isSizeScale: boolean; - dragMode: EScatterSelectSettings; - hexbinOptions: EHexbinOptions; -} +import { EHexbinOptions, IHexbinConfig } from './interfaces'; export const defaultDensityConfig: IHexbinConfig = { type: ESupportedPlotlyVis.HEXBIN, @@ -52,11 +40,7 @@ export function hexinbMergeDefaultConfig(columns: VisColumn[], config: IHexbinCo return merged; } -export async function getHexData( - columns: VisColumn[], - numColumnsSelected: ColumnInfo[], - colorColumn: ColumnInfo | null, -): Promise<{ +export type ResolvedHexValues = { numColVals: { resolvedValues: (VisNumericalValue | VisCategoricalValue)[]; type: EColumnTypes.NUMERICAL | EColumnTypes.CATEGORICAL; @@ -68,8 +52,10 @@ export async function getHexData( color?: Record; info: ColumnInfo; }; -}> { - const numCols: VisNumericalColumn[] = [columns[0] as VisNumericalColumn, columns[1] as VisNumericalColumn]; +}; + +export async function getHexData(columns: VisColumn[], numColumnsSelected: ColumnInfo[], colorColumn: ColumnInfo | null): Promise { + const numCols: VisNumericalColumn[] = columns.filter((col) => numColumnsSelected.find((e) => e.id === col.info.id)) as VisNumericalColumn[]; const numColVals = await resolveColumnValues(numCols); diff --git a/src/vis/index.ts b/src/vis/index.ts index 09f4efb4c..e967b592b 100644 --- a/src/vis/index.ts +++ b/src/vis/index.ts @@ -10,3 +10,13 @@ export * from './VisSidebar'; export * from './general'; export * from './interfaces'; export * from './sidebar'; + +// Export interfaces ONLY since else the lazy loading will break +export * from './bar/interfaces'; +export * from './correlation/interfaces'; +export * from './heatmap/interfaces'; +export * from './violin/interfaces'; +export * from './hexbin/interfaces'; +export * from './scatter/interfaces'; +export * from './raincloud/interfaces'; +export * from './sankey/interfaces'; diff --git a/src/vis/interfaces.ts b/src/vis/interfaces.ts index 35fb58f10..c42dd3be2 100644 --- a/src/vis/interfaces.ts +++ b/src/vis/interfaces.ts @@ -6,7 +6,6 @@ export enum ESupportedPlotlyVis { BAR = 'Bar chart', HEXBIN = 'Hexbin plot', HEATMAP = 'Heatmap plot', - PARALLEL_COORDINATES = 'Parallel plot', RAINCLOUD = 'Raincloud plot', SANKEY = 'Sankey', CORRELATION = 'Correlation plot', @@ -16,12 +15,6 @@ export interface BaseVisConfig { type: string; } -export enum EHexbinOptions { - COLOR = 'Color', - PIE = 'Pie', - BINS = 'Bins', -} - export enum EAggregateTypes { COUNT = 'Count', MIN = 'Minimum', @@ -35,12 +28,6 @@ export enum EColumnTypes { CATEGORICAL = 'Categorical', } -export enum EGeneralFormType { - DROPDOWN = 'Dropdown', - BUTTON = 'Button', - SLIDER = 'Slider', -} - export enum EFilterOptions { IN = 'Filter in', OUT = 'Filter out', @@ -59,49 +46,11 @@ export enum EScatterSelectSettings { PAN = 'pan', } -export enum ECorrelationPlotMode { - PVALUE = 'p-value', - CORRELATION = 'correlation', -} - -export enum ECorrelationType { - PEARSON = 'Pearson', - SPEARMAN = 'Spearman', -} - export enum EScaleType { LINEAR = 'Linear', LOG = 'Log', } -export enum ECloudType { - SPLIT_VIOLIN = 'Split violin', - HEATMAP = 'Heatmap', - HISTOGRAM = 'Histogram', -} - -export enum ELightningType { - MEAN_AND_DEV = 'Mean and deviation', - MEDIAN_AND_DEV = 'Median and deviation', - MEAN = 'Mean', - BOXPLOT = 'Boxplot', -} - -export enum ERainType { - DOTPLOT = 'Dot plot', - BEESWARM = 'Beeswarm', - WHEATPLOT = 'Wheat plot', - STRIPPLOT = 'Strip plot', -} - -export enum ESortTypes { - NONE = 'NONE', - CAT_ASC = 'CAT_ASC', - CAT_DESC = 'CAT_DESC', - COUNT_ASC = 'COUNT_ASC', - COUNT_DESC = 'COUNT_DESC', -} - type ValueGetter = () => T | Promise; export interface IVisCommonValue { diff --git a/src/vis/sidebar/AggregateRainSwitch.tsx b/src/vis/raincloud/AggregateRainSwitch.tsx similarity index 66% rename from src/vis/sidebar/AggregateRainSwitch.tsx rename to src/vis/raincloud/AggregateRainSwitch.tsx index 5820a8715..335e0d399 100644 --- a/src/vis/sidebar/AggregateRainSwitch.tsx +++ b/src/vis/raincloud/AggregateRainSwitch.tsx @@ -7,5 +7,5 @@ interface AggregateRainSwitchProps { } export function AggregateRainSwitch({ callback, currentValue }: AggregateRainSwitchProps) { - return callback(event.currentTarget.checked)} label="Aggregate rain" />; + return callback(event.currentTarget.checked)} label="Aggregate rain" />; } diff --git a/src/vis/raincloud/Raincloud.tsx b/src/vis/raincloud/Raincloud.tsx index ecc986135..6c06e3aa6 100644 --- a/src/vis/raincloud/Raincloud.tsx +++ b/src/vis/raincloud/Raincloud.tsx @@ -2,7 +2,7 @@ import { Box, Container } from '@mantine/core'; import { useResizeObserver } from '@mantine/hooks'; import { op, table } from 'arquero'; import React, { useCallback, useMemo, useState } from 'react'; -import { ColumnInfo, ECloudType, EColumnTypes, ELightningType, ERainType, VisCategoricalValue, VisNumericalValue } from '../interfaces'; +import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../interfaces'; import { XAxis } from '../hexbin/XAxis'; import { Brush } from './Brush'; @@ -10,6 +10,7 @@ import { Heatmap } from './cloud/Heatmap'; import { Histogram } from './cloud/Histogram'; import { SplitViolin } from './cloud/SplitViolin'; import { useXScale } from './hooks/useXScale'; +import { ECloudType, ELightningType, ERainType, IRaincloudConfig, IRaindropCircle } from './interfaces'; import { Boxplot } from './lightning/Boxplot'; import { Mean } from './lightning/Mean'; import { MeanAndInterval } from './lightning/MeanAndInterval'; @@ -19,7 +20,6 @@ import { Circle } from './rain/Circle'; import { DotPlot } from './rain/DotPlot'; import { StripPlot } from './rain/StripPlot'; import { WheatPlot } from './rain/WheatPlot'; -import { IRaincloudConfig, IRaindropCircle } from './utils'; const margin = { top: 0, diff --git a/src/vis/sidebar/RaincloudCloudSelect.tsx b/src/vis/raincloud/RaincloudCloudSelect.tsx similarity index 89% rename from src/vis/sidebar/RaincloudCloudSelect.tsx rename to src/vis/raincloud/RaincloudCloudSelect.tsx index 9dd5fa1f0..1f31c3c80 100644 --- a/src/vis/sidebar/RaincloudCloudSelect.tsx +++ b/src/vis/raincloud/RaincloudCloudSelect.tsx @@ -1,7 +1,6 @@ import { Select } from '@mantine/core'; import * as React from 'react'; -import { i18n } from '../../i18n'; -import { ECloudType } from '../interfaces'; +import { ECloudType } from './interfaces'; interface HexbinOptionSelectProps { callback: (c: ECloudType) => void; diff --git a/src/vis/raincloud/RaincloudGrid.tsx b/src/vis/raincloud/RaincloudGrid.tsx index 178dd6709..e557af0cf 100644 --- a/src/vis/raincloud/RaincloudGrid.tsx +++ b/src/vis/raincloud/RaincloudGrid.tsx @@ -1,9 +1,11 @@ import { SimpleGrid } from '@mantine/core'; import React from 'react'; import { VisColumn } from '../interfaces'; -import { IRaincloudConfig, getRaincloudData } from './utils'; +import { IRaincloudConfig } from './interfaces'; +import { getRaincloudData } from './utils'; import { useAsync } from '../../hooks/useAsync'; +import { InvalidCols } from '../general/InvalidCols'; import { Raincloud } from './Raincloud'; export function RaincloudGrid({ @@ -21,10 +23,13 @@ export function RaincloudGrid({ return ( - {data && + {data && config.numColumnsSelected.length >= 1 ? ( data.numColVals.map((numCol) => { return ; - })} + }) + ) : ( + + )} ); } diff --git a/src/vis/sidebar/RaincloudLightningSelect.tsx b/src/vis/raincloud/RaincloudLightningSelect.tsx similarity index 90% rename from src/vis/sidebar/RaincloudLightningSelect.tsx rename to src/vis/raincloud/RaincloudLightningSelect.tsx index b3f73e56b..c0eeec828 100644 --- a/src/vis/sidebar/RaincloudLightningSelect.tsx +++ b/src/vis/raincloud/RaincloudLightningSelect.tsx @@ -1,7 +1,6 @@ import { Select } from '@mantine/core'; import * as React from 'react'; -import { i18n } from '../../i18n'; -import { ELightningType } from '../interfaces'; +import { ELightningType } from './interfaces'; interface HexbinOptionSelectProps { callback: (c: ELightningType) => void; diff --git a/src/vis/sidebar/RaincloudRainSelect.tsx b/src/vis/raincloud/RaincloudRainSelect.tsx similarity index 89% rename from src/vis/sidebar/RaincloudRainSelect.tsx rename to src/vis/raincloud/RaincloudRainSelect.tsx index ec116fd1d..cbba136a7 100644 --- a/src/vis/sidebar/RaincloudRainSelect.tsx +++ b/src/vis/raincloud/RaincloudRainSelect.tsx @@ -1,7 +1,6 @@ import { Select } from '@mantine/core'; import * as React from 'react'; -import { i18n } from '../../i18n'; -import { ERainType } from '../interfaces'; +import { ERainType } from './interfaces'; interface HexbinOptionSelectProps { callback: (c: ERainType) => void; diff --git a/src/vis/raincloud/RaincloudVis.tsx b/src/vis/raincloud/RaincloudVis.tsx index 2f1f9b572..04055d1ef 100644 --- a/src/vis/raincloud/RaincloudVis.tsx +++ b/src/vis/raincloud/RaincloudVis.tsx @@ -4,7 +4,7 @@ import { useRef } from 'react'; import { ICommonVisProps } from '../interfaces'; import { RaincloudGrid } from './RaincloudGrid'; -import { IRaincloudConfig } from './utils'; +import { IRaincloudConfig } from './interfaces'; export function RaincloudVis({ config, columns, selectionCallback = () => null, selectedMap = {} }: ICommonVisProps) { const ref = useRef(); diff --git a/src/vis/raincloud/RaincloudVisSidebar.tsx b/src/vis/raincloud/RaincloudVisSidebar.tsx index 74ccc0994..22e38e9f5 100644 --- a/src/vis/raincloud/RaincloudVisSidebar.tsx +++ b/src/vis/raincloud/RaincloudVisSidebar.tsx @@ -1,13 +1,11 @@ -import { Container, Divider, Stack } from '@mantine/core'; import * as React from 'react'; -import { ColumnInfo, ESupportedPlotlyVis, ICommonVisSideBarProps, VisColumn } from '../interfaces'; -import { AggregateRainSwitch } from '../sidebar/AggregateRainSwitch'; +import { ColumnInfo, ICommonVisSideBarProps, VisColumn } from '../interfaces'; import { NumericalColumnSelect } from '../sidebar/NumericalColumnSelect'; -import { RaincloudCloudSelect } from '../sidebar/RaincloudCloudSelect'; -import { RaincloudLightningSelect } from '../sidebar/RaincloudLightningSelect'; -import { RaincloudRainSelect } from '../sidebar/RaincloudRainSelect'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { IRaincloudConfig } from './utils'; +import { AggregateRainSwitch } from './AggregateRainSwitch'; +import { RaincloudCloudSelect } from './RaincloudCloudSelect'; +import { RaincloudLightningSelect } from './RaincloudLightningSelect'; +import { RaincloudRainSelect } from './RaincloudRainSelect'; +import { IRaincloudConfig } from './interfaces'; export function RaincloudVisSidebar({ config, @@ -19,20 +17,16 @@ export function RaincloudVisSidebar({ setConfig: (config: IRaincloudConfig) => void; } & ICommonVisSideBarProps) { return ( - - - setConfig({ ...(config as any), type })} currentSelected={config.type} /> - - setConfig({ ...config, numColumnsSelected })} - columns={columns} - currentSelected={config.numColumnsSelected || []} - /> - setConfig({ ...config, cloudType: cloud })} currentSelected={config.cloudType} /> - setConfig({ ...config, lightningType: lightning })} currentSelected={config.lightningType} /> - setConfig({ ...config, rainType: rain })} currentSelected={config.rainType} /> - setConfig({ ...config, aggregateRain })} currentValue={config.aggregateRain} /> - - + <> + setConfig({ ...config, numColumnsSelected })} + columns={columns} + currentSelected={config.numColumnsSelected || []} + /> + setConfig({ ...config, cloudType: cloud })} currentSelected={config.cloudType} /> + setConfig({ ...config, lightningType: lightning })} currentSelected={config.lightningType} /> + setConfig({ ...config, rainType: rain })} currentSelected={config.rainType} /> + setConfig({ ...config, aggregateRain })} currentValue={config.aggregateRain} /> + ); } diff --git a/src/vis/raincloud/cloud/Heatmap.tsx b/src/vis/raincloud/cloud/Heatmap.tsx index 54d92594c..ae9bd0d13 100644 --- a/src/vis/raincloud/cloud/Heatmap.tsx +++ b/src/vis/raincloud/cloud/Heatmap.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from 'react'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; import { useKdeCalc } from './useKdeCalc'; const margin = { diff --git a/src/vis/raincloud/cloud/Histogram.tsx b/src/vis/raincloud/cloud/Histogram.tsx index 2b9d6ee76..479f3da48 100644 --- a/src/vis/raincloud/cloud/Histogram.tsx +++ b/src/vis/raincloud/cloud/Histogram.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from 'react'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; import { useKdeCalc } from './useKdeCalc'; const margin = { diff --git a/src/vis/raincloud/cloud/SplitViolin.tsx b/src/vis/raincloud/cloud/SplitViolin.tsx index a39980c06..d8fb716fd 100644 --- a/src/vis/raincloud/cloud/SplitViolin.tsx +++ b/src/vis/raincloud/cloud/SplitViolin.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from 'react'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; import { useKdeCalc } from './useKdeCalc'; const margin = { diff --git a/src/vis/raincloud/interfaces.ts b/src/vis/raincloud/interfaces.ts new file mode 100644 index 000000000..817e0f10f --- /dev/null +++ b/src/vis/raincloud/interfaces.ts @@ -0,0 +1,40 @@ +import { BaseVisConfig, ColumnInfo, ESupportedPlotlyVis } from '../interfaces'; + +export interface IRaincloudConfig { + type: ESupportedPlotlyVis.RAINCLOUD; + numColumnsSelected: ColumnInfo[]; + cloudType: ECloudType; + rainType: ERainType; + lightningType: ELightningType; + aggregateRain: boolean; +} + +export interface IRaindropCircle { + id: string[]; + x: number; + y: number; +} + +export enum ERainType { + DOTPLOT = 'Dot plot', + BEESWARM = 'Beeswarm', + WHEATPLOT = 'Wheat plot', + STRIPPLOT = 'Strip plot', +} + +export enum ECloudType { + SPLIT_VIOLIN = 'Split violin', + HEATMAP = 'Heatmap', + HISTOGRAM = 'Histogram', +} + +export enum ELightningType { + MEAN_AND_DEV = 'Mean and deviation', + MEDIAN_AND_DEV = 'Median and deviation', + MEAN = 'Mean', + BOXPLOT = 'Boxplot', +} + +export function isRaincloudConfig(s: BaseVisConfig): s is IRaincloudConfig { + return s.type === ESupportedPlotlyVis.RAINCLOUD; +} diff --git a/src/vis/raincloud/lightning/Boxplot.tsx b/src/vis/raincloud/lightning/Boxplot.tsx index ef7760093..5a59bed5b 100644 --- a/src/vis/raincloud/lightning/Boxplot.tsx +++ b/src/vis/raincloud/lightning/Boxplot.tsx @@ -4,7 +4,7 @@ import React, { useMemo } from 'react'; import ColumnTable from 'arquero/dist/types/table/column-table'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 0, diff --git a/src/vis/raincloud/lightning/Mean.tsx b/src/vis/raincloud/lightning/Mean.tsx index 7cd98494b..fd3e60b5c 100644 --- a/src/vis/raincloud/lightning/Mean.tsx +++ b/src/vis/raincloud/lightning/Mean.tsx @@ -4,7 +4,7 @@ import React, { useMemo } from 'react'; import ColumnTable from 'arquero/dist/types/table/column-table'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 0, diff --git a/src/vis/raincloud/lightning/MeanAndInterval.tsx b/src/vis/raincloud/lightning/MeanAndInterval.tsx index d02864cc2..1db5e558f 100644 --- a/src/vis/raincloud/lightning/MeanAndInterval.tsx +++ b/src/vis/raincloud/lightning/MeanAndInterval.tsx @@ -4,7 +4,7 @@ import React, { useMemo } from 'react'; import ColumnTable from 'arquero/dist/types/table/column-table'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 0, diff --git a/src/vis/raincloud/lightning/MedianAndInterval.tsx b/src/vis/raincloud/lightning/MedianAndInterval.tsx index c5ae05c7a..d727ea808 100644 --- a/src/vis/raincloud/lightning/MedianAndInterval.tsx +++ b/src/vis/raincloud/lightning/MedianAndInterval.tsx @@ -4,7 +4,7 @@ import React, { useMemo } from 'react'; import ColumnTable from 'arquero/dist/types/table/column-table'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 0, diff --git a/src/vis/raincloud/rain/BeeSwarm.tsx b/src/vis/raincloud/rain/BeeSwarm.tsx index f45aac9ad..55ce5099f 100644 --- a/src/vis/raincloud/rain/BeeSwarm.tsx +++ b/src/vis/raincloud/rain/BeeSwarm.tsx @@ -5,7 +5,7 @@ import forceBoundary from 'd3-force-boundary'; import * as d3 from 'd3v7'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 30, diff --git a/src/vis/raincloud/rain/Circle.tsx b/src/vis/raincloud/rain/Circle.tsx index 93821f5dd..987e332e5 100644 --- a/src/vis/raincloud/rain/Circle.tsx +++ b/src/vis/raincloud/rain/Circle.tsx @@ -1,7 +1,7 @@ import { Tooltip } from '@mantine/core'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useSpring, easings, animated } from 'react-spring'; -import { ERainType } from '../../interfaces'; +import { ERainType } from '../interfaces'; export function Circle({ x, diff --git a/src/vis/raincloud/rain/DotPlot.tsx b/src/vis/raincloud/rain/DotPlot.tsx index 24425237a..26076eec1 100644 --- a/src/vis/raincloud/rain/DotPlot.tsx +++ b/src/vis/raincloud/rain/DotPlot.tsx @@ -4,7 +4,7 @@ import { bin, op } from 'arquero'; import ColumnTable from 'arquero/dist/types/table/column-table'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 30, diff --git a/src/vis/raincloud/rain/StripPlot.tsx b/src/vis/raincloud/rain/StripPlot.tsx index ab5d860c3..7feb23c3f 100644 --- a/src/vis/raincloud/rain/StripPlot.tsx +++ b/src/vis/raincloud/rain/StripPlot.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react'; import ColumnTable from 'arquero/dist/types/table/column-table'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 30, diff --git a/src/vis/raincloud/rain/WheatPlot.tsx b/src/vis/raincloud/rain/WheatPlot.tsx index 0565d4084..97334a31b 100644 --- a/src/vis/raincloud/rain/WheatPlot.tsx +++ b/src/vis/raincloud/rain/WheatPlot.tsx @@ -4,7 +4,7 @@ import { bin, op } from 'arquero'; import ColumnTable from 'arquero/dist/types/table/column-table'; import { ColumnInfo, EColumnTypes, VisCategoricalValue, VisNumericalValue } from '../../interfaces'; import { useXScale } from '../hooks/useXScale'; -import { IRaincloudConfig } from '../utils'; +import { IRaincloudConfig } from '../interfaces'; const margin = { top: 30, diff --git a/src/vis/raincloud/utils.ts b/src/vis/raincloud/utils.ts index f3a11390b..01dee6717 100644 --- a/src/vis/raincloud/utils.ts +++ b/src/vis/raincloud/utils.ts @@ -1,30 +1,7 @@ import { merge } from 'lodash'; import { resolveColumnValues } from '../general/layoutUtils'; -import { - BaseVisConfig, - ColumnInfo, - ECloudType, - EColumnTypes, - ELightningType, - ERainType, - ESupportedPlotlyVis, - VisCategoricalValue, - VisColumn, - VisNumericalValue, -} from '../interfaces'; - -export interface IRaincloudConfig { - type: ESupportedPlotlyVis.RAINCLOUD; - numColumnsSelected: ColumnInfo[]; - cloudType: ECloudType; - rainType: ERainType; - lightningType: ELightningType; - aggregateRain: boolean; -} - -export function isRaincloud(s: BaseVisConfig): s is IRaincloudConfig { - return s.type === ESupportedPlotlyVis.RAINCLOUD; -} +import { ColumnInfo, EColumnTypes, ESupportedPlotlyVis, VisCategoricalValue, VisColumn, VisNumericalValue } from '../interfaces'; +import { ECloudType, ELightningType, ERainType, IRaincloudConfig } from './interfaces'; const defaultConfig: IRaincloudConfig = { type: ESupportedPlotlyVis.RAINCLOUD, @@ -55,9 +32,3 @@ export async function getRaincloudData( return { numColVals }; } - -export interface IRaindropCircle { - id: string[]; - x: number; - y: number; -} diff --git a/src/vis/sankey/SankeyVis.tsx b/src/vis/sankey/SankeyVis.tsx index 1e2cf8484..ac5f1b3e2 100644 --- a/src/vis/sankey/SankeyVis.tsx +++ b/src/vis/sankey/SankeyVis.tsx @@ -2,9 +2,10 @@ import { Group, MantineTheme, Stack, useMantineTheme } from '@mantine/core'; import * as React from 'react'; import { useAsync } from '../../hooks/useAsync'; import { PlotlyComponent } from '../../plotly'; +import { InvalidCols } from '../general/InvalidCols'; import { resolveColumnValues } from '../general/layoutUtils'; import { ICommonVisProps, VisCategoricalColumn, VisColumn } from '../interfaces'; -import { ISankeyConfig } from './utils'; +import { ISankeyConfig } from './interfaces'; /** * Performs the data transformation that maps the fetched data to @@ -203,6 +204,7 @@ export function SankeyVis({ config, columns, selectedList, selectionCallback, di ) : ( -

Select at least 2 categorical attributes.

+ )} diff --git a/src/vis/sankey/SankeyVisSidebar.tsx b/src/vis/sankey/SankeyVisSidebar.tsx index d80be3cf3..c0f2e1be8 100644 --- a/src/vis/sankey/SankeyVisSidebar.tsx +++ b/src/vis/sankey/SankeyVisSidebar.tsx @@ -1,9 +1,7 @@ -import { Container, Stack } from '@mantine/core'; import * as React from 'react'; -import { ColumnInfo, ESupportedPlotlyVis, ICommonVisSideBarProps } from '../interfaces'; +import { ColumnInfo, ICommonVisSideBarProps } from '../interfaces'; import { CategoricalColumnSelect } from '../sidebar/CategoricalColumnSelect'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { ISankeyConfig } from './utils'; +import { ISankeyConfig } from './interfaces'; export function SankeyVisSidebar({ config, @@ -13,15 +11,10 @@ export function SankeyVisSidebar({ style: { width = '20em', ...style } = {}, }: ICommonVisSideBarProps) { return ( - - - setConfig({ ...(config as any), type })} currentSelected={config.type} /> - setConfig({ ...config, catColumnsSelected })} - columns={columns} - currentSelected={config.catColumnsSelected || []} - /> - - + setConfig({ ...config, catColumnsSelected })} + columns={columns} + currentSelected={config.catColumnsSelected || []} + /> ); } diff --git a/src/vis/sankey/interfaces.ts b/src/vis/sankey/interfaces.ts new file mode 100644 index 000000000..ceeb29578 --- /dev/null +++ b/src/vis/sankey/interfaces.ts @@ -0,0 +1,10 @@ +import { BaseVisConfig, ColumnInfo, ESupportedPlotlyVis } from '../interfaces'; + +export interface ISankeyConfig extends BaseVisConfig { + type: ESupportedPlotlyVis.SANKEY; + catColumnsSelected: ColumnInfo[]; +} + +export function isSankeyConfig(s: BaseVisConfig): s is ISankeyConfig { + return s.type === ESupportedPlotlyVis.SANKEY; +} diff --git a/src/vis/sankey/utils.ts b/src/vis/sankey/utils.ts index dac88ccf9..442d01ea1 100644 --- a/src/vis/sankey/utils.ts +++ b/src/vis/sankey/utils.ts @@ -1,10 +1,6 @@ import { merge } from 'lodash'; -import { BaseVisConfig, ColumnInfo, ESupportedPlotlyVis, VisColumn } from '../interfaces'; - -export interface ISankeyConfig extends BaseVisConfig { - type: ESupportedPlotlyVis.SANKEY; - catColumnsSelected: ColumnInfo[]; -} +import { ESupportedPlotlyVis, VisColumn } from '../interfaces'; +import { ISankeyConfig } from './interfaces'; const defaultConfig: ISankeyConfig = { type: ESupportedPlotlyVis.SANKEY, diff --git a/src/vis/sidebar/ColorSelect.tsx b/src/vis/scatter/ColorSelect.tsx similarity index 90% rename from src/vis/sidebar/ColorSelect.tsx rename to src/vis/scatter/ColorSelect.tsx index 6b2d88e5a..c112bb5e1 100644 --- a/src/vis/sidebar/ColorSelect.tsx +++ b/src/vis/scatter/ColorSelect.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Select, Stack } from '@mantine/core'; import { ColumnInfo, EColumnTypes, VisColumn, ENumericalColorScaleType } from '../interfaces'; -import { SelectDropdownItem, getCol } from './utils'; -import { NumericalColorButtons } from './NumericalColorButtons'; +import { SelectDropdownItem, getCol } from '../sidebar/utils'; +import { NumericalColorButtons } from '../sidebar/NumericalColorButtons'; interface ColorSelectProps { callback: (c: ColumnInfo) => void; diff --git a/src/vis/scatter/OpacitySlider.tsx b/src/vis/scatter/OpacitySlider.tsx new file mode 100644 index 000000000..1ad4fa79a --- /dev/null +++ b/src/vis/scatter/OpacitySlider.tsx @@ -0,0 +1,38 @@ +import { Input, Slider } from '@mantine/core'; +import debounce from 'lodash/debounce'; +import { useMemo } from 'react'; + +import * as React from 'react'; +import { useSyncedRef } from '../../hooks'; + +interface OpacitySliderProps { + callback: (n: number) => void; + currentValue: number; +} + +export function OpacitySlider({ callback, currentValue }: OpacitySliderProps) { + const syncedCallback = useSyncedRef(callback); + + const debouncedCallback = useMemo(() => { + return debounce((n: number) => syncedCallback.current?.(n), 10); + }, [syncedCallback]); + + return ( + + { + debouncedCallback(n); + }} + /> + + ); +} diff --git a/src/vis/scatter/ScatterVis.tsx b/src/vis/scatter/ScatterVis.tsx index b1cfa8a06..2c13b4227 100644 --- a/src/vis/scatter/ScatterVis.tsx +++ b/src/vis/scatter/ScatterVis.tsx @@ -10,7 +10,8 @@ import { InvalidCols } from '../general/InvalidCols'; import { beautifyLayout } from '../general/layoutUtils'; import { EScatterSelectSettings, ICommonVisProps } from '../interfaces'; import { BrushOptionButtons } from '../sidebar/BrushOptionButtons'; -import { IScatterConfig, createScatterTraces } from './utils'; +import { createScatterTraces } from './utils'; +import { IScatterConfig } from './interfaces'; export function ScatterVis({ config, diff --git a/src/vis/scatter/ScatterVisSidebar.tsx b/src/vis/scatter/ScatterVisSidebar.tsx index d5af431bf..49f2c36b2 100644 --- a/src/vis/scatter/ScatterVisSidebar.tsx +++ b/src/vis/scatter/ScatterVisSidebar.tsx @@ -1,15 +1,13 @@ -import { Container, Divider, Stack } from '@mantine/core'; import merge from 'lodash/merge'; import * as React from 'react'; import { useMemo } from 'react'; -import { ColumnInfo, EColumnTypes, ENumericalColorScaleType, ESupportedPlotlyVis, ICommonVisSideBarProps } from '../interfaces'; -import { ColorSelect } from '../sidebar/ColorSelect'; +import { ColumnInfo, EColumnTypes, ENumericalColorScaleType, ICommonVisSideBarProps } from '../interfaces'; import { FilterButtons } from '../sidebar/FilterButtons'; import { NumericalColumnSelect } from '../sidebar/NumericalColumnSelect'; -import { OpacitySlider } from '../sidebar/OpacitySlider'; import { SingleColumnSelect } from '../sidebar/SingleColumnSelect'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { IScatterConfig } from './utils'; +import { ColorSelect } from './ColorSelect'; +import { OpacitySlider } from './OpacitySlider'; +import { IScatterConfig } from './interfaces'; const defaultConfig = { color: { @@ -32,55 +30,44 @@ export function ScatterVisSidebar({ config, optionsConfig, columns, filterCallba }, [optionsConfig]); return ( - - - setConfig({ ...(config as any), type })} currentSelected={config.type} /> - - setConfig({ ...config, numColumnsSelected })} - columns={columns} - currentSelected={config.numColumnsSelected || []} - /> - + <> + setConfig({ ...config, numColumnsSelected })} + columns={columns} + currentSelected={config.numColumnsSelected || []} + /> - - {mergedOptionsConfig.color.enable - ? mergedOptionsConfig.color.customComponent || ( - setConfig({ ...config, color })} - numTypeCallback={(numColorScaleType: ENumericalColorScaleType) => setConfig({ ...config, numColorScaleType })} - currentNumType={config.numColorScaleType} - columns={columns} - currentSelected={config.color} - /> - ) - : null} - {mergedOptionsConfig.shape.enable - ? mergedOptionsConfig.shape.customComponent || ( - setConfig({ ...config, shape })} - columns={columns} - currentSelected={config.shape} - /> - ) - : null} - - - - { - if (config.alphaSliderVal !== e) { - setConfig({ ...config, alphaSliderVal: e }); - } - }} - currentValue={config.alphaSliderVal} - /> - - - {mergedOptionsConfig.filter.enable ? mergedOptionsConfig.filter.customComponent || : null} - - + {mergedOptionsConfig.color.enable + ? mergedOptionsConfig.color.customComponent || ( + setConfig({ ...config, color })} + numTypeCallback={(numColorScaleType: ENumericalColorScaleType) => setConfig({ ...config, numColorScaleType })} + currentNumType={config.numColorScaleType} + columns={columns} + currentSelected={config.color} + /> + ) + : null} + {mergedOptionsConfig.shape.enable + ? mergedOptionsConfig.shape.customComponent || ( + setConfig({ ...config, shape })} + columns={columns} + currentSelected={config.shape} + /> + ) + : null} + { + if (config.alphaSliderVal !== e) { + setConfig({ ...config, alphaSliderVal: e }); + } + }} + currentValue={config.alphaSliderVal} + /> + {mergedOptionsConfig.filter.enable ? mergedOptionsConfig.filter.customComponent || : null} + ); } diff --git a/src/vis/scatter/interfaces.ts b/src/vis/scatter/interfaces.ts new file mode 100644 index 000000000..7f495fe55 --- /dev/null +++ b/src/vis/scatter/interfaces.ts @@ -0,0 +1,16 @@ +import { BaseVisConfig, ColumnInfo, ENumericalColorScaleType, EScatterSelectSettings, ESupportedPlotlyVis } from '../interfaces'; + +export interface IScatterConfig extends BaseVisConfig { + type: ESupportedPlotlyVis.SCATTER; + numColumnsSelected: ColumnInfo[]; + color: ColumnInfo | null; + numColorScaleType: ENumericalColorScaleType; + shape: ColumnInfo | null; + dragMode: EScatterSelectSettings; + alphaSliderVal: number; + sizeSliderVal: number; +} + +export function isScatterConfig(s: BaseVisConfig): s is IScatterConfig { + return s.type === ESupportedPlotlyVis.SCATTER; +} diff --git a/src/vis/scatter/utils.ts b/src/vis/scatter/utils.ts index 5c5589a91..38d589472 100644 --- a/src/vis/scatter/utils.ts +++ b/src/vis/scatter/utils.ts @@ -5,7 +5,6 @@ import { getCssValue } from '../../utils'; import { DEFAULT_COLOR, SELECT_COLOR } from '../general/constants'; import { columnNameWithDescription, resolveColumnValues, resolveSingleColumn } from '../general/layoutUtils'; import { - BaseVisConfig, ColumnInfo, EColumnTypes, ENumericalColorScaleType, @@ -20,17 +19,7 @@ import { VisNumericalValue, } from '../interfaces'; import { getCol } from '../sidebar'; - -export interface IScatterConfig extends BaseVisConfig { - type: ESupportedPlotlyVis.SCATTER; - numColumnsSelected: ColumnInfo[]; - color: ColumnInfo | null; - numColorScaleType: ENumericalColorScaleType; - shape: ColumnInfo | null; - dragMode: EScatterSelectSettings; - alphaSliderVal: number; - sizeSliderVal: number; -} +import { IScatterConfig } from './interfaces'; function calculateDomain(domain: [number | undefined, number | undefined], vals: number[]): [number, number] { if (!domain) return null; @@ -179,9 +168,9 @@ export async function createScatterTraces( }, hovertext: validCols[0].resolvedValues.map( (v, i) => - `${v.id}
x: ${v.val}
y: ${validCols[1].resolvedValues[i].val}
${ - colorCol ? `${columnNameWithDescription(colorCol.info)}: ${colorCol.resolvedValues[i].val}` : '' - }`, + `${v.id}
x: ${v.val}
y: ${validCols[1].resolvedValues[i].val}${ + colorCol ? `
${columnNameWithDescription(colorCol.info)}: ${colorCol.resolvedValues[i].val}` : '' + }${shapeCol ? `
${columnNameWithDescription(shapeCol.info)}: ${shapeCol.resolvedValues[i].val}` : ''}`, ), hoverinfo: 'text', text: validCols[0].resolvedValues.map((v) => v.id.toString()), diff --git a/src/vis/sidebar/BarDirectionButtons.tsx b/src/vis/sidebar/BarDirectionButtons.tsx deleted file mode 100644 index 236977f66..000000000 --- a/src/vis/sidebar/BarDirectionButtons.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Container, SegmentedControl, Stack, Text } from '@mantine/core'; -import * as React from 'react'; -import { EBarDirection } from '../barGood/utils'; - -interface BarDirectionProps { - callback: (s: EBarDirection) => void; - currentSelected: EBarDirection; -} - -export function BarDirectionButtons({ callback, currentSelected }: BarDirectionProps) { - return ( - - - - Direction - - - - - ); -} diff --git a/src/vis/sidebar/CategoricalColumnSelect.tsx b/src/vis/sidebar/CategoricalColumnSelect.tsx index eec786bef..56bee6aed 100644 --- a/src/vis/sidebar/CategoricalColumnSelect.tsx +++ b/src/vis/sidebar/CategoricalColumnSelect.tsx @@ -1,7 +1,7 @@ import { MultiSelect } from '@mantine/core'; import * as React from 'react'; import { ColumnInfo, EColumnTypes, VisColumn } from '../interfaces'; -import { SelectDropdownItem } from './utils'; +import { SelectDropdownItem, SelectLabelComponent } from './utils'; interface CategoricalColumnSelectProps { callback: (s: ColumnInfo[]) => void; @@ -17,6 +17,7 @@ export function CategoricalColumnSelect({ callback, columns, currentSelected }: return ( void }) { - return ( -
-
- ); -} diff --git a/src/vis/sidebar/FilterButtons.tsx b/src/vis/sidebar/FilterButtons.tsx index 35e001c46..b4cd20445 100644 --- a/src/vis/sidebar/FilterButtons.tsx +++ b/src/vis/sidebar/FilterButtons.tsx @@ -1,4 +1,4 @@ -import { Button, Stack, Tooltip, Text } from '@mantine/core'; +import { Button, Input, Tooltip } from '@mantine/core'; import * as React from 'react'; import { EFilterOptions } from '../interfaces'; @@ -8,11 +8,8 @@ interface FilterButtonsProps { export function FilterButtons({ callback }: FilterButtonsProps) { return ( - - - Filter - - + + - + ); } diff --git a/src/vis/sidebar/HexSizeSlider.tsx b/src/vis/sidebar/HexSizeSlider.tsx deleted file mode 100644 index c5cd90207..000000000 --- a/src/vis/sidebar/HexSizeSlider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Box, Slider, Stack, Text } from '@mantine/core'; -import debounce from 'lodash/debounce'; -import * as React from 'react'; -import { useMemo } from 'react'; -import { useSyncedRef } from '../../hooks'; - -interface OpacitySliderProps { - callback: (n: number) => void; - currentValue: number; -} - -export function HexSizeSlider({ callback, currentValue }: OpacitySliderProps) { - const syncedCallback = useSyncedRef(callback); - - const debouncedCallback = useMemo(() => { - return debounce((n: number) => syncedCallback.current?.(n), 10); - }, [syncedCallback]); - - return ( - - - Size - - - { - debouncedCallback(n); - }} - /> - - - ); -} diff --git a/src/vis/sidebar/NumericalColorButtons.tsx b/src/vis/sidebar/NumericalColorButtons.tsx index 015fa0cde..04acb5511 100644 --- a/src/vis/sidebar/NumericalColorButtons.tsx +++ b/src/vis/sidebar/NumericalColorButtons.tsx @@ -1,4 +1,4 @@ -import { Group, SegmentedControl } from '@mantine/core'; +import { Group, Input, SegmentedControl } from '@mantine/core'; import * as React from 'react'; import { ENumericalColorScaleType } from '../interfaces'; @@ -12,31 +12,35 @@ export function NumericalColorButtons({ callback, currentSelected }: NumericalCo const divergentColors = ['#337ab7', '#7496c1', '#a5b4ca', '#d3d3d3', '#e5b19d', '#ec8e6a', '#ec6836']; return ( - - {divergentColors.map((d) => { - return ; - })} - - ), - value: ENumericalColorScaleType.DIVERGENT, - }, - { - label: ( - - {sequentialColors.map((d) => { - return ; - })} - - ), - value: ENumericalColorScaleType.SEQUENTIAL, - }, - ]} - /> + + + {divergentColors.map((d) => { + return ; + })} + + ), + value: ENumericalColorScaleType.DIVERGENT, + }, + { + label: ( + + {sequentialColors.map((d) => { + return ; + })} + + ), + value: ENumericalColorScaleType.SEQUENTIAL, + }, + ]} + /> + ); } diff --git a/src/vis/sidebar/NumericalColumnSelect.tsx b/src/vis/sidebar/NumericalColumnSelect.tsx index f82919d13..d2fc61513 100644 --- a/src/vis/sidebar/NumericalColumnSelect.tsx +++ b/src/vis/sidebar/NumericalColumnSelect.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { MultiSelect } from '@mantine/core'; import { ColumnInfo, EColumnTypes, VisColumn } from '../interfaces'; -import { SelectDropdownItem } from './utils'; +import { SelectDropdownItem, SelectLabelComponent } from './utils'; interface NumericalColumnSelectProps { callback: (s: ColumnInfo[]) => void; @@ -20,6 +20,7 @@ export function NumericalColumnSelect({ callback, columns, currentSelected }: Nu { diff --git a/src/vis/sidebar/OpacitySlider.tsx b/src/vis/sidebar/OpacitySlider.tsx deleted file mode 100644 index ce031fd61..000000000 --- a/src/vis/sidebar/OpacitySlider.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Box, Slider, Stack, Text } from '@mantine/core'; -import debounce from 'lodash/debounce'; -import { useMemo } from 'react'; - -import * as React from 'react'; -import { useSyncedRef } from '../../hooks'; - -interface OpacitySliderProps { - callback: (n: number) => void; - currentValue: number; -} - -export function OpacitySlider({ callback, currentValue }: OpacitySliderProps) { - const syncedCallback = useSyncedRef(callback); - - const debouncedCallback = useMemo(() => { - return debounce((n: number) => syncedCallback.current?.(n), 10); - }, [syncedCallback]); - - return ( - - - Opacity - - - { - debouncedCallback(n); - }} - /> - - - ); -} diff --git a/src/vis/sidebar/SingleValueSelect.tsx b/src/vis/sidebar/SingleValueSelect.tsx deleted file mode 100644 index f5e0715f4..000000000 --- a/src/vis/sidebar/SingleValueSelect.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Select } from '@mantine/core'; -import * as React from 'react'; -import { SelectDropdownItem } from './utils'; - -interface SingleValueSelectProps { - callback: (s: string) => void; - availableFilterValues: string[]; - currentSelected: string; - placeholder: string; -} - -export function SingleValueSelect({ callback, availableFilterValues, currentSelected, placeholder }: SingleValueSelectProps) { - return ( - + Visualization type + + } + content={{currentVis?.description}} + /> + } + styles={{ + label: { + width: '100%', + }, + }} // components={{Option: optionLayout}} onChange={(e) => callback(e as ESupportedPlotlyVis)} name="visTypes" + itemComponent={SelectItem} + maxDropdownHeight={380} data={visTypes.map((t) => { return { value: t.type, label: t.type, + description: t.description, }; })} value={currentSelected} diff --git a/src/vis/sidebar/index.ts b/src/vis/sidebar/index.ts index dee6a8e2b..80fd42d75 100644 --- a/src/vis/sidebar/index.ts +++ b/src/vis/sidebar/index.ts @@ -1,14 +1,14 @@ -export * from './BarDirectionButtons'; -export * from './BarDisplayTypeButtons'; -export * from './BarGroupTypeButtons'; +export * from '../bar/BarDirectionButtons'; +export * from '../bar/BarDisplayTypeButtons'; +export * from '../bar/BarGroupTypeButtons'; export * from './BrushOptionButtons'; export * from './CategoricalColumnSelect'; -export * from './ColorSelect'; +export * from '../scatter/ColorSelect'; export * from './FilterButtons'; -export * from './GroupSelect'; +export * from '../bar/GroupSelect'; export * from './NumericalColorButtons'; export * from './NumericalColumnSelect'; -export * from './OpacitySlider'; +export * from '../scatter/OpacitySlider'; export * from './utils'; -export * from './ViolinOverlayButtons'; +export * from '../violin/ViolinOverlayButtons'; export * from './VisTypeSelect'; diff --git a/src/vis/sidebar/utils.tsx b/src/vis/sidebar/utils.tsx index eb7c153f2..a76f27e39 100644 --- a/src/vis/sidebar/utils.tsx +++ b/src/vis/sidebar/utils.tsx @@ -37,3 +37,46 @@ export const SelectDropdownItem = forwardRef(({ value
)); + +export function SelectLabelComponent({ + value, + label, + description, + onRemove, + classNames, + ...others +}: MultiSelectValueProps & { value: string; description: string }) { + return ( +
+ + {label} + + {description} + + + } + > + ({ + display: 'flex', + cursor: 'default', + alignItems: 'center', + backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[1], + paddingLeft: theme.spacing.xs, + borderRadius: theme.radius.sm, + })} + > + + {label} + + + + +
+ ); +} diff --git a/src/vis/stories/Iris.stories.tsx b/src/vis/stories/Iris.stories.tsx index dee46f256..6afb1cff3 100644 --- a/src/vis/stories/Iris.stories.tsx +++ b/src/vis/stories/Iris.stories.tsx @@ -1,9 +1,9 @@ import { ComponentStory } from '@storybook/react'; import React, { useState } from 'react'; import { Vis } from '../LazyVis'; -import { EBarDirection, EBarDisplayType, EBarGroupingType } from '../barGood/utils'; +import { EBarDirection, EBarDisplayType, EBarGroupingType } from '../bar/interfaces'; import { BaseVisConfig, EAggregateTypes, EColumnTypes, ENumericalColorScaleType, EScatterSelectSettings, ESupportedPlotlyVis, VisColumn } from '../interfaces'; -import { EViolinOverlay } from '../violin/utils'; +import { EViolinOverlay } from '../violin/interfaces'; export function fetchIrisData(): VisColumn[] { const dataPromise = import('./irisData').then((m) => diff --git a/src/vis/stories/Vis/Bar/BarRandom.stories.tsx b/src/vis/stories/Vis/Bar/BarRandom.stories.tsx index de24a1a63..f73a9a880 100644 --- a/src/vis/stories/Vis/Bar/BarRandom.stories.tsx +++ b/src/vis/stories/Vis/Bar/BarRandom.stories.tsx @@ -1,7 +1,7 @@ import { ComponentStory } from '@storybook/react'; import React from 'react'; import { Vis } from '../../../LazyVis'; -import { EBarDirection, EBarDisplayType, EBarGroupingType } from '../../../barGood/utils'; +import { EBarDirection, EBarDisplayType, EBarGroupingType } from '../../../bar/interfaces'; import { BaseVisConfig, EAggregateTypes, EColumnTypes, ESupportedPlotlyVis, VisColumn } from '../../../interfaces'; function RNG(seed) { diff --git a/src/vis/stories/Vis/Correlation/CorrelationIris.stories.tsx b/src/vis/stories/Vis/Correlation/CorrelationIris.stories.tsx index bd8854410..5f3163c92 100644 --- a/src/vis/stories/Vis/Correlation/CorrelationIris.stories.tsx +++ b/src/vis/stories/Vis/Correlation/CorrelationIris.stories.tsx @@ -1,7 +1,8 @@ import { ComponentStory } from '@storybook/react'; import React, { useState } from 'react'; import { Vis } from '../../../LazyVis'; -import { BaseVisConfig, ECorrelationType, EScaleType, ESupportedPlotlyVis } from '../../../interfaces'; +import { ECorrelationType } from '../../../correlation/interfaces'; +import { BaseVisConfig, EScaleType, ESupportedPlotlyVis } from '../../../interfaces'; import { fetchIrisData } from '../../fetchIrisData'; // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export diff --git a/src/vis/stories/Vis/Heatmap/HeatmapRandom.stories.tsx b/src/vis/stories/Vis/Heatmap/HeatmapRandom.stories.tsx index 0b443e0a8..ba26f6463 100644 --- a/src/vis/stories/Vis/Heatmap/HeatmapRandom.stories.tsx +++ b/src/vis/stories/Vis/Heatmap/HeatmapRandom.stories.tsx @@ -2,7 +2,8 @@ import { ComponentStory } from '@storybook/react'; import * as d3 from 'd3v7'; import React from 'react'; import { Vis } from '../../../LazyVis'; -import { BaseVisConfig, EAggregateTypes, EColumnTypes, ENumericalColorScaleType, ESortTypes, ESupportedPlotlyVis, VisColumn } from '../../../interfaces'; +import { ESortTypes } from '../../../heatmap/interfaces'; +import { BaseVisConfig, EAggregateTypes, EColumnTypes, ENumericalColorScaleType, ESupportedPlotlyVis, VisColumn } from '../../../interfaces'; function RNG(seed) { const m = 2 ** 35 - 31; @@ -149,7 +150,8 @@ Basic.args = { name: 'category2', }, ], - sortedBy: ESortTypes.CAT_ASC, + xSortedBy: ESortTypes.CAT_ASC, + ySortedBy: ESortTypes.CAT_ASC, color: null, numColorScaleType: ENumericalColorScaleType.SEQUENTIAL, aggregateColumn: null, @@ -178,7 +180,8 @@ Multiples.args = { name: 'category3', }, ], - sortedBy: ESortTypes.CAT_ASC, + xSortedBy: ESortTypes.CAT_ASC, + ySortedBy: ESortTypes.CAT_ASC, color: null, numColorScaleType: ENumericalColorScaleType.SEQUENTIAL, aggregateColumn: null, diff --git a/src/vis/stories/Vis/Hexbin/HexbinRandom.stories.tsx b/src/vis/stories/Vis/Hexbin/HexbinRandom.stories.tsx index 5e74fd99d..44cf32f7c 100644 --- a/src/vis/stories/Vis/Hexbin/HexbinRandom.stories.tsx +++ b/src/vis/stories/Vis/Hexbin/HexbinRandom.stories.tsx @@ -1,7 +1,8 @@ import { ComponentStory } from '@storybook/react'; import React from 'react'; import { Vis } from '../../../LazyVis'; -import { BaseVisConfig, EColumnTypes, EHexbinOptions, EScatterSelectSettings, ESupportedPlotlyVis, VisColumn } from '../../../interfaces'; +import { EHexbinOptions } from '../../../hexbin/interfaces'; +import { BaseVisConfig, EColumnTypes, EScatterSelectSettings, ESupportedPlotlyVis, VisColumn } from '../../../interfaces'; function RNG(seed) { const m = 2 ** 35 - 31; diff --git a/src/vis/stories/Vis/Raincloud/RaincloudRandom.stories.tsx b/src/vis/stories/Vis/Raincloud/RaincloudRandom.stories.tsx index c0284fce9..f590ed0ee 100644 --- a/src/vis/stories/Vis/Raincloud/RaincloudRandom.stories.tsx +++ b/src/vis/stories/Vis/Raincloud/RaincloudRandom.stories.tsx @@ -1,7 +1,8 @@ import { ComponentStory } from '@storybook/react'; import React from 'react'; import { Vis } from '../../../LazyVis'; -import { BaseVisConfig, ECloudType, EColumnTypes, ELightningType, ERainType, ESupportedPlotlyVis, VisColumn } from '../../../interfaces'; +import { BaseVisConfig, EColumnTypes, ESupportedPlotlyVis, VisColumn } from '../../../interfaces'; +import { ECloudType, ELightningType, ERainType } from '../../../raincloud/interfaces'; function RNG(seed) { const m = 2 ** 35 - 31; diff --git a/src/vis/stories/Vis/Violin/ViolinIris.stories.tsx b/src/vis/stories/Vis/Violin/ViolinIris.stories.tsx index c0afc6a0d..db316fbae 100644 --- a/src/vis/stories/Vis/Violin/ViolinIris.stories.tsx +++ b/src/vis/stories/Vis/Violin/ViolinIris.stories.tsx @@ -2,7 +2,7 @@ import { ComponentStory } from '@storybook/react'; import React from 'react'; import { Vis } from '../../../LazyVis'; import { BaseVisConfig, ESupportedPlotlyVis } from '../../../interfaces'; -import { EViolinOverlay } from '../../../violin/utils'; +import { EViolinOverlay } from '../../../violin/interfaces'; import { fetchIrisData } from '../../fetchIrisData'; // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export diff --git a/src/vis/violin/ViolinOverlayButtons.tsx b/src/vis/violin/ViolinOverlayButtons.tsx new file mode 100644 index 000000000..adda8b7bd --- /dev/null +++ b/src/vis/violin/ViolinOverlayButtons.tsx @@ -0,0 +1,25 @@ +import { Input, SegmentedControl } from '@mantine/core'; +import * as React from 'react'; +import { EViolinOverlay } from './interfaces'; + +interface ViolinOverlayProps { + callback: (s: EViolinOverlay) => void; + currentSelected: EViolinOverlay; +} + +export function ViolinOverlayButtons({ callback, currentSelected }: ViolinOverlayProps) { + return ( + + + + ); +} diff --git a/src/vis/violin/ViolinVis.tsx b/src/vis/violin/ViolinVis.tsx index 00b465760..41760868d 100644 --- a/src/vis/violin/ViolinVis.tsx +++ b/src/vis/violin/ViolinVis.tsx @@ -9,7 +9,8 @@ import { Plotly } from '../../plotly/full'; import { InvalidCols } from '../general'; import { beautifyLayout } from '../general/layoutUtils'; import { ICommonVisProps } from '../interfaces'; -import { IViolinConfig, createViolinTraces } from './utils'; +import { createViolinTraces } from './utils'; +import { IViolinConfig } from './interfaces'; export function ViolinVis({ config, columns, scales, dimensions, selectedList, selectedMap, selectionCallback }: ICommonVisProps) { const { value: traces, status: traceStatus, error: traceError } = useAsync(createViolinTraces, [columns, config, scales, selectedList, selectedMap]); diff --git a/src/vis/violin/ViolinVisSidebar.tsx b/src/vis/violin/ViolinVisSidebar.tsx index 11f6fcb36..eeb52df25 100644 --- a/src/vis/violin/ViolinVisSidebar.tsx +++ b/src/vis/violin/ViolinVisSidebar.tsx @@ -1,14 +1,12 @@ -import { Container, Divider, Stack } from '@mantine/core'; import merge from 'lodash/merge'; import * as React from 'react'; import { useMemo } from 'react'; -import { ColumnInfo, ESupportedPlotlyVis, ICommonVisSideBarProps } from '../interfaces'; +import { ColumnInfo, ICommonVisSideBarProps } from '../interfaces'; import { CategoricalColumnSelect } from '../sidebar/CategoricalColumnSelect'; import { FilterButtons } from '../sidebar/FilterButtons'; import { NumericalColumnSelect } from '../sidebar/NumericalColumnSelect'; -import { ViolinOverlayButtons } from '../sidebar/ViolinOverlayButtons'; -import { VisTypeSelect } from '../sidebar/VisTypeSelect'; -import { EViolinOverlay, IViolinConfig } from './utils'; +import { ViolinOverlayButtons } from './ViolinOverlayButtons'; +import { EViolinOverlay, IViolinConfig } from './interfaces'; const defaultConfig = { overlay: { @@ -35,22 +33,17 @@ export function ViolinVisSidebar({ }, [optionsConfig]); return ( - - setConfig({ ...(config as any), type })} currentSelected={config.type} /> - - - setConfig({ ...config, numColumnsSelected })} - columns={columns} - currentSelected={config.numColumnsSelected || []} - /> - setConfig({ ...config, catColumnsSelected })} - columns={columns} - currentSelected={config.catColumnsSelected || []} - /> - - + <> + setConfig({ ...config, numColumnsSelected })} + columns={columns} + currentSelected={config.numColumnsSelected || []} + /> + setConfig({ ...config, catColumnsSelected })} + columns={columns} + currentSelected={config.catColumnsSelected || []} + /> {mergedOptionsConfig.overlay.enable ? mergedOptionsConfig.overlay.customComponent || ( @@ -62,6 +55,6 @@ export function ViolinVisSidebar({ : null} {mergedOptionsConfig.filter.enable ? mergedOptionsConfig.filter.customComponent || : null} - + ); } diff --git a/src/vis/violin/interfaces.ts b/src/vis/violin/interfaces.ts new file mode 100644 index 000000000..906bae180 --- /dev/null +++ b/src/vis/violin/interfaces.ts @@ -0,0 +1,17 @@ +import { BaseVisConfig, ColumnInfo, ESupportedPlotlyVis } from '../interfaces'; + +export enum EViolinOverlay { + NONE = 'None', + BOX = 'Box', +} + +export interface IViolinConfig extends BaseVisConfig { + type: ESupportedPlotlyVis.VIOLIN; + numColumnsSelected: ColumnInfo[]; + catColumnsSelected: ColumnInfo[]; + violinOverlay: EViolinOverlay; +} + +export function isViolinConfig(s: BaseVisConfig): s is IViolinConfig { + return s.type === ESupportedPlotlyVis.VIOLIN; +} diff --git a/src/vis/violin/utils.ts b/src/vis/violin/utils.ts index efa186bcf..4c6cb29e2 100644 --- a/src/vis/violin/utils.ts +++ b/src/vis/violin/utils.ts @@ -2,30 +2,8 @@ import merge from 'lodash/merge'; import { i18n } from '../../i18n'; import { SELECT_COLOR } from '../general/constants'; import { columnNameWithDescription, resolveColumnValues } from '../general/layoutUtils'; -import { - BaseVisConfig, - ColumnInfo, - EColumnTypes, - ESupportedPlotlyVis, - PlotlyData, - PlotlyInfo, - Scales, - VisCategoricalColumn, - VisColumn, - VisNumericalColumn, -} from '../interfaces'; - -export enum EViolinOverlay { - NONE = 'None', - BOX = 'Box', -} - -export interface IViolinConfig extends BaseVisConfig { - type: ESupportedPlotlyVis.VIOLIN; - numColumnsSelected: ColumnInfo[]; - catColumnsSelected: ColumnInfo[]; - violinOverlay: EViolinOverlay; -} +import { EColumnTypes, ESupportedPlotlyVis, PlotlyData, PlotlyInfo, Scales, VisCategoricalColumn, VisColumn, VisNumericalColumn } from '../interfaces'; +import { EViolinOverlay, IViolinConfig } from './interfaces'; const defaultConfig: IViolinConfig = { type: ESupportedPlotlyVis.VIOLIN,