fabric.js rotate a combined path - javascript

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! :)

Related

How to Create a path in svg for d3 brush

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

Making a dataplot in HTML 5's Canvas

I'm working in canvas to take information and plot it onto a graph. Eventually it will go to php and retrieve the data from a server, but for now I just need it to plot any data correctly.
I have the canvas drawn out how I'd like it and began plotting the data, but when I do it doesn't give me a thin path, it's more like a giant blob that covers everything. When looking at my code, it's important to know that it is mostly just initialization of the canvas, but I need to post mostly all of it in order to give context for what is happening in the program.
var canvas ;
var context ;
var Val_max;
var Val_min;
var sections;
var xScale;
var yScale;
var Apple = [10.25,10.30,10.10,10.20];
function init() {
Val_max = 10.5;
Val_min = 10;
var stepSize = .049999999999999; //.5 results in inaccurate results
var columnSize = 50;
var rowSize = 20;
var margin = 20;
var xAxis = [" "," "," ", " ", " ", "10AM", " ", " ", " ", " ", " ", "11AM", " ", " ", " ", " ", " ", "12PM", " ", " ", " ", " ", " ", "1PM", " ", " ", " ", " ", " ", "2PM", " ", " ", " ", " ", " ", "3PM", " ", " ", " ", " ", " ", "4PM"];
sections = xAxis.length-1;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
context.fillStyle = "#808080";
yScale = (canvas.height - columnSize - margin) / (Val_max - Val_min); // Total height of the graph/range of graph
xScale = (canvas.width - rowSize - margin) / sections; // Total width of the graph/number of ticks on the graph
context.strokeStyle="#808080"; // color of grid lines
context.beginPath();
// print Parameters on X axis, and grid lines on the graph
context.moveTo(xScale+margin, columnSize - margin);
context.lineTo(xScale+margin, columnSize + (yScale * 10 * stepSize) - margin); //draw y axis
for (i=1;i<=sections;i++) {
var x = i * xScale;
context.moveTo(x + margin, columnSize + (yScale * 10 * stepSize) - margin);
context.lineTo(x + margin, columnSize + (yScale * 10 * stepSize) - margin - 5); //draw ticks along x-axis
context.fillText(xAxis[i], x,canvas.height - margin); //Time along x axis
}
// print row header and draw horizontal grid lines
var count = 0;
context.moveTo(xScale+margin, 260);
context.lineTo(canvas.width - margin, 260); // draw x axis
for (scale=Val_max;scale>=Val_min;scale = scale - stepSize) {
scale = scale.toFixed(2);
var y = columnSize + (yScale * count * stepSize) - margin;
context.fillText(scale, margin - 20,y);
context.moveTo(xScale+margin, y);
context.lineTo(xScale+margin+5, y); //Draw ticks along y-axis
count++;
}
context.stroke();
context.translate(rowSize,canvas.height + Val_min * yScale);
context.scale(1,-1 * yScale);
// Color of each dataplot items
context.strokeStyle="#FF0066";
//plotData(Apple);
context.strokeStyle="#9933FF";
//plotData(Samsung);
context.strokeStyle="#000";
//plotData(Nokia);
}
Ok that's the initialization of the canvas, I know it's messy but I think I'll have to reference something from it for the next function.
function plotData(dataSet) {
var margin = 20;
context.beginPath();
context.moveTo(xScale+margin, dataSet[0]);
for (i=0;i<sections;i++) {
context.lineTo(i * xScale + margin, dataSet[i]);
}
context.stroke();
}
This function is supposed to take the data from the array and plot it on the graph. I can get it to draw, but it's not a thin line. Here's a picture of the blob that I'm getting.
It also doesn't seem to be accurately plotting the coordinates from my array either.
I know this question is pretty in depth, but any help would be very appreciated!
The translate and scale are applied to the current transform. Each time you call them you translate and scale a little more.
Use save and restore to get back the original transform.
context.save(); // <--------------------- added
context.translate(rowSize,canvas.height + Val_min * yScale);
context.scale(1,-1 * yScale);
// Color of each dataplot items
context.strokeStyle="#FF0066";
//plotData(Apple);
context.strokeStyle="#9933FF";
//plotData(Samsung);
context.strokeStyle="#000";
//plotData(Nokia);
context.restore(); // <-------------------- added

