Snap.svg transform not working - possible issue with element selection? - javascript

function base_axes() {
var s = Snap("#base_axes");
var dim = 0.1*window.innerWidth;
var x_line = s.line(0, dim, 0, dim);
x_line.attr("stroke", "#5e0734");
x_line.attr("stroke-width", "5px");
x_line.animate({
x2: window.innerWidth
}, 1000, mina.easein);
var y_line = s.line(dim, 0, dim, 0);
y_line.attr("stroke", "#5e0734");
y_line.attr("stroke-width", "5px");
y_line.animate({
y2: window.innerHeight
}, 1000, mina.easein);
Snap.load('https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg', function(data) {
var logo = s.append(data);
var bbox = logo.getBBox();
var scale_factor = dim/bbox.height;
var transform_string = "s" + scale_factor + "," + scale_factor;
logo.transform(transform_string);
});
}
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/snap.svg.js"></script>
<body onload="base_axes()">
<svg id="base_axes" height="100%" width="100%"></svg>
I'm using this to load an svg into another svg and then transform it:
<svg id="base" height="100%" width="100%"></svg>
JS:
var s = Snap("#base");
var dim = 1;
Snap.load('img.svg', function(data) {
var logo = s.append(data);
var bbox = logo.getBBox();
console.log(bbox);
var scale_factor = dim/bbox.height;
var transform_string = "'s" + scale_factor + "," + scale_factor + "'";
logo.transform(transform_string);
});
But nothing happens. In an effort to troubleshoot, I replaced the bottom line with logo.transform('s0.1,0.1'), and that failed too. Is there something wrong with the creation of logo?
To clarify - the first svg, #id, is selected correctly, the new svg (logo) is correctly appended to it, and bbox is calculated correctly, but the final transform does nothing. The transform string looks correct, evaluating to s0.03,0.03, but the final line logo.transform(transform_string) does nothing.

I think your problem is this line, but I can't be sure without seeing it in a test example...
var logo = s.append(data);
If you look at the docs here, it says it returns the parent element. So you are saying 'logo = the svg element'.
The svg element doesn't allow transforms (in some browsers, see Roberts comment below).
So you either want to select for example a 'g' element in the svg logo by selecting it (eg s.select('#logo') or Snap('#logo'), but we haven't seen that to know if it exists), or append the svg logo to a 'g' element thats inside your svg element eg
<svg id="base" height="100%" width="100%"><g id="logo"></g></svg>
then you can apply the transform to that, rather than the svg, eg
var logo = Snap('#logo').append(data)
You also need to remove the quotes in your transform string. I.e
var transform_string = "s" + scale_factor + "," + scale_factor;

Related

Mousemove event won't fire on canvas even if z-index is max

I’m a bit at loss since a couple of weeks, trying to get a mousemove event to work on a set of layered canvases.
Following advice in an earlier post, I confirmed that the event triggers properly only when the z-index of the targeted layer is on top (as demonstrated by this simple fiddle):
However, in the extended code I’m working with (d3 parcoords), despite having the same HTML structure as above, I can’t get the event to fire for canvases in the parallel coordinates chart on top.
This bl.ocks shows the extended version, and how the event won't work even when the targeted layered canvas has the greatest z-index (although the event does work well in a simple canvas below the chart).
I tried making a minimal example out of the parcoords file, but can’t manage to get a useful and working version given the number of interconnected functions.
I’m hoping someone who knows the original parcoords code might be able to clarify how exactly the chart's canvases are organized, and if something particular there might be causing the mousemove event not to work. Alternatively, maybe some experienced eyes might catch something I’m missing in the examples I posted.
Any tips much appreciated!
Extract of code from d3.parcoords.js which generates the canvases:
var pc = function(selection) {
selection = pc.selection = d3.select(selection);
__.width = selection[0][0].clientWidth;
__.height = selection[0][0].clientHeight;
// canvas data layers
["marks", "foreground", "brushed", "highlight", "clickable_colors"].forEach(function(layer, i) {
canvas[layer] = selection
.append("canvas")
.attr({
id: layer, //added an id for easier selecting for mouse event
class: layer,
style: "position:absolute;z-index: " + i
})[0][0];
ctx[layer] = canvas[layer].getContext("2d");
});
// svg tick and brush layers
pc.svg = selection
.append("svg")
.attr("width", __.width)
.attr("height", __.height)
.style("font", "14px sans-serif")
.style("position", "absolute")
.append("svg:g")
.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
return pc;
};
Function used to draw the squares and set the mousemove event:
//This custom function returns polyline ID on click, based on its HEX color in the hidden canvas "clickable_colors"
//Loosely based on http://jsfiddle.net/DV9Bw/1/ and https://stackoverflow.com/questions/6735470/get-pixel-color-from-canvas-on-mouseover
function getPolylineID() {
function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}
// set up some squares
var my_clickable_canvas = document.getElementById('clickable_colors');
var context = my_clickable_canvas.getContext('2d');
context.fillStyle = "rgb(255,0,0)";
context.fillRect(0, 0, 50, 50);
context.fillStyle = "rgb(0,0,255)";
context.fillRect(55, 0, 50, 50);
$("#clickable_colors").mousemove(function(e) {
//$(document).mousemove(function(e) {
//debugger;
var pos = findPos(this);
var x = e.pageX - pos.x;
//console.log(x)
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" + y;
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
$('#status').html(coord + "<br>" + hex);
console.log("Polyline's hex:" + hex)
});
}
The svg is covering your canvases.
The canvases and svg are all on the same level, but you have not set a z-index on the svg, and as it is rendered after all the canvases.
Simply putting z-index: 0; on the svg in the page you linked fixed it for me in Chrome.
It seems to simply be a problem with z-indexes.
You should set both css position and z-index on all the canvases and the sgv on the same level.
EDIT
Sorry, I was wrong about it being just the z-index.
I could get it to work by removing the following css.
.parcoords > canvas {
pointer-events: none;
}
But that seems to be in the library you are using, so just override it.
.parcoords > canvas {
pointer-events: auto;
}

