D3 non-circular arc - javascript

I'm looking to create non-circular arcs using SVG in HTML using d3js v3. My problem is that the non-circular arcs I'm able to create are actually circular arcs transformed. As a result, the arc's stroke width isn't uniform and looks awkward. Here is a jsFiddle example:
var vis = d3.select("body").append("svg")
vis.attr("width", "100%")
.attr("height", "100%")
.append("path")
.attr("d", d3.svg.arc()
.innerRadius(500)
.outerRadius(500)
.startAngle(-90 * (Math.PI / 180))
.endAngle(90 * (Math.PI / 180)))
.attr("stroke", "black")
.attr("transform", "translate(510,170) scale(1,0.3)")
.attr("stroke-width", 14);
Here is an example of the result:
Here is what the arc should look like (drawn in Viso):
Does anyone know of a way to create SVG arc's that are non-circular (meaning the radius changes)?

You can create a simple line chart and use d3.curveBasis or any other interpolation you like. Play with the data to get the desired result.
Here's the snippet:
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
var width = 550 - margin.left - margin.right;
var height = 150 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var valueline = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
}).curve(d3.curveBasis);
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 + ")");
var data = [{
x: 1,
y: "0"
}, {
x: 1.05,
y: "0.6"
}, {
x: 3,
y: "1.3"
}, {
x: 4.95,
y: "0.6"
}, {
x: 5,
y: "0"
}];
data.forEach(function(d) {
d.x = d.x
d.y = +d.y;
});
x.domain(d3.extent(data, function(d) {
return d.x;
}));
y.domain([0, d3.max(data, function(d) {
return d.y;
})]);
svg.append("path")
.attr("d", valueline(data));
path {
stroke: black;
stroke-width: 8;
fill: none;
stroke-linecap: round;
shape-rendering: geometricprecision;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

Related

D3.js Zoom on area graph, weird behaivor if y domain takes negative value

I've been trying to do a Area graph with zoom, which works great unless i give a negative value to Y domain. And then it looks great if i don't try to zoom along the y axis
I've tried using the min value of of y for y0 and while that fixes the rendering (it looks god awful and it renders X where it has no value).
// set the dimensions and margins of the graph
let margin = {
top: 0,
right: 0,
bottom: 30,
left: 30
},
width = 440 - margin.left - margin.right,
height = 240 - margin.top - margin.bottom;
// append the svg object to the body of the page
let 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://raw.githubusercontent.com/dimitriiBirsan/RandomNumbersYear1970-2031/main/testing%20negative%20numbers.csv",
// When reading the csv, I must format variables:
function(d) {
return {
date: d3.timeParse("%m/%d/%Y")(d.date),
value: d.value
}
},
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
let x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return +d.date;
}))
.range([0, width]);
let xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(4));
// Add Y axis
let y = d3.scaleLinear()
.domain(d3.extent(data, function(d) {
return +d.value;
}))
.range([height, 0]);
let yAxis = svg.append("g")
.call(d3.axisLeft(y));
function make_x_gridlines(x) {
return d3.axisBottom(x)
}
function make_y_gridlines(y) {
return d3.axisLeft(y)
}
// Add a clipPath : everything out of this area won't be drawn
let clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
// Add the area
let line = svg.append('g')
.attr("clip-path", "url(#clip)")
line.append("path")
.datum(data)
.attr("class", "polyArea")
.attr("fill", "blue")
.attr("opacity", 0.7)
.attr("stroke", "none")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) {
return x(d.date)
})
.y0(y(0))
.y1(function(d) {
return y(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null))
);
let zoom = d3.zoom()
.scaleExtent([1, 50]) // This control how much you can unzoom (x0.5) and zoom (x20)
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", updateChart);
// This adds an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr('transform', 'translate(' + margin.top + ')')
.call(zoom);
function updateChart() {
// recover the new scale
let newX = d3.event.transform.rescaleX(x);
let newY = d3.event.transform.rescaleY(y);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX).ticks(5))
yAxis.call(d3.axisLeft(newY))
// update location
svg
.select('.polyArea')
.attr("d", d3.area()
.x(function(d) {
return newX(d.date)
})
.y0(y(0))
.y1(function(d) {
return newY(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null)))
}
})
.grid line {
stroke: grey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width 0;
}
<main>
<div id="my_dataviz">
</div>
</main>
<script src="https://d3js.org/d3.v4.js"></script>
You forgot to use newY(0) instead of y(0) so now when you zoom, the zero line is where it should be, not where it was when you started
// set the dimensions and margins of the graph
let margin = {
top: 0,
right: 0,
bottom: 30,
left: 30
},
width = 440 - margin.left - margin.right,
height = 240 - margin.top - margin.bottom;
// append the svg object to the body of the page
let 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://raw.githubusercontent.com/dimitriiBirsan/RandomNumbersYear1970-2031/main/testing%20negative%20numbers.csv",
// When reading the csv, I must format variables:
function(d) {
return {
date: d3.timeParse("%m/%d/%Y")(d.date),
value: d.value
}
},
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
let x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return +d.date;
}))
.range([0, width]);
let xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(4));
// Add Y axis
let y = d3.scaleLinear()
.domain(d3.extent(data, function(d) {
return +d.value;
}))
.range([height, 0]);
let yAxis = svg.append("g")
.call(d3.axisLeft(y));
function make_x_gridlines(x) {
return d3.axisBottom(x)
}
function make_y_gridlines(y) {
return d3.axisLeft(y)
}
// Add a clipPath : everything out of this area won't be drawn
let clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
// Add the area
let line = svg.append('g')
.attr("clip-path", "url(#clip)")
line.append("path")
.datum(data)
.attr("class", "polyArea")
.attr("fill", "blue")
.attr("opacity", 0.7)
.attr("stroke", "none")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) {
return x(d.date)
})
.y0(y(0))
.y1(function(d) {
return y(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null))
);
let zoom = d3.zoom()
.scaleExtent([1, 50]) // This control how much you can unzoom (x0.5) and zoom (x20)
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", updateChart);
// This adds an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr('transform', 'translate(' + margin.top + ')')
.call(zoom);
function updateChart() {
// recover the new scale
let newX = d3.event.transform.rescaleX(x);
let newY = d3.event.transform.rescaleY(y);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX).ticks(5))
yAxis.call(d3.axisLeft(newY))
// update location
svg
.select('.polyArea')
.attr("d", d3.area()
.x(function(d) {
return newX(d.date)
})
.y0(newY(0))
.y1(function(d) {
return newY(d.value)
})
.curve(d3.curveStepAfter)
.defined((d, i) => (i != null)))
}
})
.grid line {
stroke: grey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width 0;
}
<main>
<div id="my_dataviz">
</div>
</main>
<script src="https://d3js.org/d3.v4.js"></script>

