I'm using Raphael with Meteor and have run into a problem. I'm creating a paper by using var paper = Raphael("paper", 800, 600); If I put this code in a rendered function, like Template.board.rendered, I can use it just fine. However, the paper var from this template apparently can't be accessed by other functions, even if I call them from within the template. I thought that I could get around this by creating the paper in Meteor.startup, but I still have the same problem.
For instance, the following code doesn't work:
Pieces = new Meteor.Collection('pieces');
Meteor.startup(function () {
var paper = Raphael("paper", 800, 600);
});
Template.board.rendered = function () {
// If I create the paper in here along with the contents of createGoban
// it works, but then I can't use it anywhere else...
// var paper = Raphael("paper", 800, 600);
createGoban();
};
var createGoban = function () {
// Create goban
// Based off of svg in public domain: http://commons.wikimedia.org/wiki/File:Blank_Go_board.svg
// Converted using http://readysetraphael.com/
var rect_a = paper.rect(0, 0, 96, 96);
rect_a.attr({fill: '#DCB35C','stroke-width': '0','stroke-opacity': '1'}).data('id', 'rect_a');
var path_b = paper.path("M2.9,93h90.2m-.2-5H3m0-5h90m0-5H3m0-5h90m0-5H3m0-5h90m0-5H3m0-5h90m0-5H3m0-5h90m0-5H3m0-5h90m0-5H3m0-5h90m0-5H3m0-5h90m0-5H3m-.1-5h90.2M3,3V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3m5,0V93m5,0V3");
path_b.attr({stroke: '#000',"stroke-width": '0.2',fill: 'none','stroke-opacity': '1'}).data('id', 'path_b');
var path_c = paper.path("M18,78l0,0m30,0l0,0m30,0l0,0m0-30l0,0m-30,0l0,0m-30,0l0,0m0-30l0,0m30,0l0,0m30,0l0,0");
path_c.attr({stroke: '#000',"stroke-width": '4',"stroke-linecap": 'round','stroke-opacity': '1','fill': '#000000'}).data('id', 'path_c');
var goban = [rect_a, path_b, path_c];
// Scale goban up since rsr outputs reduced size
for(var i = 0; i < goban.length; i++) {
goban[i].transform('S5,5,0,0');
};
};
Your code works fine after removing the var inside startup(), which makes paper global:
Meteor.startup(function () {
paper = Raphael("paper", 800, 600);
});
Related
I am looking for a more efficient way to constrain/set text in Raphael.
I have text that can be written in a box. That text should be centered (based on a number that could change if user wanted to shift text left/right) and the text cannot go beyond the boundaries of the paper.
This is what I do now and its not manageable performance wise
// Build a path
var path = this.paper.print(x, x, text, font, size, 'middle')
// Center line by getting bounding box and shifting to half of that
var bb = path.getBBox()
path.transform('...T' + [-bb.width / 2, 0])
// Compare paper size vs bb
// if it goes beyond I adjust X and Y accordingly and redo the above
So ideally I would like to predict the size of the text before it prints - I am not sure this is possible though as it is probably font dependent. I have looked for a command to contrain text but do not see one?
The other thought I had was to create some kind of shadow paper that does not print to screen and use that to determine size before I render to user. I am not sure where the lag is though - if it's in the screen rendering good but if its in the general logic of creating the svg then that wont help.
I'd appreciate suggestions
You can use opentype.js for measuring text, also you can get svg path by using Path.toPathData method. There is no need of cufon compiled js fonts.
For text measure, create a canvas element somewhere in your DOM.
<canvas id="canvas" style="display: none;"></canvas>
this function will load text and count width, height
function measureText(text, font_name, size) {
var dfd = $.Deferred();
opentype.load('/fonts/' + font_name + '.ttf', function (err, font) {
if (err) {
dfd.reject('Font is not loaded');
console.log('Could not load font: ' + err);
} else {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
var tmp_path = font.getPath(text, 100, 100, size);
tmp_path.draw(ctx); // Draw path on the canvas
var ascent = 0;
var descent = 0;
var width = 0;
var scale = 1 / font.unitsPerEm * size;
var glyphs = font.stringToGlyphs(text_string);
for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i];
if (glyph.advanceWidth) {
width += glyph.advanceWidth * scale;
}
if (i < glyphs.length - 1) {
var kerningValue = font.getKerningValue(glyph, glyphs[i + 1]);
width += kerningValue * scale;
}
ascent = Math.max(ascent, glyph.yMax);
descent = Math.min(descent, glyph.yMin);
}
return dfd.resolve({
width: width,
height: ascent * scale,
actualBoundingBoxDescent: descent * scale,
fontBoundingBoxAscent: font.ascender * scale,
fontBoundingBoxDescent: font.descender * scale
});
}
});
return dfd.promise();
},
then use this function to get text dimensions:
$.when(measureText("ABCD", "arial", 48)).done(function(res) {
var height = res.height,
width = res.width;
...
});
I have a program where the user can add images to a paper, using Raphael. Now I want to be able to change an image to another image when clicked. Is this possible? Do I have to add an ID to the images? If so, how?
var imgURL = "img/img.png";
var bankNoteImg = new Image();
bankNoteImg.src = imgURL;
var width = bankNoteImg.width;
var height = bankNoteImg.height;
var image = paper.image(imgURL, X, Y, width, height);
Use the click and attr function of Raphael api.
Element.click will take a function as a parameter. Use this to access the image element inside the function.
I created a simple demo
var paper = Raphael(document.getElementById("papercanvas"), 200, 200);
var img = paper.image(URL1, 100, 100, 100 , 100);
function changeImageSource() {
this.attr('src', URL2);
}
img.click(changeImageSource);
I've created 4 rects using for loop. If you hover on any of these rects a rect will be displayed alongside. But the problem is that newly displayed rect doesn't hide on mouse out. What is wrong with my code?
JS Fiddle
window.onload = function() {
var paper = Raphael(0, 0, 640, 540);
function aa(h1,h2){
var showbox = paper.rect(h1+300,h2,100,100);
}
function ab(){
showbox.hide();
}
for (i = 0; i < 2; i++) {
for (j = 0; j < 2; j++) {
(function(i, j) {
var boxes = paper.rect(0 + (j * 100), 0 + (i * 100), 100, 100).attr({
fill: '#303030',
stroke: 'white'
});
boxes.node.onmouseover = function() {
var h1 = boxes.getBBox().x;
var h2 = boxes.getBBox().y;
aa(h1,h2);
};
boxes.node.onmouseout = function() {
ab();
};
})(i, j);
}
}
You've got javascript scope problems (plus two other smaller problems, see below).
The variable showbox is defined within function aa. So your onmouseout function can't see it. Get Firebug or your browser's equivalent up and you'll see a stack of showbox is not defined errors.
Tip: When working with Raphael, I usually create an object or array that contains all my created objects, keyed for easy access and scoped above all my Raphael-related functions so all of them can access it (see jsfiddle example below).
How to access your Raphael objects is a piece of application design you need to decide on early on, else you'll need to do lots of refactoring further down the line (been there, it hurts!).
Here's an adapted version of your code that works:
jsfiddle
Edit: I added comments explaining each change.
It also fixes two other problems:
(In the jsfiddle code) No such attr as display: none; in Raphael, try .attr({opacity:0}) or .hide()...
...BUT... don't! Your mouseover event creates rectangles, your mouseout event... hides them...? You're going to have an ever-growing stack of invisible rectangles that could eventually crash someone's browser. Either show, then hide, or create, then remove - don't create then hide!
The mouseover / mouseout events themselves are actually fine! :-)
Okay, this has made me feel a little peevish because it seems like it should be straightforward but... it's not?
I have a situation where I am layering several SVGs and divs over the top of each other using specific z-index attributes to control their rendering order. All of that works fine, but the only way I can discover to get the DOM node for a given paper element is as follows:
// given containerID, width, height, and zIndex are in scope
var canvas = Raphael( containerID, width, height );
var tempObject = canvas.rect( 0, 0, 1, 1, 0 ).attr( { fill: 'none', stroke: 'none' } );
var svgNode = tempObject.node.parentNode;
$(svgNode).css( { 'z-index': zIndex } );
This works, but it is patently inelegant. Anyone know how to get the paper's svg node directly?
canvas.canvas should do the trick.
To avoid naming confusion, you can do:
var paper = Raphael( containerID, width, height );
var svgNode = paper.canvas;
I have seen graphs in Flash & stuff that basically adapt nicely to whatever the size of the browser or flexible element they are inside of.... I'm not really too well versed with raphaelJS but can you do this, and if so, how?
In raphaeljs, you can call .setSize on a Raphael object to update its size. To adapt to runtime changes in browser window size, you can respond to a window resize event. Using jQuery, you could do:
// initialize Raphael object
var w = $(window).width(),
h = $(window).height();
var paper = Raphael($("#my_element").get(0), w,h);
$(window).resize(function(){
w = $(window).width();
h = $(window).height();
paper.setSize(w,h);
redraw_element(); // code to handle re-drawing, if necessary
});
This will get you a responsive SVG
var w = 500, h=500;
var paper = Raphael(w,h);
paper.setViewBox(0,0,w,h,true);
paper.setSize('100%', '100%');
Normally you could set the width to %100 and define a viewBox within SVG. But, Raphael JS manually set a width and height directly on your SVG elements, which kills this technique.
Bosh's answer is great, but it was distorting the aspect ratio for me. Had to tweak a few things to get it working correctly. Also, included some logic to maintain a maximum size.
// Variables
var width;
var height;
var maxWidth = 940;
var maxHeight = 600;
var widthPer;
function setSize() {
// Setup width
width = window.innerWidth;
if (width > maxWidth) width = maxWidth;
// Setup height
widthPer = width / maxWidth;
height = widthPer * maxHeight;
}
var paper = Raphael(document.getElementById("infographic"), 940, 600);
paper.setViewBox(0, 0, 940, 600, true);
window.onresize = function(event) {
setSize();
paper.setSize(width,height);
redraw_element();
}