5 changed files with 562 additions and 3 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,302 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
import 'tooltipster/dist/css/tooltipster.bundle.min.css'; |
|||
import 'tooltipster/dist/js/tooltipster.bundle.min.js'; |
|||
import 'tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css'; |
|||
|
|||
import './image-map.scss'; |
|||
|
|||
const pinShape = '<path id="pin" d="m 12.033721,23.509909 c 0.165665,-3.220958 1.940547,-8.45243 4.512974,-11.745035 1.401507,-1.7940561 2.046337,-3.5425327 2.046337,-4.6032909 0,-3.6844827 -2.951858,-6.67149197 -6.592948,-6.67149197 l -1.68e-4,0 c -3.6412584,0 -6.5929483,2.98700927 -6.5929483,6.67149197 0,1.0607582 0.6448307,2.8092348 2.0463367,4.6032909 2.5724276,3.292605 4.3471416,8.524077 4.5129736,11.745035 l 0.06745,0 z" style="fill:#f2756a;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-opacity:1"/>'; |
|||
const circleShape = '<circle id="circle" fill-rule="evenodd" cy="6.9234" cx="12" clip-rule="evenodd" r="1.5"/>'; |
|||
const pinSvg = `<svg class="image-map-pin-image" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">${pinShape}${circleShape}</svg>`; |
|||
|
|||
export default class TbImageMap { |
|||
|
|||
constructor($containerElement, initCallback, imageUrl, posFunction) { |
|||
|
|||
this.tooltips = []; |
|||
|
|||
$containerElement.append('<div id="image-map-container"><div id="image-map"></div></div>'); |
|||
|
|||
this.imageMapContainer = angular.element('#image-map-container', $containerElement); |
|||
this.imageMap = angular.element('#image-map', $containerElement); |
|||
this.aspect = 0; |
|||
this.width = 0; |
|||
this.height = 0; |
|||
this.markers = []; |
|||
|
|||
if (!imageUrl) { |
|||
imageUrl = 'data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg=='; |
|||
} |
|||
|
|||
this.imageMap.css({backgroundImage: 'url('+imageUrl+')'}); |
|||
|
|||
if (angular.isDefined(posFunction) && posFunction.length > 0) { |
|||
try { |
|||
this.posFunction = new Function('origXPos, origYPos', posFunction); |
|||
} catch (e) { |
|||
this.posFunction = null; |
|||
} |
|||
} |
|||
if (!this.posFunction) { |
|||
this.posFunction = (origXPos, origYPos) => {return {x: origXPos, y: origYPos}}; |
|||
} |
|||
|
|||
var imageMap = this; |
|||
var testImage = new Image(); // eslint-disable-line no-undef
|
|||
testImage.onload = function() { |
|||
imageMap.aspect = testImage.width / testImage.height; |
|||
imageMap.onresize(); |
|||
if (initCallback) { |
|||
setTimeout(initCallback, 0); //eslint-disable-line
|
|||
} |
|||
} |
|||
testImage.src = imageUrl; |
|||
} |
|||
|
|||
onresize() { |
|||
if (this.aspect > 0) { |
|||
var width = this.imageMapContainer.width(); |
|||
if (width > 0) { |
|||
var height = width / this.aspect; |
|||
var imageMapHeight = this.imageMapContainer.height(); |
|||
if (imageMapHeight > 0 && height > imageMapHeight) { |
|||
height = imageMapHeight; |
|||
width = height * this.aspect; |
|||
} |
|||
if (this.width !== width) { |
|||
this.width = width; |
|||
this.height = width / this.aspect; |
|||
this.imageMap.css({width: this.width, height: this.height}); |
|||
this.markers.forEach((marker) => { |
|||
this.updateMarkerDimensions(marker); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
inited() { |
|||
return this.aspect > 0 ? true : false; |
|||
} |
|||
|
|||
updateMarkerLabel(marker, settings) { |
|||
if (settings.showLabel) { |
|||
marker.labelElement.css({color: settings.labelColor}); |
|||
marker.labelElement.html(`<b>${settings.labelText}</b>`); |
|||
} |
|||
} |
|||
|
|||
updateMarkerColor(marker, color) { |
|||
marker.pinSvgElement.css({fill: color}); |
|||
} |
|||
|
|||
updateMarkerImage(marker, settings, image, maxSize) { |
|||
var testImage = new Image(); // eslint-disable-line no-undef
|
|||
var imageMap = this; |
|||
testImage.onload = function() { |
|||
var width; |
|||
var height; |
|||
var aspect = testImage.width / testImage.height; |
|||
if (aspect > 1) { |
|||
width = maxSize; |
|||
height = maxSize / aspect; |
|||
} else { |
|||
width = maxSize * aspect; |
|||
height = maxSize; |
|||
} |
|||
var size = Math.max(width, height); |
|||
marker.size = size; |
|||
if (marker.imgElement) { |
|||
marker.imgElement.remove(); |
|||
} |
|||
marker.imgElement = angular.element(`<img src="${image}" aria-label="pin" class="image-map-pin-image"/>`); |
|||
var left = (size - width)/2; |
|||
var top = (size - height)/2; |
|||
marker.imgElement.css({width: width, height: height, left: left, top: top}); |
|||
marker.pinElement.append(marker.imgElement); |
|||
imageMap.updateMarkerDimensions(marker); |
|||
} |
|||
testImage.src = image; |
|||
} |
|||
|
|||
updateMarkerDimensions(marker) { |
|||
var pinElement = marker.pinElement; |
|||
pinElement.css({width: marker.size, height: marker.size}); |
|||
var left = marker.x * this.width - marker.size/2; |
|||
var top = marker.y * this.height - marker.size; |
|||
pinElement.css({left: left, top: top}); |
|||
} |
|||
|
|||
createMarker(position, settings, onClickListener, markerArgs) { |
|||
var marker = { |
|||
size: 34, |
|||
position: position |
|||
}; |
|||
var pos = this.posFunction(position.x, position.y); |
|||
marker.x = pos.x; |
|||
marker.y = pos.y; |
|||
marker.pinElement = angular.element('<div class="image-map-pin"></div>'); |
|||
|
|||
if (settings.showLabel) { |
|||
marker.labelElement = angular.element(`<div class="image-map-pin-title"><b>${settings.labelText}</b></div>`); |
|||
marker.labelElement.css({color: settings.labelColor}); |
|||
marker.pinElement.append(marker.labelElement); |
|||
} |
|||
|
|||
marker.imgElement = angular.element(pinSvg); |
|||
marker.pinSvgElement = marker.imgElement.find('#pin'); |
|||
marker.pinElement.append(marker.imgElement); |
|||
|
|||
marker.pinSvgElement.css({fill: settings.color}); |
|||
|
|||
this.updateMarkerDimensions(marker); |
|||
|
|||
this.imageMap.append(marker.pinElement); |
|||
|
|||
if (settings.useMarkerImage) { |
|||
this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34); |
|||
} |
|||
|
|||
if (settings.displayTooltip) { |
|||
this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs); |
|||
} |
|||
|
|||
if (onClickListener) { |
|||
marker.pinElement.on('click', onClickListener); |
|||
} |
|||
|
|||
this.markers.push(marker); |
|||
return marker; |
|||
} |
|||
|
|||
removeMarker(marker) { |
|||
var index = this.markers.indexOf(marker); |
|||
if (index > -1) { |
|||
marker.pinElement.remove(); |
|||
this.markers.splice(index, 1); |
|||
} |
|||
} |
|||
|
|||
createTooltip(marker, pattern, replaceInfo, markerArgs) { |
|||
var popup = new Popup(marker.pinElement); |
|||
popup.setContent(''); |
|||
this.tooltips.push( { |
|||
markerArgs: markerArgs, |
|||
popup: popup, |
|||
pattern: pattern, |
|||
replaceInfo: replaceInfo |
|||
}); |
|||
} |
|||
|
|||
updatePolylineColor(/*polyline, settings, color*/) { |
|||
} |
|||
|
|||
createPolyline(/*locations, settings*/) { |
|||
} |
|||
|
|||
removePolyline(/*polyline*/) { |
|||
} |
|||
|
|||
fitBounds() { |
|||
} |
|||
|
|||
createLatLng(x, y) { |
|||
return new Position(x, y); |
|||
} |
|||
|
|||
extendBoundsWithMarker() { |
|||
} |
|||
|
|||
getMarkerPosition(marker) { |
|||
return marker.position; |
|||
} |
|||
|
|||
setMarkerPosition(marker, position) { |
|||
marker.position = position; |
|||
var pos = this.posFunction(position.x, position.y); |
|||
marker.x = pos.x; |
|||
marker.y = pos.y; |
|||
this.updateMarkerDimensions(marker); |
|||
} |
|||
|
|||
getPolylineLatLngs(/*polyline*/) { |
|||
} |
|||
|
|||
setPolylineLatLngs(/*polyline, latLngs*/) { |
|||
} |
|||
|
|||
createBounds() { |
|||
return {}; |
|||
} |
|||
|
|||
extendBounds() { |
|||
} |
|||
|
|||
invalidateSize() { |
|||
this.onresize(); |
|||
} |
|||
|
|||
getTooltips() { |
|||
return this.tooltips; |
|||
} |
|||
|
|||
} |
|||
|
|||
class Position { |
|||
constructor(x, y) { |
|||
this.x = x; |
|||
this.y = y; |
|||
} |
|||
|
|||
equals(loc) { |
|||
return loc && loc.x == this.x && loc.y == this.y; |
|||
} |
|||
} |
|||
|
|||
class Popup { |
|||
constructor(anchor) { |
|||
anchor.tooltipster( |
|||
{ |
|||
theme: 'tooltipster-shadow', |
|||
delay: 100, |
|||
trigger: 'custom', |
|||
triggerOpen: { |
|||
click: true, |
|||
tap: true |
|||
}, |
|||
trackOrigin: true |
|||
} |
|||
); |
|||
this.tooltip = anchor.tooltipster('instance'); |
|||
var contentElement = angular.element('<div class="image-map-pin-tooltip">' + |
|||
'<a class="image-map-pin-tooltip-close-button" id="close" style="outline: none;">×</a>' + |
|||
'<div flex id="tooltip-content" layout="column">' + |
|||
'</div>' + |
|||
'</div>'); |
|||
var popup = this; |
|||
contentElement.find('#close').on('click', function() { |
|||
popup.tooltip.close(); |
|||
}); |
|||
this.content = contentElement.find('#tooltip-content'); |
|||
this.tooltip.content(contentElement); |
|||
} |
|||
|
|||
setContent(content) { |
|||
this.content.html(content); |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
.image-map-pin-tooltip { |
|||
pointer-events: all; |
|||
padding: 5px; |
|||
.image-map-pin-tooltip-close-button { |
|||
cursor: pointer; |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
padding: 6px 6px 0 0; |
|||
border: none; |
|||
text-align: center; |
|||
width: 20px; |
|||
height: 16px; |
|||
font: 18px/16px Tahoma, Verdana, sans-serif; |
|||
color: #b0b0b0; |
|||
text-decoration: none; |
|||
font-weight: bold; |
|||
background: transparent; |
|||
&:hover { |
|||
color: #919191; |
|||
} |
|||
} |
|||
#tooltip-content { |
|||
line-height: normal; |
|||
font-size: 13px; |
|||
font-weight: 300; |
|||
color: #333; |
|||
} |
|||
} |
|||
|
|||
#image-map-container { |
|||
width: 100%; |
|||
height: 100%; |
|||
#image-map { |
|||
color: rgba(0, 0, 0, 0.870588); |
|||
position: relative; |
|||
margin: auto; |
|||
background: transparent no-repeat scroll 0 0; |
|||
background-size: cover; |
|||
|
|||
&.is-pointer { |
|||
cursor: pointer !important; |
|||
} |
|||
|
|||
.movable { |
|||
cursor: move; |
|||
} |
|||
|
|||
.image-map-pin { |
|||
outline: none; |
|||
position: absolute; |
|||
background: none; |
|||
.image-map-pin-title { |
|||
position: relative; |
|||
white-space: nowrap; |
|||
text-align: center; |
|||
line-height: 1.5; |
|||
font-size: 12px; |
|||
font-weight: 400; |
|||
top: -20px; |
|||
&:before { |
|||
content: ""; |
|||
margin-left: -100%; |
|||
} |
|||
&:after { |
|||
content: ""; |
|||
margin-right: -100%; |
|||
} |
|||
} |
|||
.image-map-pin-image { |
|||
position: absolute; |
|||
pointer-events: none; |
|||
top: 0; |
|||
bottom: 0; |
|||
right: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue