Raphael.js: how to let all overlapping items handle events - raphael

Say I have a small red rectangle on top of a bigger blue rectangle:
var paper = Raphael(0,0,500,500);
var rect1 = paper.rect(0, 0, 100, 100);
rect1.attr({fill: 'blue'});
rect1.click(greet);
var rect2 = paper.rect(10, 10, 50, 50);
rect2.attr({fill: 'red'});
var greet = function(){
alert('hi');
};
How can I make the blue rectangle handle the event as well when the red rectangle is clicked?

Related

tooltip messes up bar chart in Chart.js

I am using chart.js to create a stacked bar chart and while I have succeed to move and rotate the xaxis labels to be shown on the top of the bars, when I use mouse to move over the bars, the whole chart appears distorted.
this is the normal
and this is what i get after mouse over:
here is the code
this is what i use to rotate xaxis labels:
https://jsfiddle.net/staverist/zhocr17t/96/
animation: {
duration: 1,
onComplete: function() {
var chartInstance = this.chart;
var ctx = chartInstance.ctx;
ctx.textAlign = "left";
ctx.font = "bold 10px Arial";
ctx.fillStyle = "black";
Chart.helpers.each(this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
Chart.helpers.each(meta.data.forEach(function (bar, index) {
ctx.save();
// Translate 0,0 to the point you want the text
ctx.translate(bar._model.x, bar._model.y - 30);
// Rotate context by -90 degrees
ctx.rotate(-0.5 * Math.PI);
// Draw text
//ctx.fillText(value,0,0);
if(bar._datasetIndex==0){
ctx.fillText(bar._model.label, 0, 0);
ctx.restore();
}
}),this)
}),this);
}
This is because you are not restoring the canvas state after rotating it. Instead of restoring it inside the if statement, you should restore it outside, like so :
...
if (bar._datasetIndex == 0) {
ctx.fillText(bar._model.label, 0, 0);
}
ctx.restore(); //<- restore canvas state
...
also, you should better create a plugin (to prevent label flickering), instead of drawing the labels on animation complete.
Here is the working example on JSFiddle

Famo.us how to select the surfaces in a scrollView that were not clicked on?

I have a scrollView that contains 5 surfaces. If I click on a surface, I would like the others to be either faded out, z-indexed far behind or translated off the screen. The problem is, I do not know how to implement the selection of the other surfaces.
Famo.us Code:
Famous.Engine = famous.core.Engine;
Famous.Surface = famous.core.Surface;
Famous.RenderNode = famous.core.RenderNode;
Famous.Transform = famous.core.Transform;
Famous.Modifier = famous.core.Modifier;
Famous.EventHandler = famous.core.EventHandler;
Famous.ContainerSurface = famous.surfaces.ContainerSurface;
Famous.ScrollView = famous.views.Scrollview;
Famous.Transitionable = famous.transitions.Transitionable;
Famous.SnapTransition = famous.transitions.SnapTransition;
Famous.Easing = famous.transitions.Easing;
Famous.TransitionableTransform = famous.transitions.TransitionableTransform;
Famous.StateModifier = famous.modifiers.StateModifier;
var projectsList = document.getElementById('projects-list');
var mainContext = Famous.Engine.createContext(projectsList);
var scrollView = new Famous.ScrollView({
direction: 0
});
Famous.Transitionable.registerMethod('snap', Famous.SnapTransition);
var snap = { method: 'snap', period: 600, dampingRatio: 0.6 }
var surfaces = [];
for (var i = 0; i < 5; i++) {
var surface = new Famous.Surface({
size: [undefined, undefined],
properties: {
backgroundColor: "#fff", // "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
textAlign: "center"
}
});
surface.open = false;
surface.state = new Famous.Modifier();
surface.trans = new Famous.Transitionable(500);
surface.state.sizeFrom(function(){
return [this.trans.get(), undefined];
}.bind(surface));
surface.node = new Famous.RenderNode();
surface.node.add(surface.state).add(surface);
surface.pipe(scrollView);
surface.on('click',function(event){
if (this.open) {
this.trans.halt();
this.trans.set(500, snap);
/* place code to reverse the animation that placed the other surfaces off-screen here */
} else {
this.trans.halt();
this.trans.set($(window).width(), snap);
/* how to implement the selection of the other surfaces that were not clicked */
}
this.open = !this.open;
}.bind(surface));
surfaces.push(surface.node);
// sequenceFrom method sets the collection of renderables under the Scrollview instance's control. You can pass array of items or ViewSequence object.
scrollView.sequenceFrom(surfaces);
}
mainContext.add(scrollView);
An example Surface HTML Generated:
<div class="famous-surface" style="background-color: rgb(255, 255, 255); text-align: center; width: 500px; height: 662px; opacity: 0.999999; transform-origin: 0% 0% 0px; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);">
<div class="surface-content-wrapper">
<div class="container-fluid">
<section class="row project-preview">
<article class="col-lg-12">
<img class="img-responsive" src="/images/project_name_header.png">
<h1>A Surface</h1>
<div class="project-stats">
</article>
</section>
</div>
</div>
</div>
All the surfaces in the scrollView have the same class attributes. So if I click on the first surface, how do I tell famo.us to do something with the remaining four surfaces?
When I click on the specific surface the console logs for this and event.currentTarget are:
this: Surface { _matrix=[16], _opacity=1, _origin=[2], more...}
project...875d127 (line 116)
event: <div class="famous-surface" style="background-color: rgb(255, 255, 255); text-align: center; width: 500px; height: 662px; opacity: 0.999999; transform-origin: 0% 0% 0px; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);">
You can use the scrollview's backing array directly.
surfaces
Once you get here, you'll have access to the nodes that are in your surfaces array. From there, it's a matter of excluding the surface that was returned as this from the array and interacting with the properties that you placed on your surface object, which you can do using underscore.
_(surfaces).each(function(node) {
surface = node._child;
if (surface != clickedSurface) {
//Do whatever you want to do to the node or surfaces
}
});
Your instinct that you should avoid using the siblings relationship is right. Further, I'd bet that you'd run into same nasty bugs in the future if you tried to manipulate your layout at all. You should stick to your famo.us objects whenever possible.
UPDATE: Please see answer given by #obsidian06 for proper solution.
For now, I'm going with Underscore's each() block that animates the surface's opacity on the click event.
var $otherSurfacesNotClicked = $(event.currentTarget).siblings();
_.each($otherSurfacesNotClicked, function(surface){
console.log("surface in each loop: ", surface);
$(surface).animate({opacity: 0});
});
There's a performance hit on mobile. Most likely the problem is using the jQuery animate(). Need to find the native Famo.us way of performing the same task.

