I am trying to change "d" attribute of all lines in certain SVG map to make straigh lines curved.
d="M514 222L488 66"
Is there any universal algorithm to change any straigt line "d" attribute (like this one above) and get curved line as result?
This is how I would do it: For the curve (a quadratic Bézier curve Q) I need to calculate the position of the control point. In this case I want the control point in the middle of the line at a distance R.
Please read the comments in the code to understand it.
// a variable to define the curvature
let R = 50;
// the points of the original line
let linePoints = [
{x:514,y:222},
{x:488,y:66}
]
//the length of the line
let length = thePath.getTotalLength();
//a point in the middle of the line
let point = thePath.getPointAtLength(length/2);
// calculate the angle of the line
let dy = linePoints[1].y - linePoints[0].y;
let dx = linePoints[1].x - linePoints[0].x;
let angle = Math.atan2(dy,dx);
let cp = {//control point for the bézier as a perpendicular line to thePath
x:point.x + R*Math.cos(angle + Math.PI/2),
y:point.y + R*Math.sin(angle + Math.PI/2)
}
//the new d attribute for the path
let d = `M${linePoints[0].x}, ${linePoints[0].y} Q${cp.x},${cp.y} ${linePoints[1].x}, ${linePoints[1].y}`;
//set the new d attribute
thePath.setAttributeNS(null,"d",d)
svg {
border: 1px solid;
width: 100vh;
}
path {
stroke: black;
fill: none;
}
<svg viewBox = "300 0 400 300">
<path id="thePath" d="M514, 222L488, 66" />
</svg>
I have a problem with a grid of rects in an SVG.
Here is my code over at jsFiddle: https://jsfiddle.net/swpcpvxL/
It is supposed to make a square by plotting one pixel at a time. I use a 1 by 1 SVG rect to plot a pixel.
var svg = document.getElementById("mysvg");
for (var y = 50; y <= 150; ++y) {
for (var x = 50; x <= 150; ++x) {
var r = Math.floor((x + y) / 250);
var g = Math.floor((Math.sin(x/10.0) + 1) * x);
var b = Math.floor((Math.sin(y/10.0) + 1) * x);
var color = "rgb(" + r + "," + g + "," + b + ")";
var k = document.createElementNS("http://www.w3.org/2000/svg", "rect");
k.setAttribute("x", x);
k.setAttribute("y", y);
k.setAttribute("width", "1");
k.setAttribute("height", "1");
k.setAttribute("style", "fill: " + color);
svg.appendChild(k);
}
}
My issue is that on Firefox the rect don't plot correctly. They show up the wrong color (washed out) and are actually translucent. I think the issue is that Firefox is doing anti-aliasing or something on the rects instead of just plotting the rect right on the pixel I want. I also tested on IE - it doesn't have this problem and the code works correctly. I don't have Chrome to test with.
I uploaded an image of what it looks like for me in Firefox here.
As you can see in the image, I can see the circles through the rects. This is not what I want at all!
How can I fix the Firefox problem? Or is there a better way to generate and plot a bitmap in a SVG like this? I've noticed that this method is a bit slow, so maybe there is a better approach.
Thanks!
I see the problem in my Firefox too. It does look like a blending problem of some sort. You can also see the image "flicker" if you move the window around or resize it.
A work around is to plot each rect as a 2x2 box, instead of a 1x1. So the only change is this:
k.setAttribute("width", "2");
k.setAttribute("height", "2");
You still step on the X and Y by one pixel at a time.
That way each rect overlaps the one on each side by 1 pixel. It will make your entire 100 square one pixel bigger to the right and bottom. You may want to shift the origin to compensate, if you use this method and care about that.
I've got a svg-based loading animation for a web project, which draws a 360°-arc on hover. This takes about .8s to finish. After that a container slides in, see my fiddle: http://jsfiddle.net/s2XCL/2/
function drawCircle(arc) {
var i = 0;
var circle = document.getElementById(arc);
var angle = 0;
var radius = 75;
window.timer = window.setInterval(
function () {
angle -= 5;
angle %= 360;
var radians = (angle / 180) * Math.PI;
var x = 200 + Math.cos(radians) * radius;
var y = 200 + Math.sin(radians) * radius;
var e = circle.getAttribute("d");
if (i === 0) {
var d = e + " M " + x + " " + y;
} else {
var d = e + " L " + x + " " + y;
}
circle.setAttribute("d", d);
i++;
}, 10);
}
function removeCircle() {
// var circle = document.getElementById(arc);
// circle.style.display = "none";
}
The function is called inside of the svg-tag in the HTML-markup:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="xMidYMid" style="width:400px; height:400px;" onmouseover="drawCircle('arc2'); removeCircle();">
<path d="M200,200 " id="arc2" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="5"></path>
</svg>
This works as intended, but occupies an entire 3 GHz-core on both of my two testing machines. I am currently learning JS, so I can only guess what causes the performance lack.
I also want the svg to disappear after completing the animation or the mouse leaving the container, so if anyone got a tip on that, that'd be great.
For any fellow googlers, see here http://jsfiddle.net/s2XCL/4/ for the solution. Feel free to use the snippet aswell.
The problem is that you never clear your timeouts. This will cause your timer to keep running, and every time you move your mouse above the svg element, it will keep creating new timers. That means that if you move your mouse a bit too long, it'll keep adding more and more to the path tag, and it will create a lot of lag.
To fix this, simply add a clearInterval for your global timer variable:
clearInterval(timer);
You'll also want to make sure this only runs when you initially move your mouse on top of the svg, not every time you move within it, by changing the event handler to mouseenter, not mousemove. And finally, you would need to clear the path's d attribute, otherwise the circle won't ever be removed.
The function would have the following: (http://jsfiddle.net/s2XCL/3/)
clearInterval(window.timer||0); //clear the previous timer, or "timer 0" (nonexistent) when it's not defined yet
circle.setAttribute("d", "M200,200 "); //reset d attribute to default
window.timer = window.setInterval(//interval
Also, building a whole path each time is not a very efficient way to do it.
A better way would be to use the dasharray trick for animating drawing of a path. What you do is slowly increase the first number in stroke-dasharray until it is equal to the length of the path. In this case, that's the circumference of the circle.
stroke-dasharray="0 1000"
stroke-dasharray="5 1000"
stroke-dasharray="10 1000"
...etc...
Here's a fiddle showing this trick applied to your case.
Are all canvas tag dimensions in pixels?
I am asking because I understood them to be.
But my math is broken or I am just not grasping something here.
I have been doing python mostly and just jumped back into Java Scripting.
If I am just doing something stupid let me know.
For a game I am writing, I wanted to have a blocky gradient.
I have the following:
HTML
<canvas id="heir"></canvas>
CSS
#media screen {
body { font-size: 12pt }
/* Game Rendering Space */
canvas {
width: 640px;
height: 480px;
border-style: solid;
border-width: 1px;
}
}
JavaScript (Shortened)
function testDraw ( thecontext )
{
var myblue = 255;
thecontext.save(); // Save All Settings (Before this Function was called)
for (var i = 0; i < 480; i = i + 10 ) {
if (myblue.toString(16).length == 1)
{
thecontext.fillStyle = "#00000" + myblue.toString(16);
} else {
thecontext.fillStyle = "#0000" + myblue.toString(16);
}
thecontext.fillRect(0, i, 640, 10);
myblue = myblue - 2;
};
thecontext.restore(); // Restore Settings to Save Point (Removing Styles, etc...)
}
function main ()
{
var targetcontext = document.getElementById(“main”).getContext("2d");
testDraw(targetcontext);
}
To me this should produce a series of 640w by 10h pixel bars. In Google Chrome and Fire Fox I get 15 bars. To me that means ( 480 / 15 ) is 32 pixel high bars.
So I change the code to:
function testDraw ( thecontext )
{
var myblue = 255;
thecontext.save(); // Save All Settings (Before this Function was called)
for (var i = 0; i < 16; i++ ) {
if (myblue.toString(16).length == 1)
{
thecontext.fillStyle = "#00000" + myblue.toString(16);
} else {
thecontext.fillStyle = "#0000" + myblue.toString(16);
}
thecontext.fillRect(0, (i * 10), 640, 10);
myblue = myblue - 10;
};
thecontext.restore(); // Restore Settings to Save Point (Removing Styles, etc...)
}
And get a true 32 pixel height result for comparison. Other than the fact that the first code snippet has shades of blue rendering in non-visible portions of the canvas they are measuring 32 pixels.
Now back to the Original Java Code...
If I inspect the canvas tag in Chrome it reports 640 x 480.
If I inspect it in Fire Fox it reports 640 x 480.
BUT! Fire Fox exports the original code to png at 300 x 150 (which is 15 rows of 10). Is it some how being resized to 640 x 480 by the CSS instead of being set to a true 640 x 480?
Why, how, what? O_o I confused...
I believe you just need to set the width and height of your canvas when you create it in the html. Those values control the size of the coordinate space in your canvas and the defaults are 300 x 150.
<canvas id="heir" width="640" height="480"></canvas>
From http://dev.w3.org/html5/spec/Overview.html#canvas
The canvas element has two attributes
to control the size of the coordinate
space: width and height. These
attributes, when specified, must have
values that are valid non-negative
integers. The rules for parsing
non-negative integers must be used to
obtain their numeric values. If an
attribute is missing, or if parsing
its value returns an error, then the
default value must be used instead.
The width attribute defaults to 300,
and the height attribute defaults to
150.
I have an HTML page with some textual spans marked up something like this:
...
<span id="T2" class="Protein">p50</span>
...
<span id="T3" class="Protein">p65</span>
...
<span id="T34" ids="T2 T3" class="Positive_regulation">recruitment</span>
...
I.e. each span has an ID and refers to zero or more spans via their IDs.
I would like to visualize these references as arrows.
Two questions:
How can I map an ID of a span to the screen coordinates of the rendering of the span?
How do I draw arrows going from one rendering to another?
The solution should work in Firefox, working in other browsers is a plus but not really necessary. The solution could use jQuery, or some other lightweight JavaScript library.
This captured my interest for long enough to produce a little test. The code is below, and you can see it in action
It lists all the spans on the page (might want to restrict that to just those with ids starting with T if that is suitable), and uses the 'ids' attribute to build the list of links. Using a canvas element behind the spans, it draws arc arrows alternately above and below the spans for each source span.
<script type="application/x-javascript">
function generateNodeSet() {
var spans = document.getElementsByTagName("span");
var retarr = [];
for(var i=0;i<spans.length; i++) {
retarr[retarr.length] = spans[i].id;
}
return retarr;
}
function generateLinks(nodeIds) {
var retarr = [];
for(var i=0; i<nodeIds.length; i++) {
var id = nodeIds[i];
var span = document.getElementById(id);
var atts = span.attributes;
var ids_str = false;
if((atts.getNamedItem) && (atts.getNamedItem('ids'))) {
ids_str = atts.getNamedItem('ids').value;
}
if(ids_str) {
retarr[id] = ids_str.split(" ");
}
}
return retarr;
}
// degrees to radians, because most people think in degrees
function degToRad(angle_degrees) {
return angle_degrees/180*Math.PI;
}
// draw a horizontal arc
// ctx: canvas context;
// inax: first x point
// inbx: second x point
// y: y value of start and end
// alpha_degrees: (tangential) angle of start and end
// upside: true for arc above y, false for arc below y.
function drawHorizArc(ctx, inax, inbx, y, alpha_degrees, upside)
{
var alpha = degToRad(alpha_degrees);
var startangle = (upside ? ((3.0/2.0)*Math.PI + alpha) : ((1.0/2.0)*Math.PI - alpha));
var endangle = (upside ? ((3.0/2.0)*Math.PI - alpha) : ((1.0/2.0)*Math.PI + alpha));
var ax=Math.min(inax,inbx);
var bx=Math.max(inax,inbx);
// tan(alpha) = o/a = ((bx-ax)/2) / o
// o = ((bx-ax)/2/tan(alpha))
// centre of circle is (bx+ax)/2, y-o
var circleyoffset = ((bx-ax)/2)/Math.tan(alpha);
var circlex = (ax+bx)/2.0;
var circley = y + (upside ? 1 : -1) * circleyoffset;
var radius = Math.sqrt(Math.pow(circlex-ax,2) + Math.pow(circley-y,2));
ctx.beginPath();
if(upside) {
ctx.moveTo(bx,y);
ctx.arc(circlex,circley,radius,startangle,endangle,1);
} else {
ctx.moveTo(bx,y);
ctx.arc(circlex,circley,radius,startangle,endangle,0);
}
ctx.stroke();
}
// draw the head of an arrow (not the main line)
// ctx: canvas context
// x,y: coords of arrow point
// angle_from_north_clockwise: angle of the line of the arrow from horizontal
// upside: true=above the horizontal, false=below
// barb_angle: angle between barb and line of the arrow
// filled: fill the triangle? (true or false)
function drawArrowHead(ctx, x, y, angle_from_horizontal_degrees, upside, //mandatory
barb_length, barb_angle_degrees, filled) { //optional
(barb_length==undefined) && (barb_length=13);
(barb_angle_degrees==undefined) && (barb_angle_degrees = 20);
(filled==undefined) && (filled=true);
var alpha_degrees = (upside ? -1 : 1) * angle_from_horizontal_degrees;
//first point is end of one barb
var plus = degToRad(alpha_degrees - barb_angle_degrees);
a = x + (barb_length * Math.cos(plus));
b = y + (barb_length * Math.sin(plus));
//final point is end of the second barb
var minus = degToRad(alpha_degrees + barb_angle_degrees);
c = x + (barb_length * Math.cos(minus));
d = y + (barb_length * Math.sin(minus));
ctx.beginPath();
ctx.moveTo(a,b);
ctx.lineTo(x,y);
ctx.lineTo(c,d);
if(filled) {
ctx.fill();
} else {
ctx.stroke();
}
return true;
}
// draw a horizontal arcing arrow
// ctx: canvas context
// inax: start x value
// inbx: end x value
// y: y value
// alpha_degrees: angle of ends to horizontal (30=shallow, >90=silly)
function drawHorizArcArrow(ctx, inax, inbx, y, //mandatory
alpha_degrees, upside, barb_length) { //optional
(alpha_degrees==undefined) && (alpha_degrees=45);
(upside==undefined) && (upside=true);
drawHorizArc(ctx, inax, inbx, y, alpha_degrees, upside);
if(inax>inbx) {
drawArrowHead(ctx, inbx, y, alpha_degrees*0.9, upside, barb_length);
} else {
drawArrowHead(ctx, inbx, y, (180-alpha_degrees*0.9), upside, barb_length);
}
return true;
}
function drawArrow(ctx,fromelem,toelem, //mandatory
above, angle) { //optional
(above==undefined) && (above = true);
(angle==undefined) && (angle = 45); //degrees
midfrom = fromelem.offsetLeft + (fromelem.offsetWidth / 2) - left - tofromseparation/2;
midto = toelem.offsetLeft + ( toelem.offsetWidth / 2) - left + tofromseparation/2;
//var y = above ? (fromelem.offsetTop - top) : (fromelem.offsetTop + fromelem.offsetHeight - top);
var y = fromelem.offsetTop + (above ? 0 : fromelem.offsetHeight) - canvasTop;
drawHorizArcArrow(ctx, midfrom, midto, y, angle, above);
}
var canvasTop = 0;
function draw() {
var canvasdiv = document.getElementById("canvas");
var spanboxdiv = document.getElementById("spanbox");
var ctx = canvasdiv.getContext("2d");
nodeset = generateNodeSet();
linkset = generateLinks(nodeset);
tofromseparation = 20;
left = canvasdiv.offsetLeft - spanboxdiv.offsetLeft;
canvasTop = canvasdiv.offsetTop - spanboxdiv.offsetTop;
for(var key in linkset) {
for (var i=0; i<linkset[key].length; i++) {
fromid = key;
toid = linkset[key][i];
var above = (i%2==1);
drawArrow(ctx,document.getElementById(fromid),document.getElementById(toid),above);
}
}
}
</script>
And you just need a call somewhere to the draw() function:
<body onload="draw();">
Then a canvas behind the set of spans.
<canvas style='border:1px solid red' id="canvas" width="800" height="7em"></canvas><br />
<div id="spanbox" style='float:left; position:absolute; top:75px; left:50px'>
<span id="T2">p50</span>
...
<span id="T3">p65</span>
...
<span id="T34" ids="T2 T3">recruitment</span>
</div>
Future modifications, as far as I can see:
Flattening the top of longer arrows
Refactoring to be able to draw non-horizontal arrows: add a new canvas for each?
Use a better routine to get the total offsets of the canvas and span elements.
[Edit Dec 2011: Fixed, thanks #Palo]
Hope that's as useful as it was fun.
You have a couple options: svg or canvas.
From the looks of it you don't need these arrows to have any particular mathematical form, you just need them to go between elements.
Try WireIt. Have a look at this WireIt Demo (which has been deprecated). It uses a canvas tag for each individual wire between the floating dialog divs, then sizes and positions each canvas element to give the appearance of a connecting line at just the right spot. You may have to implement an additional rotating arrowhead, unless you don't mind the arrows coming in to each element at the same angle.
Edit: the demo has been deprecated.
Edit: Ignore this answer, #Phil H nailed it
A great library for arrows is JointJS that is based on Raphael as shown above. With JointJS you can easily draw arrows with curves or vertices without any complicated stuff ;-)
var j34 = s3.joint(s4, uml.arrow).setVertices(["170 130", "250 120"]);
This defines an arrow 'j34' that connects two js items s3 with s4. Everything else can be read in the documentation of JointJS.
If you don't need curved arrows, you could use absolutely positioned divs above or below the list. You could then use css to style those divs plus a couple of images that make up the arrow head. Below is an example using the icon set from the jQuery UI project (sorry about the long URL).
Here's the CSS to get things started:
<style>
.below{
border-bottom:1px solid #000;
border-left:1px solid #000;
border-right:1px solid #000;
}
.below span{
background-position:0px -16px;
top:-8px;
}
.above{
border-top:1px solid #000;
border-left:1px solid #000;
border-right:1px solid #000;
}
.above span{
background-position:-64px -16px;
bottom:-8px;
}
.arrow{
position:absolute;
display:block;
background-image:url(http://jquery-ui.googlecode.com/svn/trunk/themes/base/images/ui-icons_454545_256x240.png);
width:16px;
height:16px;
margin:0;
padding:0;
}
.left{left:-8px;}
.right{right:-9px;}
</style>
Now we can start to assemble arrow divs. For instance, to style the arrow from "requires" to "promoter" in your example above, you could do left,bottom, and right borders on the div with and upward facing arrow graphic in the top left of the div.
<div class='below' style="position:absolute;top:30px;left:30px;width:100px;height:16px">
<span class='arrow left'></span>
</div>
The inline styles would be need to be applied by script after you figured out the locations of the things you would need to connect. Let's say that your list looks like this:
<span id="promoter">Promoter</span><span>Something Else</span><span id="requires">Requires</span>
Then the following script will position your arrow:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
<script>
$(function(){
var promoterPos=$("#promoter").offset();
var requiresPos=$("#requires").offset();
$("<div class='below'><span class='arrow left'></span></div>")
.css({position:"absolute",left:promoterPos.left,right:promoterPos.top+$("#promoter").height()})
.width(requiresPos.left-promoterPos.left)
.height(16)
.appendTo("body");
});
</script>
Go ahead and paste the examples above into a blank html page. It's kind of neat.
You could try this JavaScript Vector Graphics Library - it's very clever stuff, hope it helps.
EDIT: As this link is dead, here is another link from Archive.org.
I try to go with open web technologies wherever possible but the truth is that HTML & JavaScript (or jQuery) aren't the tools for this particular job (sad but true), especially as the diagrams you're drawing increase in complexity.
On the other hand, Flash was made for this. Significantly less ActionScript 3.0 code would be required to parse that XML, layout your text (with more control over fonts & super/subscripts) and render the curves (see the flash.display.Graphics class methods like curveTo). Overall you'll be looking at less code, better maintainability, fewer hacks, wider compatibility and more stable drawing libraries.
Good luck with the project.
As others have mentioned, Javascript and html are not good tools for this sort of thing.
John Resig wrote an implementation of Processing.org in JavaScript. It uses the canvas element, so it will work in modern versions of Firefox, but it will not work in all browsers. If you only care about Firefox, this would probably be the way to go.
You might be able to use SVG, but again, this is not supported in all browsers.
I needed a similar solution, and I was looking into RaphaelJS JavaScript Library. For example you can draw a straight arrow from (x1,y1) to (x2,y2) with:
Raphael.fn.arrow = function (x1, y1, x2, y2, size) {
var angle = Math.atan2(x1-x2,y2-y1);
angle = (angle / (2 * Math.PI)) * 360;
var arrowPath = this.path(“M” + x2 + ” ” + y2 + ” L” + (x2 - size) + ” ” + (y2 - size) + ” L” + (x2 - size) + ” ” + (y2 + size) + ” L” + x2 + ” ” + y2 ).attr(“fill”,”black”).rotate((90+angle),x2,y2);
var linePath = this.path(“M” + x1 + ” ” + y1 + ” L” + x2 + ” ” + y2);
return [linePath,arrowPath];
}
I haven't figure out how to draw a curved arrow, but I'm sure it's possible.
You could get the curved arrow ends using a handful of position:absolute divs with background-image set to transparent GIFs... a set for beginning (top and bottom)... a bacground:repeat div for expandible middle, and another pair for the ends (top and bottom).
You can use this library: just annotate your SVG lines with the ids of the source & target element. It uses MutationObserver to observe changes in the connected elements.