How do I add multiple draggable area in a Radar chart - javascript

Im new to javascript and english, sorry for bad english.
I have a radar chart created using this demo code
Now I want to add another area to the same graph, using a button, I am not able to do this.
Please help me.
$("#btn").click(function(){
// here i want to add another set of daata
});
$("#btn").click(function(){
d = [
{axis: "axisA", value: 2, order:0},
{axis: "axisB", value: 8, order:1},
{axis: "axisC", value: 4, order:2},
{axis: "axisD", value: 6, order:3}
];
RadarChart.draw("#chart", d);
})
var RadarChart = {draw: function(id, d, options){
var cfg = {
radius: 6, w: 600,h: 600,
factor: 1,
factorLegend: .85,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
opacityArea: 0.5,
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.map(function(o){return o.value})));
var allAxis = (d.map(function(i, j){return i.axis}));
var total = allAxis.length;
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
d3.select(id).select("svg").remove();
var g = d3.select(id).append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g");
var tooltip;
drawFrame();
var maxAxisValues = [];
drawAxis();
var dataValues = [];
reCalculatePoints();
var areagg = initPolygon();
drawPoly();
drawnode();
function drawFrame(){
for(var j=0; j<cfg.levels; 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-width", "0.5px").attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")");;
}
}
function drawAxis(){
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(j, i){
maxAxisValues[i] = {x:cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total)), y:0};
return maxAxisValues[i].x;
})
.attr("y2", function(j, i){
maxAxisValues[i].y = cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));
return maxAxisValues[i].y;
})
.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", "10px").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))-20*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);});
}
function reCalculatePoints(){
g.selectAll(".nodes")
.data(d, function(j, i){
dataValues[i] =
[
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[d[0].length] = dataValues[0];
}
function initPolygon(){
return g.selectAll("area").data([dataValues])
.enter()
.append("polygon")
.attr("class", "radar-chart-serie0")
.style("stroke-width", "2px")
.style("stroke", cfg.color(0))
.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", 0.7);
})
.on('mouseout', function(){
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.style("fill", function(j, i){return cfg.color(0);})
.style("fill-opacity", cfg.opacityArea);
}
function drawPoly(){
areagg.attr("points",function(de) {
var str="";
for(var pti=0;pti<de.length;pti++){
str=str+de[pti][0]+","+de[pti][1]+" ";
}
return str;
});
}
function drawnode(){
g.selectAll(".nodes")
.data(d).enter()
.append("svg:circle").attr("class", "radar-chart-serie0")
.attr('r', cfg.radius)
.attr("alt", function(j){return Math.max(j.value, 0);})
.attr("cx", function(j, i){
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(0)).style("fill-opacity", 0.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(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", 0.7);
})
.on('mouseout', function(){
tooltip.transition(200).style('opacity', 0);
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.call(d3.behavior.drag().on("drag", move)) // for drag & drop
.append("svg:title")
.text(function(j){return Math.max(j.value, 0)});
}
//Tooltip
tooltip = g.append('text').style('opacity', 0).style('font-family', 'sans-serif').style('font-size', 13);
function move(dobj, i){
this.parentNode.appendChild(this);
var dragTarget = d3.select(this);
var oldData = dragTarget.data()[0];
var oldX = parseFloat(dragTarget.attr("cx")) - 300;
var oldY = 300 - parseFloat(dragTarget.attr("cy"));
var newY = 0, newX = 0, newValue = 0;
var maxX = maxAxisValues[i].x - 300;
var maxY = 300 - maxAxisValues[i].y;
if(oldX == 0) {
newY = oldY - d3.event.dy;
if(Math.abs(newY) > Math.abs(maxY)) {
newY = maxY;
}
newValue = (newY/oldY) * oldData.value;
}
else
{
var slope = oldY / oldX;
newX = d3.event.dx + parseFloat(dragTarget.attr("cx")) - 300;
if(Math.abs(newX) > Math.abs(maxX)) {
newX = maxX;
}
newY = newX * slope;
var ratio = newX / oldX;
newValue = ratio * oldData.value;
}
dragTarget
.attr("cx", function(){return newX + 300 ;})
.attr("cy", function(){return 300 - newY;});
d[oldData.order].value=newValue;
reCalculatePoints();
drawPoly();
}}};
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<button id="btn">Click to add</button>
<button id="btnB">Click to add Another area</button>
<div id="chart" >
</div>

<!DOCTYPE html>
<html>
<body>
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<button id="btn">Click to add</button>
<button id="btnB">Click to add Another area</button>
<div id="chart"></div>
<script>
d = [
{axis: "axisA", value: 2, order:0},
{axis: "axisB", value: 8, order:1},
{axis: "axisC", value: 4, order:2},
{axis: "axisD", value: 6, order:3}
];
var RadarChart = {draw: function(id, d, options){
var cfg = {
radius: 6, w: 600,h: 600,
factor: 1,
factorLegend: .85,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
opacityArea: 0.5,
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.map(function(o){return o.value})));
var allAxis = (d.map(function(i, j){return i.axis}));
var total = allAxis.length;
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2);
d3.select(id).select("svg").remove();
var g = d3.select(id).append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g");
var tooltip;
drawFrame();
var maxAxisValues = [];
drawAxis();
var dataValues = [];
reCalculatePoints();
var areagg = initPolygon();
drawPoly();
drawnode();
function drawFrame(){
for(var j=0; j<cfg.levels; 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-width", "0.5px").attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")");;
}
}
function drawAxis(){
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(j, i){
maxAxisValues[i] = {x:cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total)), y:0};
return maxAxisValues[i].x;
})
.attr("y2", function(j, i){
maxAxisValues[i].y = cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));
return maxAxisValues[i].y;
})
.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", "10px").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))-20*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);});
}
function reCalculatePoints(){
g.selectAll(".nodes")
.data(d, function(j, i){
dataValues[i] =
[
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[d[0].length] = dataValues[0];
}
function initPolygon(){
return g.selectAll("area").data([dataValues])
.enter()
.append("polygon")
.attr("class", "radar-chart-serie0")
.style("stroke-width", "2px")
.style("stroke", cfg.color(0))
.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", 0.7);
})
.on('mouseout', function(){
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.style("fill", function(j, i){return cfg.color(0);})
.style("fill-opacity", cfg.opacityArea);
}
function drawPoly(){
areagg.attr("points",function(de) {
var str="";
for(var pti=0;pti<de.length;pti++){
str=str+de[pti][0]+","+de[pti][1]+" ";
}
return str;
});
}
function drawnode(){
g.selectAll(".nodes")
.data(d).enter()
.append("svg:circle").attr("class", "radar-chart-serie0")
.attr('r', cfg.radius)
.attr("alt", function(j){return Math.max(j.value, 0);})
.attr("cx", function(j, i){
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(0)).style("fill-opacity", 0.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(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", 0.7);
})
.on('mouseout', function(){
tooltip.transition(200).style('opacity', 0);
g.selectAll("polygon").transition(200).style("fill-opacity", cfg.opacityArea);
})
.call(d3.behavior.drag().on("drag", move)) // for drag & drop
.append("svg:title")
.text(function(j){return Math.max(j.value, 0)});
}
//Tooltip
tooltip = g.append('text').style('opacity', 0).style('font-family', 'sans-serif').style('font-size', 13);
function move(dobj, i){
this.parentNode.appendChild(this);
var dragTarget = d3.select(this);
var oldData = dragTarget.data()[0];
var oldX = parseFloat(dragTarget.attr("cx")) - 300;
var oldY = 300 - parseFloat(dragTarget.attr("cy"));
var newY = 0, newX = 0, newValue = 0;
var maxX = maxAxisValues[i].x - 300;
var maxY = 300 - maxAxisValues[i].y;
if(oldX == 0) {
newY = oldY - d3.event.dy;
if(Math.abs(newY) > Math.abs(maxY)) {
newY = maxY;
}
newValue = (newY/oldY) * oldData.value;
}
else
{
var slope = oldY / oldX;
newX = d3.event.dx + parseFloat(dragTarget.attr("cx")) - 300;
if(Math.abs(newX) > Math.abs(maxX)) {
newX = maxX;
}
newY = newX * slope;
var ratio = newX / oldX;
newValue = ratio * oldData.value;
}
dragTarget
.attr("cx", function(){return newX + 300 ;})
.attr("cy", function(){return 300 - newY;});
d[oldData.order].value=newValue;
reCalculatePoints();
drawPoly();
}}};
$("#btnB").click(function(){
d.push({axis: "axisA", value: 1, order:4}, {axis: "axisB", value: 3, order:5}, {axis: "axisC", value: 5, order:6}, {axis: "axisD", value: 7, order:7})
RadarChart.draw("#chart", d);
});
$("#btn").click(function(){
RadarChart.draw("#chart", d);
});
</script>
</body>
<html>

Related

Cannot add Image to SVG Circle in d3

I'm working with d3, but can't seem to add an image to my SVG circle? I've tried different ways but won't give the output I want. Can someone help me?
The image loaded correctly so that shouldn't be the problem. I also tried the fill attribute but it didn't work.
import React, { Component } from "react";
import "../styles/style.css";
import $ from "jquery";
import * as d3 from "d3";
import btcImage from "../images/btc.png";
const crypto = require("../data/cryptoData.json");
const coins = [];
for (let i = 0; i < crypto.data.length; i++) {
var coin = crypto.data[i];
coins.push(coin);
}
var data = [];
coins.forEach((coin) => {
var text = coin.symbol;
var r = coin.quote.USD.market_cap / 4000000000;
data.push({
text: text,
category: coin.quote.USD.percent_change_24h,
image: btcImage,
r: r,
r_change_1: coin.quote.USD.market_cap / 4000000000,
r_change_2: coin.quote.USD.market_cap / 4000000000,
});
});
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function (d) {
var r = d.r + 10,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function (quad, x1, y1, x2, y2) {
if (quad.point && quad.point !== d) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.r + quad.point.r;
if (l < r) {
l = ((l - r) / l) * (1 + alpha);
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
const bubbleCloud = (element) => {
var container = d3.select(".bubble-cloud");
var $container = $(".bubble-cloud");
var containerWidth = $container.width();
var containerHeight = $container.height();
var svgContainer = container
.append("svg")
.attr("width", containerWidth)
.attr("height", containerHeight);
// prepare layout
var force = d3.layout
.force()
.size([containerWidth, containerHeight])
.gravity(0)
.charge(0)
.friction(1);
// load data
force.nodes(data).start();
// create item groups
var node = svgContainer
.selectAll(".node")
.data(data)
.enter()
.append("g")
.attr("class", "node")
.call(force.drag);
// create circles
node
.append("defs")
.append("pattern")
.attr("height", 60)
.attr("width", 60)
.attr("x", 0)
.attr("y", 0)
.append("image")
.attr("xlink:href", function (d) {
return d.image;
})
.attr("height", 60)
.attr("width", 60)
.attr("x", 0)
.attr("y", 0);
node
.append("circle")
.attr("r", 1e-6)
.style("fill", function (d) {
return d.image;
});
// create labels
node
.append("text")
.text(function (d) {
return d.text;
})
.classed("text", true)
.style({
fill: "#ffffff",
"text-anchor": "middle",
"font-size": "12px",
"font-weight": "bold",
"text-transform": "uppercase",
"font-family": "Tahoma, Arial, sans-serif",
});
node
.append("text")
.text(function (d) {
return d.category;
})
.classed("category", true)
.style({
color: "white",
"font-family": "Tahoma, Arial, sans-serif",
"text-anchor": "middle",
"font-size": "9px",
});
node
//.append("line")
//.classed("line", true)
//.attr({
// x1: 0,
// y1: 0,
// x2: 0,
// y2: 0,
//})
.attr("stroke-width", 1)
.attr("stroke", function (d) {
return d.stroke;
});
// put circle into movement
force.on("tick", function (e) {
d3.selectAll("circle")
.each(collide(0.1))
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
// boundaries
if (d.x <= d.r) {
d.x = d.r + 1;
}
if (d.x >= containerWidth - d.r) {
d.x = containerWidth - d.r - 1;
}
return d.x;
})
.attr("cy", function (d) {
// boundaries
if (d.y <= d.r) {
d.y = d.r + 1;
}
if (d.y >= containerHeight - d.r) {
d.y = containerHeight - d.r - 1;
}
return d.y;
});
d3.selectAll("line").attr({
x1: function (d) {
return d.x - d.r + 10;
},
y1: function (d) {
return d.y;
},
x2: function (d) {
return d.x + d.r - 10;
},
y2: function (d) {
return d.y;
},
});
d3.selectAll(".text")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y - 10;
});
d3.selectAll(".category")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y + 20;
});
force.alpha(0.1);
});
};
class Bubbles extends Component {
render() {
return <div className="bubble-cloud" ref={bubbleCloud}></div>;
}
}
export default Bubbles;
I think I'm overlooking something
Output:

