markerclusterer: anchor offset for cluster icons - google-maps

I'm trying to slightly offset cluster icons created by the Google Maps Markerclusterer (V3). Short of modifying the existing code, I can't find a way to do this. Does anybody have an idea?
The Styles object in which you can provide a custom image URL accepts an anchor property, but this is to offset the generated marker item count.
Thanks!

The proper way to do it is to adjust the anchorIcon property like this:
var clusterStyles = [
{
height: 64,
width: 53,
anchorIcon: [20, 140]
},
{
height: 64,
width: 53,
anchorIcon: [20, 140]
},
{
height: 64,
width: 53,
anchorIcon: [20, 140]
}
];
var mcOptions = {
styles: clusterStyles
};
var markerCluster = new MarkerClusterer(map, markers, mcOptions);

The accepted answer does not work well enough for me - adding transparent space to the icon image can change the way click and hover events behave due to the increased size of the object.
I would use the anchorIcon property except it's only available in Marker Clusterer Plus, not the other Marker Clusterer plugin (which I'm using).
For those that specifically want to use Marker Clusterer - you can override ClusterIcon.prototype.getPosFromLatLng_. The ClusterIcon object is global, so we can modify it at the top-level of any script file without messing with the plugin's source code.
This will anchor the marker to the bottom of the icon:
ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
var pos = this.getProjection().fromLatLngToDivPixel(latlng);
pos.x -= parseInt(this.width_ / 2);
pos.y -= parseInt(this.height_);
return pos;
};

I changed the code of marcerclusterer.js to support anchorText parameter by modifying following two functions:
/**
* Sets the icon to the the styles.
*/
ClusterIcon.prototype.useStyle = function() {
var index = Math.max(0, this.sums_.index - 1);
index = Math.min(this.styles_.length - 1, index);
var style = this.styles_[index];
this.url_ = style['url'];
this.height_ = style['height'];
this.width_ = style['width'];
this.textColor_ = style['textColor'];
this.anchor_ = style['anchor'];
this.anchorText_ = style['anchorText']; //added to support anchorText parameter by Frane Poljak, Locastic
this.textSize_ = style['textSize'];
this.backgroundPosition_ = style['backgroundPosition'];
};
/**
* Adding the cluster icon to the dom.
* #ignore
*/
ClusterIcon.prototype.onAdd = function() {
this.div_ = document.createElement('DIV');
if (this.visible_) {
var pos = this.getPosFromLatLng_(this.center_);
this.div_.style.cssText = this.createCss(pos);
////added to support anchorText parameter by Frane Poljak, Locastic
if (typeof this.anchorText_ === 'object' && typeof this.anchorText_[0] === 'number' && typeof this.anchorText_[1] === 'number') {
this.div_.innerHTML = '<span style="position:relative;top:' + String(this.anchorText_[0]) + 'px;left:' + String(this.anchorText_[1]) + 'px;">' + this.sums_.text + '</span>'
} else this.div_.innerHTML = this.sums_.text;
}
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.div_);
var that = this;
google.maps.event.addDomListener(this.div_, 'click', function() {
that.triggerClusterClick();
});
};

You could add some transparent space to one side of your cluster icon's PNG, so that the part of the icon which you'd like to be centred is actually also centred in your PNG. This should not increase the weight of your image by more than a few bytes.

anchor / anchorIcon/ anchorText properties didn't work for me...so I made kind of workaround:
I use setCalculator() function to set the cluster text:
https://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/docs/reference.html
when I am setting the cluster text property I am wrapping the value with <span>,
something like this:
markerCluster.setCalculator(function (markers) {
return {
text: '<span class="myClass">' + value+ '</span>',
index: index
};
});
now you can control the cluster label position with ".myClass":
span.myClass{
position: relative;
top: -15px;
.....
}

Related

How to make a image dynamically change to text

