Appending an element to an existing element in d3.js - javascript

I am trying to create a realtime barchart that plots values over time, using d3.js
This is how I am doing it.
var dataset = [ 5, 10, 15, 20, 25 ];
var w = 1800;
var h = 500;
var barPadding = 1;
setInterval(function(){
dataset.push(Math.floor(Math.random()*51));
draw();
},1000);
function draw(){
d3.select("svg").remove();
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect").data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i){return 12*i;})
.attr("y", function(d){return h -d*4; })
.attr("width", 11)
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal")
.attr("fill", function(d) { return "rgb(0, 0, " + (d * 10) + ")";});
}
The problem is that I am redrawing the whole graph every time a new value is added to the data array.
How do I append a bar to the bar graph that is already drawn, every time a new value is added to the array, rather than redrawing it every time?

You're close, just stop redrawing the svg element. If you only need to add new elements, then that's what your draw function should do when it's called.
var dataset = [ 5, 10, 15, 20, 25 ];
var w = 1800;
var h = 300;
var barPadding = 1;
var container = d3.select("body").append("svg").attr("width", w).attr("height", h).append("g");
setInterval(function(){
dataset.push(Math.floor(Math.random()*51));
draw();
},1000);
function draw(){
container.selectAll("rect").data(dataset).enter().append("rect")
.attr("x", function(d, i){return 12*i;})
.attr("y", function(d){return h -d*4; })
.attr("width", 11)
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal")
.attr("fill", function(d) { return "rgb(0, 0, " + (d * 10) + ")";});
}​
http://jsfiddle.net/Wexcode/LYqfU/

Not sure what kind of effect you're looking for, but have a look at this fiddle.
The following redraw function will keep adding to the barchart so it will continue to grow to the right:
function redraw() {
var rect = svg.selectAll("rect")
.data(dataset);
rect.enter().insert("rect", "line")
.attr("x", function(d, i) { return 12*(i+1); })
.attr("y", function(d) { return h -d*4 })
.attr("width", 11)
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal")
.attr("fill", function(d) { return "rgb(0, 0, " + (d * 10) + ")";})
rect.transition()
.duration(800)
.attr("x", function(d, i) { return 12*i; });
}
Borrowed from an mbostock tutorial.

Related

Adding foreign element to d3,js now showing

