d3 v4 - can't scroll vertically on svg containing bar chart - d3.js

EDIT - added a code snippet (bottom)
The amount of rows are dynamic, so I don't know what the total height needed is. I have initiated the height as 800px, but the list can grow longer. I have a container div with an overflow-y:scroll value, but that did not solve the issue. D3 code and css below.
There is a scroll bar, but if I scroll down no more rows show.
The chart itself only measures about 644px (in Photoshop), so I'm not sure why it is cutting off where it does, or if that is a separate issue.
$(document).ready(function() {
let randProb = () => {
let min = 0;
let max = 1;
return (Math.random() * (max - min) + min).toFixed(4)
};
var data = [];
let initData = () => {
for (let i=0; i<86; ++i) {
data.push({
"attribute":`ATTRIBUTE_${i}`,
"probabilityOfMastery":randProb()
});
}
// console.log('data initialized', data);
};
initData();
// set the dimensions and margins of the graph
var margin = {top: 0, right: 20, bottom: 0, left: 160},
width = 1220 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
// set the ranges
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
var x = d3.scaleLinear()
.range([0, width]);
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select(".analyticsContainer").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// format the data
data.forEach(function(d) {
d.probabilityOfMastery = +d.probabilityOfMastery;
});
// Scale the range of the data in the domains
x.domain([0, 1])
y.domain(data.map(function(d) { return d.attribute; }));
//y.domain([0, d3.max(data, function(d) { return d.probabilityOfMastery; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("width", 0 )
.attr("y", function(d) { return y(d.attribute); })
.transition()
.duration(1000)
//- .delay(function(d, i) {
//- return i * 60
//- })
.attr("height", y.bandwidth())
.attr("width", function(d) {return x(d.probabilityOfMastery); } )
;
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
svg.append("g")
.call(d3.axisLeft(y));
});
.analyticsWrapper {
height:calc(100vh - 120px);
max-height:100%;
width:100vw;
max-width: 100%;
display:flex;
flex-direction: column;
justify-content: center;
align-items: center;
border:1px solid red
}
.analyticsContainer {
font-size: 40px;
width:100%;
height:100%;
justify-content: center;
overflow-y: scroll;
padding:3.5vw;
height:800px;
}
svg {
width:90vw;
max-height: 100%;
max-width: 100%;
overflow: scroll;
}
.bar {
fill: #11b9b9;
}
.yAxis text {
font-weight: 700;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<body>
<div class="analyticsWrapper">
<div class="analyticsContainer">
</div>
</div>
</body>

Related

Switch between datasets in a d3 barchart

I need to switch between two datasets from the same csv file,
I have a small dataset of predicted v actual league positions from last years English premier League.
dataset1 = Actual League Position
dataset2 = Predicted league Position
My render data function does not seem to be working as my second dataset (i.e. Predicted) is not displayed when i click the radio button - see attached pic - only the actual position dataset is being displayed
I've provided a link to my code: github link to my code
Copy of relevant code below
function render(data){
var bars = g.selectAll("bar")
.data(data)
//enter
bars.enter().append("rect")
.attr("class", "bar")
.attr("x1", 0)
.attr("x2", 0)
.attr("y", function(d) { return y(d.Team); })
.attr("height", y.bandwidth())
.attr("width", function(d) { return x2(d.Predicted_Finish); })
.style("fill", "#a02f2b")
//exit
bars.exit()
.transition()
.duration(300)
.remove()
}
function init()
{
//setup the svg
var svg = d3.select("svg"),
margin = {top: 20, right: 10, bottom: 65, left: 110}//position of axes
frame
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
//setup our ui
d3.select("#Actual")
.on("click", function(d,i) {
console.log(Actual);
render(Actual)
})
d3.select("#Predicted")
.on("click", function(d,i) {
console.log(Predicted);
render(Predicted)
})
render(Actual)
}
init();
This code can be significantly simplified.
First, couple of format problems:
Improperly placed <body>, and no </body> or </html>
<form> around your buttons is not needed (it's causing a submit)
Second, your code can be restructured. You don't need a full enter, update, exit pattern here since your data doesn't really change. You just want to toggle between two variables in your single dataset. With that in mind, here's how it ends up looking:
<head>
<meta charset="utf-8">
<title>CSS Example</title>
<link href="https://fonts.googleapis.com/css?family=Passion+One" rel="stylesheet">
<style>
.my-text {
font-size: 1.95em;
font-family: 'Passion One', cursive;
fill: #000000;
}
.bar {
fill: #71df3e;
}
.bar:hover {
fill: white;
}
.axis--x path {
display: none;
}
body {
background-color: orange;
}
.axisx text {
fill: black;
}
.axisy text {
fill: black;
}
.axisx line {
stroke: black;
}
.axisy line {
stroke: black;
}
.axisx path {
stroke: black;
}
.axisy path {
stroke: black;
}
</style>
</head>
<body>
<div id="buttons">
<button id="Actual">Actual</button>
<button id="Predicted">Predicted</button>
</div>
<svg width="1200" height="500">
<text class="my-text" x="330" y="20">EPL PREDICTIONS VERSUS REALITY</text>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//define svg canvas
var svg = d3.select("svg"),
margin = {
top: 20,
right: 10,
bottom: 65,
left: 110
} //position of axes frame
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
//next our graph
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data.csv", function(d) {
d.Actual_Finish = +d.Actual_Finish;
d.Predicted_Finish = +d.Predicted_Finish;
return d;
}, function(error, data)
{
if (error) throw error;
data = data;
//define our x and y axis scales and variables, remembering we have 2 x variables
x1 = d3.scaleLinear().rangeRound([800, 1])
//experiment with the max numbers to bring the x scale within the margin
.domain([0, d3.max(data, function(d) {
return d.Actual_Finish;
})]);
y = d3.scaleBand().rangeRound([0, height])
.padding(0.5).domain(data.map(function(d) {
return d.Team;
}));
//append x axis to svg
g.append("g")
.style("font", "14px arial") //font and size of x axis labels
.attr("class", "axisx")
.call(d3.axisBottom(x1).ticks(20))
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x1))
.append("text")
.attr("x", 450) //position of x1 axis label: x co-ordinate
.attr("y", 35) //position of x axis label: y co-ordinate
.attr("dx", "1.0em") //also position of X axis label
.attr("text-anchor", "middle")
.text("League Position");
//append y axis to svg
g.append("g") //append y axis to svg
.style("font", "14px arial") //font and size of y axis labels
.attr("class", "axisy")
.call(d3.axisLeft(y).ticks(20)) //no. of ticks on y axis
.append("text")
.attr("transform", "rotate(-360)") //rotate the axis label text by -90
.attr("y", -20) //position of y axis label
.attr("dy", "1.0em") //sets the unit amount the y axis label moves above
.attr("text-anchor", "end")
.text("Team");
var bars = g.selectAll('.bar')
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x1", 0)
.attr("x2", 0)
.attr("height", y.bandwidth())
.style("fill", "#a02f2b");
render('Actual_Finish')
function render(which) {
bars.attr("y", function(d) {
return y(d.Team);
})
.attr("width", function(d) {
return x1(d[which]);
});
}
d3.select("#Actual")
.on("click", function(d, i) {
render('Actual_Finish')
});
d3.select("#Predicted")
.on("click", function(d, i) {
render('Predicted_Finish')
});
});
</script>
</body>
</html>
Running code can be seen here.