d3js arc height tweening

I am trying to tween the heights of the various arcs in this chart
jsfiddle
http://jsfiddle.net/0ht35rpb/193/
I've seen this example but not sure how to start implementing it.
d3 how to tween inner radius for pie chart
I've seen this sample on various tweening maths. http://andyshora.com/tweening-shapes-paths-d3-js.html
var $this = $("#chart");
var data = [{
"label": "Overall Stress",
"value": 89
},{
"label": "Emotional Stress",
"value": 1
},{
"label": "Behavioural difficulties",
"value": 29
},{
"label": "hyperactivity and concetration",
"value": 89
},{
"label": "Getting along with others",
"value": 19
},{
"label": "Keen and helpful behaviour",
"value": 45
}];
var w = 350;
var h = 350;
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f", "#c12fff"];
return colores_g[n % colores_g.length];
}
var arcGenerator = {
radius: 70,
oldData: "",
init: function(el, data, w, h){
var stardata = [
{
"segments": data
}
];
this.el = el;
var clone = $.extend(true, {}, stardata);
this.oldData = this.setData(clone, false);
this.setup(el, this.setData(stardata, true), w, h);
},
update: function(data){
var clone = $.extend(true, {}, data);
this.animate(this.setData(data, true));
this.oldData = this.setData(clone, false);
},
animate: function(data){
var that = this;
var chart = d3.select(this.el);
that.generateArcs(chart, data);
},
setData: function(data, isSorted){
var diameter = 2 * Math.PI * this.radius;
var localData = new Array();
var displacement = 0;
var oldBatchLength = 0;
$.each(data, function(index, value) {
var riseLevels = value.segments;
var riseLevelCount = riseLevels.length;
if(oldBatchLength !=undefined){
displacement+=oldBatchLength;
}
var arcBatchLength = 2*Math.PI;
var arcPartition = arcBatchLength/riseLevelCount;
$.each(riseLevels, function( ri, value ) {
var startAngle = (ri*arcPartition);
var endAngle = ((ri+1)*arcPartition);
if(index!=0){
startAngle+=displacement;
endAngle+=displacement;
}
riseLevels[ri]["startAngle"] = startAngle;
riseLevels[ri]["endAngle"] = endAngle;
});
oldBatchLength = arcBatchLength;
localData.push(riseLevels);
});
var finalArray = new Array();
$.each(localData, function(index, value) {
$.each(localData[index], function(i, v) {
finalArray.push(v);
});
});
return finalArray;
},
generateArcs: function(chart, data){
var that = this;
//_arc paths
//append previous value to it.
$.each(data, function(index, value) {
if(that.oldData[index] != undefined){
data[index]["previousEndAngle"] = that.oldData[index].endAngle;
}
else{
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = that.arcpaths.selectAll("path")
.data(data);
arcpaths.enter().append("svg:path")
.style("fill", function(d, i){
return colores_google(i);
})
.transition()
.ease(d3.easeElastic)
.duration(750)
.attrTween("d", arcTween);
arcpaths.transition()
.ease(d3.easeElastic)
.style("fill", function(d, i){
return colores_google(i);
})
.duration(750)
.attrTween("d",arcTween);
arcpaths.exit().transition()
.ease(d3.easeBounce)
.duration(750)
.attrTween("d", arcTween)
.remove();
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return that.getArc()(i(t));
};
}
//_arc paths
var r = that.radius + 40;
var ir = that.radius - 30;
var legendHeight = this.legendPaddingTop;
var ySpace = 18;
var labelPadding = 3;
//draw labels legends
var labels = that.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) + labelPadding;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
var legend = that.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();
//reset legend height
//console.log("optimum height for legend", legendHeight);
$this.find('.legend').attr("height", legendHeight);
/*
//__labels
var starlabels = that.starlabels.selectAll("text")
.data(data);
starlabels.enter()
.append("text")
.attr("text-anchor", "middle")
starlabels.text(function(d) {
return d.label;
})
.each(function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
d.cy = Math.sin(a) * (ir+((r-ir)/2));
d.x = d.x || Math.cos(a) * (r + 20);
d.y = d.y || Math.sin(a) * (r + 20);
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;
})
.transition()
.duration(300)
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.y = Math.sin(a) * (r + 20);
});
starlabels.exit().remove();
//__labels
//__pointers
that.pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3)
.style("fill", "#005a70");
var pointers = that.pointers.selectAll("path.pointer")
.data(data);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "#005a70")
.attr("marker-end", "url(#circ)");
pointers
.transition()
.duration(300)
.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;
}
});
pointers.exit().remove();
//__pointers
*/
},
setup: function(el, data, w, h){
var chart = d3.select(el).append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h)
var arcchart = chart.append("g")
.attr("class", "starchart")
.attr("transform", "translate("+w/4+","+h/2+")");
this.arcpaths = arcchart.append("g")
.attr("class", "arcpaths");
this.starlabels = arcchart.append("g")
.attr("class", "labels");
this.pointers = arcchart.append("g")
.attr("class", "pointer");
var margin = 25;
var padding = 15;
this.legendPaddingTop = 30;
var legend = chart.append("g")
.attr("class", "legend")
.attr("width", w/3)
.attr("height", h - 50)
.attr("transform", "translate(" + (w-20) + "," + (h/4) + ")");
this.label_group = legend.append("g")
.attr("class", "label_group")
.attr("transform", "translate(" + (-(w / 3) + 20) + "," + 0 + ")");
this.legend_group = legend.append("g")
.attr("class", "legend_group")
.attr("transform", "translate(" + (-(w / 3) - 100) + "," + 0 + ")");
var radiusControl = 16;
this.dataset = "big";//more than 2 results
if(data.length <=2){
radiusControl = 65;//make the radius smaller to compromise with there being less results
this.dataset = "small";
}
this.radius = w/4 - radiusControl;
this.generateArcs(chart, data);
},
getArc: function(){
var that = this;
var arc = d3.arc()
.innerRadius(function(d, i){
var threshold = 50;
if(that.dataset == "small"){
threshold = 20;
}
return that.radius-threshold;//negative makes it deeper
})
.outerRadius(function(d){
var maxHeight = 120;
var ratio = (d.value/maxHeight * 100)+that.radius;
return ratio;
})
.startAngle(function(d, i){
return d.startAngle;
})
.endAngle(function(d, i){
return d.endAngle;
});
return arc;
}
}
arcGenerator.init($this[0], data, w, h);
In your attrTween function instead of interpolating angle interpolate the value:
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle; <-- incorrect
var i = d3.interpolate(prev, b);
Interpolate outer radius like below
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.value = 0;
var i = d3.interpolate(prev, b);
working code here

