D3.js code in Reveal.js slide - javascript

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.

Related

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

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.

D3 - dragging multiple elements across groups

I'm trying to achieve a D3 drag behavior, for which I'm not finding an example.
The below code creates a couple of rows with a rectangle, circle and ellipse each, and the drag feature is enabled for the circle.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Drag many</title>
<style media="screen">
.active {
stroke: #000;
stroke-width: 2px;
}
</style>
</head>
<body>
<svg width=1000 height=500></svg>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script type="text/javascript">
var svg=d3.select("svg"),
mainGroup = svg.append("g");
var squareData = [
{x:25, y:23, width:15, height:15},
{x:25, y:63, width:15, height:15}
];
var circleData = [
{cx:60, cy:30, r:10},
{cx:60, cy:70, r:10}
];
var ellipseData = [
{cx:90, cy:30, rx:10, ry:15},
{cx:90, cy:70, rx:10, ry:15}
];
var squareGroup = mainGroup.append("g");
squareGroup.selectAll(null)
.data(squareData)
.enter().append("rect")
.attr("class", (d,i)=>`rectGroup row_${i+1}`)
.attr("x", d=>d.x)
.attr("y", d=>d.y)
.attr("width", d=>d.width)
.attr("height", d=>d.height)
.attr("fill", "blue");
circleGroup = mainGroup.append("g");
circleGroup.selectAll(null)
.data(circleData)
.enter().append("circle")
.attr("class", (d,i)=>`circleGroup row_${i+1}`)
.attr("cx", d=>d.cx)
.attr("cy", d=>d.cy)
.attr("r", d=>d.r)
.attr("fill", "red")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
ellipseGroup = mainGroup.append("g");
ellipseGroup.selectAll(null)
.data(ellipseData)
.enter().append("ellipse")
.attr("class", (d,i)=>`ellipseGroup row_${i+1}`)
.attr("cx", d=>d.cx)
.attr("cy", d=>d.cy)
.attr("rx", d=>d.rx)
.attr("ry", d=>d.ry)
.attr("fill", "green");
function dragstarted(d){
d3.select(this).classed("active", true);
}
function dragended(d){
d3.select(this).classed("active", false);
}
function dragged(d){
//var dx = d3.event.x - d3.select(this).attr("cx")
d3.select(this).attr("cx", d.x = d3.event.x);
// I really need to move all the elements in this row here,
// with the circle being the pivot...
}
</script>
</body>
</html>
I would like to modify the drag behavior of the circle to pull the entire row with it rather than move alone.
How can I achieve this?
It looks like you give each row a class so instead of:
function dragged(d){d3.select(this).attr("cx", d.x = d3.event.x);}
get the class of that row (from the looks of your code it should be the second class) with:var thisRow = d3.select(this).attr("class").split()[1];
.split() will split on spaces which is how getting the class list will spit out its result.
After getting the class move all of that class:
d3.selectAll("." + thisRow).attr("cx", d.x = d3.event.x);
I'm not really sure if there's a better way of finding the row, but here's how I solved it using the concept from #pmkroeker
function dragged(d){
var deltaX = d3.event.x - d3.select(this).attr("cx");
d3.select(this).attr("cx", d.x = d3.event.x); // move the circle
// find the row
var row = d3.select(this).attr("class").split(/\s+/).filter(s=>s.startsWith("row_"))[0];
// grab other elements of the row and move them as well
d3.selectAll("."+row).filter(".rectGroup").attr("x", d=>d.x = d.x+deltaX);
d3.selectAll("."+row).filter(".ellipseGroup").attr("cx", d=>d.cx = d.cx+deltaX);
}

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 is not defined - ReferenceError

