0

I want my bar graph to have different coloured versions of the same icon for the legend tiles. I am currently using font-awesome 5 for text in my HTML document but I do not mind using other icon libraries if I must (as long as it's free). At the moment my graph has squares for the legend tiles as that is the default. In font-awesome the icon I want is class="fa fa-bar-chart". The class for the legend tile is called .c3-legend-item-tile

I tried code from Use Font Awesome Icons in CSS but the code there didn't help change the legend tiles. Neither did Using Font Awesome icon for bullet points, with a single list item element

I don't want my icons to float above the bar chart like the example from Adding icons to bar charts made using c3js I just want the tiles to change from the default square to an icon.

https://jsfiddle.net/SharonM/k49gbs13/25/ has a rough example of what I've tried. (Labels show what I want the actual tiles to look like but I do not actually want labels)

.c3-chart-text .c3-text {
  font-family: 'FontAwesome';
}

.c3-legend-item-tile::before {
  font-family: 'FontAwesome';
  content: '\uf080'!important;
  font-weight: 900!important;
  margin: 0 5px 0 -15px!important;
}

I've tried before and after and also just the class itself.

Update:

I also tried:

d3.select(string).insert('div', '.chart').attr('class', 'legend').selectAll('span')
    .data(this.allIDs)
    .enter().append('span')
    .attr('data-id', function (id) { return id; })
    .html(function (id) { return '<i class="fa fa-bar-chart" aria-hidden="true"> </i>'+ id; })
    .each(function (id) {
        d3.select(this).style('background-color', chart.color(id));
    })
    .on('mouseover', function (id) {
        chart.focus(id);
    })
    .on('mouseout', function (id) {
        chart.revert();
    })
    .on('click', function (id) {
        chart.toggle(id);
    });

where string is the name of my container class but that did not do what I wanted at all. It created new 'legends' on the side with the icon I wanted but that bypassed my onclick checks when toggling which I could re-implement in this function but it just looks really bad. I'd rather the original little square was replaced by the icon.

Sharon
  • 138
  • 16

1 Answers1

1

Version for C3 < 0.7

It turns out the way to do this and keep the colours is to do stuff to the .c3-legend-item-tile(s). Since they're rectangles you can't add text to them but what you can do is use masks and patterns on them to give the impression of text.

My first attempt just replaced the fill style of the c3-legend-item-tiles with a pattern, where the pattern was the text character needed. However, this removed the colour and just showed them as black - not very handy

It turns out what you can do though is add a mask separately from the fill style and reuse the pattern within there --> How to change color of SVG pattern on usage? . Here we set the mask to be a rectangle, which in turn uses the pattern as a fill, and hey presto the 'icons' appear in the right colour as the fill style per tile stays as it was...

https://jsfiddle.net/j2596x0u/

var chart = c3.generate({
    data: {
        columns: [
            ['data1', 30, -200, -100, 400, 150, 250],
            ['data2', -50, 150, -150, 150, -50, -150],
            ['data3', -100, 100, -40, 100, -150, -50]
        ],

        type: 'bar',
        labels: {
//            format: function (v, id, i, j) { return "Default Format"; },
            format: {
                data1: function (v, id, i, j) { return "\uf080"; },
                data2: function (v, id, i, j) { return "\uf080"; },
                data3: function (v, id, i, j) { return "\uf080"; },
            }
        }
    },
    grid: {
        y: {
            lines: [{value: 0}]
        }
    },
    onrendered: function () {
        d3.selectAll(".c3-legend-item-tile").attr("mask", "url(#iconMask)");
    }
});

d3.select("svg defs").append("mask")
    .attr("id", "iconMask")
    .attr("x", 0)
  .attr("y", 0)
  .attr("width", 1)
  .attr("height", 1)
  .attr("maskContentUnits", "objectBoundingBox")
  .append("rect")
  .attr("x", 0)
  .attr("y", 0)
  .attr("width", 1)
  .attr("height", 1)
  .style("fill", "url(#iconPattern)")
;


d3.select("svg defs").append("pattern")
    .attr("id", "iconPattern")
    .attr("x", 0)
  .attr("y", 0)
  .attr("width", 1)
  .attr("height", 1)
  .style("fill", "#fff")
  .attr("patternContentUnits", "objectBoundingBox")
  .append("text")
  .attr("x", "0.2em")
  .attr("dy", "1em")
  .attr("font-size", 0.8)
  .attr ("font-family", "FontAwesome")
  .text("\uf080")
;

I won't pretend it was simple, I had to use objectBoundingBox and normalise everything in the masks and patterns to between 0 and 1 to get them to show up in the right place from the example I found, and the font-size was trial and error. But, yeh, it's doable.


C3 0.7+

Ha, it turns out the mask didn't work simply because the dom considers the lines to have zero height even though it is drawn with 10px thickness. If I change the y2 attribute so the line is technically a diagonal there's enough space for the text mask and pattern to render:

For c3 versions where .c3-legend-item-tile's are lines rather than rects

onrendered: function () {
   d3.selectAll(".c3-legend-item-tile").attr("mask", "url(#iconMask)")
      .each (function (d,i) {
            var d3this = d3.select(this);
          d3this.attr("y2", +d3this.attr("y1") + 10);
      })
   ;
}

https://jsfiddle.net/u7coz2p5/3/

mgraham
  • 6,147
  • 1
  • 21
  • 20
  • Thank you for your answer. It works in jsFiddle perfectly but when I tried it, it did not work for me. No Icons appear at all. Does it require any special dependencies? I have D3 version 5 and C3 version 0.7.2 if this makes any difference – Sharon Aug 05 '19 at 08:16
  • I'm also using FontAwesome 5 rather than 4 if that changes anything? – Sharon Aug 05 '19 at 08:42
  • 1
    Aaargh, in c3 0.7 they've changed the tiles to be thick lines rather than rects, I'm not sure if this can be worked around now. I'll have a look, but no promises – mgraham Aug 05 '19 at 09:00
  • Thank you :) At the moment I've just made the tiles 'display:none' and added a tspan in the text with the icon. Needed to set the CSS for each tspan. A slightly 'hacky' method but it looks nice as long as you don't look at the code! If you can't find a way to do it in c307 I'll accept your answer as long as you say the requirements so future viewers will know. – Sharon Aug 05 '19 at 09:41
  • I'll just use my tspan method for now since I need to use latest version of C3 but this answer is great for people needing to use earlier version of c3! :) – Sharon Aug 07 '19 at 11:24
  • 1
    I fixed it so it works with newer versions, but by the end of this you might think your tspan solution is the way to go :-) – mgraham Aug 07 '19 at 11:35
  • It's good to see it can be done! :D Thank you. I'll ask my manager which method he prefers when he gets back (I'm only an intern so I won't be the one maintaining the code) – Sharon Aug 07 '19 at 11:38