Related
I would like move the g element include multiple tspan text to enter.
demo()
function demo() {
//subtitle("iptable filter")
// 2. 描画用のデータ準備
var width = 800
var height = 400
var data = {name:"AAA\nBBB",children: [
{name: "CCC\nDDD",children:[
{name:"EEE\nFFF"}
]},
{name: "GGG\nHHH",children:[
{name:"III\nJJJ"}
]},
{name: "KKK\nLLL",children: [
{name: "MMM\nNNN"}
]},
{name: "OOO\nPPP",children:[
{name: "QQQ\nRRR"}
]}
]}
var root = d3.hierarchy(data);
var tree = d3.tree()
.size([height, width])
tree(root);
var margin = {left:80,top:20,right:20,bottom:20}
var svg = d3.select('body').append("svg")
.attr('width',width + margin.left + margin.right)
.attr('height',height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x +
"C" + (d.parent.y + 100) + "," + d.x +
" " + (d.parent.y + 100) + "," + d.parent.x +
" " + d.parent.y + "," + d.parent.x;
});
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
var txtnode = node.append("text")
.attr("text-anchor", 'start')
.attr("dominant-baseline","text-before-edge")//"middle"
.attr("font-size", "200%")
.selectAll('tspan')
.data(d => d.data.name.split('\n'))
.join('tspan')
.attr('class','tspan')
.attr('x',0)
.attr('y',(d,i) => i*25)
.text(d => d)
node.each((d,i,n) =>{
var bbox = d3.select(n[i]).node().getBBox()
var margin = 4
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
d.bbox = bbox
})
node.insert("rect",'text')
.attr('fill','#FEFECE')
.attr('fill','none')
.attr('rx',5.5)
.attr('ry',5.5)
.attr('stroke','#A80036')
.attr('stroke-width',2)
.attr('x',d => d.bbox.x)
.attr('y',d => d.bbox.y)
.attr('width',d => d.bbox.width)
.attr('height',d => d.bbox.height)
node.attr('dx',(d,i,n) => {
var x = d.bbox.width/2
return -x
})
.attr('dy',(d,i,n) => {
var x = d.bbox.width/2
var y = d.bbox.height/2
return -y
})
g.selectAll('.link')
.attr('fill','none')
.attr('stroke','#555')
.attr('stroke-opacity',1)
.attr('stroke-width',4)
}
<script src="https://d3js.org/d3.v6.min.js"></script>
The attribute dx and dy doesn't work in this example. what's the proper way to move the g element to make it move to center?
For repositioning them dynamically, an easy approach is getting the size of the element and translating it up/left by half its height/width:
node.each(function(d) {
const thisSize = this.getBoundingClientRect();
d3.select(this).attr("transform", `translate(${d.y - thisSize.width/2},${d.x - thisSize.height/2})`)
});
Here is your code with that change:
demo()
function demo() {
//subtitle("iptable filter")
// 2. 描画用のデータ準備
var width = 800
var height = 400
var data = {
name: "AAA\nBBB",
children: [{
name: "CCC\nDDD",
children: [{
name: "EEE\nFFF"
}]
},
{
name: "GGG\nHHH",
children: [{
name: "III\nJJJ"
}]
},
{
name: "KKK\nLLL",
children: [{
name: "MMM\nNNN"
}]
},
{
name: "OOO\nPPP",
children: [{
name: "QQQ\nRRR"
}]
}
]
}
var root = d3.hierarchy(data);
var tree = d3.tree()
.size([height, width])
tree(root);
var margin = {
left: 80,
top: 20,
right: 20,
bottom: 20
}
var svg = d3.select('body').append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x +
"C" + (d.parent.y + 100) + "," + d.x +
" " + (d.parent.y + 100) + "," + d.parent.x +
" " + d.parent.y + "," + d.parent.x;
});
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "node");
var txtnode = node.append("text")
.attr("text-anchor", 'start')
.attr("dominant-baseline", "text-before-edge") //"middle"
.attr("font-size", "200%")
.selectAll('tspan')
.data(d => d.data.name.split('\n'))
.join('tspan')
.attr('class', 'tspan')
.attr('x', 0)
.attr('y', (d, i) => i * 25)
.text(d => d)
node.each((d, i, n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 4
bbox.x -= margin
bbox.y -= margin
bbox.width += 2 * margin
bbox.height += 2 * margin
d.bbox = bbox
})
node.insert("rect", 'text')
.attr('fill', '#FEFECE')
.attr('fill', 'none')
.attr('rx', 5.5)
.attr('ry', 5.5)
.attr('stroke', '#A80036')
.attr('stroke-width', 2)
.attr('x', d => d.bbox.x)
.attr('y', d => d.bbox.y)
.attr('width', d => d.bbox.width)
.attr('height', d => d.bbox.height)
node.attr('dx', (d, i, n) => {
var x = d.bbox.width / 2
return -x
})
.attr('dy', (d, i, n) => {
var x = d.bbox.width / 2
var y = d.bbox.height / 2
return -y
})
g.selectAll('.link')
.attr('fill', 'none')
.attr('stroke', '#555')
.attr('stroke-opacity', 1)
.attr('stroke-width', 4)
node.each(function(d) {
const thisSize = this.getBoundingClientRect();
d3.select(this).attr("transform", `translate(${d.y - thisSize.width/2},${d.x - thisSize.height/2})`)
});
}
<script src="https://d3js.org/d3.v6.min.js"></script>
I am working on a d3 application - which features a bubble chart. I have a version which is displaying - but the old force code from version 3 - but I am unsure how to incorporate version 4 force effects. I want to give the bubbles a bit of animation - charge/gravity type effects so there is always some movement.
//old code with no force effects
http://jsfiddle.net/xzd9eamt/2/
var $this = $('.bubblechart');
var data = [{
"label": "Chinese",
"value": 20
}, {
"label": "American",
"value": 10
}, {
"label": "Indian",
"value": 50
}];
var width = $this.data('width'),
height = $this.data('height');
var color = d3.scaleOrdinal()
.range(["#ff5200", "red", "green"]);
var margin = {
top: 20,
right: 15,
bottom: 30,
left: 20
},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var svg = d3.select($this[0])
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr('class', 'bubblechart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bubbles = svg.append('g').attr('class', 'bubbles');
var force = d3.forceSimulation()
.force("collide", d3.forceCollide(12))
.force("center", d3.forceCenter(width / 2, height / 2))
.nodes(data)
//.on("tick", tick);
var bubbles = svg.append("g")
.attr("class", "bubbles")
data = funnelData(data, width, height);
var padding = 4;
var maxRadius = d3.max(data, function(d) {
return parseInt(d.radius)
});
var scale = (width / 6) / 100;
var nodes = bubbles.selectAll("circle")
.data(data);
// Enter
nodes.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", 10)
.style("fill", function(d, i) {
return color(i);
})
//.call(d3.drag());
// Update
nodes
.transition()
.delay(300)
.duration(1000)
.attr("r", function(d) {
return d.radius * scale;
})
// Exit
nodes.exit()
.transition()
.duration(250)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", 1)
.remove();
draw('all');
function funnelData(data, width, height) {
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var max_amount = d3.max(data, function(d) {
return parseInt(d.value)
})
var radius_scale = d3.scalePow().exponent(0.5).domain([0, max_amount]).range([2, 85])
$.each(data, function(index, elem) {
elem.radius = radius_scale(elem.value) * .8;
elem.all = 'all';
elem.x = getRandom(0, width);
elem.y = getRandom(0, height);
});
return data;
}
function draw(varname) {
var foci = {
"all": {
name: "All",
x: width / 2,
y: height / 2
}
};
//force.on("tick", tick(foci, varname, .55));
//force.start();
}
function tick(foci, varname, k) {
return function(e) {
data.forEach(function(o, i) {
var f = foci[o[varname]];
o.y += (f.y - o.y) * k * e.alpha;
o.x += (f.x - o.x) * k * e.alpha;
});
nodes
.each(collide(.1))
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
}
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
body {
background: #eeeeee;
}
.line {
fill: none;
stroke-width: 2px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<h1>BubbleChart I</h1>
<div class="bubblechart" data-width="300" data-height="300" />
There are a couple of things you need to know with d3 versus d4.
Firstly, nodes was empty, because the new nodes were not added to the selection automatically. Since d3 v4, you need to call .merge() to combine the update and enter parts of a selection. This article is a really good summary.
Secondly, there is no need to do all this data wrangling yourself. d3-force updates d.x and d.y automatically with the correct values, so you can remove the collide function and the part where you calculate d.x and d.y. All you need to do is just draw the circles at the correct place.
var $this = $('.bubblechart');
var data = [{
"label": "Chinese",
"value": 20
}, {
"label": "American",
"value": 10
}, {
"label": "Indian",
"value": 50
}];
var width = $this.data('width'),
height = $this.data('height');
var color = d3.scaleOrdinal()
.range(["#ff5200", "red", "green"]);
var margin = {
top: 20,
right: 15,
bottom: 30,
left: 20
},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var svg = d3.select($this[0])
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr('class', 'bubblechart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bubbles = svg.append('g').attr('class', 'bubbles');
var force = d3.forceSimulation()
.force("collide", d3.forceCollide(12))
.force("center", d3.forceCenter(width / 2, height / 2))
.nodes(data);
var bubbles = svg.append("g")
.attr("class", "bubbles")
data = funnelData(data, width, height);
var padding = 4;
var maxRadius = d3.max(data, function(d) {
return parseInt(d.radius)
});
var scale = (width / 6) / 100;
var nodes = bubbles.selectAll("circle")
.data(data);
// Enter
nodes.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", 10)
.style("fill", function(d, i) {
return color(i);
})
.call(d3.drag());
// Update
nodes
.transition()
.delay(300)
.duration(1000)
.attr("r", function(d) {
return d.radius * scale;
})
// Exit
nodes.exit()
.transition()
.duration(250)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", 1)
.remove();
draw('all');
function funnelData(data, width, height) {
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var max_amount = d3.max(data, function(d) {
return parseInt(d.value)
})
var radius_scale = d3.scalePow().exponent(0.5).domain([0, max_amount]).range([2, 85])
$.each(data, function(index, elem) {
elem.radius = radius_scale(elem.value) * .8;
elem.all = 'all';
elem.x = width / 2;
elem.y = height / 2;
});
return data;
}
function draw(varname) {
var foci = {
"all": {
name: "All",
x: width / 2,
y: height / 2
}
};
force.on("tick", tick(foci, varname, .55));
}
function tick(foci, varname, k) {
return function(e) {
bubbles.selectAll("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
}
body {
background: #eeeeee;
}
.line {
fill: none;
stroke-width: 2px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<h1>BubbleChart I</h1>
<div class="bubblechart" data-width="300" data-height="300" />
I just managed to convert the respective heatmap from SVG to canvas. (due to large dataset vs performance issue) However the position of the generated heatmap has goes to a new region. I not sure how am I going to do about this. By changing the transform does not change anything as well.
My code:
var units = [];
for(var unit_i = 0; unit_i<=101;){
if(unit_i==0){
units.push(1);
unit_i = unit_i + 5;
}
else{
units.push(unit_i);
unit_i = unit_i + 4;
}
}
var times = [];
for(var times_i = 0; times_i<=1440;){
if(times_i==0){
times.push(1);
times_i = times_i + 10;
}
else{
times.push(times_i);
times_i = times_i + 9;
}
}
var newSample = [{unit:null, timestamp: null, level: null}];
//by using below method we can observe the delay is not due to the data during insertion
for(var unit=1; unit<=99; unit++){
for(var timestamp = 1; timestamp<=100; timestamp++){
var i = Math.random() * 1400;
newSample.push({unit:unit, timestamp: timestamp, level:i});
}
}
var hours = 0;
var hoursIndicator = 0;
var margin = {
top: 170,
right: 100,
bottom: 70,
left: 100
};
var width = 2500,//optimized width
//gridSize = Math.floor(width / times.length),//optimized gridsize
gridSize = 10;//if 20 each interval will have 5
height = 50 * (units.length); //optimized, the greater the length, the greater the height
console.log("this is gridSize:" + gridSize +", this is height: " + height + ", and this is width: " + width);
//SVG container
var svg = d3.select('.trafficCongestions')
.append("svg")
//.style("position", "absolute")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom)//optimization
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = d3.select('.trafficCongestions').append("canvas")
.attr("id", "canvas")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom);//optimization
var context = canvas.node().getContext("2d");
context.clearRect(0, 0, width, height);
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
//Reset the overall font size
var newFontSize = width * 62.5 / 900;
//heatmap drawing starts from here
var colorScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d, i) {return d.level; })/2, d3.max(newSample, function(d, i) {return d.level; })])
.range(["#009933", "#FFCC00", "#990000"])//obtain the max data value of count
//y-axis (solely based on data of days)
var dayLabels = svg.selectAll(".dayLabel")
.data(units)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) {
return (i) * (gridSize * 4)/*adjusts the interval distance with (n - 1) concept*/; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize + ")");
//x-axis (solely based on data of times)
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d, i) {
var hrs = Math.floor(d/60);
var mins = d%60;
if(hrs<10){
if(mins<10){
return "0"+hrs + ":0" + mins;
}
return "0"+ hrs + ":" + mins;
}
return hrs +":"+ mins;
})
.attr("x", function(d, i) { return i * (gridSize * 9)/*adjusts the interval distance with (n - 1) concept*/; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + 1 + ", -6)")
var heatMap = dataContainer.selectAll("custom.rect")
.data(newSample)
.enter().append("custom")
.attr("x", function(d) {
return (d.timestamp - 1) * (gridSize);
})
.attr("y", function(d) {
console.log(d.unit);
return (d.unit - 1) * (gridSize);
})
.classed("rect", true)
.attr("class", " rect bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.attr("strokeStyle", "rgba(255,255,255, 0.6)")//to have the middle line or not
.attr("fillStyle", function(d,i){
return colorScale(d.level);
});
drawCanvas();
//Append title to the top
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -90)
.style("text-anchor", "middle")
.text("Sample Result");
svg.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -60)
.style("text-anchor", "middle")
.text("HEATMAP");
//Append credit at bottom
svg.append("text")
.attr("class", "credit")
.attr("x", width/2)
.attr("y", gridSize * (units.length+1) + 80)
.style("text-anchor", "middle");
//Extra scale since the color scale is interpolated
var countScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d) {return d.level; })])
.range([0, width])
//Calculate the variables for the temp gradient
var numStops = 10;
countRange = countScale.domain();
countRange[2] = countRange[1] - countRange[0];
countPoint = [];
for(var i = 0; i < numStops; i++) {
countPoint.push(i * countRange[2]/(numStops-1) + countRange[0]);
}//for i
//Create the gradient
svg.append("defs")
.append("linearGradient")
.attr("id", "legendLevel")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(d3.range(numStops))
.enter().append("stop")
.attr("offset", function(d,i) {
return countScale( countPoint[i] )/width;
})
.attr("stop-color", function(d,i) {
return colorScale( countPoint[i] );
});
var legendWidth = Math.min(width, 400);//the width of the legend
console.log(width);
//Color Legend container
var legendsvg = svg.append("g")
.attr("class", "legendWrapper")
.attr("transform", "translate(" + (width/2) + "," + (gridSize * 100 + 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", 10)
.style("fill", "url(#legendLevel)");
//Append title
legendsvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -10)
.style("text-anchor", "middle")
.text("Level");
//Set scale for x-axis
var xScale = d3.scaleLinear()
.range([-legendWidth/2, legendWidth/2])
.domain([ 0, d3.max(newSample, function(d) { return d.level; })] );
//Define x-axis
var xAxis = d3.axisBottom()
.ticks(5)
.scale(xScale);
//Set up X axis
legendsvg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (10) + ")")
.call(xAxis);
function drawCanvas(){
var elements = dataContainer.selectAll("custom.rect");
elements.each(function(d){
var node = d3.select(this);
context.beginPath();
context.fillStyle = node.attr("fillStyle");
context.rect(node.attr("x"), node.attr("y"), node.attr("width"), node.attr("height"));
context.fill();
context.closePath();
});
}
html { font-size: 100%; }
.timeLabel, .dayLabel {
font-size: 1rem;
fill: #AAAAAA;
font-weight: 300;
}
.title {
font-size: 1.8rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 1.0rem;
fill: #AAAAAA;
font-weight: 300;
}
.credit {
font-size: 1.2rem;
fill: #AAAAAA;
font-weight: 400;
}
.axis path, .axis tick, .axis line {
fill: none;
stroke: none;
}
.legendTitle {
font-size: 1.3rem;
fill: #4F4F4F;
font-weight: 300;
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="trafficCongestions" class="trafficCongestions"></div>
As you're using svg to just show up the text and legend, I'd say you can absolute position the canvas on top of the SVG - by CSS.
Here are the CSS changes:
div#trafficCongestions {
position: relative;
}
canvas {
position: absolute;
top: 170px;
left: 100px;
}
You can do the above using d3 style as well `cause the margins are defined in the script. I just wanted to show that this is an option to use.
var units = [];
for(var unit_i = 0; unit_i<=101;){
if(unit_i==0){
units.push(1);
unit_i = unit_i + 5;
}
else{
units.push(unit_i);
unit_i = unit_i + 4;
}
}
var times = [];
for(var times_i = 0; times_i<=1440;){
if(times_i==0){
times.push(1);
times_i = times_i + 10;
}
else{
times.push(times_i);
times_i = times_i + 9;
}
}
var newSample = [{unit:null, timestamp: null, level: null}];
//by using below method we can observe the delay is not due to the data during insertion
for(var unit=1; unit<=99; unit++){
for(var timestamp = 1; timestamp<=100; timestamp++){
var i = Math.random() * 1400;
newSample.push({unit:unit, timestamp: timestamp, level:i});
}
}
var hours = 0;
var hoursIndicator = 0;
var margin = {
top: 170,
right: 100,
bottom: 70,
left: 100
};
var width = 2500,//optimized width
//gridSize = Math.floor(width / times.length),//optimized gridsize
gridSize = 10;//if 20 each interval will have 5
height = 50 * (units.length); //optimized, the greater the length, the greater the height
//console.log("this is gridSize:" + gridSize +", this is height: " + height + ", and this is width: " + width);
//SVG container
var svg = d3.select('.trafficCongestions')
.append("svg")
//.style("position", "absolute")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom)//optimization
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var canvas = d3.select('.trafficCongestions').append("canvas")
.attr("id", "canvas")
.attr("width", width + margin.left + margin.right)//optimization
.attr("height", height + margin.top + margin.bottom);//optimization
var context = canvas.node().getContext("2d");
context.clearRect(0, 0, width, height);
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
//Reset the overall font size
var newFontSize = width * 62.5 / 900;
//heatmap drawing starts from here
var colorScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d, i) {return d.level; })/2, d3.max(newSample, function(d, i) {return d.level; })])
.range(["#009933", "#FFCC00", "#990000"])//obtain the max data value of count
//y-axis (solely based on data of days)
var dayLabels = svg.selectAll(".dayLabel")
.data(units)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) {
return (i) * (gridSize * 4)/*adjusts the interval distance with (n - 1) concept*/; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize + ")");
//x-axis (solely based on data of times)
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d, i) {
var hrs = Math.floor(d/60);
var mins = d%60;
if(hrs<10){
if(mins<10){
return "0"+hrs + ":0" + mins;
}
return "0"+ hrs + ":" + mins;
}
return hrs +":"+ mins;
})
.attr("x", function(d, i) { return i * (gridSize * 9)/*adjusts the interval distance with (n - 1) concept*/; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + 1 + ", -6)")
var heatMap = dataContainer.selectAll("custom.rect")
.data(newSample)
.enter().append("custom")
.attr("x", function(d) {
return (d.timestamp - 1) * (gridSize);
})
.attr("y", function(d) {
//console.log(d.unit);
return (d.unit - 1) * (gridSize);
})
.classed("rect", true)
.attr("class", " rect bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.attr("strokeStyle", "rgba(255,255,255, 0.6)")//to have the middle line or not
.attr("fillStyle", function(d,i){
return colorScale(d.level);
});
drawCanvas();
//Append title to the top
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -90)
.style("text-anchor", "middle")
.text("Sample Result");
svg.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -60)
.style("text-anchor", "middle")
.text("HEATMAP");
//Append credit at bottom
svg.append("text")
.attr("class", "credit")
.attr("x", width/2)
.attr("y", gridSize * (units.length+1) + 80)
.style("text-anchor", "middle");
//Extra scale since the color scale is interpolated
var countScale = d3.scaleLinear()
.domain([0, d3.max(newSample, function(d) {return d.level; })])
.range([0, width])
//Calculate the variables for the temp gradient
var numStops = 10;
countRange = countScale.domain();
countRange[2] = countRange[1] - countRange[0];
countPoint = [];
for(var i = 0; i < numStops; i++) {
countPoint.push(i * countRange[2]/(numStops-1) + countRange[0]);
}//for i
//Create the gradient
svg.append("defs")
.append("linearGradient")
.attr("id", "legendLevel")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(d3.range(numStops))
.enter().append("stop")
.attr("offset", function(d,i) {
return countScale( countPoint[i] )/width;
})
.attr("stop-color", function(d,i) {
return colorScale( countPoint[i] );
});
var legendWidth = Math.min(width, 400);//the width of the legend
//console.log(width);
//Color Legend container
var legendsvg = svg.append("g")
.attr("class", "legendWrapper")
.attr("transform", "translate(" + (width/2) + "," + (gridSize * 100 + 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", 10)
.style("fill", "url(#legendLevel)");
//Append title
legendsvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -10)
.style("text-anchor", "middle")
.text("Level");
//Set scale for x-axis
var xScale = d3.scaleLinear()
.range([-legendWidth/2, legendWidth/2])
.domain([ 0, d3.max(newSample, function(d) { return d.level; })] );
//Define x-axis
var xAxis = d3.axisBottom()
.ticks(5)
.scale(xScale);
//Set up X axis
legendsvg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (10) + ")")
.call(xAxis);
function drawCanvas(){
var elements = dataContainer.selectAll("custom.rect");
elements.each(function(d){
var node = d3.select(this);
context.beginPath();
context.fillStyle = node.attr("fillStyle");
context.rect(node.attr("x"), node.attr("y"), node.attr("width"), node.attr("height"));
context.fill();
context.closePath();
});
}
html { font-size: 100%; }
div#trafficCongestions {
position: relative;
}
canvas {
position: absolute;
top: 170px;
left: 100px;
}
.timeLabel, .dayLabel {
font-size: 1rem;
fill: #AAAAAA;
font-weight: 300;
}
.title {
font-size: 1.8rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 1.0rem;
fill: #AAAAAA;
font-weight: 300;
}
.credit {
font-size: 1.2rem;
fill: #AAAAAA;
font-weight: 400;
}
.axis path, .axis tick, .axis line {
fill: none;
stroke: none;
}
.legendTitle {
font-size: 1.3rem;
fill: #4F4F4F;
font-weight: 300;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="trafficCongestions" class="trafficCongestions"></div>
Let me know if this makes sense. If not, let's look for other approaches.
Edit as per comments: (visual studio didn't support the CSS styling added by above approach)
Added the style using d3:
canvas.style('position', 'absolute').style('top', margin.top+'px').style('left', margin.left+'px')
And this worked.
I have groups(svg groups) of different height, I want these groups of svg not to overlap each other and have uniform spacing between them.
Following is a link to my jsfiddle.
https://jsfiddle.net/3jxqgjcL/1/
var margin = { top: 10, right: 10, bottom: 30, left: 10 },
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
distance = 0, barHeight = 75, i = 0;
function yAxis() {
if (i == 0) {
i++;
return 2;
} else {
distance = parseInt(barHeight) * i;
i++;
return distance;
}
}
var rectangles = d3.range(5).map(function() {
return {
x: 5,
y: Math.round(yAxis())
};
});
var color = d3.scaleOrdinal(d3.schemeCategory20);
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 group = svg.selectAll('g')
.data(rectangles)
.enter().append("g")
.attr("class", "groups")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.append("rect")
.attr("height", function(d,i){return Math.round(Math.random() * 100)})
.attr("width", 360)
.style("fill", function(d, i) {
return color(i);
});
var getheight = d3.select('g').node();
console.log("getheight"+ getheight.getBoundingClientRect().height);
group.append("text")
.attr("text-anchor", "start")
.style("fill", "steelblue")
.text("Close");
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).attr("transform", "translate(" + margin.left / 2 + "," + (d.y = d3.event.y) + ")");
}
function dragended(d) {
d3.select(this).classed("active", false);
var theseGroups = svg.selectAll(".groups").sort(function(a, b) {
return d3.ascending(a.y, b.y);
});
theseGroups.attr("transform", function(d, i) {
return "translate(" + margin.left / 2 + "," + (d.y = barHeight * i) + ")";
})
}
var margin = {top: 30, right: 20, bottom: 30, left: 70},
h= 500;
w = 960;
ruleColor = "#CCC";
minVal = 0;
maxVal = 100;
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var viz = d3.select("#radar")
.append('svg:svg')
.attr('width', w)
.attr('height', h)
.attr('class', 'vizSvg');
viz.append("svg:rect")
.attr('id', 'axis-separator')
.attr('x', 0)
.attr('y', 0)
.attr('height', 0)
.attr('width', 0)
.attr('height', 0);
vizBody = viz.append("svg:g")
.attr('id', 'body');
var heightCircleConstraint = 500 - margin.top - margin.bottom;
var widthCircleConstraint = width = 960 - margin.left - margin.right,
circleConstraint = d3.min([heightCircleConstraint, widthCircleConstraint]);
var radius = d3.scale.linear().domain([minVal, maxVal]).range([0, (circleConstraint / 2)]);
var radiusLength = radius(maxVal);
var centerXPos = widthCircleConstraint / 2 + margin.left;
var centerYPos = heightCircleConstraint / 2 + margin.top;
vizBody.attr("transform",
"translate(" + centerXPos + ", " + centerYPos + ")");
d3.json("data/radar.json", function(error, data) {
var hours = [];
var series = [[]];
data.QualitySummaryObject.forEach(function(d,i) {
series[0][i] = d.extractPercentage;
hours[i] = d.extractorName;
});
for (i = 0; i < series.length; i += 1) {
series[i].push(series[i][0]);
}
//console.log(series.length);
var radialTicks = radius.ticks(5);
vizBody.selectAll('.circle-ticks').remove();
vizBody.selectAll('.line-ticks').remove();
var circleAxes = vizBody.selectAll('.circle-ticks')
.data(radialTicks)
.enter().append('svg:g')
.attr("class", "circle-ticks");
circleAxes.append("svg:circle")
.attr("r", function (d, i) {
return radius(d);
})
.attr("class", "circle")
.style("stroke", ruleColor)
.style("fill", "none");
circleAxes.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", function (d) {
return -1 * radius(d);
})
.text(String);
var lineAxes = vizBody.selectAll('.line-ticks')
.data(hours)
.enter().append('svg:g')
.attr("transform", function (d, i) {
return "rotate(" + ((i / hours.length * 360) - 90) +
")translate(" + radius(maxVal) + ")";
})
.attr("class", "line-ticks");
lineAxes.append('svg:line')
.attr("x2", -1 * radius(maxVal))
.style("stroke", ruleColor)
.style("fill", "none");
lineAxes.append('svg:text')
.text(String)
.attr("text-anchor", "middle")
.attr("transform","translate(15) rotate(90)");
//var highlightedDotSize = 4;
var groups = vizBody.selectAll('.series').data(series);
//console.log(hours.length);
groups.enter().append("svg:g")
.attr('class', 'series')
.style('fill', "green")
.style('stroke',"#ccc");
//groups.exit().remove();
//console.log(Math.PI);
var lines = groups.append('svg:path')
.attr("class", "line")
/*.attr("d", d3.svg.line.radial()
.radius(function (d) {
return 10;
})
.angle(function (d, i) {
if (i == hours.length) {
i = 0;
} //close the line
return (i / hours.length) * 2 * Math.PI;
}))*/
.style("stroke-width", 1)
.style("fill", "rgba(124,240,10,0.1)");
/*groups.selectAll(".curr-point")
.data(function (d) {
return [d[0]];
})
.enter().append("svg:circle")
.attr("class", "curr-point")
.attr("r", 0);
groups.selectAll(".clicked-point")
.data(function (d) {
return [d[0]];
})
.enter().append("svg:circle")
.attr('r', 0)
.attr("class", "clicked-point");*/
lines.attr("d", d3.svg.line.radial()
.radius(function (d) {
return radius(d);
})
.angle(function (d, i) {
if (i === hours.length) {
i = 0;
} //close the line
return (i / hours.length) * 2 * Math.PI;
}));
});
i implemented this code to create radar chart with a json data here is the json data format
{
"QualitySummaryObject": [
{
"extractPercentage": 68.81964,
"extractorName": "Classification"
},
{
"extractPercentage": 74.09091,
"extractorName": "Keyword Match"
},
{
"extractPercentage": 54.62963,
"extractorName": "LocationBroadcast"
},
{
"extractPercentage": 98.91892,
"extractorName": "Qualification"
},
{
"extractPercentage": 98.76923,
"extractorName": "User Profile Location"
},
{
"extractPercentage": 80.15909,
"extractorName": "Valid Person Name"
},
]
}
Now i want to put tooltip on each node point .. but i am not able to get any idea how to do that any body can help?
Here is an example of Twitter Bootstrap tooltips running on SVGs with D3 http://markhansen.co.nz/stolen-vehicles-pt2/
To get it working on newer versions see Why doesn't Twitter Bootstrap popover work with SVG elements? You'll need to use a 2.3.0+ version of bootstrap or the fix I posted in that thread.