Visuals Editing

The following canvas demonstrates the tool for creating, moving and resizing visuals. This tool works with WellLog Widgets and allows for easy visual interactive manipulation of Log Visuals. The tool works in both live editing and ghost mode, allows styling of handles, and fires all the events required for full control over user actions. The modes work as follows:

  • Edit Mode - Allows moving and resizing along the depth axis of the track. The tool will fire events every time the handle has been captured, dragged, or released by the user.
  • Insert Mode - Allows inserting new visuals into a track. The tool then will take over the mouse events, and a ghost representing the position of insertion will be dragged along the mouse pointer. The tool will fire events every time a move occurs.
  • Delete Mode - Allows deleting existing visuals from a track.

Note that adjacent lithology ranges are bound to each other. I.e. changing some range geometry will affect the adjacent ranges. To unbound ranges use Shift button.

Information about the events, styling the handles, and other properties for the tool can be found in: geotoolkit.welllog.widgets.tools.WellLogVisualsEditingTool class documentation

import { KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { HeaderType as LogAxisVisualHeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { TrackType as WellLogTrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { LogAnnotation } from "@int/geotoolkit/welllog/LogAnnotation.ts";
import { EditEvents } from "@int/geotoolkit/controls/tools/EditEvents.ts";
import { Layer } from "@int/geotoolkit/scene/Layer.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { from } from "@int/geotoolkit/selection/from.ts";
import { EditMode } from "@int/geotoolkit/controls/tools/EditMode.ts";
import { CssStyle } from "@int/geotoolkit/css/CssStyle.ts";
import { LogMarker } from "@int/geotoolkit/welllog/LogMarker.ts";
import { LabelFillMode, LineType, LogLithology, NameOrientation } from "@int/geotoolkit/welllog/LogLithology.ts";
import { SquarePainter } from "@int/geotoolkit/scene/shapes/painters/SquarePainter.ts";
import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { Events, Events as SelectionEvents } from "@int/geotoolkit/controls/tools/Selection.ts";
import { LogAbstractVisual } from "@int/geotoolkit/welllog/LogAbstractVisual.ts";
import { LithologyAttributeCallback } from "/src/code/WellLog/utils/editing/LithologyAttributeCallback.ts";
import { VisualsEditor } from "/src/code/WellLog/utils/editing/VisualsEditor.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
import { LogAnnotationEditor } from "@int/geotoolkit/welllog/widgets/tools/html/LogAnnotationEditor.ts";
import { LogLithologyEditor } from "@int/geotoolkit/welllog/widgets/tools/html/LogLithologyEditor.ts";
const LITHOLOGY_MIN_DEVICE_HEIGHT = 5;
const activeStyle = new CssStyle(`
    .geotoolkit.scene.shapes.SymbolShape:active {
        fillstyle: #00ff00;
    }
`);
const ghostStyles = {
  "fillstyle": "rgba(255, 0, 0, 0.3)",
  "linestyle": {
    "color": KnownColors.DarkRed,
    "width": 1,
    "pattern": [7, 3]
  }
};
const boundingHandleStyles = {
  "linestyle": {
    "color": KnownColors.DarkBlue,
    "pattern": [3, 3],
    "pixelsnapmode": true
  }
};
const handleStyles = {
  "bbox": {
    "painter": SquarePainter,
    "alignment": AnchorType.Center,
    "sizeisindevicespace": true,
    "width": 7,
    "height": 7,
    "fillstyle": {
      "color": "white",
      "shadow": {
        "enable": true,
        "color": "rgba(0,0,0,0.3)",
        "offsetx": 1,
        "offsety": 1
      }
    },
    "linestyle": {
      "color": "#7a7a7a",
      "pixelsnapmode": true
    }
  }
};
const depths = [98, 150, 230, 310];
const lithologyNames = ["Default Range \u21161", "Default Range \u21162", "Default Range \u21163"];
const fillStyles = [
  KnownColors.Green,
  KnownColors.Grey,
  KnownColors.Yellow
];
const lineTypes = [
  LineType.DISCONTINUE,
  LineType.DISCONTINUE,
  LineType.CONTINUE,
  LineType.DISCONTINUE
];
class VisualsEditing {
  constructor(setEditButton) {
    this.setEditButton = setEditButton;
    this.setEditButton = setEditButton;
    this._widget = null;
  }
  createBasicWellLogWidget() {
    const widget = createWellLogWidget().setAxisHeaderType(LogAxisVisualHeaderType.Simple).setDepthLimits(50, 400);
    widget.addTrack(WellLogTrackType.IndexTrack);
    widget.addTrack(WellLogTrackType.LinearTrack).addChild([
      new LogMarker().setDepth(200).setLineStyle(KnownColors.Green)
    ]);
    widget.addTrack(WellLogTrackType.IndexTrack);
    widget.addTrack(WellLogTrackType.LinearTrack).addChild([
      new LogAnnotation({
        "bounds": new Rect(0, 200, 1, 300),
        "text": "Text1",
        "textstyle": "#7cb342",
        "fillstyle": "#fdd835",
        "linestyle": {
          "color": "gray",
          "pixelsnapmode": true
        },
        "fixedheight": false
      })
    ]);
    widget.addTrack(WellLogTrackType.IndexTrack);
    widget.addTrack(WellLogTrackType.LinearTrack).addChild([
      new LogLithology({
        "depths": depths,
        "fillstyles": fillStyles,
        "linetypes": lineTypes,
        "names": lithologyNames,
        "nameorientation": NameOrientation.Regular,
        "labelfillmode": LabelFillMode.NONE
      })
    ]);
    widget.addTrack(WellLogTrackType.IndexTrack);
    return widget;
  }
  addWidgetToCanvas(canvas, widget) {
    this._widget = widget;
    this._plot = new Plot({
      "canvaselement": canvas,
      "root": widget
    });
    this._widget.fitToWidth().fitToHeight();
    const manipulatorLayer = new Layer({
      "id": "manipulator.layer",
      "cssstyle": activeStyle
    });
    widget.getTrackManipulatorLayer().addChild(manipulatorLayer);
    this._wellLogEditor = this.initializeEditingTool(manipulatorLayer, widget);
    const selectionTool = this._widget.getToolByName("pick");
    selectionTool.on(SelectionEvents.onSelectionEnd, (evt, sender, eventArgs) => {
      if ((this.getMode() & EditMode.CreateWithBounds) !== 0)
        return;
      const selection = eventArgs.getSelection();
      if (selection == null || selection.length === 0)
        return;
      let selected = null;
      for (let i = selection.length - 1; i >= 0; i--) {
        const node = selection[i];
        if (node instanceof LogAbstractVisual) {
          selected = node;
          break;
        }
      }
      this._wellLogEditor.editNode(selected);
    });
    this._widget.getTool().insert(0, this._wellLogEditor);
    this.initializeInlineEditor(widget);
    this.setMode(EditMode.EditBbox);
  }
  initializeEditingTool(manipulatorLayer, widget) {
    const editorOpts = {
      "mindeviceheight": LITHOLOGY_MIN_DEVICE_HEIGHT,
      "attributecallback": LithologyAttributeCallback,
      "handles": handleStyles,
      "bbox": boundingHandleStyles,
      "ghost": ghostStyles
    };
    return new VisualsEditor({
      "layer": manipulatorLayer,
      "widget": widget,
      "editor": editorOpts
    }).on(EditEvents.CommandApplying, (evt, sender, args) => {
      const visual = sender.getShape();
      if (!(visual instanceof LogAnnotation)) {
        return;
      }
      const command = args.getCommand();
      switch (command.getEventName()) {
        case EditEvents.Start:
        case EditEvents.Continue:
        case EditEvents.Translated:
        case EditEvents.Resized:
          const editor = sender.getEditor();
          const track = visual.getTrack();
          const deviceTr = editor.getDeviceTransform();
          const devBounds = deviceTr.transformRect(editor.getBounds());
          if (!this.checkCollision(visual, track, devBounds)) {
            command.reject();
          }
      }
    }).on(EditEvents.End, () => {
      this.setEditButton(true);
    });
  }
  initializeInlineEditor(widget) {
    widget.getToolByName("pick").on(Events.onDoubleClick, (event, sender, eventArgs) => {
      const logVisual = sender.getSelection().filter((node) => node instanceof LogAbstractVisual)[0];
      if (!(logVisual instanceof LogAnnotation) && !(logVisual instanceof LogLithology)) {
        return;
      }
      const plot = eventArgs.getPlot();
      const plotPoint = eventArgs.getPlotPoint();
      if (logVisual instanceof LogAnnotation) {
        LogAnnotationEditor.editVisual(logVisual, plot, plotPoint);
      }
      if (logVisual instanceof LogLithology) {
        LogLithologyEditor.editVisual(logVisual, plot, plotPoint);
      }
      eventArgs.stopPropagation();
    });
  }
  execute(command) {
    this._wellLogEditor.getHistory().push(command);
  }
  getEditingTool() {
    return this._wellLogEditor;
  }
  setInsertMode(nodeId, options) {
    this._wellLogEditor.insertNode(nodeId, options);
  }
  getMode() {
    return this._wellLogEditor.getEditMode();
  }
  setMode(value) {
    this._wellLogEditor.setEditMode(value);
  }
  toggleFreeResizeMode() {
    this._wellLogEditor.setFreeResizeModeEnabled(!this._wellLogEditor.isFreeResizeModeEnabled());
  }
  toggleGhostMode() {
    let mode = this.getMode();
    mode ^= EditMode.Ghost;
    this._wellLogEditor.setEditMode(mode);
  }
  undo() {
    this._wellLogEditor.undo();
  }
  redo() {
    this._wellLogEditor.redo();
  }
  getUIOptions() {
    return {
      canUndo: this._wellLogEditor.canUndo(),
      canRedo: this._wellLogEditor.canRedo()
    };
  }
  getHistory() {
    return this._wellLogEditor.getHistory();
  }
  getPlot() {
    return this._plot;
  }
  checkCollision(visual, track, deviceBounds) {
    const intersect = from(track).where(
      (node) => node instanceof LogAnnotation && node !== visual && node.getSceneTransform().transformRect(node.getRect()).intersects(deviceBounds)
    ).selectFirst() != null;
    return !intersect;
  }
}
function createScene(canvas, setEditButton, update) {
  const app = new VisualsEditing(setEditButton || (() => true));
  const widget = app.createBasicWellLogWidget();
  app.addWidgetToCanvas(canvas, widget);
  app.getHistory().on(EditEvents.CommandApplied, update);
  return app;
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'), this?.setEditButton, this.updateUI.bind(this));

# Visuals Editor

The following code snippet shows how to manage the Insert Mode and set proper editing modes.