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

feat(partition): monotonic font size scaling #681

Merged
merged 15 commits into from
May 27, 2020

Conversation

monfera
Copy link
Contributor

@monfera monfera commented May 25, 2020

Summary

  • Fixes Partition chart text should scale uniformly along sectors/rings  #661 by implementing monotonic font size scaling
  • Turns the font size selection from O(N) to O(log(N)), resulting in one order of magnitude speedup for complex cases (many nodes, broad font size range, fine font size increments)
  • The hill climber, initially used for just linked label text truncation, is extracted and reused for this
  • The hill climber accuracy was improved, and the PR exploits this by truncating less of the linked label texts
  • Very large functions in fill_text_layout.ts got broken up, and some changes were needed by this (typed arg lists) as well as by switching to the hill climber, but otherwise its logic remained unchanged (fill part of mocks didn't change except the two changed 3-layer ones)
  • Improves typing in general, adds some generics and retires a few anys

Details

The feature implements per-layer monotonic font scaling.

image

The 3-layer sunburst and treemap have per layer knobs to see the effect.

image

The tradeoff is reduced scaling in legibility: a single bad case (eg. narrow box, long word) can yield a small, maybe unrenderably small text for all the subsequent boxes too, even if those could otherwise be rendered. For this reason, and also, not to cause a breaking change, the current PR has layers: [ { fillLabel: { monotonic: false } } ] as a default; can be changed if it's important enough to cause a breaking change. Also, maybe the current logic could be augmented by still allowing the use of the smallest font size even if there was a larger value with text that didn't fit even with the smallest font size.

The logic currently achieves monotonicity per layer. This is uncontroversial with the treemap as groove texts are bound to be small. For sunburst, it looks preferable too, because otherwise a single, small inner slice can lead to small fonts in all the outer rings. However it can still lead to cases where an outer sector has a larger font than an inner sector of lower value, eg. Mineral fuels vs Machinery / North America (can be the other way around too). Once we introduce global monotonicity, new tradeoffs of diminishing returns arise 😸

The per layer monotonicity also means that font size constraints do not get reset for groups. In other words, if there are two top level groups A and B in a two-layer chart, then the font size of a node in Group A can't be larger than any of the font sizes of nodes in Group B that have higher values.

There's no constraint for linearity or other proportionality. So it's still possible to have unbalanced texts, eg. if the biggest box is "China" and a very close runner-up is "United States" then the font size for the latter will be unproportionately small, also depending on box aspect ratios.

The current issue and PR response leaves other aspects of "text ink weight perception" unaddressed: text lengths and even the character shapes in the texts impact visual weight. Further, possible improvements might help, but again, there are diminishing returns, so prioritization would be needed:

  • shortening texts with ...
  • line break with hyphenation for long fill text words
  • letting the chart choose from an alternative set of text lengths as eg. SAP does, eg.
    • "UK" (or ISO "GB" / "GBR")
    • "United Kingdom"
    • "United Kingdom of Great Britain and Northern Ireland"
  • shoot for a horizontal aspect ratio for nodes with long words (general problem might be NP-hard)

These would bring improvements in addition to helping with text ink proportionality

Checklist

Delete any items that are not applicable to this PR.

  • Any consumer-facing exports were added to src/index.ts (and stories only import from ../src except for test data & storybook)
  • This was checked for cross-browser compatibility, including a check against IE11
  • Proper documentation or storybook story was added for features that require explanation or tutorials
  • Unit tests were updated or added to match the most common scenarios

@monfera monfera added enhancement New feature or request chore :partition Partition/PieChart/Donut/Sunburst/Treemap chart related labels May 25, 2020
@monfera monfera self-assigned this May 25, 2020
@monfera monfera requested a review from markov00 May 25, 2020 13:17
@codecov-commenter
Copy link

codecov-commenter commented May 25, 2020

Codecov Report

Merging #681 into master will increase coverage by 0.30%.
The diff coverage is 93.82%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #681      +/-   ##
==========================================
+ Coverage   72.63%   72.94%   +0.30%     
==========================================
  Files         266      266              
  Lines        8686     8711      +25     
  Branches     1696     1696              
==========================================
+ Hits         6309     6354      +45     
+ Misses       2338     2318      -20     
  Partials       39       39              
Impacted Files Coverage Δ
...hart_types/partition_chart/layout/config/config.ts 68.75% <ø> (ø)
...types/partition_chart/layout/types/config_types.ts 100.00% <ø> (ø)
...es/partition_chart/layout/types/viewmodel_types.ts 80.00% <ø> (ø)
...rtition_chart/layout/viewmodel/link_text_layout.ts 15.51% <16.66%> (+5.39%) ⬆️
.../chart_types/partition_chart/layout/utils/calcs.ts 78.57% <92.10%> (+28.57%) ⬆️
...rtition_chart/layout/viewmodel/fill_text_layout.ts 96.01% <98.03%> (+0.70%) ⬆️
..._types/partition_chart/layout/circline_geometry.ts 98.43% <100.00%> (ø)
...ypes/partition_chart/layout/viewmodel/viewmodel.ts 89.14% <100.00%> (-0.09%) ⬇️
...n_chart/state/selectors/get_legend_items_labels.ts 94.59% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9429dcf...0759cdd. Read the comment docs.

@@ -151,6 +151,10 @@ export const configMetadata = {
type: 'number',
reconfigurable: false, // there's no real reason to reconfigure it; finding the largest possible font is good for readability
},
monotonic: {
dflt: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion, but to me seems that a monotonic font scale avoids misunderstanding when reading the chart. I prefer having it on by default. The consumer then can opt to turn it off to maximize the font size depending on space. As you already highlighted in the PR description it's a tradeoff between readability and understandability.
@rshen91 @nickofthyme what do you think?
We should not be afraid of turning it on and mark the PR as breaking change, but we should prefer giving the user a good default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to switch, I was on the fence about it and the config is there either way. A single long word or narrow box can turn subsequent boxes less legible, if at all (see PR desc.) ie. assumes more about the data, iow more vulnerable input-wise. But, monotonically shrinking text is preattentively preferable 😄 The breaking change tipped the balance for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...maybe it's not even a breaking change? We don't have an API contract that specifies text maximization. We should retain freedom for changing data and text ink that honors API contracts. Worth a discussion, a strict interpretation would require a breaking change for any pixel level visual change, while a too loose interpretation would throw curveballs to dependency maintainers

Copy link
Collaborator

@nickofthyme nickofthyme May 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree, should default to true. I don't know if making it a breaking change is necessary though, It's not that noticeable of a change in most cases and it always looks better, IMHO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have strong feelings, but I think it's great we're making this available. I'm leaning towards the stance stated above, it's probably best to set it on by default and just have the option for consumers to set it off if readability suffers. I don't think it's a breaking change and it's nice that the consumer can always set it to false if they liked the font sizing beforehand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the common thinking, done in e166746

@@ -62,6 +62,7 @@ export interface FillFontSizeRange {
minFontSize: Pixels;
maxFontSize: Pixels;
idealFontSizeJump: Ratio;
monotonic: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a tsdoc to describe the effect of this property monotonic

Suggested change
monotonic: boolean;
/** the fonts are scaled in.... **/
monotonic: boolean;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: it got renamed in a subsequent commit e166746

type NumberMap = (n: number) => number;

/** @internal */
export function monotonicHillClimb(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even if this method is internal, a quick description of this method in a tsdoc is useful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -182,7 +182,7 @@ function rectangleConstruction(treeHeight: number, topGroove: number) {
}

/** @internal */
export function shapeViewModel(
export function shapeViewModel<C>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where we are using this generic, do we really need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch, it was used at some point but no more, will remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/chart_types/partition_chart/layout/utils/calcs.ts Outdated Show resolved Hide resolved
fontWeight,
valueFormatter,
padding,
} = Object.assign(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a type here that this merge is going to resolve to?

Suggested change
} = Object.assign(
}: MyType = Object.assign(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's currently no specific type for this, and making one up for this wouldn't be terribly informative as it's just the reiteration of what's in the const bracket. I'm planning further extraction from this function (not in this PR though) as it's still too large, in which case a proper boundary, carrying these resolved/cascaded values, needs to arise. But it needs a bit of design work, glad to chat about it on a call separately or on the team sync, the upshot is that I feel uncomfortable typing this transitional thing without that design. Still open to being convinced if I can learn about the reasons 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure that's fine, this merging of several values is just hard to decipher. Even if the types were defined inline that would be better, something like...

Suggested change
} = Object.assign(
}: {
fontWeight: number,
valueFormatter: () => string,
padding: number,
} = Object.assign(

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even a combination of types that satisfy the resolved values, surely you have all the types to combine into the correct values.

Suggested change
} = Object.assign(
}: MyFontType & Pick<MyStyleType, 'style'> = Object.assign(

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, this in fine for now just wanted to start the discussion. 👍

@monfera monfera merged commit ea2489b into elastic:master May 27, 2020
markov00 pushed a commit that referenced this pull request May 28, 2020
# [19.4.0](v19.3.0...v19.4.0) (2020-05-28)

### Bug Fixes

* **partition:** consider legendMaxDepth on legend size ([#654](#654)) ([9429dcf](9429dcf)), closes [#639](#639)

### Features

* **partition:** enable grooves in all group layers ([#666](#666)) ([f5b4767](f5b4767))
* **partition:** linked text overflow avoidance ([#670](#670)) ([b6e5911](b6e5911)), closes [#633](#633)
* **partition:** monotonic font size scaling ([#681](#681)) ([ea2489b](ea2489b)), closes [#661](#661)
* **tooltip:** improve positioning with popperjs ([#651](#651)) ([6512950](6512950)), closes [#596](#596)
@markov00
Copy link
Member

🎉 This PR is included in version 19.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@markov00 markov00 added the released Issue released publicly label May 28, 2020
AMoo-Miki pushed a commit to AMoo-Miki/OpenSearch-Dashboards that referenced this pull request Feb 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
chore enhancement New feature or request :partition Partition/PieChart/Donut/Sunburst/Treemap chart related released Issue released publicly
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Partition chart text should scale uniformly along sectors/rings
5 participants