SVG path doesn't seem to orientate itself correctly

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.

Classifying a Raphael paper into 20x20 divisions

I have a paper with 400 x 500 in size. I am trying to divide this into 20 x 20 pixels with below code
var dpH = 500, dpW = 400, drawPad = new Raphael(document.getElementById('p'),
dpW, dpH);
for ( var i = 0; i <= dpH / 20; i++) {
drawPad.path("M" + 1 + " " + ((i * 20) + 1) + "L" + dpW + " "
+ ((i * 20) + 1));
}
for ( var j = 0; j <= (dpW / 20); j++) {
drawPad.path("M" + ((j * 20) + 1) + " " + 1 + "L" + ((j * 20) + 1) + " "
+ dpH);
}
And HTML markup is like below
<div id="p" style="background-image:url(image.png)"> </div>
with same height and width of Background Image.
My original requirement was making the image.png as Rapheal paper. But I was failed to do that. So I made it as background-image to the DIV#P. Then converted the DIv to Paper.
Here are my questions related to above
Does all the pixels of Background-Image and DIV match with each other?
The way I did above is to classify the total paper into 20x20 pixel divisions. Is that correct way of doing?
What is the width of the drawn line?
Please help me on this.
Ok, so if I understand you correctly; What you really want is to get the raw image data for 20x20 squares of the image.
Here's how you can extract image data with Canvas (also on jsFiddle):
var dpH = 500,
dpW = 400,
ctx = document.getElementById('p').getContext('2d'),
exportData = function() {
var data;
for (var y=0, yl=dpH/20; y<yl; y++) {
for (var x=0, xl=dpW/20; x<xl; x++) {
imgData = ctx.getImageData(x*20, y*20, 20, 20).data;
console.log("Image data for " + x*20 + ", " + y*20, imgData);
// data is an array with 4 values pr pixel
// Top left pixel in the 20x20 square
r = imgData[0]; // red
g = imgData[1]; // green
b = imgData[2]; // blue
a = imgData[3]; // alpha
console.log("RGBa of " + x*20 + ", " + y*20 + ": ", r, g, b, a);
}
}
},
drawImage = function() {
ctx.drawImage(this, 0, 0);
exportData(this);
};
var img = new Image();
img.onload = drawImage;
img.src = "image.png"; // has to be on the same domain
** Original answer **
The result is a DIV with an SVG-element inside, and a background image behind it. The browser (if it supports SVG) will render them on top of each other. Do you want to extract pixel values? If so, you have to do this through HTML5 Canvas instead of SVG.
Sorry, I don't understand. What are you trying to accomplish? Do you want the pixel data for 20x20 squares? With Raphael you are just drawing lines on top of the picture.
The defaut with of a path is 1 pixels. You can change this by setting an attribute on the path. Example (also on jsfiddle.net):
var dpH = 500,
dpW = 400,
drawPad = Raphael(document.getElementById('p'), dpW, dpH),
style = {
"stroke" : "#fff", // white
"stroke-width" : 2 // default 1
};
for ( var i = 0; i <= dpH / 20; i++) {
drawPad.path("M" + 1 + " " + ((i * 20) + 1) + "L" + dpW + " "
+ ((i * 20) + 1)).attr(style);
}
for ( var j = 0; j <= (dpW / 20); j++) {
drawPad.path("M" + ((j * 20) + 1) + " " + 1 + "L" + ((j * 20) + 1) + " "
+ dpH).attr(style);
}​

Sankey diagram in javascript

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' }}
/>

Categories