I am trying to use a "fancy graph" found at http://bl.ocks.org/kerryrodden/7090426:
The way I've done it was to download the code and simply edit the CSV file to match my data. Then I simply open the .html-file in Firefox to see the interactive graph. However, using it at a another computer I get the following errors:
ReferenceError: d3 is not defined sequences.js:25
ReferenceError: d3 is not defined index.html:28
As I have almost no knowledge of d3 or javascript I am a bit lost.
Can any of you give me a hint to what is causing the errors and how I should correct the code?
I've done a single alteration to the code making it the following:
Javascript:
// Dimensions of sunburst.
var width = 750;
var height = 600;
var radius = Math.min(width, height) / 2;
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
w: 75, h: 30, s: 3, t: 10
};
// Mapping of step names to colors.
var colors = {
"G0": "#5687d1",
"G1": "#5c7b61",
"G2": "#de783b",
"G3": "#6ab975",
"G4": "#a173d1",
"G5": "#72d1a1",
"Afgang": "#615c7b"
};
// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
// Use d3.text and d3.csv.parseRows so that we do not need to have a header
// row, and can receive the csv as an array of arrays.
d3.text("sequences.csv", function(text) {
var csv = d3.csv.parseRows(text);
var json = buildHierarchy(csv);
createVisualization(json);
});
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(json) {
// Basic setup of page elements.
initializeBreadcrumbTrail();
drawLegend();
d3.select("#togglelegend").on("click", toggleLegend);
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(json)
.filter(function(d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
nodes = nodes.filter(function(d) {
return (d.name != "end"); // BJF: Do not show the "end" markings.
});
var path = vis.data([json]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function(d) { return colors[d.name]; })
.style("opacity", 1)
.on("mouseover", mouseover);
// Add the mouseleave handler to the bounding circle.
d3.select("#container").on("mouseleave", mouseleave);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
};
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseover(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
d3.select("#percentage")
.text(percentageString);
d3.select("#explanation")
.style("visibility", "");
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray, percentageString);
// Fade all the segments.
d3.selectAll("path")
.style("opacity", 0.3);
// Then highlight only those that are an ancestor of the current segment.
vis.selectAll("path")
.filter(function(node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style("opacity", 1);
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
// Hide the breadcrumb trail
d3.select("#trail")
.style("visibility", "hidden");
// Deactivate all segments during transition.
d3.selectAll("path").on("mouseover", null);
// Transition each segment to full opacity and then reactivate it.
d3.selectAll("path")
.transition()
.duration(1000)
.style("opacity", 1)
.each("end", function() {
d3.select(this).on("mouseover", mouseover);
});
d3.select("#explanation")
.transition()
.duration(1000)
.style("visibility", "hidden");
}
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name + d.depth; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function(d) { return colors[d.name]; });
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function drawLegend() {
// Dimensions of legend item: width, height, spacing, radius of rounded rect.
var li = {
w: 75, h: 30, s: 3, r: 3
};
var legend = d3.select("#legend").append("svg:svg")
.attr("width", li.w)
.attr("height", d3.keys(colors).length * (li.h + li.s));
var g = legend.selectAll("g")
.data(d3.entries(colors))
.enter().append("svg:g")
.attr("transform", function(d, i) {
return "translate(0," + i * (li.h + li.s) + ")";
});
g.append("svg:rect")
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.style("fill", function(d) { return d.value; });
g.append("svg:text")
.attr("x", li.w / 2)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.key; });
}
function toggleLegend() {
var legend = d3.select("#legend");
if (legend.style("visibility") == "hidden") {
legend.style("visibility", "");
} else {
legend.style("visibility", "hidden");
}
}
// Take a 2-column CSV and transform it into a hierarchical structure suitable
// for a partition layout. The first column is a sequence of step names, from
// root to leaf, separated by hyphens. The second column is a count of how
// often that sequence occurred.
function buildHierarchy(csv) {
var root = {"name": "root", "children": []};
for (var i = 0; i < csv.length; i++) {
var sequence = csv[i][0];
var size = +csv[i][1];
if (isNaN(size)) { // e.g. if this is a header row
continue;
}
var parts = sequence.split("-");
var currentNode = root;
for (var j = 0; j < parts.length; j++) {
var children = currentNode["children"];
var nodeName = parts[j];
var childNode;
if (j + 1 < parts.length) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k]["name"] == nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {"name": nodeName, "children": []};
children.push(childNode);
}
currentNode = childNode;
} else {
// Reached the end of the sequence; create a leaf node.
childNode = {"name": nodeName, "size": size};
children.push(childNode);
}
}
}
return root;
};
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Flow for G1 customers</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<link rel="stylesheet" type="text/css"
href="https://fonts.googleapis.com/css?family=Open+Sans:400,600">
<link rel="stylesheet" type="text/css" href="sequences.css"/>
</head>
<body>
<div id="main">
<div id="sequence"></div>
<div id="chart">
<div id="explanation" style="visibility: hidden;">
<span id="percentage"></span><br/>
of G1 customers follow this flow.
</div>
</div>
</div>
<div id="sidebar">
<input type="checkbox" id="togglelegend"> Legend<br/>
<div id="legend" style="visibility: hidden;"></div>
</div>
<script type="text/javascript" src="sequences.js"></script>
<script type="text/javascript">
// Hack to make this example display correctly in an iframe on bl.ocks.org
d3.select(self.frameElement).style("height", "700px");
</script>
</body>
</html>
Had the same issue, though I initially thought it is because of security restrictions of browser, that wasn't the case. It worked when I added the charset to the script tag as shown below:
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
The same is shown in the d3 docs, though it doesn't mention this issue specifically: http://d3js.org/
Yes, having it locally works too.
<script src="d3.min.js"></script>
Here is the full example:
<!doctype html>
<html>
<head>
<title>D3 tutorial</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<!--<script src="d3.min.js"></script>-->
</head>
<body>
<script>
var canvas = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
var circle = canvas.append("circle")
.attr("cx",250)
.attr("cy", 250)
.attr("r", 50)
.attr("fill", "red");
</script>
</body>
</html>
There may be security restrictions that prevent your browser from downloading the D3 script. What you can do is to download the scripts, place them in the same folder as your files, and change the referenced paths in your source.
You may also need to add:
<meta charset="utf-8">
or
<meta content="utf-8" http-equiv="encoding">
to your head section
in case browser does not prevent it from downloading and still getting the error, d3.js should be placed before jquery.
I just moved my reference to the package as the first import in my head tag:
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
...
...
</head>
Seemed to work for me
Replace <meta charset="ISO-8859-1"> with <meta charset="UTF-8">
I had to do a grunt build to get get rid of this error. (Using Yeoman and Ember.js.)
And for JavaScript noobs like me - problem could be that you don't import it correctly. Try reading import docs and things like:
import * as d3 from 'd3-transition'
If you are using Visual Studio you can go to Tools -> Options -> Text Editor -> JavaScript -> IntelliSense and check the box "Download remote references". That did the trick for me.
UPDATE: there is now a d3-webpack-loader package which makes it easy to load d3 in webpack. I am not the creator of the package, I've only used it to see if it works. Here's a quick example.
// Install the loader
npm install --save d3-webpack-loader
// Install the d3 microservices you need
npm install --save d3-color
npm install --save d3-selection
In our entry.js file we'll require d3 using the d3-webpack-loader with:
const d3 = require('d3!');
and can then use some of the d3 methods with:
d3.selectAll("p").style("color", d3.color("red").darker());
Super late to this response but none of the above solutions worked out for me. I found a fix though!
I am on macOS Catalina. For some bizarre reason, it turned out to be a decompression/unpack issue with the .tgz file from Observable's website. I use an application called The Unarchiver to decompress files, but in this case, the .tgz file did not properly unpack. There was a folder and file missing compared to a friend's computer not using the same program.
Solution: I unpacked .tgz without a third party program – just used macOS (simply double clicking on the file). Then, I loaded the page locally and it worked!
If double clicking on the file fails to unpack, try running tar -xzf filename.tgz in Terminal.
I assume that you are importing the d3 from online.
In your HTML, make sure that you are importing the d3 before connecting your JavaScript file.
// Importing D3.js
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8" defer></script>
// Importing D3-Scale
<script src="https://d3js.org/d3-scale.v3.min.js"></script>
// Connecting my JS file
<script src="app.js" defer></script>

