Because there's very little NVD3 documentation, I'm unsure about how to modify the code to create pulsing bubbles in a scatter chart. I'd like to be able to be able to iterate through an array of data for each bubble, and animate the size of the bubble based on that data, in a continuous loop. At the moment my code is pasting all the data points on top of each other as soon as the chart is called.
I've found various examples of doing something similar using D3, but nothing exactly the same. And I'm still not sure how I would apply this to NVD3 as it doesn't seem to use the same enter/exit way of doing things as D3. I've provided an example of my current code and data below. I've also created a JSFiddle so you can see it (not) working: http://jsfiddle.net/9oyaypz0/. Any help would be very greatly appreciated.
function makeScatter(data, div, showLabels,title) {
nv.addGraph(function() {
var width = 450,
height = 275;
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.transitionDuration(350)
.sizeRange([0, 10000])
.margin({bottom:75})
.color(d3.scale.category10().range());
chart.tooltipContent(function(key, x, y, obj) {
return '<h4>' + key + '</h4><h5 style="clear:both;line-height:0.1rem;text-align:center;">' + obj.point.size + '</h5>';
});
chart.xAxis.tickFormat(function (d) { return ''; });
chart.yAxis.tickFormat(function (d) { return ''; });
chart.forceX([0]);
chart.forceX([5]);
chart.forceY([0]);
chart.forceY([6]);
chart.scatter.onlyCircles(false);
if(title) {
d3.select('title')
.append("text")
.attr("x", (width / 2))
.attr("y", 100)
.style("font-size", "16px")
.attr("text-anchor", "middle")
.text(title);
$(div).append("<h2>"+title+"</h2>");
}
d3.select(div)
.datum(data)
.transition().duration(1200)
.attr('width', width)
.attr('height', height)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
};
And the data is like so:
var data = [{ key: "Test Bubble 1",
values: [{ series: 0,
shape: "circle",
size: 1592,
x: 5,
y: 4 },
{ series: 1,
shape: "circle",
size: 1560,
x: 5,
y: 4 },
{ series: 2,
shape: "circle",
size: 1512,
x: 5,
y: 4 },
{ series: 3,
shape: "circle",
size: 1487,
x: 5,
y: 4 }]
},
{ key: "Test Bubble 2",
values: [{ series: 0,
shape: "circle",
size: 592,
x: 5,
y: 4 },
{ series: 1,
shape: "circle",
size: 560,
x: 5,
y: 4 },
{ series: 2,
shape: "circle",
size: 512,
x: 5,
y: 4 },
{ series: 3,
shape: "circle",
size: 487,
x: 5,
y: 4 }]
}]
Related
i want to update the graph but it does not work. I want to update the line and the circles. I tried to add
.exit().remove()
to update the circle and
g.selectAll("path").attr("d", line);
to update the paths.
However it does not work.
Updating the outside group with exit().remove() works fine. (Checkbox in this example).
Updating only the path and circle does not work. (Update Button in this example)
I dont want to remove all lines in the graph and append it again, because i want to add transition when data changes.
Here is a JS Fiddle: LINK
Here is my code:
var data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}],
[{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
];
var svg = d3.select("svg");
var line = d3.line()
.x((d) => d.point.x)
.y((d) => d.point.y);
function updateGraph() {
console.log(data)
var allGroup = svg.selectAll(".pathGroup").data(data);
var g = allGroup.enter()
.append("g")
.attr("class", "pathGroup")
allGroup.exit().remove()
g.append("path")
.attr("class", "line")
.attr("stroke", "red")
.attr("stroke-width", "1px")
.attr("d", line);
g.selectAll("path").attr("d", line);
g.selectAll(null)
.data(d => d)
.enter()
.append("circle")
.attr("r", 4)
.attr("fill", "teal")
.attr("cx", d => d.point.x)
.attr("cy", d => d.point.y)
.exit().remove()
}
updateGraph()
document.getElementById('update').onclick = function(e) {
data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}],
[{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 300
}
},
]
];
updateGraph()
}
$('#cb1').click(function() {
if ($(this).is(':checked')) {
data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}],
[{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
];
} else {
data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
];
}
updateGraph()
});
Problem
The reason why allGroup.exit().remove() does nothing is that the updated dataset still has the same number of items as the original one. The exit selection is therefore empty.
The variable data contains lines, not points. The one defined at page load, and the one inside update listener contain two lines, only the number of points in them differs.
You can check this by putting a console.log(data.length) inside function updateGraph.
Solution 1
Change your data structure. You can assign an id property to each line, and use .data's, key function. cf. d3-selection documentation.
Updated jsFiddle implementing solution 1: see here.
This solution requires less changes.
Solution 2
In case you have no control over the data structure, you can transition the line drawing inside the update selection, rather than the exit one.
Given the following code which calls the update function which creates 4 nodes with a circle and text element nested in a g element, waits 500ms, then calls the function again with updated data:
var data1 = [
{ x: 10, y: 10, text: "A" },
{ x: 30, y: 30, text: "B" },
{ x: 50, y: 50, text: "C" },
{ x: 70, y: 70, text: "D" }
];
var data2 = [
{ x: 30, y: 10, text: "X" },
{ x: 50, y: 30, text: "Y" },
{ x: 70, y: 50, text: "Z" },
{ x: 90, y: 70, text: "W" }
];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() { update(data2); }, 500);
function update(data) {
var nodes = svg.selectAll(".node")
.data(data);
var nodesUpdate = nodes
.attr("class", "node update")
var nodesEnter = nodes.enter();
var node = nodesEnter.append("g")
.attr("class", "node enter")
node
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
node.append("circle")
.attr("r", 10)
.style("opacity", 0.2);
node.append("text")
.text(function(d) { return d.text; });
}
With the code as it is the second call has no effect, because everything is set in the enter selection. I'm trying to make it so I can call update with new data, and change properties on both the enter and update selections, without duplicating code. I can achieve this for top-level elements (ie the g elements) using merge, by making this change:
node
.merge(nodesUpdate)
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
Now the nodes update their position after 500ms. However, I haven't been able to figure out how to update the text element. If I do nodes.selectAll("text") I end up with nested data, which doesn't work.
I've scoured the following docs to try and figure this out:
https://bl.ocks.org/mbostock/3808218
https://github.com/d3/d3-selection
https://bost.ocks.org/mike/nest/
It should just be nodes.select when dealing with a subselection.
Here's a quick refactor with comments and clearer variable names:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var data1 = [{
x: 10,
y: 10,
text: "A"
}, {
x: 30,
y: 30,
text: "B"
}, {
x: 50,
y: 50,
text: "C"
}, {
x: 70,
y: 70,
text: "D"
}];
var data2 = [{
x: 30,
y: 10,
text: "X"
}, {
x: 50,
y: 30,
text: "Y"
}, {
x: 70,
y: 50,
text: "Z"
}, {
x: 90,
y: 70,
text: "W"
}];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() {
update(data2);
}, 500);
function update(data) {
var nodesUpdate = svg.selectAll(".node")
.data(data); // UPDATE SELECTION
var nodesEnter = nodesUpdate.enter()
.append("g")
.attr("class", "node"); // ENTER THE Gs
nodesEnter.append("text"); // APPEND THE TEXT
nodesEnter.append("circle") // APPEND THE CIRCLE
.attr("r", 10)
.style("opacity", 0.2);
var nodesEnterUpdate = nodesEnter.merge(nodesUpdate); // UPDATE + ENTER
nodesEnterUpdate // MOVE POSITION
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodesEnterUpdate.select("text") // SUB-SELECT THE TEXT
.text(function(d) {
return d.text;
});
}
</script>
</body>
</html>
Without refactoring a lot of your code, the simplest solution is using a key in the data function, followed by an "exit" selection:
var nodes = svg.selectAll(".node")
.data(data, d=> d.text);
nodes.exit().remove();
Here is the demo:
var data1 = [{
x: 10,
y: 10,
text: "A"
}, {
x: 30,
y: 30,
text: "B"
}, {
x: 50,
y: 50,
text: "C"
}, {
x: 70,
y: 70,
text: "D"
}];
var data2 = [{
x: 30,
y: 10,
text: "X"
}, {
x: 50,
y: 30,
text: "Y"
}, {
x: 70,
y: 50,
text: "Z"
}, {
x: 90,
y: 70,
text: "W"
}];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() {
update(data2);
}, 500);
function update(data) {
var nodes = svg.selectAll(".node")
.data(data, d => d.text);
nodes.exit().remove();
var nodesUpdate = nodes
.attr("class", "node update")
var nodesEnter = nodes.enter();
var node = nodesEnter.append("g")
.attr("class", "node enter")
node
.merge(nodesUpdate)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("circle")
.attr("r", 10)
.style("opacity", 0.2);
node.append("text")
.text(function(d) {
return d.text;
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
This will create a different "enter" selection. If, on the other hand, you want to get the data bound to the "update" selection, you'll have to refactor your code.
I am using Highcharts and it is working just amazing, i am stuck at a place where i want to plot a pie chart in which every pie slice (in a single pie chart) has a different radius.
Below is the image attached of the expexted pie chart.
You can skip making it a donout or designing it this specific. I just want to know how each pie slice can have different radius.
Each series in a pie chart can have their own size. So, I stacked a bunch of pie series calculating their begin and end angles. You'll have to do a little clean up to get the tooltips displaying the value instead of 100, but I think it's a workable solution.
Note: The following code makes a bad assumption that the data points add to 100. void fixes that assumption in his fiddle http://jsfiddle.net/58zfb8gy/1.
http://jsfiddle.net/58zfb8gy/
$(function() {
var data = [{
name: 'Thane',
y: 25,
color: 'red'
}, {
name: 'Nagpur',
y: 15,
color: 'blue'
}, {
name: 'Pune',
y: 30,
color: 'purple'
}, {
name: 'Mumbai',
y: 30,
color: 'green'
}];
var start = -90;
var series = [];
for (var i = 0; i < data.length; i++) {
var end = start + 360 * data[i].y / 100;
data[i].y = 100;
series.push({
type: 'pie',
size: 100 + 50 * i,
innerSize: 50,
startAngle: start,
endAngle: end,
data: [data[i]]
});
start = end;
};
$('#container').highcharts({
series: series
});
});
Another way I toyed with, that I didn't like as much, was having each series have invisible points:
series = [{
type: 'pie',
size: 100,
innerSize: 50,
data: [{y:25, color: 'red'}, {y:75, color:'rgba(0,0,0,0)'}]
},{
type: 'pie',
size: 150,
innerSize: 50,
data: [{y:25, color: 'rgba(0,0,0,0)'},{y:15, color: 'blue'}, {y:60, color:'rgba(0,0,0,0)'}]
}, ... ];
The variablepie series type, introduced in Highcharts 6.0.0, handles this with less code. In this series type you can specify a z-parameter for each data point to alter its z-size.
For example (JSFiddle, documentation):
Highcharts.chart('container', {
chart: {
type: 'variablepie'
},
title: {
text: 'Variable pie'
},
series: [{
minPointSize: 10,
innerSize: '20%',
zMin: 0,
name: 'countries',
data: [{
name: 'Pune',
y: 35,
z: 25
}, {
name: 'Mumbai',
y: 30,
z: 20
}, {
name: 'Nagpur',
y: 15,
z: 15
} , {
name: 'Thane',
y: 25,
z: 10
}]
}]
});
This requires including:
<script src="https://code.highcharts.com/modules/variable-pie.js"></script>
I am trying to create stacked bar chart using plottable.js from the same dataset. Is it possible?
Here is my sample dataset
var data = [{ x: 0, y: 1, m: 10 },
{ x: 1, y: 1, m: 9 },
{ x: 2, y: 3, m: 5 },
{ x: 3, y: 2, m: 5 },
{ x: 4, y: 4, m: 8 },
{ x: 5, y: 3, m: 7 },
{ x: 6, y: 5, m: 5 }];
In the example posted on http://plottablejs.org/components/plots/stacked-bar/, it used two dataset.
var plot = new Plottable.Plots.StackedBar()
.addDataset(new Plottable.Dataset(primaryData).metadata(5))
.addDataset(new Plottable.Dataset(secondaryData).metadata(3))
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale)
.attr("fill", function(d, i, dataset) { return dataset.metadata(); }, colorScale)
.renderTo("svg#example");
My question is, since I am using the same dataset and I need to change the 'y' function into two distinct functions to something like this:
.y(function(d) { return d.y; }, yScale)
.y(function(d) { return d.m; }, yScale)
Is it possible? If yes, how?
Thanks...
You can create two different Plottable.Datasets based on the same data.
So start like this:
var ds1 = new Plottable.Dataset(data);
var ds2 = new Plottable.Dataset(data);
However, later you'll need to distinguish the two datasets to know which attribute to use, so you should add metadata
var ds1 = new Plottable.Dataset(data, {name: "ds1"});
var ds2 = new Plottable.Dataset(data, {name: "ds2"});
now later, you can reference the name of the dataset to know which value to return in the .y() call
plot.y(function(d, i, ds){
if(ds.metadata().name == "ds1"){
return d.y;
}
else{
return d.m;
}
}, yScale);
Trying to create a custom line chart in which there is only one simple line, with a gradient background - the background of every part of the line is determined according to the y-value at that point (changes in values are guaranteed to be mild).
I'm having trouble with the basic configuration. This is my code:
js:
// General definitions
var HEIGHT, MARGINS, WIDTH, formatDay, lineFunc, graph, graph_data, weekdays, x, xAxis, y, yAxis;
WIDTH = 360;
HEIGHT = 130;
MARGINS = {
top: 20,
right: 30,
bottom: 20,
left: 20
};
graph = d3.select("#graph");
// Define Axes
weekdays = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"];
formatDay = function(d) {
return weekdays[d % 6];
};
x = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([
d3.min(graph_data, function(d) {
return d.x;
}), d3.max(graph_data, function(d) {
return d.x + 1;
})
]);
y = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([
d3.min(graph_data, function(d) {
return d.y;
}), d3.max(graph_data, function(d) {
return d.y;
})
]);
xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(formatDay);
yAxis = d3.svg.axis().scale(y).tickSize(10).orient("left");
// Line Function
lineFunc = d3.svg.line().x(function(d) {
return x(d.x);
}).y(function(d) {
return y(d.y);
}).interpolate("basis");
// Define Line Gradient
graph.append("linearGradient").attr("id", "line-gradient").attr("gradientUnits", "userSpaceOnUse").attr("x1", 0).attr("y1", y(0)).attr("x2", 0).attr("y2", y(200)).selectAll("stop").data([
{
offset: "0%",
color: "#F0A794"
}, {
offset: "20%",
color: "#F0A794"
}, {
offset: "20%",
color: "#E6A36A"
}, {
offset: "40%",
color: "#E6A36A"
}, {
offset: "40%",
color: "#CE9BD2"
}, {
offset: "62%",
color: "#CE9BD2"
}, {
offset: "62%",
color: "#AA96EE"
}, {
offset: "82%",
color: "#AA96EE"
}, {
offset: "82%",
color: "#689BE7"
}, {
offset: "90%",
color: "#689BE7"
}, {
offset: "90%",
color: "1AA1DF"
}, {
offset: "100%",
color: "1AA1DF"
}
]).enter().append("stop").attr("offset", function(d) {
return d.offset;
}).attr("stop-color", function(d) {
return d.color;
});
// Draw Line
graph.append("svg:path").attr("d", lineFunc(graph_data));
// Draw Axes
graph.append("svg:g").attr("class", "x axis").attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")").call(xAxis);
graph.append("svg:g").attr("class", "y axis").attr("transform", "translate(" + MARGINS.left + ",0)").call(yAxis);
style
#line-gradient {
fill: none;
stroke: url(#line-gradient);
stroke-width: 7px;
stroke-linejoin: "round";
}
Sample data
graph_data = [{
x: 1,
y: 22
}, {
x: 2,
y: 20
}, {
x: 3,
y: 10
}, {
x: 4,
y: 40
}, {
x: 5,
y: 5
}, {
x: 6,
y: 30
}, {
x: 7,
y: 60
}]
What i'm getting looks like this:
Can any of you D3.js experts tell me what I'm doing wrong, and what needs to change in order for my line to be a line rather than an area, having the line background gradient explained above, and round edges?
Many thanks in advance!
Here's a fiddle: http://jsfiddle.net/henbox/gu4y7fk8/
You should give the path a class name, like this:
graph.append("svg:path")
.attr("class","chartpath")
.attr("d", lineFunc(graph_data));
And then the CSS styling you have should be on that path element rather than the lineargradient element
.chartpath { /*note: not #line-gradient*/
fill: none;
stroke: url(#line-gradient);
stroke-width: 7px;
stroke-linejoin: "round";
}
I also fixed up a couple of other things:
Missing # on a couple of the color codes, so changed (color: "1AA1DF" to color: "#1AA1DF"
I changed the max y value for the gradient from 200 to 60, so that the changing color gradient of the line is more visible in the example (.attr("y2", y(200)) to .attr("y2", y(60)))