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(xy): adaptive tick raster #1420

Merged
merged 53 commits into from
Oct 13, 2021

Conversation

monfera
Copy link
Contributor

@monfera monfera commented Oct 7, 2021

Summary

PR for lowering the targeted tick count until a compliant tick raster is the result. Compliance criteria are informed by the need to convey truth on the chart, and also by dataviz best practices:

  1. All tick labels must fit (none should overlap with any another);
  2. The tick labels should have a preference for a (generally) decimal base, eg. 0-5-10 over 0-4-8-12
  3. Nicing, if applies, should be observed
  4. Landmark ticks, such as zero, or the extreme tick labels in case of nicing, must be preserved
  5. There must be at least two distinct ticks, otherwise the screen projection can't be correlated with the shown data, rendering the chart meaningless
  6. All ticks must have a unique label, otherwise there's ambiguity about which represents the true value (if any)
  7. The raster should yield even spacing of the tick/gridlines even if screenspace tick label sizes vary
  8. Coarser semantic units (eg. hours) should be split into an integral number of smaller units (eg. 1,2,3,4,5,6,10,12,15,20,30mins) - note that due to other constraints, some will be not generated, eg. a cadence of 3 minutes (not just our constraints but also what matured into underlying scale generators like D3, which we can relax if needed in the future)

Details

The current method relies on tick label culling (and optionally, tick/gridline culling) for avoiding overlap and duplicated tick labels, but the (inherently post hoc) culling of the tick labels doesn't guarantee compliance with the above items. For examples:

  • maybe, due to rounding in the formatter, some labels are culled due to duplication, but the surviving one isn't necessarily the one at the actual position of the shown value
  • there could be multiple generated ticks, but culling can remove some such that less than two remain
  • unusual cadences may be generated, eg. an unusual 3-minute cadence; decimal boundaries can't be prioritized by culling
  • variable tick label length (in pixels) can yield an uneven raster, where the distance between any two adjacent ticks can significantly vary
  • the zero value, and/or the last tick label, placed expressly by nicing, may be culled

In short, we deactivate the culling of a single raster; instead, we iteratively generate the raster that's closest to the desired tick count, while complying with the rules

adaptiveTickCount is set to false, ie. old mode, to make all but one VRT images identical with the current snapshots

Issues

Checklist

  • The proper chart type label has been added (e.g. :xy, :partition)
  • The proper feature labels have been added (e.g. :interactions, :axis)
  • The :theme label has been added and the @elastic/eui-design team has been pinged when there are Theme API changes
  • All related issues have been linked (i.e. closes #123, fixes #123)
  • New public API exports have been added to packages/charts/src/index.ts
  • Unit tests have been added or updated to match the most common scenarios
  • The proper documentation and/or storybook story has been added or updated
  • The code has been checked for cross-browser compatibility (Chrome, Firefox, Safari, Edge)
  • Visual changes have been tested with all available themes including dark, light, eui-dark & eui-light

@monfera monfera added enhancement New feature or request wip work in progress :axis Axis related issue :data Data/series/scales related issue :xy Bar/Line/Area chart related labels Oct 7, 2021
@monfera monfera marked this pull request as draft October 7, 2021 19:00
@monfera monfera force-pushed the duplicated-tick-label-fix branch 2 times, most recently from 222d8b9 to fbe20ba Compare October 10, 2021 10:07
@@ -161,7 +161,7 @@ module.exports = {
},
],
'sort-keys': 0,
'no-irregular-whitespace': 'error',
'no-irregular-whitespace': 'warn',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's fair play to use non-breaking space or whatever in strings. If such a value is commented out, it triggers this condition, and it can't be locally bypassed with an ignore line

};

/** @internal */
export function getAxesDimensions(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The former getAxesDimensions got split into a reducer part (this here) and an actual calculation part (getAxisSizeForLabel)

return axisLayerCount * maxLabelBoxGirth;
};

const getAxisSizeForLabel = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The logic in getAxisSizeForLabel matches the former calc in getAxesDimensions except for the use of getAllAxisLayersGirth which permits an axis girth reflecting multiple axis tick layers

const maxLabelBoxSize = horizontal ? maxLabelBboxHeight : maxLabelBboxWidth;
const labelSize = tickLabel.visible ? maxLabelBoxSize + innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0;
const maxLabelBoxGirth = horizontal ? maxLabelBboxHeight : maxLabelBboxWidth;
const allLayersGirth = getAllAxisLayersGirth(tickLabel, maxLabelBoxGirth, horizontal);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Drying up logic

@@ -91,30 +93,40 @@ const getJoinedVisibleAxesData = createCustomCachedSelector(
}, new Map()),
);

/** @internal */
export const getLabelBox = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Breaking out this from computeAxisTicksDimensionsSelector in part to detach the logic from the reduction, and in part to enable reuse of the logic

Comment on lines +29 to +32
export const axisSpecsLookupSelector = createCustomCachedSelector(
getAxisSpecsSelector,
(axisSpecs: AxisSpec[]): Map<string, AxisSpec> => axisSpecs.reduce((acc, spec) => acc.set(spec.id, spec), new Map()),
);
Copy link
Contributor Author

