diff --git a/__tests__/integration/snapshots/static/alphabetIntervalAutoPadding.png b/__tests__/integration/snapshots/static/alphabetIntervalAutoPadding.png new file mode 100644 index 0000000000..6db15bb9a7 Binary files /dev/null and b/__tests__/integration/snapshots/static/alphabetIntervalAutoPadding.png differ diff --git a/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingCustom.png b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingCustom.png new file mode 100644 index 0000000000..178b06b622 Binary files /dev/null and b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingCustom.png differ diff --git a/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingD3Format.png b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingD3Format.png new file mode 100644 index 0000000000..77892a9883 Binary files /dev/null and b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingD3Format.png differ diff --git a/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingMaxRatio.png b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingMaxRatio.png new file mode 100644 index 0000000000..090d894c80 Binary files /dev/null and b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingMaxRatio.png differ diff --git a/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingNoTitle.png b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingNoTitle.png new file mode 100644 index 0000000000..1c47658ac1 Binary files /dev/null and b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingNoTitle.png differ diff --git a/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingPosition.png b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingPosition.png new file mode 100644 index 0000000000..e370f8dc0d Binary files /dev/null and b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingPosition.png differ diff --git a/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingStyle.png b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingStyle.png new file mode 100644 index 0000000000..26be18c73a Binary files /dev/null and b/__tests__/integration/snapshots/static/alphabetIntervalAutoPaddingStyle.png differ diff --git a/__tests__/integration/snapshots/static/browserLineAreaTrendingAutoPadding.png b/__tests__/integration/snapshots/static/browserLineAreaTrendingAutoPadding.png new file mode 100644 index 0000000000..747d10d3bc Binary files /dev/null and b/__tests__/integration/snapshots/static/browserLineAreaTrendingAutoPadding.png differ diff --git a/__tests__/integration/snapshots/static/flarePointCirclePackCustomAutoPadding.png b/__tests__/integration/snapshots/static/flarePointCirclePackCustomAutoPadding.png new file mode 100644 index 0000000000..eaae1fcd89 Binary files /dev/null and b/__tests__/integration/snapshots/static/flarePointCirclePackCustomAutoPadding.png differ diff --git a/__tests__/integration/snapshots/static/gdpIntervalFlexAutoPaddingRotate.png b/__tests__/integration/snapshots/static/gdpIntervalFlexAutoPaddingRotate.png new file mode 100644 index 0000000000..00fe1a1ce9 Binary files /dev/null and b/__tests__/integration/snapshots/static/gdpIntervalFlexAutoPaddingRotate.png differ diff --git a/__tests__/integration/snapshots/static/indicesLineChartScaleRelationsAutoPaddingRound.png b/__tests__/integration/snapshots/static/indicesLineChartScaleRelationsAutoPaddingRound.png new file mode 100644 index 0000000000..71cd7021ec Binary files /dev/null and b/__tests__/integration/snapshots/static/indicesLineChartScaleRelationsAutoPaddingRound.png differ diff --git a/__tests__/integration/snapshots/static/marketIntervalMarimekkoAutoPaddingFlex.png b/__tests__/integration/snapshots/static/marketIntervalMarimekkoAutoPaddingFlex.png new file mode 100644 index 0000000000..b47ea45e17 Binary files /dev/null and b/__tests__/integration/snapshots/static/marketIntervalMarimekkoAutoPaddingFlex.png differ diff --git a/__tests__/integration/snapshots/static/mockPointLogTicksAutoPaddingTickCount.png b/__tests__/integration/snapshots/static/mockPointLogTicksAutoPaddingTickCount.png new file mode 100644 index 0000000000..561bc0d23f Binary files /dev/null and b/__tests__/integration/snapshots/static/mockPointLogTicksAutoPaddingTickCount.png differ diff --git a/__tests__/integration/snapshots/static/ordersLineMissingFieldAutoPaddingUndefined.png b/__tests__/integration/snapshots/static/ordersLineMissingFieldAutoPaddingUndefined.png new file mode 100644 index 0000000000..9917be95fe Binary files /dev/null and b/__tests__/integration/snapshots/static/ordersLineMissingFieldAutoPaddingUndefined.png differ diff --git a/__tests__/integration/snapshots/static/peoplePointStackedAutoPaddingNumberLabel.png b/__tests__/integration/snapshots/static/peoplePointStackedAutoPaddingNumberLabel.png new file mode 100644 index 0000000000..c9b86b8462 Binary files /dev/null and b/__tests__/integration/snapshots/static/peoplePointStackedAutoPaddingNumberLabel.png differ diff --git a/__tests__/integration/snapshots/static/populationIntervalDivergingAutoPaddingUndefinedTitle.png b/__tests__/integration/snapshots/static/populationIntervalDivergingAutoPaddingUndefinedTitle.png new file mode 100644 index 0000000000..dcfe853971 Binary files /dev/null and b/__tests__/integration/snapshots/static/populationIntervalDivergingAutoPaddingUndefinedTitle.png differ diff --git a/__tests__/integration/snapshots/static/tranLineMultiAxesAutoPaddingTickMethod.png b/__tests__/integration/snapshots/static/tranLineMultiAxesAutoPaddingTickMethod.png new file mode 100644 index 0000000000..9e661c45b4 Binary files /dev/null and b/__tests__/integration/snapshots/static/tranLineMultiAxesAutoPaddingTickMethod.png differ diff --git a/__tests__/integration/snapshots/static/vaccinesCellScaleRelationAutoPaddingTickFilter.png b/__tests__/integration/snapshots/static/vaccinesCellScaleRelationAutoPaddingTickFilter.png new file mode 100644 index 0000000000..52e53aa76a Binary files /dev/null and b/__tests__/integration/snapshots/static/vaccinesCellScaleRelationAutoPaddingTickFilter.png differ diff --git a/__tests__/integration/snapshots/static/weatherLineMultiAxesAutoPadding.png b/__tests__/integration/snapshots/static/weatherLineMultiAxesAutoPadding.png new file mode 100644 index 0000000000..2742d178a1 Binary files /dev/null and b/__tests__/integration/snapshots/static/weatherLineMultiAxesAutoPadding.png differ diff --git a/__tests__/plots/static/alphabet-interval-auto-padding-custom.ts b/__tests__/plots/static/alphabet-interval-auto-padding-custom.ts new file mode 100644 index 0000000000..6581ce8057 --- /dev/null +++ b/__tests__/plots/static/alphabet-interval-auto-padding-custom.ts @@ -0,0 +1,67 @@ +import { Rect, Text } from '@antv/g'; +import { G2Spec } from '../../../src'; + +export function alphabetIntervalAutoPaddingCustom(): G2Spec { + return { + type: 'interval', + padding: 'auto', + transform: [{ type: 'sortX', by: 'y', reverse: true }], + data: { + type: 'fetch', + value: 'data/alphabet.csv', + }, + encode: { + x: 'letter', + y: 'frequency', + color: 'steelblue', + }, + axis: { + y: { + labelFormatter: (d) => { + const width = 60; + const height = 15; + const rect = new Rect({ + style: { x: -width, y: -height / 2, height, width, fill: 'red' }, + }); + const text = new Text({ + style: { + x: width / 2, + text: d + '', + fontSize: 12, + textBaseline: 'top', + textAlign: 'center', + }, + }); + rect.appendChild(text); + return rect; + }, + }, + x: { + labelFormatter: (d) => { + const width = 60; + const height = 15; + const rect = new Rect({ + style: { x: 0, y: 0, height, width, fill: 'red' }, + }); + const text = new Text({ + style: { + x: width / 2, + text: d + '', + fontSize: 12, + textBaseline: 'top', + textAlign: 'center', + }, + }); + rect.appendChild(text); + return rect; + }, + }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/alphabet-interval-auto-padding-d3-format.ts b/__tests__/plots/static/alphabet-interval-auto-padding-d3-format.ts new file mode 100644 index 0000000000..e80411e733 --- /dev/null +++ b/__tests__/plots/static/alphabet-interval-auto-padding-d3-format.ts @@ -0,0 +1,27 @@ +import { G2Spec } from '../../../src'; + +export function alphabetIntervalAutoPaddingD3Format(): G2Spec { + return { + type: 'interval', + padding: 'auto', + transform: [{ type: 'sortX', by: 'y', reverse: true }], + data: { + type: 'fetch', + value: 'data/alphabet.csv', + }, + encode: { + x: 'letter', + y: 'frequency', + color: 'steelblue', + }, + axis: { + y: { labelFormatter: '.0%' }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/alphabet-interval-auto-padding-max-ratio.ts b/__tests__/plots/static/alphabet-interval-auto-padding-max-ratio.ts new file mode 100644 index 0000000000..99af1ed74b --- /dev/null +++ b/__tests__/plots/static/alphabet-interval-auto-padding-max-ratio.ts @@ -0,0 +1,40 @@ +import { G2Spec } from '../../../src'; + +function appendZero(count) { + return Array.from({ length: count }, () => 0).join(''); +} + +export function alphabetIntervalAutoPaddingMaxRatio(): G2Spec { + return { + type: 'interval', + padding: 'auto', + margin: 50, + inset: 10, + transform: [{ type: 'sortX', by: 'y', reverse: true }], + data: { + type: 'fetch', + value: 'data/alphabet.csv', + }, + encode: { + x: 'letter', + y: 'frequency', + color: 'steelblue', + }, + axis: { + y: [ + { labelFormatter: (d) => d + appendZero(40) }, + { labelFormatter: (d) => appendZero(40) + d, position: 'right' }, + ], + x: [ + { labelFormatter: (d) => appendZero(40) + d }, + { labelFormatter: (d) => d + appendZero(40), position: 'top' }, + ], + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/alphabet-interval-auto-padding-no-title.ts b/__tests__/plots/static/alphabet-interval-auto-padding-no-title.ts new file mode 100644 index 0000000000..4b035bf10f --- /dev/null +++ b/__tests__/plots/static/alphabet-interval-auto-padding-no-title.ts @@ -0,0 +1,28 @@ +import { G2Spec } from '../../../src'; + +export function alphabetIntervalAutoPaddingNoTitle(): G2Spec { + return { + type: 'interval', + padding: 'auto', + transform: [{ type: 'sortX', by: 'y', reverse: true }], + data: { + type: 'fetch', + value: 'data/alphabet.csv', + }, + encode: { + x: 'letter', + y: 'frequency', + color: 'steelblue', + }, + axis: { + y: { title: false }, + x: { title: false }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/alphabet-interval-auto-padding-position.ts b/__tests__/plots/static/alphabet-interval-auto-padding-position.ts new file mode 100644 index 0000000000..5c2cf64111 --- /dev/null +++ b/__tests__/plots/static/alphabet-interval-auto-padding-position.ts @@ -0,0 +1,28 @@ +import { G2Spec } from '../../../src'; + +export function alphabetIntervalAutoPaddingPosition(): G2Spec { + return { + type: 'interval', + padding: 'auto', + transform: [{ type: 'sortX', by: 'y', reverse: true }], + data: { + type: 'fetch', + value: 'data/alphabet.csv', + }, + encode: { + x: 'letter', + y: 'frequency', + color: 'steelblue', + }, + axis: { + y: { labelFormatter: (d) => d + '0000', position: 'right' }, + x: { labelFormatter: (d) => d + '0000', position: 'top' }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/alphabet-interval-auto-padding-style.ts b/__tests__/plots/static/alphabet-interval-auto-padding-style.ts new file mode 100644 index 0000000000..5f72f462c6 --- /dev/null +++ b/__tests__/plots/static/alphabet-interval-auto-padding-style.ts @@ -0,0 +1,34 @@ +import { G2Spec } from '../../../src'; + +export function alphabetIntervalAutoPaddingStyle(): G2Spec { + return { + type: 'interval', + padding: 'auto', + transform: [{ type: 'sortX', by: 'y', reverse: true }], + data: { + type: 'fetch', + value: 'data/alphabet.csv', + }, + encode: { + x: 'letter', + y: 'frequency', + color: 'steelblue', + }, + axis: { + y: { + labelFormatter: (d) => d + '0000', + style: { + labelFontSize: (_, i) => 10 + i, + labelFill: 'steelblue', + }, + }, + x: { labelFormatter: (d) => d + '0000', style: { labelFontSize: 20 } }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/alphabet-interval-auto-padding.ts b/__tests__/plots/static/alphabet-interval-auto-padding.ts new file mode 100644 index 0000000000..10d90c4fe4 --- /dev/null +++ b/__tests__/plots/static/alphabet-interval-auto-padding.ts @@ -0,0 +1,28 @@ +import { G2Spec } from '../../../src'; + +export function alphabetIntervalAutoPadding(): G2Spec { + return { + type: 'interval', + padding: 'auto', + transform: [{ type: 'sortX', by: 'y', reverse: true }], + data: { + type: 'fetch', + value: 'data/alphabet.csv', + }, + encode: { + x: 'letter', + y: 'frequency', + color: 'steelblue', + }, + axis: { + y: { labelFormatter: (d) => d + '0000' }, + x: { labelFormatter: (d) => d + '0000' }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/browser-line-area-trending-auto-padding.ts b/__tests__/plots/static/browser-line-area-trending-auto-padding.ts new file mode 100644 index 0000000000..371f3cb405 --- /dev/null +++ b/__tests__/plots/static/browser-line-area-trending-auto-padding.ts @@ -0,0 +1,46 @@ +import { G2Spec } from '../../../src'; +import { browser } from '../../data/browser'; + +export function browserLineAreaTrendingAutoPadding(): G2Spec { + return { + type: 'view', + data: browser, + padding: 'auto', + style: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + children: [ + { + type: 'line', + encode: { + x: 'name', + y: 'value', + shape: 'hv', + }, + scale: { + y: { domain: [0, 50] }, + }, + style: { + opacity: 0.5, + }, + }, + { + type: 'area', + encode: { + x: 'name', + y: 'value', + shape: 'hv', + }, + scale: { + y: { domain: [0, 50] }, + }, + style: { + opacity: 0.5, + }, + }, + ], + }; +} diff --git a/__tests__/plots/static/flare-point-circle-pack-custom-auto-padding.ts b/__tests__/plots/static/flare-point-circle-pack-custom-auto-padding.ts new file mode 100644 index 0000000000..7d185aeda8 --- /dev/null +++ b/__tests__/plots/static/flare-point-circle-pack-custom-auto-padding.ts @@ -0,0 +1,43 @@ +import { interpolateHcl } from 'd3-interpolate'; +import { G2Spec } from '../../../src'; + +export async function flarePointCirclePackCustomAutoPadding(): Promise { + return { + type: 'view', + padding: 'auto', + width: 1000, + height: 1000, + style: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + children: [ + { + type: 'pack', + layout: { padding: 5 }, + data: { + type: 'fetch', + value: 'data/flare.json', + }, + encode: { + value: 'value', + color: 'depth', + }, + legend: { color: false }, + scale: { + color: { + domain: [0, 5], + range: ['hsl(152,80%,80%)', 'hsl(228,30%,40%)'], + interpolate: interpolateHcl, + }, + }, + style: { + labelText: (d) => + d.r >= 10 && d.height === 0 ? `${d.data.name}` : '', + }, + }, + ], + }; +} diff --git a/__tests__/plots/static/gdp-interval-flex-auto-padding-rotate.ts b/__tests__/plots/static/gdp-interval-flex-auto-padding-rotate.ts new file mode 100644 index 0000000000..3ff33a1c34 --- /dev/null +++ b/__tests__/plots/static/gdp-interval-flex-auto-padding-rotate.ts @@ -0,0 +1,31 @@ +import { G2Spec } from '../../../src'; + +export function gdpIntervalFlexAutoPaddingRotate(): G2Spec { + return { + type: 'interval', + width: 1000, + padding: 'auto', + data: { + type: 'fetch', + value: 'data/gdp.csv', + }, + transform: [{ type: 'flexX', field: 'gdp' }], + legend: { color: false }, + encode: { + x: 'country', + y: 'value', + color: 'country', + }, + axis: { + x: { + style: { labelTransform: 'rotate(90)' }, + }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/index.ts b/__tests__/plots/static/index.ts index 783af08c44..453aaf573e 100644 --- a/__tests__/plots/static/index.ts +++ b/__tests__/plots/static/index.ts @@ -204,3 +204,22 @@ export { aaplLineBasicTranspose } from './aapl-line-basic-transpose'; export { alphabetIntervalSortXDomain } from './alphabet-interval-sort-x-domain'; export { basicIntervalZeroDomainMin } from './basic-interval-zero-domain-min'; export { aaplLineAxisYHide } from './aapl-line-axis-y-hide'; +export { alphabetIntervalAutoPadding } from './alphabet-interval-auto-padding'; +export { alphabetIntervalAutoPaddingPosition } from './alphabet-interval-auto-padding-position'; +export { alphabetIntervalAutoPaddingCustom } from './alphabet-interval-auto-padding-custom'; +export { alphabetIntervalAutoPaddingStyle } from './alphabet-interval-auto-padding-style'; +export { weatherLineMultiAxesAutoPadding } from './weather-line-multi-axes-auto-padding'; +export { alphabetIntervalAutoPaddingMaxRatio } from './alphabet-interval-auto-padding-max-ratio'; +export { alphabetIntervalAutoPaddingD3Format } from './alphabet-interval-auto-padding-d3-format'; +export { alphabetIntervalAutoPaddingNoTitle } from './alphabet-interval-auto-padding-no-title'; +export { browserLineAreaTrendingAutoPadding } from './browser-line-area-trending-auto-padding'; +export { flarePointCirclePackCustomAutoPadding } from './flare-point-circle-pack-custom-auto-padding'; +export { gdpIntervalFlexAutoPaddingRotate } from './gdp-interval-flex-auto-padding-rotate'; +export { indicesLineChartScaleRelationsAutoPaddingRound } from './indices-line-chart-scale-relations-auto-padding-round'; +export { mockPointLogTicksAutoPaddingTickCount } from './mock-point-log-ticks-auto-padding-tick-count'; +export { ordersLineMissingFieldAutoPaddingUndefined } from './orders-line-missing-field-auto-padding-undefined'; +export { peoplePointStackedAutoPaddingNumberLabel } from './people-point-stacked-auto-padding-number-label'; +export { populationIntervalDivergingAutoPaddingUndefinedTitle } from './population-interval-diverging-auto-padding-undefined-title'; +export { tranLineMultiAxesAutoPaddingTickMethod } from './train-line-multi-axes-auto-padding-tick-method'; +export { vaccinesCellScaleRelationAutoPaddingTickFilter } from './vaccines-cell-scale-relation-auto-padding-tick-filter'; +export { marketIntervalMarimekkoAutoPaddingFlex } from './market-interval-marimekko-auto-padding-flex'; diff --git a/__tests__/plots/static/indices-line-chart-scale-relations-auto-padding-round.ts b/__tests__/plots/static/indices-line-chart-scale-relations-auto-padding-round.ts new file mode 100644 index 0000000000..d3932554c1 --- /dev/null +++ b/__tests__/plots/static/indices-line-chart-scale-relations-auto-padding-round.ts @@ -0,0 +1,50 @@ +import { G2Spec } from '../../../src'; + +export function indicesLineChartScaleRelationsAutoPaddingRound(): G2Spec { + return { + type: 'line', + padding: 'auto', + data: { + type: 'fetch', + value: 'data/indices.csv', + }, + transform: [ + { + type: 'normalizeY', + basis: 'first', + groupBy: 'color', + }, + ], + encode: { + x: (d) => new Date(d.Date), + y: 'Close', + color: 'Symbol', + }, + scale: { + y: { type: 'log' }, + color: { + relations: [['AMZN', '#ff0000']], + }, + }, + axis: { + y: { title: '↑ Change in price (%)' }, + }, + labels: [ + { + text: 'Symbol', + selector: 'last', + style: { + fontSize: 10, + }, + }, + ], + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} + +indicesLineChartScaleRelationsAutoPaddingRound.maxError = 100; diff --git a/__tests__/plots/static/market-interval-marimekko-auto-padding-flex.ts b/__tests__/plots/static/market-interval-marimekko-auto-padding-flex.ts new file mode 100644 index 0000000000..dd40742f99 --- /dev/null +++ b/__tests__/plots/static/market-interval-marimekko-auto-padding-flex.ts @@ -0,0 +1,60 @@ +import { G2Spec } from '../../../src'; + +export function marketIntervalMarimekkoAutoPaddingFlex(): G2Spec { + return { + type: 'interval', + width: 900, + height: 800, + padding: 'auto', + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + data: { + type: 'fetch', + value: 'data/market.csv', + }, + transform: [ + { type: 'flexX', reducer: 'sum' }, + { type: 'stackY' }, + { type: 'normalizeY' }, + ], + scale: { x: { padding: 0 } }, + axis: { y: false }, + encode: { + x: 'market', + y: 'value', + color: 'segment', + }, + style: { + inset: 0.5, + }, + labels: [ + { + text: 'segment', + style: { + x: 5, + y: 5, + textAnchor: 'start', + textBaseline: 'top', + fontSize: 10, + fill: '#fff', + }, + }, + { + text: 'value', + style: { + x: 5, + y: 5, + textAnchor: 'start', + textBaseline: 'top', + dy: 12, + fontSize: 10, + fill: '#fff', + }, + }, + ], + }; +} diff --git a/__tests__/plots/static/mock-point-log-ticks-auto-padding-tick-count.ts b/__tests__/plots/static/mock-point-log-ticks-auto-padding-tick-count.ts new file mode 100644 index 0000000000..cc6a49b635 --- /dev/null +++ b/__tests__/plots/static/mock-point-log-ticks-auto-padding-tick-count.ts @@ -0,0 +1,35 @@ +import { G2Spec } from '../../../src'; + +export function mockPointLogTicksAutoPaddingTickCount(): G2Spec { + return { + type: 'point', + padding: 'auto', + data: [ + [5, 0.09459459], + [10, 0.22972973], + [15, 0.36486486], + [20, 0.5], + [25, 0.63513514], + [30, 0.77027027], + [35, 0.90540541], + ], + encode: { + x: (d) => d[0], + y: (d) => d[1], + }, + scale: { + x: { type: 'log', nice: true }, + y: { type: 'log', domain: [0.001, 1] }, + }, + axis: { + x: { tickCount: -1 }, + y: { tickCount: -1 }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/orders-line-missing-field-auto-padding-undefined.ts b/__tests__/plots/static/orders-line-missing-field-auto-padding-undefined.ts new file mode 100644 index 0000000000..964d1eecf7 --- /dev/null +++ b/__tests__/plots/static/orders-line-missing-field-auto-padding-undefined.ts @@ -0,0 +1,24 @@ +import { G2Spec } from '../../../src'; +import { orders } from '../../data/orders'; + +export function ordersLineMissingFieldAutoPaddingUndefined(): G2Spec { + return { + type: 'line', + padding: 'auto', + data: orders, + encode: { + x: 'hour', + y: 'new_recharge_orders', + color: 'date', + }, + axis: { + x: { labelFormatter: (d) => (d === undefined ? '' : d) }, + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + }; +} diff --git a/__tests__/plots/static/people-point-stacked-auto-padding-number-label.ts b/__tests__/plots/static/people-point-stacked-auto-padding-number-label.ts new file mode 100644 index 0000000000..ce290bda77 --- /dev/null +++ b/__tests__/plots/static/people-point-stacked-auto-padding-number-label.ts @@ -0,0 +1,52 @@ +import { G2Spec } from '../../../src'; + +export function peoplePointStackedAutoPaddingNumberLabel(): G2Spec { + return { + type: 'view', + height: 360, + padding: 'auto', + style: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + children: [ + { + type: 'point', + data: { + type: 'fetch', + value: 'data/people.csv', + }, + transform: [{ type: 'stackY', y1: 'y' }], + scale: { + x: { nice: true }, + }, + axis: { + y: { + title: '← Women · Men →', + labelFormatter: Math.abs, + }, + x: { + title: 'Age →', + titlePosition: 'left-bottom', + titleTransform: 'translate(100%, 0)', + }, + }, + encode: { + x: (d) => 2021 - d.birth, + y: (d) => (d.gender === 'M' ? 1 : -1), + color: 'gender', + shape: 'point', + }, + }, + { + type: 'lineY', + data: [0], + style: { + stroke: 'black', + }, + }, + ], + }; +} diff --git a/__tests__/plots/static/population-interval-diverging-auto-padding-undefined-title.ts b/__tests__/plots/static/population-interval-diverging-auto-padding-undefined-title.ts new file mode 100644 index 0000000000..5530871dde --- /dev/null +++ b/__tests__/plots/static/population-interval-diverging-auto-padding-undefined-title.ts @@ -0,0 +1,37 @@ +import { G2Spec } from '../../../src'; + +export function populationIntervalDivergingAutoPaddingUndefinedTitle(): G2Spec { + return { + type: 'interval', + padding: 'auto', + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + data: { + type: 'fetch', + value: 'data/population.csv', + transform: [ + { + type: 'filter', + callback: (d) => d.year === 2000, + }, + ], + }, + coordinate: { transform: [{ type: 'transpose' }] }, + scale: { + color: { type: 'ordinal', range: ['#ca8861', '#675193'] }, + x: { range: [1, 0] }, + }, + axis: { + y: { labelFormatter: '~s' }, + }, + encode: { + x: 'age', + y: (d) => (d.sex === 1 ? -d.people : d.people), + color: 'sex', + }, + }; +} diff --git a/__tests__/plots/static/train-line-multi-axes-auto-padding-tick-method.ts b/__tests__/plots/static/train-line-multi-axes-auto-padding-tick-method.ts new file mode 100644 index 0000000000..b6f97387d9 --- /dev/null +++ b/__tests__/plots/static/train-line-multi-axes-auto-padding-tick-method.ts @@ -0,0 +1,49 @@ +import { csv } from 'd3-fetch'; +import { autoType } from 'd3-dsv'; +import { G2Spec } from '../../../src'; + +export async function tranLineMultiAxesAutoPaddingTickMethod(): Promise { + const data = await csv('data/train.csv', autoType); + const distanceName = new Map(data.map((d) => [d.distance, d.name])); + const xAxis = { + tickMethod: () => Array.from(distanceName.keys()), + labelFormatter: (d) => distanceName.get(d), + title: false, + }; + return { + type: 'line', + width: 800, + height: 1000, + padding: 'auto', + data, + encode: { + x: 'distance', + y: 'time', + color: 'type', + series: 'number', + }, + viewStyle: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + scale: { + color: { + domain: ['N', 'L', 'B'], + range: ['rgb(34, 34, 34)', 'rgb(183, 116, 9)', 'rgb(192, 62, 29)'], + }, + y: { range: [0, 1], tickCount: 15, utc: true }, + }, + legend: false, + axis: { + x: [ + { ...xAxis, position: 'top' }, + { + ...xAxis, + position: 'bottom', + }, + ], + }, + }; +} diff --git a/__tests__/plots/static/vaccines-cell-scale-relation-auto-padding-tick-filter.ts b/__tests__/plots/static/vaccines-cell-scale-relation-auto-padding-tick-filter.ts new file mode 100644 index 0000000000..4ef8fa2343 --- /dev/null +++ b/__tests__/plots/static/vaccines-cell-scale-relation-auto-padding-tick-filter.ts @@ -0,0 +1,79 @@ +import { G2Spec } from '../../../src'; + +export function vaccinesCellScaleRelationAutoPaddingTickFilter(): G2Spec { + return { + type: 'view', + width: 1152, + height: 780, + padding: 'auto', + data: { + type: 'fetch', + value: 'data/vaccines.csv', + }, + style: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + axis: { + x: { + tickFilter: (d) => d % 10 === 0, + position: 'top', + }, + y: { + labelAutoRotate: false, + }, + }, + scale: { + color: { + palette: 'puRd', + relations: [ + [(d) => d === null, '#eee'], + [0, '#fff'], + ], + }, + }, + children: [ + { + type: 'cell', + encode: { + x: 'year', + y: 'name', + color: 'value', + }, + style: { + inset: 0.5, + }, + }, + { + type: 'lineX', + data: [1963], + style: { + stroke: 'black', + }, + labels: [ + { + text: '1963', + position: 'bottom', + style: { + dy: 3, + fontSize: 10, + textBaseline: 'top', + }, + }, + { + text: 'Measles vaccine introduced', + position: 'bottom', + style: { + textBaseline: 'top', + fontSize: 10, + fontWeight: 'bold', + dy: 12, + }, + }, + ], + }, + ], + }; +} diff --git a/__tests__/plots/static/weather-line-multi-axes-auto-padding.ts b/__tests__/plots/static/weather-line-multi-axes-auto-padding.ts new file mode 100644 index 0000000000..477f7fd4da --- /dev/null +++ b/__tests__/plots/static/weather-line-multi-axes-auto-padding.ts @@ -0,0 +1,90 @@ +import { G2Spec } from '../../../src'; +import { weather } from '../../data/weather'; + +export function weatherLineMultiAxesAutoPadding(): G2Spec { + return { + type: 'view', + data: weather, + padding: 'auto', + style: { + viewFill: '#4e79a7', + plotFill: '#f28e2c', + mainFill: '#e15759', + contentFill: '#76b7b2', + }, + children: [ + { + type: 'line', + encode: { + x: 'Month', + y: 'Temperature', + color: '#EE6666', + shape: 'smooth', + }, + scale: { + y: { independent: true, domainMax: 30 }, + }, + axis: { + y: { + title: 'Temperature (°C)', + labelFormatter: (d) => d + '00000', + grid: false, + style: { + titleFill: '#EE6666', + }, + }, + }, + }, + { + type: 'interval', + encode: { + x: 'Month', + y: 'Evaporation', + color: '#5470C6', + }, + scale: { + y: { independent: true, domainMax: 200 }, + }, + style: { + fillOpacity: 0.8, + }, + axis: { + y: { + title: 'Temperature (°C)', + labelFormatter: (d) => d + '00000', + grid: null, + style: { + titleFill: '#5470C6', + }, + }, + }, + }, + { + type: 'line', + encode: { + x: 'Month', + y: 'Precipitation', + color: '#91CC75', + }, + scale: { + y: { independent: true }, + }, + style: { + lineWidth: 2, + lineDash: [2, 2], + }, + axis: { + y: { + position: 'right', + title: 'Precipitation (ml)', + grid: false, + labelFormatter: (d) => d + '00000', + style: { + titleFill: '#91CC75', + }, + }, + }, + }, + ], + }; +} diff --git a/src/component/axis.ts b/src/component/axis.ts index 36b4b973c7..9b5e4db639 100644 --- a/src/component/axis.ts +++ b/src/component/axis.ts @@ -477,6 +477,7 @@ const LinearAxisComponent: GCC = (options) => { tickFilter, tickMethod, transform, + indexBBox, ...userDefinitions } = options; @@ -526,6 +527,7 @@ const LinearAxisComponent: GCC = (options) => { gridLength, // Always showLine, make title could align the end of axis. line: true, + indexBBox, ...(!internalAxisStyle.line ? { lineOpacity: 0 } : null), ...overrideStyle, ...important, diff --git a/src/composition/mark.ts b/src/composition/mark.ts index 890a46319f..e3e4c4a8e3 100644 --- a/src/composition/mark.ts +++ b/src/composition/mark.ts @@ -37,6 +37,7 @@ export const Mark: CC = () => { labelTransform, parentKey, clip, + viewStyle, ...mark } = options; @@ -72,6 +73,7 @@ export const Mark: CC = () => { marginRight, parentKey, clip, + style: viewStyle, marks: [{ ...mark, key: `${key}-0`, data }], }, ]; diff --git a/src/runtime/component.ts b/src/runtime/component.ts index 9513c30d0b..43909eefe6 100644 --- a/src/runtime/component.ts +++ b/src/runtime/component.ts @@ -2,8 +2,10 @@ * @see https://github.com/antvis/G2/discussions/4557 */ import { Coordinate } from '@antv/coord'; -import { isEqual } from '@antv/util'; -import { group } from 'd3-array'; +import { deepMix, isEqual } from '@antv/util'; +import { group, max, sum } from 'd3-array'; +import { format } from 'd3-format'; +import { DisplayObject, Text } from '@antv/g'; import { getPolarOptions, getRadialOptions, @@ -11,7 +13,7 @@ import { type RadialOptions, } from '../coordinate'; import { combine } from '../utils/array'; -import { defined } from '../utils/helper'; +import { capitalizeFirst, defined, subObject } from '../utils/helper'; import { LEGEND_INFER_STRATEGIES } from '../component/constant'; import { coordOf, @@ -36,6 +38,8 @@ import { import { GuideComponent, GuideComponentComponent as GCC, + ScaleComponent, + Scale, } from './types/component'; import { G2CoordinateOptions, @@ -90,13 +94,13 @@ export function inferComponent( }); } - const inferedComponents = inferComponentsType(displayedScales, coordinates); + const inferredComponents = inferComponentsType(displayedScales, coordinates); - inferedComponents.forEach(([type, relativeScales]) => { + inferredComponents.forEach(([type, relativeScales]) => { const { props } = createGuideComponent(type); const { defaultPosition, defaultOrientation, defaultSize, defaultOrder } = props; - // @todo to be comfirm if the scale can be merged. + // @todo to be confirm if the scale can be merged. const scale: G2ScaleOptions = Object.assign({}, ...relativeScales); const { guide: guideOptions, field } = scale; // A scale may have multiple guides. @@ -263,10 +267,10 @@ function inferComponentsType( scales: G2ScaleOptions[], coordinates: G2CoordinateOptions[], ): [string | GCC, G2ScaleOptions[]][] { - const avaliableScales = scales.filter((scale) => isValidScale(scale)); + const availableScales = scales.filter((scale) => isValidScale(scale)); return [ - ...inferLegendComponentType(avaliableScales, coordinates), - ...inferAxisComponentType(avaliableScales, coordinates), + ...inferLegendComponentType(availableScales, coordinates), + ...inferAxisComponentType(availableScales, coordinates), ]; } @@ -307,7 +311,7 @@ function inferAxisPositionAndOrientation( // todo, in current resolution, the radar chart is implement by parallel + polar coordinate. // implementation plan to be confirmed. // in current implementation, it must to add the first position encode to it's last. - // so we won't render the last axis repeatly. + // so we won't render the last axis repeatably. if (type === 'axisRadar') { const positions = scales.filter((scale) => scale.name.startsWith('position'), @@ -396,7 +400,7 @@ function inferComponentPositionAndOrientation( return ordinalPositionAndOrientation; } -function inferSrollableType(name: string, type: string, coordinates = []) { +function inferScrollableType(name: string, type: string, coordinates = []) { if (name === 'x') return isTranspose(coordinates) ? `${type}Y` : `${type}X`; if (name === 'y') return isTranspose(coordinates) ? `${type}X` : `${type}Y`; return null; @@ -424,7 +428,7 @@ function inferScrollableComponents( scale: G2ScaleOptions, options: Record, ) { - const componentType = inferSrollableType(channelName, type, coordinates); + const componentType = inferScrollableType(channelName, type, coordinates); if (!options || !componentType) return; const { props } = createGuideComponent(componentType); @@ -450,3 +454,197 @@ function inferScrollableComponents( }) .filter((d) => !!d); } + +// !!! Note Mutate component.size and component.style. +export function computeComponentSize( + component: G2GuideComponentOptions, + crossSize: number, + crossPadding: [number, number], + position: GCP, + theme: G2Theme, + library: G2Library, +): G2GuideComponentOptions { + const [useScale] = useLibrary( + 'scale', + library, + ); + + // Only compute and update size of axis component in padding area. + // @todo Legend, slider. + const { type } = component; + const paddingAreas = ['left', 'right', 'bottom', 'top']; + if (typeof type !== 'string' || !type.startsWith('axis')) return; + if (!paddingAreas.includes(position)) return; + + // If padding is auto, use hide as the labelTransform by default + // to avoid overlap between labels. + component.transform = component.transform || [{ type: 'hide' }]; + + const { labelFormatter, scales, title, tickCount, tickMethod, tickFilter } = + component; + const isVertical = position === 'left' || position === 'right'; + + // Get styles to be applied. + const style = styleOf(component, position, theme); + const { tickLength = 0, labelSpacing = 0, titleSpacing = 0, ...rest } = style; + + // Init scale, the tickCount of axis has higher priority than scale. + const [scaleOptions] = scales; + if (tickCount !== undefined) scaleOptions.tickCount = tickCount; + if (tickMethod !== undefined) scaleOptions.tickMethod = tickMethod; + const scale = useScale(scaleOptions); + + // Get labels to be rendered. + const labels = labelsOf(scale, labelFormatter, tickFilter); + const labeStyle = subObject(rest, 'label'); + const labelBBoxes = labels.map((d, i) => { + const normalizeStyle = Object.fromEntries( + Object.entries(labeStyle).map(([key, value]) => [ + key, + typeof value === 'function' ? value(d, i) : value, + ]), + ); + // Auto padding should ignore transform for horizontal axis. + if (!isVertical) normalizeStyle.transform = 'none'; + return computeLabelSize(d, normalizeStyle); + }); + + const maxLabelWidth = max(labelBBoxes, (d) => d.width); + const paddingTick = tickLength + labelSpacing; + + if (isVertical) { + component.size = maxLabelWidth + paddingTick; + } else { + // If the labels can't be placed horizontally, + // rotate 90 deg to display them. + if (overflowX(scale, labelBBoxes, crossSize, crossPadding, tickFilter)) { + component.size = maxLabelWidth + paddingTick; + component.style = { + ...component.style, + labelTransform: 'rotate(90)', + }; + } else { + const maxLabelHeight = max(labelBBoxes, (d) => d.height); + component.size = maxLabelHeight + paddingTick; + } + } + + // Cache boxes to avoid computed twice. + const I = labels.map((_, i) => i); + component.indexBBox = new Map(I.map((i) => [i, [labels[i], labelBBoxes[i]]])); + + if (title === false || title === null || title === undefined) return; + + // Get title to be rendered. + const titleStyle = subObject(rest, 'title'); + const titleText = Array.isArray(title) ? title.join(',') : title; + const titleBBox = computeLabelSize(titleText, titleStyle); + + if (isVertical) { + component.size += titleSpacing + titleBBox.width; + } else { + component.size += titleSpacing + titleBBox.height; + } +} + +function styleOf( + axis: G2GuideComponentOptions, + position: GCP, + theme: G2Theme, +): Record { + const { + axis: baseStyle, + // @ts-ignore + [`axis${capitalizeFirst(position)}`]: positionStyle, + } = theme; + return deepMix({}, baseStyle, positionStyle, axis.style); +} + +function ticksOf(scale: Scale, tickFilter: (d: any) => boolean): any[] { + const ticks = scale.getTicks ? scale.getTicks() : scale.getOptions().domain; + if (!tickFilter) return ticks; + return ticks.filter(tickFilter); +} + +function labelsOf( + scale: Scale, + labelFormatter: (d: any) => string | DisplayObject, + tickFilter, +): (string | DisplayObject)[] { + const T = ticksOf(scale, tickFilter); + const ticks = T.map((d) => (typeof d === 'number' ? prettyNumber(d) : d)); + const formatter = labelFormatter + ? typeof labelFormatter === 'string' + ? format(labelFormatter) + : labelFormatter + : scale.getFormatter + ? scale.getFormatter() + : (d) => `${d}`; + return ticks.map(formatter); +} + +function offsetOf(scale: Scale, d: any): number { + if (!scale.getBandWidth) return 0; + const offset = scale.getBandWidth(d) / 2; + return offset; +} + +function overflowX( + scale: Scale, + labelBBoxes: DOMRect[], + crossSize: number, + crossPadding: [number, number], + tickFilter: (d: any) => boolean, +): boolean { + // If actual size bigger than container size, overflow. + const totalSize = sum(labelBBoxes, (d) => d.width); + if (totalSize > crossSize) return true; + + // Clone scale to get visual position for labels. + const scaleX = scale.clone(); + scaleX.update({ range: [0, crossSize] }); + const ticks = ticksOf(scale, tickFilter); + const X = ticks.map((d) => scaleX.map(d) + offsetOf(scaleX, d)); + + const I = ticks.map((_, i) => i); + const startX = -crossPadding[0]; + const endX = crossSize + crossPadding[1]; + const extent = (x, bbox) => { + const { width } = bbox; + return [x - width / 2, x + width / 2]; + }; + + // Collision detection. + for (let i = 0; i < I.length; i++) { + const x = X[i]; + const [x0, x1] = extent(x, labelBBoxes[i]); + // If a label is out of plot area, overflow. + if (x0 < startX || x1 > endX) return true; + const y = X[i + 1]; + if (y) { + // If two labels intersect, overflow. + const [y0] = extent(y, labelBBoxes[i + 1]); + if (x1 > y0) return true; + } + } + return false; +} + +function computeLabelSize( + d: string | DisplayObject, + style: Record, +): DOMRect { + const shape = normalizeLabel(d); + shape.attr({ ...style, visibility: 'none' }); + const bbox = shape.getBBox(); + return bbox; +} + +function normalizeLabel(d: string | DisplayObject): DisplayObject { + if (d instanceof DisplayObject) return d; + return new Text({ style: { text: `${d}` } }); +} + +function prettyNumber(n: number) { + return Math.abs(n) < 1e-15 ? n : parseFloat(n.toFixed(15)); +} diff --git a/src/runtime/layout.ts b/src/runtime/layout.ts index 6771d190f6..79cb2766eb 100644 --- a/src/runtime/layout.ts +++ b/src/runtime/layout.ts @@ -3,21 +3,24 @@ import { ascending, group } from 'd3-array'; import { isParallel, isPolar, isRadar, radiusOf } from '../utils/coordinate'; import { capitalizeFirst } from '../utils/helper'; import { divide } from '../utils/array'; +import { camelCase } from '../utils/string'; import { GuideComponentPosition as GCP, GuideComponentOrientation as GCO, Layout, Section, SectionArea, + G2Theme, } from './types/common'; -import { G2GuideComponentOptions, G2View } from './types/options'; +import { computeComponentSize } from './component'; +import { G2GuideComponentOptions, G2Library, G2View } from './types/options'; export function computeLayout( components: G2GuideComponentOptions[], options: G2View, + theme: G2Theme, + library: G2Library, ): Layout { - const padding = computePadding(components, options); - const { paddingLeft, paddingRight, paddingTop, paddingBottom } = padding; const { width, height, @@ -33,18 +36,62 @@ export function computeLayout( marginBottom = margin, marginTop = margin, marginRight = margin, + padding, + paddingBottom = padding, + paddingLeft = padding, + paddingRight = padding, + paddingTop = padding, } = options; + const MAX_PADDING_RATIO = 1 / 3; + const clamp = (origin, computed, max) => + origin === 'auto' ? Math.min(max, computed) : computed; + + // Compute paddingLeft and paddingRight first to get innerWidth. + const horizontalPadding = computePadding( + components, + height, + [0, 0], + ['left', 'right'], + options, + theme, + library, + ); + const { paddingLeft: pl0, paddingRight: pr0 } = horizontalPadding; + const viewWidth = width - marginLeft - marginRight; + const pl = clamp(paddingLeft, pl0, viewWidth * MAX_PADDING_RATIO); + const pr = clamp(paddingRight, pr0, viewWidth * MAX_PADDING_RATIO); + const iw = viewWidth - pl - pr; + + // Compute paddingBottom and paddingTop based on innerWidth. + const verticalPadding = computePadding( + components, + iw, + [pl, pr], + ['bottom', 'top'], + options, + theme, + library, + ); + const { paddingTop: pt0, paddingBottom: pb0 } = verticalPadding; + const viewHeight = height - marginBottom - marginTop; + const pb = clamp(paddingBottom, pb0, viewHeight * MAX_PADDING_RATIO); + const pt = clamp(paddingTop, pt0, viewHeight * MAX_PADDING_RATIO); + const ih = viewHeight - pb - pt; + return { - ...padding, width, height, insetLeft, insetTop, insetBottom, insetRight, - innerWidth: width - paddingLeft - paddingRight - marginLeft - marginRight, - innerHeight: height - paddingTop - paddingBottom - marginTop - marginBottom, + innerWidth: iw, + innerHeight: ih, + paddingLeft: pl, + paddingRight: pr, + paddingTop: pt, + paddingBottom: pb, marginLeft, marginBottom, marginTop, @@ -59,18 +106,13 @@ export function computeLayout( */ function computePadding( components: G2GuideComponentOptions[], + crossSize: number, + crossPadding: [number, number], + positions: GCP[], options: G2View, + theme: G2Theme, + library: G2Library, ) { - const positions: GCP[] = [ - 'left', - 'right', - 'top', - 'bottom', - 'top-left', - 'top-right', - 'bottom-left', - 'bottom-right', - ]; const positionComponents = group(components, (d) => d.position); const { padding, @@ -86,12 +128,25 @@ function computePadding( paddingRight, }; for (const position of positions) { - const key = `padding${capitalizeFirst(position)}`; - if (layout[key] === undefined) { + const key = `padding${capitalizeFirst(camelCase(position))}`; + const value = layout[key]; + if (value === undefined || value === 'auto') { if (!positionComponents.has(position)) { layout[key] = 30; } else { const components = positionComponents.get(position); + if (value === 'auto') { + components.forEach((component) => + computeComponentSize( + component, + crossSize, + crossPadding, + position, + theme, + library, + ), + ); + } const totalSize = components.reduce((sum, { size }) => sum + size, 0); layout[key] = totalSize; } @@ -161,7 +216,7 @@ export function placeComponents( /** * @description non-entity components: axis in the center, inner, outer, component in the center * @description entity components: other components - * @description no volumn components take up no extra space + * @description no volume components take up no extra space */ const [nonEntityComponents, entityComponents] = divide( diff --git a/src/runtime/plot.ts b/src/runtime/plot.ts index ddfa7671ff..7191083c0d 100644 --- a/src/runtime/plot.ts +++ b/src/runtime/plot.ts @@ -537,7 +537,7 @@ function initializeState( options, library, ); - const layout = computeLayout(components, options); + const layout = computeLayout(components, options, theme, library); const coordinate = createCoordinate(layout, options, library); const framedStyle = frame ? deepMix({ mainLineWidth: 1, mainStroke: '#000' }, style) diff --git a/src/spec/composition.ts b/src/spec/composition.ts index ddd822836a..d9f9826c64 100644 --- a/src/spec/composition.ts +++ b/src/spec/composition.ts @@ -5,7 +5,7 @@ import { Transform } from './transform'; import { Scale } from './scale'; import { Data } from './data'; import { LabelTransform } from './labelTransform'; -import { Literal2Object } from './utils'; +import { Literal2Object, Padding } from './utils'; import { TitleComponent } from './component'; import { Mark } from './mark'; @@ -40,11 +40,11 @@ export type ViewComposition = { data?: Data; key?: string; class?: string; - padding?: number; - paddingLeft?: number; - paddingRight?: number; - paddingTop?: number; - paddingBottom?: number; + padding?: Padding; + paddingLeft?: Padding; + paddingRight?: Padding; + paddingTop?: Padding; + paddingBottom?: Padding; margin?: number; marginLeft?: number; marginBottom?: number; @@ -99,7 +99,7 @@ export type SpaceFlexComposition = { data?: Data; direction?: 'col' | 'row'; ratio?: number[]; - padding?: number; + padding?: Padding; children?: Node[]; }; @@ -118,10 +118,11 @@ export type FacetRectComposition = { type?: 'facetRect'; transform?: Transform; data?: Data; - paddingLeft?: number; - paddingRight?: number; - paddingTop?: number; - paddingBottom?: number; + padding?: Padding; + paddingLeft?: Padding; + paddingRight?: Padding; + paddingTop?: Padding; + paddingBottom?: Padding; margin?: number; marginLeft?: number; marginBottom?: number; @@ -149,10 +150,11 @@ export type FacetRectComposition = { export type RepeatMatrixComposition = { type?: 'repeatMatrix'; - paddingLeft?: number; - paddingRight?: number; - paddingTop?: number; - paddingBottom?: number; + padding?: Padding; + paddingLeft?: Padding; + paddingRight?: Padding; + paddingTop?: Padding; + paddingBottom?: Padding; margin?: number; marginLeft?: number; marginBottom?: number; @@ -181,10 +183,11 @@ export type RepeatMatrixComposition = { export type FacetCircleComposition = { type?: 'facetCircle'; - paddingLeft?: number; - paddingRight?: number; - paddingTop?: number; - paddingBottom?: number; + padding?: Padding; + paddingLeft?: Padding; + paddingRight?: Padding; + paddingTop?: Padding; + paddingBottom?: Padding; margin?: number; marginLeft?: number; marginBottom?: number; diff --git a/src/spec/mark.ts b/src/spec/mark.ts index b0a06b59c2..4d1f5b28cd 100644 --- a/src/spec/mark.ts +++ b/src/spec/mark.ts @@ -15,7 +15,7 @@ import { TitleComponent, TooltipComponent, } from './component'; -import { Closeable, Literal2Object } from './utils'; +import { Closeable, Literal2Object, Padding } from './utils'; export type Mark = | IntervalMark @@ -121,11 +121,11 @@ export type BaseMark = { y?: number; width?: number; height?: number; - paddingLeft?: number; - paddingRight?: number; - paddingBottom?: number; - paddingTop?: number; - padding?: number; + paddingLeft?: Padding; + paddingRight?: Padding; + paddingBottom?: Padding; + paddingTop?: Padding; + padding?: Padding; inset?: number; insetLeft?: number; insetBottom?: number; diff --git a/src/spec/utils.ts b/src/spec/utils.ts index d3e5998630..699f6f0534 100644 --- a/src/spec/utils.ts +++ b/src/spec/utils.ts @@ -14,3 +14,5 @@ export type UsePrefix< }; export type Closeable = T | boolean | null; + +export type Padding = number | 'auto'; diff --git a/src/utils/size.ts b/src/utils/size.ts index 27dd8b1c0e..db4fbb92e5 100644 --- a/src/utils/size.ts +++ b/src/utils/size.ts @@ -54,21 +54,27 @@ export function getBBoxSize(options: G2View): Size { insetTop = inset, insetBottom = inset, } = options; + + // @todo Add this padding to theme. + // 30 is default size for padding, which defined in runtime. + const maybeAuto = (padding) => (padding === 'auto' ? 30 : padding); + const finalWidth = width - - paddingLeft - - paddingRight - + maybeAuto(paddingLeft) - + maybeAuto(paddingRight) - marginLeft - marginRight - insetLeft - insetRight; const finalHeight = height - - paddingTop - - paddingBottom - + maybeAuto(paddingTop) - + maybeAuto(paddingBottom) - marginTop - marginBottom - insetTop - insetBottom; + return { width: finalWidth, height: finalHeight }; }