javascript to add area element with onmouseover attribute

I am trying to add area elements to an image map dynamically. The image is set to display transparently superimposed over a canvas. My goal is to write text on the canvas, use the same coordinates to create the area element on the map, and draw a rectangle on the canvas surrounding the text when the user hovers over the text. (Ultimately I want it to trigger a tooltip, too.) I have done this already with the same map and canvas setup using area elements hardcoded in HTML.
My problem is that I can create the area, appendChild it to the map element and add attributes. However, mousing over the text never triggers the function call to draw the rectangle.
The function used to add the areas to the map (shown as cMap) is "addArea", and the function to draw the rectangle on the canvas (context is ctx) is "labelHover". I have tried every different syntax I have seen demonstrated for adding the .onmouseover attribute to the area, but the alert in the labelHover function never triggers.
function addArea(pX, lY, idX, tipText) {
var labelArea = document.createElement('area');
cMap.appendChild(labelArea);
labelArea.className = "labelArea";
var tlTipID = "tlTip" + idX;
labelArea.id = tlTipID;
labelArea.shape = "rect";
areaCoords = pX + "," + (lY + 42) + "," + (pX + 100) + "," + (lY + 54);
labelArea.coords = areaCoords;
// alert(labelArea.coords);
labelArea.onmouseover = function(){labelHover(pX, lY+42)};
labelArea.onmouseleave = function(){labelLeave(pX, lY+42)};
}
and
function labelHover(ulx,uly) {
ctx.lineWidth = "1";
ctx.strokeStyle = "#ff0000";
ctx.strokeRect(ulx,uly,100,12);
alert(ulx);
}
Thanks for any help.
try this:
labelArea.setAttribute('onmouseover', "labelHover('" + pX + "," + (lY+42) + "')");
labelArea.setAttribute('onmouseout', "labelLeave('" + pX + "," + (lY+42) + "')");

multiple canvases are not working

