-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Label.js
113 lines (104 loc) · 4.09 KB
/
Label.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import labelLayout from './LabelLayout';
import {Transform} from 'vega-dataflow';
import {array, error, inherits, isFunction} from 'vega-util';
const Output = [
'x',
'y',
'opacity',
'align',
'baseline'
];
const Anchors = [
'top-left',
'left',
'bottom-left',
'top',
'bottom',
'top-right',
'right',
'bottom-right'
];
/**
* Compute text label layout to annotate marks.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {Array<number>} params.size - The size of the layout, provided as a [width, height] array.
* @param {function(*,*): number} [params.sort] - An optional
* comparator function for sorting label data in priority order.
* @param {Array<string>} [params.anchor] - Label anchor points relative to the base mark bounding box.
* The available options are 'top-left', 'left', 'bottom-left', 'top',
* 'bottom', 'top-right', 'right', 'bottom-right', 'middle'.
* @param {Array<number>} [params.offset] - Label offsets (in pixels) from the base mark bounding box.
* This parameter is parallel to the list of anchor points.
* @param {number | null} [params.padding=0] - The amount (in pixels) that a label may exceed the layout size.
* If this parameter is null, a label may exceed the layout size without any boundary.
* @param {string} [params.lineAnchor='end'] - For group line mark labels only, indicates the anchor
* position for labels. One of 'start' or 'end'.
* @param {string} [params.markIndex=0] - For group mark labels only, an index indicating
* which mark within the group should be labeled.
* @param {Array<number>} [params.avoidMarks] - A list of additional mark names for which the label
* layout should avoid overlap.
* @param {boolean} [params.avoidBaseMark=true] - Boolean flag indicating if labels should avoid
* overlap with the underlying base mark being labeled.
* @param {string} [params.method='naive'] - For area make labels only, a method for
* place labels. One of 'naive', 'reduced-search', or 'floodfill'.
* @param {Array<string>} [params.as] - The output fields written by the transform.
* The default is ['x', 'y', 'opacity', 'align', 'baseline'].
*/
export default function Label(params) {
Transform.call(this, null, params);
}
Label.Definition = {
type: 'Label',
metadata: { modifies: true },
params: [
{ name: 'size', type: 'number', array: true, length: 2, required: true },
{ name: 'sort', type: 'compare' },
{ name: 'anchor', type: 'string', array: true, default: Anchors },
{ name: 'offset', type: 'number', array: true, default: [1] },
{ name: 'padding', type: 'number', default: 0, null: true },
{ name: 'lineAnchor', type: 'string', values: ['start', 'end'], default: 'end' },
{ name: 'markIndex', type: 'number', default: 0 },
{ name: 'avoidBaseMark', type: 'boolean', default: true },
{ name: 'avoidMarks', type: 'data', array: true },
{ name: 'method', type: 'string', default: 'naive'},
{ name: 'as', type: 'string', array: true, length: Output.length, default: Output }
]
};
inherits(Label, Transform, {
transform(_, pulse) {
function modp(param) {
const p = _[param];
return isFunction(p) && pulse.modified(p.fields);
}
const mod = _.modified();
if (!(mod || pulse.changed(pulse.ADD_REM) || modp('sort'))) return;
if (!_.size || _.size.length !== 2) {
error('Size parameter should be specified as a [width, height] array.');
}
const as = _.as || Output;
// run label layout
labelLayout(
pulse.materialize(pulse.SOURCE).source || [],
_.size,
_.sort,
array(_.offset == null ? 1 : _.offset),
array(_.anchor || Anchors),
_.avoidMarks || [],
_.avoidBaseMark !== false,
_.lineAnchor || 'end',
_.markIndex || 0,
_.padding === undefined ? 0 : _.padding,
_.method || 'naive'
).forEach(l => {
// write layout results to data stream
const t = l.datum;
t[as[0]] = l.x;
t[as[1]] = l.y;
t[as[2]] = l.opacity;
t[as[3]] = l.align;
t[as[4]] = l.baseline;
});
return pulse.reflow(mod).modifies(as);
}
});