I am using legends for d3 charts. Below is code -
var padding = { left:50, right:100, top:30, bottom:30 };
var legend = svgColorCode.selectAll(".legend")
.data(keyText) //.filter(x=>x!="Completed On Time")
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(" + (((keys.length - (i)) * -25) + (i == 0 ? 0 : (i == 1 ? 60 : (i == 2 ? 90 : (i == 3 ? 100 : 0))))) + "," + (height - 190) + ")"; })
.attr("fill", function (d, i) { return colors[i]; });
legend.append("rect")
.attr("x", (x, i) => (padding.top * 2 + labHeight * (i)) + 40)
.attr("width", 18)
.attr("height", 18)
.style("fill", function (d, i) { return colors[operationalKeys[i]]; })
legend.append("text")
.attr("x", (x, i) => (padding.top * 2 + labHeight * i) + 60)
.attr("y", 9)
.attr("font-size", "0.65rem")
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function (d) { console.log("d");console.log(d); return d.replace(" ","\n"); })
.call(wrap)
;
Here I have long texts for legends and small place to accomodate it. I want to wrap the text for legends.
For that I followed this SO answer and used wrap method. But seems wrap is not working in this case.
How can I wrap my text for legends in d3 js?
I am working on a project with d3.js where I want a visualisation to centre on the screen. My current solution works perfectly on desktop screens, even when resizing, but when I load the webpage on my iPhone screen, the visualisation is no longer centred. The target div for this visualisation is:
<div id="grades_circular" class="my_dataviz"></div>
The css to style is:
.my_dataviz {
display: flex;
justify-content: center;}
The JavaScript code for the visualisation is :
const grades_margin = {top: 30, right: 0, bottom: 70, left: 0},
grades_width = 460 - grades_margin.left - grades_margin.right,
grades_height = 460 - grades_margin.top - grades_margin.bottom;
innerRadius = 50,
outerRadius = Math.min(grades_width, grades_height) / 2;
const grades_svg = d3.select("#grades_circular")
.append("svg")
.attr("width", grades_width + grades_margin.left + grades_margin.right)
.attr("height", grades_height + grades_margin.top + grades_margin.bottom)
.append("g")
.attr("transform", `translate(${grades_width/2+grades_margin.left}, ${grades_height/2+grades_margin.top})`);
d3.csv("https://raw.githubusercontent.com/ben-austin27/ben-austin27.github.io/main/data/results.csv").then( function(grades_data) {
const grades_x = d3.scaleBand()
.range([0, 2 * Math.PI]) // X axis goes from 0 to 2pi = all around the circle. If I stop at 1Pi, it will be around a half circle
.align(0) // This does nothing
.domain(grades_data.map(d => d.module)); // The domain of the X axis is the list of states.
const grades_y = d3.scaleRadial()
.range([innerRadius, outerRadius]) // Domain will be define later.
.domain([40, 100]); // Domain of Y is from 0 to the max seen in the data
// Add the bars
bars = grades_svg.append("g")
.selectAll("path")
.data(grades_data)
.join("path")
.attr("fill", d => "#" + d.color )
.attr("d", d3.arc() // imagine your doing a part of a donut plot
.innerRadius(innerRadius)
.outerRadius(innerRadius+0.05)//d => grades_y(d['grade'])
.startAngle(d => grades_x(d.module))
.endAngle(d => grades_x(d.module) + grades_x.bandwidth())
.padAngle(0.05)
.padRadius(innerRadius))
modules = grades_svg.append("g")
.selectAll("g")
.data(grades_data)
.join("g")
.attr("text-anchor", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "end" : "start"; })
.attr("transform", function(d) { return "rotate(" + ((grades_x(d.module) + grades_x.bandwidth() / 2) * 180 / Math.PI - 90) + ")"+"translate(" + (innerRadius+10) + ",0)"; })//
.append("text")
.text(function(d){return(d.module)})
.attr("transform", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)"; })
.style("font-size", "11px")
.attr("alignment-baseline", "middle")
grades = grades_svg.append("g")
.selectAll("g")
.data(grades_data)
.join("g")
.attr("text-anchor", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "end" : "start"; })
.attr("transform", function(d) { return "rotate(" + ((grades_x(d.module) + grades_x.bandwidth() / 2) * 180 / Math.PI - 90) + ")"+"translate(" + (grades_y(d['grade'])+7) + ",0)"; })//
.append("text")
.text(function(d){return(d.grade)})
.attr("transform", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)"; })
.style("font-size", "11px")
.attr("alignment-baseline", "middle")
function update_bars() {
d3.selectAll("path")
.transition()
.ease(d3.easePolyInOut.exponent(3)) //https://observablehq.com/#d3/easing-animations
.duration(2000)
.attr("d", d3.arc() // imagine your doing a part of a donut plot
.innerRadius(innerRadius)
.outerRadius(d => grades_y(d['grade']))
.startAngle(d => grades_x(d.module))
.endAngle(d => grades_x(d.module) + grades_x.bandwidth())
.padAngle(0.05)
.padRadius(innerRadius))
// alter opactity of the labeling as well, after 2 seconds
}
var controller = new ScrollMagic.Controller();
new ScrollMagic.Scene({
// the element to scroll inside
triggerElement: '#grades_circular'
})
.on('enter', function(e) {
update_bars(e);
}).addTo(controller)
});
Thanks!
I'm trying to animate a circle being drained, with is working well. However I want a "marker" to follow the drain level, and I'm having difficulties understanding how to do this.
I already made a example embedded in this post, where the circle fill animates, and the number animates.
The final state can be seen here:
The issue is; that I want to place the "XXX used" marker based on the drain percentage. But I have had little luck figuring out how to achieve this.
So it has to move up and down, but also left and right depending on the percentage.
My code is as follows:
const usedAmount = 200;
const totalAmount = 400;
const radius = 120;
const valuePercent = (usedAmount / totalAmount) * 100;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
let grad = svg
.append("defs")
.append("linearGradient")
.attr("id", "grad")
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "0%");
grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');
let arc = d3.arc()
.innerRadius( radius - 40 )
.outerRadius( radius )
.endAngle(2 * Math.PI)
.startAngle(0 * Math.PI);
let cutout = svg.select('defs')
.append('clipPath')
.attr('clip-rule', 'evenodd')
.attr('id', 'cutout')
.append("path")
.attr('transform', 'translate(' + radius + ',' + radius + ')')
.attr('d', arc)
.attr('clip-rule', 'evenodd')
.attr('fill', '#ccc');
svg.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", radius)
.attr("clip-path", "url(#cutout)") // Apply the mask
.attr("fill", "url(#grad)");
grad
.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("y1", valuePercent + 1 + '%');
var marker = svg.append('g')
.attr('class', 'gauge__fillup__follow')
.attr("transform", "translate(" + 200 + "," + 50 + ")");
marker.append("rect")
.attr("x", 10)
.attr("y", 6)
.attr("fill", "#000")
.attr("width", 35)
.attr("height", 3);
marker.append('svg:text')
.attr('class', 'label')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 0)
.attr('dy', 12)
.attr('text-anchor', 'left')
.datum({textContent: ''})
.text(200)
.transition()
.duration(3000)
.delay(300)
.ease(d3.easeQuad)
.tween("text", function(d) {
const i = d3.interpolate(0, this.textContent, d);
return (t) => {
d3.select(this).text(Math.round(i(t)));
};
});
marker.append('svg:text')
.attr('class', 'label')
.attr('color', 'white')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 16)
.attr('dy', 12)
.attr('text-anchor', 'left')
.text('used');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>
Ended up with this result: https://codepen.io/Saturate/pen/BROzBe
You just have to translate the group down by the same amount:
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("transform", "translate(" + 220 + ","
+ ((usedAmount / totalAmount) * (radius * 2)) + ")");
Here is the demo:
const usedAmount = 200;
const totalAmount = 400;
const radius = 120;
const valuePercent = (usedAmount / totalAmount) * 100;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
let grad = svg
.append("defs")
.append("linearGradient")
.attr("id", "grad")
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "0%");
grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');
let arc = d3.arc()
.innerRadius(radius - 40)
.outerRadius(radius)
.endAngle(2 * Math.PI)
.startAngle(0 * Math.PI);
let cutout = svg.select('defs')
.append('clipPath')
.attr('clip-rule', 'evenodd')
.attr('id', 'cutout')
.append("path")
.attr('transform', 'translate(' + radius + ',' + radius + ')')
.attr('d', arc)
.attr('clip-rule', 'evenodd')
.attr('fill', '#ccc');
svg.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", radius)
.attr("clip-path", "url(#cutout)") // Apply the mask
.attr("fill", "url(#grad)");
grad
.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("y1", valuePercent + 1 + '%');
var marker = svg.append('g')
.attr('class', 'gauge__fillup__follow')
.attr("transform", "translate(" + 220 + "," + 0 + ")");
marker.append("rect")
.attr("x", 10)
.attr("y", 1)
.attr("fill", "#000")
.attr("width", 35)
.attr("height", 3);
marker.append('svg:text')
.attr('class', 'label')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', -6)
.attr('dy', 12)
.attr('text-anchor', 'left')
.datum({
textContent: ''
})
.text(200)
.transition()
.duration(3000)
.delay(300)
.ease(d3.easeQuad)
.tween("text", function(d) {
const i = d3.interpolate(0, this.textContent, d);
return (t) => {
d3.select(this).text(Math.round(i(t)));
};
});
marker.append('svg:text')
.attr('class', 'label')
.attr('color', 'white')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 10)
.attr('dy', 12)
.attr('text-anchor', 'left')
.text('used');
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("transform", "translate(" + 220 + "," + ((usedAmount / totalAmount )*(radius*2)) + ")");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>
EDIT: Here is the translation with attrTween, going from up to down and left to right:
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attrTween("transform", function() {
return function(t) {
return "translate(" + (radius * (1 + (Math.sin(Math.PI / 2 * t)))) + ","
+ (((usedAmount / totalAmount) * (radius * 2)) * (1 - (Math.cos(Math.PI / 2 * t)))) + ")"
}
});
Here is the demo:
const usedAmount = 200;
const totalAmount = 400;
const radius = 120;
const valuePercent = (usedAmount / totalAmount) * 100;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
let grad = svg
.append("defs")
.append("linearGradient")
.attr("id", "grad")
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "0%");
grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');
let arc = d3.arc()
.innerRadius(radius - 40)
.outerRadius(radius)
.endAngle(2 * Math.PI)
.startAngle(0 * Math.PI);
let cutout = svg.select('defs')
.append('clipPath')
.attr('clip-rule', 'evenodd')
.attr('id', 'cutout')
.append("path")
.attr('transform', 'translate(' + radius + ',' + radius + ')')
.attr('d', arc)
.attr('clip-rule', 'evenodd')
.attr('fill', '#ccc');
svg.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", radius)
.attr("clip-path", "url(#cutout)") // Apply the mask
.attr("fill", "url(#grad)");
grad
.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attr("y1", valuePercent + 1 + '%');
var marker = svg.append('g')
.attr('class', 'gauge__fillup__follow')
.attr("transform", "translate(" + (radius) + "," + 0 + ")");
marker.append("rect")
.attr("x", 10)
.attr("y", 1)
.attr("fill", "#000")
.attr("width", 35)
.attr("height", 3);
marker.append('svg:text')
.attr('class', 'label')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', -6)
.attr('dy', 12)
.attr('text-anchor', 'left')
.datum({
textContent: ''
})
.text(200)
.transition()
.duration(3000)
.delay(300)
.ease(d3.easeQuad)
.tween("text", function(d) {
const i = d3.interpolate(0, this.textContent, d);
return (t) => {
d3.select(this).text(Math.round(i(t)));
};
});
marker.append('svg:text')
.attr('class', 'label')
.attr('color', 'white')
.attr('z-index', '4')
.attr('x', 50)
.attr('y', 10)
.attr('dy', 12)
.attr('text-anchor', 'left')
.text('used');
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attrTween("transform", function() {
return function(t) {
return "translate(" + (radius * (1 + (Math.sin(Math.PI / 2 * t)))) + "," + (((usedAmount / totalAmount) * (radius * 2)) * (1 - (Math.cos(Math.PI / 2 * t)))) + ")"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>
Your question made me realize that i didn't understand trigonometry as well as i would like, so i took a stab at it. Once i learned the fundamentals of pi and sine, the answer became quite clear.
Great article i read that led me to my answer: https://betterexplained.com/articles/intuitive-understanding-of-sine-waves/
https://jsfiddle.net/zk0wsq5a/2/
marker.transition()
.duration(3000)
.ease(d3.easeQuad)
.delay(300)
.attrTween("transform", function() {
return function(t) {
let distance = Math.PI * t * (usedAmount / totalAmount)
let x = radius + (radius * Math.sin(distance))
let y = radius * (1-Math.cos(distance))
return `translate(${x} ,${y})`
}
});
I would like to extend this D3.js sunburst example in the way that it can display images rather than text. http://www.jasondavies.com/coffee-wheel/
The interesting part of the js example code is following:
d3.json("wheel.json", function(error, json) {
var nodes = partition.nodes({children: json});
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.on("click", click);
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d) {
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", click);
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; });
The question is how I can replace the text with an image in this scenario.
Thank you very much.
var image = vis.selectAll("image").data(nodes);
var imageEnter = image.enter().append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("width", 16)
.attr("height", 16)
.attr("x", function (d) {
return 33; // calculate x
}).attr("y", function (d) {
return 33; // calculate y
});
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
force-directed images and labels: http://bl.ocks.org/mbostock/950642
I have this D3 radial tree graph and what I need is the images to not being rotated, but appear straight together it's corresponding blue circle. Here is the code working in Codepen and I copy it here:
var radius = 960 / 2;
var tree = d3.layout.tree()
.size([360, radius - 120])
.separation(function (a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var diagonal = d3.svg.diagonal.radial()
.projection(function (d) { return [d.y, d.x / 180 * Math.PI]; })
var vis = d3.select('#graph').append('svg:svg')
.attr('width', radius * 2)
.attr('height', radius * 2 - 150)
.append('g')
.attr('transform', 'translate(' + radius + ',' + radius + ')');
d3.json('flare2.json', function (json) {
var nodes = tree.nodes(json);
var link = vis.selectAll('path.link')
.data(tree.links(nodes))
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal);
var node = vis.selectAll('g.node')
.data(nodes)
.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; });
node.append('circle')
.attr('r', 4.5);
node.append('image')
.attr('xlink:href', 'img/avatar.40.gif')
.attr('width', 40)
.attr('height', 40)
.attr('x', 10);
node.append('image')
.attr('xlink:href', 'img/avatar.41.gif')
.attr('width', 40)
.attr('height', 40)
.attr('x', 50 );
});
Your nodes are rotated
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
And your images are appended to your nodes
node.append('image')
So you need to rotate the images back
.attr("transform", function(d) { return "rotate(" + (90 - d.x) + ")"; })
I'm not sure exactly how you want to position them, but you need to translate them on both x and y.
See a working example: http://codepen.io/anon/pen/qiCeG