How to add custom tick labels in d3.js? - javascript

I want to add custom tick labels on the x axis,like 1,2,3,4,3,2,1 in this pattern. But the code that I am using doesn't show the decreasing numbers.
var margin = {
top: 100,
right: 100,
bottom: 100,
left: 100
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain([1, 2, 3, 4, 5, 4, 3, 2, 1])
.rangePoints([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
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.append("g")
.attr("class", "x axis")
.call(xAxis);
Any kind of help would be appreciated. Thanks.

Little hack it needed to solve this issue.
Cause
d3.scale.ordinal(), domain calculation in purely mathematical logic. Each
domain associated with a range. ( f(x)=y ).
Duplication not allowed because it will make ambiguity
Solution
Create linear scale with total item in the axis as domain [0, itemLength]
While creating axis use tickFormat to find out the index of the element
var margin = {
top: 100,
right: 100,
bottom: 100,
left: 100
},
width = 560 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xdata = ["1", "2", "3", "4", "5", "4", "3", "2", "1", "0"];
var linear = d3.scale.linear()
.domain([0, 10])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(linear)
.orient("bottom");
var axis = d3.svg.axis()
.scale(linear)
.orient("top")
.ticks(10)
.tickFormat(function (d) {
return xdata[d];
});
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.append("g")
.attr("class", "x axis")
.call(axis);
.axis path, line {
fill: none;
stroke: #000;
stroke-linecap: round;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

D3 - display x value data ticks as percentages

Have a line graph shown as here: https://imgur.com/1kadiJF
Would love the x-axis to display a & sign after each tick, 10%, 20%, etc. How does one accomplish this?
I've read up on a format method one can use, as well as saw people using Math and actually doing a conversion, but figure there must be a quick way to add a string % after each tick, surely!
Using V4 of D3 here
<script>
// set the dimensions and margins of the graph
const formatPercent = d3.format(".0%")
const margin = {top: 20, right: 30, bottom: 60, left: 250},
width = 1000 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
const svg = d3.select(".line")
.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 + ")");
// Parse the Data
d3.json("./data/linedata.json", function(data) {
// Add X axis
const x = d3.scaleLinear()
.domain([0, 100])
.range([ 0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text").attr('class', 'xaxis')
.attr("transform", "translate(0,0 )")
.style("text-anchor", "end");
// Y axis
const y = d3.scaleBand()
.range([ 0, height ])
.domain(data.map(function(d) { return d.desc; }))
.padding(.1)
svg.append("g").attr('class', 'xaxis')
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", x(0) )
.attr("y", function(d) { return y(d.desc); })
.attr("width", function(d) { return x(d.total); })
.attr("height", y.bandwidth() )
.attr("fill", "#008080")
})
</script>
Have you tried using the tickFormat function?
Something like this should work:
.tickFormat(d => d + "%")

How to fix a multiple brush chart in d3

I am so close this is killing me. I've generated a simple brush for one column and it's generating the limits it's set to perfectly. The thing is I'd like multiple brushes for multiple columns ['A', 'B', 'C', 'D']. I could write this out four times, but I've put it in a function that doesn't appear to work. Please see the working code below, I've commented out the part that doesn't work. I know I need to somehow bind the data and loop through, but how do I do this efficiently?
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(["A", "B", "C", "D"])
.rangeBands([0, 200])
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 100])
var xAxis = d3.svg.axis()
.scale(x)
var svg = d3.select("#timeline").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 + ")");
// define brush control element and its events
var brush = d3.svg.brush().y(y)
.on("brushend", () => console.log('A Extent: ', brush.extent()))
// create svg group with class brush and call brush on it
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush);
// set brush extent to rect and define objects height
brushg.selectAll("rect")
.attr("x", x("A"))
.attr("width", 20);
/*
var brush = (d) => {
var brush = d3.svg.brush().y(y)
.on("brushend", () => console.log(d, ' Extent: ', brush.extent()))
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush1);
brushg.selectAll("rect")
.attr("x", x("A"))
.attr("width", 20);
}
*/
.brush {
fill: lightgray;
fill-opacity: .75;
shape-rendering: crispEdges;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js'></script>
<div class="container">
<div id="timeline"></div>
</div>
Treat the brushes as data, as they map to each ordinal value on the x axis. Create the brushes with .enter and append all the necessary functionality. The .each function is similar to call, but runs on each element separately. This is very useful to contain the generation of the brushes.
xData = ["A", "B", "C", "D"];
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(xData)
.rangeBands([0, 200])
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 100])
var xAxis = d3.svg.axis()
.scale(x)
var svg = d3.select("#timeline").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 + ")");
const brushes = svg.selectAll('g.brush')
.data(xData)
.enter()
.append('g')
.attr('class', 'brush')
.each(function(d) {
const el = d3.select(this);
const brush = d3.svg.brush().y(y).on("brushend", () => console.log(d, ' Extent: ', brush.extent()));
el.call(brush);
el.selectAll("rect")
.attr("x", x(d))
.attr("width", 20);
});
.brush {
fill: lightgray;
fill-opacity: .75;
shape-rendering: crispEdges;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js'></script>
<div class="container">
<div id="timeline"></div>
</div>

D3.js Adjust labels closer to each other

I'm graphing data with long Y-axis labels (temperature ranges). How do I adjust the labels so that they're centered on the ticks when they're rotated 90 degrees and don't run off the edge of the graph? I've attempted to use this.getBBox() to apply a transform based on the iteration of the label, but that doesn't seem to work.
Thanks!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 100, right: 100, bottom: 100, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var categories = {
temperature: ["29-30°C - Red","31-33° C - Green","> 33° C - Blue"],
sleep: ["7 - 8 hours", "5 - 6 hours", "4 or less hours"]
}
var mindate = new Date(2012,0,1),
maxdate = new Date(2012,0,31);
var xScale = d3.time.scale()
.domain([mindate, maxdate]) // values between for month of january
.range([0, width - margin.right]);
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale);
var yKey = "temperature";
var yScale = d3.scale.ordinal()
.domain(categories[yKey])
.rangePoints([0, height]);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
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.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "yAxis axis")
.call(yAxis)
.selectAll("text")
.attr("x", 10)
.attr("y",20)
.attr("transform", "rotate(90)")
.style("text-anchor", "start")
</script>
Here's what I've tried but this doesn't seem to move the labels much. I'm not sure how to set the "x" inside the following function without using a translate.
d3.selectAll(".yAxis text")
.attr("transform", function(d,i) {
return "translate(" + -20 + "," + ((this.getBBox().width/(i+2))) + ")rotate(-90)"
})
Aha! The answer is that d3.scale.ordinal has a padding setting, so all I have to do is change my yScale to
var yScale = d3.scale.ordinal()
.domain(categories[yKey])
.rangePoints([0, height],1);
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 100, right: 100, bottom: 100, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var categories = {
temperature: ["29-30°C - Red","31-33° C - Green","> 33° C - Blue"],
sleep: ["7 - 8 hours", "5 - 6 hours", "4 or less hours"]
}
var mindate = new Date(2012,0,1),
maxdate = new Date(2012,0,31);
var xScale = d3.time.scale()
.domain([mindate, maxdate]) // values between for month of january
.range([0, width - margin.right]);
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale);
var yKey = "temperature";
var yScale = d3.scale.ordinal()
.domain(categories[yKey])
.rangePoints([0, height],1);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
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.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "yAxis axis")
.call(yAxis)
.selectAll("text")
.attr("x", 10)
.attr("y",20)
.attr("transform", "rotate(90)")
.style("text-anchor", "middle")
</script>

