I'm looking for a spider chart/radar chart for HTML/javascript that is also interactive. I would like the user to move all the endpoints and store the end result.
I have been searching for a while and although I have found some nice chart components all of them where static and could only updated using code.
Take a look at this.
This is what can be achieved with this alangrafu's code:
The example is really not interactive in the sense that you described, but it is interactive in other ways, and nothing stops you to implement the interactivity you desire, having the code from example as a good starting point.
An Interactive D3 Radar chart example:
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Radar chart</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="RadarChart.js"></script>
<style>
body {
overflow: hidden;
margin: 0;
font-size: 14px;
font-family: "Helvetica Neue", Helvetica;
}
#chart {
position: absolute;
top: 50px;
left: 100px;
}
</style>
</head>
<body>
<div id="body">
<div id="chart"></div>
</div>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
RadarChart.js
var RadarChart = {
draw: function(id, d, options){
var cfg = {
radius: 5,
w: 600,
h: 600,
factor: 1,
factorLegend: .85,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
opacityArea: 0.5,
ToRight: 5,
TranslateX: 80,
TranslateY: 30,
ExtraWidthX: 100,
ExtraWidthY: 100,
color: d3.scale.category10()
};
if('undefined' !== typeof options){
for(var i in options){
if('undefined' !== typeof options[i]){
cfg[i] = options[i];
}
}
}
cfg.maxValue = Math.max(cfg.maxValue, d3.max(d, function(i){return d3.max(i.map(function(o){return o.value;}))}));
var allAxis = (d[0].map(function(i, j){return i.axis}));
var total = allAxis.length;
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
var Format = d3.format('%');
d3.select(id).select("svg").remove();
var g = d3.select(id)
.append("svg")
.attr("width", cfg.w+cfg.ExtraWidthX)
.attr("height", cfg.h+cfg.ExtraWidthY)
.append("g")
.attr("transform", "translate(" + cfg.TranslateX + "," + cfg.TranslateY + ")");
;
var tooltip;
//Circular segments
for(var j=0; j<cfg.levels-1; j++){
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
g.selectAll(".levels")
.data(allAxis)
.enter()
.append("svg:line")
.attr("x1", function(d, i){return levelFactor*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
.attr("y1", function(d, i){return levelFactor*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
.attr("x2", function(d, i){return levelFactor*(1-cfg.factor*Math.sin((i+1)*cfg.radians/total));})
.attr("y2", function(d, i){return levelFactor*(1-cfg.factor*Math.cos((i+1)*cfg.radians/total));})
.attr("class", "line")
.style("stroke", "grey")
.style("stroke-opacity", "0.75")
.style("stroke-width", "0.3px")
.attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")");
}
//Text indicating at what % each level is
for(var j=0; j<cfg.levels; j++){
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels);
g.selectAll(".levels")
.data([1]) //dummy data
.enter()
.append("svg:text")
.attr("x", function(d){return levelFactor*(1-cfg.factor*Math.sin(0));})
.attr("y", function(d){return levelFactor*(1-cfg.factor*Math.cos(0));})
.attr("class", "legend")
.style("font-family", "sans-serif")
.style("font-size", "10px")
.attr("transform", "translate(" + (cfg.w/2-levelFactor + cfg.ToRight) + ", " + (cfg.h/2-levelFactor) + ")")
.attr("fill", "#737373")
.text(Format((j+1)*cfg.maxValue/cfg.levels));
}
series = 0;
var axis = g.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
axis.append("line")
.attr("x1", cfg.w/2)
.attr("y1", cfg.h/2)
.attr("x2", function(d, i){return cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total));})
.attr("y2", function(d, i){return cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));})
.attr("class", "line")
.style("stroke", "grey")
.style("stroke-width", "1px");
axis.append("text")
.attr("class", "legend")
.text(function(d){return d})
.style("font-family", "sans-serif")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "1.5em")
.attr("transform", function(d, i){return "translate(0, -10)"})
.attr("x", function(d, i){return cfg.w/2*(1-cfg.factorLegend*Math.sin(i*cfg.radians/total))-60*Math.sin(i*cfg.radians/total);})
.attr("y", function(d, i){return cfg.h/2*(1-Math.cos(i*cfg.radians/total))-20*Math.cos(i*cfg.radians/total);});
d.forEach(function(y, x){
dataValues = [];
g.selectAll(".nodes")
.data(y, function(j, i){
dataValues.push([
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)),
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
]);
});
dataValues.push(dataValues[0]);
g.selectAll(".area")
.data([dataValues])
.enter()
.append("polygon")
.attr("class", "radar-chart-serie"+series)
.style("stroke-width", "2px")
.style("stroke", cfg.color(series))
.attr("points",function(d) {
var str="";
for(var pti=0;pti<d.length;pti++){
str=str+d[pti][0]+","+d[pti][2]+" ";
}
return str;
})
.style("fill", function(j, i){return cfg.color(series)})
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function (d){
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", 0.1);
g.selectAll(z)
.transition(200)
.style("fill-opacity", .7);
})
.on('mouseout', function(){
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", cfg.opacityArea);
});
series++;
});
series=0;
d.forEach(function(y, x){
g.selectAll(".nodes")
.data(y).enter()
.append("svg:circle")
.attr("class", "radar-chart-serie"+series)
.attr('r', cfg.radius)
.attr("alt", function(j){return Math.max(j.value, 0)})
.attr("cx", function(j, i){
dataValues.push([
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)),
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))
]);
return cfg.w/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total));
})
.attr("cy", function(j, i){
return cfg.h/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total));
})
.attr("data-id", function(j){return j.axis})
.style("fill", cfg.color(series)).style("fill-opacity", .9)
.on('mouseover', function (d){
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 5;
tooltip
.attr('x', newX)
.attr('y', newY)
.text(Format(d.value))
.transition(200)
.style('opacity', 1);
z = "polygon."+d3.select(this).attr("class");
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", 0.1);
g.selectAll(z)
.transition(200)
.style("fill-opacity", .7);
})
.on('mouseout', function(){
tooltip
.transition(200)
.style('opacity', 0);
g.selectAll("polygon")
.transition(200)
.style("fill-opacity", cfg.opacityArea);
})
.append("svg:title")
.text(function(j){return Math.max(j.value, 0)});
series++;
});
//Tooltip
tooltip = g.append('text')
.style('opacity', 0)
.style('font-family', 'sans-serif')
.style('font-size', '13px');
}
};
Script.js
var w = 500,
h = 500;
var colorscale = d3.scale.category10();
//Legend titles
var LegendOptions = ['Smartphone','Tablet'];
//Data
var d = [
[
{axis:"Email",value:0.59},
{axis:"Social Networks",value:0.56},
{axis:"Internet Banking",value:0.42},
{axis:"News Sportsites",value:0.34},
{axis:"Search Engine",value:0.48},
{axis:"View Shopping sites",value:0.14},
{axis:"Paying Online",value:0.11},
{axis:"Buy Online",value:0.05},
{axis:"Stream Music",value:0.07},
{axis:"Online Gaming",value:0.12},
{axis:"Navigation",value:0.27},
{axis:"App connected to TV program",value:0.03},
{axis:"Offline Gaming",value:0.12},
{axis:"Photo Video",value:0.4},
{axis:"Reading",value:0.03},
{axis:"Listen Music",value:0.22},
{axis:"Watch TV",value:0.03},
{axis:"TV Movies Streaming",value:0.03},
{axis:"Listen Radio",value:0.07},
{axis:"Sending Money",value:0.18},
{axis:"Other",value:0.07},
{axis:"Use less Once week",value:0.08}
],[
{axis:"Email",value:0.48},
{axis:"Social Networks",value:0.41},
{axis:"Internet Banking",value:0.27},
{axis:"News Sportsites",value:0.28},
{axis:"Search Engine",value:0.46},
{axis:"View Shopping sites",value:0.29},
{axis:"Paying Online",value:0.11},
{axis:"Buy Online",value:0.14},
{axis:"Stream Music",value:0.05},
{axis:"Online Gaming",value:0.19},
{axis:"Navigation",value:0.14},
{axis:"App connected to TV program",value:0.06},
{axis:"Offline Gaming",value:0.24},
{axis:"Photo Video",value:0.17},
{axis:"Reading",value:0.15},
{axis:"Listen Music",value:0.12},
{axis:"Watch TV",value:0.1},
{axis:"TV Movies Streaming",value:0.14},
{axis:"Listen Radio",value:0.06},
{axis:"Sending Money",value:0.16},
{axis:"Other",value:0.07},
{axis:"Use less Once week",value:0.17}
]
];
//Options for the Radar chart, other than default
var mycfg = {
w: w,
h: h,
maxValue: 0.6,
levels: 6,
ExtraWidthX: 300
}
//Call function to draw the Radar chart
//Will expect that data is in %'s
RadarChart.draw("#chart", d, mycfg);
////////////////////////////////////////////
/////////// Initiate legend ////////////////
////////////////////////////////////////////
var svg = d3.select('#body')
.selectAll('svg')
.append('svg')
.attr("width", w+300)
.attr("height", h)
//Create the title for the legend
var text = svg.append("text")
.attr("class", "title")
.attr('transform', 'translate(90,0)')
.attr("x", w - 70)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
.text("What % of owners use a specific service in a week");
//Initiate Legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', 'translate(90,20)')
;
//Create colour squares
legend.selectAll('rect')
.data(LegendOptions)
.enter()
.append("rect")
.attr("x", w - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i){ return colorscale(i);})
;
//Create text next to squares
legend.selectAll('text')
.data(LegendOptions)
.enter()
.append("text")
.attr("x", w - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(function(d) { return d; })
;
Live Example: http://bl.ocks.org/nbremer/6506614
Still looking? Check out this project on github, I think that is exactly what you are looking for:
https://github.com/jmstriegel/jquery.spidergraph
Demo: http://www.jqueryrain.com/?jhRGLHlE
I have been looking for such a library on my own for a long time and came across your post. Did you find another solution as well?
Related
I am trying to add a regression line to a scatter plot. I am using the below code as an example for scatter plot.
http://bl.ocks.org/majetisiri/57da501b3182bd08d17402261c7187f7
I am appending the path to svg as explained here:
Plot regression line on a scatter plot from regression coefficients
But the regression line is not visible.
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<!-- Example based on http://bl.ocks.org/mbostock/3887118 -->
<!-- Tooltip example from http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html -->
<style>
body {
font: 11px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
.tooltip {
position: absolute;
width: 200px;
height: 28px;
pointer-events: none;
}
h1 {
text-align: center;
}
h2 {
text-align: left;
}
</style>
<body>
<p><span><label for="y-axis">Select y-axis</label></span>
<select id="y-value">
<option value="FLFPR">Female LFPR</option>
<option value="lnGDP">Log GDP per capita</option>
<option value="Fertility">Fertility rate</option>
</select>
<p><span><label for="x-axis">Select x-axis</label></span>
<select id="x-value">
<option value="FLFPR">Female LFPR</option>
<option value="lnGDP">Log GDP per capita</option>
<option value="Fertility">Fertility rate</option>
</select>
<button onclick="setGraph()">submit</button>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://unpkg.com/d3-regression#1.2.3/dist/d3-regression.min.js"></script>
<script>
function drawGraph(xText, yText) {
$('svg').remove();
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/*
* value accessor - returns the value to encode for a given data object.
* scale - maps value to a visual display encoding, such as a pixel position.
* map function - maps from data value to display value
* axis - sets up axis
*/
// setup x
var xValue = function(d) { return d[xText];}, // data -> value
xScale = d3.scale.linear().range([0, width]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
// setup y
var yValue = function(d) { return d[yText];}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup fill color
var cValue = function(d) { return d.IG;},
color = d3.scale.category20();
// add the graph canvas to the body of the webpage
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// load data
d3.csv("scatter2.csv", function(error, data) {
// change string (from CSV) into number format
data.forEach(function(d) {
d[yText] = +d[yText];
d[xText] = +d[xText];
//console.log (d.School);
//console.dir (d);
});
// don't want dots overlapping axis, so add in buffer to data domain
xScale.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(xText);
// y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yText);
// draw dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 6.6)
.attr("opacity", 0.9)
.style("stroke", function(d) { return color(cValue(d));})
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["Player"] + "<br/> " + d.School + "<br/>(" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(10," + (i+7) * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
// get regression coefficients
regData = data.map(item => ({x: item[xText], y: item[yText]}));
res = drawRegressionLine(regData)
console.log("regression results")
console.log(res)
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
// var line = d3.svg.line()
// .x(function(d) { return xScale(d['x']); })
// .y(function(d) { return yScale(d['y']); });
// var lineFunction = d3.svg.line()
// .x(function(d) { return d.x; })
// .y(function(d) { return d.y; })
// .interpolate("linear");
var regLine = svg.append("path")
.datum(res)
.attr("d", line)
.style("stroke", "steelblue")
.style("stroke-width", "6px");
// var lineGraph = svg.append("path")
// .attr("d", line(res))
// .attr("stroke", "blue")
// .attr("stroke-width", 2)
// .attr("fill", "black");
});
}
// draw regression line
function drawRegressionLine(regData) {
console.log("beginning")
console.log("inside draw regression lilne")
linearRegression = d3.regressionLinear()
.x(d => d.x)
.y(d => d.y);
res = linearRegression(regData)
return res;
}
// drawGraph('Passing TD', 'Rushing TD');
function setGraph() {
console.log("inside set graph")
console.log($('#x-value').val())
drawGraph($('#x-value').val(), $('#y-value').val());
}
</script>
</body>
</html>
Please help me find what is wrong with the code.
It looks like you are not passing the your linear regression into your x and y scale.
Try:
var line = d3.svg.line()
.x(function(d) { return xScale(d[0]); })
.y(function(d) { return yScale(d[1]); })
.interpolate("linear");
This is a follow on from my previous question
d3 rect in one group interfering with rect in another group
Two issues:
Incorrect drag behaviour. When clicking on the second rect to drag it, it jumps to where the third one is.
I added a anonymous function which runs when the svg in clicked on anywhere. This should add a new rect. However that is the working.
I know I should have only one issue per question but these are related and I suspect they will be solved together.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*.active {
stroke: #000;
stroke-width: 2px;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
// create group
var group = svg.selectAll("g")
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 200)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
}
data.push(newData);
group.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
</script>
Here are the explanations to your issues:
You are reassigning var groups, that is, you have two var groups in your code, the last one overwriting the first one. Just remove the last variable.
In your function to append new rectangles, you are using an update selection that selects rectangles. However, your enter selection appends groups (<g>) elements, not rectangles.
Have a look at the refactored function, it binds the data to a newly created group and appends the rectangle to that group:
var newGroup = svg.selectAll(".group")
.data(data, function(d) {
return d.x
})
.enter()
.append("g")
//etc...
newGroup.append("rect")
//etc...
Also, use a key selection in the data binding, so you know exactly what rectangle is being dragged:
.data(data, function(d){return d.x})
Here is your code with those changes:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*.active {
stroke: #000;
stroke-width: 2px;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data, function(d){return d.x})
.enter().append("g")
.attr("class", "group")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: coords[0],
}
data.push(newData);
var newGroup = svg.selectAll(".group")
.data(data, function(d){return d.x})
.enter()
.append("g")
.attr("class", "group")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
newGroup.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
</script>
For correctly drag-and-drop behavior, rewrite your code like this:
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var svg = d3.select("svg");
var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];
var groove = svg.append("g")
.attr("class", "groove_group");
groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");
groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");
// create group
var group = svg.selectAll(null)
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);
group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
}
data.push(newData);
group.selectAll("rect")
.data(data)
.exit()
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.2/d3.min.js"></script>
<svg width="960" height="500"></svg>
But, what the problem with adding the new element, I have no idea.
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 am looking to animate this chart.
http://jsfiddle.net/NYEaX/1554/
var invisiblebubble = mask.append("circle")
.data(data);
invisiblebubble
.attr("cx", 550)
.attr("cy", 250)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
I've animated the mask circle - looking to implement other animations/suggestions for the labels. If they tween like a pie chart, tween in an arc, fade in etc..
I did create a transition on the radius of the circle - kind of looked like the warner bros ending.
var invisiblebubble = mask.append("circle")
.data(data);
invisiblebubble
.attr("cx", 550)
.attr("cy", 250)
.transition()
.duration(2000)
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
How do I animate other features like the labels/pointers
I've managed to improve the inverse bubble chart with this code.
Where I have to set a fixed size for the circle first, mask it, then animate it - for the purpose of the labels.
function maskMaker(el){
var backcolor = $(el).data("color");
var backopacity = $(el).data("opacity");
var height = $(el).data("height");
var width = $(el).data("width");
var labelName = $(el).data("label-name");
var bubbleValue = $(el).data("bubble-value");
var displaceLeft = $(el).data("displace-left");
var displaceTop = $(el).data("displace-top");
var data = [{
"label": labelName,
"x": displaceLeft,
"y": displaceTop,
"value": bubbleValue
}];
console.log("MASK data", data);
// Set the main elements for the series chart
var svgroot = d3.select($(el)[0]).append("svg");
// filters go in defs element
var defs = svgroot.append("defs");
var mask = defs.append("mask")
.attr("id", "myMask");
mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "white")
.style("opacity", backopacity);
var invisiblebubble = mask.append("circle")
.data(data);
//create a fixed bubble first
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", function(d) {
return d.value-20;
});
//now mask the fixed circle
var masker = defs.append(function() {
return mask.node().cloneNode(true)
})
.attr("id", "myMaskForPointer")
.select("rect")
.style("opacity", 0.8);
//animate this circle
invisiblebubble
.attr("cx", "50%")
.attr("cy", "50%")
.attr("r", 10)
.transition()
.duration(900)
.attr("r", function(d) {
return d.value;
});
//apply the rest of the chart elements
var svg = svgroot
.attr("class", "series")
.attr("width", "1120px")
.attr("height", "500px")
.append("g")
.attr("transform", "translate(0,0)")
var rect = svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("mask", "url(#myMask)")
.style("fill", backcolor);
/*
//__labels
var centrallabel = svgroot.append("g")
.attr("class", "centrallabel")
.data(data);
centrallabel
.append("text")
.attr("text-anchor", "middle")
.attr("x", 550)
.attr("y", 250 + 10)
.text(function(d) {
return "200";
})
*/
function addLabel(){
//__labels
var labels = svgroot.append("g")
.attr("class", "labels")
//__ enter
var labels = labels.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
//__ update
//labels
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y-10;
})
.text(function(d) {
return d.label;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
d.sy = d.oy = d.y + 5;
d.cx = 550;
d.cy = 250;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
//__ exit
labels.exit().remove();
//__labels
}
function addPointer(){
//__pointers
var pointers = svgroot.append("g")
.attr("class", "pointers");
var dots = defs.append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3);
var pointers = pointers.selectAll("path.pointer")
.data(data);
//__ enter
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.attr("marker-end", "url(#circ)")
.attr("mask", "url(#myMaskForPointer)")
//__ update
//pointers
.attr("d", function(d) {
if (d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
//__ exit
pointers.exit().remove();
//__pointers
}
//delay for the mask
setTimeout(function(){
addLabel();
addPointer();
}, 1000);
}
Fade in could be implementing like :
centrallabel
.append("text")
.attr("text-anchor", "middle")
.attr("x", 550)
.style("opacity",0)
.attr("y", 250 + 10)
.text(function(d) {
return "200";
})
.transition()
.duration(2000)
.style("opacity", 1)
As far as other animations you could start the pointer line off screen then transition it to it's endpoint. Or start with its length at 0 and transition it to full size. Transform/Translate would probably be useful - see (https://bl.ocks.org/mbostock/1345853)
I have code like this that creates multiple D3 donut multiples.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
svg {
padding: 10px 0 0 10px;
}
.arc {
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var radius = 74,
padding = 10;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - 30);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
d3.csv("data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));
data.forEach(function(d) {
d.ages = color.domain().map(function(name) {
return {name: name, population: +d[name]};
});
});
var legend = d3.select("body").append("svg")
.attr("class", "legend")
.attr("width", radius * 2)
.attr("height", radius * 2)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function(d) { return d; });
var svg = d3.select("body").selectAll(".pie")
.data(data)
.enter().append("svg")
.attr("class", "pie")
.attr("width", radius * 2)
.attr("height", radius * 2)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.name); });
svg.append("text")
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.State; });
});
</script>
I am looking for a way to implement the D3 tooltip so that I can see the exact data of each chunk of the donut when I put my cursor over it. I understand that there are other examples of the tooltip on here but none of them have worked with the donut multiples example.
Here is some example data
State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over
AL,310504,552339,259034,450818,1231572,1215966,641667
AK,52083,85640,42153,74257,198724,183159,50277
AZ,515910,828669,362642,601943,1804762,1523681,862573
AR,202070,343207,157204,264160,754420,727124,407205
CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496
CO,358280,587154,261701,466194,1464939,1290094,511094
CT,211637,403658,196918,325110,916955,968967,478007
DE,59319,99496,47414,84464,230183,230528,121688
DC,36352,50439,25225,75569,193557,140043,70648
FL,1140516,1938695,925060,1607297,4782119,4746856,3187797
GA,740521,1250460,557860,919876,2846985,2389018,981024
HI,87207,134025,64011,124834,356237,331817,190067
ID,121746,201192,89702,147606,406247,375173,182150
IL,894368,1558919,725973,1311479,3596343,3239173,1575308
IN,443089,780199,361393,605863,1724528,1647881,813839
IA,201321,345409,165883,306398,750505,788485,444554
KS,202529,342134,155822,293114,728166,713663,366706
KY,284601,493536,229927,381394,1179637,1134283,565867
LA,310716,542341,254916,471275,1162463,1128771,540314
ME,71459,133656,69752,112682,331809,397911,199187
MD,371787,651923,316873,543470,1556225,1513754,679565
The D3 doc of this can be found at
http://bl.ocks.org/mbostock/3888852
I'm not sure what you're referring to when you say "the D3 tooltip", because d3 doesn't have any built-in tooltip functionality. That said, there are some good third-party plugins out there for doing tooltips in d3.
d3-tip is one that would work well for what you're trying to do.
You can create a tooltip function to display your population data for each arc like this:
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) { return d.data.population; })
.direction('s');
Then you can call the function on your svg selection:
svg.call(tip);
Finally, you can use mouse event listeners on your arcs to show and hide the tooltip:
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.name); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
Here's a PLUNK with a working example.
You can also check up on the d3-tip documentation here.
I too got the same TypeError while debugging my javascript code using d3js while adding tooltip for my barcharts.
bars.append('rect')
.attr('y', maxHeight)
.attr('height', 0)
.attr('width', function (d) { return x.rangeBand(d) - 1; })
.attr('class', 'bar')
.transition().duration(1500)
.attr('y', function (d, i) { return y(d.y); })
.attr('height', function (d, i) { return maxHeight - y(d.y); });
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
In the above code i used ".on('mouseover', tip.show)" on "bars.append()".This is wrong usage.
Later, i found that ".on('mouseover', tip.show)" should be applied on function ".select('rect') or .selectall('rect')".
Now below snippet is working correctly for my application.
bars.append('rect')
.attr('y', maxHeight)
.attr('height', 0)
.attr('width', function (d) { return x.rangeBand(d) - 1; })
.attr('class', 'bar')
.transition().duration(1500)
.attr('y', function (d, i) { return y(d.y); })
.attr('height', function (d, i) { return maxHeight - y(d.y); });
bars.select('rect')
.on('mouseover', tip.show)
.on('mouseout', tip.hide);