I tried to generate multiple canvases on the fly and when I create a new canvas, the previous one disappears. See here for an example:
http://jsfiddle.net/adrianh/5jspv/4/
Here is the javascript code:
var circleCount = 0;
function circleRect(rect)
{
var diameter = Math.sqrt(rect.width*rect.width+rect.height*rect.height);
var cx = (rect.right + rect.left)/2;
var cy = (rect.top + rect.bottom)/2;
var left = Math.floor(cx - diameter/2);
var top = Math.floor(cy - diameter/2);
diameter = Math.floor(diameter);
var html = "<canvas id='circleCanvas"+circleCount+"' "+
"width='"+(diameter+2)+"' "+
"height='"+(diameter+2)+"' "+
"style='"+
"position:absolute;"+
"z-index:0;"+
"left:"+(left-1)+"px;"+
"top:"+(top-1)+"px;"+
//"border:1px solid;"+
"' />";
alert(html);
var container = document.getElementById("circles");
container.innerHTML += html;
var c=document.getElementById("circleCanvas"+circleCount);
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.arc(diameter/2+1,diameter/2+1,diameter/2,0,2*Math.PI);
ctx.stroke();
++circleCount;
}
$(".circled").each(function(i, obj) {
var rect = obj.getBoundingClientRect();
circleRect(rect);
});
Why is only one canvas showing up?
It will be more reliable to manipulate the DOM rather than trying to inline things with innerHTML. This code uses jQuery's DOM manipulation methods:
var circleCount = 0;
function circleRect(rect)
{
var diameter = Math.sqrt(rect.width*rect.width+rect.height*rect.height);
var cx = (rect.right + rect.left)/2;
var cy = (rect.top + rect.bottom)/2;
var left = Math.floor(cx - diameter/2);
var top = Math.floor(cy - diameter/2);
diameter = Math.floor(diameter);
var html = $("<canvas id='circleCanvas"+circleCount+"' "+
"width='"+(diameter+2)+"' "+
"height='"+(diameter+2)+"' "+
"style='"+
"position:absolute;"+
"z-index:0;"+
"left:"+(left-1)+"px;"+
"top:"+(top-1)+"px;"+
"' />");
$("#circles").append(html);
var ctx=html[0].getContext("2d");
ctx.beginPath();
ctx.arc(diameter/2+1,diameter/2+1,diameter/2,0,2*Math.PI);
ctx.stroke();
++circleCount;
}
You could also use the standard createElement and appendChild if you don't really need jQuery.
The innerHTML property has a number of drawbacks, although there is nothing specific I can find about not using += with it, the fact that insertAdjacentHTML exists would seem to indicate that you shouldn't really expect it to work well. (forgot this bit earlier) In this case, as you correctly surmised in your comment, the canvas you've drawn on is replaced by a new one when the assignment to innerHTML happens.

How to scale text using Raphael.js

I came to know about Raphael.js recently and I was trying to do some hands on.
I would like to scale a text but it is not working.
After searching a lot in Google I have not find any idea.
Code is:
var paper = Raphael("paper1", 1000,1000);
var txt = paper.text(20,50,"Hello World").attr({
"font-family":"Arial","font-size":"30px", "font-weight": "normal",
fill: "#000000", stroke:"black", "stroke-width": "0px",
"text-anchor" : "start" , "font-style": "normal"});
flip is working by txt.scale(-1,1)
but txt.scale(2,1) is not scaling the text.
Is there a way to scale a text?
Note: Font size of the text needs to remain same i.e 30px in my case.
Is there a way to scale a text?
DEMO
I checked and it works like a charm
var r = Raphael('test');
var t = r.text(200, 100, "I am a text").attr({fill:'red', 'font-size':30});
$('#zoomin').click(function() {
t.scale(2);
});
$('#zoomout').click(function() {
t.scale(.5);
});
If we scale the text only then font size shall be changed.
We need to convert the text into an image and the we need to scale it.
To do that I have used a hidden canvas.
Following code works for me.
<canvas id='textCanvas' style="display:none"></canvas>
<script>
var paper = Raphael("paper1", 1000,1000);
var img = paper.image(getTxtImg("Hello World","italic","bold","15px","arial",
"#000000",130,35),20,28,300,80)
img.scale(2,1) //Double in width
img.scale(.5,1) //Half in width
function getTxtImg(txt,style,weight,fontsize,fontfamily,color,w,h)
{
var tCtx = document.getElementById('textCanvas').getContext('2d');
tCtx.canvas.width = w;
tCtx.canvas.height = h ;
var fontstr = "" + style + " " + weight + " " + fontsize + " " + fontfamily + " ";
tCtx.font = fontstr
tCtx.fillStyle = color
tCtx.fillText(txt, 0, h);
return tCtx.canvas.toDataURL();
}
</script>

