Strange Bug in my code with D3.JS and Bar Charts - javascript

Hi guys im learning D3 and im trying to make dynamic charts which with changing the chart group will show the same group charts let me show you with two images:
i followed the documents tutorials which are working ! but the behave iof my code is
weird when i apply the filter by selecting the group
it shows bars in random locations or on top of each others
also after filter apply it never shows the very first bar of array
here is the code
var cereals;
if (manufacturer === 'All')
cereals = data.filter(d => d.manufacturer !== manufacturer);
else cereals = data.filter(d => d.manufacturer === manufacturer);
// **** Draw and Update your chart here ****
var bars = chartG.selectAll('.bar')
.data(cereals,function(d,i){
return d.sugar;
});
var barsEnter = bars.enter()
.append('g')
.attr('class', 'bar');
barsEnter.merge(bars)
.attr('transform', function(d,i){
return 'translate('+[i * barBand + 4,0]+')';
});
/*barsEnter.append('rect')
.attr("class", "bar")
.attr("width", barWidth)
.attr("height", function(d) { return chartHeight - sugarScale(d.sugar); });
*/
barsEnter.append('rect')
.attr("class", "bar")
.attr("y", function(d) { return sugarScale(d.sugar); })
.attr("width", barWidth)
.attr("height", function(d) { return chartHeight - sugarScale(d.sugar); });
barsEnter.append('text')
.attr('dy', '0.9em')
.attr('dx', '0.6em')
.attr('class','axis-label')
.attr("transform", "translate(0," + chartHeight + ")")
.text(function(d){
console.log(d)
return d.cerealName;
});
bars.exit().remove();
Here is full code if you want to check!
main.js
// Global function called when select element is changed
function onCategoryChanged() {
var select = d3.select('#categorySelect').node();
var category = select.options[select.selectedIndex].value;
// Update chart with the selected category of cereal
updateChart(category);
}
// recall that when data is loaded into memory, numbers are loaded as strings
// this function helps convert numbers into string during data preprocessing
function dataPreprocessor(row) {
return {
cerealName: row['Cereal Name'],
manufacturer: row['Manufacturer'],
sugar: +row['Sugars']
};
}
var svg = d3.select('svg');
// Get layout parameters
var svgWidth = +svg.attr('width');
var svgHeight = +svg.attr('height');
var padding = { t: 60, r: 20, b: 80, l: 60 };
// Compute chart dimensions
var chartWidth = svgWidth - padding.l - padding.r;
var chartHeight = svgHeight - padding.t - padding.b;
// Variable for the spacing of bar charts
var barBand;
var barWidth;
// scales
var sugarScale; // y axis
var xBandScale; // x axis
// Create a group element for appending chart elements
var chartG = svg.append('g')
.attr('transform', `translate(${padding.l}, ${padding.t})`);
var data;
d3.csv('cereals.csv', dataPreprocessor).then(function(dataset) {
// Create global variables here and intialize the chart
data = dataset;
// Compute the spacing for bar bands based on number of cereals
barBand = chartWidth / data.length;
barWidth = 0.7 * barBand;
// **** Your JavaScript code goes here ****\
dataset
// Add axes to chart
addAxes();
// Main Part
sugarScale.domain([0, d3.max(dataset, function(d) { return d.sugar; })]);0
chartG.append("g").call(d3.axisLeft(sugarScale)).append("text").text("value");
// Update the chart for All cereals to initialize
updateChart('All');
});
function addAxes() {
// **** Draw the axes here ****
sugarScale = d3.scaleLinear().range ([chartHeight,0]),
xBandScale = d3.scaleLinear().range([0, chartWidth])
}
function updateChart(manufacturer) {
// Create a filtered array of cereals based on the manufacturer
var cereals;
if (manufacturer === 'All')
cereals = data.filter(d => d.manufacturer !== manufacturer);
else cereals = data.filter(d => d.manufacturer === manufacturer);
// **** Draw and Update your chart here ****
var bars = chartG.selectAll('.bar')
.data(cereals,function(d,i){
return d.sugar;
});
var barsEnter = bars.enter()
.append('g')
.attr('class', 'bar');
barsEnter.merge(bars)
.attr('transform', function(d,i){
return 'translate('+[i * barBand + 4,0]+')';
});
/*barsEnter.append('rect')
.attr("class", "bar")
.attr("width", barWidth)
.attr("height", function(d) { return chartHeight - sugarScale(d.sugar); });
*/
barsEnter.append('rect')
.attr("class", "bar")
.attr("y", function(d) { return sugarScale(d.sugar); })
.attr("width", barWidth)
.attr("height", function(d) { return chartHeight - sugarScale(d.sugar); });
barsEnter.append('text')
.attr('dy', '0.9em')
.attr('dx', '0.6em')
.attr('class','axis-label')
.attr("transform", "translate(0," + chartHeight + ")")
.text(function(d){
console.log(d)
return d.cerealName;
});
bars.exit().remove();
}
// Remember code outside of the data callback function will run before the data loads
index.html
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lab 4 - ABCs of D3</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="main">
<svg width="600" height="330" style="border: 1px solid #777;">
</svg>
<div>
<label for="categorySelect">Manufacturer: </label>
<select class="custom-select" id="categorySelect" onchange="onCategoryChanged()">
<option selected value="All">All</option>
<option value="General Mills">General Mills</option>
<option value="Quaker Oats">Quaker Oats</option>
<option value="Ralston Purina">Ralston Purina</option>
<option value="Kelloggs">Kelloggs</option>
<option value="Nabisco">Nabisco</option>
<option value="Post">Post</option>
</select>
</div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="./main.js"></script>
</html>
Styles
.axis-label {
text-anchor: middle;
font-size: 12px;
font-weight: semibold;
}
body {
font-family: "Open Sans";
margin: 0;
}
.bar {
fill: #4571C9;
}
#main {
margin: 20px;
}
cereals.csv
Cereal Name,Manufacturer,Sugars
Kix,General Mills,3
Life,Quaker Oats,6
Trix,General Mills,12
Smacks,Kelloggs,15
Basic 4,General Mills,8
Crispix,Kelloggs,3
All-Bran,Kelloggs,5
Cheerios,General Mills,1
Clusters,General Mills,7
Wheaties,General Mills,3
100% Bran,Nabisco,6
Bran Chex,Ralston Purina,6
Corn Chex,Ralston Purina,3
Corn Pops,Kelloggs,12
Rice Chex,Ralston Purina,2
Special K,Kelloggs,3
Grape-Nuts,Post,3
Honey-comb,Post,11
Wheat Chex,Ralston Purina,3
Apple Jacks,Kelloggs,14
This is example of strange bug :

