Last updated

Basic Tools

This tutorial shows how Tools add user interaction and scene control to CarnacJS scenes. The following Tools are explained in detail:

  1. CrossHair — add a crosshair cursor that tracks the mouse
  2. Panning — translate the scene as the mouse moves
  3. Zoom — zoom the scene as the mouse wheel rolls
  4. RubberBand — zoom the scene according to a user drawn rubber band
  5. DOMSupport — provides DOM features into geotoolkit scene
  6. Measure — measures distances or areas on the scene

Each of these examples have some common elements. Firstly, the basic scene is created as an AnnotatedNode. This node has associated special Groups: a model and north, east, south and west axis annotations. Please see the Annotated Nodes tutorial for more information.

The examples use the geotoolkit/controls/tools/Tools. The Tools module must be used to construct the ToolsContaineron geotoolkit/plot/Plot. The container dispatches events from the Plot to the Tools.

The examples create a scenegraph node: geotoolkit/scene/Layer. This layer is added as a child of the AnnotatedNode model. The Tool uses the Layer as a place to keep shapes that render user controls or animations.

It is also possible to change mouse cursor for different tools.

# CrossHair

The CrossHair Tool example adds a crosshair cursor that tracks the mouse. The settings variable configures the display of the CrossHair. This variable has sections for the center, north, east, south and west of the CrossHair. In this example, a rectangle with the cursor position is displayed at the CrossHair center, north and west. This variable also configures the line styles and fill styles used for the CrossHair.


.cg-tooltip-holder {
  position: relative;
}

.cg-tooltip {
  position: absolute;
  display: block;
  padding: 2px 12px 3px 7px;
  overflow: visible !important;
  font-family: Roboto, Helvetica, Arial, sans-serif;
  font-size: 13px;
  background: white !important;
  opacity: 0.9;
  color: #333333;
  border: solid 1px gray;
  border-radius: 5px;
  text-align: left;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
  border-radius: 5px;
  margin: 0 !important;
  z-index: 10000;
  max-width: 400px;
  text-wrap: normal !important;
  white-space: normal !important;
}
/* Default setting for tooltip */
.cg-tooltip-container {
  position: absolute;
  display: block;
  overflow: visible !important;
  font-family: Roboto, Helvetica, Arial, sans-serif;
  font-size: 12px;
  padding: 3px 7px;
  background: #f7f7f7;
  color: #333333;
  border: 1px solid #938e8e;
  opacity: 0.8;
  text-align: left;
  box-shadow: 3px 3px 10px #888;
  margin: 0 !important;
  z-index: 10000;
  max-width: 400px;
  text-wrap: normal !important;
  white-space: normal !important;
  user-select: none;
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
  .cg-tooltip-container {
    border-radius: 0;
  }
}
/* Default left arrow for tooltip */
.cg-tooltip-arrow-left::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 0;
  top: 50%;
  border: 5px solid transparent;
  border-left: 0;
  border-right: 5px solid  #938e8e;
  transform: translate(calc(-100%), -50%);
}
.cg-tooltip-arrow-left::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 0;
  top: 50%;
  border: 4px solid transparent;
  border-left: 0;
  border-right: 4px solid #f7f7f7;
  transform: translate(calc(-100%), -50%);
}
/* Default top arrow for tooltip */
.cg-tooltip-arrow-top::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  top: 0;
  border: 5px solid transparent;
  border-top: 0;
  border-bottom: 5px solid #938e8e;
  transform: translate(-50%, -100%);
}
.cg-tooltip-arrow-top::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  top: 0;
  border: 4px solid transparent;
  border-top: 0;
  border-bottom: 4px solid #f7f7f7;
  transform: translate(-50%, -100%);
}
/* Default right arrow for tooltip */
.cg-tooltip-arrow-right::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  right: 0;
  top: 50%;
  border: 5px solid transparent;
  border-right: 0;
  border-left: 5px solid #938e8e;
  transform: translate(100%, -50%);
}
.cg-tooltip-arrow-right::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  right: 0;
  top: 50%;
  border: 4px solid transparent;
  border-right: 0;
  border-left: 4px solid #f7f7f7;
  transform: translate(100%, -50%);
}
/* Default bottom arrow for tooltip */
.cg-tooltip-arrow-bottom::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  bottom: 0px;
  border: 5px solid transparent;
  border-bottom: 0;
  border-top: 5px solid #938e8e;
  transform: translate(-50%, 100%);
  z-index: 10000;
}
.cg-tooltip-arrow-bottom::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  bottom: 0;
  border: 4px solid transparent;
  border-bottom: 0;
  border-top: 4px solid #f7f7f7;
  transform: translate(-50%, 100%);
  z-index: 10000;
}
/* Tooltip item name */
/* Tooltip item value */
/* .cg-tooltip-item-value */
/* Tooltip item value */
.cg-tooltip-item-unit {
  text-transform: none;
}