calculating start position and width of a bar on 24 hour time scale(D3 JS)

I want make a bar graph where i can show a particular time period represented by bars.
$(document).ready(function() {
render_chart();
});
function render_chart() {
var dataset = {
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
],
"gates": [
"Test-Gate"
],
"operators": [
"Operator1",
"Operator2",
"Operator3",
],
"layers": [
[{
"fromHours": "14:20:00",
"toHours": "23:00:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "08:30:00",
"toHours": "11:20:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "16:00:00",
"toHours": "18:00:00",
"gate": "Test-Gate"
}]
]
};
n = dataset["operators"].length;
var today = new Date();
today.setHours(0, 0, 0, 0);
todayMillis = today.getTime();
var layersData = dataset["layers"];
console.log("BEFORE", layersData[0][0]);
layersData.forEach(function(layer) {
layer.forEach(function(innerLayer) {
var fromHourParts = innerLayer.fromHours.split(/:/);
var toHourParts = innerLayer.toHours.split(/:/);
innerLayer.fromHours = new Date();
innerLayer.fromHours.setTime(todayMillis + getTimePeriodMillis(fromHourParts));
innerLayer.toHours = new Date();
innerLayer.toHours.setTime(todayMillis + getTimePeriodMillis(toHourParts));
})
});
console.log("AFTER", dataset["layers"][0][0]);
function getTimePeriodMillis(parts) {
return (parseInt(parts[0], 10) * 60 * 60 * 1000) +
(parseInt(parts[1], 10) * 60 * 1000) +
(parseInt(parts[2], 10) * 1000);
}
function appendExtraZeroToSingleValues(inputValue) {
if (inputValue === 0) {
return inputValue + "0";
}
return inputValue;
}
var parseTime = d3.timeParse("%H:%M");
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 100
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("#groupchart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tomorrow = new Date();
tomorrow.setHours(0, 0, 0, 0);
tomorrow.setDate(today.getDate());
var xScale = d3.scaleTime()
.domain([new Date(), new Date()])
.nice(d3.timeDay, 1)
.range([0, width - margin.left]);
var yScale = d3.scaleBand()
.domain(dataset["gates"])
.rangeRound([0, height])
.padding(.08);
var xAxis = d3.axisBottom(xScale)
.ticks(24)
.tickSize(-height)
.tickFormat(d3.timeFormat("%H"));
console.log("X-DOMAIN", xScale.domain());
console.log("X-RANGE", xScale.range());
var yAxis = d3.axisLeft(yScale)
.tickSize(-(width - margin.left))
.tickPadding(5);
var layer = svg.selectAll(".layer")
.data(dataset["layers"])
.enter().append("g")
.attr("class", "layer");
var rect = layer.selectAll("rect")
.data(function(d, i) {
d.map(function(b) {
b.colorIndex = i;
return b;
});
return d;
})
.enter()
.append("rect")
.transition()
.duration(500)
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d, i, j) {
var k = Array.prototype.indexOf.call(j[i].parentNode.parentNode.childNodes, j[i].parentNode);
return yScale(d.gate) + yScale.bandwidth() / n * k;
})
.attr("height", yScale.bandwidth() / n)
.transition()
.attr("x", function(d) {
console.log(xScale(d.fromHours));
return xScale(d.fromHours);
})
.attr("width", function(d) {
console.log(xScale(d.toHours - d.fromHours));
return xScale(Math.abs(d.toHours - d.fromHours / 36e5));
})
.attr("class", "bar")
.style("fill", function(d) {
return dataset["colors"][d.colorIndex];
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.select("g")
.attr("class", "y axis")
.call(yAxis);
var legend = svg.append("g")
.attr("class", "legend");
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * 120) + (width / 3);
})
.attr("y", width / 2.8)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i) {
return dataset["colors"][i];
})
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("text")
.attr("x", function(d, i) {
return (i * 120) + (width / 3) + 12;
})
.attr("y", (width / 2.8) + 10)
.text(function(d) {
return d;
});
var tooltip = d3.select("body")
.append('div')
.attr('class', 'tooltip');
tooltip.append('div')
.attr('class', 'gate');
tooltip.append('div')
.attr('class', 'tempRange');
svg.selectAll("rect")
.on('mouseover', function(d, i) {
if (!d.gate) return null;
tooltip.select('.gate').html("<b>" + dataset["operators"][i] + "</b>");
tooltip.select('.tempRange').html(appendExtraZeroToSingleValues(d.fromHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.fromHours.getMinutes()) + " Hours to " +
appendExtraZeroToSingleValues(d.toHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.toHours.getMinutes()) + " Hours");
tooltip.style('display', 'block');
tooltip.style('opacity', 2);
})
.on('mousemove', function(d) {
if (!d.gate) return null;
tooltip.style('top', (d3.event.layerY + 10) + 'px')
.style('left', (d3.event.layerX - 25) + 'px');
})
.on('mouseout', function() {
tooltip.style('display', 'none');
tooltip.style('opacity', 0);
});
}
.axis .tick line {
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.2);
}
.axis path,
.axis line {
fill: none;
font: 10px sans-serif;
stroke: #000;
shape-rendering: crispEdges;
}
.legend {
padding: 5px;
font-size: 15px;
font-family: 'Roboto', sans-serif;
background: yellow;
box-shadow: 2px 2px 1px #888;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #333;
font-size: 12px;
left: 130px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
z-index: 10;
display: block;
opacity: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Bar Graph</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="groupchart" class="chart"></div>
</body>
</html>
Tried like above, but cannot figure out how to do it properly. I messed up and bars does not end inside the graph somehow. The x axis domain is all messed up and also the width of the rects. It works fine if i take another scale. But in time-scale i cannot make it to work.
What i am doing wrong?
Instead of passing the difference of the values to the scale...
scale(a - b)
...pass each value to the scale and subtract them:
scale(a) - scale(b).
In your case:
return xScale(Math.abs(d.toHours)) - xScale(Math.abs(d.fromHours));
Here is the code with that change:
$(document).ready(function() {
render_chart();
});
function render_chart() {
var dataset = {
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
],
"gates": [
"Test-Gate"
],
"operators": [
"Operator1",
"Operator2",
"Operator3",
],
"layers": [
[{
"fromHours": "14:20:00",
"toHours": "23:00:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "08:30:00",
"toHours": "11:20:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "16:00:00",
"toHours": "18:00:00",
"gate": "Test-Gate"
}]
]
};
n = dataset["operators"].length;
var today = new Date();
today.setHours(0, 0, 0, 0);
todayMillis = today.getTime();
var layersData = dataset["layers"];
console.log("BEFORE", layersData[0][0]);
layersData.forEach(function(layer) {
layer.forEach(function(innerLayer) {
var fromHourParts = innerLayer.fromHours.split(/:/);
var toHourParts = innerLayer.toHours.split(/:/);
innerLayer.fromHours = new Date();
innerLayer.fromHours.setTime(todayMillis + getTimePeriodMillis(fromHourParts));
innerLayer.toHours = new Date();
innerLayer.toHours.setTime(todayMillis + getTimePeriodMillis(toHourParts));
})
});
console.log("AFTER", dataset["layers"][0][0]);
function getTimePeriodMillis(parts) {
return (parseInt(parts[0], 10) * 60 * 60 * 1000) +
(parseInt(parts[1], 10) * 60 * 1000) +
(parseInt(parts[2], 10) * 1000);
}
function appendExtraZeroToSingleValues(inputValue) {
if (inputValue === 0) {
return inputValue + "0";
}
return inputValue;
}
var parseTime = d3.timeParse("%H:%M");
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 100
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("#groupchart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tomorrow = new Date();
tomorrow.setHours(0, 0, 0, 0);
tomorrow.setDate(today.getDate());
var xScale = d3.scaleTime()
.domain([new Date(), new Date()])
.nice(d3.timeDay, 1)
.range([0, width - margin.left]);
var yScale = d3.scaleBand()
.domain(dataset["gates"])
.rangeRound([0, height])
.padding(.08);
var xAxis = d3.axisBottom(xScale)
.ticks(24)
.tickSize(-height)
.tickFormat(d3.timeFormat("%H"));
console.log("X-DOMAIN", xScale.domain());
console.log("X-RANGE", xScale.range());
var yAxis = d3.axisLeft(yScale)
.tickSize(-(width - margin.left))
.tickPadding(5);
var layer = svg.selectAll(".layer")
.data(dataset["layers"])
.enter().append("g")
.attr("class", "layer");
var rect = layer.selectAll("rect")
.data(function(d, i) {
d.map(function(b) {
b.colorIndex = i;
return b;
});
return d;
})
.enter()
.append("rect")
.transition()
.duration(500)
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d, i, j) {
var k = Array.prototype.indexOf.call(j[i].parentNode.parentNode.childNodes, j[i].parentNode);
return yScale(d.gate) + yScale.bandwidth() / n * k;
})
.attr("height", yScale.bandwidth() / n)
.transition()
.attr("x", function(d) {
console.log(xScale(d.fromHours));
return xScale(d.fromHours);
})
.attr("width", function(d) {
console.log(xScale(d.toHours - d.fromHours));
return xScale(Math.abs(d.toHours)) - xScale(Math.abs(d.fromHours));
})
.attr("class", "bar")
.style("fill", function(d) {
return dataset["colors"][d.colorIndex];
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.select("g")
.attr("class", "y axis")
.call(yAxis);
var legend = svg.append("g")
.attr("class", "legend");
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * 120) + (width / 3);
})
.attr("y", width / 2.8)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i) {
return dataset["colors"][i];
})
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("text")
.attr("x", function(d, i) {
return (i * 120) + (width / 3) + 12;
})
.attr("y", (width / 2.8) + 10)
.text(function(d) {
return d;
});
var tooltip = d3.select("body")
.append('div')
.attr('class', 'tooltip');
tooltip.append('div')
.attr('class', 'gate');
tooltip.append('div')
.attr('class', 'tempRange');
svg.selectAll("rect")
.on('mouseover', function(d, i) {
if (!d.gate) return null;
tooltip.select('.gate').html("<b>" + dataset["operators"][i] + "</b>");
tooltip.select('.tempRange').html(appendExtraZeroToSingleValues(d.fromHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.fromHours.getMinutes()) + " Hours to " +
appendExtraZeroToSingleValues(d.toHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.toHours.getMinutes()) + " Hours");
tooltip.style('display', 'block');
tooltip.style('opacity', 2);
})
.on('mousemove', function(d) {
if (!d.gate) return null;
tooltip.style('top', (d3.event.layerY + 10) + 'px')
.style('left', (d3.event.layerX - 25) + 'px');
})
.on('mouseout', function() {
tooltip.style('display', 'none');
tooltip.style('opacity', 0);
});
}
.axis .tick line {
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.2);
}
.axis path,
.axis line {
fill: none;
font: 10px sans-serif;
stroke: #000;
shape-rendering: crispEdges;
}
.legend {
padding: 5px;
font-size: 15px;
font-family: 'Roboto', sans-serif;
background: yellow;
box-shadow: 2px 2px 1px #888;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #333;
font-size: 12px;
left: 130px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
z-index: 10;
display: block;
opacity: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Bar Graph</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="groupchart" class="chart"></div>
</body>
</html>

Format the legend of my donut chart

I try to make this chart with D3js:
I've no idea how to make my "%" small like a <sup> tag (image). I tried to remove the suffix from the format()... it's give me a float.
How can I do that? Do I need to substring from my current text and add a new text? What's the better way?
Code :
makeDonut(1, 45);
function makeDonut(id, percent) {
var duration = 2000,
transition = 200,
width = 180,
height = 180;
var dataset = {
lower: calcPercent(0),
upper: calcPercent(percent)
},
radius = Math.min(width, height) / 3,
pie = d3.pie().sort(null),
format = d3.format(".0%");
var arc = d3.arc()
.innerRadius(radius * .8)
.outerRadius(radius / .7);
var svg = d3.select("#graph" + id).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.lower))
.enter().append("path")
.attr("class", function(d, i) {
return "g" + id + "-color" + i
})
.attr("d", arc)
.each(function(d) {
this._current = d;
});
var text = svg.append("text")
.attr("class", "g" + id + "-text")
.attr("text-anchor", "middle")
.attr("dy", ".3em");
var progress = 0;
var timeout = setTimeout(function() {
clearTimeout(timeout);
path = path.data(pie(dataset.upper));
path.transition().duration(duration).attrTween("d", function(a) {
var i = d3.interpolate(this._current, a);
var i2 = d3.interpolate(progress, percent)
this._current = i(0);
return function(t) {
text.text(format(i2(t) / 100));
return arc(i(t));
};
});
}, 200);
};
function calcPercent(percent) {
return [percent, 100 - percent];
};
.g1-color0 {
fill: #5ca747;
}
.g1-color1 {
fill: #dcdcdc;
}
.g1-text {
font-family: 'Roboto Condensed', sans-serif;
fill: #5ca747;
font-size: 50px;
font-weight: bold;
}
.graph1 span {
font-family: 'Roboto Condensed', sans-serif;
text-transform: uppercase;
font-size: 26px;
color: #5ca747;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="graph1 col-sm-4 text-center">
<div id="graph1"></div>
<span>3 projecten klaar</span>
</div>
I created a JSFiffle
An easy (and lazy) solution is just creating another text selection for the % symbol:
var percentText = svg.append("text")
.attr("class", "g" + id + "-percent")
.attr("text-anchor", "middle")
.attr("dx", "1.2em")
.attr("dy", "-0.3em")
.text("%");
Here is the demo:
makeDonut(1, 45);
function makeDonut(id, percent) {
var duration = 2000,
transition = 200,
width = 180,
height = 180;
var dataset = {
lower: calcPercent(0),
upper: calcPercent(percent)
},
radius = Math.min(width, height) / 3,
pie = d3.pie().sort(null);
var arc = d3.arc()
.innerRadius(radius * .8)
.outerRadius(radius / .7);
var svg = d3.select("#graph" + id).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.lower))
.enter().append("path")
.attr("class", function(d, i) {
return "g" + id + "-color" + i
})
.attr("d", arc)
.each(function(d) {
this._current = d;
});
var text = svg.append("text")
.attr("class", "g" + id + "-text")
.attr("text-anchor", "middle")
.attr("dy", ".3em");
var percentText = svg.append("text")
.attr("class", "g" + id + "-percent")
.attr("text-anchor", "middle")
.attr("dx", "1.2em")
.attr("dy", "-0.3em")
.text("%");
var progress = 0;
var timeout = setTimeout(function() {
clearTimeout(timeout);
path = path.data(pie(dataset.upper));
path.transition().duration(duration).attrTween("d", function(a) {
var i = d3.interpolate(this._current, a);
var i2 = d3.interpolate(progress, percent)
this._current = i(0);
return function(t) {
if(~~(i2(t))>=10){percentText.attr("dx", "1.8em")}
text.text(~~(i2(t)));
return arc(i(t));
};
});
}, 200);
};
function calcPercent(percent) {
return [percent, 100 - percent];
};
.g1-color0 {
fill: #5ca747;
}
.g1-color1 {
fill: #dcdcdc;
}
.g1-text {
font-family: 'Roboto Condensed', sans-serif;
fill: #5ca747;
font-size: 50px;
font-weight: bold;
}
.g1-percent {
font-family: 'Roboto Condensed', sans-serif;
fill: #5ca747;
font-size: 20px;
font-weight: bold;
}
.graph1 span {
font-family: 'Roboto Condensed', sans-serif;
text-transform: uppercase;
font-size: 26px;
color: #5ca747;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="graph1 col-sm-4 text-center">
<div id="graph1"></div>
<span>3 projecten klaar</span>
</div>

sortable bar chart in d3.js

I'm a newbie in d3.js and I'm trying to create a sortable bar chart. I'm following the example but I'm unable to get the same result. The bars move but the labels do not. I'm not sure what I'm doing wrong. Any guidance is hugely appreciated. Thanks
<label><input id="sort" type="checkbox"> Sort values</label>
<div id="check"></div>
<script src="~/Scripts/d3-tip.js"></script>
<script>
debugger;
var data=#Html.Raw(#JsonConvert.SerializeObject(Model));
var margin = {
top: 20,
right: 20,
bottom: 50,
left: 40
},
width = 250 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x=d3.scaleBand()
.rangeRound([0, width])
.padding(0.1);
var y=d3.scaleLinear().range([height,0]);
var chart = d3.select("#check")
.append("svg:svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tip=d3.tip()
.attr("class","d3-tip")
.offset([-10,0])
.html(function(d){
return "<strong>Rec Items:</strong> <span style='color:red'>" + d.TotalRecItemsSum + "</span>"
})
chart.call(tip)
x.domain(data.map(function(d){
return d.AppClientName;
})) //x domain is a map of all client names
y.domain([0,d3.max(data,function(d){return d.TotalRecItemsSum;})])
//y is range from maximum(count) value in array until 0
chart.selectAll(".bar").data(data).enter().append("svg:rect")
.attr("x",function(d){
return x(d.AppClientName);
})
.attr("class", "bar")
.attr("width",x.bandwidth())
.attr("y",function (d){
return y(d.TotalRecItemsSum);})
.attr("height",function(d){
return height-y(d.TotalRecItemsSum);})
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
var xAxis=d3.axisBottom().scale(x);
var yAxis=d3.axisLeft().scale(y);
chart.append("g")
.attr("class","y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("TotalRecItemSum");
chart.append("g").attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform","rotate(90)")//In some cases the labels will overlap
//so I want to rotate the label's so they are vertical
.attr("x", 0) //After that some finetuning to align everything the right way:
.attr("y", -6)
.attr("dx", ".6em")
.attr("class","x axis")
.attr("class","text")
.style("text-anchor", "start")
d3.select("#sort").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("#sort").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.TotalRecItemsSum - a.TotalRecItemsSum; }
: function(a, b) { return d3.ascending(a.AppClientName, b.AppClientName); })
.map(function(d) { return d.AppClientName; }))
.copy();
chart.selectAll(".bar")
.sort(function(a, b) { return x0(a.AppClientName) - x0(b.AppClientName); });
var transition = chart.transition().duration(750),
delay = function(d, i) { return i * 100; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.AppClientName); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.chart rect {
stroke: white;
fill: orange;
}
.bar:hover {
fill: orangered;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
.x.axis path {
display: none;
}
I'm using d3 version 4. And in the above code, my x-axis has the client names and y-axis is a numerical value. So the bars are arranged in ascending order on checking the checkbox, only the labels do not move. Also, another issue is the checkbox gets checked automatically after a couple of seconds, that's happening because of var timeout, so should I be putting timeout inside function change?
Pls ask me any questions if my explanation is unclear, I always have trouble articulating. Thanks.

Legend not aligned properly in d3 responsive chart

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Testing Pie Chart</title>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<style type="text/css">
#container {
margin: 20px;
}
#chart {
position: absolute;
background-color: #eee;
}
#chart legend{
position: absolute;
margin: 100px;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #900C3F;
display: inline-block;
font-size: 12px;
left: 600px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
width: 150px;
z-index: 10;
opacity: 1;
}
rect {
stroke-width: 2;
}
path {
stroke: #ffffff;
stroke-width: 0.5;
}
div.tooltip {
position: absolute;
z-index: 999;
padding: 10px;
background: #f4f4f4;
border: 0px;
border-radius: 3px;
pointer-events: none;
font-size: 11px;
color: #080808;
line-height: 16px;
border: 1px solid #d4d4d4;
}
</style>
</head>
<body>
<div id="container">
<svg id="chart" width="600" height="300" viewBox="0 0 600 300" perserveAspectRatio="xMinYMid">
<div id="toolTip" class="tooltip" style="opacity: 0;"></div>
<script type="text/javascript">
var div = d3.select("#toolTip");
var data = [
{"IP":"192.168.12.1", "count":20},
{"IP":"76.09.45.34", "count":40},
{"IP":"34.91.23.76", "count":80},
{"IP":"192.168.19.32", "count":16},
{"IP":"192.168.10.89", "count":50},
{"IP":"192.178.34.07", "count":18},
{"IP":"192.168.12.98", "count":30}];
var width = 300,
height = 300;
var margin = {top: 15, right: 15, bottom: 20, left: 40},
radius = Math.min(width, height) / 2 - 10;
var legendRectSize = 18,
legendSpacing = 4;
var color = d3.scale.category20b();
var arc = d3.svg.arc()
.outerRadius(radius);
var arcOver = d3.svg.arc()
.outerRadius(radius + 5);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.count; });
var labelArc = d3.svg.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var svg = d3.select("#chart").append("svg")
.datum(data)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var arcs = svg.selectAll(".arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
var arcs2 = svg.selectAll(".arc2")
.data(pie)
.enter().append("g")
.attr("class", "arc2");
arcs.append("path")
.attr("fill", function(d, i) { return color(i); })
.on("mouseover", function(d) {
var htmlMsg="";
div.transition()
.style("opacity",0.9);
var total = d3.sum(data.map(function(d) {
return d.count;
}));
var percent = Math.round(1000 * d.data.count / total) / 10;
div.html(
"IP :"+ d.data.IP +""+"<br/>"+
"Count : " + d.data.count +"<br/>" +
"Percent: " + percent + '%'+ htmlMsg)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY) + "px");
svg.selectAll("path").sort(function (a, b) {
if (a != d) return -1;
else return 1;
});
var endAngle = d.endAngle + 0.1;
var startAngle = d.startAngle - 0.1;
var arcOver = d3.svg.arc()
.outerRadius(radius + 10).endAngle(endAngle).startAngle(startAngle);
d3.select(this)
.attr("stroke","white")
.transition()
.ease("bounce")
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
d3.select(this).transition()
.attr("d", arc)
.attr("stroke","none");
})
.transition()
.ease("bounce")
.duration(2000)
.attrTween("d", tweenPie);
function tweenPie(b) {
b.innerRadius = 0;
var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
return function(t) { return arc(i(t)); };
}
var k=0;
arcs2.append("text")
.transition()
.ease("elastic")
.duration(2000)
.delay(function (d, i) {
return i * 250;
})
.attr("x","6")
.attr("dy", ".35em")
.text(function(d) { if(d.data.count >0){ k = k+1; return d.data.count;} })
.attr("transform", function(d) { if (k >1){return "translate(" + labelArc.centroid(d) + ") rotate(" + angle(d) + ")";} else{return "rotate(-360)";} })
.attr("font-size", "10px");
function type(d) {
d.count = +d.count;
return d;
}
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
var legend = d3.select("#chart")
.append("svg")
.attr("class", "legend")
.attr("width", radius+50)
.attr("height", radius * 2)
.selectAll("g")
.data(color.domain())
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.data(data)
.text(function(d,i) { return d.IP; });
</script>
</svg>
</div>
<script type="text/javascript">
var chart = $("#chart"),
aspect = chart.width() / chart.height(),
container = chart.parent();
$(window).on("resize", function() {
var targetWidth = container.width();
chart.attr("width", targetWidth);
chart.attr("height", Math.round(targetWidth / aspect));
}).trigger("resize");
</script>
</script>
</body>
</html>
Hello, I am new to D3.js. I am facing issues when building a responsive pie chart. The chart is responsive with tooltips also attached to it. But when I try to attach the legend to the chart the legend is overlapping the chart. Please help me. I am stuck. How can I place the legend beside my pie chart. Here are my codes that I have tried so far.
Thank for any help in advance.
This could be resolved in 3 ways:
Width of chart:
Here, the pie chart is too big for the given dimensions. Hence, the legends overlap. You could try changing the width: 300 to width: 700
Radius of circle:
If you can't change the width, you could reduce the radius of the pie chart. Currently, it selects minimum of the width/height and divides by two subtracting 10 for margin. radius = Math.min(width, height) / 2 You could additionally specify radius = Math.min(width, height) / 2 - 50 to further reduce the radius by pixels.
Transform of the center:
Or you can also move the center of the pie chart further right. Currently, it is located halfway of the dimensions. .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"). You could make it say to be at 3/4 of the width and 40% of height .attr("transform", "translate(" + width * 3 / 4 + "," + height * 2 / 5 + ")")
I have utilised all three of the ways in your snippet:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Testing Pie Chart</title>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<style type="text/css">
#container {
margin: 20px;
}
#chart {
position: absolute;
background-color: #eee;
}
#chart legend{
position: absolute;
margin: 100px;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #900C3F;
display: inline-block;
font-size: 12px;
left: 600px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
width: 150px;
z-index: 10;
opacity: 1;
}
rect {
stroke-width: 2;
}
path {
stroke: #ffffff;
stroke-width: 0.5;
}
div.tooltip {
position: absolute;
z-index: 999;
padding: 10px;
background: #f4f4f4;
border: 0px;
border-radius: 3px;
pointer-events: none;
font-size: 11px;
color: #080808;
line-height: 16px;
border: 1px solid #d4d4d4;
}
</style>
</head>
<body>
<div id="container">
<svg id="chart" width="600" height="300" viewBox="0 0 600 300" perserveAspectRatio="xMinYMid">
<div id="toolTip" class="tooltip" style="opacity: 0;"></div>
<script type="text/javascript">
var div = d3.select("#toolTip");
var data = [
{"IP":"192.168.12.1", "count":20},
{"IP":"76.09.45.34", "count":40},
{"IP":"34.91.23.76", "count":80},
{"IP":"192.168.19.32", "count":16},
{"IP":"192.168.10.89", "count":50},
{"IP":"192.178.34.07", "count":18},
{"IP":"192.168.12.98", "count":30}];
var width = 400,
height = 300;
var margin = {top: 15, right: 15, bottom: 20, left: 40},
radius = Math.min(width, height) / 2 - 50;
var legendRectSize = 18,
legendSpacing = 4;
var color = d3.scale.category20b();
var arc = d3.svg.arc()
.outerRadius(radius);
var arcOver = d3.svg.arc()
.outerRadius(radius + 5);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.count; });
var labelArc = d3.svg.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var svg = d3.select("#chart").append("svg")
.datum(data)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width * 3 / 4 + "," + height * 2/ 5 + ")");
var arcs = svg.selectAll(".arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
var arcs2 = svg.selectAll(".arc2")
.data(pie)
.enter().append("g")
.attr("class", "arc2");
arcs.append("path")
.attr("fill", function(d, i) { return color(i); })
.on("mouseover", function(d) {
var htmlMsg="";
div.transition()
.style("opacity",0.9);
var total = d3.sum(data.map(function(d) {
return d.count;
}));
var percent = Math.round(1000 * d.data.count / total) / 10;
div.html(
"IP :"+ d.data.IP +""+"<br/>"+
"Count : " + d.data.count +"<br/>" +
"Percent: " + percent + '%'+ htmlMsg)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY) + "px");
svg.selectAll("path").sort(function (a, b) {
if (a != d) return -1;
else return 1;
});
var endAngle = d.endAngle + 0.1;
var startAngle = d.startAngle - 0.1;
var arcOver = d3.svg.arc()
.outerRadius(radius + 10).endAngle(endAngle).startAngle(startAngle);
d3.select(this)
.attr("stroke","white")
.transition()
.ease("bounce")
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
d3.select(this).transition()
.attr("d", arc)
.attr("stroke","none");
})
.transition()
.ease("bounce")
.duration(2000)
.attrTween("d", tweenPie);
function tweenPie(b) {
b.innerRadius = 0;
var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
return function(t) { return arc(i(t)); };
}
var k=0;
arcs2.append("text")
.transition()
.ease("elastic")
.duration(2000)
.delay(function (d, i) {
return i * 250;
})
.attr("x","6")
.attr("dy", ".35em")
.text(function(d) { if(d.data.count >0){ k = k+1; return d.data.count;} })
.attr("transform", function(d) { if (k >1){return "translate(" + labelArc.centroid(d) + ") rotate(" + angle(d) + ")";} else{return "rotate(-360)";} })
.attr("font-size", "10px");
function type(d) {
d.count = +d.count;
return d;
}
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
var legend = d3.select("#chart")
.append("svg")
.attr("class", "legend")
.attr("width", radius+50)
.attr("height", radius * 2)
.selectAll("g")
.data(color.domain())
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.data(data)
.text(function(d,i) { return d.IP; });
</script>
</svg>
</div>
<script type="text/javascript">
var chart = $("#chart"),
aspect = chart.width() / chart.height(),
container = chart.parent();
$(window).on("resize", function() {
var targetWidth = container.width();
chart.attr("width", targetWidth);
chart.attr("height", Math.round(targetWidth / aspect));
}).trigger("resize");
</script>
</script>
</body>
</html>

Resources