D3 percentage superscript - d3.js

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>

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.

How to set different color for same text in D3?

I am using D3 to have some text on lines. For some reason, I need some part of the text to have different color, For example, I might have black text then red text like this: "black(red)".
What is the best way to do this?
Can I have different color in a single text?
If I have to draw another text for the second color, how can I append it to first text element?
This is my how I generate texts:
var texts = lines.append("svg:text")
.text(function (d) {
return d.label;
})
.attr("class", "link-label")
.attr("text-anchor", "middle")
Unless you want to deal with <defs> and gradients, you cannot set two or more fill attributes to the same <text>.
The easiest solution is breaking that text of yours and appending a <tspan> with another style. How to break the text is a problem that depends on your exact goal, which you didn't specify in the question. In the following demo, I'm using a hyphen to break the texts.
Here it is:
var svg = d3.select("svg");
var data = ["This is some text - with different colours",
"This is another text - with different colours",
"This is the last text - and the colours are still different"
];
var text = svg.selectAll(null)
.data(data)
.enter()
.append("text")
.attr("y", function(d, i) {
return 40 + i * 40
})
.text(function(d) {
return d.split("-")[0]
})
.append("tspan")
.style("fill", "red")
.text(function(d) {
return d.split("-")[1]
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400"></svg>

d3.js trying to call data method twice on same selector produces strange results

I'm new to D3 and am trying to build a table like structure out of rectangles. I would like the header to be a different color than the rest of the rectangles. I've written the following code:
table = svgContainer.selectAll('rect')
.data([managedObj])
.enter()
.append('rect')
.attr("width", 120)
.attr("height", 20)
.attr("fill", "blue")
.text(function(d) {
return d.name;
});
// create table body
table.selectAll('rect')
.data(managedObj.data)
.enter()
.append('rect')
.attr("y", function() {
shift += 20;
return shift;
})
.attr("width", 120)
.attr("height", 20)
.attr("fill", "red")
.text(function(d) {
return d.name;
});
This is producing the following results:
This is almost what I intended except it is nesting the second group of rectangles inside the first rectangle. This causes only the first blue rectangle to be visible. I'm assuming this has something to do with calling the data method twice. How can I fix this issue?
I think I understand the intended result, so I'll give it a go:
This line :
table.selectAll('rect')
is selecting the rectangle just created here:
table = svgContainer.selectAll('rect')....append('rect')....
You don't want to append rectangles to that rectangle (or any rectangle for that matter) because this won't work, but you do want to append them to the SVG itself.
So instead of table.selectAll you should be using svgContainer.selectAll, but there are two other issues:
if you use svgContainer.selectAll('rect') you will be selecting the rect you have already appended, when you actually want an empty selection. See the answer here.
you cannot place text in a rect (See answer here), instead you could append g elements and then append text and rect elements to those. And, for ease of positioning, you could translate the g elements so that positioning the rectangles and text is more straight forward.
So, your code could look like:
var data = ["test1","test2","test3","test4"];
var svgContainer = d3.select('body').append('svg').attr('width',900).attr('height',400);
var header = svgContainer.selectAll('g')
.data([data])
.enter()
.append('g')
.attr('transform','translate(0,0)');
header.append('rect')
.attr("width", 120)
.attr("height", 20)
.attr("fill", "blue");
header.append('text')
.attr('y',15)
.attr('x',5)
.text(function(d) {
return "header";
});
// create table body
var boxes = svgContainer.selectAll('.box')
.data(data)
.enter()
.append('g')
.attr('class','box')
.attr('transform',function(d,i) { return 'translate(0,'+((i+1)*20)+')'; });
boxes.append('rect').attr("width", 120)
.attr("height", 20)
.attr("fill", "red");
boxes.append('text')
.attr('y',15)
.attr('x',5)
.text(function(d) {
return d;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

D3 (with angular) Failing to insert a span inside a div

I have this working code. where the d3 part is basically:
var bar = chart.append("div").attr("class", "chart")
.selectAll('div')
.data(scope.data.sort().reverse()).enter().append("div")
.transition().ease("elastic")
.style("width", function(d) { return (d[0]/sum)*attrs.chartWidth + "px"; })//This is where I base the width as a precentage from the sum and calculate it according to the chart-width attribute
.style("background-color",function(){i++;if (i<=colors.length-1){return colors[i-1]} else {return colors[(i-1)%colors.length]}}).text(function(d) { return d[1] ; })
but when I try to append("span") in the chaining so the text would be on the span and not in the parent div. the text just disappears and the dev console shows no clue of both the span and the text. Also tried insert("span") and even replacing the .text for .html(function(d){return "<span>"+d[1]+"</span>"}
neither work.
any clues? Thanks!
The problem is that you are starting a transition in the chain. The transition object provides many functions just like a normal d3.selection including .remove, .text and .html, but does not allow .append operation.
You should re-factor the code to read:
var bar = chart.append("div").attr("class", "chart")
.selectAll('div')
.data(scope.data.sort().reverse()).enter().append("div");
bar
.transition().ease("elastic")
.style("width", function(d) { return (d[0]/sum)*attrs.chartWidth + "px"; })//This is where I base the width as a precentage from the sum and calculate it according to the chart-width attribute
.style("background-color",function(){i++;if (i<=colors.length-1){return colors[i-1]} else {return colors[(i-1)%colors.length]}}) })
bar.append('span')
.text(function(d) { return d[1] });
Demo
As a side note, while selecting the background-color, you do not need to maintain the index variable yourself, d3 passes the data d and the index i to the setter function you provide to .style:
.style("background-color",
function(d, i){ // <-- 'd' and 'i' are passed by d3
if (i<=colors.length-1)
{return colors[i-1]}
else {return colors[(i-1)%colors.length]}})
})

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