.cg-tooltip-item-name {
    text-transform: capitalize;
    white-space: nowrap;
    vertical-align: middle;
    font-size: 13px;
}
.cg-tooltip-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  white-space: pre-wrap;
  font-size: 12px;
  line-height: 100%;
  margin: 1px 0;
}
.cg-tooltip-title {
  font-size: 13px;
  height: 14px;
  text-transform: capitalize;
}
.cg-tooltip-title .cg-tooltip-symbol {
  margin-right: 0 !important;
}
.cg-tooltip-title-name {
  vertical-align: middle;
}
.cg-tooltip-row + .cg-tooltip-row {
  margin-top: 4px;
}
.cg-tooltip-row.cg-tooltip-title + .cg-tooltip-row {
  margin-top: 5px;
}
.cg-tooltip-item-value + .cg-tooltip-item-unit {
    margin-left: 1px;
}
/* Tooltip symbol */
.cg-tooltip-symbol-cell {
  display: inline-flex;
  min-width: 13px; /* 10px size + 3px margin */
}
.cg-tooltip-symbol {
  margin-right: 3px;
  background-color: transparent;
  display: block;
}
.cg-tooltip-symbol > img {
  display: block;
}
.cg-tooltip-list-cell {
  display: inline-flex;
}
.cg-tooltip-list-symbol {
  display: block;
  margin-right: 3px;
  width: 6px;
  height: 6px;
  vertical-align: middle;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.4);
}
.cg-tooltip-symbol-legacy {
  border-radius: 4px;
  margin-right: 5px;
  height: 8px;
  width: 8px;
  display: inline-block;
}
.cg-tooltip-title-legacy {
  font-weight: 900;
}

/* Tooltip symbol circle */
.cg-tooltip-symbol.circle {
  height: 10px;
  width: 10px;
  display: inline-block;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.4);
}
/* Tooltip symbol line */
.cg-tooltip-symbol.line {
    height: 10px;
    width: 10px;
    display: inline-block;
    transform: scale(1.2, 0.2);
}
/* Tooltip symbol diamond */
.cg-tooltip-symbol.diamond {
    height: 10px;
    width: 10px;
    display: inline-block;
    transform: rotate(45deg);
    border-radius: 0px;
}
/* Tooltip symbol square */
.cg-tooltip-symbol.square {
    height: 10px;
    width: 10px;
    display: inline-block;
    border-radius: 0px;
    border: 1px solid rgba(0,0,0,.4);
}

# Custom CrossHair

The Custom CrossHair Tool demonstrates that you can change rendering type from Carnac shape to HTML DIV element to display tooltip labels out of canvas limits.

