How to prevent loss hover in Raphael? - javascript

I'm developing some page when I use Raphael liblary to draw some items.
my App
So my problem is in that when I'm moving to some rect it growing up but when my mouse is on text which is positioning on my rect, it loss his hover. You can see it on my app example.
var paper = new Raphael(document.getElementById('holder'), 500, object.length * 100);
drawLine(paper, aType.length, bType.length, cType.length, cellSize, padding);
process = function(i,label)
{
txt = paper.text(390,((i+1)* cellSize) - 10,label.devRepo)
.attr({ stroke: "none", opacity: 0, "font-size": 20});
var a = paper.rect(200, ((i+1)* cellSize) - 25, rectWidth, rectHeight)
.hover(function()
{
this.animate({ transform : "s2"}, 1000, "elastic");
this.prev.animate({opacity: 1}, 500, "elastic");
this.next.attr({"font-size" : 30});
},
function()
{
this.animate({ transform : "s1" }, 1000, "elastic");
this.prev.animate({opacity: 0}, 500);
this.next.attr({"font-size" : 15});
});
}
I have tried e.preventDefault(); on hover of this.next and some other solutions but it's doesn't work.
Any help would be appreciated.

Most people will suggest you place a transparent rectangle over the box and the labels and attach the hover functions to that instead. (If memory serves, you have to make the opacity 0.01 instead of 0 to prevent the object from losing its attached events.) This works fine, but I don't love this solution; it feels hacky and clutters the page with unnecessary objects.
Instead, I recommend this: Remove the second function from the hover, making it functionally a mouseover function only. Before you draw any of the rectangles and labels, make a rectangular "mat" the size of the paper. Then, attach the function that minimizes the label as a mouseover on the mat. In other words, you're changing the trigger from mousing out of the box to mousing over the area outside of it.
I left a tiny bit of opacity and color on the mat to be sure it's working. You can just change the color to your background color.
var mat = paper.rect(0, 0, paper.width, paper.height).attr({fill: "#F00", opacity: 0.1});
Now, you want to make a container for all the rectangles so you can loop through them to see which need to be minimized. I made an object called "rectangles" that contains the objects we're concerned with. Then:
mat.mouseover(function () {
for (var c = 0; c < rectangles.length; c += 1) {
//some measure to tell if rectangle is presently expanded
if (rectangles[c].next.attr("font-size")) {
rectangles[c].animate({
transform : "s1"
}, 1000, "elastic");
rectangles[c].prev.animate({opacity: 0}, 500);
rectangles[c].next.attr({"font-size" : 15});
}
}
});
Then I just removed the mouseout function from the individual rectangles.
jsBin
To be clear, this will have some downsides: If people run the mouse around really fast, they can expand several rectangles at the same time. This is remedied as soon as the mouse touches the mat. I think the functionality looks pretty nice. But the invisible mats is always an option.

I wrote a small extension to Raphael - called hoverInBounds - that resolves this limitation.
Demo: http://jsfiddle.net/amustill/Bh276/1
Raphael.el.hoverInBounds = function(inFunc, outFunc) {
var inBounds = false;
// Mouseover function. Only execute if `inBounds` is false.
this.mouseover(function() {
if (!inBounds) {
inBounds = true;
inFunc.call(this);
}
});
// Mouseout function
this.mouseout(function(e) {
var x = e.offsetX || e.clientX,
y = e.offsetY || e.clientY;
// Return `false` if we're still inside the element's bounds
if (this.isPointInside(x, y)) return false;
inBounds = false;
outFunc.call(this);
});
return this;
}

Related

Position mouse pointer on line/path in Raphael

