SVG incorrect text BBox calculation when google fonts used - javascript

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>

Related

D3 JS - Why d3 is not appending a string?

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?

How to use an exit selection on text with tspan

I have a Pie Chart which updates as you move the slider, the chart also has labels which I want to update and reposition as the data changes. When I update the chart new labels are drawn but the old labels are not removed. I have managed to get the Join/Enter/Update/Remove sequence working on the chart itself but not the labels.
Is there anything different about text that means this update sequence does not work?
function update() {
// Scales
const colors = d3.quantize(d3.interpolateSpectral, dataset.length);
const colorScale = d3
.scaleOrdinal()
.domain(dataset.map((d) => d.type))
.range(colors);
//Define Pie Chart
const generationPie = d3
.pie()
.padAngle(0.005)
.value((d) => d.generation)
.sort(null);
//Pass dataset into Pie Chart
const slices = generationPie(dataset);
//JOIN DATA
const arcs = arcGroup.selectAll("path").data(slices);
//ENTER NEW DATA
arcs
.enter()
.append("path")
.attr("d", arc)
.attr("fill", (d) => colorScale(d.data.type))
.each(function (d) {
this._current = d;
});
//UPDATE
arcs.transition().duration(1000).attrTween("d", arcTween);
//REMOVE OLD DATA
arcs.exit().remove();
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
//==============================================================
//Data timestamp
//Join
const dataTimeLabel = dataTimestamp.text(timeFormat(dataTime));
//Update
dataTimeLabel.transition().duration(1000);
//ENTER
dataTimeLabel.enter().text(timeFormat(dataTime));
//REMOVE
dataTimestamp.exit().remove();
//==============================================================
//Labels
const labelsGroup = ctr
.append("g")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
);
//JOIN
const labelArcs = labelsGroup.selectAll("text").data(slices);
//ENTER
labelArcs
.enter()
.append("text")
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`)
.append("tspan")
.attr("font-weight", "bold")
.attr("font-size", 14)
.attr("x", "-2em")
.text((d) => d.data.type)
.append("tspan")
.attr("x", "-2.3em")
.attr("y", "+1.2em")
.attr("fill-opacity", 0.7)
.attr("font-size", 12)
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %")
//UPDATE
labelArcs.transition().duration(1000)
//REMOVE
labelArcs.exit().remove();
}
Any help would be really welcome.
On top of the comment about re-appending the groups and the answer about splitting the selection, you have to change the text position in your update selection...
labelArcs.transition().duration(1000)
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`);
...as well as changing the text values in the <tspan> elements:
labelArcs.select(".type")
.text((d) => d.data.type);
labelArcs.select(".value")
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %");
Here is the resulting code:
async function draw() {
// Data
let dataset;
let dataTime;
const commaFormat = d3.format(",");
const timeFormat = d3.timeFormat("%d-%m-%Y %I:%M");
// Dimensions
let dimensions = {
width: 700,
height: 700,
margins: 10,
};
dimensions.ctrWidth = dimensions.width - dimensions.margins * 2;
dimensions.ctrHeight = dimensions.height - dimensions.margins * 2;
const radius = dimensions.ctrHeight / 3;
// Draw Image
const svg = d3
.select("#chart-area")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height);
const ctr = svg
.append("g") // <g>
.attr(
"transform",
`translate(${dimensions.margins}, ${dimensions.margins})`
);
const arc = d3
.arc()
.outerRadius(radius)
.innerRadius(radius * 0.8);
const arcLabels = d3
.arc()
.outerRadius(radius * 1.2)
.innerRadius(radius * 1.2);
const arcGroup = ctr
.append("g")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
);
const dataTimestamp = svg
.append("text")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
)
.attr("text-anchor", "middle")
.append("tspan")
.attr("x", "+0.2em")
.attr("y", "+2.5em")
.attr("font-size", 20);
//Labels
const labelsGroup = ctr
.append("g")
.attr(
"transform",
`translate(${dimensions.ctrHeight / 2}, ${dimensions.ctrWidth / 2})`
);
d3.json("https://raw.githubusercontent.com/1crisl/test/main/data.json").then((data) => {
const timeArray = data.map((record) => {
return record.unixTime;
});
const minTime = d3.min(timeArray);
const maxTime = d3.max(timeArray);
let i = timeArray.length - 1;
$("#dateLabel1").text(timeFormat(minTime));
$("#dateLabel2").text(timeFormat(maxTime));
$("#date-slider").slider({
max: timeArray.length - 1,
min: 0,
value: timeArray.length - 1,
change: (event, ui) => {
i = $("#date-slider").slider("value");
dataTime = data[i].unixTime;
dataset = data[i].data.filter(function(obj) {
return obj.percentage > "1";
});
update();
},
});
dataTime = data[i].unixTime;
dataset = data[i].data.filter(function(obj) {
return obj.percentage > "1";
});
update();
});
function update() {
// Scales
const colors = d3.quantize(d3.interpolateSpectral, dataset.length); //Generates a colour for each item in the dataset array
const colorScale = d3
.scaleOrdinal()
.domain(dataset.map((d) => d.type))
.range(colors);
//Define Pie Chart
const generationPie = d3
.pie()
.padAngle(0.005)
.value((d) => d.generation)
.sort(null);
//Pass dataset into Pie Chart
const slices = generationPie(dataset);
//JOIN DATA
const arcs = arcGroup.selectAll("path").data(slices);
//ENTER NEW DATA
arcs
.enter()
.append("path")
.attr("d", arc)
.attr("fill", (d) => colorScale(d.data.type))
.each(function(d) {
this._current = d;
});
//UPDATE
arcs.transition().duration(1000).attrTween("d", arcTween);
//REMOVE OLD DATA
arcs.exit().remove();
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
//==============================================================
//Data timestamp
//Join
const dataTimeLabel = dataTimestamp.text(timeFormat(dataTime));
//Update
dataTimeLabel.transition().duration(1000);
//ENTER
dataTimeLabel.enter().text(timeFormat(dataTime));
//REMOVE
dataTimeLabel.exit().remove();
//==============================================================
//JOIN
const labelArcs = labelsGroup.selectAll("text").data(slices, d => d.data.type);
//ENTER
const textGroup = labelArcs
.enter()
.append("text")
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`);
textGroup
.append("tspan")
.attr("font-weight", "bold")
.attr("font-size", 14)
.attr("x", "-2em")
.attr("class", "type")
.text((d) => d.data.type);
textGroup
.append("tspan")
.attr("x", "-2.3em")
.attr("y", "+1.2em")
.attr("fill-opacity", 0.7)
.attr("font-size", 12)
.attr("class", "value")
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %");
//UPDATE
labelArcs.select(".type")
.text((d) => d.data.type);
labelArcs.select(".value")
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %");
labelArcs.transition().duration(1000)
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`);
//REMOVE
labelArcs.exit().remove();
}
}
draw();
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="">
<title>Visualisation</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<!-- jQueryUI styling -->
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
</head>
<body>
<!-- Bootstrap grid setup -->
<div class="container">
<div class="row">
<div class="col-md-12 d-flex justify-content-center">
<div id="chart-area"></div>
</div>
<div class="col-md-12 d-flex justify-content-center">
<div id="slider-div">
<label>Data Extent</label>
<br>
<label><span id="dateLabel1">01/01/2000</span> to <span id="dateLabel2">18/03/2021</span></label>
<div id="date-slider"></div>
</div>
</div>
</div>
</div>
<!-- External JS libraries -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
<!-- Custom JS -->
<script src="js/main.js"></script>
</body>
</html>
PS: your percentages are way off.
Try to split code:
const textGroup = labelArcs
.enter()
.append("text")
.attr("transform", (d) => `translate(${arcLabels.centroid(d)})`)
textGroup.append("tspan")
.attr("font-weight", "bold")
.attr("font-size", 14)
.attr("x", "-2em")
.text((d) => d.data.type)
textGroup.append("tspan")
.attr("x", "-2.3em")
.attr("y", "+1.2em")
.attr("fill-opacity", 0.7)
.attr("font-size", 12)
.text((d) => commaFormat(d.data.percentage).toLocaleString() + " %")
If it does not help, please provide a snippet of a fiddle to work with