How to have make this visualization in d3.js using values from a column matching a specific string

I am trying to implement the following problem while learning d3.js for visualization.
Using the following titanic dataset:
Plot in scatterplot :
a)the male passengers using an SVG square (width 5, x and y - 2.5 )
b)the female passengers using a circle of radius 2.8
c) Have the survived column used as opacity such that the dead have opacity 0.25 and alive have opacity: 1;
fill-opacity:.1;
stroke: black;
Make the scatterplot axes, make the y axis to log scale, and add the passengers name on their mark (using the SVG title element).
I am implementing the following code to achieve my goals but, I have am not successful in displaying my graph.
Can anyone please help me.
The titanic dataset - here
And my code here:
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - 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) {
const data = rawData.map(function(d) {
return {
age: Number(d.age),
fare: Number(d.fare),
sex: d.sex,
survived: d.survived === "1",
name: d.name
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")");
// Add Y axis
var y = d3.scaleLog()
.domain([1e+0, 1e+3])
.range([height, 0]);
svg.append("g");
// Add dots
svg.append('g')
.selectAll("dot").select("female")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
//.attr("r", 2.8)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
svg.append('g')
.selectAll("dot").select("male")
.data(data)
.enter()
.append("rect")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
//.attr("width", 5)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
.append("svg:title")
.text(function(d) {
return d.name
});
})
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
can anyone please highlight where i am making mistake and help me please
You really, really need to read the manual, especially the SVG one. rect nodes don't have cx and cy, they have x and y, width, and height. And circle needs a radius r in order to be visible.
And you gave all the properties you read a lowercase starting letter. They need capitals. Look up a manual on debugging.
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - 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) {
const data = rawData.map(function(d) {
return {
age: Number(d.Age),
fare: Number(d.Fare),
sex: d.Sex,
survived: d.Survived === "1",
name: d.Name
};
});
// Add X axis
var x = d3.scaleLinear()
.domain([0, 80])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")");
// Add Y axis
var y = d3.scaleLog()
.domain([1e+0, 1e+3])
.range([height, 0]);
svg.append("g");
// Add dots
svg.append('g')
.selectAll("dot").select("female")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.age);
})
.attr("cy", function(d) {
return y(d.fare);
})
.attr("r", 2.8)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
svg.append('g')
.selectAll("dot").select("male")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.age);
})
.attr("y", function(d) {
return y(d.fare);
})
.attr("width", 5)
.attr("height", 5)
.style("opacity", function(d) {
return d.survived ? "1" : "0.25";
})
.style("stroke", "black")
.style("fill-opacity", 0.1)
.append("svg:title")
.text(function(d) {
return d.name
});
})
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>

