This Widgets Interaction tutorial demonstrates how the WellLog widget can interact with the Cross Plot and Histogram widget. All these widgets are created separately and added to the one canvas. Multiple layers in one canvas can be synchronized automatically because they are located in the same plot, correlating the depths of points in the WellLog with the corresponding depths of points in the Cross Plot. If the widgets are located in different plots, then the scale can be synchronized similar to plot synchronization.
In the Widgets below, the tools like polygon selection and selection tool can be used to select specific data points. These selected points will be marked in the WellLog Widget. The Rubber band zoom tool helps to further analyse these points.
import "@int/geotoolkit/src/geotoolkit/report/Parser.ts";
import { from } from "@int/geotoolkit/selection/from.ts";
import { KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { Point } from "@int/geotoolkit/util/Point.ts";
import { Events as CrossHairEvents } from "@int/geotoolkit/controls/tools/CrossHair.ts";
import { Events as WellLogEvents } from "@int/geotoolkit/welllog/widgets/Events.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { Events as SplitterEvents, Splitter } from "@int/geotoolkit/controls/tools/splitter/Splitter.ts";
import { LogCurve } from "@int/geotoolkit/welllog/LogCurve.ts";
import { HorizontalBoxLayout } from "@int/geotoolkit/layout/HorizontalBoxLayout.ts";
import { VerticalBoxLayout } from "@int/geotoolkit/layout/VerticalBoxLayout.ts";
import { Events as SelectionEvents } from "@int/geotoolkit/controls/tools/Selection.ts";
import { SelectionMode } from "@int/geotoolkit/controls/tools/SelectionMode.ts";
import { Events as AbstractToolEvents } from "@int/geotoolkit/controls/tools/AbstractTool.ts";
import { Events as PolygonSelectionEvents } from "@int/geotoolkit/controls/tools/PolygonSelection.ts";
import { ReportML } from "/src/code/WellLog/AdditionalFunctionality/WidgetsInteraction/reports/ReportML.ts";
import { defaultHighlightCallback } from "@int/geotoolkit/charts/widgets/ChartWidget.ts";
import { NodeOrder } from "@int/geotoolkit/scene/CompositeNode.ts";
import { Events as DomEvents } from "@int/geotoolkit/dom.ts";
import { Grid } from "@int/geotoolkit/axis/Grid.ts";
import { PseudoClass } from "@int/geotoolkit/css/CssStyle.ts";
import { Transformation } from "@int/geotoolkit/util/Transformation.ts";
import { Orientation } from "@int/geotoolkit/util/Orientation.ts";
import { initializeWellLogWidget } from "/src/code/WellLog/AdditionalFunctionality/WidgetsInteraction/widgets/welllog.ts";
import { Data } from "/src/code/WellLog/AdditionalFunctionality/WidgetsInteraction/data/data.ts";
import { initializeCrossPlotWidget } from "/src/code/WellLog/AdditionalFunctionality/WidgetsInteraction/widgets/crossplot.ts";
import { initializeHistogramWidget } from "/src/code/WellLog/AdditionalFunctionality/WidgetsInteraction/widgets/histogram.ts";
import { exportToPdf } from "/src/code/WellLog/AdditionalFunctionality/WidgetsInteraction/reports/exportToPdf.ts";
import { SelectedItem } from "@int/geotoolkit/selection/SelectedItem.ts";
import { CrossPlotChart } from "@int/geotoolkit/charts/CrossPlotChart.ts";
function getDepths(dataSource, indices, offset) {
const dataSourceDepths = dataSource.getDepths();
const depths = [];
for (let i = 0; i < indices.length; i++) {
depths.push(dataSourceDepths[indices[i] + offset]);
}
return depths;
}
const __tmpTransformation = new Transformation();
class WidgetsInteraction {
constructor(setRubberBandSelectionButton, setPolygonSelectionButton, setZoomButton) {
this._wellLogWidget = null;
this._crossPlotWidget = null;
this._histogramWidget = null;
this._activeHistogram = null;
this._prevOffset = 0;
this._offset = 0;
this.setRubberBandSelectionButton = setRubberBandSelectionButton;
this.setPolygonSelectionButton = setPolygonSelectionButton;
this.setZoomButton = setZoomButton;
this._data = new Data();
}
synchronizeDataViewLimits() {
const limits = this._wellLogWidget.getVisibleDepthLimits();
const fromIndex = this._data.getLongestLogDataSource().getIndexAt(limits.getLow()) || 0;
const toIndex = this._data.getLongestLogDataSource().getIndexAt(limits.getHigh()) || 0;
this._data.updateLimitsFilter(fromIndex, toIndex);
const selection = this._data.getChartDataSource().getSelection().getSelection(PseudoClass.Selection);
const result = {};
for (const series in selection) {
result[series] = selection[series].map((sample) => this._offset + sample - fromIndex);
}
this._offset = fromIndex;
this._data.getChartDataSource().getSelection().setSelection(result, PseudoClass.Selection);
}
initializeWellLog() {
const widget = initializeWellLogWidget(this._data);
widget.getToolByName("cross-hair").setEnabled(true).on(CrossHairEvents.onPositionChanged, (event, sender, args) => {
const position = args.getPosition();
if (isNaN(position.getY()) || isNaN(position.getX())) {
this._crossPlotWidget.getToolByName("cross-hair").setPosition(new Point(NaN, NaN));
return;
}
const index = this._data.getLogDataSource("GR").getIndexAt(position.getY());
const pos = [];
[this._data.getLogDataSource("GR"), this._data.getLogDataSource("CALI")].forEach((logData) => {
pos.push(logData.getValue(index));
});
const crossPlotModelLimits = this._crossPlotWidget.getChart("crossplot").getOverlay().getModelLimits();
const crossHairModelLimits = this._crossPlotWidget.getManipulatorLayer().getModelLimits();
const crossHairPosition = new Point(pos[0], pos[1]);
Transformation.getRectToRectInstance(crossPlotModelLimits, crossHairModelLimits, false, true, false, __tmpTransformation);
__tmpTransformation.transformPoint(crossHairPosition, crossHairPosition);
this._crossPlotWidget.getToolByName("cross-hair").setPosition(crossHairPosition);
});
widget.getToolByName("rubberband").setAutoDisabled(true).on(AbstractToolEvents.onEnabledStateChanged, (event, sender) => {
this.setZoomButton(sender.isEnabled());
});
widget.on(WellLogEvents.VisibleDepthLimitsChanged, () => {
this._prevOffset = this._offset;
this.synchronizeDataViewLimits();
});
return widget;
}
initializeCrossPlot(settings) {
const crossPlotWidget = initializeCrossPlotWidget(this._data, settings);
const selectionTool = crossPlotWidget.getToolByName("pick");
selectionTool.setAutoDisabled(true).on(SelectionEvents.onSelectionChanged, (evt, sender, eventArgs) => {
const selection = eventArgs.getSelection();
for (let i = 0; i < selection.length; i++) {
const item = selection[i];
if (item instanceof SelectedItem && item.getData(CrossPlotChart)) {
const data = item.getData(CrossPlotChart);
this.selectCrossPlotIndices(data["GR"]);
break;
}
}
}).setSelectionMode(SelectionMode.RubberBand).on(AbstractToolEvents.onEnabledStateChanged, (event, sender) => {
this.setRubberBandSelectionButton(sender.isEnabled());
});
crossPlotWidget.getToolByName("polygon-selection").on(PolygonSelectionEvents.onSelectionEnd, (evt, sender, eventArgs) => {
const selection = eventArgs.getSelection();
for (let i = 0; i < selection.length; i++) {
const item = selection[i];
if (item instanceof SelectedItem && item.getData(CrossPlotChart)) {
const data = item.getData(CrossPlotChart);
this.selectCrossPlotIndices(data["GR"]);
break;
}
}
}).on(AbstractToolEvents.onEnabledStateChanged, (event, sender) => {
this.setPolygonSelectionButton(sender.isEnabled());
});
return crossPlotWidget;
}
selectCrossPlotIndices(indices, reset) {
const selection = this._data.getChartDataSource().getSelection();
if (reset)
selection.clear({ "state": PseudoClass.Selection });
if (indices.length > 0) {
selection.select({
"GR": indices,
"CALI": indices,
"SP": indices
}, PseudoClass.Selection);
}
this.updateHighlightedWellLogDepths();
}
activateHistogram(histogram) {
if (this._activeHistogram === histogram) {
return this;
}
this._activeHistogram = histogram;
const logCurve = from(this._wellLogWidget).where((node) => node instanceof LogCurve).where((node) => node.getDataSource().getName() === histogram).selectFirst();
this._wellLogWidget.getToolByName("pick").setSelection([logCurve]);
from(this._histogramWidget.getModel()).where((node) => node instanceof Grid).select((grid) => {
this._histogramWidget.getModel().changeChildOrder(grid.getParent(), NodeOrder.First);
});
this._histogramWidget.changeChartOrder(this._histogramWidget.getChart(histogram), NodeOrder.Last);
return this;
}
initializeHistogram(settings) {
const highlightedId = "GR";
const widget = initializeHistogramWidget(this._data, settings);
widget.setProperties({
"highlight": {
"callback": (items, event, args) => {
switch (event) {
case DomEvents.Hover:
case DomEvents.Leave:
defaultHighlightCallback.call(widget, items, event, args);
break;
case DomEvents.Click:
if (items.length === 0) {
this.resetSelection();
return;
}
const item = items[0];
const selection = widget.getDataSource().getSelection();
const name = item.getSender().getName();
selection.toggle(item.getData(), PseudoClass.Selection);
this.updateHighlightedWellLogDepths();
this.activateHistogram(name);
}
}
}
});
this._activeHistogram = highlightedId;
widget.changeChartOrder(widget.getChart(highlightedId), NodeOrder.Last);
return widget;
}
disableRubberBand() {
this._wellLogWidget.getToolByName("rubberband").setEnabled(false);
}
zoomIn() {
this._wellLogWidget.scale(5 / 4);
this.disableRubberBand();
}
zoomOut() {
this._wellLogWidget.scale(4 / 5);
this.disableRubberBand();
}
fitToHeight() {
this._wellLogWidget.fitToHeight();
this.disableRubberBand();
}
rubberBandZoom() {
this._wellLogWidget.getToolByName("rubberband").setEnabled(true);
}
updateHighlightedWellLogDepths() {
const selection = this._data.getChartDataSource().getSelection().getSelection(PseudoClass.Selection) || {};
const indices = [].concat(...Object.values(selection)).sort((a, b) => a - b);
for (let i = 1; i < indices.length; ) {
if (indices[i] === indices[i - 1]) {
indices.splice(i, 1);
} else {
i++;
}
}
this._wellLogWidget.setDepthMarkers(getDepths(this._data.getLogDataSource("GR"), indices, this._offset), "rgba(255, 0, 0, 0.5)");
}
toggleRubberBandSelection() {
this._crossPlotWidget.getToolByName("pick").toggle();
this._crossPlotWidget.getToolByName("polygon-selection").setEnabled(false);
}
togglePolygonSelection() {
this._crossPlotWidget.getToolByName("polygon-selection").toggle();
this._crossPlotWidget.getToolByName("pick").setEnabled(false);
}
resetSelection() {
this.selectCrossPlotIndices([], true);
}
initialize(canvas) {
this._plot = new Plot({
"canvaselement": canvas,
"root": new Group().setAutoModelLimitsMode(true).setLayout(new HorizontalBoxLayout()).addChild([
this._wellLogWidget = this.initializeWellLog().setMarginsStyle({
"right": "5px"
}).setLayoutStyle({
"width": "300px"
}),
this._widgetsContainer = new Group().setLayout(new VerticalBoxLayout()).setLayoutStyle({
"width": "495px"
}).addChild([
this._crossPlotWidget = this.initializeCrossPlot({
"x": "GR",
"y": "CALI",
"z": "SP"
}).setLayoutStyle({
"height": "50%"
}),
this._histogramWidget = this.initializeHistogram({
"GR": {
"color": KnownColors.Green
},
"CALI": {
"color": KnownColors.Orange
},
"SP": {
"color": KnownColors.Blue
}
}).setLayoutStyle({
"height": "50%"
})
])
])
});
this._wellLogWidget.setHeaderHeight("auto").fitToHeight().fitToWidth();
this._wellLogWidget.getToolByName("splitter").setEnabled(false);
this._plot.getTool().add([
new Splitter({
"mouseradius": 5,
"name": "vertical-splitter",
"orientation": Orientation.Vertical,
"layer": this._plot.getRoot()
}).setPlots([this._wellLogWidget, this._widgetsContainer]).on(SplitterEvents.onPlotSizeChanged, () => {
this._wellLogWidget.fitToWidth();
}),
new Splitter({
"name": "horizontal-splitter",
"orientation": Orientation.Horizontal,
"layer": this._widgetsContainer
}).setPlots([this._crossPlotWidget, this._histogramWidget])
]);
}
exportToPDF(options, printHighlights) {
let oldSelection = null;
return exportToPdf(options, (cgDomDocument) => {
if (!printHighlights) {
oldSelection = this._data.getChartDataSource().getSelection();
this._data.getChartDataSource().removeSelection();
}
const highlightedDepths = printHighlights ? this._wellLogWidget.getDepthMarkers() : null;
const wellLogNode = from(cgDomDocument).where((node) => node instanceof ReportML.Nodes.WellLog).selectFirst();
if (wellLogNode != null) {
const template = this._wellLogWidget.saveTemplate();
const templateJSON = JSON.parse(template);
wellLogNode.setDataSources({
"GR": this._data.getLogDataSource("GR"),
"CALI": this._data.getLogDataSource("CALI"),
"SP": this._data.getLogDataSource("SP")
}).setDepthLimits(this._wellLogWidget.getVisibleDepthLimits()).setVisibleDepthLimits(this._wellLogWidget.getVisibleDepthLimits()).setTemplate(templateJSON).setDepthMarkers(highlightedDepths);
}
const dataViews = {
"GR": this._data.getDataView("GR"),
"CALI": this._data.getDataView("CALI"),
"SP": this._data.getDataView("SP")
};
const crossPlotNode = from(cgDomDocument).where((node) => node instanceof ReportML.Nodes.CrossPlot).selectFirst();
if (crossPlotNode != null) {
const crossPlotModelLimits = this._crossPlotWidget.getChart("crossplot").getOverlay().getModelLimits().clone();
crossPlotNode.setModelLimits(crossPlotModelLimits);
crossPlotNode.setDataSource(this._data);
}
const multiHistogramsNode = from(cgDomDocument).where((node) => node instanceof ReportML.Nodes.MultiHistogram).selectFirst();
if (multiHistogramsNode != null) {
const histogramHighlight = {};
for (const name in dataViews) {
histogramHighlight[name] = printHighlights ? this._data.getChartDataSource().getSelection().getIndices(name, PseudoClass.Selection) : [];
}
multiHistogramsNode.setDataSource(this._data).setHighlight(histogramHighlight).setActiveHistogram(this._activeHistogram);
}
return cgDomDocument;
}).finally(() => {
if (oldSelection) {
this._data.getChartDataSource().setSelection(oldSelection);
}
});
}
getPlot() {
return this._plot;
}
}
function createScene(canvas, setRubberBandSelectionButton, setPolygonSelectionButton, setZoomButton) {
const app = new WidgetsInteraction(setRubberBandSelectionButton, setPolygonSelectionButton, setZoomButton);
app.initialize(canvas);
return app;
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'), this.setRubberBandSelectionButton, this.setPolygonSelectionButton, this.setZoomButton);
# WellLog Widget Initialization
The following code shows how to initialize WellLog Widget tools to interact with other Widgets.
# Cross Plot Widget Initialization
The following code shows how to initialize Cross Plot tools interact with WellLog Widget.