I want to draw a Sankey diagram using Javascript. Can anyone provide some direction regarding the algorithms or libraries that are available for this?
In case helpful to others: I've extracted my javascript sankey diagram drawing code here:
http://tamc.github.com/Sankey/
The original usage is on this UK government site:
http://2050-calculator-tool.decc.gov.uk/pathways/2022222122222103332220023211022330220130233022012/sankey
This is a basic Sankey diagram using raphaeljs
function Sankey(x0, y0, height, losses) {
var initialcolor = Raphael.getColor();
var start = x0 + 200;
var level = y0 + height;
var heightunit = height / 100;
var remaining = 100 * heightunit;
function drawloss(start, level, loss) {
var thecolor = Raphael.getColor();
paper.path("M" + (start - 100) + "," + (level - loss) + "L" + start + "," + (level - loss)).attr({stroke: thecolor});
paper.path("M" + (start - 100) + "," + level + "L" + start + "," + level).attr({stroke: thecolor});
paper.path("M " + start + "," + level + " Q" + (start + 100) + "," + level + " " + (start + 100) + "," + (level + 100)).attr({stroke: thecolor});
paper.path("M " + start + "," + (level - loss) + " Q" + (start + 100 + loss) + "," + (level - loss) + " " + (start + 100 + loss) + "," + (level + 100)).attr({stroke: thecolor});
paper.path("M " + (start + 100) + "," + (level + 100) + " L " + (start - 10 + 100) + "," + (level + 100) + " L " + (start + loss / 2 + 100) + "," + (level + 110) + " L " + (start + loss + 10 + 100) + "," + (level + 100) + " L " + (start + loss + 100) + ", " + (level + 100)).attr({stroke: thecolor});
}
function drawremaining(start, level, loss) {
paper.path("M 100," + y0 + "L" + (start + 100) + "," + y0).attr({stroke: initialcolor});
paper.path("M" + (start - 100) + "," + level + "L" + (start + 100) + "," + level).attr({stroke: initialcolor});
paper.path("M " + (start + 100) + " " + y0 + " L " + (start + 100) + " " + (y0 - 10) + " L " + (start + 110) + " " + (y0 + loss / 2) + " L " + (start + 100) + " " + (level + 10) + " L " + (start + 100) + " " + level).attr({stroke: initialcolor});
}
function drawstart(x0, y0, width, height) {
paper.path("M " + x0 + "," + y0 + "L" + (x0 + width) + "," + y0).attr({stroke: initialcolor});
paper.path("M " + x0 + "," + (y0 + height) + "L" + (x0 + width) + "," + y0 + height)).attr({stroke: initialcolor});
paper.path("M " + x0 + "," + y0 + "L" + x0 + "," + (y0 + height)).attr({stroke: initialcolor});
}
drawstart(x0, y0, 100, height);
for (var i in losses) {
drawloss(start, level, losses[i] * heightunit);
remaining -= losses[i] * heightunit;
level -= losses[i] * heightunit;
start += 100;
}
}
And I use it like this:
<div id="notepad" style="height:1000px; width:1000px; background: #eee"></div>
<script type="text/javascript">
var paper = Raphael(document.getElementById("notepad"), 1020, 1000);
var losses=[50, 30, 5];
Sankey(10, 100, 200, losses);
</script>
D3.js uses a plugin to create sankey diagrams pretty well.
http://bost.ocks.org/mike/sankey/
Here is a fairly detailed explanation of how Mike Bostock's D3-based Sankey DIagram code works: http://www.d3noob.org/2013/02/sankey-diagrams-description-of-d3js-code.html
I have implemented this on a Grails-based app server and it works.
Google Charts includes the Sankey Diagram: https://developers.google.com/chart/interactive/docs/gallery/sankey
Thanks to zenify for starting me on the path, I had to rejig some of the copied code above to get it to work but it definitely gives a good starting point. The code below can be copied into a .htm file and you just need to have raphael-min.js in the same directory for it to work.
Regards / Colm
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" class="JS">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Raphael makes Sankey</title>
<script type="text/javascript" src="raphael-min.js"></script>
<script type="text/javascript">
function Sankey(x0,y0,height,losses){
initialcolor= Raphael.getColor();
var start=x0+200;
var level=y0+height;
var heightunit=height/100;
var remaining=100*heightunit;
function drawloss(start,level,loss){
var thecolor=Raphael.getColor();
paper.path("M"+(start-100)+","+(level-loss)+"L"+start+","+(level-loss)).attr({stroke: thecolor});
paper.path("M"+(start-100)+","+(level)+"L"+start+","+(level)).attr({stroke: thecolor});
paper.path("M "+start+","+level+" Q"+(start+100)+","+level+" "+(start+100)+","+(level+100)).attr({stroke: thecolor});
paper.path("M "+start+","+(level-loss)+" Q"+(start+100+loss)+","+(level-loss)+" "+(start+100+loss)+","+(level+100)).attr({stroke: thecolor});
paper.path("M "+(start+100)+","+(level+100)+" L "+(start-10+100)+","+(level+100)+" L "+(start+(loss/2)+100)+","+(level+110)+" L "+(start+(loss)+10+100)+","+(level+100)+" L "+(start+(loss)+100)+", "+(level+100)).attr({stroke: thecolor});
}
function drawremaining(start,level,loss){
paper.path("M 100,"+y0+"L"+(start+100)+","+y0).attr({stroke: initialcolor});
paper.path("M"+(start-100)+","+(level)+"L"+(start+100)+","+(level)).attr({stroke: initialcolor});
paper.path("M "+(start+100)+" "+y0+" L "+(start+100)+" "+(y0-10)+" L "+(start+110)+" "+(y0+(loss/2))+" L "+(start+100)+" "+(level+10)+" L "+(start+100)+" "+(level)).attr({stroke: initialcolor});
}
function drawstart(x0, y0, width, height){
paper.path("M "+x0+","+y0+"L"+(x0+width)+","+y0+"").attr({stroke: initialcolor});
paper.path("M "+x0+","+(y0+height)+"L"+(x0+width)+","+y0+height+"").attr({stroke: initialcolor});
paper.path("M "+x0+","+y0+"L"+x0+","+(y0+height)+"").attr({stroke: initialcolor});
}
drawstart(x0,y0,100,height);
for (var i in losses){
drawloss(start,level,losses[i]*heightunit);
remaining-=losses[i]*heightunit;
level-=losses[i]*heightunit;
start+=100;
}
drawremaining(start, level, remaining);
}
</script>
</head>
<body id="blog">
<div id="notepad" style="height:1000px; width:1000px; background: #eee"></div>
<script type="text/javascript">
var paper = Raphael(document.getElementById("notepad"), 1020, 1000);
var losses=[50, 30, 5];
Sankey(10, 100, 200, losses);
</script>
</body>
</html>
Update 2020:
For anyone struggling to bring D3 Sankey examples to life, I found this supereasy video tutorial. Worked like a charm for me :)
https://reactviz.holiday/sankey/
Also, in case you can't make this one work either, react-google-charts have a pretty nice looking alternative which couldn't be easier to work with (at least implementing the example was just copy-pasting the whole component from here https://react-google-charts.com/sankey-diagram):
import Chart from "react-google-charts";
<Chart
width={600}
height={'300px'}
chartType="Sankey"
loader={<div>Loading Chart</div>}
data={[
['From', 'To', 'Weight'],
['A', 'X', 5],
['A', 'Y', 7],
['A', 'Z', 6],
['B', 'X', 2],
['B', 'Y', 9],
['B', 'Z', 4],
]}
rootProps={{ 'data-testid': '1' }}
/>
Related
here what i having a svg like below
currently svg code is like this
<path class="c" d="M-8046.012,2842.011h-1.6" transform="translate(8047.61 -2837.554)"/>
</g></g></g></svg>
and currently this is using groups and path combined and this i want to attach as a d3 brush handle but here the problem is currently im creating a brush handle like below
const focusHandle = focusBrush.selectAll(".handle--custom")
.data([{type: "w"}, {type: "e"}])
.enter().append("path")
.attr("class", "handle--custom")
.attr("stroke", "#000")
.attr("cursor", "ew-resize")
.attr("d", brushResizePath)
const brushResizePath = (d) => {
var e = +(d.type == "e"),
x = e ? 1 : -1,
y = this.height / 2;
return "M" + (.5 * x) + "," + y + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6) + "V" + (2 * y - 6) + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y) + "Z" + "M" + (2.5 * x) + "," + (y + 8) + "V" + (2 * y - 8) + "M" + (4.5 * x) + "," + (y + 8) + "V" + (2 * y - 8);
}
ex: "M0.5,54A6,6 0 0 1 6.5,60V102A6,6 0 0 1 0.5,108ZM2.5,62V100M4.5,62V100" like path
so how can i implement the above brush handle to this
Currently my brush is like this
I found a few issues with your code here.
Arrow functions don't assign this, so this in the arrow function is actually the window, I'm not sure if that's intended or not. If you want this to be the path, you need to use a function block instead of an arrow function.
Neither the window nor the paths have a height attribute, I think you want innerHeight instead. If you look at the d attribute that gets set on the path in you'll see there's some NaNs where you're trying to use y.
brushResizePath is being used before it's defined, move the definition above the const focusHandle bit.
Here's my CodePen with it working: https://codepen.io/Thource/pen/RwWqmZj
I'm attempting to generate a path around 2 circles that should follow them as I move them around. I've based this on an example that I found and built a prototype of what I'm expecting to achieve example
I've started including this in my application, but for some reason I can't seem to get the green path to draw around the correct positions, and I can't figure out why.
I've put together a code example to illustrate:
function generatePath(planet, moon, join) {
function distanceBetween(x1, y1, x2, y2) {
var a = (x2 - x1) * (x2 - x1);
var b = (y2 - y1) * (y2 - y1);
return Math.sqrt(a + b);
};
function circleYFromX(circle, x) {
return Math.sqrt(Math.pow(circle.r, 2) - Math.pow(x - circle.x, 2));
};
function calculateAngle(origin, point) {
var tan = (point.y - origin.y) / (point.x - origin.x);
var angle = Math.atan(tan) / Math.PI * 180 + 90;
if (point.x < origin.x) angle += 180;
return angle;
};
// Work out the distance between the moon and planet
var distance = distanceBetween(planet.x, planet.y, moon.x, moon.y);
var originDistance = planet.r - moon.r;
var distanceDiff = distance - originDistance;
if (distanceDiff < 1) {
distanceDiff = 1;
}
console.log(distance);
console.log(planet.r);
console.log(moon.r);
console.log(join.r);
console.log(planet.r + moon.r + 2 * join.r);
// Determine if the moon has moved out of the planet's gravitational pull
if (distance > 2 * join.r + planet.r + moon.r) {
return;
}
moon.h = 0;
moon.k = 0 - planet.r + moon.r - distanceDiff;
var triangleA = planet.r + join.r; // Side planet
var triangleB = moon.r + join.r; // Side moon
var triangleC = Math.abs(moon.k - 0); // Side c
var triangleP = (triangleA + triangleB + triangleC) / 2; // Triangle half perimeter
var triangleArea = Math.sqrt(triangleP * (triangleP - triangleA) * (triangleP - triangleB) * (triangleP - triangleC)); // Triangle area
var triangleH;
var triangleD;
if (triangleC >= triangleA) {
var triangleH = 2 * triangleArea / triangleC; // Triangle height
var triangleD = Math.sqrt(Math.pow(triangleA, 2) - Math.pow(triangleH, 2)); // Big circle bisection of triangleC
} else {
var triangleH = 2 * triangleArea / triangleA; // Triangle height
var triangleD = Math.sqrt(Math.pow(triangleC, 2) - Math.pow(triangleH, 2)); // Small circle bisection of triangleA
}
planet.tan = triangleH / triangleD;
planet.angle = Math.atan(planet.tan);
planet.sin = Math.sin(planet.angle);
planet.intersectX = planet.sin * planet.r;
planet.cos = Math.cos(planet.angle);
planet.intersectY = planet.cos * planet.r;
join.x = 0 + planet.sin * (planet.r + join.r);
join.y = 0 - planet.cos * (planet.r + join.r);
var coord1 = {
x: -planet.intersectX,
y: -planet.intersectY
};
var coord2 = {
x: planet.intersectX,
y: -planet.intersectY
}
moon.tan = (moon.k - join.y) / (moon.h - join.x);
moon.angle = Math.atan(moon.tan);
moon.intersectX = join.x - Math.cos(moon.angle) * (join.r);
moon.intersectY = join.y - Math.sin(moon.angle) * (join.r);
// If we have any bad values then just return no path
if (isNaN(coord1.x) || isNaN(coord1.y) || isNaN(coord2.x) || isNaN(coord2.y)) {
return;
}
var pathD = "M " + coord1.x + " " + coord1.y + " A " + planet.r + " " + planet.r + " 0 1 0 " + coord2.x + " " + coord2.y;
if (join.x - join.r <= 0 && moon.k < join.y) {
var crossOverY = circleYFromX(join, 0);
pathD += "A " + join.r + " " + join.r + " 0 0 1 0 " + (join.y + crossOverY);
pathD += "m 0 -" + (crossOverY * 2);
}
pathD += "A " + join.r + " " + join.r + " 0 0 1 " + moon.intersectX + " " + moon.intersectY;
var largeArcFlag = 1;
if (join.y < moon.k) {
largeArcFlag = 0;
}
pathD += "a " + moon.r + " " + moon.r + " 0 " + largeArcFlag + " 0 " + (moon.intersectX * -2) + " 0";
if (join.x - join.r <= 0 && moon.k < join.y) {
pathD += "A " + join.r + " " + join.r + " 0 0 1 0 " + (join.y - crossOverY);
pathD += "m 0 " + (crossOverY * 2);
}
pathD += "A " + join.r + " " + join.r + " 0 0 1 " + coord1.x + " " + coord1.y;
pathD += "A " + join.r + " " + join.r + " 0 0 1 " + coord1.x + " " + coord1.y;
return pathD;
};
var container = d3.select(".planet");
var moon = d3.select(".moon");
var tempPlanet = { x: -181.77581967381693, y: -144.9613789321555, r: 152 };
var tempMoon = { x: 0, y: 0, r: 32 };
var link = { r: 7.9 };
var pathD = generatePath(tempPlanet, tempMoon, { r: 31 });
if (pathD) {
moon.append("path")
.attr("d", pathD)
.attr("transform", "translate(" + [-181.77581967381693, 144.9613789321555] + ")")
.attr("class", "gravity")
.style("fill", "none")
.style("stroke", "red")
.style("stroke-linecap", "round")
.style("stroke-width", 2);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="1680" height="523">
<g width="1680" height="523">
<g class="galaxy-main" width="1680" height="523">
<g class="planet selected" transform="translate(341,300) scale(0.5,0.5)">
<circle r="150" style="fill: rgb(72, 119, 159); stroke-dasharray: 944.477796076938px; stroke-dashoffset: 0px; stroke-width: 8px; stroke: rgb(255, 255, 255);"></circle>
<g class="moon" transform="translate(181.77581967381693,-144.96137893215555)">
<circle r="30" class="moon-circle" id="3" style="fill: rgb(72, 119, 159);"></circle>
</g>
</g>
</g>
</g>
</svg>
Instead of drawing a green fill I'm currently drawing a red outline. What you should be able to see is that the red outline correctly surrounds the larger circle (planet) but goes vertically up instead of around the smaller circle (moon).
It appears as thought we're just missing a rotation, but the original prototype I built doesn't know about a rotation, just the center of each circle. In this case this should be really simple:
Moon
var tempMoon = { x: 0, y: 0, r: 32 };
Always located at (0, 0) as this circle sits in the center of the group which the path will be appended to
Planet
var tempPlanet = { x: -181.77581967381693, y: -144.9613789321555, r: 152 };
The planet is at the center of the group, which also contains the moon group. Therefore it's location is always just an inverse translation which positions the group containing the moon
I believe the locations are correct (I've tried adding circles on the moon layer to confirm they are in the correct place - which they are). I feel that this must be somehow down to the groups but I still can't pinpoint why this isn't rendering with the correct orientation.
Unfortunately it seems that I was missing a translate and rotate in the code toward the end that I'd missed previously for some reason. Including this in made it work as expected.
when i create dynamically svg text circle is working fine in number.but i am using small letter (example:- ffffffiiiisasdasdas) text circle create half but i need full circle and Shape should be react according to content which we are placing ..
please check image following
1.http://tinypic.com/view.php?pic=15fll6b&s=8
2.http://tinypic.com/view.php?pic=fe1181&s=8
following code use
<svg width="845" height="350" viewBox="0 0 845 350" clip-rule="nonzero" >
<g
data-ng-attr-fill="{{addText.color}}"
data-ng-attr-width="{{addText.w}}"
data-ng-attr-height="{{addText.h}}"
data-ng-attr-transform="{{rotate(addText)}}"
strok <defs>
<path stroke-width = "3"
fill="userSpaceOnUse"
data-ng-attr-id="temp-{{addText.id}}"
data-ng-attr-d="{{makeBox1(addText, true)}}" />
</defs>
<text ng-if="addText.text" glyph-orientation-vertical="90" lengthAdjust="spacingAndGlyphs" "
data-ng-attr-text-anchor="{{addText.text.anchor}}"
data-ng-attr-font-family="{{addText.text.font}}"
data-ng-attr-font-style="{{addText.text.italic ? 'italic' : 'none'}}"
data-ng-attr-font-weight="{{addText.text.bold ? 'bold' : 'normal'}}"
data-ng-attr-font-size="{{addText.text.size}}"
data-ng-attr-x="{{arcMid(addText)}}"
letter-spacing="2";
style="text-align:justify"
kerning="8">
<textPath data-ng-xlink-href="#temp-{{addText.id}}" method = "stretch"
writing-mode="lr-tb" clip-rule="nonzero" xlink:href="">
{{addText.text.text}}</textPath>
</text>
<path fill="none" stroke="#EEE" data-ng-attr-d="{{makeBox1(addText,true)}}" />
</g>
</svg>
javascript
$scope.makeBox1 = function makeBox1(item, temp) {
if (item.c == 1) {
var ma = $('#txtsearch').val();
var legth = ma.length;
console.log(legth + "m");
if (item.r == 0) {
item.r = item.h / 2;
}
var x1 = item.x + item.w / 2,
y1 = item.y + (item.h),
x2 = x1 + 1,
r = item.r;
if (temp) {
x1 = 270 + item.w / 2;
y1 = 30 + (item.h);
x2 = x1 + 1;
}
item.r = ((((((legth) * item.text.size * 5) / (legth / 0.9))) * legth * .02) + (legth * 0.09));
if (item.r > 137) {
item.r = 137;
}
x1 = 424.5;
y1 = 293;
r = item.r;
x2 = 425.5;
var y2 = 293
return "M " + x1 + " " + y1 + " " +
"A " + r + " " + r + " 0 1 1 " + x2 + " " + y2;
}
}
It's working for numbers because they always have a fixed width - or almost always. That is because you typically want numbers to align in columns - in invoices for instance.
Your complicated equation for calculating the circle radius is based on the font size. The formula has obviously been tweaked so it works well with the width of numerals in the font you are using. But it won't work with general non-numeric text. Or probably with numerals in a different font.
It is going to be hard to get this to work perfectly in every case, because different browsers may implement their <textPath> elements differently.
The best you can do is to measure the text length, and then calculate the radius from that, by dividing by (2*PI).
You can get the length of the text in a <text> element by calling getComputedTextLength() on the element.
var msg="ffffffiiiisasdasdas";
// Get SVG
var mysvg = document.getElementById("mysvg");
// Get text length
var tmp = document.createElementNS("http://www.w3.org/2000/svg", "text");
tmp.textContent = msg;
mysvg.appendChild(tmp);
var len = tmp.getComputedTextLength();
mysvg.removeChild(tmp);
//alert("len = "+len);
// Make the circle path for the msg to sit on
var x1 = 424.5,
y1 = 293,
x2 = 425.5,
y2 = 293;
var r = len / (2 * Math.PI);
var circ = document.createElementNS("http://www.w3.org/2000/svg", "path");
circ.setAttribute("id", "circ");
circ.setAttribute("d", "M " + x1 + " " + y1 + " " +
"A " + r + " " + r + " 0 1 1 " + x2 + " " + y2);
mysvg.appendChild(circ);
// Make the textPath element
var tp1 = document.createElementNS("http://www.w3.org/2000/svg", "text");
mysvg.appendChild(tp1);
var tp2= document.createElementNS("http://www.w3.org/2000/svg", "textPath");
tp2.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#circ");
tp2.textContent = msg;
tp1.appendChild(tp2);
svg text {
font-family: sans-serif;
font-size: 20px;
}
#circ {
fill: none;
stroke: black;
}
<svg id="mysvg" width="845" height="350" viewBox="0 0 845 350">
</svg>
I already searched for days and tried really a lot of things to get this right.
I want to use piecharts as progress pie. I created two fabric paths, which draws the pie chart and it works as it should.
Now I want to rotate the paths at the center point, but it doesn't work. It's actually a simple rotation. The main problem is, that the rotation point depends on the ratio of the chart. I have multiple charts and if I change one, all other charts changes as well.
I combined the two paths into a group, so every piechart is a group containing two paths.
Here are two of my piecharts. Selectable true to see what is selected.
http://i.imgur.com/Q4NLsNf.png
http://i.imgur.com/N8AldM0.png
I want the selectable Rectangle to be evenly spaced out over the whole circle, so that the rotation point is exactly at the center. I don't understand why the selectable area is always the smaller part of the pie chart.
Can anybody help me out?
That's how I calculate the pie chart
for(var i = 0; i < sectorAngleArr.length; i++)
{
startAngle = endAngle;
endAngle = startAngle + sectorAngleArr[i];
x1 = parseInt(left - (mainProgRad) * Math.sin(Math.PI*startAngle / 180));
y1 = parseInt(top - (mainProgRad) * Math.cos(Math.PI*startAngle / 180));
x2 = parseInt(left - (mainProgRad) * Math.sin(Math.PI * endAngle / 180));
y2 = parseInt(top - (mainProgRad) * Math.cos(Math.PI * endAngle / 180));
And thats how I draw it
if(i == 0 && sectorAngleArr[0] <= 180)
{
pathString = "M " + (left) + "," + (top) + " L " + (x1) + "," + (y1) + " A " + mainProgRad + "," + mainProgRad + " 0 0,0 " + (x2) + "," + (y2) + " z";
var path0 = new fabric.Path(pathString);
path0.set(
{
fill:" rgba(80, 80, 220, 0.4)",
stroke:"#0000cc",
strokeWidth:"1",
});
}
else if(i == 0 && sectorAngleArr[0] > 180)
{
pathString = "M " + (left) + "," + (top) + " L " + (x1) + "," + (y1) + " A " + mainProgRad + "," + mainProgRad + " 0 1,0 " + (x2) + "," + (y2) + " z";
var path0 = new fabric.Path(pathString);
path0.set(
{
fill:" rgba(80, 80, 220, 0.4)",
stroke:"#0000cc",
strokeWidth:"1",
});
}
else if(i == 1 && sectorAngleArr[1] <= 180)
{
pathString = "M " + (left) + "," + (top) + " L " + (x1) + "," + (y1) + " A " + mainProgRad + "," + mainProgRad + " 0 0,0 " + (x2) + "," + (y2) + " z";
var path1 = new fabric.Path(pathString);
path1.set(
{
fill:" rgba(220, 80, 80, 0.4)",
stroke:"#cc00cc",
strokeWidth:"1",
});
}
else
{
pathString = "M " + (left) + "," + (top) + " L " + (x1) + "," + (y1) + " A " + mainProgRad + "," + mainProgRad + " 0 1,0 " + (x2) + "," + (y2) + " z";
var path1 = new fabric.Path(pathString);
path1.set(
{
fill:" rgba(220, 80, 80, 0.4)",
stroke:"#cc00cc",
strokeWidth:"1",
});
}
}
var progressGroup = new fabric.Group([path0, path1],
{
left: left,
top: top,
originX: "center",
originY: "center",
scaleX: -1,
selectable:true
});
all.add(progressGroup);
I hope you can help me out!
EDIT: One good step forward was to use fabric.Pathgroup instead of fabric.Group...it reacts more as expected. But its still not working :)
OK, I find a workaround. The problem was, that the arc was ignored and the selecable area was around the 2 lines which were drawn. So I created more pieces of the pie to get the selectable area surrounding the wohle pie chart and the center, was now the center
You can see it here
http://i.imgur.com/nT7Es3O.png
EDIT: After getting some problems with an odd amount of pieces I made it just easy and drew a rectangle behind the pie chart and made it invisible. It is necessary that the rectangle has an absolute position. Now everthing works fine! :)
It's late and the part of my brain where Douglas Crockford lives is closed. Ive tried a few things but nothing's doing as expected.
I've got a canvas where I draw a 2 lines, then fade them out on a timer but only the last line in the loop is being faded out. Here's my fiddle, look down to line 50ish in the JS, to see it in action drag your mouse around in the bottom right pane:
http://jsfiddle.net/mRsvc/4/
this is the function, basically the timeout only gets the last value in the loop, I've seen this before and I'm sure if I wasn't so delirious it might be simpler. Here's the function in particular:
function update()
{
var i;
this.context.lineWidth = BRUSH_SIZE;
this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " + BRUSH_PRESSURE + ")";
for (i = 0; i < scope.painters.length; i++)
{
scope.context.beginPath();
var dx = scope.painters[i].dx;
var dy = scope.painters[i].dy;
scope.context.moveTo(dx, dy);
var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dx -= dx1;
var dx2 = scope.painters[i].dx;
var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dy -= dy1;
var dy2 = scope.painters[i].dy;
scope.context.lineTo(dx2, dy2);
scope.context.stroke();
for(j=FADESTEPS;j>0;j--)
{
setTimeout(function()
{
var x=dx,y=dy,x2=dx2,y2=dy2;
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j);
}
}
}
The problem is that the variables dx, dy, etc that you refer to in the function you pass to setTimeout() are defined in the surrounding scope and by the time any of the timeouts actually runs these variables all hold the values from the last iteration of the loop(s).
You need to create an extra containing function to close over the values from each iteration. Try something like the following:
for(j=FADESTEPS;j>0;j--) {
(function(x,y,x2,y2) {
setTimeout(function() {
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j);
})(dx, dy, dx2, dy2);
}
This creates a new anonymous function for each iteration of the j=FADESTEPS loop, executing it immediately and passing the dx, etc. values as they were at the time each iteration of the loop ran, and moving the x, y, etc. variables out of your existing function and making them parameters of the new one so then by the time the timeout runs it will use the correct values.
You can try something like this:
`<script>
for(j=10;j>0;j--)
{
var fn = function(ind){return function()
{
console.log(ind);
};
}(j);
setTimeout(fn,
1000);
}
</script>`
Or another way (as soon as you do not use IE, but let it learn canvas at first :))
for(j=FADESTEPS;j>0;j--)
{
setTimeout(function(x,y,x2,y2)
{
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j,dx,dy,dx2,dy2);
}
ps: there is no need in set of extra functions (the reasons are clear)
First of all j is a global.
Second of all, you never close the paths that you begin, which can cause memory leaks. It seems really slow and this may be why. You need to call closePath() whenever you're done with the paths you start with beginPath()
Next, I think there's some general funniness with how this works. You're fading out by drawing over the last thing with white. I've done something similar to this before, but instead I cleared the whole screen and kept drawing things over and over again. It worked okay for me.
Explanation
The other answers about dx and dy being passed from the higher scope are the right answers though. Async functions defined in synchronous for loops will take the last version of the state.
for (var i = 0; i < 10; i++) setTimeout(function() { console.log(i)}, 10 )
10
10
// ...
I would suggest you to use an array and store the points avoiding setTimeOut call in a loop. Somewhat like this.
this.interval = setInterval(update, REFRESH_RATE);
var _points = [];
function update() {
var i;
this.context.lineWidth = BRUSH_SIZE;
this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " + BRUSH_PRESSURE + ")";
for (i = 0; i < scope.painters.length; i++) {
scope.context.beginPath();
var dx = scope.painters[i].dx;
var dy = scope.painters[i].dy;
scope.context.moveTo(dx, dy);
var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dx -= dx1;
var dx2 = scope.painters[i].dx;
var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dy -= dy1;
var dy2 = scope.painters[i].dy;
scope.context.lineTo(dx2, dy2);
scope.context.stroke();
_points.push([dx, dy, dx2, dy2]);
clear();
}
}
function clear(){
if(_points.length < FADESTEPS){
return;
}
var p = _points.shift();
if(!p){
return;
}
var x = p[0],
y = p[1],
x2 = p[2],
y2 = p[3];
scope.context.beginPath();
scope.context.lineWidth = BRUSH_SIZE + 1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth = BRUSH_SIZE;
}
I know this is not exactly what you need, but I think this can be modified to get it.