How to add a title for a NVD3.js graph - d3.js

I want to add a title text over the graph in NVD3.js.
I tried the following,
nv.addGraph(function() {
var chart = nv.models.linePlusBarWithFocusChart()
.margin({top: 30, right: 60, bottom: 50, left: 70})
.x(function(d,i) { return i })
.color(d3.scale.category10().range());
chart.append("text")
.attr("x", 200)
.attr("y", 100)
.attr("text-anchor", "middle")
.text("Sample Charts");
}
Its (Highlighted) working for D3.js but not working for NVD3.js. I'm getting a Blank page..
I want to place the text over the Chart as like the following image (Which is created in Paint)
How can I achieve this?

You can use D3 to select the container SVG and append the title:
d3.select('#chart svg')
.append("text")
.attr("x", 200)
.attr("y", 100)
.attr("text-anchor", "middle")
.text("Sample Charts");
This assumes that you're using the same ID etc for the SVG as in the NVD3 examples, if not, simply adjust the selector accordingly.

Taking a different approach, I wanted to have better control over the layout and style of the chart so instead of adding it as an text element inside the <svg> tag I added the title outside with help from jQuery.
var $svg = $('#chart svg');
$svg.parent().append('<div class="chart-title">Sample Charts</div>');
chart-title can be used to style the text element inside svg tags as well, but centering is much easier than having to use the width of the svg to set the x value like so:
svg.append('text')
.attr('x', width / 2)
.attr('y', 20)
.attr('text-anchor', 'middle')
.attr('class', 'chart-title')
.text('Sample Charts');

The above solution only works if you are setting the width. In my case the width was set to auto so until it created the chart it didn't have a width to refference. I actually modified the nvd3 code because I thought it was silly that some charts had titles and some didn't. There were 3 places I modified in each chart. Inside the margin section I added
var margin = {top: 30, right: 30, bottom: 30, left: 60}
, marginTop = null
.
.
.
, title = false
;
Inside the chart function I added
function chart(selection) {
.
.
.
if(title) {
g.append("text")
.attr("x", (availableWidth - margin.left)/ 2)
.attr("y", -4)
.attr("text-anchor", "middle")
.text(title);
}
Inside the chart options I added
chart._options = Object.create({}, {
// simple options, just get/set the necessary values
width: {get: function(){return width;}, set: function(_){width=_;}},
.
.
.
title: {get: function(){return title;}, set: function(_){title=_;}},
Then in your controller or where ever use
chart.title('Your chart Title');

Related

How can I make an element place itself, depending on its value, at the top of a bar?

I am new to the world of d3.js and I am trying that depending on the numeric value generated when clicking on the generateNumber function, an image is put where it corresponds. Obviously, if the value is 100, it must be in the highest part of the bar, and otherwise if is zero in the lowest part. I intend to make this dynamic depending on the size of the bar.
If it is not too much trouble, I would like to put a text in front of the image indicating the number that the function generated.
this is my live code:
https://jsfiddle.net/891vzjct/1/
<div id="visualization"></div>
<button onclick="generateNumber()">
Generate number
</button>
</div>
function generateNumber(){
let number=Math.floor(Math.random() * 101);;
console.log(number);
console.log(yScale(number));
d3.select("#indicador").attr("y",yScale(number));
}
let heightRectangle=10;
d3.select("#visualization").append('svg')
var vis = d3.select("svg").attr("width",800).attr("height",614).style("border","1px solid red");
vis.append("image")
.attr("id","indicador")
.attr("href","https://www.shareicon.net/data/256x256/2015/08/17/86784_left_512x512.png")
.attr("width",30)
.attr("height",30)
.style("transform","scale(0.5) translate(113px)")
//Doing my color bar
var arr = d3.range(101)
let maxRange=600;
var yScale = d3.scale.linear().domain([0, 100]).range([maxRange,0])
var colorScale = d3.scale.linear().domain([0,50,100])
.range(["green", "yellow", "red"])
vis.selectAll('rect').data(arr).enter()
.append('rect')
.attr({
y : function(d,i) { console.log(d,yScale(d)); return i*+4 },
x : 20,
height: heightRectangle,
width: 40,
fill: function(d) { return colorScale(d) }
});
/*
var y = d3.scale.linear()
.range([300, 0])
.domain([100, 0]);
var yAxis = d3.axisBottom()
.scale(y)
.ticks(5);
vis.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,30)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("axis title");*/
How can I do this?
If you want to put a text in front of the image(#indicador), you can append a text and then change the attr y and text in the generateNumber() function, basically the same as what you did to the image(#indicador).
A working example here.
Also I changed some code from yours.
the maxRange is 410 in your case, instead of 600 according to the rectangles generated.
I removed the scale(0.5) of the image since I can just set the width and height to 15px. If you try to scale, take care of the transform-origin.

D3 percentage superscript

I am trying to get a little percent sign superscript.
I found and example which works but not percent
var svgText = svg.append('text').text('This is a test : mm²')
Is there a way that I could do the same with the percent?
.text(function (d) {return d.site + 'mm²';});
to make 75 % superscripted
Why not use a tspan? This will allow you to format any text how you want, superscript or otherwise, no matter if there is a unicode superscript symbol you can use:
Within a element, text and font properties and the current text
position can be adjusted with absolute or relative coordinate values
by including a <tspan> element. (MDN)
There are a few approaches you could take in this regard, but if you can extract the text that needs to be superscript (or generate it on the fly), then you can create the superscript and regular text relatively easly. Below I use a tspan to hold the regular text and another to hold the superscript:
var svg = d3.select("body").append("svg");
var data = [
{text: "Here's some normal text", super:"Here's superscript"},
{text:"Some text", super:"α,β,γ,%,!,1,2,3"}
];
var text = svg.selectAll()
.data(data)
.enter()
.append("text")
.attr("x", 10)
.attr("y", function(d,i) { return i * 20 + 20; });
// Main content:
text.append("tspan")
.text(function(d) { return d.text; })
.attr("font-size", 14)
// Superscript content:
text.append("tspan")
.text(function(d) { return " " +d.super; })
.attr("dy",-5)
.attr("font-size",11)
<script src="https://d3js.org/d3.v5.min.js"></script>
With a bit of string manipulation you could use this pattern without pre-existing properties for each text string (below I use only one text span with normal text just being added as normal):
var svg = d3.select("body").append("svg");
var data = [
"Length is 10px - 10%",
"Height is 20px - 30%"
];
var text = svg.selectAll()
.data(data)
.enter()
.append("text")
.attr("x", 10)
.attr("y", function(d,i) { return i * 20 + 20; })
.text(function(d) {
return d.split("-")[0];
});
// Superscript content:
text.append("tspan")
.text(function(d) { return d.split("-")[1]; })
.attr("dy",-5)
.attr("font-size",11)
<script src="https://d3js.org/d3.v5.min.js"></script>
The tspan approach is useful in that it maintains text position, which is easier to manage than placing multiple text elements where the position of each depends on the width of other text elements.
Since there is no superscript character for % in unicode, you have to take the approach laid out by Andrew Reid in his answer. Although there is nothing wrong with his approach, you could make your life a little easier and the code a bit more readable by using the baseline-shift attribute of the <tspan>:
The baseline-shift attribute allows repositioning of the dominant-baseline relative to the dominant-baseline of the parent text content element. The shifted object might be a sub- or superscript.
Since you can nest the tspan inside your normal text, there is no need to explicitly position the element. Your code could be something along the following lines:
<text x="100" y="100">
Test
<tspan baseline-shift="super" font-weight="bolder" font-size="62%">75%</tspan>
</text>
Have a look at the following snippet for a working D3 demo:
var svg = d3.select("body").append("svg");
var text = svg
.append("text")
.attr("x", 50)
.attr("y", 50)
.text("Test");
// Superscript
text.append("tspan")
.text("75%")
.attr("baseline-shift", "super")
.attr("font-size", "62%")
.attr("font-weight", "bolder");
<script src="https://d3js.org/d3.v5.js"></script>

dc.js: Composite Chart filtering/labeling

I have the following composite chart made in dc.js:
barChart
.dimension(savingsDimension)
.colors('#009900')
.centerBar(true)
.elasticY(true)
.title(function(d) { return d.key + ": " + d.value; });
barChart2
.dimension(savingsDimension)
.colors('#000099')
.centerBar(true)
.elasticY(true)
.title(function(d) { return d.key + ": " + d.value; });
var lineChart = dc.lineChart(compositeChart)
.dimension(savingsDimension)
.colors('red')
.useRightYAxis(true)
.renderDataPoints({
radius: 3,
fillOpacity: 0.5,
strokeOpacity: 0.8
});
var xUnits = data.map(function (d) {return d.short_date; }).sort();
compositeChart
.width(1300)
.height(350)
.x(d3.scale.ordinal().domain(xUnits))
.xUnits(dc.units.ordinal)
.xAxisLabel('Month')
.brushOn(false)
.elasticY(true)
.margins({left: 80, top: 10, right: 190, bottom: 80})
.legend(dc.legend().x(1160).y(220).itemHeight(13).gap(5))
.compose([barChart, barChart2,
lineChart
]).renderlet(function(chart){
chart.selectAll("g.x text")
.attr('transform', "rotate(-65)")
.attr('x', -20);
});
barChart.group(fSavingsDimensionGroup, ' First Savings');
barChart2.group(sSavingsDimensionGroup, 'Second Savings');
The first thing I am having trouble with is making it so that I can select an x-range on this composite chart which will then filter all of my other charts. Right now, I can select certain bars and filter it that way, but I can't select a range like in this example: http://dc-js.github.io/dc.js/examples/filtering.html
I tried using .controlsUseVisibility(true) but it just errors out.
Also, even though I have .centerBar(true) on both my bar charts, the labels still aren't centered. Not sure what I am doing wrong there.
Edit #1:
Changed the code to:
compositeChart
.width(1300)
.height(350)
.x(d3.time.scale().domain([savingsDimension.bottom(1)
[0].billing_period_start, savingsDimension.top(1)
[0].billing_period_start]))
[0].billing_period_start, savingsDimension.top(1)
[0].billing_period_start))
.xAxisLabel('Month')
.elasticY(true)
.margins({left: 80, top: 10, right: 190, bottom: 80})
.legend(dc.legend().x(1160).y(220).itemHeight(13).gap(5))
.renderlet(function(chart){
chart.selectAll("g.x text")
.attr('transform', "rotate(-65)")
.attr('x', -36)
.attr('y', -20);
});
compositeChart.xAxis().tickFormat(d3.time.format('%m-%Y')).ticks(24);
compositeChart.xUnits(d3.time.months)
And the chart now looks like:
The bars are weirdly spaced out and I have no idea why.
I can now select a range on the chart, but it doesn't actually do any sort of filtering to the chart or any other chart on the page.
Currently the filtering behavior is selected by the type of x scale, so to get continuous brushing you could use a quantitative scale such as d3.time.scale(), convert your dates to date objects, and then use xAxis().tickFormat() to display them the way you want.
Here is the feature request to allow the range brush on ordinal charts. It is mainly a question of how to design the feature in a general way.
You are moving the tick labels with your renderlet, so you should adjust the displacement there in order to center your labels.

Why isn't my updated data re-rendering?

I'm playing around with the "update" pattern in D3.js. I am just creating a simple bar graph that will update the data when you press the "Change" button.
My problem is that when you press the "Change" button, the first three rendered bars do not get re-rendered. I debugged and saw that the data was properly applied (__data__ was correct) but the re-application failed.
Here is my code and a link to it in CodePen:
var myData = [ 100, 200, 300 ];
d3.select('body').append('button').text("Change").on("click", function() {
myData = [200, 400, 600, 700, 800, 900, 1000];
update(myData);
});
var svg = d3.select('body').append('svg')
.attr("class", "chart")
.attr("y", 30);
var update = function(data) {
var bars = svg.selectAll('g')
.data(data);
var groups = bars.enter()
.append("g")
.attr("transform", function(d,i) {return "translate(0," + i*25 + ")"});
groups
.append("rect")
.attr("height", 25)
.attr("fill", "pink")
.attr("stroke", "white");
groups
.append("text")
.attr("x", 10)
.attr("y", 18)
.attr("fill", "red");
bars.selectAll("rect")
.attr("width", String);
bars.selectAll("text")
.text(String);
};
update(myData);
It works if you change the .selectAll() in your update selection handling to .select():
bars.select("rect")
.attr("width", String);
bars.select("text")
.text(String);
By using selectAll(), you're accessing the data that is bound to the elements that you're selecting (i.e. the rectangles and text elements), which was bound when you appended the elements. This data hasn't been updated though as you've only updated it for the containing g elements. Using .select() instead also binds the new data to the child elements.
The general pattern that you're using is a nested selection and can be a bit confusing to start with and lead to unexpected results.

d3.js axis - making it have 0 ticks and no markings

Is it possible to create a d3.js axis and have there be no tick marks and no numbering scheme? Basically, can I make the axis invisible? I'm using the code below to create my axes:
svg.selectAll("axis")
.data(d3.range(angle.domain()[1]))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + angle(d) * 180 / Math.PI + ")"; })
.call(d3.svg.axis()
.scale(radius.copy().range([0,0]))
.ticks(1)
.orient("left"))
.append("text")
.style("color", "white")
.attr("y",
function (d) {
if (window.innerWidth < 455){
console.log("innerWidth less than 455: ",window.innerWidth);
return -(0);
}
else{
console.log("innerWidth greater than 455: ",window.innerWidth);
return -(0);
}
})
.attr("dy", "0em");
If you don't want your axis to be visible, just don't draw them (basically comment out this code).
If you really just want to turn them white, you can use the following classes:
.axis line, .axis text, .axis path {
color: white;
}
This would be the easiest way to manipulate them to turn them 'on' and 'off'. Also, if you ever need to figure out how to style a d3 diagram, you can navigate through the SVG just like you do html and style with CSS the same way too.
For example, here is the SVG for the tick marks in the axis.
<line class="tick" y2="6" x2="0"></line>
You can see that I targeted the element (line) but you could also target (.tick) as well.

Resources