Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Hooks for multi-picking and cursor-tracking, changes to python directory to provide functionality in Dash #2398

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
390 changes: 196 additions & 194 deletions python/package-lock.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions python/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"validate": "npm run typecheck && npm run lint"
},
"dependencies": {
"@deck.gl/core": "^8.9.35",
"@deck.gl/core": "^9.0.36",
"@deck.gl/react": "^9.0.36",
"@emerson-eps/color-tables": "^0.4.85",
"@equinor/eds-core-react": "0.33.0",
"@equinor/eds-icons": "^0.19.1",
Expand All @@ -45,8 +46,8 @@
"leaflet-draw": "^1.0.4",
"lodash": "^4.17.21",
"mathjs": "^9.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropdown-tree-select": "^2.8.0",
"react-redux": "^8.1.1",
"react-resize-detector": "^9.0.0"
Expand All @@ -61,7 +62,7 @@
"@types/leaflet": "^1.8.0",
"@types/leaflet-draw": "^1.0.8",
"@types/lodash": "^4.14.199",
"@types/react": "^18.2.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
Expand Down
202 changes: 195 additions & 7 deletions python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,209 @@
import React from "react";
import { SubsurfaceViewerProps } from "@webviz/subsurface-viewer";
import {
MapMouseEvent,
SubsurfaceViewerProps,
ViewStateType,
} from "@webviz/subsurface-viewer";
import { DeckGLRef } from "@deck.gl/react";
import { PickingInfoPerView, useMultiViewPicking } from "@webviz/subsurface-viewer/src/hooks/useMultiViewPicking";

Check failure on line 8 in python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Replace `·PickingInfoPerView,·useMultiViewPicking·` with `⏎····PickingInfoPerView,⏎····useMultiViewPicking,⏎`
import { useMultiViewCursorTracking } from "@webviz/subsurface-viewer/src/hooks/useMultiViewCursorTracking";
import { isEqual } from "lodash";
import ViewAnnotation from "../ViewAnnotation/ViewAnnotation";

const SubsurfaceViewerComponent = React.lazy(
() =>
import(
/* webpackChunkName: "webviz-subsurface-viewer" */ "@webviz/subsurface-viewer"
)
const SubsurfaceViewerComponent = React.lazy(() =>
import(
/* webpackChunkName: "webviz-subsurface-viewer" */ "@webviz/subsurface-viewer"
).then((module) => ({
default:
module.DashSubsurfaceViewer as unknown as React.ComponentType<SubsurfaceViewerProps>,
}))
);

const SubsurfaceViewer: React.FC<SubsurfaceViewerProps> = (props) => {
const { views, children, ...rest } = props;

if (!views) {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<SubsurfaceViewerComponent {...rest}>
{props.children}
</SubsurfaceViewerComponent>
</React.Suspense>
);
}

return (
<React.Suspense fallback={<div>Loading...</div>}>
<SubsurfaceViewerComponent {...props} />
<MultiViewSubsurfaceViewer {...rest} views={views}>
{children}
</MultiViewSubsurfaceViewer>
</React.Suspense>
);
};

function MultiViewSubsurfaceViewer(
props: SubsurfaceViewerProps &
Required<Pick<SubsurfaceViewerProps, "views">>
) {
const { onMouseEvent, getCameraPosition } = props;

const deckGlRef = React.useRef<DeckGLRef>(null);

const [mouseHover, setMouseHover] = React.useState<boolean>(false);
const [cameraPosition, setCameraPosition] = React.useState<
ViewStateType | undefined
>(undefined);
const [prevCameraPosition, setPrevCameraPosition] = React.useState<
ViewStateType | undefined
>(undefined);

if (!isEqual(prevCameraPosition, props.cameraPosition)) {
setPrevCameraPosition(props.cameraPosition);
}

const { getPickingInfo, activeViewportId, pickingInfoPerView } =
useMultiViewPicking({
deckGlRef,
multiPicking: true,
pickDepth: 1,
});

const handleMouseEvent = React.useCallback(
function handleMouseEvent(event: MapMouseEvent) {
if (event.type === "hover") {
getPickingInfo(event);
}
onMouseEvent?.(event);
},
[getPickingInfo, onMouseEvent]
);

const handleCameraPositionChange = React.useCallback(
function handleCameraPositionChange(position: ViewStateType) {
setCameraPosition(position);
getCameraPosition?.(position);
},
[getCameraPosition]
);

const viewports = props.views?.viewports ?? [];
const layers = props.layers ?? [];

const { viewports: adjustedViewports, layers: adjustedLayers } =
useMultiViewCursorTracking({
activeViewportId,
worldCoordinates:
pickingInfoPerView[activeViewportId]?.coordinates ?? null,
viewports,
layers,
crosshairProps: {
color: [255, 255, 255, 255],
sizePx: 32,
visible: mouseHover,
},
});

Check failure on line 105 in python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Delete `····`
const children = React.Children.toArray(props.children);
for (const viewport of adjustedViewports) {
children.push(
<ViewAnnotation key={viewport.id} id={viewport.id}>
<ReadoutComponent
viewId={viewport.id}
pickingInfoPerView={pickingInfoPerView}
/>
</ViewAnnotation>
);
}

return (
<div
onMouseEnter={() => setMouseHover(true)}
onMouseLeave={() => setMouseHover(false)}
onBlur={() => setMouseHover(false)}
onFocus={() => setMouseHover(true)}
>
<SubsurfaceViewerComponent
{...props}
coords={{visible: false}}

Check failure on line 127 in python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Replace `visible:·false` with `·visible:·false·`
onMouseEvent={handleMouseEvent}
layers={adjustedLayers}
views={{
...props.views,
viewports: adjustedViewports,
layout: props.views.layout,
}}
cameraPosition={cameraPosition}
deckGlRef={deckGlRef}
getCameraPosition={handleCameraPositionChange}
>
{children}
</SubsurfaceViewerComponent>
</div>
);
}

function ReadoutComponent(props: {
viewId: string;
pickingInfoPerView: PickingInfoPerView;
}): React.ReactNode {
return (
<div
style={{
position: "absolute",
bottom: 8,
left: 8,
background: "#fff",
padding: 8,
borderRadius: 4,
display: "grid",
gridTemplateColumns: "8rem auto",
border: "1px solid #ccc",
fontSize: "0.8rem",
}}
>
<div>X:</div>
<div>
{roundToSignificant(props.pickingInfoPerView[props.viewId]?.coordinates

Check failure on line 166 in python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Replace `props.pickingInfoPerView[props.viewId]?.coordinates` with `⏎····················props.pickingInfoPerView[props.viewId]?.coordinates?.at(0)`
?.at(0))}

Check failure on line 167 in python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Delete `····?.at(0)`
</div>
<div>Y:</div>
<div>
{roundToSignificant(props.pickingInfoPerView[props.viewId]?.coordinates

Check failure on line 171 in python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Replace `············{roundToSignificant(props.pickingInfoPerView[props.viewId]?.coordinates` with `················{roundToSignificant(⏎····················props.pickingInfoPerView[props.viewId]?.coordinates?.at(1)`
?.at(1))}

Check failure on line 172 in python/src/components/SubsurfaceViewer/SubsurfaceViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Delete `····?.at(1)`
</div>
{props.pickingInfoPerView[props.viewId]?.layerPickingInfo.map(
(el) => (
<React.Fragment key={`${el.layerId}`}>
<div style={{ fontWeight: "bold" }}>{el.layerName}</div>
{el.properties.map((prop, i) => (
<React.Fragment key={`${el.layerId}-${i}}`}>
<div style={{ gridColumn: 1 }}>{prop.name}</div>
<div>
{typeof prop.value === "string"
? prop.value
: roundToSignificant(prop.value)}
</div>
</React.Fragment>
))}
</React.Fragment>
)
) ?? ""}
</div>
);
}

const roundToSignificant = function (num: number | undefined) {
if (num === undefined) {
return "-";
}
// Returns two significant figures (non-zero) for numbers with an absolute value less
// than 1, and two decimal places for numbers with an absolute value greater
// than 1.
return parseFloat(
num.toExponential(Math.max(1, 2 + Math.log10(Math.abs(num))))
);
};

SubsurfaceViewer.displayName = "SubsurfaceViewer";

export default SubsurfaceViewer;
9 changes: 2 additions & 7 deletions python/src/components/WellLogViewer/WellLogViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@
}))
);

