Fading out two over-layed objects uniformly in RaphaelJS/SVG - svg

I have two overlayed rectangles:
I'm trying to fade them out uniformly as if they were one object.
The problem:
When I animate their opacity from 1 to 0, the top rectangle becomes transparent and reveals the edges of the rectangle beneath it.
Here is my code:
var paper = Raphael(50,50,250,350)
var rect = paper.rect (20,40,200,200).attr({"fill":"red","stroke":"black"})
var rect2 = paper.rect (100,140,200,200).attr({"fill":"red","stroke":"black"})
var set=paper.set()
set.push(rect)
set.push(rect2)
set.click(function () {fadeOut()})
function fadeOut() {
rect.animate({"opacity":0},3000)
rect2.animate({"opacity":0},3000)
setTimeout(function () {
rect.attr({"opacity":1})
rect2.attr({"opacity":1})
},3100)
}
When the set is clicked, the rectangles fade out in 3 seconds. (look at the red rectangles in my fiddle, it will clarify my problem)
https://jsfiddle.net/apoL5rfp/1/
In my fiddle I also create a similar looking green path that performs the fade out CORRECTLY.
I can I achieve the same type of fadeout with multiple objects?

I think this is quite difficult in Raphael alone.
Few options spring to mind. Don't use Raphael, use something like Snap, put them in a group and change opacity in the group.
var g = paper.g(rect, rect2);
g.click(function () { fadeOut( this )} )
function fadeOut( el ) {
el.animate({"opacity":0},3000)
setTimeout(function () {
el.attr({"opacity":1})
},3100)
}
jsfiddle
However, you may be tied to Raphael, which makes things a bit tricky, as it doesn't support groups. You could place an 'blank' object over it (which matches same as background) and animate its opacity in the opposite way, like this..(note the disabling of clicks on top object in css)
var rectBlank = paper.rect(18,20,250,330).attr({ fill: 'white', stroke: "white", opacity: 0 });
var set=paper.set()
....
rectBlank.animate({"opacity":1},3000)
jsfiddle
Otherwise I think you may need to use a filter, which may help a bit. SO question

Related

Best way to dynamically style svg marker elements

My question: Can svg <marker> elements inherit color from the <line> they are referenced on?
The background: I have a D3 graph that has different styled lines, and I want my lines to have arrows at the end.
So at the top of my <svg> I have const defs = svg.append('defs'); and then further along I generate my defs using a generator function:
function makeDefs(defs: Selection<SVGDefsElement, unknown, null, undefined>, color: string, status: string) {
const markerSize = 3
const start = defs.append
.append('marker')
.attr('id', `arrow-start-${color}-${status}`)
.attr('viewBox', '-5 -10 20 20')
.attr('markerWidth', markerSize)
.attr('markerHeight', markerSize)
.attr('orient', 'auto-start-reverse');
start
.append('path')
.attr(
'd',
status === 'PUBLISHED' ? customPaths.arrowLarge : customPaths.arrowSmall
)
.attr('stroke', color)
.attr('fill', color);
}
And use it like so:
makeDefs(defs, 'red', 'DRAFT');
And then I add the markers to my lines with:
// d3 code to draw the lines etc
line.attr(
'marker-start',
(d) =>
`url(
#arrow-start-${d.color}-${d.status}
)`
);
This all works great, my arrows have lines. But my current setup feels burdensome and clunky. I have about 20 colors and 3 statuses. With my current setup that would be 60 different:
makeDefs(defs, 'one-of-20-colors', 'one-of-3-statues');
My understanding of markers is that they can inherit color using the currentcolor attribute. Currently my <defs> sit up top underneath my main <svg> so any color inherited is inherited directly from that top level svg which is not what I want. The issue in my case is my <line> elements are the elements who's color I want to inherit, but according to the MDN docs <line>s cannot have <defs> as children, thus leaving me with the only option, of defining all my <defs> up front all at once.
Is there a trick or some attribute I'm missing here?
Any way to pass color to my marker when doing:
line.attr(
'marker-start',
(d) =>
`url(
#arrow-start-${d.color}-${d.status}
)`
);
?
For what is is worth, I'm currently wrapping all my <line>s in <g>. I suppose I could wrap them in <svg>s instead, and apply the fill and stroke there, and then define my <defs> per svg container? I tried this briefly and swapping the <g> for an <svg> broke a lot, but I'm not even sure if it would work, or be better for that matter.