d3 v5 use nested values when scaling axis

I have the following code that nests my data based on region and date. The problem I am having is that I don't know how to define yScale to dynamically draw the axis so that the max sum from the nested data is returned (the max val of the nested data is higher than the max val in the dataset bc it is aggregated). Thus my yAxis is truncated and the chart doesn't show all the data.
In the code, if I hardcode the domain to .domain([0, 3500]) then the axis is correct, but otherwise it is not correct. I don't want to hardcode the domain. How do I reference the nested values?
EDITED to show code provided in comments, which helps but doesn't entirely fix when the script is run on the entire dataset. Please see pic at bottom.
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) {
return parseInt(d.count,10);
})])
.range([h - padding, padding])
// var yScale = d3.scaleLinear()
// .domain([0, 3500])
// .range([h - padding, padding]) //not supposed to hard code the scale but it is not working
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nested Chart</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
.pagebreak {
page-break-before: always;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.point {
fill: none;
size: 2px
}
.dot {
fill: #ffab00;
stroke: #fff;
}
</style>
</head>
<div style="width:800px; margin:0 auto;" class='body'></div>
<div class="pagebreak"> </div>
<body>
<script type="text/javascript">
var parseTime = d3.timeParse("%Y");
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var padding = 20;
/////////////////get the data//////////////////
const csv = `state,region,year,count
Alabama,South,2010,1
Alabama,South,2011,1
Alabama,South,2012,0
Alabama,South,2013,0
Alabama,South,2014,2
Alabama,South,2015,6
Alaska,West,2010,2245
Alaska,West,2011,1409
Alaska,West,2012,1166
Alaska,West,2013,1329
Alaska,West,2014,1296
Alaska,West,2015,1575
Connecticut,Northeast,2010,0
Connecticut,Northeast,2011,0
Connecticut,Northeast,2012,0
Connecticut,Northeast,2013,0
Connecticut,Northeast,2014,0
Connecticut,Northeast,2015,1
Missouri,Midwest,2010,2
Missouri,Midwest,2011,3
Missouri,Midwest,2012,2
Missouri,Midwest,2013,0
Missouri,Midwest,2014,1
Missouri,Midwest,2015,5
California,West,2010,546
California,West,2012,243
California,West,2013,240
Wyoming,West,2015,198
California,West,2011,195
California,West,2014,191`;
const dataset = d3.csvParse(csv);
dataset.forEach(function(d) {
d.date = parseTime(d.year);
d.region = d['region'];
d.state = d['state'];
d.count = d['count'];
//console.log(d)
});
/////////////////scales the data//////////////////
var xScale = d3.scaleTime()
.domain([d3.min(dataset, function(d) {
return d.date
}), d3.max(dataset, function(d) {
return d.date
})]).range([padding, w - padding * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) {
console.log(d.count)
return d.count ///ERROR HERE
})]).range([h - padding, padding])
// var yScale = d3.scaleLinear()
// .domain([0, 3500])
// .range([h - padding, padding]) //not supposed to hard code the scale but it is not working otherwise...commented out above
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
/////////////////charts start here//////////////////
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 + ")");
var svg1 = 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 + ")");
//Define the line
var valueLine = d3.line()
.x(function(d) {
return xScale(new Date(d.key));
})
.y(function(d) {
return yScale(d.value);
})
var nest = d3.nest()
.key(function(d) {
return d.region;
})
.key(function(d) {
return d.date;
})
.rollup(function(leaves) {
return d3.sum(leaves, function(d) {
return (d.count)
});
})
.entries(dataset)
console.log(nest)
// Set the color scheme
var colors = d3.scaleOrdinal()
.domain(["South", "West", "Northeast","Midwest"])
.range(["#EF5285", "#88F284" , "#5965A3","#900C3F"]);
var regYear = svg.selectAll(".regYear")
.data(nest)
.enter()
.append("g")
.attr("stroke", function(d){ return colors(d.key)}); // Adding color!
// console.log(regYear)
var paths = regYear.selectAll(".line")
.data(function(d) {
return [d.values]
})
.enter()
.append("path");
// Draw the line
paths
.attr("d", function(d) {
return valueLine(d)
})
.attr("class", "line")
.style("fill", "none");
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) { return xScale(i) })
.attr("cy", function(d) { return yScale(d.count) })//this is not working
.attr("r", 5);
svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
//draw Y axis
svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
// add label
svg.append("text").attr("x", (w / 2)).attr("y", h + 30).attr("text-anchor", "middle").text("Year");
svg.append("text").attr("x", padding).attr("y", padding - 20).attr("text-anchor", "middle").text("# of Events");
//add title
svg.append("text").attr("x", (w / 2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("x", w - 65)
.attr("y", 25)
.attr("height", 100)
.attr("width", 100);
////////////////////////////////////END///////////////////////////
</script>
</body>
</html>
UPDATE :
The max value of yAxis is less than the actual max value of the nest data, so it's truncated.
You have to use your nest data inside yScale calculation rather than using the original dataset data.
Steps to achieve this:
define nest first
get the totalMax value by flatting nest twice
use totalMax to calculate the yScale value
var totalMax = Object
.entries(nest)
.reduce(function(totalMax, [key, regionValue]){
const regionMax = Object
.entries(regionValue.values)
.reduce(function(regionMax, [key,yearValue]){
return parseInt(yearValue.value,10) > regionMax ? parseInt(yearValue.value, 10) : regionMax;
}, 0)
return parseInt(regionMax, 10) > totalMax ? parseInt(regionMax, 10) : totalMax;
}, 0)
var yScale = d3.scaleLinear().domain([0, totalMax]).range([h - padding, padding])
I write a Demo base on your code :
var parseTime = d3.timeParse("%Y");
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var padding = 20;
/////////////////get the data//////////////////
const csv = `state,region,year,count
Alabama,South,2010,1
Alabama,South,2011,1
Alabama,South,2012,0
Alabama,South,2013,0
Alabama,South,2014,2
Alabama,South,2015,6
Alaska,West,2010,2245
Alaska,West,2011,1409
Alaska,West,2012,1166
Alaska,West,2013,1329
Alaska,West,2014,1296
Alaska,West,2015,1575
Connecticut,Northeast,2010,0
Connecticut,Northeast,2011,0
Connecticut,Northeast,2012,0
Connecticut,Northeast,2013,0
Connecticut,Northeast,2014,0
Connecticut,Northeast,2015,1
Missouri,Midwest,2010,2
Missouri,Midwest,2011,3
Missouri,Midwest,2012,2
Missouri,Midwest,2013,0
Missouri,Midwest,2014,1
Missouri,Midwest,2015,5
California,West,2010,546
California,West,2012,243
California,West,2013,240
Wyoming,West,2015,198
California,West,2011,195
California,West,2014,191`;
const dataset = d3.csvParse(csv);
dataset.forEach(function(d) {
d.date = parseTime(d.year);
d.region = d['region'];
d.state = d['state'];
d.count = d['count'];
});
var nest = d3.nest()
.key(function(d) {
return d.region;
})
.key(function(d) {
return d.date;
})
.rollup(function(leaves) {
return d3.sum(leaves, function(d) {
return parseInt(d.count, 10);
});
})
.entries(dataset)
/////////////////scales the data//////////////////
var xScale = d3.scaleTime()
.domain([d3.min(dataset, function(d) {
return d.date
}), d3.max(dataset, function(d) {
return d.date
})]).range([padding, w - padding * 2])
var totalMax = Object
.entries(nest)
.reduce(function(totalMax, [key, regionValue]){
const regionMax = Object
.entries(regionValue.values)
.reduce(function(regionMax, [key,yearValue]){
return parseInt(yearValue.value,10) > regionMax ? parseInt(yearValue.value, 10) : regionMax;
}, 0)
return parseInt(regionMax, 10) > totalMax ? parseInt(regionMax, 10) : totalMax;
}, 0)
var yScale = d3.scaleLinear()
.domain([0, totalMax]).range([h - padding, padding])
// var yScale = d3.scaleLinear()
// .domain([0, 3500])
// .range([h - padding, padding]) //not supposed to hard code the scale but it is not working otherwise...commented out above
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
/////////////////charts start here//////////////////
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 + ")");
var svg1 = 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 + ")");
//Define the line
var valueLine = d3.line()
.x(function(d) {
return xScale(new Date(d.key));
})
.y(function(d) {
return yScale(d.value);
})
// Set the color scheme
var colors = d3.scaleOrdinal()
.domain(["South", "West", "Northeast","Midwest"])
.range(["#EF5285", "#88F284" , "#5965A3","#900C3F"]);
var regYear = svg.selectAll(".regYear")
.data(nest)
.enter()
.append("g")
.attr("stroke", function(d){ return colors(d.key)}); // Adding color!
var paths = regYear.selectAll(".line")
.data(function(d) {
return [d.values]
})
.enter()
.append("path");
// Draw the line
paths
.attr("d", function(d) {
return valueLine(d)
})
.attr("class", "line")
.style("fill", "none");
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) { return xScale(i) })
.attr("cy", function(d) { return yScale(d.count) })//this is not working
.attr("r", 5);
svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
//draw Y axis
svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
// add label
svg.append("text").attr("x", (w / 2)).attr("y", h + 30).attr("text-anchor", "middle").text("Year");
svg.append("text").attr("x", padding).attr("y", padding - 20).attr("text-anchor", "middle").text("# of Events");
//add title
svg.append("text").attr("x", (w / 2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("x", w - 65)
.attr("y", 25)
.attr("height", 100)
.attr("width", 100);
////////////////////////////////////END///////////////////////////
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nested Chart</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
.pagebreak {
page-break-before: always;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.point {
fill: none;
size: 2px
}
.dot {
fill: #ffab00;
stroke: #fff;
}
</style>
</head>
<div style="width:800px; margin:0 auto;" class='body'></div>
<div class="pagebreak"> </div>
<body>
</body>
</html>
I noticed the data type of your d.count is string so that the max won't be correct.
console.log(d3.max(['6','2245'])) // it's 6!
Try converting the value to the number before return it :
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) {
return parseInt(d.count,10);
})])
.range([h - padding, padding])
To get max value inside nested array you can nest d3.max function in the way like"
var maxCountSum = d3.max(nest, function(d) {
return d3.max(d.values, function (f) {
return f.value
});
});
Then apply to yScale domain:
var yScale = d3.scaleLinear()
.domain([0, maxCountSum]).range([height, 0]);