D3 multiple Axis interpolation

I am working on a project to plot a stream of csv/Json data (bar chart) where the order of arrival of the data is important.
The Y axis is unique, but there are multiple X axes that correspond to different measures of the data.
I am having trouble producing a nice graph that looks like this, given the following data:
x0,x1,x2,y,idx
-1,z,w2,10,0
0,z,w2,9,1
1,z,w2,8,2
-1,k,w2,11,3
0,k,5q,5,4
1,k,5q,8,5
idx represent the order the data arrives in.
this is what I get
X=["idx","x0","x1","x2"];
Y=["y"];
var margin = {
top: 80,
right: 180,
bottom: 180,
left: 180
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = [],
x = [];
var x_uid = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
for (var idx = 0; idx < X.length; idx++) {
x[idx] = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
xAxis[idx] = d3.svg.axis()
.scale(x[idx])
.orient("bottom");
}
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// .ticks(8, "%");
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 + ")");
var data = [{
x0:-1,
x1:z,
x2:w2,
y:10,
idx:0
},
{
x0:0,
x1:z,
x2:w2,
y:10,
idx:1
},
{
x0:1,
x1:z,
x2:w2,
y:10,
idx:2
},
{
x0:-1,
x1:j,
x2:w2,
y:10,
idx:3
},
{
x0:0,
x1:j,
x2:5q,
y:10,
idx:4
},
{
x0:1,
x1:j,
x2:5q,
y:10,
idx:5
}]
if(data) {
for (var idx = 0; idx < X.length; idx++) {
x[idx].domain(data.map(function(d) {
return d[X[idx]];
}));
}
x_uid.domain(data.map(function(d) {
return d.idx;
}));
y.domain([0, d3.max(data, function(d) {
d.value = d[Y[0]];
return d.value;
})]);
for (var idx = 0; idx < X.length; idx++)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + idx * 25) + ")")
.call(xAxis[idx]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x_uid(d.idx);
})
.attr("width", 1)
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js"></script>
<div id="chart"></div>
Offsetting the ticks' text is not an issue, but I am having problems with the interpolation due to the multiplicities of the values:
e.g. width of w2 > width of 5q
e.g. x0 axis should be -1 0 1 -1 0 1 but d3 interpolates as -1 0 1
I tried using rangeRoundBand instead of rangeRoundPoint but the issue is similar.
I also tried playing around with tickValues but to no avail.
I tried doing my own interpolation using linear scales instead of ordinal, but it becomes very messy very quickly because is forces me to manually calculate and adjust all the ticks' positions and texts while taking into account the d3.behavior zoom level etc...
function adjustTickPosition(selection, count, scale, translate, rotate) {
//selection = axis
//count = multiplicity of each tick
//scale = d3.behavior.zoom scale
//translate = d3.behavior.zoom translation
//rotate = irrelevent here (additional styling)
console.info( selection.selectAll("g.tick"))
// cancel previous position
//
// /!\ For some reason there is always 100 ticks instead of the appropriate number
//
selection.selectAll("g.tick")
.attr("transform", "translate(0,0)");
// align tick marks
selection.selectAll("g.tick line")
.attr('transform', function (d, k) {
if (k <= count.length - 1) {
var newPosition = scaleTranslate(count[k]);
if (newPosition > width || newPosition < 0) {
d3.select(this.parentNode).style("visibility", "hidden");
} else
d3.select(this.parentNode).style("visibility", "visible");
return 'translate(' + newPosition + ',0)';
} else
return 'translate(0,0)';
});
// offset tick label compared to tick marks
selection.selectAll("g.tick text")
.attr('transform', function (d, k) {
if (k <= count.length - 1) {
var pos, transform;
if (k > 0) pos = (count[k - 1] + count[k]) / 2;
else pos = count[k] / 2;
var newPosition = scaleTranslate(pos);
if (newPosition > width || newPosition < 0) {
d3.select(this.parentNode).style("visibility", "hidden");
} else
d3.select(this.parentNode).style("visibility", "visible");
var transform = 'translate(' + newPosition + ',0)';
if (rotate) transform += ' rotate(-65)';
return transform;
} else
return 'translate(0,0)';
});
if (rotate) selection.selectAll("g.tick text").style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em");
return selection;
function scaleTranslate(v) {
return v / count[count.length - 1] * width * scale + translate[0];
}
}
Could someone please show me how to properly use axes ticks for this kind of purpose?
Thank you in advance
I made my own class/object because d3 was apparently not meant for this kind of graph
function chartAxis(key, args) {
//***************************
// PRIVATE
//***************************
var _direction = args ? (args.direction || "x") : "x";
var _width = args ? (args.width || 500) : 500;
var _alignTicks = args ? (args.alignTicks || false) : false;
var _tickSize = args ? (args.tickSize || 0) : 0;
var _numTicks = args ? (args.numTicks || 10) : 10;
var _offset = args ? (args.offset || 25) : 25;
var _zoom = args ? (args.zoom || {
s: 1,
t: 0
}) : {
s: 1,
t: 0
};
var _totalLength;
function consecutiveReduction(list, key) {
var Bin = function (val, cnt) {
return {
value: val,
count: cnt,
cumulativeCount: 0,
center: 0,
position: 0
};
};
var result = list.map(function (d) {
return key ? d[key] : d;
}).reduce(function (acc, d) {
var currentBin = acc[acc.length - 1];
if ((acc.length > 0) && d === currentBin.value) {
//add to current bin
currentBin.count++;
} else {
//create new bin
acc.push(new Bin(d, 1));
}
return acc;
}, []);
result.forEach(accumulate);
result.forEach(positionTick);
return result;
}
function positionTick(d) {
d.position = ApplyZoom(d.cumulativeCount);
d.center = _alignTicks ? d.position : ApplyZoom(d.cumulativeCount - d.count / 2);
function ApplyZoom(val) {
var translate;
if (_zoom.t.length > 1)
translate = (_direction == "x") ? _zoom.t[0] : _zoom.t[1];
else
translate = _zoom.t;
return val / _totalLength * _width * _zoom.s + translate;
}
}
function accumulate(d, i, arr) {
d.cumulativeCount = d.count;
if (i > 0) d.cumulativeCount += arr[i - 1].cumulativeCount;
}
//***************************
// PUBLIC
//***************************
var xAxis = function (selection) {
selection.each(function (data) {
// calculate
_totalLength = data.length;
var tickData = consecutiveReduction(data, key);
console.log(tickData.map(function (d) {
return d.count
}))
console.table(data,key)
//create parent axis with clip-path
var axis = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", 0)
.attr("y", _offset - _tickSize)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
var axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: 0,
y1: _offset,
x2: _width,
y2: _offset,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _width + 10,
y: _offset
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function (d) {
return d.position
})
.attr("x2", function (d) {
return d.position
})
.attr("y1", function (d) {
return _offset - _tickSize
})
.attr("y2", function (d) {
return _offset + 5
});
ticks.select(".tick text")
.text(function (d) {
return d.value;
})
.attr("x", function (d) {
return d.center;
})
.attr("y", function (d) {
return _offset + 10;
})
.style("text-anchor", "middle")
.style("text-length", function (d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
})
};
var yAxis = function (selection) {
selection.each(function (data) {
// calculate
_totalLength = data.length;
var tickData = d3.extent(data, function (d) {
return d[key];
});
var tickRange = (tickData[1] - tickData[0]) / (_numTicks - 4 + 1); // -4 -> [0.85*min min ... max 1.15*max]
console.log(tickData.map(function (d) {
return d.count
}))
console.log(_tickSize)
//create parent axis with clip-path
var axis = axisLine = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", _offset)
.attr("y", 0)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: _offset,
y1: 0,
x2: _offset,
y2: _width,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _offset,
y: -10
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function (d) {
return _offset - 5
})
.attr("x2", function (d) {
return _offset + _tickSize
})
.attr("y1", function (d) {
return d.position
})
.attr("y2", function (d) {
return d.position
});
ticks.select(".tick text")
.text(function (d) {
return d.value;
})
.attr("x", function (d) {
return _offset + 10;
})
.attr("y", function (d) {
return d.center;
})
.style("text-anchor", "middle")
.style("text-length", function (d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
}); // end select.foreach
}; // end yAxis
xAxis.BindToZoom = function (zoomObject) {
_zoom = zoomObject;
return xAxis;
}
yAxis.BindToZoom = function (zoomObject) {
_zoom = zoomObject;
return yAxis;
}
return (_direction == "x") ? xAxis : yAxis;
}
Usage:
function chartAxis(key, args) {
//***************************
// PRIVATE
//***************************
var _direction = args ? (args.direction || "x") : "x";
var _width = args ? (args.width || 500) : 500;
var _alignTicks = args ? (args.alignTicks || false) : false;
var _tickSize = args ? (args.tickSize || 0) : 0;
var _numTicks = args ? (args.numTicks || 10) : 10;
var _offset = args ? (args.offset || 25) : 25;
var _zoom = args ? (args.zoom || {
s: 1,
t: 0
}) : {
s: 1,
t: 0
};
var _totalLength;
function consecutiveReduction(list, key) {
var Bin = function(val, cnt) {
return {
value: val,
count: cnt,
cumulativeCount: 0,
center: 0,
position: 0
};
};
var result = list.map(function(d) {
return key ? d[key] : d;
}).reduce(function(acc, d) {
var currentBin = acc[acc.length - 1];
if ((acc.length > 0) && d === currentBin.value) {
//add to current bin
currentBin.count++;
} else {
//create new bin
acc.push(new Bin(d, 1));
}
return acc;
}, []);
result.forEach(accumulate);
result.forEach(positionTick);
return result;
}
function positionTick(d) {
d.position = ApplyZoom(d.cumulativeCount);
d.center = _alignTicks ? d.position : ApplyZoom(d.cumulativeCount - d.count / 2);
function ApplyZoom(val) {
var translate;
if (_zoom.t.length > 1)
translate = (_direction == "x") ? _zoom.t[0] : _zoom.t[1];
else
translate = _zoom.t;
return val / _totalLength * _width * _zoom.s + translate;
}
}
function accumulate(d, i, arr) {
d.cumulativeCount = d.count;
if (i > 0) d.cumulativeCount += arr[i - 1].cumulativeCount;
}
//***************************
// PUBLIC
//***************************
var xAxis = function(selection) {
selection.each(function(data) {
// calculate
_totalLength = data.length;
var tickData = consecutiveReduction(data, key);
//create parent axis with clip-path
var axis = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", 0)
.attr("y", _offset - _tickSize)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
var axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: 0,
y1: _offset,
x2: _width,
y2: _offset,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _width + 10,
y: _offset
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function(d) {
return d.position
})
.attr("x2", function(d) {
return d.position
})
.attr("y1", function(d) {
return _offset - _tickSize
})
.attr("y2", function(d) {
return _offset + 5
});
ticks.select(".tick text")
.text(function(d) {
return d.value;
})
.attr("x", function(d) {
return d.center;
})
.attr("y", function(d) {
return _offset + 10;
})
.style("text-anchor", "middle")
.style("text-length", function(d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
})
};
var yAxis = function(selection) {
selection.each(function(data) {
// calculate
_totalLength = data.length;
var tickData = consecutiveReduction(data, key);
//create parent axis with clip-path
var axis = axisLine = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", _offset)
.attr("y", 0)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: _offset,
y1: 0,
x2: _offset,
y2: _width,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _offset,
y: -10
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function(d) {
return _offset - 5
})
.attr("x2", function(d) {
return _offset + _tickSize
})
.attr("y1", function(d) {
return d.position
})
.attr("y2", function(d) {
return d.position
});
ticks.select(".tick text")
.text(function(d) {
return d.value;
})
.attr("x", function(d) {
return _offset + 10;
})
.attr("y", function(d) {
return d.center;
})
.style("text-anchor", "middle")
.style("text-length", function(d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
}); // end select.foreach
}; // end yAxis
xAxis.BindToZoom = function(zoomObject) {
_zoom = zoomObject;
return xAxis;
}
yAxis.BindToZoom = function(zoomObject) {
_zoom = zoomObject;
return yAxis;
}
return (_direction == "x") ? xAxis : yAxis;
}
var data = [{
"a": 1,
"b": 3,
c: 1
}, {
"a": 1,
"b": 3,
c: 2
}, {
"a": 1,
"b": 2,
c: 3
}, {
"a": 1,
"b": 3,
c: 4
}, {
"a": 2,
"b": 3,
c: 5
}, {
"a": 3,
"b": "a",
c: 6
}, {
"a": 1,
"b": "a",
c: 7
}];
X = ["b", "a", "c"];
var axesDOM = d3.select("svg")
.selectAll(".axis")
.data(X).enter()
.append("g").attr("class", "axis");
axesDOM.each(function(x, i) {
d3.select(this).datum(data)
.call(new chartAxis(x, {
width: 200,
offset: 25 + i * 25,
direction: "x"
}));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="200px" height="200px"></svg>

Problems to label nodes in a particular force layout D3

I have made an algorithm with D3 to vizualize some datas. The algorithm is doing what i'm expecting of him. He separates nodes in multiple focis with the Force layout, links and nodes appears and disappears dynamicaly.
The problems comes with the labeling of the nodes. D3 seems to do the work but multiples times for each nodes (when i look at it with firebug).
Here is my code :
var actions = [
{"action":"arrivee","id":"001","service":1},
{"action":"arrivee","id":"002","service":1},
{"action":"arrivee","id":"003","service":1},
{"action":"arrivee","id":"004","service":1},
{"action":"arrivee","id":"005","service":1},
{"action":"arrivee","id":"006","service":3},
{"action":"arrivee","id":"007","service":3},
{"action":"arrivee","id":"008","service":3},
{"action":"arrivee","id":"009","service":3},
{"action":"arrivee","id":"010","service":2},
{"action":"arrivee","id":"011","service":2},
{"action":"arrivee","id":"012","service":2},
{"action":"arrivee","id":"013","service":2},
{"action":"arrivee","id":"014","service":4},
{"action":"arrivee","id":"015","service":4},
{"action":"arrivee","id":"016","service":4},
{"action":"arrivee","id":"017","service":4},
{"action":"contact","id":"0","source":"001","target":"017"},
{"action":"contact","id":"1","source":"016","target":"012"},
{"action":"contact","id":"2","source":"004","target":"011"},
{"action":"contact","id":"3","source":"001","target":"010"},
{"action":"fincontact","id":"0"},
{"action":"depart","id":"017"},
{"action":"arrivee","id":"018","service":2},
{"action":"arrivee","id":"019","service":1},
{"action":"arrivee","id":"020","service":1},
{"action":"arrivee","id":"021","service":0},
{"action":"arrivee","id":"022","service":0},
{"action":"arrivee","id":"023","service":0},
{"action":"arrivee","id":"024","service":0},
{"action":"arrivee","id":"025","service":0},
{"action":"arrivee","id":"026","service":0},
{"action":"arrivee","id":"027","service":3},
{"action":"arrivee","id":"028","service":2},
{"action":"arrivee","id":"029","service":2},
{"action":"arrivee","id":"030","service":2},
{"action":"arrivee","id":"031","service":2},
{"action":"arrivee","id":"032","service":4},
{"action":"arrivee","id":"033","service":4},
{"action":"arrivee","id":"034","service":4},
{"action":"arrivee","id":"035","service":4},
{"action":"contact","id":"4","source":"013","target":"002"},
{"action":"contact","id":"5","source":"009","target":"008"},
{"action":"contact","id":"6","source":"005","target":"007"},
{"action":"contact","id":"7","source":"009","target":"014"},
{"action":"fincontact","id":"7"},
{"action":"fincontact","id":"6"},
{"action":"fincontact","id":"5"},
{"action":"fincontact","id":"4"},
{"action":"fincontact","id":"3"},
{"action":"fincontact","id":"2"},
{"action":"fincontact","id":"1"},
{"action":"depart","id":"016"},
{"action":"depart","id":"015"},
{"action":"depart","id":"014"},
{"action":"depart","id":"013"},
{"action":"depart","id":"012"},
{"action":"depart","id":"011"},
{"action":"depart","id":"010"},
{"action":"depart","id":"009"},
{"action":"depart","id":"008"},
{"action":"depart","id":"007"},
{"action":"depart","id":"006"},
{"action":"depart","id":"005"},
{"action":"depart","id":"004"},
{"action":"depart","id":"003"},
{"action":"depart","id":"002"},
{"action":"depart","id":"018"},
{"action":"depart","id":"019"},
{"action":"depart","id":"020"},
{"action":"depart","id":"021"},
{"action":"depart","id":"022"},
{"action":"depart","id":"023"},
{"action":"depart","id":"024"},
{"action":"depart","id":"025"},
{"action":"depart","id":"026"},
{"action":"depart","id":"027"},
{"action":"depart","id":"028"},
{"action":"depart","id":"029"},
{"action":"depart","id":"030"},
{"action":"depart","id":"031"},
{"action":"depart","id":"032"},
{"action":"depart","id":"033"},
{"action":"depart","id":"034"},
{"action":"depart","id":"035"},
{"action":"depart","id":"001"}]
var vv = window,
w = vv.innerWidth,
h = vv.innerHeight;
var rmax = 30;
foci = [{x: 500, y: 150}, {x: 200, y: 500}, {x: 700, y: 500}, {x: 400, y: 700}, {x: 600, y: 700}];
//canevas selection
var svg = d3.select("#animviz")
.append("svg")
.attr("width", w)
.attr("height", h);
var fill = d3.scale.category10();
//link and node class creation
svg.append("g").attr("class", "links");
svg.append("g").attr("class", "nodes");
//to know if the graphs are up to date
var uptodate = true;
var nIntervId;
//containers de noeuds et liens
var nodes = [], links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w, h])
.friction(0.9)
.charge(-50)
.gravity(0.02)
.linkDistance(50)
.charge(-500)
.on("tick", tick);
var iter = 0;
var node = svg.select(".nodes").selectAll(".node");
var link = svg.select(".links").selectAll(".link");
//repeat an action every "interval"
var interval = 0.2;
nIntervId = setInterval(function() {
var action = readData();
addData(action);
if(!uptodate){
update();
}
}, interval*1000);
function addData(action) {
uptodate = false;
switch(action.action) {
case "arrivee":
nodes.push({id: action.id, service: action.service});
break;
case "depart":
for (var i = nodes.length - 1; i >= 0; i--) {
if(nodes[i].id == action.id){
nodes.splice(i, 1);
}
};
break;
case "contact":
var source;
var target;
for (var i = nodes.length - 1; i >= 0; i--) {
if(nodes[i].id == action.source){
source = nodes[i];
}
if(nodes[i].id == action.target){
target = nodes[i];
}
};
links.push({source:source, target:target, id:action.id});
break;
case "fincontact":
for (var i = links.length - 1; i >= 0; i--) {
if(links[i].id == action.id){
links.splice(i, 1);
}
};
break;
default:
uptodate = true;
}
}
function readData(){
var n = iter;
iter++;
var data = actions[n];
return data;
}
function update() {
force.start();
link = link.data(force.links(), function(d) { return d.source.id+"-"+d.target.id; });
link.enter()
.append("line")
.attr("class", "link")
.attr("stroke", "#ccc")
.attr("stroke-width", 2);
link.exit().remove();
var r = d3.scale.sqrt()
.domain(d3.extent(force.nodes(), function(d) {return d.weight; }))
.range([15, rmax]);
node = node.data(force.nodes(), function(d) { return d.id; });
node.enter()
.append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 20)
.style("stroke-width", "10")
.style("fill", "white")//function(d, i) { return fill(d.service ); })
.style("stroke", function(d, i) { return d3.rgb(i & 1 ? "red" : "green") })
node.append("text")
.attr("text-anchor","middle")
.text(function(d) {return d.id});
node.exit().remove();
}
function tick(e) {
var k = 0.5 * e.alpha;
nodes.forEach(function(o, i) {
o.y += (foci[o.service].y - o.y) * k;
o.x += (foci[o.service].x - o.x) * k;
});
link.attr("x1", function(d) {return d.source.x;})
.attr("y1", function(d) {return d.source.y;})
.attr("x2", function(d) {return d.target.x;})
.attr("y2", function(d) {return d.target.y;});
node.attr("cx", function(d) { return d.x = Math.max(rmax, Math.min(w - rmax, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(rmax, Math.min(h - rmax, d.y)); });
}
readData() read the dataframe, addData() is a big loop to have dynamical moves and update() is the D3 part where is my attempt to label my nodes.
I'm missing something obvious.
Thanks you.
After a few days of searches i'm able to post the working code as an answer.
var actions = [
{"action":"arrivee","id":"001","service":1},
{"action":"arrivee","id":"002","service":1},
{"action":"arrivee","id":"003","service":1},
{"action":"arrivee","id":"004","service":1},
{"action":"arrivee","id":"005","service":1},
{"action":"arrivee","id":"006","service":3},
{"action":"arrivee","id":"007","service":3},
{"action":"arrivee","id":"008","service":3},
{"action":"arrivee","id":"009","service":3},
{"action":"arrivee","id":"010","service":2},
{"action":"arrivee","id":"011","service":2},
{"action":"arrivee","id":"012","service":2},
{"action":"arrivee","id":"013","service":2},
{"action":"arrivee","id":"014","service":4},
{"action":"arrivee","id":"015","service":4},
{"action":"arrivee","id":"016","service":4},
{"action":"arrivee","id":"017","service":4},
{"action":"contact","id":"0","source":"001","target":"017"},
{"action":"contact","id":"1","source":"016","target":"012"},
{"action":"contact","id":"2","source":"004","target":"011"},
{"action":"contact","id":"3","source":"001","target":"010"},
{"action":"fincontact","id":"0"},
{"action":"depart","id":"017"},
{"action":"arrivee","id":"018","service":2},
{"action":"arrivee","id":"019","service":1},
{"action":"arrivee","id":"020","service":1},
{"action":"arrivee","id":"021","service":0},
{"action":"arrivee","id":"022","service":0},
{"action":"arrivee","id":"023","service":0},
{"action":"arrivee","id":"024","service":0},
{"action":"arrivee","id":"025","service":0},
{"action":"arrivee","id":"026","service":0},
{"action":"arrivee","id":"027","service":3},
{"action":"arrivee","id":"028","service":2},
{"action":"arrivee","id":"029","service":2},
{"action":"arrivee","id":"030","service":2},
{"action":"arrivee","id":"031","service":2},
{"action":"arrivee","id":"032","service":4},
{"action":"arrivee","id":"033","service":4},
{"action":"arrivee","id":"034","service":4},
{"action":"arrivee","id":"035","service":4},
{"action":"contact","id":"4","source":"013","target":"002"},
{"action":"contact","id":"5","source":"009","target":"008"},
{"action":"contact","id":"6","source":"005","target":"007"},
{"action":"contact","id":"7","source":"009","target":"014"},
{"action":"fincontact","id":"7"},
{"action":"fincontact","id":"6"},
{"action":"fincontact","id":"5"},
{"action":"fincontact","id":"4"},
{"action":"fincontact","id":"3"},
{"action":"fincontact","id":"2"},
{"action":"fincontact","id":"1"},
{"action":"depart","id":"016"},
{"action":"depart","id":"015"},
{"action":"depart","id":"014"},
{"action":"depart","id":"013"},
{"action":"depart","id":"012"},
{"action":"depart","id":"011"},
{"action":"depart","id":"010"},
{"action":"depart","id":"009"},
{"action":"depart","id":"008"},
{"action":"depart","id":"007"},
{"action":"depart","id":"006"},
{"action":"depart","id":"005"},
{"action":"depart","id":"004"},
{"action":"depart","id":"003"},
{"action":"depart","id":"002"},
{"action":"depart","id":"018"},
{"action":"depart","id":"019"},
{"action":"depart","id":"020"},
{"action":"depart","id":"021"},
{"action":"depart","id":"022"},
{"action":"depart","id":"023"},
{"action":"depart","id":"024"},
{"action":"depart","id":"025"},
{"action":"depart","id":"026"},
{"action":"depart","id":"027"},
{"action":"depart","id":"028"},
{"action":"depart","id":"029"},
{"action":"depart","id":"030"},
{"action":"depart","id":"031"},
{"action":"depart","id":"032"},
{"action":"depart","id":"033"},
{"action":"depart","id":"034"},
{"action":"depart","id":"035"},
{"action":"depart","id":"001"}]
var vv = window,
w = vv.innerWidth,
h = vv.innerHeight;
foci = [{x: 500, y: 150}, {x: 200, y: 500}, {x: 700, y: 500}, {x: 400, y: 700}, {x: 600, y: 700}];
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var links = [], nodes = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w, h])
.friction(0.9)
.charge(-50)
.gravity(0.02)
.linkDistance(50)
.charge(-500)
.on("tick", tick);
var link;
var node;
var iter = 0;
var interval = 0.2;
var uptodate = true;
var nIntervId;
nIntervId = setInterval(function() {
//alert("je passe");
var action = readData();
addData(action);
if(!uptodate){
update();
}
}, interval*1000);
function addData(action) {
uptodate = false;
switch(action.action) {
case "arrivee":
nodes.push({id: action.id, service: action.service});
break;
case "depart":
for (var i = nodes.length - 1; i >= 0; i--) {
if(nodes[i].id == action.id){
nodes.splice(i, 1);
}
};
break;
case "contact":
var source;
var target;
for (var i = nodes.length - 1; i >= 0; i--) {
if(nodes[i].id == action.source){
source = nodes[i];
}
if(nodes[i].id == action.target){
target = nodes[i];
}
};
links.push({source:source, target:target, id:action.id});
break;
case "fincontact":
for (var i = links.length - 1; i >= 0; i--) {
if(links[i].id == action.id){
links.splice(i, 1);
}
};
break;
default:
uptodate = true;
}
}
function readData(){
var n = iter;
iter++;
var data = actions[n];
return data;
}
function update(){
force.start();
link = svg.selectAll(".link")
.data(force.links());
link.enter().append("line")
.attr("class", "link");
link.exit().remove();
node = svg.selectAll(".node")
.data(force.nodes());
node.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 20)
.style("stroke-width", "10")
.style("fill", "white")//function(d, i) { return fill(d.service ); })
.style("stroke", function(d, i) { return d3.rgb(i & 1 ? "red" : "green") });
node.append("text")
.attr("text-anchor","middle")
.text(function(d) { return d.id });
node.exit().remove();
}
function tick(e) {
var k = 0.5 * e.alpha;
nodes.forEach(function(o, i) {
o.y += (foci[o.service].y - o.y) * k;
o.x += (foci[o.service].x - o.x) * k;
});
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
};
Thank you for helping.

d3.js bubble chart animation

I am working on a bubble chart application. At the moment though - my chart appears broken, not sure why.
I will be looking to try and animate the bubbles when new data sets come in.
http://jsfiddle.net/NYEaX/175/
setup: function(rawData, w, h){
var format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([w, h])
.padding(1.5);
var svg = d3.select(methods.el).append("svg")
.attr("width", w)
.attr("height", h)
.attr("class", "bubblechart")
.attr('viewBox', "0 0 "+parseInt(w, 10)+" "+parseInt(h, 10))
.attr('perserveAspectRatio', "xMinYMid");
var root = methods.conformData(rawData);
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: node.name, className: node.name, value: node.size});
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", h + "px");
//this.resizeBubble();
}
Here is the latest bubble chart code. Although its not updating correctly for multiple charts
http://jsfiddle.net/pPMqQ/18/
animateBubbles: function(selector, data){
data = this.funnelData(data, methods.width, methods.height);
var padding = 4;
var maxRadius = d3.max(data, function (d) { return parseInt(d.radius)});
var year_centers = {
"2008": {name:"2008", x: 150, y: 300},
"2009": {name:"2009", x: 550, y: 300},
"2010": {name:"2010", x: 900, y: 300}
}
var all_center = { "all": {name:"All Grants", x: methods.width/2, y: methods.height/2}};
var bubbleholder = d3.select(selector + " .bubbleholder");
var bubbles = d3.select(selector + " .bubbles");
var labelbubble = d3.select(selector + " .labelbubble");
var nodes = bubbles.selectAll("circle")
.data(data);
// Enter
nodes.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.style("fill", function (d) { return methods.fill(d.label); })
.call(methods.force.drag);
// Update
nodes
.transition()
.delay(300)
.duration(1000)
.attr("r", function (d) { return d.radius; })
// Exit
nodes.exit()
.transition()
.duration(250)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.remove();
var labels = labelbubble.selectAll("text")
.data(data);
// Enter
labels.enter()
.append("text")
.attr("class", "title")
.text(function(d) { return d.label; })
.attr("x", function (d) { return d.x; })
.attr("y", function (d) { return d.y; })
// Update
labels
.transition()
.delay(300)
.duration(1000)
// .attr("x", function (d) { return d.x; })
//.attr("y", function (d) { return d.y; })
// Exit
labels.exit()
.transition()
.duration(250)
.remove();
draw('all');
function draw (varname) {
var foci = varname === "all" ? all_center: year_centers;
methods.force.on("tick", tick(foci, varname, .55));
methods.force.start();
}
function tick (foci, varname, k) {
return function (e) {
data.forEach(function(o, i) {
var f = foci[o[varname]];
o.y += (f.y - o.y) * k * e.alpha;
o.x += (f.x - o.x) * k * e.alpha;
});
nodes
.each(collide(.1))
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
labels
.each(collide(.1))
.attr("x", function (d) { return d.x; })
.attr("y", function (d) { return d.y; });
}
}
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
},

Categories