Commit 17534d7e authored by kiraacorsac's avatar kiraacorsac
Browse files

hovering over 3d viewport shows coordinates in toolbar

parent 714d7875
Pipeline #115548 passed with stage
in 1 minute and 46 seconds
@import '@fluentui/react/dist/sass/References';
@import "@fluentui/react/dist/sass/References";
ul, li {
ul,
li {
list-style-type: none;
}
.App {
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
}
.topPanel {
flex-grow: 0;
flex-basis: 44px;
border-bottom: 1px solid $ms-color-gray140;
flex-grow: 0;
flex-basis: 44px;
border-bottom: 1px solid $ms-color-gray140;
}
.mainArea {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
}
.flexlayout__layout {
flex: 1;
position: relative;
overflow: hidden;
}
flex: 1;
position: relative;
overflow: hidden;
}
.flexlayout__splitter {
background-color: #1a1a1a;
}
@media (hover: hover) {
.flexlayout__splitter:hover {
background-color: #333333;
}
}
.flexlayout__splitter_border {
z-index: 10;
}
.flexlayout__splitter_drag {
z-index: 1000;
border-radius: 5px;
background-color: #404040;
}
.flexlayout__outline_rect {
position: absolute;
cursor: move;
box-sizing: border-box;
border: 2px solid #cfe8ff;
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.2);
border-radius: 5px;
z-index: 1000;
}
.flexlayout__outline_rect_edge {
cursor: move;
border: 2px solid #b7d1b5;
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.2);
border-radius: 5px;
z-index: 1000;
box-sizing: border-box;
}
.flexlayout__edge_rect {
position: absolute;
z-index: 1000;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background-color: gray;
}
.flexlayout__drag_rect {
position: absolute;
cursor: move;
color: white;
background-color: #121212;
border: 2px solid #333333;
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.3);
border-radius: 5px;
z-index: 1000;
box-sizing: border-box;
opacity: 0.9;
text-align: center;
display: flex;
justify-content: center;
flex-direction: column;
overflow: hidden;
padding: 10px;
word-wrap: break-word;
font-size: medium;
font-family: Roboto, Arial, sans-serif;
.flexlayout__splitter {
background-color: #1a1a1a;
}
@media (hover: hover) {
.flexlayout__splitter:hover {
background-color: #333333;
}
}
.flexlayout__splitter_border {
z-index: 10;
}
.flexlayout__splitter_drag {
z-index: 1000;
border-radius: 5px;
background-color: #404040;
}
.flexlayout__outline_rect {
position: absolute;
cursor: move;
box-sizing: border-box;
border: 2px solid #cfe8ff;
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.2);
border-radius: 5px;
z-index: 1000;
}
.flexlayout__outline_rect_edge {
cursor: move;
border: 2px solid #b7d1b5;
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.2);
border-radius: 5px;
z-index: 1000;
box-sizing: border-box;
}
.flexlayout__edge_rect {
position: absolute;
z-index: 1000;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background-color: gray;
}
.flexlayout__drag_rect {
position: absolute;
cursor: move;
color: white;
background-color: #121212;
border: 2px solid #333333;
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.3);
border-radius: 5px;
z-index: 1000;
box-sizing: border-box;
opacity: 0.9;
text-align: center;
display: flex;
justify-content: center;
flex-direction: column;
overflow: hidden;
padding: 10px;
word-wrap: break-word;
font-size: medium;
font-family: Roboto, Arial, sans-serif;
}
.flexlayout__tabset {
overflow: hidden;
background: none;
}
.flexlayout__tabset_header {
}
.flexlayout__tabset_header_content {
flex-grow: 1;
}
.flexlayout__tabset_tabbar_outer {
box-sizing: border-box;
position: absolute;
left: 0;
right: 0;
overflow: hidden;
display: flex;
background: none;
}
.flexlayout__tabset_tabbar_outer_top {
/* border-bottom: 1px solid #262626; */
}
.flexlayout__tabset_tabbar_outer_bottom {
/* border-top: 1px solid #262626; */
}
.flexlayout__tabset_tabbar_inner {
position: relative;
box-sizing: border-box;
display: flex;
flex-grow: 1;
overflow: hidden;
}
.flexlayout__tabset_tabbar_inner_tab_container {
display: flex;
box-sizing: border-box;
position: absolute;
top: 0;
bottom: 0;
width: 10000px;
}
.flexlayout__tabset_tabbar_inner_tab_container_top {
}
.flexlayout__tabset_tabbar_inner_tab_container_bottom {
}
.flexlayout__tabset-selected {
// background-color: #111c25;
}
.flexlayout__tabset-maximized {
}
.flexlayout__tab {
overflow: hidden;
position: absolute;
box-sizing: border-box;
}
.flexlayout__tab_button {
display: inline-flex;
align-items: center;
box-sizing: border-box;
cursor: pointer;
padding: 0px 8px 0px 8px;
margin-right: 8px;
background-color: #2f2f2f;
}
.flexlayout__tabset-selected .flexlayout__tab_button--selected {
color: white;
background-color: #6db1e8;
}
@media (hover: hover) {
.flexlayout__tab_button:hover {
}
}
.flexlayout__tab_button--unselected {
color: gray;
}
.flexlayout__tab_button_top {
}
.flexlayout__tab_button_bottom {
}
.flexlayout__tab_button_leading {
display: inline-block;
}
.flexlayout__tab_button_content {
font-size: $ms-font-size-14;
font-weight: $ms-font-weight-semibold;
display: inline-block;
overflow: hidden;
background: none;
}
.flexlayout__tabset_header {
}
.flexlayout__tabset_header_content {
flex-grow: 1;
}
.flexlayout__tabset_tabbar_outer {
box-sizing: border-box;
position: absolute;
left: 0;
right: 0;
overflow: hidden;
display: flex;
background: none;
}
.flexlayout__tabset_tabbar_outer_top {
/* border-bottom: 1px solid #262626; */
}
.flexlayout__tabset_tabbar_outer_bottom {
/* border-top: 1px solid #262626; */
}
.flexlayout__tabset_tabbar_inner {
position: relative;
box-sizing: border-box;
display: flex;
flex-grow: 1;
overflow: hidden;
}
.flexlayout__tabset_tabbar_inner_tab_container {
display: flex;
box-sizing: border-box;
position: absolute;
top: 0;
bottom: 0;
width: 10000px;
}
.flexlayout__tabset_tabbar_inner_tab_container_top {
}
.flexlayout__tabset_tabbar_inner_tab_container_bottom {
}
.flexlayout__tabset-selected {
// background-color: #111c25;
}
.flexlayout__tabset-maximized {
}
.flexlayout__tab {
overflow: hidden;
position: absolute;
box-sizing: border-box;
}
.flexlayout__tab_button {
display: inline-flex;
align-items: center;
box-sizing: border-box;
cursor: pointer;
padding: 0px 8px 0px 8px;
margin-right: 8px;
background-color: #2f2f2f;
}
.flexlayout__tabset-selected .flexlayout__tab_button--selected {
color: white;
background-color: #6db1e8;
}
@media (hover: hover) {
.flexlayout__tab_button:hover {
}
.flexlayout__tab_button_textbox {
border: none;
/* color: green;
}
.flexlayout__tab_button--unselected {
color: gray;
}
.flexlayout__tab_button_top {
}
.flexlayout__tab_button_bottom {
}
.flexlayout__tab_button_leading {
display: inline-block;
}
.flexlayout__tab_button_content {
font-size: $ms-font-size-14;
font-weight: $ms-font-weight-semibold;
display: inline-block;
}
.flexlayout__tab_button_textbox {
border: none;
/* color: green;
background-color: #262626; */
}
.flexlayout__tab_button_textbox:focus {
outline: none;
}
}
.flexlayout__tab_button_textbox:focus {
outline: none;
}
.flexlayout__tab_button_trailing {
margin-left: 8px;
}
.flexlayout__tab_button_trailing {
margin-left: 8px;
}
.flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
color: white;
}
.flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
color: white;
}
.flexlayout__tab_button_trailing, .flexlayout__tab_button_trailing i {
width: 8px;
height: 8px;
font-size: 8px;
}
.flexlayout__tab_button_trailing,
.flexlayout__tab_button_trailing i {
width: 8px;
height: 8px;
font-size: 8px;
}
.flexlayout__splitter {
background-color: $ms-color-gray140;
}
.flexlayout__splitter {
background-color: $ms-color-gray140;
}
.toolOptionsPanel {
display: flex;
width: 100%;
height: 44px;
background-color: $ms-color-gray190;
border-bottom: 1px solid $ms-color-gray150;
justify-content: center;
}
\ No newline at end of file
align-items: center;
padding: 8px;
}
......@@ -25,7 +25,9 @@ import { ApplicationState, APPLICATION_STATE_VERSION } from './modules/storage/s
import { ForceGraphViewport } from './components/viewports/ForceGraphViewport';
import { ToolsList } from './components/Tools/ToolsList';
import { ToolOptions } from './components/Tools/ToolOptions';
import { CoordinatePreview } from './components/Tools/CoordinatePreview';
import { NewGenomicDataDialog } from './components/dialogs/NewGenomicDataDialog';
import { coordinatePreviewReducer } from './modules/storage/models/coordinatePreview';
export enum Tool {
PointSelection = 0,
......@@ -196,6 +198,19 @@ function App(): JSX.Element {
// restore?: React.ReactNode;
// more?: React.ReactNode;
}
//#endregion
const [coordinatePreview, dispatchCoordinatePreview] = useReducer(
coordinatePreviewReducer,
{
type: "bin-coordinates-single",
dataId: undefined,
visible: false,
from: 0,
to: 0
},
);
//#region Viewports creation
const updateModel = (model: FlexLayout.Model) => {
......@@ -250,6 +265,7 @@ function App(): JSX.Element {
graphicsLibrary={graphicsLibrary}
configurationID={config}
configurationsReducer={[configurations, dispatchConfigurations]}
coordinatePreviewReducer={[coordinatePreview, dispatchCoordinatePreview]}
dataReducer={[data, dispatchData]}
selectionsReducer={[selections, dispatchSelections]}
></ChromatinViewport>
......@@ -397,7 +413,12 @@ function App(): JSX.Element {
</div>
<div className="toolOptionsPanel">
{activeTab && <ToolOptions configurationID={activeTab.getConfig()} configurationsReducer={[configurations, dispatchConfigurations]}></ToolOptions>}
{activeTab && <>
<ToolOptions configurationID={activeTab.getConfig()} configurationsReducer={[configurations, dispatchConfigurations]}></ToolOptions>
<Separator vertical></Separator>
<CoordinatePreview style={{ padding: "0px 8px" }} coordinatePreviewReducer={[coordinatePreview, dispatchCoordinatePreview]} dataReducer={[data, dispatchData]}
></CoordinatePreview>
</>}
</div>
<div className="mainArea">
......
import { Dispatch } from "react";
import { binToGenomicCoordinate } from "../../modules/coordniatesUtils";
import { CoordinatePreviewAction, CoordinatePreviewState } from "../../modules/storage/models/coordinatePreview";
import { BinPositionsData, DataAction, DataState } from "../../modules/storage/models/data";
import { ConfigurationAction, ConfigurationState } from "../../modules/storage/models/viewports";
import { useConfigurationTypeless } from "../hooks";
export function CoordinatePreview(props: {
coordinatePreviewReducer: [CoordinatePreviewState, React.Dispatch<CoordinatePreviewAction>],
dataReducer: [DataState, Dispatch<DataAction>],
style?: React.CSSProperties
}) {
const [coordinatePreview, dispatchCoordinatePreview] = props.coordinatePreviewReducer;
const [data, dispatchData] = props.dataReducer;
if (!coordinatePreview.visible) {
return <></>
}
const dataModel = data.data.filter(d => d.id == coordinatePreview.dataId)[0];
if (!dataModel) {
return <></>
}
if (dataModel.type == '3d-positions') {
const dataModel3d = dataModel as BinPositionsData;
const [genomicFrom, genomicTo] = binToGenomicCoordinate(coordinatePreview.from, dataModel3d.basePairsResolution)
return <div style={props.style}>Bin: {coordinatePreview.from} (Genomic: {genomicFrom} - {genomicTo})</div>
}
return <></>
}
\ No newline at end of file
import React, { Dispatch } from "react";
import './Tools.scss';
import { ChromatinViewportConfiguration, ChromatinViewportTool, ChromatinViewportToolType, ConfigurationAction, ConfigurationState, ViewportConfigurationType } from '../../modules/storage/models/viewports';
import { ChromatinViewportConfiguration, ChromatinViewportToolType, ConfigurationAction, ConfigurationState, ToolConfiguration, ViewportConfigurationType } from '../../modules/storage/models/viewports';
import { useConfigurationTypeless } from "../hooks";
import { Label, Separator, Slider, Stack } from "@fluentui/react";
import { Text } from '@fluentui/react/lib/Text';
......@@ -28,16 +28,19 @@ export function ToolOptions(props: {
} as ChromatinViewportConfiguration);
}
switch (tool.type) {
// Chromatin Sphere Selecetion
case ChromatinViewportToolType.PointSelection:
return <Stack horizontal styles={{ root: { height: '100%' } }} tokens={{ childrenGap: '8px', padding: '8px' }} >
<Stack.Item align="center">
<Text nowrap variant='medium'>Hold <b>CONTROL</b> and click a pointed bin to the selection. Hold <b>CTRL + ALT</b> to remove a bin from the selection.</Text>
</Stack.Item>
</Stack>
case ChromatinViewportToolType.SphereSelection:
return <Stack horizontal styles={{ root: { height: '100%' } }} tokens={{ childrenGap: '8px', padding: '8px' }} >
const toolOptionsMenuFactory = (tool: ToolConfiguration | undefined): JSX.Element => {
if (!tool) {
return <></>
}
if (tool.type == ChromatinViewportToolType.PointSelection) {
return <Stack.Item align="center">
<Text nowrap variant='medium'>Hold <b>CONTROL</b> and click a pointed bin to the selection. Hold <b>CTRL + ALT</b> to remove a bin from the selection.</Text>
</Stack.Item>
}
if (tool.type == ChromatinViewportToolType.SphereSelection) {
return <>
<Stack.Item align="center">
<Label>Spherical Selection</Label>
</Stack.Item>
......@@ -56,10 +59,10 @@ export function ToolOptions(props: {
<Stack.Item align="center">
<Text nowrap variant='medium'>Hold <b>CONTROL</b> and click to add all bins in the sphere to the selection. Hold <b>CTRL + ALT</b> to remove bins from the selection.</Text>
</Stack.Item>
</Stack>
// Chromatin Join Path Selection
case ChromatinViewportToolType.JoinSelection:
return <Stack horizontal styles={{ root: { height: '100%' } }} tokens={{ childrenGap: '8px', padding: '8px' }} >
</>
}
if (tool.type == ChromatinViewportToolType.JoinSelection) {
return <>
<Stack.Item align="center">
{tool.from == null && (<Text nowrap variant='medium'>Select a bin as starting point for a path.</Text>)}
</Stack.Item>
......@@ -71,7 +74,19 @@ export function ToolOptions(props: {
<Stack.Item align="center">
<Text nowrap variant='medium'>Hold <b>CONTROL</b> to select a path and add all bins on it to the selection. Hold <b>CTRL + ALT</b> to remove bins on the path from the selection.</Text>
</Stack.Item>
</Stack>
default: return <div></div>
</>
}
return <></>
}
return <Stack horizontal styles={{ root: { height: '100%' } }} tokens={{ childrenGap: '8px', padding: '0px 8px' }} >
{
toolOptionsMenuFactory(tool)
}
</Stack>
}
......@@ -3,7 +3,7 @@ import { Dispatch, useState } from "react";
import { TextFile, UploadTextFilesButton } from "../buttons/UploadTextFilesButton";
import { ApplicationState } from "../../modules/storage/state";
import { DataAction, DataActionKind, DataState } from "../../modules/storage/models/data";
import { fromBEDtoSparse1DNumericData, fromBEDtoSparse1DTextData } from "../../modules/1d_data_utils";
import { fromBEDtoSparse1DNumericData, fromBEDtoSparse1DTextData } from "../../modules/coordniatesUtils";
import { CSVDelimiter, parseBED } from "../../modules/parsing";
export function NewGenomicDataDialog(props: {
......
......@@ -4,12 +4,14 @@ import { ChromatinViewportConfiguration, ConfigurationAction, ConfigurationState
import { useCustomCompareEffect, useDeepCompareEffect, useMouse, usePrevious } from "react-use";
import { ChromatinIntersection, ChromatinPart, ChromatinRepresentation, ContinuousTube, Sphere, Spheres, CullPlane, BinPosition } from "../../modules/graphics";
import { vec3, vec4 } from "gl-matrix";
import { BinPositionsData, Data, DataAction, DataState, isoDataID, Position3D, Positions3D } from "../../modules/storage/models/data";
import { BinPositionsData, Data, DataAction, DataID, DataState, isoDataID, Position3D, Positions3D } from "../../modules/storage/models/data";
import { SelectionAction, SelectionActionKind, SelectionState } from "../../modules/storage/models/selections";
import produce from "immer";
import { useConfiguration } from "../hooks";
import { useKey } from "rooks";
import * as Chroma from "chroma-js";
import { CoordinatePreviewAction, CoordinatePreviewState } from "../../modules/storage/models/coordinatePreview";
import { iso } from "newtype-ts";
const SphereSelectionName = 'SPHERE_SELECTION';
......@@ -17,11 +19,13 @@ export function ChromatinViewport(props: {
graphicsLibrary: GraphicsModule.GraphicsLibrary,
configurationID: number,
configurationsReducer: [ConfigurationState, Dispatch<ConfigurationAction>],
coordinatePreviewReducer: [CoordinatePreviewState, React.Dispatch<CoordinatePreviewAction>],
dataReducer: [DataState, Dispatch<DataAction>],
selectionsReducer: [SelectionState, Dispatch<SelectionAction>],
}): JSX.Element {
// Configuration/Data
const configurationReducer = useConfiguration<ChromatinViewportConfiguration>(props.configurationID, props.configurationsReducer);
const [coordinatePreview, dispatchCoordinatePreview] = props.coordinatePreviewReducer;
const [data, dataDispatch] = props.dataReducer;
const [globalSelections, globalSelectionsDispatch] = props.selectionsReducer;
......@@ -268,73 +272,63 @@ export function ChromatinViewport(props: {
setClosestIntersection(() => viewport.closestIntersectionBin({ x: mousePosition.elX * window.devicePixelRatio, y: mousePosition.elY * window.devicePixelRatio }));
}, [viewport, mousePosition]);
// Calculate 1D Mapping
useEffect(() => {
const distances = [];
if (!closestIntersection) {
dispatchCoordinatePreview({
visible: false
})
return;
}