Related
I have a project where I am using D3 js to create a few charts. I am trying to make these charts responsive when the window size changes. To do this I already used viewbox to define the svg:
var svg = d3
.select(this.$refs["chart"])
.classed("svg-container", true)
.append("svg")
.attr("class", "chart")
.attr(
"viewBox",
`0 0 ${width + margin.left + margin.right} ${height +
margin.top +
margin.bottom}`
)
.attr("preserveAspectRatio", "xMinYMin meet")
.classed("svg-content-responsive", true)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
I also use to set the width and height the same as the div where the SVG is inside. So that this chart uses the same size as the div it is inside:
width = this.$refs["chart"].clientWidth - margin.left - margin.right,
height = this.$refs["chart"].clientHeight - margin.top - margin.bottom;
The width and height of this div is set to 100% of it's parent div. So when I am resizing the window the div where the svg is in can change size and aspect ratio.
So this is what the chart looks initially when the page is loaded. So it's getting its height and width from the div it is in:
But when i resize the chart shrinks to still fit inside the new width of the parent div. But the height changes with it. So I assume that the aspect ratio stays the same:
I have tried to update the svg viewport when the window resizes. But the vieuwport isn't being updated when I inspect the SVG element in DOM of the developer tools in Chrome. I have added console logs to check if the width and height of the parent also change and they seem to change. But the updated viewport doesn't gets applied to the svg:
d3.select(window).on("resize", () => {
svg.attr(
"viewBox",
`0 0 ${this.$refs["chart"].clientWidth} ${this.$refs["chart"].clientHeight}`
);
});
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/vue"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<style>
.area {
fill: url(#area-gradient);
stroke-width: 0px;
}
body{
width: 100%;
height: 100%;
}
.app{
width: 100%;
height: 100%;
}
#page{
width: 100%;
height: 100%;
}
.my_dataviz{
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="app">
<div class="page">
<div id="my_dataviz" ref="chart"></div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
type: Array,
required: true,
},
mounted() {
const minScale = 0,
maxScale = 35;
var data = [{
key: 'One',
value: 33,
},
{
key: 'Two',
value: 30,
},
{
key: 'Three',
value: 37,
},
{
key: 'Four',
value: 28,
},
{
key: 'Five',
value: 25,
},
{
key: 'Six',
value: 15,
},
];
console.log(this.$refs["chart"].clientHeight)
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 0,
bottom: 30,
left: 40
},
width =
this.$refs["chart"].clientWidth - margin.left - margin.right,
height =
this.$refs["chart"].clientHeight - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.3);
var y = d3.scaleLinear().range([height, 0]);
// 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(this.$refs['chart'])
.classed('svg-container', true)
.append('svg')
.attr('class', 'chart')
.attr(
'viewBox',
`0 0 ${width + margin.left + margin.right} ${
height + margin.top + margin.bottom
}`
)
.attr('preserveAspectRatio', 'xMinYMin meet')
.classed('svg-content-responsive', true)
.append('g')
.attr(
'transform',
'translate(' + margin.left + ',' + margin.top + ')'
);
// format the data
data.forEach(function(d) {
d.value = +d.value;
});
// Scale the range of the data in the domains
x.domain(
data.map(function(d) {
return d.key;
})
);
y.domain([minScale, maxScale]);
//Add horizontal lines
let oneFourth = (maxScale - minScale) / 4;
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth))
.attr('y2', y(oneFourth))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 2))
.attr('y2', y(oneFourth * 2))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 3))
.attr('y2', y(oneFourth * 3))
.style('stroke', 'gray');
//Defenining the tooltip div
let tooltip = d3
.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0)
.style('opacity', 0);
// append the rectangles for the bar chart
svg
.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function(d) {
return x(d.key);
})
.attr('width', x.bandwidth())
.attr('y', function(d) {
return y(d.value);
})
.attr('height', function(d) {
console.log(height, y(d.value))
return height - y(d.value);
})
.attr('fill', '#206BF3')
.attr('rx', 5)
.attr('ry', 5)
.on('mouseover', (e, i) => {
d3.select(e.currentTarget).style('fill', 'white');
tooltip.transition().duration(500).style('opacity', 0.9);
tooltip
.html(
`<div><h1>${i.key} ${
this.year
}</h1><p>${converter.addPointsToEveryThousand(
i.value
)} kWh</p></div>`
)
.style('left', e.pageX + 'px')
.style('top', e.pageY - 28 + 'px');
})
.on('mouseout', (e) => {
d3.select(e.currentTarget).style('fill', '#206BF3');
tooltip.transition().duration(500).style('opacity', 0);
});
// Add the X Axis and styling it
let xAxis = svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
xAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
xAxis.selectAll('.tick text').attr('color', 'gray');
xAxis.selectAll('.tick line').attr('stroke', 'gray');
// add the y Axis and styling it also only show 0 and max tick
let yAxis = svg.append('g').call(
d3
.axisLeft(y)
.tickValues([this.minScale, this.maxScale])
.tickFormat((d) => {
if (d > 1000) {
d = Math.round(d / 1000);
d = d + 'K';
}
return d;
})
);
yAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
yAxis.selectAll('.tick text').attr('color', 'gray');
yAxis.selectAll('.tick line').attr('stroke', 'gray');
d3.select(window).on('resize', () => {
svg.attr(
'viewBox',
`0 0 ${this.$refs['chart'].clientWidth} ${this.$refs['chart'].clientHeight}`
);
});
},
});
</script>
</body>
</html>
There are different approaches to "responsivity" with SVG and in D3 in particular. Using viewBox is one way to handle it, listening for resize events and redrawing the svg is another. If you're going to listen for resize events and re-render, you'll want to make sure you're using the D3 general update pattern.
1. Behavior you're seeing is expected when using viewBox and preserveAspectRatio.
2. In your example Vue and D3 seem to be in conflict over who is in control of the DOM.
Here are some examples to dynamic resizing using different approaches. Run them in full-size windows and use the console to log out the viewport dimensions.
Sara Soueidan's article Understanding SVG Coordinate Systems is really good. Curran Kelleher's example here uses the general update pattern for something that's more idiomatic.
Really hope this helps and good luck with the project! If you find that this answers your question, please mark it as the accepted answer. 👍
Forcing D3 to recalculate the size of the rects and axes on resize events ("sticky" to size of container):
const margin = {top: 20, right: 20, bottom: 50, left: 20}
const width = document.body.clientWidth
const height = document.body.clientHeight
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const minScale = 0,
maxScale = 35;
const xScale = d3.scaleBand()
.range([0, width])
.padding(0.3);;
const yScale = d3.scaleLinear()
.range([0, height]);
const xAxis = d3.axisBottom(xScale)
const yAxis = d3.axisLeft(yScale)
const svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const data = [
{
key: 'One',
value: 33,
},
{
key: 'Two',
value: 30,
},
{
key: 'Three',
value: 37,
},
{
key: 'Four',
value: 28,
},
{
key: 'Five',
value: 25,
},
{
key: 'Six',
value: 15,
},
];
// format the data
data.forEach((d) => {
d.value = +d.value;
});
// Scale the range of the data in the domains
xScale.domain(data.map((d) => d.key));
yScale.domain([minScale, maxScale]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.append("text")
.attr("class", "label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")")
.style("text-anchor", "middle")
.text("X Axis");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("width", xScale.bandwidth())
.attr('x', (d) => xScale(d.key))
.attr("y", d => yScale(d.value))
.attr('height', function (d) {
return height - yScale(d.value);
})
.attr('fill', '#206BF3')
.attr('rx', 5)
.attr('ry', 5);
// Define responsive behavior
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
xScale.rangeRound([0, width], 0.1);
yScale.range([height, 0]);
// Update the axis and text with the new scale
svg.select(".x.axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.select(".label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");
svg.select(".y.axis")
.call(yAxis);
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", xScale.bandwidth())
.attr('x', (d) => xScale(d.key))
.attr("y", d => yScale(d.value))
.attr('height', (d) => height - yScale(d.value));
};
// Call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
// Call the resize function
resize();
.bar {
fill: #206BF3;
}
.bar:hover {
fill: red;
cursor: pointer;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<link rel="stylesheet" type="text/css" href="./style.css" />
</head>
<body>
<svg id="chart"></svg>
<script src="https://d3js.org/d3.v6.js"></script>
<script src="./chart.js"></script>
</body>
Using general update pattern (with transition to illustrate changes):
let data = [
{letter: 'A', frequency: 20},
{letter: 'B', frequency: 60},
{letter: 'C', frequency: 30},
{letter: 'D', frequency: 20},
];
chart(data);
function chart(data) {
var svg = d3.select("#chart"),
margin = {top: 55, bottom: 0, left: 85, right: 0},
width = parseInt(svg.style("width")) - margin.left - margin.right,
height = parseInt(svg.style("height")) - margin.top - margin.bottom;
// const barWidth = width / data.length
const xScale = d3.scaleBand()
.domain(data.map(d => d.letter))
.range([margin.left, width - margin.right])
.padding(0.5)
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.frequency)])
.range([0, height])
const xAxis = svg.append("g")
.attr("class", "x-axis")
const yAxis = svg.append("g")
.attr("class", "y-axis")
redraw(width, height);
function redraw(width, height) {
yScale.range([margin.top, height - margin.bottom])
svg.selectAll(".y-axis")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale)
.ticks(data, d => d.frequency)
.tickFormat(function(d, i) {
return data[i].frequency;
}));
xScale.rangeRound([margin.left, width - margin.right]);
svg.selectAll(".x-axis").transition().duration(0)
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale));
var bar = svg.selectAll(".bar")
.data(data)
bar.exit().remove();
bar.enter().append("rect")
.attr("class", "bar")
.style("fill", "steelblue")
.merge(bar)
// origin of each rect is at top left corner, so width goes to right
// and height goes to bottom :)
.style('transform', 'scale(1, -1)')
.transition().duration(1000)
.attr("width", xScale.bandwidth())
.attr("height", d => yScale(d.frequency))
.attr("y", -height)
.attr("x", d => xScale(d.letter))
.attr("transform", (d, i) => `translate(${0},${0})`)
}
d3.select(window).on('resize', function() {
width = parseInt(svg.style("width")) - margin.left - margin.right,
height = parseInt(svg.style("height")) - margin.top - margin.bottom;
redraw(width, height);
});
}
<!DOCTYPE html>
<html>
<head>
<title>Bar Chart - redraw on window resize</title>
<style>
#chart {
outline: 1px solid red;
position: absolute;
width: 95%;
height: 95%;
overflow: visible;
}
</style>
</head>
<body>
<script type="text/javascript">
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
console.log('viewport width is: '+ windowWidth + ' and viewport height is: ' + windowHeight + '. Resize the browser window to fire the resize event.');
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js"></script>
<svg id="chart"></svg>
<script src="./responsiveBarWindowWidth.js"></script>
</body>
</html>
And here is your graph, only instead of a hard-coded value of 500px for the #my_dataviz parent, assign it a value of 100vh, which allows the svg to respond to the parent container's height and adjust the width accordingly.
Plunker: https://plnkr.co/edit/sBa6VmRH27xcgNiB?preview
Assigning height of 100vh to parent container
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/vue"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<style>
.area {
fill: url(#area-gradient);
stroke-width: 0px;
}
// changed from 500px:
#my_dataviz {
height: 100vh
}
</style>
</head>
<body>
<div id="app">
<div class="page">
<div class="">
<div id="my_dataviz" ref="chart"></div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
type: Array,
required: true,
},
mounted() {
const minScale = 0,
maxScale = 35;
var data = [
{
key: 'One',
value: 33,
},
{
key: 'Two',
value: 30,
},
{
key: 'Three',
value: 37,
},
{
key: 'Four',
value: 28,
},
{
key: 'Five',
value: 25,
},
{
key: 'Six',
value: 15,
},
];
console.log(this.$refs["chart"].clientHeight)
// set the dimensions and margins of the graph
var margin = { top: 20, right: 0, bottom: 30, left: 40 },
width =
this.$refs["chart"].clientWidth - margin.left - margin.right,
height =
this.$refs["chart"].clientHeight - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.3);
var y = d3.scaleLinear().range([height, 0]);
// 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(this.$refs['chart'])
.classed('svg-container', true)
.append('svg')
.attr('class', 'chart')
.attr(
'viewBox',
`0 0 ${width + margin.left + margin.right} ${
height + margin.top + margin.bottom
}`
)
.attr('preserveAspectRatio', 'xMinYMin meet')
.classed('svg-content-responsive', true)
.append('g')
.attr(
'transform',
'translate(' + margin.left + ',' + margin.top + ')'
);
// format the data
data.forEach(function (d) {
d.value = +d.value;
});
// Scale the range of the data in the domains
x.domain(
data.map(function (d) {
return d.key;
})
);
y.domain([minScale, maxScale]);
//Add horizontal lines
let oneFourth = (maxScale - minScale) / 4;
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth))
.attr('y2', y(oneFourth))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 2))
.attr('y2', y(oneFourth * 2))
.style('stroke', 'gray');
svg
.append('svg:line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(oneFourth * 3))
.attr('y2', y(oneFourth * 3))
.style('stroke', 'gray');
//Defenining the tooltip div
let tooltip = d3
.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0)
.style('opacity', 0);
// append the rectangles for the bar chart
svg
.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function (d) {
return x(d.key);
})
.attr('width', x.bandwidth())
.attr('y', function (d) {
return y(d.value);
})
.attr('height', function (d) {
console.log(height, y(d.value))
return height - y(d.value);
})
.attr('fill', '#206BF3')
.attr('rx', 5)
.attr('ry', 5)
.on('mouseover', (e, i) => {
d3.select(e.currentTarget).style('fill', 'white');
tooltip.transition().duration(500).style('opacity', 0.9);
tooltip
.html(
`<div><h1>${i.key} ${
this.year
}</h1><p>${converter.addPointsToEveryThousand(
i.value
)} kWh</p></div>`
)
.style('left', e.pageX + 'px')
.style('top', e.pageY - 28 + 'px');
})
.on('mouseout', (e) => {
d3.select(e.currentTarget).style('fill', '#206BF3');
tooltip.transition().duration(500).style('opacity', 0);
});
// Add the X Axis and styling it
let xAxis = svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
xAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
xAxis.selectAll('.tick text').attr('color', 'gray');
xAxis.selectAll('.tick line').attr('stroke', 'gray');
// add the y Axis and styling it also only show 0 and max tick
let yAxis = svg.append('g').call(
d3
.axisLeft(y)
.tickValues([this.minScale, this.maxScale])
.tickFormat((d) => {
if (d > 1000) {
d = Math.round(d / 1000);
d = d + 'K';
}
return d;
})
);
yAxis
.select('.domain')
.attr('stroke', 'gray')
.attr('stroke-width', '3px');
yAxis.selectAll('.tick text').attr('color', 'gray');
yAxis.selectAll('.tick line').attr('stroke', 'gray');
d3.select(window).on('resize', () => {
svg.attr(
'viewBox',
`0 0 ${this.$refs['chart'].clientWidth} ${this.$refs['chart'].clientHeight}`
);
});
},
});
</script>
</body>
</html>
For learning how to use d3.js, I was trying to use titanic dataset for learning available on kaggle.
I am trying to achieve the objective:
Make a scatterplot of age vs fare with age in x axis and fare in y axis
Use the sex column to have male as square and female as circles in the scatterplot
Have opacity to indicate the condition - survived or not survived.
I have used the following code:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
.attr("r", 1.5)
.style("fill", "#69b3a2")
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
The condition I want to implement is:
if(d.sex == "female"){
return d3.symbolCircle;
} else if (d.sex == "male"){
return d3.symbolSquare;
}
But, being absolute new to the syntax, I am not understanding how. Also, How to have two colors for the 3rd objective of indicating survivor vs dead.
Can anyone help me please. I really thank you in advance.
In case you do not have the dataset, it can also be found here.
Firstly, you need to look at your data. Your properties are all uppercase, and reading a CSV file always means that you need to parse your rows: numbers and dates are still strings, you need to cast them as such:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
// All values are strings here, so we need to parse some of them.
// You can do that using `+x` or `Number(x)`, where `x = "123"`
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
// cabin: d.Cabin,
// embarked: e.Embarked,
fare: Number(d.Fare),
// name: d.Name,
// parch: Number(d.Parch),
// passengerId: Number(d.PassengerId)
// pclass: Number(Pclass),
sex: d.Sex,
// sibSp: Number(d.SibSp),
survived: d.Survived === "1"
// ticket: d.Ticket,
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
.attr("r", 1.5)
.style("fill", "#69b3a2")
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
Secondly, circles can only be circles. To draw both squares and circles, you'll need to use <path>. Your d3.symbol* were correct, but you need to access their .draw() function. d3.path is a generator to easily draw the d attribute of a path:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
// All values are strings here, so we need to parse some of them.
// You can do that using `+x` or `Number(x)`, where `x = "123"`
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
// cabin: d.Cabin,
// embarked: e.Embarked,
fare: Number(d.Fare),
// name: d.Name,
// parch: Number(d.Parch),
// passengerId: Number(d.PassengerId)
// pclass: Number(Pclass),
sex: d.Sex,
// sibSp: Number(d.SibSp),
survived: d.Survived === "1"
// ticket: d.Ticket,
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("transform", function(d) {
return "translate(" + [x(d.age), y(d.fare)] + ")";
})
.attr("d", function(d) {
const path = d3.path();
const shape = d.sex == "female" ? d3.symbolCircle : d3.symbolSquare;
shape.draw(path, 8);
return path.toString();
})
.style("fill", "#69b3a2")
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
(As an aside, you also could have drawn rects for both, but given the female ones rounded corners with the rx attribute).
Finally, you can use a colour scale for fill, with d3.scaleOrdinal, but if you have only two colours and don't use it to colour multiple things (like a line chart and a legend), just use an if statement:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 30,
left: 60
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", function(rawData) {
// All values are strings here, so we need to parse some of them.
// You can do that using `+x` or `Number(x)`, where `x = "123"`
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
// cabin: d.Cabin,
// embarked: e.Embarked,
fare: Number(d.Fare),
// name: d.Name,
// parch: Number(d.Parch),
// passengerId: Number(d.PassengerId)
// pclass: Number(Pclass),
sex: d.Sex,
// sibSp: Number(d.SibSp),
survived: d.Survived === "1"
// ticket: d.Ticket,
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 600])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("transform", function(d) {
return "translate(" + [x(d.age), y(d.fare)] + ")";
})
.attr("d", function(d) {
const path = d3.path();
const shape = d.sex == "female" ? d3.symbolCircle : d3.symbolSquare;
shape.draw(path, 8);
return path.toString();
})
.style("fill", function(d) {
return d.survived ? "#69b3a2" : "#ddd";
})
})
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
One interesting thing in SVG is that a <rect> with the rx and ry attributes equal to half its width or height (those being the same, of course) becomes effectively a circle.
So, supposing you have
var diameter = 3;
All you need is:
.attr("rx", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
.attr("ry", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
And, of course, subtract the x and y positions by half the diameter (i.e., the radius).
That seems like a hack, but the advantage of that approach is that it's quite easy to transition between the square and the "circle" by just changing the rx/ry values (one can transition paths, but that's a bit more complicated). Have a look at this transition, with an exaggerated radius and a clipped domain:
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 10,
left: 60
},
width = 660 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var diameter = 12;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv", row, function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 300])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.Age) - diameter / 2;
})
.attr("y", function(d) {
return y(d.Fare) - diameter / 2;
})
.attr("width", diameter)
.attr("height", diameter)
.style("fill", "#69b3a2")
.transition()
.duration(2000)
.attr("rx", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
.attr("ry", function(d) {
return d.Sex === "male" ? 0 : diameter / 2
})
.style("fill", function(d) {
return +d.Survived ? "#69b3a2" : "tan"
});
})
function row(d) {
d.Age = +d.Age;
d.Fare = +d.Fare;
return d;
}
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
I've a D3 bar chart with Chinese and English text on x-axis. Whenever the Chinese text comes, the labels are overlapping. I'm unable to wrap the text into multiple lines. If it's only of English text, I'm able to wrap it. Is there a way to wrap the text if it has Chinese text too?
Snippet
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
Fiddle for the same snippet.
Your wrap function currently splits on white spaces (/\s+/) and wraps these parts in their own <tspan> elements.
It needs to be smarter to be able to wrap-words.
The easy fix is to wrap each character in its own <tspan>.
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
// split on each character
words = text.text().split('').reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
// join with empty string
tspan.text(line.join(""));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
// join with empty string
tspan.text(line.join(""));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
But doing so, we have the same behavior as word-break: break-all, that is, it will break even non CJK texts.
What we want is the word-break: normal behavior. And for this, the best is to use HTML and CSS.
By generating a dummy HTML element, we can check where every character should be rendered, thanks to Range.getBoundingClientRect method:
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function getLines(text, width) {
// create a dummy element
var dummy = d3.select('body')
.append('p')
.classed('dummy-text-wrapper', true)
// set its size to the one we want
.style('width', width + 'px')
.text(text);
var textNode = dummy.node().childNodes[0];
var lines = [''];
var range = document.createRange();
var current = 0;
// get default top value
range.setStart(textNode, 0);
var prevTop = range.getBoundingClientRect().top;
var nextTop = prevTop;
// iterate through all characters
while (current < text.length) {
// move the cursor
range.setStart(textNode, current+1);
// check top position
nextTop = range.getBoundingClientRect().top;
if(nextTop !== prevTop) {
// new line
lines.push("");
}
// add the current character to the last line
lines[lines.length - 1] += text[current++];
prevTop = nextTop;
}
// clean up the DOM
dummy.remove();
return lines;
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text(),
lines = getLines(words, width),
line = [],
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy"));
text.text('');
lines.forEach(function(words, lineNumber) {
text.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", lineNumber * lineHeight + dy + "em")
.text(words);
});
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
.dummy-text-wrapper {
word-break: normal;
display: inline-block;
opacity: 0;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
I am trying to plot some extremely small values with d3.js. Is there a direct way to visualise the tick labels in scientific (exponential) notation?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<!-- load the d3.js library -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var data = [[0.3, 5e-300],[0.1, 3e-300],[0.7, 4e-300],[0.2, 7e-300],[0.6, 2.5e-300],[0.9, 4.2e-300]]
// set the ranges
var x = d3.scaleLinear().range([0, width]).domain([0, d3.max(data, function(d) { return d[0]; })]);
var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(data, function(d) { return d[1]; })]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").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 + ")");
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 8);
// 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)
.tickFormat(d3.formatPrefix(".1s", 1e-300)));;
</script>
</body>
Here's an example created with in matplotlib. I would like to achieve the same thing with regard to y-axis notation
A solution with d3.format:
svg.append("g")
.call(d3.axisLeft(y)
.tickFormat(d3.format(".1e")));
Here is a demo:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var data = [[0.3, 5e-300],[0.1, 3e-300],[0.7, 4e-300],[0.2, 7e-300],[0.6, 2.5e-300],[0.9, 4.2e-300]]
// set the ranges
var x = d3.scaleLinear().range([0, width]).domain([0, d3.max(data, function(d) { return d[0]; })]);
var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(data, function(d) { return d[1]; })]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").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 + ")");
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 8);
// 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)
.tickFormat(d3.format(".1e")));
<script src="https://d3js.org/d3.v4.min.js"></script>
I came through this problem just when I though I had d3 undercontrol... I can't find out why my code is crashing when I update my barChart using an empty array of data...
here is my code:
<!DOCTYPE html>
<html>
<head>
<title>Simple tables in D3</title>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<style type="text/css">
.chart rect {
fill: steelblue;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.axis {
font: 10px sans-serif;
}
</style>
</head>
<body>
<svg class="chart">
</svg>
<script>
var scanCounters = JSON.parse("[{\"scan\":\"111\",\"repetition\":3},{\"scan\":\"222\",\"repetition\":2},{\"scan\":\"333\",\"repetition\":4},{\"scan\":\"123\",\"repetition\":2},{\"scan\":\"456\",\"repetition\":1},{\"scan\":\"789\",\"repetition\":1}]");
var scanCounters2 = JSON.parse("[{\"scan\":\"111\",\"repetition\":8},{\"scan\":\"222\",\"repetition\":6},{\"scan\":\"333\",\"repetition\":5},{\"scan\":\"123\",\"repetition\":3}]");
var scanCounters3 = JSON.parse("[]");
var columns = ["scan", "repetition"];
var margin = {top: 20, right: 30, bottom: 30, left: 40};
var width = 960;
var height = 500;
var innerWidth = width - margin.left - margin.right;
var innerHeight = height - margin.top - margin.bottom;
var bcScan = barChart(600, 400, "chart");
bcScan.update(scanCounters);
setTimeout(function(){
bcScan.update(scanCounters2);
}, 1000);
setTimeout(function(){
bcScan.update(scanCounters3);
}, 2000);
function barChart(w, h, node) {
var bC = {};
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("." + node)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
bC.update = function(data) {
x.domain(data.map(function (d) {
return d[Object.keys(d)[0]];
}));
y.domain([0, d3.max(data, function (d) {
return d[Object.keys(d)[1]];
})]);
svg.select(".x.axis")
.transition()
.duration(300).call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
svg.select(".y.axis")
.transition()
.duration(300)
.call(yAxis);
var bars = svg.selectAll(".bar").data(data, function (d) {
return d[Object.keys(d)[0]];
});
bars.exit()
.transition()
.duration(300)
.attr("y", y(0))
.attr("height", height - y(0))
.style('fill-opacity', 1e-6)
.remove();
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("y", y(0))
.attr("height", height - y(0));
var trans = bars.transition().duration(300).attr("x", function (d) {
return x(d[Object.keys(d)[0]]);
});
trans.attr("width", x.rangeBand())
.attr("y", function (d) {
return y(d[Object.keys(d)[1]]);
})
.attr("height", function (d) {
return height - y(d[Object.keys(d)[1]]);
});
};
return bC;
}
</script>
</body>
</html>
The problem is when I call bcScan.update(scanCounters3); where scanCounters3 is an empty array.
This is the error message:
Error: Invalid value for attribute height="NaN"
Error: Invalid value for attribute y="NaN"
QUESTON: Could someone help me to understand why it is crashing?
thanks
#EthanJewett, is correct, if you look at your y.domain() on each iteration you get:
[0, 4]
[0, 8]
[0, NaN]
So on your last iteration, your exit transition is doing math on an NaN:
.attr("y", y(0))
.attr("height", height - y(0))
I would just drop those lines altogether and make the exit transition:
bars.exit()
.transition()
.duration(300)
.style('fill-opacity', 1e-6)
.remove();