GnuCash
Contact   Instructions
Bug 797893 - Unable to change font size in charts
Summary: Unable to change font size in charts
Status: RESOLVED FIXED
Alias: None
Product: GnuCash
Classification: Unclassified
Component: Reports (show other bugs)
Version: git-maint
Hardware: PC Linux
: Normal normal
Target Milestone: ---
Assignee: reports
QA Contact: reports
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-08-03 19:11 EDT by hong
Modified: 2020-09-30 17:11 EDT (History)
4 users (show)

See Also:


Attachments
Example chart under HiDPI on Debian. (126.89 KB, image/png)
2020-09-21 14:47 EDT, hong
no flags Details
sample patch (948 bytes, patch)
2020-09-22 06:30 EDT, Christopher Lam
no flags Details
don't set fontSize in options, copy from body selector into default fontSize (2.24 KB, patch)
2020-09-25 07:49 EDT, Christopher Lam
no flags Details

Description hong 2020-08-03 19:11:27 EDT
Since 4.x, I can no longer change the font size of charts in reports via editing stylesheets or selecting fonts. This leads to very dismal appearance on HiDPI displays.

Seems like this is caused by migration to charts.js.
Comment 1 Christopher Lam 2020-09-20 06:54:31 EDT
A screenshot in HiDPI would be useful -- which fonts are too small?
Comment 2 John Ralls 2020-09-20 15:40:48 EDT
chris, the issue isn't so much the HiDPI, it's that the chartjs strings aren't stylable with CSS, it's all hard-coded javascript. E.g. from an export of an Asset Piechart:

var chartjsoptions = {
  "type" : "pie",
  "data" :
  {
    "labels" : [ "Stocks - $3,988.28 (37.7%)", "Open Funds - $2,348.49 (
23.6%)", "Bonds - $1,952.50 (14.4%)", "Closed Funds - $972.66 (6
.4%)", "Purchase - $603.98 (5.1%)", "Primary - $457.51 (3.8%)", "Other -
 $325.39 (9.0%)"],
    "datasets" : [
      {
        "urls" : [ "gnc-report:id=21#", "gnc-report:id=22#", "gnc-report:id=23#"
, "gnc-report:id=24#", "gnc-register:acct-guid=d5bd17d34367962e5ba29e9a457a1015#
", "gnc-report:id=25#", "gnc-report:id=20#"],
        "data" : [ 3851988.28, 2412348.49, 1473952.5, 657672.66, 517603.98, 3898
57.51, 924525.39],
        "label" : "Accounts",
        "backgroundColor" : [ "#FF4136", "#FF851B", "#FFDC00", "#2ECC40", "#0074
D9", "#001f3f", "#85144b"],
        "borderColor" : [ "#FF4136", "#FF851B", "#FFDC00", "#2ECC40", "#0074D9",
 "#001f3f", "#85144b"]
      }]
  },
  "options" :
  {
    "maintainAspectRatio" : false,
    "chartArea" :
  {
      "backgroundColor" : "#fffdf6"
    },
    "legend" :
    {
      "position" : "right",
      "reverse" : false,
      "labels" :
      {
        "fontColor" : "black"
      }
    },
    "elements" :
    {
      "line" :
      {
        "tension" : 0
      },
      "point" :
      {
        "pointStyle" : false
      }
    },
    "tooltips" :
    {
      "callbacks" :
      {
        "label" : false
      }
    },
    "scales" :
    {
      "xAxes" : [
        {
          "display" : false,
          "type" : "category",
          "distribution" : "series",
          "offset" : true,
          "gridLines" :
          {
            "display" : true,
            "lineWidth" : 1.5
          },
          "scaleLabel" :
          {
            "display" : true,
            "labelString" : ""
          },
          "ticks" :
          {
            "fontSize" : 12,
            "maxRotation" : 30
          }
        },
        {
          "position" : "top",
          "ticks" :
          {
            "display" : false
          },
          "gridLines" :
          {
            "display" : false,
            "drawTicks" : false
          }
        }],
      "yAxes" : [
        {
          "stacked" : false,
          "display" : false,
          "gridLines" :
          {
            "display" : true,
            "lineWidth" : 1.5
          },
          "scaleLabel" :
          {
            "display" : 1.5,
            "labelString" : ""
          },
          "ticks" :
          {
            "fontSize" : 10,
            "beginAtZero" : false
          }
        },
        {
          "position" : "right",
          "ticks" :
          {
            "display" : false
          },
          "gridLines" :
          {
            "display" : false,
            "drawTicks" : false
          }
        }]
    },
    "title" :
    {
      "display" : true,
      "fontSize" : 16,
      "fontStyle" : "",
      "text" : [ "Assets", "Balance at 12/31/2019: $10,948.81"]
    }
  }
};
Comment 3 Christopher Lam 2020-09-21 01:50:09 EDT
Sure. I wanted to have an idea how bad things would look in HiDPI. I don't have a Retina display to test. Options to fix this are:

1) add options for fontSizes -- there are currently 3 different ones in use in html-chart - options in either General Preferences or from the Stylesheet.

2) add 1 option for fontSize multiplier (default 1) -- in General Pref or Stylesheet -- and we multiply fontsizes manually

