I have this json file:
[
{
"id": "1",
"name": "Hello world",
"shape": "rect",
"fill": "pink"
}
]
And I also have this javascript file:
const svg = d3.select('svg')
var stringToHTML = function (str) {
var parser = new DOMParser();
var doc = parser.parseFromString(str, 'text/html');
return doc.body;
};
d3.json("tree.json").then(data => {
const elements = svg.selectAll('*')
.data(data);
elements.enter()
.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 100)
.attr("height", 100)
.attr("fill", d => d.fill)
elements.enter()
.append('text')
.attr("x", 50)
.attr("y", 60)
.html("text", d => {d.name})
})
and this html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<svg width="600" height="600">
</svg>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="index.js"></script>
</body>
</html>
Now the output is:
1st output
but when I switch "rect" with d => d.shape it doesn't behave as before.
d3.json("tree.json").then(data => {
const elements = svg.selectAll('*')
.data(data);
elements.enter()
.append(d => d.)
.attr("x", 20)
.attr("y", 20)
.attr("width", 100)
.attr("height", 100)
.attr("fill", d => d.fill)
But how is it different than the previous one? I also tried outputting d.shape, and it prints string. Then how can I make something that will create a shape according to the shape data?
Related
I am trying to generate a line dynamically based on a text's BBox created previously. However, the line is placed inaccurately. I was hoping for the code to place the line at the beginning of the text and not far from text.
I am not sure what has gone wrong here. BBox returns the smallest possible rectangle around the svg element, but why the line is placed far away when it is based on the same BBox dimension.
const body = d3.select('body');
//global specs
const width = 1536;
const height = 720;
const svgns = 'http://www.w3.org/2000/svg';
//generate svg
const svg = body.append('svg')
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`);
//background rect
svg.append('rect')
.attr('class', 'vBoxRect')
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('fill', '#EFEFEF');
//text data
const data = [{ "cat": "This is a test of text using Javascript" }];
//create grp
const grp = svg
.append('g')
.attr('class', 'test')
//create text
const svgText1 = grp
.append('g')
.classed('svgText', true)
.selectAll('text')
.data(data)
.join('text')
.attr('class', (d, i) => { return `textSvgOne` + `${i}` })
.each(
function(d, i) {
const element = svg.node();
const vBox = element.viewBox.baseVal;
const width = vBox.width / 2;
const height = vBox.height / 2;
d3.select(this)
.attr('x', `${width}`)
.attr('y', `${height}`)
}
)
.text((d, i) => { return d.cat })
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('alignment-baseline', 'middle')
.style('font-size', 'xx-large')
.style('font-family', "'Oswald', sans-serif");
//create line
const border1 = d3.select('g.svgText')
.selectAll('line')
.data(data)
.join('line')
.each(
function(d, i) {
const current = d3.select(this);
const target = current.node().parentNode.childNodes[0];
const box = target.getBBox();
const x = box.x;
const y = box.y;
const height = box.height;
current
.attr('class', (d, i) => { return `textSvgBorder` + `${i}` })
.attr('x1', x)
.attr('x2', x)
.attr('y1', y)
.attr('y2', `${y+height}`)
.attr('stroke', 'black')
}
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<style>
#import url('https://fonts.googleapis.com/css2?family=DM+Sans&display=swap');
#import url('https://fonts.googleapis.com/css2?family=Oswald:wght#200&display=swap');
</style>
<script type="text/javascript" src="prod.js">
</script>
</body>
</html>
#enxaneta thanks for the hint. The following adapted from this answer perfectly works in chrome/firefox/brave/edge.
The BBox calculation is wrapped in the following
promise document.fonts.ready.then(()=>) and the font has following declartaion now
<link rel="preconnect" href="https://fonts.gstatic.com/" />
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />
<link href='https://fonts.googleapis.com/css2?family=Oswald:wght#200&display=block' rel='stylesheet' type='text/css'>
const body = d3.select('body');
//global specs
const width = 1536;
const height = 720;
const svgns = 'http://www.w3.org/2000/svg';
//text data
const data = [{
"cat": "This is a test of text using Javascript"
}];
//generate svg
const svg = body.append('svg')
.attr('xmlns', svgns)
.attr('viewBox', `0 0 ${width} ${height}`);
//background rect
svg.append('rect')
.attr('class', 'vBoxRect')
.attr('width', `${width}`)
.attr('height', `${height}`)
.attr('fill', '#EFEFEF');
//create grp
const grp = svg
.append('g')
.attr('class', 'test')
//create text
const svgText1 = grp
.append('g')
.classed('svgText', true)
.selectAll('text')
.data(data)
.join('text')
.attr('class', (d, i) => {
return `textSvgOne` + `${i}`
})
.each(
function(d, i) {
const element = svg.node();
const vBox = element.viewBox.baseVal;
const width = vBox.width / 2;
const height = vBox.height / 2;
d3.select(this)
.attr('x', `${width}`)
.attr('y', `${height}`)
}
)
.text((d, i) => {
return d.cat
})
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('alignment-baseline', 'middle')
.attr('font-size', 'xx-large')
.style('font-family', "'Oswald', sans-serif");
//create line dynamically based on text BBox upon promise fulfillment
document.fonts.ready.then(() => {
d3.select('g.svgText')
.selectAll('line')
.data(data)
.join('line')
.each(
function(d, i) {
const current = d3.select(this);
const target = d3.select(`.textSvgOne` + `${i}`).node();
//console.log(target);
const box = target.getBBox();
const x = box.x;
const y = box.y;
const height = box.height;
current
.attr('class', (d, i) => {
return `textSvgBorder` + `${i}`
})
.attr('x1', x)
.attr('x2', x)
.attr('y1', y)
.attr('y2', `${y+height}`)
.attr('stroke', 'black')
}
)
}
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="preconnect" href="https://fonts.gstatic.com/" />
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />
<link href='https://fonts.googleapis.com/css2?family=Oswald:wght#200&display=block' rel='stylesheet' type='text/css'>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<!--
<style>
#import url('https://fonts.googleapis.com/css2?family=DM+Sans&display=swap');
#import url('https://fonts.googleapis.com/css2?family=Oswald:wght#200&display=block');
</style>
-->
<script type="text/javascript" src="prod.js">
</script>
</body>
</html>
I am new in D3.js and can't fix a seemingly small problem. The thing is I don't realy understand what's wrong with "my" code. The error that I get on the console tells me: "SyntaxError: expected expression, got '<' ", which doesn't make any sense for me. May be some of you can help me with this. Here it is:
var xyr = [
{x:1,y:1,r:1},
{x:2,y:2,r:2},
{x:3,y:3,r:3}
];
var body = d3.select("body");
var svg = body.append("svg").attr("width",250).attr("height",250);
var scaleX = d3.scaleLinear().range([0,250]);
var scaleY = d3.scaleLinear().range([0,250]);
function render(data){
//Data Binding
var circles = svg.selectAll("circle").data(data);
scaleX.domain(d3.extent(data,function(d){ return d.x; }));
scaleY.domain(d3.extent(data,function(d){ return d.y; }));
//Debugging (working correct --> 125)
circles
//Enter
.enter().apend("circle").attr("r",5)
//Update
.merge(circles)
.attr("cx", function (d){ return scaleX; })
.attr("cy", function (d){ return scaleY; });
//Exit
circles.exit().remove();
};
//Invoking the funktion with var xyr
render(xyr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
First remove the <script> and </script> tags, those are throwing the errors. Second, you link v3 when your code is v4/5 (I used v4 in my snippet). Third you had some spelling mistakes that threw some errors. Lastly (and the biggest issue with d3 rendering), is with this:
.merge(circles)
.attr("cx", function (d){ return scaleX; })
.attr("cy", function (d){ return scaleY; });
which was missing the link to the data, which was updated to
.merge(circles)
.attr("cx", function (d){ return scaleX(d.x); })
.attr("cy", function (d){ return scaleY(d.y); });
var xyr = [{
x: 1,
y: 1,
r: 1
},
{
x: 2,
y: 2,
r: 2
},
{
x: 3,
y: 3,
r: 3
}
];
var body = d3.select("body");
var svg = body.append("svg").attr("width", 250).attr("height", 250);
var scaleX = d3.scaleLinear().range([0, 250]);
var scaleY = d3.scaleLinear().range([0, 250]);
function render(data) {
//Data Binding
var circles = svg.selectAll("circle").data(data);
scaleX.domain(d3.extent(data, function(d) {
return d.x;
}));
scaleY.domain(d3.extent(data, function(d) {
return d.y;
}));
//Debugging (working correct --> 125)
circles
//Enter
.enter().append("circle").attr("r", 5)
//Update
.merge(circles)
.attr("cx", function(d) {
return scaleX(d.x);
})
.attr("cy", function(d) {
return scaleY(d.y);
});
//Exit
circles.exit().remove();
};
//Invoking the funktion with var xyr
render(xyr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
I want to flip the line so that the higher value goes up and the lower value goes down. I tried to use scale(1,-1) but it doesn't output anything. Please see my code below
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="paths"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script>
var canvas = d3.select(".paths").append("svg")
.attr("width", 500)
.attr("height", 500);
var data = [
{x:10, y:200},
{x:30, y:170},
{x:50, y:70},
{x:70, y:140},
{x:90, y:150},
{x:110, y:120},
{x:130, y:150},
{x:150, y:140},
{x:170, y:110}
];
var group = canvas.append('g')
.attr("transform", "scale(1,1)");
var line = d3.svg.line()
.x(function(d){ return d.x })
.y(function(d){ return d.y });
group.selectAll("path")
.data([data])
.enter()
.append("path")
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 2);
</script>
</body>
</html>
https://jsbin.com/dayoxon/7/edit?html,output
You have to use a scale, which by the way will fix another problem you have: your data values should not be (or normally will not be) SVG coordinates.
This is a basic example of a linear scale:
var scale = d3.scale.linear()
.domain([0, 200])
.range([height,0]);
Here, the domain goes from 0 to 200, which is the maximum in your data. Then, those values will be mapped to:
.range([height, 0])
Where height is the height of the SVG.
Finally, use the scale in the line generator:
var line = d3.svg.line()
.x(function(d){ return d.x })
.y(function(d){ return scale(d.y) });
Here is your code with that scale:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="paths"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script>
var canvas = d3.select(".paths").append("svg")
.attr("width", 500)
.attr("height", 300);
var data = [
{x:10, y:200},
{x:30, y:170},
{x:50, y:70},
{x:70, y:140},
{x:90, y:150},
{x:110, y:120},
{x:130, y:150},
{x:150, y:140},
{x:170, y:110}
];
var group = canvas.append('g');
var scale = d3.scale.linear()
.domain([0, 200])
.range([300,0]);
var line = d3.svg.line()
.x(function(d){ return d.x })
.y(function(d){ return scale(d.y) });
group.selectAll("path")
.data([data])
.enter()
.append("path")
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 2);
</script>
</body>
</html>
I made a D3 map following Let's make a map tutorial by M. Bostock.
It is intended to create .subunit.id class and color it using CSS like .subunit.23 { fill: #f44242; }. But while .subunit is adressed well I can not reach each unit by specifying its id. Any ideas?
TopoJSON file is available here
https://gist.github.com/Avtan/649bbf5a28fd1f76278c752aca703d18
<!DOCTYPE html>
<meta charset="utf-8">
<html lang="en">
<style>
.subunit {
fill: #4286f4;
stroke: #efbfe9;}
.subunit.23 { fill: #f44242; }
</style>
<head>
<title>D3 Uzbekisztan map</title>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script> -->
</head>
<body>
<script>
var width = 960,
height = 1160;
var projection = d3.geo.albers()
.center([-10, 40])
.rotate([-75, 0])
.parallels([38, 44])
.scale(4000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
d3.json("uzb_topo.json", function (error, uzb) {
if (error) return console.error(error);
console.log(uzb);
svg.selectAll(".subunit")
.data(topojson.feature(uzb, uzb.objects.uzb).features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
});
</script>
</body>
</html>
IDs cannot start by a number. Right now, you're setting two different classes, and the last one start with a number.
A simple solution is removing the space in your class name. So, this:
.attr("class", function(d) { return "subunit " + d.id; })
Should be this:
.attr("class", function(d) { return "subunit" + d.id; })//no space
And set your CSS accordingly.
Another solution is adding a letter before the number, like this:
.attr("class", function(d) { return "subunit " + "a" + d.id; })
So, you'll have the classes "a01", "a02", "a03" etc...
I am trying to create a D3.js packed circle diagram.
When I embed the data in the HTML file, it works fine. When I put the data in an external file, I get nothing (blank DOM, no console msgs).
If you uncomment the var data declaration and comment out the d3.json (and the corresponding closing parentheses) it works fine.
I can see the "2013 Inf-2.json" file in the browser and it appears well formed (it passes jsonlint validation). It includes everything from the first "{" through/including the last "}". Much as the embedded example.
I'm running this through httpd (:80) on OSX Mavericks and trying to render the chart in Chrome or Safari.
<!DOCTYPE html>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript" src="./d3.v3.min.js"></script>
</head>
<body>
<div id="chart2"></div>
<script type="text/javascript">
var w = 640, h = 480;
/*
var data ={
"name" : "root",
"children" : [
{
"name":"URIN TRACT INFECTION NOS",
"size":12196
},
{
"name":"ACUTE PHARYNGITIS",
"size":6679
},
{
"name":"PNEUMONIA ORGANISM NOS",
"size":6452
},
{
"name":"BRONCHITIS NOS",
"size":2636
},
{
"name":"CELLULITIS OF LEG",
"size":2348
},
{
"name":"OBSTR CHRONIC BRONCHITIS W (ACUTE) EXACERBATION",
"size":2203
}
]
}
*/
var data = d3.json("2013 Inf-2.json", function(error, root) {
var canvas = d3.select("#chart2")
.append("svg:svg")
.attr("width", w)
.attr("height", h);
var nodes = d3.layout.pack()
.value(function(d) { return d.size; })
.size([w, h])
.nodes(data);
// Get rid of root node
nodes.shift();
canvas.selectAll("circles")
.data(nodes)
.enter().append("svg:circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; })
.attr("fill", "green")
.attr("stroke", "grey");
});
</script>
</html>
You should change line
var data = d3.json("2013 Inf-2.json", function(error, root) {
to
var data = d3.json("2013 Inf-2.json", function(error, data) {
So you just have to replace "root" with "data"