What is the difference between svg's x and dx attribute? - svg

What is the difference between svg's x and dx attribute (or y and dy)? When would be a proper time to use the axis shift attribute (dx) versus the location attribute (x)?
For example, I have noticed a lot of d3 examples doing something like this
chart.append("text")
.attr("x", 0)
.attr("y", 0)
.attr("dy", -3)
.text("I am a label")
What is the advantage or reasoning for setting both y and dy when the following seems to do the same thing?
chart.append("text")
.attr("x", 0)
.attr("y", -3)
.text("I am a label")

x and y are absolute coordinates and dx and dy are relative coordinates (relative to the specified x and y).
In my experience, it is not common to use dx and dy on <text> elements (although it might be useful for coding convenience if you, for example, have some code for positioning text and then separate code for adjusting it).
dx and dy are mostly useful when using <tspan> elements nested inside a <text> element to establish fancier multi-line text layouts.
For more details you can check out the Text section of the SVG spec.

To add to Scott's answer, dy used with em (font size units) is very useful for vertically aligning text relative to the absolute y coordinate. This is covered in the MDN dy text element example.
Using dy=0.35em can help vertically centre text regardless of font size. It also helps if you want to rotate the centre of your text around a point described by your absolute coordinates.
<style>
text { fill: black; text-anchor: middle; }
line { stroke-width: 1; stroke: lightgray; }
</style>
<script>
dataset = d3.range(50,500,50);
svg = d3.select("body").append("svg");
svg.attr('width',500).attr('height', 500);
svg.append("line").attr('x1', 0).attr('x2', 500).attr('y1', 100).attr('y2', 100);
svg.append("line").attr('x1', 0).attr('x2', 500).attr('y1', 200).attr('y2', 200);
group = svg.selectAll("g")
.data(dataset)
.enter()
.append("g");
// Without the dy=0.35em offset
group.append("text")
.text("My text")
.attr("x",function (d) {return d;})
.attr("y",100)
.attr("transform", function(d, i) {return "rotate("+45*i+","+d+",100)";});
// With the dy=0.35em offset
group.append("text")
.text("My text")
.attr("x",function (d) {return d;})
.attr("y",200)
.attr("dy","0.35em")
.attr("transform", function(d, i) {return "rotate("+45*i+","+d+",200)";});
<script>
View it in Codepen
If you don't include "dy=0.35em", the words rotate around the bottom of the text and after 180 align below where they were before rotation. Including "dy=0.35em" rotates them around the centre of the text.
Note that dy can't be set using CSS.

Related

SVG g hides when parent g is set to a height