I am trying to add checkboxes and text in a node. See below image of what I am trying to achieve.
I can see the checkbox in elements view but cannot view it in page.
But this is what I am getting for now.
Below is the code used.
(function() {
var width, height, rules, map, tasks, links, nodes, svg, tick, radius, force, link, node;
width = 960;
height = 500;
rules = [
['root', 'iot'],
['root', 'points'],
['root', 'camnative'],
['root', 'classifier'],
['points', 'classifier2'],
['camnative', 'classifier3'],
['classifier', 'consec'],
['iot', 'classifier1'],
['cloudclassif', 'schedule'],
['schedule', 'privacy'],
['privacy', 'roi'],
['roi', 'flooding'],
['classifier1', 'cloudclassif'],
['classifier2', 'cloudclassif'],
['classifier3', 'cloudclassif'],
['consec', 'cloudclassif']
];
map = d3.map();
rules.forEach(function(rule) {
map.set(rule[0], {
fixed: false
});
return map.set(rule[1], false);
});
map.set('root', {
fixed: true,
x: 100,
y: height / 2
});
// map.set('P4', {
// fixed: true,
// x: width / 2 - 100,
// y: height / 2
// });
tasks = map.keys();
links = rules.map(function(rule) {
return {
source: tasks.indexOf(rule[0]),
target: tasks.indexOf(rule[1])
};
});
nodes = tasks.map(function(k) {
var entry;
entry = {
name: k
};
if (map.get(k).fixed) {
entry.fixed = true;
entry.x = map.get(k).x;
entry.y = map.get(k).y;
}
return entry;
});
svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.append("svg:defs")
.append("svg:marker")
.attr("id", "arrow")
.attr("viewBox", "0 0 10 10")
.attr("refX", 0)
.attr("refY", 5)
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 8)
.attr("markerHeight", 6)
// .attr("orient", "auto")
.append("svg:path")
.attr("d", "M 0 0 L 10 5 L 0 10 z");
svg.append("line")
.attr("x1", 5)
.attr("x2", 50)
.attr("y1", 5)
.attr("y2", 50)
.style("stroke", "black")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrow)");
tick = function() {
var arrowheadLength = 8, // from markerWidth
nodeRadius = 10;
link.each(function(d) {
var x1 = d.source.x,
y1 = d.source.y,
x2 = d.target.x,
y2 = d.target.y,
angle = Math.atan2(y2 - y1, x2 - x1);
d.targetX = x2 - Math.cos(angle) * (nodeRadius + arrowheadLength);
d.targetY = y2 - Math.sin(angle) * (nodeRadius + arrowheadLength);
});
link.selectAll("line").attr("x1", function(d) {
return d.source.x;
}).attr("y1", function(d) {
return d.source.y;
}).attr("x2", function(d) {
return d.targetX;
}).attr("y2", function(d) {
return d.targetY;
}).attr("marker-end", "url(#arrow)");
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
};
radius = d3.scale.sqrt().range([0, 6]);
force = d3.layout.force().size([width / 2, height]).charge(-900).linkDistance(function(d) {
return 40;
});
force.nodes(nodes).links(links).on("tick", tick).start();
link = svg.selectAll(".link").data(links).enter().append("g").attr("class", "link");
link.append("line").style("stroke-width", 1).attr("marker-end", "url(#arrow)");
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(force.drag);
node.append("rect")
.attr("class", "node")
.attr("width", 100)
.attr("height", 50);
node.append("input")
.attr("type", "checkbox")
.attr("class", "mycheck")
.attr("fill", "black");
node.append("text")
.attr("x", function(d) { return (d) - 3; })
.attr("y", 50 / 2)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
console.log("HERE2");
console.log("HERE5");
}).call(this);
I checked D3 v3 appending checkbox?, but that solution does not work.
Update
Added
node.append("foreignObject")
.attr("width", 100)
.attr("height", 100)
.append("xhtml:chart")
.append("div")
.append("input")
.attr("type", "checkbox");
Add the checkboxes are added to the node.
I played around with this and got a bit further. I found discovered a few things:
As mentioned by others, put the checkbox inside of a <foreignObject>
Use the "xhtml:" prefix before the html objects inside of the foreignObject.
You have some bad math in your data functions. For example: return (d) - 3; returns NaN. You need something like return d.x - 3;
Items inside of the <g> element are positioned relative to the group. In my fiddle example (link at bottom), I positioned the top-lefts of the rectangles at (0,-16).
// Create the <foreignObject>:
let ddiv = node.append("foreignObject")
.attr("x", -10)
.attr("y", -34)
.attr("width", 50)
.attr("height",50)
.append("xhtml:div") // <<<---- xhtml: prefix!
.classed("roundedOne", true)
ddiv.append("xhtml:input") // <<<---- xhtml: prefix!
.attr("type", "checkbox")
.attr("id", (d) => d.name);
ddiv.append("xhtml:label") // <<<---- xhtml: prefix!
.attr("for", (d) => d.name);
Check out my jsFiddle: https://jsfiddle.net/visioguy/yz5tfgjm/
I added some fancy checkbox styling that fit better into your boxes, which is why I added the extra <div> and <label> to your code. You'll have to fiddle with more of the margins and sizes and offsets, and perhaps some of the force-layout parameters to get the layout to work properly.

Live Bar graph with d3 in js using data from realtime firebase

