Commit f17a6bd3 authored by Matus Talcik's avatar Matus Talcik
Browse files

working overlaying data with specific settings

parent a652dd1d
Loading
Loading
Loading
Loading
+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";
@@ -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,
@@ -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',
@@ -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 => {
@@ -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;
@@ -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

@@ -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
        });
    };

@@ -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> */}

@@ -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
@@ -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}
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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>
@@ -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}
@@ -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} />
@@ -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