This tutorial shows how different visuals can be used to display text annotations on the tracks in WellLog. An annotation is an additional visual, either text or graphics, added to provide further clarification.
# Annotations with LogMudLogSection
LogMudLogSection is an interval containing comments or text. It can be created with a set of depths or time indices and text annotations for each index. Each section extends from specified index to the next index in the array. If the text is too large and cannot fit, the visual tries to display a part of the text and also use customizable ellipses (...) to indicate more text exists, but is not displayed.
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { LogMudLogSection } from "@int/geotoolkit/welllog/LogMudLogSection.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { HeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { depths, descs } from "/src/code/WellLog/Visuals/Annotation/js/data.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
function createScene(canvas) {
const widget = createWellLogWidget().setDepthLimits(depths[0], depths[depths.length - 1]).setDepthScale(30).setAxisHeaderType(HeaderType.Scale);
const annotationHeader = widget.getHeaderContainer().getHeaderProvider().getHeaderProvider(LogMudLogSection.getClassName());
widget.getHeaderContainer().getHeaderProvider().registerHeaderProvider(LogMudLogSection.getClassName(), annotationHeader.clone().setVisible(true));
widget.addTrack(TrackType.IndexTrack);
const styles = ["#c8c8c8", "#fafafa"];
widget.addTrack(TrackType.LinearTrack).addChild(
new LogMudLogSection({
"fillstyles": (depth, text, i) => i === 2 ? "#fdd835" : styles[i % 2]
}).setName("Remarks").setPaddingStyle("2mm").setDepthsAndValues(depths, descs).setEllipsisString(" ... more")
);
return {
plot: new Plot({
"canvaselement": canvas,
"root": widget
}),
widget
};
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# LogMudLogSection customization
LogMudLogSection can be customized. You can specify section margins, text paddings, symbols, geometry type and handle hit-test.
import { from } from "@int/geotoolkit/selection/from.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { RgbaColor } from "@int/geotoolkit/util/RgbaColor.ts";
import { SymbolShape } from "@int/geotoolkit/scene/shapes/SymbolShape.ts";
import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { CirclePainter } from "@int/geotoolkit/scene/shapes/painters/CirclePainter.ts";
import { DiamondPainter } from "@int/geotoolkit/scene/shapes/painters/DiamondPainter.ts";
import {
FillMode,
LogMudLogSection,
SymbolPosition,
WrapMode
} from "@int/geotoolkit/welllog/LogMudLogSection.ts";
import { AlignmentStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { HeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { depths, descs } from "/src/code/WellLog/Visuals/Annotation/js/data.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
function onHitTest(mudLogSection) {
let selectedIndices = [];
mudLogSection.on("click", (event, sender, eventArgs) => {
let newSelectedIndices = sender.hitTest(eventArgs.getPlotPoint(), 5).map((s) => s.index);
if (newSelectedIndices !== null && selectedIndices != null) {
for (let i = newSelectedIndices.length - 1; i >= 0; i--) {
const selectedIndex = selectedIndices.indexOf(newSelectedIndices[i]);
if (selectedIndex !== -1) {
newSelectedIndices.splice(i, 1);
selectedIndices.splice(selectedIndex, 1);
}
}
if (eventArgs.getNativeEventArgs().ctrlKey && selectedIndices != null) {
newSelectedIndices = newSelectedIndices.concat(selectedIndices);
}
}
selectedIndices = newSelectedIndices;
mudLogSection.setSelectedIndices(newSelectedIndices);
});
}
function createCustomMudLogSection(fillMode) {
const mudLogSection = new LogMudLogSection({
"selectedsymbol": (depth, text, index, symbol) => symbol == null ? null : symbol.clone().setLineStyle(symbol.getLineStyle().clone().setWidth(2)),
"selectedfillstyle": (depth, text, index, fillStyle) => fillStyle == null ? null : fillStyle.clone().setColor(new RgbaColor(fillStyle.getColor()).setAlpha(1)),
"selectedtextstyle": (depth, text, index, textStyle) => textStyle == null ? null : textStyle.clone().setColor("black"),
"selectedlinestyle": (depth, text, index, lineStyle) => lineStyle == null ? null : lineStyle.clone().setWidth(3),
"oddtextstyle": {
"color": "green",
"font": "10px sans-serif",
"alignment": AlignmentStyle.Right
},
"eventextstyle": {
"color": "blue",
"font": "10px sans-serif",
"alignment": AlignmentStyle.Left
},
"oddfillstyle": "rgba(255,255,155, 0.5)",
"evenfillstyle": "rgba(155,255,155, 0.5)",
"oddlinestyle": "blue",
"evenlinestyle": "red"
}).setSymbols([
new SymbolShape({
"width": 10,
"height": 10,
"alignment": AnchorType.Center,
"sizeisindevicespace": true,
"painter": CirclePainter,
"linestyle": "blue",
"fillstyle": "rgba(155,255,155, 0.5)"
}),
new SymbolShape({
"width": 10,
"height": 10,
"alignment": AnchorType.Center,
"sizeisindevicespace": true,
"painter": DiamondPainter,
"linestyle": "red",
"fillstyle": "rgba(255,255,155, 0.5)"
})
]).setCornerRadius(8).setFillMode(fillMode).setPaddingStyle("2mm").setMarginsStyle("2mm").setSymbolMarginsStyle("2mm").setWrapMode(WrapMode.WrappedWidth).setSymbolPosition(SymbolPosition.EvenOdd).setDepthsAndValues(depths, descs);
if (fillMode === FillMode.All) {
mudLogSection.setSymbolMarginsStyle({
"right": 0
}, true);
} else {
mudLogSection.setSymbolMarginsStyle({
"left": 0
}, true);
}
onHitTest(mudLogSection);
return mudLogSection;
}
function addMudLogSectionTrack(widget, fillMode) {
const remarksTrack = widget.addTrack(TrackType.LinearTrack).addChild(createCustomMudLogSection(fillMode));
if (fillMode === FillMode.All) {
remarksTrack.setName("Fill Mode: All");
} else if (fillMode === FillMode.TextOnly) {
remarksTrack.setName("Fill Mode: Text Only");
} else if (fillMode === FillMode.Callout) {
remarksTrack.setName("Fill Mode: Callout");
}
widget.getTrackHeader(remarksTrack).setVisibleTrackTitle(true);
return remarksTrack;
}
function createScene(canvas) {
const widget = createWellLogWidget().setDepthLimits(depths[0], depths[depths.length - 1]).setDepthScale(30).setAxisHeaderType(HeaderType.Scale);
widget.addTrack(TrackType.IndexTrack);
addMudLogSectionTrack(widget, FillMode.All);
addMudLogSectionTrack(widget, FillMode.TextOnly);
addMudLogSectionTrack(widget, FillMode.Callout);
const plot = new Plot({
"canvaselement": canvas,
"root": widget
});
let minDepth = depths[0];
let maxDepth = depths[depths.length - 1];
from(widget).where('node => class(node) == "geotoolkit.welllog.LogMudLogSection"').execute((mudLogSection) => {
const mulLogBounds = mudLogSection.getBounds();
minDepth = Math.min(mulLogBounds.getTop(), minDepth);
maxDepth = Math.max(mulLogBounds.getBottom(), maxDepth);
});
widget.setDepthLimits(minDepth, maxDepth);
return {
plot,
widget
};
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# LogMudLogSection symbols
Using custom Symbols with LogMudLogSection. Besides text only information, you can specify custom symbols based on SVG template for example for each depth and specify aggregation strategy you would like to use for overlapped symbols or symbols which share same index.
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { from } from "@int/geotoolkit/selection/from.ts";
import { AbstractNode } from "@int/geotoolkit/scene/AbstractNode.ts";
import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { AlignmentStyle, TextStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { FillMode, LogMudLogSection, SymbolPosition } from "@int/geotoolkit/welllog/LogMudLogSection.ts";
import { SvgPainter } from "@int/geotoolkit/svg/SvgPainter.ts";
import { SymbolShape } from "@int/geotoolkit/scene/shapes/SymbolShape.ts";
import { Position as LogBlockPosition } from "@int/geotoolkit/welllog/LogBlock.ts";
import { HeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { symbols } from "/src/code/WellLog/Visuals/Annotation/js/symbols.ts";
import directionalSVG from "/src/code/WellLog/Visuals/Annotation/svg/directional.svg?import&raw";
import drillSVG from "/src/code/WellLog/Visuals/Annotation/svg/drill.svg?import&raw";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
const SVG_FILES = {
"directional": directionalSVG,
"drill": drillSVG
};
const _symbolPrototypes = {};
function getSvgSymbolPrototype(symbolType) {
if (_symbolPrototypes[symbolType] == null) {
const svgPainter = new SvgPainter(SVG_FILES[symbolType]);
_symbolPrototypes[symbolType] = svgPainter;
}
return _symbolPrototypes[symbolType];
}
function getSVGSymbol(symbolXMl, size) {
const svgPainter = symbolXMl instanceof SvgPainter ? symbolXMl : new SvgPainter(symbolXMl);
const geometry = svgPainter.getGeometry();
return new SymbolShape({
"cache": true,
"width": size || geometry.getModelLimits().getWidth(),
"height": size || geometry.getModelLimits().getHeight(),
"alignment": AnchorType.Center,
"sizeisindevicespace": true,
"painter": svgPainter
});
}
function processTemplate(painter, json) {
from(painter).where((node) => node instanceof AbstractNode && node.getId() !== null).execute((node) => {
const properties = json[node.getId()];
if (properties != null) {
node.setProperties(properties);
}
});
return painter;
}
function addAnnotationTrack(widget, depths, values, symbolPosition, aggregationStrategy) {
const annotationTrack = widget.addTrack(TrackType.AnnotationTrack).setWidth(120);
widget.getTrackHeader(annotationTrack).setVisibleTrackTitle(true);
const mudLog = new LogMudLogSection().setFillMode(FillMode.SymbolOnly).setSymbolPosition(symbolPosition).setSymbolMarginsStyle({
"left": "2mm"
}).setPaddingStyle("1mm").setDepthsAndValues(depths, values);
const symbolsCache = {};
mudLog.setSymbols((modelTop, value, index) => {
const svgPrototype = getSvgSymbolPrototype(value["symbol"]);
if (svgPrototype == null || svgPrototype.getSvg() == null) {
return null;
}
if (symbolsCache[index] == null) {
if (value == null) {
return null;
}
const symbolValue = value["value"];
const properties = {};
for (const name in symbolValue) {
if (symbolValue.hasOwnProperty(name)) {
const prop = symbolValue[name];
properties[name] = {
"text": typeof prop === "string" ? prop : prop.toFixed(1)
};
}
}
const SVGPainter = processTemplate(svgPrototype.clone(), properties);
symbolsCache[index] = getSVGSymbol(SVGPainter);
}
return symbolsCache[index];
});
if (aggregationStrategy) {
const textStyle = new TextStyle({
"color": "black",
"alignment": AlignmentStyle.Center,
"font": "bold 30px Roboto"
});
mudLog.setAggregation({
"strategy": aggregationStrategy,
"range": 50,
"symbol": function(aggregationDepth, aggregationIndices) {
return new SymbolShape({
"width": 40,
"height": 40,
"alignment": AnchorType.Center,
"sizeisindevicespace": true,
"painter": function(symbol, bbox, context) {
context.drawEllipse(bbox.getLeft(), bbox.getTop(), bbox.getWidth(), bbox.getHeight());
const deviceBox = context.getTransformation().transformRect(bbox);
const deviceContext = context.pushTransformation(context.getTransformation().createInverse());
deviceContext.setTextStyle(textStyle);
deviceContext.drawText(deviceBox.getCenterX(), deviceBox.getCenterY(), aggregationIndices.length + "");
},
"linestyle": {
"color": "rgba(0, 0, 0, 0.5)",
"width": 1
},
"fillstyle": {
"color": "rgba(155,155,155, 0.1)"
}
});
}
});
}
return annotationTrack.addChild(mudLog);
}
function createScene(canvas) {
const depths = [], values = [];
for (let i = 0; i < symbols.length; i++) {
depths.push(symbols[i].depth);
values.push(symbols[i].value);
}
const widget = createWellLogWidget().setDepthLimits(1800, 2400).setAxisHeaderType(HeaderType.Scale);
widget.addTrack(TrackType.IndexTrack).getBlock().setVisible(true).setPosition(LogBlockPosition.Right).setDepths(depths).setFillStyle("red").setLineStyle(null);
addAnnotationTrack(widget, depths, values, SymbolPosition.Left).setName("Overlapped symbols");
addAnnotationTrack(widget, depths, values, SymbolPosition.Left, "stack").setName("Stacked symbols");
addAnnotationTrack(widget, depths, values, SymbolPosition.Left, "collapse").setName("Collapsed symbols");
widget.addTrack(TrackType.IndexTrack).getBlock().setVisible(true).setPosition(LogBlockPosition.Left).setDepths(depths).setFillStyle("red").setLineStyle(null);
const plot = new Plot({
"canvaselement": canvas,
"root": widget
});
widget.setVisibleDepthLimits(1829, 1841);
return {
plot,
widget
};
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# State Definitions
LogMudLogSection can be used to display state definitions.
import { RgbaColor } from "@int/geotoolkit/util/RgbaColor.ts";
import { HsvColor } from "@int/geotoolkit/util/HsvColor.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { GeometryUtil } from "@int/geotoolkit/util/GeometryUtil.ts";
import { GraphicsPath } from "@int/geotoolkit/renderer/GraphicsPath.ts";
import { NodeOrder } from "@int/geotoolkit/scene/CompositeNode.ts";
import { Rectangle } from "@int/geotoolkit/scene/shapes/Rectangle.ts";
import { SymbolShape } from "@int/geotoolkit/scene/shapes/SymbolShape.ts";
import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { LineStyle } from "@int/geotoolkit/attributes/LineStyle.ts";
import { FillMode, LogMudLogSection, SymbolPosition, WrapMode } from "@int/geotoolkit/welllog/LogMudLogSection.ts";
import { HeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { PatternFactory } from "@int/geotoolkit/attributes/PatternFactory.ts";
import { DiscreteDataMap, MapType } from "@int/geotoolkit/data/DiscreteDataMap.ts";
import { LogData } from "@int/geotoolkit/welllog/data/LogData.ts";
import { InterpolationType } from "@int/geotoolkit/data/DataStepInterpolation.ts";
import { loadResources } from "/src/helpers/resources.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
loadResources("patterns");
function generateData() {
const data = [0, 2, 5, 28, 26, 30, 15, 12, 10, 7, 19, 21, 22, 8, 11, 25, 27, 10, 3, 5];
const minData = 0, maxData = 30;
const minDepth = 11440, maxDepth = 11600;
const minValue = 1, maxValue = 7;
return new LogData({
"values": data.map((val) => minValue + Math.floor((maxValue - minValue) * val / (maxData - minData))),
"depths": data.map((val, i) => minDepth + (maxDepth - minDepth) * i / (data.length - 1))
});
}
function createStateDefinitionValue(code, color, pattern, text) {
const rgba = new RgbaColor(color);
return {
code,
text,
lineStyle: {
color: HsvColor.setBrightness(rgba, -0.2),
width: 2
},
textFillStyle: rgba.setAlpha(0.3),
symbolFillStyle: {
"color": rgba.setAlpha(0.3),
"pattern": PatternFactory.getInstance().getPattern(pattern),
"foreground": "black"
},
selectionFillStyle: {
"color": rgba.setAlpha(0.3),
"pattern": PatternFactory.getInstance().getPattern(pattern),
"foreground": HsvColor.setBrightness(rgba, -0.2)
}
};
}
function initializeDiscreteMap() {
return new DiscreteDataMap({
"type": MapType.Discrete,
"codes": [1, 2, 3, 4, 5, 6, 7],
"values": [
createStateDefinitionValue(1, "#dcedc8", "chert", "Chert"),
createStateDefinitionValue(2, "#7cb342", "chert", "Breccia"),
createStateDefinitionValue(3, "#fdd835", "lime", "Limestone"),
createStateDefinitionValue(4, "#fff9c4", "lime", "Limestone 2"),
createStateDefinitionValue(5, "#ef6c00", "shale", "Shale"),
createStateDefinitionValue(6, "#e64a19", "shale", "Shale 2"),
createStateDefinitionValue(7, "#2196f3", "salt", "Salt 2")
]
});
}
function onHitTest(stateDefinitionSection, stateDefinitionData, discreteMap) {
const getStateDefinitionByDepth = function(depth) {
const stateValue = stateDefinitionData.getValueAt(depth, 0, stateDefinitionData.getSize(), InterpolationType.Linear);
return discreteMap.getValueAt(stateValue);
};
const areaRectangle = new Rectangle().setVisible(false);
stateDefinitionSection.getParent().addChild(areaRectangle).changeChildOrder(areaRectangle, NodeOrder.Before, stateDefinitionSection);
let selectedIndices = [];
stateDefinitionSection.on("click", (event, sender, eventArgs) => {
let newSelectedIndices = sender.hitTest(eventArgs.getPlotPoint(), 5).map((s) => s.index);
if (newSelectedIndices !== null && selectedIndices != null) {
for (let i = newSelectedIndices.length - 1; i >= 0; i--) {
const selectedIndex = selectedIndices.indexOf(newSelectedIndices[i]);
if (selectedIndex !== -1) {
newSelectedIndices.splice(i, 1);
selectedIndices.splice(selectedIndex, 1);
}
}
if (eventArgs.getNativeEventArgs().ctrlKey && selectedIndices != null) {
newSelectedIndices = newSelectedIndices.concat(selectedIndices);
}
}
selectedIndices = newSelectedIndices;
stateDefinitionSection.setSelectedIndices(newSelectedIndices);
if (newSelectedIndices.length === 0) {
areaRectangle.setVisible(false);
} else {
const activeIndex = newSelectedIndices[0];
const depths = stateDefinitionSection.getDepths();
const fromDepth = depths[activeIndex];
const toDepth = depths[activeIndex + 1];
const stateDefinition = getStateDefinitionByDepth(fromDepth);
areaRectangle.setBounds(new Rect(0, fromDepth, 1, toDepth)).setFillStyle(stateDefinition.selectionFillStyle).setLineStyle(stateDefinition.lineStyle).setVisible(true);
}
});
}
function createStateDefinitionAnnotations(stateDefinitionData, discreteMap, fillMode) {
const glowLineStyle = new LineStyle({
"color": "white",
"width": 4,
"shadow": {
"enable": true,
"blur": 7,
"offsetx": 0,
"offsety": 0,
"color": "white"
}
});
const strokeLineStyle = new LineStyle({
"color": "white",
"width": 2
});
const stubRoundPath = new GraphicsPath();
const roundRectPainter = function(symbol, bbox, context) {
const contextTransformation = context.getTransformation();
const path = GeometryUtil.getRoundRect(bbox, 4, contextTransformation, stubRoundPath);
context.drawPath(path);
if (symbol.getFillStyle()) {
context.fillPath();
}
if (symbol.getLineStyle()) {
context.stroke();
}
};
const selectedSymbolPainter = function(symbol, bbox, context) {
const contextTransformation = context.getTransformation();
const path = GeometryUtil.getRoundRect(bbox, 8, contextTransformation, stubRoundPath);
context.drawPath(path);
context.stroke();
context.stroke();
if (symbol.getFillStyle()) {
context.fillPath();
}
context.setLineStyle(strokeLineStyle);
context.stroke();
};
const eventSymbol = new SymbolShape({
"width": 16,
"height": 16,
"alignment": AnchorType.Center,
"sizeisindevicespace": true,
"painter": roundRectPainter
}).setSilent(true);
const selectedSymbol = new SymbolShape({
"width": 16,
"height": 16,
"alignment": AnchorType.Center,
"sizeisindevicespace": true,
"linestyle": {
"color": "black",
"width": 2
},
"painter": selectedSymbolPainter
}).setSilent(true);
const getStateDefinitionByDepth = function(depth) {
const stateValue = stateDefinitionData.getValueAt(depth, 0, stateDefinitionData.getSize(), InterpolationType.Linear);
return discreteMap.getValueAt(stateValue);
};
const stateDefinitionSection = new LogMudLogSection({
"selectedsymbol": (depth, text, index, symbol) => symbol == null ? null : selectedSymbol.setFillStyle(symbol.getFillStyle()).setLineStyle(glowLineStyle.clone().setProperties({
"color": symbol.getLineStyle().getColor(),
"shadow": {
"color": symbol.getLineStyle().getColor()
}
})),
"selectedfillstyle": (depth, text, index, fillStyle) => fillStyle == null ? null : fillStyle.clone().setColor(new RgbaColor(fillStyle.getColor()).setAlpha(1)),
"selectedlinestyle": (depth, text, index, lineStyle) => lineStyle == null ? null : lineStyle.clone().setColor("black"),
"textstyle": {
"color": "black",
"font": "10px sans-serif"
},
"linestyles": (depth, text, index) => {
const stateDefinition = getStateDefinitionByDepth(depth);
return stateDefinition.lineStyle;
},
"fillstyles": (depth, text, index) => {
const stateDefinition = getStateDefinitionByDepth(depth);
return stateDefinition.textFillStyle;
},
"symbols": (depth, text, index) => {
const stateDefinition = getStateDefinitionByDepth(depth);
return eventSymbol.setFillStyle(stateDefinition.symbolFillStyle).setLineStyle(stateDefinition.lineStyle);
}
}).setDepthsAndValues(stateDefinitionData.getDepths(), (depth, index) => {
const stateDefinition = getStateDefinitionByDepth(depth);
return "(" + stateDefinition.code + ") " + stateDefinition.text;
}).setWrapMode(WrapMode.WrappedWidth).setCornerRadius(4).setFillMode(fillMode).setPaddingStyle("1mm").setMarginsStyle("1mm").setSymbolMarginsStyle({
"top": 0,
"left": "1mm",
"right": "2mm"
}).setSymbolPosition(SymbolPosition.Left).setEllipsisString(" ... more");
if (fillMode === FillMode.All) {
stateDefinitionSection.setSymbolMarginsStyle({
"right": 0
}, true);
}
return stateDefinitionSection;
}
function addStateDefinitionTrack(widget, stateDefinitionData, fillMode) {
let stateDefinitionSection = null;
const discreteMap = initializeDiscreteMap();
const stateDefinitionsTrack = widget.addTrack(TrackType.LinearTrack).setName("State Definitions").addChild([
stateDefinitionSection = createStateDefinitionAnnotations(stateDefinitionData, discreteMap, fillMode)
]);
onHitTest(stateDefinitionSection, stateDefinitionData, discreteMap);
widget.getTrackHeader(stateDefinitionsTrack).setVisibleTrackTitle(true);
return stateDefinitionsTrack;
}
function createScene(canvas) {
const stateDefinitionData = generateData();
const depths = stateDefinitionData.getDepths();
const widget = createWellLogWidget().setDepthLimits(depths[0], depths[depths.length - 1]).setAxisHeaderType(HeaderType.Scale);
widget.addTrack(TrackType.IndexTrack);
addStateDefinitionTrack(widget, stateDefinitionData, FillMode.TextOnly);
const plot = new Plot({
"canvaselement": canvas,
"root": widget
});
widget.fitToHeight().scale(2);
return {
plot,
widget
};
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Annotations with LogAnnotation
LogAnnotation is used to display a simple annotation in the track. It requires specified bounds in the track coordinates, which are depth or time in the vertical axis and relative coordinates from 0 to 1 in the horizontal axis. Annotations don't handle intersections with other annotations in the track.
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { LogAnnotation } from "@int/geotoolkit/welllog/LogAnnotation.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { depths, descs } from "/src/code/WellLog/Visuals/Annotation/js/data.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
function createScene(canvas) {
const widget = createWellLogWidget().setDepthLimits(depths[0], depths[depths.length - 1]).setDepthScale(30);
widget.addTrack(TrackType.IndexTrack);
const annotation1 = new LogAnnotation({
"bounds": new Rect(0.2, depths[1], 0.8, depths[2]),
"text": descs[1],
"textstyle": "#7cb342",
"fillstyle": "#eeeeee"
});
const annotation2 = new LogAnnotation({
"bounds": new Rect(0.2, depths[3], 0.8, depths[5]),
"text": descs[3]
}).setTextStyle("#7cb342");
const remarksTrack = widget.addTrack(TrackType.LinearTrack).setName("Remarks").addChild([annotation1, annotation2]);
widget.getTrackHeader(remarksTrack).setVisibleTrackTitle(true);
return new Plot({
"canvaselement": canvas,
"root": widget
});
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Floating Annotation
AnnotationOverlay is used to display floating widget annotations. These annotations display in an overlay layer on top of all tracks in the widget. An overlay may have inline editable text, a small image, and an anchor position in any track. The code below shows how to add the annotation overlay. Plus you can change annotation geometry or add additional buttons to it to fit your application needs
New annotations can be added by an integrated tool or by using the addAnnotation method, which returns a reference to an instance of a new annotation. The code below creates two annotations and specifies positions related to curvesTrack, where anchor x is a value in the range from 0 to 1 (left border and right border of the track) and y coordinate is a depth value. If a tool is disabled, then annotations are not movable and editable by default. To enable the overlay annotation tool, set overlay.setEnabled(true);
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { LogCurve } from "@int/geotoolkit/welllog/LogCurve.ts";
import { LogData } from "@int/geotoolkit/welllog/data/LogData.ts";
import { Point } from "@int/geotoolkit/util/Point.ts";
import { Dimension } from "@int/geotoolkit/util/Dimension.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { depths, descs } from "/src/code/WellLog/Visuals/Annotation/js/data.ts";
import { curveData } from "/src/code/WellLog/utils/curveData.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
import { Path } from "@int/geotoolkit/scene/shapes/Path.ts";
import {
AnnotationOverlay as WelllogAnnotationOverlay
} from "@int/geotoolkit/welllog/widgets/overlays/AnnotationOverlay.ts";
import { AnnotationOverlay } from "@int/geotoolkit/widgets/overlays/AnnotationOverlay.ts";
function calloutWithButtonsGeometry(transformation, targetPoint, anchorPoint, calloutsFrame, options, overlay) {
const geometry = AnnotationOverlay.getGeometry("rectangle-callouts").call(this, transformation, targetPoint, anchorPoint, calloutsFrame, options);
const annotationOverlay = overlay;
if (options["annotation"] !== annotationOverlay.getActiveAnnotation()) {
return geometry;
}
const calloutPath = geometry["geometry"];
const buttonX = calloutsFrame.getRight() - 15;
const buttonY = calloutsFrame.getTop() - 15;
const buttonPath = new Path().setLineStyle(options["linestyle"]).setFillStyle("#C1E3E8").moveTo(buttonX, buttonY).lineTo(buttonX + 10, buttonY).lineTo(buttonX + 10, buttonY + 10).lineTo(buttonX, buttonY + 10).lineTo(buttonX, buttonY).close().on("click", () => {
if (annotationOverlay.getEnabled()) {
const activeAnnotation = annotationOverlay.getActiveAnnotation();
if (activeAnnotation != null) {
annotationOverlay.removeAnnotation(activeAnnotation);
}
}
});
const xButtonPath = new Path().setLineStyle({ "color": "black", "width": 2 }).moveTo(buttonX + 2, buttonY + 2).lineTo(buttonX + 9, buttonY + 9).moveTo(buttonX + 9, buttonY + 2).lineTo(buttonX + 2, buttonY + 9);
return {
"geometry": [calloutPath, buttonPath, xButtonPath],
"clip": null
};
}
function createScene(canvas) {
const widget = createWellLogWidget().setDepthLimits(depths[0], depths[depths.length - 1]).setDepthScale(30);
widget.addTrack(TrackType.IndexTrack);
const data = new LogData({
"name": "GR",
"depths": depths,
"values": curveData["GR"]
});
const curvesTrack = widget.addTrack(TrackType.LinearTrack).addChild(
new LogCurve(data).setLineStyle({
"color": "#ef6c00",
"width": 2
})
);
widget.addTrack(TrackType.LinearTrack).setName("Remarks");
const frame = new Dimension(70, 50);
const overlay = new WelllogAnnotationOverlay(widget);
AnnotationOverlay.registerGeometry("callout-with-buttons", function(transformation, targetPoint, anchorPoint, calloutsFrame, options) {
return calloutWithButtonsGeometry.call(this, transformation, targetPoint, anchorPoint, calloutsFrame, options, overlay);
});
overlay.addAnnotation({
"text": descs[1],
"target": curvesTrack,
"anchor": new Point(1, depths[1]),
"options": {
"offset": new Point(40 + frame.getWidth() / 2, 20 + frame.getHeight() / 2),
"geometrytype": "callout-with-buttons"
}
});
overlay.addAnnotation({
"text": descs[3],
"target": curvesTrack,
"anchor": new Point(1, depths[3]),
"options": {
"offset": new Point(40 + frame.getWidth() / 2, -20 - frame.getHeight() / 2)
}
});
overlay.setEnabled(true).setOptions({
"candelete": true,
"cancreate": false,
"canmove": true,
"canedit": true
});
const plot = new Plot({
"canvaselement": canvas,
"root": widget
});
return {
plot,
overlay
};
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));