Selected element won't change in d3

This snippet is a slightly modified version of the example http://bl.ocks.org/4063550 of a Reingold-Tilford Tree. Everything is similar, except I have slightly changed the code where one appends the text to a node. I want to attach an id to each node text so that I can later tweak the text a bit so the graph is more readable.
Here I add the id's and slightly tweak the rotation of the entire diagram. My changes are enclosed with the **.
node.append("text")
.attr("dy", ".31em")
**.attr("id", function(d,i) {return "n" + i;})**
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "**rotate(10)**translate(8)" :"**rotate(199)**translate(-8)"; })
.text(function(d) { return d.name; });
});
Right after the example code with the above being the only changes, I want to put the following line, for example, into the same script.
d3.select("#n1").text("test");
When I load the page it's not changing the text at node id = "n1"! If I put exactly that same line into the console with Firebug, it changes the n1 node's text as desired.
After browsing countless tutorials and the like, I think that I don't understand something fundamental about how this stuff works, any advice would be appreciated!
Edit: Here is the complete html file as requested! It is verbatim the example I linked above, with the only exceptions being what I described above.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var diameter = 960;
var tree = d3.layout.tree()
.size([360, diameter / 2 - 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 svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("thesis.json", function(error, root) {
var nodes = tree.nodes(root),
links = tree.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);
var node = svg.selectAll(".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("text")
.attr("dy", ".25em")
.attr("id", function(d,i) {return "n" + i;})
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "rotate(10)translate(8)" : "rotate(199)translate(-8)"; })
.text(function(d) { return d.name; });
});
d3.select(self.frameElement).style("height", diameter + "px");
d3.select("#n1").text("test");
</script>
</body>
</html>
The issue is that d3.json makes an asynchronous call. That is, the code within that block is only executed when the network call returns with the JSON data. The code after the block is executed immediately though. So, you are trying to modify an element that does not exist yet.
The solution is to move the code that modifies the graph inside the d3.json block, or into a function that is called inside that block.

Categories