I am using the D3 library to create a Zoomable Treemap for my application data using Javascript and JSON. I see online that many times d3 category for Color is being used to determine the colors of each section. However, I wish to color the sections of treemap using my application logic. Like below:
If conditionA
color = red
If conditionB
color=green
....
Is there any way to achieve this.. Check the values of my JSON Object and set the color of a section only on the basis of some conditions; and have all other sections set to a default color?
Your question is a little vague (next time include some code!), but in general, say you have data like this:
var data = [{
conditionA: true,
conditionB: false
}, {
conditionA: false,
conditionB: true
}, {
conditionA: false,
conditionB: false
}];
then it's as simple as:
svg.selectAll('.SomeCircles')
.data(data)
.enter()
.append('circle')
.attr('r', 20)
.attr('cx', function(d, i) {
return i * 25 + 25;
})
.attr('cy', function(d, i) {
return i * 25 + 25;
})
.attr('class', 'SomeCircles')
.style('fill', function(d) { //<-- filling based on an attribute of my data
if (d.conditionA) {
return 'red';
} else if (d.conditionB) {
return 'green';
} else {
return 'blue';
}
});
Here is an example.
Related
I'm working on a bar chart that updates its data based on the mouseover of another element. When the chart updates, if there are less bars in the new chart, the chart permanently has fewer bars and changing the data back does not add them back in. I've added a gif to show this - when it gets down to 3 bars, they never come back.
Here's my code:
var scatter_versus_dataset; // the main set
var scatter_versus_dataset_filtered;
// set versus y scale
scatter_versus_y = d3.scaleBand().range([0, SCATTER_VERSUS_HEIGHT])
// set versus x scale
scatter_versus_x_fatal = d3.scaleLinear().range([0, SCATTER_VERSUS_WIDTH / 3]);
scatter_versus_x_nonfatal = d3.scaleLinear().range([-1 * SCATTER_VERSUS_WIDTH / 3, 0 ])
// set the versus colors
scatter_versus_z = d3.scaleOrdinal().range(STACK_COLOURS);
...
function updateScatterVersus(code){
// filter the set
scatter_versus_dataset_filtered = scatter_versus_dataset.filter(function (d) { return (d.majorOccCodeGroup == code) })
scatter_versus_y.domain(scatter_versus_dataset_filtered.map(function (d) { return d.occupation; })).padding(BAR_PADDING);
scatter_versus_x_fatal.domain([0, d3.max(scatter_versus_dataset_filtered, function (d) { return d.f_total_rate; })]).nice();
scatter_versus_x_nonfatal.domain([d3.min(scatter_versus_dataset_filtered, function (d) { return +-1 * d.nf_total_rate; }), 0]).nice();
var bars = d3.selectAll("#scatter_versus_fatal_rect")
.data(scatter_versus_dataset_filtered)
bars.exit()
.remove()
bars.transition()
.duration(600)
.attr("y", function (d) {
return scatter_versus_y(d.occupation);
})
.attr("x", function (d) {
return scatter_versus_x_fatal(0) + SCATTER_VERSUS_GAP_HALF;
})
.attr("width", function (d) {
return scatter_versus_x_fatal(d.f_total_rate);
})
.attr("height", scatter_versus_y.bandwidth())
bars.enter()
.append("rect")
.attr('id', 'scatter_versus_fatal_rect')
.classed("bar", true)
.attr("y", function (d) {
return scatter_versus_y(d.occupation);
})
.attr("x", function (d) {
return scatter_versus_x_fatal(0) + SCATTER_VERSUS_GAP_HALF;
})
.attr("width", function (d) {
return scatter_versus_x_fatal(d.f_total_rate);
})
.attr("height", scatter_versus_y.bandwidth())
}
The process for redrawing the other side of the chart is exactly the same. The problem is still there if i only draw one of the sides.
The data is just from a csv, and I don't think it's the problem - the filtered set has the right number of entries and it's fine in other charts. It's probably something to do with the removal and redrawing but I can't find many examples of this. Or perhaps a key? I can upload some data if needed but it's a pretty big CSV.
id in HTML is unique, only 1 tag should have it.
Select the div for the bars, then selectAll tags with class is bar and bind data.
Remove the id you add to the rects.
var bars = d3.select("#scatter_versus_fatal_rect")
.selectAll(".bar")
.data(scatter_versus_dataset_filtered);
bars.enter()
.append("rect")
// .attr('id', 'scatter_versus_fatal_rect')
.classed("bar", true)
......
I have a scatter plot and a table. Each circle in the scatter plot has a corresponding row in the table. When I apply classes to the circles for CSS purposes, I also want to have that same class be assigned to the corresponding table row. They have the same data value, but are appended to separate elements.
Here is my circle class event:
my_circles.each(function(d,i) {
if (my_bool===true) {
d3.select(this).classed('selected',true);
//d3.selectAll('tr').filter(d===???)
}
});
I was trying to use a filter to select only the table rows of matching d value, but it didn't quite work out, I didn't know how to finish the line. Which got me thinking, maybe there is a better way, like the post title, assign classes to all elements bound to the same data.
If you have another solution aside from any of my ideas, that would be fine too.
Probably the easiest solution will be to check in the .classed() method for the tr selection, if the data bound to that tr matches the one for the selected circle.
my_circles.each(function(d,i) {
if (my_bool===true) {
d3.select(this).classed("selected",true);
d3.selectAll('tr')
.classed("selected", trData => d === trData); // Set class if data matches
}
});
This, however, is a bit clumsy and may be time-consuming because it will iterate over all trs each time this code is called. In case this is in an outer loop for handling multiple selected circles—as mentioned in your comment—things will get even worse.
D3 v4
For a slim approach I would prefer using D3's local variables, which are new to v4, to store the references between circles and table rows. This will require just a one-time setup which will depend on the rest of your code, but might go somewhat along the following lines:
// One-time setup
var tableRows = d3.local();
my_circles.each(function(d) {
var row = d3.selectAll("tr").filter(trData => d === trData);
tableRows.set(this, row); // Store row reference for this circle
});
This creates a new local variable tableRows which is used to store the reference to the corresponding table row for each circle. Later on you are then able to retrieve the reference to the row without the need for further iterations.
my_circles.each(function(d,i) {
if (my_bool===true) {
d3.select(this).classed('selected',true);
tableRows.get(this).classed("selected", true); // Use local variable to get row
}
});
D3 v3
If you are not yet using D3 there are, of course, other ways to achieve the same thing. Personally, I would prefer using a WeakMap to store the references. Because the API of the WeakMap also features get and set methods similar to d3.local, all you need to do is to change the line creating the local reference store while keeping the rest of the above code as is:
// var tableRows = d3.local();
var tableRows = new WeakMap(); // use a WeakMap to hold the references
You can use dataIndex for this purpose. Here is a code snippet for the same.
var data = ["A", "B", "C"];
var color = d3.scale.category10();
var container = d3.select("body")
.append("svg")
.attr("height", 500)
.attr("width", 500);
var my_circles = container.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("name", function(d, i) {
return "circle" + i
})
.attr("r", 10)
.attr("cx", function(d, i) {
return (i + 1) * 50
})
.attr("cy", function(d, i) {
return (i + 1) * 50
})
.style("fill", function(d, i) {
return color(i)
});
container.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("name", function(d, i) {
return "rect" + i
})
.attr("width", 15)
.attr("height", 15)
.attr("x", function(d, i) {
return i * 50 + 200
})
.attr("y", function(d, i) {
return (i + 1) * 50
})
.style("fill", function(d, i) {
return color(i)
});
my_circles.each(function(d, i) {
d3.select(this).classed("selected" + i, true);
container.selectAll("[name=rect" + i + "]").classed("selected" + i, true);
});
svg {
border: 1px solid black;
background: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I am working on a pie chart today in D3 (fun!).
Alright, the issue I am having is the colours aren't being assigned properly.
Here's the jsfiddle:
https://jsfiddle.net/zh34ud25/5/
Most(?) relevant code:
var color = d3.scale.ordinal()
.range(["#71b2b9", "#dcdcdc"]);
color.domain(d3.keys(dataUnbilledRevenue[0].values[0]).filter(function(key) {
if (key === 'Unbilled_Revenue'
|| key === 'Billed_Revenue') {
return key
}
}));
// This returns the data into two separate objects which can be graphed.
// In this case, Amount and Quantity.
var datasets = color.domain().map(function(name) {
return {
name: name,
values: dataUnbilledRevenue.map(function(d) {
return {
Value: +d.values[0][name]
};
})
};
});
This is simple, doing this will give the colors to pie:
pieValues.append("path")
.attr("d", arc)
.attr('class', 'pie-point')
.style("fill", function(d) {
return color(d.data.name)
})
working code here
So I have a .csv file having multiple columns of data.
x,y,d1,d2,d3
28,77,1,2,3
27,78,4,5,6
21,79,2,7,9
10,80,5,7,8
I am able to create a scatter plot of d1, d2 & d3 on a single graph but what is required is: first d1 is plotted then it is removed and d2 is plotted & so on.
I know this can be achieved using separate function & setTimeout() for each data set but I have many data sets like this, so writing same code multiple times is not efficient.
Can somebody help me out in this?
EDIT:
So this is a part of code modified according to what #Lars suggested and it is working as I wanted!
var indices= d3.keys(mydata1[0])
.filter(function(d) { return (d !== "xaxis" && d!="yaxis"); }).sort();
indices.forEach(function(d, i) {
setTimeout(function() { update(d); }, 5000 * i);
});
function update(idx) {
p.selectAll("ellipse").remove(); //remove previous plot--to give animation like effect
p.selectAll(".R")
.attr("class", "ellipse")
.data(mydata1)
.enter()
.append("ellipse")
.attr("cx", function(d){ return scaleX(d["xaxis"]);})
.attr("cy", function(d){return scaleY(d["yaxis"]);})
.attr({
"rx": 3,
"ry": 4,
})
.attr("fill", function(d)
{
d[idx]=(d[idx]/2)+32;
for(i=0; i<64; i++)
{
if (d[idx]==0)
return mycolor[0];
else if(d[idx]>i && d[idx]<=(i+1))
return mycolor[i];
else if(d[idx]<0)
return "none";
}
});
}
You have basically two ways of doing this. First, setTimeout to update the part of the data that is referenced. This would look something like the following, assuming data holds your data.
function update(idx) {
svg.selectAll("circle")
.attr("cx", function(d) { return xScale(d[idx]); })
.attr("cy", function(d) { return yScale(d[idx]); });
}
var indices = ["d1", "d2", "d3"];
indices.forEach(function(d, i) {
setTimeout(function() { update(d); }, 1000 * i);
});
Alternatively, you can use D3's .transition() to effect the same thing. This is a bit awkward as it wasn't intended for this kind of thing. The idea is to create a set of dummy elements to drive the transitions.
svg.selectAll("dummy").data(indices).enter().append("dummy")
.transition().duration(1000).delay(function(d, i) { return i * 1000; })
.attr("foo", function(d) { update(d); });
I would recommend going with the approach that uses setTimeout.
I am trying to create a scatterplot of hundreds of datapoints, each with about 5 different attributes.
The data is loaded from a .csv as an array of objects, each of which looks like this:
{hour: "02",yval: "63",foo: "33", goo:"0", bar:"1"},
I want to display the scatterplot with the following attributes:
Shape for bar:
-circle to represent all points where bar=0, and a triangle-down to represent those where bar=1 (this is a dummy variable).
Color for foo and goo:
All points start as grey. goo is categorical with values [0,1,2] while foo is quantitative with a range from 0-50. foo and goo are mutually exclusive, so only one of them has a value. In other words, for each data point either foo=0 or goo=0.
Points with goo=1 should be orange; points with goo=2 should be red.
foo should be mapped onto a linear color scale from light blue to dark blue, ie d3.scale.linear().domain([0, 50]).range(["#87CEFF", "#0000FF"]);
I can do each of these individually, but defining everything together is creating issues for me.
My code with reproducible data is here: http://jsfiddle.net/qy5ohw0x/3/
Issues
For the symbol, i tried
.append("svg:path")
.attr("d", d3.svg.symbol())
which did not work. I tried a different approach altogether, but this did not map the values correctly:
var series = svg.selectAll("g.series")
.data(dataSet, function(d, i) { return d.bar; })
.enter()
.append("svg:g")
series.selectAll("g.point")
.data(dataSet)
.enter()
.append("svg:path")
.attr("transform", function(d, i) { return "translate(" + d.hour + "," + d.yval + ")"; })
.attr("d", function(d,i, j) { return d3.svg.symbol().type(symbolType[j])(); })
.attr("r", 2);
For the goo colors (grey/orange/red), i mapped the values to the 3 colors manually:
First define var colors = ["grey", "orange", "red"];
Then while drawing the data points chain
.style("fill", function (d) { return colors[d.type]; })
This worked alone, but not with the different symbols.
Finally, can i chain a second color .attr for foo? d3.scale.linear().domain([0, 50]).range(["#87CEFF", "#0000FF"]); would probably work if this is possible.
Again, the jsfiddle is here: http://jsfiddle.net/qy5ohw0x/3/
Thanks!!
Just do all the logic and comparisons in a function(d) for each attribute.
First set up some helpers:
// symbol generators
var symbolTypes = {
"triangleDown": d3.svg.symbol().type("triangle-down"),
"circle": d3.svg.symbol().type("circle")
};
// colors for foo
var fooColors = d3.scale
.linear()
.domain([0, 50])
.range(["#87CEFF", "#0000FF"]);
Then append a path for each symbol:
svg.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("class", "dot")
// position it, can't use x/y on path, so translate it
.attr("transform", function(d) {
return "translate(" + (x(d.hour) + (Math.random() * 12 - 6)) + "," + y(d.yval) + ")";
})
// assign d from our symbols
.attr("d", function(d,i){
if (d.bar === "0") // circle if bar === 0
return symbolTypes.circle();
else
return symbolTypes.triangleDown();
})
// fill based on goo and foo
.style("fill", function(d,i){
if (d.goo !== "0"){
if (d.goo === "1")
return "red";
else
return "orange";
}else{
return fooColors(d.foo);
}
});
Updated fiddle.
On a side note, I actually think straight d3 is way more intuitive than nvd3 for this situation.
It's much simplier with nvd3.js
function prepareData (data) {
return [{
key: 'Group 1',
values: data.map(function (item) {
item.shape = item.bar == "0" ? 'circle' : 'triangle-down';
item.x = Number(item.hour);
item.y = Number(item.yval);
item.size = 0.1;
item.disabled = Math.random() > 0.4;
return item;
})
}]
}
nv.addGraph(function() {
var chart = nv.models.scatterChart()
.showDistX(false)
.showDistY(true)
.showLegend(false)
//Axis settings
chart.xAxis.tickFormat(d3.format('3.0f'));
chart.yAxis.tickFormat(d3.format('3.0f'));
d3.select('#chart svg')
.datum(prepareData(dataSet))
.call(chart)
// A bit hacky but works
var fooscale = d3.scale.linear().domain([0, 50]).range(["#87CEFF", "#0000FF"]);
function colorer(d) {
if (d.goo == '1')
return 'orange';
else if (d.goo == '2')
return 'red';
else if (d.goo == '0')
return fooscale(d.foo);
return 'gray';
}
d3.selectAll('.nv-point')
.attr({
'stroke': colorer,
'fill': colorer
})
nv.utils.windowResize(chart.update);
return chart;
});
See https://jsfiddle.net/qy5ohw0x/4/
PS Unfortunately Nvd3 lacks docs, so use it's github instead