How to transform (rotate) svg text element without changing coordinates? - javascript

I'm new to this d3.js. Here I'm trying to draw a bar chart using the d3.v3.js library. Here I'm facing few problems. I have tried to include measure labels onto the y-axis. But once I transform the labels -90 degree its position is automatically changing.
Here's the code I'm used to drawing:
svg.append("g")
.attr("id","x_axis")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// .attr("transform", "translate(" + bandSize + ", 0)")
svg.append("g")
.attr("class", "x axis")
.attr("id","dimLabel")
.append("text")
.style("text-anchor", "end")
.attr("x", width/2)
.attr("y", height+55)
.text(dimensionLabels[0]);
svg.append("g")
.attr("id","y_axis1")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
//.attr("transform", "rotate(-90)")
.attr("y", function(d){return y(d3.max(data, function(d) { return d.Metric1; }));});
//Measure Labels
svg.append("g")
.attr("class", "y axis")
.attr("id","measureLabels")
.append("text")
.style("text-anchor", "end")
.attr("x", 0)
.attr("y", height/2)
.text(measureLabels[0]+", "+measureLabels[1])
.attr("transform", "rotate(-90)");
Output Image:
Any help is greatly appreciated.

What you see right now is the expected result, because the rotate function of the transform attribute rotates the element around the origin (0,0), not around its center.
The easiest solution is dropping your attr("x") and attr("y") and positioning the text using the same transform:
var width = 300,
height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var text = svg.append("text")
.text("Sum (sales), Sum (quantity)")
.attr("text-anchor", "middle")
.attr("transform", "translate(20," + (height / 2) + ") rotate(-90)")
svg {
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
Another solution is setting the center of the rotate to the same x and y values:
var width = 300,
height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var text = svg.append("text")
.text("Sum (sales), Sum (quantity)")
.attr("text-anchor", "middle")
.attr("x", 20)
.attr("y", 150)
.attr("transform", "rotate(-90, 20, 150)")
svg {
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

I think you are looking for the css property transform-origin
https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin

Related

Can't draw rectangular in a barchart

The following code in d3js is supposed to draw a bar chart. It draws the axis and adds the labels to them. But it doesn't draw the bars. My guess is it can't find the svg.selectAll(".bar") but I don't know how I should fix it.
P.S. I was following this tutorial: https://github.com/colorfest/d3js/blob/master/js/bargraph/bargraph.js
function drawBarchart(geography){
var x_labels = ["Very Good", "Good", "Fair", "Poor", "Mentioned", "Not Mentioned"];
var margin = {top: 20, right: 20, bottom: 100, left: 60},
width = 800 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
x = d3.scale.ordinal().rangeRoundBands([0,width], 0.5),
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")
.ticks(5)
var svg = d3.select("#barchart")
.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 + ")");
d3.json("data.json", function (data)
{
x.domain(x_labels.map(function (l)
{
return l;
}));
y.domain([0,300]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-0.5em")
.attr("dy", "-.55em")
.attr("y", 30)
.attr("transform", "rotate(-45)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", "0.8em")
.attr("text-anchor", "end")
.text("Score");
svg.selectAll(".bar")
.data(data[6][geography.id])
.enter()
.append("rect")
.style("fill", "orange")
.attr("x", function(d)
{
return x(d.name);
})
.attr("width", x.rangeBand())
.attr("y", function (d)
{
return y(d["Health"][0]);
})
.attr("height", function (d)
{
return height;
});
}
I updated part of you code block. You were correct with it no finding .bar as the rects were not given the bar class.
svg.selectAll(".bar")
.data(data[6][geography.id])
.enter()
.append("rect")
.attr("class", "bar")
.style("fill", "orange")
.attr("x", function(d)
{
return x(d.name);
})
.attr("width", x.rangeBand())
.attr("y", function (d)
{
return y(d["Health"][0]);
})
.attr("height", function (d)
{
return height;
});
}

Cannot read property 'getHours' of undefined

I am trying to build a simple bar chart with some financial data in json format.
I am having this strange error popup when I try to use my parsed time. When I console log it shows the data to be parsed ok - it doesn't return null or anything.
d3.v3.min.js:1 Uncaught TypeError: Cannot read property 'getHours' of undefined
at H (d3.v3.min.js:1)
at SVGTextElement.t (d3.v3.min.js:1)
at SVGTextElement.arguments.length.each.function.n.textContent (d3.v3.min.js:3)
at d3.v3.min.js:3
at Y (d3.v3.min.js:1)
at Array.Co.each (d3.v3.min.js:3)
at Array.Co.text (d3.v3.min.js:3)
at SVGGElement.<anonymous> (d3.v3.min.js:5)
at d3.v3.min.js:3
at Y (d3.v3.min.js:1)
Its hard to troubleshoot this as I cannot understand which part of my code is responsible for causing this problem because it points toward d3.v3.min.js. I assume that the problem is in d3.extent as I used it to replace d3.max for the x-axis.
This is the code that gives the error:
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%H:%M:%S.%L").parse;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%H:%M:%S.%L"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
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 + ")");
d3.json("https://gist.githubusercontent.com/kvyb/cba0e652b7fd9349604cf45ced75fbf9/raw/3f824e76c38479a1a327abbb5c85a5962fec6f21/schudata.json", function(error, data) {
data["bboList"].forEach(function(d) {
d.timeStr = parseDate(d.timeStr);
d.value = +d.ask;
console.log(d.timeStr)
});
x.domain(d3.extent(data, function(d) { return d.timeStr; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.timeStr); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
</script>
</body>
P.S.
There is another issue which is outside the bounds of this question, but needs to be included.
In order to get the bars to render also replaced
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
with
var x = d3.time.scale().range([0, width]);
and changing the width for the bars to a fixed value.
I assume that the problem is in d3.extent...
Yes, you are correct, and the explanation is simple: d3.extent accepts an array as the first argument. However, your data is not an array, but an object with two properties (each one having one array).
Thus, you should select one of them to set your domains (and your bar's data as well):
x.domain(d3.extent(data["bboList"], function(d) {
return d.timeStr;
}));
y.domain([0, d3.max(data["bboList"], function(d) {
return d.value;
})]);
Here is your working code:
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%H:%M:%S.%L").parse;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%H:%M:%S.%L"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
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 + ")");
d3.json("https://gist.githubusercontent.com/kvyb/cba0e652b7fd9349604cf45ced75fbf9/raw/3f824e76c38479a1a327abbb5c85a5962fec6f21/schudata.json", function(error, data) {
data["bboList"].forEach(function(d) {
d.timeStr = parseDate(d.timeStr);
d.value = +d.ask;
});
x.domain(d3.extent(data["bboList"], function(d) { return d.timeStr; }));
y.domain([0, d3.max(data["bboList"], function(d) { return d.value; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll("bar")
.data(data["bboList"])
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.timeStr); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
path, line {
fill: none;
stroke: black;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
PS: Have in mind that I'm only answering your question ("Cannot read property 'getHours' of undefined"). You still have some problems drawing those bars.

Amend position of D3 axes labels

In below graph :
the axes labels are appearing at top of y axis and to extreme right of x axis.
How can amend the positioning of the labels so they appear in center of axes positions and behind tick points (marked in blue lines) ? :
Here is fiddle : http://jsfiddle.net/zzz8svuq/11/
and code :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<style>
body {
font: 11px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
stroke-width: 1.5px;
}
</style>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<pre style="display:none" id="data">
label,x,y,r
l1,100,30,50
l2,5,5,100
l3,50,50,20
</pre>
<script type="text/javascript">
$( document ).ready(function() {
var margin = {top: 20, right: 20, bottom: 130, left: 140},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xValue = function (d) {
return d.x;
}
xScale = d3.scale.linear().range([0, width - margin.left - margin.right]) // value -> display
xMap = function (d) {
return xScale(xValue(d));
}
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yValue = function (d) {
return d["y"];
} // data -> value
yScale = d3.scale.linear().range([height, 0]) // value -> display
yMap = function (d) {
return yScale(yValue(d));
} // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
var rValue = function (d) {
return d["r"];
} // data -> value
rMap = function (d) {
return d["r"];
} // data -> display
// setup fill color
var cValue = function (d) {
return d.Manufacturer;
},
color = d3.scale.category10();
// add the graph canvas to the body of the webpage
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 + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
/* d3.csv("data.csv", function (error, data) {*/
var data = d3.csv.parse( d3.select("pre#data").text() );
data.forEach(function (d) {
d.x = +d.x;
d["y"] = +d["y"];
});
// don't want dots overlapping axis, so add in buffer to data domain
xScale.domain([d3.min(data, xValue) - 1, d3.max(data, xValue) + 1]);
yScale.domain([d3.min(data, yValue) - 1, d3.max(data, yValue) + 1]);
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("x value");
// y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("y value");
// draw dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("stroke", "red")
.attr("stroke-width" , "2px")
.attr("r", rMap)
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", "none")
// });
});
</script>
</body>
</html>
Just change the positioning:
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", width / 2) //<- place at center of width**
.attr("y", 30) //<- and below axis**
.style("text-anchor", "end")
.text("x value");
// y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -30) //<- place behind axis**
.attr("x", -height/2) //<- place at center of height**
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("y value");
Updated fiddle.

Colour of points repeat after 10 data sets on D3.js scatter plot

Problem:
I've got a D3.js scatter plot that has 16 different data sets, but it seems like D3 has only 10 different colours built-in before it repeats. You can see what I mean by clicking that link.
Code:
function updatePlot() {
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.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 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.sepalLength = +d.sepalLength;
d.sepalWidth = +d.sepalWidth;
});
x.domain(d3.extent(data, function(d) { return d.sepalWidth; })).nice();
y.domain(d3.extent(data, function(d) { return d.sepalLength; })).nice();
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("HPF/LPF Intensity Ratio");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("HPF Intensity (relative units)")
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.sepalWidth); })
.attr("cy", function(d) { return y(d.sepalLength); })
.style("fill", function(d) { return color(d.species); });
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
}
(The code is pretty much copy/paste from here with a little customisation in the areas of D3 that I understand)
Thanks!
This part of the docs has the answer: Ordinal-Scales#categorical-colors. Thanks to user and Lars Kotthoff!
Simply replaced category10 with category20.

How to display value labels above graph bars?

Is there a way to display values above the bars in this graph? I have the values being retrieved from a TSV, but am currently having difficulty getting the bar values to be displayed as labels above each respective bar.
This is the data that I have as TSV:
This is what I currently have for rendering the graph:
margin =
top: 30
right: 30
bottom: 40
left: 60
width = 960 - margin.left - margin.right
height = 500 - margin.top - margin.bottom
formatPercent = d3.format("")
x = d3.scale.ordinal()
.rangeRoundBands([width, 0], .1)
y = d3.scale.linear()
.range([height, 0])
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatPercent)
yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent)
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 + ")")
d3.tsv("/Home/GetTsv/data.tsv", (error, data)->
data.forEach((d)->
d.Total = +d.Total
)
x.domain(data.map((d)-> d.Year))
y.domain([0, d3.max(data, (d)-> d.Total)])
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("y", 30)
.attr("x", width / 2)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Year")
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -60)
.attr("x", -(height / 2))
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Total Activity")
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", (d)-> x(d.Year))
.attr("width", x.rangeBand())
.attr("y", (d)-> y(d.Total))
.attr("height", (d)-> height - y(d.Total))
# svg.selectAll("text")
# .data(data)
# .enter()
# .append("text")
# .text((d)-> d.Total)
# .attr("x", (d, i)-> i * (width / data.length))
# .attr("y", (d)-> height - d)
)
This is what my graph looks like:
But I would like to have labels above the bars, similar to this:
The code that is commented-out is my attempt at trying to make the value labels show above the bars.
Pasting this code in the text section should do the trick:
.attr("text-anchor", "middle")
.attr("x", function(d, i) { return i * (width / dataset.length) + (width / dataset.length) / 2; })
.attr("y", function(d) { return height - (d * 10) - 10; })
the -10 there makes some distance from the bar, in this case 10px obviously. also the other numbers are to be tweaked a bit, i just pasted the code from a file i had so i don't know exactly what it would look like on your code since i don't have the dataset to try.
If it doesn't can you paste the script in http://jsfiddle.net/ so I can try and work it out? Let me know how it works out!

Categories