diff --git a/src/component/1d/LinesSeries.tsx b/src/component/1d/LinesSeries.tsx index d005cb7c1..68910a9b3 100644 --- a/src/component/1d/LinesSeries.tsx +++ b/src/component/1d/LinesSeries.tsx @@ -25,7 +25,7 @@ const style = css` `; function LinesSeries() { - const { data, displayerKey, xDomains } = useChartData(); + const { displayerKey, xDomains, data } = useChartData(); const activeSpectra = useActiveSpectra(); const spectra = (data?.filter( (d) => isSpectrum1D(d) && d.display.isVisible && xDomains[d.id], diff --git a/src/component/context/DispatchContext.tsx b/src/component/context/DispatchContext.tsx index d8b483a9a..57af650d3 100644 --- a/src/component/context/DispatchContext.tsx +++ b/src/component/context/DispatchContext.tsx @@ -13,7 +13,6 @@ import type { PeaksActions } from '../reducer/actions/PeaksActions.js'; import type { PreferencesActions } from '../reducer/actions/PreferencesActions.js'; import type { RangesActions } from '../reducer/actions/RangesActions.js'; import type { SpectrumActions } from '../reducer/actions/SpectraActions.js'; -import type { SpectraAnalysisActions } from '../reducer/actions/SpectraAnalysisAction.js'; import type { ToolsActions } from '../reducer/actions/ToolsActions.js'; import type { ZonesActions } from '../reducer/actions/ZonesActions.js'; import type { ActionType } from '../reducer/types/ActionType.js'; @@ -21,7 +20,6 @@ import type { ActionType } from '../reducer/types/ActionType.js'; export type Action = | ToolsActions | SpectrumActions - | SpectraAnalysisActions | LoadActions | IntegralsActions | RangesActions diff --git a/src/component/context/SortSpectraContext.tsx b/src/component/context/SortSpectraContext.tsx new file mode 100644 index 000000000..3fdcda76b --- /dev/null +++ b/src/component/context/SortSpectraContext.tsx @@ -0,0 +1,273 @@ +import lodashGet from 'lodash/get.js'; +import type { Spectrum } from 'nmr-load-save'; +import type { ReactNode } from 'react'; +import { + createContext, + useCallback, + useContext, + useMemo, + useState, +} from 'react'; + +import nucleusToString from '../utility/nucleusToString.js'; + +type SortDirection = 'asc' | 'desc' | null; +type SpectraReference = { id: string } & Partial>; + +interface BaseSortOptions { + sortDirection?: SortDirection; +} + +interface SortByReferenceOptions extends BaseSortOptions { + path: string; + sortByReferences: SpectraReference[]; + sortType: 'sortByReference'; +} +interface SortByReferenceIndexesOptions { + sortByReferences: SpectraReference[]; + sortType: 'sortByReferenceIndexes'; +} + +interface SortByPathOptions extends BaseSortOptions { + path: string; + nucleus?: string; + sortType: 'sortByPath'; +} + +type SortOptions = + | SortByPathOptions + | SortByReferenceOptions + | SortByReferenceIndexesOptions; + +type SortFuncOptions = SortOptions & { activeSort?: string }; + +interface SortSpectraContextState { + sortOptions: SortOptions | null; + sort: (options: SortFuncOptions) => void; + reset: () => void; + activeSort: string | null; +} + +const SortSpectraContext = createContext(null); + +export function useSortSpectra(): SortSpectraContextState { + const context = useContext(SortSpectraContext); + if (!context) { + throw new Error('useSpectra must be used within a SpectraProvider'); + } + + return context; +} + +interface SpectraProviderProps { + children: ReactNode; +} + +function sortSpectraByPath( + originSpectra: Spectrum[], + sortOptions: SortByPathOptions, +) { + const outSpectra = [...originSpectra]; + const { path, sortDirection, nucleus } = sortOptions; + + const sortedSpectra: SortItem[] = []; + const originIndexes: number[] = []; + + for (let index = 0; index < originSpectra.length; index++) { + const spectrum = originSpectra[index]; + if ( + !nucleus || + (nucleus && nucleusToString(spectrum.info.nucleus) === nucleus) + ) { + sortedSpectra.push({ + spectrum, + sortValue: lodashGet(spectrum, path), + }); + originIndexes.push(index); + } + } + + sortArray(sortedSpectra, sortDirection); + + for (let index = 0; index < sortedSpectra.length; index++) { + outSpectra[originIndexes[index]] = sortedSpectra[index].spectrum; + } + return outSpectra; +} + +function sortSpectraByReferences( + originSpectra: Spectrum[], + sortOptions: SortByReferenceOptions, +) { + const outSpectra = [...originSpectra]; + const { path, sortDirection, sortByReferences } = sortOptions; + + const spectraReferenceMap = new Map(); + const sortedSpectra: SortItem[] = []; + const originIndexes: number[] = []; + + for (const reference of sortByReferences || []) { + spectraReferenceMap.set(reference.id, reference); + } + + for (let index = 0; index < originSpectra.length; index++) { + const spectrum = originSpectra[index]; + if (spectraReferenceMap.size > 0 && spectraReferenceMap.has(spectrum.id)) { + sortedSpectra.push({ + spectrum, + sortValue: lodashGet(spectraReferenceMap.get(spectrum.id), path), + }); + originIndexes.push(index); + } + } + sortArray(sortedSpectra, sortDirection); + + for (let index = 0; index < sortedSpectra.length; index++) { + outSpectra[originIndexes[index]] = sortedSpectra[index].spectrum; + } + return outSpectra; +} +function sortSpectraByReferenceIndexes( + originSpectra: Spectrum[], + sortOptions: SortByReferenceIndexesOptions, +) { + const { sortByReferences } = sortOptions; + const outSpectra = [...originSpectra]; + const spectraReferenceMap = new Map(); + const originIndexes: number[] = []; + const sortedSpectra = new Map(); + + for (const reference of sortByReferences || []) { + spectraReferenceMap.set(reference.id, reference); + } + + for (let index = 0; index < originSpectra.length; index++) { + const spectrum = originSpectra[index]; + if (spectraReferenceMap.size > 0 && spectraReferenceMap.has(spectrum.id)) { + originIndexes.push(index); + sortedSpectra.set(spectrum.id, spectrum); + } + } + + for (let index = 0; index < originIndexes.length; index++) { + const spectrum = sortedSpectra.get(sortByReferences[index].id); + if (spectrum) { + outSpectra[originIndexes[index]] = spectrum; + } + } + return outSpectra; +} + +interface SortItem { + spectrum: Spectrum; + sortValue: string | number | boolean | null | undefined; +} + +function sortArray(data: SortItem[], sortDirection?: SortDirection) { + const direction = sortDirection === 'asc' ? 1 : -1; + + if (!sortDirection) { + return data; + } + + data.sort((a, b) => { + const valueA = a.sortValue; + const valueB = b.sortValue; + + if (valueA == null && valueB != null) return -1 * direction; + if (valueB == null && valueA != null) return 1 * direction; + if (valueA == null && valueB == null) return 0; + + if (typeof valueA === 'string' && typeof valueB === 'string') { + return direction * valueA.localeCompare(valueB); + } + + if (typeof valueA === 'number' && typeof valueB === 'number') { + return direction * (valueA - valueB); + } + + if (typeof valueA === 'boolean' && typeof valueB === 'boolean') { + return direction * (Number(valueA) - Number(valueB)); + } + + return 0; + }); +} + +export function sortSpectra( + originSpectra: Spectrum[], + sortOptions: SortOptions, +) { + const { sortType } = sortOptions; + switch (sortType) { + case 'sortByPath': + return sortSpectraByPath(originSpectra, sortOptions); + case 'sortByReference': + return sortSpectraByReferences(originSpectra, sortOptions); + case 'sortByReferenceIndexes': + return sortSpectraByReferenceIndexes(originSpectra, sortOptions); + + default: + return originSpectra; + } +} + +function getNextSortType(currentSort: SortDirection): SortDirection { + if (currentSort === null) return 'asc'; + if (currentSort === 'asc') return 'desc'; + return null; +} + +export function SortSpectraProvider(props: SpectraProviderProps) { + const { children } = props; + const [sortOptions, setSortOptions] = useState(null); + const [activeSort, setActiveSort] = useState(null); + + const sort = useCallback( + (options: SortFuncOptions) => { + setActiveSort(options?.activeSort ?? null); + setSortOptions((prevOptions) => { + if (options.sortType !== 'sortByReferenceIndexes') { + let previousOptions = { ...prevOptions }; + if (activeSort !== options.activeSort) { + previousOptions = { ...prevOptions, sortDirection: null }; + } + const newOptions: SortOptions = { + ...previousOptions, + ...options, + sortDirection: + options.sortDirection ?? + getNextSortType( + (previousOptions as BaseSortOptions)?.sortDirection ?? null, + ), + }; + return newOptions; + } + + return { + ...prevOptions, + ...options, + }; + }); + }, + [activeSort], + ); + + const state = useMemo(() => { + function reset() { + setSortOptions(null); + } + return { + activeSort, + sortOptions, + sort, + reset, + }; + }, [activeSort, sort, sortOptions]); + + return ( + + {children} + + ); +} diff --git a/src/component/elements/ReactTable/ReactTable.tsx b/src/component/elements/ReactTable/ReactTable.tsx index aa38a8b44..bce7b4d62 100644 --- a/src/component/elements/ReactTable/ReactTable.tsx +++ b/src/component/elements/ReactTable/ReactTable.tsx @@ -57,7 +57,7 @@ type TableOptions = UseTableOptions & UseSortByOptions; interface SortEvent { - onSortEnd?: (data: any) => void; + onSortEnd?: (data: any, isTableSorted?: boolean) => void; } export interface BaseRowStyle { @@ -186,6 +186,7 @@ function TableInner( rows, prepareRow, rowSpanHeaders, + state, // Access the sort state here } = useTable( { columns: memoColumns, @@ -196,6 +197,8 @@ function TableInner( useRowSpan, ) as TableInstanceWithHooks; + const { sortBy } = state as any; + function clickHandler(event, row) { setRowIndex(row.index); onClick?.(event, row); @@ -218,11 +221,12 @@ function TableInner( useEffect(() => { if (isSortedEventTriggered.current) { + const isTableSorted = sortBy.length > 0; const data = rows.map((row) => row.original); - onSortEnd?.(data); + onSortEnd?.(data, isTableSorted); isSortedEventTriggered.current = false; } - }, [onSortEnd, rows]); + }, [onSortEnd, rows, sortBy?.length]); function headerClickHandler() { isSortedEventTriggered.current = true; diff --git a/src/component/hooks/useSpectraPerNucleus.ts b/src/component/hooks/useSpectraPerNucleus.ts index 19b59eda0..4e733cc3c 100644 --- a/src/component/hooks/useSpectraPerNucleus.ts +++ b/src/component/hooks/useSpectraPerNucleus.ts @@ -6,20 +6,20 @@ import nucleusToString from '../utility/nucleusToString.js'; export default function useSpectraByActiveNucleus() { const { - data, + data: spectra, view: { spectra: { activeTab }, }, } = useChartData(); return useMemo(() => { - if (data) { + if (spectra) { return ( - data.filter( + spectra.filter( (spectrum) => activeTab === nucleusToString(spectrum.info.nucleus), ) || [] ); } return []; - }, [activeTab, data]); + }, [activeTab, spectra]); } diff --git a/src/component/main/InnerNMRium.tsx b/src/component/main/InnerNMRium.tsx index 4db3152a3..74f30c111 100644 --- a/src/component/main/InnerNMRium.tsx +++ b/src/component/main/InnerNMRium.tsx @@ -7,6 +7,7 @@ import { GlobalProvider } from '../context/GlobalContext.js'; import { KeyModifiersProvider } from '../context/KeyModifierContext.js'; import { LoggerProvider } from '../context/LoggerContext.js'; import { PreferencesProvider } from '../context/PreferencesContext.js'; +import { SortSpectraProvider } from '../context/SortSpectraContext.js'; import { ToasterProvider } from '../context/ToasterContext.js'; import { TopicMoleculeProvider } from '../context/TopicMoleculeContext.js'; import { AlertProvider } from '../elements/Alert.js'; @@ -90,31 +91,33 @@ export function InnerNMRium({ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/component/main/NMRiumStateProvider.tsx b/src/component/main/NMRiumStateProvider.tsx index 3d48d5af4..928cb0e32 100644 --- a/src/component/main/NMRiumStateProvider.tsx +++ b/src/component/main/NMRiumStateProvider.tsx @@ -1,13 +1,15 @@ +import { produce } from 'immer'; import type { NmriumState } from 'nmr-load-save'; import { readNMRiumObject } from 'nmr-load-save'; import type { ReactNode } from 'react'; -import { useCallback, useEffect, useReducer, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react'; import { toJSON } from '../../data/SpectraManager.js'; import { ChartDataProvider } from '../context/ChartContext.js'; import { DispatchProvider } from '../context/DispatchContext.js'; import { useLogger } from '../context/LoggerContext.js'; import { usePreferences } from '../context/PreferencesContext.js'; +import { sortSpectra, useSortSpectra } from '../context/SortSpectraContext.js'; import checkActionType from '../reducer/IgnoreActions.js'; import { initialState, @@ -128,10 +130,24 @@ export default function NMRiumStateProvider(props: NMRiumStateProviderProps) { }); } }, [nmriumData, dispatch, dispatchPreferences]); + const { sortOptions } = useSortSpectra(); + + const spectra = useMemo(() => { + if (sortOptions) { + return sortSpectra(state.data, sortOptions); + } + return state.data; + }, [sortOptions, state.data]); + + const updatedState = useMemo(() => { + return produce(state, (draft) => { + draft.data = spectra; + }); + }, [state, spectra]); return ( - {children} + {children} ); } diff --git a/src/component/panels/SpectraPanel/SpectraTable.tsx b/src/component/panels/SpectraPanel/SpectraTable.tsx index 1c369558d..57957a48f 100644 --- a/src/component/panels/SpectraPanel/SpectraTable.tsx +++ b/src/component/panels/SpectraPanel/SpectraTable.tsx @@ -20,6 +20,7 @@ import { ClipboardFallbackModal } from '../../../utils/clipboard/clipboardCompon import { useClipboard } from '../../../utils/clipboard/clipboardHooks.js'; import { useChartData } from '../../context/ChartContext.js'; import { useDispatch } from '../../context/DispatchContext.js'; +import { useSortSpectra } from '../../context/SortSpectraContext.js'; import { useToaster } from '../../context/ToasterContext.js'; import type { ContextMenuItem } from '../../elements/ContextMenuBluePrint.js'; import { ContextMenu } from '../../elements/ContextMenuBluePrint.js'; @@ -160,6 +161,7 @@ export function SpectraTable(props: SpectraTableProps) { const { rawWriteWithType, shouldFallback, cleanShouldFallback, text } = useClipboard(); const { molecules } = useChartData(); + const { sort, reset } = useSortSpectra(); const COLUMNS: Partial< Record<(string & {}) | PredefinedSpectraColumn, Column> @@ -338,13 +340,12 @@ export function SpectraTable(props: SpectraTableProps) { return columns; }, [COLUMNS, spectraPreferences.columns]); - function handleSortEnd(data) { - dispatch({ - type: 'ORDER_SPECTRA', - payload: { - data, - }, - }); + function handleSortEnd(data, isTabledSorted) { + if (isTabledSorted) { + sort({ sortType: 'sortByReferenceIndexes', sortByReferences: data }); + } else { + reset(); + } } function handleRowStyle(data) { diff --git a/src/component/panels/SpectraPanel/SpectraTabs.tsx b/src/component/panels/SpectraPanel/SpectraTabs.tsx index dbcb1fdbe..5729bf977 100644 --- a/src/component/panels/SpectraPanel/SpectraTabs.tsx +++ b/src/component/panels/SpectraPanel/SpectraTabs.tsx @@ -15,13 +15,13 @@ import { SpectraTable } from './SpectraTable.js'; import SpectrumSetting from './base/setting/SpectrumSetting.js'; interface SpectraTabsInnerProps { - data: Spectrum[]; + spectra: Spectrum[]; activeTab: string; activeSpectra: Record; } function SpectraTabsInner({ - data, + spectra, activeSpectra, activeTab, }: SpectraTabsInnerProps) { @@ -34,10 +34,10 @@ function SpectraTabsInner({ const dispatch = useDispatch(); const spectraGroupByNucleus = useMemo(() => { - if (!data) return []; + if (!spectra) return []; const groupByNucleus = groupByInfoKey('nucleus'); - return groupByNucleus(data, true); - }, [data]); + return groupByNucleus(spectra, true); + }, [spectra]); const { setActiveSpectrum } = useSetActiveSpectrumAction(); function onTabChangeHandler(tab) { @@ -120,11 +120,11 @@ const MemoizedSpectra = memo(SpectraTabsInner); export default function SpectraTabs() { const { - data, + data: spectra, view: { spectra: { activeTab, activeSpectra }, }, } = useChartData(); - return ; + return ; } diff --git a/src/component/panels/multipleAnalysisPanel/AnalysisChart.tsx b/src/component/panels/multipleAnalysisPanel/AnalysisChart.tsx index 751a77613..8e5a9ae14 100644 --- a/src/component/panels/multipleAnalysisPanel/AnalysisChart.tsx +++ b/src/component/panels/multipleAnalysisPanel/AnalysisChart.tsx @@ -1,20 +1,49 @@ +import type { ButtonProps } from '@blueprintjs/core'; import { css } from '@emotion/react'; import lodashGet from 'lodash/get.js'; +import type { + JpathTableColumn, + Spectrum, + WorkSpacePanelPreferences, +} from 'nmr-load-save'; import { useMemo, useRef, useState } from 'react'; import { ResponsiveChart } from 'react-d3-utils'; -import { FaFileExport } from 'react-icons/fa'; +import { FaCopy, FaFileExport, FaFileImage } from 'react-icons/fa'; import { Axis, LineSeries, Plot } from 'react-plot'; -import { Button } from 'react-science/ui'; +import { Button, Toolbar } from 'react-science/ui'; import type { SpectraAnalysisData } from '../../../data/data1d/multipleSpectraAnalysis.js'; +import { ClipboardFallbackModal } from '../../../utils/clipboard/clipboardComponents.js'; +import { useClipboard } from '../../../utils/clipboard/clipboardHooks.js'; import { useChartData } from '../../context/ChartContext.js'; +import { useSortSpectra } from '../../context/SortSpectraContext.js'; import { useToaster } from '../../context/ToasterContext.js'; import { Input2 } from '../../elements/Input2.js'; import Label from '../../elements/Label.js'; +import { ToolbarPopoverItem } from '../../elements/ToolbarPopoverItem.js'; +import type { ToolbarPopoverMenuItem } from '../../elements/ToolbarPopoverItem.js'; +import { usePanelPreferences } from '../../hooks/usePanelPreferences.js'; import useSpectraByActiveNucleus from '../../hooks/useSpectraPerNucleus.js'; import { copyPNGToClipboard } from '../../utility/export.js'; import { getSpectraObjectPaths } from '../../utility/getSpectraObjectPaths.js'; +const MOL_EXPORT_MENU: ToolbarPopoverMenuItem[] = [ + { + icon: , + text: 'Copy chart to clipboard', + data: { + id: 'copyChart', + }, + }, + { + icon: , + text: 'Copy tab-delimited to clipboard', + data: { + id: 'copyData', + }, + }, +]; + interface PlotAxisOptions { xPath: string; yPath: string; @@ -42,12 +71,72 @@ function prepareAnalysisData( return spectraAnalysis; } -function usePlotData( - analysisData: SpectraAnalysisData, - options: PlotAxisOptions & { paths: Record }, +type PlotOptions = PlotAxisOptions & { paths: Record }; + +export function getPlotDataAsString( + spectraAnalysis: SpectraAnalysisData, + options: { + plotOptions: PlotOptions; + spectra: Spectrum[]; + spectraPanelPreferences: WorkSpacePanelPreferences['spectra']; + }, ) { - const spectra = useSpectraByActiveNucleus(); + const { plotOptions, spectra, spectraPanelPreferences } = options; + const { xPath, yPath } = plotOptions; + + if (spectraAnalysis) { + const { xData, yData, xPathKeys, yPathKeys } = preparePlotData( + spectraAnalysis, + plotOptions, + ); + + const columnsLabels: string[] = [xPath || 'serial', yPath || 'serial']; + let headerIndex = 0; + // listed the spectra panel columns + for (const col of spectraPanelPreferences.columns) { + if (col.visible && 'jpath' in col) { + columnsLabels.splice(headerIndex, 0, col.label); + headerIndex++; + } + } + + let result = `${columnsLabels.join('\t')}\n`; + let index = 0; + + for (const spectrum of spectra) { + const cellsValues: string[] = []; + + // listed the spectra cell values + for (const col of spectraPanelPreferences.columns) { + if (col.visible && 'jpath' in col) { + const jpath = (col as JpathTableColumn)?.jpath; + const value = lodashGet(spectrum, jpath, `null`); + cellsValues.push(value); + } + } + const x = xData + ? xData[spectrum.id] + : lodashGet(spectrum, xPathKeys, index); + const y = yData + ? yData[spectrum.id] + : lodashGet(spectrum, yPathKeys, index); + + cellsValues.push(x, y); + + result += `${cellsValues.join('\t')}\n`; + index++; + } + + return result; + } + return null; +} + +function preparePlotData( + analysisData: SpectraAnalysisData, + options: PlotOptions, +) { const { yPath, xPath, paths } = options; const xPathKeys = paths?.[xPath]; const yPathKeys = paths?.[yPath]; @@ -63,7 +152,18 @@ function usePlotData( if (analysisColumns.includes(yPath)) { yData = prepareAnalysisData(analysisData, yPath); } + return { xPathKeys, yPathKeys, xData, yData }; +} +function usePlotData( + analysisData: SpectraAnalysisData, + options: { plotOption: PlotOptions; spectra: Spectrum[] }, +) { + const { spectra, plotOption } = options; + const { xData, yData, xPathKeys, yPathKeys } = preparePlotData( + analysisData, + plotOption, + ); return spectra.map((spectrum, index) => { const x = xData ? xData[spectrum.id] @@ -96,8 +196,17 @@ function getAnalysisColumnsPaths(spectraAnalysisData: SpectraAnalysisData) { export default function AnalysisChart(props: PlotChartPros) { const { spectraAnalysisData } = props; - const { data } = useChartData(); + const { + data, + view: { + spectra: { activeTab }, + }, + } = useChartData(); const toaster = useToaster(); + const spectraPreferences = usePanelPreferences('spectra', activeTab); + const { sort } = useSortSpectra(); + const spectra = useSpectraByActiveNucleus(); + const chartParentRef = useRef(null); const [plotOptions, setPlotOptions] = useState({ xPath: '', @@ -126,7 +235,7 @@ export default function AnalysisChart(props: PlotChartPros) { setPlotOptions((prevOptions) => ({ ...prevOptions, [name]: value })); } - function handleCopy() { + function handleCopyChart() { if (chartParentRef.current) { void copyPNGToClipboard(svgId, { rootElement: chartParentRef.current, @@ -136,10 +245,90 @@ export default function AnalysisChart(props: PlotChartPros) { } } - const plotData = usePlotData(spectraAnalysisData, { ...plotOptions, paths }); + const plotData = usePlotData(spectraAnalysisData, { + plotOption: { ...plotOptions, paths }, + spectra, + }); const xLabel = paths?.[plotOptions.xPath]?.at(-1) || ''; const yLabel = paths?.[plotOptions.yPath]?.at(-1) || ''; + const { rawWriteWithType, cleanShouldFallback, shouldFallback, text } = + useClipboard(); + + function handleCopyData() { + const data = getPlotDataAsString(spectraAnalysisData, { + plotOptions: { ...plotOptions, paths }, + spectra, + spectraPanelPreferences: spectraPreferences, + }); + if (!data) return; + + void rawWriteWithType(data).then(() => + toaster.show({ message: 'Data copied to clipboard', intent: 'success' }), + ); + } + + function exportHandler(selected) { + switch (selected?.id) { + case 'copyChart': { + handleCopyChart(); + break; + } + case 'copyData': { + handleCopyData(); + break; + } + default: + break; + } + } + + function handleSort(axis: 'x' | 'y') { + const analysisData = preparePlotData(spectraAnalysisData, { + ...plotOptions, + paths, + }); + let sortByReferences; + let path; + let activeSort; + if (axis === 'x') { + if (analysisData.xData) { + path = 'value'; + sortByReferences = Object.keys(analysisData.xData).map( + (spectrumKey) => ({ + id: spectrumKey, + value: analysisData.xData[spectrumKey], + }), + ); + } else { + path = plotOptions.xPath; + } + activeSort = plotOptions.xPath; + } + + if (axis === 'y') { + if (analysisData.yData) { + path = 'value'; + sortByReferences = Object.keys(analysisData.yData).map( + (spectrumKey) => ({ + id: spectrumKey, + value: analysisData.yData[spectrumKey], + }), + ); + } else { + path = plotOptions.yPath; + } + activeSort = plotOptions.yPath; + } + + sort({ + sortType: sortByReferences ? 'sortByReference' : 'sortByPath', + path, + sortByReferences, + activeSort, + }); + } + return (
@@ -149,6 +338,12 @@ export default function AnalysisChart(props: PlotChartPros) { value={plotOptions.xPath} filterItems={datalist} onChange={handleChangeKey} + rightElement={ + handleSort('x')} + /> + } /> -
+
); } + +interface SortButtonProps { + value: string; + onClick: ButtonProps['onClick']; +} + +function SortButton(props: SortButtonProps) { + const { value, onClick } = props; + const { sortOptions, activeSort } = useSortSpectra(); + + let icon: ButtonProps['icon'] = 'sort'; + + if ( + activeSort === value && + sortOptions?.sortType !== 'sortByReferenceIndexes' + ) { + switch (sortOptions?.sortDirection) { + case 'asc': + icon = 'sort-asc'; + break; + case 'desc': + icon = 'sort-desc'; + break; + default: + } + } + return