Rotate labels in column chart d3.JS - javascript

I want to rotate the labels in yellow color.
image for the labels
i have gone through this link
rotate x axis text in d3
but in this link the values being passed to the translate function are static values.
<text transform="translate(200,100)rotate(180)">Hello!</text>
i want to pass dynamic values returned by a function.
so in this code the x and y are taking values from functions so i want to pass these values to the translate attribute but getting error in the console
d3.min.js:1 Error: Invalid value for attribute transform="translate(\"function(d){ return xScale(d.country) + xScale.rangeBand()/2; }\",\"function(d){ return yScale(d.populationValue)+ 12; }\")rotate(-90)"
.attr({
"x": function(d){ return xScale(d.country) + xScale.rangeBand()/2; },
"y": function(d){ return yScale(d.populationValue)+ 12; },
"text-anchor": 'middle',
"fill": 'yellow',
"transform": 'translate("function(d){ return xScale(d.country) + xScale.rangeBand()/2; }","function(d){ return yScale(d.populationValue)+ 12; }")rotate(-90)'
expected output

You have to return all the translate string using the function:
"transform": function(d){
return "translate(" + xScale(d.country) + xScale.rangeBand()/2
+ "," + yScale(d.populationValue) + 12 + ")rotate(-90)"
};
PS: after you do this, I bet that the result will not be what you expect... but that will be another problem, for another SO question.

Related

Why are d, this, and the variable all undefined in my drag function?

I'm trying to do some logic based on the coordinates of a rectangle, when dragged. I want to select all circles within the rectangle.
function dragmove(d) {
var barz = document.querySelector("#visual");
var point = d3.mouse(barz),
tempP = {
x: point[0],
y: point[1]
};
d3.event.sourceEvent.stopPropagation();
d3.select(this).style({
opacity: 0.05
})
console.log(selectionBox.x); //turns out undefined
console.log(d.x); //also undefined
console.log(d3.select(this)); //undefined
vis.selectAll("circle").filter(function (d, i) {
return (d.x > d3.select(this).x && d.x < (d3.select(this).x + d3.select(this).width))
}).style({
opacity: 0.1
});
If you didn't already notice, right now I only have it checking within the x coordinates, at least until I finish fixing this. Here's the fiddle.
Whenever I try to run it, it doesn't pull any errors, but it doesn't work as intended because the reference is undefined. Is there any reason why none of the references work at all?
To reproduce this you need to first drag on the canvas to draw a rectangle, and then drag that rectangle
It seems like your origin function isn't quite right. I tried the one from this answer
var drag = d3.behavior.drag()
.origin(function(d) {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on("dragstart", dragstarter)
.on("drag", dragmove);
Now it passes in a valid object to the dragmove function.
In the following code:
vis.selectAll("circle").filter(function (d, i) {
return (d.x > d3.select(this).x && d.x < (d3.select(this).x + d3.select(this).width))
})...
the reference to this is undefined because of how the Array.prototype.filter function works. According to the specs, we can provide our own this as the second parameter to the filter function, so:
vis.selectAll("circle").filter(function (d, i) {
return (d.x > d3.select(this).x && d.x < (d3.select(this).x + d3.select(this).width))
}, this)...
Updated your fiddle

Creating variable number of elements with D3.js

I am pretty new to d3.js and maybe my question is very basic, but I haven't been able to find an answer...
I am trying to achieve the following, based on an array of data like this:
var data = [
{
"items": 10,
"selected": 8
},
{
"items": 12,
"selected": 4
}];
I would like to create a row of circles for every element of the array. The number of circles should be the equal to the items property and in every row, the circle in selected position should be special (like a different color). For the example above, it should display something similar to:
OOOOOOO*OO
OOO*OOOOOOOO
For the first step, any tips on how to create a variable number of SVG elements based on data values would be a great help.
Here's an example I made on codepen.
Check out the code below and/or fork the codepen and have a play.
Essentially, what is happening here is that I add a g element for each item in your data array. Normally we might be-able to create a circle for each data element, but since that is contained within a property and variable, I've used an each loop (d3 each). This creates a loop of the number of items, and creates a circle for each. If the element is selected, the fill color changes.
things to note:
The g element has a transform of 30 * i on the y axis. This means that each group will stack down the page.
In the for loop we get access to the g element using this, but we must use the d3 select function to reference it as a d3 object so we can append.
The code:
//add an svg canvas to the page
var svg = d3.select("body")
.append("svg")
.attr("transform", "translate(" + 20 + "," + 20 + ")"); //this will give it a bottom and left margin
//set up some basic data
var data = [
{
"items": 10,
"selected": 8
},
{
"items": 12,
"selected": 4
}];
var groups = svg.selectAll("g")
.data(data)
.enter()
.append("g").attr("transform", function(d, i) {
return "translate(0," + i * 30 + ")";
});
groups.each(function(d,i) {
for (var x = 1; x <= d.items; x++) {
d3.select(this).append('circle').attr({cx: function(d,i) { return (x + 1) * 22; } ,
cy: 10 ,
r: 10 ,
fill: function(d,i) { return x == d.selected ? "yellow" : "blue" }
});
}
});

Pixel size of text on server side before writing it on page

I want to calculate text with maximum pixel size beforehand and then allocate the maximum width size to all other texts. getBBox gives me below error:
TypeError: Object [ TEXT ] has no method 'getBBox'
and getComputedTextLength() also does not work.
Both these methods give pixel size after it is written to page. I am creating chart on server side i.e. nodejs and I want to calculate pixel size before writing it to page. Is there any method for this?
Edit:
Adding code
chart.selectAll("svg:text")
.data(d3Data)
.enter().append("svg:text")
.attr("class", "d3LegendText")
.attr("transform", function(d, i) {
textLength = d.seriesName.length*6;
if (prev_legend_x + textLength + legend_padding_x > width) {
legend_y = legend_y + 15;
legend_x = 7;
} else {
legend_x = prev_legend_x;
}
prev_legend_x = legend_x + textLength + legend_padding_x;
return "translate(" + legend_x + "," + legend_y + ")"; })
.text(function(d, i) {
console.log(this.getBBox().width);
return d.seriesName;
})
You can ingnore variables in transform. I have written it to calculate pixel size depending on number of characters in text. But I want exact width of text.

NVD3.js yAxis and tooltip different percision

I'm using NVD3.js to display a multi-line chart.
I would like the yAxis to display to 2 decimal numbers
edited answer
var chart;
nv.addGraph(function () {
chart = nv.models.lineChart()
.options({
margin: { left: 140, bottom: 50 },
x: function (d, i) {
return i;
},
showXAxis: true,
showYAxis: true,
transitionDuration: 250,
tooltips: true,
tooltipContent: function (key, x, y, e, graph) {
return '<h3>' + key + '</h3>' +
'<p>' + e.point.y + ' at ' + x + '</p>'
}
});
// chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the parent chart, so need to chain separately
chart.xAxis
.axisLabel("Maturity")
.tickFormat(function(d) { return pivotedData[0].values[d].x; });
chart.yAxis
.axisLabel('Model Spread').tickFormat(d3.format(',.2f'));
d3.select('#chart1 svg')
.datum(pivotedData)
.call(chart);
//TODO: Figure out a good way to do this automatically
nv.utils.windowResize(chart.update);
chart.dispatch.on('stateChange', function (e) { nv.log('New State:', JSON.stringify(e)); });
return chart;
});
But in the tool tip on the lines i would like to display to 12 decimals places.
Is this possible?
You can set the function that is called to format the contents of a tooltip through .tooltipContent of the chart object. The default function is defined as follows.
tooltip = function(key, x, y, e, graph) {
return '<h3>' + key + '</h3>' +
'<p>' + y + ' at ' + x + '</p>'
}
You can format the value of y in there to your liking. Note that y is defined as
yAxis.tickFormat()(lines.y()(e.point, e.pointIndex))
This means that the tick formatting for the axis will affect it, so in order to achieve a higher precision there, you need to get the value directly -- lines can be accessed through chart.lines.
In the nvd3 source they have lines like so (Line Chart):
interactiveLayer.dispatch.on('elementMousemove', function(e) {
...
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
interactiveLayer.tooltip
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
...
.valueFormatter(function(d,i) {
return yAxis.tickFormat()(d);
})
.data(
{
value: xValue,
series: allData
}
)();
...
}
So if you want to override something like tooltip.contentGenerator you are stuck with the data it gives you. I think a good way to do this would be to have a setter for tooltip x formatter or something then it will use that over the axis formatter. Or simply sending over the raw data along with the value and series.
My usecase is that I am showing a timescale of 7 days on the graph I only tick the start of each day. However on the tooltip I want to give a more detailed view of that data.
Here is my pull request specific to the guidelines tooltip
https://github.com/novus/nvd3/pull/444
Calling: chart.tooltipXAxisFormatter(d3.time.format(toolTipFormat)); will set it.

Can't make paths draw growing slowly with D3

Using the d3 graphics library, I can't seem to make paths draw slowly so they can be seen growing.
This site has a perfect example in the "Line Chart (Unrolling)" section, but no code is given for that section. Could someone please help me with the lines of D3 code that could make that happen?
When I try appending delay() or duration() such as in the following code snippet, the path still draws immediately, And all the SVG code after this segment fails to render.
var mpath = svg.append ('path');
mpath.attr ('d', 'M35 48 L22 48 L22 35 L22 22 L35 22 L35 35 L48 35 L48 48')
.attr ('fill', 'none')
.attr ('stroke', 'blue')
.duration (1000);
A common pattern when animating lines in svg is setting a stroke-dasharray of the length of the path and then animate stroke-dashoffset:
var totalLength = path.node().getTotalLength();
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
.attr("stroke-dashoffset", 0);
You can see a demo here:
http://bl.ocks.org/4063326
I believe the "D3 way" to do this is with a custom tween function. You can see a working implementation here: http://jsfiddle.net/nrabinowitz/XytnD/
This assumes that you have a generator called line set up with d3.svg.line to calculate the path:
// add element and transition in
var path = svg.append('path')
.attr('class', 'line')
.attr('d', line(data[0]))
.transition()
.duration(1000)
.attrTween('d', pathTween);
function pathTween() {
var interpolate = d3.scale.quantile()
.domain([0,1])
.range(d3.range(1, data.length + 1));
return function(t) {
return line(data.slice(0, interpolate(t)));
};
}​
The pathTween function here returns an interpolator that takes a given slice of the line, defined by how far we are through the transition, and updates the path accordingly.
It's worth noting, though, that I suspect you'd get better performance and a smoother animation by taking the easy route: put a white rectangle (if your background is simple) or a clipPath (if your background is complex) over the line, and transition it over to the right to reveal the line underneath.
Based on the post that you link to, I came up with the following example:
var i = 0,
svg = d3.select("#main");
String.prototype.repeat = function(times) {
return (new Array(times + 1)).join(this);
}
segments = [{x:35, y: 48}, {x: 22, y: 48}, {x: 22, y: 35}, {x: 34, y:35}, {x: 34, y:60}];
line = "M"+segments[0].x + " " + segments[0].y
new_line = line + (" L" + segments[0].x + " " + segments[0].y).repeat(segments.length);
var mpath = svg.append ('path').attr ('d',new_line )
.attr ('fill', 'none')
.attr ('stroke', 'blue')
for (i=0; i<segments.length; i++)
{
new_segment = " " + "L"+segments[i].x + " " + segments[i].y
new_line = line + new_segment.repeat(segments.length-i)
mpath.transition().attr('d',new_line).duration(1000).delay(i*1000);
line = line + new_segment
}
It is a bit ugly, but works. You can see it on jsFiddle

Categories