I'm developing a legend toggling d3.js pie chart application using this jsfiddle as my latest version http://jsfiddle.net/Qh9X5/3328/ .
I am aiming to get a streamlined working example where the legend can toggle the slices, trying to deactivate all slices - resorts in a reset which reactivates all the slices. Splitting up presentation and application layer logic.
Tweening needs improvement too - as the slices pop into existence then re-tween smoothly.
How do I improve/fix the various bugs in this code base?
onLegendClick: function(dt, i){
//_toggle rectangle in legend
var completeData = jQuery.extend(true, [], methods.currentDataSet);
newDataSet = completeData;
if(methods.manipulatedData){
newDataSet = methods.manipulatedData;
}
d3.selectAll('rect')
.data([dt], function(d) {
return d.data.label;
})
.style("fill-opacity", function(d, j) {
var isActive = Math.abs(1-d3.select(this).style("fill-opacity"));
if(isActive){
newDataSet[j].total = completeData[j].total;
}else{
newDataSet[j].total = 0;
}
return isActive;
});
//animate slices
methods.animateSlices(newDataSet);
//stash manipulated data
methods.manipulatedData = newDataSet;
}
Here is the entire js code - I've used the tidyup. I wasn't sure about using the shortcuts as I'm not sure the values will be correct. The latest fiddle - http://jsfiddle.net/Qh9X5/3340/
$(document).ready(function () {
var pieChart = {
el: "",
init: function (el, options) {
var clone = jQuery.extend(true, {}, options["data"]);
pieChart.el = el;
pieChart.setup(clone, options["width"], options["height"], options["r"], options["ir"]);
},
getArc: function (radius, innerradius) {
var arc = d3.svg.arc()
.innerRadius(innerradius)
.outerRadius(radius);
return arc;
},
setup: function (dataset, w, h, r, ir) {
var padding = 80;
this.width = w;
this.height = h;
this.radius = r
this.innerradius = ir;
this.color = d3.scale.category20();
this.pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.total;
});
this.arc = this.getArc(this.radius, this.innerradius);
this.svg = d3.select(pieChart.el["selector"]).append("svg")
.attr("width", this.width + padding)
.attr("height", this.height + padding);
this.holder = this.svg.append("g")
.attr("transform", "translate(" + ((this.width / 2) + (padding / 2)) + "," + ((this.height / 2) + (padding / 2)) + ")");
this.piec = this.holder.append("g")
.attr("class", "piechart");
this.segments = this.holder.append("g")
.attr("class", "segments");
this.labels = this.holder.append("g")
.attr("class", "labels");
this.pointers = this.holder.append("g")
.attr("class", "pointers");
this.legend = this.svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(" + -(this.width / 4) + "," + this.height + ")");
},
oldPieData: "",
pieTween: function (r, ir, d, i) {
var that = this;
var theOldDataInPie = pieChart.oldPieData;
// Interpolate the arcs in data space
var s0;
var e0;
if (theOldDataInPie[i]) {
s0 = theOldDataInPie[i].startAngle;
e0 = theOldDataInPie[i].endAngle;
} else if (!(theOldDataInPie[i]) && theOldDataInPie[i - 1]) {
s0 = theOldDataInPie[i - 1].endAngle;
e0 = theOldDataInPie[i - 1].endAngle;
} else if (!(theOldDataInPie[i - 1]) && theOldDataInPie.length > 0) {
s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle;
e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle;
} else {
s0 = 0;
e0 = 0;
}
var i = d3.interpolate({
startAngle: s0,
endAngle: e0
}, {
startAngle: d.startAngle,
endAngle: d.endAngle
});
return function (t) {
var b = i(t);
return pieChart.getArc(r, ir)(b);
};
},
removePieTween: function (r, ir, d, i) {
var that = this;
s0 = 2 * Math.PI;
e0 = 2 * Math.PI;
var i = d3.interpolate({
startAngle: d.startAngle,
endAngle: d.endAngle
}, {
startAngle: s0,
endAngle: e0
});
return function (t) {
var b = i(t);
return pieChart.getArc(r, ir)(b);
};
},
animateSlices: function (dataSet) {
var r = $(pieChart.el["selector"]).data("r");
var ir = $(pieChart.el["selector"]).data("ir");
this.piedata = pieChart.pie(dataSet);
//__slices
this.path = pieChart.segments.selectAll("path.pie")
.data(this.piedata, function (d) {
return d.data.label
});
this.path.enter().append("path")
.attr("class", "pie")
.attr("fill", function (d, i) {
return pieChart.color(i);
})
.attr("stroke", "#ffffff")
.transition()
.duration(300)
.attrTween("d", function (d, i) {
return pieChart.pieTween(r, ir, d, i);
});
this.path.transition()
.duration(300)
.attrTween("d", function (d, i) {
return pieChart.pieTween(r, ir, d, i);
});
this.path.exit()
.transition()
.duration(300)
.attrTween("d", function (d, i) {
return pieChart.removePieTween(r, ir, d, i);
})
.remove();
//__slices
//__labels
var labels = pieChart.labels.selectAll("text")
.data(this.piedata, function (d) {
return d.data.label
});
labels.enter()
.append("text")
.attr("text-anchor", "middle")
labels.attr("x", function (d) {
var a = d.startAngle + (d.endAngle - d.startAngle) / 2 - Math.PI / 2;
d.cx = Math.cos(a) * (ir + ((r - ir) / 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;
d.cy = Math.sin(a) * (ir + ((r - ir) / 2));
return d.y = Math.sin(a) * (r + 20);
})
.attr("opacity", function (d) {
var opacityLevel = 1;
if (d.value == 0) {
opacityLevel = 0;
}
return opacityLevel;
})
.text(function (d) {
return d.data.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;
})
.transition()
.duration(300)
labels.transition()
.duration(300)
labels.exit().remove();
//__labels
//__pointers
pieChart.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);
var pointers = pieChart.pointers.selectAll("path.pointer")
.data(this.piedata, function (d) {
return d.data.label
});
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
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;
}
})
.attr("opacity", function (d) {
var opacityLevel = 1;
if (d.value == 0) {
opacityLevel = 0;
}
return opacityLevel;
})
.transition()
.duration(300)
pointers.transition()
.duration(300)
pointers.exit().remove();
},
onToggle: function (sliceData, index) {
//_toggle rectangle in legend
//_toggle slice
var completeData = jQuery.extend(true, [], pieChart.currentDataSet);
var dataLength = completeData.length;
var newDataSet = completeData;
if (pieChart.manipulatedData) {
newDataSet = pieChart.manipulatedData;
}
d3.selectAll('rect')
.data([sliceData], function (d) {
return d.data.label;
})
.style("fill-opacity", function (d) {
var isActive = Math.abs(1 - d3.select(this).style("fill-opacity"));
if (isActive) {
newDataSet[index].total = completeData[index].total;
newDataSet[index].value = completeData[index].value;
} else {
newDataSet[index].total = 0;
newDataSet[index].value = 0;
}
return isActive;
});
//if all elements are to be not shown - reset to show all slices again.
//animate slices
pieChart.animateSlices(newDataSet);
//stash manipulated data
pieChart.manipulatedData = newDataSet;
},
update: function (el, dataSet) {
var that = this;
pieChart.el = el;
pieChart.svg = d3.select(pieChart.el["selector"] + " .piechart");
pieChart.segments = d3.select(pieChart.el["selector"] + " .segments");
pieChart.labels = d3.select(pieChart.el["selector"] + " .labels");
pieChart.pointers = d3.select(pieChart.el["selector"] + " .pointers");
pieChart.legend = d3.select(pieChart.el["selector"] + " .legend");
dataSet.forEach(function (d) {
d.total = +d.value;
});
pieChart.currentDataSet = dataSet;
pieChart.animateSlices(dataSet);
//__legends
var w = 200;
// add legend
var legend = pieChart.legend; //.append("g")
var legendRects = legend.selectAll('rect')
.data(this.piedata, function (d) {
return d.data.label
});
legendRects.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 pieChart.color(i);
})
.style("stroke", function (d, i) {
return pieChart.color(i);
})
.on('click', function(d, i){
pieChart.onToggle(d, i);
})
.transition()
.duration(300)
legendRects.style("fill", function (d, i) {
return pieChart.color(i);
})
.style("stroke", function (d, i) {
return pieChart.color(i);
})
.transition()
.duration(300)
legendRects.exit().remove();
var legendText = legend.selectAll('text.label')
.data(this.piedata, function (d) {
return d.data.label
});
legendText.enter()
.append("text")
.attr("class", "label")
.attr("x", w - 52)
.attr("y", function (d, i) {
return i * 20 + 9;
})
.text(function (d) {
return d.data.label;
})
.transition()
.duration(300)
legendText.text(function (d) {
return d.data.label;
})
.transition()
.duration(300)
legendText.exit().remove();
var legendTextVals = legend.selectAll('text.vals')
.data(this.piedata, function (d) {
return d.data.label
});
legendTextVals.enter()
.append("text")
.attr("class", "vals")
.attr("x", w + 20)
.attr("y", function (d, i) {
return i * 20 + 9;
})
.text(function (d) {
return d.data.value;
})
.transition()
.duration(300)
legendTextVals.text(function (d) {
return d.data.value;
})
.transition()
.duration(300)
legendTextVals.exit().remove();
//__pointers
this.oldPieData = this.piedata;
}
};
var dataCharts = [{
"data": [{
"segments": [{
"label": "apple",
"value": 53245
}, {
"label": "cherry",
"value": 145
}, {
"label": "pear",
"value": 2245
}, {
"label": "bananana",
"value": 15325
}]
}]
}, {
"data": [{
"segments": [{
"label": "milk",
"value": 122
}, {
"label": "cheese",
"value": 44
}, {
"label": "grapes",
"value": 533
}]
}]
}, {
"data": [{
"segments": [{
"label": "pineapple",
"value": 1532
}, {
"label": "orange",
"value": 1435
}, {
"label": "grapes",
"value": 22
}]
}]
}, {
"data": [{
"segments": [{
"label": "lemons",
"value": 133
}, {
"label": "mango",
"value": 435
}, {
"label": "melon",
"value": 2122
}]
}]
}];
var clone = jQuery.extend(true, {}, dataCharts);
//__invoke concentric
$('[data-role="piechart"]').each(function (index) {
var selector = "piechart" + index;
$(this).attr("id", selector);
var options = {
data: clone[0].data,
width: $(this).data("width"),
height: $(this).data("height"),
r: $(this).data("r"),
ir: $(this).data("ir")
}
pieChart.init($("#" + selector), options);
pieChart.update($("#" + selector), clone[0].data[0].segments);
});
$(".testers a").on("click", function (e) {
e.preventDefault();
var clone = jQuery.extend(true, {}, dataCharts);
var min = 0;
var max = 3;
//__invoke pie chart
$('[data-role="piechart"]').each(function (index) {
pos = Math.floor(Math.random() * (max - min + 1)) + min;
pieChart.update($("#" + $(this).attr("id")), clone[pos].data[0].segments);
});
});
});
Related
I am trying to make buuble plot according to geo location.All my datas come from local .csv file. First i plotted only with one datasets and it worked fine.Now i have inserted two more datasets and everything is messed up. The code doesnt plot naything at all. I think it could be because i am not using any staements and writing the same code below the previous one.If you could give me some suggestions ,it would be quite helpful.
csv data :link
function createBubbleChart(error, countries, continentNames) {
var airports = countries.map(function(country) { return +country.airports; }), population = countries.map(function(country) { return +country.population; }),median_age = countries.map(function(country) { return +country.median_age; });
var meanairports = d3.mean(airports),meanpopulation = d3.mean(population),meanmedian_age = d3.mean(median_age),
airportsExtent = d3.extent(airports),populationExtent = d3.extent(population),median_ageExtent = d3.extent(median_age),
airportsScaleX,populationScaleX,median_ageScaleX,
airportsScaleY,populationScaleY,median_ageScaleY;
/* var airports = countries.map(function(country){ return +country.airports; });
var meanairports = d3.mean(airports),
airportsExtent = d3.extent(airports),
airportsScaleX,
airportsScaleY;*/
var continents = d3.set(countries.map(function(country) { return country.ContinentCode; }));
var continentColorScale = d3.scaleOrdinal(d3.schemeCategory10)
.domain(continents.values());
var width = 1200,
height = 800;
var svg,
circles,
circleSize = { min: 10, max: 80 };
var circleRadiusScale = d3.scaleSqrt()
.domain(airportsExtent)
.domain(populationExtent)
.domain(median_ageExtent)
.range([circleSize.min, circleSize.max]);
var forces,
forceSimulation;
createSVG();
toggleContinentKey(!flagFill());
createCirclesairports();
createCirclespopulation();
createCirclesmedian_age();
createForces();
createForceSimulation();
addFlagDefinitions();
addFillListenerairports();
addFillListenerpopulation();
addFillListenermedian_age();
addGroupingListeners();
function createSVG() {
svg = d3.select("#bubble-chart")
.append("svg")
.attr("width", width)
.attr("height", height);
}
function toggleContinentKey(showContinentKey) {
var keyElementWidth = 150,
keyElementHeight = 30;
var onScreenYOffset = keyElementHeight*1.5,
offScreenYOffset = 100;
if (d3.select(".continent-key").empty()) {
createContinentKey();
}
var continentKey = d3.select(".continent-key");
if (showContinentKey) {
translateContinentKey("translate(0," + (height - onScreenYOffset) + ")");
} else {
translateContinentKey("translate(0," + (height + offScreenYOffset) + ")");
}
function createContinentKey() {
var keyWidth = keyElementWidth * continents.values().length;
var continentKeyScale = d3.scaleBand()
.domain(continents.values())
.range([(width - keyWidth) / 2, (width + keyWidth) / 2]);
svg.append("g")
.attr("class", "continent-key")
.attr("transform", "translate(0," + (height + offScreenYOffset) + ")")
.selectAll("g")
.data(continents.values())
.enter()
.append("g")
.attr("class", "continent-key-element");
d3.selectAll("g.continent-key-element")
.append("rect")
.attr("width", keyElementWidth)
.attr("height", keyElementHeight)
.attr("x", function(d) { return continentKeyScale(d); })
.attr("fill", function(d) { return continentColorScale(d); });
d3.selectAll("g.continent-key-element")
.append("text")
.attr("text-anchor", "middle")
.attr("x", function(d) { return continentKeyScale(d) + keyElementWidth/2; })
.text(function(d) { return continentNames[d]; });
// The text BBox has non-zero values only after rendering
d3.selectAll("g.continent-key-element text")
.attr("y", function(d) {
var textHeight = this.getBBox().height;
// The BBox.height property includes some extra height we need to remove
var unneededTextHeight = 4;
return ((keyElementHeight + textHeight) / 2) - unneededTextHeight;
});
}
function translateContinentKey(translation) {
continentKey
.transition()
.duration(500)
.attr("transform", translation);
}
}
function flagFill() {
return isChecked("#flags");
}
function isChecked(elementID) {
return d3.select(elementID).property("checked");
}
/* function createCircles(){
createCirclesairport();
}*/
/* function createCircles() {
var formatairports = d3.format(","),formatpopulation = d3.format(","),formatmedian_age = d3.format(",");
circles = svg.selectAll("circle")
.data(countries)
.enter()
.append("circle")
.attr("r", function(d) { return circleRadiusScale(d.airports),(d.population),(d.median_age); })
.on("mouseover", function(d) {
updateCountryInfo(d);
})
.on("mouseout", function(d) {
updateCountryInfo();
});
updateCircles();
function updateCountryInfo(country) {
var info = "";
if (country) {
info = [country.CountryName, formatairports(country.airports),formatpopulation(country.population),formatmedian_age(country.median_age)].join(": ");
}
d3.select("#country-info").html(info);
}
}*/
/* function updateCirclesairport(){
updateCircles();
}*/
function createCirclesairports() {
var formatairports = d3.format(",");
circles = svg.selectAll("circle")
.data(countries)
.enter()
.append("circle")
.attr("r", function(d) { return circleRadiusScale(d.airports); })
.on("mouseover", function(d) {
updateCountryInfoairports(d);
})
.on("mouseout", function(d) {
updateCountryInfoairports();
});
updateCirclesairports();
function updateCountryInfoairports(country) {
var info = "";
if (country) {
info = [country.CountryName, formatairports(country.airports)].join(": ");
}
d3.select("#country-info").html(info);
}
}
function updateCirclesairports() {
circles
.attr("fill", function(d) {
return flagFill() ? "url(#" + d.CountryCode + ")" : continentColorScale(d.ContinentCode);
});
}
function createCirclespopulation() {
var formatpopulation = d3.format(",");
circles = svg.selectAll("circle")
.data(countries)
.enter()
.append("circle")
.attr("r", function(d) { return circleRadiusScale(d.population); })
.on("mouseover", function(d) {
updateCountryInfopopulation(d);
})
.on("mouseout", function(d) {
updateCountryInfopopulation();
});
updateCirclespopulation();
function updateCountryInfopopulation(populationcountry) {
var info = "";
if (country) {
info = [country.CountryName, formatpopulation(country.population)].join(": ");
}
d3.select("#country-info").html(info);
}
}
function updateCirclespopulation() {
circles
.attr("fill", function(d) {
return flagFill() ? "url(#" + d.CountryCode + ")" : continentColorScale(d.ContinentCode);
});
}
function createCirclesmedian_age() {
var formatmedian_age = d3.format(",");
circles = svg.selectAll("circle")
.data(countries)
.enter()
.append("circle")
.attr("r", function(d) { return circleRadiusScale(d.median_age); })
.on("mouseover", function(d) {
updateCountryInfomedian_age(d);
})
.on("mouseout", function(d) {
updateCountryInfomedian_age();
});
updateCirclesmedian_age();
function updateCountryInfomedian_age(country) {
var info = "";
if (country) {
info = [country.CountryName, formatmedian_age(country.median_age)].join(": ");
}
d3.select("#country-info").html(info);
}
}
function updateCirclesmedian_age() {
circles
.attr("fill", function(d) {
return flagFill() ? "url(#" + d.CountryCode + ")" : continentColorScale(d.ContinentCode);
});
}
/*function createForces(){
createForcesairport();
}
*/
function createForces() {
var forceStrength = 0.05;
forces = {
combine: createCombineForces(),
countryCenters: createCountryCenterForces(),
continent: createContinentForces(),
airports: createairportsForces(),
population: createpopulationForces(),
median_age: createmedian_ageForces(),
};
function createCombineForces() {
return {
x: d3.forceX(width / 2).strength(forceStrength),
y: d3.forceY(height / 2).strength(forceStrength)
};
}
function createCountryCenterForces() {
var projectionStretchY = 0.25,
projectionMargin = circleSize.max,
projection = d3.geoEquirectangular()
.scale((width / 2 - projectionMargin) / Math.PI)
.translate([width / 2, height * (1 - projectionStretchY) / 2]);
return {
x: d3.forceX(function(d) {
return projection([d.CenterLongitude, d.CenterLatitude])[0];
}).strength(forceStrength),
y: d3.forceY(function(d) {
return projection([d.CenterLongitude, d.CenterLatitude])[1] * (1 + projectionStretchY);
}).strength(forceStrength)
};
}
function createContinentForces() {
return {
x: d3.forceX(continentForceX).strength(forceStrength),
y: d3.forceY(continentForceY).strength(forceStrength)
};
function continentForceX(d) {
if (d.ContinentCode === "EU") {
return left(width);
} else if (d.ContinentCode === "AF") {
return left(width);
} else if (d.ContinentCode === "AS") {
return right(width);
} else if (d.ContinentCode === "NA" || d.ContinentCode === "SA") {
return right(width);
}
return center(width);
}
function continentForceY(d) {
if (d.ContinentCode === "EU") {
return top(height);
} else if (d.ContinentCode === "AF") {
return bottom(height);
} else if (d.ContinentCode === "AS") {
return top(height);
} else if (d.ContinentCode === "NA" || d.ContinentCode === "SA") {
return bottom(height);
}
return center(height);
}
function left(dimension) { return dimension / 4; }
function center(dimension) { return dimension / 2; }
function right(dimension) { return dimension / 4 * 3; }
function top(dimension) { return dimension / 4; }
function bottom(dimension) { return dimension / 4 * 3; }
}
function createairportsForces() {
var projectionStretchY = 0.25,
projectionMargin = circleSize.max,
projection = d3.geoEquirectangular()
.scale((width / 2 - projectionMargin) / Math.PI)
.translate([width / 2, height * (1 - projectionStretchY) / 2]);
var scaledairportsMargin = circleSize.max;
airportsScaleX = d3.scaleBand()
/*.domain(continentNamesDomain)*/
.range([scaledairportsMargin, width - scaledairportsMargin*2]);
airportsScaleY = d3.scaleLog()
.domain(airportsExtent)
.range([height - scaledairportsMargin, scaledairportsMargin*2]);
var continentNamesDomain = continents.values().map(function(continentCode) {
return continentNames[continentCode];
});
var centerCirclesInScaleBandOffset = airportsScaleX.bandwidth() / 2;
return {
x: d3.forceX(function(d) {
return projection([d.CenterLongitude, d.CenterLatitude])[0];
}).strength(forceStrength),
y: d3.forceY(function(d) {
return projection([d.CenterLongitude, d.CenterLatitude])[1] * (1 + projectionStretchY);
}).strength(forceStrength),
x: d3.forceX(function(d) {
return airportsScaleX(continentNames[d.ContinentCode]) + centerCirclesInScaleBandOffset;
}).strength(forceStrength),
y: d3.forceY(function(d) {
return airportsScaleY(d.airports);
}).strength(forceStrength),
};
/*
var continentNamesDomain = continents.values().map(function(continentCode) {
return continentNames[continentCode];
});
var scaledairportsMargin = circleSize.max;
airportsScaleX = d3.scaleBand()
.domain(continentNamesDomain)
.range([scaledairportsMargin, width - scaledairportsMargin*2]);
airportsScaleY = d3.scaleLog()
.domain(airportsExtent)
.range([height - scaledairportsMargin, scaledairportsMargin*2]);
var centerCirclesInScaleBandOffset = airportsScaleX.bandwidth() / 2;
return {
x: d3.forceX(function(d) {
return airportsScaleX(continentNames[d.ContinentCode]) + centerCirclesInScaleBandOffset;
}).strength(forceStrength),
y: d3.forceY(function(d) {
return airportsScaleY(d.airports);
}).strength(forceStrength)
};*/
}
function createpopulationForces() {
var continentNamesDomain = continents.values().map(function(continentCode) {
return continentNames[continentCode];
});
var scaledpopulationMargin = circleSize.max;
populationScaleX = d3.scaleBand()
.domain(continentNamesDomain)
.range([scaledpopulationMargin, width - scaledpopulationMargin*2]);
populationScaleY = d3.scaleLog()
.domain(populationExtent)
.range([height - scaledpopulationMargin, scaledpopulationMargin*2]);
var centerCirclesInScaleBandOffset = populationScaleX.bandwidth() / 2;
return {
x: d3.forceX(function(d) {
return populationScaleX(continentNames[d.ContinentCode]) + centerCirclesInScaleBandOffset;
}).strength(forceStrength),
y: d3.forceY(function(d) {
return populationScaleY(d.population);
}).strength(forceStrength)
};
}
function createmedian_ageForces() {
var continentNamesDomain = continents.values().map(function(continentCode) {
return continentNames[continentCode];
});
var scaledmedian_ageMargin = circleSize.max;
median_ageScaleX = d3.scaleBand()
.domain(continentNamesDomain)
.range([scaledmedian_ageMargin, width - scaledmedian_ageMargin*2]);
median_ageScaleY = d3.scaleLog()
.domain(median_ageExtent)
.range([height - scaledmedian_ageMargin, scaledmedian_ageMargin*2]);
var centerCirclesInScaleBandOffset = median_ageScaleX.bandwidth() / 2;
return {
x: d3.forceX(function(d) {
return median_ageScaleX(continentNames[d.ContinentCode]) + centerCirclesInScaleBandOffset;
}).strength(forceStrength),
y: d3.forceY(function(d) {
return median_ageScaleY(d.median_age);
}).strength(forceStrength)
};
}
}
function createForceSimulation() {
forceSimulation = d3.forceSimulation()
.force("x", forces.combine.x)
.force("y", forces.combine.y)
.force("collide", d3.forceCollide(forceCollideairports))
.force("collide", d3.forceCollide(forceCollidepopulation))
.force("collide", d3.forceCollide(forceCollidemedian_age));
forceSimulation.nodes(countries)
.on("tick", function() {
circles
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
}
function forceCollideairports(d) {
return countryCenterGrouping() || airportsGrouping() ? 0 : circleRadiusScale(d.airports) + 1 ;
}
function forceCollidepopulation(d) {
return countryCenterGrouping() || populationGrouping() ? 0 : circleRadiusScale(d.population) + 1 ;
}
function forceCollidemedian_age(d) {
return countryCenterGrouping() || median_ageGrouping() ? 0 : circleRadiusScale(d.median_age) + 1 ;
}
function countryCenterGrouping() {
return isChecked("#country-centers");
}
function airportsGrouping() {
return isChecked("#airports");
}
function populationGrouping() {
return isChecked("#population");
}
function median_ageGrouping() {
return isChecked("#median_age");
}
function addFlagDefinitions() {
var defs = svg.append("defs");
defs.selectAll(".flag")
.data(countries)
.enter()
.append("pattern")
.attr("id", function(d) { return d.CountryCode; })
.attr("class", "flag")
.attr("width", "100%")
.attr("height", "100%")
.attr("patternContentUnits", "objectBoundingBox")
.append("image")
.attr("width", 1)
.attr("height", 1)
// xMidYMid: center the image in the circle
// slice: scale the image to fill the circle
.attr("preserveAspectRatio", "xMidYMid slice")
.attr("xlink:href", function(d) {
return "flags/" + d.CountryCode + ".svg";
});
}
/* function addFillListener(){
addFillListenerairport();
}
*/
function addFillListenerairports() {
d3.selectAll('input[name="fill"]')
.on("change", function() {
toggleContinentKey(!flagFill() && !airportsGrouping());
updateCirclesairports();
});
}
function addFillListenerpopulation() {
d3.selectAll('input[name="fill"]')
.on("change", function() {
toggleContinentKey(!flagFill() && !populationGrouping() );
updateCirclespopulation();
});
}
function addFillListenermedian_age() {
d3.selectAll('input[name="fill"]')
.on("change", function() {
toggleContinentKey(!flagFill() && !median_ageGrouping());
updateCirclesmedian_age();
});
}
function addGroupingListeners() {
addListener("#combine", forces.combine);
addListener("#country-centers", forces.countryCenters);
addListener("#continents", forces.continent);
addListener("#airports", forces.airports);
addListener("#population", forces.population);
addListener("#median_age", forces.median_age);
function addListener(selector, forces) {
d3.select(selector).on("click", function() {
updateForces(forces);
toggleContinentKey(!flagFill() && !airportsGrouping() && !populationGrouping() && !median_ageGrouping());
toggleairportsAxes(airportsGrouping()),
togglepopulationAxes(populationGrouping()),
togglemedian_ageAxes(median_ageGrouping());
});
}
function updateForces(forces) {
forceSimulation
.force("x", forces.x)
.force("y", forces.y)
.force("collide", d3.forceCollide(forceCollideairports))
.force("collide", d3.forceCollide(forceCollidepopulation))
.force("collide", d3.forceCollide(forceCollidemedian_age))
.alphaTarget(0.5)
.restart();
}
function toggleairportsAxes(showAxes) {
var onScreenXOffset = 40,
offScreenXOffset = -40;
var onScreenYOffset = 40,
offScreenYOffset = 100;
if (d3.select(".x-axis").empty()) {
createAxes();
}
var xAxis = d3.select(".x-axis"),
yAxis = d3.select(".y-axis");
if (showAxes) {
translateAxis(xAxis, "translate(0," + (height - onScreenYOffset) + ")");
translateAxis(yAxis, "translate(" + onScreenXOffset + ",0)");
} else {
translateAxis(xAxis, "translate(0," + (height + offScreenYOffset) + ")");
translateAxis(yAxis, "translate(" + offScreenXOffset + ",0)");
}
function createAxes() {
var numberOfTicks = 10,
tickFormat = ".0s";
var xAxis = d3.axisBottom(airportsScaleX)
.ticks(numberOfTicks, tickFormat);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (height + offScreenYOffset) + ")")
.call(xAxis)
.selectAll(".tick text")
.attr("font-size", "16px");
var yAxis = d3.axisLeft(airportsScaleY)
.ticks(numberOfTicks, tickFormat);
svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + offScreenXOffset + ",0)")
.call(yAxis);
}
function translateAxis(axis, translation) {
axis
.transition()
.duration(500)
.attr("transform", translation);
}
}
function togglepopulationAxes(showAxes) {
var onScreenXOffset = 40,
offScreenXOffset = -40;
var onScreenYOffset = 40,
offScreenYOffset = 100;
if (d3.select(".x-axis").empty()) {
createAxes();
}
var xAxis = d3.select(".x-axis"),
yAxis = d3.select(".y-axis");
if (showAxes) {
translateAxis(xAxis, "translate(0," + (height - onScreenYOffset) + ")");
translateAxis(yAxis, "translate(" + onScreenXOffset + ",0)");
} else {
translateAxis(xAxis, "translate(0," + (height + offScreenYOffset) + ")");
translateAxis(yAxis, "translate(" + offScreenXOffset + ",0)");
}
function createAxes() {
var numberOfTicks = 10,
tickFormat = ".0s";
var xAxis = d3.axisBottom(populationScaleX)
.ticks(numberOfTicks, tickFormat);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (height + offScreenYOffset) + ")")
.call(xAxis)
.selectAll(".tick text")
.attr("font-size", "16px");
var yAxis = d3.axisLeft(populationScaleY)
.ticks(numberOfTicks, tickFormat);
svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + offScreenXOffset + ",0)")
.call(yAxis);
}
function translateAxis(axis, translation) {
axis
.transition()
.duration(500)
.attr("transform", translation);
}
}
function togglemedian_ageAxes(showAxes) {
var onScreenXOffset = 40,
offScreenXOffset = -40;
var onScreenYOffset = 40,
offScreenYOffset = 100;
if (d3.select(".x-axis").empty()) {
createAxes();
}
var xAxis = d3.select(".x-axis"),
yAxis = d3.select(".y-axis");
if (showAxes) {
translateAxis(xAxis, "translate(0," + (height - onScreenYOffset) + ")");
translateAxis(yAxis, "translate(" + onScreenXOffset + ",0)");
} else {
translateAxis(xAxis, "translate(0," + (height + offScreenYOffset) + ")");
translateAxis(yAxis, "translate(" + offScreenXOffset + ",0)");
}
function createAxes() {
var numberOfTicks = 10,
tickFormat = ".0s";
var xAxis = d3.axisBottom(median_ageScaleX)
.ticks(numberOfTicks, tickFormat);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + (height + offScreenYOffset) + ")")
.call(xAxis)
.selectAll(".tick text")
.attr("font-size", "16px");
var yAxis = d3.axisLeft(median_ageScaleY)
.ticks(numberOfTicks, tickFormat);
svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + offScreenXOffset + ",0)")
.call(yAxis);
}
function translateAxis(axis, translation) {
axis
.transition()
.duration(500)
.attr("transform", translation);
}
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="style.css" rel="stylesheet">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script src="scriptpopulation.js"></script>
<title>D3 Country Bubble Chart</title>
</head>
<body>
<div id="controls">
<span>
<label><input id="country-centers" type="radio" name="grouping" value="country-centers-">Country Centers</label>
<label><input id="population" type="radio" name="grouping" value="population">population</label>
<label><input id="airports" type="radio" name="grouping" value="airports">Airportss</label>
<label><input id="median_age" type="radio" name="grouping" value="median_age">smedian_age</label>
</span>
<span>
<label><input id="colors" type="radio" name="fill" value="colors" checked>Colors</label>
<label><input id="flags" type="radio" name="fill" value="flags">Flags</label>
</span>
</div>
<div id="country-info"></div>
<div id="bubble-chart"></div>
<script>
d3.queue()
.defer(d3.csv, "countries.csv")
.defer(d3.json, "continent-names.json")
.await(createBubbleChart);
</script>
</body>
</html>
strong text
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
I am trying to create this particular d3 application where a series of data can be dynamically displayed like this. Each segment contains two pieces of data.
The first step is to print the circles so there is sufficient space between the series but also the largest circle is always under the smaller circle.
//version 3 -- with correct labels and legend--
http://jsfiddle.net/0ht35rpb/33/
//******version 2 fiddle******
http://jsfiddle.net/1oka61mL/10/
-- How to set the diagonal labels properly - same angles, aligned properly?
-- Add legend?
-- Mask the bottom pointers in an opposite color then continue the line in a different color?
//******Latest Jsfiddle******
http://jsfiddle.net/0ht35rpb/26/
var width = 600;
var height = 400;
var svg = d3.select('svg').attr("width", width).attr("height", height);
//Count
//Checkins
//Popularity
var data = [{
"name": "Twitter",
"items": [{
"id": 0,
"label": "Count",
"value": 200
}, {
"id": 1,
"label": "Checkins",
"value": 1000
}, {
"id": 2,
"label": "Popularity",
"value": 30
}]
}, {
"name": "Facebook",
"items": [{
"id": 0,
"label": "Count",
"value": 500
}, {
"id": 1,
"label": "Checkins",
"value": 300
}, {
"id": 2,
"label": "Popularity",
"value": 740
}]
}, {
"name": "Ebay",
"items": [{
"id": 0,
"label": "Count",
"value": 4000
}, {
"id": 1,
"label": "Checkins",
"value": 1000
}, {
"id": 2,
"label": "Popularity",
"value": 40
}]
}, {
"name": "Foursquare",
"items": [{
"id": 0,
"label": "Count",
"value": 2000
}, {
"id": 1,
"label": "Checkins",
"value": 3000
}, {
"id": 2,
"label": "Popularity",
"value": 4500
}]
}];
var outerRadius = [];
// organise the data.
// Insert indices and sort items in each series
// keep a running total of max circle size in each series
// for later positioning
var x = 0;
var totalWidth = d3.sum(
data.map(function(series) {
series.items.forEach(function(item, i) {
item.index = i;
});
series.items.sort(function(a, b) {
return b.value - a.value;
});
var maxr = Math.sqrt(series.items[0].value);
outerRadius.push(maxr);
x += maxr;
series.xcentre = x;
x += maxr;
return maxr * 2;
})
);
// make scales for position and colour
var scale = d3.scale.linear().domain([0, totalWidth]).range([0, width]);
//var colScale = d3.scale.category10();
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
return colores_g[n % colores_g.length];
}
// add a group per series, position the group according to the values and position scale we calculated above
var groups = svg.selectAll("g").data(data);
groups.enter().append("g");
groups.attr("transform", function(d) {
return ("translate(" + d.xcentre + ",0)");
});
// then add circles per series, biggest first as items are sorted
// colour according to index (the property we inserted previously so we can
// keep track of their original position in the series)
var circles = groups.selectAll("circle").data(function(d) {
return d.items;
}, function(d) {
return d.index;
});
circles.enter().append("circle").attr("cy", height / 2).attr("cx", 0);
circles
.attr("r", function(d) {
return Math.sqrt(d.value);
})
.style("fill", function(d) {
return colores_google(d.index);
});
var labelsgroups = svg.selectAll("text").data(data);
labelsgroups.enter().append("text");
labelsgroups
.attr("y", function(d, i) {
d.y = 300;
d.cy = 200;
return 300;
})
.attr("x", function(d) {
d.x = d.xcentre;
d.cx = d.xcentre;
return d.xcentre;
})
.text(function(d) {
return d.name;
})
.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;
})
.attr("text-anchor", "middle");
var pointersgroups = svg.selectAll("path.pointer").data(data);
pointersgroups.enter().append("path");
pointersgroups
.attr("class", "pointer")
.attr("marker-end", "url(#circ)");
pointersgroups
.attr("d", function(d) {
return "M" + (d.xcentre) + "," + (d.oy - 25) + "L" + (d.xcentre) + "," + (d.sy - 25) + " " + d.xcentre + "," + (d.cy);
})
function fetchValue(items, label) {
for (i = 0; i <= items.length; i++) {
if (items[i].label == label) {
return items[i].value;
}
}
}
function fetchRadius(items, label) {
for (i = 0; i <= items.length; i++) {
if (items[i].label == label) {
return Math.sqrt(items[i].value);
}
}
}
/*
var labels1groups = svg.selectAll(".label1").data(data);
labels1groups.enter().append("text");
labels1groups
.attr("class", "label1")
.attr("y", function(d, i) {
d.y = 100;
d.cy = 100;
return 100;
})
.attr("x", function(d) {
d.x = d.xcentre;
d.cx = d.xcentre+50;
return d.xcentre+50;
})
.text(function(d) {
return fetchValue(d.items, "Count");
})
.attr("transform", function(d, i) {
return "translate(" + (15 * i) + "," + (i * 45) + ") rotate(-45)";
})
.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 ;
})
.attr("text-anchor", "left");
*/
var gridSize = 100;
var labels1groups = svg.selectAll(".label2")
.data(data)
.enter().append("text")
.text(function(d) {
return fetchValue(d.items, "Count");
//return d;
})
.attr("x", function(d, i) {
d.x = i * gridSize + 50;
d.cx = i * gridSize + 50;
return i * gridSize;
})
.attr("y", function(d, i) {
d.y = 105;
d.cy = 50;
return 0;
})
.attr("transform", function(d, i) {
return "translate(" + gridSize / 2 + ", -6)" +
"rotate(-45 " + ((i + 0.5) * gridSize) + " " + (-6) + ")";
})
.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;
})
.style("text-anchor", "end")
.attr("class", function(d, i) {
return ((i >= 8 && i <= 16) ?
"timeLabel mono axis axis-worktime" :
"timeLabel mono axis");
});
var pointers1groups = svg.selectAll("path.pointer1").data(data);
pointers1groups.enter().append("path");
pointers1groups
.attr("class", "pointer1")
.attr("marker-end", "url(#circ)");
pointers1groups
.attr("d", function(d, i) {
//d.y = outerRadius[i];
//d.y = d.oy - d.cy;
//fetchRadius(d.items, "Count");
//(d.xcentre+100)
// + " " + d.cx + "," + d.cy
//return "M "+ (d.xcentre) +" 25 ,L "+ dist +" 75";
return "M" + (d.xcentre) + "," + (d.y + d.oy - fetchRadius(d.items, "Count") - 10) + "L" + (d.xcentre + 80) + "," + d.cy;
})
//Older Jsfiddle
http://jsfiddle.net/59bunh8u/51/
var rawData = [{
"name": "Twitter",
"items" : [
{
"label" : "15 billion",
"unit" : "per day",
"value" : 1500
},
{
"label" : "450 checkins",
"unit" : "per day",
"value" : 450
}
]
},
{
"name": "Facebook",
"items" : [
{
"label" : "5 billion",
"unit" : "per day",
"value" : 5000
},
{
"label" : "2000 checkins",
"unit" : "per day",
"value" : 2000
}
]
}];
$.each(rawData, function(index, value) {
var total = 0;
var layerSet = [];
var ratios = [25, 100];
$.each(value["items"], function(i, v) {
total += v["value"];
});
value["total"] = total;
});
var w = $this.data("width");
var h = $this.data("height");
var el = $this;
var margin = {
top: 65,
right: 90,
bottom: 5,
left: 150
};
var svg = d3.select(el[0]).append("svg")
.attr("class", "series")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var defs = svg.append("svg:defs");
$.each(rawData, function(i, v) {
circleDraw(i, v["items"]);
});
//calculates where each element should be placed
function calculateDistance (d, i, items) {
var dcx = 0;
for (var k = 0; k < i; k++) {
dcx += Math.sqrt(items[k].value);
}
return dcx + 10 * i;
}
function getPercentage(value, total) {
return ((value / total) * 100);
}
function circleDraw(index, data){
data.sort(function(a, b) {
return parseFloat(b.value) - parseFloat(a.value);
});
var circlelayer = svg.append("g")
.attr("class", "circlelayer");
var circle = circlelayer.selectAll("circle")
.data(data);
circle.enter().append("circle")
.attr("class", function(d, i) {
if (i == 0) {
return "blue";
}
return "gold";
})
.attr("cy", 60)
.attr("cx", function(d, i) {
return calculateDistance(d, index, data);
})
.attr("r", function(d, i) {
return Math.sqrt(d.value);
});
circle.exit().remove();
}
Here is how you could drawn the lines:
groups.append("line")
.attr("class", "pointer1")
.attr("marker-end", "url(#circ)")
.attr("x1", 0)
.attr("y1", height / 2)
.attr("x2", 0)
.attr("y2",height - 10);
var linesG = svg.selectAll(".slines").data(data)
.enter().append("g")
.attr("transform", function(d) {
return ("translate(" + d.xcentre + "," + height/2 +") rotate(-45)");
});
linesG.append("line")
.attr("class", "pointer1")
.attr("marker-end", "url(#circ)")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 150)
.attr("y2", 0);
linesG.append("text")
.text(function(d) {
return fetchValue(d.items, "Count");
})
.attr("text-anchor", "end")
.attr("y", -5)
.attr("x", 150);
Updated jsfiddle
I've managed to get the diagonal markers and pointers in alignment, pointing to the correct circle colors to represent that set. I am keen to fine tune this chart and have more control over the padding and chart width/height parameters. The chart looks stable but would be keen to test it with different values and sized data sets.
/LATEST/
http://jsfiddle.net/0ht35rpb/33/
var width = 760;
var height = 400;
var svg = d3.select('#serieschart')
.append("svg:svg")
.attr("width", width)
.attr("height", height);
//Count
//Checkins
//Popularity
var data = [{
"name": "Twitter",
"items": [{
"id": 0,
"label": "Count",
"value": 200
}, {
"id": 1,
"label": "Checkins",
"value": 1000
}, {
"id": 2,
"label": "Popularity",
"value": 30
}]
}, {
"name": "Facebook",
"items": [{
"id": 0,
"label": "Count",
"value": 500
}, {
"id": 1,
"label": "Checkins",
"value": 300
}, {
"id": 2,
"label": "Popularity",
"value": 740
}]
}, {
"name": "Ebay",
"items": [{
"id": 0,
"label": "Count",
"value": 4000
}, {
"id": 1,
"label": "Checkins",
"value": 1000
}, {
"id": 2,
"label": "Popularity",
"value": 40
}]
}, {
"name": "Foursquare",
"items": [{
"id": 0,
"label": "Count",
"value": 2000
}, {
"id": 1,
"label": "Checkins",
"value": 3000
}, {
"id": 2,
"label": "Popularity",
"value": 4500
}]
}];
var legend_group = svg.append("g")
.attr("class", "legend")
.attr("width", 80)
.attr("height", 100)
.append("svg:g")
.attr("class", "legendsection")
.attr("transform", "translate(0,30)");
var legend = legend_group.selectAll("circle").data(data[0].items);
legend.enter().append("circle")
.attr("cx", 70)
.attr("cy", function(d, i) {
return 15 * i;
})
.attr("r", 7)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {
return colores_google(i);
});
legend.exit().remove();
var legendtext = legend_group.selectAll("text").data(data[0].items);
legendtext.enter().append("text")
.attr("class", "labels")
.attr("dy", function(d, i) {
return 15 * i;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
legendtext.exit().remove();
var m = [80, 20, 20, 10];
var w =+ width - m[0];
var h =+ height - m[1];
var chart = svg.append("g")
.attr("class", "serieschart")
.attr("width", w)
.attr("height", h);
var outerRadius = [];
// organise the data.
// Insert indices and sort items in each series
// keep a running total of max circle size in each series
// for later positioning
var x = 0;
var totalWidth = d3.sum(
data.map(function(series) {
series.items.forEach(function(item, i) {
item.index = i;
});
series.items.sort(function(a, b) {
return b.value - a.value;
});
var maxr = Math.sqrt(series.items[0].value);
outerRadius.push(maxr);
x += maxr;
series.xcentre = x;
x += maxr;
return maxr * 2;
})
);
// make scales for position and colour
var scale = d3.scale.linear().domain([0, totalWidth]).range([0, w]);
//var colScale = d3.scale.category10();
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
return colores_g[n % colores_g.length];
}
function fetchValue(items, label) {
for (i = 0; i <= items.length; i++) {
if (items[i].label == label) {
return items[i].value;
}
}
}
function fetchRadius(items, label) {
for (i = 0; i <= items.length; i++) {
if (items[i].label == label) {
return Math.sqrt(items[i].value);
}
}
}
// add a group per series, position the group according to the values and position scale we calculated above
var groups = chart.selectAll("g.seriesGroup").data(data);
var newGroups = groups.enter().append("g").attr("class", "seriesGroup");
newGroups.append("text")
.attr("class", "seriesName")
.attr("text-anchor", "middle");
newGroups.append("line")
.attr("class", "seriesName")
.attr("y1", h - 40)
.attr("y2", h / 2);
newGroups.append("text")
.attr("class", "datumValue")
.attr("y", 10)
//.attr("transform", "rotate(-45)")
;
newGroups.append("g").attr("class", "circleGroup");
newGroups.append("g").attr("class", "datumLine")
.append("line")
.attr("class", "datumValue")
.attr("y2", 40);
var focus = "Count";
groups.attr("transform", function(d) {
return "translate(" + scale(d.xcentre) + ",0)";
});
groups.select("text.seriesName")
.text(function(d) {
return d.name;
})
.attr("y", h - 20);
groups.select("text.datumValue")
.text(function(d) {
return fetchValue(d.items, focus);
})
.attr("transform", function(d) {
return "translate(" + ((h / 2) - 20 - scale(fetchRadius(d.items, focus))) + ",20) rotate(-45)";
});
groups.select("line.datumValue")
.attr("y1", function(d) {
return (h / 2) - scale(fetchRadius(d.items, focus));
})
.attr("x2", function(d) {
return (h / 2) - scale(fetchRadius(d.items, focus) + 20);
});
// then add circles per series, biggest first as items are sorted
// colour according to index (the property we inserted previously so we can
// keep track of their original position in the series)
var circles = groups
.select(".circleGroup")
.selectAll("circle").data(function(d) {
return d.items;
}, function(d) {
return d.index;
});
circles.enter().append("circle").attr("cy", h / 2).attr("cx", 0);
circles
.attr("r", function(d) {
return scale(Math.sqrt(d.value));
})
.style("fill", function(d) {
return colores_google(d.index);
});
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>
http://jsfiddle.net/NYEaX/1791/
In creating a relationship chart - that shows common traits - I am struggling to create the curved arcs that will match the position of the dots.
What is the best way of plotting these arcs so it dips below the horizon?
var data = [{
"userName": "Rihanna",
"userImage": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSTzjaQlkAJswpiRZByvgsb3CVrfNNLLwjFHMrkZ_bzdPOWdxDE2Q",
"userDetails": [{
"Skills & Expertise": [{
"id": 2,
"tag": "Javascript"
}, {
"id": 3,
"tag": "Design"
}],
"Location": [{
"id": 0,
"tag": "London"
}, {
"id": 1,
"tag": "Germany"
}],
"Company": [{
"id": 0,
"tag": "The Old County"
}]
}]
}, {
"userName": "Brad",
"userImage": "https://lh3.googleusercontent.com/-XdASQvEzIzE/AAAAAAAAAAI/AAAAAAAAAls/5vbx7yVLDnc/photo.jpg",
"userDetails": [{
"Skills & Expertise": [{
"id": 0,
"tag": "JAVA"
}, {
"id": 1,
"tag": "PHP"
}, {
"id": 2,
"tag": "Javascript"
}],
"Location": [{
"id": 0,
"tag": "London"
}],
"Company": [{
"id": 0,
"tag": "The Old County"
}, {
"id": 1,
"tag": "Bakerlight"
}]
}]
}]
var viz = d3.select("#viz")
.append("svg")
.attr("width", 600)
.attr("height", 600)
.append("g")
.attr("transform", "translate(40,100)")
var patternsSvg = viz
.append('g')
.attr('class', 'patterns');
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
return colores_g[n % colores_g.length];
}
function getRadius(d) {
var count = d.commonTags.split(",").length;
var ratio = count * 2.3;
if (count == 1) {
ratio = 8;
}
return ratio;
}
//create patterns for user images
$.each(data, function(index, value) {
var defs = patternsSvg.append('svg:defs');
defs.append('svg:pattern')
.attr('id', index + "-" + value.userName.toLowerCase())
.attr('width', 1)
.attr('height', 1)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', 75)
.attr('height', 75);
console.log(value.userDetails[0]);
});
//create common data assement
var data1 = [{
"commonLabel": "Groups",
"commonTags": "test1, test2, test3, test4, test5, test6, test7"
}, {
"commonLabel": "Skills & Expertise",
"commonTags": "test1, test2, test3, test1, test2, test3, test1, test2, test3, test1, test2"
}, {
"commonLabel": "Location",
"commonTags": "test1"
}, {
"commonLabel": "Company",
"commonTags": "test1"
}]
//add curved paths
var distanceBetween = 70;
var pathStart = -400;
var path = viz.append("svg:g").selectAll("path")
.data(data1)
path
.enter().append("svg:path")
.attr("class", function(d) {
return "link "
})
path.attr("d", function(d, i) {
var sx = 0;
var tx = 235;
var sy = 120;
var ty = 120;
pathStart += 125;
var dx = 0;
var dy = getRadius(d) + (distanceBetween * i) - pathStart;
var dr = Math.sqrt(dx * dx + dy * dy);
console.log("dy", dy);
return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
});
//add curved paths
//create circles to hold the user images
var circle = viz.append("svg:g").selectAll("circle")
.data(data);
//enter
circle
.enter()
.append("svg:circle")
.attr("id", function(d) {
return d.userName;
})
.attr("r", function(d) {
return "30";
})
.attr("cx", function(d, i) {
return "235" * i;
})
.attr("cy", function(d, i) {
return "120";
})
.style("fill", function(d, i) {
return "url(#" + i + "-" + d.userName.toLowerCase() + ")";
})
var distanceBetween = 65;
var circle = viz.append("svg:g").selectAll("circle")
.data(data1);
//enter
circle
.enter()
.append("svg:circle")
.attr("id", function(d) {
return d.commonLabel;
})
.attr("r", function(d) {
return getRadius(d);
})
.attr("cx", function(d, i) {
return 125;
})
.attr("cy", function(d, i) {
return distanceBetween * i;
})
.style("fill", function(d, i) {
return colores_google(i);
});
var text = viz.append("svg:g").selectAll("g")
.data(data1)
text
.enter().append("svg:g");
text.append("svg:text")
.attr("text-anchor", "middle")
.attr("x", "125")
.attr("y", function(d, i) {
return getRadius(d) + 15 + (distanceBetween * i);
})
.text(function(d) {
return d.commonLabel;
})
.attr("id", function(d) {
return "text" + d.commonLabel;
});
var counters = viz.append("svg:g").selectAll("g")
.data(data1)
counters
.enter().append("svg:g");
counters.append("svg:text")
.attr("text-anchor", "middle")
.attr("x", "125")
.attr("y", function(d, i) {
return ((getRadius(d) / 2) + (distanceBetween * i)) - 3;
})
.text(function(d) {
var count = d.commonTags.split(",").length;
if (count > 1) {
return count;
}
})
.attr("id", function(d) {
return "textcount" + d.commonLabel;
});
Live demo:
http://jsfiddle.net/blackmiaool/p58a0w3h/1/
First of all, you should remove all the magic numbers to keep the code clean and portable.
This one, for example:
pathStart += 125;
How to draw arcs correctly is a math problem. The code is as blow:
path.attr("d", function (d, i) {
const sx = 0;
const sy = height/2;
const a=width/2;
const b = ((data1.length-1)/2-i)*distanceBetween;
const c = Math.sqrt(a * a + b * b);
const angle=Math.atan(a/b);
let r;
if(b===0){
r=0;
}else{
r=1/2*c/Math.cos(angle);//also equals c/b*(c/2)
// r=c/b*(c/2);
}
return `M${sx},${sy} A${r},${r} 0 0,${b>0?1:0} ${width},${height/2}`;
});
And the diagram: