UNPKG

dicom-microscopy-viewer

Version:
577 lines (494 loc) 17.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: annotations/markups/_MarkupManager.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: annotations/markups/_MarkupManager.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>import Circle from 'ol/geom/Circle' import DragPan from 'ol/interaction/DragPan' import LineString from 'ol/geom/LineString' import Overlay from 'ol/Overlay' import VectorLayer from 'ol/layer/Vector' import 'ol/ol.css' import VectorSource from 'ol/source/Vector' import Style from 'ol/style/Style' import Stroke from 'ol/style/Stroke' import Collection from 'ol/Collection' import Feature from 'ol/Feature' import { fromCircle } from 'ol/geom/Polygon' import Enums from '../../enums' import { _getUnitSuffix } from '../../utils' import { coordinateWithOffset } from '../../scoord3dUtils' import defaultStyles from '../styles' /** * Build a new LineString instance with the shortest * distance between a given overlay and a feature. * * @param {object} feature The feature * @param {object} overlay The overlay instance * * @returns {LineString} The smallest line between the overlay and feature * * @private */ const _getShortestLineBetweenOverlayAndFeature = (feature, overlay) => { let result let distanceSq = Infinity let featureGeometry = feature.getGeometry() if (featureGeometry instanceof Circle) { featureGeometry = fromCircle(featureGeometry).clone() } const geometry = featureGeometry.getLinearRing ? featureGeometry.getLinearRing(0) : featureGeometry; (geometry.getCoordinates() || geometry.getExtent()).forEach(coordinates => { const closest = overlay.getPosition() const distanceNew = Math.pow(closest[0] - coordinates[0], 2) + Math.pow(closest[1] - coordinates[1], 2) if (distanceNew &lt; distanceSq) { distanceSq = distanceNew result = [coordinates, closest] } }) const coordinates = overlay.getPosition() const closest = geometry.getClosestPoint(coordinates) const distanceNew = Math.pow(closest[0] - coordinates[0], 2) + Math.pow(closest[1] - coordinates[1], 2) if (distanceNew &lt; distanceSq) { distanceSq = distanceNew result = [closest, coordinates] } return new LineString(result) } class _MarkupManager { constructor ({ map, pyramid, affine, drawingSource, formatters, onClick, onStyle } = {}) { this._map = map this._pyramid = pyramid this._affine = affine this._formatters = formatters this._drawingSource = drawingSource this.onClick = onClick this.onStyle = onStyle this._markups = new Map() this._listeners = new Map() this._links = new Collection([], { unique: true }) const defaultColor = defaultStyles.stroke.color this._styleTag = document.createElement('style') this._styleTag.innerHTML = this._getTooltipStyles(defaultColor) this._linksVector = new VectorLayer({ source: new VectorSource({ features: this._links }) }) this._markupsOverlay = new Overlay({ element: this._styleTag }) this._map.addOverlay(this._markupsOverlay) this._map.addLayer(this._linksVector) } /** * Set markups visibility. * * @param {boolean} isVisible * @returns {void} */ setVisible (isVisible) { this._linksVector.setVisible(isVisible) if (isVisible) { this._markups.forEach((markup) => { this._map.removeOverlay(markup.overlay) this._map.addOverlay(markup.overlay) }) return } this._markups.forEach((markup) => { this._map.addOverlay(markup.overlay) this._map.removeOverlay(markup.overlay) }) } /** * Checks whether a markup exists. * * @param {string} id The markup id * @return {boolean} */ has (id) { return this._markups.has(id) } /** * Returns a markup * * @param {string} id The markup id * @return {object} The markup */ get (id) { return this._markups.get(id) } /** * Removes a markup and its link. * * @param {string} id The markup id * @return {string} The markup id */ remove (id) { const markup = this.get(id) if (!markup) { return id } const links = this._links.getArray() const link = links.find((feature) => feature.getId() === id) if (link) { this._links.remove(link) } this._map.removeOverlay(markup.overlay) this._markups.delete(id) if (this._listeners.get(id)) { this._listeners.delete(id) } return id } /** * Set markup visibility. * * @param {string} id The markup id * @param {boolean} isVisible The markup visibility */ setVisibility (id, isVisible) { const markup = this.get(id) if (!markup) { return } const links = this._links.getArray() const link = links.find((feature) => feature.getId() === id) const feature = this._drawingSource.getFeatureById(id) if (!isVisible) { this._map.removeOverlay(markup.overlay) this._links.remove(link) } else { this._map.addOverlay(markup.overlay) this._drawLink(feature) } return isVisible } /** * Creates a new markup * * @param {object} options The options * @param {Feature} options.feature The feature to plug the measure markup * @param {string} options.value The inner content of element * @param {boolean} options.isLinkable Create a link between feature and markup * @param {boolean} options.isDraggable Allow markup to be dragged * @param {array} offset Markup offset * @return {object} The markup object */ create ({ feature, value = '', isLinkable = true, isDraggable = true }) { const id = feature.getId() if (!id) { console.warn('Failed to create markup, feature id not found') return } if (this.has(id)) { console.warn('Markup for feature already exists', id) return this.get(id) } const markup = { id, isLinkable, isDraggable } const element = document.createElement('div') element.id = markup.isDraggable ? Enums.InternalProperties.Markup : '' element.className = 'ol-tooltip ol-tooltip-measure' element.innerText = value const spacedCoordinate = coordinateWithOffset(feature) markup.element = element markup.overlay = new Overlay({ className: 'markup-container', positioning: 'center-center', stopEvent: false, dragging: false, position: spacedCoordinate, element: markup.element }) this._map.addOverlay(markup.overlay) this._markups.set(id, markup) this._drawLink(feature) this._wireInternalEvents(feature) return markup } /** * Wire internal events to markup feature. * * @param {object} feature * @returns {void} */ _wireInternalEvents (feature) { const id = feature.getId() const markup = this.get(id) const listener = feature.on( Enums.FeatureEvents.CHANGE, (event) => { if (this.has(id)) { const view = this._map.getView() const unitSuffix = _getUnitSuffix(view) const format = this._getFormatter(event.target) const output = format( event.target, unitSuffix, this._pyramid, this._affine ) this.update({ feature, value: output, coordinate: event.target.getGeometry().getLastCoordinate() }) this._drawLink(feature) } } ) this._listeners.set(id, listener) this._styleTooltip(feature) /** Keep markup style after external style changes */ feature.on( Enums.FeatureEvents.PROPERTY_CHANGE, ({ key: property, target: feature }) => { if (property === Enums.InternalProperties.StyleOptions) { this._styleTooltip(feature) } } ) /** Update markup style on feature geometry change */ feature.getGeometry().on(Enums.FeatureGeometryEvents.CHANGE, () => { this._styleTooltip(feature) }) let dragPan const dragProperty = 'dragging' this._map.getInteractions().forEach((interaction) => { if (interaction instanceof DragPan) { dragPan = interaction } }) markup.element.addEventListener(Enums.HTMLElementEvents.MOUSE_DOWN, () => { const markup = this.get(id) if (markup) { dragPan.setActive(false) markup.overlay.set(dragProperty, true) } }) this._map.on(Enums.MapEvents.POINTER_MOVE, (event) => { const markup = this.get(id) if ( markup &amp;&amp; markup.overlay.get(dragProperty) === true &amp;&amp; markup.isDraggable ) { /** Doesn't need to have the offset */ markup.overlay.setPosition(event.coordinate) this._drawLink(feature) } }) this._map.on(Enums.MapEvents.POINTER_UP, () => { const markup = this.get(id) if ( markup &amp;&amp; markup.overlay.get(dragProperty) === true &amp;&amp; markup.isDraggable ) { dragPan.setActive(true) markup.overlay.set(dragProperty, false) } }) } onDrawAbort ({ feature }) { this.remove(feature.getId()) } /** * Updates the feature's markup tooltip style. * * @param {object} feature * @returns {void} */ _styleTooltip (feature) { const styleOptions = feature.get(Enums.InternalProperties.StyleOptions) if (styleOptions &amp;&amp; styleOptions.stroke) { const { color } = styleOptions.stroke const tooltipColor = color || defaultStyles.stroke.color const links = this._links.getArray() const link = links.find((link) => link.getId() === feature.getId()) if (link) { const styles = link.getStyle() const stroke = styles.getStroke() stroke.setColor(tooltipColor) styles.setStroke(stroke) link.setStyle(styles) } const marker = this.get(feature.getId()) if (marker) { marker.element.style.color = tooltipColor } } } /** * Returns tooltip styles. * * @param {string} color */ _getTooltipStyles (color) { return ` .ol-tooltip { color: ${color}; white-space: nowrap; font-size: 17px; font-weight: bold; } .ol-tooltip-measure { opacity: 1; } .ol-tooltip-static { color: ${color}; } .ol-tooltip-measure:before, .ol-tooltip-static:before { content: '', } #markup { cursor: move; } .markup-container { display: block !important; } ` } /** * Checks if feature has the correct markup. * * @param {Feature} feature The feature */ _isValidFeature (feature) { return Object.values(Enums.Markup).includes( feature.get(Enums.InternalProperties.Markup) ) } /** * Update markup content. * * @param {object} markup The markup properties * @param {Feature} markup.feature The markup feature * @param {string} markup.value The markup content * @param {string} markup.coordinate The markup coordinate */ update ({ feature, value, coordinate }) { const id = feature.getId() if (!id) { console.warn('Failed attempt to update markup, feature with empty id') return } const markup = this.get(id) if (!markup) { console.warn('No markup found for given feature') return } if (value) { markup.element.innerText = value } if (coordinate) { markup.overlay.setPosition(coordinate) } this._markups.set(id, markup) } /** * This event is responsible assign markup classes on drawEnd event * * @param {object} event The event */ onDrawEnd (event) { const feature = event.feature if (this._isValidFeature(feature)) { const featureId = feature.getId() const markup = this.get(featureId) if (markup) { markup.element.className = 'ol-tooltip ol-tooltip-static' this._markups.set(featureId, markup) } } } /** * This event is responsible assign markup classes on update event * * @param {object} event The event */ onUpdate (feature) { if (this._isValidFeature(feature)) { const featureId = feature.getId() const markup = this.get(featureId) if (markup) { markup.element.className = 'ol-tooltip ol-tooltip-static' this._markups.set(featureId, markup) } } } /** * Returns the string format function for a given markup. * * @param {object} feature The feature * @returns {function} format function */ _getFormatter (feature) { const markup = feature.get(Enums.InternalProperties.Markup) const formatter = this._formatters[markup] if (!formatter) return () => '' return formatter } /** * Draws a link between the feature and the markup. * * @param {object} feature The feature */ _drawLink (feature) { const markup = this.get(feature.getId()) if (!markup || !markup.isLinkable) { return } const line = _getShortestLineBetweenOverlayAndFeature( feature, markup.overlay ) const updated = this._links.getArray().some((feature) => {// eslint-disable-line if (feature.getId() === markup.id) { feature.setGeometry(line) return true } }) if (!updated) { const feature = new Feature({ geometry: line, name: 'Line' }) feature.setId(markup.id) feature.setStyle( new Style({ stroke: new Stroke({ color: defaultStyles.stroke.color, lineDash: [0.3, 7], width: 3 }) }) ) this._links.push(feature) } } } export default _MarkupManager </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="annotation.html">annotation</a></li><li><a href="api.html">api</a></li><li><a href="color.html">color</a></li><li><a href="events.html">events</a></li><li><a href="mapping.html">mapping</a></li><li><a href="metadata.html">metadata</a></li><li><a href="opticalPath.html">opticalPath</a></li><li><a href="roi.html">roi</a></li><li><a href="scoord3d.html">scoord3d</a></li><li><a href="segment.html">segment</a></li><li><a href="utils.html">utils</a></li><li><a href="viewer.html">viewer</a></li></ul><h3>Classes</h3><ul><li><a href="annotation.AnnotationGroup.html">AnnotationGroup</a></li><li><a href="color.PaletteColorLookupTable.html">PaletteColorLookupTable</a></li><li><a href="mapping.ParameterMapping.html">ParameterMapping</a></li><li><a href="mapping.Transformation.html">Transformation</a></li><li><a href="metadata.Comprehensive3DSR.html">Comprehensive3DSR</a></li><li><a href="metadata.MicroscopyBulkSimpleAnnotations.html">MicroscopyBulkSimpleAnnotations</a></li><li><a href="metadata.ParametricMap.html">ParametricMap</a></li><li><a href="metadata.Segmentation.html">Segmentation</a></li><li><a href="metadata.SOPClass.html">SOPClass</a></li><li><a href="metadata.VLWholeSlideMicroscopyImage.html">VLWholeSlideMicroscopyImage</a></li><li><a href="module.exports_module.exports.html">exports</a></li><li><a href="opticalPath.OpticalPath.html">OpticalPath</a></li><li><a href="roi.ROI.html">ROI</a></li><li><a href="scoord3d.Ellipse.html">Ellipse</a></li><li><a href="scoord3d.Ellipsoid.html">Ellipsoid</a></li><li><a href="scoord3d.Multipoint.html">Multipoint</a></li><li><a href="scoord3d.Point.html">Point</a></li><li><a href="scoord3d.Polygon.html">Polygon</a></li><li><a href="scoord3d.Polyline.html">Polyline</a></li><li><a href="scoord3d.Scoord3D.html">Scoord3D</a></li><li><a href="segment.Segment.html">Segment</a></li><li><a href="viewer.LabelImageViewer.html">LabelImageViewer</a></li><li><a href="viewer.OverviewImageViewer.html">OverviewImageViewer</a></li><li><a href="viewer.VolumeImageViewer.html">VolumeImageViewer</a></li></ul><h3>Global</h3><ul><li><a href="global.html#addTask">addTask</a></li><li><a href="global.html#cancelTask">cancelTask</a></li><li><a href="global.html#decode">decode</a></li><li><a href="global.html#getStatistics">getStatistics</a></li><li><a href="global.html#handleMessageFromWorker">handleMessageFromWorker</a></li><li><a href="global.html#initialize">initialize</a></li><li><a href="global.html#loadWebWorkerTask">loadWebWorkerTask</a></li><li><a href="global.html#setTaskPriority">setTaskPriority</a></li><li><a href="global.html#spawnWebWorker">spawnWebWorker</a></li><li><a href="global.html#startTaskOnWebWorker">startTaskOnWebWorker</a></li><li><a href="global.html#terminateAllWebWorkers">terminateAllWebWorkers</a></li><li><a href="global.html#transform">transform</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu Sep 29 2022 16:54:54 GMT-0400 (Eastern Daylight Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>