FabricJS: Issue when changing objects coordinates inside a Group after some Transformations

I am looking for a way to space (and move in general) objects inside a group regardless of their transformation.
In the small example i have made below, you will see 2 circles and I am attempting to space them by a certain factor.
It is ok until I rotate or resize the group and it is still ok the very first time the coordinates are changed after a transformation but calling the same function twice while the spacing is still correct, the group starts jumping all over the places..
I am using Fabric JS version: 1.7.7
Here is a sample:
https://jsfiddle.net/u0patgck/
console.clear();
var canvas = new fabric.Canvas('root');
var circles = [];
circles.push(new fabric.Circle({id: "circle1", radius: 20, fill: 'green', left: 150, top: 100}));
circles.push(new fabric.Circle({id: "circle2", radius: 20, fill: 'green', left: 200, top: 100}));
g = new fabric.Group(circles);
//once circles are added to group their coordinates change so I store their new original base coordinates in custom variables
g.forEachObject(function(obj){
obj.originalLeft = obj.getLeft();
obj.originalTop = obj.getTop();
}, g);
canvas.add(g);
canvas.renderAll();
function moveCircles(){
var space = parseFloat(document.getElementById("spacingPixels").value);
g.forEachObject(function(obj){
switch (obj.id){
case "circle1":
obj.setLeft(obj.originalLeft - space)
break;
case "circle2":
obj.setLeft(obj.originalLeft + space)
break;
default:
}
})
g.addWithUpdate();
canvas.renderAll()
}
Steps to Reproduce:
Test 1 (ok):
Click the "Move Circles" button as many times you want (circles coordinates are recalculated and the group is not "misbehaving".
Test 2 (ok first time but not second time):
Rotate the group first.
Click the move circles button ONE time (all is still good)
Click the move circles button another time (you will see the group starts moving by itself around the canvas).
Expected Behaviour:
The group should not move around or change the order of the objects inside.
The group moves erratically.
At the moment I am caching the transformations manually and restoring them after changing the coordinates (like below)
https://jsfiddle.net/0tdg1dof/
var a = {scaleX: g.getScaleX(), scaleY: g.getScaleY(), skewX: g.getSkewX(), skewY: g.getSkewY(), flipX: g.getFlipX(), flipY:g.getFlipX(), angle: g.getAngle()};
fabric.util.resetObjectTransform(g);
g.addWithUpdate();
g.setScaleX(a.scaleX);
g.setScaleY(a.scaleY);
g.setSkewX(a.skewX);
g.setSkewY(a.skewY);
g.setFlipX(a.flipX);
g.setFlipY(a.flipY);
g.setAngle(a.angle);
Is there a better way to do this?
Thank you.
AlessandroDM, I believe you did right solution, because you are manipulating just only group object. If you will not manipulate (restore) group objects (parent) you will need to do manipulation each child inside the group like angle, scale, etc. Also, you have one minor bug, if you will rotate group, and after that resize it (not diagonal controls, scaleX should not be equal to scaleY) and then click "Move Circles" your group will behave as you described in Test 2. In order to fix that you will need to set scaleX and scaleY values after all your values which you want to set:
var a = {scaleX: g.getScaleX(), scaleY: g.getScaleY(), skewX: g.getSkewX(), skewY: g.getSkewY(), flipX: g.getFlipX(), flipY:g.getFlipX(), angle: g.getAngle()};
fabric.util.resetObjectTransform(g);
g.addWithUpdate();
g.setSkewX(a.skewX);
g.setSkewY(a.skewY);
g.setFlipX(a.flipX);
g.setFlipY(a.flipY);
g.setAngle(a.angle);
g.setScaleX(a.scaleX);
g.setScaleY(a.scaleY);
It is not a fabricjs bug setting scale after all manipulations, even native HTML5 canvas required scaling after all manipulations.

how to get text bounding box in famo.us

I am attempting to draw an SVG bezier curve that starts at the end of a text string that is in a Surface. I can set the size of the Surface to [true, true], which is supposed to make the size equal the text bounding box. But if I later try "mySurface.size[0]" in order to get the width, it returns "true"! I need to get a number for the width and height of that bounding box, in order to calculate the end point of my bezier curve! The equivalent DOM approach would just be to use the .getBBox() function.. how do I do this in Famo.us?
this is maybe because the surface hasn't rendered yet. there are a few similar questions here, one of them from me:
how can we get the size of a surface within famo.us?
you could also try deferring or using a setTimeout or Engine.nextTick() to check the size on the next loop through.
if you find an elegant solution let us know as this is a big problem in many places using famous - having to do multiple highjinks where you can't really position a scene on the initial setup - you have to let it render and then adjust...
You can use the 'getSize' function and specify 'true' to get the real size of the surface:
var realSize = surface.getSize(true);
#ljzerenHein, thanks for the hint.. unfortunately, surface.getSize(true) returns null!
#dcsan, thanks for the link. I believe you may be right, however the solution linked to ends up being much too involved for me.
After much searching, hacking, and experimenting, I've settled on the following approach:
-] use the DOM to get untransformed bounding boxes for text strings
-] format the text strings in SVG form
-] make it so the strings are invisible (set fill and stroke to none)
-] reuse the same "div" element for all the strings that I want to measure
-] once I have the untransformed bounding box, then set the famous surface size to that and then apply modifiers.
-] if I need the bounding box after all transforms have been applied, then get the total accumulated transforms for the surface and multiply that with the original untransformed bounding box
Here's the code to create the DOM element, insert SVG text, then get the bounding box:
//Do this part once, of creating a DOM element and adding it to the document
var el1 = document.createElement("div");
document.body.appendChild(el1); //only need to append once -- just set innerHTML after
//now set the SVG text string -- from this point down can be repeated for multiple
// strings without removing or re-adding the element, nor fiddling with the DOM
var text1_1_1_SVG = '<svg> <text x="0" y="0" style="font-family: Arial; font-size: 12;fill:none;stroke:none" id="svgText1">' + myFamousSurface.content + '</text> </svg>';
//note the id is inside the text element! Also the fill and stroke are null so nothing paints
el1.innerHTML = text1_1_1_SVG;
//now get the element -- this seems to be what triggers the bounding box calc
var test = document.getElementById("svgText1"); //this is ID in the text element
//get the box, take the values out of it, and display them
var rect = test.getBoundingClientRect();
var str = "";
for (i in rect) { //a trick for getting all the attributes of the object
str += i + " = " + rect[i] + " ";
}
console.log("svgText1: " + str);
FYI, all of the SVGTextElement methods seem to be callable upon gotElem.
SVGTextElement docs here: http://msdn.microsoft.com/en-us/library/ie/ff972126(v=vs.85).aspx
#seanhalle
I'm pretty sure .getSize(true) is returning null because the element has not yet been added to the DOM. Keep in mind that famo.us is synchronized with animation-frames, and updates to the DOM happen don't happen instantly. Accesssing the DOM directly (aka pinging) is strongly disadviced because you will loose the performance benefits that famo.us promises.
What I would do is create a custom view to wrap your surface inside and implement a render-method in it. In the render-method, use getSize(true) to get the size. If it returns null,
you know it has not yet been committed to the DOM.
view in action as jsfiddle
define('MyView', function (require, exports, module) {
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
function MyView() {
View.apply(this, arguments);
this.surface = new Surface();
this.add(this.surface);
}
MyView.prototype = Object.create(View.prototype);
MyView.prototype.constructor = MyView;
MyView.prototype.render = function() {
var size = this.getSize(true);
if (size) {
if (!this.hasSize) {
this.hasSize = true;
console.log('now we have a size: ' + size);
}
this.surface.setContent('Size: ' + JSON.stringify(size));
} else {
console.log('no size yet');
}
return this._node.render();
};
module.exports = MyView;
});