I am relatively new to SVG. I am plotting a chart which will dynamically plot temperature or wind value on x,y axis respectively. I am good with x-axis positioning.
But when it comes to Y-Axis, position come right but if the value is higher than parent chart height, it get cut off as you can see in the picture.
Need help on how to force visible the circle/element even it exceeds the parent element width or height?
Here is how mark up gets generated
I am using d3.js for this.
Here is the code for generating circle with text
let svg = container
.append("svg")
.attr("width", width)
.attr("height", height+(height*0.7));
const chart = svg
.append("g")
.attr('class','chartwind')
.attr("transform", "translate(" + padding.left + "," + (padding.top) + ")")
chart.append("path")
.data(datapoints)
.attr("class", "line wind-line")
.style("stroke", colorScale("wind"))
.style('stroke-width', '2px')
.attr("d", windLine)
let windPoint = chart.selectAll('g.windpoint').data(datapoints).enter().append('g').attr('class', 'windpoint').attr('transform', function (d) {
return 'translate(' + (Math.round(xScale(d.parsedUtcDateTimeNow)) - 10) + ',' + yScale(d.windSpeed) + ')';
})
chart.selectAll('.windcircle')
.data(Infos)
.enter().append('circle')
.attr("class", "windcircle")
.style("stroke", colorScale("wind"))
.style('stroke-width', '2px')
.attr("cx", (d) => xScale(d.parsedUtcDateTimeNow))
.attr("cy", (d) => {
return yScale(d.windSpeed);
})
.attr('r', 3)
.style("fill", colorScale("wind"))
windPoint
.append('circle')
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function (d) {
return 8;
})
.attr("fill", "green")
.attr("transform", "translate(0,-23)")
windPoint
.append('text')
.attr("fill", "white")
.attr("transform", "translate(0,-19)")
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "600")
.text((d) => {
return d.windGust;
});
windPoint
.append('text')
.attr('text-anchor', 'middle')
//.attr("transform","translate(0,5)")
.text((d) => {
return d.windSpeed;
});
Update
This is how I am constructing SVG height and width
function getSizesById(id) {
const container = document.querySelector(`#${id}`)
if (!container)
return null
return {
height: container.clientHeight,
width: container.clientWidth,
}
}
const { width, height } = (() => {
return getSizesById(containerID)
})()
const { width, height } = (() => {
return getSizesById(containerID)
})(),
padding = {
...size.padding,
top: 20,
bottom: 20
},
chartHeight = height - padding.bottom - padding.top,
chartWidth = width - padding.left - padding.right
const container = d3.select(`#${containerID}`)
let svg = container
.append("svg")
.attr("width", width)
.attr("height", height);
Note: SVG height (or chart) should look with in the DIV and only when the data points go over the scale, then we need data to be visible over the div height.
<g> elements do not have any inherent size. They are only a logical wrapper for a group of markup tags, and a place to give them some common properties. What restricts the visible parts of your chart is the <svg> element.
Your code shows that each datapoint is represented by grafic elements that span a bounding box of (-8 -31 16 35) (left - top - width - height). This is how much space you need to show all of it.
Remove the transform attribute from your .chartwind group. The space you need to show the complete graph is
left: lower boundary of your xScale range minus 8
top: lower boundary of your yScale range minus 31 (in the downward coordinate system)
width: extent of your xScale range plus 16
height: extent of your yScale range plus 35
Add some padding if you like.
Add a viewBox attribute with these four numbers to the <svg> element: viewbox="<left> <top> <width> <height>". The area described like this will then be fitted inside the available space of the <svg> element, without you having to do any further figuring out of transformations.

Vertical arc using D3.js

How do I draw semi-circular arc along the y-axis ( perpendicular axis ) of an svg using D3 ?
Here is an image of what I want to achieve target
Here is my code so far :-
var canvas = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var group = canvas.append("g")
.attr("transform","translate(0,200)");
var origin = d3.svg.arc()
.outerRadius(50)
.innerRadius(0)
.startAngle(-1.5755)
.endAngle(1.5755);
var arcs1 = group.append('g')
.attr('class','arc')
.attr("transform","translate(50,0)");
var yAxis = group.append("line")
.attr("transform","translate(50,0)")
.attr("x1", 0)
.attr("y1", -200)
.attr("x2", 0)
.attr("y2", height)
.attr("stroke-width", 2)
.attr("stroke", "black");
First, to draw the arc like your target you want your startAngle to start at 0 and endAngle to be 3.14 or Math.PI.
var origin = d3.svg.arc()
.outerRadius(50)
.innerRadius(0)
.startAngle(0)
.endAngle(Math.PI);
Then just append a path to arcs1 and use your origin function to draw it.
arcs1.append("path").attr("d", origin);
You can take a look at a live demo at http://jsfiddle.net/7EfS5/.
Edit
To change the color of your arc, you can either use .attr("fill", color) or .style("fill", color)
arcs1.append("path").attr("d", origin)
.attr("fill", "red");
Hope this help.
When you make a d3 arc, an angle of 0 represents the top of a circle. So to make an arc like the one shown in your image, you will want your start angle to be 0 and your end angle to be halfway around a circle, which is Pi radians:
var arc = d3.svg.arc()
.outerRadius(50)
.innerRadius(0)
.startAngle(0)
.endAngle(Math.PI);
As far as positioning your arc, you can use an svg transform to translate the origin of the arc to the position you want. For instance, if your y-axis has height h you can translate the origin to the center of the axis when you create the path element for your arc like this:
svg.append('path')
.attr('transform', 'translate(0,' + h/2 + ')')
.attr('d', arc);
HERE is a demo.