import type { ColorMapFunction } from "../components/ColorMapFunction";
import type { WellPickProps } from "../components/WellLogView";

Check failure on line 12 in python/src/components/WellLogViewer/WellLogViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Delete `⏎`
// react-docgen / dash-generate-components/extract-meta.js does not properly parse
// the imported WellLogViewerProps. Hence, we have to recreate them here.
/**
* WellLogView additional options
*/
type WellLogViewOptions = {
/** The maximum zoom value */
maxContentZoom?: number;
Expand Down Expand Up @@ -56,7 +51,7 @@
template: object;

/** Prop containing color function/table array */
colorMapFunctions: ColorMapFunction[];
colorMapFunctions: any;

Check failure on line 54 in python/src/components/WellLogViewer/WellLogViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Unexpected any. Specify a different type

/** Orientation of the track plots on the screen. Default is false */
horizontal?: boolean;
Expand All @@ -68,7 +63,7 @@
selection?: number[];

/** Well picks data */
wellpick?: WellPickProps;
wellpick?: any;

Check failure on line 66 in python/src/components/WellLogViewer/WellLogViewer.tsx

View workflow job for this annotation

GitHub Actions / python (3.8)

Unexpected any. Specify a different type

/** Primary axis id: " md", "tvd", "time"... */
primaryAxis?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { useMultiViewCursorTracking } from "./useMultiViewCursorTracking";

export type {
UseMultiViewCursorTrackingProps,
UseMultiViewCursorTrackingReturnType,
} from "./useMultiViewCursorTracking";
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from "react";
import { v4 } from "uuid";
import { CrosshairLayer } from "../../layers";

import type { CrosshairLayerProps } from "../../layers";
import type { ViewportType } from "../../views/viewport";
import type { TLayerDefinition } from "../../SubsurfaceViewer";

export type UseMultiViewCursorTrackingProps = {
worldCoordinates: number[] | null;
crosshairProps?: Omit<CrosshairLayerProps, "id" | "worldCoordinates">;
viewports: ViewportType[];
layers: TLayerDefinition[];
activeViewportId: string;
};

export type UseMultiViewCursorTrackingReturnType = {
viewports: ViewportType[];
layers: TLayerDefinition[];
};

export function useMultiViewCursorTracking(
props: UseMultiViewCursorTrackingProps
): UseMultiViewCursorTrackingReturnType {
const id = React.useRef<string>(`crosshair-${v4()}`);

let worldCoordinates: [number, number, number] | null = null;
if (props.worldCoordinates?.length === 3) {
worldCoordinates = props.worldCoordinates as [number, number, number];
}
if (props.worldCoordinates?.length === 2) {
worldCoordinates = [...props.worldCoordinates, 0] as [
number,
number,
number,
];
}

const crosshairLayer = new CrosshairLayer({
id: id.current,
worldCoordinates,
...props.crosshairProps,
});

const layers = [...props.layers, crosshairLayer];

const viewports = props.viewports.map((viewport) => {
if (viewport.id !== props.activeViewportId) {
return {
...viewport,
layerIds: [...(viewport.layerIds ?? []), id.current],
};
}

return viewport;
});

return {
viewports,
layers,
};
}
Loading
Loading