How to manage event through a fill color with highchart?

I've got two series in an Highchart graph. Both are handling event like mousemove (to show tooltip) and mouse click.
Unfortunatly one of those series is an area serie which block any event to be triggered by the other one.
Let's say I've got those series with those plot options :
plotOptions: {
series: {
cursor: 'pointer',
point: {
events: {
click: function() {
alert ('Category: '+ this.category +', value: '+ this.y);
}
}
}
}
},
series: [{
color:'red',
data: [29.9, 51.5, 16.4, 19.2, 44.0, 16.0, 35.6, 48.5, 26.4, 4.1, 9.6, 54.4]
},
{
type:'area',
data: [39.9, 75.5, 120.4, 150.2, 130.0, 120.0, 140.6, 158.5, 216.4, 194.1, 95.6, 54.4]}
]
}
as seen in this JSFiddle
I can never click or see the tooltip on the red series points.
What I could do is to change the order of the series so the red one is over the blue one.
( like this). But I've got situations where I've got several area graphs over each other like that. In this case, change the order won't help.
Is there a way I can handle events through the fill area?
Ok I found a way that suit my need : manimulating SVG element order as indicated in this topic :
SVG re-ordering z-index (Raphael optional)
I'm using this code in the redraw event of the chart :
redraw : function(){
//get a reference on the first series svg element.
var svgSeriesParent = document.querySelectorAll('.highcharts-series')[0];
//loop on all the path drawn by highchart
var pathArray = document.querySelectorAll('path');
for (var pathIndex = 0; pathIndex < pathArray.length; pathIndex++) {
var pathObj = pathArray[pathIndex];
//if attribute fill is not "none", this is an area path.
if ($(pathObj).attr('fill') !== "none") {
//move the current fill path before the first series svg element.
svgSeriesParent.insertBefore(pathObj, svgSeriesParent.firstChild);
}
}
}
See the result in this JSFiddle
Limitation :
The fill area doesn't have the same svg group anymore thus, hidding / showing the series is broken : all the fill area are considered being part of the first series. (click in the legend to see it).
In my case I don't need to hide series so I'll go on with this solution.
The svg order manipulation is not complicated that's why I would expect highchart to manage this case : add a fillZIndex parametter doesn't seem difficult and could be pretty usefull...
Only what comes to my mind is using 4 series, 2 area and 2 lines and use index.
http://jsfiddle.net/j8qYK/3/

Raphael js: animating path with it's drop shadow

I'm not sure how to fade in and out a path and the drop shadow I've created for it:
var p = "M10,10L810,10L810,190L10,190L10,10";
var s = "M16,16L816,16L816,196L16,196L16,16";
var paper = Raphael(100, 100, 830, 210);
var shadow = paper.path(s);
shadow.attr({stroke: "none", fill: "#999999", opacity:0.1});
shadow.blur(4);
var c = paper.path(p);
c.attr({fill:"#ffffff", stroke:"none"});
Do I have to manually animate c and shadow at the same time? Is there a way I can just tell this particular paper to fade everything inside of it?
put your path and your shadow in a set. apply your animation to the set and it'll affect both.
Here's a really terrible example where I move both at once.
Maybe animating the drop shadow path to match the specification of the other path?
http://www.irunmywebsite.com/raphael/additionalhelp.php?q=fadingdropshadow
the amadan answer is incorrect. please discard it.
If you put elements in a set, the glow will be part of the set but set is logical only.
it means that if you animate the set, your shadow animation will look wierd.
use set only to refer to logical zoning for events for instance.
best is to animate the shadows in // of the other objects.

Resources