I've created a sample Asp.Net MVC 4 application where I've used D3.js to append an SVG element and then inside the SVG I've appended a text element (see code below). This all works fine until I try to append an img to the SVG using a local png file. The img gets appended to the DOM, but the img is not rendered on the page. Any ideas what I'm doing wrong here, and how to go about fixing it?
#{
ViewBag.Title = "Home Page";
}
<script src="~/Scripts/d3.v3.js"></script>
<script type="text/javascript">
var svg = d3.select("body")
.append("svg")
.attr("width", 200)
.attr("height", 100)
.style("border", "1px solid black");
var text = svg.selectAll("text")
.data([0])
.enter()
.append("text")
.text("Testing")
.attr("x", "40")
.attr("y", "60");
var imgs = svg.selectAll("img").data([0]);
imgs.enter()
.append("img")
.attr("xlink:href", "#Url.Content("~/Content/images/icons/refresh.png")")
.attr("x", "60")
.attr("y", "60")
.attr("width", "20")
.attr("height", "20");
</script>
#Richard Marr - Below is an attempt to do the same thing in straight HTML, which gives me the same result. I'm not sure about my code to get the refresh.png file from the local drive this way.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v2.js"></script>
</head>
<body>
<script type="text/javascript">
var svg = d3.select("body")
.append("svg")
.attr("width", 200)
.attr("height", 100)
.style("border", "1px solid black");
var text = svg.selectAll("text")
.data([0])
.enter()
.append("text")
.text("Testing")
.attr("x", "40")
.attr("y", "60");
var imgs = svg.selectAll("img").data([0]);
imgs.enter()
.append("svg:img")
.attr("xlink:href", "file:///D:/d3js_projects/refresh.png")
.attr("x", "60")
.attr("y", "60")
.attr("width", "20")
.attr("height", "20");
</script>
</body>
</html>
nodeEnter.append("svg:image")
.attr('x', -9)
.attr('y', -12)
.attr('width', 20)
.attr('height', 24)
.attr("xlink:href", "resources/images/check.png")
In SVG (contrasted with HTML), you will want to use <image> instead of <img> for elements.
Try changing your last block with:
var imgs = svg.selectAll("image").data([0]);
imgs.enter()
.append("svg:image")
...
My team also wanted to add images inside d3-drawn circles, and came up with the following (fiddle):
index.html:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="timeline.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.js"
integrity="sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI="
crossorigin="anonymous"></script>
<script src="./timeline.js"></script>
</head>
<body>
<div class="timeline"></div>
</body>
</html>
timeline.css:
.axis path,
.axis line,
.tick line,
.line {
fill: none;
stroke: #000000;
stroke-width: 1px;
}
timeline.js:
// container target
var elem = ".timeline";
var props = {
width: 1000,
height: 600,
class: "timeline-point",
// margins
marginTop: 100,
marginRight: 40,
marginBottom: 100,
marginLeft: 60,
// data inputs
data: [
{
x: 10,
y: 20,
key: "a",
image: "https://unsplash.it/300/300",
id: "a"
},
{
x: 20,
y: 10,
key: "a",
image: "https://unsplash.it/300/300",
id: "b"
},
{
x: 60,
y: 30,
key: "a",
image: "https://unsplash.it/300/300",
id: "c"
},
{
x: 40,
y: 30,
key: "a",
image: "https://unsplash.it/300/300",
id: "d"
},
{
x: 50,
y: 70,
key: "a",
image: "https://unsplash.it/300/300",
id: "e"
},
{
x: 30,
y: 50,
key: "a",
image: "https://unsplash.it/300/300",
id: "f"
},
{
x: 50,
y: 60,
key: "a",
image: "https://unsplash.it/300/300",
id: "g"
}
],
// y label
yLabel: "Y label",
yLabelLength: 50,
// axis ticks
xTicks: 10,
yTicks: 10
}
// component start
var Timeline = {};
/***
*
* Create the svg canvas on which the chart will be rendered
*
***/
Timeline.create = function(elem, props) {
// build the chart foundation
var svg = d3.select(elem).append('svg')
.attr('width', props.width)
.attr('height', props.height);
var g = svg.append('g')
.attr('class', 'point-container')
.attr("transform",
"translate(" + props.marginLeft + "," + props.marginTop + ")");
var g = svg.append('g')
.attr('class', 'line-container')
.attr("transform",
"translate(" + props.marginLeft + "," + props.marginTop + ")");
var xAxis = g.append('g')
.attr("class", "x axis")
.attr("transform", "translate(0," + (props.height - props.marginTop - props.marginBottom) + ")");
var yAxis = g.append('g')
.attr("class", "y axis");
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 1)
.attr("x", 0 - ((props.height - props.yLabelLength)/2) )
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text(props.yLabel);
// add placeholders for the axes
this.update(elem, props);
};
/***
*
* Update the svg scales and lines given new data
*
***/
Timeline.update = function(elem, props) {
var self = this;
var domain = self.getDomain(props);
var scales = self.scales(elem, props, domain);
self.drawPoints(elem, props, scales);
};
/***
*
* Use the range of values in the x,y attributes
* of the incoming data to identify the plot domain
*
***/
Timeline.getDomain = function(props) {
var domain = {};
domain.x = props.xDomain || d3.extent(props.data, function(d) { return d.x; });
domain.y = props.yDomain || d3.extent(props.data, function(d) { return d.y; });
return domain;
};
/***
*
* Compute the chart scales
*
***/
Timeline.scales = function(elem, props, domain) {
if (!domain) {
return null;
}
var width = props.width - props.marginRight - props.marginLeft;
var height = props.height - props.marginTop - props.marginBottom;
var x = d3.scale.linear()
.range([0, width])
.domain(domain.x);
var y = d3.scale.linear()
.range([height, 0])
.domain(domain.y);
return {x: x, y: y};
};
/***
*
* Create the chart axes
*
***/
Timeline.axes = function(props, scales) {
var xAxis = d3.svg.axis()
.scale(scales.x)
.orient("bottom")
.ticks(props.xTicks)
.tickFormat(d3.format("d"));
var yAxis = d3.svg.axis()
.scale(scales.y)
.orient("left")
.ticks(props.yTicks);
return {
xAxis: xAxis,
yAxis: yAxis
}
};
/***
*
* Use the general update pattern to draw the points
*
***/
Timeline.drawPoints = function(elem, props, scales, prevScales, dispatcher) {
var g = d3.select(elem).selectAll('.point-container');
var color = d3.scale.category10();
// add images
var image = g.selectAll('.image')
.data(props.data)
image.enter()
.append("pattern")
.attr("id", function(d) {return d.id})
.attr("class", "svg-image")
.attr("x", "0")
.attr("y", "0")
.attr("height", "70px")
.attr("width", "70px")
.append("image")
.attr("x", "0")
.attr("y", "0")
.attr("height", "70px")
.attr("width", "70px")
.attr("xlink:href", function(d) {return d.image})
var point = g.selectAll('.point')
.data(props.data);
// enter
point.enter()
.append("circle")
.attr("class", "point")
.on('mouseover', function(d) {
d3.select(elem).selectAll(".point").classed("active", false);
d3.select(this).classed("active", true);
if (props.onMouseover) {
props.onMouseover(d)
};
})
.on('mouseout', function(d) {
if (props.onMouseout) {
props.onMouseout(d)
};
})
// enter and update
point.transition()
.duration(1000)
.attr("cx", function(d) {
return scales.x(d.x);
})
.attr("cy", function(d) {
return scales.y(d.y);
})
.attr("r", 30)
.style("stroke", function(d) {
if (props.pointStroke) {
return d.color = props.pointStroke;
} else {
return d.color = color(d.key);
}
})
.style("fill", function(d) {
if (d.image) {
return ("url(#" + d.id + ")");
}
if (props.pointFill) {
return d.color = props.pointFill;
} else {
return d.color = color(d.key);
}
});
// exit
point.exit()
.remove();
// update the axes
var axes = this.axes(props, scales);
d3.select(elem).selectAll('g.x.axis')
.transition()
.duration(1000)
.call(axes.xAxis);
d3.select(elem).selectAll('g.y.axis')
.transition()
.duration(1000)
.call(axes.yAxis);
};
$(document).ready(function() {
Timeline.create(elem, props);
})
I do not know why, but the image should not be duplicated, tripled, etc ... should remove the previous one and load it again but with another rotation data. This is my code:
data.csv
enter image description here
d3.csv("data/data.csv").then(function(data){
//console.log(data);
// Clean data
formattedData = data.map(function(id){
id.rot_1 = +id.rot_1;
id.trans_1 = +id.trans_1;
return id;
});
// First run of the visualization
update(formattedData[0]);})
$("#play-button")
.on("click", function(){
var button = $(this);
if (button.text() == "Play"){
button.text("Pause");
interval = setInterval(step, 1000);
}
else {
button.text("Play");
clearInterval(interval);
}})
function step(){
// At the end of our data, loop back
time = (time < 76) ? time+1 : 0
update(formattedData[time]); }
function update(data) {
// Standard transition time for the visualization
var t = d3.transition()
.duration(1000);
//console.log(d3.selectAll(data));
//console.log(data)
// original
var imgs1 = g.append("image") // en vez de g es svg
.attr("xlink:href", "img/picturetest.png");
// EXIT old elements not present in new data.
imgs1.exit()
.attr("class", "exit")
.selectAll("svg:image")
.remove();
//console.log(data)
// ENTER new elements present in new data.
imgs1.enter()
.append("svg:image") // svg:image
//.attr("xlink:href", "img/picturetest.png")
.attr("class", "enter")
.merge(imgs1)
.transition(t)
.attr("x", 0) // 150
.attr("y", 0) // 80
.attr("width", 200)
.attr("height", 200)
.attr("transform", "rotate("+data.rot_1+") translate("+data.trans_1+")" ); }`
var svg = d3.select("body")
.append("svg")
.style("width", 200)
.style("height", 100)
Related
I'm a newbie both in JSON and js. I'm trying to load json data based on this radarchart. If I add the data just after var data = MY DATA, it works but if I load json file, it doesn't. For loading data, I follow the last suggestion entitled “What does d3.json return?” (v5) of this post--in fact the others didn't worked too--; thus, I have added <script src="https://d3js.org/d3.v5.min.js"></script> in addition to <script src="https://d3js.org/d3.v4.min.js"></script> which I used for other barplots and piecharts in the same webpage.
It works
<head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
<script src="../other/radarChart.js" charset="utf-8"></script>
</head>
<body>
<!-- a few barplots and piecharts -->
<!-- afterwards the following radarChart -->
<table>
<tr>
<td style="radarChart">
<div class="radarChart" style="display: inline-flex;"></div>
<script>
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var marginRadar = { top: 150, right: 80, bottom: 50, left: 80 },
widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
heightRadar = Math.min(width, window.innerHeight - marginRadar.top - marginRadar.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var data = [
{
"name": "Clan A",
"axes": [
{"axis": "cognition", "value": 3.12},
{"axis": "communication","value": 9.38},
{"axis": "competition","value": 18.75},
{"axis": "consumption","value": 6.25},
{"axis": "contact","value": 21.88},
{"axis": "emotion","value": 6.25},
{"axis": "motion","value": 18.75},
{"axis": "perception","value": 3.12},
{"axis": "possession","value": 3.12},
{"axis": "social","value": 3.12}
]
},
{"name": "clan B",
"axes": [
{"axis": "cognition","value": 3.12},
{"axis": "communication","value": 0.00},
{"axis": "competition","value": 0.00},
{"axis": "consumption","value": 0.00},
{"axis": "contact","value": 9.38},
{"axis": "emotion","value": 0.00},
{"axis": "motion","value": 0.00},
{"axis": "perception","value": 0.00},
{"axis": "possession","value": 0.00},
{"axis": "social","value": 0.00}
]
}
];
var radarChartOptions = {
w: 290,
h: 350,
margin: marginRadar,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: { title: 'Legend', translateX: 150, translateY: 100 },
unit: '%'
};
// Draw the chart, get a reference the created svg element :
let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
</script>
</td>
</tr>
</table>
</body>
It doesn't work: loading json file [note: everything is the same before and after the following suggestion, except that I have added <script src="https://d3js.org/d3.v5.min.js"></script> between <td> and <div class="radarChart"..> so that there is no conflict with barplots and piecharts which need v4 instead of v5]:
<table>
<tr>
<td style="radarChart">
<!-- add v5 here -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<div class="radarChart" style="display: inline-flex;"></div>
<!-- [same data ] -->
[...]
var data = d3.json("../other/radarChart-data.json");
console.log(data)
<!-- [same data after] -->
[...]
Thus my question is: how to load JSON file for this radarchart? I prefer to load a file since "value: XX" are not fixed, but changed according to new values adding in related xml files.
In advance, thanks so much for your kind help.
The solution suggested here works:
https://www.quora.com/How-can-I-load-data-from-a-JSON-file-into-a-variable-in-JavaScript-without-using-Ajax?fbclid=IwAR1ilYFGKBaNsUupYOwIBSVMHBDP24o7j87WP5GzZSrlwIsyLK1riU5JCRQ
It bypasses the constraints of Ajax in a beautifully simple way!
move the declaration of your var data into a separate file
load this file as a javascript
use the variable as you need to!
Important: Both imports of d3 edit the global scope, so one will override the other. I recommend to just choose v4 for the radar chart.
My answer is very long, because I had to include the RadarChart code to make it work. Just scroll down to the very end to see what I did. d3.json needs a function as a second argument, and will call that function with the result:
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/// mthh - 2017 /////////////////////////////////////////
// Inspired by the code of alangrafu and Nadieh Bremer //
// (VisualCinnamon.com) and modified for d3 v4 //////////
/////////////////////////////////////////////////////////
const max = Math.max;
const sin = Math.sin;
const cos = Math.cos;
const HALF_PI = Math.PI / 2;
const RadarChart = function RadarChart(parent_selector, data, options) {
//Wraps SVG text - Taken from http://bl.ocks.org/mbostock/7555321
const wrap = (text, width) => {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
} //wrap
const cfg = {
w: 600, //Width of the circle
h: 600, //Height of the circle
margin: {
top: 20,
right: 20,
bottom: 20,
left: 20
}, //The margins of the SVG
levels: 3, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.1, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scaleOrdinal(d3.schemeCategory10), //Color function,
format: '.2%',
unit: '',
legend: false
};
//Put all of the options into a variable called cfg
if ('undefined' !== typeof options) {
for (var i in options) {
if ('undefined' !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
// var maxValue = max(cfg.maxValue, d3.max(data, function(i){return d3.max(i.map(function(o){return o.value;}))}));
let maxValue = 0;
for (let j = 0; j < data.length; j++) {
for (let i = 0; i < data[j].axes.length; i++) {
data[j].axes[i]['id'] = data[j].name;
if (data[j].axes[i]['value'] > maxValue) {
maxValue = data[j].axes[i]['value'];
}
}
}
maxValue = max(cfg.maxValue, maxValue);
const allAxis = data[0].axes.map((i, j) => i.axis), //Names of each axis
total = allAxis.length, //The number of different axes
radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
Format = d3.format(cfg.format), //Formatting
angleSlice = Math.PI * 2 / total; //The width in radians of each "slice"
//Scale for the radius
const rScale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue]);
/////////////////////////////////////////////////////////
//////////// Create the container SVG and g /////////////
/////////////////////////////////////////////////////////
const parent = d3.select(parent_selector);
//Remove whatever chart with the same id/class was present before
parent.select("svg").remove();
//Initiate the radar chart SVG
let svg = parent.append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar");
//Append a g element
let g = svg.append("g")
.attr("transform", "translate(" + (cfg.w / 2 + cfg.margin.left) + "," + (cfg.h / 2 + cfg.margin.top) + ")");
/////////////////////////////////////////////////////////
////////// Glow filter for some extra pizzazz ///////////
/////////////////////////////////////////////////////////
//Filter for the outside glow
let filter = g.append('defs').append('filter').attr('id', 'glow'),
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation', '2.5').attr('result', 'coloredBlur'),
feMerge = filter.append('feMerge'),
feMergeNode_1 = feMerge.append('feMergeNode').attr('in', 'coloredBlur'),
feMergeNode_2 = feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
/////////////////////////////////////////////////////////
/////////////// Draw the Circular grid //////////////////
/////////////////////////////////////////////////////////
//Wrapper for the grid & axes
let axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid.selectAll(".levels")
.data(d3.range(1, (cfg.levels + 1)).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", d => radius / cfg.levels * d)
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter", "url(#glow)");
//Text indicating at what % each level is
axisGrid.selectAll(".axisLabel")
.data(d3.range(1, (cfg.levels + 1)).reverse())
.enter().append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", d => -d * radius / cfg.levels)
.attr("dy", "0.4em")
.style("font-size", "10px")
.attr("fill", "#737373")
.text(d => Format(maxValue * d / cfg.levels) + cfg.unit);
/////////////////////////////////////////////////////////
//////////////////// Draw the axes //////////////////////
/////////////////////////////////////////////////////////
//Create the straight lines radiating outward from the center
var axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
//Append the lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", (d, i) => rScale(maxValue * 1.1) * cos(angleSlice * i - HALF_PI))
.attr("y2", (d, i) => rScale(maxValue * 1.1) * sin(angleSlice * i - HALF_PI))
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
//Append the labels at each axis
axis.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", (d, i) => rScale(maxValue * cfg.labelFactor) * cos(angleSlice * i - HALF_PI))
.attr("y", (d, i) => rScale(maxValue * cfg.labelFactor) * sin(angleSlice * i - HALF_PI))
.text(d => d)
.call(wrap, cfg.wrapWidth);
/////////////////////////////////////////////////////////
///////////// Draw the radar chart blobs ////////////////
/////////////////////////////////////////////////////////
//The radial line function
const radarLine = d3.radialLine()
.curve(d3.curveLinearClosed)
.radius(d => rScale(d.value))
.angle((d, i) => i * angleSlice);
if (cfg.roundStrokes) {
radarLine.curve(d3.curveCardinalClosed)
}
//Create a wrapper for the blobs
const blobWrapper = g.selectAll(".radarWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", d => radarLine(d.axes))
.style("fill", (d, i) => cfg.color(i))
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function(d, i) {
//Dim all blobs
parent.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", 0.1);
//Bring back the hovered over blob
d3.select(this)
.transition().duration(200)
.style("fill-opacity", 0.7);
})
.on('mouseout', () => {
//Bring back all blobs
parent.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", cfg.opacityArea);
});
//Create the outlines
blobWrapper.append("path")
.attr("class", "radarStroke")
.attr("d", function(d, i) {
return radarLine(d.axes);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", (d, i) => cfg.color(i))
.style("fill", "none")
.style("filter", "url(#glow)");
//Append the circles
blobWrapper.selectAll(".radarCircle")
.data(d => d.axes)
.enter()
.append("circle")
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
.attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
.style("fill", (d) => cfg.color(d.id))
.style("fill-opacity", 0.8);
/////////////////////////////////////////////////////////
//////// Append invisible circles for tooltip ///////////
/////////////////////////////////////////////////////////
//Wrapper for the invisible circles on top
const blobCircleWrapper = g.selectAll(".radarCircleWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarCircleWrapper");
//Append a set of invisible circles on top for the mouseover pop-up
blobCircleWrapper.selectAll(".radarInvisibleCircle")
.data(d => d.axes)
.enter().append("circle")
.attr("class", "radarInvisibleCircle")
.attr("r", cfg.dotRadius * 1.5)
.attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
.attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function(d, i) {
tooltip
.attr('x', this.cx.baseVal.value - 10)
.attr('y', this.cy.baseVal.value - 10)
.transition()
.style('display', 'block')
.text(Format(d.value) + cfg.unit);
})
.on("mouseout", function() {
tooltip.transition()
.style('display', 'none').text('');
});
const tooltip = g.append("text")
.attr("class", "tooltip")
.attr('x', 0)
.attr('y', 0)
.style("font-size", "12px")
.style('display', 'none')
.attr("text-anchor", "middle")
.attr("dy", "0.35em");
if (cfg.legend !== false && typeof cfg.legend === "object") {
let legendZone = svg.append('g');
let names = data.map(el => el.name);
if (cfg.legend.title) {
let title = legendZone.append("text")
.attr("class", "title")
.attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY})`)
.attr("x", cfg.w - 70)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
.text(cfg.legend.title);
}
let legend = legendZone.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY + 20})`);
// Create rectangles markers
legend.selectAll('rect')
.data(names)
.enter()
.append("rect")
.attr("x", cfg.w - 65)
.attr("y", (d, i) => i * 20)
.attr("width", 10)
.attr("height", 10)
.style("fill", (d, i) => cfg.color(i));
// Create labels
legend.selectAll('text')
.data(names)
.enter()
.append("text")
.attr("x", cfg.w - 52)
.attr("y", (d, i) => i * 20 + 9)
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(d => d);
}
return svg;
}
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var marginRadar = {
top: 150,
right: 80,
bottom: 50,
left: 80
},
widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
heightRadar = Math.min(widthRadar, window.innerHeight - marginRadar.top - marginRadar.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var radarChartOptions = {
w: 290,
h: 350,
margin: marginRadar,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: {
title: 'Legend',
translateX: 150,
translateY: 100
},
unit: '%'
};
// Just some example JSON data I found at https://support.oneskyapp.com/hc/en-us/articles/208047697-JSON-sample-files
d3.json('https://support.oneskyapp.com/hc/en-us/article_attachments/202761727/example_2.json', function(fakeData) {
// Which I then map into your format - you don't need to do this
const data = Object.entries(fakeData['quiz']).map(([topic, questionObject]) => ({
name: topic,
axes: [
{ axis: 'difficulty', value: Object.keys(questionObject).length },
{ axis: 'depth', value: Object.keys(questionObject).length },
{ axis: 'duration', value: Object.keys(questionObject).length },
{ axis: 'delicacy', value: Object.keys(questionObject).length }
]
}));
// Draw the chart, get a reference the created svg element :
let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
});
<head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
</head>
<body>
<table>
<tr>
<td style="radarChart">
<div class="radarChart" style="display: inline-flex;"></div>
</td>
</tr>
</table>
This question is actually a continuation of the question from here. I have made some changes to my fiddle and code since then but I am still facing the same problem. Link to my fiddle and code can be found here.
I am using a for loop to plot the lines as I want the chart to be dynamic which means the number of lines is drawn according to the number of arrays in the data array. In this case, there are 2 arrays in my data array as shown below.
var data = [[{x: 0, y: 0}, {x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 40, y: 40}],
[{x: 0, y: 0}, {x: 10, y: 200}, {x: 20, y: 300}, {x: 30, y: 400}, {x: 40, y: 500}]];
From my fiddle, the blue line will be toggled on and off when I click on both 'Y-Axis 1' and 'Y-Axis 2'. However, I want the red line to be toggled on and off when I click on Y-Axis 2. This is happening because I am assigning the same id to both lines in this piece of code.
//************* Plotting of graph ***************
var colors = ["blue", "red"];
//plot of chart
for (var i = 0; i < 2; i++){
var lineFunction = d3.line()
.x(function(d) {return x(d.x); })
.y(function(d) {return yScale[i](d.y); })
.curve(d3.curveLinear);
//plot lines
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine")
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", "blackDot")
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover )
.on("mouseleave", mouseleave )
}
Is there a better way to plot the lines so that I can assign a specific id to each line being plotted and toggle the lines according to the legend? I have tried using forEach() but can't seem to get it to work. Any help is greatly appreciated!
First of all: you should not use a loop (for, while, forEach etc...) to append elements in a D3 code. That's not idiomatic, and you'll end up bending over backwards to fix things, like this very question will demonstrate.
The simplest fix without refactoring the code for a more idiomatic one, which will take a lot of work, is using the indices for setting the lines' IDs...
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine" + i)
... and then, in the click listener, using this cumbersome and awkward window property, which is the elements' IDs:
.on("click", function(d, i) {
var active = window["blueLine" + i].active ? false : true,
newOpacity = active ? 0 : 1;
d3.select("#blueLine" + i).style("opacity", newOpacity);
window["blueLine" + i].active = active;
});
Here is your code with those changes:
var xValueArray = [0, 10, 20, 30, 40];
var arr = [
[0, 10, 20, 30, 40],
[0, 200, 300, 400, 500]
];
//data array is obtained after structuring arr array
var data = [
[{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
[{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}]
];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
//const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'primaryYLabel')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(80,20)")
.call(yAxis);
//************* Mouseover ***************
var tooltip = d3.select("body")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px")
.style("position", "absolute")
// A function that change this tooltip when the user hover a point.
// Its opacity is set to 1: we can now see it. Plus it set the text and position of tooltip depending on the datapoint (d)
var mouseover = function(d) {
tooltip
.html("x: " + d.x + "<br/>" + "y: " + d.y)
.style("opacity", 1)
.style("left", (d3.mouse(this)[0] + 90) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
// A function that change this tooltip when the leaves a point: just need to set opacity to 0 again
var mouseleave = function(d) {
tooltip
.transition()
.duration(200)
.style("opacity", 0)
}
//************* Plotting of graph ***************
var colors = ["blue", "red"];
//plot of chart
for (var i = 0; i < 2; i++) {
var lineFunction = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[i](d.y);
})
.curve(d3.curveLinear);
//plot lines
var paths = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine" + i)
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
.attr("class", "blackDot")
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
}
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 20;
textTranslation += 20;
}
//************* Legend ***************
var legend = svg.selectAll('.legend')
.data(data)
.enter()
.append('g')
.attr('class', 'legend');
legend.append('rect')
.attr('x', width - 5)
.attr('y', function(d, i) {
return (i * 20) + 120;
})
.attr('width', 18)
.attr('height', 4)
.attr("fill", function(d, i) {
return colors[i]
});
legend.append('text')
.attr('x', width - 10)
.attr('y', function(d, i) {
return (i * 20) + 120;
})
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
return yLabelArray[i]
})
.on("click", function(d, i) {
//Determine if current line is visible
var active = window["blueLine" + i].active ? false : true,
newOpacity = active ? 0 : 1;
//Hide or show the elements
d3.select("#blueLine" + i).style("opacity", newOpacity);
//Update whether or not the elements are active
window["blueLine" + i].active = active;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>
I am working on a d3 applicaton - with a pie chart -- I would like to get animation onload and on a call to action. Like when the chart becomes visible during a scroll.
Where the pie segments grow around the central pivot. So tween or snap to the other segment like a relay race
http://jsfiddle.net/pg886/192/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<div class="piechart" data-role="piechart" data-width=400 data-height=400 data-radius=30 data-innerradius=20
data-data=x>
</div>
<style>
.piechart{
/*border: 1px solid black;*/
/*text-align: center;
font-size: 12px;*/
}
</style>
<script>
$( document ).ready(function() {
console.log("test")
var $this = $('.piechart');
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}];
var w = $this.data("width");
var h = $this.data("height");
var ir = $this.data("innerradius");
var r = $this.data("radius");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
//var colores_g = ["#47abd5", "#005a70", "#f5a0a3", "#ff7276", "#a9a19c", "#d0743c", "#ff8c00"];
return colores_g[n % colores_g.length];
}
var radius = Math.min(w, h) / 4;
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var labelArc = d3.svg.arc()
.outerRadius(radius - r)
.innerRadius(radius - ir);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var chart = d3.select('.piechart').append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h)
.attr("transform", "translate(0,0)");
var piechart = chart
.append("g")
.attr("class", "piechart")
.attr("width", (radius*2))
.attr("transform", "translate(0,"+h/4+")");
var path_group = piechart.append("g")
.attr("class", "path_group")
.attr("transform", "translate(90," + ((h / 4) - 20) + ")");
var padding = 45;
var legendPaddingTop = 30;
var legend = chart.append("g")
.attr("class", "legend")
.attr("width", w/2)
.attr("height", h)
.attr("transform", "translate(" + (w - 50) + "," + (h / 4) + ")");
var label_group = legend.append("svg:g")
.attr("class", "label_group")
.attr("transform", "translate(" + (-(w / 3) + 20) + "," + 0 + ")");
var legend_group = legend.append("svg:g")
.attr("class", "legend_group")
.attr("transform", "translate(" + (-(w / 3) - 100) + "," + 0 + ")");
var g = path_group.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return colores_google(i);
});
var legendHeight = legendPaddingTop;
var ySpace = 18;
//draw labels
var labels = label_group.selectAll("text.labels")
.data(data);
labels.enter().append("svg:text")
.attr("class", "labels")
.attr("dy", function(d, i) {
legendHeight+=ySpace;
return (ySpace * i) + 4;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
//draw labels
//draw legend
var legend = legend_group.selectAll("circle").data(data);
legend.enter().append("svg:circle")
.attr("cx", 100)
.attr("cy", function(d, i) {
return ySpace * i;
})
.attr("r", 7)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {
return colores_google(i);
});
legend.exit().remove();
//draw legend
//reset legend height
//console.log("optimum height for legend", legendHeight);
$this.find('.legend').attr("height", legendHeight);
function type(d) {
d.value = +d.value;
return d;
}
});
</script>
So you can achieve this pretty easily, and there are a couple of blocks that will help you.
Arc Tween Firstly this block gives you an example of how to tween an arc. Basically you can't get that automatically so you have to write your own attrTween function. Fortunately this is pretty simple and Mike Bostock gives a really good example in there.
Here's a code sample - but the link gives a really good verbose description of what's going on.
.attrTween("d", function(d) {
var interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
}
Next you want something like this Donut with transitions. This is actually the end-result for where you're trying to get to. This effect is really easy to achieve, all you need to do is set your angles correctly and the timings.
Angles: So here you want both the endAngle and the startAngle to be the same at the start (which should be the endAngle value of the previous segment or 0 for the first segment).
Timing: You want to allow 1 animation to complete before you start the next, simply by delaying them. You can see how that's done with this snippet:
.transition()
.delay(function(d,i) { return i * 500; })
.duration(500)
.attrTween(...)
const dataset = [
{ age: "<5", population: 2704659 },
{ age: "5-13", population: 4499890 },
{ age: "14-17", population: 2159981 },
{ age: "18-24", population: 3853788 },
{ age: "25-44", population: 14106543 },
{ age: "45-64", population: 8819342 },
{ age: "≥65", population: 612463 },
];
const TIME = 2000 / dataset.length;
const color = d3.scaleOrdinal(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
const pie = d3.pie()
.sort(null)
.value(function(d) { return d.population; });
const path = d3.arc()
.innerRadius(0)
.outerRadius(350);
d3.select("#container")
.selectAll(".arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.append("path")
.attr("fill", function(d) { return color(d.data.age); })
.transition()
.duration(TIME)
.ease(d3.easeLinear)
.delay(function(d, i) { return i * TIME; })
.attrTween("d", function(d) {
// Note the 0.1 to prevent errors generating the path
const angleInterpolation = d3.interpolate(d.startAngle + 0.1, d.endAngle);
return function(t) {
d.endAngle = angleInterpolation(t);
return path(d);
}
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="800" height="800">
<g id="container" transform="translate(400, 400)"></g>
</svg>
I am trying to follow this example here for a D3 stacked chart. I've tested it locally and it works fine.
I have adapted the code to match my csv dataset, but unfortunately I get issues with the calculation of y and height attributes:
Error: attribute y: Expected length, "NaN".
Error: attribute height: Expected length, "NaN".
Here is my adapted source code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Enterprise Elements Analysis - In/Out of Scope</title>
<script src="http://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<style type="text/css">
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
</style>
</head>
<body>
<script type="text/javascript">
// Our D3 code will go here
var ratData = [];
d3.csv("./etcounts.csv", function(d) {
return {
type: d.type,
in_scope: +d.in_scope,
out_scope: +d.out_scope
};
}, function(error, rows) {
data = rows;
console.log(data);
createVisualization();
});
function createVisualization() {
// Setup svg using with margins
var margin = {bottom: 75, left: 15, right: 85};
var w = 200 - margin.left - margin.right;
var h = 175 - margin.bottom;
// get length of Array
var arrayLength = data.length; // length of dataset
var x_axisLength = 100; // length of x-axis in our layout
var y_axisLength = 100; // length of y-axis in our layout
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ",10)");
// set up the properties for stack
var stack = d3.stack()
.keys(["In Scope", "Out Scope"])
.order(d3.stackOrderDescending)
.offset(d3.stackOffsetNone);
// transpose your data using stack
var series = stack(data);
// view the stack
console.log(series);
// setup the Y scale
var yScale = d3.scaleLinear()
.domain([0, d3.max(series, function(d) {
return d3.max(d, function(d) {
return d[1];
});
})])
.range([h, 0]);
// Set some colors into an array
var colors = ["#dfd6d6", "#d85f41"]; // choose colors
// Create groups for each series, rect elements for each segment
var groups = svg.selectAll("g.type")
.data(series)
.enter().append("g")
.attr("class", "type")
.style("fill", function(d, i) {
return colors[i]; // color the rectangles
});
// Create the rectangles
var rect = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d,i) {
return i * (x_axisLength/arrayLength) + 30; // Set x coordinate of rectangle to index of data value (i) *25
})
.attr("y", function(d) {
return yScale(d[1]); // set base of rectangle
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]); // set height of rectangle
})
.attr("width", (x_axisLength/arrayLength) - 1) // set width of rectangle
.on("mouseover", function() {
tooltip.style("display", null); // hide tooltip
})
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.data.city + ": " + (d[1] - d[0])); // populate tooltip
})
.on("mouseout", function() {
tooltip.style("display", "none");
});
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(" + i * 50 + ", 110)"; });
legend.append("rect")
.attr("x", w - 70)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {return colors.slice().reverse()[i];});
legend.append("text")
.attr("x", w - 49)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
switch (i) {
case 0: return "In";
case 1: return "Out";
}
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px");
// Create y-axis
svg.append("line")
.attr("x1", 30)
.attr("y1", 0)
.attr("x2", 30)
.attr("y2", 100)
.attr("stroke-width", 2)
.attr("stroke", "black");
// y-axis label
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "middle")
.text("Elements")
.attr("transform", "translate(20, 50) rotate(-90)")
.attr("font-size", "14px")
.attr("font-family", "'Open Sans', sans-serif");
// Create x-axis
svg.append("line")
.attr("x1", 30)
.attr("y1", 100)
.attr("x2", 130)
.attr("y2", 100)
.attr("stroke-width", 2)
.attr("stroke", "black");
}
</script>
</body>
</html>
My Dataset (etcounts.csv) is here:
type,in_scope,out_scope
ERKRS,1,1
KKBER,6,5
KOKRS,1,31
BUKRS,78,143
VKORG,23,13
BWKEY,51,6
EKORG,5,6
WERKS,51,65
LGORT,9,180
SPART,9,3
VTWEG,2,0
PERSA,47,73
Unfortunately my D3/JS skills are not quite up to par, but I would appreciate any help. Thanks - John
Instead of
var stack = d3.stack()
.keys(["In Scope", "Out Scope"]) <-- there is no key as such
.order(d3.stackOrderDescending)
.offset(d3.stackOffsetNone);
it should have been:
var stack = d3.stack()
.keys(["in_scope", "out_scope"])
.order(d3.stackOrderDescending)
.offset(d3.stackOffsetNone);
Reason: there is no keys in your CSV "In Scope", "Out Scope"
It should have been "in_scope", "out_scope"
EDIT
For tool tip :
tooltip.select("text").text(d.data.city + ": " + (d[1] - d[0]));
should have been
tooltip.select("text").text(d.data.type + ": " + (d[1] - d[0]));
Reason: There is no data.city in your CSV.
working code here
I'm trying to make a pie chart that is updated when I press the button "2016" but instead of updating I create a new pie chart, how can I change the values of my pie chart? Thanks in advance. I tried to search a question but all of them are so specific.
var dataset = [{
key: "Alumnos",
value: 15
}, {
key: "AlumnosFCT",
value: 12
}];
var w = 300;
var h = 300;
var outerRadius = w / 2;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var color = d3.scale.ordinal()
.domain([15, 12])
.range(["#FF4081", "#3F51B5"]);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " + outerRadius + ")");
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.text(function(d) {
return d.value;
});
d3.selectAll("button").on("click", function() {
var paragraphID = d3.select(this).attr("id");
if (paragraphID == "2016") {
dataset.push({
key: "Alumnos",
value: 20
}, {
key: "AlumnosFCT",
value: 18
});
dataset.shift();
dataset.shift();
}
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var color = d3.scale.ordinal()
.domain([15, 12])
.range(["#FF4081", "#3F51B5"]);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " + outerRadius + ")");
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.text(function(d) {
return d.value;
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="2016">2016</button>
In general, d3 is pretty good about managing DOM elements as long as you work within their API. In that way you can write a function that can create new elements for new data, or update existing elements with new data pertaining to those elements.
See the following updated version of your code snippet, specifically pulling out the data dependent DOM manipulations into a function called update:
/***
* Original Code
***/
var dataset = [{
key: "Alumnos",
value: 15
}, {
key: "AlumnosFCT",
value: 12
}];
var w = 300;
var h = 300;
var outerRadius = w / 2;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var color = d3.scale.ordinal()
.domain([15, 12])
.range(["#FF4081", "#3F51B5"]);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
/***
* update function for data dependent manipulations
***/
function update(data) {
var arcs = svg.selectAll("g.arc").data(pie(data));
arcs.exit().remove();
arcs.enter().append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " + outerRadius + ")");
var paths = arcs.selectAll('path').data(function (d, i) {
d.idx = i;
return [d];
})
paths.enter().append('path');
paths
.attr("fill", function(d) {
return color(d.idx);
})
.attr("d", arc);
var texts = arcs.selectAll('text').data(function (d) {
return [d];
})
texts.enter().append('text');
texts.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.text(function(d) {
return d.value;
});
}
update(dataset);
/***
* Handler to set new data based on button clicked
***/
d3.select('button').on('click', function() {
var newData;
if (this.id === '2016') {
newData = [{
key: "Alumnos",
value: 20
}, {
key: "AlumnosFCT",
value: 18
}];
update(newData);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="2016">2016</button>
(depending on your browser, you might need to scroll the view to see the "2016" button)
Note the following advantages:
only elements whose data need to change are updated when update is called.
if you add a new data point when updating, a new element will be added without touching elements that should remain unchanged (via enter)
if you remove a data point when updating, that element will be removed (via exit)
d3 version: 3.4.11