Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply different font to each line in label annotation options #739

Closed
antbankdata opened this issue May 17, 2022 · 9 comments · Fixed by #801
Closed

Apply different font to each line in label annotation options #739

antbankdata opened this issue May 17, 2022 · 9 comments · Fixed by #801

Comments

@antbankdata
Copy link

Hi,

I am trying to create the following label in my graph:
image

I am, however, unable to make the second line bold, unless i either create 2 labels or create a canvas (which doesn't scale very well).

Would it be possible to change the font parameter to accept a list of FontSpec? Or possibly change content, such that you can pass text styling options alongside the text?

@stockiNail
Copy link
Collaborator

stockiNail commented May 17, 2022

@antbankdata as you have seen, you can do it by a canvas or image and not configuring the plugin and not sure if this could be an enhancement to implement as common use case.

Nevertheless I have prepared a codepen where, leveraging on the canvas, you could have a multi-line content with different font for each row.

The getLabelContent function can be improved (it's just a sample and I didn't spend too much time to improve that) and the canvas should be scalable, based on the content and fonts passed as arguments.

https://codepen.io/stockinail/pen/KKQWzzR

image

@stockiNail
Copy link
Collaborator

@antbankdata FYI, I have created another codepen, https://codepen.io/stockinail/pen/MWQpZKb, changing the function to create the label by a canvas.
It improves a little bit, invoking toFont only once and adds colors and textAlign options.

Hopefully this solves your issue.

image

// checks and gets the index
// when colors and fonts, passed as arguments are not the same length of the content length
const checkAndGetIndex = (i, array) => Math.max(0, Math.min(i, array.length - 1));

/**
 * @param {string|string[]} content - text of the label
 * @param {FontSpec|FontSpec[]} pfonts - the fonts to apply to each row of the content.
 *     If less than the content length, the last font object is used for the rest of the lines 
 * @param {Color|Color[]} [pcolors = ['black']] - the colors to apply to each row of the content.
 *     If less than the content length, the last color is used for the rest of the lines 
 * @param {LabelTextAlign} [textAlign = 'center'] - text alignment inside the canvas
 * @returns {HTMLCanvasElement} the canvas to add to annotation label as content
 */
const createTextLabel = function(content, pfonts, pcolors = ['black'], textAlign = 'center') {
  // check and normalize the arguments
  const lines = Chart.helpers.isArray(content) ? content : [content];
  const fonts = Chart.helpers.isArray(pfonts) ? pfonts : [pfonts];
  const colors = Chart.helpers.isArray(pcolors) ? pcolors : [pcolors];
  const toFonts = [];
  fonts.forEach((el) => toFonts.push(Chart.helpers.toFont(el)));
  // create canvas to return
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  // calculate the size of the canvas
  // based on content and fonts 
  const count = lines.length;
  let width = 0;
  let height = 0;
  for (let i = 0; i < count; i++) {
    const font = toFonts[checkAndGetIndex(i, toFonts)];
    const text = lines[i];
    ctx.font = font.string;
    width = Math.max(width, ctx.measureText(text).width);
    height += font.lineHeight;
  }
  canvas.width = width;
  canvas.height = height;
  // draw the text content on the canvas
  ctx.save();
  ctx.textBaseline = 'middle';
  ctx.textAlign = textAlign;
  let x = 0;
  if (textAlign === 'center') {
    x = width / 2;
  } else if (textAlign === 'end' || textAlign === 'right') {
    x = width;
  }
  let y = 0;
  for (let i = 0; i < count; i++) {
    const font = toFonts[checkAndGetIndex(i, toFonts)];
    const lh = font.lineHeight;
    y += lh / 2;
    const text = lines[i];
    ctx.fillStyle = colors[checkAndGetIndex(i, colors)];
    ctx.font = font.string;
    ctx.fillText(text, x, y);
    y += lh / 2;
  }
  ctx.restore();
  return canvas;
};

@kurkle @LeeLenaleee I was thinking to add a sample about this use case. Make sense to you?

@LeeLenaleee
Copy link
Collaborator

Feels a bit to complex to me to be used in an example.

@stockiNail
Copy link
Collaborator

Feels a bit to complex to me to be used in an example.

Anyway the code is written here therefore whoever can take and use it if wants.

@antbankdata
Copy link
Author

@stockiNail Thank you very much for the quick and detailed answer. I will likely implement some variation of this createTextLabel function.

I do however still want to emphasize that i, as a consumer of the chartjs-plugin-annotation, would very much prefer to be able to implement it more elegantly, by just passing it as a property.

@stockiNail
Copy link
Collaborator

I do however still want to emphasize that i, as a consumer of the chartjs-plugin-annotation, would very much prefer to be able to implement it more elegantly, by just passing it as a property.

@antbankdata yes, I just proposed a work-around so you are not stuck, if urgently needed. I have tagged this issue as enhancement because this shall be evaluated, before closing it.

In my opinion, going to a multiple fonts (and colors) for the labels, we could create a different way to manage labels comparing with Chart.js where a text can have only 1 font and color. It is only a doubt as it can be implemented externally.

@kurkle @LeeLenaleee what do you think?

@LeeLenaleee
Copy link
Collaborator

I am bit scared for the rabit hole you can open with this. Like for this use case you only need to be able to style individual lines. But what happens if someone wants to style indevidual words or even individual letters. Need to give the current text render implementation a good look but I think it will be a pain in the ass to make it work nicely

@antbankdata
Copy link
Author

@stockiNail
Hi.

When i try to implement the canvas solution, the text becomes a little blurry (at font size 12). I have tried several solutions online to make the text less blurry (using devicePixelRatio), however, it remains blurry. Do you know if it is possible to fix the blur issue, or if that is just the downside of using canvas?

You can see the issue in your newest codepen if you change the font size to 12 and size of the chart to 300x200.

@stockiNail
Copy link
Collaborator

@antbankdata I think that it may be due to the width and height values which are set to canvas dimension (and automatically rounded to an integer).

Can you try the following?

  ...
  canvas.width = Math.ceil(width);
  canvas.height = Math.ceil(height);
  ...

I have changed the the codepen and, if I'm not wrong, sounds a bit better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants