Chart.js icon indicating copy to clipboard operation
Chart.js copied to clipboard

Customizeable Axis Title Rendering

Open sassomedia opened this issue 7 years ago • 4 comments

In order to make 2-axis (opposing axis) charts a bit more readable than this https://jsfiddle.net/tah74jm4/ (which line belongs to which label?), the designer wanted a color indication on the label for each line. I know there are legends, however that takes up additional space and is also not very flexible in positioning the legend items. In this case, a legend next to each label would better indicate which values belong to which line.

One option we were considering was coloring the label in the matching color of the line, however since the colors can be dynamic based on user selection, there could be colors which would make the label text hard to read. So this option was thrown out.

The option chosen was a black label with a dot next to it in the color of the line, as you can see here:

screen shot 2016-08-25 at 1 55 06 pm

This is a screenshot of an actual graph, which I was able to generate by making a slight modification in the draw function in core.scale.js in the scaleLabel.display part:

if (scaleLabel.display) {
  // Draw the scale label
  scaleLabelX = options.position === 'left' ? this.left + (scaleLabelFontSize / 2) : this.right - (scaleLabelFontSize / 2);
  scaleLabelY = this.top + ((this.bottom - this.top) / 2);
  var rotation = options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;

  // added "if-condition" allows for multi-colored label
  if (scaleLabel.labelString instanceof Array) {
    var x = 0;
    var fullText = '';
    var fullTextOffset;
    var labelOffset;

    context.save();

    helpers.each(scaleLabel.labelString, function (obj) {
      fullText += obj.text;
    });

    fullTextOffset = context.measureText(fullText).width / 2;
    labelOffset = options.position === 'left' ? fullTextOffset : fullTextOffset * -1;

    context.translate(scaleLabelX, scaleLabelY + labelOffset);
    context.rotate(rotation);
    context.font = scaleLabelFont;
    context.textBaseline = 'middle';

    helpers.each(scaleLabel.labelString, function (obj) {
      context.fillStyle = obj.color;
      context.fillText(obj.text, x, 0);
      x += context.measureText(obj.text).width;
    });

    context.restore();
  } else {
    context.save();
    context.translate(scaleLabelX, scaleLabelY);
    context.rotate(rotation);
    context.textAlign = "center";
    context.fillStyle = scaleLabelFontColor; // render in correct colour
    context.font = scaleLabelFont;
    context.textBaseline = 'middle';
    context.fillText(scaleLabel.labelString, 0, 0);
    context.restore();
  }
}

This would allow me to pass in an Array of Objects into the labelString as opposed to just a String. This looks like the following:

'yAxes': [{
  'id': 'y-axis-1',
  'type': 'linear',
  'position': 'left',
  'scaleLabel': {
    'display': true,
    'labelString': [{
      'text': '● ',
      'color': '#fc6062'
    }, {
      'text': 'My Left Label Text',
      'color': '#000'
    }],
    'fontFamily': 'AB',
    'fontColor': '#000',
    'fontSize': 11
  },
}, {
  'id': 'y-axis-2',
  'type': 'linear',
  'position': 'right',
  'scaleLabel': {
    'display': true,
    'labelString': [{
      'text': '● ',
      'color': '#4dc1e7'
    }, {
      'text': 'My Right Label Text',
      'color': '#000'
    }],
    'fontFamily': 'AB',
    'fontColor': '#000',
    'fontSize': 11
  }
}]

Obviously, a change like that should not be done directly in the library. If I were to create a custom Scale though, I would have to override the whole draw function, which is not ideal either as there is a lot of code in there which might change with library versions going forward.

Question is, how would I be able to implement something like this in the least intrusive way? Any suggestions would be greatly appreciated, thanks!

sassomedia avatar Aug 25 '16 19:08 sassomedia

One idea would be to factor out a drawLabel function. The current implementation would do what we currently do. Then only that function could be overridden. The challenge would be figuring out the API to that function so that we don't have a lot of duplicated code for getting config options, etc.

etimberg avatar Aug 25 '16 22:08 etimberg

Thanks for the suggestion! And yes, good point, that does get a bit tricky with the API. I'll give this some thought...

sassomedia avatar Aug 25 '16 22:08 sassomedia

If anyone is wanting to implement this, the idea is to replace these lines with a separate function that could be overridden to provide this behaviour. Shouldn't be too hard to do, the key will be documenting the API and explicitly making this function for public use.

etimberg avatar Mar 16 '17 01:03 etimberg

Hi , Is this issue is open , i want to work on it.

poojavirgo avatar Jul 13 '22 04:07 poojavirgo