Unable to wrap the Chinese text on x-axis in D3 bar chart

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>

Putting custom labels on D3 X axis graph

My making progress with my graph, it seems to mostly work. However I'm struggling to put my data values on the x axis. In this case, there should be 3 x axis labels, (test1, test2,test3).
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
It seems to just label it by how many entries there are (0,1,2) rather than using the name. What I tried was changing this:
var line = d3.line()
.x(function(d, i) {
return xScale(i);
To this (which I must admit was a bit of a guess).
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
Unfortunately that didn't work and I'm not sure what I can try next. Here is the full code if that helps.
http://jsfiddle.net/spadez/cfz3g4w2/
You are using the wrong scale for your x data. You have discrete data and want an ordinal scale.
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input is an array of names
.range([0, width]); // output
Running code:
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
// Count number of datapoints
var n = Object.keys(dataset).length;
// Find max of the data points for Y axis
var mymax = Math.max.apply(Math, dataset.map(function(o) {
return o.y;
}));
// 2. Use the margin convention practice
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right;
height = window.innerHeight - margin.top - margin.bottom;
// 5. X scale will use the index of our data
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, mymax]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
}) // set the x values for the line generator
.y(function(d) {
return yScale(d.y);
}) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
// 1. Add the SVG to the page and employ #2
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 + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) {
return xScale(d.name)
})
.attr("cy", function(d) {
return yScale(d.y)
})
.attr("r", 5)
.on("mouseover", function(a, b, c) {
console.log(a)
this.attr('class', 'focus')
})
.on("mouseout", function() {})
.on("mousemove", mousemove);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(d);
}
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
.overlay {
fill: none;
pointer-events: all;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #ffab00;
stroke: #fff;
}
.focus circle {
fill: none;
stroke: steelblue;
}
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v5.min.js"></script>

Categories

Resources