Why are labels/text not rendering on this d3.js scatter plot?

I've been practicing using D3.js by making a simple scatter plot with some dummy data. Unfortunately after spending an hour of googling and looking at examples, I still can't figure out why my labels are not rendering. Could someone point me in the right direction on what I'm doing wrong?
I'm assuming the text is not rendering because when I put in dummy numbers for the X and Y attributes, I still don't get the labels to render on the page.
const points = [
[1,2],
[2,4],
[3,6],
[4,8],
[5,10]
]
const w = 500
const h = 500
const maxX = d3.max(points, (d) => d[0]);
const maxY = d3.max(points, (d) => d[1]);
console.log(maxX, maxY);
const scaleX = d3.scaleLinear()
.domain([0, maxX])
.range([0, w]);
const scaleY = d3.scaleLinear()
.domain([0, maxY])
.range([0,h]);
const svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
;
svg.selectAll("circle")
.data(points)
.enter()
.append('circle')
.attr('cx',(d) => scaleX(d[0]))
.attr('cy',(d) =>h - scaleY(d[1]))
.attr('r', 5)
.attr('fill', 'black')
// adding axae
const xAxis = d3.axisBottom(scaleX);
const yAxis = d3.axisLeft(scaleY);
svg.append("g")
.attr("transform", "translate(0," + (h -20) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(" + 20 + ", 0)")
.call(yAxis);
// adding labels
svg.selectAll("text")
.data(points)
.enter()
.append("text")
.text((d) => (d[0] + "," + d[1]))
.attr("x", (d) => scaleX(d[0] + 10))
.attr("y", (d) => scaleY(d[1]))
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<script src='app.js'></script>
</body>
</html>
The code was almost correct, whenever you want to insert(enter) some nodes you must check if there is no selection corresponding to that.
As you have already rendered the axes, so there are already text nodes in my chart but i wanted to create an empty selection. Always use a unique indentifier for that
Replace // adding labels
svg.selectAll("text")
with // adding labels
svg.selectAll(".mylables")
Change x and y coordinate as below, you can tweek accordingly.
.attr("x", (d) => scaleX(d[0]) + 10)
.attr("y", (d) => h - scaleY(d[1]))
const points = [
[1, 2],
[2, 4],
[3, 6],
[4, 8],
[5, 10]
]
const w = 500
const h = 500
const maxX = d3.max(points, (d) => d[0]);
const maxY = d3.max(points, (d) => d[1]);
console.log(maxX, maxY);
const scaleX = d3.scaleLinear()
.domain([0, maxX])
.range([0, w]);
const scaleY = d3.scaleLinear()
.domain([0, maxY])
.range([0, h]);
const svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
svg.selectAll("circle")
.data(points)
.enter()
.append('circle')
.attr('cx', (d) => scaleX(d[0]))
.attr('cy', (d) => h - scaleY(d[1]))
.attr('r', 5)
.attr('fill', 'black')
// adding axae
const xAxis = d3.axisBottom(scaleX);
const yAxis = d3.axisLeft(scaleY);
svg.append("g")
.attr("transform", "translate(0," + (h - 20) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(" + 20 + ", 0)")
.call(yAxis);
// adding labels
svg.selectAll(".mylabels")
.data(points)
.enter()
.append("text")
.style('class', 'mylables')
.text((d) => {
debugger;
return (d[0] + "," + d[1])
})
.attr("x", (d) => scaleX(d[0]) + 10)
.attr("y", (d) => h - scaleY(d[1]))
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<script src='app.js'></script>
</body>
</html>

d3.js donut chart - add drop shadow to the highlighted section

In my project, I am using d3.js donut chart to display some data. On click of each of the arc sections, it will be highlighted. The requirement is to add a drop shadow effect to the highlighted section when clicked, so it will highlighted much more. I am using stroke to create a shadow effect, but it is not looking like a shadow.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title> D3 Js Example </title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>
<div id="my_dataviz"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.7/d3.min.js" ></script>
<script>
var lastSelected = "";
var firstSelected = "";
var width = 450
height = 450
margin = 40
var radius = Math.min(width, height) / 2 - margin
var normalArc = d3.arc().outerRadius(radius - 30).innerRadius(radius - 70);
var biggerArc = d3.arc().outerRadius(radius - 80).innerRadius(radius - 10);
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = {a: 9, b: 20, c:30, d:8, e:12}
var color = d3.scaleOrdinal()
.domain(data)
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"])
var pie = d3.pie()
.value(function(d) {return d.value; })
var data_ready = pie(d3.entries(data))
svg
.selectAll('whatever')
.data(data_ready)
.enter()
.append('path')
.attr('d', normalArc)
.attr('fill', function(d){ return(color(d.data.key)) })
.style("opacity", 0.7)
.attr('d', function(d, index) {
// If this is the first segment make it a wider arc
if (index === 0) {
firstSelected = this;
return biggerArc(d);
} else {
return normalArc(d);
}
}).on("click", function(d) {
if (firstSelected) {
d3.select(firstSelected).attr("d", normalArc).style("stroke-width", "0px")
firstSelected = false;
}
if (lastSelected) {
d3.select(lastSelected).attr("d", normalArc).style("stroke-width", "0px")
}
d3.select(this).attr("d", biggerArc).style("stroke", "black").style("stroke-width", "10px")
.style("stroke-opacity","0.08")
.style('stroke-location',"outer")
.style('paint-order','stroke')
.style('stroke-linejoin',"round")
lastSelected = this;
})
</script>
</body>
</html>
After hours of searching i am able to find the answer. we need to create the filter for the drop shadow and append that to the svg and in which ever arc you want ,you just add the filter as an attribute
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title> D3 Js Example </title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>
<div id="my_dataviz"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.7/d3.min.js" ></script>
<script>
var lastSelected = "";
var firstSelected = "";
var width = 450
height = 450
margin = 40
var radius = Math.min(width, height) / 2 - margin
var normalArc = d3.arc().outerRadius(radius - 30).innerRadius(radius - 70);
var biggerArc = d3.arc().outerRadius(radius - 80).innerRadius(radius - 10);
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
/* For the drop shadow filter... */
var defs = svg.append("defs");
var filter = defs.append("filter")
.attr("id", "dropshadow")
filter.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", 4)
.attr("result", "blur");
filter.append("feOffset")
.attr("in", "blur")
.attr("dx", 2)
.attr("dy", 2)
.attr("result", "offsetBlur");
var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode")
.attr("in", "offsetBlur")
feMerge.append("feMergeNode")
.attr("in", "SourceGraphic");
var data = {a: 9, b: 20, c:30, d:8, e:12}
var color = d3.scaleOrdinal()
.domain(data)
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"])
var pie = d3.pie()
.value(function(d) {return d.value; })
var data_ready = pie(d3.entries(data))
svg
.selectAll('whatever')
.data(data_ready)
.enter()
.append('path')
.attr('d', normalArc)
.attr('fill', function(d){ return(color(d.data.key)) })
.style("opacity", 0.7)
.attr('d', function(d, index) {
// If this is the first segment make it a wider arc
if (index === 0) {
firstSelected = this;
return biggerArc(d);
} else {
return normalArc(d);
}
}).on("click", function(d) {
if (firstSelected) {
d3.select(firstSelected).attr("d", normalArc).attr("filter", "");
firstSelected = false;
}
if (lastSelected) {
d3.select(lastSelected).attr("d", normalArc).attr("filter", "");
}
d3.select(this).attr("d", biggerArc).attr("filter", "url(#dropshadow)");
lastSelected = this;
})
if(firstSelected){
d3.select(firstSelected).attr("filter", "url(#dropshadow)");
}
</script>
</body>
</html>

Flip d3js svg line

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>

Categories