How to display d3 elements on a gridster box?

I am trying to create an area chart using d3, using this array:
var jsonResObj = {
initial_hours: [
1800, 1700, 1030, 1130, 950, 1249, 1225, 1821, 1250,
1505, 38, 130, 1520, 1600, 1330, 1930, 1806, 1535
]
};
The code for the axis and the chart is here:
var margin = {top: 20, right: 20, bottom: 40, left: 50},
width = 350 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom,
days = jsonResObj.initial_hours.length;
var x = d3.scale.linear()
.domain([0, days])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(jsonResObj.initial_hours)])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var area = d3.svg.area()
.x(function(d) { return x(days); })
.y0(height)
.y1(function(d) { return y(jsonResObj.initial_hours); });
var svg = d3.select("#box1").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.append("path")
.datum(jsonResObj)
.attr("class", "area")
.attr("d", area);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
My Gridster box:
<li data-sizey="3" data-sizex="3" data-col="3" data-row="1">
<div class="gridster-box" id="box1">
<div class="handle-resize"></div>
</div>
</li>
When I load my page, only the axes show up and the area selector
is 0px * 0px
How do I draw these area charts in my gridster box?
You need to transform the data so each data point is an object containing the x and y properties and can properly be accessed by your area function.
The way you have it currently, there is no way for d3 to know which day or hour an area data point is referring to.
See code below showing changes to the area function to refer to the day and hour properties of the data object passed via function(d). The data transformation is right after the svg variable declaration. Here's a working fiddle of the solution.
var jsonResObj = {
initial_hours: [
1800, 1700, 1030, 1130, 950, 1249, 1225, 1821, 1250,1505, 38, 130, 1520, 1600, 1330, 1930, 1806, 1535
]
};
var margin = {top: 20, right: 20, bottom: 40, left: 50},
width = 350 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom,
days = jsonResObj.initial_hours.length;
var x = d3.scale.linear()
.domain([0, days])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(jsonResObj.initial_hours)])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var area = d3.svg.area()
.x(function(d) { return x(d.day); })
.y0(height)
.y1(function(d) { return y(d.hour); });
var svg = d3.select("#box1").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 = [];
jsonResObj.initial_hours.forEach(function(d,i){
data.push({day: i, hour: d})
});
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(yAxis);

How to unsqueezed the y-axis in D3.js

I have a this script which try to plot the x and y-axis:
var margin = {top: 100, right: 100, bottom: 100, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(["apple", "orange", "banana"])
.rangePoints([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
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.append("g")
.attr("class", "x axis")
.call(xAxis);
var y = d3.scale.ordinal()
.domain(["1", "2", "3"])
.rangePoints([0, 3]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
As you can see upon running the code the y-axis is squeezed down. How can I unsqueezed it?
You need to set your y range correctly
var y = d3.scale.ordinal()
.domain(["1", "2", "3"])
.rangePoints([height, 0]);
From the documentation (https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangePoints),
# ordinal.rangePoints(interval[, padding])
Sets the output range from the specified continuous interval. The
array interval contains two elements representing the minimum and
maximum numeric value. This interval is subdivided into n
evenly-spaced points, where n is the number of (unique) values in the
input domain.
You input domain has 3 values which you want to space out over the height of the chart.
Stack Snippet
You might also want to translate your x axis to the bottom (if not, just revert the translate on the x axis)
var margin = {top: 100, right: 100, bottom: 100, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(["apple", "orange", "banana"])
.rangePoints([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
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.append("g")
.attr("class", "x axis")
.call(xAxis)
.attr("transform", "translate(0, " + height + ")");;
var y = d3.scale.ordinal()
.domain(["1", "2", "3"])
.rangePoints([height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>

Categories