import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { LabelDisplayMode } from "@int/geotoolkit/controls/tools/CrossHair.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { createAnnotatedWidget } from "/src/code/Carnac/Tools/BasicTools/createModel.ts";
function createScene(canvas) {
  const annotatedWidget = createAnnotatedWidget();
  annotatedWidget.getToolByName("cross-hair").setProperties({
    "enabled": true,
    "center": {
      "visible": true,
      "displaymode": LabelDisplayMode.DivElement,
      "tooltip": {
        "cssclasses": "cg-tooltip cg-tooltip-center"
      },
      "alignment": AnchorType.LeftBottom,
      "offset": "5px",
      "textconverter": (x, y) => "[" + Math.round(x) + " x " + Math.round(y) + "]"
    },
    "north": {
      "visible": true,
      "displaymode": LabelDisplayMode.DivElement,
      "alignment": AnchorType.BottomCenter,
      "offset": "5px"
    },
    "west": {
      "visible": true,
      "displaymode": LabelDisplayMode.DivElement,
      "alignment": AnchorType.RightCenter,
      "offset": "5px"
    },
    "linestyle": {
      "color": "darkgray",
      "width": 1,
      "pattern": [6, 4],
      "pixelsnapmode": {
        x: true,
        y: true
      }
    }
  });
  return new Plot({
    "canvaselement": canvas,
    "root": annotatedWidget
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Panning

The Panning tool supports events shown in the Events enum from geotoolkit/controls/tools/Panning and it has several built-in functions to customize the tool. For more details, please refer to geotoolkit/controls/tools/Panning.


.cg-tooltip-holder {
  position: relative;
}

.cg-tooltip {
  position: absolute;
  display: block;
  padding: 2px 12px 3px 7px;
  overflow: visible !important;
  font-family: Roboto, Helvetica, Arial, sans-serif;
  font-size: 13px;
  background: white !important;
  opacity: 0.9;
  color: #333333;
  border: solid 1px gray;
  border-radius: 5px;
  text-align: left;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
  border-radius: 5px;
  margin: 0 !important;
  z-index: 10000;
  max-width: 400px;
  text-wrap: normal !important;
  white-space: normal !important;
}
/* Default setting for tooltip */
.cg-tooltip-container {
  position: absolute;
  display: block;
  overflow: visible !important;
  font-family: Roboto, Helvetica, Arial, sans-serif;
  font-size: 12px;
  padding: 3px 7px;
  background: #f7f7f7;
  color: #333333;
  border: 1px solid #938e8e;
  opacity: 0.8;
  text-align: left;
  box-shadow: 3px 3px 10px #888;
  margin: 0 !important;
  z-index: 10000;
  max-width: 400px;
  text-wrap: normal !important;
  white-space: normal !important;
  user-select: none;
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
  .cg-tooltip-container {
    border-radius: 0;
  }
}
/* Default left arrow for tooltip */
.cg-tooltip-arrow-left::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 0;
  top: 50%;
  border: 5px solid transparent;
  border-left: 0;
  border-right: 5px solid  #938e8e;
  transform: translate(calc(-100%), -50%);
}
.cg-tooltip-arrow-left::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 0;
  top: 50%;
  border: 4px solid transparent;
  border-left: 0;
  border-right: 4px solid #f7f7f7;
  transform: translate(calc(-100%), -50%);
}
/* Default top arrow for tooltip */
.cg-tooltip-arrow-top::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  top: 0;
  border: 5px solid transparent;
  border-top: 0;
  border-bottom: 5px solid #938e8e;
  transform: translate(-50%, -100%);
}
.cg-tooltip-arrow-top::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  top: 0;
  border: 4px solid transparent;
  border-top: 0;
  border-bottom: 4px solid #f7f7f7;
  transform: translate(-50%, -100%);
}
/* Default right arrow for tooltip */
.cg-tooltip-arrow-right::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  right: 0;
  top: 50%;
  border: 5px solid transparent;
  border-right: 0;
  border-left: 5px solid #938e8e;
  transform: translate(100%, -50%);
}
.cg-tooltip-arrow-right::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  right: 0;
  top: 50%;
  border: 4px solid transparent;
  border-right: 0;
  border-left: 4px solid #f7f7f7;
  transform: translate(100%, -50%);
}
/* Default bottom arrow for tooltip */
.cg-tooltip-arrow-bottom::before {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  bottom: 0px;
  border: 5px solid transparent;
  border-bottom: 0;
  border-top: 5px solid #938e8e;
  transform: translate(-50%, 100%);
  z-index: 10000;
}
.cg-tooltip-arrow-bottom::after {
  content: '';
  position: absolute;
  display: block;
  width: 0px;
  left: 50%;
  bottom: 0;
  border: 4px solid transparent;
  border-bottom: 0;
  border-top: 4px solid #f7f7f7;
  transform: translate(-50%, 100%);
  z-index: 10000;
}
/* Tooltip item name */
/* Tooltip item value */
/* .cg-tooltip-item-value */
/* Tooltip item value */
.cg-tooltip-item-unit {
  text-transform: none;
}

