How could I scale a rectangle in svg to fit the text using d3.js.
When you run it for the first time the yellowgreen rect covers the text nicely, but if you will resize the screen the text size and position will be changed, while the rectangle stays the same.
Below is the code, here is the fiddle:
debugger;
var svg = d3.select("#drawRegion")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
svg.append("rect")
.attr("x", "0")
.attr("y", "0")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "yellow");
const rectAroundText = svg
.append("rect");
const textMiddleX = 50;
const textMiddleY = 50;
const testText = svg
.append("text");
testText
.attr("x", textMiddleX + "%")
.attr("y", textMiddleY + "50%")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "central")
.attr("x", "50%")
.attr("y", "50%")
.attr("fill", "#000")
.classed("scalingText", true)
.text("svdfv");
const textBox = testText.node().getBBox();
rectAroundText
.attr("x", textBox.x)
.attr("y", textBox.y)
.attr("width", textBox.width)
.attr("height", textBox.height)
.attr("fill", "yellowgreen");
<div id="drawRegion">
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
I would like the rectangle to scale up and move along with the text. Is it possible?
Related
Hi i have a heatmap here that im trying to give color to. Right now its all over red but I want to use the d3.interpolateRdYlBu, i want my values that are lower to be the blue and the higher be the red so i would like it to gel nicely. I know that its reading correctly since i get the red and no other errors in my console but Im not doing something right that it doesnt take my value into account and do accordingly. Any help would be appreciated!
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<!-- Load color palettes -->
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 80, right: 25, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/Nataliemcg18/Data/master/NASA_Surface_Temperature.csv", function(data) {
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
var myGroups = d3.map(data, function(d){return d.group;}).keys()
var myVars = d3.map(data, function(d){return d.variable;}).keys()
// Build X scales and axis:
var x = d3.scaleBand()
.range([ 0, width ])
.domain(myGroups)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0))
.select(".domain").remove()
// Build Y scales and axis:
var y = d3.scaleBand()
.range([ height, 0 ])
.domain(myVars)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.call(d3.axisLeft(y).tickSize(0))
.select(".domain").remove()
// Build color scale
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1,100])
// create a tooltip
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "green")
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.html("The exact value of this cell is: " + d.value, )
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
// add the squares
svg.selectAll()
.data(data, function(d) {return d.group+':'+d.variable;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.group) })
.attr("y", function(d) { return y(d.variable) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return myColor(d.value)} )
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
})
// Add title to graph
svg.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("A d3.js heatmap");
// Add subtitle to graph
svg.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("A short description of the take-away message of this chart.");
</script>
In your colour scale, you've set the domain as [0,100]. Your values however are between 0 and 1.5, and you want them reversed
so, this should fix it:
// Build color scale
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1.3,0])
To be even more thorough, you can use the d3.max and d3.min functions to work out the max in min for you:
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([d3.max(data, d=>d.value),d3.min(data, d=>d.value)])
Heres a jsFiddle with this working: https://jsfiddle.net/x8zyud5t/
I have code to make a circle and I'd like to place text on top of it.
I'm using this for my example: https://bl.ocks.org/mbostock/raw/7341714/
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select(".info-container")
.append("svg")
.attr("class","comparison-svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", circleRadius(d.properties.contextvalue))
.attr("cy", infoHeight/2)
.attr("cx", infoWidth/2)
.style("fill","grey")
.style("stroke","black")
.style("stroke-width","3px")
circle.append("text")
.text(d.properties.contextvalue)
.style("display", "block")
.style("y", infoHeight/2)
.style("x", infoHeight/2)
.style("color","red")
.style("font-size","20px")
The circle works, but the text won't appear on top of it. Instead, it is in the top left corner of the SVG element. I've tried position: absolute along with top and left and it stays in the same corner.
In D3, the attr methods uses Element.setAttribute internally, while style uses CSSStyleDeclaration.setProperty().
In an SVG <text> element, x and y are attributes. Therefore, change those style() methods for attr(). Also, get rid of that .style("display", "block").
So, it should be:
circle.append("text")
.text(d.properties.contextvalue)
.attr("y", infoHeight/2)
.attr("x", infoHeight/2)
.style("color","red")
.style("font-size","20px")
Here is your code with that change:
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select("body")
.append("svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", 50)
.attr("cy", infoHeight / 2)
.attr("cx", infoWidth / 2)
.style("fill", "lightgrey")
.style("stroke", "black")
.style("stroke-width", "3px")
circle.append("text")
.text("Foo Bar Baz")
.attr("y", infoHeight / 2)
.attr("x", infoHeight / 2)
.style("color", "red")
.style("font-size", "20px")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Finally, pay attention to the position of the text: it's not entered (regarding the circle). If you want to center it, use text-anchor and dominant-baseline:
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select("body")
.append("svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", 50)
.attr("cy", infoHeight / 2)
.attr("cx", infoWidth / 2)
.style("fill", "lightgrey")
.style("stroke", "black")
.style("stroke-width", "3px")
circle.append("text")
.text("Foo Bar Baz")
.attr("y", infoHeight / 2)
.attr("x", infoHeight / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("color", "red")
.style("font-size", "20px")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I am using SVG to draw text and shapes in D3.js, and want to draw shapes inline with text and with similar dimensions to the text. The only way I can think of doing this is draw a rect around each tspan then draw the shape in relative position to the tspan rect. The result being:
This is a rectangle [] this is a circle ()
Where the brackets above represent the SVG shapes. Current code is below.
js:
function setupSVG(){
d3.select("div#chartId")
.append("div")
.classed("svg-container", true)
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 200 200")
.attr("id", "svg_area_id")
}
function renderSVGText(){
var svgArea = d3.select("svg#svg_area_id");
svgArea.append("rect")
.attr("x", 100)
.attr("y", 0)
.attr("height", 10)
.attr("width", 10)
.attr("id", "shape");
var group = svgArea.append("g")
.attr("width", "100%")
.attr("height", "100%")
.style("stroke", "red") //I only want to draw rect stroke
.style("fill", "none");
var text = group.append("text")
.attr("y", "0")
.attr("font-size",52)
.attr("dy", "1em")
.style('fill', 'black')
var tspan1 = text.append('tspan')
tspan1.text("This is a square");
var tspan2 = text.append('tspan')
tspan2.text("and this is a triangle");
var boundingRect = group.append("rect")
//see http://phrogz.net/SVG/tspan_bounding_box.xhtml
var bbox = tspan1.getBoundingClientRect();
var pt = svg.createSVGPoint();
pt.x = bbox.left;
pt.y = bbox.top;
var pt2 = pt.matrixTransform(xform);
rect.setAttribute('x',pt2.x);
rect.setAttribute('y',pt2.y);
pt.x = bbox.right;
pt.y = bbox.bottom;
pt = pt.matrixTransform(xform);
boundingRect.attr('width', pt.x-pt2.x);
boundingRect.attr('height',pt.y-pt2.y);
/* this draws a rect around all text
var textSize = text.node().getBBox();
boundingRect.attr("width", textSize.width)
.attr("height", textSize.height);
*/
}
html:
<div class="svg-container" id="chartId"></div>
css:
.svg-container {
display: inline-block;
width: 512px;
height: 512px;
padding-top: 50px;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
border: 1px solid black;
}
Any ideas on how to do this? Any easier ways than the track I am following?
I tried to get tspan dimensions using tspan.node().getComputedTextLength() but this returned an error, I presume because it hadn't been rendered at the call time. I just used text.node().getBBox() to get each text blocks dimensions instead:
function renderSVGText(){
var svgArea = d3.select("svg#svg_area_id");
var group = svgArea.append("g")
.attr("width", "100%")
.attr("height", "100%")
.style("stroke", "red")
.style("fill", "none");
var text = group.append("text")
.attr("y", "0")
.attr("font-size",52)
.attr("dy", "1em")
.attr("id", "text_id")
.style('fill', 'black');
var tspan1 = text.append('tspan')
.attr("id", "tspan1_id")
tspan1.text("This is a square");
var boundingRect = svgArea.append("rect")
.style("stroke", "pink")
.style("fill", "none");
var textSize = text.node().getBBox();
boundingRect.attr("width", textSize.width)
.attr("height", textSize.height);
svgArea.append("rect")
.attr("x", textSize.width+10)
.attr("y", 0)
.attr("height", textSize.height)
.attr("width", textSize.height)
.attr("id", "shape");
var text2 = group.append("text")
.attr("x", textSize.width+textSize.height+20)
.attr("y", "0")
.attr("font-size",52)
.attr("dy", "1em")
.attr("id", "text2_id")
.style('fill', 'black');
var tspan2 = text2.append('tspan')
tspan2.text("and this is a triangle");
}
In the following example, is there a way to for the zoomArea to detect a mouse wheel event that happens while pointing on one of the grey circles? The aim is to not interrupt the zoom behaviour when doing so. The circles should still be able to receive pointer events in order to e.g. display tooltips.
var dataset = [0, 2345786000, 10000000000];
var svg = d3.select("body").append("svg");
var w = 500, h = 200;
var padding = 50;
svg.attr("width", w)
.attr("height", h);
// Background pattern
var patternSize = 5;
svg.append("defs")
.append("pattern")
.attr("id", "dotPattern")
.attr("patternUnits", "userSpaceOnUse")
.attr("width", patternSize)
.attr("height", patternSize)
.append("circle")
.attr("cx", patternSize / 2)
.attr("cy", patternSize / 2)
.attr("r", 2)
.style("stroke", "none")
.style("fill", "lightgrey")
.style("opacity", 0.5);
var xScale = d3.time.scale()
.domain([0, 10000000000])
.range([padding, w-padding]);
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(5);
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h-padding) + ")")
.call(xAxis);
var zoom = d3.behavior.zoom()
.on("zoom", build)
.scaleExtent([1, 20]);
zoom.x(xScale);
var clipPath = svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width",w-2*padding)
.attr("height", h-padding);
var zoomArea = svg.append("g")
.attr("class", "zoomArea")
.style("cursor","move")
.attr("clip-path", "url(#clip)");
var zoomRect = zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "url(#dotPattern)")
.style("pointer-events", "all")
.style("cursor","move")
.call(zoom);
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey")
function build(){
svg.select("g.axis").call(xAxis);
d3.selectAll("circle")
.attr("cx", function(d){
return xScale(d);
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Call zoom on circles as well.
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey")
.call(zoom);//call zoom on circle
Working code here
Hope this helps!
Another way of doing the same:
First make a rectangle with the fill background,don't attach the zoom listener to it.
var zoomRect = zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "url(#dotPattern)")
.style("cursor","move");//no zoom call
Not attach circles.
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey");
Now make another rectangle same as the first except it has zoom behavior and fill transparent..so that its above all elements to handle the zoom behavior.
zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "transparent")
.style("pointer-events", "all")
.style("cursor","move")
.call(zoom);
Working example here
Hope this helps too!
It appears I've followed the steps properly to fill a circle with an external image. I've tried numerous different image links, yet still can't resolve this pixelation issue. Any help is appreciated, here's a link to my fiddle:
http://jsfiddle.net/SdN2F/7/
Code issue snipet:
var photoCircle = d3.select('svg')
.append("circle")
.attr("cx", width-160)
.attr("cy", height-140)
.attr("r", radius-35)
.style("fill", "url(#photo)");
var image = d3.select('svg')
.append("pattern")
.attr("id", "photo")
.attr("x", 0)
.attr("y", 0)
.attr("width", width-160)
.attr("height", height-140)
.append("image")
.attr("x", 0)
.attr("y", 0)
.attr("width", width-160)
.attr("height", height-140)
.attr("xlink:href", "http://static-1.nexusmods.com/15/mods/110/images/50622-1-1391287636.jpeg");
You need to set the patternUnits attribute of pattern.
var image = d3.select('svg').append('defs')
.append("pattern")
.attr("id", "photo")
.attr("patternUnits","userSpaceOnUse")
.attr("x", 0)
.attr("y", 0)
.attr("width", 2*radius)
.attr("height", 2*radius)
.append("image")
.attr("x", 0)
.attr("y", 0)
.attr("width", 2*radius)
.attr("height", 2*radius)
.attr("xlink:href", "http://static-1.nexusmods.com/15/mods/110/images/50622-1-1391287636.jpeg");
jsFiddle: http://jsfiddle.net/chrisJamesC/SdN2F/11/