Skip to content

Commit

Permalink
feat(partition): treemap padding (#660)
Browse files Browse the repository at this point in the history
This adds per-layer, per-side configurability for padding between a piece of treemap text and its rectangular container.

close #659
  • Loading branch information
monfera authored May 5, 2020
1 parent 16d9974 commit ed1e8be
Show file tree
Hide file tree
Showing 18 changed files with 420 additions and 73 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion src/chart_types/partition_chart/layout/types/config_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Distance, Pixels, Radian, Radius, Ratio, SizeRatio, TimeMs } from './ge
import { Font, FontFamily, PartialFont } from './types';
import { $Values as Values } from 'utility-types';
import { Color, StrokeStyle, ValueFormatter } from '../../../../utils/commons';
import { PerSideDistance } from '../../../../utils/dimensions';

export const PartitionLayout = Object.freeze({
sunburst: 'sunburst' as 'sunburst',
Expand All @@ -28,13 +29,17 @@ export const PartitionLayout = Object.freeze({

export type PartitionLayout = Values<typeof PartitionLayout>; // could use ValuesType<typeof HierarchicalChartTypes>

export type PerSidePadding = PerSideDistance;

export type Padding = Pixels | Partial<PerSidePadding>;

interface LabelConfig extends Font {
textColor: Color;
textInvertible: boolean;
textOpacity: Ratio;
valueFormatter: ValueFormatter;
valueFont: PartialFont;
padding: number;
padding: Padding;
}

export type FillLabelConfig = LabelConfig;
Expand Down
19 changes: 14 additions & 5 deletions src/chart_types/partition_chart/layout/types/viewmodel_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,32 @@ export type LinkLabelVM = {
valueFontSpec: Font;
};

/* @internal */
export interface RowBox extends Font {
text: string;
width: Distance;
verticalOffset: Distance;
wordBeginning: Distance;
}

interface RowCentroid {
rowCentroidX: Coordinate;
rowCentroidY: Coordinate;
interface RowAnchor {
rowAnchorX: Coordinate;
rowAnchorY: Coordinate;
}

export interface RowSpace extends RowCentroid {
/* @internal */
export interface RowSpace extends RowAnchor {
maximumRowLength: Distance;
}

export interface TextRow extends RowCentroid {
/* @internal */
export interface TextRow extends RowAnchor {
length: number;
maximumLength: number;
rowWords: Array<RowBox>;
}

/* @internal */
export interface RowSet {
id: string;
rows: Array<TextRow>;
Expand All @@ -70,18 +74,22 @@ export interface RowSet {
container?: any;
}

/* @internal */
export interface QuadViewModel extends ShapeTreeNode {
strokeWidth: number;
strokeStyle: string;
fillColor: string;
}

/* @internal */
export interface OutsideLinksViewModel {
points: Array<PointTuple>;
}

/* @internal */
export type PickFunction = (x: Pixels, y: Pixels) => Array<QuadViewModel>;

/* @internal */
export type ShapeViewModel = {
config: Config;
quadViewModel: QuadViewModel[];
Expand All @@ -93,6 +101,7 @@ export type ShapeViewModel = {
outerRadius: number;
};

/* @internal */
export const nullShapeViewModel = (specifiedConfig?: Config, diskCenter?: PointObject): ShapeViewModel => ({
config: specifiedConfig || config,
quadViewModel: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License. */

import { getRectangleRowGeometry } from './fill_text_layout';

describe('Test that getRectangleRowGeometry works with:', () => {
const container = { x0: 0, y0: 0, x1: 200, y1: 100 };
const cx = 0;
const cy = 0;
const totalRowCount = 1;
const linePitch = 50;
const rowIndex = 0;
const fontSize = 50;
const _rotation = 0;
const verticalAlignment = 'top';

const defaultPadding = 2;
const overhangOffset = -4.5;

test('scalar, zero padding', () => {
const padding = 0;
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount,
linePitch,
rowIndex,
fontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 200,
rowAnchorX: 0,
rowAnchorY: overhangOffset,
});
});

test('scalar, nonzero padding', () => {
const padding = 10;
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount,
linePitch,
rowIndex,
fontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 200 - padding * 2,
rowAnchorX: 0,
rowAnchorY: overhangOffset - padding,
});
});

test('per-side, fully specified padding', () => {
const padding = { top: 5, bottom: 10, left: 20, right: 30 };
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount,
linePitch,
rowIndex,
fontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 200 - padding.left - padding.right,
rowAnchorX: -5, // (left - right) / 2
rowAnchorY: overhangOffset - padding.top,
});
});

test('per-side, partially specified padding', () => {
const padding = { bottom: 10, right: 30 };
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount,
linePitch,
rowIndex,
fontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 200 - defaultPadding - padding.right,
rowAnchorX: -(30 /* right padding */ / 2 - 2 /* 2: default left padding */ / 2),
rowAnchorY: overhangOffset - defaultPadding,
});
});

test('not enough height with per-side, partially specified padding', () => {
const padding = { top: 80, bottom: 80 };
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount,
linePitch,
rowIndex,
fontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 0, // Height of 100 - 2 * 80 < 50
rowAnchorX: NaN, // if text can't be placed, what is its anchor?
rowAnchorY: NaN, // if text can't be placed, what is its anchor?
});
});

test('not enough height with per-side, asymmetric padding', () => {
const padding = { top: 10, bottom: 50 };
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount,
linePitch,
rowIndex,
fontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 0,
rowAnchorX: NaN, // if text can't be placed, what is its anchor?
rowAnchorY: NaN, // if text can't be placed, what is its anchor?
});
});

test('just enough height to fit row with per-side, asymmetric padding', () => {
const padding = { top: 10, bottom: 30 };
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount,
linePitch,
rowIndex,
fontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 200 - 2 * defaultPadding, // Height of 100 - 2 * 80 < 50
rowAnchorX: 0,
rowAnchorY: overhangOffset - padding.top,
});
});