.cg-tooltip-item-name {
    text-transform: capitalize;
    white-space: nowrap;
    vertical-align: middle;
    font-size: 13px;
}
.cg-tooltip-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  white-space: pre-wrap;
  font-size: 12px;
  line-height: 100%;
  margin: 1px 0;
}
.cg-tooltip-title {
  font-size: 13px;
  height: 14px;
  text-transform: capitalize;
}
.cg-tooltip-title .cg-tooltip-symbol {
  margin-right: 0 !important;
}
.cg-tooltip-title-name {
  vertical-align: middle;
}
.cg-tooltip-row + .cg-tooltip-row {
  margin-top: 4px;
}
.cg-tooltip-row.cg-tooltip-title + .cg-tooltip-row {
  margin-top: 5px;
}
.cg-tooltip-item-value + .cg-tooltip-item-unit {
    margin-left: 1px;
}
/* Tooltip symbol */
.cg-tooltip-symbol-cell {
  display: inline-flex;
  min-width: 13px; /* 10px size + 3px margin */
}
.cg-tooltip-symbol {
  margin-right: 3px;
  background-color: transparent;
  display: block;
}
.cg-tooltip-symbol > img {
  display: block;
}
.cg-tooltip-list-cell {
  display: inline-flex;
}
.cg-tooltip-list-symbol {
  display: block;
  margin-right: 3px;
  width: 6px;
  height: 6px;
  vertical-align: middle;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.4);
}
.cg-tooltip-symbol-legacy {
  border-radius: 4px;
  margin-right: 5px;
  height: 8px;
  width: 8px;
  display: inline-block;
}
.cg-tooltip-title-legacy {
  font-weight: 900;
}

/* Tooltip symbol circle */
.cg-tooltip-symbol.circle {
  height: 10px;
  width: 10px;
  display: inline-block;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.4);
}
/* Tooltip symbol line */
.cg-tooltip-symbol.line {
    height: 10px;
    width: 10px;
    display: inline-block;
    transform: scale(1.2, 0.2);
}
/* Tooltip symbol diamond */
.cg-tooltip-symbol.diamond {
    height: 10px;
    width: 10px;
    display: inline-block;
    transform: rotate(45deg);
    border-radius: 0px;
}
/* Tooltip symbol square */
.cg-tooltip-symbol.square {
    height: 10px;
    width: 10px;
    display: inline-block;
    border-radius: 0px;
    border: 1px solid rgba(0,0,0,.4);
}

# Rubber Band

The RubberBand tool supports events shown in the Events enum from geotoolkit/controls/tools/RubberBand and it has several built-in functions to customize the tool, such as final view selection (see geotoolkit/controls/tools/RubberBandRenderMode), draw styles and others. For more details, please refer to geotoolkit/controls/tools/RubberBand.

