blob: 411f1d2f92eb7f929b53bfd17a95df2a31b79159 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 OrdinalMeta from '../../data/OrdinalMeta';
import ComponentModel from '../../model/Component';
import Model from '../../model/Model';
import {
BoxLayoutOptionMixin, CommonTooltipOption, ComponentOption, ItemStyleOption, LabelOption,
LineStyleOption,
NullUndefined, OrdinalNumber, OrdinalRawValue,
PositionSizeOption
} from '../../util/types';
import Matrix from './Matrix';
import { MatrixDim, MatrixXYLocator } from './MatrixDim';
import { MatrixBodyCorner } from './MatrixBodyCorner';
import { CoordinateSystemHostModel } from '../CoordinateSystem';
import tokens from '../../visual/tokens';
export interface MatrixOption extends ComponentOption, BoxLayoutOptionMixin {
mainType?: 'matrix';
x?: MatrixDimensionOption;
y?: MatrixDimensionOption;
body?: MatrixBodyOption;
corner?: MatrixCornerOption;
// Only for the matrix overall style, won't be inherited by x/y/coner/body.
backgroundStyle?: ItemStyleOption;
// Used on the outer border and the divider line.
borderZ2?: number;
tooltip?: CommonTooltipOption<MatrixTooltipFormatterParams>;
// PENDING: do we need to support other states, i.e., `emphasis`, `blur`, `select`?
}
interface MatrixBodyCornerBaseOption extends MatrixCellStyleOption {
/**
* Only specify some special cell definitions.
* It can represent both body cells and top-left corner cells.
*
* [body/corner cell locating]:
* The rule is uniformly applied, such as, in `matrix.dataToPoint`
* and `matrix.dataToLayout` and `xxxComponent.coord`.
* Suppose the matrix.x/y dimensions (header) are defined as:
* matrix: {
* x: [{ value: 'Xa0', children: ['Xb0', 'Xb1'] }, 'Xa1'],
* y: [{ value: 'Ya0', children: ['Yb0', 'Yb1'] }],
* }
* -----------------------------------------
* | | | Xa0 | |
* |-------+-------+---------------| Xa1 |
* |cornerQ|cornerP| Xb0 | Xb1 | |
* |-------+-------+-------+-------+--------
* | | Yb0 | bodyR | bodyS | |
* | Ya0 |-------+-------+---------------|
* | | Yb1 | | bodyT |
* |---------------|------------------------
* "Locator number" (`MatrixXYLocator`):
* The term `locator` refers to a integer number to locate cells on x or y direction.
* Use the top-left cell of the body as the origin point (0, 0),
* the non-negative locator indicates the right/bottom of the origin point;
* the negative locator indicates the left/top of the origin point.
* "Ordinal number" (`OrdinalNumber`):
* This term follows the same meaning as that in category axis of cartesian. They are
* non-negative integer, designating each string `matrix.x.data[i].value`/`matrix.y.data[i].value`.
* 'Xb0', 'Xb2', 'Xa1', 'Xa0' are assigned with the ordinal numbers 0, 1, 2, 3.
* For every leaf dimension cell, `OrdinalNumber` and `MatrixXYLocator` is the same.
*
* A cell or pixel point or rect can be determined/located by a pair of `MatrixCoordValueOption`.
* See also `MatrixBodyCornerCellOption['coord']`.
*
* - The body cell `bodyS` above can be located by:
* - `coord: [1, 0]` (`MatrixXYLocator` or `OrdinalNumber`, which is a non-negative integer)
* - `coord: ['Xb1', 'Yb0']`
* - `coord: ['Xb1', 0]` (mix them)
* - The corner cell `cornerQ` above can be located by:
* - `coord: [-2, -1]` (negative `MatrixXYLocator`)
* - But it is NOT supported to use `coord: ['Y1_0', 'X1_0']` (XY transposed form) here.
* It's mathematically sound, but may introduce confusion and unnecessary
* complexity (consider the 'Xa1' case), and corner locating is not frequently used.
* - `mergeCells`: Body cells or corner cells can be merged, such as "bodyT" above, an input
* - The merging can be defined by:
* `matrix.data[i]: {coord: [['Xb1', 'Xa1'], 'Yb0'], mergeCells: true}`.
* - Input `['Xa1', 'Yb1']` to `dataToPoint` will get a point in the center of "bodyT".
* - Input `['Xa1', 'Yb1']` to `dataToLayout` will get a rect of the "bodyT".
* - If inputing a non-leaf dimension cell to locate, such as `['Xa0', 'Yb0']`,
* - it returns only according to the center of the dimension cells, regardless of the body span.
* (therefore, the result can be on the boundary of two body cells.)
* And the oridinal number assigned to 'Xa0' is 3, thus input `[3, 'Yb0']` get the some result.
* - The dimension (header) cell can be located by negative `MatrixXYLocator`. For example:
* - The center of the node 'Ya0' can be located by `[-2, 'Ya0']`.
*/
data?: MatrixBodyCornerCellOption[];
}
export interface MatrixBodyOption extends MatrixBodyCornerBaseOption {
}
export interface MatrixCornerOption extends MatrixBodyCornerBaseOption {
}
/**
* Commonly used as `MatrixCoordRangeOption[]`
* Can locate a cell or a rect range of cells.
* `[2, 8]` indicates a cell.
* `[2, null/undefined/NaN]` means y is the entire column.
* `[null/undefined/NaN, 8]` is the opposite.
* `[[2, 5], 8]` indicates a rect of cells in x range of `2~5` and y `8`.
* `[[2, 5], null/undefined/NaN]` indicates a x range of `2~5` and y is the entire column.
* `[[2, 5], [7, 8]]` indicates a rect of cells in x range of `2~5` and y range of `7~8`.
* `['aNonLeaf', 8]` indicates a rect of cells in x range of `aNonLeaf` and y `8`.
* @see {parseCoordRangeOption}
* @see {MatrixBodyCornerBaseOption['data']}
*/
export type MatrixCoordRangeOption = (MatrixCoordValueOption | MatrixCoordValueOption[] | NullUndefined);
/**
* `OrdinalRawValue` is originally provided by `matrix.x/y.data[i].value` or `series.data`.
*/
export type MatrixCoordValueOption = OrdinalRawValue | OrdinalNumber | MatrixXYLocator;
export interface MatrixBaseCellOption extends MatrixCellStyleOption {
}
export interface MatrixBodyCornerCellOption extends MatrixBaseCellOption {
// Text that can be displayed.
value?: string;
// Use it to reference a coord in matrix.
coord?: MatrixCoordRangeOption[];
// Merge cells determined by `coord`.
mergeCells?: boolean;
}
interface MatrixDimensionOption extends MatrixCellStyleOption, MatrixDimensionLevelOption {
type?: 'category'; // For internal usage; force be 'category'.
show?: boolean;
data?: MatrixDimensionCellLooseOption[];
// `levels[0]`: the topmost (for x dimension) or leftmost (for y dimension) level.
// If not specified, use null/undefined, such as `levels: [null, null, {levelSize: 10}]`
levels?: (MatrixDimensionLevelOption | NullUndefined)[];
dividerLineStyle?: LineStyleOption;
}
export interface MatrixDimensionCellOption extends MatrixBaseCellOption {
// Use it to define a coord in matrix.
// Do not use type `OrdinalRawValue` here. Number input is forbiden due to the possible confusion.
// e.g., if `matrix.x.data: [1, 2, 3]` is allowed, then querying `coord: [1, null]` will actually get
// the second column, since number represents the index (i.e. `MatrixXYLocator` or `OrdinalNumber`).
value?: string;
// column width (for x dimension) or row height (for y dimension).
// If not specified (null/undefined), auto calculate it.
// Only available on leaves, to avoid unnecessary complex.
// If it is a percentage, such as '30%', based on the matrix width/height, rather than the canvas size.
size?: PositionSizeOption;
children?: MatrixDimensionCellOption[];
}
export type MatrixDimensionCellLooseOption = MatrixDimensionCellOption | MatrixDimensionCellOption['value'];
export interface MatrixDimensionLevelOption {
// `matrix.levelSize` specifies the default size of every tree levels.
// `matrix.levels[i].levelSize` specifies the size of a certain level.
// For x dimension, that is height; for y dimension, that is width.
// If not specified (null/undefined), auto calculate it.
// If it is a percentage, such as '30%', based on the matrix width/height, rather than the canvas size.
levelSize?: PositionSizeOption;
// Other level specific options may added if needed, such as border-bottom/right style.
}
export interface MatrixDimensionModel extends Model<MatrixDimensionOption> {
}
/**
* Two levels of cascade inheritance:
* - priority-high: style options defined in `matrix.x/y/coner/body.data[i]` (in cell)
* - priority-low: style options defined in `matrix.x/y/coner/body`
*/
export interface MatrixCellStyleOption {
// [NOTE - padding]:
// - Consider the option for the space between cell boundary to text, percentage value for
// `label.width/height` is not supported, because it is not ideal - the padding would vary
// with the cell size, making the layout inconsistent.
// - The inner text padding uses `lable.style.padding`.
// The text truncation rect is obtained by cell rect minus by padding.
// - The inner series / other coord sys padding is not supported, to avoid necessary complexity.
// Consider some series, such as heatmap, prefer no padding.
label?: LabelOption;
itemStyle?: ItemStyleOption;
cursor?: string;
// By default, auto decide whether to be silent, considering tooltip.
silent?: boolean | NullUndefined;
// Used when style conflict - especially for thick border style.
z2?: number;
}
export interface MatrixTooltipFormatterParams {
componentType: 'matrix'
matrixIndex: number
name: string
$vars: ['name', 'xyLocator']
}
const defaultLabelOption: LabelOption = {
show: true,
color: tokens.color.secondary,
// overflow: 'truncate',
overflow: 'break',
lineOverflow: 'truncate',
padding: [2, 3, 2, 3],
// Prefer to use `padding`, rather than distance.
distance: 0,
};
function makeDefaultCellItemStyleOption(isCorner: boolean) {
return {
color: 'none',
borderWidth: 1,
borderColor: isCorner ? 'none' : tokens.color.borderTint,
};
};
const defaultDimOption: MatrixDimensionOption = {
show: true,
label: defaultLabelOption,
itemStyle: makeDefaultCellItemStyleOption(false),
silent: undefined,
dividerLineStyle: {
width: 1,
color: tokens.color.border,
},
};
const defaultBodyOption: MatrixBodyOption = {
label: defaultLabelOption,
itemStyle: makeDefaultCellItemStyleOption(false),
silent: undefined,
};
const defaultCornerOption: MatrixCornerOption = {
label: defaultLabelOption,
itemStyle: makeDefaultCellItemStyleOption(true),
silent: undefined,
};
const defaultMatrixOption: MatrixOption = {
// As a most basic coord sys, `z` should be lower than
// other series and coord sys, such as, grid.
z: -50,
left: '10%',
top: '10%',
right: '10%',
bottom: '10%',
x: defaultDimOption,
y: defaultDimOption,
body: defaultBodyOption,
corner: defaultCornerOption,
backgroundStyle: {
color: 'none',
borderColor: tokens.color.axisLine,
borderWidth: 1,
},
};
class MatrixModel extends ComponentModel<MatrixOption> implements CoordinateSystemHostModel {
static type = 'matrix';
type = MatrixModel.type;
coordinateSystem: Matrix;
static layoutMode = 'box' as const;
private _dimModels: {
x: MatrixDimensionModel;
y: MatrixDimensionModel;
};
private _body: MatrixBodyCorner<'body'>;
private _corner: MatrixBodyCorner<'corner'>;
static defaultOption: MatrixOption = defaultMatrixOption;
optionUpdated(): void {
// Simply re-create all to follow model changes.
const dimModels = this._dimModels = {
// Do not use matrixModel as the parent model, for preventing from cascade-fetching options to it.
x: new MatrixDimensionModel(this.get('x', true) || {}),
y: new MatrixDimensionModel(this.get('y', true) || {}),
};
dimModels.x.option.type = dimModels.y.option.type = 'category';
const xDim = dimModels.x.dim = new MatrixDim('x', dimModels.x);
const yDim = dimModels.y.dim = new MatrixDim('y', dimModels.y);
const dims = {x: xDim, y: yDim};
this._body = new MatrixBodyCorner(
'body', new Model(this.getShallow('body')), dims
);
this._corner = new MatrixBodyCorner(
'corner', new Model(this.getShallow('corner')), dims
);
}
getDimensionModel(dim: 'x' | 'y'): MatrixDimensionModel {
return this._dimModels[dim];
}
getBody(): MatrixBodyCorner<'body'> {
return this._body;
}
getCorner(): MatrixBodyCorner<'corner'> {
return this._corner;
}
}
export class MatrixDimensionModel extends Model<MatrixDimensionOption> {
dim: MatrixDim;
getOrdinalMeta(): OrdinalMeta {
return this.dim.getOrdinalMeta();
}
}
export default MatrixModel;