Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/component/1d/LinesSeries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useVerticalAlign } from '../hooks/useVerticalAlign.js';
import { useVisibleSpectra1D } from '../hooks/use_visible_spectra_1d.ts';

import Line from './Line.js';
import { SpectrumLabel } from './SpectrumLabel.tsx';
import { useInsetOptions } from './inset/InsetProvider.js';

const BOX_SIZE = 10;
Expand All @@ -31,6 +32,7 @@ function LinesSeries() {
<g className="spectra">
{spectra.map((d, i) => (
<g key={d.id}>
<SpectrumLabel index={i} spectrum={d} />
<Line display={d.display} id={d.id} data={get1DDataXY(d)} index={i} />
<HeadlightRectStackMode spectrumID={d.id} index={i} />
</g>
Expand Down
59 changes: 59 additions & 0 deletions src/component/1d/SpectrumLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Spectrum, SpectrumLabelField } from '@zakodium/nmrium-core';
import { SVGStyledText } from 'react-science/ui';

import { useChartData } from '../context/ChartContext.tsx';
import { usePreferences } from '../context/PreferencesContext.tsx';
import { useScaleChecked } from '../context/ScaleContext.tsx';
import { useVerticalAlign } from '../hooks/useVerticalAlign.ts';
import { getValueByPath } from '../utility/getValueByPath.ts';

export function SpectrumLabel({
index,
spectrum,
}: {
index: number;
spectrum: Spectrum;
}) {
const { spectraBottomMargin, shiftY } = useScaleChecked();
const {
height,
margin,
toolOptions: { selectedTool },
} = useChartData();
const { current } = usePreferences();
const verticalAlign = useVerticalAlign();
const { fields, visible, valueStyle } = current.spectraLabel;

if (
verticalAlign !== 'stack' ||
selectedTool !== 'zoom' ||
!Array.isArray(fields) ||
fields.length === 0 ||
!visible
) {
return null;
}

const innerHeight = height - margin.bottom - spectraBottomMargin;
const label = getSpectrumLabel(fields, spectrum);

return (
<SVGStyledText
{...valueStyle}
dy={-5}
transform={`translate(${margin.left},${innerHeight - shiftY * index})`}
>
{label}
</SVGStyledText>
);
}

function getSpectrumLabel(
fields: SpectrumLabelField[],
spectrum: Spectrum,
): string {
return fields
.filter((field) => field.visible)
.map((field) => getValueByPath(spectrum, field.jpath, field.format))
.join(', ');
}
2 changes: 1 addition & 1 deletion src/component/1d/peaks/usePeakShapesPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function usePeakShapesPath(spectrum: Spectrum1D) {
const { peaks } = options;
pathSeries = peaksToXY(peaks, {
frequency,
nbPoints: width,
nbPoints: Math.ceil(width),
from: xDomain[0],
to: xDomain[1],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ImportFiltersTab } from './tabs/import_filters_tab.js';
import { NucleiTab } from './tabs/nuclei_tab.js';
import { PanelsTab } from './tabs/panels_tab.js';
import { SpectraColorsTab } from './tabs/spectra_colors_tab.tsx';
import { SpectraLabelTab } from './tabs/spectra_label_tab.tsx';
import { TitleBlockTab } from './tabs/title_block_tab.js';
import { ToolsTab } from './tabs/tools_tab.tsx';
import {
Expand Down Expand Up @@ -107,6 +108,12 @@ export const GeneralSettingsDialogBody = withForm({
panel={<SpectraColorsTab form={form} />}
/>

<Tab
title="Spectra label"
id="spectra-label"
panel={<SpectraLabelTab form={form} />}
/>

{isExperimentalEnabled && (
<Tab
id="external-apis"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { Classes } from '@blueprintjs/core';
import styled from '@emotion/styled';
import { useField, useStore } from '@tanstack/react-form';
import { useCallback, useMemo, useState } from 'react';
import { FaRegTrashAlt } from 'react-icons/fa';
import {
Button,
FieldGroupSVGTextStyleFields,
TableDragRowHandler,
createTableColumnHelper,
withForm,
} from 'react-science/ui';
import type { z } from 'zod';

import { useChartData } from '../../../../context/ChartContext.js';
import { getSpectraObjectPaths } from '../../../../utility/getSpectraObjectPaths.js';
import { CellActions, CellActionsButton } from '../ui/cell_actions.tsx';
import { CellCheckbox } from '../ui/cell_checkbox.tsx';
import { CellInput } from '../ui/cell_input.tsx';
import { TableSettings } from '../ui/table.js';
import { TableSection } from '../ui/table_section.js';
import type { spectraLabelFieldTabValidationWithUUID } from '../validation/spectra_label_tab_validation.ts';
import { defaultGeneralSettingsFormValues } from '../validation.js';

export const SpectraLabelTab = withForm({
defaultValues: defaultGeneralSettingsFormValues,
render: ({ form }) => {
const { Section, AppField } = form;

return (
<>
<Section title="Visibility">
<AppField name="spectraLabel.visible">
{({ Checkbox }) => <Checkbox label="Display spectra labels" />}
</AppField>
</Section>

<TableFields form={form} />

<Section title="Styling">
<FieldGroupSVGTextStyleFields
form={form}
fields="spectraLabel.valueStyle"
label="Field value"
previewText="Placeholder"
/>
</Section>
</>
);
},
});

type Field = z.input<typeof spectraLabelFieldTabValidationWithUUID>;
function getEmptyField(): Field {
return {
format: '',
jpath: '',
visible: true,
uuid: crypto.randomUUID(),
};
}
const TableFields = withForm({
defaultValues: defaultGeneralSettingsFormValues,
render: function Fields({ form }) {
const { Field } = form;
const fields = useField({
form,
name: 'spectraLabel.fields',
mode: 'array',
});
const { removeValue, setValue, pushValue, name, store } = fields;

const [autoFocus, setAutoFocus] = useState<string>('');
function onAddField() {
const value = getEmptyField();
pushValue(value, { dontRunListeners: true });
setAutoFocus(value.uuid);
}

const { data } = useChartData();
const { datalist } = useMemo(() => getSpectraObjectPaths(data), [data]);

const onDeleteAt = useCallback(
(index: number) => {
removeValue(index);
},
[removeValue],
);

const columns = useMemo(() => {
const columnHelper = createTableColumnHelper<Field>();
return [
columnHelper.display({
id: 'dnd',
header: '',
meta: {
tdStyle: { textAlign: 'center' },
},
cell: () => <TableDragRowHandlerStyled size="small" />,
}),
columnHelper.accessor('jpath', {
header: 'Field',
cell: ({ row: { index, original } }) => (
<Field name={`${name}[${index}].jpath`}>
{(field) => (
<CellInput
field={field}
autoFocus={original.uuid === autoFocus ? true : undefined}
filterItems={datalist}
onBlur={() => setAutoFocus('')}
/>
)}
</Field>
),
}),
columnHelper.accessor('format', {
header: 'Format',
cell: ({ row: { index } }) => (
<Field name={`${name}[${index}].format`}>
{(field) => <CellInput field={field} />}
</Field>
),
}),
columnHelper.accessor('visible', {
header: 'Visible',
meta: {
tdStyle: { textAlign: 'center' },
},
cell: ({ row: { index } }) => (
<Field name={`${name}[${index}].visible`}>
{(field) => <CellCheckbox field={field} />}
</Field>
),
}),
columnHelper.display({
id: 'actions',
header: '',
meta: {
thStyle: {
width: '60px',
},
},
cell: ({ row: { index } }) => {
return (
<CellActions>
<CellActionsButton
intent="danger"
onClick={() => onDeleteAt(index)}
>
<FaRegTrashAlt className={Classes.ICON} />
</CellActionsButton>
</CellActions>
);
},
}),
];
}, [Field, autoFocus, datalist, name, onDeleteAt]);

const onRowOrderChanged = useCallback(
(value: Field[]) => {
setValue(value);
},
[setValue],
);

const fieldsData = useStore(store, (s) => s.value);

return (
<TableSection
title="Fields"
actions={
<Button
size="small"
variant="outlined"
intent="primary"
icon="plus"
onClick={onAddField}
>
Add Field
</Button>
}
>
<TableSettings
data={fieldsData}
columns={columns}
onRowOrderChanged={onRowOrderChanged}
getRowId={getRowId}
emptyContent="No Fields"
/>
</TableSection>
);
},
});

function getRowId(row: Field) {
return row.uuid;
}

const TableDragRowHandlerStyled = styled(TableDragRowHandler)`
margin: 0 2px;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { nmrLoadersValidation } from './validation/import_filters_tab_validation
import { nucleiValidation } from './validation/nuclei_tab_validation.js';
import { displayPanelsValidation } from './validation/panels_tab_validation.js';
import { spectraColorsTabValidation } from './validation/spectra_colors_tab_validation.ts';
import { spectraLabelTabValidation } from './validation/spectra_label_tab_validation.ts';
import { infoBlockTabValidation } from './validation/title_block_tab_validation.js';
import { toolBarButtonsValidation } from './validation/tools_tab_validation.ts';

Expand Down Expand Up @@ -45,6 +46,7 @@ export const workspaceValidation = z.object({
export: exportPreferencesValidation,
peaksLabel: peaksLabelValidation,
axis: axisValidation,
spectraLabel: spectraLabelTabValidation,
});

export const defaultGeneralSettingsFormValues: z.input<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { svgTextStyleFieldsSchema } from 'react-science/ui';
import { z } from 'zod';

import { jpathCodec, withUUID } from './utils.ts';

const spectraLabelFieldTabValidation = z.object({
format: z.string(),
jpath: jpathCodec,
visible: z.boolean(),
});

export const spectraLabelFieldTabValidationWithUUID = withUUID(
spectraLabelFieldTabValidation,
);

const spectraLabelFieldsTabValidation = z.array(
spectraLabelFieldTabValidationWithUUID,
);

export const spectraLabelTabValidation = z.object({
visible: z.boolean(),
fields: spectraLabelFieldsTabValidation,
valueStyle: svgTextStyleFieldsSchema.optional(),
});
Loading
Loading