Loading app/src/components/RightPanel/ChromatinViewportConfigurationPanel.tsx +280 −121 Original line number Diff line number Diff line import { Callout, DefaultButton, TextField, ColorPicker, ComboBox, IComboBoxOption, IComboBox, Label, Slider, IColor, Dropdown, IDropdownOption, Stack, Separator, ChoiceGroup, IChoiceGroupOption, Checkbox, IButtonStyles } from "@fluentui/react"; import { Callout, DefaultButton, TextField, ColorPicker, ComboBox, IComboBoxOption, IComboBox, Label, Slider, IColor, Dropdown, IDropdownOption, Stack, Separator, ChoiceGroup, IChoiceGroupOption, Checkbox, IButtonStyles, PrimaryButton, getColorFromRGBA } from "@fluentui/react"; import { Model, TabNode } from "flexlayout-react"; import React, { Dispatch, FormEvent, MouseEvent, useState } from "react"; import { toNumber } from "lodash"; Loading @@ -13,6 +13,8 @@ import { useConfiguration, useSelections, useViewportName } from "../hooks"; import { SelectionsPart } from "./SelectionsPart"; import { CutawaysPart } from "./CutawaysPart"; import { vec3 } from "gl-matrix"; import { Delete16Regular } from "@fluentui/react-icons"; import { quantile } from "simple-statistics"; export function ChromatinViewportConfigurationPanel(props: { model: Model, Loading @@ -34,9 +36,8 @@ export function ChromatinViewportConfigurationPanel(props: { id: string, text: string } > = [ { key: 'none', id: 'none', text: 'None' }, { key: 'single-color', id: 'none', text: 'Single color' }, { key: 'centromers', id: 'centromers', Loading Loading @@ -68,16 +69,54 @@ export function ChromatinViewportConfigurationPanel(props: { text: 'Solvent Accesibility Surface Area' }, ] const [selectedPrimaryDataID, setSelectedPimaryDataID] = useState<DataID | null>(null); const [selectedSecondaryDataID, setSelectedSecondaryDataID] = useState<DataID | null>(null); const selectedPrimaryData = selectedPrimaryDataID ? data.data.find(d => d.id == selectedPrimaryDataID) || null : null; const selectedSecondaryData = selectedSecondaryDataID ? data.data.find(d => d.id == selectedSecondaryDataID) || null : null; const showSecondaryData = selectedPrimaryDataID && selectedPrimaryData && selectedPrimaryData.type == 'bed-annotation'; const enableAddDataButton = (selectedPrimaryData && selectedPrimaryData.type == '3d-positions') || (selectedSecondaryDataID != null); const onChangePrimaryData = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption<number> | undefined): void => { if (!option || !option.key || typeof option.key != "number") return; const id = option.key; if (typeof id === 'number') { setSelectedPimaryDataID(() => isoDataID.wrap(id)); } }; const onChangeSecondaryData = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption<number> | undefined): void => { if (!option || !option.key || typeof option.key != "number") return; const id = option.key; if (typeof id === 'number') { setSelectedSecondaryDataID(() => isoDataID.wrap(id)); } }; const dataOptions = data.data .filter(d => d.type == '3d-positions' || d.type == 'bed-annotation') .map(d => { return { key: isoDataID.unwrap(d.id), id: isoDataID.unwrap(d.id).toString(), text: d.name, } as IDropdownOption; }); const data3DOptions = data.data .filter(d => d.type == '3d-positions') // .filter(d => configuration.data ? !viewportDataIDs.includes(configuration.data.id) : true) .map(d => { return { key: isoDataID.unwrap(d.id), id: isoDataID.unwrap(d.id).toString(), text: d.name, } as IComboBoxOption; } as IDropdownOption; }); const centromerDataOptions = data.data .filter(d => d.type == '3d-positions') .map(d => { Loading Loading @@ -183,59 +222,133 @@ export function ChromatinViewportConfigurationPanel(props: { }; // const setColorMappingMode = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { // if (!configuration || !option) return; // updateConfiguration({ // ...configuration, // colorMappingMode: option.key as ChromatinViewportColorMappingMode, // mapValues: { // ...configuration.mapValues, // id: -1, // }, // }) // } const setColorMappingMode = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { if (configuration.selectedDatum == null || !option) return; const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], colorMappingMode: option.key as ChromatinViewportColorMappingMode, mapValues: { ...data[configuration.selectedDatum].mapValues, id: -1 } }; updateConfiguration({ ...configuration, data }) } // #endregion //#region Data Parts // const removeData3D = (index: number) => { // if (!configuration) return; const removeData3D = (index: number) => { if (!configuration) return; // const newData = [...configuration.data]; // newData.splice(index, 1); const newData = [...configuration.data]; newData.splice(index, 1); // updateConfiguration({ // ...configuration, // selectedDataIndex: configuration.selectedDataIndex == index ? null : configuration.selectedDataIndex, // data: newData, // }); // }; updateConfiguration({ ...configuration, selectedDatum: configuration.selectedDatum == index ? null : configuration.selectedDatum, data: newData, }); }; const setData3D = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { if (!configuration || !option) return; const addData = () => { if (!configuration || !selectedPrimaryData) return; // const selectedDataId: DataID = isoDataID.wrap(option.key as number); // const selectedData = data.data.filter(d => d.id == selectedDataId)[0] as BinPositionsData; const data = [...configuration.data]; // updateConfiguration({ // ...configuration, // data: { // ...configuration.data, //#region Calculate radius range const positions = (selectedPrimaryData.type == '3d-positions' ? selectedPrimaryData as BinPositionsData : selectedSecondaryData as BinPositionsData).values; const distances = []; for (let i = 0; i < positions.length - 1; i++) { distances.push( vec3.distance(vec3.fromValues(positions[i].x, positions[i].y, positions[i].z), vec3.fromValues(positions[i + 1].x, positions[i + 1].y, positions[i + 1].z)) ); } // id: selectedDataId, const quantiles = quantile(distances, [0.05, 0.95]); // representation: ChromatinRepresentation.ContinuousTube, // radius: 0.0, const radius = quantiles[0] / 4.0; const radiusRange = { min: 0.0, max: quantiles[0] / 2.0 }; //#region Calculate radius range // selections: [], // }, // chromosomes: new Array(selectedData.chromosomes.length).fill(true), // mapValues: { // id: -1, // aggregationFunction: 'mean' // } // }); data.push({ id: selectedPrimaryData.id, secondaryID: selectedSecondaryDataID, chromosomes: [], representation: ChromatinRepresentation.Spheres, color: getColorFromRGBA({ r: 255, g: 255, b: 255, a: 100}), radius, radiusRange, selections: [], mapValues: { id: -1, aggregationFunction: 'mean' }, tooltip: { tooltipDataIDs: [], tooltipTextAggregation: 'none', tooltipNumericAggregation: 'none', }, showTooltip: true, colorMappingMode: 'single-color', labeling: { showDebugViewport: false, showLabelingOverlay: false, showLabelAnchors: false, useMaxDistCPU: false, shownDebugTexture: 'id', }, sasa: { method: 'constant', probeSize: 0, accuracy: 100, individual: false }, density: { probeSize: 0.1, individual: false } }); updateConfiguration({ ...configuration, data, }); }; const setDataColor = (ev: React.SyntheticEvent<HTMLElement, Event>, color: IColor): void => { if (configuration.selectedDatum == null) return; const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], color }; updateConfiguration({ ...configuration, data }); }; const setSelectedDatum = (index: number) => { updateConfiguration({ ...configuration, selectedDatum: index, }); }; const [isSingleColorCalloutVisible, setIsSingleColorCalloutVisible] = useState<boolean>(false); const setColorMappingData = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { // if (option) { // const selectedDataId: number = option.key as number; Loading Loading @@ -334,15 +447,14 @@ export function ChromatinViewportConfigurationPanel(props: { } const setRadius = (radius: number) => { if (!configuration.data) return; if (configuration.selectedDatum == null) return; // updateConfiguration({ // ...configuration, // data: { // ...configuration.data, // radius // } // }); const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], radius }; updateConfiguration({ ...configuration, data }); } //#endregion Loading Loading @@ -408,12 +520,15 @@ export function ChromatinViewportConfigurationPanel(props: { { key: 3, text: 'Spline' }, ]; const representationChanged = (event: FormEvent<HTMLDivElement>, option: IDropdownOption<any> | undefined, index: number | undefined): void => { if (!option || typeof option.key != 'number') return; const representationChanged = (event: FormEvent<HTMLDivElement>, option: IDropdownOption<any> | undefined): void => { if (configuration.selectedDatum == null || !option || typeof option.key != 'number') return; const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], representation: option.key - 1 }; updateConfiguration({ ...configuration, representation: option.key - 1, data }); }; Loading Loading @@ -469,46 +584,64 @@ export function ChromatinViewportConfigurationPanel(props: { </Stack> {/* List of 3D data */} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> <Text nowrap block variant='large'>3D Data</Text> {/* <ComboBox label="" allowFreeform={false} autoComplete={'on'} <Dropdown label="Data" // eslint-disable-next-line react/jsx-no-bind placeholder="Select 3D data or bin positions" options={dataOptions} onChange={onChangePrimaryData} /> {showSecondaryData && (<Dropdown label="Map bins on positions of" // eslint-disable-next-line react/jsx-no-bind placeholder="Select 3D data" options={data3DOptions} onChange={setData3D} onItemClick={setData3D} selectedKey={configuration.data ? isoDataID.unwrap(configuration.data.id) : null} style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} /> */} onChange={onChangeSecondaryData} />)} <PrimaryButton text='Add Data' style={{ marginTop: '8px' }} disabled={!enableAddDataButton} onClick={addData} /> <Dropdown <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> {configuration.data.map((datum, index) => (<div className={"treeViewListItem " + (configuration.selectedDatum == index ? 'selected' : '')} key={index} onClick={() => setSelectedDatum(index)}> <span style={{ display: 'block', width: '4px' }}></span> <Text className="text" nowrap>{datum.id}</Text> <Delete16Regular primaryFill={'white'} className='icon iconHoverRed' onClick={() => removeData3D(index)}></Delete16Regular> </div>) )} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> <Text nowrap block variant='large' style={{ marginBottom: '5px' }}>Data Visualization</Text> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && (<Dropdown label="Representation" selectedKey={configuration.representation + 1} selectedKey={configuration.data[configuration.selectedDatum].representation + 1} // eslint-disable-next-line react/jsx-no-bind onChange={representationChanged} placeholder="Select representation" options={representationDropdownOptions} onChange={representationChanged} />)} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && (<Slider label="Radius" min={configuration.data[configuration.selectedDatum].radiusRange.min} max={configuration.data[configuration.selectedDatum].radiusRange.max} step={(configuration.data[configuration.selectedDatum].radiusRange.max - configuration.data[configuration.selectedDatum].radiusRange.min) / 100.0} value={toNumber(configuration.data[configuration.selectedDatum].radius)} showValue={false} onChange={(value) => setRadius(value)} /> )} {/* 3D DATA REPRESENTATION */} {/* <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> <Text nowrap block variant='large' style={{ marginBottom: '5px' }}>3D Data Visualization</Text> <Stack tokens={{ childrenGap: '8px' }}> {configuration.data && (<Slider label="Radius" min={configuration.radiusRange.min} max={configuration.radiusRange.max} step={(configuration.radiusRange.max - configuration.radiusRange.min) / 100.0} value={toNumber(configuration.data.radius)} showValue={false} onChange={(value) => setRadius(value)} /> )} <CutawaysPart configurationReducer={configurationReducer}></CutawaysPart> </Stack> */} Loading @@ -517,15 +650,41 @@ export function ChromatinViewportConfigurationPanel(props: { <Separator></Separator> <Text nowrap block variant='large'>Color by</Text> {/* <ComboBox {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && <ComboBox style={{ marginTop: '8px', padding: '4px' }} options={colorMappingModes1D} onChange={setColorMappingMode} selectedKey={configuration.colorMappingMode} /> */} selectedKey={configuration.data[configuration.selectedDatum].colorMappingMode} />} {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == 'single-color' && <> <div style={{ display: 'block', width: '100%', marginTop: '8px' }}></div> <DefaultButton id="singleColorButton" styles={{ root: { width: 'calc(100% - 8px)', margin: '0px 4px 0px 4px' } }} key="singleColorButton" onClick={() => setIsSingleColorCalloutVisible(true)}> <span style={{ color: '#' + configuration.data[configuration.selectedDatum].color.hex}}>{configuration.data[configuration.selectedDatum].color.hex}</span> </DefaultButton> {isSingleColorCalloutVisible && ( <Callout gapSpace={0} target={'#singleColorButton'} onDismiss={() => setIsSingleColorCalloutVisible(false)} setInitialFocus > <ColorPicker color={configuration.data[configuration.selectedDatum].color} onChange={setDataColor} alphaType={'none'} showPreview={true} strings={{ hueAriaLabel: 'Hue', }} /> </Callout> )} </>} {/* {configuration.colorMappingMode == 'centromers' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == 'centromers' && <> {centromerDataOptions.length <= 0 && ("No more data available.")} { centromerDataOptions.length > 0 && (<ComboBox Loading @@ -537,13 +696,13 @@ export function ChromatinViewportConfigurationPanel(props: { style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={ (configuration.mapValues.id >= 0) ? configuration.mapValues.id : null (configuration.data[configuration.selectedDatum].mapValues.id >= 0) ? configuration.data[configuration.selectedDatum].mapValues.id : null } />) }</> } */} } {configuration.colorMappingMode == 'sasa' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == 'sasa' && <> <ComboBox label="Method" allowFreeform={false} Loading Loading @@ -589,7 +748,7 @@ export function ChromatinViewportConfigurationPanel(props: { </> } {configuration.colorMappingMode == '3d-density' && configuration.data && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == '3d-density' && configuration.data && <> <Slider label="Probe size" min={0} //minimum distance between any two bins (all will be white but two) Loading @@ -603,11 +762,9 @@ export function ChromatinViewportConfigurationPanel(props: { label="Compute for each chromosome individually" checked={configuration.density.individual} onChange={(e, individual) => setDensityConfiguration({ ...configuration.density, individual: individual ?? false })} /> </> } </>} {/* {configuration.colorMappingMode == '1d-density' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == '1d-density' && <> {densityDataOptions.length <= 0 && ("No more data available.")} { densityDataOptions.length > 0 && (<ComboBox Loading @@ -619,13 +776,13 @@ export function ChromatinViewportConfigurationPanel(props: { style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={ (configuration.mapValues.id >= 0) ? configuration.mapValues.id : null (configuration.data[configuration.selectedDatum].mapValues.id >= 0) ? configuration.data[configuration.selectedDatum].mapValues.id : null } />) }</> } */} } {/* {configuration.colorMappingMode == '1d-numerical' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == '1d-numerical' && <> {numericDataOptions.length <= 0 && ("No more data available.")} { numericDataOptions.length > 0 && (<><ComboBox Loading @@ -637,7 +794,7 @@ export function ChromatinViewportConfigurationPanel(props: { style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={ (configuration.mapValues.id >= 0) ? configuration.mapValues.id : null (configuration.data[configuration.selectedDatum].mapValues.id >= 0) ? configuration.data[configuration.selectedDatum].mapValues.id : null } /> <ComboBox Loading @@ -648,11 +805,11 @@ export function ChromatinViewportConfigurationPanel(props: { onChange={setAggragationFunction} style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={configuration.mapValues.aggregationFunction} selectedKey={configuration.data[configuration.selectedDatum].mapValues.aggregationFunction} /> </>) }</> } */} } <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> Loading Loading @@ -705,6 +862,7 @@ export function ChromatinViewportConfigurationPanel(props: { {/* SELECTIONS */} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> {configuration.selectedDatum && ( <SelectionsPart selections={selections} configurationReducer={configurationReducer} Loading @@ -712,8 +870,9 @@ export function ChromatinViewportConfigurationPanel(props: { selectionsReducer={props.selectionsReducer} selectedDataIndex={configuration.selectedDatum} ></SelectionsPart> )} <Separator></Separator> {/* <Separator></Separator> <Text nowrap block variant='large'>Labeling</Text> <Checkbox label="Show labels" checked={configuration.labeling.showLabelingOverlay} onChange={handleShowLabelingOverlayChange} /> <Checkbox label="Max Dist on CPU" checked={configuration.labeling.useMaxDistCPU} onChange={handleUseMaxDistCPUChange} /> Loading @@ -721,6 +880,6 @@ export function ChromatinViewportConfigurationPanel(props: { <Separator></Separator> <Checkbox label="Show anchors" checked={configuration.labeling.showLabelAnchors} onChange={handleShowLabelAnchorsChange} /> <Checkbox label="Show debug overlay" styles={{ root: { marginTop: '10px' } }} checked={configuration.labeling.showDebugViewport} onChange={handleShowDebugViewportChange} /> <ComboBox label="Texture" options={labelingDebugTextureOptions} onChange={setShownDebugTexture} /> <ComboBox label="Texture" options={labelingDebugTextureOptions} onChange={setShownDebugTexture} /> */} </div> } Loading
app/src/components/RightPanel/ChromatinViewportConfigurationPanel.tsx +280 −121 Original line number Diff line number Diff line import { Callout, DefaultButton, TextField, ColorPicker, ComboBox, IComboBoxOption, IComboBox, Label, Slider, IColor, Dropdown, IDropdownOption, Stack, Separator, ChoiceGroup, IChoiceGroupOption, Checkbox, IButtonStyles } from "@fluentui/react"; import { Callout, DefaultButton, TextField, ColorPicker, ComboBox, IComboBoxOption, IComboBox, Label, Slider, IColor, Dropdown, IDropdownOption, Stack, Separator, ChoiceGroup, IChoiceGroupOption, Checkbox, IButtonStyles, PrimaryButton, getColorFromRGBA } from "@fluentui/react"; import { Model, TabNode } from "flexlayout-react"; import React, { Dispatch, FormEvent, MouseEvent, useState } from "react"; import { toNumber } from "lodash"; Loading @@ -13,6 +13,8 @@ import { useConfiguration, useSelections, useViewportName } from "../hooks"; import { SelectionsPart } from "./SelectionsPart"; import { CutawaysPart } from "./CutawaysPart"; import { vec3 } from "gl-matrix"; import { Delete16Regular } from "@fluentui/react-icons"; import { quantile } from "simple-statistics"; export function ChromatinViewportConfigurationPanel(props: { model: Model, Loading @@ -34,9 +36,8 @@ export function ChromatinViewportConfigurationPanel(props: { id: string, text: string } > = [ { key: 'none', id: 'none', text: 'None' }, { key: 'single-color', id: 'none', text: 'Single color' }, { key: 'centromers', id: 'centromers', Loading Loading @@ -68,16 +69,54 @@ export function ChromatinViewportConfigurationPanel(props: { text: 'Solvent Accesibility Surface Area' }, ] const [selectedPrimaryDataID, setSelectedPimaryDataID] = useState<DataID | null>(null); const [selectedSecondaryDataID, setSelectedSecondaryDataID] = useState<DataID | null>(null); const selectedPrimaryData = selectedPrimaryDataID ? data.data.find(d => d.id == selectedPrimaryDataID) || null : null; const selectedSecondaryData = selectedSecondaryDataID ? data.data.find(d => d.id == selectedSecondaryDataID) || null : null; const showSecondaryData = selectedPrimaryDataID && selectedPrimaryData && selectedPrimaryData.type == 'bed-annotation'; const enableAddDataButton = (selectedPrimaryData && selectedPrimaryData.type == '3d-positions') || (selectedSecondaryDataID != null); const onChangePrimaryData = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption<number> | undefined): void => { if (!option || !option.key || typeof option.key != "number") return; const id = option.key; if (typeof id === 'number') { setSelectedPimaryDataID(() => isoDataID.wrap(id)); } }; const onChangeSecondaryData = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption<number> | undefined): void => { if (!option || !option.key || typeof option.key != "number") return; const id = option.key; if (typeof id === 'number') { setSelectedSecondaryDataID(() => isoDataID.wrap(id)); } }; const dataOptions = data.data .filter(d => d.type == '3d-positions' || d.type == 'bed-annotation') .map(d => { return { key: isoDataID.unwrap(d.id), id: isoDataID.unwrap(d.id).toString(), text: d.name, } as IDropdownOption; }); const data3DOptions = data.data .filter(d => d.type == '3d-positions') // .filter(d => configuration.data ? !viewportDataIDs.includes(configuration.data.id) : true) .map(d => { return { key: isoDataID.unwrap(d.id), id: isoDataID.unwrap(d.id).toString(), text: d.name, } as IComboBoxOption; } as IDropdownOption; }); const centromerDataOptions = data.data .filter(d => d.type == '3d-positions') .map(d => { Loading Loading @@ -183,59 +222,133 @@ export function ChromatinViewportConfigurationPanel(props: { }; // const setColorMappingMode = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { // if (!configuration || !option) return; // updateConfiguration({ // ...configuration, // colorMappingMode: option.key as ChromatinViewportColorMappingMode, // mapValues: { // ...configuration.mapValues, // id: -1, // }, // }) // } const setColorMappingMode = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { if (configuration.selectedDatum == null || !option) return; const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], colorMappingMode: option.key as ChromatinViewportColorMappingMode, mapValues: { ...data[configuration.selectedDatum].mapValues, id: -1 } }; updateConfiguration({ ...configuration, data }) } // #endregion //#region Data Parts // const removeData3D = (index: number) => { // if (!configuration) return; const removeData3D = (index: number) => { if (!configuration) return; // const newData = [...configuration.data]; // newData.splice(index, 1); const newData = [...configuration.data]; newData.splice(index, 1); // updateConfiguration({ // ...configuration, // selectedDataIndex: configuration.selectedDataIndex == index ? null : configuration.selectedDataIndex, // data: newData, // }); // }; updateConfiguration({ ...configuration, selectedDatum: configuration.selectedDatum == index ? null : configuration.selectedDatum, data: newData, }); }; const setData3D = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { if (!configuration || !option) return; const addData = () => { if (!configuration || !selectedPrimaryData) return; // const selectedDataId: DataID = isoDataID.wrap(option.key as number); // const selectedData = data.data.filter(d => d.id == selectedDataId)[0] as BinPositionsData; const data = [...configuration.data]; // updateConfiguration({ // ...configuration, // data: { // ...configuration.data, //#region Calculate radius range const positions = (selectedPrimaryData.type == '3d-positions' ? selectedPrimaryData as BinPositionsData : selectedSecondaryData as BinPositionsData).values; const distances = []; for (let i = 0; i < positions.length - 1; i++) { distances.push( vec3.distance(vec3.fromValues(positions[i].x, positions[i].y, positions[i].z), vec3.fromValues(positions[i + 1].x, positions[i + 1].y, positions[i + 1].z)) ); } // id: selectedDataId, const quantiles = quantile(distances, [0.05, 0.95]); // representation: ChromatinRepresentation.ContinuousTube, // radius: 0.0, const radius = quantiles[0] / 4.0; const radiusRange = { min: 0.0, max: quantiles[0] / 2.0 }; //#region Calculate radius range // selections: [], // }, // chromosomes: new Array(selectedData.chromosomes.length).fill(true), // mapValues: { // id: -1, // aggregationFunction: 'mean' // } // }); data.push({ id: selectedPrimaryData.id, secondaryID: selectedSecondaryDataID, chromosomes: [], representation: ChromatinRepresentation.Spheres, color: getColorFromRGBA({ r: 255, g: 255, b: 255, a: 100}), radius, radiusRange, selections: [], mapValues: { id: -1, aggregationFunction: 'mean' }, tooltip: { tooltipDataIDs: [], tooltipTextAggregation: 'none', tooltipNumericAggregation: 'none', }, showTooltip: true, colorMappingMode: 'single-color', labeling: { showDebugViewport: false, showLabelingOverlay: false, showLabelAnchors: false, useMaxDistCPU: false, shownDebugTexture: 'id', }, sasa: { method: 'constant', probeSize: 0, accuracy: 100, individual: false }, density: { probeSize: 0.1, individual: false } }); updateConfiguration({ ...configuration, data, }); }; const setDataColor = (ev: React.SyntheticEvent<HTMLElement, Event>, color: IColor): void => { if (configuration.selectedDatum == null) return; const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], color }; updateConfiguration({ ...configuration, data }); }; const setSelectedDatum = (index: number) => { updateConfiguration({ ...configuration, selectedDatum: index, }); }; const [isSingleColorCalloutVisible, setIsSingleColorCalloutVisible] = useState<boolean>(false); const setColorMappingData = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption) => { // if (option) { // const selectedDataId: number = option.key as number; Loading Loading @@ -334,15 +447,14 @@ export function ChromatinViewportConfigurationPanel(props: { } const setRadius = (radius: number) => { if (!configuration.data) return; if (configuration.selectedDatum == null) return; // updateConfiguration({ // ...configuration, // data: { // ...configuration.data, // radius // } // }); const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], radius }; updateConfiguration({ ...configuration, data }); } //#endregion Loading Loading @@ -408,12 +520,15 @@ export function ChromatinViewportConfigurationPanel(props: { { key: 3, text: 'Spline' }, ]; const representationChanged = (event: FormEvent<HTMLDivElement>, option: IDropdownOption<any> | undefined, index: number | undefined): void => { if (!option || typeof option.key != 'number') return; const representationChanged = (event: FormEvent<HTMLDivElement>, option: IDropdownOption<any> | undefined): void => { if (configuration.selectedDatum == null || !option || typeof option.key != 'number') return; const data = [...configuration.data]; data[configuration.selectedDatum] = { ...data[configuration.selectedDatum], representation: option.key - 1 }; updateConfiguration({ ...configuration, representation: option.key - 1, data }); }; Loading Loading @@ -469,46 +584,64 @@ export function ChromatinViewportConfigurationPanel(props: { </Stack> {/* List of 3D data */} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> <Text nowrap block variant='large'>3D Data</Text> {/* <ComboBox label="" allowFreeform={false} autoComplete={'on'} <Dropdown label="Data" // eslint-disable-next-line react/jsx-no-bind placeholder="Select 3D data or bin positions" options={dataOptions} onChange={onChangePrimaryData} /> {showSecondaryData && (<Dropdown label="Map bins on positions of" // eslint-disable-next-line react/jsx-no-bind placeholder="Select 3D data" options={data3DOptions} onChange={setData3D} onItemClick={setData3D} selectedKey={configuration.data ? isoDataID.unwrap(configuration.data.id) : null} style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} /> */} onChange={onChangeSecondaryData} />)} <PrimaryButton text='Add Data' style={{ marginTop: '8px' }} disabled={!enableAddDataButton} onClick={addData} /> <Dropdown <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> {configuration.data.map((datum, index) => (<div className={"treeViewListItem " + (configuration.selectedDatum == index ? 'selected' : '')} key={index} onClick={() => setSelectedDatum(index)}> <span style={{ display: 'block', width: '4px' }}></span> <Text className="text" nowrap>{datum.id}</Text> <Delete16Regular primaryFill={'white'} className='icon iconHoverRed' onClick={() => removeData3D(index)}></Delete16Regular> </div>) )} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> <Text nowrap block variant='large' style={{ marginBottom: '5px' }}>Data Visualization</Text> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && (<Dropdown label="Representation" selectedKey={configuration.representation + 1} selectedKey={configuration.data[configuration.selectedDatum].representation + 1} // eslint-disable-next-line react/jsx-no-bind onChange={representationChanged} placeholder="Select representation" options={representationDropdownOptions} onChange={representationChanged} />)} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && (<Slider label="Radius" min={configuration.data[configuration.selectedDatum].radiusRange.min} max={configuration.data[configuration.selectedDatum].radiusRange.max} step={(configuration.data[configuration.selectedDatum].radiusRange.max - configuration.data[configuration.selectedDatum].radiusRange.min) / 100.0} value={toNumber(configuration.data[configuration.selectedDatum].radius)} showValue={false} onChange={(value) => setRadius(value)} /> )} {/* 3D DATA REPRESENTATION */} {/* <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> <Text nowrap block variant='large' style={{ marginBottom: '5px' }}>3D Data Visualization</Text> <Stack tokens={{ childrenGap: '8px' }}> {configuration.data && (<Slider label="Radius" min={configuration.radiusRange.min} max={configuration.radiusRange.max} step={(configuration.radiusRange.max - configuration.radiusRange.min) / 100.0} value={toNumber(configuration.data.radius)} showValue={false} onChange={(value) => setRadius(value)} /> )} <CutawaysPart configurationReducer={configurationReducer}></CutawaysPart> </Stack> */} Loading @@ -517,15 +650,41 @@ export function ChromatinViewportConfigurationPanel(props: { <Separator></Separator> <Text nowrap block variant='large'>Color by</Text> {/* <ComboBox {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && <ComboBox style={{ marginTop: '8px', padding: '4px' }} options={colorMappingModes1D} onChange={setColorMappingMode} selectedKey={configuration.colorMappingMode} /> */} selectedKey={configuration.data[configuration.selectedDatum].colorMappingMode} />} {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == 'single-color' && <> <div style={{ display: 'block', width: '100%', marginTop: '8px' }}></div> <DefaultButton id="singleColorButton" styles={{ root: { width: 'calc(100% - 8px)', margin: '0px 4px 0px 4px' } }} key="singleColorButton" onClick={() => setIsSingleColorCalloutVisible(true)}> <span style={{ color: '#' + configuration.data[configuration.selectedDatum].color.hex}}>{configuration.data[configuration.selectedDatum].color.hex}</span> </DefaultButton> {isSingleColorCalloutVisible && ( <Callout gapSpace={0} target={'#singleColorButton'} onDismiss={() => setIsSingleColorCalloutVisible(false)} setInitialFocus > <ColorPicker color={configuration.data[configuration.selectedDatum].color} onChange={setDataColor} alphaType={'none'} showPreview={true} strings={{ hueAriaLabel: 'Hue', }} /> </Callout> )} </>} {/* {configuration.colorMappingMode == 'centromers' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == 'centromers' && <> {centromerDataOptions.length <= 0 && ("No more data available.")} { centromerDataOptions.length > 0 && (<ComboBox Loading @@ -537,13 +696,13 @@ export function ChromatinViewportConfigurationPanel(props: { style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={ (configuration.mapValues.id >= 0) ? configuration.mapValues.id : null (configuration.data[configuration.selectedDatum].mapValues.id >= 0) ? configuration.data[configuration.selectedDatum].mapValues.id : null } />) }</> } */} } {configuration.colorMappingMode == 'sasa' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == 'sasa' && <> <ComboBox label="Method" allowFreeform={false} Loading Loading @@ -589,7 +748,7 @@ export function ChromatinViewportConfigurationPanel(props: { </> } {configuration.colorMappingMode == '3d-density' && configuration.data && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == '3d-density' && configuration.data && <> <Slider label="Probe size" min={0} //minimum distance between any two bins (all will be white but two) Loading @@ -603,11 +762,9 @@ export function ChromatinViewportConfigurationPanel(props: { label="Compute for each chromosome individually" checked={configuration.density.individual} onChange={(e, individual) => setDensityConfiguration({ ...configuration.density, individual: individual ?? false })} /> </> } </>} {/* {configuration.colorMappingMode == '1d-density' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == '1d-density' && <> {densityDataOptions.length <= 0 && ("No more data available.")} { densityDataOptions.length > 0 && (<ComboBox Loading @@ -619,13 +776,13 @@ export function ChromatinViewportConfigurationPanel(props: { style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={ (configuration.mapValues.id >= 0) ? configuration.mapValues.id : null (configuration.data[configuration.selectedDatum].mapValues.id >= 0) ? configuration.data[configuration.selectedDatum].mapValues.id : null } />) }</> } */} } {/* {configuration.colorMappingMode == '1d-numerical' && <> {configuration.selectedDatum != null && configuration.data.length > configuration.selectedDatum && configuration.data[configuration.selectedDatum].colorMappingMode == '1d-numerical' && <> {numericDataOptions.length <= 0 && ("No more data available.")} { numericDataOptions.length > 0 && (<><ComboBox Loading @@ -637,7 +794,7 @@ export function ChromatinViewportConfigurationPanel(props: { style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={ (configuration.mapValues.id >= 0) ? configuration.mapValues.id : null (configuration.data[configuration.selectedDatum].mapValues.id >= 0) ? configuration.data[configuration.selectedDatum].mapValues.id : null } /> <ComboBox Loading @@ -648,11 +805,11 @@ export function ChromatinViewportConfigurationPanel(props: { onChange={setAggragationFunction} style={{ marginTop: '8px', padding: '4px' }} shouldRestoreFocus={false} selectedKey={configuration.mapValues.aggregationFunction} selectedKey={configuration.data[configuration.selectedDatum].mapValues.aggregationFunction} /> </>) }</> } */} } <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> Loading Loading @@ -705,6 +862,7 @@ export function ChromatinViewportConfigurationPanel(props: { {/* SELECTIONS */} <div style={{ display: 'block', width: '100%', marginTop: '16px' }}></div> <Separator></Separator> {configuration.selectedDatum && ( <SelectionsPart selections={selections} configurationReducer={configurationReducer} Loading @@ -712,8 +870,9 @@ export function ChromatinViewportConfigurationPanel(props: { selectionsReducer={props.selectionsReducer} selectedDataIndex={configuration.selectedDatum} ></SelectionsPart> )} <Separator></Separator> {/* <Separator></Separator> <Text nowrap block variant='large'>Labeling</Text> <Checkbox label="Show labels" checked={configuration.labeling.showLabelingOverlay} onChange={handleShowLabelingOverlayChange} /> <Checkbox label="Max Dist on CPU" checked={configuration.labeling.useMaxDistCPU} onChange={handleUseMaxDistCPUChange} /> Loading @@ -721,6 +880,6 @@ export function ChromatinViewportConfigurationPanel(props: { <Separator></Separator> <Checkbox label="Show anchors" checked={configuration.labeling.showLabelAnchors} onChange={handleShowLabelAnchorsChange} /> <Checkbox label="Show debug overlay" styles={{ root: { marginTop: '10px' } }} checked={configuration.labeling.showDebugViewport} onChange={handleShowDebugViewportChange} /> <ComboBox label="Texture" options={labelingDebugTextureOptions} onChange={setShownDebugTexture} /> <ComboBox label="Texture" options={labelingDebugTextureOptions} onChange={setShownDebugTexture} /> */} </div> }