In this jsFiddle I have a Raphael canvas with a line/path where the user can drag it.
Problem is that the exact top pixel of the mouse pointer has to be on the line to be able to drag it. I need Raphael to be more "forgiving" and let the user drag the line if it touches the top, say, 3 pixels of the pointer.
I added mouseover/mouseout events to actually tell when the mouse is over the line, otherwise is very difficult to know.
So the question is: is there a way to drag the line without having to accurately position the mouse pointer?
var paper = Raphael("canvas", 600, 600);
var line = this.paper.path('M10 10 L50 50');
var start = function () {
this.odx = 0;
this.ody = 0;
},
move = function (dx, dy) {
this.translate(dx - this.odx, dy - this.ody);
this.odx = dx;
this.ody = dy;
},
up = function () {};
line.drag(move, start, up);
line.mouseover(function(e) {
line.attr('stroke', 'red');
});
line.mouseout(function(e) {
line.attr('stroke', 'black');
});
The Raphael library creates an SVG element (not a CANVAS element) for drawing purposes, and drawing lines creates PATH elements within the SVG element.
I had some success by increasing the line width on hover. You do have to trigger the width change by at least moving the mouse over the line, but it is a lot easier to select afterwards. While the visual effect produced may not be perfect (some width flicking can occur) continuing the drag operation proceeds without problem.
You could try increasing the line width by either modifying the stroke-width attribute in the mouseover and mouseout handlers:
line.mouseover(function(e) {
line.attr('stroke', 'red');
line.attr('stroke-width', '9')
});
line.mouseout(function(e) {
line.attr('stroke', 'black');
line.attr('stroke-width', '1')
});
or by modifying the stroke-width styling when hovering over all path elements in CSS using
path:hover {
stroke-width: 9;
}
According to this question Raphael does not provide an abstraction of all possible CSS styling so it could use VML in XML documents. If you can live without IE 9 support and expect users to have a modern browser you may be interested in the following snippet which
increases the width of a line on mouseover by setting class name draggable, which sets a move cursor,
when a move is started replaces draggable with dragging ( which removes the width increase) and sets a move cursor over the svg container,
removes class draggable, but not dragging, on mouseout
cleans up classes and the cursor in the up function.
"use strict";
var container = document.getElementById("canvas");
var paper = Raphael("canvas", 600, 600);
var line = this.paper.path('M10 10 L50 50');
var start = function () {
this.odx = 0;
this.ody = 0;
},
move = function (dx, dy) {
this.translate(dx - this.odx, dy - this.ody);
this.odx = dx;
this.ody = dy;
if( this.node.classList.contains("draggable")) {
this.node.classList.replace("draggable", "dragging");
container.classList.add("moveCursor");
}
},
up = function (e) {
line.node.classList.remove("draggable", "dragging");
container.classList.remove("moveCursor");
};
line.drag(move, start, up);
line.mouseover(function(e) {
if(!e.target.classList.contains("dragging")) {
e.target.setAttribute("class", "draggable");
}
});
.draggable:hover {
stroke-width: 9;
stroke:red;
cursor: move;
}
path.dragging {
stroke: red;
}
.moveCursor{
cursor:move;
}
svg {
border: thin solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<!-- body-html -->
<div id="canvas"></div>
The code is basically for demonstration - if you drag a line outside the SVG element you can't see it or grab it to drag back.
Some CSS rules (i.e. path.dragging) needed additional specificity to take priority over the defaults supplied by Raphael.

How to avoid event's lag with KineticJS?

I am in the prototyping stage of making a 2d map builder for a hybrid web/text adventure game and so far KineticJS seems like an ideal fit. Only issue currently is that given enough velocity on mouse movement, it will skip over cells and never fire their mouseover event handler.
2 core functionality goals: When the user highlights a cell, it would be marked as "active". Additionally if they're holding the mouse down and move across the grid, cells would be flipped on or off ( this maybe refactored to set all on if not if the first cell is not active, or vice versa ).
My question: Is there a way to ensure all cells are triggered, regardless of mouse cursor velocity? If not, is there a better way to draw a line over the cells so that it consistently triggers all relevant cells?
The entire prototype has been put into jsFiddle ( http://jsfiddle.net/7ggS4/ ) but for future sake, the rest will be copied below as well.
<head>
<title>KineticJS</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/kineticjs/4.7.2/kinetic.min.js"></script>
</head>
<body>
<div id="canvas"></div>
</body>
<script defer="defer">
/**
Return's a KS layer
*/
function Grid(cells, stage) {
//Constants
// Illrelevant comment - It seriously pisses me off that canvas uses
// string color codes ( either name or string hexidecimal ) instead of taking an
// integer or actual hexidecimal 0xFFFF values. This just seems painfully inefficient.
this.activeCellColor = "green";
this.clearCellColor = "blue";
this.highlightCellColor = "red";
this.cells = cells,
this.layer = new Kinetic.Layer(),
this.grid = new Array(),
this.isMouseDown = false,
this.mouseLeft = false,
this.mouseRight = false,
this.adjRow = stage.getWidth() / cells,
this.adjCol = stage.getHeight() / cells;
this.generate();
stage.add(this.layer)
}
Grid.prototype.generate = function(){
var i, rx, ry, rect;
for (i = 0; i < this.cells * this.cells; i++) {
rx = Math.floor(i / this.cells) * this.adjRow;
ry = (i % this.cells) * this.adjCol;
rect = new Kinetic.Rect({
x: rx,
y: ry,
width: this.adjRow,
height: this.adjCol,
fill: this.clearCellColor,
stroke: 'black',
strokeWidth: .2,
cell: {x: Math.floor(i / this.cells), y: i % this.cells},
active: false,
grid: this //Just in case .bind(this) doesn't work right
});
rect.on('mouseenter', this.onMouseEnter.bind(this));
rect.on('mouseleave', this.onMouseLeave.bind(this));
rect.on('mousedown', this.onMouseDown.bind(this));
rect.on('mouseup', this.onMouseUp.bind(this));
this.grid.push(rect);
this.layer.add(rect);
}
}
Grid.prototype.onMouseEnter = function(evt) {
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
if (this.isMouseDown == true) {
src.attrs.active = ! src.attrs.active;
}
if (src.attrs.active == false) {
src.setFill(this.highlightCellColor);
} else {
src.setFill(this.activeCellColor);
}
this.layer.batchDraw();
}
Grid.prototype.onMouseLeave = function(evt) {
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
if (src.attrs.active == false) {
src.setFill(this.clearCellColor);
this.layer.batchDraw();
}
}
Grid.prototype.onMouseUp = function(evt){
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
this.isMouseDown = false;
}
Grid.prototype.onMouseDown = function(evt){
var src = evt.targetNode;
console.log(evt.type, this.isMouseDown, src.attrs.cell)
this.isMouseDown = true;
src.attrs.active = ! src.attrs.active;
if (src.attrs.active) {
src.setFill(this.activeCellColor);
} else {
src.setFill(this.clearCellColor);
}
this.layer.batchDraw();
}
var stage = new Kinetic.Stage({
container: 'canvas',
width: 600,
height: 600
}),
myGrid = new Grid(50, stage);
</script>
50x50=2500 active objects: that's too many for Kinetic to handle.
Remember that each "intelligent" Kinetic cell has a lot of overhead associated with it.
How about reducing your grid to 20x20?
Alternatively, you will have to separate the mouse handling from the cell handling to gain the required performance.
Mouse Handling
Your mouse handling would only involve capturing mouse points into an array of accumulated points. You can use this kind of code to capture mouse points on the stage:
$(stage.getContent()).on('click', function (event) {
myPointsArray.push(stage.getMousePosition());
});
Cell processing
The cell processing would involve applying those accumulated points to affect your grid cells. An effective place to do this code would be in a requestAnimationFrame (RAF) loop. You won't be doing animations, but RAF gives high performance because it is aware of the availability of system resources. An RAF loop would look like this:
function processPointsArray(array){
// request another loop even before we're done with this one
requestAnimationFrame(processPointsArray);
// process the points array and affect your cells here
}
A processing efficiency
RAF is called up to 60 times per second, so your user will probably navagate only a small portion of your grid during that time. You can increase performance by calculating the min/max x and y coordinates in the accumulated points array and only process those grid cells within that boundary.

How to make the Chart.js animate when scrolled to that section?

I am trying to use the pie chart from Chart.js (http://www.chartjs.org/docs/#pieChart-exampleUsage). Everything works smooth, but the animation happens as soon as the page loads, but since the user has to scroll down to see the chart, they won't see the animation. Is there anyway I can make the animation to start only when scrolled to that position? Also if possible, is it possible to animate everytime when that chart becomes into view?
My code is as follows:
<canvas id="canvas" height="450" width="450"></canvas>
<script>
var pieData = [
{
value: 30,
color:"#F38630"
},
{
value : 50,
color : "#E0E4CC"
},
{
value : 100,
color : "#69D2E7"
}
];
var myPie = new Chart(document.getElementById("canvas").getContext("2d")).Pie(pieData);
</script>
You can combine the check for whether something is viewable with a flag to keep track of whether the graph has been drawn since it appeared in the viewport (though doing this with the plugin bitiou posted would be simpler):
http://jsfiddle.net/TSmDV/
var inView = false;
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemTop <= docViewBottom) && (elemBottom >= docViewTop));
}
$(window).scroll(function() {
if (isScrolledIntoView('#canvas')) {
if (inView) { return; }
inView = true;
new Chart(document.getElementById("canvas").getContext("2d")).Pie(data);
} else {
inView = false;
}
});
Best to use deferred plugin
https://chartjs-plugin-deferred.netlify.com/
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-deferred#1"></script>
new Chart(ctx, {
// ... data ...
options: {
// ... other options ...
plugins: {
deferred: {
xOffset: 150, // defer until 150px of the canvas width are inside the viewport
yOffset: '50%', // defer until 50% of the canvas height are inside the viewport
delay: 500 // delay of 500 ms after the canvas is considered inside the viewport
}
}
}
});
I don't know if you could do that, I had the same issue and resolved it without any plugin in this simple way, check out:
$(window).bind("scroll", function(){
$('.chartClass').each(function(i){
var dougData = [
{value: 100, color:"#6abd79"},
{value: 20, color:"#e6e6e6"}
];
var graphic = new Chart(document.getElementById("html-charts").getContext("2d")).Doughnut(dougData, options);
$(window).unbind(i);
});
});
I had the same problem with Chart.js and found a really great solution.
There is a package on GitHub that is called ChartNew.js by FVANCOP.
He expanded it and added several functions.
Look at the sample, the charts are drawn by scrolling down.
Responsible is the statement
dynamicDisplay : true
Using IntersectionObserver is the more modern approach, and gives you the ability to choose how much of the element must be visible before triggering an event.
A threshold of 0 means it will trigger if any part of the element is visible, while a threshold of 1 means the entire element must be visible.
It performs better than listening to scroll, and will only fire once when the element transitions from hidden to visible, even while you are continuously scrolling. And it also works if the page content changes due to other events, such as other content being hidden/shown, or window resize, etc.
This is how I made a radial chart that animates every time at least 20% of it appears into view:
const options = {
series: [75],
chart: {
type: 'radialBar',
},
};
const chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
const observer = new IntersectionObserver(function(entries) {
if (entries[0].isIntersecting === true) {
chart.updateSeries([0], false); // reset data to 0, then
chart.updateSeries([75], true); // set original data and animate
// you can disconnect the observer if you only want this to animate once
// observer.disconnect();
}
}, { threshold: [0.2] });
observer.observe(document.querySelector("#chart"));
This is what you want:
Check if element is visible after scrolling
Next time please check if there's already an answer ;)
Alternatively: jquery.appear