The author of this Raphael JS extension has vanished from the face of the internet, help me with his script

The extension I'm talking about is the Raphael-zpd: http://pohjoisespoo.net84.net/src/raphael-zpd.js
/* EDIT The script is added to a Raphael document with this command var zpd = new RaphaelZPD(paper, { zoom: true, pan: true, drag: false}); where paper is your canvas */
The script was originally released at the authors github http://www.github.com/somnidea which no longer exists.
What I wanted to do was run the mousewheel zoom out to the threshold as soon as the raphael is loaded. The zoomthreshold is set at the beginning of the script zoomThreshold: [-37, 20]. In the mousewheel scroll function it is compared to zoomCurrent which is by default 0 me.zoomCurrent = 0;
This is the whole mousewheel event part
me.handleMouseWheel = function(evt) {
if (!me.opts.zoom) return;
if (evt.preventDefault)
evt.preventDefault();
evt.returnValue = false;
var svgDoc = evt.target.ownerDocument;
var delta;
if (evt.wheelDelta)
delta = evt.wheelDelta / 3600; // Chrome/Safari
else
delta = evt.detail / -90; // Mozilla
if (delta > 0) {
if (me.opts.zoomThreshold)
if (me.opts.zoomThreshold[1] <= me.zoomCurrent) return;
me.zoomCurrent++;
} else {
if (me.opts.zoomThreshold)
if (me.opts.zoomThreshold[0] >= me.zoomCurrent) return;
me.zoomCurrent--;
}
var z = 1 + delta; // Zoom factor: 0.9/1.1
var g = svgDoc.getElementById("viewport"+me.id);
var p = me.getEventPoint(evt);
p = p.matrixTransform(g.getCTM().inverse());
// Compute new scale matrix in current mouse position
var k = me.root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
me.setCTM(g, g.getCTM().multiply(k));
if (!me.stateTf)
me.stateTf = g.getCTM().inverse();
me.stateTf = me.stateTf.multiply(k.inverse());
}
The reason I can't just draw a smaller SVG to begin with is that I'm using raster images as the background and need them to be higher resolution. I would still like to start at the furthest point I've set at the threshold. Is it possible for me to somehow use this script to do this? I'm naturally using it otherwise to handle mouse zoom/pan.
//EDIT
There is also this function at the end of the script, but so far I've been unable to work it.
Raphael.fn.ZPDPanTo = function(x, y) {
var me = this;
if (me.gelem.getCTM() == null) {
alert('failed');
return null;
}
var stateTf = me.gelem.getCTM().inverse();
var svg = document.getElementsByTagName("svg")[0];
if (!svg.createSVGPoint) alert("no svg");
var p = svg.createSVGPoint();
p.x = x;
p.y = y;
p = p.matrixTransform(stateTf);
var element = me.gelem;
var matrix = stateTf.inverse().translate(p.x, p.y);
var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
element.setAttribute("transform", s);
return me;
}
Seems like it's used for panning through the document through say click events so that a click would execute the function with the given coordinates. However, as said I've been unable to work it. I don't know how it's supposed to function. I tried paper.ZPDPanTo(100, 100); as well as just ZPDPanTo(100,100) but nothing happens.
You may also want to check out the working branch for Raphaël 2.0, which supposedly adds support for viewBox and transforms, see https://github.com/DmitryBaranovskiy/raphael/tree/2.0.
This doesn't answer your question fully, but it seems quite possible that Raphaël 2.0 will address your use-case.
If you're using pure svg then you can manipulate the zoom&pan positions via the SVG DOM properties currentTranslate and currentScale, see this example.
An example using RAPHAEL ZPD:
var paper = Raphael("container",800,760);
window.paper = paper;
zpd = new RaphaelZPD(paper, { zoom: true, pan: true, drag: false });
paper.circle(100,100, 50).attr({fill:randomRGB(),opacity:0.95});
paper.rect(100,100, 250, 300).attr({fill:randomRGB(),opacity:0.65});
paper.circle(200,100, 50).attr({fill:randomRGB(),opacity:0.95});
paper.circle(100,200, 50).attr({fill:randomRGB(),opacity:0.95});
http://jsfiddle.net/4PkRm/1/

Categories

Resources