famo.us - RenderController and draggable surfaces

I have two surfaces. One is draggable and one is 'full screen' (size: [undefined, undefined]). I'd like to drag the first surface (yellow in my example) right and have the second (green)surface appear. When I click on the green surface, I'd like show the first surface again back in the original starting point (center of screen).
I'd also like the green surface to be non-draggable.
I'm a famo.us novice, and any help is GREATLY appreciated!
fiddle here: http://jsfiddle.net/cjs123456/vfzy4j51/
// the position state
var position = [0, 0];
// create a Sync to listen to mouse events
var sync = new MouseSync();
var renderController = new RenderController();
var mySurface = new Surface({
size: [350, 200],
content: 'drag me right more than 100px to see other surface',
properties: {
backgroundColor: "hsl(" + (5 * 360 / 40) + ", 100%, 50%)",
lineHeight: '200px',
textAlign: 'center',
cursor: 'pointer'
}
});
// Surface provides events that the sync listens to
mySurface.pipe(sync);
// Syncs have `start`, `update` and `end` events. On `update` we increment the position state of the surface based
// on the change in x- and y- displacements
sync.on('update', function(data){
position[0] += data.delta[0];
position[1] += data.delta[1];
console.log(data.position[0]);
if (data.position[0] > 100) {
console.log("FULL");
renderController.show(fullSurface);
}
});
// this modifier reads from the position state to create a translation Transform that is applied to the surface
var positionModifier = new Modifier({
transform : function(){
return Transform.translate(position[0], position[1], 0);
}
});
// a modifier that centers the surface
var centerModifier = new Modifier({
origin : [0.5, 0.5],
align: [0.5, 0.5]
});
var fullSurface = new Surface({
size: [undefined, undefined],
content: 'Click me to show other surface',
properties: {
backgroundColor: "hsl(" + (9 * 360 / 40) + ", 100%, 50%)",
lineHeight: '400px',
textAlign: 'center'
}
});
fullSurface.on("click", function() {
renderController.show(mySurface);
});
renderController.show(mySurface);
var mainContext = Engine.createContext();
var node = mainContext.add(centerModifier).add(positionModifier);
node.add(renderController);
});
There are more than one way to solve this issue.
Control the dragging of the render controller
Create separate render controller and control views
Because your use case here is simple enough, I will show the creating of a separate render controller
Here is the jsFiddle Example of the Code
Create a background render controller
var renderController = new RenderController();
var backRenderController = new RenderController();
Change the node to be the center modifier. Add our draggable to the node. Add the background controller to the node.
var node = mainContext.add(centerModifier)
node.add(positionModifier).add(renderController);
node.add(backRenderController);
Control the draggable render contoller view (Line 42)
renderController.hide();
backRenderController.show(fullSurface);
Clicking on the background will reset the position of the draggable back to the origin, hide the background and show the draggable again.
// Set draggable position back to the origin
position = [0, 0];
// Hide the back render element
backRenderController.hide();
// Show the draggable
renderController.show(mySurface);
Full code:
// the position state
var position = [0, 0];
// create a Sync to listen to mouse events
var sync = new MouseSync();
var renderController = new RenderController();
var backRenderController = new RenderController();
var mySurface = new Surface({
size: [350, 200],
content: 'drag me right more than 100px to see other surface',
properties: {
backgroundColor: "hsl(" + (5 * 360 / 40) + ", 100%, 50%)",
lineHeight: '200px',
textAlign: 'center',
cursor: 'pointer'
}
});
// Surface provides events that the sync listens to
mySurface.pipe(sync);
// Syncs have `start`, `update` and `end` events. On `update` we increment the position state of the surface based
// on the change in x- and y- displacements
sync.on('update', function (data) {
position[0] += data.delta[0];
position[1] += data.delta[1];
console.log(data.position[0]);
if (data.position[0] > 100) {
console.log("FULL");
renderController.hide();
backRenderController.show(fullSurface);
}
//else {
// mySurface.setPosition([0,0,0], {
// curve: Easing.outBack,
// duration: 300
// });
//}
});
// this modifier reads from the position state to create a translation Transform that is applied to the surface
var positionModifier = new Modifier({
transform: function () {
return Transform.translate(position[0], position[1], 0);
}
});
// a modifier that centers the surface
var centerModifier = new Modifier({
origin: [0.5, 0.5],
align: [0.5, 0.5]
});
var fullSurface = new Surface({
size: [undefined, undefined],
content: 'Click me to show other surface',
properties: {
backgroundColor: "hsl(" + (9 * 360 / 40) + ", 100%, 50%)",
lineHeight: '400px',
textAlign: 'center'
}
});
fullSurface.on("click", function () {
// Set draggable position back to the origin
position = [0, 0];
// Hide the back render element
backRenderController.hide();
// Show the draggable
renderController.show(mySurface);
});
//var node = mainContext.add(myModifier);
//node.add(draggable).add(surface);
renderController.show(mySurface);
var mainContext = Engine.createContext();
//mainContext.add(myModifier).add(draggable).add(renderController);
var node = mainContext.add(centerModifier)
node.add(positionModifier).add(renderController);
node.add(backRenderController);

