[Vega] Allow faceted Vega-Lite charts to take correct size (#103352)

* [Vega] Allow faceted Vega-Lite charts to take correct size

* Add unit test

* Update autosize docs

* Add warning when autosize=none

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Wylie Conlon 2021-06-28 14:41:06 -04:00 committed by GitHub
parent 3f0197e323
commit cfd836014d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 27 deletions

View file

@ -23,16 +23,8 @@ Learn more about {kib} extension, additional *Vega* resources, and examples.
====== Automatic sizing
Most users will want their Vega visualizations to take the full available space, so unlike
Vega examples, `width` and `height` are not required parameters in {kib}. To set the width
or height manually, set `autosize: none`. For example, to set the height to a specific pixel value:
```
autosize: none
width: container
height: 200
```
The default {kib} settings which are inherited by your visualizations are:
Vega examples, `width` and `height` are not required parameters in {kib} because your
spec will be merged with the default {kib} settings in most cases:
```
autosize: {
@ -43,17 +35,36 @@ width: container
height: container
```
{kib} is able to merge your custom `autosize` settings with the defaults. The options `fit-x`
and `fit-y` are supported but not recommended over the default `fit` setting.
These default settings are *not* applied if:
* <<vega-with-a-map, Your spec uses `type=map`>>
* Your spec is Vega-Lite and contains a facet, row, column, repeat, or concat operator. In these
cases, providing `width` and `height` will affect the child size.
To set the width or height manually, set `autosize: none` and provide the exact pixel sizes, including
padding for the title, legend and axes.
```
autosize: none
width: 600
height: 200
padding: {
top: 20
bottom: 20
left: 55
right: 150
}
```
To learn more, read about
https://vega.github.io/vega/docs/specification/#autosize[autosize]
in the Vega documentation.
https://vega.github.io/vega/docs/specification/#autosize[Vega autosize]
and https://vega.github.io/vega-lite/docs/size.html[Vega-Lite autosize].
WARNING: Autosize in Vega-Lite has https://vega.github.io/vega-lite/docs/size.html#limitations[several limitations]
that can result in a warning like `Autosize "fit" only works for single views and layered views.`
The recommended fix for this warning is to convert your spec to Vega using the <<vega-browser-debugging-console, browser console>>
NOTE: Autosize in Vega-Lite has https://vega.github.io/vega-lite/docs/size.html#limitations[several limitations]
which can affect the height and width of your visualization, but these limitations do not exist in Vega.
If you need full control, convert your spec to Vega using the <<vega-browser-debugging-console, browser console>>
`VEGA_DEBUG.vega_spec` output.
To disable these warnings, you can <<vega-additional-configuration-options, add extra options to your spec>>.
[float]
[[vega-theme]]

View file

@ -14,6 +14,29 @@ import { bypassExternalUrlCheck } from '../vega_view/vega_base_view';
jest.mock('../services');
describe(`VegaParser.parseAsync`, () => {
function check(spec, useResize, expectedSpec, warnCount) {
return async () => {
const searchApiStub = {
search: jest.fn(() => ({ toPromise: jest.fn(() => Promise.resolve({})) })),
resetSearchStats: jest.fn(),
};
expectedSpec = expectedSpec || cloneDeep(spec);
const mockGetServiceSettings = async () => {
return {
getFileLayers: async () => [],
getUrlForRegionLayer: async (layer) => {
return layer.url;
},
};
};
const vp = new VegaParser(spec, searchApiStub, 0, 0, mockGetServiceSettings);
await vp.parseAsync();
expect(vp.warnings).toHaveLength(warnCount || 0);
expect(vp.useResize).toEqual(useResize);
expect(vp.vlspec).toEqual(expectedSpec);
};
}
test(`should throw an error in case of $spec is not defined`, async () => {
const vp = new VegaParser('{}');
@ -23,6 +46,41 @@ describe(`VegaParser.parseAsync`, () => {
vp.error.startsWith('Your specification requires a "$schema" field with a valid URL')
).toBeTruthy();
});
test(
`should apply autosize on layer spec`,
check(
{
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
layer: [{ mark: 'bar' }],
encoding: { x: { field: 'a' } },
},
true,
expect.objectContaining({
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
layer: [{ mark: 'bar' }],
encoding: { x: { field: 'a' } },
autosize: { type: 'fit', contains: 'padding' },
width: 'container',
height: 'container',
})
)
);
test(
`should not apply autosize on faceted spec`,
check(
{
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
mark: 'circle',
encoding: { row: { field: 'a' } },
},
false,
expect.not.objectContaining({
autosize: { type: 'fit', contains: 'padding' },
})
)
);
});
describe(`VegaParser._setDefaultValue`, () => {

View file

@ -14,7 +14,7 @@ import { euiPaletteColorBlind } from '@elastic/eui';
import { euiThemeVars } from '@kbn/ui-shared-deps/theme';
import { i18n } from '@kbn/i18n';
import { logger, Warn, version as vegaVersion } from 'vega';
import { logger, Warn, None, version as vegaVersion } from 'vega';
import { compile, TopLevelSpec, version as vegaLiteVersion } from 'vega-lite';
import { EsQueryParser } from './es_query_parser';
import { Utils } from './utils';
@ -149,14 +149,14 @@ The URL is an identifier only. Kibana and your browser will never access this UR
if (this.useMap) {
this.mapConfig = this._parseMapConfig();
this.useResize = false;
} else if (this.spec) {
this._compileWithAutosize();
}
await this._resolveDataUrls();
if (this.isVegaLite) {
this._compileVegaLite();
} else {
this._compileWithAutosize();
}
}
@ -238,6 +238,36 @@ The URL is an identifier only. Kibana and your browser will never access this UR
* Convert VegaLite to Vega spec
*/
private _compileVegaLite() {
if (!this.useMap) {
// Compile without warnings to get the normalized spec, this simplifies the autosize detection
const normalized = compile(this.spec as TopLevelSpec, { logger: logger(None) }).normalized;
// Vega-Lite allows autosize when there is a single mark or layered chart, but
// does not allow autosize for other specs.
if ('mark' in normalized || 'layer' in normalized) {
this._compileWithAutosize();
} else {
this.useResize = false;
if (
normalized.autosize &&
typeof normalized.autosize !== 'string' &&
normalized.autosize.type === 'none'
) {
this._onWarning(
i18n.translate('visTypeVega.vegaParser.widthAndHeightParamsAreRequired', {
defaultMessage:
'Nothing is rendered when {autoSizeParam} is set to {noneParam} while using faceted or repeated {vegaLiteParam} specs. To fix, remove {autoSizeParam} or use {vegaParam}.',
values: {
autoSizeParam: '"autosize"',
noneParam: '"none"',
vegaLiteParam: 'Vega-Lite',
vegaParam: 'Vega',
},
})
);
}
}
}
this.vlspec = this.spec;
const vegaLogger = logger(Warn); // note: eslint has a false positive here
vegaLogger.warn = this._onWarning.bind(this);

View file

@ -232,18 +232,15 @@ export class VegaBaseView {
}
resize() {
if (this._parser.useResize && this._view && this.updateVegaSize(this._view)) {
if (this._parser.useResize && this._view) {
this.updateVegaSize(this._view);
return this._view.runAsync();
}
}
updateVegaSize(view) {
// For some reason the object is slightly scrollable without the extra padding.
// This might be due to https://github.com/jquery/jquery/issues/3808
// Which is being fixed as part of jQuery 3.3.0
const heightExtraPadding = 6;
const width = Math.max(0, this._$container.width());
const height = Math.max(0, this._$container.height()) - heightExtraPadding;
const width = Math.floor(Math.max(0, this._$container.width()));
const height = Math.floor(Math.max(0, this._$container.height()));
if (view.width() !== width || view.height() !== height) {
view.width(width).height(height);