How to resize and position a map in Raphael js?

I am trying to include a world map in a dynamicaly crreated div using Raphael js. Once the path is defined, I am updating rapahel paper object's viewbox to scale the path. Although this part is working, I am not able to align the path to the center of the div. The following is what I have done so far according to this answer:
var paper = Raphael("map"),
path = paper.path(w_path).attr({fill: "#000", "stroke-width": 0, "stroke-linejoin": "round", opacity: 1}),
box = path.getBBox(),
margin = Math.max(box.width, box.height) * 0.025; // set the viewbox to match the bounding box of the path with 2.5% margins
paper.setViewBox(box.x - margin, box.y - margin, box.width + margin * 2, box.height + margin * 2);
My question is how can I align the path at the center of the div (map div), both vertically and horizontally?
UPDATE:
I have added a jsFiddle for this.

vertical text in d3 (not rotated)

I am trying to get text to display vertically in svg using d3. I do not want to rotate it, however: I want the letters to remain horizontal, but one above the other. Setting writing-mode=tb does not seem to do anything. Here is what I tried:
svg.append("text")
.attr("x", 1000)
.attr("y", 400)
.attr("id", "title")
.attr("font-size", 50)
.attr("style", "font-family: arial; fill: lightgreen; writing-mode: tb")
.text("Top 100 Mentions");
The text shows up in the right location, with the right font etc, but it is horizontal.
When talking about rotating SVG text, there are multiple different aspects:
how the browser determines where to position the next character based on the previous character's position;
how the characters are rotated relative to the baseline (rotate attribute);
how the final text block as a whole is rotated relative to the coordinate system (transform attribute).
Full, rather difficult to comprehend, specs here.
The 'writing-mode' property is supposed to change the first aspect without affecting the others, but as you discovered it's not implemented at all in Firefox, while IE rotates the text but doesn't respect the horizontal-glyph rule.
Theoretically, you should be able to get the same effect by combining the rotate and transform attributes: transform the entire <text> element to rotate it into a vertical position, then reverse-rotate the individual characters to get them back to horizontal. But in practice, it gets messy...
For starters, the double rotations cause the text to be end up on the left of your (x,y) point, so if (x,y) is (0,0) it will be clipped outside the SVG without supplemental shift. Because of the transforms, you'll need a negative dy value to move the text back to the right of the anchor point.
Second, there is the fact that the rotation is applied to each character in place, the spacing of the characters isn't adjusted to account for the fact that an "l" is much taller than it is wide. So unless you're using monospace, things look pretty jumbled up. You're supposed to be able to change the letter spacing with the kerning and letterspacing properties, but browser support for those is also poor: IE11 doesn't seem to acknowledge the kerning value, and Firefox doesn't acknowledge either.
A final option is to take control of the layout yourself: use the power of d3 and the string .split("") method to break your title into single-character <tspan> elements that can be positioned one below each other and centered neatly within the <text> element. The downside is that this adds extra DOM elements, you can still select the block of text as a whole, just the same as you could select a phrase in an HTML paragraph even if each letter was styled as a separate <span>. I'm not sure if screen readers will automatically assume that there are spaces between the letters, though...
Comparison
This fiddle tries out the three ways to get horizontal characters in a vertical text label (writing-mode vs double rotate vs splitting into <tspan>s):
http://jsfiddle.net/hx5Th/11/
Code:
var svg = d3.select("body").append("svg");
//Green text, uses writing-mode property //
svg.append("text")
.attr("x", 40)
.attr("y", 40)
.attr("id", "title")
.attr("font-size", 50)
.attr("style", "fill: lightgreen; writing-mode: tb; glyph-orientation-vertical: 0")
.text("Top 100 Mentions");
//Black text, uses a double rotate //
svg.append("text")
.attr("x", 40)
.attr("y", 40)
.attr("id", "title")
.attr("font-size", 50)
.attr("rotate", -90)
.attr("dx", "1em")
.attr("dy", "-1em")
.attr("kerning", 0)
.attr("letter-spacing", "0.5em")
.attr("transform", "translate(150,0) rotate(90)")
.text("Top 100 Mentions");
//Blue text, uses d3 to create a series of tspans//
svg.append("text")
.attr("x", 40)
.attr("y", 40)
.attr("font-size", 50)
.attr("id", "title")
.style("fill", "blue")
.attr("transform", "translate(300,0)")
.attr("text-anchor", "middle")
.selectAll("tspan")
.data("Top 100 Mentions".split(""))
.enter().append("tspan")
.attr("x", 0)
.attr("dy", "0.8em")
.text(function(d){return d;});
Results (all on a Windows 7 system):
Chrome 33
IE 11
Firefox
I think this is d3 for the win...
html embedded in svg:
<svg
x="0px" y="0px"
width="200px" height="200px"
viewbox="0 0 200 200"
>
<foreignObject
x="20" y="20"
width="160" height="160"
>
<!-- we must set xmlns attribute
for foreignObject.childNode -->
<div
xmlns="http://www.w3.org/1999/xhtml"
style="
/* HTML-CSS vertical text */
writing-mode: vertical-lr;
text-orientation: upright;
line-height: 2em;
font-family: sans-serif;
font-size: 150%;
"
>Hello<br/> World</div>
</foreignObject>
</svg>
You can control the position and orientation of text by binding it to a path element. Example below with a running version on jsFiddle:
var svg = d3.select("body").append("svg"),
pi = Math.PI;
var arc = d3.svg.arc()
.innerRadius(150)
.outerRadius(180)
.startAngle(0)
.endAngle(-pi/2)
var path = svg.append("path")
.attr("id","path1")
.attr("d","M150 150 L150 20 Z")
.attr("style","stroke:rgb(255,0,0);stroke-width:2")
// Add a text label.
var text = svg.append("text")
.attr("x", 6)
.attr("dy", 15);
text.append("textPath")
.attr("stroke","black")
.attr("xlink:href","#path1")
.text("abc");
This should do what you want (although I don't know how to express it in d3):
<text x="100" y="100" transform="rotate(90, 100, 100)" style="glyph-orientation-horizontal: 270;">Some vertical text not rotated</text>
Obviously change the x and y coords to your own values.
For Firefox, the only thing I can find that they have implemented as of FF 30 is textLength. I added a fourth append of text to AmeliaBR's jsfiddle to demonstrate. It still looks like crap thought and D3 is still the winner. http://jsfiddle.net/hx5Th/11/
//Black text, uses a double rotate //
svg.append("text")
.attr("x", 40)
.attr("y", 40)
.attr("id", "title")
.attr("font-size", 50)
.attr("rotate", -90)
.attr("dx", "1em")
.attr("dy", "-1em")
.attr("textLength", "12.8em")
.attr("transform", "translate(450,0) rotate(90)")
.text("Top 100 Mentions");

Detecting visible nodes in d3.js force layout after zoom-pan

I use a force directed layout in d3.js once on load to position nodes and edges. Then I can zoom and pan the svg. When I zoom in I want to detect which nodes and edges are visible so I can implement lazy loading of additional data only for the visible nodes and edges.
Does anyone know what is the best way to get the (partialy) visible elements?
Code is below (just pasted some examples together):
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g')
svg.append('svg:rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'white')
function redraw() {
trans=d3.event.translate;
scale=d3.event.scale
svg.attr("transform", "translate(" + trans + ")" + " scale(" + scale + ")")
console.log(scale)
}
If you are using scales, you can find the currently visible range using scale.invert. So to find the visible values for an axis where the width is 600px, use x.invert(0) and x.invert(600).

Resources