gradient color fill with opacity in raphael

I'm having trouble keeping the same level of opacity on an element with a gradient filled color
var paper = Raphael(0, 0, 300, 300);
paper.path(["M", 20, 20, "h", 200, "v", 200, "h", -200, "z"]).attr({
"stroke-width": 3,
stroke: 'red',
"opacity": 0.5,
fill: "90-red-red"
});
http://jsfiddle.net/zhirkovski/vvAaz/1/
as you can see the gradient starts off at 0.5, but increases to 1 by the time it reaches the second color, why? Even if you change the colors, one of them renders at opacity = 1, is this a bug? if so is there a work-around, or is it something i'm doing wrong?
From my own investigation this looks like a limitation of VML and subsequently Raphael. You can find more information via the following bug report: https://github.com/DmitryBaranovskiy/raphael/issues/211
It really limits what you can do with gradients and fades which is a bane for all of us. The best way to do this would be with jQuery:
// Setting up defaults
var paper = Raphael("canvas", 200, 200);
var bgBottom = paper.rect(0, 0, 200, 200).attr({fill: "90-#999-#fff"});
var bgTop = paper.rect(0, 0, 200, 200).attr({fill: "90-#999-#fff"});
// New gradient to fade to
bgBottom.attr({fill: "90-#069-#000"});
$(bgTop.node).animate({opacity: 0}, 1000);
You can then animate the top in and out with fill changes:
bgTop.attr({fill: "90-#f0f-#fff"});
$(bgTop.node).animate({opacity: 1}, 1000);
Here's my jsfiddle to help demonstrate: http://jsfiddle.net/spQsf/
Hope this helps!

Raphael.js - How to programmatically generate element name?

I have a number of Raphael rectangle elements hard-coded into the page via php, which generates them on the fly depending on how many are needed when the page is called.
What I'm trying to do is write a javascript function that will take an id number and alter the relevant Raphael function's background color.
I know the below example won't work, but this is what I'm trying to achieve.
var rectangle_1 = paper.rect(100, 10, 200, 75);
var rectangle_2 = paper.rect(400, 10, 200, 75);
var rectangle_3 = paper.rect(700, 10, 200, 75);
change_color('1');
function change_color(id) {
variable_name = 'rectangle_' + id;
variable_name.attr({fill: 'blue', stroke: 'black', 'stroke-width': 3});
}
Clearly this doesn't work and any help and advice on how to generate an element name on the fly would be greatly appreciated,
regards,
Ste
Just use an array or an object to store the rects instead of named variables.
var rectangles = [];
rectangles[0] = paper.rect(100, 10, 200, 75);
rectangles[1] = paper.rect(400, 10, 200, 75);
rectangles[2] = paper.rect(700, 10, 200, 75);
change_color(1);
function change_color(id) {
rectangles[id].attr({fill: 'blue', stroke: 'black', 'stroke-width': 3});
}
If you need string IDs, you can do it as an object:
var rectangles = {};
rectangles['one'] = ...