I am trying to recreate this chart design. With a dougnut chart, surrounded by curved data labels and an inside curved bar chart.
//starting demo
http://jsfiddle.net/NYEaX/1753/
//LATEST demo with inner bars//
http://jsfiddle.net/NYEaX/1761/
I've tried to map out the outer labels using a path, but the labels are not showing up correctly?
var labelRadius = (radius * 0.95);
var numBars = data.length;
// Labels
var labels = svg.append('g')
.classed('labels', true);
labels.append('def')
.append('path')
.attr('id', 'label-path')
.attr('d', 'm0 ' + -labelRadius + ' a' + labelRadius + ' ' + labelRadius + ' 0 1,1 -0.01 0');
labels.selectAll('text')
.data(data)
.enter()
.append('text')
.style('text-anchor', 'middle')
.append('textPath')
.attr('xlink:href', '#label-path')
.attr('startOffset', function(d, i) {
return i * 100 / numBars + 50 / numBars + '%';
})
.text(function(d) {
return d.group;
});
Live Demo:
http://jsfiddle.net/zu8m9ckd/8/
var $this = $("#progress");
var data = [{
"group": "Chinese",
"value": 22.6,
"children": [{
"growth": 1277.4
}]
}, {
"group": "Portuguese",
"value": 4.2,
"children": [{
"growth": 989.6
}]
}, {
"group": "Spanish",
"value": 7.8,
"children": [{
"growth": 743.2
}]
}, {
"group": "Rest",
"value": 17.8,
"children": [{
"growth": 588.5
}]
}, {
"group": "French",
"value": 3.0,
"children": [{
"growth": 398.2
}]
}, {
"group": "English",
"value": 27.3,
"children": [{
"growth": 281.2
}]
}, {
"group": "German",
"value": 3.8,
"children": [{
"growth": 173.1
}]
}, {
"group": "Japanese",
"value": 5.0,
"children": [{
"growth": 110.6
}]
}, {
"group": "Korean",
"value": 2.0,
"children": [{
"growth": 107.1
}]
}, {
"group": "Arabic",
"value": 3.3,
"children": [{
"growth": 2501.2
}]
}, {
"group": "Russian",
"value": 3.0,
"children": [{
"growth": 1825.8
}]
}];
var w = 500;
var h = w;
var radius = Math.min(w, h) / 2 - 50;
var svg = d3.select($this[0])
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
svg.append("g")
.attr("class", "innerslices");
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "labels");
svg.append("g")
.attr("class", "labelsvals");
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.value;
});
const outerRadius=0.85;
const innerRadius=0.75;
const earthRadius=0.05;
const arc = d3.svg.arc()
.outerRadius(radius * outerRadius)
.innerRadius(radius * innerRadius);
const outerArc = d3.svg.arc()
.innerRadius(radius - 20)
.outerRadius(radius - 20);
const innerArc = d3.svg.arc()
.innerRadius(radius - 55)
.outerRadius(radius - 55);
svg.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
function colores_google(n) {
var colores_g = ["#e9168a", "#f8dd2f", "#448875", "#c3bd75", "#2b2d39", "#311854", "#553814", "#f7b363", "#89191d", "#c12f34", "#2b2a2c", "#c5b8a6", "#57585b"];
return colores_g[n % colores_g.length];
}
var totalsArray = [];
$.each(data, function (index, value) {
value["groupid"] = index;
var total = 0;
$.each(value.children, function (i, v) {
v["groupid"] = index;
total += v.growth;
});
value["total"] = total;
totalsArray.push(total);
});
var maxTotal = Math.max.apply(Math, totalsArray);
//slices
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data))
slice.enter()
.insert("path")
.style("fill", function (d) {
return "#3b453f"; //colores_google(d.data.groupid);
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attr("d", function (d) {
return arc(d);
})
slice.exit()
.remove();
//slices
//innerslices
var innerslice = svg.select(".innerslices").selectAll("path.innerslice")
.data(pie(data));
innerslice.enter()
.insert("path")
.style("fill", function (d) {
return "#8fdfff"; //colores_google(d.data.groupid);
})
.attr("class", "innerslice");
innerslice
.transition().duration(1000)
.attr("d", function (d) {
var arc3 = d3.svg.arc()
.outerRadius(radius * innerRadius)
.innerRadius(radius * (innerRadius-(innerRadius-earthRadius) * (d.data.children[0].growth / maxTotal)));
return arc3(d);
})
innerslice.exit()
.remove();
//innerslice
var pieData = pie(data);
var pieAngle = pieData.map(function (p) {
return (p.startAngle + p.endAngle) / 2 / Math.PI * 180;
});
const labels = svg.append('g')
.classed('labels', true);
//base on angle to change `text-anchor` and `transform(rotate)` to make the position of text correct
labels.selectAll('text')
.data(data)
.enter()
.append('text')
.style('text-anchor', function (d, i) { //important
const p = pieData[i];
const angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
return "start"
}
return "end"
})
.attr("transform", function (d, i) { //important
const p = pieData[i];
let angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //rotation depends on the angle
angle = angle - 180;
}
return `translate(${outerArc.centroid(p)}) rotate(${angle+90} 0 0) `
})
.text(function (d) {
return d.group;
});
//similar with outer black text
labels.selectAll('text.inner')
.data(data)
.enter()
.append('text')
.attr("class","inner")
.style('text-anchor', function (d, i) { //important
const p = pieData[i];
const angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
return "end"
}
return "start"
})
.attr("transform", function (d, i) { //important
const p = pieData[i];
let angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //rotation depends on the angle
angle = angle - 180;
}
return `translate(${innerArc.centroid(p)}) rotate(${angle+90} 0 0) `
})
.text(function (d) {
return d.children[0].growth+"%";
});
const labelFontSize = 10;
const labelValRadius = (radius * 0.80 - labelFontSize * 0.35); //calculate correct radius
const labelValRadius1 = (radius * 0.80 + labelFontSize * 0.35); //why 0.35? I don't know. Try to google it.
const labelsVals = svg.append('g')
.classed('labelsvals', true);
//define two paths to make the direction of labels correct
labelsVals.append('def')
.append('path')
.attr('id', 'label-path-1')
.attr('d', `m0 ${-labelValRadius} a${labelValRadius} ${labelValRadius} 0 1,1 -0.01 0`);
labelsVals.append('def')
.append('path')
.attr('id', 'label-path-2')
.attr('d', `m0 ${-labelValRadius1} a${labelValRadius1} ${labelValRadius1} 0 1,0 0.01 0`);
labelsVals.selectAll('text')
.data(data)
.enter()
.append('text')
.style('font-size', labelFontSize)
.style('font-weight', "bold")
.style('text-anchor', 'middle')
.append('textPath')
.attr('href', function (d, i) {
const p = pieData[i];
const angle = pieAngle[i];
if (angle > 90 && angle <= 270) { //based on angle to choose the path
return '#label-path-2';
} else {
return '#label-path-1';
}
})
.attr('startOffset', function (d, i) {
const p = pieData[i];
const angle = pieAngle[i];
let percent = (p.startAngle + p.endAngle) / 2 / 2 / Math.PI * 100;
if (angle > 90 && angle <= 270) { //calculate the correct percent for each path respectively
return 100 - percent + "%";
}
return percent + "%";
})
.text(function (d) {
if (d.value > 2) {//according to the simple image, the percent less than 3% should only show int part
return d.value.toFixed(1) + "%";
} else {
return d.value.toFixed(0) + "%";
}
});
body {
background: #eeeeee;
}
path {
stroke-width: 1px;
stroke: #eeeeee;
}
.small {
fill: steelblue;
}
.big {
stroke: #666;
fill: #ddd;
}
.small:hover {
stroke: steelblue;
fill: lightsteelblue;
}
.test {
padding: 30px
}
#progress {
position: relative;
margin-top: 20px
}
.progresschart {
background: white;
border-radius: 100px;
width: 100px;
height: 100px;
overflow: hidden;
border: 1px solid grey;
margin-top: 5px;
}
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.progresslabels {
position: absolute;
top: 0px;
left: 0;
}
.labelsvals {
fill: #ffffff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="progress"></div>
There are many parts in your code should be corrected. I choose two main parts to explain:
White Label:
//base on angle to change `text-anchor` and `transform(rotate)` to make the position of text correct
labels.selectAll('text')
.data(data)
.enter()
.append('text')
.style('text-anchor', function (d, i) { //important
const p = pieData[i];
const angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
return "start"
}
return "end"
})
.attr("transform", function (d, i) { //important
const p = pieData[i];
let angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //rotation depends on the angle
angle = angle - 180;
}
return `translate(${outerArc.centroid(p)}) rotate(${angle+90} 0 0) `
})
.text(function (d) {
return d.group;
});
Outer Black Label:
const labelFontSize = 10;
const labelValRadius = (radius * 0.80 - labelFontSize * 0.35); //calculate correct radius
const labelValRadius1 = (radius * 0.80 + labelFontSize * 0.35); //why 0.35? I don't know. Try to google it.
const labelsVals = svg.append('g')
.classed('labelsvals', true);
//define two paths to make the direction of labels correct
labelsVals.append('def')
.append('path')
.attr('id', 'label-path-1')
.attr('d', `m0 ${-labelValRadius} a${labelValRadius} ${labelValRadius} 0 1,1 -0.01 0`);
labelsVals.append('def')
.append('path')
.attr('id', 'label-path-2')
.attr('d', `m0 ${-labelValRadius1} a${labelValRadius1} ${labelValRadius1} 0 1,0 0.01 0`);
labelsVals.selectAll('text')
.data(data)
.enter()
.append('text')
.style('font-size', labelFontSize)
.style('font-weight', "bold")
.style('text-anchor', 'middle')
.append('textPath')
.attr('href', function (d, i) {
const p = pieData[i];
const angle = pieAngle[i];
if (angle > 90 && angle <= 270) { //based on angle to choose the path
return '#label-path-2';
} else {
return '#label-path-1';
}
})
.attr('startOffset', function (d, i) {
const p = pieData[i];
const angle = pieAngle[i];
let percent = (p.startAngle + p.endAngle) / 2 / 2 / Math.PI * 100;
if (angle > 90 && angle <= 270) { //calculate the correct percent for each path respectively
return 100 - percent + "%";
}
return percent + "%";
})
.text(function (d) {
if (d.value > 2) {//according to the simple image, the percent less than 3% should only show int part
return d.value.toFixed(1) + "%";
} else {
return d.value.toFixed(0) + "%";
}
});
Inner Black Label
//similar with outer black label
labels.selectAll('text.inner')
.data(data)
.enter()
.append('text')
.attr("class","inner")
.style('text-anchor', function (d, i) { //important
const p = pieData[i];
const angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //text-anchor depends on the angle
return "end"
}
return "start"
})
.attr("transform", function (d, i) { //important
const p = pieData[i];
let angle = pieAngle[i];
if (angle > 0 && angle <= 180) { //rotation depends on the angle
angle = angle - 180;
}
return `translate(${innerArc.centroid(p)}) rotate(${angle+90} 0 0) `
})
.text(function (d) {
return d.children[0].growth+"%";
});
In this fiddle I've managed to get the doughnut ring, with the inner bars - and some labels.
But I need to get the labels in the correct orientations, colors, scales reflecting more accurately.
//latest code
http://jsfiddle.net/NYEaX/1761/
//innerslices
var arc2 = d3.svg.arc()
.outerRadius(radius * 0.75)
.innerRadius(radius * 0.25);
var innerslice = svg.select(".innerslices").selectAll("path.innerslice")
.data(pie(data));
innerslice.enter()
.insert("path")
.style("fill", function(d) {
return "#8fdfff";//colores_google(d.data.groupid);
})
.attr("class", "innerslice");
innerslice
.transition().duration(1000)
.attrTween("d", function(d) {
var arc3 = d3.svg.arc()
.outerRadius(radius * 0.75)
.innerRadius(radius * 0.25 * (d.data.children[0].growth / maxTotal));
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc3(interpolate(t));
};
})
innerslice.exit()
.remove();
//innerslice
Related
I am working on a d3 application - which features a hooped chart. I have a version which is a radial 360 type of chart - but I am unsure how you'd configure the path to arc in this fashion.
//old js fiddle of a crescent chart
http://jsfiddle.net/2wfktc3g/
var $this = $('.crescentchart');
var data = [{
label: 'Fudge Brownie',
value: 5,
},
{
label: 'Cherry Vanilla',
value: 60,
},
{
label: 'Pistachio',
value: 5,
},
{
label: 'Caramel',
value: 10,
}
];
var oldData = "";
var width = $this.data('width'),
height = $this.data('height'),
radius = $this.data('r'),
thickness = $this.data("thickness"),
spacing = $this.data("spacing");
var color = d3.scaleOrdinal()
.range(["#bc658d", "#82c4c3", "#f9d89c", "#f5a7a7"]);
var svg = d3.select($this[0])
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr('class', 'crescentchart')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var segments = svg.append('g').attr('class', 'segments');
var data = setData(data, radius);
//append previous value to it.
$.each(data, function(index, value) {
if (oldData[index] != undefined) {
data[index]["previousEndAngle"] = oldData[index].endAngle;
} else {
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = segments.selectAll("path")
.data(data);
arcpaths.enter().append("path")
.style("fill", function(d, i) {
return color(i);
})
.transition()
.ease(d3.easeElastic)
.duration(750)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.transition()
.ease(d3.easeElastic)
.style("fill", function(d, i) {
return color(i);
})
.duration(750)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.exit().transition()
.ease(d3.easeBounce)
.duration(750)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
})
.remove();
function arcTween(b, thickness, ir) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return getArc(thickness, ir)(i(t));
};
}
function getRadiusRing(ir, i) {
return ir - (i * (thickness + spacing));
}
function getArc(thickness, ir) {
var arc = d3.arc()
.innerRadius(function(d) {
return getRadiusRing(ir, d.index);
})
.outerRadius(function(d) {
return getRadiusRing(ir + thickness, d.index);
})
.startAngle(function(d, i) {
return d.startAngle;
})
.endAngle(function(d, i) {
return d.endAngle;
});
return arc;
}
function setData(data, r) {
var diameter = (2 * Math.PI) * r;
var segmentValueSum = 0;
$.each(data, function(ri, va) {
segmentValueSum += va.value;
});
$.each(data, function(ri, va) {
var segmentValue = va.value;
var fraction = segmentValue / segmentValueSum;
var arcBatchLength = fraction * (2 * Math.PI);
var arcPartition = arcBatchLength;
var startAngle = Math.PI;
var endAngle = startAngle + arcPartition;
data[ri]["startAngle"] = startAngle;
data[ri]["endAngle"] = endAngle;
data[ri]["index"] = ri;
});
return data;
}
//legend
var legendsvgw = 150;
var legendsvgh = 100;
var ringRadius = 5;
var vertical = 20;
var legendsvg = d3.select($this[0])
.append("svg")
.attr("width", legendsvgw)
.attr("height", legendsvgh)
.append("g")
.attr('class', 'legendsvg')
.attr("transform", "translate(" + 10 + "," + 10 + ")");
var legend = legendsvg.append("g")
.attr("class", "legend");
var labels = legend.selectAll("text.labels")
.data(data);
labels.enter().append("text")
.attr("class", "labels")
.attr("dx", 15)
.attr("dy", function(d, i) {
return (vertical * i) + ringRadius * 2;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
var ring = legend.selectAll("circle")
.data(data);
ring.enter().append("circle")
.attr("cy", function(d, i) {
return (vertical * i) + ringRadius;
})
.attr("r", ringRadius)
.attr("width", ringRadius * 2)
.attr("height", ringRadius * 2)
.style("fill", function(d, i) {
return color(i);
});
ring.exit().remove();
body {
background: #eeeeee;
}
.arc path {
stroke: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<h1>CrescentChart I</h1>
<div class="crescentchart" data-width="300" data-height="300" data-r="90" data-thickness="6" data-spacing="10" />
Trying to keep it in line with your current implementation, I'd consider two different cases. One where d.fraction <= 0.25, so the arc part is the only thing required to draw the bar. In that case, just use the arc. The other, when d.fraction > 0.25, you can draw yourself, because you can assume already that the arc goes exactly from 180 to 270 degrees, and all you need it to draw the bar on top before closing it. I used d3.path() extensively for this example, I recommend you keep it next to you when reading the code.
var $this = $('.crescentchart');
var data = [{
label: 'Fudge Brownie',
value: 45,
},
{
label: 'Cherry Vanilla',
value: 60,
},
{
label: 'Pistachio',
value: 5,
},
{
label: 'Caramel',
value: 10,
}
];
var oldData = "";
var width = $this.data('width'),
height = $this.data('height'),
radius = $this.data('r'),
thickness = $this.data("thickness"),
spacing = $this.data("spacing");
var color = d3.scaleOrdinal()
.range(["#bc658d", "#82c4c3", "#f9d89c", "#f5a7a7"]);
var svg = d3.select($this[0])
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr('class', 'crescentchart')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var segments = svg.append('g').attr('class', 'segments');
var data = setData(data, radius);
//append previous value to it.
$.each(data, function(index, value) {
if (oldData[index] != undefined) {
data[index]["previousFraction"] = oldData[index].fraction;
} else {
data[index]["previousFraction"] = 0;
}
});
var arcpaths = segments.selectAll("path")
.data(data);
arcpaths.enter().append("path")
.style("fill", function(d, i) {
return color(i);
})
.transition()
.ease(d3.easeElastic)
.duration(3000)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.transition()
.ease(d3.easeElastic)
.style("fill", function(d, i) {
return color(i);
})
.duration(3000)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
});
arcpaths.exit().transition()
.ease(d3.easeBounce)
.duration(3000)
.attrTween("d", function(d) {
return arcTween(d, thickness, radius);
})
.remove();
function arcTween(b, thickness, ir) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
prev.fraction = b.previousFraction;
var i = d3.interpolate(prev, b);
const arc = getArc(thickness, ir);
return function(t) {
return arc(i(t));
};
}
function getRadiusRing(ir, i) {
return ir - (i * (thickness + spacing));
}
// The portion of the fraction that is drawn as an arc,
// instead of as a straight part
const arcPortion = 3 / 8;
function getArc(thickness, ir) {
var arc = d3.arc()
.innerRadius(function(d) {
return getRadiusRing(ir, d.index);
})
.outerRadius(function(d) {
return getRadiusRing(ir + thickness, d.index);
})
.startAngle(- arcPortion * 2 * Math.PI)
.endAngle(function(d, i) {
return -(d.fraction + arcPortion) * 2 * Math.PI;
});
// This function is only called when endAngle is greater than
// 3 * Math.PI, otherwise we simply draw an arc, because it makes
// no difference.
function jShape(d, i) {
const context = d3.path();
const innerRadius = getRadiusRing(ir, d.index);
const outerRadius = getRadiusRing(ir + thickness, d.index);
const startAngle = -arcPortion * 2 * Math.PI - Math.PI / 2;
const endAngle = 0;
// Start at the correct position on the inside
context.moveTo(
innerRadius * Math.cos(startAngle),
innerRadius * Math.sin(startAngle)
);
context.arc(0, 0, innerRadius, startAngle, endAngle, true);
// Now draw the straight part
const fullLength = innerRadius * 2 * Math.PI;
// The first 0.25 corresponds to the curved part drawn earlier.
const straightLength = (d.fraction - arcPortion) * fullLength
context.lineTo(
innerRadius * Math.cos(endAngle),
innerRadius * Math.sin(endAngle) - straightLength
);
// Move to the outside
context.lineTo(
outerRadius * Math.cos(endAngle),
innerRadius * Math.sin(endAngle) - straightLength
);
context.lineTo(
outerRadius * Math.cos(endAngle),
outerRadius * Math.sin(endAngle)
);
// And curve back
context.arc(0, 0, outerRadius, endAngle, startAngle, false);
context.closePath();
return context + "";
}
return function(d, i) {
return d.fraction <= arcPortion ? arc(d, i) : jShape(d, i);
}
}
function setData(data, r) {
var segmentValueSum = 0;
$.each(data, function(ri, va) {
segmentValueSum += va.value;
});
$.each(data, function(ri, va) {
var segmentValue = va.value;
var fraction = segmentValue / segmentValueSum;
data[ri]["index"] = ri;
data[ri]["fraction"] = fraction;
});
return data;
}
body {
background: #eeeeee;
}
.arc path {
stroke: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<h1>CrescentChart I</h1>
<div class="crescentchart" data-width="300" data-height="300" data-r="90" data-thickness="6" data-spacing="10" />
How to make a line from arrows like a dashed line but somewhat like this "> > > >" for direction of the flow. I am trying to connect nodes via lines but my nodes have to resizable rectangles and it hides the arrowheads , which is why I am looking for such arrow(ed) line. Specifying arrow in the middle could also solve the problem but attr("marker-middle", "url(#arrowhead)") does not work in my code.
Here's the relevant code
svg
.append("defs")
.append("marker")
.attrs({
id: "arrowhead",
viewBox: "-0 -5 10 10",
refX: 13,
refY: 0,
orient: "auto",
markerWidth: 3,
markerHeight: 3
})
.append("svg:path")
.attr("d", "M 0,-5 L 10 ,0 L 0,5")
.attr("fill", "black")
.call(
d3.zoom().on("zoom", function() {
console.log(d3.event.transform);
svg.attr("transform", d3.event.transform);
})
);
var link = svg
.append("g")
.selectAll("line_class.line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", function(d) {
return Math.max(
((d.thickness - min_thickness) /
(max_thickness - min_thickness + 0.01)) *
5,
3
);
})
.style("stroke", "pink")
.text("text", function(d) {
return d.linkname;
})
.on("mouseover", function(d) {
div
.transition()
.duration(200)
.style("opacity", 0.9);
div
.html("Name: " + d.linkname)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY - 28 + "px")
.style("width", "auto")
.style("height", "auto");
})
.on("mouseout", function(d) {
div
.transition()
.duration(500)
.style("opacity", 0);
});
link.call(updateState1);
function updateState1() {
link
.each(function(d) {
//d3.select(this).attr("marker-mid", "url(#arrowhead)");
d3.select(this).attr("marker-end", "url(#arrowhead)");
});
}
var config = [];
var graph = {
nodes: [
{ name: "A" },
{ name: "B" },
{ name: "C" },
{ name: "D" },
{ name: "Dummy1" },
{ name: "Dummy2" },
{ name: "Dummy3" },
{ name: "Dummy4" }
],
links: [
{ source: "A", target: "B", linkname: "A0" },
{ source: "A", target: "C", linkname: "A0" },
{ source: "A", target: "D", linkname: "A1" },
{ source: "Dummy1", target: "A", linkname: "input" },
{ source: "B", target: "Dummy2", linkname: "B" },
{ source: "C", target: "Dummy3", linkname: "C" },
{ source: "D", target: "Dummy4", linkname: "D" }
]
};
var optArray = [];
labelAnchors = [];
labelAnchorLinks = [];
highlightNode_button = d3.select("#highlightNode");
highlightNode_button.on("click", highlightNode);
shrinkNode_button = d3.select("#shrinkNode");
shrinkNode_button.on("click", minimizeNode);
for (var i = 0; i < graph.nodes.length - 1; i++) {
optArray.push(graph.nodes[i].name);
}
optArray = optArray.sort();
$(function() {
$("#targetNode").autocomplete({
source: optArray
});
});
//used to find max thickness of all the nodes, which is used for normalizing later on.
var max_thickness = d3.max(graph.links, function(d) {
return d.thickness;
});
//used to find min thickness of all the nodes, which is used for normalizing later on.
var min_thickness = d3.min(graph.links, function(d) {
return d.thickness;
});
var svg1 = d3.select("svg");
var width = +screen.width;
var height = +screen.height - 500;
svg1.attr("width", width).attr("height", height);
var zoom = d3.zoom().on("zoom", zoomed);
function zoomed() {
svg.attr("transform", d3.event.transform);
}
var svg = svg1
// .call(
// zoom.on("zoom", function() {
// svg.attr("transform", d3.event.transform);
// })
// )
.call(zoom)
.on("dblclick.zoom", null)
.append("g");
// Defining the gradient
//used for coloring the nodes
var gradient = svg
.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
// Define the gradient colors
gradient
.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#b721ff")
.attr("stop-opacity", 1);
gradient
.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#21d4fd")
.attr("stop-opacity", 1);
var linkText = svg
.selectAll(".gLink")
.data(graph.links)
.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("x", function(d) {
if (d.target.x > d.source.x) {
return d.source.x + (d.target.x - d.source.x) / 2;
} else {
return d.target.x + (d.source.x - d.target.x) / 2;
}
})
.attr("y", function(d) {
if (d.target.y > d.source.y) {
return d.source.y + (d.target.y - d.source.y) / 2;
} else {
return d.target.y + (d.source.y - d.target.y) / 2;
}
})
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("dy", ".35em")
.text(function(d) {
return d.linkname;
});
var dragDrop = d3
.drag()
.on("start", node => {
node.fx = node.x;
node.fy = node.y;
})
.on("drag", node => {
simulation.alphaTarget(1).restart();
node.fx = d3.event.x;
node.fy = d3.event.y;
})
.on("end", node => {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
node.fx = null;
node.fy = null;
});
var linkForce = d3
.forceLink(graph.links)
.id(function(d) {
return d.name;
})
.distance(50);
//.strength(0.5) to specify the pulling strength from each link
// strength from each node
// Saving a reference to node attribute
var nodeForce = d3.forceManyBody().strength(-30);
var simulation = d3
.forceSimulation(graph.nodes)
.force("links", linkForce)
.force("charge", nodeForce)
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
var div = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg
.append("defs")
.append("marker")
.attrs({
id: "arrowhead",
viewBox: "-0 -5 10 10",
refX: 100,
refY: 0,
orient: "auto-start-reverse",
markerWidth: 3,
markerHeight: 3
})
.append("svg:path")
.attr("d", "M 0,-5 L 10 ,0 L 0,5")
.attr("fill", "black");
{
{
/* .call(
d3.zoom().on("zoom", function() {
console.log(d3.event.transform);
svg.attr("transform", d3.event.transform);
})
); */
}
}
var link = svg
.append("g")
.selectAll("line_class.line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", function(d) {
return Math.max(
((d.thickness - min_thickness) / (max_thickness - min_thickness + 0.01)) *
5,
3
);
})
.style("stroke", "pink")
.text("text", function(d) {
return d.linkname;
})
.on("mouseover", function(d) {
div
.transition()
.duration(200)
.style("opacity", 0.9);
div
.html("Name: " + d.linkname)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY - 28 + "px")
.style("width", "auto")
.style("height", "auto");
})
.on("mouseout", function(d) {
div
.transition()
.duration(500)
.style("opacity", 0);
})
.attr("marker-start", "url(#arrowhead)");
// .attr("marker-start", "url(#arrowhead)")
// .attr("marker-mid", "url(#arrowhead)")
// .attr("marker-end", "url(#arrowhead)");
// var line = link
var edgepaths = svg
.selectAll(".edgepath")
.data(graph.links)
.enter()
.append("path")
.attr("d", function(d) {
return (
"M " +
d.source.x +
" " +
d.source.y +
" L " +
d.target.x +
" " +
d.target.y
);
})
.attr("class", "edgepath")
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.attr("fill", "blue")
.attr("stroke", "red")
.attr("id", function(d, i) {
return "edgepath" + i;
})
.style("pointer-events", "none");
var radius = 4;
var node_data_array = [];
var node_name = "";
var node = svg
.append("g")
.selectAll("circle_class.circle")
.data(graph.nodes)
.enter()
.append("rect")
.attr("width", 40)
.attr("height", 20)
.attr("fill", "url(#gradient)")
.style("transform", "translate(-20px,-10px)")
.attr("stroke", "purple")
.attr("id", function(d) {
node_name = d.name;
return d.name;
})
.on("dblclick", connectedNodes);
var textElements_nodes = svg
.append("g")
.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.text(function(d) {
return d.name;
})
.attr("font-size", 15)
.attr("dx", 15)
.attr("dy", -5);
node.call(dragDrop);
link.call(updateState1);
var path = document.querySelector("path"),
totalLength = path.getTotalLength(),
group = totalLength / 20,
start;
var arrowheads = d3
.select("svg")
.selectAll("use")
.data(
d3.range(20).map(function(d) {
return d * group + 50;
})
)
.enter()
.append("use")
.attr("xlink:href", "#arrowhead");
path.style.strokeDasharray = "50," + (group - 50);
requestAnimationFrame(update);
function update(t) {
if (!start) {
start = t;
}
var offset = (-group * ((t - start) % 900)) / 900;
path.style.strokeDashoffset = offset;
arrowheads.attr("transform", function(d) {
var l = d - offset;
if (l < 0) {
l = totalLength + l;
} else if (l > totalLength) {
l -= totalLength;
}
var p = pointAtLength(l);
return "translate(" + p + ") rotate( " + angleAtLength(l) + ")";
});
//requestAnimationFrame(update);
}
function pointAtLength(l) {
var xy = path.getPointAtLength(l);
return [xy.x, xy.y];
}
// Approximate tangent
function angleAtLength(l) {
var a = pointAtLength(Math.max(l - 0.01, 0)), // this could be slightly negative
b = pointAtLength(l + 0.01); // browsers cap at total length
return (Math.atan2(b[1] - a[1], b[0] - a[0]) * 180) / Math.PI;
}
// link.attr("marker-end", "url(#end)");
function updateState1() {
link.each(function(d) {
var colors = ["red", "green", "blue"];
var num = 0;
if (d.source.name.startsWith("Dummy")) {
num = 1;
console.log("Inside 1");
console.log(
"Source is ",
d.source.name,
" and target is ",
d.target.name
);
} else if (d.target.name.startsWith("Dummy")) {
num = 2;
console.log("Inside 2");
console.log(
"Source is ",
d.source.name,
" and target is ",
d.target.name
);
} else {
num = 0;
for (i = 0; i < graph.input_nodes.length; i++) {
if (graph.input_nodes[i].name == d.source.name) {
num = 1;
}
}
}
d3.select(this).style("stroke", function(d) {
return colors[num];
});
{
{
/* d3.select(this).attr("marker-end", "url(#arrowhead)"); */
}
}
{
{
/* d3.select(this).attr("marker-mid", "url(#arrowhead)"); */
}
}
// .attr("marker-end", "url(#arrowhead)");
});
}
function pointOnRect(x, y, minX, minY, maxX, maxY, validate) {
//assert minX <= maxX;
//assert minY <= maxY;
if (validate && minX < x && x < maxX && minY < y && y < maxY)
throw "Point " +
[x, y] +
"cannot be inside " +
"the rectangle: " +
[minX, minY] +
" - " +
[maxX, maxY] +
".";
var midX = (minX + maxX) / 2;
var midY = (minY + maxY) / 2;
// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
var m = (midY - y) / (midX - x);
if (x <= midX) {
// check "left" side
var minXy = m * (minX - x) + y;
if (minY <= minXy && minXy <= maxY) return { x: minX, y: minXy };
}
if (x >= midX) {
// check "right" side
var maxXy = m * (maxX - x) + y;
if (minY <= maxXy && maxXy <= maxY) return { x: maxX, y: maxXy };
}
if (y <= midY) {
// check "top" side
var minYx = (minY - y) / m + x;
if (minX <= minYx && minYx <= maxX) return { x: minYx, y: minY };
}
if (y >= midY) {
// check "bottom" side
var maxYx = (maxY - y) / m + x;
if (minX <= maxYx && maxYx <= maxX) return { x: maxYx, y: maxY };
}
// edge case when finding midpoint intersection: m = 0/0 = NaN
if (x === midX && y === midY) return { x: x, y: y };
// Should never happen :) If it does, please tell me!
throw "Cannot find intersection for " +
[x, y] +
" inside rectangle " +
[minX, minY] +
" - " +
[maxX, maxY] +
".";
}
var label_toggle = 0;
function show_hide_node_labels() {
if (label_toggle) {
textElements_nodes.style("visibility", "visible");
} else {
textElements_nodes.style("visibility", "hidden");
}
label_toggle = !label_toggle;
}
var toggle = 0;
var linkedByIndex = {};
for (i = 0; i < graph.nodes.length; i++) {
linkedByIndex[i + "," + i] = 1;
}
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
//DAT GUI for controls
var gui = new dat.GUI({ width: 300 });
config = {
linkStrength: 1,
linkDistance: 180,
nodeStrength: -30,
Width: 40,
Height: 20,
restart: reset,
showHideNodeLabels: show_hide_node_labels
};
var linkDistanceChanger = gui
.add(config, "linkDistance", 0, 400)
.step(1)
.name("Link Distance");
linkDistanceChanger.onChange(function(value) {
linkForce.distance(value);
simulation.alpha(1).restart();
});
var linkStrengthChanger = gui
.add(config, "linkStrength", 0, 1)
.name("Link Strength");
linkStrengthChanger.onChange(function(value) {
linkForce.strength(value);
simulation.alpha(1).restart();
});
var nodeStrengthChanger = gui
.add(config, "nodeStrength", -500, -1)
.step(1)
.name("Node Strength");
nodeStrengthChanger.onChange(function(value) {
nodeForce.strength(value);
simulation.alpha(1).restart();
});
var widthChanger = gui
.add(config, "Width", 4, 100)
.step(1)
.name("Node Width");
widthChanger.onChange(function(value) {
node.attr("width", value);
og_width = value;
// d3.select("#arrowhead").attrs({
// markerWidth: value * 0.4,
// markerHeight: value * 0.4
// });
simulation.alpha(1).restart();
});
var heightChanger = gui
.add(config, "Height", 2, 80)
.step(1)
.name("Node Height");
heightChanger.onChange(function(value) {
node.attr("height", value);
og_height = value;
// d3.select("#arrowhead").attrs({
// markerWidth: value * 0.4,
// markerHeight: value * 0.4
// });
simulation.alpha(1).restart();
});
gui.add(config, "showHideNodeLabels").name("Show/Hide Node Labels");
gui.add(config, "restart").name("Restart");
for (i = 0; i < gui.__ul.childNodes.length; i++) {
gui.__ul.childNodes[i].classList += " longtext";
}
function reset() {
window.location.reload();
}
//This function looks up whether a pair are neighbours
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function connectedNodes() {
if (toggle == 0) {
//Reduce the opacity of all but the neighbouring nodes
d = d3.select(this).node().__data__;
node.style("opacity", function(o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
link.style("opacity", function(o) {
return (d.index == o.source.index) | (d.index == o.target.index)
? 1
: 0.1;
});
textElements_nodes.style("opacity", function(o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
toggle = 1;
} else {
//Put them back to opacity=1
node.style("opacity", 1);
link.style("opacity", 1);
textElements_nodes.style("opacity", 1);
toggle = 0;
}
}
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
})
.attr("d", function(d) {
var inter = pointOnRect(
d.source.x,
d.source.y,
d.target.x - 20,
d.target.y - 20,
d.target.x + 40 - 20,
d.target.y + 20 - 20
);
return (
"M" + d.source.x + "," + d.source.y + "L" + inter.x + "," + inter.y
);
});
node
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
textElements_nodes.attr("x", node => node.x).attr("y", node => node.y);
edgepaths.attr("d", function(d) {
var path =
"M " +
d.source.x +
" " +
d.source.y +
" L " +
d.target.x +
" " +
d.target.y;
return path;
});
var ticking = false;
}
var pulse = false;
function pulsed(rect) {
(function repeat() {
if (pulse) {
rect
.transition()
.duration(200)
.attr("stroke-width", 0)
.attr("stroke-opacity", 0)
.attr("width", config.Width)
.attr("height", config.Height)
.transition()
.duration(200)
.attr("stroke-width", 0)
.attr("stroke-opacity", 0.5)
.attr("width", config.Width * 3)
.attr("height", config.Height * 3)
.transition()
.duration(400)
.attr("stroke-width", 65)
.attr("stroke-opacity", 0)
.attr("width", config.Width)
.attr("height", config.Height)
.ease(d3.easeSin)
.on("end", repeat);
} else {
ticking = false;
rect
.attr("width", config.Width)
.attr("height", config.Height)
.attr("fill", "url(#gradient)")
.attr("stroke", "purple");
}
})();
}
function highlightNode() {
var userInput = document.getElementById("targetNode");
var temp = userInput.value;
var userInputRefined = temp.replace(/[/]/g, "\\/");
// make userInput work with "/" as they are considered special characters
// the char "/" is escaped with escape characters.
theNode = d3.select("#" + userInputRefined);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
pulse = true;
if (pulse) {
pulsed(theNode);
}
scalingFactor = 0.5;
// Create a zoom transform from d3.zoomIdentity
var transform = d3.zoomIdentity
.translate(
screen.width / 2 - scalingFactor * theNode.attr("x"),
screen.height / 4 - scalingFactor * theNode.attr("y")
)
.scale(scalingFactor);
// Apply the zoom and trigger a zoom event:
svg1.call(zoom.transform, transform);
}
function minimizeNode() {
var userInput = document.getElementById("targetNode");
var temp = userInput.value;
var userInputRefined = temp.replace(/[/]/g, "\\/");
// make userInput work with "/" as they are considered special characters
// the char "/" is escaped with escape characters.
theNode = d3.select("#" + userInputRefined);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
pulse = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<title>Force Layout Example 9</title>
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.longtext {
line-height: 13px;
height: 40px;
font-size: 120%;
}
rect.zoom-panel {
cursor: move;
fill: #fff;
pointer-events: all;
}
.bar {
fill: rgb(70, 180, 70);
}
.graph-svg-component {
background-color: green;
}
path {
fill: none;
stroke: #d3008c;
stroke-width: 2px;
}
#arrowhead {
fill: #d3008c;
stroke: none;
}
</style>
</head>
<body>
<svg></svg>
<div id="content"></div>
<script src="http://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-drag.v1.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"
></script>
<link
href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css"
rel="stylesheet"
type="text/css"
/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
</body>
</html>
I think this is what you want, this code snippet based on this bl.ocks.org snippet and it meant to be animated by I removed the requestAnimationFrame call from the update function.
regarding your original issue with nodes hiding the arrow, you can use refX and refY attribute on <marker> to change its offset.
var path = document.querySelector("path"),
totalLength = path.getTotalLength(),
group = totalLength / 20,
start;
var arrowheads = d3.select("svg").selectAll("use")
.data(d3.range(20).map(function(d){ return d * group + 50; }))
.enter()
.append("use")
.attr("xlink:href", "#arrowhead");
path.style.strokeDasharray = "50," + (group - 50);
requestAnimationFrame(update);
function update(t) {
if (!start) {
start = t;
}
var offset = -group * ((t - start) % 900) / 900;
path.style.strokeDashoffset = offset;
arrowheads.attr("transform",function(d){
var l = d - offset;
if (l < 0) {
l = totalLength + l;
} else if (l > totalLength) {
l -= totalLength;
}
var p = pointAtLength(l);
return "translate(" + p + ") rotate( " + angleAtLength(l) + ")";
});
//requestAnimationFrame(update);
}
function pointAtLength(l) {
var xy = path.getPointAtLength(l);
return [xy.x, xy.y];
}
// Approximate tangent
function angleAtLength(l) {
var a = pointAtLength(Math.max(l - 0.01,0)), // this could be slightly negative
b = pointAtLength(l + 0.01); // browsers cap at total length
return Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI;
}
path {
fill: none;
stroke: #d3008c;
stroke-width: 2px;
}
#arrowhead {
fill: #d3008c;
stroke: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="960" height="500">
<path d="M636.5,315c-0.4-18.7,1.9-27.9-5.3-35.9
c-22.7-25-107.3-2.8-118.3,35.9c-7,24.4,20.6,37.2,16,71c-4,29.6-30.8,60.7-56.5,61.1c-30.8,0.4-32.9-43.8-81.7-70.2
c-50.9-27.6-110.1-12.9-125.2-9.2c-66.1,16.4-82.2,56.9-109.2,47.3c-38-13.6-55.9-112.1-19.8-143.5c39-34,121.2,27.7,148.1-3.8
c18-21.1,3.1-74.3-25.2-105.3c-31.1-34.1-70.1-32.4-105.3-76.3c-8.2-10.2-16.9-23.8-15.3-39.7c1.2-11.4,7.5-23.3,15.3-29
c33.8-25,101.6,62.6,193.1,59.5c40.1-1.3,38.7-18.5,99.2-38.9c126.2-42.6,242.4-4.9,297.7,13c54.7,17.7,105.4,35,129.8,82.4
c13,25.3,22.9,67.7,4.6,87c-11.6,12.3-25.1,5.1-46.6,20.6c-2.8,2-28.9,21.4-32.1,49.6c-3.1,27.4,18.7,35,29,70.2
c8.8,30.1,8.5,77.8-18.3,99.2c-32.3,25.8-87,0.6-100-5.3c-69.6-32-67.2-88.4-73.3-109.2z"/>
<defs>
<path id="arrowhead" d="M7,0 L-7,-5 L-7,5 Z" />
</defs>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
Below is my code for the text. I just need to flip horizontally the "SAMPLE 1" text to make it more readable.
function computeTextRotation(d) {
var angle = (d.x + d.dx / 2) * 180 / Math.PI - 90;
return angle;
}
var texts = svg.selectAll("text")
.data(partitioned_data)
.enter().append("text")
.filter(filter_min_arc_size_text)
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.attr("x", function(d) {
return radius / 3 * d.depth;
})
.attr("dx", "0.5em") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d, i) {
return d.name
})
.style("font-size", 11);
Here's one solution...
function computeTextRotation(d) {
var rotation = (d.startAngle + d.endAngle)/2 * 180 / Math.PI - 90;
return {
global: rotation,
correction: rotation > 90 ? 180 : 0
};
}
//...
text.attr("transform", function(d) {
var r = computeTextRotation(d);
return "rotate(" + r.global + ")"
+ "translate(" + radius / 3 * d.depth + ",0)"
+ "rotate(" + -r.correction + ")";
});
Here's a working example...
var crm = (function() {
var prevData = [];
return function crm(f) {
var max = 10000;
oldPieData = JSON.parse(JSON.stringify(prevData));
prevData = f([
{name: 'SMR', value: Math.random() * max},
{name: 'PSF', value: Math.random() * max},
{name: 'Insurance', value: Math.random() * max},
{name: 'Other', value: Math.random() * max}
])
}
})();
pieTween = function(d, i) {
var s0;
var e0;
if(oldPieData[i]){
s0 = oldPieData[i].startAngle;
e0 = oldPieData[i].endAngle;
} else if (!(oldPieData[i]) && oldPieData[i-1]) {
s0 = oldPieData[i-1].endAngle;
e0 = oldPieData[i-1].endAngle;
} else if(!(oldPieData[i-1]) && oldPieData.length > 0){
s0 = oldPieData[oldPieData.length-1].endAngle;
e0 = oldPieData[oldPieData.length-1].endAngle;
} else {
s0 = 0;
e0 = 0;
}
var i = d3.interpolate({startAngle: s0, endAngle: e0}, {startAngle: d.startAngle, endAngle: d.endAngle});
return function(t) {
var b = i(t);
return arc(b);
};
};
var pie = Math.PI * 2;
var w = 200,
h = 200;
var ir = 45;
var duration = 750;
var chart = d3.select('.chart')
.attr('width', w)
.attr('height',h)
.append('g')
.attr('transform', 'translate('+w/2+','+ h/2 + ')'),
pieChart = d3.layout.pie()
.value(function(d){ return d.value; })
.sort(null),
oldPieData = [],
groups = chart.append ("g")
.attr("class", "groups"),
// group at the center of donut
center_group = chart.append('g')
.attr("class", "center_group")
.append('text')
.attr({'text-anchor': 'middle', dy: "0.35em"});
createPieChart = function(data){
var radius = 95, innerRadius = radius - 70;
var pieData,
color = d3.scale.ordinal()
.range(['#469AB2', '#F0AD4E', '#5CB85C', '#D9534F'])
.domain(data.map(function(d){return d.name}));
// displaying total calls at the center
center_group.text(d3.format(",.1f")(d3.sum(data, function(d){return d.value})));
arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
var arcs = groups.selectAll('path')
.data((pieData = pieChart(data)), function(d){return d.data.name});
arcs.enter().append('path')
.attr('class', 'arc');
arcs.attr('fill', function(d){ return color(d.data.name)})
.transition()
.duration(duration)
.attrTween("d", pieTween)
.ease("bounce");
function computeTextRotation(d) {
var rotation = (d.startAngle + d.endAngle)/2 * 180 / Math.PI - 90;
return {
global: rotation,
correction: rotation > 90 ? 180 : 0
};
}
var labels = groups.selectAll("text")
.data(pieData);
labels.enter().append("text")
.attr({"text-anchor": "middle"})
.text(function(d) {
return d.data.name
})
.attr("dy", ".35em") // vertical-align
.style("font-size", 11);
labels
.transition()
.duration(duration)
.attr("transform", function(d) {
var r = computeTextRotation(d);
return "rotate(" + r.global + ")" + "translate(" + (radius + innerRadius) / 2 + ",0)" + "rotate(" + -r.correction + ")";
})
.call(endAll, function(){
window.requestAnimationFrame(
function(){crm(createPieChart)}
)
});
return pieData;
};
crm(createPieChart);
body {margin: 1px;}
.chart text {
/* fill: white;*/
font: 10px sans-serif;
}
#pie-chart-div {
position: relative;
top: 6em;
}
.chart {
position: relative;
/* top: 2em;*/
left: 5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/transitions/end-all/endAll.js"></script>
<svg class="chart"></svg>
I'm trying to create a chart which basically has a number of horizontal lines on it. The data for the lines is taken from the function that generates random arrays of objects like this:
{
"process": 2,
"time": 5
}
And also there is a setInterval function that redraws the chart every 3s. How can I make it change not that abruptly, but with easing and transitions?
Here is my code below:
html:
<div class="container">
<div class="jumbotron"><h3>Operating System Processes Model</h3></div>
<div class="enter">Enter the number of processes: </div>
<input type="number" min="0" max="30" id="input">
</div>
<svg id="visualisation" width="1000" height="500"></svg>
utils.js:
// Renders grid in the system of axes
function renderGrid() {
// Vertical lines
for(var i = 5; i <= 50; i+=5) {
vis.append('svg:path')
.attr('d', lineGen(
[
{
"process": "0",
"time": + i
},
{
"process": "50",
"time": + i
},
]
))
.attr('stroke', '#777')
.attr('stroke-dasharray', "5,5")
.attr('stroke-width', 1)
.attr('fill', 'none');
// Horizontal lines
vis.append('svg:path')
.attr('d', lineGen(
[
{
"process": i,
"time": "0"
},
{
"process": i,
"time": "50"
},
]
))
.attr('stroke', '#777')
.attr('stroke-dasharray', "5,5")
.attr('stroke-width', 1)
.attr('fill', 'none');
};
}
// Generate single line
var lineGen = d3.svg.line()
.x(function(d) {
return xScale(d.time);
})
.y(function(d) {
return yScale(d.process);
})
.interpolate("basis");
// Generate random color
function getRandomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// Generate random data array
function getDataArray(count) {
var array = [];
for(var i = 0; i < count; ++i) {
var proc = Math.round(Math.random() * (50 - 1)) + 1;
var time1 = Math.round(Math.random() * (50 - 1)) + 1;
var time2 = Math.round(Math.random() * (50 - 1)) + 1;
var data = [
{
"process": proc,
"time": Math.max(time1, time2)
},
{
"process": proc,
"time": Math.min(time1, time2)
},
];
array.push(data);
}
return array;
}
// Generate random data
function renderProcesses() {
vis.selectAll('.line-process').remove();
// var processCount = Math.round(Math.random() * (50 - 1)) + 1;
var processCount = document.getElementById('input').value;
var arr = getDataArray(processCount);
for(var i = 0; i < processCount; ++i) {
var processLength = Math.round(Math.random() * (50 - 1)) + 1;
var color = getRandomColor();
vis.append('svg:path')
.attr('class', 'line-process')
.attr('d', lineGen(arr[i]))
.attr('stroke', color)
.attr('stroke-width', 5)
.attr('stroke-linecap', 'round')
.attr('fill', 'none')
.on("mouseover", function() {
d3.select(this)
.attr('stroke', "#1abc9c");
})
.on("mouseout", function() {
d3.select(this)
.attr('stroke', color)
});
}
}
axis.js:
// Draw the axis
var vis = d3.select("#visualisation"),
width = 1000,
height = 500,
margins = {
top: 20,
right: 20,
bottom: 20,
left: 50
},
xScale = d3.scale.linear().range([margins.left, width - margins.right]).domain([0,50]),
yScale = d3.scale.linear().range([height - margins.top, margins.bottom]).domain([0,50]),
xAxis = d3.svg.axis()
.scale(xScale),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
vis.append("svg:g")
.transition()
.attr("transform", "translate(0," + (height - margins.bottom) + ")")
.attr("class", "axis")
.call(xAxis);
vis.append("svg:g")
.transition()
.attr("transform", "translate(" + (margins.left) + ",0)")
.attr("class", "axis")
.call(yAxis);
styles for the axises:
.axis {
path {
fill: none;
stroke: #777;
shape-rendering: crispEdges;
}
text {
font-family: Lato;
font-size: 12px;
}
}
and finally, main.js:
renderGrid();
setInterval(renderProcesses, 3000);
Here is the screenshot:
So, Could you help me with those transitions?
And, another question, as you can see on the screenshot, some lines can be drown on each other. How can I avoid it?
Just wondering is it possible to do something like this with d3?
http://jsfiddle.net/8T7Ew/
Where when you click on a certain pie slice the slice moves on click?
Have the pie created up to this point just wondering if I can add this feature
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
});
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
</script>
Data is coming from a csv file.
Thanks
You can increase the arc radius of pie for highlighting. JSFiddle
var arcOver = d3.svg.arc()
.outerRadius(r + 10);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); })
.on("mouseenter", function(d) {
d3.select(this)
.attr("stroke","white")
.transition()
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arc)
.attr("stroke","none");
});
I prefer changing Radius rather than stroke as it gives you smooth and nicer animation...
Using a function like this:
function pathEnter() {
t = d3.select(this);
t.transition()
.attr('d', pathIn);
}
Run this code below to see the interaction:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
</style>
<svg width="520" height="280"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = Math.min(width, height) / 2 - 20,
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var pie = d3.pie()
.sort(null)
.value(function(d) {
return d.population;
});
var path = d3.arc()
.outerRadius(radius)
.innerRadius(0);
var pathIn = d3.arc()
.outerRadius(radius + 6)
.innerRadius(0);
function pathEnter() {
t = d3.select(this);
t.transition()
.attr('d', pathIn);
}
function pathOut() {
t = d3.select(this);
t.transition()
.attr('d', path);
}
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
data = [{
age: '<5',
population: '2704659'
},
{
age: '5-13',
population: '4499890'
},
{
age: '14-17',
population: '2159981'
},
{
age: '18-24',
population: '3853788'
},
{
age: '25-44',
population: '14106543'
},
{
age: '45-64',
population: '8819342'
},
{
age: '≥65',
population: '612463'
},
];
data.population = +data.population;
var arc = g.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.on('mouseenter', pathEnter)
.on('mouseout', pathOut)
.attr("fill", function(d) {
return color(d.data.age);
});
arc.append("text")
.attr("transform", function(d) {
return "translate(" + label.centroid(d) + ")";
})
.attr("dy", "0.35em")
.text(function(d) {
return d.data.age;
});
</script>
Using the attribute transform="translate(x, y)" would do the actual moving of each slice of the pie.
http://jsfiddle.net/qkHK6/3306/
Building on #Gilsha's answer (I know this question is old, but I figured I'd put this answer up for archival purposes)...
g.append("path")
.attr("d", arc)
.attr("opacity", "1.0")
.on("mouseenter", function (d) {
var arcOver = d3.arc()
.outerRadius(radius).innerRadius(0).startAngle(d.startAngle + 0.01).endAngle(d.endAngle - 0.01);
var transformText = getTranslate(d.startAngle + 0.01, d.endAngle - 0.01, 20);
d3.select(this)
.attr("d", arcOver)
.transition()
.duration(200).ease(d3.easeBack)
.attr("transform", transformText)
.attr("style", "fill: rgb(102, 102, 102)");
})
.on("mouseleave", function (d) {
d3.select(this)
.attr("d", arc)
.transition().ease(d3.easeBack)
.duration(600)
.attr("transform", "translate(0,0)")
.attr("style", "fill: " + color(d.data));
})
.style("fill", function (d) { return color(d.data); });
Also d3.arc() is from version 4.
Helper methods below:
getTranslate = function (startAngle, endAngle, distance) {
var xTranslate, yTranslate;
var startQ = getQuadrant(startAngle);
var endQ = getQuadrant(endAngle);
//Assume there are 7 possibilities since last slice always ends at Tau or 12 o'clock when doing a d.endAngle
switch (true) {
case (startQ == 1 && endQ == 1):
xTranslate = distance * 0.5;
yTranslate = distance * -1.5;
break;
case (startQ == 1 && endQ == 4):
xTranslate = distance * 1.5;
yTranslate = distance * 0.5;
break;
case (startQ == 4 && endQ == 4):
xTranslate = distance * 0.5;
yTranslate = distance * 1.5;
break;
case (startQ == 4 && endQ == 3):
xTranslate = distance * -0.5;
yTranslate = distance * 1.5;
break;
case (startQ == 3 && endQ == 3):
xTranslate = distance * -1.5;
yTranslate = distance * 0.5;
break;
case (startQ == 3 && endQ == 2):
xTranslate = distance * -1.5;
yTranslate = distance * -0.5;
break;
case (startQ == 2 && endQ == 2):
xTranslate = distance * -0.5;
yTranslate = distance * -1.5;
break;
}
return "translate(" + xTranslate + "," + yTranslate + ")";
}
getQuadrant = function (angle) {
switch (true) {
case angle < (Math.PI * 0.5):
return 1;
break;
case angle >= (Math.PI * 1.5):
return 2;
break;
case ((Math.PI < angle) && angle <= (Math.PI * 1.5)):
return 3;
break;
default:
return 4;
}
}