From 09bfc04e0d9ecee67d9a8ea33aeb7d53e1388f8f Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Tue, 25 Feb 2025 13:01:06 +0100 Subject: [PATCH 01/25] Renamed getStratigraphicUnits to getStratigraphicUnitsForCase. Added getStratigraphicUnitsForStratColumn --- .../primary/primary/routers/surface/router.py | 44 +++-- .../api/autogen/@tanstack/react-query.gen.ts | 37 ++++- frontend/src/api/autogen/sdk.gen.ts | 151 +++++++++++++----- frontend/src/api/autogen/types.gen.ts | 46 +++++- .../utils/layers/WellpicksLayer.ts | 4 +- 5 files changed, 212 insertions(+), 70 deletions(-) diff --git a/backend_py/primary/primary/routers/surface/router.py b/backend_py/primary/primary/routers/surface/router.py index 9bdc82807..2bda79db9 100644 --- a/backend_py/primary/primary/routers/surface/router.py +++ b/backend_py/primary/primary/routers/surface/router.py @@ -74,7 +74,9 @@ async def get_realization_surfaces_metadata( surf_meta_task = tg.create_task(access.get_realization_surfaces_metadata_async()) surf_meta_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-meta")) - strat_units_task = tg.create_task(_get_stratigraphic_units_for_case_async(authenticated_user, case_uuid)) + strat_units_task = tg.create_task( + _get_stratigraphic_units_for_strat_column_async(authenticated_user, case_uuid) + ) strat_units_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-strat")) perf_metrics.reset_lap_timer() @@ -106,7 +108,9 @@ async def get_observed_surfaces_metadata( surf_meta_task = tg.create_task(access.get_observed_surfaces_metadata_async()) surf_meta_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-meta")) - strat_units_task = tg.create_task(_get_stratigraphic_units_for_case_async(authenticated_user, case_uuid)) + strat_units_task = tg.create_task( + _get_stratigraphic_units_for_strat_column_async(authenticated_user, case_uuid) + ) strat_units_task.add_done_callback(lambda _: perf_metrics.record_lap_no_reset("get-strat")) perf_metrics.reset_lap_timer() @@ -314,8 +318,8 @@ async def get_wellbore_stratigraphic_columns( return [converters.to_api_stratigraphic_column(col) for col in strat_columns] -@router.get("/stratigraphic_units") -async def get_stratigraphic_units( +@router.get("/stratigraphic_units_for_case") +async def get_stratigraphic_units_for_case( # fmt:off response: Response, authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)], @@ -324,7 +328,29 @@ async def get_stratigraphic_units( ) -> list[schemas.StratigraphicUnit]: perf_metrics = ResponsePerfMetrics(response) - strat_units = await _get_stratigraphic_units_for_case_async(authenticated_user, case_uuid) + case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid) + strat_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async() + perf_metrics.record_lap("get-strat-ident") + + strat_units = await _get_stratigraphic_units_for_strat_column_async(authenticated_user, strat_column_identifier) + api_strat_units = [converters.to_api_stratigraphic_unit(strat_unit) for strat_unit in strat_units] + + LOGGER.info(f"Got stratigraphic units in: {perf_metrics.to_string()}") + + return api_strat_units + + +@router.get("/stratigraphic_units_for_strat_column") +async def get_stratigraphic_units_for_strat_column( + # fmt:off + response: Response, + authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)], + strat_column: Annotated[str, Query(description="SMDA stratigraphic column identifier")], + # fmt:on +) -> list[schemas.StratigraphicUnit]: + perf_metrics = ResponsePerfMetrics(response) + + strat_units = await _get_stratigraphic_units_for_strat_column_async(authenticated_user, strat_column) api_strat_units = [converters.to_api_stratigraphic_unit(strat_unit) for strat_unit in strat_units] LOGGER.info(f"Got stratigraphic units in: {perf_metrics.to_string()}") @@ -332,15 +358,11 @@ async def get_stratigraphic_units( return api_strat_units -async def _get_stratigraphic_units_for_case_async( - authenticated_user: AuthenticatedUser, case_uuid: str +async def _get_stratigraphic_units_for_strat_column_async( + authenticated_user: AuthenticatedUser, strat_column_identifier: str ) -> list[StratigraphicUnit]: perf_metrics = PerfMetrics() - case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid) - strat_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async() - perf_metrics.record_lap("get-strat-ident") - smda_access: SmdaAccess | DrogonSmdaAccess if is_drogon_identifier(strat_column_identifier=strat_column_identifier): smda_access = DrogonSmdaAccess() diff --git a/frontend/src/api/autogen/@tanstack/react-query.gen.ts b/frontend/src/api/autogen/@tanstack/react-query.gen.ts index 2bc749d40..b2c900fc8 100644 --- a/frontend/src/api/autogen/@tanstack/react-query.gen.ts +++ b/frontend/src/api/autogen/@tanstack/react-query.gen.ts @@ -45,7 +45,8 @@ import { getSensitivities, getStatisticalVectorData, getStatisticalVectorDataPerSensitivity, - getStratigraphicUnits, + getStratigraphicUnitsForCase, + getStratigraphicUnitsForStratColumn, getSurfaceData, getTableData, getTableDefinition, @@ -115,7 +116,8 @@ import type { GetSensitivitiesData_api, GetStatisticalVectorDataData_api, GetStatisticalVectorDataPerSensitivityData_api, - GetStratigraphicUnitsData_api, + GetStratigraphicUnitsForCaseData_api, + GetStratigraphicUnitsForStratColumnData_api, GetSurfaceDataData_api, GetTableDataData_api, GetTableDefinitionData_api, @@ -747,14 +749,14 @@ export const getWellboreStratigraphicColumnsOptions = (options: Options) => [ - createQueryKey("getStratigraphicUnits", options), +export const getStratigraphicUnitsForCaseQueryKey = (options: Options) => [ + createQueryKey("getStratigraphicUnitsForCase", options), ]; -export const getStratigraphicUnitsOptions = (options: Options) => { +export const getStratigraphicUnitsForCaseOptions = (options: Options) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { - const { data } = await getStratigraphicUnits({ + const { data } = await getStratigraphicUnitsForCase({ ...options, ...queryKey[0], signal, @@ -762,7 +764,28 @@ export const getStratigraphicUnitsOptions = (options: Options +) => [createQueryKey("getStratigraphicUnitsForStratColumn", options)]; + +export const getStratigraphicUnitsForStratColumnOptions = ( + options: Options +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getStratigraphicUnitsForStratColumn({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getStratigraphicUnitsForStratColumnQueryKey(options), }); }; diff --git a/frontend/src/api/autogen/sdk.gen.ts b/frontend/src/api/autogen/sdk.gen.ts index 51998bd0d..a5939b475 100644 --- a/frontend/src/api/autogen/sdk.gen.ts +++ b/frontend/src/api/autogen/sdk.gen.ts @@ -114,9 +114,12 @@ import type { GetStatisticalVectorDataPerSensitivityError_api, GetStatisticalVectorDataPerSensitivityResponse_api, GetStatisticalVectorDataResponse_api, - GetStratigraphicUnitsData_api, - GetStratigraphicUnitsError_api, - GetStratigraphicUnitsResponse_api, + GetStratigraphicUnitsForCaseData_api, + GetStratigraphicUnitsForCaseError_api, + GetStratigraphicUnitsForCaseResponse_api, + GetStratigraphicUnitsForStratColumnData_api, + GetStratigraphicUnitsForStratColumnError_api, + GetStratigraphicUnitsForStratColumnResponse_api, GetSurfaceDataData_api, GetSurfaceDataError_api, GetSurfaceDataResponse_api, @@ -349,12 +352,14 @@ export const getTimestampsList = ( export const getHistoricalVectorData = ( options: Options, ) => { - return (options?.client ?? client).get( - { - ...options, - url: "/timeseries/historical_vector_data/", - }, - ); + return (options?.client ?? client).get< + GetHistoricalVectorDataResponse_api, + GetHistoricalVectorDataError_api, + ThrowOnError + >({ + ...options, + url: "/timeseries/historical_vector_data/", + }); }; /** @@ -435,10 +440,12 @@ export const getRealizationVectorAtTimestamp = ( options: Options, ) => { - return (options?.client ?? client).get({ - ...options, - url: "/inplace_volumetrics/table_definitions/", - }); + return (options?.client ?? client).get( + { + ...options, + url: "/inplace_volumetrics/table_definitions/", + }, + ); }; /** @@ -606,10 +613,12 @@ export const postGetSampleSurfaceInPoints = ( options: Options, ) => { - return (options?.client ?? client).get({ - ...options, - url: "/surface/delta_surface_data", - }); + return (options?.client ?? client).get( + { + ...options, + url: "/surface/delta_surface_data", + }, + ); }; /** @@ -618,7 +627,11 @@ export const getDeltaSurfaceData = ( export const getMisfitSurfaceData = ( options: Options, ) => { - return (options?.client ?? client).get({ + return (options?.client ?? client).get< + GetMisfitSurfaceDataResponse_api, + GetMisfitSurfaceDataError_api, + ThrowOnError + >({ ...options, url: "/surface/misfit_surface_data", }); @@ -641,14 +654,34 @@ export const getWellboreStratigraphicColumns = ( - options: Options, +export const getStratigraphicUnitsForCase = ( + options: Options, ) => { - return (options?.client ?? client).get({ + return (options?.client ?? client).get< + GetStratigraphicUnitsForCaseResponse_api, + GetStratigraphicUnitsForCaseError_api, + ThrowOnError + >({ ...options, - url: "/surface/stratigraphic_units", + url: "/surface/stratigraphic_units_for_case", + }); +}; + +/** + * Get Stratigraphic Units For Strat Column + */ +export const getStratigraphicUnitsForStratColumn = ( + options: Options, +) => { + return (options?.client ?? client).get< + GetStratigraphicUnitsForStratColumnResponse_api, + GetStratigraphicUnitsForStratColumnError_api, + ThrowOnError + >({ + ...options, + url: "/surface/stratigraphic_units_for_strat_column", }); }; @@ -701,10 +734,12 @@ export const getParameters = ( export const getIsSensitivityRun = ( options: Options, ) => { - return (options?.client ?? client).get({ - ...options, - url: "/parameters/is_sensitivity_run/", - }); + return (options?.client ?? client).get( + { + ...options, + url: "/parameters/is_sensitivity_run/", + }, + ); }; /** @@ -814,7 +849,11 @@ export const getTableData = ( export const getWellCompletionsData = ( options: Options, ) => { - return (options?.client ?? client).get({ + return (options?.client ?? client).get< + GetWellCompletionsDataResponse_api, + GetWellCompletionsDataError_api, + ThrowOnError + >({ ...options, url: "/well_completions/well_completions_data/", }); @@ -844,10 +883,12 @@ export const getDrilledWellboreHeaders = ( export const getWellTrajectories = ( options: Options, ) => { - return (options?.client ?? client).get({ - ...options, - url: "/well/well_trajectories/", - }); + return (options?.client ?? client).get( + { + ...options, + url: "/well/well_trajectories/", + }, + ); }; /** @@ -924,7 +965,11 @@ export const getWellborePicksInStratColumn = ( options: Options, ) => { - return (options?.client ?? client).get({ + return (options?.client ?? client).get< + GetWellboreCompletionsResponse_api, + GetWellboreCompletionsError_api, + ThrowOnError + >({ ...options, url: "/well/wellbore_completions/", }); @@ -950,12 +995,14 @@ export const getWellboreCasings = ( export const getWellborePerforations = ( options: Options, ) => { - return (options?.client ?? client).get( - { - ...options, - url: "/well/wellbore_perforations/", - }, - ); + return (options?.client ?? client).get< + GetWellborePerforationsResponse_api, + GetWellborePerforationsError_api, + ThrowOnError + >({ + ...options, + url: "/well/wellbore_perforations/", + }); }; /** @@ -996,7 +1043,11 @@ export const getLogCurveData = ( export const getSeismicCubeMetaList = ( options: Options, ) => { - return (options?.client ?? client).get({ + return (options?.client ?? client).get< + GetSeismicCubeMetaListResponse_api, + GetSeismicCubeMetaListError_api, + ThrowOnError + >({ ...options, url: "/seismic/seismic_cube_meta_list/", }); @@ -1054,7 +1105,11 @@ export const getDepthSlice = ( export const postGetSeismicFence = ( options: Options, ) => { - return (options?.client ?? client).post({ + return (options?.client ?? client).post< + PostGetSeismicFenceResponse_api, + PostGetSeismicFenceError_api, + ThrowOnError + >({ ...options, headers: { "Content-Type": "application/json", @@ -1071,7 +1126,11 @@ export const postGetSeismicFence = ( export const getPolygonsDirectory = ( options: Options, ) => { - return (options?.client ?? client).get({ + return (options?.client ?? client).get< + GetPolygonsDirectoryResponse_api, + GetPolygonsDirectoryError_api, + ThrowOnError + >({ ...options, url: "/polygons/polygons_directory/", }); @@ -1154,7 +1213,9 @@ export const getVfpTableNames = ( /** * Get Vfp Table */ -export const getVfpTable = (options: Options) => { +export const getVfpTable = ( + options: Options, +) => { return (options?.client ?? client).get({ ...options, url: "/vfp/vfp_table/", @@ -1164,7 +1225,9 @@ export const getVfpTable = (options: Optio /** * Login Route */ -export const loginRoute = (options?: Options) => { +export const loginRoute = ( + options?: Options, +) => { return (options?.client ?? client).get({ ...options, url: "/login", diff --git a/frontend/src/api/autogen/types.gen.ts b/frontend/src/api/autogen/types.gen.ts index 8997bb723..48ea2b6f7 100644 --- a/frontend/src/api/autogen/types.gen.ts +++ b/frontend/src/api/autogen/types.gen.ts @@ -2248,7 +2248,7 @@ export type GetWellboreStratigraphicColumnsResponses_api = { export type GetWellboreStratigraphicColumnsResponse_api = GetWellboreStratigraphicColumnsResponses_api[keyof GetWellboreStratigraphicColumnsResponses_api]; -export type GetStratigraphicUnitsData_api = { +export type GetStratigraphicUnitsForCaseData_api = { body?: never; path?: never; query: { @@ -2257,26 +2257,60 @@ export type GetStratigraphicUnitsData_api = { */ case_uuid: string; }; - url: "/surface/stratigraphic_units"; + url: "/surface/stratigraphic_units_for_case"; }; -export type GetStratigraphicUnitsErrors_api = { +export type GetStratigraphicUnitsForCaseErrors_api = { /** * Validation Error */ 422: HttpValidationError_api; }; -export type GetStratigraphicUnitsError_api = GetStratigraphicUnitsErrors_api[keyof GetStratigraphicUnitsErrors_api]; +export type GetStratigraphicUnitsForCaseError_api = + GetStratigraphicUnitsForCaseErrors_api[keyof GetStratigraphicUnitsForCaseErrors_api]; -export type GetStratigraphicUnitsResponses_api = { +export type GetStratigraphicUnitsForCaseResponses_api = { /** * Successful Response */ 200: Array; }; -export type GetStratigraphicUnitsResponse_api = GetStratigraphicUnitsResponses_api[keyof GetStratigraphicUnitsResponses_api]; +export type GetStratigraphicUnitsForCaseResponse_api = + GetStratigraphicUnitsForCaseResponses_api[keyof GetStratigraphicUnitsForCaseResponses_api]; + +export type GetStratigraphicUnitsForStratColumnData_api = { + body?: never; + path?: never; + query: { + /** + * SMDA stratigraphic column identifier + */ + strat_column: string; + }; + url: "/surface/stratigraphic_units_for_strat_column"; +}; + +export type GetStratigraphicUnitsForStratColumnErrors_api = { + /** + * Validation Error + */ + 422: HttpValidationError_api; +}; + +export type GetStratigraphicUnitsForStratColumnError_api = + GetStratigraphicUnitsForStratColumnErrors_api[keyof GetStratigraphicUnitsForStratColumnErrors_api]; + +export type GetStratigraphicUnitsForStratColumnResponses_api = { + /** + * Successful Response + */ + 200: Array; +}; + +export type GetStratigraphicUnitsForStratColumnResponse_api = + GetStratigraphicUnitsForStratColumnResponses_api[keyof GetStratigraphicUnitsForStratColumnResponses_api]; export type GetParameterNamesAndDescriptionData_api = { body?: never; diff --git a/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts b/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts index ea1d6a2f1..1f8cdc64d 100644 --- a/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts +++ b/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts @@ -1,4 +1,4 @@ -import { getStratigraphicUnitsOptions, getWellborePicksForWellboreOptions } from "@api"; +import { getStratigraphicUnitsForCaseOptions, getWellborePicksForWellboreOptions } from "@api"; import { transformFormationData } from "@equinor/esv-intersection"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import type { QueryClient } from "@tanstack/query-core"; @@ -81,7 +81,7 @@ export class WellpicksLayer extends BaseLayer Date: Tue, 25 Feb 2025 13:02:37 +0100 Subject: [PATCH 02/25] Added abstract implementation of Layer interface --- .../_shared/LayerFramework/abstracts.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 frontend/src/modules/_shared/LayerFramework/abstracts.ts diff --git a/frontend/src/modules/_shared/LayerFramework/abstracts.ts b/frontend/src/modules/_shared/LayerFramework/abstracts.ts new file mode 100644 index 000000000..5101eb7a6 --- /dev/null +++ b/frontend/src/modules/_shared/LayerFramework/abstracts.ts @@ -0,0 +1,53 @@ +import { QueryClient } from "@tanstack/query-core"; + +import { ItemDelegate } from "./delegates/ItemDelegate"; +import { LayerColoringType, LayerDelegate } from "./delegates/LayerDelegate"; +import { LayerManager } from "./framework/LayerManager/LayerManager"; +import { Layer, SerializedLayer, Settings, SettingsContext, StoredData } from "./interfaces"; + +export abstract class LayerAbstractImpl< + TSettings extends Settings, + TData, + TStoredData extends StoredData = Record +> implements Layer +{ + private _layerDelegate: LayerDelegate; + private _itemDelegate: ItemDelegate; + + constructor( + layerManager: LayerManager, + name: string, + settingsContext: SettingsContext, + coloringType: LayerColoringType + ) { + this._itemDelegate = new ItemDelegate(name, layerManager); + this._layerDelegate = new LayerDelegate(this, layerManager, settingsContext, coloringType); + } + + getSettingsContext() { + return this._layerDelegate.getSettingsContext(); + } + + getItemDelegate(): ItemDelegate { + return this._itemDelegate; + } + + getLayerDelegate(): LayerDelegate { + return this._layerDelegate; + } + + serializeState(): SerializedLayer { + return this._layerDelegate.serializeState(); + } + deserializeState(serializedState: SerializedLayer): void { + this._layerDelegate.deserializeState(serializedState); + } + + abstract doSettingsChangesRequireDataRefetch(prevSettings: TSettings, newSettings: TSettings): boolean; + abstract fetchData(queryClient: QueryClient): Promise; + + // Optional methods from inter + // makeBoundingBox?(): BoundingBox | null; + // predictBoundingBox?(): BoundingBox | null; + // makeValueRange?(): [number, number] | null; +} From 27be7430e8ca3291eeeb0b9ccb276ab8c1ac9caa Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Tue, 25 Feb 2025 13:07:04 +0100 Subject: [PATCH 03/25] WIP: Implemented well-picks layer for well-log-viewer --- .../layers/WellPicksLayer/WellPicksLayer.ts | 38 ++++ .../WellPicksLayer/WellPicksLayerContext.ts | 129 ++++++++++++++ .../layers/WellPicksLayer/index.ts | 0 .../layers/WellPicksLayer/types.ts | 13 ++ .../WellLogViewer/settings/atoms/baseAtoms.ts | 4 + .../settings/atoms/persistedAtoms.ts | 6 + .../LayerManagerComponentWrapper.tsx | 164 ++++++++++++++++++ .../templateTrackSettings.tsx | 28 +-- .../WellLogViewer/settings/settings.tsx | 23 ++- .../DataLayerManager/DataLayerManager.ts | 2 + .../settings/SettingRegistry.ts | 8 +- .../ObjectSelectionSetting.tsx | 153 ++++++++++++++++ .../SingleStringChoiceSetting.tsx | 75 ++++++++ .../StringSelectionSetting.tsx | 108 ++++++++++++ .../settings/settingsDefinitions.ts | 15 +- 15 files changed, 744 insertions(+), 22 deletions(-) create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/index.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts create mode 100644 frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx create mode 100644 frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx create mode 100644 frontend/src/modules/_shared/LayerFramework/settings/implementations/SingleStringChoiceSetting.tsx create mode 100644 frontend/src/modules/_shared/LayerFramework/settings/implementations/StringSelectionSetting.tsx diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts new file mode 100644 index 000000000..bc254951a --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts @@ -0,0 +1,38 @@ +import { WellborePick_api } from "@api"; +import { LayerAbstractImpl } from "@modules/_shared/LayerFramework/abstracts"; +import { LayerColoringType } from "@modules/_shared/LayerFramework/delegates/LayerDelegate"; +import { LayerManager } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; +import { LayerRegistry } from "@modules/_shared/LayerFramework/layers/LayerRegistry"; +import { SettingType } from "@modules/_shared/LayerFramework/settings/settingsTypes"; + +import _ from "lodash"; + +import { WellPicksSettingsContext } from "./WellPicksLayerContext"; +import { WellPicksLayerSettings } from "./types"; + +export class WellPicksLayer extends LayerAbstractImpl { + constructor(layerManager: LayerManager) { + const settingsContext = new WellPicksSettingsContext(layerManager); + + super(layerManager, "Bore Well-picks", settingsContext, LayerColoringType.COLORSET); + } + + doSettingsChangesRequireDataRefetch( + prevSettings: WellPicksLayerSettings, + newSettings: WellPicksLayerSettings + ): boolean { + return !_.isEqual(prevSettings, newSettings); + } + + fetchData(): Promise { + const settings = this.getSettingsContext().getDelegate().getSettings(); + const chosenWellPicks = settings[SettingType.WELL_PICKS].getDelegate().getValue() ?? []; + + // ! Not actually any reason for this to be a promise. No data to fetch, it's already available in the well-picks + return new Promise((resolve) => { + resolve(chosenWellPicks); + }); + } +} + +LayerRegistry.registerLayer(WellPicksLayer); diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts new file mode 100644 index 000000000..4f9da2eea --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts @@ -0,0 +1,129 @@ +import { + WellborePick_api, + getStratigraphicUnitsForStratColumnOptions, + getWellborePicksInStratColumnOptions, + getWellboreStratigraphicColumnsOptions, +} from "@api"; +import { SettingsContextDelegate } from "@modules/_shared/LayerFramework/delegates/SettingsContextDelegate"; +import { LayerManager } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; +import { DefineDependenciesArgs, SettingsContext } from "@modules/_shared/LayerFramework/interfaces"; +import { ObjectSelectionSetting } from "@modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting"; +import { SingleStringChoiceSetting } from "@modules/_shared/LayerFramework/settings/implementations/SingleStringChoiceSetting"; +import { SettingType } from "@modules/_shared/LayerFramework/settings/settingsTypes"; + +import _ from "lodash"; + +import { WellPicksLayerSettings } from "./types"; + +export class WellPicksSettingsContext implements SettingsContext { + private _contextDelegate: SettingsContextDelegate; + + constructor(layerManager: LayerManager) { + const settings = { + [SettingType.STRAT_COLUMN]: new SingleStringChoiceSetting("Stratigraphic Column", SettingType.STRAT_COLUMN), + [SettingType.SMDA_INTERPRETER]: new SingleStringChoiceSetting("Interpreter", SettingType.SMDA_INTERPRETER), + [SettingType.WELL_PICKS]: new ObjectSelectionSetting( + "Well picks", + SettingType.WELL_PICKS, + "pickIdentifier", + "pickIdentifier" + ), + }; + + this._contextDelegate = new SettingsContextDelegate(this, layerManager, settings); + } + + getDelegate() { + return this._contextDelegate; + } + + // ? This feels unintivite? I find it strange to define what is essentially derived- and query-atoms with these methods. Maybe its just the naming? + // availableSettingsUpdater -> getSettingOptions ? + // Feels a little like re-inventing the wheel... + defineDependencies(args: DefineDependenciesArgs): void { + const { helperDependency, availableSettingsUpdater, queryClient } = args; + + const columnOptions = helperDependency(({ getGlobalSetting, abortSignal }) => { + const wellboreUuid = getGlobalSetting("wellboreUuid"); + + if (!wellboreUuid) return null; + + return queryClient.fetchQuery({ + ...getWellboreStratigraphicColumnsOptions({ + query: { wellbore_uuid: wellboreUuid }, + signal: abortSignal, + }), + }); + }); + + const wellPickOptions = helperDependency(({ getGlobalSetting, getLocalSetting, abortSignal }) => { + const wellboreUuid = getGlobalSetting("wellboreUuid"); + const stratColumn = getLocalSetting(SettingType.STRAT_COLUMN); + + if (!wellboreUuid || !stratColumn) return null; + return queryClient.fetchQuery({ + ...getWellborePicksInStratColumnOptions({ + query: { wellbore_uuid: wellboreUuid, strat_column: stratColumn }, + signal: abortSignal, + }), + }); + }); + + // ! unused for now + const wellPickUnits = helperDependency(({ getLocalSetting, abortSignal }) => { + const stratColumn = getLocalSetting(SettingType.STRAT_COLUMN); + + if (!stratColumn) return null; + return queryClient.fetchQuery({ + ...getStratigraphicUnitsForStratColumnOptions({ + query: { strat_column: stratColumn }, + signal: abortSignal, + }), + }); + }); + + availableSettingsUpdater(SettingType.STRAT_COLUMN, ({ getHelperDependency }) => { + const columns = getHelperDependency(columnOptions); + + if (!columns) return []; + return _.map(columns, "identifier"); + }); + + availableSettingsUpdater(SettingType.SMDA_INTERPRETER, ({ getHelperDependency }) => { + const wellPicks = getHelperDependency(wellPickOptions); + + if (!wellPicks) return []; + + const picksByInterpreter = _.groupBy(wellPicks, "interpreter"); + + return _.keys(picksByInterpreter); + }); + + availableSettingsUpdater(SettingType.WELL_PICKS, ({ getLocalSetting, getHelperDependency }) => { + const wellPicks = getHelperDependency(wellPickOptions); + const pickUnits = getHelperDependency(wellPickUnits); + const interpreter = getLocalSetting(SettingType.SMDA_INTERPRETER); + + if (!wellPicks || !interpreter || !pickUnits) return []; + + return _.filter(wellPicks, ["interpreter", interpreter]); + + // TODO: Maybe merge the unit ones together? + // const filteredPicks = _.filter(wellPicks, ["interpreter", interpreter]); + + // // @ts-expect-error - Complains about "lithology-type" but that field actually doesn't matter + // const { nonUnitPicks, unitPicks } = transformFormationData(filteredPicks, pickUnits); + + // console.log(nonUnitPicks, unitPicks); + + // if (nonUnitPicks.length) { + // // ? Is this relevant for users, or? Should we propagate this to the ui? + // console.warn(`Found ${nonUnitPicks.length} non-unit picks!`, nonUnitPicks); + // } + + // return unitPicks; + }); + } + + areCurrentSettingsValid?: ((settings: WellPicksLayerSettings) => boolean) | undefined; +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/index.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts new file mode 100644 index 000000000..142e8f2d3 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts @@ -0,0 +1,13 @@ +import { WellborePick_api } from "@api"; +import { transformFormationData } from "@equinor/esv-intersection"; +import { SettingType } from "@modules/_shared/LayerFramework/settings/settingsTypes"; + +// ! esv-intersection doesn't export this, so need derive it like this +// unused for now, miiiight use later +export type PairedUnitPick = ReturnType["unitPicks"][0]; + +export type WellPicksLayerSettings = { + [SettingType.STRAT_COLUMN]: string | null; + [SettingType.SMDA_INTERPRETER]: string | null; + [SettingType.WELL_PICKS]: WellborePick_api[] | null; +}; diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts index 1d07a3354..b176e5230 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts @@ -1,5 +1,9 @@ +import { LayerManager } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; + import { atom } from "jotai"; +export const layerManagerAtom = atom(null); + export const userSelectedFieldIdentifierAtom = atom(null); export const userSelectedWellboreUuidAtom = atom(null); diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts index e1f7379b1..f11db5513 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts @@ -1,5 +1,6 @@ import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; import { atomWithModuleInstanceStorage, clearModuleInstanceStorage } from "@modules/WellLogViewer/utils/atoms"; +import { SerializedLayerManager } from "@modules/_shared/LayerFramework/interfaces"; import type { Getter, Setter } from "jotai"; import { atom } from "jotai"; @@ -34,6 +35,11 @@ export const padDataWithEmptyRowsAtom = atom( (get, set, newVal) => setPersistentModuleField(get, set, "padDataWithEmptyRows", newVal), ); +export const serializedManagerStateAtom = atom( + (get) => getPersistentModuleField(get, "layerManagerState", undefined), + (get, set, newVal) => setPersistentModuleField(get, set, "layerManagerState", newVal) +); + export function clearStorageForInstance(instanceId: string) { clearModuleInstanceStorage(instanceId, STORAGE_KEY); } diff --git a/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx new file mode 100644 index 000000000..d33672e69 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx @@ -0,0 +1,164 @@ +import React from "react"; + +import { WorkbenchSession } from "@framework/WorkbenchSession"; +import { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { WellPicksLayer } from "@modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer"; +import { LayersActionGroup } from "@modules/_shared/LayerFramework/LayersActions"; +import { GroupDelegate, GroupDelegateTopic } from "@modules/_shared/LayerFramework/delegates/GroupDelegate"; +import { LayerManager, LayerManagerTopic } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; +import { LayerManagerComponent } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManagerComponent"; +import { useQueryClient } from "@tanstack/react-query"; + +import { useAtom } from "jotai"; + +import { layerManagerAtom } from "../atoms/baseAtoms"; +import { serializedManagerStateAtom } from "../atoms/persistedAtoms"; + +enum LayerActionIdents { + TRACK = "track", + SETTINGS = "settings", + PLOT = "plot", + WELL_PICKS = "well_picks", +} + +const LAYER_ACTIONS: LayersActionGroup[] = [ + { + label: "Log viewer", + children: [ + { + label: "Track", + identifier: LayerActionIdents.TRACK, + }, + { + label: "Viewer Settings", + identifier: LayerActionIdents.SETTINGS, + }, + { + label: "Well picks", + identifier: LayerActionIdents.WELL_PICKS, + }, + ], + }, +]; + +function usePersistedLayerManager( + workbenchSession: WorkbenchSession, + workbenchSettings: WorkbenchSettings +): LayerManager | null { + const queryClient = useQueryClient(); + + const hasAppliedPersistedState = React.useRef(false); + const [layerManager, setLayerManager] = useAtom(layerManagerAtom); + const [serializedManagerState, setSerializedManagerState] = useAtom(serializedManagerStateAtom); + + // const applyPersistedManagerState = React.useCallback(function applyPersistedManagerState(layerManager) {}, [ + // serializedManagerState, + // ]); + + const persistManagerState = React.useCallback( + function persistManagerState() { + if (!layerManager) return; + + setSerializedManagerState(layerManager.serializeState()); + }, + [layerManager, setSerializedManagerState] + ); + + React.useEffect( + function initalizeLayerManagerEffect() { + const newLayerManager = new LayerManager(workbenchSession, workbenchSettings, queryClient); + setLayerManager(newLayerManager); + hasAppliedPersistedState.current = false; + + return () => newLayerManager.beforeDestroy(); + }, + [queryClient, setLayerManager, workbenchSession, workbenchSettings] + ); + + // applyPersistedManagerState(layerManager); + React.useEffect( + function applyManagerState() { + if (!layerManager || !serializedManagerState) return; + if (hasAppliedPersistedState.current) return; + + layerManager.deserializeState(serializedManagerState); + + hasAppliedPersistedState.current = true; + }, + [serializedManagerState, layerManager] + ); + + React.useEffect( + function setupManagerListenersEffect() { + if (!layerManager) return; + // + persistManagerState(); + + const unsubscribeDataRev = layerManager + .getPublishSubscribeDelegate() + .makeSubscriberFunction(LayerManagerTopic.LAYER_DATA_REVISION)(persistManagerState); + + const unsubscribeExpands = layerManager + .getGroupDelegate() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(GroupDelegateTopic.CHILDREN_EXPANSION_STATES)(persistManagerState); + + return function onUnmountEffect() { + unsubscribeDataRev(); + unsubscribeExpands(); + }; + }, + [layerManager, persistManagerState] + ); + + return layerManager; + + // const applyPersistedManagerState = React.useCallback(function applyPersistedManagerState(layerManager: LayerManager) { + // const serializedState = window.localStorage.getItem(`${props.moduleInstanceId}-settings`); + // if (!serializedState) return; + + // const parsedState = JSON.parse(serializedState); + // if (parsedState.fieldIdentifier) { + // layerManager.setFieldIdentifier(parsedState.fieldIdentifier); + // } + // if (parsedState.preferredViewLayout) { + // layerManager.setPreferredViewLayout(parsedState.preferredViewLayout); + // } + // }, []) + + // React.useEffect(() => applyPersistedState(layerManager), + // [layerManager, applyPersistedState] + // ); +} + +export type LayerManagerComponentWrapperProps = { + workbenchSession: WorkbenchSession; + workbenchSettings: WorkbenchSettings; +}; + +export function LayerManagerComponentWrapper(props: LayerManagerComponentWrapperProps): React.ReactNode { + const layerManager = usePersistedLayerManager(props.workbenchSession, props.workbenchSettings); + + const layerActionCallback = React.useCallback( + function layerActionCallback(identifier: string, groupDelegate: GroupDelegate) { + switch (identifier) { + case LayerActionIdents.WELL_PICKS: + return groupDelegate.appendChild(new WellPicksLayer(layerManager!)); + } + }, + [layerManager] + ); + + if (!layerManager) return
; + + return ( + <> + } + layerActions={LAYER_ACTIONS} + onLayerAction={layerActionCallback} + /> + + ); +} diff --git a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx b/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx index 8f4b3566e..400fae2b1 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx @@ -109,7 +109,7 @@ export function TemplateTrackSettings(props: TemplateTrackSettingsProps): React. ); return ( -
+
- - {trackConfigs.map((config) => ( - - ))} - +
+ + {trackConfigs.map((config) => ( + + ))} + +
); } diff --git a/frontend/src/modules/WellLogViewer/settings/settings.tsx b/frontend/src/modules/WellLogViewer/settings/settings.tsx index 22a81b7d9..9393732e3 100644 --- a/frontend/src/modules/WellLogViewer/settings/settings.tsx +++ b/frontend/src/modules/WellLogViewer/settings/settings.tsx @@ -18,10 +18,11 @@ import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePr import { useAtomValue, useSetAtom } from "jotai"; import _ from "lodash"; -import { userSelectedFieldIdentifierAtom, userSelectedWellboreUuidAtom } from "./atoms/baseAtoms"; +import { layerManagerAtom, userSelectedFieldIdentifierAtom, userSelectedWellboreUuidAtom } from "./atoms/baseAtoms"; import { selectedFieldIdentifierAtom, selectedWellboreHeaderAtom } from "./atoms/derivedAtoms"; import { availableFieldsQueryAtom, drilledWellboreHeadersQueryAtom } from "./atoms/queryAtoms"; -import { TemplateTrackSettings } from "./components/TemplateTrackSettings"; +import { LayerManagerComponentWrapper } from "./components/LayerManagerComponentWrapper"; +// import { TemplateTrackSettings } from "./components/TemplateTrackSettings"; import { ViewerSettings } from "./components/ViewerSettings"; import type { InterfaceTypes } from "../interfaces"; @@ -60,6 +61,7 @@ export function Settings(props: ModuleSettingsProps) { // Utilities const syncedSettingKeys = props.settingsContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, props.workbenchServices); + const layerManager = useAtomValue(layerManagerAtom); // Field selection const availableFields = useAtomValue(availableFieldsQueryAtom)?.data ?? []; @@ -86,6 +88,14 @@ export function Settings(props: ModuleSettingsProps) { const statusWriter = useSettingsStatusWriter(props.settingsContext); const wellboreHeadersErrorStatus = usePropagateApiErrorToStatusWriter(wellboreHeaders, statusWriter) ?? ""; + React.useEffect(() => { + layerManager?.updateGlobalSetting("fieldId", selectedField); + }, [layerManager, selectedField]); + + React.useEffect(() => { + layerManager?.updateGlobalSetting("wellboreUuid", selectedWellboreHeader?.wellboreUuid ?? null); + }, [layerManager, selectedWellboreHeader]); + return (
@@ -112,11 +122,16 @@ export function Settings(props: ModuleSettingsProps) { - + - + {/* */} + +
); } diff --git a/frontend/src/modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager.ts b/frontend/src/modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager.ts index d033b5294..6a08d7e3a 100644 --- a/frontend/src/modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager.ts +++ b/frontend/src/modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager.ts @@ -44,6 +44,7 @@ export type LayerManagerTopicPayload = { export type GlobalSettings = { fieldId: string | null; + wellboreUuid: string | null; ensembles: readonly RegularEnsemble[]; realizationFilterFunction: EnsembleRealizationFilterFunction; intersectionPolylines: IntersectionPolyline[]; @@ -229,6 +230,7 @@ export class DataLayerManager implements ItemGroup, PublishSubscribe CustomSettingImplementation = { new (params?: any): CustomSettingImplementation; - } + }, >( type: TSetting, label: string, customSettingImplementation: TSettingImpl, options?: { customConstructorParameters?: ConstructorParameters; - } + }, ): void { if (this._registeredSettings.has(type)) { throw new Error(`Setting ${type} already registered`); @@ -43,7 +43,7 @@ export class SettingRegistry { static makeSetting( type: TSetting, - defaultValue?: SettingTypes[TSetting] + defaultValue?: SettingTypes[TSetting], ): SettingManager { const stored = this._registeredSettings.get(type); if (!stored) { diff --git a/frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx b/frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx new file mode 100644 index 000000000..5b3e941ca --- /dev/null +++ b/frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx @@ -0,0 +1,153 @@ +import React from "react"; + +import { DenseIconButton } from "@lib/components/DenseIconButton"; +import { Select, SelectOption } from "@lib/components/Select"; +import { Deselect, SelectAll } from "@mui/icons-material"; + +import { SettingDelegate } from "../../delegates/SettingDelegate"; +import { AvailableValuesType, Setting, SettingComponentProps } from "../../interfaces"; +import { SettingRegistry } from "../SettingRegistry"; +import { SettingType } from "../settingsTypes"; + +type ValueType = T[] | null; + +/** + * ! Currently unsure if this one is keepable. Might be too complicated compared to what's offered + */ +export class ObjectSelectionSetting implements Setting> { + private _delegate: SettingDelegate>; + private _label: string; + private _settingType: SettingType; + private _optValKey: keyof T; + private _optLabelKey: keyof T; + private _multiple: boolean; + + constructor( + label: string, + settingType: SettingType, + optValKey: keyof T, + optLabelKey: keyof T, + multiple: boolean = false + ) { + this._delegate = new SettingDelegate>(null, this); + this._settingType = settingType; + this._optValKey = optValKey; + this._optLabelKey = optLabelKey; + this._multiple = multiple; + this._label = label; + } + + getConstructorParams() { + return [this._settingType, this._label, this._optValKey, this._optLabelKey, this._multiple]; + } + + getType(): SettingType { + return this._settingType; + } + + getLabel(): string { + return this._label; + } + + getDelegate(): SettingDelegate> { + return this._delegate; + } + + transformValueToOption(object: T): SelectOption { + return { + value: this.getOptionValue(object), + label: this.getOptionLabel(object), + }; + } + + getOptionValue(object: T): string { + const value = object[this._optValKey]; + + if (typeof value !== "string") throw new Error(`Expected object value to be a string, but got ${typeof value}`); + + return value; + } + + getOptionLabel(object: T): string { + const value = object[this._optLabelKey]; + + if (typeof value !== "string") throw new Error(`Expected object value to be a string, but got ${typeof value}`); + + return value; + } + + fixupValue(availableValues: AvailableValuesType>, currentValue: ValueType): ValueType { + if (!currentValue) { + return availableValues; + } + + const matchingValues = currentValue.filter((value) => + availableValues.some((availableValue) => this.getOptionValue(availableValue) === this.getOptionValue(value)) + ); + if (matchingValues.length === 0) { + return availableValues; + } + return matchingValues; + } + + makeComponent(): (props: SettingComponentProps>) => React.ReactNode { + // ! Bind class methods to ensure stable refs + const transformObjectToOption = this.transformValueToOption.bind(this); + const getObjectValue = this.getOptionValue.bind(this); + + return function DrilledWellbores(props: SettingComponentProps>) { + const options: SelectOption[] = React.useMemo( + () => props.availableValues.map(transformObjectToOption), + [props.availableValues] + ); + + function handleChange(selectedIdents: string[]) { + const selectedObjects = props.availableValues.filter((obj) => + selectedIdents.includes(getObjectValue(obj)) + ); + props.onValueChange(selectedObjects); + } + + function selectAll() { + const allIdents = props.availableValues.map((obj) => getObjectValue(obj)); + handleChange(allIdents); + } + + function selectNone() { + handleChange([]); + } + + const selectedValues = React.useMemo( + () => props.value?.map((obj) => getObjectValue(obj)) ?? [], + [props.value] + ); + + return ( +
+
+ + + Select all + + + + Clear selection + +
+ +
+ ); + }; + return renderFunc.bind(this); + } + + fixupValue?: ((availableValues: any[], currentValue: ValueType) => ValueType) | undefined; + isValueValid?: ((availableValues: any[], value: ValueType) => boolean) | undefined; + serializeValue?: ((value: ValueType) => string) | undefined; + deserializeValue?: ((serializedValue: string) => ValueType) | undefined; + valueToString?: ((args: ValueToStringArgs) => string) | undefined; +} + +SettingRegistry.registerSetting(StringSelectionSetting); diff --git a/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts b/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts index d32984fdb..bacb70d8a 100644 --- a/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts +++ b/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts @@ -1,4 +1,4 @@ -import type { SurfaceStatisticFunction_api, WellboreHeader_api } from "@api"; +import type { SurfaceStatisticFunction_api, WellboreHeader_api, WellborePick_api } from "@api"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import type { ColorScaleSpecification } from "@framework/components/ColorScaleSelector/colorScaleSelector"; import type { ColorSet } from "@lib/utils/ColorSet"; @@ -34,12 +34,16 @@ export enum Setting { POLYGONS_ATTRIBUTE = "polygonsAttribute", POLYGONS_NAME = "polygonsName", REALIZATION = "realization", + STRAT_COLUMN = "stratColumn", SEISMIC_CROSSLINE = "seismicCrossline", SEISMIC_DEPTH_SLICE = "seismicDepthSlice", SEISMIC_INLINE = "seismicInline", SENSITIVITY = "sensitivity", SHOW_GRID_LINES = "showGridLines", + SMDA_WELLBORE_HEADER = "smdaWellboreHeader", SMDA_WELLBORE_HEADERS = "smdaWellboreHeaders", + SMDA_INTERPRETER = "smdaInterpreter", + WELL_PICKS = "well_picks", SMDA_WELLBORE_PICKS = "smdaWellborePicks", STATISTIC_FUNCTION = "statisticFunction", SURFACE_NAME = "surfaceName", @@ -65,11 +69,16 @@ export const settingCategories = { [Setting.SEISMIC_INLINE]: SettingCategory.NUMBER_WITH_STEP, [Setting.SENSITIVITY]: SettingCategory.SINGLE_SELECT, [Setting.SHOW_GRID_LINES]: SettingCategory.BOOLEAN, + [Setting.SMDA_INTERPRETER]: SettingCategory.SINGLE_SELECT, + [Setting.SMDA_WELLBORE_HEADER]: SettingCategory.SINGLE_SELECT, [Setting.SMDA_WELLBORE_HEADERS]: SettingCategory.MULTI_SELECT, [Setting.SMDA_WELLBORE_PICKS]: SettingCategory.MULTI_SELECT, [Setting.STATISTIC_FUNCTION]: SettingCategory.SINGLE_SELECT, + [Setting.STRAT_COLUMN]: SettingCategory.SINGLE_SELECT, [Setting.SURFACE_NAME]: SettingCategory.SINGLE_SELECT, [Setting.TIME_OR_INTERVAL]: SettingCategory.SINGLE_SELECT, + // ? Use SMDA wellbore picks instead? + [Setting.WELL_PICKS]: SettingCategory.MULTI_SELECT, } as const; export type SettingCategories = typeof settingCategories; @@ -93,11 +102,15 @@ export type SettingTypes = { [Setting.SEISMIC_INLINE]: number | null; [Setting.SENSITIVITY]: SensitivityNameCasePair | null; [Setting.SHOW_GRID_LINES]: boolean; + [Setting.SMDA_WELLBORE_HEADER]: string | null; [Setting.SMDA_WELLBORE_HEADERS]: WellboreHeader_api[] | null; [Setting.SMDA_WELLBORE_PICKS]: string[] | null; + [Setting.SMDA_INTERPRETER]: string | null; [Setting.STATISTIC_FUNCTION]: SurfaceStatisticFunction_api; + [Setting.STRAT_COLUMN]: string | null; [Setting.SURFACE_NAME]: string | null; [Setting.TIME_OR_INTERVAL]: string | null; + [Setting.WELL_PICKS]: WellborePick_api[] | null; }; export type PossibleSettingsForCategory = { From 6bd6977535e1eb8b4ed2dbd29791e2c33ef49bca Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Thu, 3 Apr 2025 16:04:35 +0200 Subject: [PATCH 04/25] WIP -- Deleted old layer implementations --- .../layers/WellPicksLayer/WellPicksLayer.ts | 38 ------ .../WellPicksLayer/WellPicksLayerContext.ts | 129 ------------------ .../layers/WellPicksLayer/index.ts | 0 .../layers/WellPicksLayer/types.ts | 13 -- .../_shared/LayerFramework/abstracts.ts | 53 ------- .../SingleStringChoiceSetting.tsx | 75 ---------- 6 files changed, 308 deletions(-) delete mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts delete mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts delete mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/index.ts delete mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts delete mode 100644 frontend/src/modules/_shared/LayerFramework/abstracts.ts delete mode 100644 frontend/src/modules/_shared/LayerFramework/settings/implementations/SingleStringChoiceSetting.tsx diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts deleted file mode 100644 index bc254951a..000000000 --- a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { WellborePick_api } from "@api"; -import { LayerAbstractImpl } from "@modules/_shared/LayerFramework/abstracts"; -import { LayerColoringType } from "@modules/_shared/LayerFramework/delegates/LayerDelegate"; -import { LayerManager } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; -import { LayerRegistry } from "@modules/_shared/LayerFramework/layers/LayerRegistry"; -import { SettingType } from "@modules/_shared/LayerFramework/settings/settingsTypes"; - -import _ from "lodash"; - -import { WellPicksSettingsContext } from "./WellPicksLayerContext"; -import { WellPicksLayerSettings } from "./types"; - -export class WellPicksLayer extends LayerAbstractImpl { - constructor(layerManager: LayerManager) { - const settingsContext = new WellPicksSettingsContext(layerManager); - - super(layerManager, "Bore Well-picks", settingsContext, LayerColoringType.COLORSET); - } - - doSettingsChangesRequireDataRefetch( - prevSettings: WellPicksLayerSettings, - newSettings: WellPicksLayerSettings - ): boolean { - return !_.isEqual(prevSettings, newSettings); - } - - fetchData(): Promise { - const settings = this.getSettingsContext().getDelegate().getSettings(); - const chosenWellPicks = settings[SettingType.WELL_PICKS].getDelegate().getValue() ?? []; - - // ! Not actually any reason for this to be a promise. No data to fetch, it's already available in the well-picks - return new Promise((resolve) => { - resolve(chosenWellPicks); - }); - } -} - -LayerRegistry.registerLayer(WellPicksLayer); diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts deleted file mode 100644 index 4f9da2eea..000000000 --- a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayerContext.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { - WellborePick_api, - getStratigraphicUnitsForStratColumnOptions, - getWellborePicksInStratColumnOptions, - getWellboreStratigraphicColumnsOptions, -} from "@api"; -import { SettingsContextDelegate } from "@modules/_shared/LayerFramework/delegates/SettingsContextDelegate"; -import { LayerManager } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; -import { DefineDependenciesArgs, SettingsContext } from "@modules/_shared/LayerFramework/interfaces"; -import { ObjectSelectionSetting } from "@modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting"; -import { SingleStringChoiceSetting } from "@modules/_shared/LayerFramework/settings/implementations/SingleStringChoiceSetting"; -import { SettingType } from "@modules/_shared/LayerFramework/settings/settingsTypes"; - -import _ from "lodash"; - -import { WellPicksLayerSettings } from "./types"; - -export class WellPicksSettingsContext implements SettingsContext { - private _contextDelegate: SettingsContextDelegate; - - constructor(layerManager: LayerManager) { - const settings = { - [SettingType.STRAT_COLUMN]: new SingleStringChoiceSetting("Stratigraphic Column", SettingType.STRAT_COLUMN), - [SettingType.SMDA_INTERPRETER]: new SingleStringChoiceSetting("Interpreter", SettingType.SMDA_INTERPRETER), - [SettingType.WELL_PICKS]: new ObjectSelectionSetting( - "Well picks", - SettingType.WELL_PICKS, - "pickIdentifier", - "pickIdentifier" - ), - }; - - this._contextDelegate = new SettingsContextDelegate(this, layerManager, settings); - } - - getDelegate() { - return this._contextDelegate; - } - - // ? This feels unintivite? I find it strange to define what is essentially derived- and query-atoms with these methods. Maybe its just the naming? - // availableSettingsUpdater -> getSettingOptions ? - // Feels a little like re-inventing the wheel... - defineDependencies(args: DefineDependenciesArgs): void { - const { helperDependency, availableSettingsUpdater, queryClient } = args; - - const columnOptions = helperDependency(({ getGlobalSetting, abortSignal }) => { - const wellboreUuid = getGlobalSetting("wellboreUuid"); - - if (!wellboreUuid) return null; - - return queryClient.fetchQuery({ - ...getWellboreStratigraphicColumnsOptions({ - query: { wellbore_uuid: wellboreUuid }, - signal: abortSignal, - }), - }); - }); - - const wellPickOptions = helperDependency(({ getGlobalSetting, getLocalSetting, abortSignal }) => { - const wellboreUuid = getGlobalSetting("wellboreUuid"); - const stratColumn = getLocalSetting(SettingType.STRAT_COLUMN); - - if (!wellboreUuid || !stratColumn) return null; - return queryClient.fetchQuery({ - ...getWellborePicksInStratColumnOptions({ - query: { wellbore_uuid: wellboreUuid, strat_column: stratColumn }, - signal: abortSignal, - }), - }); - }); - - // ! unused for now - const wellPickUnits = helperDependency(({ getLocalSetting, abortSignal }) => { - const stratColumn = getLocalSetting(SettingType.STRAT_COLUMN); - - if (!stratColumn) return null; - return queryClient.fetchQuery({ - ...getStratigraphicUnitsForStratColumnOptions({ - query: { strat_column: stratColumn }, - signal: abortSignal, - }), - }); - }); - - availableSettingsUpdater(SettingType.STRAT_COLUMN, ({ getHelperDependency }) => { - const columns = getHelperDependency(columnOptions); - - if (!columns) return []; - return _.map(columns, "identifier"); - }); - - availableSettingsUpdater(SettingType.SMDA_INTERPRETER, ({ getHelperDependency }) => { - const wellPicks = getHelperDependency(wellPickOptions); - - if (!wellPicks) return []; - - const picksByInterpreter = _.groupBy(wellPicks, "interpreter"); - - return _.keys(picksByInterpreter); - }); - - availableSettingsUpdater(SettingType.WELL_PICKS, ({ getLocalSetting, getHelperDependency }) => { - const wellPicks = getHelperDependency(wellPickOptions); - const pickUnits = getHelperDependency(wellPickUnits); - const interpreter = getLocalSetting(SettingType.SMDA_INTERPRETER); - - if (!wellPicks || !interpreter || !pickUnits) return []; - - return _.filter(wellPicks, ["interpreter", interpreter]); - - // TODO: Maybe merge the unit ones together? - // const filteredPicks = _.filter(wellPicks, ["interpreter", interpreter]); - - // // @ts-expect-error - Complains about "lithology-type" but that field actually doesn't matter - // const { nonUnitPicks, unitPicks } = transformFormationData(filteredPicks, pickUnits); - - // console.log(nonUnitPicks, unitPicks); - - // if (nonUnitPicks.length) { - // // ? Is this relevant for users, or? Should we propagate this to the ui? - // console.warn(`Found ${nonUnitPicks.length} non-unit picks!`, nonUnitPicks); - // } - - // return unitPicks; - }); - } - - areCurrentSettingsValid?: ((settings: WellPicksLayerSettings) => boolean) | undefined; -} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/index.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts b/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts deleted file mode 100644 index 142e8f2d3..000000000 --- a/frontend/src/modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { WellborePick_api } from "@api"; -import { transformFormationData } from "@equinor/esv-intersection"; -import { SettingType } from "@modules/_shared/LayerFramework/settings/settingsTypes"; - -// ! esv-intersection doesn't export this, so need derive it like this -// unused for now, miiiight use later -export type PairedUnitPick = ReturnType["unitPicks"][0]; - -export type WellPicksLayerSettings = { - [SettingType.STRAT_COLUMN]: string | null; - [SettingType.SMDA_INTERPRETER]: string | null; - [SettingType.WELL_PICKS]: WellborePick_api[] | null; -}; diff --git a/frontend/src/modules/_shared/LayerFramework/abstracts.ts b/frontend/src/modules/_shared/LayerFramework/abstracts.ts deleted file mode 100644 index 5101eb7a6..000000000 --- a/frontend/src/modules/_shared/LayerFramework/abstracts.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { QueryClient } from "@tanstack/query-core"; - -import { ItemDelegate } from "./delegates/ItemDelegate"; -import { LayerColoringType, LayerDelegate } from "./delegates/LayerDelegate"; -import { LayerManager } from "./framework/LayerManager/LayerManager"; -import { Layer, SerializedLayer, Settings, SettingsContext, StoredData } from "./interfaces"; - -export abstract class LayerAbstractImpl< - TSettings extends Settings, - TData, - TStoredData extends StoredData = Record -> implements Layer -{ - private _layerDelegate: LayerDelegate; - private _itemDelegate: ItemDelegate; - - constructor( - layerManager: LayerManager, - name: string, - settingsContext: SettingsContext, - coloringType: LayerColoringType - ) { - this._itemDelegate = new ItemDelegate(name, layerManager); - this._layerDelegate = new LayerDelegate(this, layerManager, settingsContext, coloringType); - } - - getSettingsContext() { - return this._layerDelegate.getSettingsContext(); - } - - getItemDelegate(): ItemDelegate { - return this._itemDelegate; - } - - getLayerDelegate(): LayerDelegate { - return this._layerDelegate; - } - - serializeState(): SerializedLayer { - return this._layerDelegate.serializeState(); - } - deserializeState(serializedState: SerializedLayer): void { - this._layerDelegate.deserializeState(serializedState); - } - - abstract doSettingsChangesRequireDataRefetch(prevSettings: TSettings, newSettings: TSettings): boolean; - abstract fetchData(queryClient: QueryClient): Promise; - - // Optional methods from inter - // makeBoundingBox?(): BoundingBox | null; - // predictBoundingBox?(): BoundingBox | null; - // makeValueRange?(): [number, number] | null; -} diff --git a/frontend/src/modules/_shared/LayerFramework/settings/implementations/SingleStringChoiceSetting.tsx b/frontend/src/modules/_shared/LayerFramework/settings/implementations/SingleStringChoiceSetting.tsx deleted file mode 100644 index 9119bc669..000000000 --- a/frontend/src/modules/_shared/LayerFramework/settings/implementations/SingleStringChoiceSetting.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from "react"; - -import { Dropdown } from "@lib/components/Dropdown"; -import { SelectOption } from "@lib/components/Select"; - -import _ from "lodash"; - -import { SettingDelegate } from "../../delegates/SettingDelegate"; -import { Setting, SettingComponentProps } from "../../interfaces"; -import { SettingRegistry } from "../SettingRegistry"; -import { SettingType } from "../settingsTypes"; - -type ValueType = string | null; - -export class SingleStringChoiceSetting implements Setting { - private _delegate: SettingDelegate; - private _label: string; - private _settingType: SettingType; - - constructor(label: string, settingType: SettingType) { - this._label = label; - this._settingType = settingType; - this._delegate = new SettingDelegate(null, this); - } - - getLabel(): string { - return this._label; - } - - getType(): SettingType { - return this._settingType; - } - - getDelegate(): SettingDelegate { - return this._delegate; - } - - makeComponent(): (props: SettingComponentProps) => React.ReactNode { - return function StringSelectComp(props: SettingComponentProps) { - const options: SelectOption[] = React.useMemo(() => { - return props.availableValues.map((stringVals) => ({ - value: stringVals, - label: _.upperFirst(stringVals), - })); - }, [props.availableValues]); - - function handleChange(selected: string) { - props.onValueChange(selected); - } - - return ( -
- -
- ); - }; - } - - getConstructorParams?: (() => any[]) | undefined; - - // fixupValue?: ((availableValues: any[], currentValue: ValueType) => ValueType) | undefined; - // isValueValid?: ((availableValues: any[], value: ValueType) => boolean) | undefined; - // serializeValue?: ((value: ValueType) => string) | undefined; - // deserializeValue?: ((serializedValue: string) => ValueType) | undefined; - // valueToString?: ((args: ValueToStringArgs) => string) | undefined; - // getConstructorParams?: (() => any[]) | undefined; -} - -SettingRegistry.registerSetting(SingleStringChoiceSetting); From c6ed660bf79274ee3e9914f4bc6ee386baa7cf8c Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Fri, 4 Apr 2025 11:32:59 +0200 Subject: [PATCH 05/25] WIP: implement well-picks and basic plot configs --- .../dataProviders/plots/AreaPlotProvider.ts | 40 ++++++ .../dataProviders/plots/LinearPlotProvider.ts | 47 +++++++ .../dataProviders/plots/_shared.ts | 58 +++++++++ .../wellpicks/WellPicksProvider.ts | 108 ++++++++++++++++ .../groups/ContinuousLogTrack.ts | 24 ++++ .../registerFrameworkExtensions.ts | 16 +++ .../LayerFramework/visualizations/plots.ts | 64 ++++++++++ .../LayerFramework/visualizations/tracks.ts | 38 ++++++ .../visualizations/wellpicks.ts | 21 +++ .../src/modules/WellLogViewer/interfaces.ts | 14 +- .../WellLogViewer/settings/atoms/baseAtoms.ts | 4 +- .../settings/atoms/persistedAtoms.ts | 6 +- .../LayerManagerComponentWrapper.tsx | 120 +++++++++++------- .../WellLogViewer/utils/logViewerTemplate.ts | 16 +-- .../utils/useLogViewerVisualizationFactory.ts | 56 ++++++++ .../components/SubsurfaceLogViewerWrapper.tsx | 54 +++++--- .../src/modules/WellLogViewer/view/view.tsx | 44 ++----- .../LayerFramework/groups/groupTypes.ts | 1 + .../implementations/LogCurveSetting.tsx | 69 ++++++++++ .../implementations/NumberSetting.tsx | 54 ++++++++ .../ObjectSelectionSetting.tsx | 90 +++++-------- .../StaticDropdownStringSetting.tsx | 50 ++++++++ .../settings/registerAllSettings.ts | 30 +++++ .../settings/settingsDefinitions.ts | 32 ++++- .../visualization/VisualizationFactory.ts | 9 ++ 25 files changed, 880 insertions(+), 185 deletions(-) create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/_shared.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/groups/ContinuousLogTrack.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts create mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/visualizations/wellpicks.ts create mode 100644 frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts create mode 100644 frontend/src/modules/_shared/LayerFramework/settings/implementations/LogCurveSetting.tsx create mode 100644 frontend/src/modules/_shared/LayerFramework/settings/implementations/NumberSetting.tsx create mode 100644 frontend/src/modules/_shared/LayerFramework/settings/implementations/StaticDropdownStringSetting.tsx diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider.ts b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider.ts new file mode 100644 index 000000000..5b9dc6c0c --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider.ts @@ -0,0 +1,40 @@ +import type { WellboreLogCurveData_api } from "@api"; +import type { CustomDataLayerImplementation } from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; +import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; + +import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; + +export const AreaPlotSettings = [...baseLinearSettings, Setting.PLOT_VARIANT, Setting.COLOR_SCALE] as const; +export type AreaPlotSettingTypes = typeof AreaPlotSettings; +type SettingsTypeMap = MakeSettingTypesMap; + +export class AreaPlotProvider implements CustomDataLayerImplementation { + settings = AreaPlotSettings; + + // Uses the same external things as the other types + fetchData = fetchData; + defineDependencies(args: DefineDependenciesArgs) { + defineDependencies(args); + + args.availableSettingsUpdater(Setting.PLOT_VARIANT, () => { + return ["area", "gradientfill"]; + }); + } + + getDefaultName() { + return "Area plot"; + } + + areCurrentSettingsValid() { + // TODO + return true; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { + return true; + // return _.isEqual(prevSettings?.logCurve, newSettings?.logCurve); + } +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider.ts b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider.ts new file mode 100644 index 000000000..232505548 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider.ts @@ -0,0 +1,47 @@ +import type { WellboreLogCurveData_api } from "@api"; +import type { + CustomDataLayerImplementation, + DataLayerInformationAccessors, +} from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; +import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; + +import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; + +export const linearPlotSettings = [...baseLinearSettings, Setting.PLOT_VARIANT, Setting.COLOR] as const; +export type LinearPlotSettingTypes = typeof linearPlotSettings; +type SettingsTypeMap = MakeSettingTypesMap; + +export class LinearPlotProvider + implements CustomDataLayerImplementation +{ + // Uses the same external things as the other types + defineDependencies(args: DefineDependenciesArgs) { + defineDependencies(args); + + args.availableSettingsUpdater(Setting.PLOT_VARIANT, () => { + return ["line", "linestep", "dot"]; + }); + } + fetchData = fetchData; + settings = linearPlotSettings; + + getDefaultName() { + return "Linear plot"; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + areCurrentSettingsValid(accessor: DataLayerInformationAccessors) { + // TODO + + return true; + } + + // TODO: Figure out why prev-settings is undefined + // eslint-disable-next-line @typescript-eslint/no-unused-vars + doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { + // return !_.isEqual(prevSettings?.logCurve, newSettings?.logCurve); + return true; + } +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/_shared.ts b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/_shared.ts new file mode 100644 index 000000000..2c4245e2f --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/_shared.ts @@ -0,0 +1,58 @@ +import type { WellboreLogCurveData_api } from "@api"; +import { getLogCurveDataOptions, getWellboreLogCurveHeadersOptions } from "@api"; +import type { FetchDataParams } from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; +import { Setting, type Settings } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; + +export const baseSettings = [ + Setting.LOG_CURVE, + // TODO: Only needed to infer track settings. Remove once we can define view visualizations + Setting.TRACK_WIDTH, +] as const; + +export const baseLinearSettings = [...baseSettings, Setting.SCALE /*, Setting.COLOR */] as const; +export const baseDiscreteSettings = [...baseSettings, Setting.SHOW_LINES, Setting.SHOW_LABELS, Setting.LABEL_DIR]; + +export function defineDependencies(args: DefineDependenciesArgs) { + const { availableSettingsUpdater, helperDependency } = args; + + const curveHeaderQueryDep = helperDependency(async ({ getGlobalSetting, abortSignal }) => { + const wellboreId = getGlobalSetting("wellboreUuid") ?? ""; + return await args.queryClient.fetchQuery({ + ...getWellboreLogCurveHeadersOptions({ + query: { wellbore_uuid: wellboreId }, + signal: abortSignal, + }), + }); + }); + + availableSettingsUpdater(Setting.LOG_CURVE, ({ getHelperDependency, getGlobalSetting }) => { + const wellboreId = getGlobalSetting("wellboreUuid"); + const headerData = getHelperDependency(curveHeaderQueryDep); + + if (!wellboreId || !headerData) return []; + + return headerData; + }); +} + +export function fetchData( + args: FetchDataParams, +): Promise { + const { getSetting, getGlobalSetting, queryClient } = args; + + const wellboreId = getGlobalSetting("wellboreUuid"); + const curveHeader = getSetting(Setting.LOG_CURVE); + + if (!wellboreId || !curveHeader) return Promise.reject(); + + return queryClient.fetchQuery({ + ...getLogCurveDataOptions({ + query: { + wellbore_uuid: wellboreId, + curve_name: curveHeader.curveName, + log_name: curveHeader.logName, + }, + }), + }); +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider.ts b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider.ts new file mode 100644 index 000000000..134733af5 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider.ts @@ -0,0 +1,108 @@ +import type { WellborePick_api } from "@api"; +import { getWellborePicksInStratColumnOptions, getWellboreStratigraphicColumnsOptions } from "@api"; +import type { + CustomDataLayerImplementation, + DataLayerInformationAccessors, + FetchDataParams, +} from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; +import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; + +import _ from "lodash"; + +export const wellPickSettings = [Setting.STRAT_COLUMN, Setting.SMDA_INTERPRETER, Setting.WELL_PICKS] as const; +export type WellPickSettingTypes = typeof wellPickSettings; +type SettingsTypeMap = MakeSettingTypesMap; + +export class WellborePicksProvider implements CustomDataLayerImplementation { + // Uses the same external things as the other types + defineDependencies(args: DefineDependenciesArgs) { + const { helperDependency, availableSettingsUpdater, queryClient } = args; + + const columnOptions = helperDependency(({ getGlobalSetting, abortSignal }) => { + const wellboreUuid = getGlobalSetting("wellboreUuid"); + + if (!wellboreUuid) return null; + + return queryClient.fetchQuery({ + ...getWellboreStratigraphicColumnsOptions({ + query: { wellbore_uuid: wellboreUuid }, + signal: abortSignal, + }), + }); + }); + + const wellPickOptions = helperDependency(({ getGlobalSetting, getLocalSetting, abortSignal }) => { + const wellboreUuid = getGlobalSetting("wellboreUuid"); + const stratColumn = getLocalSetting(Setting.STRAT_COLUMN); + + if (!wellboreUuid || !stratColumn) return null; + + return queryClient.fetchQuery({ + ...getWellborePicksInStratColumnOptions({ + query: { wellbore_uuid: wellboreUuid, strat_column: stratColumn }, + signal: abortSignal, + }), + }); + }); + + availableSettingsUpdater(Setting.STRAT_COLUMN, ({ getHelperDependency }) => { + const columns = getHelperDependency(columnOptions); + + if (!columns) return []; + return _.map(columns, "identifier"); + }); + + availableSettingsUpdater(Setting.SMDA_INTERPRETER, ({ getHelperDependency }) => { + const wellPicks = getHelperDependency(wellPickOptions); + + if (!wellPicks) return []; + + const picksByInterpreter = _.groupBy(wellPicks, "interpreter"); + + return _.keys(picksByInterpreter); + }); + + availableSettingsUpdater(Setting.WELL_PICKS, ({ getLocalSetting, getHelperDependency }) => { + const wellPicks = getHelperDependency(wellPickOptions); + // const pickUnits = getHelperDependency(wellPickUnits); + const interpreter = getLocalSetting(Setting.SMDA_INTERPRETER); + + if (!wellPicks || !interpreter) return []; + + return _.filter(wellPicks, ["interpreter", interpreter]); + }); + } + + settings = wellPickSettings; + + fetchData(args: FetchDataParams): Promise { + const { getSetting } = args; + + const chosenWellPicks = getSetting(Setting.WELL_PICKS); + + // ! Not actually any reason for this to be a promise. No data to fetch, it's already available in the well-picks + return new Promise((resolve) => { + resolve(chosenWellPicks ?? []); + }); + } + + getDefaultName() { + return "Wellbore picks"; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + areCurrentSettingsValid(accessor: DataLayerInformationAccessors) { + // TODO + + return true; + } + + // TODO: Figure out why prev-settings is undefined + // eslint-disable-next-line @typescript-eslint/no-unused-vars + doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { + // return !_.isEqual(prevSettings?.logCurve, newSettings?.logCurve); + return true; + } +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/groups/ContinuousLogTrack.ts b/frontend/src/modules/WellLogViewer/LayerFramework/groups/ContinuousLogTrack.ts new file mode 100644 index 000000000..be3bde212 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/groups/ContinuousLogTrack.ts @@ -0,0 +1,24 @@ +import type { CustomGroupImplementationWithSettings } from "@modules/_shared/LayerFramework/interfacesAndTypes/customGroupImplementation"; +import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; + +const continuousTrackSettings = [Setting.TRACK_WIDTH, Setting.SCALE] as const; +export type ContinuousTrackSettings = typeof continuousTrackSettings; +type TrackSettingTypes = MakeSettingTypesMap; + +export class ContinuousLogTrack + implements CustomGroupImplementationWithSettings +{ + settings = continuousTrackSettings; + + getDefaultSettingsValues(): TrackSettingTypes { + return { + [Setting.TRACK_WIDTH]: 3, + [Setting.SCALE]: "linear", + }; + } + + getDefaultName(): string { + return "Track"; + } +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts b/frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts new file mode 100644 index 000000000..207ac94f6 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts @@ -0,0 +1,16 @@ +import { GroupRegistry } from "@modules/_shared/LayerFramework/groups/GroupRegistry"; +import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; +import { LayerRegistry } from "@modules/_shared/LayerFramework/layers/LayerRegistry"; + +import { AreaPlotProvider } from "./dataProviders/plots/AreaPlotProvider"; +import { DifferentialPlotProvider } from "./dataProviders/plots/DiffPlotProvider"; +import { LinearPlotProvider } from "./dataProviders/plots/LinearPlotProvider"; +import { WellborePicksProvider } from "./dataProviders/wellpicks/WellPicksProvider"; +import { ContinuousLogTrack } from "./groups/ContinuousLogTrack"; + +GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK, ContinuousLogTrack); + +LayerRegistry.registerLayer(LinearPlotProvider.name, LinearPlotProvider); +LayerRegistry.registerLayer(AreaPlotProvider.name, AreaPlotProvider); +LayerRegistry.registerLayer(DifferentialPlotProvider.name, DifferentialPlotProvider); +LayerRegistry.registerLayer(WellborePicksProvider.name, WellborePicksProvider); diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts new file mode 100644 index 000000000..7c18a1393 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts @@ -0,0 +1,64 @@ +import type { WellboreLogCurveData_api } from "@api"; +import type { Settings } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import type { + MakeVisualizationFunction, + ReduceAccumulatedDataFunction, + VisualizationTarget, +} from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; + +import _ from "lodash"; + +import type { AreaPlotSettingTypes } from "../dataProviders/plots/AreaPlotProvider"; +import type { LinearPlotSettingTypes } from "../dataProviders/plots/LinearPlotProvider"; + +export const DATA_ACC_KEY = "LOG_CURVE_DATA"; + +type PlotVisualizationFunc = MakeVisualizationFunction< + PlotSettings, + WellboreLogCurveData_api, + VisualizationTarget.WSC_WELL_LOG +>; + +export const makeAreaPlotConfig: PlotVisualizationFunc = (args) => { + const data = args.getData(); + const plotVariant = args.getSetting(Setting.PLOT_VARIANT); + + return { + name: data?.name ?? "", + type: plotVariant ?? undefined, + + // TODO: Color + // TODO: Fill/Func + }; +}; + +export const makeLinePlotConfig: PlotVisualizationFunc = (args) => { + const data = args.getData(); + + const plotVariant = args.getSetting(Setting.PLOT_VARIANT) ?? undefined; + const curveName = data?.name ?? ""; + + return { + name: curveName, + type: plotVariant, + // TODO: Color + }; +}; + +export const plotDataAccumulator: ReduceAccumulatedDataFunction<[], WellboreLogCurveData_api, Record> = ( + acc, + args, +) => { + const existingData = _.get(acc, DATA_ACC_KEY, []) as WellboreLogCurveData_api[]; + const newData = args.getData(); + + // TODO: Use this to set up name-uniqueness checks? + + if (!newData || _.find(existingData, ["name", newData.name])) return acc; + + return { + ...acc, + [DATA_ACC_KEY]: [...existingData, newData], + }; +}; diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts new file mode 100644 index 000000000..4c4f2cf28 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts @@ -0,0 +1,38 @@ +import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import type { + TargetViewReturnTypes, + ViewDataCollectorFunction, + VisualizationTarget, + VisualizationViewBasic, +} from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; +import type { TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; + +import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; + +export const makeContinuousTrackConfig: ViewDataCollectorFunction< + ContinuousTrackSettings, + VisualizationTarget.WSC_WELL_LOG +> = (args) => { + const trackWidth = args.getSetting(Setting.TRACK_WIDTH) ?? undefined; + const trackScale = args.getSetting(Setting.SCALE) ?? undefined; + + return { + title: args.name, + required: true, + width: trackWidth, + scale: trackScale, + // Need to fill this later, as they + plots: [], + }; +}; + +type BasicViewVisualization = VisualizationViewBasic; + +type ViewVisualization = BasicViewVisualization & TargetViewReturnTypes[VisualizationTarget.WSC_WELL_LOG]; +type TrackVisualization = BasicViewVisualization & TemplateTrack; + +export function isTrackGroup(groupVisualization: ViewVisualization): groupVisualization is TrackVisualization { + if (groupVisualization == null) return false; + + return "plots" in groupVisualization; +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/wellpicks.ts b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/wellpicks.ts new file mode 100644 index 000000000..94abf943c --- /dev/null +++ b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/wellpicks.ts @@ -0,0 +1,21 @@ +import type { WellborePick_api } from "@api"; +import { createLogViewerWellPicks } from "@modules/WellLogViewer/utils/queryDataTransform"; +import type { + MakeVisualizationFunction, + VisualizationTarget, +} from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; +import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; + +import type { WellPickSettingTypes } from "../dataProviders/wellpicks/WellPicksProvider"; + +export const makeLogViewerWellPicks: MakeVisualizationFunction< + WellPickSettingTypes, + WellborePick_api[], + VisualizationTarget.WSC_WELL_LOG +> = (args): WellPickProps | null => { + const data = args.getData(); + + if (!data) return null; + + return createLogViewerWellPicks(data); +}; diff --git a/frontend/src/modules/WellLogViewer/interfaces.ts b/frontend/src/modules/WellLogViewer/interfaces.ts index 41c486159..abc9ac735 100644 --- a/frontend/src/modules/WellLogViewer/interfaces.ts +++ b/frontend/src/modules/WellLogViewer/interfaces.ts @@ -1,36 +1,34 @@ -import type { WellboreHeader_api, WellboreLogCurveHeader_api, WellborePick_api } from "@api"; +import type { WellboreHeader_api, WellborePick_api } from "@api"; import type { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; +import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; +import { layerManagerAtom } from "./settings/atoms/baseAtoms"; import { - requiredCurvesAtom, selectedFieldIdentifierAtom, selectedWellboreHeaderAtom, selectedWellborePicksAtom, - wellLogTemplateTracksAtom, } from "./settings/atoms/derivedAtoms"; import { padDataWithEmptyRowsAtom, viewerHorizontalAtom } from "./settings/atoms/persistedAtoms"; -import type { TemplateTrackConfig } from "./types"; export type InterfaceTypes = { settingsToView: SettingsToViewInterface; }; export type SettingsToViewInterface = { + layerManager: DataLayerManager | null; + selectedField: string | null; wellboreHeader: WellboreHeader_api | null; - templateTracks: TemplateTrackConfig[]; viewerHorizontal: boolean; padDataWithEmptyRows: boolean; selectedWellborePicks: WellborePick_api[]; - requiredCurves: WellboreLogCurveHeader_api[]; }; export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + layerManager: (get) => get(layerManagerAtom), selectedField: (get) => get(selectedFieldIdentifierAtom), wellboreHeader: (get) => get(selectedWellboreHeaderAtom), - templateTracks: (get) => get(wellLogTemplateTracksAtom), viewerHorizontal: (get) => get(viewerHorizontalAtom), padDataWithEmptyRows: (get) => get(padDataWithEmptyRowsAtom), selectedWellborePicks: (get) => get(selectedWellborePicksAtom), - requiredCurves: (get) => get(requiredCurvesAtom), }; diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts index b176e5230..61e6eeadc 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts @@ -1,8 +1,8 @@ -import { LayerManager } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; +import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; import { atom } from "jotai"; -export const layerManagerAtom = atom(null); +export const layerManagerAtom = atom(null); export const userSelectedFieldIdentifierAtom = atom(null); export const userSelectedWellboreUuidAtom = atom(null); diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts index f11db5513..6ac9d725f 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts @@ -1,6 +1,6 @@ import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; import { atomWithModuleInstanceStorage, clearModuleInstanceStorage } from "@modules/WellLogViewer/utils/atoms"; -import { SerializedLayerManager } from "@modules/_shared/LayerFramework/interfaces"; +import type { SerializedDataLayerManager } from "@modules/_shared/LayerFramework/interfacesAndTypes/serialization"; import type { Getter, Setter } from "jotai"; import { atom } from "jotai"; @@ -35,9 +35,9 @@ export const padDataWithEmptyRowsAtom = atom( (get, set, newVal) => setPersistentModuleField(get, set, "padDataWithEmptyRows", newVal), ); -export const serializedManagerStateAtom = atom( +export const serializedManagerStateAtom = atom( (get) => getPersistentModuleField(get, "layerManagerState", undefined), - (get, set, newVal) => setPersistentModuleField(get, set, "layerManagerState", newVal) + (get, set, newVal) => setPersistentModuleField(get, set, "layerManagerState", newVal), ); export function clearStorageForInstance(instanceId: string) { diff --git a/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx index d33672e69..569869ad6 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx @@ -1,33 +1,55 @@ import React from "react"; -import { WorkbenchSession } from "@framework/WorkbenchSession"; -import { WorkbenchSettings } from "@framework/WorkbenchSettings"; -import { WellPicksLayer } from "@modules/WellLogViewer/LayerFramework/layers/WellPicksLayer/WellPicksLayer"; -import { LayersActionGroup } from "@modules/_shared/LayerFramework/LayersActions"; -import { GroupDelegate, GroupDelegateTopic } from "@modules/_shared/LayerFramework/delegates/GroupDelegate"; -import { LayerManager, LayerManagerTopic } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManager"; -import { LayerManagerComponent } from "@modules/_shared/LayerFramework/framework/LayerManager/LayerManagerComponent"; +import { WellLogCurveTypeEnum_api } from "@api"; +import type { WorkbenchSession } from "@framework/WorkbenchSession"; +import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { AreaPlotProvider } from "@modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider"; +import { LinearPlotProvider } from "@modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider"; +import { WellborePicksProvider } from "@modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider"; +import { TrackIcon } from "@modules/WellLogViewer/_shared/components/icons"; +import type { ActionGroup } from "@modules/_shared/LayerFramework/Actions"; +import type { GroupDelegate } from "@modules/_shared/LayerFramework/delegates/GroupDelegate"; +import { GroupDelegateTopic } from "@modules/_shared/LayerFramework/delegates/GroupDelegate"; +import { + DataLayerManager, + LayerManagerTopic, +} from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; +import { DataLayerManagerComponent } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManagerComponent"; +import { GroupRegistry } from "@modules/_shared/LayerFramework/groups/GroupRegistry"; +import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; +import { LayerRegistry } from "@modules/_shared/LayerFramework/layers/LayerRegistry"; +import { ShowChart, ViewDay } from "@mui/icons-material"; import { useQueryClient } from "@tanstack/react-query"; import { useAtom } from "jotai"; +import "../../LayerFramework/registerFrameworkExtensions"; import { layerManagerAtom } from "../atoms/baseAtoms"; import { serializedManagerStateAtom } from "../atoms/persistedAtoms"; enum LayerActionIdents { - TRACK = "track", + CONTINUOUS_TRACK = "cont_track", + DISCRETE_TRACK = "disc_track", SETTINGS = "settings", PLOT = "plot", WELL_PICKS = "well_picks", } -const LAYER_ACTIONS: LayersActionGroup[] = [ +enum PlotActionIdents { + LINE = "line", + AREA = "area", + STACKED = "stacked", + DIFF = "diff", +} + +const LAYER_ACTIONS: ActionGroup[] = [ { label: "Log viewer", children: [ { - label: "Track", - identifier: LayerActionIdents.TRACK, + label: "Continuous Track", + identifier: LayerActionIdents.CONTINUOUS_TRACK, + icon: , }, { label: "Viewer Settings", @@ -39,40 +61,45 @@ const LAYER_ACTIONS: LayersActionGroup[] = [ }, ], }, + { + label: "Plots", + children: [ + { icon: , label: "Line plot", identifier: PlotActionIdents.LINE }, + { icon: , label: "Area plot", identifier: PlotActionIdents.AREA }, + { icon: , label: "Stacked plot", identifier: PlotActionIdents.STACKED }, + { icon: , label: "Differential plot", identifier: PlotActionIdents.DIFF }, + ], + }, ]; function usePersistedLayerManager( workbenchSession: WorkbenchSession, - workbenchSettings: WorkbenchSettings -): LayerManager | null { + workbenchSettings: WorkbenchSettings, +): DataLayerManager | null { const queryClient = useQueryClient(); const hasAppliedPersistedState = React.useRef(false); const [layerManager, setLayerManager] = useAtom(layerManagerAtom); const [serializedManagerState, setSerializedManagerState] = useAtom(serializedManagerStateAtom); - // const applyPersistedManagerState = React.useCallback(function applyPersistedManagerState(layerManager) {}, [ - // serializedManagerState, - // ]); - const persistManagerState = React.useCallback( function persistManagerState() { if (!layerManager) return; setSerializedManagerState(layerManager.serializeState()); }, - [layerManager, setSerializedManagerState] + [layerManager, setSerializedManagerState], ); React.useEffect( function initalizeLayerManagerEffect() { - const newLayerManager = new LayerManager(workbenchSession, workbenchSettings, queryClient); + const newLayerManager = new DataLayerManager(workbenchSession, workbenchSettings, queryClient); setLayerManager(newLayerManager); hasAppliedPersistedState.current = false; return () => newLayerManager.beforeDestroy(); }, - [queryClient, setLayerManager, workbenchSession, workbenchSettings] + [queryClient, setLayerManager, workbenchSession, workbenchSettings], ); // applyPersistedManagerState(layerManager); @@ -85,7 +112,7 @@ function usePersistedLayerManager( hasAppliedPersistedState.current = true; }, - [serializedManagerState, layerManager] + [serializedManagerState, layerManager], ); React.useEffect( @@ -108,27 +135,10 @@ function usePersistedLayerManager( unsubscribeExpands(); }; }, - [layerManager, persistManagerState] + [layerManager, persistManagerState], ); return layerManager; - - // const applyPersistedManagerState = React.useCallback(function applyPersistedManagerState(layerManager: LayerManager) { - // const serializedState = window.localStorage.getItem(`${props.moduleInstanceId}-settings`); - // if (!serializedState) return; - - // const parsedState = JSON.parse(serializedState); - // if (parsedState.fieldIdentifier) { - // layerManager.setFieldIdentifier(parsedState.fieldIdentifier); - // } - // if (parsedState.preferredViewLayout) { - // layerManager.setPreferredViewLayout(parsedState.preferredViewLayout); - // } - // }, []) - - // React.useEffect(() => applyPersistedState(layerManager), - // [layerManager, applyPersistedState] - // ); } export type LayerManagerComponentWrapperProps = { @@ -141,24 +151,36 @@ export function LayerManagerComponentWrapper(props: LayerManagerComponentWrapper const layerActionCallback = React.useCallback( function layerActionCallback(identifier: string, groupDelegate: GroupDelegate) { + if (!layerManager) return; + switch (identifier) { case LayerActionIdents.WELL_PICKS: - return groupDelegate.appendChild(new WellPicksLayer(layerManager!)); + return groupDelegate.appendChild(LayerRegistry.makeLayer(WellborePicksProvider.name, layerManager)); + + case LayerActionIdents.CONTINUOUS_TRACK: + return groupDelegate.appendChild(GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK, layerManager)); + + case PlotActionIdents.LINE: + return groupDelegate.appendChild(LayerRegistry.makeLayer(LinearPlotProvider.name, layerManager)); + case PlotActionIdents.AREA: + return groupDelegate.appendChild(LayerRegistry.makeLayer(AreaPlotProvider.name, layerManager)); + + default: + break; } }, - [layerManager] + [layerManager], ); if (!layerManager) return
; return ( - <> - } - layerActions={LAYER_ACTIONS} - onLayerAction={layerActionCallback} - /> - + } + groupActions={LAYER_ACTIONS} + onAction={layerActionCallback} + /> ); } diff --git a/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts b/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts index 8920ece57..311765328 100644 --- a/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts +++ b/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts @@ -2,35 +2,25 @@ * Utilities and constants used for generating well-log-viewer template configs */ import { WellLogCurveTypeEnum_api } from "@api"; -import type { - Template, - TemplatePlot, - TemplateTrack, -} from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; +import type { Template } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; import _ from "lodash"; import { v4 } from "uuid"; import { CURVE_COLOR_PALETTE, DIFF_CURVE_COLORS } from "./logViewerColors"; import { MAIN_AXIS_CURVE } from "./queryDataTransform"; -import { getUniqueCurveNameForPlotConfig } from "./strings"; import type { TemplatePlotConfig, TemplateTrackConfig } from "../types"; export const DEFAULT_MAX_VISIBLE_TRACKS = 5; export function createLogTemplate(templateTracks: TemplateTrackConfig[], nonUniqueNames?: Set): Template { + // TODO: Implement non-unique names return { // AFAIK, this name is not show anywhere name: "Well log viewer", scale: { primary: MAIN_AXIS_CURVE.name, allowSecondary: true }, - tracks: templateTracks.map((track) => ({ - ...track, - plots: track.plots.map((plot) => ({ - ...plot, - name: getUniqueCurveNameForPlotConfig(plot, nonUniqueNames), - })) as TemplatePlot[], - })), + tracks: templateTracks, }; } diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts new file mode 100644 index 000000000..418200eab --- /dev/null +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts @@ -0,0 +1,56 @@ +import React from "react"; + +import type { WellboreLogCurveData_api } from "@api"; +import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; +import { LayerManagerTopic } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; +import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; +import type { VisualizationTarget } from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; +import { VisualizationFactory } from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; + +import { AreaPlotProvider } from "../LayerFramework/dataProviders/plots/AreaPlotProvider"; +import { LinearPlotProvider } from "../LayerFramework/dataProviders/plots/LinearPlotProvider"; +import { WellborePicksProvider } from "../LayerFramework/dataProviders/wellpicks/WellPicksProvider"; +import { ContinuousLogTrack } from "../LayerFramework/groups/ContinuousLogTrack"; +import { makeAreaPlotConfig, makeLinePlotConfig, plotDataAccumulator } from "../LayerFramework/visualizations/plots"; +import { makeContinuousTrackConfig } from "../LayerFramework/visualizations/tracks"; +import { makeLogViewerWellPicks } from "../LayerFramework/visualizations/wellpicks"; + +const VISUALIZATION_FACTORY = new VisualizationFactory< + VisualizationTarget.WSC_WELL_LOG, + never, + Record +>(); + +VISUALIZATION_FACTORY.registerLayerFunctions(LinearPlotProvider.name, LinearPlotProvider, { + makeVisualizationFunction: makeLinePlotConfig, + reduceAccumulatedDataFunction: plotDataAccumulator, +}); +VISUALIZATION_FACTORY.registerLayerFunctions(AreaPlotProvider.name, AreaPlotProvider, { + makeVisualizationFunction: makeAreaPlotConfig, + reduceAccumulatedDataFunction: plotDataAccumulator, +}); + +VISUALIZATION_FACTORY.registerViewFunction(GroupType.WELL_LOG_TRACK, ContinuousLogTrack, makeContinuousTrackConfig); + +VISUALIZATION_FACTORY.registerLayerFunctions(WellborePicksProvider.name, WellborePicksProvider, { + makeVisualizationFunction: makeLogViewerWellPicks, +}); + +type MakeFuncReturn = ReturnType<(typeof VISUALIZATION_FACTORY)["make"]>; + +export function useLogViewerVisualizationFactoryProduct(dataLayerManager: DataLayerManager) { + const [previousRevision, setPreviousRevision] = React.useState(); + const [previousProduct, setPreviousProduct] = React.useState(); + + const latestRevision = React.useSyncExternalStore( + dataLayerManager.getPublishSubscribeDelegate().makeSubscriberFunction(LayerManagerTopic.LAYER_DATA_REVISION), + dataLayerManager.makeSnapshotGetter(LayerManagerTopic.LAYER_DATA_REVISION), + ); + + if (previousRevision !== latestRevision) { + setPreviousRevision(latestRevision); + setPreviousProduct(VISUALIZATION_FACTORY.make(dataLayerManager)); + } + + return previousProduct; +} diff --git a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx index 5b00821a0..01d314d71 100644 --- a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx @@ -1,16 +1,20 @@ import React from "react"; -import type { WellboreHeader_api, WellboreLogCurveData_api, WellborePick_api, WellboreTrajectory_api } from "@api"; +import type { WellboreHeader_api, WellboreTrajectory_api } from "@api"; import type { IntersectionReferenceSystem } from "@equinor/esv-intersection"; import type { ModuleViewProps } from "@framework/Module"; import { SyncSettingKey } from "@framework/SyncSettings"; import type { GlobalTopicDefinitions, WorkbenchServices } from "@framework/WorkbenchServices"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; import { createContinuousColorScaleForMap } from "@modules/3DViewer/view/utils/colorTables"; -import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; +import { DATA_ACC_KEY as PLOT_DATA_ACC_KEY } from "@modules/WellLogViewer/LayerFramework/visualizations/plots"; +import { isTrackGroup } from "@modules/WellLogViewer/LayerFramework/visualizations/tracks"; +import { useLogViewerVisualizationFactoryProduct } from "@modules/WellLogViewer/utils/useLogViewerVisualizationFactory"; +import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; import { WellLogViewer } from "@webviz/well-log-viewer"; import type { Info } from "@webviz/well-log-viewer/dist/components/InfoTypes"; -import type { WellLogController } from "@webviz/well-log-viewer/dist/components/WellLogView"; +import type { TemplatePlot, TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; +import type { WellLogController, WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; import { useAtomValue } from "jotai"; import { isEqual } from "lodash"; @@ -19,7 +23,7 @@ import { ReadoutWrapper } from "./ReadoutWrapper"; import type { InterfaceTypes } from "../../interfaces"; import { createLogTemplate } from "../../utils/logViewerTemplate"; -import { createLogViewerWellPicks, createWellLogSets } from "../../utils/queryDataTransform"; +import { createWellLogSets } from "../../utils/queryDataTransform"; import { nonUniqueCurveNamesAtom } from "../atoms/derivedAtoms"; const AXIS_MNEMOS = { @@ -39,15 +43,14 @@ type GlobalHoverMd = GlobalTopicDefinitions["global.hoverMd"]; export type SubsurfaceLogViewerWrapperProps = { // Data wellboreHeader: WellboreHeader_api | null; - curveData: WellboreLogCurveData_api[]; + dataLayerManager: DataLayerManager; + trajectoryData: WellboreTrajectory_api; intersectionReferenceSystem: IntersectionReferenceSystem; - wellpicks: WellborePick_api[]; // Viewer config horizontal: boolean; padDataWithEmptyRows: boolean; - templateTrackConfigs: TemplateTrackConfig[]; // Passing the module props to make context and service access less cumbersome moduleProps: ModuleViewProps; @@ -145,24 +148,41 @@ function useCreateGlobalVerticalScaleBroadcastFunc( return broadcastVerticalScaleChange; } -export function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { +function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { const nonUniqueCurveNames = useAtomValue(nonUniqueCurveNamesAtom); - const trackConfigs = props.templateTrackConfigs; const trajectoryData = props.trajectoryData; - const curveData = props.curveData; const intersectionReferenceSystem = props.intersectionReferenceSystem; const padDataWithEmptyRows = props.padDataWithEmptyRows; - const wellpicks = React.useMemo(() => createLogViewerWellPicks(props.wellpicks), [props.wellpicks]); + const factoryProduct = useLogViewerVisualizationFactoryProduct(props.dataLayerManager); + + const wellpicks = React.useMemo(() => { + return factoryProduct?.layers.find((layer) => layer.layer && "wellpick" in layer.layer)?.layer as WellPickProps; + }, [factoryProduct]); // Curve data transform is a bit heavy, so we use Memo-hooks to reduce re-render overhead - const template = React.useMemo( - () => createLogTemplate(trackConfigs, nonUniqueCurveNames), - [trackConfigs, nonUniqueCurveNames], - ); + // TODO: This would arguably be something for the "root view" + const template = React.useMemo(() => { + const views = factoryProduct?.views ?? []; + + const trackTemplates = views.reduce((acc, v) => { + if (!isTrackGroup(v)) return acc; + + const fullTrackTemplate = { + ...v, + plots: v.layers.map(({ layer }) => layer as TemplatePlot), + } as TemplateTrack; + + return [...acc, fullTrackTemplate]; + }, [] as TemplateTrack[]); + + return createLogTemplate(trackTemplates, nonUniqueCurveNames); + }, [factoryProduct, nonUniqueCurveNames]); const welllog = React.useMemo(() => { + const curveData = factoryProduct?.accumulatedData[PLOT_DATA_ACC_KEY] ?? []; + return createWellLogSets( curveData, trajectoryData, @@ -170,7 +190,7 @@ export function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { nonUniqueCurveNames, padDataWithEmptyRows, ); - }, [curveData, trajectoryData, intersectionReferenceSystem, padDataWithEmptyRows, nonUniqueCurveNames]); + }, [factoryProduct, trajectoryData, intersectionReferenceSystem, padDataWithEmptyRows, nonUniqueCurveNames]); return { template, welllog, wellpicks }; } @@ -291,7 +311,7 @@ export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProp /> diff --git a/frontend/src/modules/WellLogViewer/view/view.tsx b/frontend/src/modules/WellLogViewer/view/view.tsx index b7be67641..82e1a2818 100644 --- a/frontend/src/modules/WellLogViewer/view/view.tsx +++ b/frontend/src/modules/WellLogViewer/view/view.tsx @@ -1,46 +1,29 @@ import React from "react"; import type { ModuleViewProps } from "@framework/Module"; -import { useViewStatusWriter } from "@framework/StatusWriter"; -import { ContentError } from "@modules/_shared/components/ContentMessage"; -import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePropagateApiErrorToStatusWriter"; +// import { useViewStatusWriter } from "@framework/StatusWriter"; import { CircularProgress } from "@mui/material"; -import type { UseQueryResult } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; import { intersectionReferenceSystemAtom } from "./atoms/derivedAtoms"; -import { logCurveDataQueryAtom, wellboreTrajectoryQueryAtom } from "./atoms/queryAtoms"; +import { wellboreTrajectoryQueryAtom } from "./atoms/queryAtoms"; import { SubsurfaceLogViewerWrapper } from "./components/SubsurfaceLogViewerWrapper"; import type { InterfaceTypes } from "../interfaces"; export function View(props: ModuleViewProps) { - const statusWriter = useViewStatusWriter(props.viewContext); + // const statusWriter = useViewStatusWriter(props.viewContext); + + const layerManager = props.viewContext.useSettingsToViewInterfaceValue("layerManager"); // Passed setting atoms const selectedWellboreHeader = props.viewContext.useSettingsToViewInterfaceValue("wellboreHeader"); - const templateTracks = props.viewContext.useSettingsToViewInterfaceValue("templateTracks"); const viewerHorizontal = props.viewContext.useSettingsToViewInterfaceValue("viewerHorizontal"); const padDataWithEmptyRows = props.viewContext.useSettingsToViewInterfaceValue("padDataWithEmptyRows"); - const wellborePicks = props.viewContext.useSettingsToViewInterfaceValue("selectedWellborePicks"); - const wellboreTrajectoryDataQuery = useAtomValue(wellboreTrajectoryQueryAtom); const intersectionReferenceSystem = useAtomValue(intersectionReferenceSystemAtom); - const curveDataQueries = useAtomValue(logCurveDataQueryAtom); - - const mainElementsLoading = curveDataQueries.isPending || wellboreTrajectoryDataQuery.isPending; - const mainElementsSuccess = curveDataQueries.isSuccess && wellboreTrajectoryDataQuery.isSuccess; - - statusWriter.setLoading(mainElementsLoading); - - usePropagateApiErrorToStatusWriter(wellboreTrajectoryDataQuery, statusWriter); - usePropagateApiErrorToStatusWriter( - // ! Cast is safe, since MergedQueryResult includes `.error` - curveDataQueries as UseQueryResult, - statusWriter, - ); React.useEffect( function setModuleName() { @@ -57,34 +40,23 @@ export function View(props: ModuleViewProps) { [props.viewContext, selectedWellboreHeader?.uniqueWellboreIdentifier], ); - if (mainElementsLoading) { + if (!layerManager || !wellboreTrajectoryDataQuery.data || !intersectionReferenceSystem) { return (
); } - if (!mainElementsSuccess) { - return Error loading curve data.; - } - if (!intersectionReferenceSystem) { - return Unexpected null for reference system.; - } - if (!wellboreTrajectoryDataQuery.data) { - return Unexpected null for trajectory data; - } return ( ); } diff --git a/frontend/src/modules/_shared/LayerFramework/groups/groupTypes.ts b/frontend/src/modules/_shared/LayerFramework/groups/groupTypes.ts index 9a7ae4eb6..606f9b78f 100644 --- a/frontend/src/modules/_shared/LayerFramework/groups/groupTypes.ts +++ b/frontend/src/modules/_shared/LayerFramework/groups/groupTypes.ts @@ -1,3 +1,4 @@ export enum GroupType { VIEW = "VIEW", + WELL_LOG_TRACK = "WELL_LOG_TRACK", } diff --git a/frontend/src/modules/_shared/LayerFramework/settings/implementations/LogCurveSetting.tsx b/frontend/src/modules/_shared/LayerFramework/settings/implementations/LogCurveSetting.tsx new file mode 100644 index 000000000..3865ed254 --- /dev/null +++ b/frontend/src/modules/_shared/LayerFramework/settings/implementations/LogCurveSetting.tsx @@ -0,0 +1,69 @@ +import type React from "react"; + +import type { WellboreLogCurveHeader_api } from "@api"; +import { Dropdown, type DropdownOptionGroup } from "@lib/components/Dropdown"; +import { makeSelectValueForCurveHeader } from "@modules/WellLogViewer/utils/strings"; + +import _ from "lodash"; + +import type { + CustomSettingImplementation, + SettingComponentProps, +} from "../../interfacesAndTypes/customSettingImplementation"; +import type { SettingCategory } from "../settingsDefinitions"; + +type ValueType = WellboreLogCurveHeader_api | null; + +export class LogCurveSetting implements CustomSettingImplementation { + defaultValue: ValueType = null; + + getLabel(): string { + return "Curve"; + } + + // TODO: Set up serialization + // serializeValue(value: ValueType): string { + // if (!value) return ""; + // return [value.source, value.logName, value.curveName].join("::"); + // } + + // deserializeValue(serializedValue: string): ValueType { + // if(!serializedValue) return null + // else return null + // } + + makeComponent(): (props: SettingComponentProps) => React.ReactNode { + return function DrilledWellbores(props: SettingComponentProps) { + const selectedValue = makeSelectValueForCurveHeader(props.value); + const availableValues = props.availableValues ?? []; + + const curveOptions = _.chain(availableValues) + .groupBy("logName") + .entries() + .map(([logName, curves]) => ({ + label: logName, + options: curves.map((curve) => ({ + value: makeSelectValueForCurveHeader(curve), + label: curve.curveName, + })), + })) + .value(); + + function handleChange(selectedIdent: string) { + const selected = availableValues.find((v) => makeSelectValueForCurveHeader(v) === selectedIdent); + + props.onValueChange(selected ?? null); + } + + return ( + + ); + }; + } +} diff --git a/frontend/src/modules/_shared/LayerFramework/settings/implementations/NumberSetting.tsx b/frontend/src/modules/_shared/LayerFramework/settings/implementations/NumberSetting.tsx new file mode 100644 index 000000000..6b9e33fbe --- /dev/null +++ b/frontend/src/modules/_shared/LayerFramework/settings/implementations/NumberSetting.tsx @@ -0,0 +1,54 @@ +import type { ChangeEvent } from "react"; +import type React from "react"; + +import { Input } from "@lib/components/Input"; + +import type { + CustomSettingImplementation, + SettingComponentProps, +} from "../../interfacesAndTypes/customSettingImplementation"; +import type { SettingCategory } from "../settingsDefinitions"; + +type ValueType = number | null; +type SteppedNumberSettingCompProps = SettingComponentProps; + +export class NumberSetting implements CustomSettingImplementation { + private _min?: number; + private _max?: number; + + constructor(props: { minMax?: [number?, number?] }) { + this._min = props.minMax?.[0] ?? undefined; + this._max = props.minMax?.[1] ?? undefined; + } + + isValueValid(): boolean { + return true; + } + + getIsStatic(): boolean { + return true; + } + + makeComponent(): (props: SteppedNumberSettingCompProps) => React.ReactNode { + const minValue = this._min; + const maxValue = this._max; + + return function BooleanSwitch(props: SteppedNumberSettingCompProps) { + function handleChange(e: ChangeEvent) { + props.onValueChange(e.target.valueAsNumber ?? null); + } + + return ; + }; + } + + serializeValue(value: ValueType): string { + if (!value) return ""; + return String(value); + } + + deserializeValue(serializedValue: string): ValueType { + if (serializedValue === "") return null; + return Number(serializedValue); + } +} diff --git a/frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx b/frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx index 5b3e941ca..e4ebc100b 100644 --- a/frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx +++ b/frontend/src/modules/_shared/LayerFramework/settings/implementations/ObjectSelectionSetting.tsx @@ -1,66 +1,42 @@ import React from "react"; import { DenseIconButton } from "@lib/components/DenseIconButton"; -import { Select, SelectOption } from "@lib/components/Select"; +import type { SelectOption } from "@lib/components/Select"; +import { Select } from "@lib/components/Select"; import { Deselect, SelectAll } from "@mui/icons-material"; -import { SettingDelegate } from "../../delegates/SettingDelegate"; -import { AvailableValuesType, Setting, SettingComponentProps } from "../../interfaces"; -import { SettingRegistry } from "../SettingRegistry"; -import { SettingType } from "../settingsTypes"; +import type { + CustomSettingImplementation, + SettingComponentProps, +} from "../../interfacesAndTypes/customSettingImplementation"; +import type { MakeAvailableValuesTypeBasedOnCategory } from "../../interfacesAndTypes/utils"; +import type { SettingCategory } from "../settingsDefinitions"; +const STABLE_EMPTY_ARR = [] as const; type ValueType = T[] | null; /** * ! Currently unsure if this one is keepable. Might be too complicated compared to what's offered */ -export class ObjectSelectionSetting implements Setting> { - private _delegate: SettingDelegate>; - private _label: string; - private _settingType: SettingType; +export class ObjectSelectionSetting> + implements CustomSettingImplementation, SettingCategory.MULTI_SELECT> +{ private _optValKey: keyof T; private _optLabelKey: keyof T; - private _multiple: boolean; - - constructor( - label: string, - settingType: SettingType, - optValKey: keyof T, - optLabelKey: keyof T, - multiple: boolean = false - ) { - this._delegate = new SettingDelegate>(null, this); - this._settingType = settingType; - this._optValKey = optValKey; - this._optLabelKey = optLabelKey; - this._multiple = multiple; - this._label = label; - } - - getConstructorParams() { - return [this._settingType, this._label, this._optValKey, this._optLabelKey, this._multiple]; - } - - getType(): SettingType { - return this._settingType; - } - getLabel(): string { - return this._label; - } - - getDelegate(): SettingDelegate> { - return this._delegate; + constructor(optValKey: keyof T, optLabelKey?: keyof T) { + this._optValKey = optValKey; + this._optLabelKey = optLabelKey ?? optValKey; } - transformValueToOption(object: T): SelectOption { + private transformValueToOption(object: T): SelectOption { return { value: this.getOptionValue(object), label: this.getOptionLabel(object), }; } - getOptionValue(object: T): string { + private getOptionValue(object: T): string { const value = object[this._optValKey]; if (typeof value !== "string") throw new Error(`Expected object value to be a string, but got ${typeof value}`); @@ -68,7 +44,7 @@ export class ObjectSelectionSetting implements Setting> { return value; } - getOptionLabel(object: T): string { + private getOptionLabel(object: T): string { const value = object[this._optLabelKey]; if (typeof value !== "string") throw new Error(`Expected object value to be a string, but got ${typeof value}`); @@ -76,13 +52,18 @@ export class ObjectSelectionSetting implements Setting> { return value; } - fixupValue(availableValues: AvailableValuesType>, currentValue: ValueType): ValueType { + fixupValue( + currentValue: ValueType, + availableValues: MakeAvailableValuesTypeBasedOnCategory, SettingCategory.MULTI_SELECT>, + ): ValueType { if (!currentValue) { return availableValues; } const matchingValues = currentValue.filter((value) => - availableValues.some((availableValue) => this.getOptionValue(availableValue) === this.getOptionValue(value)) + availableValues.some( + (availableValue) => this.getOptionValue(availableValue) === this.getOptionValue(value), + ), ); if (matchingValues.length === 0) { return availableValues; @@ -90,26 +71,26 @@ export class ObjectSelectionSetting implements Setting> { return matchingValues; } - makeComponent(): (props: SettingComponentProps>) => React.ReactNode { + makeComponent(): (props: SettingComponentProps, SettingCategory.MULTI_SELECT>) => React.ReactNode { // ! Bind class methods to ensure stable refs const transformObjectToOption = this.transformValueToOption.bind(this); const getObjectValue = this.getOptionValue.bind(this); - return function DrilledWellbores(props: SettingComponentProps>) { + return function DrilledWellbores(props: SettingComponentProps, SettingCategory.MULTI_SELECT>) { + const availableValues = props.availableValues ?? STABLE_EMPTY_ARR; + const options: SelectOption[] = React.useMemo( - () => props.availableValues.map(transformObjectToOption), - [props.availableValues] + () => availableValues.map(transformObjectToOption), + [availableValues], ); function handleChange(selectedIdents: string[]) { - const selectedObjects = props.availableValues.filter((obj) => - selectedIdents.includes(getObjectValue(obj)) - ); + const selectedObjects = availableValues.filter((obj) => selectedIdents.includes(getObjectValue(obj))); props.onValueChange(selectedObjects); } function selectAll() { - const allIdents = props.availableValues.map((obj) => getObjectValue(obj)); + const allIdents = availableValues.map((obj) => getObjectValue(obj)); handleChange(allIdents); } @@ -119,7 +100,7 @@ export class ObjectSelectionSetting implements Setting> { const selectedValues = React.useMemo( () => props.value?.map((obj) => getObjectValue(obj)) ?? [], - [props.value] + [props.value], ); return ( @@ -148,6 +129,3 @@ export class ObjectSelectionSetting implements Setting> { }; } } - -// ! Cast needed since typescript cant handle the "key of T" constructor params -SettingRegistry.registerSetting(ObjectSelectionSetting as { new (...params: any[]): Setting }); diff --git a/frontend/src/modules/_shared/LayerFramework/settings/implementations/StaticDropdownStringSetting.tsx b/frontend/src/modules/_shared/LayerFramework/settings/implementations/StaticDropdownStringSetting.tsx new file mode 100644 index 000000000..4f6f8188c --- /dev/null +++ b/frontend/src/modules/_shared/LayerFramework/settings/implementations/StaticDropdownStringSetting.tsx @@ -0,0 +1,50 @@ +import type { DropdownOption, DropdownOptionOrGroup } from "@lib/components/Dropdown"; +import { Dropdown } from "@lib/components/Dropdown"; + +import type { + CustomSettingImplementation, + SettingComponentProps, +} from "../../interfacesAndTypes/customSettingImplementation"; +import type { SettingCategory } from "../settingsDefinitions"; + +type ValueType = T | null; + +export class StaticDropdownStringSetting + implements CustomSettingImplementation, SettingCategory.SINGLE_SELECT> +{ + private _staticOptions: DropdownOptionOrGroup>[]; + + constructor(props: { options?: ValueType[] | DropdownOption[] }) { + const options = props.options ?? []; + + this._staticOptions = options.map((opt) => { + if (opt === null) return { label: "None", value: null }; + if (typeof opt === "string") return { label: opt, value: opt }; + return opt; + }); + } + + getIsStatic() { + return true; + } + + makeComponent(): (props: SettingComponentProps, SettingCategory.SINGLE_SELECT>) => React.ReactNode { + const staticOptions = this._staticOptions; + + return function DropdownStringSetting( + props: SettingComponentProps, SettingCategory.SINGLE_SELECT>, + ) { + const options = staticOptions; + + return ( + + ); + }; + } +} diff --git a/frontend/src/modules/_shared/LayerFramework/settings/registerAllSettings.ts b/frontend/src/modules/_shared/LayerFramework/settings/registerAllSettings.ts index 2025cd3e3..dd9de5de3 100644 --- a/frontend/src/modules/_shared/LayerFramework/settings/registerAllSettings.ts +++ b/frontend/src/modules/_shared/LayerFramework/settings/registerAllSettings.ts @@ -1,3 +1,5 @@ +import type { WellborePick_api } from "@api"; + import { SettingRegistry } from "./SettingRegistry"; import { BooleanSetting } from "./implementations/BooleanSetting"; import { ColorScaleSetting } from "./implementations/ColorScaleSetting"; @@ -8,11 +10,39 @@ import { EnsembleSetting } from "./implementations/EnsembleSetting"; import { Direction as GridLayerRangeDirection, GridLayerRangeSetting } from "./implementations/GridLayerRangeSetting"; import { Direction as GridLayerDirection, GridLayerSetting } from "./implementations/GridLayerSetting"; import { IntersectionSetting } from "./implementations/IntersectionSetting"; +import { LogCurveSetting } from "./implementations/LogCurveSetting"; +import { NumberSetting } from "./implementations/NumberSetting"; +import { ObjectSelectionSetting } from "./implementations/ObjectSelectionSetting"; import { SeismicSliceDirection, SeismicSliceSetting } from "./implementations/SeismicSliceSetting"; import { SensitivitySetting } from "./implementations/SensitivitySetting"; +import { StaticDropdownStringSetting } from "./implementations/StaticDropdownStringSetting"; import { StatisticFunctionSetting } from "./implementations/StatisticFunctionSetting"; import { Setting } from "./settingsDefinitions"; +SettingRegistry.registerSetting(Setting.STRAT_COLUMN, "Stratigraphic Column", DropdownStringSetting); +SettingRegistry.registerSetting(Setting.SMDA_INTERPRETER, "Interpreter", DropdownStringSetting); +SettingRegistry.registerSetting(Setting.WELL_PICKS, "Interpreter", ObjectSelectionSetting, { + customConstructorParameters: ["pickIdentifier"], +}); + +SettingRegistry.registerSetting(Setting.TRACK_WIDTH, "Track width", NumberSetting, { + customConstructorParameters: [{ minMax: [1, 6] }], +}); +SettingRegistry.registerSetting(Setting.SCALE, "Scale", StaticDropdownStringSetting, { + customConstructorParameters: [ + { + options: [ + { value: "log", label: "Logarithmic" }, + { value: "linear", label: "Linear" }, + ], + }, + ], +}); + +// @ts-expect-error -- Setting type is a string literal, but Dropdown setting doesn't accept that as a valid type +SettingRegistry.registerSetting(Setting.PLOT_VARIANT, "Plot variant", DropdownStringSetting); +SettingRegistry.registerSetting(Setting.LOG_CURVE, "Log curve", LogCurveSetting); + SettingRegistry.registerSetting(Setting.ATTRIBUTE, "Attribute", DropdownStringSetting); SettingRegistry.registerSetting(Setting.ENSEMBLE, "Ensemble", EnsembleSetting); SettingRegistry.registerSetting(Setting.COLOR_SCALE, "Color Scale", ColorScaleSetting); diff --git a/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts b/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts index bacb70d8a..9f2a0809b 100644 --- a/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts +++ b/frontend/src/modules/_shared/LayerFramework/settings/settingsDefinitions.ts @@ -1,7 +1,13 @@ -import type { SurfaceStatisticFunction_api, WellboreHeader_api, WellborePick_api } from "@api"; +import type { + SurfaceStatisticFunction_api, + WellboreHeader_api, + WellboreLogCurveHeader_api, + WellborePick_api, +} from "@api"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import type { ColorScaleSpecification } from "@framework/components/ColorScaleSelector/colorScaleSelector"; import type { ColorSet } from "@lib/utils/ColorSet"; +import type { TemplatePlotType } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; import { isEqual } from "lodash"; @@ -21,6 +27,16 @@ export enum SettingCategory { } export enum Setting { + // Assorted styling visual settings + SHOW_LABELS = "showLabels", + LABEL_DIR = "labelDir", + SHOW_LINES = "showLines", + TRACK_WIDTH = "trackWidth", + SCALE = "scale", + + LOG_CURVE = "logCurve", + PLOT_VARIANT = "plotType", + ATTRIBUTE = "attribute", ENSEMBLE = "ensemble", COLOR_SCALE = "colorScale", @@ -51,6 +67,13 @@ export enum Setting { } export const settingCategories = { + [Setting.SHOW_LABELS]: SettingCategory.BOOLEAN, + [Setting.LABEL_DIR]: SettingCategory.SINGLE_SELECT, + [Setting.SHOW_LINES]: SettingCategory.BOOLEAN, + [Setting.TRACK_WIDTH]: SettingCategory.NUMBER_WITH_STEP, + [Setting.SCALE]: SettingCategory.SINGLE_SELECT, + [Setting.LOG_CURVE]: SettingCategory.SINGLE_SELECT, + [Setting.PLOT_VARIANT]: SettingCategory.SINGLE_SELECT, [Setting.ATTRIBUTE]: SettingCategory.SINGLE_SELECT, [Setting.ENSEMBLE]: SettingCategory.SINGLE_SELECT, [Setting.COLOR_SCALE]: SettingCategory.STATIC, @@ -84,6 +107,13 @@ export const settingCategories = { export type SettingCategories = typeof settingCategories; export type SettingTypes = { + [Setting.SHOW_LABELS]: boolean; + [Setting.SCALE]: "linear" | "log" | null; + [Setting.LABEL_DIR]: "horizontal" | "vertical" | null; + [Setting.SHOW_LINES]: boolean; + [Setting.TRACK_WIDTH]: number | null; + [Setting.LOG_CURVE]: WellboreLogCurveHeader_api | null; + [Setting.PLOT_VARIANT]: TemplatePlotType | null; [Setting.ATTRIBUTE]: string | null; [Setting.ENSEMBLE]: RegularEnsembleIdent | null; [Setting.COLOR_SCALE]: ColorScaleSpecification | null; diff --git a/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts b/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts index 6f404902c..5a352d104 100644 --- a/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts +++ b/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts @@ -4,6 +4,12 @@ import type { StatusMessage } from "@framework/ModuleInstanceStatusController"; import type { GlobalTopicDefinitions } from "@framework/WorkbenchServices"; import * as bbox from "@lib/utils/bbox"; import type { ColorScaleWithId } from "@modules/_shared/components/ColorLegendsContainer/colorLegendsContainer"; +import type { + Template, + TemplatePlot, + TemplateTrack, +} from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; +import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; import type { GroupDelegate } from "../delegates/GroupDelegate"; import { DataLayer, DataLayerStatus } from "../framework/DataLayer/DataLayer"; @@ -28,6 +34,7 @@ import type { SettingTypes, Settings } from "../settings/settingsDefinitions"; export enum VisualizationTarget { DECK_GL = "deck_gl", ESV = "esv", + WSC_WELL_LOG = "wsc_well_log", // VIDEX = "videx", } @@ -60,6 +67,7 @@ export type EsvView = { export type TargetViewReturnTypes = { [VisualizationTarget.DECK_GL]: Record; [VisualizationTarget.ESV]: EsvView; + [VisualizationTarget.WSC_WELL_LOG]: TemplateTrack | Template; }; export interface ViewDataCollectorFunction< @@ -77,6 +85,7 @@ export interface ViewDataCollectorFunction< export type TargetLayerReturnTypes = { [VisualizationTarget.DECK_GL]: DeckGlLayer; [VisualizationTarget.ESV]: EsvLayer; + [VisualizationTarget.WSC_WELL_LOG]: TemplatePlot | WellPickProps | null; }; export type Annotation = ColorScaleWithId; // Add more possible annotation types here, e.g. ColorSets etc. From 5d3628633d8497509608a4b2818906f7ee9673fb Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Mon, 7 Apr 2025 12:06:35 +0200 Subject: [PATCH 06/25] Added duplicate curve-name example to drogon curve data. Added clarifying comment about lognames in SSDL --- .../primary/primary/routers/well/router.py | 5 +-- .../ssdl_access/drogon/_drogon_well_data.py | 37 +++++++++++++++++++ .../ssdl_access/drogon/drogon_ssdl_access.py | 9 ++++- .../services/ssdl_access/well_access.py | 9 ++++- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/backend_py/primary/primary/routers/well/router.py b/backend_py/primary/primary/routers/well/router.py index 457364a25..1006801c9 100644 --- a/backend_py/primary/primary/routers/well/router.py +++ b/backend_py/primary/primary/routers/well/router.py @@ -316,14 +316,13 @@ async def get_log_curve_data( # Handle DROGON if is_drogon_identifier(wellbore_uuid=wellbore_uuid): well_access_drogon = DrogonWellAccess(authenticated_user.get_ssdl_access_token()) - curve_data = await well_access_drogon.get_log_curve_data_async(wellbore_uuid, curve_name) + curve_data = await well_access_drogon.get_log_curve_data_async(wellbore_uuid, curve_name, log_name) return converters.convert_wellbore_log_curve_data_to_schema(curve_data) if source == schemas.WellLogCurveSourceEnum.SSDL_WELL_LOG: - # Note that log name is not used on SSDL; but afaik curve names are not unique across all logs... well_access = SsdlWellAccess(authenticated_user.get_ssdl_access_token()) - log_curve = await well_access.get_log_curve_data_async(wellbore_uuid, curve_name) + log_curve = await well_access.get_log_curve_data_async(wellbore_uuid, curve_name, log_name) return converters.convert_wellbore_log_curve_data_to_schema(log_curve) diff --git a/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py b/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py index e31d6f039..2ecb1202e 100644 --- a/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py +++ b/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py @@ -8,6 +8,7 @@ well_log_headers_2 = [ types.WellboreLogCurveHeader(curve_name="BS", curve_unit="IN", log_name="DROGON_CONTINUOUS"), + types.WellboreLogCurveHeader(curve_name="BS", curve_unit="IN", log_name="DROGON_CONTINUOUS_2"), ] @@ -151,4 +152,40 @@ (1000, None), ], ), + # Curve name is *not* unique across all logs in a well, since some log runs might present the same data. + # This is an example of such a curve. + "BS_2": types.WellboreLogCurveData( + name="BS", + log_name="DROGON_CONTINUOUS_2", + index_min=100, + index_max=1000, + index_unit="m", + unit="IN", + curve_alias=None, + curve_description=None, + curve_unit_desc=None, + min_curve_value=200, + max_curve_value=1000, + no_data_value=None, + DataPoints=[ + (100, 100), + (150, 200), + (250, 200), + (300, 300), + (350, 300), + (400, 400), + (450, 400), + (500, 500), + (550, 600), + (600, 600), + (650, 700), + (700, 700), + (750, 800), + (800, 800), + (850, 900), + (900, 900), + (950, 600), + (1000, 600), + ], + ), } diff --git a/backend_py/primary/primary/services/ssdl_access/drogon/drogon_ssdl_access.py b/backend_py/primary/primary/services/ssdl_access/drogon/drogon_ssdl_access.py index 31166d896..526c44b9e 100644 --- a/backend_py/primary/primary/services/ssdl_access/drogon/drogon_ssdl_access.py +++ b/backend_py/primary/primary/services/ssdl_access/drogon/drogon_ssdl_access.py @@ -33,10 +33,15 @@ async def get_log_curve_headers_for_field_async(self, field_uuid: str) -> list[t raise NotImplementedError # pylint: disable=unused-argument - async def get_log_curve_data_async(self, wellbore_uuid: str, curve_name: str) -> types.WellboreLogCurveData: + async def get_log_curve_data_async( + self, wellbore_uuid: str, curve_name: str, log_name: str + ) -> types.WellboreLogCurveData: if wellbore_uuid == "drogon_vertical": return _drogon_well_data.well_log_data_map_1[curve_name] if wellbore_uuid == "drogon_horizontal": - return _drogon_well_data.well_log_data_map_2[curve_name] + for curve in _drogon_well_data.well_log_data_map_2.values(): + if curve.name == curve_name and curve.log_name == log_name: + return curve + raise ValueError(f"No curve {curve_name=} found for log {log_name=}!") raise ValueError(f"Unexpected drogon well name: {wellbore_uuid=}!") diff --git a/backend_py/primary/primary/services/ssdl_access/well_access.py b/backend_py/primary/primary/services/ssdl_access/well_access.py index 9157faf57..e6fcb3435 100644 --- a/backend_py/primary/primary/services/ssdl_access/well_access.py +++ b/backend_py/primary/primary/services/ssdl_access/well_access.py @@ -67,8 +67,13 @@ async def get_log_curve_headers_for_field_async(self, field_uuid: str) -> List[t raise InvalidDataError(f"Invalid log curve headers for field {field_uuid}", Service.SSDL) from error return result - async def get_log_curve_data_async(self, wellbore_uuid: str, curve_name: str) -> types.WellboreLogCurveData: - params = {"normalized_data": False} + async def get_log_curve_data_async( + self, wellbore_uuid: str, curve_name: str, log_name: str + ) -> types.WellboreLogCurveData: + # ! Note: SSDL does not actually take the curve name into account when fetching data, but curve names are not unique across + # ! all logs, and there's no documentation about how one should specify the log, when picking curves. For now, I'm including + # ! log name as an argument, since we should fix that at some point in the future. + params = {"normalized_data": False, "log_name": log_name} endpoint = f"WellLog/{wellbore_uuid}/{curve_name}" ssdl_data = await ssdl_get_request_async(access_token=self._ssdl_token, endpoint=endpoint, params=params) try: From 13ab61a81639ddf6520131c3d3c0ad94edc49f27 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Mon, 7 Apr 2025 13:18:20 +0200 Subject: [PATCH 07/25] WIP -- Made manager actions group dependent --- .../LayerManagerComponentWrapper.tsx | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx index 569869ad6..adefc052f 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx @@ -15,8 +15,10 @@ import { LayerManagerTopic, } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; import { DataLayerManagerComponent } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManagerComponent"; +import { Group } from "@modules/_shared/LayerFramework/framework/Group/Group"; import { GroupRegistry } from "@modules/_shared/LayerFramework/groups/GroupRegistry"; import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; +import type { ItemGroup } from "@modules/_shared/LayerFramework/interfacesAndTypes/entities"; import { LayerRegistry } from "@modules/_shared/LayerFramework/layers/LayerRegistry"; import { ShowChart, ViewDay } from "@mui/icons-material"; import { useQueryClient } from "@tanstack/react-query"; @@ -42,36 +44,6 @@ enum PlotActionIdents { DIFF = "diff", } -const LAYER_ACTIONS: ActionGroup[] = [ - { - label: "Log viewer", - children: [ - { - label: "Continuous Track", - identifier: LayerActionIdents.CONTINUOUS_TRACK, - icon: , - }, - { - label: "Viewer Settings", - identifier: LayerActionIdents.SETTINGS, - }, - { - label: "Well picks", - identifier: LayerActionIdents.WELL_PICKS, - }, - ], - }, - { - label: "Plots", - children: [ - { icon: , label: "Line plot", identifier: PlotActionIdents.LINE }, - { icon: , label: "Area plot", identifier: PlotActionIdents.AREA }, - { icon: , label: "Stacked plot", identifier: PlotActionIdents.STACKED }, - { icon: , label: "Differential plot", identifier: PlotActionIdents.DIFF }, - ], - }, -]; - function usePersistedLayerManager( workbenchSession: WorkbenchSession, workbenchSettings: WorkbenchSettings, @@ -141,6 +113,47 @@ function usePersistedLayerManager( return layerManager; } +function makeOptionsForGroup(group: ItemGroup): ActionGroup[] { + if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK) { + return [ + { + label: "Plots", + children: [ + { icon: , label: "Line plot", identifier: PlotActionIdents.LINE }, + { icon: , label: "Area plot", identifier: PlotActionIdents.AREA }, + { + icon: , + label: "Stacked plot", + identifier: PlotActionIdents.STACKED, + }, + { + icon: , + label: "Differential plot", + identifier: PlotActionIdents.DIFF, + }, + ], + }, + ]; + } + + return [ + { + label: "Log viewer", + children: [ + { + label: "Continuous Track", + identifier: LayerActionIdents.CONTINUOUS_TRACK, + icon: , + }, + { + label: "Well picks", + identifier: LayerActionIdents.WELL_PICKS, + }, + ], + }, + ]; +} + export type LayerManagerComponentWrapperProps = { workbenchSession: WorkbenchSession; workbenchSettings: WorkbenchSettings; @@ -178,8 +191,8 @@ export function LayerManagerComponentWrapper(props: LayerManagerComponentWrapper } - groupActions={LAYER_ACTIONS} + additionalHeaderComponents={null} + groupActions={makeOptionsForGroup} onAction={layerActionCallback} /> ); From 2426bdb8ffe8e9c95d1125af4e93d0494961e1d3 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Mon, 7 Apr 2025 14:12:38 +0200 Subject: [PATCH 08/25] Added handling of duplicate curve names --- .../LayerFramework/visualizations/plots.ts | 31 ++++++++++--- .../LayerFramework/visualizations/tracks.ts | 7 +-- .../settings/atoms/derivedAtoms.ts | 46 ++++++++++++------- frontend/src/modules/WellLogViewer/types.ts | 30 +++++++++++- .../WellLogViewer/utils/logViewerTemplate.ts | 19 ++++++-- .../modules/WellLogViewer/utils/strings.ts | 9 ++-- .../utils/useLogViewerVisualizationFactory.ts | 9 ++-- .../WellLogViewer/view/atoms/baseAtoms.ts | 3 +- .../WellLogViewer/view/atoms/derivedAtoms.ts | 18 -------- .../view/atoms/interfaceEffects.ts | 4 +- .../WellLogViewer/view/atoms/queryAtoms.ts | 32 ++----------- .../view/components/ReadoutWrapper.tsx | 8 ++-- .../components/SubsurfaceLogViewerWrapper.tsx | 29 ++++++------ .../visualization/VisualizationFactory.ts | 8 +--- 14 files changed, 137 insertions(+), 116 deletions(-) diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts index 7c18a1393..e0690d4c4 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts +++ b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts @@ -13,6 +13,12 @@ import type { AreaPlotSettingTypes } from "../dataProviders/plots/AreaPlotProvid import type { LinearPlotSettingTypes } from "../dataProviders/plots/LinearPlotProvider"; export const DATA_ACC_KEY = "LOG_CURVE_DATA"; +export const DUPLICATE_NAMES_ACC_KEY = " DUPLICATE_CURVE_NAMES"; + +export type FactoryAccResult = { + [DATA_ACC_KEY]: WellboreLogCurveData_api[]; + [DUPLICATE_NAMES_ACC_KEY]: Set; +}; type PlotVisualizationFunc = MakeVisualizationFunction< PlotSettings, @@ -24,8 +30,12 @@ export const makeAreaPlotConfig: PlotVisualizationFunc = ( const data = args.getData(); const plotVariant = args.getSetting(Setting.PLOT_VARIANT); + const curveName = data?.name ?? ""; + const logName = data?.logName ?? ""; + return { - name: data?.name ?? "", + logName: logName, + name: curveName, type: plotVariant ?? undefined, // TODO: Color @@ -38,27 +48,36 @@ export const makeLinePlotConfig: PlotVisualizationFunc = const plotVariant = args.getSetting(Setting.PLOT_VARIANT) ?? undefined; const curveName = data?.name ?? ""; + const logName = data?.logName ?? ""; return { + logName: logName, name: curveName, type: plotVariant, // TODO: Color }; }; -export const plotDataAccumulator: ReduceAccumulatedDataFunction<[], WellboreLogCurveData_api, Record> = ( +export const plotDataAccumulator: ReduceAccumulatedDataFunction<[], WellboreLogCurveData_api, FactoryAccResult> = ( acc, args, ) => { - const existingData = _.get(acc, DATA_ACC_KEY, []) as WellboreLogCurveData_api[]; const newData = args.getData(); + if (!newData) return acc; + + const duplicatedNames = _.get(acc, DUPLICATE_NAMES_ACC_KEY, new Set()) as Set; + const curveData = _.get(acc, DATA_ACC_KEY, []) as WellboreLogCurveData_api[]; - // TODO: Use this to set up name-uniqueness checks? + const existingCurve = _.find(curveData, ["name", newData.name]); + const sameName = existingCurve?.name === newData.name; + const sameLog = existingCurve?.logName === newData.logName; - if (!newData || _.find(existingData, ["name", newData.name])) return acc; + if (sameName && sameLog) return acc; + if (sameName) duplicatedNames.add(newData.name); return { ...acc, - [DATA_ACC_KEY]: [...existingData, newData], + [DATA_ACC_KEY]: [...curveData, newData], + [DUPLICATE_NAMES_ACC_KEY]: duplicatedNames, }; }; diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts index 4c4f2cf28..57f7e4244 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts +++ b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts @@ -1,3 +1,4 @@ +import type { TemplateTrack } from "@modules/WellLogViewer/types"; import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; import type { TargetViewReturnTypes, @@ -5,7 +6,7 @@ import type { VisualizationTarget, VisualizationViewBasic, } from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; -import type { TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; +import type { TemplatePlotScale } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; @@ -20,8 +21,8 @@ export const makeContinuousTrackConfig: ViewDataCollectorFunction< title: args.name, required: true, width: trackWidth, - scale: trackScale, - // Need to fill this later, as they + scale: trackScale as TemplatePlotScale, + // Need to fill this later, as they're not defined yet. plots: [], }; }; diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts index 34f32f31f..e2b4b067f 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts @@ -43,24 +43,36 @@ export const selectedWellboreHeaderAtom = atom((get) return availableWellboreHeaders.find((wh) => wh.wellboreUuid === selectedWellboreId) ?? availableWellboreHeaders[0]; }); +/** + * @deprecated Handled by DataFramework instead + */ export const availableContinuousCurvesAtom = atom((get) => { const logCurveHeaders = get(wellLogCurveHeadersQueryAtom).data ?? []; return _.filter(logCurveHeaders, ["curveType", WellLogCurveTypeEnum_api.CONTINUOUS]); }); +/** + * @deprecated Handled by DataFramework instead + */ export const availableDiscreteCurvesAtom = atom((get) => { const logCurveHeaders = get(wellLogCurveHeadersQueryAtom).data ?? []; return _.filter(logCurveHeaders, ["curveType", WellLogCurveTypeEnum_api.DISCRETE]); }); +/** + * @deprecated Handled by DataFramework instead + */ export const availableFlagCurvesAtom = atom((get) => { const logCurveHeaders = get(wellLogCurveHeadersQueryAtom).data ?? []; return _.filter(logCurveHeaders, ["curveType", WellLogCurveTypeEnum_api.FLAG]); }); +/** + * @deprecated Handled by DataFramework instead + */ export const wellLogTemplateTracksAtom = atom((get) => { const templateTrackConfigs = get(logViewerTrackConfigsAtom); @@ -72,22 +84,9 @@ export const wellLogTemplateTracksAtom = atom((get) => { }); }); -export const requiredCurvesAtom = atom((get) => { - const templateTracks = get(logViewerTrackConfigsAtom); - - const logCurveHeaders = get(wellLogCurveHeadersQueryAtom).data ?? []; - const availableCurves = _.map(logCurveHeaders, "curveName"); - - return _.chain(templateTracks) - .flatMap("plots") - .filter("_isValid") // Do not bother with invalid configs - .flatMap(({ _curveHeader, _curveHeader2 }) => [_curveHeader, _curveHeader2]) - .filter((header): header is WellboreLogCurveHeader_api => !!header) - .filter((header) => availableCurves.includes(header.curveName)) - .uniqBy(makeSelectValueForCurveHeader) - .value(); -}); - +/** + * @deprecated Handled by DataFramework instead + */ export const selectedWellPickColumnAtom = atom((get) => { const userSelectedWellPickColumn = get(userSelectedWellPickColumnAtom) ?? ""; const wellboreStratColumns = get(wellboreStratColumnsQueryAtom).data ?? []; @@ -98,17 +97,26 @@ export const selectedWellPickColumnAtom = atom((get) => { return wellboreStratColumns[0]; }); +/** + * @deprecated Handled by DataFramework instead + */ export const wellPicksByInterpreterAtom = atom>((get) => { const picks = get(wellborePicksQueryAtom).data ?? []; return _.groupBy(picks, "interpreter"); }); +/** + * @deprecated Handled by DataFramework instead + */ export const availableWellPickInterpretersAtom = atom((get) => { const wellPicksByInterpreter = get(wellPicksByInterpreterAtom); return Object.keys(wellPicksByInterpreter); }); +/** + * @deprecated Handled by DataFramework instead + */ export const selectedWellPickInterpreter = atom((get) => { const selectedInterpreter = get(userSelectedWellPickInterpreterAtom); const availableInterpreters = get(availableWellPickInterpretersAtom); @@ -119,6 +127,9 @@ export const selectedWellPickInterpreter = atom((get) => { return availableInterpreters[0]; }); +/** + * @deprecated Handled by DataFramework instead + */ export const selectedWellborePicksAtom = atom((get) => { const wellPicksByInterpreter = get(wellPicksByInterpreterAtom); const selectedInterpreter = get(selectedWellPickInterpreter); @@ -131,6 +142,9 @@ export const selectedWellborePicksAtom = atom((get) => { return interpreterPicks.filter(({ pickIdentifier }) => selectedWellPicks.includes(pickIdentifier)); }); +/** + * @deprecated Handled by DataFramework instead + */ export const missingCurvesAtom = atom((get) => { const templateTracks = get(logViewerTrackConfigsAtom) ?? []; const availableCurvesQuery = get(wellLogCurveHeadersQueryAtom); diff --git a/frontend/src/modules/WellLogViewer/types.ts b/frontend/src/modules/WellLogViewer/types.ts index 7197d1e19..2726bdae9 100644 --- a/frontend/src/modules/WellLogViewer/types.ts +++ b/frontend/src/modules/WellLogViewer/types.ts @@ -1,8 +1,29 @@ import type { WellLogCurveTypeEnum_api, WellboreLogCurveHeader_api } from "@api"; -import type { TemplatePlot, TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; +import type { + TemplatePlot as TemplatePlotSSC, + Template as TemplateSSC, + TemplateTrack as TemplateTrackSSC, +} from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; + +/** + * The well-log viewer does not allow any distinction to which log a curve should be taken from. We add this as an + * extension to the type, so it can be used in a later workaround (see `logViewerTemplate.ts`) + */ +export type TemplatePlot = TemplatePlotSSC & { + logName: string; +}; + +export type TemplateTrack = Omit & { + plots: TemplatePlot[]; +}; + +export type Template = Omit & { + tracks: TemplateTrack[]; +}; /** * Extension of the SS-comp library type to add some state types to help with editing settings + * @deprecated */ export type TemplatePlotConfig = Partial & { // Used for state updates @@ -17,9 +38,13 @@ export type TemplatePlotConfig = Partial & { /** * Extension of the SS-comp library type to add some state types to help with editing settings + * @deprecated */ export type TemplateTrackConfig = ContinuousTemplateTrackConfig | DiscreteTemplateTrackConfig; +/** + * @deprecated + */ export type ContinuousTemplateTrackConfig = Omit & { // ID used to allow the settings-menu to drag-sort them _key: string; @@ -27,6 +52,9 @@ export type ContinuousTemplateTrackConfig = Omit & { plots: TemplatePlotConfig[]; }; +/** + * @deprecated + */ export type DiscreteTemplateTrackConfig = Omit & { // ID used to allow the settings-menu to drag-sort them _key: string; diff --git a/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts b/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts index 311765328..f6e4cf5d5 100644 --- a/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts +++ b/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts @@ -2,25 +2,34 @@ * Utilities and constants used for generating well-log-viewer template configs */ import { WellLogCurveTypeEnum_api } from "@api"; -import type { Template } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; import _ from "lodash"; import { v4 } from "uuid"; import { CURVE_COLOR_PALETTE, DIFF_CURVE_COLORS } from "./logViewerColors"; import { MAIN_AXIS_CURVE } from "./queryDataTransform"; +import { getUniqueCurveNameForPlotConfig } from "./strings"; -import type { TemplatePlotConfig, TemplateTrackConfig } from "../types"; +import type { Template, TemplatePlot, TemplatePlotConfig, TemplateTrack } from "../types"; + +// import type { TemplatePlotConfig, TemplateTrackConfig } from "../types"; export const DEFAULT_MAX_VISIBLE_TRACKS = 5; -export function createLogTemplate(templateTracks: TemplateTrackConfig[], nonUniqueNames?: Set): Template { - // TODO: Implement non-unique names +export function createLogTemplate(templateTracks: TemplateTrack[], nonUniqueNames?: Set): Template { return { // AFAIK, this name is not show anywhere name: "Well log viewer", scale: { primary: MAIN_AXIS_CURVE.name, allowSecondary: true }, - tracks: templateTracks, + + // ! Map each plot to ensure a name that points to the correct curve + tracks: templateTracks.map((track) => ({ + ...track, + plots: track.plots.map((plot) => ({ + ...plot, + name: getUniqueCurveNameForPlotConfig(plot, nonUniqueNames), + })) as TemplatePlot[], + })), }; } diff --git a/frontend/src/modules/WellLogViewer/utils/strings.ts b/frontend/src/modules/WellLogViewer/utils/strings.ts index acc43b71f..8178d2d8c 100644 --- a/frontend/src/modules/WellLogViewer/utils/strings.ts +++ b/frontend/src/modules/WellLogViewer/utils/strings.ts @@ -1,6 +1,7 @@ import type { WellboreLogCurveData_api, WellboreLogCurveHeader_api } from "@api"; import { WellLogCurveSourceEnum_api } from "@api"; -import type { TemplatePlotConfig } from "@modules/WellLogViewer/types"; + +import type { TemplatePlot } from "../types"; /** * Translates a well log curve data source to a more readable string @@ -34,12 +35,12 @@ export function simplifyLogName(logName: string, truncateLength = 0) { return logName; } -export function getUniqueCurveNameForPlotConfig(plot: TemplatePlotConfig, nonUniqueNames?: Set) { +export function getUniqueCurveNameForPlotConfig(plot: TemplatePlot, nonUniqueNames?: Set) { if (!plot.name) throw new Error("Unexpected invalid config"); - if (!plot._curveHeader) throw new Error("Unexpected invalid config"); + if (!plot.logName) throw new Error("Unexpected invalid config"); if (nonUniqueNames?.has(plot.name)) { - return makeCompoundCurveName(plot._curveHeader.curveName, plot._curveHeader.logName); + return makeCompoundCurveName(plot.name, plot.logName); } return plot.name; diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts index 418200eab..a3a583c38 100644 --- a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts @@ -1,6 +1,5 @@ import React from "react"; -import type { WellboreLogCurveData_api } from "@api"; import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; import { LayerManagerTopic } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; @@ -11,15 +10,13 @@ import { AreaPlotProvider } from "../LayerFramework/dataProviders/plots/AreaPlot import { LinearPlotProvider } from "../LayerFramework/dataProviders/plots/LinearPlotProvider"; import { WellborePicksProvider } from "../LayerFramework/dataProviders/wellpicks/WellPicksProvider"; import { ContinuousLogTrack } from "../LayerFramework/groups/ContinuousLogTrack"; +import type { FactoryAccResult as PlotFactoryAccResult } from "../LayerFramework/visualizations/plots"; import { makeAreaPlotConfig, makeLinePlotConfig, plotDataAccumulator } from "../LayerFramework/visualizations/plots"; import { makeContinuousTrackConfig } from "../LayerFramework/visualizations/tracks"; import { makeLogViewerWellPicks } from "../LayerFramework/visualizations/wellpicks"; -const VISUALIZATION_FACTORY = new VisualizationFactory< - VisualizationTarget.WSC_WELL_LOG, - never, - Record ->(); +type FactoryAccResult = PlotFactoryAccResult; +const VISUALIZATION_FACTORY = new VisualizationFactory(); VISUALIZATION_FACTORY.registerLayerFunctions(LinearPlotProvider.name, LinearPlotProvider, { makeVisualizationFunction: makeLinePlotConfig, diff --git a/frontend/src/modules/WellLogViewer/view/atoms/baseAtoms.ts b/frontend/src/modules/WellLogViewer/view/atoms/baseAtoms.ts index 657df1055..d13314564 100644 --- a/frontend/src/modules/WellLogViewer/view/atoms/baseAtoms.ts +++ b/frontend/src/modules/WellLogViewer/view/atoms/baseAtoms.ts @@ -1,10 +1,9 @@ -import type { WellboreHeader_api, WellboreLogCurveHeader_api } from "@api"; +import type { WellboreHeader_api } from "@api"; import { atom } from "jotai"; export const wellboreHeaderAtom = atom(null); export const selectedFieldIdentAtom = atom(null); -export const requiredCurvesAtom = atom([]); // Local switch to avoid unneccessary queries while applying interface effects. // See issue #846 (https://github.com/equinor/webviz/issues/846) diff --git a/frontend/src/modules/WellLogViewer/view/atoms/derivedAtoms.ts b/frontend/src/modules/WellLogViewer/view/atoms/derivedAtoms.ts index b081848c3..b0833ce1f 100644 --- a/frontend/src/modules/WellLogViewer/view/atoms/derivedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/view/atoms/derivedAtoms.ts @@ -4,7 +4,6 @@ import { IntersectionReferenceSystem } from "@equinor/esv-intersection"; import { atom } from "jotai"; import _ from "lodash"; -import { requiredCurvesAtom } from "./baseAtoms"; import { wellboreTrajectoryQueryAtom } from "./queryAtoms"; export const intersectionReferenceSystemAtom = atom((get) => { @@ -28,20 +27,3 @@ function trajectoryToReferenceSystemPath(trajectory: WellboreTrajectory_api): nu return [easting, northing, tvd]; }); } - -// The Subsurface template pattern is exceptionally cumbersome and only uses curve name both for data lookup and curve titles -// (with no way to override it, or specify a log run). This atom provides a list of all curve names that are not unique -// across all selected curves, allowing us to override the names when adding them to the track -export const nonUniqueCurveNamesAtom = atom>((get) => { - const requiredCurves = get(requiredCurvesAtom); - - const seenNames = new Set(); - const nonUniqueNames = new Set(); - - requiredCurves.forEach(({ curveName }) => { - if (seenNames.has(curveName)) nonUniqueNames.add(curveName); - else seenNames.add(curveName); - }); - - return nonUniqueNames; -}); diff --git a/frontend/src/modules/WellLogViewer/view/atoms/interfaceEffects.ts b/frontend/src/modules/WellLogViewer/view/atoms/interfaceEffects.ts index 48466f3f4..001d74ce0 100644 --- a/frontend/src/modules/WellLogViewer/view/atoms/interfaceEffects.ts +++ b/frontend/src/modules/WellLogViewer/view/atoms/interfaceEffects.ts @@ -1,7 +1,7 @@ import type { InterfaceEffects } from "@framework/Module"; import type { SettingsToViewInterface } from "@modules/WellLogViewer/interfaces"; -import { lockQueriesAtom, requiredCurvesAtom, selectedFieldIdentAtom, wellboreHeaderAtom } from "./baseAtoms"; +import { lockQueriesAtom, selectedFieldIdentAtom, wellboreHeaderAtom } from "./baseAtoms"; export const settingsToViewInterfaceEffects: InterfaceEffects = [ (getInterfaceValue, setAtomValue) => { @@ -12,11 +12,9 @@ export const settingsToViewInterfaceEffects: InterfaceEffects { const locked = get(lockQueriesAtom); @@ -25,26 +22,3 @@ export const wellboreTrajectoryQueryAtom = atomWithQuery((get) => { enabled: Boolean(!locked && fieldIdent && wellboreUuid), }; }); -export const logCurveDataQueryAtom = atomWithQueries((get) => { - // TODO: Handle patterns? Can be found on SMDA Geology Standard, under "symbol" - const locked = get(lockQueriesAtom); - const wellboreUuid = get(wellboreHeaderAtom)?.wellboreUuid ?? ""; - const requiredCurves = get(requiredCurvesAtom); - - return { - queries: requiredCurves.map(({ source, logName, curveName }) => () => ({ - ...getLogCurveDataOptions({ - query: { - wellbore_uuid: wellboreUuid, - curve_name: curveName, - log_name: logName, - source, - }, - }), - enabled: Boolean(!locked && wellboreUuid && source && logName && curveName), - })), - combine(results: QueryObserverResult[]) { - return mergeResults(results); - }, - }; -}); diff --git a/frontend/src/modules/WellLogViewer/view/components/ReadoutWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/ReadoutWrapper.tsx index 3d803719b..5106267e5 100644 --- a/frontend/src/modules/WellLogViewer/view/components/ReadoutWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/ReadoutWrapper.tsx @@ -1,6 +1,6 @@ import type React from "react"; -import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; +import type { TemplateTrack } from "@modules/WellLogViewer/types"; import type { InfoItem, ReadoutItem } from "@modules/_shared/components/ReadoutBox"; import { ReadoutBox } from "@modules/_shared/components/ReadoutBox"; import type { Info } from "@webviz/well-log-viewer/dist/components/InfoTypes"; @@ -10,7 +10,7 @@ import _ from "lodash"; import { DEFAULT_MAX_VISIBLE_TRACKS } from "../../utils/logViewerTemplate"; export type ReadoutWrapperProps = { - templateTracks: TemplateTrackConfig[]; + templateTracks: TemplateTrack[]; wellLogReadout: Info[]; hide?: boolean; }; @@ -25,7 +25,7 @@ export function ReadoutWrapper(props: ReadoutWrapperProps): React.ReactNode { return ; } -function parseWellLogReadout(wellLogInfo: Info[], templateTracks: TemplateTrackConfig[]): ReadoutItem[] { +function parseWellLogReadout(wellLogInfo: Info[], templateTracks: TemplateTrack[]): ReadoutItem[] { return _.chain(wellLogInfo) .filter(({ type }) => type !== "separator") .groupBy("iTrack") @@ -35,7 +35,7 @@ function parseWellLogReadout(wellLogInfo: Info[], templateTracks: TemplateTrackC .value(); } -function infoToReadoutItem(infos: Info[], iTrack: number, templateTracks: TemplateTrackConfig[]): ReadoutItem { +function infoToReadoutItem(infos: Info[], iTrack: number, templateTracks: TemplateTrack[]): ReadoutItem { // The axis curves are printes with index -1 if (iTrack === -1) { return { diff --git a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx index 01d314d71..45524bf11 100644 --- a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx @@ -7,24 +7,25 @@ import { SyncSettingKey } from "@framework/SyncSettings"; import type { GlobalTopicDefinitions, WorkbenchServices } from "@framework/WorkbenchServices"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; import { createContinuousColorScaleForMap } from "@modules/3DViewer/view/utils/colorTables"; -import { DATA_ACC_KEY as PLOT_DATA_ACC_KEY } from "@modules/WellLogViewer/LayerFramework/visualizations/plots"; +import { + DUPLICATE_NAMES_ACC_KEY, + DATA_ACC_KEY as PLOT_DATA_ACC_KEY, +} from "@modules/WellLogViewer/LayerFramework/visualizations/plots"; import { isTrackGroup } from "@modules/WellLogViewer/LayerFramework/visualizations/tracks"; +import type { TemplatePlot, TemplateTrack } from "@modules/WellLogViewer/types"; import { useLogViewerVisualizationFactoryProduct } from "@modules/WellLogViewer/utils/useLogViewerVisualizationFactory"; import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; import { WellLogViewer } from "@webviz/well-log-viewer"; import type { Info } from "@webviz/well-log-viewer/dist/components/InfoTypes"; -import type { TemplatePlot, TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; import type { WellLogController, WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; -import { useAtomValue } from "jotai"; -import { isEqual } from "lodash"; +import _ from "lodash"; import { ReadoutWrapper } from "./ReadoutWrapper"; import type { InterfaceTypes } from "../../interfaces"; import { createLogTemplate } from "../../utils/logViewerTemplate"; import { createWellLogSets } from "../../utils/queryDataTransform"; -import { nonUniqueCurveNamesAtom } from "../atoms/derivedAtoms"; const AXIS_MNEMOS = { md: ["RKB", "DEPTH", "DEPT", "MD", "TDEP", "MD_RKB"], @@ -67,7 +68,7 @@ function useSubscribeToGlobalHoverMdChange( React.useEffect( function registerMdHoverSubscriber() { function handleGlobalValueChange(newValue: GlobalHoverMd) { - if (!isEqual(lastReceivedChange, newValue)) { + if (!_.isEqual(lastReceivedChange, newValue)) { lastReceivedChange.current = newValue; if (newValue?.wellboreUuid === wellboreUuid) { @@ -149,8 +150,6 @@ function useCreateGlobalVerticalScaleBroadcastFunc( } function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { - const nonUniqueCurveNames = useAtomValue(nonUniqueCurveNamesAtom); - const trajectoryData = props.trajectoryData; const intersectionReferenceSystem = props.intersectionReferenceSystem; const padDataWithEmptyRows = props.padDataWithEmptyRows; @@ -165,6 +164,8 @@ function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { // TODO: This would arguably be something for the "root view" const template = React.useMemo(() => { const views = factoryProduct?.views ?? []; + const accData = factoryProduct?.accumulatedData ?? {}; + const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); const trackTemplates = views.reduce((acc, v) => { if (!isTrackGroup(v)) return acc; @@ -177,20 +178,22 @@ function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { return [...acc, fullTrackTemplate]; }, [] as TemplateTrack[]); - return createLogTemplate(trackTemplates, nonUniqueCurveNames); - }, [factoryProduct, nonUniqueCurveNames]); + return createLogTemplate(trackTemplates, duplicatedCurveNames); + }, [factoryProduct?.accumulatedData, factoryProduct?.views]); const welllog = React.useMemo(() => { - const curveData = factoryProduct?.accumulatedData[PLOT_DATA_ACC_KEY] ?? []; + const accData = factoryProduct?.accumulatedData ?? {}; + const curveData = _.get(accData, PLOT_DATA_ACC_KEY, []); + const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); return createWellLogSets( curveData, trajectoryData, intersectionReferenceSystem, - nonUniqueCurveNames, + duplicatedCurveNames, padDataWithEmptyRows, ); - }, [factoryProduct, trajectoryData, intersectionReferenceSystem, padDataWithEmptyRows, nonUniqueCurveNames]); + }, [factoryProduct?.accumulatedData, trajectoryData, intersectionReferenceSystem, padDataWithEmptyRows]); return { template, welllog, wellpicks }; } diff --git a/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts b/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts index 5a352d104..de194e7db 100644 --- a/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts +++ b/frontend/src/modules/_shared/LayerFramework/visualization/VisualizationFactory.ts @@ -3,12 +3,8 @@ import type { Layer as EsvLayer } from "@equinor/esv-intersection"; import type { StatusMessage } from "@framework/ModuleInstanceStatusController"; import type { GlobalTopicDefinitions } from "@framework/WorkbenchServices"; import * as bbox from "@lib/utils/bbox"; +import type { TemplatePlot, TemplateTrack } from "@modules/WellLogViewer/types"; import type { ColorScaleWithId } from "@modules/_shared/components/ColorLegendsContainer/colorLegendsContainer"; -import type { - Template, - TemplatePlot, - TemplateTrack, -} from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; import type { GroupDelegate } from "../delegates/GroupDelegate"; @@ -67,7 +63,7 @@ export type EsvView = { export type TargetViewReturnTypes = { [VisualizationTarget.DECK_GL]: Record; [VisualizationTarget.ESV]: EsvView; - [VisualizationTarget.WSC_WELL_LOG]: TemplateTrack | Template; + [VisualizationTarget.WSC_WELL_LOG]: TemplateTrack; }; export interface ViewDataCollectorFunction< From d0d81bf2cb815ef0eedfa0b9f9dd9ed05991372d Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Wed, 9 Apr 2025 10:44:09 +0200 Subject: [PATCH 09/25] WIP -- Bug in manager init --- .../dataProviders/plots/AreaPlotProvider.ts | 12 +- .../dataProviders/plots/DiffPlotProvider.ts | 36 +++ .../dataProviders/plots/LinearPlotProvider.ts | 22 +- .../plots/StackedPlotProvider.ts | 0 .../dataProviders/plots/_shared.ts | 7 +- .../wellpicks/WellPicksProvider.ts | 17 +- .../groups/ContinuousLogTrack.ts | 6 +- .../groups/DiscreteLogTrack.ts | 16 ++ .../DataProviderFramework/groups/shared.ts | 1 + .../registerFrameworkExtensions.ts | 16 ++ .../visualizations/plots.ts | 49 +++-- .../visualizations/tracks.ts | 40 ++++ .../visualizations/wellpicks.ts | 6 +- .../registerFrameworkExtensions.ts | 16 -- .../LayerFramework/visualizations/tracks.ts | 39 ---- .../src/modules/WellLogViewer/interfaces.ts | 8 +- .../WellLogViewer/settings/atoms/baseAtoms.ts | 4 +- .../settings/atoms/persistedAtoms.ts | 8 +- .../LayerManagerComponentWrapper.tsx | 199 ----------------- .../ProviderManagerComponentWrapper.tsx | 205 ++++++++++++++++++ .../WellLogViewer/settings/settings.tsx | 16 +- .../utils/useLogViewerVisualizationFactory.ts | 58 +++-- .../components/SubsurfaceLogViewerWrapper.tsx | 17 +- .../src/modules/WellLogViewer/view/view.tsx | 6 +- .../implementations/ColorSetSetting.tsx | 92 ++++++++ .../implementations/SingleColorSetting.tsx | 70 ++++++ .../settings/registerAllSettings.ts | 2 + .../settings/settingsDefinitions.ts | 3 + .../visualization/VisualizationAssembler.ts | 4 +- 29 files changed, 612 insertions(+), 363 deletions(-) rename frontend/src/modules/WellLogViewer/{LayerFramework => DataProviderFramework}/dataProviders/plots/AreaPlotProvider.ts (66%) create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts rename frontend/src/modules/WellLogViewer/{LayerFramework => DataProviderFramework}/dataProviders/plots/LinearPlotProvider.ts (58%) create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts rename frontend/src/modules/WellLogViewer/{LayerFramework => DataProviderFramework}/dataProviders/plots/_shared.ts (81%) rename frontend/src/modules/WellLogViewer/{LayerFramework => DataProviderFramework}/dataProviders/wellpicks/WellPicksProvider.ts (84%) rename frontend/src/modules/WellLogViewer/{LayerFramework => DataProviderFramework}/groups/ContinuousLogTrack.ts (70%) create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts rename frontend/src/modules/WellLogViewer/{LayerFramework => DataProviderFramework}/visualizations/plots.ts (53%) create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts rename frontend/src/modules/WellLogViewer/{LayerFramework => DataProviderFramework}/visualizations/wellpicks.ts (76%) delete mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts delete mode 100644 frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx create mode 100644 frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx create mode 100644 frontend/src/modules/_shared/DataProviderFramework/settings/implementations/ColorSetSetting.tsx create mode 100644 frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts similarity index 66% rename from frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider.ts rename to frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts index 5b9dc6c0c..8cdb2d3ae 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts @@ -1,8 +1,8 @@ import type { WellboreLogCurveData_api } from "@api"; -import type { CustomDataLayerImplementation } from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; -import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; -import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; -import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import type { CustomDataProviderImplementation } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; @@ -10,7 +10,9 @@ export const AreaPlotSettings = [...baseLinearSettings, Setting.PLOT_VARIANT, Se export type AreaPlotSettingTypes = typeof AreaPlotSettings; type SettingsTypeMap = MakeSettingTypesMap; -export class AreaPlotProvider implements CustomDataLayerImplementation { +export class AreaPlotProvider + implements CustomDataProviderImplementation +{ settings = AreaPlotSettings; // Uses the same external things as the other types diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts new file mode 100644 index 000000000..931a95bb2 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts @@ -0,0 +1,36 @@ +import type { WellboreLogCurveData_api } from "@api"; +import type { CustomDataProviderImplementation } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; + +import _ from "lodash"; + +import { defineDependencies, fetchData } from "./_shared"; + +// TODO: Need a clean way to +export const differentialPlotSettings = [Setting.LOG_CURVE, Setting.LOG_CURVE, Setting.SCALE] as const; +export type DifferentialPlotSettingTypes = typeof differentialPlotSettings; +type SettingsTypeMap = MakeSettingTypesMap; + +export class DifferentialPlotProvider + implements CustomDataProviderImplementation +{ + // Uses the same external things as the other types + defineDependencies = defineDependencies; + fetchData = fetchData; + + settings = differentialPlotSettings; + + getDefaultName() { + return "Linear plot"; + } + + areCurrentSettingsValid() { + // TODO + return true; + } + + doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { + return _.isEqual(prevSettings?.logCurve, newSettings?.logCurve); + } +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts similarity index 58% rename from frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider.ts rename to frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts index 232505548..a5759e0fe 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts @@ -1,20 +1,20 @@ import type { WellboreLogCurveData_api } from "@api"; import type { - CustomDataLayerImplementation, - DataLayerInformationAccessors, -} from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; -import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; -import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; -import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; + CustomDataProviderImplementation, + DataProviderInformationAccessors, +} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; -export const linearPlotSettings = [...baseLinearSettings, Setting.PLOT_VARIANT, Setting.COLOR] as const; +export const linearPlotSettings = [...baseLinearSettings, Setting.PLOT_VARIANT /*Setting.COLOR */] as const; export type LinearPlotSettingTypes = typeof linearPlotSettings; type SettingsTypeMap = MakeSettingTypesMap; export class LinearPlotProvider - implements CustomDataLayerImplementation + implements CustomDataProviderImplementation { // Uses the same external things as the other types defineDependencies(args: DefineDependenciesArgs) { @@ -31,8 +31,10 @@ export class LinearPlotProvider return "Linear plot"; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - areCurrentSettingsValid(accessor: DataLayerInformationAccessors) { + areCurrentSettingsValid( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + accessor: DataProviderInformationAccessors, + ) { // TODO return true; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/_shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts similarity index 81% rename from frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/_shared.ts rename to frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts index 2c4245e2f..be6aaa4f8 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/plots/_shared.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts @@ -1,8 +1,9 @@ import type { WellboreLogCurveData_api } from "@api"; import { getLogCurveDataOptions, getWellboreLogCurveHeadersOptions } from "@api"; -import type { FetchDataParams } from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; -import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; -import { Setting, type Settings } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import type { FetchDataParams } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import type { Settings } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; export const baseSettings = [ Setting.LOG_CURVE, diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/wellpicks/WellPicksProvider.ts similarity index 84% rename from frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider.ts rename to frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/wellpicks/WellPicksProvider.ts index 134733af5..62b95c2c2 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/wellpicks/WellPicksProvider.ts @@ -1,13 +1,12 @@ import type { WellborePick_api } from "@api"; import { getWellborePicksInStratColumnOptions, getWellboreStratigraphicColumnsOptions } from "@api"; import type { - CustomDataLayerImplementation, - DataLayerInformationAccessors, + CustomDataProviderImplementation, + DataProviderInformationAccessors, FetchDataParams, -} from "@modules/_shared/LayerFramework/interfacesAndTypes/customDataLayerImplementation"; -import type { DefineDependenciesArgs } from "@modules/_shared/LayerFramework/interfacesAndTypes/customSettingsHandler"; -import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; -import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import { type MakeSettingTypesMap, Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import _ from "lodash"; @@ -15,7 +14,9 @@ export const wellPickSettings = [Setting.STRAT_COLUMN, Setting.SMDA_INTERPRETER, export type WellPickSettingTypes = typeof wellPickSettings; type SettingsTypeMap = MakeSettingTypesMap; -export class WellborePicksProvider implements CustomDataLayerImplementation { +export class WellborePicksProvider + implements CustomDataProviderImplementation +{ // Uses the same external things as the other types defineDependencies(args: DefineDependenciesArgs) { const { helperDependency, availableSettingsUpdater, queryClient } = args; @@ -93,7 +94,7 @@ export class WellborePicksProvider implements CustomDataLayerImplementation) { + areCurrentSettingsValid(accessor: DataProviderInformationAccessors) { // TODO return true; diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/groups/ContinuousLogTrack.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts similarity index 70% rename from frontend/src/modules/WellLogViewer/LayerFramework/groups/ContinuousLogTrack.ts rename to frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts index be3bde212..8d4adcfcf 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/groups/ContinuousLogTrack.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts @@ -1,6 +1,6 @@ -import type { CustomGroupImplementationWithSettings } from "@modules/_shared/LayerFramework/interfacesAndTypes/customGroupImplementation"; -import type { MakeSettingTypesMap } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; -import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import type { CustomGroupImplementationWithSettings } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation"; +import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; const continuousTrackSettings = [Setting.TRACK_WIDTH, Setting.SCALE] as const; export type ContinuousTrackSettings = typeof continuousTrackSettings; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts new file mode 100644 index 000000000..d59ae70d7 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts @@ -0,0 +1,16 @@ +// import type { CustomGroupImplementationWithSettings } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation"; +// import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; + +// const discreteLogSettings = [Setting.STRAT_COLUMN, Setting.SMDA_INTERPRETER]; + +// class DiscreteLogTrack implements CustomGroupImplementationWithSettings { +// constructor(parameters) {} +// settings; + +// getDefaultSettingsValues(): {} { +// throw new Error("Method not implemented."); +// } +// getDefaultName(): string { +// return "Track"; +// } +// } diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts new file mode 100644 index 000000000..bdfc741b4 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts @@ -0,0 +1 @@ +// import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions" diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts new file mode 100644 index 000000000..6c25f1b59 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts @@ -0,0 +1,16 @@ +import { DataProviderRegistry } from "@modules/_shared/DataProviderFramework/dataProviders/DataProviderRegistry"; +import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; +import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; + +import { AreaPlotProvider } from "./dataProviders/plots/AreaPlotProvider"; +import { DifferentialPlotProvider } from "./dataProviders/plots/DiffPlotProvider"; +import { LinearPlotProvider } from "./dataProviders/plots/LinearPlotProvider"; +import { WellborePicksProvider } from "./dataProviders/wellpicks/WellPicksProvider"; +import { ContinuousLogTrack } from "./groups/ContinuousLogTrack"; + +GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK, ContinuousLogTrack); + +DataProviderRegistry.registerDataProvider(LinearPlotProvider.name, LinearPlotProvider); +DataProviderRegistry.registerDataProvider(AreaPlotProvider.name, AreaPlotProvider); +DataProviderRegistry.registerDataProvider(DifferentialPlotProvider.name, DifferentialPlotProvider); +DataProviderRegistry.registerDataProvider(WellborePicksProvider.name, WellborePicksProvider); diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts similarity index 53% rename from frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts rename to frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts index e0690d4c4..f1301c8a2 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/plots.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts @@ -1,11 +1,12 @@ import type { WellboreLogCurveData_api } from "@api"; -import type { Settings } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; -import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; +import type { TemplatePlot } from "@modules/WellLogViewer/types"; +import type { Settings } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import type { - MakeVisualizationFunction, ReduceAccumulatedDataFunction, VisualizationTarget, -} from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; + VisualizationTransformer, +} from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import _ from "lodash"; @@ -20,13 +21,15 @@ export type FactoryAccResult = { [DUPLICATE_NAMES_ACC_KEY]: Set; }; -type PlotVisualizationFunc = MakeVisualizationFunction< +type PlotVisualizationFunc = VisualizationTransformer< PlotSettings, WellboreLogCurveData_api, VisualizationTarget.WSC_WELL_LOG >; -export const makeAreaPlotConfig: PlotVisualizationFunc = (args) => { +export const makeAreaPlotConfig: PlotVisualizationFunc = function makeAreaPlotConfig( + args, +): TemplatePlot { const data = args.getData(); const plotVariant = args.getSetting(Setting.PLOT_VARIANT); @@ -58,26 +61,24 @@ export const makeLinePlotConfig: PlotVisualizationFunc = }; }; -export const plotDataAccumulator: ReduceAccumulatedDataFunction<[], WellboreLogCurveData_api, FactoryAccResult> = ( - acc, - args, -) => { - const newData = args.getData(); - if (!newData) return acc; +export const plotDataAccumulator: ReduceAccumulatedDataFunction<[], WellboreLogCurveData_api, FactoryAccResult> = + function plotDataAccumulator(acc, args) { + const newData = args.getData(); + if (!newData) return acc; - const duplicatedNames = _.get(acc, DUPLICATE_NAMES_ACC_KEY, new Set()) as Set; - const curveData = _.get(acc, DATA_ACC_KEY, []) as WellboreLogCurveData_api[]; + const duplicatedNames = _.get(acc, DUPLICATE_NAMES_ACC_KEY, new Set()) as Set; + const curveData = _.get(acc, DATA_ACC_KEY, []) as WellboreLogCurveData_api[]; - const existingCurve = _.find(curveData, ["name", newData.name]); - const sameName = existingCurve?.name === newData.name; - const sameLog = existingCurve?.logName === newData.logName; + const existingCurve = _.find(curveData, ["name", newData.name]); + const sameName = existingCurve?.name === newData.name; + const sameLog = existingCurve?.logName === newData.logName; - if (sameName && sameLog) return acc; - if (sameName) duplicatedNames.add(newData.name); + if (sameName && sameLog) return acc; + if (sameName) duplicatedNames.add(newData.name); - return { - ...acc, - [DATA_ACC_KEY]: [...curveData, newData], - [DUPLICATE_NAMES_ACC_KEY]: duplicatedNames, + return { + ...acc, + [DATA_ACC_KEY]: [...curveData, newData], + [DUPLICATE_NAMES_ACC_KEY]: duplicatedNames, + }; }; -}; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts new file mode 100644 index 000000000..350189062 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts @@ -0,0 +1,40 @@ +import type { TemplateTrack } from "@modules/WellLogViewer/types"; +import type { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import type { + GroupCustomPropsCollector, + VisualizationGroup, + VisualizationTarget, +} from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import type { TemplatePlotScale } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; + +import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; + +export const makeContinuousTrackConfig: GroupCustomPropsCollector< + ContinuousTrackSettings, + GroupType.WELL_LOG_TRACK, + Record +> = function makeContinuousTrackConfig(args) { + const trackWidth = args.getSetting(Setting.TRACK_WIDTH) ?? undefined; + const trackScale = args.getSetting(Setting.SCALE) ?? undefined; + + return { + title: args.name, + required: true, + width: trackWidth, + scale: trackScale as TemplatePlotScale, + // Need to fill this later, as they're not defined yet. + plots: [], + }; +}; + +type BasicViewVisualization = VisualizationGroup; + +// type ViewVisualization = BasicViewVisualization; +type TrackVisualization = BasicViewVisualization & TemplateTrack; + +export function isTrackGroup(groupVisualization: unknown): groupVisualization is TrackVisualization { + if (groupVisualization == null || typeof groupVisualization !== "object") return false; + + return "plots" in groupVisualization; +} diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/wellpicks.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks.ts similarity index 76% rename from frontend/src/modules/WellLogViewer/LayerFramework/visualizations/wellpicks.ts rename to frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks.ts index 94abf943c..ab0e9a79b 100644 --- a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/wellpicks.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks.ts @@ -1,14 +1,14 @@ import type { WellborePick_api } from "@api"; import { createLogViewerWellPicks } from "@modules/WellLogViewer/utils/queryDataTransform"; import type { - MakeVisualizationFunction, VisualizationTarget, -} from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; + VisualizationTransformer, +} from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; import type { WellPickSettingTypes } from "../dataProviders/wellpicks/WellPicksProvider"; -export const makeLogViewerWellPicks: MakeVisualizationFunction< +export const makeLogViewerWellPicks: VisualizationTransformer< WellPickSettingTypes, WellborePick_api[], VisualizationTarget.WSC_WELL_LOG diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts b/frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts deleted file mode 100644 index 207ac94f6..000000000 --- a/frontend/src/modules/WellLogViewer/LayerFramework/registerFrameworkExtensions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GroupRegistry } from "@modules/_shared/LayerFramework/groups/GroupRegistry"; -import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; -import { LayerRegistry } from "@modules/_shared/LayerFramework/layers/LayerRegistry"; - -import { AreaPlotProvider } from "./dataProviders/plots/AreaPlotProvider"; -import { DifferentialPlotProvider } from "./dataProviders/plots/DiffPlotProvider"; -import { LinearPlotProvider } from "./dataProviders/plots/LinearPlotProvider"; -import { WellborePicksProvider } from "./dataProviders/wellpicks/WellPicksProvider"; -import { ContinuousLogTrack } from "./groups/ContinuousLogTrack"; - -GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK, ContinuousLogTrack); - -LayerRegistry.registerLayer(LinearPlotProvider.name, LinearPlotProvider); -LayerRegistry.registerLayer(AreaPlotProvider.name, AreaPlotProvider); -LayerRegistry.registerLayer(DifferentialPlotProvider.name, DifferentialPlotProvider); -LayerRegistry.registerLayer(WellborePicksProvider.name, WellborePicksProvider); diff --git a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts b/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts deleted file mode 100644 index 57f7e4244..000000000 --- a/frontend/src/modules/WellLogViewer/LayerFramework/visualizations/tracks.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { TemplateTrack } from "@modules/WellLogViewer/types"; -import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions"; -import type { - TargetViewReturnTypes, - ViewDataCollectorFunction, - VisualizationTarget, - VisualizationViewBasic, -} from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; -import type { TemplatePlotScale } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; - -import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; - -export const makeContinuousTrackConfig: ViewDataCollectorFunction< - ContinuousTrackSettings, - VisualizationTarget.WSC_WELL_LOG -> = (args) => { - const trackWidth = args.getSetting(Setting.TRACK_WIDTH) ?? undefined; - const trackScale = args.getSetting(Setting.SCALE) ?? undefined; - - return { - title: args.name, - required: true, - width: trackWidth, - scale: trackScale as TemplatePlotScale, - // Need to fill this later, as they're not defined yet. - plots: [], - }; -}; - -type BasicViewVisualization = VisualizationViewBasic; - -type ViewVisualization = BasicViewVisualization & TargetViewReturnTypes[VisualizationTarget.WSC_WELL_LOG]; -type TrackVisualization = BasicViewVisualization & TemplateTrack; - -export function isTrackGroup(groupVisualization: ViewVisualization): groupVisualization is TrackVisualization { - if (groupVisualization == null) return false; - - return "plots" in groupVisualization; -} diff --git a/frontend/src/modules/WellLogViewer/interfaces.ts b/frontend/src/modules/WellLogViewer/interfaces.ts index abc9ac735..c97d8d8df 100644 --- a/frontend/src/modules/WellLogViewer/interfaces.ts +++ b/frontend/src/modules/WellLogViewer/interfaces.ts @@ -1,8 +1,8 @@ import type { WellboreHeader_api, WellborePick_api } from "@api"; import type { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; -import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; +import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; -import { layerManagerAtom } from "./settings/atoms/baseAtoms"; +import { providerManagerAtom } from "./settings/atoms/baseAtoms"; import { selectedFieldIdentifierAtom, selectedWellboreHeaderAtom, @@ -15,7 +15,7 @@ export type InterfaceTypes = { }; export type SettingsToViewInterface = { - layerManager: DataLayerManager | null; + providerManager: DataProviderManager | null; selectedField: string | null; wellboreHeader: WellboreHeader_api | null; @@ -25,7 +25,7 @@ export type SettingsToViewInterface = { }; export const settingsToViewInterfaceInitialization: InterfaceInitialization = { - layerManager: (get) => get(layerManagerAtom), + providerManager: (get) => get(providerManagerAtom), selectedField: (get) => get(selectedFieldIdentifierAtom), wellboreHeader: (get) => get(selectedWellboreHeaderAtom), viewerHorizontal: (get) => get(viewerHorizontalAtom), diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts index 61e6eeadc..ff688468d 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts @@ -1,8 +1,8 @@ -import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; +import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; import { atom } from "jotai"; -export const layerManagerAtom = atom(null); +export const providerManagerAtom = atom(null); export const userSelectedFieldIdentifierAtom = atom(null); export const userSelectedWellboreUuidAtom = atom(null); diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts index 6ac9d725f..2173f5234 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts @@ -1,6 +1,6 @@ import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; import { atomWithModuleInstanceStorage, clearModuleInstanceStorage } from "@modules/WellLogViewer/utils/atoms"; -import type { SerializedDataLayerManager } from "@modules/_shared/LayerFramework/interfacesAndTypes/serialization"; +import type { SerializedDataProviderManager } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/serialization"; import type { Getter, Setter } from "jotai"; import { atom } from "jotai"; @@ -35,9 +35,9 @@ export const padDataWithEmptyRowsAtom = atom( (get, set, newVal) => setPersistentModuleField(get, set, "padDataWithEmptyRows", newVal), ); -export const serializedManagerStateAtom = atom( - (get) => getPersistentModuleField(get, "layerManagerState", undefined), - (get, set, newVal) => setPersistentModuleField(get, set, "layerManagerState", newVal), +export const serializedManagerStateAtom = atom( + (get) => getPersistentModuleField(get, "providerManagerState", undefined), + (get, set, newVal) => setPersistentModuleField(get, set, "providerManagerState", newVal), ); export function clearStorageForInstance(instanceId: string) { diff --git a/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx deleted file mode 100644 index adefc052f..000000000 --- a/frontend/src/modules/WellLogViewer/settings/components/LayerManagerComponentWrapper.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import React from "react"; - -import { WellLogCurveTypeEnum_api } from "@api"; -import type { WorkbenchSession } from "@framework/WorkbenchSession"; -import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; -import { AreaPlotProvider } from "@modules/WellLogViewer/LayerFramework/dataProviders/plots/AreaPlotProvider"; -import { LinearPlotProvider } from "@modules/WellLogViewer/LayerFramework/dataProviders/plots/LinearPlotProvider"; -import { WellborePicksProvider } from "@modules/WellLogViewer/LayerFramework/dataProviders/wellpicks/WellPicksProvider"; -import { TrackIcon } from "@modules/WellLogViewer/_shared/components/icons"; -import type { ActionGroup } from "@modules/_shared/LayerFramework/Actions"; -import type { GroupDelegate } from "@modules/_shared/LayerFramework/delegates/GroupDelegate"; -import { GroupDelegateTopic } from "@modules/_shared/LayerFramework/delegates/GroupDelegate"; -import { - DataLayerManager, - LayerManagerTopic, -} from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; -import { DataLayerManagerComponent } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManagerComponent"; -import { Group } from "@modules/_shared/LayerFramework/framework/Group/Group"; -import { GroupRegistry } from "@modules/_shared/LayerFramework/groups/GroupRegistry"; -import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; -import type { ItemGroup } from "@modules/_shared/LayerFramework/interfacesAndTypes/entities"; -import { LayerRegistry } from "@modules/_shared/LayerFramework/layers/LayerRegistry"; -import { ShowChart, ViewDay } from "@mui/icons-material"; -import { useQueryClient } from "@tanstack/react-query"; - -import { useAtom } from "jotai"; - -import "../../LayerFramework/registerFrameworkExtensions"; -import { layerManagerAtom } from "../atoms/baseAtoms"; -import { serializedManagerStateAtom } from "../atoms/persistedAtoms"; - -enum LayerActionIdents { - CONTINUOUS_TRACK = "cont_track", - DISCRETE_TRACK = "disc_track", - SETTINGS = "settings", - PLOT = "plot", - WELL_PICKS = "well_picks", -} - -enum PlotActionIdents { - LINE = "line", - AREA = "area", - STACKED = "stacked", - DIFF = "diff", -} - -function usePersistedLayerManager( - workbenchSession: WorkbenchSession, - workbenchSettings: WorkbenchSettings, -): DataLayerManager | null { - const queryClient = useQueryClient(); - - const hasAppliedPersistedState = React.useRef(false); - const [layerManager, setLayerManager] = useAtom(layerManagerAtom); - const [serializedManagerState, setSerializedManagerState] = useAtom(serializedManagerStateAtom); - - const persistManagerState = React.useCallback( - function persistManagerState() { - if (!layerManager) return; - - setSerializedManagerState(layerManager.serializeState()); - }, - [layerManager, setSerializedManagerState], - ); - - React.useEffect( - function initalizeLayerManagerEffect() { - const newLayerManager = new DataLayerManager(workbenchSession, workbenchSettings, queryClient); - setLayerManager(newLayerManager); - hasAppliedPersistedState.current = false; - - return () => newLayerManager.beforeDestroy(); - }, - [queryClient, setLayerManager, workbenchSession, workbenchSettings], - ); - - // applyPersistedManagerState(layerManager); - React.useEffect( - function applyManagerState() { - if (!layerManager || !serializedManagerState) return; - if (hasAppliedPersistedState.current) return; - - layerManager.deserializeState(serializedManagerState); - - hasAppliedPersistedState.current = true; - }, - [serializedManagerState, layerManager], - ); - - React.useEffect( - function setupManagerListenersEffect() { - if (!layerManager) return; - // - persistManagerState(); - - const unsubscribeDataRev = layerManager - .getPublishSubscribeDelegate() - .makeSubscriberFunction(LayerManagerTopic.LAYER_DATA_REVISION)(persistManagerState); - - const unsubscribeExpands = layerManager - .getGroupDelegate() - .getPublishSubscribeDelegate() - .makeSubscriberFunction(GroupDelegateTopic.CHILDREN_EXPANSION_STATES)(persistManagerState); - - return function onUnmountEffect() { - unsubscribeDataRev(); - unsubscribeExpands(); - }; - }, - [layerManager, persistManagerState], - ); - - return layerManager; -} - -function makeOptionsForGroup(group: ItemGroup): ActionGroup[] { - if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK) { - return [ - { - label: "Plots", - children: [ - { icon: , label: "Line plot", identifier: PlotActionIdents.LINE }, - { icon: , label: "Area plot", identifier: PlotActionIdents.AREA }, - { - icon: , - label: "Stacked plot", - identifier: PlotActionIdents.STACKED, - }, - { - icon: , - label: "Differential plot", - identifier: PlotActionIdents.DIFF, - }, - ], - }, - ]; - } - - return [ - { - label: "Log viewer", - children: [ - { - label: "Continuous Track", - identifier: LayerActionIdents.CONTINUOUS_TRACK, - icon: , - }, - { - label: "Well picks", - identifier: LayerActionIdents.WELL_PICKS, - }, - ], - }, - ]; -} - -export type LayerManagerComponentWrapperProps = { - workbenchSession: WorkbenchSession; - workbenchSettings: WorkbenchSettings; -}; - -export function LayerManagerComponentWrapper(props: LayerManagerComponentWrapperProps): React.ReactNode { - const layerManager = usePersistedLayerManager(props.workbenchSession, props.workbenchSettings); - - const layerActionCallback = React.useCallback( - function layerActionCallback(identifier: string, groupDelegate: GroupDelegate) { - if (!layerManager) return; - - switch (identifier) { - case LayerActionIdents.WELL_PICKS: - return groupDelegate.appendChild(LayerRegistry.makeLayer(WellborePicksProvider.name, layerManager)); - - case LayerActionIdents.CONTINUOUS_TRACK: - return groupDelegate.appendChild(GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK, layerManager)); - - case PlotActionIdents.LINE: - return groupDelegate.appendChild(LayerRegistry.makeLayer(LinearPlotProvider.name, layerManager)); - case PlotActionIdents.AREA: - return groupDelegate.appendChild(LayerRegistry.makeLayer(AreaPlotProvider.name, layerManager)); - - default: - break; - } - }, - [layerManager], - ); - - if (!layerManager) return
; - - return ( - - ); -} diff --git a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx new file mode 100644 index 000000000..f91ceacc7 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx @@ -0,0 +1,205 @@ +import React from "react"; + +import { WellLogCurveTypeEnum_api } from "@api"; +import type { WorkbenchSession } from "@framework/WorkbenchSession"; +import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { AreaPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider"; +import { LinearPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider"; +import { WellborePicksProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/wellpicks/WellPicksProvider"; +import { TrackIcon } from "@modules/WellLogViewer/_shared/components/icons"; +import type { ActionGroup } from "@modules/_shared/DataProviderFramework/Actions"; +import { DataProviderRegistry } from "@modules/_shared/DataProviderFramework/dataProviders/DataProviderRegistry"; +import type { GroupDelegate } from "@modules/_shared/DataProviderFramework/delegates/GroupDelegate"; +import { GroupDelegateTopic } from "@modules/_shared/DataProviderFramework/delegates/GroupDelegate"; +import { + DataProviderManager, + DataProviderManagerTopic, +} from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { DataProviderManagerComponent } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManagerComponent"; +import { Group } from "@modules/_shared/DataProviderFramework/framework/Group/Group"; +// import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; +import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; +import type { ItemGroup } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/entities"; +import { ShowChart, ViewDay } from "@mui/icons-material"; +import { useQueryClient } from "@tanstack/react-query"; + +import { useAtom } from "jotai"; + +import { providerManagerAtom } from "../atoms/baseAtoms"; +import { serializedManagerStateAtom } from "../atoms/persistedAtoms"; +import "../../DataProviderFramework/registerFrameworkExtensions"; + +enum RootActionIdents { + CONTINUOUS_TRACK = "cont_track", + DISCRETE_TRACK = "disc_track", + SETTINGS = "settings", + WELL_PICKS = "well_picks", +} + +enum PlotActionIdents { + LINE = "line", + AREA = "area", + STACKED = "stacked", + DIFF = "diff", +} + +function usePersistedProviderManager( + workbenchSession: WorkbenchSession, + workbenchSettings: WorkbenchSettings, +): DataProviderManager | null { + const queryClient = useQueryClient(); + + const hasAppliedPersistedState = React.useRef(false); + const [providerManager, setProviderManager] = useAtom(providerManagerAtom); + const [serializedManagerState, setSerializedManagerState] = useAtom(serializedManagerStateAtom); + + const persistManagerState = React.useCallback( + function persistManagerState() { + if (!providerManager) return; + + setSerializedManagerState(providerManager.serializeState()); + }, + [providerManager, setSerializedManagerState], + ); + + React.useEffect( + function initializeProviderManagerEffect() { + const newProviderManager = new DataProviderManager(workbenchSession, workbenchSettings, queryClient); + setProviderManager(newProviderManager); + hasAppliedPersistedState.current = false; + + return () => newProviderManager.beforeDestroy(); + }, + [queryClient, setProviderManager, workbenchSession, workbenchSettings], + ); + + React.useEffect( + function applyManagerState() { + if (!providerManager || !serializedManagerState) return; + if (hasAppliedPersistedState.current) return; + + providerManager.deserializeState(serializedManagerState); + + hasAppliedPersistedState.current = true; + }, + [serializedManagerState, providerManager], + ); + + React.useEffect( + function setupManagerListenersEffect() { + if (!providerManager) return; + // + persistManagerState(); + + const unsubscribeDataRev = providerManager + .getPublishSubscribeDelegate() + .makeSubscriberFunction(DataProviderManagerTopic.DATA_REVISION)(persistManagerState); + + const unsubscribeExpands = providerManager + .getGroupDelegate() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(GroupDelegateTopic.CHILDREN_EXPANSION_STATES)(persistManagerState); + + return function onUnmountEffect() { + unsubscribeDataRev(); + unsubscribeExpands(); + }; + }, + [providerManager, persistManagerState], + ); + + return providerManager; +} + +function makeOptionsForGroup(group: ItemGroup): ActionGroup[] { + if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK) { + return [ + { + label: "Plots", + children: [ + { icon: , label: "Line plot", identifier: PlotActionIdents.LINE }, + { icon: , label: "Area plot", identifier: PlotActionIdents.AREA }, + { + icon: , + label: "Stacked plot", + identifier: PlotActionIdents.STACKED, + }, + { + icon: , + label: "Differential plot", + identifier: PlotActionIdents.DIFF, + }, + ], + }, + ]; + } + + return [ + { + label: "Log viewer", + children: [ + { + label: "Continuous Track", + identifier: RootActionIdents.CONTINUOUS_TRACK, + icon: , + }, + { + label: "Well picks", + identifier: RootActionIdents.WELL_PICKS, + }, + ], + }, + ]; +} + +export type ProviderManagerComponentWrapperProps = { + workbenchSession: WorkbenchSession; + workbenchSettings: WorkbenchSettings; +}; + +export function ProviderManagerComponentWrapper(props: ProviderManagerComponentWrapperProps): React.ReactNode { + const providerManager = usePersistedProviderManager(props.workbenchSession, props.workbenchSettings); + + const groupActionCallback = React.useCallback( + function groupActionCallback(identifier: string, groupDelegate: GroupDelegate) { + if (!providerManager) return; + + switch (identifier) { + case RootActionIdents.WELL_PICKS: + return groupDelegate.appendChild( + DataProviderRegistry.makeDataProvider(WellborePicksProvider.name, providerManager), + ); + + // case RootActionIdents.CONTINUOUS_TRACK: + // return groupDelegate.appendChild( + // GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK, providerManager), + // ); + + // case PlotActionIdents.LINE: + // return groupDelegate.appendChild( + // DataProviderRegistry.makeDataProvider(LinearPlotProvider.name, providerManager), + // ); + // case PlotActionIdents.AREA: + // return groupDelegate.appendChild( + // DataProviderRegistry.makeDataProvider(AreaPlotProvider.name, providerManager), + // ); + + default: + break; + } + }, + [providerManager], + ); + + if (!providerManager) return
; + + return ( + + ); +} diff --git a/frontend/src/modules/WellLogViewer/settings/settings.tsx b/frontend/src/modules/WellLogViewer/settings/settings.tsx index 9393732e3..8cfc87c0f 100644 --- a/frontend/src/modules/WellLogViewer/settings/settings.tsx +++ b/frontend/src/modules/WellLogViewer/settings/settings.tsx @@ -18,10 +18,10 @@ import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePr import { useAtomValue, useSetAtom } from "jotai"; import _ from "lodash"; -import { layerManagerAtom, userSelectedFieldIdentifierAtom, userSelectedWellboreUuidAtom } from "./atoms/baseAtoms"; +import { providerManagerAtom, userSelectedFieldIdentifierAtom, userSelectedWellboreUuidAtom } from "./atoms/baseAtoms"; import { selectedFieldIdentifierAtom, selectedWellboreHeaderAtom } from "./atoms/derivedAtoms"; import { availableFieldsQueryAtom, drilledWellboreHeadersQueryAtom } from "./atoms/queryAtoms"; -import { LayerManagerComponentWrapper } from "./components/LayerManagerComponentWrapper"; +import { ProviderManagerComponentWrapper } from "./components/ProviderManagerComponentWrapper"; // import { TemplateTrackSettings } from "./components/TemplateTrackSettings"; import { ViewerSettings } from "./components/ViewerSettings"; @@ -61,7 +61,7 @@ export function Settings(props: ModuleSettingsProps) { // Utilities const syncedSettingKeys = props.settingsContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, props.workbenchServices); - const layerManager = useAtomValue(layerManagerAtom); + const providerManager = useAtomValue(providerManagerAtom); // Field selection const availableFields = useAtomValue(availableFieldsQueryAtom)?.data ?? []; @@ -89,12 +89,12 @@ export function Settings(props: ModuleSettingsProps) { const wellboreHeadersErrorStatus = usePropagateApiErrorToStatusWriter(wellboreHeaders, statusWriter) ?? ""; React.useEffect(() => { - layerManager?.updateGlobalSetting("fieldId", selectedField); - }, [layerManager, selectedField]); + providerManager?.updateGlobalSetting("fieldId", selectedField); + }, [providerManager, selectedField]); React.useEffect(() => { - layerManager?.updateGlobalSetting("wellboreUuid", selectedWellboreHeader?.wellboreUuid ?? null); - }, [layerManager, selectedWellboreHeader]); + providerManager?.updateGlobalSetting("wellboreUuid", selectedWellboreHeader?.wellboreUuid ?? null); + }, [providerManager, selectedWellboreHeader]); return (
@@ -128,7 +128,7 @@ export function Settings(props: ModuleSettingsProps) { {/* */} - diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts index a3a583c38..2d05b184d 100644 --- a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts @@ -1,52 +1,62 @@ import React from "react"; -import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; -import { LayerManagerTopic } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; -import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; -import type { VisualizationTarget } from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; -import { VisualizationFactory } from "@modules/_shared/LayerFramework/visualization/VisualizationFactory"; - -import { AreaPlotProvider } from "../LayerFramework/dataProviders/plots/AreaPlotProvider"; -import { LinearPlotProvider } from "../LayerFramework/dataProviders/plots/LinearPlotProvider"; -import { WellborePicksProvider } from "../LayerFramework/dataProviders/wellpicks/WellPicksProvider"; -import { ContinuousLogTrack } from "../LayerFramework/groups/ContinuousLogTrack"; -import type { FactoryAccResult as PlotFactoryAccResult } from "../LayerFramework/visualizations/plots"; -import { makeAreaPlotConfig, makeLinePlotConfig, plotDataAccumulator } from "../LayerFramework/visualizations/plots"; -import { makeContinuousTrackConfig } from "../LayerFramework/visualizations/tracks"; -import { makeLogViewerWellPicks } from "../LayerFramework/visualizations/wellpicks"; +import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { DataProviderManagerTopic } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; +import type { VisualizationTarget } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import { VisualizationAssembler } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; + +import { AreaPlotProvider } from "../DataProviderFramework/dataProviders/plots/AreaPlotProvider"; +import { LinearPlotProvider } from "../DataProviderFramework/dataProviders/plots/LinearPlotProvider"; +import { WellborePicksProvider } from "../DataProviderFramework/dataProviders/wellpicks/WellPicksProvider"; +import { ContinuousLogTrack } from "../DataProviderFramework/groups/ContinuousLogTrack"; +import type { FactoryAccResult as PlotFactoryAccResult } from "../DataProviderFramework/visualizations/plots"; +import { + makeAreaPlotConfig, + makeLinePlotConfig, + plotDataAccumulator, +} from "../DataProviderFramework/visualizations/plots"; +import { makeContinuousTrackConfig } from "../DataProviderFramework/visualizations/tracks"; +import { makeLogViewerWellPicks } from "../DataProviderFramework/visualizations/wellpicks"; type FactoryAccResult = PlotFactoryAccResult; -const VISUALIZATION_FACTORY = new VisualizationFactory(); +const VISUALIZATION_FACTORY = new VisualizationAssembler(); -VISUALIZATION_FACTORY.registerLayerFunctions(LinearPlotProvider.name, LinearPlotProvider, { - makeVisualizationFunction: makeLinePlotConfig, +VISUALIZATION_FACTORY.registerDataProviderTransformers(LinearPlotProvider.name, LinearPlotProvider, { + transformToVisualization: makeLinePlotConfig, reduceAccumulatedDataFunction: plotDataAccumulator, }); -VISUALIZATION_FACTORY.registerLayerFunctions(AreaPlotProvider.name, AreaPlotProvider, { +VISUALIZATION_FACTORY.registerDataProviderTransformers(AreaPlotProvider.name, AreaPlotProvider, { makeVisualizationFunction: makeAreaPlotConfig, reduceAccumulatedDataFunction: plotDataAccumulator, }); -VISUALIZATION_FACTORY.registerViewFunction(GroupType.WELL_LOG_TRACK, ContinuousLogTrack, makeContinuousTrackConfig); +VISUALIZATION_FACTORY.registerGroupCustomPropsCollector( + GroupType.WELL_LOG_TRACK, + ContinuousLogTrack, + makeContinuousTrackConfig, +); -VISUALIZATION_FACTORY.registerLayerFunctions(WellborePicksProvider.name, WellborePicksProvider, { +VISUALIZATION_FACTORY.registerDataProviderTransformers(WellborePicksProvider.name, WellborePicksProvider, { makeVisualizationFunction: makeLogViewerWellPicks, }); type MakeFuncReturn = ReturnType<(typeof VISUALIZATION_FACTORY)["make"]>; -export function useLogViewerVisualizationFactoryProduct(dataLayerManager: DataLayerManager) { +export function useLogViewerVisualizationFactoryProduct(dataProviderManager: DataProviderManager) { const [previousRevision, setPreviousRevision] = React.useState(); const [previousProduct, setPreviousProduct] = React.useState(); const latestRevision = React.useSyncExternalStore( - dataLayerManager.getPublishSubscribeDelegate().makeSubscriberFunction(LayerManagerTopic.LAYER_DATA_REVISION), - dataLayerManager.makeSnapshotGetter(LayerManagerTopic.LAYER_DATA_REVISION), + dataProviderManager + .getPublishSubscribeDelegate() + .makeSubscriberFunction(DataProviderManagerTopic.DATA_REVISION), + dataProviderManager.makeSnapshotGetter(DataProviderManagerTopic.DATA_REVISION), ); if (previousRevision !== latestRevision) { setPreviousRevision(latestRevision); - setPreviousProduct(VISUALIZATION_FACTORY.make(dataLayerManager)); + setPreviousProduct(VISUALIZATION_FACTORY.make(dataProviderManager)); } return previousProduct; diff --git a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx index 45524bf11..81f268d5d 100644 --- a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx @@ -10,11 +10,12 @@ import { createContinuousColorScaleForMap } from "@modules/3DViewer/view/utils/c import { DUPLICATE_NAMES_ACC_KEY, DATA_ACC_KEY as PLOT_DATA_ACC_KEY, -} from "@modules/WellLogViewer/LayerFramework/visualizations/plots"; -import { isTrackGroup } from "@modules/WellLogViewer/LayerFramework/visualizations/tracks"; +} from "@modules/WellLogViewer/DataProviderFramework/visualizations/plots"; +import { isTrackGroup } from "@modules/WellLogViewer/DataProviderFramework/visualizations/tracks"; import type { TemplatePlot, TemplateTrack } from "@modules/WellLogViewer/types"; import { useLogViewerVisualizationFactoryProduct } from "@modules/WellLogViewer/utils/useLogViewerVisualizationFactory"; -import type { DataLayerManager } from "@modules/_shared/LayerFramework/framework/DataLayerManager/DataLayerManager"; +import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { VisualizationItemType } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import { WellLogViewer } from "@webviz/well-log-viewer"; import type { Info } from "@webviz/well-log-viewer/dist/components/InfoTypes"; import type { WellLogController, WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; @@ -44,7 +45,7 @@ type GlobalHoverMd = GlobalTopicDefinitions["global.hoverMd"]; export type SubsurfaceLogViewerWrapperProps = { // Data wellboreHeader: WellboreHeader_api | null; - dataLayerManager: DataLayerManager; + providerManager: DataProviderManager; trajectoryData: WellboreTrajectory_api; intersectionReferenceSystem: IntersectionReferenceSystem; @@ -154,10 +155,14 @@ function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { const intersectionReferenceSystem = props.intersectionReferenceSystem; const padDataWithEmptyRows = props.padDataWithEmptyRows; - const factoryProduct = useLogViewerVisualizationFactoryProduct(props.dataLayerManager); + const factoryProduct = useLogViewerVisualizationFactoryProduct(props.providerManager); const wellpicks = React.useMemo(() => { - return factoryProduct?.layers.find((layer) => layer.layer && "wellpick" in layer.layer)?.layer as WellPickProps; + return factoryProduct?.children.find( + (child) => + child.itemType === VisualizationItemType.DATA_PROVIDER_VISUALIZATION && + "wellpick" in child.visualization, + )?.layer as WellPickProps; }, [factoryProduct]); // Curve data transform is a bit heavy, so we use Memo-hooks to reduce re-render overhead diff --git a/frontend/src/modules/WellLogViewer/view/view.tsx b/frontend/src/modules/WellLogViewer/view/view.tsx index 82e1a2818..cd45b6979 100644 --- a/frontend/src/modules/WellLogViewer/view/view.tsx +++ b/frontend/src/modules/WellLogViewer/view/view.tsx @@ -15,7 +15,7 @@ import type { InterfaceTypes } from "../interfaces"; export function View(props: ModuleViewProps) { // const statusWriter = useViewStatusWriter(props.viewContext); - const layerManager = props.viewContext.useSettingsToViewInterfaceValue("layerManager"); + const providerManager = props.viewContext.useSettingsToViewInterfaceValue("providerManager"); // Passed setting atoms const selectedWellboreHeader = props.viewContext.useSettingsToViewInterfaceValue("wellboreHeader"); @@ -40,7 +40,7 @@ export function View(props: ModuleViewProps) { [props.viewContext, selectedWellboreHeader?.uniqueWellboreIdentifier], ); - if (!layerManager || !wellboreTrajectoryDataQuery.data || !intersectionReferenceSystem) { + if (!providerManager || !wellboreTrajectoryDataQuery.data || !intersectionReferenceSystem) { return (
@@ -51,7 +51,7 @@ export function View(props: ModuleViewProps) { return ( { + defaultValue: ValueType = { + areBoundariesUserDefined: false, + colorScale: new ColorScale({ + colorPalette: defaultContinuousSequentialColorPalettes[0], + gradientType: ColorScaleGradientType.Sequential, + type: ColorScaleType.Continuous, + steps: 10, + }), + }; + + getLabel(): string { + return "Coloring"; + } + + getIsStatic(): boolean { + return true; + } + + isValueValid(): boolean { + return true; + } + + serializeValue(value: ValueType): string { + const serializedValue = { + areBoundariesUserDefined: value?.areBoundariesUserDefined ?? false, + colorScale: value?.colorScale.serialize() ?? this.defaultValue?.colorScale.serialize(), + }; + + return JSON.stringify(serializedValue); + } + + deserializeValue?(serializedValue: string): ValueType { + const parsedValue = JSON.parse(serializedValue); + + return { + areBoundariesUserDefined: parsedValue.areBoundariesUserDefined, + colorScale: ColorScale.fromSerialized(parsedValue.colorScale), + }; + } + + makeComponent(): (props: SettingComponentProps) => React.ReactNode { + return function ColorScaleSelectorDialog(props: SettingComponentProps) { + function handleChange(value: ColorScaleSpecification) { + props.onValueChange(value); + } + + return ( + + ); + }; + } + + overriddenValueRepresentation({ value }: OverriddenValueRepresentationArgs): React.ReactNode { + if (value === null) { + return "-"; + } + return ( + + ); + } +} diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx new file mode 100644 index 000000000..c7788ec90 --- /dev/null +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx @@ -0,0 +1,70 @@ +import type React from "react"; + +import { ColorSelect } from "@lib/components/ColorSelect"; + +import type { + CustomSettingImplementation, + OverriddenValueRepresentationArgs, + SettingComponentProps, +} from "../../interfacesAndTypes/customSettingImplementation"; +import type { SettingCategory } from "../settingsDefinitions"; + +type ValueType = string | null; + +export class SingleColorSetting implements CustomSettingImplementation { + defaultValue: ValueType = null; + + getIsStatic(): boolean { + return true; + } + + isValueValid(value: ValueType): boolean { + if (!value) return false; + + return /^#[0-9A-Fa-f]{6}$/.test(value); // Validates hex color + } + + serializeValue(value: ValueType): string { + return value ?? ""; + } + + deserializeValue?(serializedValue: string): ValueType { + if (serializedValue === "") return null; + return serializedValue; + } + + fixupValue(value: ValueType): NonNullable { + if (!value) return "#ffffff"; + return value; + } + + makeComponent(): (props: SettingComponentProps) => React.ReactNode { + return function SingleColorSettingComponent(props: SettingComponentProps) { + function handleColorChange(color: string) { + props.onValueChange(color); + } + + return ( +
+ +
+ ); + }; + } + + overriddenValueRepresentation({ value }: OverriddenValueRepresentationArgs): React.ReactNode { + if (!value) { + return "-"; + } + return ( +
+ ); + } +} diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts b/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts index dd9de5de3..bdad0ea1b 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts @@ -15,6 +15,7 @@ import { NumberSetting } from "./implementations/NumberSetting"; import { ObjectSelectionSetting } from "./implementations/ObjectSelectionSetting"; import { SeismicSliceDirection, SeismicSliceSetting } from "./implementations/SeismicSliceSetting"; import { SensitivitySetting } from "./implementations/SensitivitySetting"; +import { SingleColorSetting } from "./implementations/SingleColorSetting"; import { StaticDropdownStringSetting } from "./implementations/StaticDropdownStringSetting"; import { StatisticFunctionSetting } from "./implementations/StatisticFunctionSetting"; import { Setting } from "./settingsDefinitions"; @@ -46,6 +47,7 @@ SettingRegistry.registerSetting(Setting.LOG_CURVE, "Log curve", LogCurveSetting) SettingRegistry.registerSetting(Setting.ATTRIBUTE, "Attribute", DropdownStringSetting); SettingRegistry.registerSetting(Setting.ENSEMBLE, "Ensemble", EnsembleSetting); SettingRegistry.registerSetting(Setting.COLOR_SCALE, "Color Scale", ColorScaleSetting); +SettingRegistry.registerSetting(Setting.COLOR, "Color", SingleColorSetting); SettingRegistry.registerSetting(Setting.GRID_LAYER_K, "Grid Layer K", GridLayerSetting, { customConstructorParameters: [GridLayerDirection.K], diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts b/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts index 9f2a0809b..ff6fba906 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts @@ -41,6 +41,7 @@ export enum Setting { ENSEMBLE = "ensemble", COLOR_SCALE = "colorScale", COLOR_SET = "colorSet", + COLOR = "color", GRID_LAYER_I_RANGE = "gridLayerIRange", GRID_LAYER_J_RANGE = "gridLayerJRange", GRID_LAYER_K = "gridLayerK", @@ -78,6 +79,7 @@ export const settingCategories = { [Setting.ENSEMBLE]: SettingCategory.SINGLE_SELECT, [Setting.COLOR_SCALE]: SettingCategory.STATIC, [Setting.COLOR_SET]: SettingCategory.STATIC, + [Setting.COLOR]: SettingCategory.STATIC, [Setting.GRID_LAYER_I_RANGE]: SettingCategory.RANGE, [Setting.GRID_LAYER_J_RANGE]: SettingCategory.RANGE, [Setting.GRID_LAYER_K]: SettingCategory.NUMBER, @@ -118,6 +120,7 @@ export type SettingTypes = { [Setting.ENSEMBLE]: RegularEnsembleIdent | null; [Setting.COLOR_SCALE]: ColorScaleSpecification | null; [Setting.COLOR_SET]: ColorSet | null; + [Setting.COLOR]: string | null; [Setting.GRID_LAYER_I_RANGE]: [number, number] | null; [Setting.GRID_LAYER_J_RANGE]: [number, number] | null; [Setting.GRID_LAYER_K]: number | null; diff --git a/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts b/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts index 9a8795c3f..6215ce7e3 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts @@ -3,7 +3,7 @@ import type { Layer as EsvLayer } from "@equinor/esv-intersection"; import type { StatusMessage } from "@framework/ModuleInstanceStatusController"; import type { GlobalTopicDefinitions } from "@framework/WorkbenchServices"; import * as bbox from "@lib/utils/bbox"; -import type { TemplateTrack } from "@modules/WellLogViewer/types"; +import type { TemplatePlot } from "@modules/WellLogViewer/types"; import type { ColorScaleWithId } from "@modules/_shared/components/ColorLegendsContainer/colorLegendsContainer"; import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; @@ -41,7 +41,7 @@ export enum VisualizationTarget { export type DataProviderVisualizationTargetTypes = { [VisualizationTarget.DECK_GL]: DeckGlLayer; [VisualizationTarget.ESV]: EsvLayer; - [VisualizationTarget.WSC_WELL_LOG]: TemplateTrack | WellPickProps; + [VisualizationTarget.WSC_WELL_LOG]: TemplatePlot | WellPickProps; }; export type DataProviderVisualization = { From fbc72f422ed047e2c9feaa691f570b345c5c09d7 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Thu, 10 Apr 2025 13:02:47 +0200 Subject: [PATCH 10/25] Fixed product to well log viewer prop conversion --- .../visualizations/plots.ts | 69 +++++++------ .../visualizations/tracks.ts | 49 ++++++--- .../visualizations/wellpicks.ts | 31 ++++-- .../ProviderManagerComponentWrapper.tsx | 29 +++--- .../WellLogViewer/utils/factoryProduct.ts | 99 +++++++++++++++++++ .../WellLogViewer/utils/logViewerTemplate.ts | 23 +---- .../derivedAtoms.ts => utils/trajectory.ts} | 27 ++--- .../utils/useLogViewerVisualizationFactory.ts | 33 +++++-- .../components/SubsurfaceLogViewerWrapper.tsx | 73 ++++---------- .../src/modules/WellLogViewer/view/view.tsx | 5 +- 10 files changed, 262 insertions(+), 176 deletions(-) create mode 100644 frontend/src/modules/WellLogViewer/utils/factoryProduct.ts rename frontend/src/modules/WellLogViewer/{view/atoms/derivedAtoms.ts => utils/trajectory.ts} (55%) diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts index f1301c8a2..434b68045 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts @@ -2,10 +2,13 @@ import type { WellboreLogCurveData_api } from "@api"; import type { TemplatePlot } from "@modules/WellLogViewer/types"; import type { Settings } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -import type { - ReduceAccumulatedDataFunction, - VisualizationTarget, - VisualizationTransformer, +import { + type DataProviderVisualization, + type ReduceAccumulatedDataFunction, + type TransformerArgs, + type VisualizationGroup, + VisualizationItemType, + type VisualizationTarget, } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import _ from "lodash"; @@ -21,45 +24,42 @@ export type FactoryAccResult = { [DUPLICATE_NAMES_ACC_KEY]: Set; }; -type PlotVisualizationFunc = VisualizationTransformer< - PlotSettings, - WellboreLogCurveData_api, - VisualizationTarget.WSC_WELL_LOG ->; +type PlotVisualizationArgs = TransformerArgs; -export const makeAreaPlotConfig: PlotVisualizationFunc = function makeAreaPlotConfig( - args, -): TemplatePlot { - const data = args.getData(); - const plotVariant = args.getSetting(Setting.PLOT_VARIANT); +function getCommonConfig(data: WellboreLogCurveData_api): TemplatePlot { + return { + name: data.name, + logName: data.logName, + // TODO: main curve color + }; +} - const curveName = data?.name ?? ""; - const logName = data?.logName ?? ""; +export function makeAreaPlotConfig(args: PlotVisualizationArgs): TemplatePlot | null { + if (!args.getData()) return null; + + const data = args.getData()!; + const plotVariant = args.getSetting(Setting.PLOT_VARIANT); + const commonConfig = getCommonConfig(data); return { - logName: logName, - name: curveName, + ...commonConfig, type: plotVariant ?? undefined, - - // TODO: Color // TODO: Fill/Func }; -}; +} -export const makeLinePlotConfig: PlotVisualizationFunc = (args) => { - const data = args.getData(); +export function makeLinePlotConfig(args: PlotVisualizationArgs): TemplatePlot | null { + if (!args.getData()) return null; + const data = args.getData()!; const plotVariant = args.getSetting(Setting.PLOT_VARIANT) ?? undefined; - const curveName = data?.name ?? ""; - const logName = data?.logName ?? ""; + const commonConfig = getCommonConfig(data); return { - logName: logName, - name: curveName, + ...commonConfig, type: plotVariant, - // TODO: Color }; -}; +} export const plotDataAccumulator: ReduceAccumulatedDataFunction<[], WellboreLogCurveData_api, FactoryAccResult> = function plotDataAccumulator(acc, args) { @@ -82,3 +82,14 @@ export const plotDataAccumulator: ReduceAccumulatedDataFunction<[], WellboreLogC [DUPLICATE_NAMES_ACC_KEY]: duplicatedNames, }; }; + +export type PlotVisualization = DataProviderVisualization; + +export function isPlotVisualization( + item: VisualizationGroup | DataProviderVisualization, +): item is PlotVisualization { + if (item.itemType !== VisualizationItemType.DATA_PROVIDER_VISUALIZATION) return false; + + // TODO: Check item.providerType once that's implemented + return "logName" in item.visualization; +} diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts index 350189062..db8e65ab9 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts @@ -1,20 +1,21 @@ import type { TemplateTrack } from "@modules/WellLogViewer/types"; import type { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -import type { - GroupCustomPropsCollector, - VisualizationGroup, - VisualizationTarget, +import { + type DataProviderVisualization, // type GroupPropCollectorArgs, + type VisualizationGroup, + VisualizationItemType, + type VisualizationTarget, // VisualizationGroup, + // VisualizationTarget, } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import type { TemplatePlotScale } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; -import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; +// import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; -export const makeContinuousTrackConfig: GroupCustomPropsCollector< - ContinuousTrackSettings, - GroupType.WELL_LOG_TRACK, - Record -> = function makeContinuousTrackConfig(args) { +type TrackCustomPropsCollector = any; +// type TrackCustomPropsCollector = GroupPropCollectorArgs; + +export function makeContinuousTrackConfig(args: TrackCustomPropsCollector): TemplateTrack { const trackWidth = args.getSetting(Setting.TRACK_WIDTH) ?? undefined; const trackScale = args.getSetting(Setting.SCALE) ?? undefined; @@ -26,15 +27,31 @@ export const makeContinuousTrackConfig: GroupCustomPropsCollector< // Need to fill this later, as they're not defined yet. plots: [], }; -}; +} -type BasicViewVisualization = VisualizationGroup; +// type BasicViewVisualization = ; // type ViewVisualization = BasicViewVisualization; -type TrackVisualization = BasicViewVisualization & TemplateTrack; +// type TrackVisualization = BasicViewVisualization & TemplateTrack; + +// export type TrackVisualization = VisualizationGroup< DataProviderVisualization; +// item: VisualizationGroup | DataProviderVisualization, + +export type TrackVisualizationGroup = VisualizationGroup< + VisualizationTarget.WSC_WELL_LOG, + { + [GroupType.WELL_LOG_TRACK]: TemplateTrack; + [GroupType.VIEW]: any; + }, + never, + GroupType.WELL_LOG_TRACK +>; -export function isTrackGroup(groupVisualization: unknown): groupVisualization is TrackVisualization { - if (groupVisualization == null || typeof groupVisualization !== "object") return false; +export function isTrackGroup( + item: VisualizationGroup | DataProviderVisualization, +): item is TrackVisualizationGroup { + if (item.itemType !== VisualizationItemType.GROUP) return false; - return "plots" in groupVisualization; + // TODO: Check item.providerType once that's implemented + return "plots" in item.customProps; } diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks.ts index ab0e9a79b..caf5c7905 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks.ts @@ -1,21 +1,34 @@ import type { WellborePick_api } from "@api"; +// import { BBox } from "@lib/utils/bbox"; import { createLogViewerWellPicks } from "@modules/WellLogViewer/utils/queryDataTransform"; -import type { - VisualizationTarget, - VisualizationTransformer, +import { + type DataProviderVisualization, + type TransformerArgs, + type VisualizationGroup, + VisualizationItemType, + type VisualizationTarget, } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; import type { WellPickSettingTypes } from "../dataProviders/wellpicks/WellPicksProvider"; -export const makeLogViewerWellPicks: VisualizationTransformer< - WellPickSettingTypes, - WellborePick_api[], - VisualizationTarget.WSC_WELL_LOG -> = (args): WellPickProps | null => { +type WellpickTransformerArgs = TransformerArgs; + +export function makeLogViewerWellPicks(args: WellpickTransformerArgs): WellPickProps | null { const data = args.getData(); if (!data) return null; return createLogViewerWellPicks(data); -}; +} + +export type WellPickVisualization = DataProviderVisualization; + +export function isWellPickVisualization( + item: VisualizationGroup | DataProviderVisualization, +): item is WellPickVisualization { + if (item.itemType !== VisualizationItemType.DATA_PROVIDER_VISUALIZATION) return false; + + // TODO: Check item.providerType once that's implemented + return "wellpick" in item.visualization; +} diff --git a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx index f91ceacc7..a2e345d4d 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx @@ -17,6 +17,7 @@ import { } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; import { DataProviderManagerComponent } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManagerComponent"; import { Group } from "@modules/_shared/DataProviderFramework/framework/Group/Group"; +import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; // import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; import type { ItemGroup } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/entities"; @@ -25,9 +26,9 @@ import { useQueryClient } from "@tanstack/react-query"; import { useAtom } from "jotai"; +import "../../DataProviderFramework/registerFrameworkExtensions"; import { providerManagerAtom } from "../atoms/baseAtoms"; import { serializedManagerStateAtom } from "../atoms/persistedAtoms"; -import "../../DataProviderFramework/registerFrameworkExtensions"; enum RootActionIdents { CONTINUOUS_TRACK = "cont_track", @@ -170,19 +171,19 @@ export function ProviderManagerComponentWrapper(props: ProviderManagerComponentW DataProviderRegistry.makeDataProvider(WellborePicksProvider.name, providerManager), ); - // case RootActionIdents.CONTINUOUS_TRACK: - // return groupDelegate.appendChild( - // GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK, providerManager), - // ); - - // case PlotActionIdents.LINE: - // return groupDelegate.appendChild( - // DataProviderRegistry.makeDataProvider(LinearPlotProvider.name, providerManager), - // ); - // case PlotActionIdents.AREA: - // return groupDelegate.appendChild( - // DataProviderRegistry.makeDataProvider(AreaPlotProvider.name, providerManager), - // ); + case RootActionIdents.CONTINUOUS_TRACK: + return groupDelegate.appendChild( + GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK, providerManager), + ); + + case PlotActionIdents.LINE: + return groupDelegate.appendChild( + DataProviderRegistry.makeDataProvider(LinearPlotProvider.name, providerManager), + ); + case PlotActionIdents.AREA: + return groupDelegate.appendChild( + DataProviderRegistry.makeDataProvider(AreaPlotProvider.name, providerManager), + ); default: break; diff --git a/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts b/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts new file mode 100644 index 000000000..47564f3d1 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts @@ -0,0 +1,99 @@ +import type { WellboreTrajectory_api } from "@api"; +import type { WellLogSet } from "@webviz/well-log-viewer/dist/components/WellLogTypes"; +import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; + +import _ from "lodash"; + +import { MAIN_AXIS_CURVE, createWellLogSets } from "./queryDataTransform"; +import { getUniqueCurveNameForPlotConfig } from "./strings"; +import { trajectoryToIntersectionReference } from "./trajectory"; +import type { WellLogFactoryProduct } from "./useLogViewerVisualizationFactory"; + +import { + DATA_ACC_KEY, + DUPLICATE_NAMES_ACC_KEY, + isPlotVisualization, +} from "../DataProviderFramework/visualizations/plots"; +import type { TrackVisualizationGroup } from "../DataProviderFramework/visualizations/tracks"; +import { isTrackGroup } from "../DataProviderFramework/visualizations/tracks"; +import { isWellPickVisualization } from "../DataProviderFramework/visualizations/wellpicks"; +import type { Template, TemplatePlot, TemplateTrack } from "../types"; + +export type LogViewerProps = { + padData: boolean; + horizontal: boolean; +}; + +function getProvidedPlots(trackGroup: TrackVisualizationGroup, duplicatedCurveNames: Set) { + const plots: TemplatePlot[] = []; + + for (const child of trackGroup.children) { + if (!isPlotVisualization(child)) continue; + + const template = child.visualization; + + plots.push({ + ...template, + // ! Map each plot to ensure a name that points to the correct curve + name: getUniqueCurveNameForPlotConfig(template, duplicatedCurveNames), + }); + } + + return plots; +} + +export function createWellLogTemplateFromProduct(factoryProduct: WellLogFactoryProduct | null): Template { + const tracks: TemplateTrack[] = []; + const accData = factoryProduct?.accumulatedData; + + const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); + + for (const child of factoryProduct?.children ?? []) { + if (!isTrackGroup(child)) continue; + + const templatePlots = getProvidedPlots(child, duplicatedCurveNames!); + const templateProps = child.customProps; + + tracks.push({ + ...templateProps, + plots: templatePlots, + }); + } + + return { + // AFAIK, this name is not show anywhere + name: "Well log viewer", + scale: { primary: MAIN_AXIS_CURVE.name, allowSecondary: true }, + tracks, + }; +} + +export function createWellLogJsonFromProduct( + factoryProduct: WellLogFactoryProduct, + wellboreTrajectory: WellboreTrajectory_api, + padDataWithEmptyRows?: boolean, +): WellLogSet[] { + const accData = factoryProduct.accumulatedData; + const curveData = _.get(accData, DATA_ACC_KEY, []); + const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); + + const referenceSystem = trajectoryToIntersectionReference(wellboreTrajectory); + + return createWellLogSets( + curveData, + wellboreTrajectory, + referenceSystem, + duplicatedCurveNames, + padDataWithEmptyRows, + ); +} + +export function createWellPickPropFromProduct(factoryProduct: WellLogFactoryProduct): WellPickProps | undefined { + if (!factoryProduct) return undefined; + + // ! We only take the + const wellpickVisualization = factoryProduct.children.find((child) => isWellPickVisualization(child)); + + // Can be used as is, no need for further transformations + return wellpickVisualization?.visualization; +} diff --git a/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts b/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts index f6e4cf5d5..c038cb766 100644 --- a/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts +++ b/frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts @@ -7,32 +7,11 @@ import _ from "lodash"; import { v4 } from "uuid"; import { CURVE_COLOR_PALETTE, DIFF_CURVE_COLORS } from "./logViewerColors"; -import { MAIN_AXIS_CURVE } from "./queryDataTransform"; -import { getUniqueCurveNameForPlotConfig } from "./strings"; -import type { Template, TemplatePlot, TemplatePlotConfig, TemplateTrack } from "../types"; - -// import type { TemplatePlotConfig, TemplateTrackConfig } from "../types"; +import type { TemplatePlotConfig } from "../types"; export const DEFAULT_MAX_VISIBLE_TRACKS = 5; -export function createLogTemplate(templateTracks: TemplateTrack[], nonUniqueNames?: Set): Template { - return { - // AFAIK, this name is not show anywhere - name: "Well log viewer", - scale: { primary: MAIN_AXIS_CURVE.name, allowSecondary: true }, - - // ! Map each plot to ensure a name that points to the correct curve - tracks: templateTracks.map((track) => ({ - ...track, - plots: track.plots.map((plot) => ({ - ...plot, - name: getUniqueCurveNameForPlotConfig(plot, nonUniqueNames), - })) as TemplatePlot[], - })), - }; -} - export function makeTrackPlot(plot: Partial): TemplatePlotConfig { if (!plot.type) throw new Error(`Plot type is required`); diff --git a/frontend/src/modules/WellLogViewer/view/atoms/derivedAtoms.ts b/frontend/src/modules/WellLogViewer/utils/trajectory.ts similarity index 55% rename from frontend/src/modules/WellLogViewer/view/atoms/derivedAtoms.ts rename to frontend/src/modules/WellLogViewer/utils/trajectory.ts index b0833ce1f..ccc87e5b3 100644 --- a/frontend/src/modules/WellLogViewer/view/atoms/derivedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/utils/trajectory.ts @@ -1,29 +1,22 @@ import type { WellboreTrajectory_api } from "@api"; import { IntersectionReferenceSystem } from "@equinor/esv-intersection"; -import { atom } from "jotai"; import _ from "lodash"; -import { wellboreTrajectoryQueryAtom } from "./queryAtoms"; - -export const intersectionReferenceSystemAtom = atom((get) => { - const wellboreTrajectoryQuery = get(wellboreTrajectoryQueryAtom); - - if (wellboreTrajectoryQuery.isPending || wellboreTrajectoryQuery.isError) return null; - if (!wellboreTrajectoryQuery.data) return null; +function trajectoryToReferenceSystemPath(trajectory: WellboreTrajectory_api): number[][] { + return _.zipWith(trajectory.eastingArr, trajectory.northingArr, trajectory.tvdMslArr, (easting, northing, tvd) => { + return [easting, northing, tvd]; + }); +} - const systemPath = trajectoryToReferenceSystemPath(wellboreTrajectoryQuery.data); - const offset = wellboreTrajectoryQuery.data.mdArr[0]; +export function trajectoryToIntersectionReference( + wellboreTrajectory: WellboreTrajectory_api, +): IntersectionReferenceSystem { + const systemPath = trajectoryToReferenceSystemPath(wellboreTrajectory); + const offset = wellboreTrajectory.mdArr[0]; const referenceSystem = new IntersectionReferenceSystem(systemPath); - referenceSystem.offset = offset; return referenceSystem; -}); - -function trajectoryToReferenceSystemPath(trajectory: WellboreTrajectory_api): number[][] { - return _.zipWith(trajectory.eastingArr, trajectory.northingArr, trajectory.tvdMslArr, (easting, northing, tvd) => { - return [easting, northing, tvd]; - }); } diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts index 2d05b184d..5d2539abe 100644 --- a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts @@ -3,7 +3,10 @@ import React from "react"; import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; import { DataProviderManagerTopic } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; -import type { VisualizationTarget } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import type { + CustomGroupPropsMap, + VisualizationTarget, +} from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import { VisualizationAssembler } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import { AreaPlotProvider } from "../DataProviderFramework/dataProviders/plots/AreaPlotProvider"; @@ -20,15 +23,22 @@ import { makeContinuousTrackConfig } from "../DataProviderFramework/visualizatio import { makeLogViewerWellPicks } from "../DataProviderFramework/visualizations/wellpicks"; type FactoryAccResult = PlotFactoryAccResult; -const VISUALIZATION_FACTORY = new VisualizationAssembler(); +// type FactoryMakeResult + +const VISUALIZATION_FACTORY = new VisualizationAssembler< + VisualizationTarget.WSC_WELL_LOG, + CustomGroupPropsMap, + never, + FactoryAccResult +>(); VISUALIZATION_FACTORY.registerDataProviderTransformers(LinearPlotProvider.name, LinearPlotProvider, { transformToVisualization: makeLinePlotConfig, - reduceAccumulatedDataFunction: plotDataAccumulator, + reduceAccumulatedData: plotDataAccumulator, }); VISUALIZATION_FACTORY.registerDataProviderTransformers(AreaPlotProvider.name, AreaPlotProvider, { - makeVisualizationFunction: makeAreaPlotConfig, - reduceAccumulatedDataFunction: plotDataAccumulator, + transformToVisualization: makeAreaPlotConfig, + reduceAccumulatedData: plotDataAccumulator, }); VISUALIZATION_FACTORY.registerGroupCustomPropsCollector( @@ -38,14 +48,17 @@ VISUALIZATION_FACTORY.registerGroupCustomPropsCollector( ); VISUALIZATION_FACTORY.registerDataProviderTransformers(WellborePicksProvider.name, WellborePicksProvider, { - makeVisualizationFunction: makeLogViewerWellPicks, + transformToVisualization: makeLogViewerWellPicks, + // transformToBoundingBox: computeWellPickBBox, }); -type MakeFuncReturn = ReturnType<(typeof VISUALIZATION_FACTORY)["make"]>; +export type WellLogFactoryProduct = ReturnType<(typeof VISUALIZATION_FACTORY)["make"]>; -export function useLogViewerVisualizationFactoryProduct(dataProviderManager: DataProviderManager) { - const [previousRevision, setPreviousRevision] = React.useState(); - const [previousProduct, setPreviousProduct] = React.useState(); +export function useLogViewerVisualizationFactoryProduct( + dataProviderManager: DataProviderManager, +): WellLogFactoryProduct | null { + const [previousRevision, setPreviousRevision] = React.useState(null); + const [previousProduct, setPreviousProduct] = React.useState(null); const latestRevision = React.useSyncExternalStore( dataProviderManager diff --git a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx index 81f268d5d..0abee0c4c 100644 --- a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx @@ -1,32 +1,27 @@ import React from "react"; import type { WellboreHeader_api, WellboreTrajectory_api } from "@api"; -import type { IntersectionReferenceSystem } from "@equinor/esv-intersection"; import type { ModuleViewProps } from "@framework/Module"; import { SyncSettingKey } from "@framework/SyncSettings"; import type { GlobalTopicDefinitions, WorkbenchServices } from "@framework/WorkbenchServices"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; import { createContinuousColorScaleForMap } from "@modules/3DViewer/view/utils/colorTables"; import { - DUPLICATE_NAMES_ACC_KEY, - DATA_ACC_KEY as PLOT_DATA_ACC_KEY, -} from "@modules/WellLogViewer/DataProviderFramework/visualizations/plots"; -import { isTrackGroup } from "@modules/WellLogViewer/DataProviderFramework/visualizations/tracks"; -import type { TemplatePlot, TemplateTrack } from "@modules/WellLogViewer/types"; + createWellLogJsonFromProduct, + createWellLogTemplateFromProduct, + createWellPickPropFromProduct, +} from "@modules/WellLogViewer/utils/factoryProduct"; import { useLogViewerVisualizationFactoryProduct } from "@modules/WellLogViewer/utils/useLogViewerVisualizationFactory"; import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; -import { VisualizationItemType } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import { WellLogViewer } from "@webviz/well-log-viewer"; import type { Info } from "@webviz/well-log-viewer/dist/components/InfoTypes"; -import type { WellLogController, WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; +import type { WellLogController } from "@webviz/well-log-viewer/dist/components/WellLogView"; import _ from "lodash"; import { ReadoutWrapper } from "./ReadoutWrapper"; import type { InterfaceTypes } from "../../interfaces"; -import { createLogTemplate } from "../../utils/logViewerTemplate"; -import { createWellLogSets } from "../../utils/queryDataTransform"; const AXIS_MNEMOS = { md: ["RKB", "DEPTH", "DEPT", "MD", "TDEP", "MD_RKB"], @@ -46,9 +41,7 @@ export type SubsurfaceLogViewerWrapperProps = { // Data wellboreHeader: WellboreHeader_api | null; providerManager: DataProviderManager; - trajectoryData: WellboreTrajectory_api; - intersectionReferenceSystem: IntersectionReferenceSystem; // Viewer config horizontal: boolean; @@ -152,55 +145,25 @@ function useCreateGlobalVerticalScaleBroadcastFunc( function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { const trajectoryData = props.trajectoryData; - const intersectionReferenceSystem = props.intersectionReferenceSystem; const padDataWithEmptyRows = props.padDataWithEmptyRows; const factoryProduct = useLogViewerVisualizationFactoryProduct(props.providerManager); const wellpicks = React.useMemo(() => { - return factoryProduct?.children.find( - (child) => - child.itemType === VisualizationItemType.DATA_PROVIDER_VISUALIZATION && - "wellpick" in child.visualization, - )?.layer as WellPickProps; + if (!factoryProduct) return undefined; + return createWellPickPropFromProduct(factoryProduct); }, [factoryProduct]); - // Curve data transform is a bit heavy, so we use Memo-hooks to reduce re-render overhead - // TODO: This would arguably be something for the "root view" const template = React.useMemo(() => { - const views = factoryProduct?.views ?? []; - const accData = factoryProduct?.accumulatedData ?? {}; - const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); - - const trackTemplates = views.reduce((acc, v) => { - if (!isTrackGroup(v)) return acc; - - const fullTrackTemplate = { - ...v, - plots: v.layers.map(({ layer }) => layer as TemplatePlot), - } as TemplateTrack; - - return [...acc, fullTrackTemplate]; - }, [] as TemplateTrack[]); - - return createLogTemplate(trackTemplates, duplicatedCurveNames); - }, [factoryProduct?.accumulatedData, factoryProduct?.views]); - - const welllog = React.useMemo(() => { - const accData = factoryProduct?.accumulatedData ?? {}; - const curveData = _.get(accData, PLOT_DATA_ACC_KEY, []); - const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); - - return createWellLogSets( - curveData, - trajectoryData, - intersectionReferenceSystem, - duplicatedCurveNames, - padDataWithEmptyRows, - ); - }, [factoryProduct?.accumulatedData, trajectoryData, intersectionReferenceSystem, padDataWithEmptyRows]); - - return { template, welllog, wellpicks }; + return createWellLogTemplateFromProduct(factoryProduct); + }, [factoryProduct]); + + const wellLogSets = React.useMemo(() => { + if (!factoryProduct) return []; + return createWellLogJsonFromProduct(factoryProduct, trajectoryData, padDataWithEmptyRows); + }, [factoryProduct, trajectoryData, padDataWithEmptyRows]); + + return { template, wellLogSets, wellpicks }; } export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProps) { @@ -209,7 +172,7 @@ export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProp const [wellLogReadout, setWellLogReadout] = React.useState([]); const [showReadoutBox, setShowReadoutBox] = React.useState(false); - const { template, welllog, wellpicks } = useViewerDataTransform(props); + const { template, wellLogSets, wellpicks } = useViewerDataTransform(props); const colorScale = props.moduleProps.workbenchSettings.useContinuousColorScale({ gradientType: ColorScaleGradientType.Sequential, @@ -300,7 +263,7 @@ export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProp > ) { const padDataWithEmptyRows = props.viewContext.useSettingsToViewInterfaceValue("padDataWithEmptyRows"); const wellboreTrajectoryDataQuery = useAtomValue(wellboreTrajectoryQueryAtom); - const intersectionReferenceSystem = useAtomValue(intersectionReferenceSystemAtom); React.useEffect( function setModuleName() { @@ -40,7 +38,7 @@ export function View(props: ModuleViewProps) { [props.viewContext, selectedWellboreHeader?.uniqueWellboreIdentifier], ); - if (!providerManager || !wellboreTrajectoryDataQuery.data || !intersectionReferenceSystem) { + if (!providerManager || !wellboreTrajectoryDataQuery.data) { return (
@@ -54,7 +52,6 @@ export function View(props: ModuleViewProps) { providerManager={providerManager} wellboreHeader={selectedWellboreHeader} trajectoryData={wellboreTrajectoryDataQuery.data} - intersectionReferenceSystem={intersectionReferenceSystem} horizontal={viewerHorizontal} padDataWithEmptyRows={padDataWithEmptyRows} /> From 1268a4ea89892976beaa5c217aeeff6d5352bc51 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Fri, 11 Apr 2025 12:57:24 +0200 Subject: [PATCH 11/25] Added discrete tracks --- .../dataProviders/plots/_shared.ts | 6 +-- .../groups/ContinuousLogTrack.ts | 4 +- .../groups/DiscreteLogTrack.ts | 35 +++++++++++------- .../DataProviderFramework/groups/_shared.ts | 3 ++ .../DataProviderFramework/groups/shared.ts | 1 - .../registerFrameworkExtensions.ts | 5 ++- .../visualizations/tracks.ts | 37 ++++++------------- .../ProviderManagerComponentWrapper.tsx | 35 ++++++++++++++---- .../utils/useLogViewerVisualizationFactory.ts | 2 +- .../groups/groupTypes.ts | 3 +- 10 files changed, 76 insertions(+), 55 deletions(-) create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/groups/_shared.ts delete mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts index be6aaa4f8..98a16659f 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts @@ -5,11 +5,7 @@ import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramew import type { Settings } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -export const baseSettings = [ - Setting.LOG_CURVE, - // TODO: Only needed to infer track settings. Remove once we can define view visualizations - Setting.TRACK_WIDTH, -] as const; +export const baseSettings = [Setting.LOG_CURVE] as const; export const baseLinearSettings = [...baseSettings, Setting.SCALE /*, Setting.COLOR */] as const; export const baseDiscreteSettings = [...baseSettings, Setting.SHOW_LINES, Setting.SHOW_LABELS, Setting.LABEL_DIR]; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts index 8d4adcfcf..f83f67d6a 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts @@ -2,7 +2,9 @@ import type { CustomGroupImplementationWithSettings } from "@modules/_shared/Dat import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -const continuousTrackSettings = [Setting.TRACK_WIDTH, Setting.SCALE] as const; +import { baseSettings } from "./_shared"; + +const continuousTrackSettings = [...baseSettings, Setting.SCALE] as const; export type ContinuousTrackSettings = typeof continuousTrackSettings; type TrackSettingTypes = MakeSettingTypesMap; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts index d59ae70d7..516c4f05e 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts @@ -1,16 +1,25 @@ -// import type { CustomGroupImplementationWithSettings } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation"; -// import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import type { CustomGroupImplementationWithSettings } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation"; +import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -// const discreteLogSettings = [Setting.STRAT_COLUMN, Setting.SMDA_INTERPRETER]; +import { baseSettings } from "./_shared"; -// class DiscreteLogTrack implements CustomGroupImplementationWithSettings { -// constructor(parameters) {} -// settings; +const discreteTrackSettings = [...baseSettings] as const; +export type DiscreteTrackSettings = typeof discreteTrackSettings; +type TrackSettingTypes = MakeSettingTypesMap; -// getDefaultSettingsValues(): {} { -// throw new Error("Method not implemented."); -// } -// getDefaultName(): string { -// return "Track"; -// } -// } +export class DiscreteLogTrack + implements CustomGroupImplementationWithSettings +{ + settings = discreteTrackSettings; + + getDefaultSettingsValues(): TrackSettingTypes { + return { + [Setting.TRACK_WIDTH]: 3, + }; + } + + getDefaultName(): string { + return "Track"; + } +} diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/_shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/_shared.ts new file mode 100644 index 000000000..4eccec22a --- /dev/null +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/_shared.ts @@ -0,0 +1,3 @@ +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; + +export const baseSettings = [Setting.TRACK_WIDTH] as const; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts deleted file mode 100644 index bdfc741b4..000000000 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/shared.ts +++ /dev/null @@ -1 +0,0 @@ -// import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions" diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts index 6c25f1b59..ef95592aa 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts @@ -7,8 +7,11 @@ import { DifferentialPlotProvider } from "./dataProviders/plots/DiffPlotProvider import { LinearPlotProvider } from "./dataProviders/plots/LinearPlotProvider"; import { WellborePicksProvider } from "./dataProviders/wellpicks/WellPicksProvider"; import { ContinuousLogTrack } from "./groups/ContinuousLogTrack"; +import { DiscreteLogTrack } from "./groups/DiscreteLogTrack"; -GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK, ContinuousLogTrack); +// ? It confuses me why there's a difference in registration of groups and providers? Why does one check an external enum, while the other does not? +GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK_CONT, ContinuousLogTrack); +GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK_DISC, DiscreteLogTrack); DataProviderRegistry.registerDataProvider(LinearPlotProvider.name, LinearPlotProvider); DataProviderRegistry.registerDataProvider(AreaPlotProvider.name, AreaPlotProvider); diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts index db8e65ab9..9d9f21597 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts @@ -1,19 +1,18 @@ import type { TemplateTrack } from "@modules/WellLogViewer/types"; -import type { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; +import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -import { - type DataProviderVisualization, // type GroupPropCollectorArgs, - type VisualizationGroup, - VisualizationItemType, - type VisualizationTarget, // VisualizationGroup, - // VisualizationTarget, +import type { + DataProviderVisualization, + GroupPropsCollectorArgs, + VisualizationGroup, + VisualizationTarget, } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import { VisualizationItemType } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import type { TemplatePlotScale } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; -// import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; +import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; -type TrackCustomPropsCollector = any; -// type TrackCustomPropsCollector = GroupPropCollectorArgs; +type TrackCustomPropsCollector = GroupPropsCollectorArgs; export function makeContinuousTrackConfig(args: TrackCustomPropsCollector): TemplateTrack { const trackWidth = args.getSetting(Setting.TRACK_WIDTH) ?? undefined; @@ -29,22 +28,11 @@ export function makeContinuousTrackConfig(args: TrackCustomPropsCollector): Temp }; } -// type BasicViewVisualization = ; - -// type ViewVisualization = BasicViewVisualization; -// type TrackVisualization = BasicViewVisualization & TemplateTrack; - -// export type TrackVisualization = VisualizationGroup< DataProviderVisualization; -// item: VisualizationGroup | DataProviderVisualization, - export type TrackVisualizationGroup = VisualizationGroup< VisualizationTarget.WSC_WELL_LOG, - { - [GroupType.WELL_LOG_TRACK]: TemplateTrack; - [GroupType.VIEW]: any; - }, + { [GroupType.WELL_LOG_TRACK_CONT]: TemplateTrack }, never, - GroupType.WELL_LOG_TRACK + GroupType.WELL_LOG_TRACK_CONT >; export function isTrackGroup( @@ -52,6 +40,5 @@ export function isTrackGroup( ): item is TrackVisualizationGroup { if (item.itemType !== VisualizationItemType.GROUP) return false; - // TODO: Check item.providerType once that's implemented - return "plots" in item.customProps; + return [GroupType.WELL_LOG_TRACK_CONT, GroupType.WELL_LOG_TRACK_DISC].includes(item.groupType); } diff --git a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx index a2e345d4d..bc6714d1a 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx @@ -113,18 +113,13 @@ function usePersistedProviderManager( } function makeOptionsForGroup(group: ItemGroup): ActionGroup[] { - if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK) { + if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK_CONT) { return [ { label: "Plots", children: [ { icon: , label: "Line plot", identifier: PlotActionIdents.LINE }, { icon: , label: "Area plot", identifier: PlotActionIdents.AREA }, - { - icon: , - label: "Stacked plot", - identifier: PlotActionIdents.STACKED, - }, { icon: , label: "Differential plot", @@ -135,6 +130,21 @@ function makeOptionsForGroup(group: ItemGroup): ActionGroup[] { ]; } + if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK_DISC) { + return [ + { + label: "Plots", + children: [ + { + icon: , + label: "Stacked plot", + identifier: PlotActionIdents.STACKED, + }, + ], + }, + ]; + } + return [ { label: "Log viewer", @@ -144,9 +154,15 @@ function makeOptionsForGroup(group: ItemGroup): ActionGroup[] { identifier: RootActionIdents.CONTINUOUS_TRACK, icon: , }, + { + label: "Discrete Track", + identifier: RootActionIdents.DISCRETE_TRACK, + icon: , + }, { label: "Well picks", identifier: RootActionIdents.WELL_PICKS, + icon: , }, ], }, @@ -173,7 +189,12 @@ export function ProviderManagerComponentWrapper(props: ProviderManagerComponentW case RootActionIdents.CONTINUOUS_TRACK: return groupDelegate.appendChild( - GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK, providerManager), + GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK_CONT, providerManager), + ); + + case RootActionIdents.DISCRETE_TRACK: + return groupDelegate.appendChild( + GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK_DISC, providerManager), ); case PlotActionIdents.LINE: diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts index 5d2539abe..a6542f13e 100644 --- a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts @@ -42,7 +42,7 @@ VISUALIZATION_FACTORY.registerDataProviderTransformers(AreaPlotProvider.name, Ar }); VISUALIZATION_FACTORY.registerGroupCustomPropsCollector( - GroupType.WELL_LOG_TRACK, + GroupType.WELL_LOG_TRACK_CONT, ContinuousLogTrack, makeContinuousTrackConfig, ); diff --git a/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts b/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts index 606f9b78f..1312fd8ea 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts @@ -1,4 +1,5 @@ export enum GroupType { VIEW = "VIEW", - WELL_LOG_TRACK = "WELL_LOG_TRACK", + WELL_LOG_TRACK_CONT = "WELL_LOG_TRACK_CONTINUOUS", + WELL_LOG_TRACK_DISC = "WELL_LOG_TRACK_DISCRETE", } From d5c5f12ab8e52efe9061d6379d1b5fbac57ed352 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Fri, 11 Apr 2025 13:57:37 +0200 Subject: [PATCH 12/25] Added color setting to plots --- .../dataProviders/plots/AreaPlotProvider.ts | 2 +- .../dataProviders/plots/LinearPlotProvider.ts | 4 +- .../dataProviders/plots/_shared.ts | 2 +- .../visualizations/plots.ts | 71 +++++++++++++++---- .../WellLogViewer/utils/factoryProduct.ts | 17 ++++- .../WellLogViewer/utils/queryDataTransform.ts | 9 +++ .../components/SubsurfaceLogViewerWrapper.tsx | 33 +++------ .../implementations/SingleColorSetting.tsx | 25 ++++++- 8 files changed, 118 insertions(+), 45 deletions(-) diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts index 8cdb2d3ae..c0ff7fc24 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts @@ -6,7 +6,7 @@ import { Setting } from "@modules/_shared/DataProviderFramework/settings/setting import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; -export const AreaPlotSettings = [...baseLinearSettings, Setting.PLOT_VARIANT, Setting.COLOR_SCALE] as const; +export const AreaPlotSettings = [Setting.PLOT_VARIANT, ...baseLinearSettings, Setting.COLOR_SCALE] as const; export type AreaPlotSettingTypes = typeof AreaPlotSettings; type SettingsTypeMap = MakeSettingTypesMap; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts index a5759e0fe..bcb01eb35 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts @@ -9,7 +9,7 @@ import { Setting } from "@modules/_shared/DataProviderFramework/settings/setting import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; -export const linearPlotSettings = [...baseLinearSettings, Setting.PLOT_VARIANT /*Setting.COLOR */] as const; +export const linearPlotSettings = [Setting.PLOT_VARIANT, ...baseLinearSettings] as const; export type LinearPlotSettingTypes = typeof linearPlotSettings; type SettingsTypeMap = MakeSettingTypesMap; @@ -35,8 +35,6 @@ export class LinearPlotProvider // eslint-disable-next-line @typescript-eslint/no-unused-vars accessor: DataProviderInformationAccessors, ) { - // TODO - return true; } diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts index 98a16659f..cde6807ed 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts @@ -7,7 +7,7 @@ import { Setting } from "@modules/_shared/DataProviderFramework/settings/setting export const baseSettings = [Setting.LOG_CURVE] as const; -export const baseLinearSettings = [...baseSettings, Setting.SCALE /*, Setting.COLOR */] as const; +export const baseLinearSettings = [...baseSettings, Setting.SCALE, Setting.COLOR] as const; export const baseDiscreteSettings = [...baseSettings, Setting.SHOW_LINES, Setting.SHOW_LABELS, Setting.LABEL_DIR]; export function defineDependencies(args: DefineDependenciesArgs) { diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts index 434b68045..23d65832e 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts @@ -1,5 +1,6 @@ import type { WellboreLogCurveData_api } from "@api"; import type { TemplatePlot } from "@modules/WellLogViewer/types"; +import { isNumericalDataPoints } from "@modules/WellLogViewer/utils/queryDataTransform"; import type { Settings } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { @@ -10,26 +11,40 @@ import { VisualizationItemType, type VisualizationTarget, } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import { makeColorMapFunctionFromColorScale } from "@modules/_shared/DataProviderFramework/visualization/utils/colors"; +import type { ColorMapFunction } from "@webviz/well-log-viewer/dist/components/ColorMapFunction"; import _ from "lodash"; -import type { AreaPlotSettingTypes } from "../dataProviders/plots/AreaPlotProvider"; -import type { LinearPlotSettingTypes } from "../dataProviders/plots/LinearPlotProvider"; +import { AreaPlotProvider, type AreaPlotSettingTypes } from "../dataProviders/plots/AreaPlotProvider"; +import { DifferentialPlotProvider } from "../dataProviders/plots/DiffPlotProvider"; +import { LinearPlotProvider, type LinearPlotSettingTypes } from "../dataProviders/plots/LinearPlotProvider"; +import { StackedPlotProvider } from "../dataProviders/plots/StackedPlotProvider"; export const DATA_ACC_KEY = "LOG_CURVE_DATA"; -export const DUPLICATE_NAMES_ACC_KEY = " DUPLICATE_CURVE_NAMES"; +export const DUPLICATE_NAMES_ACC_KEY = "DUPLICATE_CURVE_NAMES"; +export const COLOR_MAP_ACC_KEY = "COLOR_MAP_FUNCTIONS"; export type FactoryAccResult = { [DATA_ACC_KEY]: WellboreLogCurveData_api[]; [DUPLICATE_NAMES_ACC_KEY]: Set; + [COLOR_MAP_ACC_KEY]: ColorMapFunction[]; }; type PlotVisualizationArgs = TransformerArgs; -function getCommonConfig(data: WellboreLogCurveData_api): TemplatePlot { +function colorFuncName(args: PlotVisualizationArgs): string { + return `colorMapping::${args.id}`; +} + +function getCommonConfig(args: PlotVisualizationArgs): TemplatePlot { + const data = args.getData()!; + const color = args.getSetting(Setting.COLOR)!; + return { name: data.name, logName: data.logName, + color, // TODO: main curve color }; } @@ -37,12 +52,18 @@ function getCommonConfig(data: WellboreLogCurveData_api): TemplatePlot { export function makeAreaPlotConfig(args: PlotVisualizationArgs): TemplatePlot | null { if (!args.getData()) return null; - const data = args.getData()!; const plotVariant = args.getSetting(Setting.PLOT_VARIANT); - const commonConfig = getCommonConfig(data); + const commonConfig = getCommonConfig(args); + + const colorFillOptions: Partial = {}; + + if (plotVariant === "gradientfill") { + colorFillOptions.colorMapFunctionName = colorFuncName(args); + } return { ...commonConfig, + ...colorFillOptions, type: plotVariant ?? undefined, // TODO: Fill/Func }; @@ -51,9 +72,8 @@ export function makeAreaPlotConfig(args: PlotVisualizationArgs): TemplatePlot | null { if (!args.getData()) return null; - const data = args.getData()!; const plotVariant = args.getSetting(Setting.PLOT_VARIANT) ?? undefined; - const commonConfig = getCommonConfig(data); + const commonConfig = getCommonConfig(args); return { ...commonConfig, @@ -61,24 +81,45 @@ export function makeLinePlotConfig(args: PlotVisualizationArgs = +export const plotDataAccumulator: ReduceAccumulatedDataFunction = function plotDataAccumulator(acc, args) { const newData = args.getData(); if (!newData) return acc; - const duplicatedNames = _.get(acc, DUPLICATE_NAMES_ACC_KEY, new Set()) as Set; - const curveData = _.get(acc, DATA_ACC_KEY, []) as WellboreLogCurveData_api[]; + const duplicatedNames = _.get(acc, DUPLICATE_NAMES_ACC_KEY) ?? new Set(); + const curveData = _.get(acc, DATA_ACC_KEY) ?? []; + const colorMapFuncDefs = _.get(acc, COLOR_MAP_ACC_KEY) ?? []; const existingCurve = _.find(curveData, ["name", newData.name]); const sameName = existingCurve?.name === newData.name; const sameLog = existingCurve?.logName === newData.logName; + if (args.getSetting(Setting.PLOT_VARIANT) === "gradientfill") { + const colorScale = args.getSetting(Setting.COLOR_SCALE)?.colorScale; + const dataPoints = newData.dataPoints; + + if (!isNumericalDataPoints(dataPoints)) { + console.warn("Cannot create color mapping for non-numeric data"); + } + + if (colorScale && isNumericalDataPoints(dataPoints)) { + const minValue = newData.minCurveValue ?? _.minBy(dataPoints, "1")![1]; + const maxValue = newData.maxCurveValue ?? _.maxBy(dataPoints, "1")![1]; + + colorMapFuncDefs.push({ + name: colorFuncName(args), + func: makeColorMapFunctionFromColorScale(colorScale, minValue, maxValue)!, + }); + } + } + if (sameName && sameLog) return acc; if (sameName) duplicatedNames.add(newData.name); return { ...acc, [DATA_ACC_KEY]: [...curveData, newData], + [COLOR_MAP_ACC_KEY]: colorMapFuncDefs, [DUPLICATE_NAMES_ACC_KEY]: duplicatedNames, }; }; @@ -90,6 +131,10 @@ export function isPlotVisualization( ): item is PlotVisualization { if (item.itemType !== VisualizationItemType.DATA_PROVIDER_VISUALIZATION) return false; - // TODO: Check item.providerType once that's implemented - return "logName" in item.visualization; + return [ + AreaPlotProvider.name, + LinearPlotProvider.name, + DifferentialPlotProvider.name, + StackedPlotProvider.name, + ].includes(item.type); } diff --git a/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts b/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts index 47564f3d1..9533f178e 100644 --- a/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts +++ b/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts @@ -1,4 +1,5 @@ import type { WellboreTrajectory_api } from "@api"; +import type { ColorMapFunction } from "@webviz/well-log-viewer/dist/components/ColorMapFunction"; import type { WellLogSet } from "@webviz/well-log-viewer/dist/components/WellLogTypes"; import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; @@ -10,6 +11,7 @@ import { trajectoryToIntersectionReference } from "./trajectory"; import type { WellLogFactoryProduct } from "./useLogViewerVisualizationFactory"; import { + COLOR_MAP_ACC_KEY, DATA_ACC_KEY, DUPLICATE_NAMES_ACC_KEY, isPlotVisualization, @@ -69,10 +71,12 @@ export function createWellLogTemplateFromProduct(factoryProduct: WellLogFactoryP } export function createWellLogJsonFromProduct( - factoryProduct: WellLogFactoryProduct, + factoryProduct: WellLogFactoryProduct | null, wellboreTrajectory: WellboreTrajectory_api, padDataWithEmptyRows?: boolean, ): WellLogSet[] { + if (!factoryProduct) return []; + const accData = factoryProduct.accumulatedData; const curveData = _.get(accData, DATA_ACC_KEY, []); const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); @@ -88,7 +92,7 @@ export function createWellLogJsonFromProduct( ); } -export function createWellPickPropFromProduct(factoryProduct: WellLogFactoryProduct): WellPickProps | undefined { +export function createWellPickPropFromProduct(factoryProduct: WellLogFactoryProduct | null): WellPickProps | undefined { if (!factoryProduct) return undefined; // ! We only take the @@ -97,3 +101,12 @@ export function createWellPickPropFromProduct(factoryProduct: WellLogFactoryProd // Can be used as is, no need for further transformations return wellpickVisualization?.visualization; } + +export function createColorMapDefsFromProduct(factoryProduct: WellLogFactoryProduct | null): ColorMapFunction[] { + if (!factoryProduct) return []; + + const accData = factoryProduct.accumulatedData; + const colorMapFuncDefs = _.get(accData, COLOR_MAP_ACC_KEY) ?? []; + + return colorMapFuncDefs; +} diff --git a/frontend/src/modules/WellLogViewer/utils/queryDataTransform.ts b/frontend/src/modules/WellLogViewer/utils/queryDataTransform.ts index 29f8d6b19..e7b4cf007 100644 --- a/frontend/src/modules/WellLogViewer/utils/queryDataTransform.ts +++ b/frontend/src/modules/WellLogViewer/utils/queryDataTransform.ts @@ -298,3 +298,12 @@ function mergePicks(pick1: WellLogDataRow, pick2: WellLogDataRow): WellLogDataRo // ! I have no clue how the well-log viewer computes the colors, but if I DONT use a plus here they all end up having the same color??? return [pick1[0], `${pick1[1]} + ${pick2[1]}`]; } + +export function isNumericalDataPoints( + dataPoints: WellboreLogCurveData_api["dataPoints"], +): dataPoints is [number, number][] { + const firstDefinedRow = dataPoints.find(([, value]) => value != null); + + if (!firstDefinedRow) return false; + return typeof firstDefinedRow[1] === "number"; +} diff --git a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx index 0abee0c4c..203c5fc33 100644 --- a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx @@ -4,9 +4,8 @@ import type { WellboreHeader_api, WellboreTrajectory_api } from "@api"; import type { ModuleViewProps } from "@framework/Module"; import { SyncSettingKey } from "@framework/SyncSettings"; import type { GlobalTopicDefinitions, WorkbenchServices } from "@framework/WorkbenchServices"; -import { ColorScaleGradientType } from "@lib/utils/ColorScale"; -import { createContinuousColorScaleForMap } from "@modules/3DViewer/view/utils/colorTables"; import { + createColorMapDefsFromProduct, createWellLogJsonFromProduct, createWellLogTemplateFromProduct, createWellPickPropFromProduct, @@ -149,21 +148,16 @@ function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { const factoryProduct = useLogViewerVisualizationFactoryProduct(props.providerManager); - const wellpicks = React.useMemo(() => { - if (!factoryProduct) return undefined; - return createWellPickPropFromProduct(factoryProduct); - }, [factoryProduct]); + const colorMapFuncDefs = React.useMemo(() => createColorMapDefsFromProduct(factoryProduct), [factoryProduct]); - const template = React.useMemo(() => { - return createWellLogTemplateFromProduct(factoryProduct); - }, [factoryProduct]); - - const wellLogSets = React.useMemo(() => { - if (!factoryProduct) return []; - return createWellLogJsonFromProduct(factoryProduct, trajectoryData, padDataWithEmptyRows); - }, [factoryProduct, trajectoryData, padDataWithEmptyRows]); + const wellpicks = React.useMemo(() => createWellPickPropFromProduct(factoryProduct), [factoryProduct]); + const template = React.useMemo(() => createWellLogTemplateFromProduct(factoryProduct), [factoryProduct]); + const wellLogSets = React.useMemo( + () => createWellLogJsonFromProduct(factoryProduct, trajectoryData, padDataWithEmptyRows), + [factoryProduct, trajectoryData, padDataWithEmptyRows], + ); - return { template, wellLogSets, wellpicks }; + return { template, wellLogSets, wellpicks, colorMapFuncDefs }; } export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProps) { @@ -172,12 +166,7 @@ export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProp const [wellLogReadout, setWellLogReadout] = React.useState([]); const [showReadoutBox, setShowReadoutBox] = React.useState(false); - const { template, wellLogSets, wellpicks } = useViewerDataTransform(props); - - const colorScale = props.moduleProps.workbenchSettings.useContinuousColorScale({ - gradientType: ColorScaleGradientType.Sequential, - }); - const colorTables = React.useMemo(() => createContinuousColorScaleForMap(colorScale), [colorScale]); + const { template, wellLogSets, wellpicks, colorMapFuncDefs } = useViewerDataTransform(props); const instanceId = props.moduleProps.viewContext.getInstanceIdString(); const syncableSettingKeys = props.moduleProps.viewContext.useSyncedSettingKeys(); @@ -271,7 +260,7 @@ export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProp layout={{ right: undefined }} axisMnemos={AXIS_MNEMOS} axisTitles={AXIS_TITLES} - colorMapFunctions={colorTables} + colorMapFunctions={colorMapFuncDefs} // Disable the pin and selection logic, since we dont use that for anything yet options={{ hideSelectionInterval: true, maxVisibleTrackNum: 12 }} onTrackMouseEvent={handleTrackMouseEvent} diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx index c7788ec90..389826414 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SingleColorSetting.tsx @@ -1,6 +1,8 @@ import type React from "react"; +import { defaultColorPalettes } from "@framework/utils/colorPalettes"; import { ColorSelect } from "@lib/components/ColorSelect"; +import type { ColorPalette } from "@lib/utils/ColorPalette"; import type { CustomSettingImplementation, @@ -11,8 +13,25 @@ import type { SettingCategory } from "../settingsDefinitions"; type ValueType = string | null; +function* makeColorGenerator(palette: ColorPalette) { + const colors = palette.getColors(); + let i = 0; + + while (true) { + yield colors[i % colors.length]; + i++; + } +} + export class SingleColorSetting implements CustomSettingImplementation { - defaultValue: ValueType = null; + // ? How do I get this one tied to work-bench settings? + static _colorGenerator = makeColorGenerator(defaultColorPalettes[0]); + + defaultValue: ValueType; + + constructor() { + this.defaultValue = SingleColorSetting._colorGenerator.next().value ?? null; + } getIsStatic(): boolean { return true; @@ -34,7 +53,7 @@ export class SingleColorSetting implements CustomSettingImplementation { - if (!value) return "#ffffff"; + if (!value) return this.defaultValue ?? ""; return value; } @@ -46,7 +65,7 @@ export class SingleColorSetting implements CustomSettingImplementation - +
); }; From e2b542390f745db80fae78098402e820565e3d83 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Mon, 14 Apr 2025 14:24:20 +0200 Subject: [PATCH 13/25] Added drogon example of text curve --- .../ssdl_access/drogon/_drogon_well_data.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py b/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py index 2ecb1202e..c5cbf6d92 100644 --- a/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py +++ b/backend_py/primary/primary/services/ssdl_access/drogon/_drogon_well_data.py @@ -9,6 +9,7 @@ well_log_headers_2 = [ types.WellboreLogCurveHeader(curve_name="BS", curve_unit="IN", log_name="DROGON_CONTINUOUS"), types.WellboreLogCurveHeader(curve_name="BS", curve_unit="IN", log_name="DROGON_CONTINUOUS_2"), + types.WellboreLogCurveHeader(curve_name="DROGON_COMMENTS", curve_unit="UNITLESS", log_name="DROGON_DISCRETE"), ] @@ -188,4 +189,25 @@ (1000, 600), ], ), + "DROGON_COMMENTS": types.WellboreLogCurveData( + name="DROGON_COMMENTS", + log_name="DROGON_DISCRETE", + index_min=100, + index_max=1000, + index_unit="m", + unit="UNITLESS", + curve_alias="TEXT", + curve_description=None, + curve_unit_desc=None, + min_curve_value=None, + max_curve_value=None, + no_data_value=None, + DataPoints=[ + (150, "High quality"), + (200, "Good quality"), + (600, "No seal"), + (800, "High quality"), + (1000, "High quality"), + ], + ), } From 0d01e4fafe715388af5289f6f123d250ee8c6681 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Mon, 14 Apr 2025 14:25:04 +0200 Subject: [PATCH 14/25] Exposed "step" prop in input component --- frontend/src/lib/components/Input/input.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/lib/components/Input/input.tsx b/frontend/src/lib/components/Input/input.tsx index 644f9f3a7..a85ede7e1 100644 --- a/frontend/src/lib/components/Input/input.tsx +++ b/frontend/src/lib/components/Input/input.tsx @@ -10,6 +10,7 @@ export type InputProps = InputUnstyledProps & { wrapperStyle?: React.CSSProperties; min?: number; max?: number; + step?: number; rounded?: "all" | "left" | "right" | "none"; debounceTimeMs?: number; onValueChange?: (value: string) => void; @@ -176,6 +177,7 @@ function InputComponent(props: InputProps, ref: React.ForwardedRef Date: Mon, 14 Apr 2025 15:23:45 +0200 Subject: [PATCH 15/25] Added stacked plot provider for discrete tracks --- .../plots/StackedPlotProvider.ts | 72 +++++++++++++++ .../dataProviders/plots/_shared.ts | 14 ++- .../registerFrameworkExtensions.ts | 2 + .../visualizations/plots.ts | 21 +++++ .../visualizations/tracks.ts | 30 +++++-- .../utils/useLogViewerVisualizationFactory.ts | 22 ++++- .../implementations/StaticRotationSetting.tsx | 87 +++++++++++++++++++ .../settings/registerAllSettings.ts | 4 + .../settings/settingsDefinitions.ts | 6 +- 9 files changed, 241 insertions(+), 17 deletions(-) create mode 100644 frontend/src/modules/_shared/DataProviderFramework/settings/implementations/StaticRotationSetting.tsx diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts index e69de29bb..89da38726 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts @@ -0,0 +1,72 @@ +import type { WellboreLogCurveData_api, WellboreLogCurveHeader_api } from "@api"; +import { WellLogCurveSourceEnum_api, WellLogCurveTypeEnum_api, getWellboreLogCurveHeadersOptions } from "@api"; +import type { CustomDataProviderImplementation } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import { type MakeSettingTypesMap, Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; + +import { baseDiscreteSettings, fetchData, verifyBasePlotSettings } from "./_shared"; + +export const stackedPlotSettings = [...baseDiscreteSettings, Setting.LABEL_ROTATION] as const; +export type StackedPlotSettingTypes = typeof stackedPlotSettings; +type SettingsTypeMap = MakeSettingTypesMap; + +export class StackedPlotProvider + implements CustomDataProviderImplementation +{ + fetchData = fetchData; + settings = stackedPlotSettings; + + getDefaultSettingsValues(): Partial { + return { + [Setting.SHOW_LABELS]: true, + [Setting.SHOW_LINES]: true, + [Setting.LABEL_ROTATION]: 90, + }; + } + + // Uses the same external things as the other types + defineDependencies(args: DefineDependenciesArgs) { + const { availableSettingsUpdater, helperDependency } = args; + + const headerQueryDeps = [ + WellLogCurveSourceEnum_api.SSDL_WELL_LOG, + WellLogCurveSourceEnum_api.SMDA_GEOLOGY, + WellLogCurveSourceEnum_api.SMDA_STRATIGRAPHY, + ].map((source) => + helperDependency(async ({ getGlobalSetting, abortSignal }) => { + const wellboreId = getGlobalSetting("wellboreUuid") ?? ""; + + return await args.queryClient.fetchQuery({ + ...getWellboreLogCurveHeadersOptions({ + query: { wellbore_uuid: wellboreId, sources: [source] }, + signal: abortSignal, + }), + }); + }), + ); + + availableSettingsUpdater(Setting.LOG_CURVE, ({ getHelperDependency, getGlobalSetting }) => { + const wellboreId = getGlobalSetting("wellboreUuid"); + const allHeaderData = headerQueryDeps.flatMap((dep) => getHelperDependency(dep)); + + const headerData = allHeaderData.filter( + (datum) => datum && datum.curveType !== WellLogCurveTypeEnum_api.CONTINUOUS, + ); + + if (!wellboreId || !headerData.length) return []; + + return headerData as WellboreLogCurveHeader_api[]; + }); + } + + getDefaultName() { + return "Linear plot"; + } + + // TODO: Figure out why prev-settings is undefined + // eslint-disable-next-line @typescript-eslint/no-unused-vars + doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { + // return !_.isEqual(prevSettings?.logCurve, newSettings?.logCurve); + return true; + } +} diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts index cde6807ed..790910383 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts @@ -8,13 +8,21 @@ import { Setting } from "@modules/_shared/DataProviderFramework/settings/setting export const baseSettings = [Setting.LOG_CURVE] as const; export const baseLinearSettings = [...baseSettings, Setting.SCALE, Setting.COLOR] as const; -export const baseDiscreteSettings = [...baseSettings, Setting.SHOW_LINES, Setting.SHOW_LABELS, Setting.LABEL_DIR]; - -export function defineDependencies(args: DefineDependenciesArgs) { +export const baseDiscreteSettings = [ + ...baseSettings, + // These might be a bit too specific to the stacked plot. + // Consider moving this if/when we introduce other discrete curves. + Setting.SHOW_LINES, + Setting.SHOW_LABELS, + Setting.LABEL_ROTATION, +] as const; + +export function defineBaseContinuousDependencies(args: DefineDependenciesArgs) { const { availableSettingsUpdater, helperDependency } = args; const curveHeaderQueryDep = helperDependency(async ({ getGlobalSetting, abortSignal }) => { const wellboreId = getGlobalSetting("wellboreUuid") ?? ""; + return await args.queryClient.fetchQuery({ ...getWellboreLogCurveHeadersOptions({ query: { wellbore_uuid: wellboreId }, diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts index ef95592aa..bdc62614e 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts @@ -5,6 +5,7 @@ import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTy import { AreaPlotProvider } from "./dataProviders/plots/AreaPlotProvider"; import { DifferentialPlotProvider } from "./dataProviders/plots/DiffPlotProvider"; import { LinearPlotProvider } from "./dataProviders/plots/LinearPlotProvider"; +import { StackedPlotProvider } from "./dataProviders/plots/StackedPlotProvider"; import { WellborePicksProvider } from "./dataProviders/wellpicks/WellPicksProvider"; import { ContinuousLogTrack } from "./groups/ContinuousLogTrack"; import { DiscreteLogTrack } from "./groups/DiscreteLogTrack"; @@ -17,3 +18,4 @@ DataProviderRegistry.registerDataProvider(LinearPlotProvider.name, LinearPlotPro DataProviderRegistry.registerDataProvider(AreaPlotProvider.name, AreaPlotProvider); DataProviderRegistry.registerDataProvider(DifferentialPlotProvider.name, DifferentialPlotProvider); DataProviderRegistry.registerDataProvider(WellborePicksProvider.name, WellborePicksProvider); +DataProviderRegistry.registerDataProvider(StackedPlotProvider.name, StackedPlotProvider); diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts index 23d65832e..b426a2c06 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts @@ -19,6 +19,7 @@ import _ from "lodash"; import { AreaPlotProvider, type AreaPlotSettingTypes } from "../dataProviders/plots/AreaPlotProvider"; import { DifferentialPlotProvider } from "../dataProviders/plots/DiffPlotProvider"; import { LinearPlotProvider, type LinearPlotSettingTypes } from "../dataProviders/plots/LinearPlotProvider"; +import type { StackedPlotSettingTypes } from "../dataProviders/plots/StackedPlotProvider"; import { StackedPlotProvider } from "../dataProviders/plots/StackedPlotProvider"; export const DATA_ACC_KEY = "LOG_CURVE_DATA"; @@ -81,6 +82,26 @@ export function makeLinePlotConfig(args: PlotVisualizationArgs): TemplatePlot | null { + if (!args.getData()) return null; + + const data = args.getData()!; + + const showLabels = args.getSetting(Setting.SHOW_LABELS); + const showLines = args.getSetting(Setting.SHOW_LINES); + const rotation = args.getSetting(Setting.LABEL_ROTATION) ?? 90; + + return { + name: data.name, + logName: data.logName, + type: "stacked", + showLabels, + showLines, + // ! The number used in WSC is a little unintuitive (no rotation == -90 degrees), so we offset the the rotation to make it work as expected in the setting form + labelRotation: rotation - 90, + }; +} + export const plotDataAccumulator: ReduceAccumulatedDataFunction = function plotDataAccumulator(acc, args) { const newData = args.getData(); diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts index 9d9f21597..13a37d88e 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/tracks.ts @@ -11,28 +11,42 @@ import { VisualizationItemType } from "@modules/_shared/DataProviderFramework/vi import type { TemplatePlotScale } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; import type { ContinuousTrackSettings } from "../groups/ContinuousLogTrack"; +import type { DiscreteTrackSettings } from "../groups/DiscreteLogTrack"; +import type { baseSettings } from "../groups/_shared"; -type TrackCustomPropsCollector = GroupPropsCollectorArgs; - -export function makeContinuousTrackConfig(args: TrackCustomPropsCollector): TemplateTrack { - const trackWidth = args.getSetting(Setting.TRACK_WIDTH) ?? undefined; - const trackScale = args.getSetting(Setting.SCALE) ?? undefined; +type DiscreteTrackCollectorArgs = GroupPropsCollectorArgs; +type ContinuousTrackCollectorArgs = GroupPropsCollectorArgs; +function getSharedConfig(args: GroupPropsCollectorArgs): TemplateTrack { return { title: args.name, + width: args.getSetting(Setting.TRACK_WIDTH) ?? undefined, required: true, - width: trackWidth, - scale: trackScale as TemplatePlotScale, // Need to fill this later, as they're not defined yet. plots: [], }; } +export function collectDiscreteTrackConfig(args: DiscreteTrackCollectorArgs): TemplateTrack { + return { + ...getSharedConfig(args), + }; +} + +export function collectContinuousTrackConfig(args: ContinuousTrackCollectorArgs): TemplateTrack { + const trackScale = args.getSetting(Setting.SCALE) ?? undefined; + + return { + ...getSharedConfig(args), + scale: trackScale as TemplatePlotScale, + }; +} + export type TrackVisualizationGroup = VisualizationGroup< VisualizationTarget.WSC_WELL_LOG, { [GroupType.WELL_LOG_TRACK_CONT]: TemplateTrack }, never, - GroupType.WELL_LOG_TRACK_CONT + GroupType.WELL_LOG_TRACK_CONT | GroupType.WELL_LOG_TRACK_DISC >; export function isTrackGroup( diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts index a6542f13e..7495acdcf 100644 --- a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts @@ -11,19 +11,24 @@ import { VisualizationAssembler } from "@modules/_shared/DataProviderFramework/v import { AreaPlotProvider } from "../DataProviderFramework/dataProviders/plots/AreaPlotProvider"; import { LinearPlotProvider } from "../DataProviderFramework/dataProviders/plots/LinearPlotProvider"; +import { StackedPlotProvider } from "../DataProviderFramework/dataProviders/plots/StackedPlotProvider"; import { WellborePicksProvider } from "../DataProviderFramework/dataProviders/wellpicks/WellPicksProvider"; import { ContinuousLogTrack } from "../DataProviderFramework/groups/ContinuousLogTrack"; +import { DiscreteLogTrack } from "../DataProviderFramework/groups/DiscreteLogTrack"; import type { FactoryAccResult as PlotFactoryAccResult } from "../DataProviderFramework/visualizations/plots"; import { makeAreaPlotConfig, makeLinePlotConfig, + makeStackedPlotConfig, plotDataAccumulator, } from "../DataProviderFramework/visualizations/plots"; -import { makeContinuousTrackConfig } from "../DataProviderFramework/visualizations/tracks"; +import { + collectContinuousTrackConfig, + collectDiscreteTrackConfig, +} from "../DataProviderFramework/visualizations/tracks"; import { makeLogViewerWellPicks } from "../DataProviderFramework/visualizations/wellpicks"; type FactoryAccResult = PlotFactoryAccResult; -// type FactoryMakeResult const VISUALIZATION_FACTORY = new VisualizationAssembler< VisualizationTarget.WSC_WELL_LOG, @@ -41,10 +46,21 @@ VISUALIZATION_FACTORY.registerDataProviderTransformers(AreaPlotProvider.name, Ar reduceAccumulatedData: plotDataAccumulator, }); +VISUALIZATION_FACTORY.registerDataProviderTransformers(StackedPlotProvider.name, StackedPlotProvider, { + transformToVisualization: makeStackedPlotConfig, + reduceAccumulatedData: plotDataAccumulator, +}); + VISUALIZATION_FACTORY.registerGroupCustomPropsCollector( GroupType.WELL_LOG_TRACK_CONT, ContinuousLogTrack, - makeContinuousTrackConfig, + collectContinuousTrackConfig, +); + +VISUALIZATION_FACTORY.registerGroupCustomPropsCollector( + GroupType.WELL_LOG_TRACK_DISC, + DiscreteLogTrack, + collectDiscreteTrackConfig, ); VISUALIZATION_FACTORY.registerDataProviderTransformers(WellborePicksProvider.name, WellborePicksProvider, { diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/StaticRotationSetting.tsx b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/StaticRotationSetting.tsx new file mode 100644 index 000000000..5605fdb61 --- /dev/null +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/StaticRotationSetting.tsx @@ -0,0 +1,87 @@ +import React from "react"; + +import { Input } from "@lib/components/Input"; + +import type { + CustomSettingImplementation, + SettingComponentProps, +} from "../../interfacesAndTypes/customSettingImplementation"; +import type { SettingCategory } from "../settingsDefinitions"; + +type ValueType = number | null; + +export type StaticRotationSettingProps = { + minMax?: [number, number]; + step?: number; + loopAround?: boolean; +}; + +export class StaticRotationSetting implements CustomSettingImplementation { + defaultValue = 0; + private _min: number; + private _max: number; + private _step: number; + private loopAround: boolean; + + constructor(props: StaticRotationSettingProps) { + this._min = props?.minMax?.[0] ?? -180; + this._max = props?.minMax?.[1] ?? 180; + this._step = props?.step ?? 45; + this.loopAround = props?.loopAround ?? true; + } + + getIsStatic() { + return true; + } + + fixupValue(v: ValueType) { + if (v == null) return 0; + return v; + } + + makeComponent(): (props: SettingComponentProps) => React.ReactNode { + const min = this._min; + const max = this._max; + const step = this._step; + const loopAround = this.loopAround; + + return function DropdownStringSetting( + props: SettingComponentProps, + ) { + const { onValueChange } = props; + const value = props.isOverridden ? props.overriddenValue : props.value; + + const handleInputChange = React.useCallback( + function handleInputChange(event: React.ChangeEvent) { + let newVal = event.target.valueAsNumber; + + if (loopAround && newVal < min) { + const diff = min - newVal; + newVal = max - diff; + } + + if (loopAround && newVal > max) { + const diff = newVal - max; + newVal = min + diff; + } + + onValueChange(newVal); + }, + [onValueChange], + ); + + return ( + + ); + }; + } +} diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts b/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts index bdad0ea1b..ef77f25df 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/registerAllSettings.ts @@ -17,6 +17,7 @@ import { SeismicSliceDirection, SeismicSliceSetting } from "./implementations/Se import { SensitivitySetting } from "./implementations/SensitivitySetting"; import { SingleColorSetting } from "./implementations/SingleColorSetting"; import { StaticDropdownStringSetting } from "./implementations/StaticDropdownStringSetting"; +import { StaticRotationSetting } from "./implementations/StaticRotationSetting"; import { StatisticFunctionSetting } from "./implementations/StatisticFunctionSetting"; import { Setting } from "./settingsDefinitions"; @@ -43,6 +44,9 @@ SettingRegistry.registerSetting(Setting.SCALE, "Scale", StaticDropdownStringSett // @ts-expect-error -- Setting type is a string literal, but Dropdown setting doesn't accept that as a valid type SettingRegistry.registerSetting(Setting.PLOT_VARIANT, "Plot variant", DropdownStringSetting); SettingRegistry.registerSetting(Setting.LOG_CURVE, "Log curve", LogCurveSetting); +SettingRegistry.registerSetting(Setting.SHOW_LABELS, "Show labels", BooleanSetting); +SettingRegistry.registerSetting(Setting.LABEL_ROTATION, "Label rotation", StaticRotationSetting); +SettingRegistry.registerSetting(Setting.SHOW_LINES, "Show lines", BooleanSetting); SettingRegistry.registerSetting(Setting.ATTRIBUTE, "Attribute", DropdownStringSetting); SettingRegistry.registerSetting(Setting.ENSEMBLE, "Ensemble", EnsembleSetting); diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts b/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts index ff6fba906..e27cca311 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts @@ -29,7 +29,7 @@ export enum SettingCategory { export enum Setting { // Assorted styling visual settings SHOW_LABELS = "showLabels", - LABEL_DIR = "labelDir", + LABEL_ROTATION = "labelRotation", SHOW_LINES = "showLines", TRACK_WIDTH = "trackWidth", SCALE = "scale", @@ -69,7 +69,7 @@ export enum Setting { export const settingCategories = { [Setting.SHOW_LABELS]: SettingCategory.BOOLEAN, - [Setting.LABEL_DIR]: SettingCategory.SINGLE_SELECT, + [Setting.LABEL_ROTATION]: SettingCategory.NUMBER_WITH_STEP, [Setting.SHOW_LINES]: SettingCategory.BOOLEAN, [Setting.TRACK_WIDTH]: SettingCategory.NUMBER_WITH_STEP, [Setting.SCALE]: SettingCategory.SINGLE_SELECT, @@ -111,7 +111,7 @@ export type SettingCategories = typeof settingCategories; export type SettingTypes = { [Setting.SHOW_LABELS]: boolean; [Setting.SCALE]: "linear" | "log" | null; - [Setting.LABEL_DIR]: "horizontal" | "vertical" | null; + [Setting.LABEL_ROTATION]: number | null; [Setting.SHOW_LINES]: boolean; [Setting.TRACK_WIDTH]: number | null; [Setting.LOG_CURVE]: WellboreLogCurveHeader_api | null; From 989b5e191ed3140acfcb142c2da4a9caef71bf0f Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Mon, 14 Apr 2025 15:50:39 +0200 Subject: [PATCH 16/25] Cleaned up some continuous track stuff --- .../dataProviders/plots/AreaPlotProvider.ts | 13 ++----- .../dataProviders/plots/DiffPlotProvider.ts | 8 ++-- .../dataProviders/plots/LinearPlotProvider.ts | 22 ++++------- .../plots/StackedPlotProvider.ts | 1 + .../dataProviders/plots/_shared.ts | 38 +++++++++++++++++-- .../groups/ContinuousLogTrack.ts | 2 +- .../groups/DiscreteLogTrack.ts | 2 +- 7 files changed, 51 insertions(+), 35 deletions(-) diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts index c0ff7fc24..d1d8fc4af 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider.ts @@ -4,7 +4,7 @@ import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramew import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; +import { baseLinearSettings, defineBaseContinuousDependencies, fetchData, verifyBasePlotSettings } from "./_shared"; export const AreaPlotSettings = [Setting.PLOT_VARIANT, ...baseLinearSettings, Setting.COLOR_SCALE] as const; export type AreaPlotSettingTypes = typeof AreaPlotSettings; @@ -13,12 +13,12 @@ type SettingsTypeMap = MakeSettingTypesMap; export class AreaPlotProvider implements CustomDataProviderImplementation { + areCurrentSettingsValid = verifyBasePlotSettings; + fetchData = fetchData; settings = AreaPlotSettings; - // Uses the same external things as the other types - fetchData = fetchData; defineDependencies(args: DefineDependenciesArgs) { - defineDependencies(args); + defineBaseContinuousDependencies(args); args.availableSettingsUpdater(Setting.PLOT_VARIANT, () => { return ["area", "gradientfill"]; @@ -29,11 +29,6 @@ export class AreaPlotProvider return "Area plot"; } - areCurrentSettingsValid() { - // TODO - return true; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { return true; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts index 931a95bb2..1c6c34fb6 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts @@ -5,7 +5,7 @@ import { Setting } from "@modules/_shared/DataProviderFramework/settings/setting import _ from "lodash"; -import { defineDependencies, fetchData } from "./_shared"; +import { defineBaseContinuousDependencies, fetchData } from "./_shared"; // TODO: Need a clean way to export const differentialPlotSettings = [Setting.LOG_CURVE, Setting.LOG_CURVE, Setting.SCALE] as const; @@ -15,10 +15,8 @@ type SettingsTypeMap = MakeSettingTypesMap; export class DifferentialPlotProvider implements CustomDataProviderImplementation { - // Uses the same external things as the other types - defineDependencies = defineDependencies; - fetchData = fetchData; - + defineDependencies = defineBaseContinuousDependencies; + fetchData = fetchData; settings = differentialPlotSettings; getDefaultName() { diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts index bcb01eb35..139fd6099 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider.ts @@ -1,13 +1,10 @@ import type { WellboreLogCurveData_api } from "@api"; -import type { - CustomDataProviderImplementation, - DataProviderInformationAccessors, -} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { CustomDataProviderImplementation } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -import { baseLinearSettings, defineDependencies, fetchData } from "./_shared"; +import { baseLinearSettings, defineBaseContinuousDependencies, fetchData, verifyBasePlotSettings } from "./_shared"; export const linearPlotSettings = [Setting.PLOT_VARIANT, ...baseLinearSettings] as const; export type LinearPlotSettingTypes = typeof linearPlotSettings; @@ -16,28 +13,23 @@ type SettingsTypeMap = MakeSettingTypesMap; export class LinearPlotProvider implements CustomDataProviderImplementation { + areCurrentSettingsValid = verifyBasePlotSettings; + fetchData = fetchData; + settings = linearPlotSettings; + // Uses the same external things as the other types defineDependencies(args: DefineDependenciesArgs) { - defineDependencies(args); + defineBaseContinuousDependencies(args); args.availableSettingsUpdater(Setting.PLOT_VARIANT, () => { return ["line", "linestep", "dot"]; }); } - fetchData = fetchData; - settings = linearPlotSettings; getDefaultName() { return "Linear plot"; } - areCurrentSettingsValid( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - accessor: DataProviderInformationAccessors, - ) { - return true; - } - // TODO: Figure out why prev-settings is undefined // eslint-disable-next-line @typescript-eslint/no-unused-vars doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts index 89da38726..dc275a54a 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider.ts @@ -13,6 +13,7 @@ type SettingsTypeMap = MakeSettingTypesMap; export class StackedPlotProvider implements CustomDataProviderImplementation { + areCurrentSettingsValid = verifyBasePlotSettings; fetchData = fetchData; settings = stackedPlotSettings; diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts index 790910383..45a363067 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/_shared.ts @@ -1,10 +1,20 @@ import type { WellboreLogCurveData_api } from "@api"; -import { getLogCurveDataOptions, getWellboreLogCurveHeadersOptions } from "@api"; -import type { FetchDataParams } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import { + WellLogCurveSourceEnum_api, + WellLogCurveTypeEnum_api, + getLogCurveDataOptions, + getWellboreLogCurveHeadersOptions, +} from "@api"; +import type { + DataProviderInformationAccessors, + FetchDataParams, +} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; import type { Settings } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import _ from "lodash"; + export const baseSettings = [Setting.LOG_CURVE] as const; export const baseLinearSettings = [...baseSettings, Setting.SCALE, Setting.COLOR] as const; @@ -25,7 +35,7 @@ export function defineBaseContinuousDependencies(a return await args.queryClient.fetchQuery({ ...getWellboreLogCurveHeadersOptions({ - query: { wellbore_uuid: wellboreId }, + query: { wellbore_uuid: wellboreId, sources: [WellLogCurveSourceEnum_api.SSDL_WELL_LOG] }, signal: abortSignal, }), }); @@ -37,10 +47,29 @@ export function defineBaseContinuousDependencies(a if (!wellboreId || !headerData) return []; - return headerData; + return headerData.filter((curve) => curve.curveType !== WellLogCurveTypeEnum_api.DISCRETE); }); } +export function verifyBasePlotSettings( + accessor: DataProviderInformationAccessors, +): boolean { + const availableCurves = accessor.getAvailableSettingValues(Setting.LOG_CURVE) ?? []; + const selectedCurve = accessor.getSetting(Setting.LOG_CURVE); + + return selectedCurve && !!_.find(availableCurves, (curve) => curve.curveName === selectedCurve.curveName); +} + +export function verifyContinuousPlotSetting( + accessor: DataProviderInformationAccessors, +) { + if (!verifyBasePlotSettings(accessor)) return false; + + const selectedCurve = accessor.getSetting(Setting.LOG_CURVE)!; + + return [WellLogCurveTypeEnum_api.CONTINUOUS, WellLogCurveTypeEnum_api.FLAG].includes(selectedCurve.curveType); +} + export function fetchData( args: FetchDataParams, ): Promise { @@ -57,6 +86,7 @@ export function fetchData( wellbore_uuid: wellboreId, curve_name: curveHeader.curveName, log_name: curveHeader.logName, + source: curveHeader.source, }, }), }); diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts index f83f67d6a..e582e8593 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/ContinuousLogTrack.ts @@ -21,6 +21,6 @@ export class ContinuousLogTrack } getDefaultName(): string { - return "Track"; + return "Continuous track"; } } diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts index 516c4f05e..223e88666 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiscreteLogTrack.ts @@ -20,6 +20,6 @@ export class DiscreteLogTrack } getDefaultName(): string { - return "Track"; + return "Discrete track"; } } From 971dcc739e5f3777e45a1859080d80cc3c3479e5 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Tue, 15 Apr 2025 09:26:00 +0200 Subject: [PATCH 17/25] Split Well log viewer logic into distinct wrappers --- .../ProviderManagerComponentWrapper.tsx | 27 ++- .../WellLogViewer/utils/factoryProduct.ts | 2 +- ...ts => useLogViewerVisualizationProduct.ts} | 2 +- .../components/ProviderManagerWrapper.tsx | 159 ++++++++++++++++++ .../components/SubsurfaceLogViewerWrapper.tsx | 64 ++++--- .../src/modules/WellLogViewer/view/view.tsx | 6 +- 6 files changed, 218 insertions(+), 42 deletions(-) rename frontend/src/modules/WellLogViewer/utils/{useLogViewerVisualizationFactory.ts => useLogViewerVisualizationProduct.ts} (98%) create mode 100644 frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx diff --git a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx index bc6714d1a..f8ff0e757 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx @@ -5,10 +5,10 @@ import type { WorkbenchSession } from "@framework/WorkbenchSession"; import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; import { AreaPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider"; import { LinearPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider"; +import { StackedPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider"; import { WellborePicksProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/wellpicks/WellPicksProvider"; import { TrackIcon } from "@modules/WellLogViewer/_shared/components/icons"; import type { ActionGroup } from "@modules/_shared/DataProviderFramework/Actions"; -import { DataProviderRegistry } from "@modules/_shared/DataProviderFramework/dataProviders/DataProviderRegistry"; import type { GroupDelegate } from "@modules/_shared/DataProviderFramework/delegates/GroupDelegate"; import { GroupDelegateTopic } from "@modules/_shared/DataProviderFramework/delegates/GroupDelegate"; import { @@ -18,18 +18,28 @@ import { import { DataProviderManagerComponent } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManagerComponent"; import { Group } from "@modules/_shared/DataProviderFramework/framework/Group/Group"; import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; -// import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; import type { ItemGroup } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/entities"; -import { ShowChart, ViewDay } from "@mui/icons-material"; +import { HorizontalRule, ShowChart, ViewDay } from "@mui/icons-material"; import { useQueryClient } from "@tanstack/react-query"; import { useAtom } from "jotai"; -import "../../DataProviderFramework/registerFrameworkExtensions"; import { providerManagerAtom } from "../atoms/baseAtoms"; import { serializedManagerStateAtom } from "../atoms/persistedAtoms"; +// import type { DataProviderRegistry } from "@modules/_shared/DataProviderFramework/dataProviders/DataProviderRegistry"; + +import("../../DataProviderFramework/registerFrameworkExtensions"); + +// @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available +let DataProviderRegistry; +import("@modules/_shared/DataProviderFramework/dataProviders/DataProviderRegistry").then( + ({ DataProviderRegistry: provider }) => { + DataProviderRegistry = provider; + }, +); + enum RootActionIdents { CONTINUOUS_TRACK = "cont_track", DISCRETE_TRACK = "disc_track", @@ -184,6 +194,7 @@ export function ProviderManagerComponentWrapper(props: ProviderManagerComponentW switch (identifier) { case RootActionIdents.WELL_PICKS: return groupDelegate.appendChild( + // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available DataProviderRegistry.makeDataProvider(WellborePicksProvider.name, providerManager), ); @@ -199,13 +210,21 @@ export function ProviderManagerComponentWrapper(props: ProviderManagerComponentW case PlotActionIdents.LINE: return groupDelegate.appendChild( + // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available DataProviderRegistry.makeDataProvider(LinearPlotProvider.name, providerManager), ); case PlotActionIdents.AREA: return groupDelegate.appendChild( + // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available DataProviderRegistry.makeDataProvider(AreaPlotProvider.name, providerManager), ); + case PlotActionIdents.STACKED: + return groupDelegate.appendChild( + // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available + DataProviderRegistry.makeDataProvider(StackedPlotProvider.name, providerManager), + ); + default: break; } diff --git a/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts b/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts index 9533f178e..479dfa4fd 100644 --- a/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts +++ b/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts @@ -8,7 +8,7 @@ import _ from "lodash"; import { MAIN_AXIS_CURVE, createWellLogSets } from "./queryDataTransform"; import { getUniqueCurveNameForPlotConfig } from "./strings"; import { trajectoryToIntersectionReference } from "./trajectory"; -import type { WellLogFactoryProduct } from "./useLogViewerVisualizationFactory"; +import type { WellLogFactoryProduct } from "./useLogViewerVisualizationProduct"; import { COLOR_MAP_ACC_KEY, diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationProduct.ts similarity index 98% rename from frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts rename to frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationProduct.ts index 7495acdcf..a80f2760e 100644 --- a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationFactory.ts +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationProduct.ts @@ -70,7 +70,7 @@ VISUALIZATION_FACTORY.registerDataProviderTransformers(WellborePicksProvider.nam export type WellLogFactoryProduct = ReturnType<(typeof VISUALIZATION_FACTORY)["make"]>; -export function useLogViewerVisualizationFactoryProduct( +export function useLogViewerVisualizationProduct( dataProviderManager: DataProviderManager, ): WellLogFactoryProduct | null { const [previousRevision, setPreviousRevision] = React.useState(null); diff --git a/frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx new file mode 100644 index 000000000..c1035f5a9 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx @@ -0,0 +1,159 @@ +import React from "react"; + +import type { WellboreTrajectory_api } from "@api"; +import { + COLOR_MAP_ACC_KEY, + DATA_ACC_KEY, + DUPLICATE_NAMES_ACC_KEY, + isPlotVisualization, +} from "@modules/WellLogViewer/DataProviderFramework/visualizations/plots"; +import { + type TrackVisualizationGroup, + isTrackGroup, +} from "@modules/WellLogViewer/DataProviderFramework/visualizations/tracks"; +import { isWellPickVisualization } from "@modules/WellLogViewer/DataProviderFramework/visualizations/wellpicks"; +import type { Template, TemplatePlot, TemplateTrack } from "@modules/WellLogViewer/types"; +import { MAIN_AXIS_CURVE, createWellLogSets } from "@modules/WellLogViewer/utils/queryDataTransform"; +import { getUniqueCurveNameForPlotConfig } from "@modules/WellLogViewer/utils/strings"; +import { trajectoryToIntersectionReference } from "@modules/WellLogViewer/utils/trajectory"; +import type { WellLogFactoryProduct } from "@modules/WellLogViewer/utils/useLogViewerVisualizationProduct"; +import { useLogViewerVisualizationProduct } from "@modules/WellLogViewer/utils/useLogViewerVisualizationProduct"; +import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { CircularProgress } from "@mui/material"; +import type { ColorMapFunction } from "@webviz/well-log-viewer/dist/components/ColorMapFunction"; +import type { WellLogSet } from "@webviz/well-log-viewer/dist/components/WellLogTypes"; +import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; + +import _ from "lodash"; + +import type { SubsurfaceLogViewerWrapperProps } from "./SubsurfaceLogViewerWrapper"; +import { SubsurfaceLogViewerWrapper } from "./SubsurfaceLogViewerWrapper"; + +export type LogViewerProps = { + padData: boolean; + horizontal: boolean; +}; + +function getProvidedPlots(trackGroup: TrackVisualizationGroup, duplicatedCurveNames: Set) { + const plots: TemplatePlot[] = []; + + for (const child of trackGroup.children) { + if (!isPlotVisualization(child)) continue; + + const template = child.visualization; + + plots.push({ + ...template, + // ! Map each plot to ensure a name that points to the correct curve + name: getUniqueCurveNameForPlotConfig(template, duplicatedCurveNames), + }); + } + + return plots; +} + +function createWellLogTemplateFromProduct(factoryProduct: WellLogFactoryProduct | null): Template { + const tracks: TemplateTrack[] = []; + const accData = factoryProduct?.accumulatedData; + + const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); + + for (const child of factoryProduct?.children ?? []) { + if (!isTrackGroup(child)) continue; + + const templatePlots = getProvidedPlots(child, duplicatedCurveNames!); + const templateProps = child.customProps; + + tracks.push({ + ...templateProps, + plots: templatePlots, + }); + } + + return { + // AFAIK, this name is not show anywhere + name: "Well log viewer", + scale: { primary: MAIN_AXIS_CURVE.name, allowSecondary: true }, + tracks, + }; +} + +function createWellLogJsonFromProduct( + factoryProduct: WellLogFactoryProduct | null, + wellboreTrajectory: WellboreTrajectory_api, + padDataWithEmptyRows?: boolean, +): WellLogSet[] { + if (!factoryProduct) return []; + + const accData = factoryProduct.accumulatedData; + const curveData = _.get(accData, DATA_ACC_KEY, []); + const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); + + const referenceSystem = trajectoryToIntersectionReference(wellboreTrajectory); + + return createWellLogSets( + curveData, + wellboreTrajectory, + referenceSystem, + duplicatedCurveNames, + padDataWithEmptyRows, + ); +} + +function createWellPickPropFromProduct(factoryProduct: WellLogFactoryProduct | null): WellPickProps | undefined { + if (!factoryProduct) return undefined; + + // ! We only take the + const wellpickVisualization = factoryProduct.children.find((child) => isWellPickVisualization(child)); + + // Can be used as is, no need for further transformations + return wellpickVisualization?.visualization; +} + +function createColorMapDefsFromProduct(factoryProduct: WellLogFactoryProduct | null): ColorMapFunction[] { + if (!factoryProduct) return []; + + const accData = factoryProduct.accumulatedData; + const colorMapFuncDefs = _.get(accData, COLOR_MAP_ACC_KEY) ?? []; + + return colorMapFuncDefs; +} + +export type ProviderManagerWrapperProps = { + providerManager: DataProviderManager; + trajectoryData: WellboreTrajectory_api; + padDataWithEmptyRows: boolean; +} & Omit; + +export function ProviderManagerWrapper(props: ProviderManagerWrapperProps): React.ReactNode { + const { trajectoryData, padDataWithEmptyRows, providerManager, ...rest } = props; + + const factoryProduct = useLogViewerVisualizationProduct(providerManager); + + const colorMapFuncDefs = React.useMemo(() => createColorMapDefsFromProduct(factoryProduct), [factoryProduct]); + const wellPicks = React.useMemo(() => createWellPickPropFromProduct(factoryProduct), [factoryProduct]); + const template = React.useMemo(() => createWellLogTemplateFromProduct(factoryProduct), [factoryProduct]); + + const wellLogSets = React.useMemo( + () => createWellLogJsonFromProduct(factoryProduct, trajectoryData, padDataWithEmptyRows), + [factoryProduct, trajectoryData, padDataWithEmptyRows], + ); + + if (!factoryProduct) { + return ( +
+ +
+ ); + } + + return ( + + ); +} diff --git a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx index 203c5fc33..98d47d2fa 100644 --- a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx @@ -1,20 +1,15 @@ import React from "react"; -import type { WellboreHeader_api, WellboreTrajectory_api } from "@api"; +import type { WellboreHeader_api } from "@api"; import type { ModuleViewProps } from "@framework/Module"; import { SyncSettingKey } from "@framework/SyncSettings"; import type { GlobalTopicDefinitions, WorkbenchServices } from "@framework/WorkbenchServices"; -import { - createColorMapDefsFromProduct, - createWellLogJsonFromProduct, - createWellLogTemplateFromProduct, - createWellPickPropFromProduct, -} from "@modules/WellLogViewer/utils/factoryProduct"; -import { useLogViewerVisualizationFactoryProduct } from "@modules/WellLogViewer/utils/useLogViewerVisualizationFactory"; -import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import type { Template } from "@modules/WellLogViewer/types"; import { WellLogViewer } from "@webviz/well-log-viewer"; +import type { ColorMapFunction } from "@webviz/well-log-viewer/dist/components/ColorMapFunction"; import type { Info } from "@webviz/well-log-viewer/dist/components/InfoTypes"; -import type { WellLogController } from "@webviz/well-log-viewer/dist/components/WellLogView"; +import type { WellLogSet } from "@webviz/well-log-viewer/dist/components/WellLogTypes"; +import type { WellLogController, WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; import _ from "lodash"; @@ -39,12 +34,17 @@ type GlobalHoverMd = GlobalTopicDefinitions["global.hoverMd"]; export type SubsurfaceLogViewerWrapperProps = { // Data wellboreHeader: WellboreHeader_api | null; - providerManager: DataProviderManager; - trajectoryData: WellboreTrajectory_api; + wellLogSets: WellLogSet[]; + wellPicks?: WellPickProps; + + // Data + // providerManager: DataProviderManager; + // trajectoryData: WellboreTrajectory_api; // Viewer config + viewerTemplate: Template; + colorMapFunctions?: ColorMapFunction[]; horizontal: boolean; - padDataWithEmptyRows: boolean; // Passing the module props to make context and service access less cumbersome moduleProps: ModuleViewProps; @@ -142,23 +142,23 @@ function useCreateGlobalVerticalScaleBroadcastFunc( return broadcastVerticalScaleChange; } -function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { - const trajectoryData = props.trajectoryData; - const padDataWithEmptyRows = props.padDataWithEmptyRows; +// function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { +// const trajectoryData = props.trajectoryData; +// const padDataWithEmptyRows = props.padDataWithEmptyRows; - const factoryProduct = useLogViewerVisualizationFactoryProduct(props.providerManager); +// const factoryProduct = useLogViewerVisualizationProduct(props.providerManager); - const colorMapFuncDefs = React.useMemo(() => createColorMapDefsFromProduct(factoryProduct), [factoryProduct]); +// const colorMapFuncDefs = React.useMemo(() => createColorMapDefsFromProduct(factoryProduct), [factoryProduct]); - const wellpicks = React.useMemo(() => createWellPickPropFromProduct(factoryProduct), [factoryProduct]); - const template = React.useMemo(() => createWellLogTemplateFromProduct(factoryProduct), [factoryProduct]); - const wellLogSets = React.useMemo( - () => createWellLogJsonFromProduct(factoryProduct, trajectoryData, padDataWithEmptyRows), - [factoryProduct, trajectoryData, padDataWithEmptyRows], - ); +// const wellpicks = React.useMemo(() => createWellPickPropFromProduct(factoryProduct), [factoryProduct]); +// const template = React.useMemo(() => createWellLogTemplateFromProduct(factoryProduct), [factoryProduct]); +// const wellLogSets = React.useMemo( +// () => createWellLogJsonFromProduct(factoryProduct, trajectoryData, padDataWithEmptyRows), +// [factoryProduct, trajectoryData, padDataWithEmptyRows], +// ); - return { template, wellLogSets, wellpicks, colorMapFuncDefs }; -} +// return { template, wellLogSets, wellpicks, colorMapFuncDefs }; +// } export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProps) { // uses an internal controller to change things like zoom, selection and so on. Use this when possible to avoid uneccessary re-renders @@ -166,8 +166,6 @@ export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProp const [wellLogReadout, setWellLogReadout] = React.useState([]); const [showReadoutBox, setShowReadoutBox] = React.useState(false); - const { template, wellLogSets, wellpicks, colorMapFuncDefs } = useViewerDataTransform(props); - const instanceId = props.moduleProps.viewContext.getInstanceIdString(); const syncableSettingKeys = props.moduleProps.viewContext.useSyncedSettingKeys(); const wellboreUuid = props.wellboreHeader?.wellboreUuid ?? ""; @@ -252,15 +250,15 @@ export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProp > diff --git a/frontend/src/modules/WellLogViewer/view/view.tsx b/frontend/src/modules/WellLogViewer/view/view.tsx index 33cb114e7..c919ca3ef 100644 --- a/frontend/src/modules/WellLogViewer/view/view.tsx +++ b/frontend/src/modules/WellLogViewer/view/view.tsx @@ -7,7 +7,7 @@ import { CircularProgress } from "@mui/material"; import { useAtomValue } from "jotai"; import { wellboreTrajectoryQueryAtom } from "./atoms/queryAtoms"; -import { SubsurfaceLogViewerWrapper } from "./components/SubsurfaceLogViewerWrapper"; +import { ProviderManagerWrapper } from "./components/ProviderManagerWrapper"; import type { InterfaceTypes } from "../interfaces"; @@ -47,13 +47,13 @@ export function View(props: ModuleViewProps) { } return ( - ); } From c9ae7303a1ba705aa8817cccc25a10bb4cd9c0fd Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Tue, 15 Apr 2025 13:21:17 +0200 Subject: [PATCH 18/25] WIP -- Implemented diff curve plot provider --- .../dataProviders/plots/DiffPlotProvider.ts | 24 ++- .../groups/DiffPlotGroup.ts | 7 + .../registerFrameworkExtensions.ts | 6 +- .../visualizations/plots.ts | 40 ++++- .../ProviderManagerComponentWrapper.tsx | 143 ++++++++++++------ frontend/src/modules/WellLogViewer/types.ts | 1 + .../WellLogViewer/utils/factoryProduct.ts | 112 -------------- .../utils/useLogViewerVisualizationProduct.ts | 6 + .../components/ProviderManagerWrapper.tsx | 43 ++++-- .../components/SubsurfaceLogViewerWrapper.tsx | 22 --- .../groups/groupTypes.ts | 1 + 11 files changed, 190 insertions(+), 215 deletions(-) create mode 100644 frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiffPlotGroup.ts delete mode 100644 frontend/src/modules/WellLogViewer/utils/factoryProduct.ts diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts index 1c6c34fb6..4d5b7d92b 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider.ts @@ -1,22 +1,18 @@ import type { WellboreLogCurveData_api } from "@api"; import type { CustomDataProviderImplementation } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; -import _ from "lodash"; +import { baseLinearSettings, defineBaseContinuousDependencies, fetchData } from "./_shared"; -import { defineBaseContinuousDependencies, fetchData } from "./_shared"; +export const differentialPlotSettings = [...baseLinearSettings] as const; +export type DiffPlotSettingTypes = typeof differentialPlotSettings; +type SettingsTypeMap = MakeSettingTypesMap; -// TODO: Need a clean way to -export const differentialPlotSettings = [Setting.LOG_CURVE, Setting.LOG_CURVE, Setting.SCALE] as const; -export type DifferentialPlotSettingTypes = typeof differentialPlotSettings; -type SettingsTypeMap = MakeSettingTypesMap; - -export class DifferentialPlotProvider - implements CustomDataProviderImplementation +export class DiffPlotProvider + implements CustomDataProviderImplementation { - defineDependencies = defineBaseContinuousDependencies; - fetchData = fetchData; + defineDependencies = defineBaseContinuousDependencies; + fetchData = fetchData; settings = differentialPlotSettings; getDefaultName() { @@ -28,7 +24,9 @@ export class DifferentialPlotProvider return true; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars doSettingsChangesRequireDataRefetch(prevSettings: SettingsTypeMap, newSettings: SettingsTypeMap): boolean { - return _.isEqual(prevSettings?.logCurve, newSettings?.logCurve); + // return _.isEqual(prevSettings?.logCurve, newSettings?.logCurve); + return true; } } diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiffPlotGroup.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiffPlotGroup.ts new file mode 100644 index 000000000..fa1441662 --- /dev/null +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/groups/DiffPlotGroup.ts @@ -0,0 +1,7 @@ +import type { CustomGroupImplementation } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation"; + +export class DiffPlotGroup implements CustomGroupImplementation { + getDefaultName(): string { + return "Differential plot"; + } +} diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts index bdc62614e..ef228655a 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/registerFrameworkExtensions.ts @@ -3,19 +3,21 @@ import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/Gro import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; import { AreaPlotProvider } from "./dataProviders/plots/AreaPlotProvider"; -import { DifferentialPlotProvider } from "./dataProviders/plots/DiffPlotProvider"; +import { DiffPlotProvider } from "./dataProviders/plots/DiffPlotProvider"; import { LinearPlotProvider } from "./dataProviders/plots/LinearPlotProvider"; import { StackedPlotProvider } from "./dataProviders/plots/StackedPlotProvider"; import { WellborePicksProvider } from "./dataProviders/wellpicks/WellPicksProvider"; import { ContinuousLogTrack } from "./groups/ContinuousLogTrack"; +import { DiffPlotGroup } from "./groups/DiffPlotGroup"; import { DiscreteLogTrack } from "./groups/DiscreteLogTrack"; // ? It confuses me why there's a difference in registration of groups and providers? Why does one check an external enum, while the other does not? GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK_CONT, ContinuousLogTrack); GroupRegistry.registerGroup(GroupType.WELL_LOG_TRACK_DISC, DiscreteLogTrack); +GroupRegistry.registerGroup(GroupType.WELL_LOG_DIFF_GROUP, DiffPlotGroup); DataProviderRegistry.registerDataProvider(LinearPlotProvider.name, LinearPlotProvider); DataProviderRegistry.registerDataProvider(AreaPlotProvider.name, AreaPlotProvider); -DataProviderRegistry.registerDataProvider(DifferentialPlotProvider.name, DifferentialPlotProvider); +DataProviderRegistry.registerDataProvider(DiffPlotProvider.name, DiffPlotProvider); DataProviderRegistry.registerDataProvider(WellborePicksProvider.name, WellborePicksProvider); DataProviderRegistry.registerDataProvider(StackedPlotProvider.name, StackedPlotProvider); diff --git a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts index b426a2c06..0c6b10a98 100644 --- a/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts +++ b/frontend/src/modules/WellLogViewer/DataProviderFramework/visualizations/plots.ts @@ -1,6 +1,7 @@ import type { WellboreLogCurveData_api } from "@api"; import type { TemplatePlot } from "@modules/WellLogViewer/types"; import { isNumericalDataPoints } from "@modules/WellLogViewer/utils/queryDataTransform"; +import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; import type { Settings } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { @@ -17,7 +18,8 @@ import type { ColorMapFunction } from "@webviz/well-log-viewer/dist/components/C import _ from "lodash"; import { AreaPlotProvider, type AreaPlotSettingTypes } from "../dataProviders/plots/AreaPlotProvider"; -import { DifferentialPlotProvider } from "../dataProviders/plots/DiffPlotProvider"; +import type { DiffPlotSettingTypes } from "../dataProviders/plots/DiffPlotProvider"; +import { DiffPlotProvider } from "../dataProviders/plots/DiffPlotProvider"; import { LinearPlotProvider, type LinearPlotSettingTypes } from "../dataProviders/plots/LinearPlotProvider"; import type { StackedPlotSettingTypes } from "../dataProviders/plots/StackedPlotProvider"; import { StackedPlotProvider } from "../dataProviders/plots/StackedPlotProvider"; @@ -66,7 +68,6 @@ export function makeAreaPlotConfig(args: PlotVisualizationArgs): TemplatePlot | null { + if (!args.getData()) return null; + + const commonConfig = getCommonConfig(args); + + return { + ...commonConfig, + type: "differential", + // TODO: Some way to do over/under colors (aka; fill and fill2) + }; +} + export function makeStackedPlotConfig(args: PlotVisualizationArgs): TemplatePlot | null { if (!args.getData()) return null; @@ -146,16 +159,27 @@ export const plotDataAccumulator: ReduceAccumulatedDataFunction; +export type DiffVisualizationGroup = VisualizationGroup< + VisualizationTarget.WSC_WELL_LOG, + never, + never, + GroupType.WELL_LOG_DIFF_GROUP +>; + +export function isDiffPlotGroup( + item: VisualizationGroup | DataProviderVisualization, +): item is DiffVisualizationGroup { + if (item.itemType !== VisualizationItemType.GROUP) return false; + + return item.groupType === GroupType.WELL_LOG_DIFF_GROUP; +} export function isPlotVisualization( item: VisualizationGroup | DataProviderVisualization, ): item is PlotVisualization { if (item.itemType !== VisualizationItemType.DATA_PROVIDER_VISUALIZATION) return false; - return [ - AreaPlotProvider.name, - LinearPlotProvider.name, - DifferentialPlotProvider.name, - StackedPlotProvider.name, - ].includes(item.type); + return [AreaPlotProvider.name, LinearPlotProvider.name, DiffPlotProvider.name, StackedPlotProvider.name].includes( + item.type, + ); } diff --git a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx index f8ff0e757..bf796b82a 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/ProviderManagerComponentWrapper.tsx @@ -4,6 +4,7 @@ import { WellLogCurveTypeEnum_api } from "@api"; import type { WorkbenchSession } from "@framework/WorkbenchSession"; import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; import { AreaPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/AreaPlotProvider"; +import { DiffPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/DiffPlotProvider"; import { LinearPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/LinearPlotProvider"; import { StackedPlotProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/plots/StackedPlotProvider"; import { WellborePicksProvider } from "@modules/WellLogViewer/DataProviderFramework/dataProviders/wellpicks/WellPicksProvider"; @@ -20,7 +21,7 @@ import { Group } from "@modules/_shared/DataProviderFramework/framework/Group/Gr import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; import type { ItemGroup } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/entities"; -import { HorizontalRule, ShowChart, ViewDay } from "@mui/icons-material"; +import { HorizontalRule, MultilineChart, ShowChart, ViewDay } from "@mui/icons-material"; import { useQueryClient } from "@tanstack/react-query"; import { useAtom } from "jotai"; @@ -52,6 +53,7 @@ enum PlotActionIdents { AREA = "area", STACKED = "stacked", DIFF = "diff", + DIFF_CURVE = "diffCurve", } function usePersistedProviderManager( @@ -123,60 +125,84 @@ function usePersistedProviderManager( } function makeOptionsForGroup(group: ItemGroup): ActionGroup[] { - if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK_CONT) { - return [ - { - label: "Plots", - children: [ - { icon: , label: "Line plot", identifier: PlotActionIdents.LINE }, - { icon: , label: "Area plot", identifier: PlotActionIdents.AREA }, - { - icon: , - label: "Differential plot", - identifier: PlotActionIdents.DIFF, - }, - ], - }, - ]; - } - - if (group instanceof Group && group.getGroupType() === GroupType.WELL_LOG_TRACK_DISC) { - return [ - { - label: "Plots", - children: [ - { - icon: , - label: "Stacked plot", - identifier: PlotActionIdents.STACKED, - }, - ], - }, - ]; - } + switch (group instanceof Group && group.getGroupType()) { + case GroupType.WELL_LOG_TRACK_CONT: + return [ + { + label: "Plots", + children: [ + { + icon: , + label: "Line plot", + identifier: PlotActionIdents.LINE, + }, + { + // TODO: Newer versions of MUI has a "area chart" icon + icon: , + label: "Area plot", + identifier: PlotActionIdents.AREA, + }, + { + icon: , + label: "Differential plot", + identifier: PlotActionIdents.DIFF, + }, + ], + }, + ]; - return [ - { - label: "Log viewer", - children: [ + case GroupType.WELL_LOG_TRACK_DISC: + return [ { - label: "Continuous Track", - identifier: RootActionIdents.CONTINUOUS_TRACK, - icon: , + label: "Plots", + children: [ + { + icon: , + label: "Stacked plot", + identifier: PlotActionIdents.STACKED, + }, + ], }, + ]; + + case GroupType.WELL_LOG_DIFF_GROUP: + return [ { - label: "Discrete Track", - identifier: RootActionIdents.DISCRETE_TRACK, - icon: , + label: "Plots", + children: [ + { + icon: , + label: "Differential plot curve", + identifier: PlotActionIdents.DIFF_CURVE, + }, + ], }, + ]; + + default: + return [ { - label: "Well picks", - identifier: RootActionIdents.WELL_PICKS, - icon: , + label: "Log viewer", + children: [ + { + label: "Continuous Track", + identifier: RootActionIdents.CONTINUOUS_TRACK, + icon: , + }, + { + label: "Discrete Track", + identifier: RootActionIdents.DISCRETE_TRACK, + icon: , + }, + { + label: "Well picks", + identifier: RootActionIdents.WELL_PICKS, + icon: , + }, + ], }, - ], - }, - ]; + ]; + } } export type ProviderManagerComponentWrapperProps = { @@ -208,6 +234,27 @@ export function ProviderManagerComponentWrapper(props: ProviderManagerComponentW GroupRegistry.makeGroup(GroupType.WELL_LOG_TRACK_DISC, providerManager), ); + case PlotActionIdents.DIFF: { + const diffGroup = GroupRegistry.makeGroup(GroupType.WELL_LOG_DIFF_GROUP, providerManager); + + diffGroup.getGroupDelegate().appendChild( + // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available + DataProviderRegistry.makeDataProvider(DiffPlotProvider.name, providerManager), + ); + diffGroup.getGroupDelegate().appendChild( + // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available + DataProviderRegistry.makeDataProvider(DiffPlotProvider.name, providerManager), + ); + + return groupDelegate.appendChild(diffGroup); + } + + case PlotActionIdents.DIFF_CURVE: + return groupDelegate.appendChild( + // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available + DataProviderRegistry.makeDataProvider(DiffPlotProvider.name, providerManager), + ); + case PlotActionIdents.LINE: return groupDelegate.appendChild( // @ts-expect-error -- dumb workaround, waiting for circular dependency pr to be available diff --git a/frontend/src/modules/WellLogViewer/types.ts b/frontend/src/modules/WellLogViewer/types.ts index 2726bdae9..9051120da 100644 --- a/frontend/src/modules/WellLogViewer/types.ts +++ b/frontend/src/modules/WellLogViewer/types.ts @@ -11,6 +11,7 @@ import type { */ export type TemplatePlot = TemplatePlotSSC & { logName: string; + logName2?: string; }; export type TemplateTrack = Omit & { diff --git a/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts b/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts deleted file mode 100644 index 479dfa4fd..000000000 --- a/frontend/src/modules/WellLogViewer/utils/factoryProduct.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { WellboreTrajectory_api } from "@api"; -import type { ColorMapFunction } from "@webviz/well-log-viewer/dist/components/ColorMapFunction"; -import type { WellLogSet } from "@webviz/well-log-viewer/dist/components/WellLogTypes"; -import type { WellPickProps } from "@webviz/well-log-viewer/dist/components/WellLogView"; - -import _ from "lodash"; - -import { MAIN_AXIS_CURVE, createWellLogSets } from "./queryDataTransform"; -import { getUniqueCurveNameForPlotConfig } from "./strings"; -import { trajectoryToIntersectionReference } from "./trajectory"; -import type { WellLogFactoryProduct } from "./useLogViewerVisualizationProduct"; - -import { - COLOR_MAP_ACC_KEY, - DATA_ACC_KEY, - DUPLICATE_NAMES_ACC_KEY, - isPlotVisualization, -} from "../DataProviderFramework/visualizations/plots"; -import type { TrackVisualizationGroup } from "../DataProviderFramework/visualizations/tracks"; -import { isTrackGroup } from "../DataProviderFramework/visualizations/tracks"; -import { isWellPickVisualization } from "../DataProviderFramework/visualizations/wellpicks"; -import type { Template, TemplatePlot, TemplateTrack } from "../types"; - -export type LogViewerProps = { - padData: boolean; - horizontal: boolean; -}; - -function getProvidedPlots(trackGroup: TrackVisualizationGroup, duplicatedCurveNames: Set) { - const plots: TemplatePlot[] = []; - - for (const child of trackGroup.children) { - if (!isPlotVisualization(child)) continue; - - const template = child.visualization; - - plots.push({ - ...template, - // ! Map each plot to ensure a name that points to the correct curve - name: getUniqueCurveNameForPlotConfig(template, duplicatedCurveNames), - }); - } - - return plots; -} - -export function createWellLogTemplateFromProduct(factoryProduct: WellLogFactoryProduct | null): Template { - const tracks: TemplateTrack[] = []; - const accData = factoryProduct?.accumulatedData; - - const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); - - for (const child of factoryProduct?.children ?? []) { - if (!isTrackGroup(child)) continue; - - const templatePlots = getProvidedPlots(child, duplicatedCurveNames!); - const templateProps = child.customProps; - - tracks.push({ - ...templateProps, - plots: templatePlots, - }); - } - - return { - // AFAIK, this name is not show anywhere - name: "Well log viewer", - scale: { primary: MAIN_AXIS_CURVE.name, allowSecondary: true }, - tracks, - }; -} - -export function createWellLogJsonFromProduct( - factoryProduct: WellLogFactoryProduct | null, - wellboreTrajectory: WellboreTrajectory_api, - padDataWithEmptyRows?: boolean, -): WellLogSet[] { - if (!factoryProduct) return []; - - const accData = factoryProduct.accumulatedData; - const curveData = _.get(accData, DATA_ACC_KEY, []); - const duplicatedCurveNames = _.get(accData, DUPLICATE_NAMES_ACC_KEY); - - const referenceSystem = trajectoryToIntersectionReference(wellboreTrajectory); - - return createWellLogSets( - curveData, - wellboreTrajectory, - referenceSystem, - duplicatedCurveNames, - padDataWithEmptyRows, - ); -} - -export function createWellPickPropFromProduct(factoryProduct: WellLogFactoryProduct | null): WellPickProps | undefined { - if (!factoryProduct) return undefined; - - // ! We only take the - const wellpickVisualization = factoryProduct.children.find((child) => isWellPickVisualization(child)); - - // Can be used as is, no need for further transformations - return wellpickVisualization?.visualization; -} - -export function createColorMapDefsFromProduct(factoryProduct: WellLogFactoryProduct | null): ColorMapFunction[] { - if (!factoryProduct) return []; - - const accData = factoryProduct.accumulatedData; - const colorMapFuncDefs = _.get(accData, COLOR_MAP_ACC_KEY) ?? []; - - return colorMapFuncDefs; -} diff --git a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationProduct.ts b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationProduct.ts index a80f2760e..83c3e8200 100644 --- a/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationProduct.ts +++ b/frontend/src/modules/WellLogViewer/utils/useLogViewerVisualizationProduct.ts @@ -10,6 +10,7 @@ import type { import { VisualizationAssembler } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; import { AreaPlotProvider } from "../DataProviderFramework/dataProviders/plots/AreaPlotProvider"; +import { DiffPlotProvider } from "../DataProviderFramework/dataProviders/plots/DiffPlotProvider"; import { LinearPlotProvider } from "../DataProviderFramework/dataProviders/plots/LinearPlotProvider"; import { StackedPlotProvider } from "../DataProviderFramework/dataProviders/plots/StackedPlotProvider"; import { WellborePicksProvider } from "../DataProviderFramework/dataProviders/wellpicks/WellPicksProvider"; @@ -18,6 +19,7 @@ import { DiscreteLogTrack } from "../DataProviderFramework/groups/DiscreteLogTra import type { FactoryAccResult as PlotFactoryAccResult } from "../DataProviderFramework/visualizations/plots"; import { makeAreaPlotConfig, + makeDiffPlotConfig, makeLinePlotConfig, makeStackedPlotConfig, plotDataAccumulator, @@ -45,6 +47,10 @@ VISUALIZATION_FACTORY.registerDataProviderTransformers(AreaPlotProvider.name, Ar transformToVisualization: makeAreaPlotConfig, reduceAccumulatedData: plotDataAccumulator, }); +VISUALIZATION_FACTORY.registerDataProviderTransformers(DiffPlotProvider.name, DiffPlotProvider, { + transformToVisualization: makeDiffPlotConfig, + reduceAccumulatedData: plotDataAccumulator, +}); VISUALIZATION_FACTORY.registerDataProviderTransformers(StackedPlotProvider.name, StackedPlotProvider, { transformToVisualization: makeStackedPlotConfig, diff --git a/frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx index c1035f5a9..c36c40be4 100644 --- a/frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/ProviderManagerWrapper.tsx @@ -1,10 +1,12 @@ import React from "react"; import type { WellboreTrajectory_api } from "@api"; +import type { DiffVisualizationGroup } from "@modules/WellLogViewer/DataProviderFramework/visualizations/plots"; import { COLOR_MAP_ACC_KEY, DATA_ACC_KEY, DUPLICATE_NAMES_ACC_KEY, + isDiffPlotGroup, isPlotVisualization, } from "@modules/WellLogViewer/DataProviderFramework/visualizations/plots"; import { @@ -34,19 +36,40 @@ export type LogViewerProps = { horizontal: boolean; }; -function getProvidedPlots(trackGroup: TrackVisualizationGroup, duplicatedCurveNames: Set) { +function getProvidedPlots( + trackGroup: TrackVisualizationGroup | DiffVisualizationGroup, + duplicatedCurveNames: Set, +) { const plots: TemplatePlot[] = []; for (const child of trackGroup.children) { - if (!isPlotVisualization(child)) continue; - - const template = child.visualization; - - plots.push({ - ...template, - // ! Map each plot to ensure a name that points to the correct curve - name: getUniqueCurveNameForPlotConfig(template, duplicatedCurveNames), - }); + if (isPlotVisualization(child)) { + const template = child.visualization; + + plots.push({ + ...template, + // ! Map each plot to ensure a name that points to the correct curve + name: getUniqueCurveNameForPlotConfig(template, duplicatedCurveNames), + }); + } else if (isDiffPlotGroup(child)) { + // ! Recursively get this group's children + const [primaryPlot, secondaryPlot] = getProvidedPlots(child, duplicatedCurveNames); + + if (primaryPlot && secondaryPlot) { + plots.push({ + ...primaryPlot, + name: getUniqueCurveNameForPlotConfig(primaryPlot, duplicatedCurveNames), + name2: getUniqueCurveNameForPlotConfig(secondaryPlot, duplicatedCurveNames), + logName2: secondaryPlot.logName, + color2: secondaryPlot.color, + + // Over/under colors + // TODO: Make this based on a setting + fill: "#c50f0f", + fill2: "#0d8d1e", + }); + } + } } return plots; diff --git a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx index 98d47d2fa..e01d96aa0 100644 --- a/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx +++ b/frontend/src/modules/WellLogViewer/view/components/SubsurfaceLogViewerWrapper.tsx @@ -37,10 +37,6 @@ export type SubsurfaceLogViewerWrapperProps = { wellLogSets: WellLogSet[]; wellPicks?: WellPickProps; - // Data - // providerManager: DataProviderManager; - // trajectoryData: WellboreTrajectory_api; - // Viewer config viewerTemplate: Template; colorMapFunctions?: ColorMapFunction[]; @@ -142,24 +138,6 @@ function useCreateGlobalVerticalScaleBroadcastFunc( return broadcastVerticalScaleChange; } -// function useViewerDataTransform(props: SubsurfaceLogViewerWrapperProps) { -// const trajectoryData = props.trajectoryData; -// const padDataWithEmptyRows = props.padDataWithEmptyRows; - -// const factoryProduct = useLogViewerVisualizationProduct(props.providerManager); - -// const colorMapFuncDefs = React.useMemo(() => createColorMapDefsFromProduct(factoryProduct), [factoryProduct]); - -// const wellpicks = React.useMemo(() => createWellPickPropFromProduct(factoryProduct), [factoryProduct]); -// const template = React.useMemo(() => createWellLogTemplateFromProduct(factoryProduct), [factoryProduct]); -// const wellLogSets = React.useMemo( -// () => createWellLogJsonFromProduct(factoryProduct, trajectoryData, padDataWithEmptyRows), -// [factoryProduct, trajectoryData, padDataWithEmptyRows], -// ); - -// return { template, wellLogSets, wellpicks, colorMapFuncDefs }; -// } - export function SubsurfaceLogViewerWrapper(props: SubsurfaceLogViewerWrapperProps) { // uses an internal controller to change things like zoom, selection and so on. Use this when possible to avoid uneccessary re-renders const [wellLogController, setWellLogController] = React.useState(null); diff --git a/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts b/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts index 1312fd8ea..0934d5a1d 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/groups/groupTypes.ts @@ -2,4 +2,5 @@ export enum GroupType { VIEW = "VIEW", WELL_LOG_TRACK_CONT = "WELL_LOG_TRACK_CONTINUOUS", WELL_LOG_TRACK_DISC = "WELL_LOG_TRACK_DISCRETE", + WELL_LOG_DIFF_GROUP = "WELL_LOG_DIFF_GROUP", } From a21f56ff6591cbd7b1b7a036d9dfa6df7c9e09f1 Mon Sep 17 00:00:00 2001 From: Anders Rantala Hunderi Date: Tue, 15 Apr 2025 16:12:14 +0200 Subject: [PATCH 19/25] Cleaned up old setting code --- .../_shared/components/icons.tsx | 3 +- .../plotTypeOptions.ts => constants.ts} | 14 + .../src/modules/WellLogViewer/interfaces.ts | 10 +- .../WellLogViewer/settings/atoms/baseAtoms.ts | 5 - .../settings/atoms/derivedAtoms.ts | 148 +--------- .../settings/atoms/persistedAtoms.ts | 6 - .../settings/atoms/queryAtoms.ts | 71 +---- .../ContinousTrackSettings.tsx | 65 ----- .../DiscreteTrackSettings.tsx | 192 ------------ .../components/TemplateTrackSettings/index.ts | 1 - .../private-components/SortablePlotList.tsx | 273 ------------------ .../private-components/SortableTrackItem.tsx | 78 ----- .../private-components/TrackSettings.tsx | 67 ----- .../templateTrackSettings.tsx | 184 ------------ .../settings/components/ViewerSettings.tsx | 35 +-- .../settings/components/WellpickSelect.tsx | 136 --------- .../WellLogViewer/settings/settings.tsx | 7 +- frontend/src/modules/WellLogViewer/types.ts | 43 --- .../WellLogViewer/utils/logViewerTemplate.ts | 50 ---- .../modules/WellLogViewer/utils/queries.ts | 65 ----- .../WellLogViewer/utils/queryDataTransform.ts | 14 +- .../WellLogViewer/utils/settingsImport.ts | 61 ---- .../components/ProviderManagerWrapper.tsx | 3 +- .../src/modules/WellLogViewer/view/view.tsx | 5 - 24 files changed, 29 insertions(+), 1507 deletions(-) rename frontend/src/modules/WellLogViewer/{settings/components/TemplateTrackSettings/private-components/plotTypeOptions.ts => constants.ts} (61%) delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/ContinousTrackSettings.tsx delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/DiscreteTrackSettings.tsx delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/index.ts delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/private-components/SortablePlotList.tsx delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/private-components/SortableTrackItem.tsx delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/private-components/TrackSettings.tsx delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx delete mode 100644 frontend/src/modules/WellLogViewer/settings/components/WellpickSelect.tsx delete mode 100644 frontend/src/modules/WellLogViewer/utils/logViewerTemplate.ts delete mode 100644 frontend/src/modules/WellLogViewer/utils/queries.ts delete mode 100644 frontend/src/modules/WellLogViewer/utils/settingsImport.ts diff --git a/frontend/src/modules/WellLogViewer/_shared/components/icons.tsx b/frontend/src/modules/WellLogViewer/_shared/components/icons.tsx index c15e1cf12..67cb22452 100644 --- a/frontend/src/modules/WellLogViewer/_shared/components/icons.tsx +++ b/frontend/src/modules/WellLogViewer/_shared/components/icons.tsx @@ -1,8 +1,7 @@ import { WellLogCurveTypeEnum_api } from "@api"; -import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; import { ShowChart, ViewDay } from "@mui/icons-material"; -export function TrackIcon(props: { type: TemplateTrackConfig["_type"] }) { +export function TrackIcon(props: { type: WellLogCurveTypeEnum_api }) { if (props.type === WellLogCurveTypeEnum_api.CONTINUOUS) return ; else return ; } diff --git a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/private-components/plotTypeOptions.ts b/frontend/src/modules/WellLogViewer/constants.ts similarity index 61% rename from frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/private-components/plotTypeOptions.ts rename to frontend/src/modules/WellLogViewer/constants.ts index 44da27f80..957434b21 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/private-components/plotTypeOptions.ts +++ b/frontend/src/modules/WellLogViewer/constants.ts @@ -1,5 +1,6 @@ import type { DropdownOption } from "@lib/components/Dropdown"; import type { TemplatePlotType } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; +import type { WellLogCurve } from "@webviz/well-log-viewer/dist/components/WellLogTypes"; type PlotDropdownOption = DropdownOption; export const PLOT_TYPE_OPTIONS: PlotDropdownOption[] = [ @@ -10,3 +11,16 @@ export const PLOT_TYPE_OPTIONS: PlotDropdownOption[] = [ { value: "gradientfill", label: "Gradientfill" }, { value: "differential", label: "Differential" }, ]; +export const MAIN_AXIS_CURVE: WellLogCurve = { + name: "RKB", + unit: "M", + dimensions: 1, + valueType: "float", +}; + +export const SECONDARY_AXIS_CURVE: WellLogCurve = { + name: "MSL", + unit: "M", + dimensions: 1, + valueType: "float", +}; diff --git a/frontend/src/modules/WellLogViewer/interfaces.ts b/frontend/src/modules/WellLogViewer/interfaces.ts index c97d8d8df..c1d0a2f18 100644 --- a/frontend/src/modules/WellLogViewer/interfaces.ts +++ b/frontend/src/modules/WellLogViewer/interfaces.ts @@ -1,13 +1,9 @@ -import type { WellboreHeader_api, WellborePick_api } from "@api"; +import type { WellboreHeader_api } from "@api"; import type { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; import { providerManagerAtom } from "./settings/atoms/baseAtoms"; -import { - selectedFieldIdentifierAtom, - selectedWellboreHeaderAtom, - selectedWellborePicksAtom, -} from "./settings/atoms/derivedAtoms"; +import { selectedFieldIdentifierAtom, selectedWellboreHeaderAtom } from "./settings/atoms/derivedAtoms"; import { padDataWithEmptyRowsAtom, viewerHorizontalAtom } from "./settings/atoms/persistedAtoms"; export type InterfaceTypes = { @@ -21,7 +17,6 @@ export type SettingsToViewInterface = { wellboreHeader: WellboreHeader_api | null; viewerHorizontal: boolean; padDataWithEmptyRows: boolean; - selectedWellborePicks: WellborePick_api[]; }; export const settingsToViewInterfaceInitialization: InterfaceInitialization = { @@ -30,5 +25,4 @@ export const settingsToViewInterfaceInitialization: InterfaceInitialization get(selectedWellboreHeaderAtom), viewerHorizontal: (get) => get(viewerHorizontalAtom), padDataWithEmptyRows: (get) => get(padDataWithEmptyRowsAtom), - selectedWellborePicks: (get) => get(selectedWellborePicksAtom), }; diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts index ff688468d..71ee13a31 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/baseAtoms.ts @@ -3,10 +3,5 @@ import type { DataProviderManager } from "@modules/_shared/DataProviderFramework import { atom } from "jotai"; export const providerManagerAtom = atom(null); - export const userSelectedFieldIdentifierAtom = atom(null); export const userSelectedWellboreUuidAtom = atom(null); - -export const userSelectedWellPickColumnAtom = atom(null); -export const userSelectedWellPickInterpreterAtom = atom(null); -export const userSelectedWellPicksAtom = atom([]); diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts index e2b4b067f..6c759bb58 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts @@ -1,26 +1,9 @@ -import type { WellboreHeader_api, WellboreLogCurveHeader_api, WellborePick_api } from "@api"; -import { WellLogCurveTypeEnum_api } from "@api"; -import type { TemplatePlotConfig, TemplateTrackConfig } from "@modules/WellLogViewer/types"; -import { makeSelectValueForCurveHeader } from "@modules/WellLogViewer/utils/strings"; +import type { WellboreHeader_api } from "@api"; import { atom } from "jotai"; -import _ from "lodash"; -import { - userSelectedFieldIdentifierAtom, - userSelectedWellPickColumnAtom, - userSelectedWellPickInterpreterAtom, - userSelectedWellPicksAtom, - userSelectedWellboreUuidAtom, -} from "./baseAtoms"; -import { logViewerTrackConfigsAtom } from "./persistedAtoms"; -import { - availableFieldsQueryAtom, - drilledWellboreHeadersQueryAtom, - wellLogCurveHeadersQueryAtom, - wellborePicksQueryAtom, - wellboreStratColumnsQueryAtom, -} from "./queryAtoms"; +import { userSelectedFieldIdentifierAtom, userSelectedWellboreUuidAtom } from "./baseAtoms"; +import { availableFieldsQueryAtom, drilledWellboreHeadersQueryAtom } from "./queryAtoms"; export const selectedFieldIdentifierAtom = atom((get) => { const availableFields = get(availableFieldsQueryAtom).data ?? []; @@ -42,128 +25,3 @@ export const selectedWellboreHeaderAtom = atom((get) return availableWellboreHeaders.find((wh) => wh.wellboreUuid === selectedWellboreId) ?? availableWellboreHeaders[0]; }); - -/** - * @deprecated Handled by DataFramework instead - */ -export const availableContinuousCurvesAtom = atom((get) => { - const logCurveHeaders = get(wellLogCurveHeadersQueryAtom).data ?? []; - - return _.filter(logCurveHeaders, ["curveType", WellLogCurveTypeEnum_api.CONTINUOUS]); -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const availableDiscreteCurvesAtom = atom((get) => { - const logCurveHeaders = get(wellLogCurveHeadersQueryAtom).data ?? []; - - return _.filter(logCurveHeaders, ["curveType", WellLogCurveTypeEnum_api.DISCRETE]); -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const availableFlagCurvesAtom = atom((get) => { - const logCurveHeaders = get(wellLogCurveHeadersQueryAtom).data ?? []; - - return _.filter(logCurveHeaders, ["curveType", WellLogCurveTypeEnum_api.FLAG]); -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const wellLogTemplateTracksAtom = atom((get) => { - const templateTrackConfigs = get(logViewerTrackConfigsAtom); - - return templateTrackConfigs.map((config) => { - return { - ...config, - plots: config.plots.filter(({ _isValid }) => _isValid), - } as TemplateTrackConfig; - }); -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const selectedWellPickColumnAtom = atom((get) => { - const userSelectedWellPickColumn = get(userSelectedWellPickColumnAtom) ?? ""; - const wellboreStratColumns = get(wellboreStratColumnsQueryAtom).data ?? []; - - // Selection-fixup. Defaults to first available option if possible - if (!wellboreStratColumns.length) return null; - if (wellboreStratColumns.includes(userSelectedWellPickColumn)) return userSelectedWellPickColumn; - return wellboreStratColumns[0]; -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const wellPicksByInterpreterAtom = atom>((get) => { - const picks = get(wellborePicksQueryAtom).data ?? []; - - return _.groupBy(picks, "interpreter"); -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const availableWellPickInterpretersAtom = atom((get) => { - const wellPicksByInterpreter = get(wellPicksByInterpreterAtom); - return Object.keys(wellPicksByInterpreter); -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const selectedWellPickInterpreter = atom((get) => { - const selectedInterpreter = get(userSelectedWellPickInterpreterAtom); - const availableInterpreters = get(availableWellPickInterpretersAtom); - - // Selection-fixup. Defaults to first available option if possible - if (!availableInterpreters.length) return null; - if (selectedInterpreter && availableInterpreters.includes(selectedInterpreter)) return selectedInterpreter; - return availableInterpreters[0]; -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const selectedWellborePicksAtom = atom((get) => { - const wellPicksByInterpreter = get(wellPicksByInterpreterAtom); - const selectedInterpreter = get(selectedWellPickInterpreter); - const selectedWellPicks = get(userSelectedWellPicksAtom); - - if (!selectedInterpreter) return []; - - const interpreterPicks = wellPicksByInterpreter[selectedInterpreter] ?? []; - - return interpreterPicks.filter(({ pickIdentifier }) => selectedWellPicks.includes(pickIdentifier)); -}); - -/** - * @deprecated Handled by DataFramework instead - */ -export const missingCurvesAtom = atom((get) => { - const templateTracks = get(logViewerTrackConfigsAtom) ?? []; - const availableCurvesQuery = get(wellLogCurveHeadersQueryAtom); - if (availableCurvesQuery.isPending || availableCurvesQuery.isError) return []; - - // Prepare all header select values for faster lookup - const allCurveSelectValues = new Set(availableCurvesQuery.data.map(makeSelectValueForCurveHeader)); - - const missingCurves: WellboreLogCurveHeader_api[] = []; - - const allPlots = _.flatMap(templateTracks, "plots") as TemplatePlotConfig[]; - - allPlots.forEach(({ _curveHeader, _curveHeader2 }) => { - const selectValue1 = makeSelectValueForCurveHeader(_curveHeader); - const selectValue2 = makeSelectValueForCurveHeader(_curveHeader2); - - if (_curveHeader && !allCurveSelectValues.has(selectValue1)) missingCurves.push(_curveHeader!); - if (_curveHeader2 && !allCurveSelectValues.has(selectValue2)) missingCurves.push(_curveHeader2!); - }); - - return missingCurves; -}); diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts index 2173f5234..f1acc5a63 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/persistedAtoms.ts @@ -1,4 +1,3 @@ -import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; import { atomWithModuleInstanceStorage, clearModuleInstanceStorage } from "@modules/WellLogViewer/utils/atoms"; import type { SerializedDataProviderManager } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/serialization"; @@ -20,11 +19,6 @@ function setPersistentModuleField(get: Getter, set: Setter, valueKey: string, ne set(moduleSettingsAtom, storageCopy); } -export const logViewerTrackConfigsAtom = atom( - (get) => getPersistentModuleField(get, "logViewerTrackConfigs", []), - (get, set, newVal) => setPersistentModuleField(get, set, "logViewerTrackConfigs", newVal), -); - export const viewerHorizontalAtom = atom( (get) => getPersistentModuleField(get, "viewerHorizontal", false), (get, set, newVal) => setPersistentModuleField(get, set, "viewerHorizontal", newVal), diff --git a/frontend/src/modules/WellLogViewer/settings/atoms/queryAtoms.ts b/frontend/src/modules/WellLogViewer/settings/atoms/queryAtoms.ts index e9d1b78a6..bf1e65ad2 100644 --- a/frontend/src/modules/WellLogViewer/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/WellLogViewer/settings/atoms/queryAtoms.ts @@ -1,20 +1,8 @@ -import type { StratigraphicColumn_api, WellboreLogCurveHeader_api } from "@api"; -import { - WellLogCurveSourceEnum_api, - getDrilledWellboreHeadersOptions, - getFieldsOptions, - getWellboreLogCurveHeadersOptions, - getWellborePicksInStratColumnOptions, - getWellboreStratigraphicColumnsOptions, -} from "@api"; -import { atomWithQueries } from "@framework/utils/atomUtils"; -import { mergeResults } from "@modules/WellLogViewer/utils/queries"; -import type { QueryObserverResult } from "@tanstack/react-query"; +import { getDrilledWellboreHeadersOptions, getFieldsOptions } from "@api"; import { atomWithQuery } from "jotai-tanstack-query"; -import _ from "lodash"; -import { selectedFieldIdentifierAtom, selectedWellPickColumnAtom, selectedWellboreHeaderAtom } from "./derivedAtoms"; +import { selectedFieldIdentifierAtom } from "./derivedAtoms"; export const availableFieldsQueryAtom = atomWithQuery(() => { return getFieldsOptions(); @@ -28,58 +16,3 @@ export const drilledWellboreHeadersQueryAtom = atomWithQuery((get) => { enabled: Boolean(fieldId), }; }); - -/* ! Note - No logs are returned for any of the Drogon wells, afaik. Found a working set using in one of the TROLL ones. Some of them are still on the old system, so just click around until you find a working one -*/ -export const wellLogCurveHeadersQueryAtom = atomWithQueries((get) => { - const wellboreUuid = get(selectedWellboreHeaderAtom)?.wellboreUuid ?? ""; - const sources = [ - WellLogCurveSourceEnum_api.SSDL_WELL_LOG, - WellLogCurveSourceEnum_api.SMDA_GEOLOGY, - WellLogCurveSourceEnum_api.SMDA_STRATIGRAPHY, - ]; - - // We *could* fetch all headers within a single query, but doing them seperately here for parallelism - return { - queries: sources.map((source) => () => ({ - ...getWellboreLogCurveHeadersOptions({ - query: { - wellbore_uuid: wellboreUuid, - sources: [source], - }, - }), - enabled: Boolean(wellboreUuid), - })), - // Flatten the result so we get a single list of headers - combine(results: QueryObserverResult[]) { - return mergeResults(results, (data) => data.flat()); - }, - }; -}); - -export const wellboreStratColumnsQueryAtom = atomWithQuery((get) => { - const wellboreUuid = get(selectedWellboreHeaderAtom)?.wellboreUuid ?? ""; - - return { - ...getWellboreStratigraphicColumnsOptions({ query: { wellbore_uuid: wellboreUuid } }), - enabled: Boolean(wellboreUuid), - select: (data: StratigraphicColumn_api[]): string[] => _.map(data, "identifier"), - }; -}); - -export const wellborePicksQueryAtom = atomWithQuery((get) => { - const selectedWellboreUuid = get(selectedWellboreHeaderAtom)?.wellboreUuid ?? ""; - - const selectedStratColumn = get(selectedWellPickColumnAtom) ?? ""; - - return { - ...getWellborePicksInStratColumnOptions({ - query: { - wellbore_uuid: selectedWellboreUuid, - strat_column: selectedStratColumn, - }, - }), - enabled: Boolean(selectedWellboreUuid), - }; -}); diff --git a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/ContinousTrackSettings.tsx b/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/ContinousTrackSettings.tsx deleted file mode 100644 index 9cd167f17..000000000 --- a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/ContinousTrackSettings.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; - -import { Dropdown, type DropdownOption } from "@lib/components/Dropdown"; -import { PendingWrapper } from "@lib/components/PendingWrapper"; -import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePropagateApiErrorToStatusWriter"; -import type { UseQueryResult } from "@tanstack/react-query"; -import type { TemplatePlotScale } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes"; - -import { useAtomValue } from "jotai"; - -import { SortablePlotList } from "./private-components/SortablePlotList"; -import type { TrackSettingFragmentProps } from "./private-components/TrackSettings"; - -import { availableContinuousCurvesAtom, availableFlagCurvesAtom } from "../../atoms/derivedAtoms"; -import { wellLogCurveHeadersQueryAtom } from "../../atoms/queryAtoms"; - -type TemplatePlotScaleOption = DropdownOption; - -const PLOT_SCALE_OPTIONS: TemplatePlotScaleOption[] = [ - { label: "Linear", value: "linear" }, - { label: "Logarithmic", value: "log" }, -]; - -export function ContinousTrackSettings(props: TrackSettingFragmentProps): React.ReactNode { - const dropdownId = React.useId(); - - const curveHeadersQuery = useAtomValue(wellLogCurveHeadersQueryAtom); - - const curveHeadersError = usePropagateApiErrorToStatusWriter( - // ! Cast is safe, since MergedQueryResult includes `.error` - curveHeadersQuery as UseQueryResult, - props.statusWriter, - ); - - const availableCurveHeaders = [ - ...useAtomValue(availableContinuousCurvesAtom), - ...useAtomValue(availableFlagCurvesAtom), - ]; - - return ( - <> - - - id={dropdownId} - value={props.trackConfig.scale} - options={PLOT_SCALE_OPTIONS} - filter={false} - onChange={(val) => { - if (!val) props.onFieldChange({ scale: undefined }); - else props.onFieldChange({ scale: val }); - }} - /> - -
- - props.onFieldChange({ plots: plots })} - /> - -
- - ); -} diff --git a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/DiscreteTrackSettings.tsx b/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/DiscreteTrackSettings.tsx deleted file mode 100644 index 77a2a7ac6..000000000 --- a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/DiscreteTrackSettings.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import React from "react"; - -import type { WellboreLogCurveHeader_api } from "@api"; -import { WellLogCurveSourceEnum_api, WellLogCurveTypeEnum_api } from "@api"; -import { Checkbox } from "@lib/components/Checkbox"; -import type { DropdownOption } from "@lib/components/Dropdown"; -import { Dropdown } from "@lib/components/Dropdown"; -import { PendingWrapper } from "@lib/components/PendingWrapper"; -import { RadioGroup } from "@lib/components/RadioGroup"; -import type { SelectOption } from "@lib/components/Select"; -import { Select } from "@lib/components/Select"; -import type { TemplatePlotConfig } from "@modules/WellLogViewer/types"; -import { makeTrackPlot } from "@modules/WellLogViewer/utils/logViewerTemplate"; -import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePropagateApiErrorToStatusWriter"; -import { ArrowDownward } from "@mui/icons-material"; -import type { UseQueryResult } from "@tanstack/react-query"; - -import { useAtomValue } from "jotai"; -import _ from "lodash"; - -import type { TrackSettingFragmentProps } from "./private-components/TrackSettings"; - -import { - curveSourceToText, - findCurveHeaderBySelectValue, - makeSelectValueForCurveHeader, - simplifyLogName, -} from "../../../utils/strings"; -import { availableDiscreteCurvesAtom, availableFlagCurvesAtom } from "../../atoms/derivedAtoms"; -import { wellLogCurveHeadersQueryAtom } from "../../atoms/queryAtoms"; - -const DEFAULT_SOURCE = WellLogCurveSourceEnum_api.SMDA_GEOLOGY; - -export function DiscreteTrackSettings(props: TrackSettingFragmentProps): React.ReactNode { - const { onFieldChange } = props; - - // Make sure the config type is correct - if (props.trackConfig._type !== WellLogCurveTypeEnum_api.DISCRETE) { - throw new Error("Expected track-config to be of type "); - } - - // Guaranteed to have one plot entry - const discretePlotConfig = props.trackConfig.plots[0]; - const currentCurveHeader = discretePlotConfig._curveHeader; - - const showLinesCheckId = React.useId(); - const showLabelsCheckId = React.useId(); - const labelRotationId = React.useId(); - - const categorySelectId = React.useId(); - const curveSelectId = React.useId(); - - const [activeSource, setActiveSource] = React.useState(currentCurveHeader?.source ?? DEFAULT_SOURCE); - - const curveHeadersQuery = useAtomValue(wellLogCurveHeadersQueryAtom); - const curveHeadersError = usePropagateApiErrorToStatusWriter( - // ! Cast is safe, since MergedQueryResult includes `.error` - curveHeadersQuery as UseQueryResult, - props.statusWriter, - ); - - const availableDiscreteCurves = useAtomValue(availableDiscreteCurvesAtom); - const availableFlagCurves = useAtomValue(availableFlagCurvesAtom); - const availableCurveHeaders = React.useMemo( - () => [...availableDiscreteCurves, ...availableFlagCurves], - [availableDiscreteCurves, availableFlagCurves], - ); - - const headersForSource = availableCurveHeaders.filter((header) => activeSource === header.source); - - const categoryOptions = makeLogSourceOptions(availableCurveHeaders); - const selectOptions = makeCurveOptions(activeSource, headersForSource); - - const handleCurveHeaderSelect = React.useCallback( - function handleCurveHeaderSelect([choice]: string[]) { - const headerChoice = findCurveHeaderBySelectValue(headersForSource, choice); - if (!headerChoice) throw new Error(`Selected value '${choice}' not found`); - - const newTrackPlot = makeTrackPlot({ - ...discretePlotConfig, - _curveHeader: headerChoice, - }); - - onFieldChange({ plots: [newTrackPlot] }); - }, - [onFieldChange, discretePlotConfig, headersForSource], - ); - - const handlePlotSettingsChange = React.useCallback( - function handlePlotSettingsChange(changes: Partial) { - const newPlot = makeTrackPlot({ ...discretePlotConfig, ...changes }); - - onFieldChange({ plots: [newPlot] }); - }, - [discretePlotConfig, onFieldChange], - ); - - return ( - <> - - handlePlotSettingsChange({ showLines: e.target.checked })} - /> - - - handlePlotSettingsChange({ showLabels: e.target.checked })} - /> - - - - , - value: 0, - }, - { - label: , - value: 270, - }, - ]} - onChange={(_e, v) => handlePlotSettingsChange({ labelRotation: v })} - /> - - - - - - - -
- - - applyConfigChange({ title: val })} - /> - - - applyConfigChange({ width: Number(val) })} - /> - - {makeTypeSpecificFragment({ - trackConfig: props.trackConfig, - statusWriter: props.statusWriter, - onFieldChange: applyConfigChange, - })} -
- ); -} - -function makeTypeSpecificFragment(props: TrackSettingFragmentProps) { - if (props.trackConfig._type === "discrete") return ; - else return ; -} diff --git a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx b/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx deleted file mode 100644 index 400fae2b1..000000000 --- a/frontend/src/modules/WellLogViewer/settings/components/TemplateTrackSettings/templateTrackSettings.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import React from "react"; - -import { WellLogCurveTypeEnum_api } from "@api"; -import type { SettingsStatusWriter } from "@framework/StatusWriter"; -import { Menu } from "@lib/components/Menu"; -import { MenuItem } from "@lib/components/MenuItem"; -import type { SelectOption } from "@lib/components/Select"; -import { SortableList } from "@lib/components/SortableList"; -import { arrayMove } from "@lib/utils/arrays"; -import { TrackIcon } from "@modules/WellLogViewer/_shared/components/icons"; -import type { TemplateTrackConfig } from "@modules/WellLogViewer/types"; -import { makeTrackPlot } from "@modules/WellLogViewer/utils/logViewerTemplate"; -import { configToJsonDataBlob, jsonFileToTrackConfigs } from "@modules/WellLogViewer/utils/settingsImport"; -import { Dropdown, MenuButton } from "@mui/base"; -import { FileDownload, FileUpload, MoreVert } from "@mui/icons-material"; - -import { useAtom } from "jotai"; -import { v4 } from "uuid"; - -import { SortableTrackItem } from "./private-components/SortableTrackItem"; - -import { logViewerTrackConfigsAtom } from "../../atoms/persistedAtoms"; -import { AddItemButton } from "../AddItemButton"; - -interface TemplateTrackSettingsProps { - statusWriter: SettingsStatusWriter; -} - -type TrackSelectOption = SelectOption; -const TRACK_OPTIONS: TrackSelectOption[] = [ - { - label: "Continous", - value: WellLogCurveTypeEnum_api.CONTINUOUS, - adornment: , - }, - { - label: "Discrete", - value: WellLogCurveTypeEnum_api.DISCRETE, - adornment: , - }, -]; - -export function TemplateTrackSettings(props: TemplateTrackSettingsProps): React.ReactNode { - const [trackConfigs, setTrackConfigs] = useAtom(logViewerTrackConfigsAtom); - const jsonImportInputRef = React.useRef(null); - - const handleNewPlotTrack = React.useCallback( - function handleNewPlotTrack(type: TrackSelectOption["value"]) { - const newConfig = createNewConfig(`Plot track #${trackConfigs.length + 1}`, type); - - setTrackConfigs([...trackConfigs, newConfig]); - }, - [setTrackConfigs, trackConfigs], - ); - - const handleDeleteTrack = React.useCallback( - function handleDeleteTrack(track: TemplateTrackConfig) { - setTrackConfigs(trackConfigs.filter((configs) => configs._key !== track._key)); - }, - [setTrackConfigs, trackConfigs], - ); - - const handleEditTrack = React.useCallback( - function handleEditTrack(updatedItem: TemplateTrackConfig) { - const newConfigs = trackConfigs.map((tc) => (tc._key === updatedItem._key ? updatedItem : tc)); - - setTrackConfigs(newConfigs); - }, - [setTrackConfigs, trackConfigs], - ); - - const handleTrackMove = React.useCallback( - function handleTrackMove( - movedItemId: string, - originId: string | null, - destinationId: string | null, - newPosition: number, - ) { - const currentPosition = trackConfigs.findIndex((cfg) => cfg._key === movedItemId); - const newTrackCfg = arrayMove(trackConfigs, currentPosition, newPosition); - - setTrackConfigs(newTrackCfg); - }, - [setTrackConfigs, trackConfigs], - ); - - const encodedConfigJsonUrl = React.useMemo(() => configToJsonDataBlob(trackConfigs), [trackConfigs]); - - const handleConfigJsonImport = React.useCallback( - async function readUploadedFile(evt: React.ChangeEvent) { - const file = evt.target.files?.item(0); - if (!file) return console.warn("No file given"); - - try { - const newConfig = await jsonFileToTrackConfigs(file); - - setTrackConfigs(newConfig); - } catch (error) { - console.warn("Invalid JSON content"); - console.error(error); - let errorMsg = "Unkown error"; - if (typeof error === "string") errorMsg = "error"; - if (error instanceof Error) errorMsg = error.message; - - window.alert("Invalid JSON content\n\n" + errorMsg); - } - }, - [setTrackConfigs], - ); - - return ( -
-
- - -
Plot Tracks
- - - - - - - - - - Export JSON - - - jsonImportInputRef.current?.click()}> - Import JSON - - - -
- -
- - {trackConfigs.map((config) => ( - - ))} - -
-
- ); -} - -function createNewConfig(title: string, type: TemplateTrackConfig["_type"]): TemplateTrackConfig { - if (type === WellLogCurveTypeEnum_api.DISCRETE) { - return { - _key: v4(), - _type: type, - scale: "linear", - width: 3, - title, - plots: [makeTrackPlot({ _curveHeader: null, type: "stacked" })], - }; - } - - return { - _key: v4(), - _type: type, - scale: "linear", - width: 3, - title, - plots: [] as ReturnType[], - }; -} diff --git a/frontend/src/modules/WellLogViewer/settings/components/ViewerSettings.tsx b/frontend/src/modules/WellLogViewer/settings/components/ViewerSettings.tsx index 752fa9336..d74758008 100644 --- a/frontend/src/modules/WellLogViewer/settings/components/ViewerSettings.tsx +++ b/frontend/src/modules/WellLogViewer/settings/components/ViewerSettings.tsx @@ -1,36 +1,16 @@ -import React from "react"; +import type React from "react"; -import type { SettingsStatusWriter } from "@framework/StatusWriter"; import { Checkbox } from "@lib/components/Checkbox"; import { Label } from "@lib/components/Label"; import { useAtom } from "jotai"; -import { WellpickSelect } from "./WellpickSelect"; - -import { userSelectedWellPicksAtom } from "../atoms/baseAtoms"; import { padDataWithEmptyRowsAtom, viewerHorizontalAtom } from "../atoms/persistedAtoms"; -export type ViewerSettingsProps = { - statusWriter: SettingsStatusWriter; -}; - -export function ViewerSettings(props: ViewerSettingsProps): React.ReactNode { +export function ViewerSettings(): React.ReactNode { // Well log selection const [horizontal, setHorizontal] = useAtom(viewerHorizontalAtom); const [padWithEmptyRows, setPadWithEmptyRows] = useAtom(padDataWithEmptyRowsAtom); - const [userSelectedWellPicks, setUserSelectedWellPicks] = useAtom(userSelectedWellPicksAtom); - const [addingWellpicks, setAddingWellpicks] = React.useState(!!userSelectedWellPicks.length); - - const onAddWellpickChange = React.useCallback( - function onAddWellpickChange(_evt: unknown, checked: boolean) { - // Reset selections if unchecked - if (!checked) setUserSelectedWellPicks([]); - - setAddingWellpicks(checked); - }, - [setUserSelectedWellPicks], - ); return (
@@ -42,17 +22,6 @@ export function ViewerSettings(props: ViewerSettingsProps): React.ReactNode { - - - {addingWellpicks && ( -
- -
- )}
); } diff --git a/frontend/src/modules/WellLogViewer/settings/components/WellpickSelect.tsx b/frontend/src/modules/WellLogViewer/settings/components/WellpickSelect.tsx deleted file mode 100644 index 7d358f0a2..000000000 --- a/frontend/src/modules/WellLogViewer/settings/components/WellpickSelect.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from "react"; - -import type { SettingsStatusWriter } from "@framework/StatusWriter"; -import type { DropdownOption } from "@lib/components/Dropdown"; -import { Dropdown } from "@lib/components/Dropdown"; -import { PendingWrapper } from "@lib/components/PendingWrapper"; -import type { SelectOption, SelectProps } from "@lib/components/Select"; -import { Select } from "@lib/components/Select"; -import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePropagateApiErrorToStatusWriter"; - -import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import _ from "lodash"; - -import { - userSelectedWellPickColumnAtom, - userSelectedWellPickInterpreterAtom, - userSelectedWellPicksAtom, -} from "../atoms/baseAtoms"; -import { - availableWellPickInterpretersAtom, - selectedWellPickColumnAtom, - selectedWellPickInterpreter, - wellPicksByInterpreterAtom, -} from "../atoms/derivedAtoms"; -import { wellborePicksQueryAtom, wellboreStratColumnsQueryAtom } from "../atoms/queryAtoms"; - -export type WellpickSelectProps = { - statusWriter: SettingsStatusWriter; -} & Omit; - -export function WellpickSelect(props: WellpickSelectProps): React.ReactNode { - // Wellpick query status - const wellPicksQuery = useAtomValue(wellborePicksQueryAtom); - const pickQueryError = usePropagateApiErrorToStatusWriter(wellPicksQuery, props.statusWriter); - - // Strat column selection - const stratColumnDropdownOptions = useStratColumnOptions(); - const selectedWellPickColumn = useAtomValue(selectedWellPickColumnAtom); - const setSelectedWellPickColumn = useSetAtom(userSelectedWellPickColumnAtom); - - // Pick interpreter selection - const interpreterOptions = usePickInterpreterOptions(); - const selectedInterpreter = useAtomValue(selectedWellPickInterpreter); - const setUserSelectedInterpreter = useSetAtom(userSelectedWellPickInterpreterAtom); - - // Wellpick selection - const pickOptions = useWellPickOptions(); - const [userSelectedWellPicks, setUserSelectedWellPicks] = useAtom(userSelectedWellPicksAtom); - const handleChangeUnitPicks = React.useCallback( - function handleChangeUnitPicks(value: string[]) { - // Allow the user to de-select if they click the already chosen value - const newVal = _.isEqual(value, userSelectedWellPicks) ? [] : value; - - setUserSelectedWellPicks(newVal); - }, - [userSelectedWellPicks, setUserSelectedWellPicks], - ); - - return ( - <> -
- Strat column - { - if (v === selectedWellPickColumn) return; - setUserSelectedWellPicks([]); - setSelectedWellPickColumn(v); - }} - /> - - {!!interpreterOptions.length && ( - <> - {/* ! Ensure that this label isn't the longest one, to avoid layout-shifts */} - Interpreter - { - if (v === selectedInterpreter) return; - setUserSelectedWellPicks([]); - setUserSelectedInterpreter(v); - }} - /> - - )} - Well picks - -