Hi this is my first post. I've searched for a good tutorial to help finish this D3 line chart - but haven't found what I'm looking for. All I need to do is get the orange circle to follow the same path as the dotted line. At the moment it has a straight path. Thanks for any help on this.
<!DOCTYPE html>
<!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]-->
<!--[if lt IE 9]><html class="no-js lte-ie8"><![endif]-->
<!--[if (gt IE 8)|(gt IEMobile 7)|!(IEMobile)|!(IE)]><!-->
<html lang="en"><!--<![endif]-->
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>Drawdown line chart</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
body {font: 15px sans-serif;}
.container{max-width:990px}
h2{float:left; width:100%; text-align:center; font-size:30px; color:#666666; margin:20px 0 30px}
.highlight{color:#f26522}
#chart{width:90%; margin:0 10%}
.domain {fill: none; stroke: gray; stroke-width: 1;}
</style>
</head>
<body>
<div class="container">
<h2>Click the <span class="highlight">orange dot</span> to start:</h2>
<div class="row">
<div id="chart" class="col-sm-12">
</div>
</div>
</div>
<script type="text/javascript">
// Chart data
var dataArray = [
{"x":0, "y":100000},
{"x":1, "y":90000},
{"x":2, "y":83000},
{"x":3, "y":73000},
{"x":4, "y":79000},
{"x":5, "y":72000},
{"x":6, "y":75000},
{"x":7, "y":88000},
{"x":8, "y":63000},
{"x":9, "y":71000},
{"x":10, "y":69000},
{"x":11, "y":63000},
{"x":12, "y":67000},
{"x":13, "y":63000},
{"x":14, "y":59000},
{"x":15, "y":46000},
{"x":16, "y":40000},
{"x":17, "y":32000},
{"x":18, "y":29000},
{"x":19, "y":20000},
{"x":20, "y":18000},
{"x":21, "y":17000},
{"x":22, "y":9000},
{"x":23, "y":0},
{"x":24, "y":0},
{"x":25, "y":0},
{"x":26, "y":0},
{"x":27, "y":0},
{"x":28, "y":0},
{"x":29, "y":0},
{"x":30, "y":0}
];
// Variables
var currentAge = 65
var longevity = 92
var yearsToLive = longevity - currentAge
var years = dataArray.length
var totalDrawdown = dataArray[0].y
var chartWidth = 800
var chartHeight = 400
var chartMargin = 20
var axisHeight = 20
var widthScale = d3.scale.linear()
.domain([currentAge, currentAge + years])
.range([0, chartWidth - chartMargin]);
var axis = d3.svg.axis()
.ticks(5)
.tickSize(20)
.scale(widthScale);
// Chart scaling
x_scale = d3.scale.linear().domain([0,years]).range([0, chartWidth]);
y_scale = d3.scale.linear().domain([0,totalDrawdown]).range([chartHeight - chartMargin,0]);
var lineFunction = d3.svg.line()
.x(function(d) { return x_scale(d.x) })
.y(function(d) { return y_scale(d.y) });
function getSmoothInterpolation() {
var interpolate = d3.scale.linear()
.domain([0,1])
.range([1, dataArray.length + 1]);
return function(t) {
var flooredX = Math.floor(interpolate(t));
var interpolatedLine = dataArray.slice(0, flooredX);
if(flooredX > 0 && flooredX < dataArray.length) {
var weight = interpolate(t) - flooredX;
var weightedLineAverage = dataArray[flooredX].y * weight + dataArray[flooredX-1].y * (1-weight);
interpolatedLine.push({"x":interpolate(t)-1, "y":weightedLineAverage});
}
return lineFunction(interpolatedLine);
}
}
// Canvas
var canvas = d3.select ("#chart")
.append("svg")
.attr("width", chartWidth)
.attr("height", chartHeight + axisHeight)
.attr("id", "lineChart");
// Longevity marker
var rectangle = canvas.append("rect")
.attr("width", (chartWidth/years) * ((currentAge + years) - longevity))
.attr("height", chartHeight - chartMargin)
.attr("x", (chartWidth/years) * (longevity - currentAge) )
.attr("fill","#f2f2f2");
// Destination range
var outer = canvas.append("rect")
.attr("width", 200)
.attr("height", 10)
.attr("x", 525 )
.attr("y", 380 )
.attr("fill","#f2f2f2");
var inner = canvas.append("rect")
.attr("width", 75)
.attr("height", 10)
.attr("x", 588 )
.attr("y", 380 )
.attr("fill","#d1d1d1");
var likely = canvas.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("x", 620 )
.attr("y", 380 )
.attr("fill","#666666");
// Chart path
canvas.append("path")
.attr("stroke-width", 2)
.attr("stroke", "gray")
.attr("fill", "none")
.attr("id", "journey")
.style("stroke-dasharray", ("3, 3"))
.attr("transform", "translate(" + chartMargin + ", 0)")
// Moving circle
var marker = canvas.append("circle")
.attr("id", "marker")
.attr("cx", 5 + chartMargin)
.attr("cy", 10)
.attr("r", 10)
.attr('fill', '#f26522');
// Add x axis
canvas.append("g")
.attr("transform", "translate("+ chartMargin + "," + (chartHeight - chartMargin) + ")")
.attr("fill","#aaaaaa")
.call(axis);
// Add start button
d3.select('#lineChart')
.append('circle')
.attr("cx", 5 + chartMargin)
.attr("cy", 10)
.attr("r", 10)
.attr('fill', '#f26522')
.on('click', function() {
d3.select('#lineChart > #journey')
.transition()
.duration(6000)
.attrTween('d', getSmoothInterpolation );
d3.select('#lineChart > #marker')
.transition()
.duration(6000)
.attrTween("cx", function (d, i, a) { return d3.interpolate(a, 620) })
.attrTween("cy", function (d, i, a) { return d3.interpolate(a, 400) });
});
</script>
</body>
</html>
Here is my try:
Instead of translating the circle via transition like this in the click function:
d3.select('#lineChart > #marker')
.transition()
.duration(6000)
.attrTween("cx", function (d, i, a) { return d3.interpolate(a, 620) })
.attrTween("cy", function (d, i, a) { return d3.interpolate(a, 400) });
Move the circle animation also with in the path animation like below:
function getSmoothInterpolation() {
var interpolate = d3.scale.linear()
.domain([0,1])
.range([1, dataArray.length + 1]);
return function(t) {
var flooredX = Math.floor(interpolate(t));
var interpolatedLine = dataArray.slice(0, flooredX);
if(flooredX > 0 && flooredX < dataArray.length) {
var weight = interpolate(t) - flooredX;
var weightedLineAverage = dataArray[flooredX].y * weight + dataArray[flooredX-1].y * (1-weight);
interpolatedLine.push({"x":interpolate(t)-1, "y":weightedLineAverage});
//get the length of the path
var len = d3.select("#journey").node().getTotalLength();
//get the svg point at that length
var pt = d3.select("#journey").node().getPointAtLength(len);
//translate the circle to that point.
d3.select('#lineChart > #marker').attr("transform", "translate(" +pt.x + "," + pt.y + ")");
}
return lineFunction(interpolatedLine);
}
}
working code here
Related
I was trying to make a grid of rectangles for a heatmap. But for some reason, my rectangles are obstructed by an unknown element:
I thought I accidentally created a rectangle but failed to find it. Where does this white block come from?
Below is a simplified code snippet that demonstrates the same issue.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Graph</title>
<script src="https://d3js.org/d3.v4.js" charset='utf-8'></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js" charset='utf-8'></script>
<script src="https://d3js.org/d3.v4.min.js" charset='utf-8'></script>
</head>
<body>
<svg width="700" height="700" class="Q2">
</svg>
<script type='text/javascript'>
d3.csv("https://91063e21-7b3e-460b-9992-db5d8294e143.filesusr.com/ugd/00e35d_3ff25a705bf649108058b5fd0f8a0506.csv?dn=monthStateGen.csv", DrawGrid);
function DrawGrid(error, data) {
var maxValue = 65000000;
var squareSize = 50;
var squareGap = 5;
var squareBevel = 5;
var monthNum = 12;
var stateNum = 5;
var offsetX = 30;
var height = stateNum * (squareSize + squareGap)
var width = monthNum * (squareSize + squareGap)
var svg = d3.select(".Q2")
.append("svg")
.attr("width", squareSize)
.attr("height", height);
var states = d3.map(data, function(d){return d.STATE;}).keys();
var month = d3.map(data, function(d){return d.MONTH;}).keys();
var x = d3.scaleBand()
.range([ 0, width ])
.domain(month);
var y = d3.scaleBand()
.range([ 0, height])
.domain(states);
svg.selectAll()
.data(data)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.MONTH)+offsetX })
.attr("y", function(d) { return y(d.STATE) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", squareSize )
.attr("height", squareSize )
.attr('opacity', '0.8')
.style("fill", '#333' );
}
</script>
</body>
</html>
I've been changing a few things for you.
doctype
multiple d3 lib referencing
non-composing svg
the width of the svg, which should not only be the square size
<!DOCTYPE html>
<html>
<head>
<title>Graph</title>
<script src="https://d3js.org/d3.v4.min.js" charset='utf-8'></script>
</head>
<body>
<div class="Q2">
</div>
<script type='text/javascript'>
d3.csv("https://91063e21-7b3e-460b-9992-db5d8294e143.filesusr.com/ugd/00e35d_3ff25a705bf649108058b5fd0f8a0506.csv?dn=monthStateGen.csv", DrawGrid);
function DrawGrid(error, data) {
var squareSize = 50;
var squareGap = 5;
var squareBevel = 5;
var monthNum = 12;
var stateNum = 5;
var offsetX = 30;
var height = stateNum * (squareSize + squareGap)
var width = monthNum * (squareSize + squareGap)
var svg = d3.select(".Q2")
.append("svg")
.attr("width", width + (2 * offsetX))
.attr("height", height);
var states = d3.map(data, function(d){return d.STATE;}).keys();
var month = d3.map(data, function(d){return d.MONTH;}).keys();
var x = d3.scaleBand()
.range([ 0, width ])
.domain(month);
var y = d3.scaleBand()
.range([ 0, height])
.domain(states);
svg.selectAll()
.data(data)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.MONTH) +offsetX })
.attr("y", function(d) { return y(d.STATE) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", squareSize )
.attr("height", squareSize )
.attr('opacity', '0.8')
.style("fill", '#333' );
}
</script>
</body>
</html>
I'm working on a map project where we render a map using OSM tiles and d3-tile project. I'm trying to put markers on it. However projection(long,lat) returns weird values which misplaces the markers for instance -0.4777943611111111, -0.3832333211677277 for New York:
newyork = [-74.2605518, 40.6971478];
svg.selectAll("circle")
.data([newyork]).enter()
.append("circle")
.attr("cx", function (d) { console.log(projection(d)); return -projection(d)[0]; })
.attr("cy", function (d) { return -projection(d)[1]; })
. attr("r", "20px")
.attr("fill", "red")
Full source code below
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
margin: 0;
}
</style>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-tile#0.0.4/build/d3-tile.js"></script>
<script>
var tau = 2 * Math.PI;
var width = 960;
height = 500;
// Initialize the projection to fit the world in a 1×1 square centered at the origin.
var projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var tile = d3.tile()
.size([width, height]);
var zoom = d3.zoom()
.on("zoom", zoomed);
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
var raster = svg.append("g");
// Center at US
var center = projection([-98.5, 39.5]);
console.log("Center " + center[0]);
// Apply a zoom transform equivalent to projection.{scale,translate,center}.
svg.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 12)
.translate(-center[0], -center[1]));
newyork = [-74.2605518, 40.6971478];
console.log(projection(newyork))
svg.selectAll("circle")
.data([newyork]).enter()
.append("circle")
.attr("cx", function (d) { console.log(projection(d)); return -projection(d)[0]; })
.attr("cy", function (d) { return -projection(d)[1]; })
. attr("r", "20px")
.attr("fill", "red")
function zoomed() {
var transform = d3.event.transform;
var tiles = tile
.scale(transform.k)
.translate([transform.x, transform.y])
();
var image = raster
.attr("transform", stringify(tiles.scale, tiles.translate))
.selectAll("image")
.data(tiles, function(d) {
return d;
});
image.exit().remove();
// enter:
var entered = image.enter().append("image");
// update:
image = entered.merge(image)
.attr('xlink:href', function(d) {
return 'http://' + 'abc' [d.y % 3] + '.tile.openstreetmap.org/' +
d.z + '/' + d.x + '/' + d.y + '.png';
})
.attr('x', function(d) {
return d.x * 256;
})
.attr('y', function(d) {
return d.y * 256;
})
.attr("width", 256)
.attr("height", 256);
}
function stringify(scale, translate) {
var k = scale / 256,
r = scale % 1 ? Number : Math.round;
return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
</script>
Any help is appreciated. Thanks!
For anyone looking for the answer found it here.: D3 cartography: lon/lat circles in wrong place on map (projection)
The trick is in the zoomed function transform the circle:
function zoomed() {
...
vector
.attr("transform", transform)
.attr("r", 5/transform.k);
...
}
I am trying to do the enter-update-exit pattern on this graph below (which was built with the tremendous help of some very kind ppl here at SO but I am now stuck again unfortunately. I cant make the pattern work but I am certain I pick up the correct object (named heatDotsGroup in the code below).
I can however check in Chrome's developer tools that this object contains the nodes (ellipses) but the pattern doesn't work, therefore clearly I am doing something wrong.
Any ideas please? Many thanks!
function heatmap(dataset) {
var svg = d3.select("#chart")
.select("svg")
var xLabels = [],
yLabels = [];
for (i = 0; i < dataset.length; i++) {
if (i==0){
xLabels.push(dataset[i].xLabel);
var j = 0;
while (dataset[j+1].xLabel == dataset[j].xLabel){
yLabels.push(dataset[j].yLabel);
j++;
}
yLabels.push(dataset[j].yLabel);
} else {
if (dataset[i-1].xLabel == dataset[i].xLabel){
//do nothing
} else {
xLabels.push(dataset[i].xLabel);
}
}
};
var margin = {top: 0, right: 25,
bottom: 60, left: 75};
var width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var dotSpacing = 0,
dotWidth = width/(2*(xLabels.length+1)),
dotHeight = height/(2*yLabels.length);
var daysRange = d3.extent(dataset, function (d) {return d.xKey}),
days = daysRange[1] - daysRange[0];
var hoursRange = d3.extent(dataset, function (d) {return d.yKey}),
hours = hoursRange[1] - hoursRange[0];
var tRange = d3.extent(dataset, function (d) {return d.val}),
tMin = tRange[0],
tMax = tRange[1];
var colors = ['#2C7BB6', '#00A6CA', '#00CCBC', '#90EB9D', '#FFFF8C', '#F9D057', '#F29E2E', '#E76818', '#D7191C'];
// the scale
var scale = {
x: d3.scaleLinear()
.range([-1, width]),
y: d3.scaleLinear()
.range([height, 0]),
};
var xBand = d3.scaleBand().domain(xLabels).range([0, width]),
yBand = d3.scaleBand().domain(yLabels).range([height, 0]);
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => xLabels[d]),
y: d3.axisLeft(scale.y).tickFormat((d, e) => yLabels[d]),
};
function updateScales(data){
scale.x.domain([0, d3.max(data, d => d.xKey)]),
scale.y.domain([ 0, d3.max(data, d => d.yKey)])
}
var colorScale = d3.scaleQuantile()
.domain([0, colors.length - 1, d3.max(dataset, function (d) {return d.val;})])
.range(colors);
var zoom = d3.zoom()
.scaleExtent([1, dotHeight])
.on("zoom", zoomed);
var tooltip = d3.select("body").append("div")
.attr("id", "tooltip")
.style("opacity", 0);
// SVG canvas
svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Clip path
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height+dotHeight);
// Heatmap dots
var heatDotsGroup = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("g");
//Create X axis
var renderXAxis = svg.append("g")
.attr("class", "x axis")
//.attr("transform", "translate(0," + scale.y(-0.5) + ")")
//.call(axis.x)
//Create Y axis
var renderYAxis = svg.append("g")
.attr("class", "y axis")
.call(axis.y);
function zoomed() {
d3.event.transform.y = 0;
d3.event.transform.x = Math.min(d3.event.transform.x, 5);
d3.event.transform.x = Math.max(d3.event.transform.x, (1 - d3.event.transform.k) * width);
// console.log(d3.event.transform)
// update: rescale x axis
renderXAxis.call(axis.x.scale(d3.event.transform.rescaleX(scale.x)));
// Make sure that only the x axis is zoomed
heatDotsGroup.attr("transform", d3.event.transform.toString().replace(/scale\((.*?)\)/, "scale($1, 1)"));
}
svg.call(renderPlot, dataset)
function renderPlot(selection, dataset){
//Do the axes
updateScales(dataset)
selection.select('.y.axis').call(axis.y)
selection.select('.x.axis')
.attr("transform", "translate(0," + scale.y(-0.5) + ")")
.call(axis.x)
// Do the chart
const update = heatDotsGroup.selectAll("ellipse")
.data(dataset);
update
.enter()
.append("ellipse")
.attr("cx", function (d) {return scale.x(d.xKey) - xBand.bandwidth();})
.attr("cy", function (d) {return scale.y(d.yKey) + yBand.bandwidth();})
.attr("rx", dotWidth)
.attr("ry", dotHeight)
.attr("fill", function (d) {
return colorScale(d.val);}
)
.merge(update).transition().duration(800);
update.exit().remove();
}
};
#clickMe{
height:50px;
width:150px;
background-color:lavender;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Heatmap Chart</title>
<!-- Reference style.css -->
<!-- <link rel="stylesheet" type="text/css" href="style.css">-->
<!-- Reference minified version of D3 -->
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
<script src='heatmap_v4.js' type='text/javascript'></script>
</head>
<body>
<input id="clickMe" type="button" value="click me to push new data" onclick="run();" />
<div id='chart'>
<svg width="700" height="500">
<g class="focus">
<g class="xaxis"></g>
<g class="yaxis"></g>
</g>
</svg>
</div>
<script>
function run() {
var dataset = [];
for (let i = 1; i < 360; i++) { //360
for (j = 1; j < 7; j++) { //75
dataset.push({
xKey: i,
xLabel: "xMark " + i,
yKey: j,
yLabel: "yMark " + j,
val: Math.random() * 25,
})
}
};
heatmap(dataset)
}
$(document).ready(function() {});
</script>
</body>
</html>
The issue is that you are not using the same selection each time you run the enter/exit/update cycle. When the button is pushed you:
Generate new data
Run the heatmap function
The heatmap function selects the svg and appends a fresh g called heatDotsGroup
The update function is called and passed the newly created g as a selection
The enter cycle appends everything because the new g is empty.
As a result both the exit and udpate cycles are empty. Try:
console.log(update.size(),update.exit().size()) // *Without any merge*
You should see both are empty each update. This is because all elements are entered each time, which why each update increases the number of ellipses.
I've pulled out a bunch of variable declarations and append statements from the heatmap function, things that only need to be run once (I could go further, but I just did a minimum). I've also merged your update and enter selection prior to setting attributes (as we want to set the new attributes if we update).The below snippet should demonstrate this change.
In the snippet, on button push the following happens:
Generate new data
Run the heatmap function
The heatmap function selects existing selections and doesn't append anything new
The update function is called and passed the selection used by previous update/enter/exit cycles containing any existing nodes.
The update function enters/exits/updates elements as needed based on the existing nodes.
Here's a working version based on the above:
// Things to set/append once:
var svg = d3.select("#chart")
.select("svg")
var margin = {top: 0, right: 25,bottom: 60, left: 75};
var width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
svg = 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 clip = svg.append("clipPath")
.attr("id", "clip")
.append("rect")
var heatDotsGroup = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("g");
var xAxis = svg.append("g").attr("class", "x axis");
var yAxis = svg.append("g").attr("class", "y axis")
function heatmap(dataset) {
var xLabels = [],
yLabels = [];
for (i = 0; i < dataset.length; i++) {
if (i==0){
xLabels.push(dataset[i].xLabel);
var j = 0;
while (dataset[j+1].xLabel == dataset[j].xLabel){
yLabels.push(dataset[j].yLabel);
j++;
}
yLabels.push(dataset[j].yLabel);
} else {
if (dataset[i-1].xLabel == dataset[i].xLabel){
//do nothing
} else {
xLabels.push(dataset[i].xLabel);
}
}
};
var dotSpacing = 0,
dotWidth = width/(2*(xLabels.length+1)),
dotHeight = height/(2*yLabels.length);
var daysRange = d3.extent(dataset, function (d) {return d.xKey}),
days = daysRange[1] - daysRange[0];
var hoursRange = d3.extent(dataset, function (d) {return d.yKey}),
hours = hoursRange[1] - hoursRange[0];
var tRange = d3.extent(dataset, function (d) {return d.val}),
tMin = tRange[0],
tMax = tRange[1];
var colors = ['#2C7BB6', '#00A6CA', '#00CCBC', '#90EB9D', '#FFFF8C', '#F9D057', '#F29E2E', '#E76818', '#D7191C'];
// the scale
var scale = {
x: d3.scaleLinear()
.range([-1, width]),
y: d3.scaleLinear()
.range([height, 0]),
};
var xBand = d3.scaleBand().domain(xLabels).range([0, width]),
yBand = d3.scaleBand().domain(yLabels).range([height, 0]);
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => xLabels[d]),
y: d3.axisLeft(scale.y).tickFormat((d, e) => yLabels[d]),
};
function updateScales(data){
scale.x.domain([0, d3.max(data, d => d.xKey)]),
scale.y.domain([ 0, d3.max(data, d => d.yKey)])
}
var colorScale = d3.scaleQuantile()
.domain([0, colors.length - 1, d3.max(dataset, function (d) {return d.val;})])
.range(colors);
var zoom = d3.zoom()
.scaleExtent([1, dotHeight])
.on("zoom", zoomed);
var tooltip = d3.select("body").append("div")
.attr("id", "tooltip")
.style("opacity", 0);
// SVG canvas
svg.call(zoom);
// Clip path
clip.attr("width", width)
.attr("height", height+dotHeight);
//Create X axis
var renderXAxis = xAxis
//.attr("transform", "translate(0," + scale.y(-0.5) + ")")
//.call(axis.x)
//Create Y axis
var renderYAxis = yAxis.call(axis.y);
function zoomed() {
d3.event.transform.y = 0;
d3.event.transform.x = Math.min(d3.event.transform.x, 5);
d3.event.transform.x = Math.max(d3.event.transform.x, (1 - d3.event.transform.k) * width);
// console.log(d3.event.transform)
// update: rescale x axis
renderXAxis.call(axis.x.scale(d3.event.transform.rescaleX(scale.x)));
// Make sure that only the x axis is zoomed
heatDotsGroup.attr("transform", d3.event.transform.toString().replace(/scale\((.*?)\)/, "scale($1, 1)"));
}
svg.call(renderPlot, dataset)
function renderPlot(selection, dataset){
//Do the axes
updateScales(dataset)
selection.select('.y.axis').call(axis.y)
selection.select('.x.axis')
.attr("transform", "translate(0," + scale.y(-0.5) + ")")
.call(axis.x)
// Do the chart
const update = heatDotsGroup.selectAll("ellipse")
.data(dataset);
update
.enter()
.append("ellipse")
.merge(update)
.attr("cx", function (d) {return scale.x(d.xKey) - xBand.bandwidth();})
.attr("cy", function (d) {return scale.y(d.yKey) + yBand.bandwidth();})
.attr("rx", dotWidth)
.attr("ry", dotHeight)
.attr("fill", function (d) {
return colorScale(d.val);}
)
update.exit().remove();
}
};
#clickMe{
height:50px;
width:150px;
background-color:lavender;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Heatmap Chart</title>
<!-- Reference style.css -->
<!-- <link rel="stylesheet" type="text/css" href="style.css">-->
<!-- Reference minified version of D3 -->
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
<script src='heatmap_v4.js' type='text/javascript'></script>
</head>
<body>
<input id="clickMe" type="button" value="click me to push new data" onclick="run();" />
<div id='chart'>
<svg width="700" height="500">
<g class="focus">
<g class="xaxis"></g>
<g class="yaxis"></g>
</g>
</svg>
</div>
<script>
function run() {
var dataset = [];
for (let i = 1; i < 360; i++) { //360
for (j = 1; j < 7; j++) { //75
dataset.push({
xKey: i,
xLabel: "xMark " + i,
yKey: j,
yLabel: "yMark " + j,
val: Math.random() * 25,
})
}
};
heatmap(dataset)
}
$(document).ready(function() {});
</script>
</body>
</html>
The exit selection here is still empty as the size of the data array is fixed. D3 assumes the new data replaces the old, but it can't know that the new data should be represented as new elements, unless of course we specify a key function as noted in a now deleted comment. This may or may not be the desired functionality that you want.
I have a slightly different approach than Andrew.
A bunch of global variables will get messy when you have multiple charts.
When you click the button:
call the renderPlot(dataset)
check if we have a #clip element in the svg
if not: call heatmap(dataset)
Construct all the static stuff and append to the svg.
append a datum object to the svg with the variables needed for the update
fetch the datum from the svg
update the content of the svg using the datum object
function heatmap(dataset) {
var svg = d3.select("#chart")
.select("svg");
var xLabels = [],
yLabels = [];
for (i = 0; i < dataset.length; i++) {
if (i==0){
xLabels.push(dataset[i].xLabel);
var j = 0;
while (dataset[j+1].xLabel == dataset[j].xLabel){
yLabels.push(dataset[j].yLabel);
j++;
}
yLabels.push(dataset[j].yLabel);
} else {
if (dataset[i-1].xLabel == dataset[i].xLabel){
//do nothing
} else {
xLabels.push(dataset[i].xLabel);
}
}
};
var margin = {top: 0, right: 25,
bottom: 60, left: 75};
var width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var dotSpacing = 0,
dotWidth = width/(2*(xLabels.length+1)),
dotHeight = height/(2*yLabels.length);
var daysRange = d3.extent(dataset, function (d) {return d.xKey}),
days = daysRange[1] - daysRange[0];
var hoursRange = d3.extent(dataset, function (d) {return d.yKey}),
hours = hoursRange[1] - hoursRange[0];
var tRange = d3.extent(dataset, function (d) {return d.val}),
tMin = tRange[0],
tMax = tRange[1];
var colors = ['#2C7BB6', '#00A6CA', '#00CCBC', '#90EB9D', '#FFFF8C', '#F9D057', '#F29E2E', '#E76818', '#D7191C'];
// the scale
var scale = {
x: d3.scaleLinear()
.range([-1, width]),
y: d3.scaleLinear()
.range([height, 0]),
};
var xBand = d3.scaleBand().domain(xLabels).range([0, width]),
yBand = d3.scaleBand().domain(yLabels).range([height, 0]);
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => xLabels[d]),
y: d3.axisLeft(scale.y).tickFormat((d, e) => yLabels[d]),
};
var colorScale = d3.scaleQuantile()
.domain([0, colors.length - 1, d3.max(dataset, function (d) {return d.val;})])
.range(colors);
var zoom = d3.zoom()
.scaleExtent([1, dotHeight])
.on("zoom", zoomed);
var tooltip = d3.select("body").append("div")
.attr("id", "tooltip")
.style("opacity", 0);
// SVG canvas
svg .attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Clip path
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height+dotHeight);
// Heatmap dots
var heatDotsGroup = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("g");
//Create X axis
var renderXAxis = svg.append("g")
.attr("class", "x axis")
//.attr("transform", "translate(0," + scale.y(-0.5) + ")")
//.call(axis.x)
//Create Y axis
var renderYAxis = svg.append("g")
.attr("class", "y axis")
.call(axis.y);
function zoomed() {
d3.event.transform.y = 0;
d3.event.transform.x = Math.min(d3.event.transform.x, 5);
d3.event.transform.x = Math.max(d3.event.transform.x, (1 - d3.event.transform.k) * width);
// console.log(d3.event.transform)
// update: rescale x axis
renderXAxis.call(axis.x.scale(d3.event.transform.rescaleX(scale.x)));
// Make sure that only the x axis is zoomed
heatDotsGroup.attr("transform", d3.event.transform.toString().replace(/scale\((.*?)\)/, "scale($1, 1)"));
}
var chartData = {};
chartData.scale = scale;
chartData.axis = axis;
chartData.xBand = xBand;
chartData.yBand = yBand;
chartData.colorScale = colorScale;
chartData.heatDotsGroup = heatDotsGroup;
chartData.dotWidth = dotWidth;
chartData.dotHeight = dotHeight;
svg.datum(chartData);
//svg.call(renderPlot, dataset)
}
function updateScales(data, scale){
scale.x.domain([0, d3.max(data, d => d.xKey)]),
scale.y.domain([0, d3.max(data, d => d.yKey)])
}
function renderPlot(dataset){
var svg = d3.select("#chart")
.select("svg");
if (svg.select("#clip").empty()) { heatmap(dataset); }
chartData = svg.datum();
//Do the axes
updateScales(dataset, chartData.scale);
svg.select('.y.axis').call(chartData.axis.y)
svg.select('.x.axis')
.attr("transform", "translate(0," + chartData.scale.y(-0.5) + ")")
.call(chartData.axis.x)
// Do the chart
const update = chartData.heatDotsGroup.selectAll("ellipse")
.data(dataset);
update
.enter()
.append("ellipse")
.attr("rx", chartData.dotWidth)
.attr("ry", chartData.dotHeight)
.merge(update)
.transition().duration(800)
.attr("cx", function (d) {return chartData.scale.x(d.xKey) - chartData.xBand.bandwidth();})
.attr("cy", function (d) {return chartData.scale.y(d.yKey) + chartData.yBand.bandwidth();})
.attr("fill", function (d) { return chartData.colorScale(d.val);} );
update.exit().remove();
}
#clickMe{
height:50px;
width:150px;
background-color:lavender;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Heatmap Chart</title>
<!-- Reference style.css -->
<!-- <link rel="stylesheet" type="text/css" href="style.css">-->
<!-- Reference minified version of D3 -->
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
<script src='heatmap_v4.js' type='text/javascript'></script>
</head>
<body>
<input id="clickMe" type="button" value="click me to push new data" onclick="run();" />
<div id='chart'>
<svg width="700" height="500">
<g class="focus">
<g class="xaxis"></g>
<g class="yaxis"></g>
</g>
</svg>
</div>
<script>
function run() {
var dataset = [];
for (let i = 1; i < 360; i++) { //360
for (j = 1; j < 7; j++) { //75
dataset.push({
xKey: i,
xLabel: "xMark " + i,
yKey: j,
yLabel: "yMark " + j,
val: Math.random() * 25,
})
}
};
renderPlot(dataset)
}
$(document).ready(function() {});
</script>
</body>
</html>
I am trying to combine two D3 Visualizations. I found a question before, but it did not really have a solution.
When I combine the two files the visualizations overlap and produce this:
the streamgraph component:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chart {
background: #fff;
}
p {
font: 12px helvetica;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 2px;
shape-rendering: crispEdges;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<div class="chart">
</div>
<script>
chart("Data.csv", "blue");
var datearray = [];
var colorrange = [];
function chart(csvpath, color) {
if (color == "blue") {
colorrange = ["#045A8D", "#2B8CBE", "#74A9CF", "#A6BDDB", "#D0D1E6", "#F1EEF6"];
}
else if (color == "pink") {
colorrange = ["#980043", "#DD1C77", "#DF65B0", "#C994C7", "#D4B9DA", "#F1EEF6"];
}
else if (color == "orange") {
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
}
strokecolor = colorrange[0];
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 40, bottom: 30, left: 50};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var tooltip = d3.select("body")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", "30px")
.style("left", "75px");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var z = d3.scale.ordinal()
.range(colorrange);
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(x)
.ticks(d3.time.years, 10); //tick on every 10 years
/*.scale(x)
.orient("bottom")
.text(date)
//;*/
//. tickFormat(x)
//. tickValues(date)
//was already there but out of view -> changed the left margin
var yAxis = d3.svg.axis()
.scale(y);
var stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select(".chart").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 + ")");
/* correct this function
var graph = d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});*/
var graph = d3.csv(csvpath, function(raw) {
var data = [];
raw.forEach(function (d) {
data.push({
key: d.Country,
date : new Date(1980,0,1), //I had a bug in creating the right dates
value : parseInt(d['1980-1989'].replace(',','')) //get rid of the thousand separator
});
data.push({
key: d.Country,
date : new Date(1990,0,1),
value : parseInt(d['1990-1999'].replace(',',''))
});
data.push({
key: d.Country,
date : new Date(2000,0,1),
value : parseInt(d['2000-2009'].replace(',','') )
});
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
//adding .text causes axis to dissapear
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.text(date)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
//.text(value)
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
var pro;
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})})
.on("mousemove", function(d, i) {
var mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
//find the largest smaller element
var dd = d.values.filter(function(d) { return d.date <= invertedx; });
dd = dd[dd.length -1]; //use the last element
pro = dd.value;
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "visible");
})
.on("mouseout", function(d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "hidden");
})
var vertical = d3.select(".chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", "380px")
.style("top", "10px")
.style("bottom", "30px")
.style("left", "0px")
.style("background", "#fff");
d3.select(".chart")
.on("mousemove", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px" )})
.on("mouseover", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")});
});
}
</script>
Map component:
<!DOCTYPE html>
<meta charset="utf-8">
<title>U.S Immigration Data Visualization</title>
<style>
.country:hover{
stroke: #fff;
stroke-width: 1.5px;
}
.text{
font-size:10px;
text-transform:capitalize;
}
#container {
margin: 10px 10%;
border:2px solid #000;
border-radius: 5px;
height:100%;
overflow:hidden;
background: #e1eafe;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
padding: .5em;
text-shadow: #f5f5f5 0 1px 0;
border-radius: 2px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
opacity: 0.9;
position: absolute;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-width: .5px;
stroke-opacity: .5;
}
.equator {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<br>
<h1><center>U.S Immigration Data Visualization</center></h1>
<h2><b>Work in Progress</b></h2>
<h3><b>Ex-USSR countries included in Russia</b></h3>
<h3><b>Ex-Yugoslavia included in Macedonia</b></h3>
<div id="container"></div>
<script src="js/d3.min.js"></script>
<script src="js/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.geo.tile.v0.min.js"></script>
<script>
d3.select(window).on("resize", throttle);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 9])
.on("zoom", move);
var width = document.getElementById('container').offsetWidth;
var height = width / 2;
var topo,projection,path,svg,g;
var graticule = d3.geo.graticule();
var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");
setup(width,height);
function setup(width,height){
projection = d3.geo.mercator()
.translate([(width/2), (height/2)])
.scale( width / 2 / Math.PI);
path = d3.geo.path().projection(projection);
svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.on("click", click)
.append("g");
g = svg.append("g");
}
d3.json("data/world-topo-min.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
topo = countries;
draw(topo);
});
function draw(topo) {
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
var country = g.selectAll(".country").data(topo);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d,i) { return d.properties.name; })
.style("fill", function(d, i) { return d.properties.color; });
//offsets for tooltips
var offsetL = document.getElementById('container').offsetLeft+20;
var offsetT = document.getElementById('container').offsetTop+10;
//tooltips
country
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(d.properties.name);
})
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});
//EXAMPLE: adding some capitals from external CSV file
d3.csv("Data.csv", function(err, capitals) {
capitals.forEach(function(i){
addpoint(i.CapitalLongitude, i.CapitalLatitude );
});
});
}
function redraw() {
width = document.getElementById('container').offsetWidth;
height = width / 2;
d3.select('svg').remove();
setup(width,height);
draw(topo);
}
function move() {
var t = d3.event.translate;
var s = d3.event.scale;
zscale = s;
var h = height/4;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the country hover stroke width based on zoom level
d3.selectAll(".country").style("stroke-width", 1.5 / s);
}
var throttleTimer;
function throttle() {
window.clearTimeout(throttleTimer);
throttleTimer = window.setTimeout(function() {
redraw();
}, 200);
}
//geo translation on mouse click in map
function click() {
var latlon = projection.invert(d3.mouse(this));
console.log(latlon);
}
//function to add points and text to the map (used in plotting capitals)
function addpoint(lat,lon,text) {
var gpoint = g.append("g").attr("class", "gpoint");
var x = projection([lat,lon])[0];
var y = projection([lat,lon])[1];
gpoint.append("svg:circle")
.attr("cx", x)
.attr("cy", y)
.attr("class","point")
.attr("r", 1);
//conditional in case a point has no associated text
//if(text.length>0){
// gpoint.append("text")
// .attr("x", x+2)
// .attr("y", y+2)
// .attr("class","text")
// .text(text);
//}
}
</script>
</body>
</html>
This is hard to answer without the code you actually use when you 'combine' the two SVG elements, and without the data or a working example.
What I've done is take the 2 basic components, the streamgraph (and svg node inside <div class="chart">) and the map (a separate svg node in <div id="container"></div>), and create working code that combines the 2:
http://plnkr.co/edit/WjlObRIasLYXOuEL4HDE?p=preview
This is the basic code:
<body>
<div class="chart">
</div>
<div id="container">
</div>
<script type="text/javascript">
var width = 300;
var height = width / 2;
// Equivalent of streamgraph code...
var svg_stream = d3.select(".chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("rect")
... // rect attributes
// Equivalent of map code...
var svg_map = d3.select("#container")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("circle")
... // circle attributes
</script>
</body>
What's inside each svg shouldn't make a difference to the positioning, so I've just used a rect to represent the streamgraph and a circle for the map. I've taken as much of the CSS and code from your snippets as it makes sense to. If you combine the two components as in the example above, you should not see any overlap. I'm not really able to correct your version as I don't know how you did it.
Note - You should also avoid defining duplicate variable names (like var svg for both SVGs) when combining the components
I am using aster plot of d3 in my project.
I want legend labels along with the arc radius outside the circle.
I could get an example of piechart showing labels along and outside the arc.
http://bl.ocks.org/Guerino1/2295263
But i am unable to implement the same in aster plot of d3.
http://bl.ocks.org/bbest/2de0e25d4840c68f2db1
Any help would be appreciated.
Thanks
Couple things to fix.
1.) You have to introduce margins into the aster plot for the labels.
2.) You then have to take the outer arcs, add a an svg g do you can group a path with a text:
var outerGroup = svg.selectAll(".solidArc")
.data(pie(data))
.enter()
.append("g")
outerGroup
.append("path")
.attr("fill", function(d) { return d.data.color; })
.attr("class", "solidArc")
.attr("stroke", "gray")
.attr("d", arc)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
outerGroup
.append("text")
.attr("transform", function(d) {
return "translate(" + centroid(60, width, d.startAngle, d.endAngle) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) { return d.data.label });
Note I had to create my own centroid function to move the labels outside the arc. The code in the pie chart example you linked did not work for me (it's using a old d3 version).
Here's my centroid function stolen from the d3 source:
function centroid(innerR, outerR, startAngle, endAngle){
var r = (innerR + outerR) / 2, a = (startAngle + endAngle) / 2 - (Math.PI / 2);
return [ Math.cos(a) * r, Math.sin(a) * r ];
}
Here's a working example.
Full code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Testing Pie Chart</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.4.5"></script>
<style type="text/css">
.slice text {
font-size: 16pt;
font-family: Arial;
}
</style>
</head>
<body>
<script type="text/javascript">
var canvasWidth = 500, //width
canvasHeight = 500, //height
outerRadius = 150, //radius
//outerRadius = Math.min(canvasWidth, canvasHeight) / 2,
color = d3.scale.category20(); //builtin range of colors
innerRadius =0
var colorsArray = ['#0099ff','#cc00ff','#ff3366','#cc3300','#ff6600','#ffff33','#cccc00','#0066ff'];
var dataSet = [
{"legendLabel":"Testing Text Is", "magnitude":30,'score':4.8,width:20,color:colorsArray[0] },
{"legendLabel":"Two", "magnitude":8,'score':3.2,width:20,color:colorsArray[1] },
{"legendLabel":"Three", "magnitude":40,'score':3.9,width:20,color:colorsArray[2] },
{"legendLabel":"Four", "magnitude":50,'score':3.1,width:20,color:colorsArray[3] },
{"legendLabel":"Five", "magnitude":16,'score':4.2,width:20,color:colorsArray[4] },
{"legendLabel":"Six", "magnitude":50,'score':3.1,width:20,color:colorsArray[5] },
{"legendLabel":"Seven", "magnitude":30,'score':4.3,width:20,color:colorsArray[6] },
{"legendLabel":"Eight", "magnitude":20,'score':2.3,width:20,color:colorsArray[7] }
];
var vis = d3.select("body")
.append("svg:svg")
.data([dataSet])
.attr("width", canvasWidth)
.attr("height", canvasHeight)
.append("svg:g")
.attr("transform", "translate(" + 1.5*outerRadius + "," + 1.5*outerRadius + ")") // relocate center of pie to 'outerRadius,outerRadius'
var arc = d3.svg.arc()
.outerRadius(outerRadius);
var arc1 = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(function (d) {
return (outerRadius - innerRadius) * (d.data.score / 5.0) + innerRadius;
});
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.width; });
// Select all <g> elements with class slice (there aren't any yet)
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice");
arcs.append("svg:path")
//set the color for each slice to be chosen from the color function defined above
.attr("fill", function(d, i) { return d.data.color; } )
//this creates the actual SVG path using the associated data (pie) with the arc drawing function
.attr("d", arc1);
var text = arcs.append("svg:text")
.attr("transform", function(d) {
d.outerRadius = outerRadius + 75;
d.innerRadius = outerRadius + 70;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
.style("fill", "black")
.style("font", "bold 12px Arial")
.each(function (d) {
var arr = d.data.legendLabel.split(" ");
if (arr != undefined) {
for (i = 0; i < arr.length; i++) {
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i);
}
}
});
//.text(function(d, i) { return dataSet[i].legendLabel; })
// .html(function(d, i) { return '<tspan>'+dataSet[i].legendLabel+'</tspan></n><tspan>'+dataSet[i].score+'</tspan>'})
/* arcs.append("foreignObject")
.attr("transform", function(d) {
d.outerRadius = outerRadius + 75;
d.innerRadius = outerRadius + 70;
return "translate(" + arc.centroid(d) + ")";
})
.attr("width", 50)
.attr("height", 50)
.append("xhtml:body")
.style("font", "14px 'Helvetica Neue'")
.html(function(d, i) { return dataSet[i].legendLabel+'<br>'+dataSet[i].score; });*/
</script>
</body>
</html>