I am new to javascript and have been stuck at a problem for the better part of 2 weeks. I am trying to make a bar graph that updates in real time using data from Firebase. The structure of my database is:
title:
-------child1
-------child2
-------child3
-------child4
The data to firebase is provided from a python script that is working perfectly and is updating every child of title every 10 seconds.
I made a bar graph that is updating automatically via random number generation.
//Return array of 10 random numbers
var randArray = function() {
for(var i = 0, array = new Array(); i<10; i++) {
array.push(Math.floor(Math.random()*10 + 1))
}
return array
}
var initRandArray = randArray();
var newArray;
var w = 500;
var h = 200;
var barPadding = 1;
var mAx = d3.max(initRandArray)
var yScale = d3.scale.linear()
.domain([0, mAx])
.range([0, h])
var svg = d3.select("section")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(initRandArray)
.enter()
.append("rect")
.attr("x", function(d,i) {return i*(w/initRandArray.length)})
.attr("y", function(d) {return h - yScale(d)})
.attr("width", w / initRandArray.length - barPadding)
.attr("height", function(d){return yScale(d)})
.attr("fill", function(d) {
return "rgb(136, 196, " + (d * 100) + ")";
});
svg.selectAll("text")
.data(initRandArray)
.enter()
.append("text")
.text(function(d){return d})
.attr("x", function(d, i){return (i*(w/initRandArray.length) + 20)})
.attr("y", function(d) {return h - yScale(d) + 15})
.attr("font-family", "sans-serif")
.attr("fill", "white")
setInterval(function() {
newArray = randArray();
var rects = svg.selectAll("rect")
rects.data(newArray)
.enter()
.append("rect")
rects.transition()
.ease("cubic-in-out")
.duration(2000)
.attr("x", function(d,i) {return i*(w/newArray.length)})
.attr("y", function(d) {return h - yScale(d)})
.attr("width", w / newArray.length - barPadding)
.attr("height", function(d){return yScale(d)})
.attr("fill", function(d) {
return "rgb(136, 196, " + (d * 100) + ")";
});
var labels = svg.selectAll("text")
labels.data(newArray)
.enter()
.append("text")
labels.transition()
.ease("cubic-in-out")
.duration(2000)
.text(function(d){return d})
.attr("x", function(d, i){return (i*(w/newArray.length) + 20)})
.attr("y", function(d) {return h - yScale(d) + 15})
.attr("font-family", "sans-serif")
.attr("fill", "white")
}, 3000)
Live bar chart on random number
I need to update the chart using the data from firebase. I already know how to connect firebase to js using the snapshot and have already tried it to no avail.
Also, need some help with the styling of the graph.
Please if anybody knows how I can finish this(its time sensitive).
Here's the code link in jsfiddle: Live bar chart d3
Thanks

d3js scroll visibility - series animation for pie chart