I can see 2 possible things:
You're adding class bar to your 'g' elements AND the 'rect' elements within the 'g' element - I can see that causing issues when you selectAll on that class
You're appending new rects and texts after you merged the new and existing bars. This will add more texts and rects to the existing bars. You want to append the text and rect elements before the merge.
Not necessary but might be nice:
2a) You might want to after the merge operation, 'select' the rect and do the bar Y/width/height calculation there so existing bars are updated if the values changed since the last operation. This looks like static data at the moment though.

Related

How to use an exit selection on text with tspan

I have a Pie Chart which updates as you move the slider, the chart also has labels which I want to update and reposition as the data changes. When I update the chart new labels are drawn but the old labels are not removed. I have managed to get the Join/Enter/Update/Remove sequence working on the chart itself but not the labels.
Is there anything different about text that means this update sequence does not work?
function update() {
// Scales
const colors = d3.quantize(d3.interpolateSpectral, dataset.length);
const colorScale = d3
.scaleOrdinal()
.domain(dataset.map((d) => d.type))
.range(colors);
//Define Pie Chart
const generationPie = d3
.pie()
.padAngle(0.005)
.value((d) => d.generation)
.sort(null);
//Pass dataset into Pie Chart
const slices = generationPie(dataset);
//JOIN DATA
const arcs = arcGroup.selectAll("path").data(slices);
//ENTER NEW DATA
arcs
.enter()
.append("path")
.attr("d", arc)
.attr("fill", (d) => colorScale(d.data.type))
.each(function (d) {
this._current = d;
});
//UPDATE
arcs.transition().duration(1000).attrTween("d", arcTween);
//REMOVE OLD DATA
arcs.exit().remove();
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
//==============================================================
//Data timestamp
//Join
const dataTimeLabel = dataTimestamp.text(timeFormat(dataTime));
//Update
dataTimeLabel.transition().duration(1000);
//ENTER
dataTimeLabel.enter().text(timeFormat(dataTime));
//REMOVE
dataTimestamp.exit().remove();
//==============================================================
//Labels
const labelsGroup = ctr
.append("g")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
);
//JOIN
const labelArcs = labelsGroup.selectAll("text").data(slices);
//ENTER
labelArcs
.enter()
.append("text")
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`)
.append("tspan")
.attr("font-weight", "bold")
.attr("font-size", 14)
.attr("x", "-2em")
.text((d) => d.data.type)
.append("tspan")
.attr("x", "-2.3em")
.attr("y", "+1.2em")
.attr("fill-opacity", 0.7)
.attr("font-size", 12)
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %")
//UPDATE
labelArcs.transition().duration(1000)
//REMOVE
labelArcs.exit().remove();
}
Any help would be really welcome.
On top of the comment about re-appending the groups and the answer about splitting the selection, you have to change the text position in your update selection...
labelArcs.transition().duration(1000)
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`);
...as well as changing the text values in the <tspan> elements:
labelArcs.select(".type")
.text((d) => d.data.type);
labelArcs.select(".value")
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %");
Here is the resulting code:
async function draw() {
// Data
let dataset;
let dataTime;
const commaFormat = d3.format(",");
const timeFormat = d3.timeFormat("%d-%m-%Y %I:%M");
// Dimensions
let dimensions = {
width: 700,
height: 700,
margins: 10,
};
dimensions.ctrWidth = dimensions.width - dimensions.margins * 2;
dimensions.ctrHeight = dimensions.height - dimensions.margins * 2;
const radius = dimensions.ctrHeight / 3;
// Draw Image
const svg = d3
.select("#chart-area")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height);
const ctr = svg
.append("g") // <g>
.attr(
"transform",
`translate(${dimensions.margins}, ${dimensions.margins})`
);
const arc = d3
.arc()
.outerRadius(radius)
.innerRadius(radius * 0.8);
const arcLabels = d3
.arc()
.outerRadius(radius * 1.2)
.innerRadius(radius * 1.2);
const arcGroup = ctr
.append("g")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
);
const dataTimestamp = svg
.append("text")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
)
.attr("text-anchor", "middle")
.append("tspan")
.attr("x", "+0.2em")
.attr("y", "+2.5em")
.attr("font-size", 20);
//Labels
const labelsGroup = ctr
.append("g")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
);
d3.json("https://raw.githubusercontent.com/1crisl/test/main/data.json").then((data) => {
const timeArray = data.map((record) => {
return record.unixTime;
});
const minTime = d3.min(timeArray);
const maxTime = d3.max(timeArray);
let i = timeArray.length - 1;
$("#dateLabel1").text(timeFormat(minTime));
$("#dateLabel2").text(timeFormat(maxTime));
$("#date-slider").slider({
max: timeArray.length - 1,
min: 0,
value: timeArray.length - 1,
change: (event, ui) => {
i = $("#date-slider").slider("value");
dataTime = data[i].unixTime;
dataset = data[i].data.filter(function(obj) {
return obj.percentage > "1";
});
update();
},
});
dataTime = data[i].unixTime;
dataset = data[i].data.filter(function(obj) {
return obj.percentage > "1";
});
update();
});
function update() {
// Scales
const colors = d3.quantize(d3.interpolateSpectral, dataset.length); //Generates a colour for each item in the dataset array
const colorScale = d3
.scaleOrdinal()
.domain(dataset.map((d) => d.type))
.range(colors);
//Define Pie Chart
const generationPie = d3
.pie()
.padAngle(0.005)
.value((d) => d.generation)
.sort(null);
//Pass dataset into Pie Chart
const slices = generationPie(dataset);
//JOIN DATA
const arcs = arcGroup.selectAll("path").data(slices);
//ENTER NEW DATA
arcs
.enter()
.append("path")
.attr("d", arc)
.attr("fill", (d) => colorScale(d.data.type))
.each(function(d) {
this._current = d;
});
//UPDATE
arcs.transition().duration(1000).attrTween("d", arcTween);
//REMOVE OLD DATA
arcs.exit().remove();
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
//==============================================================
//Data timestamp
//Join
const dataTimeLabel = dataTimestamp.text(timeFormat(dataTime));
//Update
dataTimeLabel.transition().duration(1000);
//ENTER
dataTimeLabel.enter().text(timeFormat(dataTime));
//REMOVE
dataTimeLabel.exit().remove();
//==============================================================
//JOIN
const labelArcs = labelsGroup.selectAll("text").data(slices, d => d.data.type);
//ENTER
const textGroup = labelArcs
.enter()
.append("text")
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`);
textGroup
.append("tspan")
.attr("font-weight", "bold")
.attr("font-size", 14)
.attr("x", "-2em")
.attr("class", "type")
.text((d) => d.data.type);
textGroup
.append("tspan")
.attr("x", "-2.3em")
.attr("y", "+1.2em")
.attr("fill-opacity", 0.7)
.attr("font-size", 12)
.attr("class", "value")
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %");
//UPDATE
labelArcs.select(".type")
.text((d) => d.data.type);
labelArcs.select(".value")
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %");
labelArcs.transition().duration(1000)
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`);
//REMOVE
labelArcs.exit().remove();
}
}
draw();
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="">
<title>Visualisation</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<!-- jQueryUI styling -->
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
</head>
<body>
<!-- Bootstrap grid setup -->
<div class="container">
<div class="row">
<div class="col-md-12 d-flex justify-content-center">
<div id="chart-area"></div>
</div>
<div class="col-md-12 d-flex justify-content-center">
<div id="slider-div">
<label>Data Extent</label>
<br>
<label><span id="dateLabel1">01/01/2000</span> to <span id="dateLabel2">18/03/2021</span></label>
<div id="date-slider"></div>
</div>
</div>
</div>
</div>
<!-- External JS libraries -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
<!-- Custom JS -->
<script src="js/main.js"></script>
</body>
</html>
PS: your percentages are way off.
Try to split code:
const textGroup = labelArcs
.enter()
.append("text")
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`)
textGroup.append("tspan")
.attr("font-weight", "bold")
.attr("font-size", 14)
.attr("x", "-2em")
.text((d) => d.data.type)
textGroup.append("tspan")
.attr("x", "-2.3em")
.attr("y", "+1.2em")
.attr("fill-opacity", 0.7)
.attr("font-size", 12)
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %")
If it does not help, please provide a snippet of a fiddle to work with

Filter data on multiple columns with checkboxes - D3

I'm creating a scatterplot in D3 and I want to use checkboxes to filter the data by two categorical variables. Currently I can filter out data using the checkboxes, but if I re-check a box in one category it will bring back all the data for that category, regardless of what is checked/unchecked in the other category. I'd like to know how best to filter data according to multiple variables at once.
Here's a simplified version of my data and code:
Data (test.csv):
color,texture,length,width
blue,fluffy,4,1
yellow,fluffy,5,2
blue,grainy,3,1
yellow,grainy,4,2
blue,grainy,5,3
yellow,grainy,5,4
HTML:
<!DOCTYPE html>
<meta charset="utf-8">
<html lang="en">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="test.js"></script>
<!-- Checkboxes -->
<div class="filter_options">
<input class="color_button" id="B" value="blue" type="checkbox"
checked="checked">Blue</input>
<input class="color_button" id="Y" value="yellow" type="checkbox"
checked="checked">Yellow</input>
</div>
<div class="filter_options">
<input class="texture_button" id="F" value="fluffy" type="checkbox"
checked="checked">Fluffy</input>
<input class="texture_button" id="G" value="grainy" type="checkbox"
checked="checked">Grainy</input>
</div>
</body>
</html>
Javascript (test.js; filtering happens at the bottom):
// Set width/height/margins
var margin = {top: 20, right: 20, bottom: 30, left: 50};
var w = 900 - margin.left - margin.right;
var h = 500 - margin.top - margin.bottom;
// Create SVG
var svg = d3.select("body").append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x and y scales
var x = d3.scale.linear()
.domain([0, 10])
.range([0, w]);
var y = d3.scale.linear()
.domain([0, 10])
.range([h, 0]);
// x axis
var xAxis = d3.svg.axis()
.ticks(6)
.scale(x);
// y axis
var yAxis = d3.svg.axis()
.ticks(7)
.scale(y)
.orient("left");
// Colors for points
var col = d3.scale.ordinal()
.domain(["blue", "yellow"])
.range(["#3b80e8", "#ecea5f"]);
// Add x axis ("Length")
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.append("text")
.attr("x", w)
.attr("y", -6)
.style("text-anchor", "end")
.text("Length");
// Add y axis ("Width")
svg.append("g")
.attr("class", "axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Width");
// Data
var dat;
d3.csv("test.csv", function(data) {
data.forEach(function(d) {
d.length = +d.length;
d.width = +d.width;
});
// Scatterplot
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d) {return x(d.length); })
.attr("cy", function(d) {return y(d.width); })
.attr("r", 7)
.style("fill", function(d) { return col(d.color); });
// Filter data by color
d3.selectAll(".color_button").on("change", function() {
var selected = this.value,
display = this.checked ? "inline" : "none";
svg.selectAll(".dot")
.filter(function(d) { return selected == d.color; })
.attr("display", display);
});
// Filter data by texture
d3.selectAll(".texture_button").on("change", function() {
var selected = this.value,
display = this.checked ? "inline" : "none";
svg.selectAll(".dot")
.filter(function(d) { return selected == d.texture; })
.attr("display", display);
});
});
In the resulting graph, if I uncheck "blue", all the blue points disappear. If I uncheck "fluffy", one more point disappears. If I then re-check "fluffy", I get back the one yellow fluffy point as well as the one blue fluffy point. I think I can see why this is happening with my code as I've written it: since when I re-check "fluffy" I'm not changing changing the state of the color checkboxes, my data isn't getting filtered according to color. I'm brand new to D3 and Javascript, so I don't know how to do better accomplish filtering on multiple columns at the same time.
Here's one way to do it. Replace the two filtering code fragments with this:
// a function that will be responsible for updating the visibility of the dots
function update() {
// colors will be the array of active colors, i.e. if only the yellow checkbox
// is checked, it will be ['yellow']
var colors = d3.selectAll('.color_button')[0]
.filter(function(e) { return e.checked; })
.map(function(e) { return e.value; });
// same thing for the textures
var textures = d3.selectAll('.texture_button')[0]
.filter(function(e) { return e.checked; })
.map(function(e) { return e.value; });
// a helper function that will return the correct display value
// it will be called for every dot
function display(d) {
// we check if the current dot's color and texture are present in the
// colors and textures arrays.
if (colors.indexOf(d.color) !== -1 && textures.indexOf(d.texture) !== -1) {
return 'inline';
} else {
return 'none';
}
}
// we change the display attribute of every dot using the display function just defined
svg.selectAll('.dot').attr('display', display);
}
// we add a simple handler to all the checkboxes: every time one changes,
// just call update
d3.selectAll(".filter_options input").on("change", update);

How to call JS function more than once in same html

This is the code I use for calling same js function in two blocks (in two div tag). The answer for the second tag (div id="frame4") also prints inside the first one (div id="frame3"). I want to print them separately. How can I do that?
<div id="frame3">
<! ----pieChart ----- !>
<h5><i>Code Coverage</i></h5>
<div id="pieChart"></div>
<script type="text/javascript">
dsPieChart(<%=coverage %>);
</script>
</div>
<!test_density !>
<div id="frame3">
<div id="pieChart"></div>
<script type="text/javascript">
dsPieChart(<%=density %>);
</script>
</div>
code for the function
function dsPieChart(x){
var formatAsPercentage = d3.format("%") ;
var dataset = [
{category: "", measure:x },
{category: "", measure:(100-x)},
]
;
var width = 100,
height = 100,
outerRadius = Math.min(width, height) / 2,
innerRadius = outerRadius * .9,
// for animation
innerRadiusFinal = outerRadius * .8,
innerRadiusFinal3 = outerRadius* .7,
color = d3.scale.category20b() //builtin range of colors
;
var vis = d3.select("#pieChart")
.append("svg:svg") //create the SVG element inside the <body>
.data([dataset]) //associate our data with the document
.attr("width", width) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", height)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")") //move the center of the pie chart from 0, 0 to radius, radius
;
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(outerRadius).innerRadius(innerRadius);
// for animation
var arcFinal = d3.svg.arc().innerRadius(innerRadiusFinal).outerRadius(outerRadius);
var arcFinal3 = d3.svg.arc().innerRadius(innerRadiusFinal3).outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.measure; }); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice") //allow us to style things in the slices (like text)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
;
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc) //this creates the actual SVG path using the associated data (pie) with the arc drawing function
.append("svg:title") //mouseover title showing the figures
// .text(function(d) { return d.data.category + ": " + d.data.measure ; });
.text(function(d) { return d.data.measure ; });
d3.selectAll("g.slice").selectAll("path").transition()
.duration(750)
.delay(10)
.attr("d", arcFinal )
;
// Add a label to the larger arcs, translated to the arc centroid and rotated.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; })
.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "translate(" + arcFinal.centroid(d) + ")rotate(" + angle(d) + ")"; })
.text(function(d) { return d.data.category; })
;
// Computes the label angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
// Pie chart title
vis.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(x +"%")
.attr("class","title")
;
function mouseover() {
d3.select(this).select("path").transition()
.duration(750)
.attr("d", arcFinal3)
;
}
function mouseout() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","blue")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal)
;
}
function up(d, i) {
/* update bar chart when user selects piece of the pie chart */
//updateBarChart(dataset[i].category);
updateBarChart(d.data.category, color(i));
updateLineChart(d.data.category, color(i));
}
}
Change function to pass second parameter for element ID.
function dsPieChart(x, selectorId){
Change the hard code selector:
var vis = d3.select("#pieChart");
To
var vis = d3.select("#" + selectorId);
Then when you call the function also identify the id selector in second paramater. Note that element ID's must be unique in a page by definition:
<div id="pieChart-1"></div>
<script type="text/javascript">
dsPieChart(<%=coverage %>, 'pieChart-1');
</script>
</div>
<div id="pieChart-2"></div>
<script type="text/javascript">
dsPieChart(<%=density %>, 'pieChart-2');
</script>
</div>

Feeding live CSV data into D3 Charts

I have an d3 web app which displays a chart containing some server info (throughput, ssl, etc.)
The app works fine with static data, as it should. I have a Python script set up to continually update my traffic.csv to include current data (new data is appended every 30 seconds, and old data past a certain time frame is removed.)
I am wondering what the best way to import this data into my graph is. I have tried to "store" my CSV data as a JavaScript variable but than D3 is unable to parse it.
I would prefer a method that would allow this graph to update in real-time(or close to). I have looked into storing my data inside an AngularJS model and attempting to bind it to the d3 chart to take advantage of Angular's realtime two-way data binding. Has this been done before?
Here is my D3 Bar Chart Code: http://plnkr.co/edit/SclU3HYF7yCwZgHDOEHg?p=preview
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html ng-app="CSVReader">
<head>
<title>Bar Chart</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
<script src="controller.js"></script>
<!-- import main controller -->
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<link rel="stylesheet" type="text/css" href="http://d1e24pw9mnwsl8.cloudfront.net/c/bootstrap/css/bootstrap.min.css">
<meta http-equiv="X-UA-Compatible" content="IE=9">
<script src="controller.js"></script>
<!-- import main controller -->
</head>
<body ng-controller="MainCtrl">
<div id="title" style="text-align:center;padding-bottom:20px;">
<h1>Traffic Throughput (in) by Time</h1>
<small>Time in "Unix-Time", Throughput in Bytes</small>
</div>
<div id="chart" style="text-align:center"></div>
<script src="http://d3js.org/d3.v2.min.js"></script>
<script>
function renderChart() {
var data = d3.csv.parse(d3.select('#csv').text());
var valueLabelWidth = 50; // space reserved for value labels (right)
var barHeight = 20; // height of one bar
var barLabelWidth = 100; // space reserved for bar labels
var barLabelPadding = 5; // padding between bar and bar labels (left)
var gridLabelHeight = 18; // space reserved for gridline labels
var gridChartOffset = 3; // space between start of grid and first bar
var maxBarWidth = 420; // width of the bar with the max value
// accessor functions
var barLabel = function(d) {
return d['unixtime'];
};
var barValue = function(d) {
return parseFloat(d['tput_bytes_in']);
};
// scales
var yScale = d3.scale.ordinal().domain(d3.range(0, data.length)).rangeBands([0, data.length * barHeight]);
var y = function(d, i) {
return yScale(i);
};
var yText = function(d, i) {
return y(d, i) + yScale.rangeBand() / 2;
};
var x = d3.scale.linear().domain([0, d3.max(data, barValue)]).range([0, maxBarWidth]);
// svg container element
var chart = d3.select('#chart').append("svg")
.attr('width', maxBarWidth + barLabelWidth + valueLabelWidth)
.attr('height', gridLabelHeight + gridChartOffset + data.length * barHeight);
// grid line labels
var gridContainer = chart.append('g')
.attr('transform', 'translate(' + barLabelWidth + ',' + gridLabelHeight + ')');
gridContainer.selectAll("text").data(x.ticks(10)).enter().append("text")
.attr("x", x)
.attr("dy", -3)
.attr("text-anchor", "middle")
.text(String);
// vertical grid lines
gridContainer.selectAll("line").data(x.ticks(10)).enter().append("line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", 0)
.attr("y2", yScale.rangeExtent()[1] + gridChartOffset)
.style("stroke", "#ccc");
// bar labels
var labelsContainer = chart.append('g')
.attr('transform', 'translate(' + (barLabelWidth - barLabelPadding) + ',' + (gridLabelHeight + gridChartOffset) + ')');
labelsContainer.selectAll('text').data(data).enter().append('text')
.attr('y', yText)
.attr('stroke', 'none')
.attr('fill', 'black')
.attr("dy", ".35em") // vertical-align: middle
.attr('text-anchor', 'end')
.text(barLabel);
// bars
var barsContainer = chart.append('g')
.attr('transform', 'translate(' + barLabelWidth + ',' + (gridLabelHeight + gridChartOffset) + ')');
barsContainer.selectAll("rect").data(data).enter().append("rect")
.attr('y', y)
.attr('height', yScale.rangeBand())
.attr('width', function(d) {
return x(barValue(d));
})
.attr('stroke', 'white')
.attr('fill', 'green');
// bar value labels
barsContainer.selectAll("text").data(data).enter().append("text")
.attr("x", function(d) {
return x(barValue(d));
})
.attr("y", yText)
.attr("dx", 3) // padding-left
.attr("dy", ".35em") // vertical-align: middle
.attr("text-anchor", "start") // text-align: right
.attr("fill", "blue")
.attr("stroke", "none")
.text(function(d) {
return d3.round(barValue(d), 2);
});
// start line
barsContainer.append("line")
.attr("y1", -gridChartOffset)
.attr("y2", yScale.rangeExtent()[1] + gridChartOffset)
.style("stroke", "#000");
}
</script>
<script id="csv" type="text/csv">
// Data goes here until I can figure out how to link to the .csv
</script>
<script>
renderChart();
</script>
</body>
</html>
I was hoping I could find a one-line solution for importing data that like this:
var data = d3.csv.parse((traffic.csv).text());

D3.js code in Reveal.js slide

I'm trying to make D3.js work on Reveal.js slides, but I can't get it to run even the most basic snippet:
<section>
<h2>Title</h2>
<div id="placeholder"></div>
<script type="text/javascript">
d3.select("#placeholder").append("p").text("TEST");
</script>
</section>
Doesn't show the "TEST" word. What am I doing wrong?
Okay here we go.
I have made a basic example with Reveal.js & D3.js and it works well.
There are two slides
First slide contains Bar chart
Second slide renders Bubble chart by taking data from a json input file
Your code works fine if it is placed outside of the section at the bottom. I have placed all D3.js code at the end of the html page before body closer.
The folder structure is show below (in snapshot)
I placed my JS inside the HTML (in order to make it easier to read/comprehend)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reveal.js with D3 JS</title>
<link rel="stylesheet" href="css/reveal.min.css">
<link rel="stylesheet" href="css/theme/default.css" id="theme">
<style>
.chart rect {
fill: #63b6db;
}
.chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
text {
font: 10px sans-serif;
}
</style>
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h2>Barebones Presentation</h2>
<p>This example contains the bare minimum includes and markup required to run a reveal.js presentation.</p>
<svg class="chart"></svg>
</section>
<section id="sect2">
<h2>No Theme</h2>
<p>There's no theme included, so it will fall back on browser defaults.</p>
<svg class="bubleCharts"></svg>
</section>
</div>
</div>
<script src="js/reveal.min.js"></script>
<script>
Reveal.initialize();
</script>
<script src="js/d3.min.js"></script>
<script type="text/javascript">
//------ code to show D3 Bar Chart on First Slide-------
var data = [44, 28, 15, 16, 23, 5];
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d; });
//---Code below will show Bubble Charts on the secon Slide -------
var diameter = 560,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select(".bubleCharts")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
</body>
</html>
Output/results
Slide 1
Slide 2
Download complete code https://github.com/aahad/D3.js/tree/master/Reveal JS with D3 JS
To learn more about how Bar Chart or Bubble Chart code works: check followings:
Bubble Chart examples: http://bl.ocks.org/mbostock/4063269
Bar Chart examples: http://bost.ocks.org/mike/bar/
Both existing answers are fine, but I'd like to point out a 3rd approach that worked for me and has some advantages.
Reveal.js has an event system and it also works with the per-slide data states.
This means that you can have a separate javascript block for each slide and have it execute only when that slide is loaded. This lets you do D3-based animations upon load of the slide and has the further advantage of placing the code for the slide closer to the text/markup of it.
For example, you could set your slide like this. Note the data-state attribute:
<section data-state="myslide1">
<h2>Blah Blah</h2>
<div id="slide1d3container"></div>
</section>
And then have an associated script block:
<script type="text/javascript">
Reveal.addEventListener( 'myslide1', function() {
var svg = d3.select("#slide1d3container").append("svg")
// do more d3 stuff
} );
</script>
Here's an example of a presentation that uses this technique:
http://explunit.github.io/d3_cposc_2014.html
I found out by myself. Of course I cannot match against ids that are not loaded yet: it works if I put the d3 javascript code after the Reveal.initialize script block at the end of the index.html file.

Categories