import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Events as RubberBandEvents, RubberBand } from "@int/geotoolkit/controls/tools/RubberBand.ts";
import { MathUtil } from "@int/geotoolkit/util/MathUtil.ts";
import { Transformation } from "@int/geotoolkit/util/Transformation.ts";
import { createAnnotatedWidget } from "/src/code/Carnac/Tools/BasicTools/createModel.ts";
function createScene(canvas) {
  const rubberBandAnnotated = createAnnotatedWidget();
  const rubberBandTool = new RubberBand({
    "enabled": true,
    "layer": rubberBandAnnotated.getManipulatorLayer(),
    "autodisabled": false,
    "linestyle": {
      "pattern": [2, 3],
      "pixelsnapmode": true
    },
    "fillstyle": "rgba(50,50,150, .5)"
  }).on(RubberBandEvents.onZoomEnd, (eventType, sender, eventArgs) => {
    const newModelLimits = eventArgs.getArea();
    if (newModelLimits.getHeight() < MathUtil.epsilon || newModelLimits.getWidth() < MathUtil.epsilon) {
      return;
    }
    const modelLimits = rubberBandAnnotated.getModel().getModelLimits();
    if (modelLimits != null && !modelLimits.isEmpty() && !modelLimits.isEmpty()) {
      const transform = Transformation.getRectToRectInstance(newModelLimits, modelLimits);
      rubberBandAnnotated.setModelTransformation(transform);
    }
  });
  rubberBandAnnotated.connectTool(rubberBandTool);
  return new Plot({
    "canvaselement": canvas,
    "root": rubberBandAnnotated
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Zoom

The Zoom tool is designed to scale the scene using mouse wheel or external API. It supports events shown in theEvents enum from geotoolkit/controls/tools/Zoom and has several built-in functions to customize the tool, such as elastic (non-instant) zoom, scale constraints and others. For more details, please refer to geotoolkit/controls/tools/Zoom.

import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Functions as EasingFunctions } from "@int/geotoolkit/animation/Easing.ts";
import { Events as RubberBandEvents, RubberBand } from "@int/geotoolkit/controls/tools/RubberBand.ts";
import { MathUtil } from "@int/geotoolkit/util/MathUtil.ts";
import { Transformation } from "@int/geotoolkit/util/Transformation.ts";
import { createAnnotatedWidget } from "/src/code/Carnac/Tools/BasicTools/createModel.ts";
let isElasicZoomEnabled = true;
function toggleElasticZoom() {
  isElasicZoomEnabled = !isElasicZoomEnabled;
}
function getElasticZoomState() {
  return isElasicZoomEnabled;
}
function createScene(canvas) {
  const zoomAnnotated = createAnnotatedWidget();
  const zoomTool = zoomAnnotated.getToolByName("zoom").setProperties({
    "speed": 1.5,
    "time": 1e3,
    "easing": EasingFunctions.EaseOutQuint
  });
  const zoomRubberBand = new RubberBand({
    "enabled": true,
    "layer": zoomAnnotated.getManipulatorLayer(),
    "autodisabled": false,
    "linestyle": {
      "pattern": [2, 3],
      "pixelsnapmode": true
    },
    "fillstyle": "rgba(50,50,150, .5)"
  }).on(RubberBandEvents.onZoomEnd, (eventType, sender, eventArgs) => {
    const newModelLimits = eventArgs.getArea();
    if (newModelLimits.getHeight() < MathUtil.epsilon || newModelLimits.getWidth() < MathUtil.epsilon) {
      return;
    }
    const modelLimits = zoomAnnotated.getModel().getModelLimits();
    if (modelLimits != null && !modelLimits.isEmpty() && !newModelLimits.isEmpty()) {
      const transform = Transformation.getRectToRectInstance(newModelLimits, modelLimits);
      if (isElasicZoomEnabled) {
        const localTransform = zoomAnnotated.getModel().getLocalTransform();
        const activeScaleX = localTransform != null ? localTransform.getScaleX() : 1;
        const activeScaleY = localTransform != null ? localTransform.getScaleY() : 1;
        const scaleX = transform.getScaleX() / activeScaleX;
        const scaleY = transform.getScaleY() / activeScaleY;
        zoomTool.zoom(scaleX, scaleY, newModelLimits.getCenterX(), newModelLimits.getCenterY());
      } else {
        zoomAnnotated.setModelTransformation(transform);
      }
    }
  });
  zoomAnnotated.connectTool(zoomRubberBand);
  return new Plot({
    "canvaselement": canvas,
    "root": zoomAnnotated
  });
}
export { createScene, getElasticZoomState, toggleElasticZoom };

createScene(document.querySelector('[ref="plot"]'));

# Drag and drop

This tool provides Drag'Drop features for geotoolkit scene.

import { Rectangle } from "@int/geotoolkit/scene/shapes/Rectangle.ts";
import { Text } from "@int/geotoolkit/scene/shapes/Text.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { VerticalBoxLayout } from "@int/geotoolkit/layout/VerticalBoxLayout.ts";
import { HorizontalBoxLayout } from "@int/geotoolkit/layout/HorizontalBoxLayout.ts";
import { Selector } from "@int/geotoolkit/selection/Selector.ts";
import { DragAndDrop, Events as DragAndDropEvents } from "@int/geotoolkit/controls/tools/DragAndDrop.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { ColorUtil, KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { Tools } from "@int/geotoolkit/controls/tools/Tools.ts";
import { init } from "@int/geotoolkit/base.js";
import { isNode } from "@int/geotoolkit/scene/Node.ts";
init({ "imports": [Tools] });
function addRectToGroup(group, color, number) {
  if (group == null) {
    return;
  }
  const rectangle = new Rectangle({
    "left": 0,
    "top": 0,
    "right": 100,
    "bottom": 100,
    "fillstyle": color
  });
  const text = new Text({
    "text": (number + 1).toString(),
    "ax": 50,
    "ay": 50,
    "width": 20,
    "height": 20,
    "textstyle": {
      "font": "9px Arial",
      "color": KnownColors.Black
    },
    "sizeisindevicespace": true
  });
  group.addChild([
    rectangle,
    text
  ]);
}
function createScene(canvas) {
  const numberOfGroups = 6;
  const first = new Group().setBounds(new Rect(10, 10, 210, 310)).setModelLimits(new Rect(0, 0, 100, 100)).setLineStyle("black").setName("container").setLayout(new VerticalBoxLayout());
  const second = new Group().setBounds(new Rect(250, 10, 700, 310)).setModelLimits(new Rect(0, 0, 100, 100)).setLineStyle("black").setName("container").setLayout(new HorizontalBoxLayout());
  for (let i = 0; i < numberOfGroups; i++) {
    const newGroup = new Group().setModelLimits(new Rect(0, 0, 100, 100)).setBounds(new Rect(0, 0, 100, 100)).setName("draggable");
    addRectToGroup(newGroup, ColorUtil.getRandomColorRgb(15), i);
    first.addChild(newGroup);
  }
  let lastTarget = null, lastLineStyle = null;
  const selector = new Selector();
  function getTargetNode(source, args) {
    const point = source.pointToModel(args.getNode(), args);
    const nodes = selector.select(args.getNode(), point.getX(), point.getY(), 2).filter(isNode);
    for (let i = nodes.length - 1; i >= 0; --i) {
      if (nodes[i] instanceof Group && nodes[i].getName() === "container") {
        return nodes[i];
      }
    }
    return null;
  }
  const plot = new Plot({
    "canvaselement": canvas,
    "root": new Group().addChild([
      first,
      second
    ])
  });
  plot.getTool().add(
    new DragAndDrop({
      "enabled": true,
      "nodefilters": [{
        "drag": (nodes) => {
          for (let i = 0; i < nodes.length; ++i) {
            if (nodes[i].getName() === "draggable") {
              return nodes[i];
            }
          }
          return null;
        }
      }]
    }).on(DragAndDropEvents.onDrop, (evt, source, args) => {
      const targetNode = getTargetNode(source, args);
      if (targetNode) {
        targetNode.addChild(args.getElements());
        targetNode.invalidateLayout();
        if (lastTarget) {
          lastTarget.setLineStyle(lastLineStyle);
          lastTarget = null;
          lastLineStyle = null;
        }
      }
    }).on(DragAndDropEvents.onDrag, (evt, source, args) => {
      const targetNode = getTargetNode(source, args);
      if (targetNode) {
        if (lastTarget) {
          lastTarget.setLineStyle(lastLineStyle);
        }
        lastTarget = targetNode;
        lastLineStyle = targetNode.getLineStyle();
        targetNode.setLineStyle({ "color": "lightblue", "width": 3 });
      }
    })
  );
  return plot;
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Measure

The Measure tool is designed to measure distances and areas on the scene. It supports style changing like a Paint tool. Use right click to clear the ruler. For more details, please refer to geotoolkit/controls/tools/Measure.

import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { AdaptiveTickGenerator } from "@int/geotoolkit/axis/AdaptiveTickGenerator.ts";
import { Grid } from "@int/geotoolkit/axis/Grid.ts";
import { Measure } from "@int/geotoolkit/controls/tools/Measure.ts";
import { createAnnotatedWidget } from "/src/code/Carnac/Tools/BasicTools/createModel.ts";
function createScene(canvas) {
  const hTickGenerator = new AdaptiveTickGenerator().setVisibleTickGrade("minor", false).setVisibleTickGrade("edge", false).setTickStyle("major", "#DCDCE3").setTickStyle("minor", "#ECECF2");
  const vTickGenerator = new AdaptiveTickGenerator().setVisibleTickGrade("minor", false).setVisibleTickGrade("edge", false).setTickStyle("major", "#DCDCE3").setTickStyle("minor", "#ECECF2");
  const grid = new Grid(hTickGenerator, vTickGenerator);
  const measureAnnotated = createAnnotatedWidget();
  measureAnnotated.fitToBounds().getModel().clearChildren().insertChild(0, grid);
  const measureTool = new Measure({
    "layer": measureAnnotated.getManipulatorLayer()
  });
  measureAnnotated.connectTool(measureTool);
  return new Plot({
    "canvaselement": canvas,
    "root": measureAnnotated
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# DOM Support

This tool provides DOM features for geotoolkit scene. After adding this tool on the scene you can subscribe/unsubscribe to the mouse event with .on/.off methods. Supported events can be found in geotoolkit/controls/tools/PointerMode. This tool also provides support for css pseudo-class hover, css pseudo-class hover, by default it sets hover on top most node.

import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { createAnnotatedWidget } from "/src/code/Carnac/Tools/BasicTools/createModel.ts";
function createScene(canvas) {
  const annotated = createAnnotatedWidget();
  annotated.getModel().setCss({ "css": [
    ".geotoolkit.scene.shapes.Rectangle:hover {",
    "   fillstyle-color: " + KnownColors.Green + ";",
    "   linestyle-color: " + KnownColors.Red + ";",
    "   linestyle-width: 2;",
    "}",
    ".geotoolkit.scene.shapes.Spline:hover, .geotoolkit.scene.shapes.Line:hover {",
    "   linestyle-width: 5;",
    "}"
  ].join("\n") });
  return new Plot({
    "canvaselement": canvas,
    "root": annotated
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Simple Tooltip

This tutorial demonstrates how to rise simple tooltip on hover event with using ToolTip.show()

import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { ToolTip } from "@int/geotoolkit/controls/tooltip/ToolTip.ts";
import { from } from "@int/geotoolkit/selection/from.ts";
import { Events as DomEvents } from "@int/geotoolkit/dom.ts";
import { Shape } from "@int/geotoolkit/scene/shapes/Shape.ts";
import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { createAnnotatedWidget } from "/src/code/Carnac/Tools/BasicTools/createModel.ts";
function createScene(canvas) {
  const annotated = createAnnotatedWidget();
  from(annotated.getModel()).where((node) => node instanceof Shape).where((node) => node.getProperty("description") != null).execute((node) => {
    node.on(DomEvents.Hover, (event, sender, eventArgs) => {
      ToolTip.getDefault(sender).setNode(sender).setHTML(sender.getProperty("description")).show(eventArgs.getPlotPoint());
    }).on(DomEvents.Leave, (event, sender) => {
      const toolTip = ToolTip.getDefault(sender);
      if (toolTip.getNode() === sender) {
        toolTip.hide().setNode(null);
      }
    });
  });
  const plot = new Plot({
    "canvaselement": canvas,
    "root": annotated
  });
  ToolTip.getDefault(plot).setProperties({
    alignment: AnchorType.BottomCenter,
    offsety: 5
  });
  return plot;
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Custom Tooltip

This example demonstrates how to create custom tooltip provider for scenegraph node and register it to all kind of shapes.
Keep in mind that top level components such as WellLog Widget, Charts and others, provides tooltips by default. However you can change providers in your application.

var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result)
    __defProp(target, key, result);
  return result;
};
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Shape } from "@int/geotoolkit/scene/shapes/Shape.ts";
import { Text } from "@int/geotoolkit/scene/shapes/Text.ts";
import { Obfuscate, SetClassName } from "@int/geotoolkit/decorators.ts";
import { CompositeTool } from "@int/geotoolkit/controls/tools/CompositeTool.ts";
import { ToolTipTool } from "@int/geotoolkit/controls/tools/ToolTipTool.ts";
import { ToolTipRegistry } from "@int/geotoolkit/controls/tooltip/ToolTipRegistry.ts";
import { NodeTooltip } from "@int/geotoolkit/controls/tooltip/NodeTooltip.ts";
import { ToolTipFormatter } from "@int/geotoolkit/controls/tooltip/ToolTipFormatter.ts";
import { createAnnotatedWidget } from "/src/code/Carnac/Tools/BasicTools/createModel.ts";
let SimpleToolTip = class extends NodeTooltip {
  constructor(format) {
    super(format);
  }
  getTooltip(visual, pt, registry, options) {
    const fillStyle = visual.getFillStyle();
    const lineStyle = visual.getLineStyle();
    const textStyle = visual instanceof Text ? visual.getTextStyle() : null;
    const color = fillStyle != null ? fillStyle.getColor() : null;
    const keys = visual.getPropertyKeys();
    const properties = {};
    if (keys != null) {
      for (let i = 0; i < keys.length; i++) {
        properties[keys[i].toLowerCase()] = visual.getProperty(keys[i]);
      }
    }
    const toolTipInfo = {
      "name": visual.getName(),
      "color": color,
      "symbol": ToolTipFormatter.formatSymbol(textStyle || fillStyle, textStyle != null ? fillStyle : lineStyle),
      "properties": properties
    };
    let formatString;
    const toolTipFormat = this.getFormat();
    if (typeof toolTipFormat === "function") {
      formatString = toolTipFormat.call(this, visual, pt, toolTipInfo, options);
    } else {
      formatString = toolTipFormat;
    }
    return registry.getFormatter().evaluate(formatString, toolTipInfo, visual);
  }
};
SimpleToolTip = __decorateClass([
  Obfuscate(),
  SetClassName("geotoolkit.controls.tools.SimpleToolTip")
], SimpleToolTip);
function createScene(canvas) {
  const annotatedWidget = createAnnotatedWidget();
  annotatedWidget.connectTool([
    new CompositeTool(annotatedWidget.getModel()).add([
      new ToolTipTool({
        "layer": annotatedWidget,
        "registry": new ToolTipRegistry().register(Shape.getClassName(), new SimpleToolTip("${symbol}${name} <br/> Description: ${properties.description}"))
      })
    ])
  ]);
  return new Plot({
    "canvaselement": canvas,
    "root": annotatedWidget
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Custom tool

This is an example how to create a simple tool with own events

var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result)
    __defProp(target, key, result);
  return result;
};
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Text } from "@int/geotoolkit/scene/shapes/Text.ts";
import { Tools } from "@int/geotoolkit/controls/tools/Tools.ts";
import { init } from "@int/geotoolkit/base.js";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { AbstractTool } from "@int/geotoolkit/controls/tools/AbstractTool.ts";
import { Obfuscate } from "@int/geotoolkit/decorators.ts";
import { ProxyEventArgs } from "@int/geotoolkit/controls/tools/ProxyEventArgs.ts";
import { Selector } from "@int/geotoolkit/selection/Selector.ts";
init({ "imports": [Tools] });
var Events = /* @__PURE__ */ ((Events2) => {
  Events2["onLeftButtonClick"] = "onLeftButtonClick";
  Events2["onRightButtonClick"] = "onRightButtonClick";
  Events2["onTouchScreenTap"] = "onTouchScreenTap";
  return Events2;
})(Events || {});
let CustomEventArgs = class extends ProxyEventArgs {
  constructor(eventArgs, eventName, button) {
    super(eventArgs, eventName);
    this._button = button;
  }
  getText() {
    return this._button;
  }
};
CustomEventArgs = __decorateClass([
  Obfuscate()
], CustomEventArgs);
let CustomTool = class extends AbstractTool {
  constructor() {
    super({
      name: "CustomTool"
    });
    const slots = {
      "pointerdown": this.onMouseDown.bind(this),
      "contextmenu": (eventArgs) => eventArgs.stopPropagation(true, true)
    };
    this.setSlots(slots);
  }
  onMouseDown(eventArgs) {
    if (this.isTouchEvent(eventArgs)) {
      this.notify("onTouchScreenTap" /* onTouchScreenTap */, this, new CustomEventArgs(eventArgs, "onTouchScreenTap" /* onTouchScreenTap */, "tap"));
      return;
    }
    const nativeEventArgs = eventArgs.getNativeEventArgs();
    if (nativeEventArgs.buttons === 1) {
      this.notify("onLeftButtonClick" /* onLeftButtonClick */, this, new CustomEventArgs(eventArgs, "onLeftButtonClick" /* onLeftButtonClick */, "left"));
    } else if (nativeEventArgs.buttons === 2) {
      this.notify("onLeftButtonClick" /* onLeftButtonClick */, this, new CustomEventArgs(eventArgs, "onLeftButtonClick" /* onLeftButtonClick */, "right"));
    }
  }
};
CustomTool = __decorateClass([
  Obfuscate()
], CustomTool);
function initializeCustomTool() {
  const getSelection = function(args) {
    return Selector.select(args.getPlot().getRoot(), args.getPlotPoint()).filter((node) => node instanceof Text);
  };
  const onClick = (eventType, sender, args) => {
    const textSelection = getSelection(args);
    if (textSelection.length > 0) {
      textSelection[0].setText(args.getText() + " button clicked");
    }
  };
  return new CustomTool().on("onLeftButtonClick" /* onLeftButtonClick */, onClick).on("onRightButtonClick" /* onRightButtonClick */, onClick).on("onTouchScreenTap" /* onTouchScreenTap */, (eventType, sender, args) => {
    const textSelection = getSelection(args);
    if (textSelection.length > 0) {
      textSelection[0].setText(args.getText() + " occured");
    }
  });
}
function createScene(canvas) {
  const plot = new Plot({
    "canvaselement": canvas,
    "root": new Group().setLineStyle("black").addChild([
      new Text({
        paddingstyle: "6px 6px 4px",
        text: "Click or Tap on Text",
        ax: 300,
        ay: 200,
        width: 200
      }).setCss("*:hover { linestyle: gray;}")
    ])
  });
  const customTool = initializeCustomTool();
  plot.getTool().add(customTool);
  return plot;
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));