I am making an online game using Phaser and I need to make buttons with text on them for it that can change based on the text because the text can be different each time. I tried checking the API document but when I put in the get size function to try to get the bounds of the text my button disappears or the code will stop working with the error saying cannot read properties of undefined (reading getBounds) and it will swap between the two every time I reload the page.
count = Phaser.Math.Between(1,4)
for(let i = 50;i <= 750;i = i +200){
bingus = this.add.text(i, 400, quiz[category][difficulty][quest][count])
answs.push(bingus)
gorp.push(count)
count++
}
if(count > 4){
count = 1
}
}
this.butt1.setDisplaySize(Phaser.Geom.Rectangle.GetSize(answs[gorp[0]].getBounds()))
You could use the Phaser.GameObjects.Text and it's displayWidth and / or displayHeight properties, together with the global Phaser.Display.Align.In.Center function.
Maybe this works also for your UseCase.
Basically:
set the text of the text Object with setText
get the current displayWidth and displayHeight of the text Object
update/adjust the size of the button Object, also with displayWidth and displayHeight properties
Center the text Object inside of the button Object, with the Phaser.Display.Align.In.Center function
Here a small working Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 500,
height: 180,
scene: {
create
},
banner: false
};
let text;
let button;
let texts = ['first Text', 'Next', 'Second Text', 'Last', 'multiline1.\nmultiline2..\nmultiline3...' ];
let padding = 20;
let currentTextIdx = 0;
function create () {
this.add.text(10, 10, 'Text cycles about every second.')
button = this.add.rectangle(250, 90, 100, 40, 0xff0000 )
.setOrigin(.5);
text = this.add.text(250, 90, texts[currentTextIdx])
.setOrigin(.5);
this.time.addEvent({ delay: 1000, startAt:999, loop: true , callback: _ => {
currentTextIdx++;
if(currentTextIdx >= texts.length){
currentTextIdx = 0;
}
let newText = texts[currentTextIdx];
text.setText(newText);
button.displayWidth = padding * 2 + text.displayWidth;
button.displayHeight = padding * 2 + text.displayHeight;
Phaser.Display.Align.In.Center(text, button);
}});
}
new Phaser.Game(config);
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

How do I force the pointer icon to 'grab', override underlying cursor styles while panning?

