I'm building a timeline in vue.js app so I decided to use vis.js but I'm having problems with it when I want to add some events. First of all when i set #drop="myDropCallback()" and when I drop one item nothing happens so the function is not called but when i put #mouseOver="myDropCallback()" then it works, its strange.
Second when I'm doing the mouseOver event I want to get the event properties with this.$refs.timeline.getEventProperties(event) but I'm getting this error every time
Error in event handler for "click": "TypeError: Cannot read property 'center' of undefined"
and this error
Cannot read property 'center' of undefined
So does anyone know how to fix that? Or am I doing something wrong?
Template
<timeline v-if="items.length > 0" ref="timeline"
:items="items"
:groups="groups"
:options="options"
#drop="myDropCallback()">
</timeline>
Drop function
myDropCallback: function (event) {
console.log('value', this.$refs.timeline.getEventProperties())
},
Picture of timeline
Here's an excerpt from the vis.js source. You'll notice that the first thing it tries to do is to find the event's center value.
Timeline.prototype.getEventProperties = function (event) {
var clientX = event.center ? event.center.x : event.clientX;
var clientY = event.center ? event.center.y : event.clientY;
var x;
if (this.options.rtl) {
x = util.getAbsoluteRight(this.dom.centerContainer) - clientX;
} else {
x = clientX - util.getAbsoluteLeft(this.dom.centerContainer);
}
var y = clientY - util.getAbsoluteTop(this.dom.centerContainer);
var item = this.itemSet.itemFromTarget(event);
var group = this.itemSet.groupFromTarget(event);
var customTime = CustomTime.customTimeFromTarget(event);
var snap = this.itemSet.options.snap || null;
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
var time = this._toTime(x);
var snappedTime = snap ? snap(time, scale, step) : time;
var element = util.getTarget(event);
var what = null;
if (item != null) {
what = 'item';
} else if (customTime != null) {
what = 'custom-time';
} else if (util.hasParent(element, this.timeAxis.dom.foreground)) {
what = 'axis';
} else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {
what = 'axis';
} else if (util.hasParent(element, this.itemSet.dom.labelSet)) {
what = 'group-label';
} else if (util.hasParent(element, this.currentTime.bar)) {
what = 'current-time';
} else if (util.hasParent(element, this.dom.center)) {
what = 'background';
}
return {
event: event,
item: item ? item.id : null,
group: group ? group.groupId : null,
what: what,
pageX: event.srcEvent ? event.srcEvent.pageX : event.pageX,
pageY: event.srcEvent ? event.srcEvent.pageY : event.pageY,
x: x,
y: y,
time: time,
snappedTime: snappedTime
};
};
So your issue is most likely due to not giving the method a valid event. I believe that this is because you aren't supplying any parameters to the getEventProperties method. I would try something like:
myDropCallback: function (event) {
console.log('value', this.$refs.timeline.getEventProperties(event))
},
Also, here is a good stack overflow post answered by one of the authors of vis.js: vis.js timeline how to add mouse over event to vis-item box-box
Related
Chart.js 2.2.1
Any idea how to trigger the code that runs when I hover over a datapoint, and that runs when I move the mouse off? I need to programmatically show and hide a chart's tooltip.
openTip(oChart, datasetIndex, pointIndex){
// how to open a specific tooltip?
}
closeTip(oChart, datasetIndex, pointIndex){
// how to close the same tooltip?
}
I would show sample code if I could, but I don't even know where to start. The chart method docs haven't helped.
JSFiddle
I would be careful accessing/modifying private variables that begin with _. You may find yourself with unexpected behavior. Why not trigger the canvas mousemove event
function openToolTip (myChart, index) {
var mouseMoveEvent, meta, point, rectangle, value;
meta = myChart.getDatasetMeta(0);
rectangle = myChart.canvas.getBoundingClientRect();
point = meta.data[index].getCenterPoint();
mouseMoveEvent = new MouseEvent('mousemove', {
clientX: rectangle.left + point.x,
clientY: rectangle.top + point.y
});
myChart.canvas.dispatchEvent(mouseMoveEvent);
},
To close the tooltip just trigger the mouseout event
function closeToolTip (myChart) {
var mouseOutEvent = new MouseEvent('mouseout');
return myChart.canvas.dispatchEvent(mouseOutEvent);
}
The code below will handle one or more tooltips.
function openTip(oChart,datasetIndex,pointIndex){
if(window.oChart.tooltip._active == undefined)
window.oChart.tooltip._active = []
var activeElements = window.oChart.tooltip._active;
var requestedElem = window.oChart.getDatasetMeta(datasetIndex).data[pointIndex];
for(var i = 0; i < activeElements.length; i++) {
if(requestedElem._index == activeElements[i]._index)
return;
}
activeElements.push(requestedElem);
//window.oChart.tooltip._view.body = window.oChart.getDatasetMeta(datasetIndex).data;
window.oChart.tooltip._active = activeElements;
window.oChart.tooltip.update(true);
window.oChart.draw();
}
function closeTip(oChart,datasetIndex,pointIndex){
var activeElements = window.oChart.tooltip._active;
if(activeElements == undefined || activeElements.length == 0)
return;
var requestedElem = window.oChart.getDatasetMeta(datasetIndex).data[pointIndex];
for(var i = 0; i < activeElements.length; i++) {
if(requestedElem._index == activeElements[i]._index) {
activeElements.splice(i, 1);
break;
}
}
window.oChart.tooltip._active = activeElements;
window.oChart.tooltip.update(true);
window.oChart.draw();
}
Complete solution provided by #BeetleJuice - https://jsfiddle.net/ucvvvnm4/5/
For Chart.js#3 here's official solution: https://www.chartjs.org/docs/latest/samples/advanced/programmatic-events.html
function triggerTooltip(chart) {
const tooltip = chart.tooltip;
if (tooltip.getActiveElements().length > 0) {
tooltip.setActiveElements([], {x: 0, y: 0});
} else {
const chartArea = chart.chartArea;
tooltip.setActiveElements([
{
datasetIndex: 0,
index: 2,
}, {
datasetIndex: 1,
index: 2,
}
],
{
x: (chartArea.left + chartArea.right) / 2,
y: (chartArea.top + chartArea.bottom) / 2,
});
}
chart.update();
}
Okay, this question might seem a bit abstract but let me make this a little clearer. This is regarding a problem I am trying to solve it using a charting library called Chartist. They have this system called plugin where in you can add some additional functionality on the charts. Now how to write a plugin is explained here and at the bottom of this page.
Now I created a plunker to demonstrate the issue... The issue I am facing is that whenever I click a drop down the value in the axisTitle never changes...
You may ask why?
The reason is these lines of chartist-plugin-axistitle.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function () {
return (root.returnExportsGlobal = factory());
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like enviroments that support module.exports,
// like Node.
module.exports = factory();
} else {
root['Chartist.plugins.ctAxisTitle'] = factory();
}
}(this, function () {
/**
* Chartist.js plugin to display a title for 1 or 2 axises.
*
*/
/* global Chartist */
(function (window, document, Chartist) {
'use strict';
var axisDefaults = {
axisTitle: '',
axisClass: 'ct-axis-title',
offset: {
x: 0,
y: 0
},
textAnchor: 'middle',
flipText: false
};
var defaultOptions = {
xAxis: axisDefaults,
yAxis: axisDefaults
};
Chartist.plugins = Chartist.plugins || {};
Chartist.plugins.ctAxisTitle = function (options) {
options = Chartist.extend({}, defaultOptions, options);
return function ctAxisTitle(chart) {
chart.on('created', function (data) {
if (!options.axisX.axisTitle && !options.axisY.axisTitle) {
throw new Error('ctAxisTitle plugin - You must provide at least one axis title');
} else if (!data.axisX && !data.axisY) {
throw new Error('ctAxisTitle plugin can only be used on charts that have at least one axis');
}
var xPos;
var yPos;
var title;
//position axis X title
if (options.axisX.axisTitle && data.axisX) {
xPos = (data.axisX.axisLength / 2) + data.options.axisX.offset + data.options.chartPadding.left;
yPos = data.options.chartPadding.top;
if (data.options.axisX.position === 'end') {
yPos += data.axisY.axisLength;
}
title = new Chartist.Svg("text");
title.addClass(options.axisX.axisClass);
title.text(options.axisX.axisTitle);
title.attr({
x: xPos + options.axisX.offset.x,
y: yPos + options.axisX.offset.y,
"text-anchor": options.axisX.textAnchor
});
data.svg.append(title, true);
}
//position axis Y title
if (options.axisY.axisTitle && data.axisY) {
xPos = 0;
yPos = (data.axisY.axisLength / 2) + data.options.chartPadding.top;
if (data.options.axisY.position === 'end') {
xPos = data.axisX.axisLength;
}
var transform = 'rotate(' + (options.axisY.flipTitle ? -90 : 90) + ', ' + xPos + ', ' + yPos + ')';
title = new Chartist.Svg("text");
title.addClass(options.axisY.axisClass);
title.text(options.axisY.axisTitle);
title.attr({
x: xPos + options.axisY.offset.x,
y: yPos + options.axisY.offset.y,
transform: transform,
"text-anchor": options.axisY.textAnchor
});
data.svg.append(title, true);
}
});
chart.on('optionsChanged', function(data){
console.log("Saras");
});
};
};
}(window, document, Chartist));
return Chartist.plugins.ctAxisTitle;
}));
If you put up a console.log just below the options = Chartist.extend({}, defaultOptions, options); line. You will see the options changing on dropdown click... but they actually never change, apart from this one time when the chart is created.
Now I somehow want the updated options to reflect in the return function, but the issue is you can only return it once.
So the question really is How do I call the ctAxisTitle function again and again on update
So is this a design flaw? Should the design of plugin be changed, if yes... how?? Or I can manipulate the code in some way to achieve the functionality.
I have also created a Github repo, to quickly get you started on it
In my opinion this is a design flaw in angular-chartist.js. If the data or options are changed they do the following:
// If chart type changed we need to recreate whole chart, otherwise we can update
if (!this.chart || newConfig.chartType !== oldConfig.chartType) {
this.renderChart();
} else {
this.chart.update(this.data, this.options);
}
So when they only rerender the chart when the chart type is changed, this isn't what you want.
If you have this file locally you could just modify that part to match your needs and always render the chart by replacing this lines with this.renderChart();
See this plunker for an example where I did the above.
i'm trying to make a magento extension work on android mobile devices. I bought this extension :
http://ecommerce.aheadworks.com/magento-extensions/layered-navigation.html
to get ajax layered navigation. It works well, but when it comes to android compatibility i get an issue on the range selector (slider selector, used for price range).
The feature works well on all device (ios included), but on android, i always got NaN instead of numbers values on the range selector. After digging the plugin's code, i found the origin in the javascript :
startDrag: function(event) {
var isLeftClick = Event.isLeftClick(event);
if (Prototype.Browser.IE) {
var ieVersion = parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5));
if (ieVersion > 8) {
isLeftClick = event.which === 1;
}
}
if (isLeftClick || event.type === 'touchstart') {
if (!this.disabled){
this.active = true;
var handle = Event.element(event);
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var track = handle;
if (track==this.track) {
var offsets = Position.cumulativeOffset(this.track);
this.event = event;
this.setValue(this.translateToValue(
(this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
));
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
} else {
// find the handle (prevents issues with Safari)
while((this.handles.indexOf(handle) == -1) && handle.parentNode)
handle = handle.parentNode;
if (this.handles.indexOf(handle)!=-1) {
this.activeHandle = handle;
this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
this.updateStyles();
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
}
}
}
Event.stop(event);
}
}
this line :
var pointer = [Event.pointerX(event), Event.pointerY(event)];
return a correct array of value (mouse coordinates) on all devices, except on android, where i get NaN. Is this a known bug of prototype.js, and can I bypass it ?
thanks,
Problem solved by digging the event object, then manually retrieving the X and Y pos (tested clientX, screenX and pageX) :
var pointer = [Event.pointerX(event), Event.pointerY(event)];
if(navigator.userAgent.match(/Android/i)){
pointer = [parseInt(event.targetTouches[0].clientX),parseInt(event.targetTouches[0].clientY)];
}
I had the same issue, I know this is an old thread but want to post my solution anyway. I needed to override the functions in swiper.js.
update: function(event) {
if (this.active) {
if (!this.dragging) this.dragging = true;
this.draw(event);
if (Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
}
},
draw: function(event) {
var pointer = [Event.pointerX(event), Event.pointerY(event)];
if(navigator.userAgent.match(/Android/i)){
pointer = [parseInt(event.targetTouches[0].clientX),parseInt(event.targetTouches[0].clientY)];
}
var offsets = Position.cumulativeOffset(this.track);
pointer[0] -= this.offsetX + offsets[0];
pointer[1] -= this.offsetY + offsets[1];
this.event = event;
this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
if (this.initialized && this.options.onSlide)
this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
},
And this fixed my issue
I managed to add interactivity to a feature layer added from a remote GeoJSON resource. When I click on a feature I get its ID, fire an AJAX request and display some relevant info about the feature, on the page outside of the map area.
I used a Select interaction.
I would like to make it even clearer to the user that he can click on the features on the map. Is there any way I can change the mouse cursor to "cursor" of "hand" when the mouse hovers a feature contained in a ol.layer.Vector ?
I couldn't find anything in the doc, on this site or by googling.
It can be done as well without jQuery:
map.on("pointermove", function (evt) {
var hit = this.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
return true;
});
if (hit) {
this.getTargetElement().style.cursor = 'pointer';
} else {
this.getTargetElement().style.cursor = '';
}
});
Here is another way to do it:
map.on('pointermove', function(e){
var pixel = map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(pixel);
map.getViewport().style.cursor = hit ? 'pointer' : '';
});
If that doesn't work, try a combination of the 2, seemed to work for my vector popup...
var target = map.getTarget();
var jTarget = typeof target === "string" ? $("#" + target) : $(target);
// change mouse cursor when over marker
$(map.getViewport()).on('mousemove', function (e) {
var pixel = map.getEventPixel(e.originalEvent);
var hit = map.forEachFeatureAtPixel(pixel, function (feature, layer) {
return true;
});
if (hit) {
jTarget.css("cursor", "pointer");
} else {
jTarget.css("cursor", "");
}
});
Thanks to the example link provided by Azathoth in the comments I worked a solution out:
using OL3 pointermove event
using jQuery to get the target element and change its cursor style
Here is the code :
var cursorHoverStyle = "pointer";
var target = map.getTarget();
//target returned might be the DOM element or the ID of this element dependeing on how the map was initialized
//either way get a jQuery object for it
var jTarget = typeof target === "string" ? $("#"+target) : $(target);
map.on("pointermove", function (event) {
var mouseCoordInMapPixels = [event.originalEvent.offsetX, event.originalEvent.offsetY];
//detect feature at mouse coords
var hit = map.forEachFeatureAtPixel(mouseCoordInMapPixels, function (feature, layer) {
return true;
});
if (hit) {
jTarget.css("cursor", cursorHoverStyle);
} else {
jTarget.css("cursor", "");
}
});
Here is the link to the example on OpenLayers site : http://openlayers.org/en/v3.0.0/examples/icon.html
For me it worked like this:
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = e.map.forEachFeatureAtPixel(pixel, function (feature, layer) {
return true;
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
I also added a layer filter:
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = e.map.forEachFeatureAtPixel(pixel, function (feature, layer) {
return layer.get('name') === 'myLayer';
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
I had to select a new solution as the old one I had use for the layer filter before did not work anymore:
var hit = e.map.hasFeatureAtPixel(e.pixel, function(layer){
return layer.get('name') === 'myLayer';
});
I did it with the following code:
var target = $(map.getTargetElement()); //getTargetElement is experimental as of 01.10.2015
map.on('pointermove', function (evt) {
if (map.hasFeatureAtPixel(evt.pixel)) { //hasFeatureAtPixel is experimental as of 01.10.2015
target.css('cursor', 'pointer');
} else {
target.css('cursor', '');
}
});
Another way (combined from parts of above answers, but even simpler):
map.on("pointermove", function (evt) {
var hit = map.hasFeatureAtPixel(evt.pixel);
map.getTargetElement().style.cursor = (hit ? 'pointer' : '');
});
Uncaught TypeError: Cannot set property 'cursor' of undefined.
Fixed with: map.getTargetElement()s.style.cursor = hit ? 'pointer' : ''; instead of map.getTarget().style.cursor = hit ? 'pointer' : '';
Simple way to get target element
var target = map.getTarget();
target = typeof target === "string" ?
document.getElementById(target) : target;
target.style.cursor = features.length > 0) ? 'pointer' : '';
If you guys are using Angular 2 you must use the following code:
this.map.on("pointermove", function (evt) {
var hit = evt.map.hasFeatureAtPixel(evt.pixel);
this.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
If the map variable is a member class you refer to it as "this.map", instead if it is declared inside the current function it can be refered to as "map". But above all, you don't write
map.getTargetElement()
but you write
this.getTargetElement()
I tried to minimize pointermove event closure, by avoiding to update style when not necessary, because it calls so very often:
Example-1: uses jQuery:
var cursorStyle = "";
map.on("pointermove", function (e) {
let newStyle = this.hasFeatureAtPixel(e.pixel) ? "pointer" : "";
newStyle !== cursorStyle && $(this.getTargetElement()).css("cursor", cursorStyle = newStyle);
});
Example-2: no jQuery:
var cursorStyle = "";
map.on("pointermove", function (e) {
let newStyle = this.hasFeatureAtPixel(e.pixel) ? "pointer" : "";
if (newStyle !== cursorStyle) {
this.getTargetElement().style.cursor = cursorStyle = newStyle;
}
});
Easy way
map.on('pointermove', (e) => {
const pixel = map.getEventPixel(e.originalEvent);
const hit = map.hasFeatureAtPixel(pixel);
document.getElementById('map').style.cursor = hit ? 'pointer' : '';
});
}
im creating a simple game and, I need some direction for debuging some data.
i have a div in my HTML file, that I want him to show some data that change when some variables get change by the user actions.
for exmple i got var xPosition, I want to send the data to a div in the HTML, so i can see the current state of xPosition.
i realy dont have any idea where to start creating this functionlity.
my questions:
is it possible to create event inside this class and register some
element to him that will show all the changing data?
do i have to use a some a timer for that sort of thing?
this is the "class":
(function ($) {
$.Player = function (element) {
this.element = (element instanceof $) ? element : $(element);
this.movmentSpeed = 10;
this.size = $(element).width();
this.xPos = 0;
this.yPos = 0;
};
$.Player.prototype = {
//register events
InitEvents: function () {
var that = this;
$(document).keydown(function (e) {
var key = e.which;
if (key == 39) {
that.moveRight(400);
} else if (key == 37) {
that.moveLeft();
}
});
this.element.css({
left: this.xPos
});
},
//movment functions
moveRight: function (worldWidth) {
if ((this.xPos + this.size) <= worldWidth) {
this.xPos += this.movmentSpeed;
this.element.css("left", '+=' + this.movmentSpeed);
}
},
moveLeft: function () {
if (this.xPos > 0) {
this.xPos -= this.movmentSpeed;
this.element.css("left", '-=' + this.movmentSpeed);
}
}
};
} (jQuery));
here the HTML:
<div id="player">
<div id="debugData"> <div>
<script>
var player = new $.Player($("#player"));
player.InitEvents();
</script>
(sorry for my english)
jsFiddle Demo
I'd say having an internal function inside the Class would help. Maybe even having a setting where you turn the logging on by doing player.logging = true;, and having it default to false;
In your prototype just add a public prototype variable for logging = false, and our prototype function log().
The internal function you could use could be something as simple as:
logging : false, // our default
log : function (str) {
if (this.logging) {
$('#debugData').html(str);
}
},
This way you could leave your log(this.xPos); (or whatever you want to log) within your code, and by removing the player.logging = true; when you instantiate your class, they won't show up, till you put that code back in.