javascript raphael object function passing

I apologize for asking this question but I am just looking for a little guidance on this morning. I simply want to create a function so that way I can make a Raphael element glow by just passing in that element. Below is the code I have. Why does this not work?
var paper = Raphael("playarea", 500, 500);
var rectangle = paper.rect(100, 100, 200, 200, 4);
function elemHover(var el)
{
el.hover(
// When the mouse comes over the object //
// Stock the created "glow" object in myCircle.g
function() {
this.g = this.glow({
color: "#0000EE",
width: 10,
opacity: 0.8
});
},
// When the mouse goes away //
// this.g was already created. Destroy it!
function() {
this.g.remove();
});
}
elemHover(rectangle);
here is the fiddle http://jsfiddle.net/aZG6C/15/
You should fill the element( rectangle in our case) to trigger the hover.
rectangle.attr("fill", "red");
Try this fiddle http://jsfiddle.net/aZG6C/17/
The full code will look like
<div id="playarea"></div>
​<script type="text/javascript">
var paper = Raphael("playarea", 500, 500);
var rectangle = paper.rect(100, 100, 200, 200, 4);
function elemHover(el)
{
el.hover(
// When the mouse comes over the object //
// Stock the created "glow" object in myCircle.g
function() {
this.g = this.glow({
color: "#0000EE",
width: 10,
opacity: 0.8
});
},
// When the mouse goes away //
// this.g was already created. Destroy it!
function() {
this.g.remove();
});
}
rectangle.attr("fill", "red");
elemHover(rectangle);
</script>​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
Update
Hover event is triggered only if the element is filled with something. If you want to have a transparent element you can try
rectangle.attr("fill", "transparent");
Check the fiddle here http://jsfiddle.net/aZG6C/20/

Stop animation and reset position of an element in Raphaël.js

I have this simple animation that moves from side to side, Im trying to create a reset button so that the animation stops and gets back to default, but I cant seem to get i right.
The library I am using is Raphael, but I'm almost sure that with a simple javascript I can reset the values inside the function.
Body onload init function
function init() {
paper = Raphael("loadSVG");
var bg = paper.rect( 0, 0, "240px", "90px", 0 );
bg.attr( {fill: "#f3f3ff"} );
rect1 = paper.rect(150, 20, 50, 50);
rect1.attr( {fill: "#ffaaaa", "stroke-width": 3} );
}
The animation function
function moveRect1() {
if( xEnd == 150 )
xEnd = 50;
else
xEnd = 150;
rect1.animate( {x: xEnd}, 1000, "Sine", function (){
moveRect1();
});
}
and the stop button that don't work :)
function stopsvgRect1() {
rect1.stop();
}
You need to bind the stop button to the function you've created.
For example like this:
document.querySelector('#stop').onclick = function() {
stopsvgRect1();
};
Open this fiddle to see it in action.
edit:
As the position should also be resetted, you can choose Raphael.transform() for this action.
Open my updated fiddle to see how it works.

Categories