@formicoidea/labre-framework-wardley 0.23.0 → 0.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/element-renderer.ts +242 -242
- package/src/element-view.ts +143 -143
- package/src/gradient.ts +137 -137
- package/src/label-layout.ts +126 -126
- package/src/legend.ts +438 -438
- package/src/node/consts.ts +109 -109
- package/src/node/node-renderer.ts +142 -142
- package/src/node/node-view.ts +11 -11
- package/src/toolbar/config.ts +280 -280
- package/src/toolbar/node-config.ts +28 -28
- package/src/toolbar/senior-tool.ts +11 -11
- package/src/toolbar/wardley-menu.ts +552 -552
- package/src/toolbar/wardley-senior-button.ts +154 -154
package/src/label-layout.ts
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import type { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
-
|
|
3
|
-
import { FONTS, MARGIN, OFFSETS } from './consts';
|
|
4
|
-
|
|
5
|
-
/** The editable label fields of a Wardley background. */
|
|
6
|
-
export type WardleyLabelField =
|
|
7
|
-
| 'xAxisTitle'
|
|
8
|
-
| 'yAxisTitle'
|
|
9
|
-
| 'evolutionStart'
|
|
10
|
-
| 'evolutionEnd'
|
|
11
|
-
| 'visibilityHigh'
|
|
12
|
-
| 'visibilityLow'
|
|
13
|
-
| 'phase0'
|
|
14
|
-
| 'phase1'
|
|
15
|
-
| 'phase2'
|
|
16
|
-
| 'phase3';
|
|
17
|
-
|
|
18
|
-
/** A label's hit box in element-local coordinates (axis-aligned, padded). */
|
|
19
|
-
export interface WardleyLabelHit {
|
|
20
|
-
field: WardleyLabelField;
|
|
21
|
-
minX: number;
|
|
22
|
-
minY: number;
|
|
23
|
-
maxX: number;
|
|
24
|
-
maxY: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Rough per-character advance — only used to size generous hit boxes. */
|
|
28
|
-
const approxTextWidth = (text: string, fontSize: number) =>
|
|
29
|
-
Math.max(fontSize, text.length * fontSize * 0.6);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Compute the clickable boxes of every *visible* Wardley label, in the
|
|
33
|
-
* element's local space. Positions are derived from the SAME constants the
|
|
34
|
-
* renderer uses (`MARGIN` / `OFFSETS` / `FONTS`), so the hit boxes track the
|
|
35
|
-
* drawn text. Boxes are padded so double-clicking is forgiving.
|
|
36
|
-
*/
|
|
37
|
-
export function getWardleyLabelHits(
|
|
38
|
-
model: WardleyBackgroundElementModel,
|
|
39
|
-
w: number,
|
|
40
|
-
h: number
|
|
41
|
-
): WardleyLabelHit[] {
|
|
42
|
-
const px0 = MARGIN.left;
|
|
43
|
-
const px1 = w - MARGIN.right;
|
|
44
|
-
const py0 = MARGIN.top;
|
|
45
|
-
const py1 = h - MARGIN.bottom;
|
|
46
|
-
const ex = (r: number) => px0 + r * (px1 - px0);
|
|
47
|
-
|
|
48
|
-
const pad = 6;
|
|
49
|
-
const hits: WardleyLabelHit[] = [];
|
|
50
|
-
|
|
51
|
-
// Horizontal label anchored on its text baseline.
|
|
52
|
-
const addH = (
|
|
53
|
-
field: WardleyLabelField,
|
|
54
|
-
text: string,
|
|
55
|
-
fontSize: number,
|
|
56
|
-
ax: number,
|
|
57
|
-
baseline: number,
|
|
58
|
-
align: 'left' | 'right'
|
|
59
|
-
) => {
|
|
60
|
-
const tw = approxTextWidth(text, fontSize);
|
|
61
|
-
const minX = align === 'right' ? ax - tw : ax;
|
|
62
|
-
const maxX = align === 'right' ? ax : ax + tw;
|
|
63
|
-
hits.push({
|
|
64
|
-
field,
|
|
65
|
-
minX: minX - pad,
|
|
66
|
-
maxX: maxX + pad,
|
|
67
|
-
minY: baseline - fontSize - pad,
|
|
68
|
-
maxY: baseline + fontSize * 0.3 + pad,
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Vertical label (drawn rotated -90°), centered on (ax, ay).
|
|
73
|
-
const addV = (
|
|
74
|
-
field: WardleyLabelField,
|
|
75
|
-
text: string,
|
|
76
|
-
fontSize: number,
|
|
77
|
-
ax: number,
|
|
78
|
-
ay: number
|
|
79
|
-
) => {
|
|
80
|
-
const tw = approxTextWidth(text, fontSize);
|
|
81
|
-
hits.push({
|
|
82
|
-
field,
|
|
83
|
-
minX: ax - fontSize - pad,
|
|
84
|
-
maxX: ax + fontSize * 0.4 + pad,
|
|
85
|
-
minY: ay - tw / 2 - pad,
|
|
86
|
-
maxY: ay + tw / 2 + pad,
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
if (model.showColumnLabels) {
|
|
91
|
-
addH('phase0', model.phase0, FONTS.phase, ex(0) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
92
|
-
addH('phase1', model.phase1, FONTS.phase, ex(0.175) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
93
|
-
addH('phase2', model.phase2, FONTS.phase, ex(0.4) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
94
|
-
addH('phase3', model.phase3, FONTS.phase, ex(0.7) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
95
|
-
}
|
|
96
|
-
if (model.showXAxis) {
|
|
97
|
-
addH('xAxisTitle', model.xAxisTitle, FONTS.axis, px1 - OFFSETS.evolutionPadRight, py1 + OFFSETS.phaseBaseline, 'right');
|
|
98
|
-
}
|
|
99
|
-
if (model.showCornerLabels) {
|
|
100
|
-
addH('evolutionStart', model.evolutionStart, FONTS.direction, px0 + OFFSETS.directionPadLeft, py0 + OFFSETS.directionTop, 'left');
|
|
101
|
-
addH('evolutionEnd', model.evolutionEnd, FONTS.direction, px1 - OFFSETS.directionPadRight, py0 + OFFSETS.directionTop, 'right');
|
|
102
|
-
}
|
|
103
|
-
if (model.showYAxis) {
|
|
104
|
-
addV('yAxisTitle', model.yAxisTitle, FONTS.axis, px0 - OFFSETS.yHug, (py0 + py1) / 2);
|
|
105
|
-
}
|
|
106
|
-
if (model.showVisibilityLabels) {
|
|
107
|
-
addV('visibilityHigh', model.visibilityHigh, FONTS.visibility, px0 - OFFSETS.yHug, py0 + OFFSETS.visibleTop);
|
|
108
|
-
addV('visibilityLow', model.visibilityLow, FONTS.visibility, px0 - OFFSETS.yHug, py1 - OFFSETS.invisibleBottom);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return hits;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** First label whose (padded) box contains the local point, or null. */
|
|
115
|
-
export function hitTestWardleyLabel(
|
|
116
|
-
hits: WardleyLabelHit[],
|
|
117
|
-
lx: number,
|
|
118
|
-
ly: number
|
|
119
|
-
): WardleyLabelHit | null {
|
|
120
|
-
for (const hit of hits) {
|
|
121
|
-
if (lx >= hit.minX && lx <= hit.maxX && ly >= hit.minY && ly <= hit.maxY) {
|
|
122
|
-
return hit;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
1
|
+
import type { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
+
|
|
3
|
+
import { FONTS, MARGIN, OFFSETS } from './consts';
|
|
4
|
+
|
|
5
|
+
/** The editable label fields of a Wardley background. */
|
|
6
|
+
export type WardleyLabelField =
|
|
7
|
+
| 'xAxisTitle'
|
|
8
|
+
| 'yAxisTitle'
|
|
9
|
+
| 'evolutionStart'
|
|
10
|
+
| 'evolutionEnd'
|
|
11
|
+
| 'visibilityHigh'
|
|
12
|
+
| 'visibilityLow'
|
|
13
|
+
| 'phase0'
|
|
14
|
+
| 'phase1'
|
|
15
|
+
| 'phase2'
|
|
16
|
+
| 'phase3';
|
|
17
|
+
|
|
18
|
+
/** A label's hit box in element-local coordinates (axis-aligned, padded). */
|
|
19
|
+
export interface WardleyLabelHit {
|
|
20
|
+
field: WardleyLabelField;
|
|
21
|
+
minX: number;
|
|
22
|
+
minY: number;
|
|
23
|
+
maxX: number;
|
|
24
|
+
maxY: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Rough per-character advance — only used to size generous hit boxes. */
|
|
28
|
+
const approxTextWidth = (text: string, fontSize: number) =>
|
|
29
|
+
Math.max(fontSize, text.length * fontSize * 0.6);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Compute the clickable boxes of every *visible* Wardley label, in the
|
|
33
|
+
* element's local space. Positions are derived from the SAME constants the
|
|
34
|
+
* renderer uses (`MARGIN` / `OFFSETS` / `FONTS`), so the hit boxes track the
|
|
35
|
+
* drawn text. Boxes are padded so double-clicking is forgiving.
|
|
36
|
+
*/
|
|
37
|
+
export function getWardleyLabelHits(
|
|
38
|
+
model: WardleyBackgroundElementModel,
|
|
39
|
+
w: number,
|
|
40
|
+
h: number
|
|
41
|
+
): WardleyLabelHit[] {
|
|
42
|
+
const px0 = MARGIN.left;
|
|
43
|
+
const px1 = w - MARGIN.right;
|
|
44
|
+
const py0 = MARGIN.top;
|
|
45
|
+
const py1 = h - MARGIN.bottom;
|
|
46
|
+
const ex = (r: number) => px0 + r * (px1 - px0);
|
|
47
|
+
|
|
48
|
+
const pad = 6;
|
|
49
|
+
const hits: WardleyLabelHit[] = [];
|
|
50
|
+
|
|
51
|
+
// Horizontal label anchored on its text baseline.
|
|
52
|
+
const addH = (
|
|
53
|
+
field: WardleyLabelField,
|
|
54
|
+
text: string,
|
|
55
|
+
fontSize: number,
|
|
56
|
+
ax: number,
|
|
57
|
+
baseline: number,
|
|
58
|
+
align: 'left' | 'right'
|
|
59
|
+
) => {
|
|
60
|
+
const tw = approxTextWidth(text, fontSize);
|
|
61
|
+
const minX = align === 'right' ? ax - tw : ax;
|
|
62
|
+
const maxX = align === 'right' ? ax : ax + tw;
|
|
63
|
+
hits.push({
|
|
64
|
+
field,
|
|
65
|
+
minX: minX - pad,
|
|
66
|
+
maxX: maxX + pad,
|
|
67
|
+
minY: baseline - fontSize - pad,
|
|
68
|
+
maxY: baseline + fontSize * 0.3 + pad,
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Vertical label (drawn rotated -90°), centered on (ax, ay).
|
|
73
|
+
const addV = (
|
|
74
|
+
field: WardleyLabelField,
|
|
75
|
+
text: string,
|
|
76
|
+
fontSize: number,
|
|
77
|
+
ax: number,
|
|
78
|
+
ay: number
|
|
79
|
+
) => {
|
|
80
|
+
const tw = approxTextWidth(text, fontSize);
|
|
81
|
+
hits.push({
|
|
82
|
+
field,
|
|
83
|
+
minX: ax - fontSize - pad,
|
|
84
|
+
maxX: ax + fontSize * 0.4 + pad,
|
|
85
|
+
minY: ay - tw / 2 - pad,
|
|
86
|
+
maxY: ay + tw / 2 + pad,
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (model.showColumnLabels) {
|
|
91
|
+
addH('phase0', model.phase0, FONTS.phase, ex(0) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
92
|
+
addH('phase1', model.phase1, FONTS.phase, ex(0.175) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
93
|
+
addH('phase2', model.phase2, FONTS.phase, ex(0.4) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
94
|
+
addH('phase3', model.phase3, FONTS.phase, ex(0.7) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline, 'left');
|
|
95
|
+
}
|
|
96
|
+
if (model.showXAxis) {
|
|
97
|
+
addH('xAxisTitle', model.xAxisTitle, FONTS.axis, px1 - OFFSETS.evolutionPadRight, py1 + OFFSETS.phaseBaseline, 'right');
|
|
98
|
+
}
|
|
99
|
+
if (model.showCornerLabels) {
|
|
100
|
+
addH('evolutionStart', model.evolutionStart, FONTS.direction, px0 + OFFSETS.directionPadLeft, py0 + OFFSETS.directionTop, 'left');
|
|
101
|
+
addH('evolutionEnd', model.evolutionEnd, FONTS.direction, px1 - OFFSETS.directionPadRight, py0 + OFFSETS.directionTop, 'right');
|
|
102
|
+
}
|
|
103
|
+
if (model.showYAxis) {
|
|
104
|
+
addV('yAxisTitle', model.yAxisTitle, FONTS.axis, px0 - OFFSETS.yHug, (py0 + py1) / 2);
|
|
105
|
+
}
|
|
106
|
+
if (model.showVisibilityLabels) {
|
|
107
|
+
addV('visibilityHigh', model.visibilityHigh, FONTS.visibility, px0 - OFFSETS.yHug, py0 + OFFSETS.visibleTop);
|
|
108
|
+
addV('visibilityLow', model.visibilityLow, FONTS.visibility, px0 - OFFSETS.yHug, py1 - OFFSETS.invisibleBottom);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return hits;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** First label whose (padded) box contains the local point, or null. */
|
|
115
|
+
export function hitTestWardleyLabel(
|
|
116
|
+
hits: WardleyLabelHit[],
|
|
117
|
+
lx: number,
|
|
118
|
+
ly: number
|
|
119
|
+
): WardleyLabelHit | null {
|
|
120
|
+
for (const hit of hits) {
|
|
121
|
+
if (lx >= hit.minX && lx <= hit.maxX && ly >= hit.minY && ly <= hit.maxY) {
|
|
122
|
+
return hit;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|