Reputation: 2617
I am using d3 to color code the fill of circles based on an attribute called type
. It may have only one type, or it may have a maximum of two. My data is in this structure:
var data = [{'company':'companyA','products':23,'aum':25997692757,'type':['Industry senior exe'],'date':'2015-02'},
{'company':'companyB','products':24,'aum':3548692757,'type':['Industry senior exe','Star'],'date':'2016-02'}
];
Note that the type
attribute is a list.
var colorMap = {
'Industry senior exe':'blue',
'Star':'Gray'
};
If there is only one type
then the fill of the circle is trivial:
.style('fill', function(d) { return colorMap[d.type[0]})
However what seems to be a bit loftier is how to handle the case when the length of the list is longer -- d.type[1]
. My goal is: if type
is more than one, the circle will be half blue and half gray. It sound simple, but it has really stumped me.
How can I handle the dynamic fill logic as articulated above in d3?
Upvotes: 2
Views: 448
Reputation: 102174
Simply put: you cannot (under the current specifications) fill one element like <circle>
with more than one colour. Therefore, one possible solution is using an SVG <linearGradient>
.
The drawback in that approach is that supposing you have several different circles with several different colour choices, giving a huge amount of combinations, you'll need to create that same huge amount of linear gradients.
For instance, using an each
after your enter selection:
var gradient = svg.append("defs")
.append("linearGradient")
.attr("id", "gradient" + i)
.attr("x1", "0%")
.attr("x2", "100%")
gradient.append("stop")
.attr("offset", "50%")
.attr("stop-color", colorMap[d.type[0]])
gradient.append("stop")
.attr("offset", "50%")
.attr("stop-color", d.type.length === 2 ? colorMap[d.type[1]] : colorMap[d.type[0]]);
Here is the demo using your data:
var data = [{
'company': 'companyA',
'products': 23,
'aum': 25997692757,
'type': ['Industry senior exe'],
'date': '2015-02'
},
{
'company': 'companyB',
'products': 24,
'aum': 3548692757,
'type': ['Industry senior exe', 'Star'],
'date': '2016-02'
}
];
var colorMap = {
'Industry senior exe': 'blue',
'Star': 'gray'
};
var svg = d3.select("svg");
var circles = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", (_, i) => 50 + i * 100)
.attr("r", 40);
circles.each(function(d, i) {
var gradient = svg.append("defs")
.append("linearGradient")
.attr("id", "gradient" + i)
.attr("x1", "0%")
.attr("x2", "100%")
gradient.append("stop")
.attr("offset", "50%")
.attr("stop-color", colorMap[d.type[0]])
gradient.append("stop")
.attr("offset", "50%")
.attr("stop-color", d.type.length === 2 ? colorMap[d.type[1]] : colorMap[d.type[0]]);
d3.select(this)
.attr("fill", "url(#gradient" + i + ")")
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
On the other hand, if you have only two combinations — all blue and blue-gray — you'll need only two linear gradients.
Upvotes: 3