3) add 1 option for built-in chartjs multiplier from https://www.chartjs.org/docs/latest/general/device-pixel-ratio.html although I do not know how it can/should/will look like.
Comment 4 Christopher Lam 2020-09-21 02:13:44 EDT
It could be that MacOS using libwebkit1 doesn't support https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio properly therefore causing this bug.
Comment 5 John Ralls 2020-09-21 11:12:22 EDT
(In reply to Christopher Lam from comment #4)
> It could be that MacOS using libwebkit1 doesn't support
> https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
> properly therefore causing this bug.

That seems unlikely given that the reporter says they're using Linux.
Comment 6 John Ralls 2020-09-21 11:52:45 EDT
(In reply to Christopher Lam from comment #3)
> Sure. I wanted to have an idea how bad things would look in HiDPI. I don't
> have a Retina display to test. Options to fix this are:

You're narrowing the bug down too much. While the reporter complained that charts don't look good on his HiDPI display, the general bug is that charts can't be styled with the stylesheets and that it's a regression from 3.x.

ChartJS does all of the drawing directly so the code needs to read the stylesheet and populate that JSON array I pasted into comment 2 with the appropriate styles. I suggest you pick (and document in the wiki) some names corresponding to the json hierarchy. Start with making it style-able with the experimental CSS stylesheet since that's the overall direction we want to go.

Something like

chart.title { font-family: "Times"; font-size: "20px"; }

in the stylesheet should change the JSON to

   "title" :
    {
      "display" : true,
      "fontSize" : 20,
      "fontFamily" : "Times",
      "fontStyle" : "",
      "text" : [ "Assets", "Balance at 12/31/2019: $10,948.81"]
    }
  }

Don't forget to include chart.default.
Comment 7 hong 2020-09-21 14:47:24 EDT
Created attachment 373871 [details]
Example chart under HiDPI on Debian.

I now attached the screenshot of an example chart. The legend texts are extremely small and difficult to read.
Comment 8 Christopher Lam 2020-09-22 06:30:03 EDT
Created attachment 373872 [details]
sample patch

I think this type of patch would work. JS to copy CSS rules into chartjs options. Not tested completely.
Comment 9 Christopher Lam 2020-09-22 10:24:45 EDT
^ patch is obviously very incomplete and incorrect; idea is to create CSS rules and copy them onto chartjsoptions object.
Comment 10 John Ralls 2020-09-22 15:15:52 EDT
(In reply to Christopher Lam from comment #9)
> ^ patch is obviously very incomplete and incorrect; idea is to create CSS
> rules and copy them onto chartjsoptions object.

I think you want to get the CSS rules from Window.GetComputedStyle() rather than assuming the first stylesheet on the list and it would be better to iterate over all of the css nodes, though parsing all of the possibilities is going to be tedious.
Comment 11 Christopher Lam 2020-09-23 02:23:15 EDT
(In reply to John Ralls from comment #10)
> (In reply to Christopher Lam from comment #9)
> > ^ patch is obviously very incomplete and incorrect; idea is to create CSS
> > rules and copy them onto chartjsoptions object.
> 
> I think you want to get the CSS rules from Window.GetComputedStyle() rather
> than assuming the first stylesheet on the list and it would be better to
> iterate over all of the css nodes, though parsing all of the possibilities
> is going to be tedious.

I looked at this. It unfortunately requires that the HTML contains an element eg. a dummy <title id="mytitle"></title> that we want to query the resultant style. We can also enumerate all stylesheets in play and pick up the styles.
Comment 12 John Ralls 2020-09-23 20:37:14 EDT
Iterating over all of the stylesheets and computing which one's value applies for a particular property is nontrivial. Much better to let the browser do it. Maybe the dummy elements wouldn't be so bad.
Comment 13 Christopher Lam 2020-09-23 20:50:21 EDT
(In reply to John Ralls from comment #12)
> Iterating over all of the stylesheets and computing which one's value
> applies for a particular property is nontrivial. Much better to let the
> browser do it. Maybe the dummy elements wouldn't be so bad.

Not too bad: scans all stylesheets, apply chart.* selectors to JSON. Not working yet. If there's another stylesheets with chart.title the second one will overwrite first.

for (let stylesheet of document.styleSheets) {
  for (let rule of stylesheet.cssRules) {
    switch (rule.selectorText) {
      case 'chart.title':
        if (rule.style.fontFamily != \"\")
        {
          chartjsoptions.options.title.fontFamily = rule.style.fontFamily;
        }
        if (rule.style.fontSize != \"\")
        {
          chartjsoptions.options.title.fontSize = parseInt (rule.style.fontSize);
        }
        break;
      case 'chart.axes':
        if (rule.style.fontSize != \"\")
        {
          chartjsoptions.options.scales.xAxes[0].ticks.fontSize = parseInt (rule.style.fontSize);
          chartjsoptions.options.scales.yAxes[0].ticks.fontSize = parseInt (rule.style.fontSize);
        }
        break;
      case 'chart.legend':
        chartjsoptions.options.legend.labels.fontSize = parseInt (rule.style.fontSize);
        break;
      default:
        break;
    }
  }
}
Comment 14 John Ralls 2020-09-23 23:43:36 EDT
> If there's another stylesheets with chart.title the second one will overwrite first.

That's not the correct way to handle it. The stylesheets are ordered somehow (I don't know how) and the properties in the "higher" ones overwrite the properties in "lower" ones... but if a property is set in the lower one and *not* overwritten it's still in effect. Like I said, not trivial.
Comment 15 Christopher Lam 2020-09-24 03:45:38 EDT
(In reply to John Ralls from comment #14)
> > If there's another stylesheets with chart.title the second one will overwrite first.
> 
> That's not the correct way to handle it. The stylesheets are ordered somehow
> (I don't know how) and the properties in the "higher" ones overwrite the
> properties in "lower" ones... but if a property is set in the lower one and
> *not* overwritten it's still in effect. Like I said, not trivial.

Agree this way wouldn't be exactly the same as browsers. But I felt it was an acceptable hack (!!). FWIW for now, the reports only ever use 1 stylesheet.

If we want to create a dummy <title> then it'll increase complexity: javascript to create element, query the css, delete element. Moreover, to use "chart.title" "chart.labels" etc as CSS selector we'll need to use <chart class="title"/> <chart class="labels"/> and query the resulting style. This is another hack?

Easier is to use report vs stylesheet vs global option for fontSize or Multiplier (!!) and html-chart (or stylesheet renderer) can set the correct multiplier.

Easiest of all, is to figure out how to make libwebkit1&2 display HiDPI-appropriate fontSizes; but I don't have HiDPI to experiment with.
Comment 16 John Ralls 2020-09-24 12:19:54 EDT
> Easiest of all, is to figure out how to make libwebkit1&2 display HiDPI-appropriate fontSizes; but I don't have HiDPI to experiment with.

That's a too-narrow view of the bug. Besides, that part is clearly a Linux (and maybe Windows) bug: On macOS (webkit's home OS) it already works.

Oh, bingo. The element to hang the css off of is <canvas>, created at https://github.com/Gnucash/gnucash/blob/maint/gnucash/report/html-chart.scm#L410 where it's wrapped in a <div>. 

> If we want to create a dummy <title> then it'll increase complexity: javascript to create element, query the css, delete element. Moreover, to use "chart.title" "chart.labels" etc as CSS selector we'll need to use <chart class="title"/> <chart class="labels"/> and query the resulting style. This is another hack?

No, you just need 
  <div id="chart">
     <div id="title"/>
     <div id="legend"/>
     <div id="x-axis"/>
     <div id="y-axis"/>
     <div id="etc"/>
  </div>

There's no requirement at all for *displaying* elements and if the elements don't display then you don't have to delete them.
Comment 17 Christopher Lam 2020-09-24 22:29:25 EDT
Maybe OP can test export report confirm if fonts still tiny when opened in web browser?
Comment 18 hong 2020-09-25 01:08:16 EDT
When opened in a web browser, the fonts are normal.
Comment 19 Christopher Lam 2020-09-25 01:28:39 EDT
(In reply to hong from comment #18)
> When opened in a web browser, the fonts are normal.

This would suggest libwebkit2 is not receiving HiDPI status from the environment?
Comment 20 hong 2020-09-25 02:15:39 EDT
I think yes, as I have reported in webkit's bug tracker: https://bugs.webkit.org/show_bug.cgi?id=215062

But I think the comments above have made a good point: This is not so much about HiDPI; User could change font size in Gnucash 3.x, but lost the ability to do so in 4.x.
Comment 21 Christopher Lam 2020-09-25 07:49:00 EDT
Created attachment 373873 [details]
don't set fontSize in options, copy from body selector into default fontSize

Luckily for me Ubuntu still has v3.8. It seems the "Text cell" style is used as base for the html-chart. This corresponds to the "body" selector. Here's a better idea. @OP: This may not work on your libwebkit.

For me it works ^_^
Comment 22 Christopher Lam 2020-09-26 05:57:13 EDT
Candidate fix
https://github.com/Gnucash/gnucash/pull/789

Copies font-style, font-size and font-family from stylesheets into chartjs.
Works well for me.

Unsure:
GnuCash stylesheets describe font-size in points eg "24pt", but ChartJS wants pixels; I've implemented a 4/3 multiplier as described in https://en.wikipedia.org/wiki/Point_(typography)#Desktop_publishing_point in this PR.
Comment 23 Christopher Lam 2020-09-26 07:27:04 EDT
(In reply to Christopher Lam from comment #22)> 
> Unsure:
> GnuCash stylesheets describe font-size in points eg "24pt", but ChartJS
> wants pixels; I've implemented a 4/3 multiplier as described in
> https://en.wikipedia.org/wiki/Point_(typography)#Desktop_publishing_point in
> this PR.

Actually libwebkit does the pt -> px conversion automatically when running getComputedStyle, so, no multiplier is needed. I've updated the PR. If @jralls is happy with it, we can merge this weekend.
Comment 24 Christopher Lam 2020-09-26 14:39:48 EDT
Merged in time for 4.2.
Charts will copy style from "Title" and "Text cell" corresponding to html <h3> and <body>.
Comment 25 hong 2020-09-30 17:11:53 EDT
Thanks, Chris. I can confirm this is fixed.

Note You need to log in before you can comment on or make changes to this bug.