@monfera monfera Oct 10, 2021

Choose a reason for hiding this comment

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

Using it is O(1)

@monfera monfera force-pushed the duplicated-tick-label-fix branch 2 times, most recently from 106c0d8 to 4af0a38 Compare October 10, 2021 13:25
@monfera
Copy link
Contributor Author

monfera commented Oct 10, 2021

test this

@monfera monfera marked this pull request as ready for review October 12, 2021 09:39
Copy link
Member

@markov00 markov00 left a comment

Choose a reason for hiding this comment

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

Code changes LGTM
Tested locally

The changes on the number of ticks in a log scale are done on purpose to improve the scale reading.

There are few TODOs to fix on a subsequent PR:

  • there is a visible reduction of the number of ticks rendered for "standard" time axis, this happens due to the changes in the cadence on the time intervals (we don't allow 2 min steps for example, but we allow only 1 minute or 5 minute steps). This, if needed can be fixed in a subsequent PR
  • fix the generation of too many ticks under specific conditions (see the high volume story) (see VRT related changes)
  • remove the double-time axis example from storybook (one VRT to remove)
  • fix the deduplication under specific conditions: with non-time axis we should keep all the generated ticks and avoid the filtering in enableDuplicatedTicks to make the ticks reduction works correctly. (a VRT with wrong deduplication rendering was added and that need to be fixed ff686d7)

@nickofthyme nickofthyme removed the wip work in progress label Oct 12, 2021
Copy link
Collaborator

@nickofthyme nickofthyme left a comment

Choose a reason for hiding this comment

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

Code change LGTM, tested logic in storybook and looks to match defined conditions.

The time ticks are definitely more sparse than previously but I think this is cover in the future work.

I appreciate the added ticks for missing labels, I'd like to see this go future where we can define primary and secondary ticks and labels say 1000s on primary and 100 on secondary. I see similarities between this idea and the timeslip multilayer axis.

Comment on lines +60 to +69
/*
const midAxisLabelFormatter = (d: any) =>
`${whiskers ? ' ' : ''}${new Intl.DateTimeFormat('en-US', { hour: 'numeric' }).format(d).padStart(2, '0')} `;
const bottomAxisLabelFormatter = (d: any) =>
`${whiskers ? ' ' : ''}${new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(d)} `;
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we avoid committing comments? It's just the same to add a boolean knob here that uses this formatter or another. Commenting it serves no purpose.

Copy link
Contributor Author

@monfera monfera Oct 12, 2021

Choose a reason for hiding this comment

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

I agree, though as it's just a story, it might be fine for a short time, the upcoming PR removes this file

@markov00 markov00 merged commit 200577b into elastic:master Oct 13, 2021
@monfera monfera mentioned this pull request Oct 14, 2021
3 tasks
nickofthyme pushed a commit that referenced this pull request Oct 15, 2021
# [38.0.0](v37.0.0...v38.0.0) (2021-10-15)

### Bug Fixes

* **deps:** update dependency @elastic/eui to v39 ([#1422](#1422)) ([2ee97aa](2ee97aa))
* **goal:** reduce whitespace for circular charts ([#1413](#1413)) ([6517523](6517523))
* **interactions:** change allowBrushingLastHistogramBin to true ([#1396](#1396)) ([9fa9783](9fa9783))
* **xy:** remove wrongly represented null/missing values in tooltip ([#1415](#1415)) ([e5963a3](e5963a3)), closes [#1414](#1414)

### Code Refactoring

* scales ([#1410](#1410)) ([a53a2ba](a53a2ba))

### Features

* **scales:** add `LinearBinary` scale type ([#1389](#1389)) ([9f2e427](9f2e427))
* **xy:** adaptive tick raster ([#1420](#1420)) ([200577b](200577b))
* **xy:** apply the data value formatter to data values over bars ([#1419](#1419)) ([e673fc7](e673fc7))

### BREAKING CHANGES

* **interactions:** allowBrushingLastHistogramBucket renamed to allowBrushingLastHistogramBin on the Settings component defaults true and is only applied for histogram type charts
* LogScaleOptions.logBase` is now a `number` instead of the object enum `LogBase`. Some edge case data or configuration _might_, with a deemed low likelihood, lead to a situation where the earlier version would have silently not rendered a bar, line or point, while the new code doesn't `catch`, therefore throw an exception (see the last item). General risk of regressions due to the quantity of code changes (altogether 3.5k)
@nickofthyme
Copy link
Collaborator

🎉 This PR is included in version 38.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@nickofthyme nickofthyme added the released Issue released publicly label Oct 15, 2021
@nickofthyme nickofthyme linked an issue Oct 20, 2021 that may be closed by this pull request
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:axis Axis related issue :data Data/series/scales related issue enhancement New feature or request released Issue released publicly :xy Bar/Line/Area chart related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve axis ticks deduplication
3 participants