I am working on a d3 applicaton - with a pie chart -- I would like to get animation onload and on a call to action. Like when the chart becomes visible during a scroll.
Where the pie segments grow around the central pivot. So tween or snap to the other segment like a relay race
http://jsfiddle.net/pg886/192/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<div class="piechart" data-role="piechart" data-width=400 data-height=400 data-radius=30 data-innerradius=20
data-data=x>
</div>
<style>
.piechart{
/*border: 1px solid black;*/
/*text-align: center;
font-size: 12px;*/
}
</style>
<script>
$( document ).ready(function() {
console.log("test")
var $this = $('.piechart');
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}];
var w = $this.data("width");
var h = $this.data("height");
var ir = $this.data("innerradius");
var r = $this.data("radius");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
//var colores_g = ["#47abd5", "#005a70", "#f5a0a3", "#ff7276", "#a9a19c", "#d0743c", "#ff8c00"];
return colores_g[n % colores_g.length];
}
var radius = Math.min(w, h) / 4;
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var labelArc = d3.svg.arc()
.outerRadius(radius - r)
.innerRadius(radius - ir);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var chart = d3.select('.piechart').append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h)
.attr("transform", "translate(0,0)");
var piechart = chart
.append("g")
.attr("class", "piechart")
.attr("width", (radius*2))
.attr("transform", "translate(0,"+h/4+")");
var path_group = piechart.append("g")
.attr("class", "path_group")
.attr("transform", "translate(90," + ((h / 4) - 20) + ")");
var padding = 45;
var legendPaddingTop = 30;
var legend = chart.append("g")
.attr("class", "legend")
.attr("width", w/2)
.attr("height", h)
.attr("transform", "translate(" + (w - 50) + "," + (h / 4) + ")");
var label_group = legend.append("svg:g")
.attr("class", "label_group")
.attr("transform", "translate(" + (-(w / 3) + 20) + "," + 0 + ")");
var legend_group = legend.append("svg:g")
.attr("class", "legend_group")
.attr("transform", "translate(" + (-(w / 3) - 100) + "," + 0 + ")");
var g = path_group.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return colores_google(i);
});
var legendHeight = legendPaddingTop;
var ySpace = 18;
//draw labels
var labels = 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) + 4;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
//draw labels
//draw legend
var legend = 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();
//draw legend
//reset legend height
//console.log("optimum height for legend", legendHeight);
$this.find('.legend').attr("height", legendHeight);
function type(d) {
d.value = +d.value;
return d;
}
});
</script>
So you can achieve this pretty easily, and there are a couple of blocks that will help you.
Arc Tween Firstly this block gives you an example of how to tween an arc. Basically you can't get that automatically so you have to write your own attrTween function. Fortunately this is pretty simple and Mike Bostock gives a really good example in there.
Here's a code sample - but the link gives a really good verbose description of what's going on.
.attrTween("d", function(d) {
var interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
}
Next you want something like this Donut with transitions. This is actually the end-result for where you're trying to get to. This effect is really easy to achieve, all you need to do is set your angles correctly and the timings.
Angles: So here you want both the endAngle and the startAngle to be the same at the start (which should be the endAngle value of the previous segment or 0 for the first segment).
Timing: You want to allow 1 animation to complete before you start the next, simply by delaying them. You can see how that's done with this snippet:
.transition()
.delay(function(d,i) { return i * 500; })
.duration(500)
.attrTween(...)
const dataset = [
{ age: "<5", population: 2704659 },
{ age: "5-13", population: 4499890 },
{ age: "14-17", population: 2159981 },
{ age: "18-24", population: 3853788 },
{ age: "25-44", population: 14106543 },
{ age: "45-64", population: 8819342 },
{ age: "≥65", population: 612463 },
];
const TIME = 2000 / dataset.length;
const color = d3.scaleOrdinal(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
const pie = d3.pie()
.sort(null)
.value(function(d) { return d.population; });
const path = d3.arc()
.innerRadius(0)
.outerRadius(350);
d3.select("#container")
.selectAll(".arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.append("path")
.attr("fill", function(d) { return color(d.data.age); })
.transition()
.duration(TIME)
.ease(d3.easeLinear)
.delay(function(d, i) { return i * TIME; })
.attrTween("d", function(d) {
// Note the 0.1 to prevent errors generating the path
const angleInterpolation = d3.interpolate(d.startAngle + 0.1, d.endAngle);
return function(t) {
d.endAngle = angleInterpolation(t);
return path(d);
}
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="800" height="800">
<g id="container" transform="translate(400, 400)"></g>
</svg>

Trying to update d3.js circles with new data

I've recently began trying to teach myself D3, and I'm to get my head round the enter, update, exit paradigm.
Below I have an example of some progress circles I'm trying to work with;
http://plnkr.co/edit/OoIL8v6FemzjzoloJxtQ?p=preview
Now, as the aim here is to update the circle path without deleting them, I believe I shouldn't be using the exit function? In which case, I was under the impression that I could update my data source inside a new function and then call for the path transition, and I would get my updated value. However, this is not the case.
I was wondering if someone could help me out and show me where I'm going wrong?
var dataset = [{
"vendor-name": "HP",
"overall-score": 45
}, {
"vendor-name": "CQ",
"overall-score": 86
}];
var dataset2 = [{
"vendor-name": "HP",
"overall-score": 22
}, {
"vendor-name": "CQ",
"overall-score": 46
}];
var width = 105,
height = 105,
innerRadius = 85;
var drawArc = d3.svg.arc()
.innerRadius(innerRadius / 2)
.outerRadius(width / 2)
.startAngle(0);
var vis = d3.select("#chart").selectAll("svg")
.data(dataset)
.enter()
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
vis.append("circle")
.attr("fill", "#ffffff")
.attr("stroke", "#dfe5e6")
.attr("stroke-width", 1)
.attr('r', width / 2);
vis.append("path")
.attr("fill", "#21addd")
.attr('class', 'arc')
.each(function(d) {
d.endAngle = 0;
})
.attr('d', drawArc)
.transition()
.duration(1200)
.ease('linear')
.call(arcTween);
vis.append('text')
.text(0)
.attr("class", "perc")
.attr("text-anchor", "middle")
.attr('font-size', '36px')
.attr("y", +10)
.transition()
.duration(1200)
.tween(".percentage", function(d) {
var i = d3.interpolate(this.textContent, d['overall-score']),
prec = (d.value + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round + "%";
};
});
function updateChart() {
vis = vis.data(dataset2)
vis.selectAll("path")
.transition()
.duration(1200)
.ease('linear')
.call(arcTween);
vis.selectAll('text')
.transition()
.duration(1200)
.tween(".percentage", function(d) {
var i = d3.interpolate(this.textContent, d['overall-score']),
prec = (d.value + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round + "%";
};
});
}
function arcTween(transition, newAngle) {
transition.attrTween("d", function(d) {
var interpolate = d3.interpolate(0, 360 * (d['overall-score'] / 100) * Math.PI / 180);
return function(t) {
d.endAngle = interpolate(t)
return drawArc(d);
};
});
}
Any help/advice is much appreciated!
Thanks all
You need to refresh your data through the DOM - svg > g > path :
// SET DATA TO SVG
var svg = d3.selectAll("svg")
.data(selectedDataset)
// SET DATA TO G
var g = svg.selectAll('g')
.data(function(d){return [d];})
// SET DATA TO PATH
var path = g.selectAll('path')
.data(function(d){ return [d]; });
Storing the d3 DOM data bind object for each step you can have control of the enter(), extit(), and transition() elements. Put changing attributes of elements in the transition() function:
// PATH ENTER
path.enter()
.append("path")
.attr("fill", "#21addd")
.attr('class', 'arc')
// PATH TRANSITION
path.transition()
.duration(1200)
.ease('linear')
.attr('d', function(d){ console.log(d);drawArc(d)})
.call(arcTween);
http://plnkr.co/edit/gm2zpDdBdQZ62YHhDbLb?p=preview

D3 Plot array of circles

I've tried the circle plot example as the following:
var x=20, y=20, r=50;
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 800)
.attr("height", 600);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", r)
.attr("cx", x)
.attr("cy", y);
But I want to figure out how to plot without a loop a sequence of circles from an array like:
data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
Maybe this example could help?
var data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
var height = 300,
width = 500;
var svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(30, 30)');
// Bind each nested array to a group element.
// This will create 3 group elements, each of which will hold 3 circles.
var circleRow = svg.selectAll('.row')
.data(data)
.enter().append('g')
.attr('transform', function(d, i) {
return 'translate(30,' + i * 60 + ')';
});
// For each group element 3 circle elements will be appended.
// This is done by binding each element in a nested array to a
// circle element.
circleRow.selectAll('.circle')
.data(function(d, i) { return data[i]; })
.enter().append('circle')
.attr('r', function(d) { return d; })
.attr('cx', function(d, i) { return i * 60; })
.attr('cy', 0);
Live Fiddle

Categories

Resources