test('two half-height rows also fit into the same area', () => {
const padding = { top: 10, bottom: 30 };
const smallFontSize = 25;
const smallLinePitch = 25;
const totalRowCount2 = 2;
const rowIndex = 0;
const smallOverhangOffset = -2.8125;
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount2,
smallLinePitch,
rowIndex,
smallFontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 200 - 2 * defaultPadding, // Height of 100 - 2 * 80 < 50
rowAnchorX: 0,
rowAnchorY: smallOverhangOffset - padding.top,
});
});

test('two half-height rows do not fit into the a slightly less high area', () => {
const padding = { top: 10, bottom: 45 };
const smallFontSize = 25;
const smallLinePitch = 25;
const totalRowCount2 = 2;
const rowIndex = 0;
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount2,
smallLinePitch,
rowIndex,
smallFontSize,
_rotation,
verticalAlignment,
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 0, // Height of 100 - (10 + smallOverhangOffset) - 45 < totalRowCount2 * smallLinePitch
rowAnchorX: NaN,
rowAnchorY: NaN,
});
});

test('paddingBottom correctly moves the row anchor with bottom alignment', () => {
const padding = { top: 0, right: 0, bottom: 20, left: 0 };
const smallFontSize = 25;
const smallLinePitch = 25;
const totalRowCount2 = 2;
const rowIndex = 0;
const result = getRectangleRowGeometry(
container,
cx,
cy,
totalRowCount2,
smallLinePitch,
rowIndex,
smallFontSize,
_rotation,
'bottom',
padding,
);
// full container width is available; small Y offset for overhang
expect(result).toEqual({
maximumRowLength: 200,
rowAnchorX: 0,
rowAnchorY: -(
(
100 /*y1*/ -
smallLinePitch * (totalRowCount2 - 1 - rowIndex) -
padding.bottom -
smallFontSize * 0.05
) /* 0.05 = 5%: default overhang multiplier */
),
});
});
});
Loading

0 comments on commit ed1e8be

Please sign in to comment.