When I set the css of the container to 'grab' I see that it changes, but most of the viewport contains divs with pointer 'hand', which override it.
I want to give the impression that the grab is strictly for panning the contents of the viewport and nothing else.
Some ideas:
use !important (but Javascript css doesn't support that).
Use an invisible or 0 opacity div for panning mode (I am going to try this
one.
Something better that is commonly used that I don't know about
yet.
I wound up creating a jsFiddle by modifying one that I found which was doing something similar.
var scale = 1.0;
var drag = {
elem: null,
x: 0,
y: 0,
state: false
};
var delta = {
x: 0,
y: 0
};
$(document).mousedown(function(e) {
if (!drag.state) {
let $workspace = $('#workspace');
let $workspaceContents = $('.workspace_contents');
$('#pan-screen').css({
width: $workspaceContents.width,
height: $workspaceContents.height
});
drag.elem = $('.workspace-contents');
drag.x = e.pageX;
drag.y = e.pageY;
drag.state = true;
}
return false;
});
$(document).mousemove(function(e) {
if (drag.state) {
delta.x = e.pageX - drag.x;
delta.y = e.pageY - drag.y;
var cur_offset = $(drag.elem).offset();
$(drag.elem).offset({
left: (cur_offset.left + delta.x),
top: (cur_offset.top + delta.y)
});
drag.x = e.pageX;
drag.y = e.pageY;
}
});
$(document).mouseup(function() {
if (drag.state) {
drag.state = false;
}
});
solution fiddle:
https://jsfiddle.net/davidjmcclelland/t51b96em/27/

How to move 3D model on Cesium

I wanted to move the model dynamically using keyboard shortcuts. I could not find relevant article on that.
So for now, I'm trying to move the model on click. When click on the model. The model has to move in one direction (increment the value 1 on tick). Find below the sandcastle code for that.
var selectedMesh; var i=0;
var viewer = new Cesium.Viewer('cesiumContainer', {
infoBox: false,
selectionIndicator: false
});
var handle = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
function createModel(url, height) {
viewer.entities.removeAll();
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);
var entity = viewer.entities.add({
name: url,
position: position,
orientation: orientation,
model: {
uri: url,
minimumPixelSize: 128
}
});
viewer.trackedEntity = entity;
viewer.clock.onTick.addEventListener(function () {
if (selectedMesh) {
console.log("Before 0 : " + selectedMesh.primitive.modelMatrix[12]);
selectedMesh.primitive.modelMatrix[12] = selectedMesh.primitive.modelMatrix[12] + 1;
console.log("After 0 : " + selectedMesh.primitive.modelMatrix[12]);
}
});
}
handle.setInputAction(function (movement) {
console.log("LEFT CLICK");
var pick = viewer.scene.pick(movement.position);
if (Cesium.defined(pick) && Cesium.defined(pick.node) && Cesium.defined(pick.mesh)) {
if (!selectedMesh) {
selectedMesh = pick;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
var options = [{
text: 'Aircraft',
onselect: function () {
createModel('../../SampleData/models/CesiumAir/Cesium_Air.bgltf', 5000.0);
}
}, {
text: 'Ground vehicle',
onselect: function () {
createModel('../../SampleData/models/CesiumGround/Cesium_Ground.bgltf', 0);
}
}, {
text: 'Milk truck',
onselect: function () {
createModel('../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.bgltf', 0);
}
}, {
text: 'Skinned character',
onselect: function () {
createModel('../../SampleData/models/CesiumMan/Cesium_Man.bgltf', 0);
}
}];
Sandcastle.addToolbarMenu(options);
When I click, the model is moving for the first time. After that, It stays on the same place. I've printed the value in the console. It seems the value is not changing. I'm not sure about the problem here. or I'm implementing the transformation wrongly.
If you keep track of the current lat and lon of the entity, and adjust that lat and lon based on user input, all you need to do is update the orientation of the entity.
var lon = // the updated lon
var lat = // updated lat
var position = Cesium.Cartesian3.fromDegrees(lon, lat, height);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
// create an orientation based on the new position
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);
Then you just need to update the orientation of the entity.
entity.orientation = orientation;
By changing the value, the models orientation, and therefore position will get updated.

Openlayers zoom in or out

I am quite new to Openlayers and was wondering if there is a method or event that returns the zooming direction, e.g. onzoomin/onzoomout events. I am using sproutcore 1.0 and trying to modify a feature font according to the zooming level. I tried working with Rules but according to the application structure this does not work. Here is my sample event of what I want to do:
this.map.events.on({ "zoomend": function (e) {
var sub = 0;
if (ZOOMOUT){
sub = this.getZoom();
} else {
sub = this.getZoom() * -1;
}
var font = myFeature.layer.styleMap.styles['default'].defaultStyle.fontSize;
font = font + sub*10;
myFeature.layer.redraw();
}});
Found a workaround using geometry bounds which gives a good result:
this.map.events.on({ "zoomend": function (e) {
var width = myFeature.geometry.bounds.right - myFeature.geometry.bounds.left;
var div = 0;
if (this.getZoom() > 12) {
div = 4;
} else {
div = 6;
}
myFeature.layer.styleMap.styles['default'].defaultStyle.fontSize = (width/((15 - this.getZoom())+1)) / div).toString() + "px";
myFeature.layer.redraw();
}});

Is possible create map html area in percentage?

I need to create something like this:
http://www.mrporter.com/journal/journal_issue71/2#2
where every product in my big image is associated with a tooltip which appears on mouse hover.
But I need this to work with fullscreen images.
The first solution I thought (as the example above) is the map html solution where each fill up exactly the boundaries of my products.
The problem is that I can't indicate precise values for my because my image size depends on window screen.
The best solution would be the possibility to set percentage values for my area.
Is this possible? Any other suggestions ?
Alternative solution using links:
CSS:
.image{
position: relative;
}
.image a{
display: block;
position: absolute;
}
HTML:
<div class="image">
<img src="image.jpg" alt="image" />
</div>
Percentage dimensions can be detected in graphic editors
There is a jQuery plugin for this jQuery RWD Image Maps.
You might want to integrate my pending pull request (manually) to support "width=100%": https://github.com/stowball/jQuery-rwdImageMaps/pull/10
you can check this this plugin is life saving.
Useful when you want to map a percentage scaled image etc.
It can be used with or without jQuery.
https://github.com/davidjbradshaw/imagemap-resizer
and you can see it working at.
http://davidjbradshaw.com/imagemap-resizer/example/
Because this can't be done with simple HTML/CSS manipulation, the only alternative is JavaScript to, effectively, recalculate the coordinates based on the resizing of the image. To this end I've put together a function (though there's two functions involved) that achieves this end:
function findSizes(el, src) {
if (!el || !src) {
return false;
}
else {
var wGCS = window.getComputedStyle,
pI = parseInt,
dimensions = {};
dimensions.actualWidth = pI(wGCS(el, null).width.replace('px', ''), 10);
var newImg = document.createElement('img');
newImg.src = src;
newImg.style.position = 'absolute';
newImg.style.left = '-10000px';
document.body.appendChild(newImg);
dimensions.originalWidth = newImg.width;
document.body.removeChild(newImg);
return dimensions;
}
}
function remap(imgElem) {
if (!imgElem) {
return false;
}
else {
var mapName = imgElem
.getAttribute('usemap')
.substring(1),
map = document.getElementsByName(mapName)[0],
areas = map.getElementsByTagName('area'),
imgSrc = imgElem.src,
sizes = findSizes(imgElem, imgSrc),
currentWidth = sizes.actualWidth,
originalWidth = sizes.originalWidth,
multiplier = currentWidth / originalWidth,
newCoords;
for (var i = 0, len = areas.length; i < len; i++) {
newCoords = areas[i]
.getAttribute('coords')
.replace(/(\d+)/g,function(a){
return Math.round(a * multiplier);
});
areas[i].setAttribute('coords',newCoords);
}
}
}
var imgElement = document.getElementsByTagName('img')[0];
remap(imgElement);​
JS Fiddle demo.
Please note, though, that this requires a browser that implements window.getComputedStyle() (most current browsers, but only in IE from version 9, and above). Also, there are no sanity checks other than ensuring the required arguments are passed into the functions. These should, though, be a start if you want to experiment.
References:
document.body.
document.createElement().
document.getElementsByName().
document.getElementsByTagName().
element.getAttribute().
element.setAttribute().
element.style.
Math.round().
node.appendChild().
node.removeChild().
parseInt().
string.replace().
string.substring().
window.getComputedStyle.
Percentages in image maps are not an option. You might want to get some scripting involved (JS) that recalculates the exact position on images resize. Of course, in that script you can work with percentages if you want.
Consider using the Raphaël JavaScript Library with some CSS. See http://raphaeljs.com/ and Drawing over an image using Raphael.js.
I know this is an old question but maybe someone needs this at some point as I did. I modified #David Thomas' answer a bit to be have this little piece of JS be able to handle future recalculations:
function findSizes(el, src) {
if (!el || !src) {
return false;
}
else {
var wGCS = window.getComputedStyle,
pI = parseInt,
dimensions = {};
dimensions.actualWidth = pI(wGCS(el, null).width.replace('px', ''), 10);
var newImg = document.createElement('img');
newImg.src = src;
newImg.style.position = 'absolute';
newImg.style.left = '-10000px';
document.body.appendChild(newImg);
dimensions.originalWidth = newImg.width;
document.body.removeChild(newImg);
return dimensions;
}
}
function remap(imgElem) {
if (!imgElem) {
return false;
}
else {
var mapName = imgElem
.getAttribute('usemap')
.substring(1),
map = document.getElementsByName(mapName)[0],
areas = map.getElementsByTagName('area'),
imgSrc = imgElem.src,
sizes = findSizes(imgElem, imgSrc),
currentWidth = sizes.actualWidth,
originalWidth = sizes.originalWidth,
multiplier = currentWidth / originalWidth,
newCoords;
for (var i = 0, len = areas.length; i < len; i++) {
// Save original coordinates for future use
var originalCoords = areas[i].getAttribute('data-original-coords');
if (originalCoords == undefined) {
originalCoords = areas[i].getAttribute('coords');
areas[i].setAttribute('data-original-coords', originalCoords);
}
newCoords = originalCoords.replace(/(\d+)/g,function(a){
return Math.round(a * multiplier);
});
areas[i].setAttribute('coords',newCoords);
}
}
}
function remapImage() {
var imgElement = document.getElementsByTagName('img')[0];
remap(imgElement);
}
// Add a window resize event listener
var addEvent = function(object, type, callback) {
if (object == null || typeof(object) == 'undefined') return;
if (object.addEventListener) {
object.addEventListener(type, callback, false);
} else if (object.attachEvent) {
object.attachEvent("on" + type, callback);
} else {
object["on"+type] = callback;
}
};
addEvent(window, "resize", remapImage);