@opendata-ai/openchart-engine 8.0.0-rc.2 → 8.0.0-rc.20

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.
Files changed (271) hide show
  1. package/dist/index.d.ts +140 -2
  2. package/dist/index.js +1403 -210
  3. package/dist/index.js.map +1 -1
  4. package/package.json +3 -4
  5. package/src/__test-fixtures__/specs.ts +0 -182
  6. package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +0 -2339
  7. package/src/__tests__/annotation-behavior.test.ts +0 -721
  8. package/src/__tests__/axes.test.ts +0 -1579
  9. package/src/__tests__/compile-animation.test.ts +0 -161
  10. package/src/__tests__/compile-chart.test.ts +0 -1303
  11. package/src/__tests__/compile-footnotes.test.ts +0 -277
  12. package/src/__tests__/compile-layer.test.ts +0 -969
  13. package/src/__tests__/compile-snapshot.test.ts +0 -156
  14. package/src/__tests__/compound-labels.test.ts +0 -147
  15. package/src/__tests__/dimensions.test.ts +0 -918
  16. package/src/__tests__/encoding-sugar.test.ts +0 -390
  17. package/src/__tests__/facet.test.ts +0 -215
  18. package/src/__tests__/helpers/invariants.ts +0 -299
  19. package/src/__tests__/known-bugs.test.ts +0 -566
  20. package/src/__tests__/layout-fuzz.test.ts +0 -358
  21. package/src/__tests__/layout-invariants.test.ts +0 -535
  22. package/src/__tests__/legend.test.ts +0 -721
  23. package/src/__tests__/mark-keys.test.ts +0 -294
  24. package/src/__tests__/ranking-recipes.test.ts +0 -260
  25. package/src/__tests__/rotation.test.ts +0 -83
  26. package/src/__tests__/scales.test.ts +0 -323
  27. package/src/__tests__/series-search.test.ts +0 -92
  28. package/src/__tests__/spec-sugar.test.ts +0 -801
  29. package/src/__tests__/you-draw-it.test.ts +0 -160
  30. package/src/annotations/__tests__/compute.test.ts +0 -1672
  31. package/src/annotations/__tests__/geometry.test.ts +0 -66
  32. package/src/annotations/__tests__/placement-fuzz.test.ts +0 -256
  33. package/src/annotations/__tests__/placement-invariants.test.ts +0 -345
  34. package/src/annotations/__tests__/placement.test.ts +0 -316
  35. package/src/annotations/__tests__/resolve-text.test.ts +0 -788
  36. package/src/annotations/__tests__/rich-text.test.ts +0 -91
  37. package/src/annotations/__tests__/thinning.test.ts +0 -285
  38. package/src/annotations/collisions.ts +0 -326
  39. package/src/annotations/compute.ts +0 -333
  40. package/src/annotations/constants.ts +0 -162
  41. package/src/annotations/geometry.ts +0 -370
  42. package/src/annotations/move.ts +0 -135
  43. package/src/annotations/placement.ts +0 -636
  44. package/src/annotations/position.ts +0 -131
  45. package/src/annotations/resolve-range.ts +0 -117
  46. package/src/annotations/resolve-refline.ts +0 -157
  47. package/src/annotations/resolve-text.ts +0 -539
  48. package/src/annotations/rich-text.ts +0 -65
  49. package/src/annotations/thinning.ts +0 -190
  50. package/src/barlist/__tests__/compile-barlist.test.ts +0 -200
  51. package/src/barlist/compile-barlist.ts +0 -391
  52. package/src/barlist/types.ts +0 -28
  53. package/src/charts/__tests__/post-process.test.ts +0 -237
  54. package/src/charts/__tests__/registry.test.ts +0 -116
  55. package/src/charts/__tests__/utils.test.ts +0 -195
  56. package/src/charts/_shared/__tests__/density-filter.test.ts +0 -32
  57. package/src/charts/_shared/density-filter.ts +0 -26
  58. package/src/charts/_shared/format-label-value.ts +0 -16
  59. package/src/charts/bar/__tests__/compute.test.ts +0 -934
  60. package/src/charts/bar/__tests__/gradient-orient.test.ts +0 -127
  61. package/src/charts/bar/__tests__/labels.test.ts +0 -292
  62. package/src/charts/bar/compute.ts +0 -539
  63. package/src/charts/bar/index.ts +0 -60
  64. package/src/charts/bar/labels.ts +0 -311
  65. package/src/charts/beeswarm/__tests__/compute.test.ts +0 -280
  66. package/src/charts/beeswarm/__tests__/dodge.test.ts +0 -148
  67. package/src/charts/beeswarm/compute.ts +0 -203
  68. package/src/charts/beeswarm/dodge.ts +0 -155
  69. package/src/charts/beeswarm/index.ts +0 -32
  70. package/src/charts/builtin.ts +0 -75
  71. package/src/charts/calendar/__tests__/compute.test.ts +0 -417
  72. package/src/charts/calendar/compute.ts +0 -319
  73. package/src/charts/calendar/index.ts +0 -32
  74. package/src/charts/column/__tests__/compute.test.ts +0 -540
  75. package/src/charts/column/__tests__/labels.test.ts +0 -179
  76. package/src/charts/column/compute.ts +0 -493
  77. package/src/charts/column/index.ts +0 -53
  78. package/src/charts/column/labels.ts +0 -229
  79. package/src/charts/dot/__tests__/compute.test.ts +0 -380
  80. package/src/charts/dot/__tests__/labels.test.ts +0 -98
  81. package/src/charts/dot/compute.ts +0 -285
  82. package/src/charts/dot/index.ts +0 -62
  83. package/src/charts/dot/labels.ts +0 -121
  84. package/src/charts/line/__tests__/compute.test.ts +0 -1194
  85. package/src/charts/line/__tests__/curves.test.ts +0 -51
  86. package/src/charts/line/__tests__/labels.test.ts +0 -94
  87. package/src/charts/line/__tests__/mark-options.test.ts +0 -175
  88. package/src/charts/line/area.ts +0 -484
  89. package/src/charts/line/compute.ts +0 -356
  90. package/src/charts/line/curves.ts +0 -85
  91. package/src/charts/line/index.ts +0 -176
  92. package/src/charts/line/labels.ts +0 -177
  93. package/src/charts/parliament/__tests__/compute.test.ts +0 -411
  94. package/src/charts/parliament/compute.ts +0 -313
  95. package/src/charts/parliament/index.ts +0 -32
  96. package/src/charts/pie/__tests__/compute.test.ts +0 -384
  97. package/src/charts/pie/__tests__/labels.test.ts +0 -132
  98. package/src/charts/pie/compute.ts +0 -324
  99. package/src/charts/pie/index.ts +0 -49
  100. package/src/charts/pie/labels.ts +0 -143
  101. package/src/charts/post-process.ts +0 -229
  102. package/src/charts/range/__tests__/compute.test.ts +0 -486
  103. package/src/charts/range/compute.ts +0 -273
  104. package/src/charts/range/index.ts +0 -82
  105. package/src/charts/range/labels.ts +0 -169
  106. package/src/charts/rect/__tests__/compute.test.ts +0 -120
  107. package/src/charts/rect/index.ts +0 -127
  108. package/src/charts/registry.ts +0 -72
  109. package/src/charts/rule/index.ts +0 -139
  110. package/src/charts/scatter/__tests__/compute.test.ts +0 -488
  111. package/src/charts/scatter/__tests__/trendline.test.ts +0 -191
  112. package/src/charts/scatter/compute.ts +0 -192
  113. package/src/charts/scatter/index.ts +0 -46
  114. package/src/charts/scatter/trendline.ts +0 -100
  115. package/src/charts/text/__tests__/compute.test.ts +0 -307
  116. package/src/charts/text/index.ts +0 -141
  117. package/src/charts/tick/__tests__/compute.test.ts +0 -75
  118. package/src/charts/tick/index.ts +0 -99
  119. package/src/charts/utils.ts +0 -170
  120. package/src/charts/waffle/__tests__/compute.test.ts +0 -305
  121. package/src/charts/waffle/compute.ts +0 -197
  122. package/src/charts/waffle/index.ts +0 -31
  123. package/src/compile/__tests__/color-scale-range.test.ts +0 -158
  124. package/src/compile/__tests__/color-scale-reverse.test.ts +0 -89
  125. package/src/compile/__tests__/data-clip.test.ts +0 -127
  126. package/src/compile/__tests__/fill-patterns.test.ts +0 -154
  127. package/src/compile/__tests__/size-scale.test.ts +0 -108
  128. package/src/compile/__tests__/watermark-obstacle.test.ts +0 -93
  129. package/src/compile/color-scale-range.ts +0 -101
  130. package/src/compile/data-clip.ts +0 -48
  131. package/src/compile/fill-patterns.ts +0 -74
  132. package/src/compile/layer.ts +0 -626
  133. package/src/compile/size-scale.ts +0 -139
  134. package/src/compile/spec-sugar.ts +0 -758
  135. package/src/compile/watermark-obstacle.ts +0 -54
  136. package/src/compile/you-draw-it.ts +0 -123
  137. package/src/compile.ts +0 -1450
  138. package/src/compiler/__tests__/a11y-warnings.test.ts +0 -148
  139. package/src/compiler/__tests__/animation.test.ts +0 -177
  140. package/src/compiler/__tests__/compile.test.ts +0 -87
  141. package/src/compiler/__tests__/fixtures/vl-parity/axis-null.json +0 -19
  142. package/src/compiler/__tests__/fixtures/vl-parity/color-value.json +0 -20
  143. package/src/compiler/__tests__/fixtures/vl-parity/count-aggregate.json +0 -22
  144. package/src/compiler/__tests__/fixtures/vl-parity/data-values.json +0 -19
  145. package/src/compiler/__tests__/fixtures/vl-parity/layer-axis-null.json +0 -14
  146. package/src/compiler/__tests__/fixtures/vl-parity/layer-count.json +0 -34
  147. package/src/compiler/__tests__/fixtures/vl-parity/layer-data-values.json +0 -22
  148. package/src/compiler/__tests__/fixtures/vl-parity/layer-legend-null.json +0 -41
  149. package/src/compiler/__tests__/fixtures/vl-parity/layer-scheme.json +0 -38
  150. package/src/compiler/__tests__/fixtures/vl-parity/layer-sort.json +0 -28
  151. package/src/compiler/__tests__/fixtures/vl-parity/layer-theta.json +0 -28
  152. package/src/compiler/__tests__/fixtures/vl-parity/layer-title.json +0 -30
  153. package/src/compiler/__tests__/fixtures/vl-parity/layer-value-def.json +0 -29
  154. package/src/compiler/__tests__/fixtures/vl-parity/legend-null.json +0 -32
  155. package/src/compiler/__tests__/fixtures/vl-parity/scheme.json +0 -25
  156. package/src/compiler/__tests__/fixtures/vl-parity/sort-by-value.json +0 -19
  157. package/src/compiler/__tests__/fixtures/vl-parity/theta-arc.json +0 -19
  158. package/src/compiler/__tests__/fixtures/vl-parity/title-subtitle.json +0 -21
  159. package/src/compiler/__tests__/fixtures/vl-parity/width-height.json +0 -23
  160. package/src/compiler/__tests__/keys.test.ts +0 -83
  161. package/src/compiler/__tests__/normalize-highlight.test.ts +0 -103
  162. package/src/compiler/__tests__/normalize.test.ts +0 -399
  163. package/src/compiler/__tests__/sparkline-defaults.test.ts +0 -153
  164. package/src/compiler/__tests__/validate-did-you-mean.test.ts +0 -86
  165. package/src/compiler/__tests__/validate-highlight.test.ts +0 -112
  166. package/src/compiler/__tests__/validate-near-miss.test.ts +0 -89
  167. package/src/compiler/__tests__/validate.test.ts +0 -629
  168. package/src/compiler/__tests__/vl-parity.test.ts +0 -68
  169. package/src/compiler/a11y-warnings.ts +0 -110
  170. package/src/compiler/animation.ts +0 -142
  171. package/src/compiler/index.ts +0 -47
  172. package/src/compiler/keys.ts +0 -55
  173. package/src/compiler/normalize.ts +0 -733
  174. package/src/compiler/sparkline-defaults.ts +0 -138
  175. package/src/compiler/types.ts +0 -241
  176. package/src/compiler/validate.ts +0 -1810
  177. package/src/endpoint-labels/__tests__/compute.test.ts +0 -587
  178. package/src/endpoint-labels/compute.ts +0 -621
  179. package/src/endpoint-labels/constants.ts +0 -54
  180. package/src/endpoint-labels/format.ts +0 -60
  181. package/src/endpoint-labels/predict.ts +0 -139
  182. package/src/format/__tests__/field-format.test.ts +0 -164
  183. package/src/format/field-format.ts +0 -27
  184. package/src/geo/__tests__/compile-map.test.ts +0 -621
  185. package/src/geo/compile-map.ts +0 -856
  186. package/src/geo/join.ts +0 -115
  187. package/src/geo/projections.ts +0 -45
  188. package/src/geo/types.ts +0 -24
  189. package/src/graphs/__tests__/community.test.ts +0 -228
  190. package/src/graphs/__tests__/compile-graph.test.ts +0 -396
  191. package/src/graphs/__tests__/encoding.test.ts +0 -415
  192. package/src/graphs/community.ts +0 -92
  193. package/src/graphs/compile-graph.ts +0 -329
  194. package/src/graphs/encoding.ts +0 -356
  195. package/src/graphs/types.ts +0 -106
  196. package/src/index.ts +0 -177
  197. package/src/layout/__tests__/metrics.test.ts +0 -97
  198. package/src/layout/__tests__/scales-highlight.test.ts +0 -192
  199. package/src/layout/axes/rotation.ts +0 -152
  200. package/src/layout/axes/thinning.ts +0 -98
  201. package/src/layout/axes/ticks.ts +0 -409
  202. package/src/layout/axes.ts +0 -757
  203. package/src/layout/dimensions.ts +0 -912
  204. package/src/layout/facet.ts +0 -91
  205. package/src/layout/gridlines.ts +0 -84
  206. package/src/layout/metrics.ts +0 -132
  207. package/src/layout/plan.ts +0 -631
  208. package/src/layout/scales.ts +0 -1053
  209. package/src/layout/shared.ts +0 -44
  210. package/src/legend/__tests__/continuous.test.ts +0 -318
  211. package/src/legend/__tests__/redundancy.test.ts +0 -108
  212. package/src/legend/__tests__/size.test.ts +0 -348
  213. package/src/legend/__tests__/suppression.test.ts +0 -382
  214. package/src/legend/compute.ts +0 -747
  215. package/src/legend/continuous.ts +0 -327
  216. package/src/legend/size.ts +0 -407
  217. package/src/legend/suppression.ts +0 -226
  218. package/src/legend/wrap.ts +0 -117
  219. package/src/sankey/__tests__/compile-sankey.test.ts +0 -458
  220. package/src/sankey/__tests__/layout.test.ts +0 -165
  221. package/src/sankey/__tests__/node-label-wrap.test.ts +0 -114
  222. package/src/sankey/__tests__/node-sort.test.ts +0 -45
  223. package/src/sankey/compile-sankey.ts +0 -706
  224. package/src/sankey/layout.ts +0 -184
  225. package/src/sankey/types.ts +0 -41
  226. package/src/tables/__tests__/bar-column.test.ts +0 -147
  227. package/src/tables/__tests__/category-colors.test.ts +0 -230
  228. package/src/tables/__tests__/compile-table.test.ts +0 -236
  229. package/src/tables/__tests__/format-cells.test.ts +0 -143
  230. package/src/tables/__tests__/heatmap.test.ts +0 -148
  231. package/src/tables/__tests__/pagination.test.ts +0 -78
  232. package/src/tables/__tests__/search.test.ts +0 -94
  233. package/src/tables/__tests__/sort.test.ts +0 -107
  234. package/src/tables/__tests__/sparkline.test.ts +0 -122
  235. package/src/tables/bar-column.ts +0 -94
  236. package/src/tables/category-colors.ts +0 -78
  237. package/src/tables/compile-table.ts +0 -427
  238. package/src/tables/format-cells.ts +0 -115
  239. package/src/tables/heatmap.ts +0 -141
  240. package/src/tables/pagination.ts +0 -46
  241. package/src/tables/search.ts +0 -66
  242. package/src/tables/sort.ts +0 -69
  243. package/src/tables/sparkline.ts +0 -113
  244. package/src/tables/utils.ts +0 -16
  245. package/src/tilemap/__tests__/compile-tilemap.test.ts +0 -576
  246. package/src/tilemap/compile-tilemap.ts +0 -755
  247. package/src/tilemap/layout.ts +0 -172
  248. package/src/tilemap/types.ts +0 -33
  249. package/src/tooltips/__tests__/compute.test.ts +0 -582
  250. package/src/tooltips/compute.ts +0 -700
  251. package/src/transforms/__tests__/aggregate.test.ts +0 -159
  252. package/src/transforms/__tests__/bin.test.ts +0 -88
  253. package/src/transforms/__tests__/calculate.test.ts +0 -146
  254. package/src/transforms/__tests__/conditional.test.ts +0 -127
  255. package/src/transforms/__tests__/filter-relative.test.ts +0 -202
  256. package/src/transforms/__tests__/filter.test.ts +0 -59
  257. package/src/transforms/__tests__/fold.test.ts +0 -79
  258. package/src/transforms/__tests__/index.test.ts +0 -93
  259. package/src/transforms/__tests__/predicates.test.ts +0 -193
  260. package/src/transforms/__tests__/timeunit.test.ts +0 -129
  261. package/src/transforms/__tests__/window.test.ts +0 -286
  262. package/src/transforms/aggregate.ts +0 -131
  263. package/src/transforms/bin.ts +0 -87
  264. package/src/transforms/calculate.ts +0 -60
  265. package/src/transforms/conditional.ts +0 -46
  266. package/src/transforms/filter.ts +0 -122
  267. package/src/transforms/fold.ts +0 -49
  268. package/src/transforms/index.ts +0 -60
  269. package/src/transforms/predicates.ts +0 -123
  270. package/src/transforms/timeunit.ts +0 -88
  271. package/src/transforms/window.ts +0 -185
@@ -1,788 +0,0 @@
1
- /**
2
- * Tests for the text-annotation resolver, focused on the `dot` and
3
- * `subtitle` fields added in the multi-series area redesign.
4
- */
5
-
6
- import type { Annotation, LayoutStrategy, Rect } from '@opendata-ai/openchart-core';
7
- import { describe, expect, it } from 'vitest';
8
- import type { NormalizedChartSpec } from '../../compiler/types';
9
- import { computeScales } from '../../layout/scales';
10
- import { computeAnnotations } from '../compute';
11
- import {
12
- DARK_CONNECTOR_STROKE,
13
- DARK_DOT_FILL,
14
- DARK_LABEL_BACKGROUND,
15
- DARK_MUTED_TEXT_FILL,
16
- DEFAULT_ANNOTATION_FONT_SIZE,
17
- DEFAULT_ANNOTATION_FONT_WEIGHT,
18
- DEFAULT_DOT_RADIUS,
19
- DEFAULT_DOT_STROKE_WIDTH,
20
- DEFAULT_LINE_HEIGHT,
21
- FALLBACK_FONT_FAMILY,
22
- LEDE_FONT_WEIGHT,
23
- LIGHT_CONNECTOR_STROKE,
24
- LIGHT_DOT_FILL,
25
- LIGHT_LABEL_BACKGROUND,
26
- LIGHT_MUTED_TEXT_FILL,
27
- MIN_CONNECTOR_LENGTH,
28
- SUBTITLE_FONT_SIZE_RATIO,
29
- SUBTITLE_FONT_WEIGHT,
30
- SUBTITLE_GAP,
31
- } from '../constants';
32
- import { heuristicMeasure } from '../geometry';
33
-
34
- const chartArea: Rect = { x: 50, y: 20, width: 500, height: 300 };
35
-
36
- const fullStrategy: LayoutStrategy = {
37
- labelMode: 'all',
38
- legendPosition: 'right',
39
- annotationPosition: 'inline',
40
- axisLabelDensity: 'full',
41
- };
42
-
43
- function makeSpec(annotations: Annotation[]): NormalizedChartSpec {
44
- return {
45
- markType: 'line',
46
- markDef: { type: 'line' },
47
- data: [
48
- { date: '2019-01-01', value: 10 },
49
- { date: '2020-01-01', value: 20 },
50
- { date: '2021-01-01', value: 30 },
51
- { date: '2022-01-01', value: 40 },
52
- ],
53
- encoding: {
54
- x: { field: 'date', type: 'temporal' },
55
- y: { field: 'value', type: 'quantitative' },
56
- },
57
- chrome: {},
58
- annotations,
59
- responsive: true,
60
- theme: {},
61
- darkMode: 'off',
62
- labels: { density: 'auto', format: '' },
63
- };
64
- }
65
-
66
- describe('text annotation: dot', () => {
67
- // A connector with no arrowhead needs a terminator at the data point, so the
68
- // engine resolves the default marker for it. `dot: false` opts out.
69
- it('resolves the default dot when a connector is enabled and no dot key is set', () => {
70
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'No dot key' }]);
71
- const scales = computeScales(spec, chartArea, spec.data);
72
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
73
-
74
- const dot = annotations[0].dot;
75
- expect(dot).toBeDefined();
76
- expect(dot!.radius).toBe(DEFAULT_DOT_RADIUS);
77
- });
78
-
79
- it('resolves the default dot for an explicit straight connector', () => {
80
- const spec = makeSpec([
81
- { type: 'text', x: '2020-01-01', y: 20, text: 'Straight', connector: 'straight' },
82
- ]);
83
- const scales = computeScales(spec, chartArea, spec.data);
84
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
85
-
86
- expect(annotations[0].dot).toBeDefined();
87
- });
88
-
89
- it('resolves the default dot for a drop-line connector', () => {
90
- const spec = makeSpec([
91
- { type: 'text', x: '2020-01-01', y: 20, text: 'Drop', connector: 'drop-line' },
92
- ]);
93
- const scales = computeScales(spec, chartArea, spec.data);
94
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
95
-
96
- expect(annotations[0].dot).toBeDefined();
97
- });
98
-
99
- it('honors an explicit dot: true on a drop-line connector', () => {
100
- const spec = makeSpec([
101
- { type: 'text', x: '2020-01-01', y: 20, text: 'Drop', connector: 'drop-line', dot: true },
102
- ]);
103
- const scales = computeScales(spec, chartArea, spec.data);
104
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
105
-
106
- const ann = annotations[0];
107
- const px = scales.x!.scale(new Date('2020-01-01')) as number;
108
- const py = scales.y!.scale(20) as number;
109
- expect(ann.dot).toBeDefined();
110
- expect(ann.dot!.x).toBeCloseTo(px, 5);
111
- expect(ann.dot!.y).toBeCloseTo(py, 5);
112
- });
113
-
114
- // An arrowhead is already a terminator; adding a marker under it double-marks
115
- // the data point.
116
- it('does not resolve a default dot when the connector has an arrowhead', () => {
117
- const spec = makeSpec([
118
- { type: 'text', x: '2020-01-01', y: 20, text: 'Arrowed', connector: 'curve' },
119
- ]);
120
- const scales = computeScales(spec, chartArea, spec.data);
121
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
122
-
123
- expect(annotations[0].dot).toBeUndefined();
124
- });
125
-
126
- it('does not populate dot when the connector is disabled', () => {
127
- const spec = makeSpec([
128
- { type: 'text', x: '2020-01-01', y: 20, text: 'Bare', connector: false },
129
- ]);
130
- const scales = computeScales(spec, chartArea, spec.data);
131
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
132
-
133
- expect(annotations[0].dot).toBeUndefined();
134
- });
135
-
136
- it('dot: false suppresses the default marker', () => {
137
- const spec = makeSpec([
138
- { type: 'text', x: '2020-01-01', y: 20, text: 'No marker', dot: false },
139
- ]);
140
- const scales = computeScales(spec, chartArea, spec.data);
141
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
142
-
143
- expect(annotations[0].dot).toBeUndefined();
144
- });
145
-
146
- it('populates dot with default styling when dot: true (light mode)', () => {
147
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'With dot', dot: true }]);
148
- const scales = computeScales(spec, chartArea, spec.data);
149
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy, false);
150
-
151
- const dot = annotations[0].dot;
152
- expect(dot).toBeDefined();
153
- expect(dot!.radius).toBe(DEFAULT_DOT_RADIUS);
154
- expect(dot!.strokeWidth).toBe(DEFAULT_DOT_STROKE_WIDTH);
155
- expect(dot!.fill).toBe(LIGHT_DOT_FILL);
156
- // Marker and its (non-arrowed) leader share the quiet gray.
157
- expect(dot!.stroke).toBe(LIGHT_CONNECTOR_STROKE);
158
- });
159
-
160
- it('populates dot with dark-mode defaults when isDark is true', () => {
161
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'Dark', dot: true }]);
162
- const scales = computeScales(spec, chartArea, spec.data);
163
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy, true);
164
-
165
- const dot = annotations[0].dot;
166
- expect(dot).toBeDefined();
167
- expect(dot!.fill).toBe(DARK_DOT_FILL);
168
- expect(dot!.stroke).toBe(DARK_CONNECTOR_STROKE);
169
- });
170
-
171
- it('respects user-supplied dot style overrides', () => {
172
- const spec = makeSpec([
173
- {
174
- type: 'text',
175
- x: '2020-01-01',
176
- y: 20,
177
- text: 'Custom dot',
178
- dot: { radius: 8, fill: '#ff00ff', stroke: '#00ff00', strokeWidth: 4 },
179
- },
180
- ]);
181
- const scales = computeScales(spec, chartArea, spec.data);
182
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
183
-
184
- const dot = annotations[0].dot;
185
- expect(dot).toBeDefined();
186
- expect(dot!.radius).toBe(8);
187
- expect(dot!.fill).toBe('#ff00ff');
188
- expect(dot!.stroke).toBe('#00ff00');
189
- expect(dot!.strokeWidth).toBe(4);
190
- });
191
-
192
- it('partial dot overrides merge with defaults', () => {
193
- const spec = makeSpec([
194
- { type: 'text', x: '2020-01-01', y: 20, text: 'Partial', dot: { radius: 3 } },
195
- ]);
196
- const scales = computeScales(spec, chartArea, spec.data);
197
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
198
-
199
- const dot = annotations[0].dot;
200
- expect(dot).toBeDefined();
201
- expect(dot!.radius).toBe(3);
202
- // Other fields fall back to defaults.
203
- expect(dot!.fill).toBe(LIGHT_DOT_FILL);
204
- expect(dot!.stroke).toBe(LIGHT_CONNECTOR_STROKE);
205
- expect(dot!.strokeWidth).toBe(DEFAULT_DOT_STROKE_WIDTH);
206
- });
207
-
208
- // The marker sits ON the data point; the connector stops short of it. The old
209
- // behavior put the dot at the pulled-back connector tip, which offset it from
210
- // the point it was marking.
211
- it('dot coordinates sit exactly on the data point, not the pulled-back connector tip', () => {
212
- const spec = makeSpec([
213
- {
214
- type: 'text',
215
- x: '2020-01-01',
216
- y: 20,
217
- text: 'Co-render',
218
- dot: true,
219
- offset: { dx: 60, dy: -60 },
220
- },
221
- ]);
222
- const scales = computeScales(spec, chartArea, spec.data);
223
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
224
-
225
- const resolved = annotations[0];
226
- const px = scales.x!.scale(new Date('2020-01-01')) as number;
227
- const py = scales.y!.scale(20) as number;
228
-
229
- expect(resolved.dot).toBeDefined();
230
- expect(resolved.dot!.x).toBeCloseTo(px, 5);
231
- expect(resolved.dot!.y).toBeCloseTo(py, 5);
232
-
233
- // The connector stops short of the marker.
234
- const connector = resolved.label!.connector!;
235
- const gap = Math.hypot(px - connector.to.x, py - connector.to.y);
236
- expect(gap).toBeGreaterThan(resolved.dot!.radius);
237
- });
238
-
239
- it('dot coordinates apply user connectorOffset.to', () => {
240
- const baseSpec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'A', dot: true }]);
241
- const baseScales = computeScales(baseSpec, chartArea, baseSpec.data);
242
- const withoutOffset = computeAnnotations(baseSpec, baseScales, chartArea, fullStrategy);
243
- const baseDot = withoutOffset[0].dot!;
244
-
245
- const withOffsetSpec = makeSpec([
246
- {
247
- type: 'text',
248
- x: '2020-01-01',
249
- y: 20,
250
- text: 'A',
251
- dot: true,
252
- connectorOffset: { to: { dx: 10, dy: -5 } },
253
- },
254
- ]);
255
- const scales2 = computeScales(withOffsetSpec, chartArea, withOffsetSpec.data);
256
- const withOffset = computeAnnotations(withOffsetSpec, scales2, chartArea, fullStrategy);
257
- const offsetDot = withOffset[0].dot!;
258
-
259
- // The user's connector offset shifts the data-side endpoint, so the marker
260
- // tracks it.
261
- expect(offsetDot.x).toBeCloseTo(baseDot.x + 10, 5);
262
- expect(offsetDot.y).toBeCloseTo(baseDot.y - 5, 5);
263
- });
264
- });
265
-
266
- describe('text annotation: background', () => {
267
- it('does not populate background when not specified', () => {
268
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'No plate' }]);
269
- const scales = computeScales(spec, chartArea, spec.data);
270
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
271
-
272
- expect(annotations[0].label.background).toBeUndefined();
273
- });
274
-
275
- it('resolves background: true to the light surface in light mode', () => {
276
- const spec = makeSpec([
277
- { type: 'text', x: '2020-01-01', y: 20, text: 'Plate', background: true },
278
- ]);
279
- const scales = computeScales(spec, chartArea, spec.data);
280
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy, false);
281
-
282
- expect(annotations[0].label.background).toBe(LIGHT_LABEL_BACKGROUND);
283
- });
284
-
285
- // The dark-mode regression: `background: true` must NOT stay white, or the
286
- // theme's light text renders light-gray-on-white and is unreadable.
287
- it('resolves background: true to the dark surface when isDark is true', () => {
288
- const spec = makeSpec([
289
- { type: 'text', x: '2020-01-01', y: 20, text: 'Plate', background: true },
290
- ]);
291
- const scales = computeScales(spec, chartArea, spec.data);
292
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy, true);
293
-
294
- expect(annotations[0].label.background).toBe(DARK_LABEL_BACKGROUND);
295
- expect(annotations[0].label.background).not.toBe(LIGHT_LABEL_BACKGROUND);
296
- });
297
-
298
- it('passes an explicit background color through unchanged in both modes', () => {
299
- const spec = makeSpec([
300
- { type: 'text', x: '2020-01-01', y: 20, text: 'Plate', background: '#ff00ff' },
301
- ]);
302
- const scales = computeScales(spec, chartArea, spec.data);
303
-
304
- expect(
305
- computeAnnotations(spec, scales, chartArea, fullStrategy, false)[0].label.background,
306
- ).toBe('#ff00ff');
307
- expect(
308
- computeAnnotations(spec, scales, chartArea, fullStrategy, true)[0].label.background,
309
- ).toBe('#ff00ff');
310
- });
311
- });
312
-
313
- describe('text annotation: subtitle', () => {
314
- it('does not populate subtitle when not specified', () => {
315
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'No subtitle' }]);
316
- const scales = computeScales(spec, chartArea, spec.data);
317
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
318
-
319
- expect(annotations[0].subtitle).toBeUndefined();
320
- });
321
-
322
- it('populates subtitle with muted styling and smaller font (light mode)', () => {
323
- const spec = makeSpec([
324
- {
325
- type: 'text',
326
- x: '2020-01-01',
327
- y: 20,
328
- text: 'Primary',
329
- subtitle: 'Methodology note',
330
- },
331
- ]);
332
- const scales = computeScales(spec, chartArea, spec.data);
333
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy, false);
334
-
335
- const sub = annotations[0].subtitle;
336
- expect(sub).toBeDefined();
337
- expect(sub!.text).toBe('Methodology note');
338
- expect(sub!.style.fill).toBe(LIGHT_MUTED_TEXT_FILL);
339
- expect(sub!.style.fontSize).toBe(
340
- Math.round(DEFAULT_ANNOTATION_FONT_SIZE * SUBTITLE_FONT_SIZE_RATIO),
341
- );
342
- });
343
-
344
- it('populates subtitle with dark-mode muted color when isDark', () => {
345
- const spec = makeSpec([
346
- { type: 'text', x: '2020-01-01', y: 20, text: 'Primary', subtitle: 'Note' },
347
- ]);
348
- const scales = computeScales(spec, chartArea, spec.data);
349
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy, true);
350
-
351
- expect(annotations[0].subtitle!.style.fill).toBe(DARK_MUTED_TEXT_FILL);
352
- });
353
-
354
- it('positions subtitle directly below single-line primary text', () => {
355
- const spec = makeSpec([
356
- { type: 'text', x: '2020-01-01', y: 20, text: 'One line', subtitle: 'sub' },
357
- ]);
358
- const scales = computeScales(spec, chartArea, spec.data);
359
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
360
-
361
- const label = annotations[0].label!;
362
- const sub = annotations[0].subtitle!;
363
- const fontSize = label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
364
- const expectedY = label.y + fontSize * DEFAULT_LINE_HEIGHT * 1 + SUBTITLE_GAP;
365
- expect(sub.y).toBe(expectedY);
366
- expect(sub.x).toBe(label.x);
367
- });
368
-
369
- it('positions subtitle below all primary lines when text contains newlines', () => {
370
- const spec = makeSpec([
371
- {
372
- type: 'text',
373
- x: '2020-01-01',
374
- y: 20,
375
- text: 'Line one\nLine two\nLine three',
376
- subtitle: 'after multi-line',
377
- },
378
- ]);
379
- const scales = computeScales(spec, chartArea, spec.data);
380
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
381
-
382
- const label = annotations[0].label!;
383
- const sub = annotations[0].subtitle!;
384
- const fontSize = label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
385
- const expectedY = label.y + fontSize * DEFAULT_LINE_HEIGHT * 3 + SUBTITLE_GAP;
386
- expect(sub.y).toBe(expectedY);
387
- });
388
-
389
- it('subtitle font size scales with custom primary fontSize', () => {
390
- const spec = makeSpec([
391
- {
392
- type: 'text',
393
- x: '2020-01-01',
394
- y: 20,
395
- text: 'Big',
396
- fontSize: 20,
397
- subtitle: 'small',
398
- },
399
- ]);
400
- const scales = computeScales(spec, chartArea, spec.data);
401
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
402
-
403
- expect(annotations[0].subtitle!.style.fontSize).toBe(Math.round(20 * SUBTITLE_FONT_SIZE_RATIO));
404
- });
405
- });
406
-
407
- describe('text annotation: dot + subtitle co-resolution', () => {
408
- it('both fields resolve simultaneously without interfering', () => {
409
- const spec = makeSpec([
410
- {
411
- type: 'text',
412
- x: '2020-01-01',
413
- y: 20,
414
- text: 'Big moment',
415
- subtitle: 'Adjusted for inflation',
416
- dot: true,
417
- offset: { dx: 60, dy: -60 },
418
- },
419
- ]);
420
- const scales = computeScales(spec, chartArea, spec.data);
421
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
422
-
423
- const resolved = annotations[0];
424
- const px = scales.x!.scale(new Date('2020-01-01')) as number;
425
- const py = scales.y!.scale(20) as number;
426
-
427
- expect(resolved.dot).toBeDefined();
428
- expect(resolved.subtitle).toBeDefined();
429
- expect(resolved.dot!.x).toBeCloseTo(px, 5);
430
- expect(resolved.dot!.y).toBeCloseTo(py, 5);
431
- expect(resolved.label!.connector).toBeDefined();
432
- });
433
-
434
- // The connector must clear the subtitle too, not just the primary line. The
435
- // subtitle is often the wider of the two.
436
- it('connector origin clears the subtitle when the subtitle is the wider line', () => {
437
- const spec = makeSpec([
438
- {
439
- type: 'text',
440
- x: '2020-01-01',
441
- y: 20,
442
- text: 'Short',
443
- subtitle: 'A considerably longer subtitle line than the primary',
444
- // Label sits below-left of the point, so the connector exits up/right
445
- // and would cross the subtitle if bounds ignored it.
446
- offset: { dx: 0, dy: 90 },
447
- },
448
- ]);
449
- const scales = computeScales(spec, chartArea, spec.data);
450
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
451
-
452
- const ann = annotations[0];
453
- const connector = ann.label!.connector!;
454
- const bounds = ann.bounds!;
455
-
456
- // The origin must sit outside the full (label ∪ subtitle) block.
457
- const insideX = connector.from.x >= bounds.x && connector.from.x <= bounds.x + bounds.width;
458
- const insideY = connector.from.y >= bounds.y && connector.from.y <= bounds.y + bounds.height;
459
- expect(insideX && insideY).toBe(false);
460
- });
461
- });
462
-
463
- describe('text annotation: connector suppression', () => {
464
- // A label pulled back toward its point leaves no room for a leader: the
465
- // standoff and marker pullback eat the whole span, and what's left is a nub
466
- // touching the marker it's meant to point at. Suppress it, keep the dot.
467
- // (anchor 'top' sets the label ANCHOR_OFFSET above the point; +dy drags it
468
- // back down onto it.)
469
- it('suppresses a connector shorter than MIN_CONNECTOR_LENGTH but keeps the dot', () => {
470
- const spec = makeSpec([
471
- {
472
- type: 'text',
473
- x: '2020-01-01',
474
- y: 20,
475
- text: 'Tiny leader',
476
- anchor: 'top',
477
- offset: { dx: 0, dy: 16 },
478
- },
479
- ]);
480
- const scales = computeScales(spec, chartArea, spec.data);
481
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
482
-
483
- expect(annotations[0].label!.connector).toBeUndefined();
484
- expect(annotations[0].dot).toBeDefined();
485
- });
486
-
487
- // The default (no offset, no anchor) must draw a leader. A bare annotation is
488
- // the spec an author writes first, and floating text with no line back to the
489
- // data is exactly what this redesign set out to kill.
490
- it('draws a connector for a bare text annotation with no offset', () => {
491
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'Default callout' }]);
492
- const scales = computeScales(spec, chartArea, spec.data);
493
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
494
-
495
- const connector = annotations[0].label!.connector;
496
- expect(connector).toBeDefined();
497
- const length = Math.hypot(
498
- connector!.to.x - connector!.from.x,
499
- connector!.to.y - connector!.from.y,
500
- );
501
- expect(length).toBeGreaterThanOrEqual(MIN_CONNECTOR_LENGTH);
502
- expect(annotations[0].dot).toBeDefined();
503
- });
504
-
505
- it('suppresses the connector when the data point lands inside the text block', () => {
506
- // Offset pulls the label back over its own point so the box straddles it.
507
- // A connector here would be drawn straight through the text.
508
- const spec = makeSpec([
509
- {
510
- type: 'text',
511
- x: '2020-01-01',
512
- y: 20,
513
- text: 'The point sits inside this label',
514
- offset: { dx: -110, dy: 4 },
515
- },
516
- ]);
517
- const scales = computeScales(spec, chartArea, spec.data);
518
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
519
-
520
- expect(annotations[0].label!.connector).toBeUndefined();
521
- expect(annotations[0].dot).toBeDefined();
522
- });
523
-
524
- it('keeps the connector once the label is far enough from the point', () => {
525
- const spec = makeSpec([
526
- { type: 'text', x: '2020-01-01', y: 20, text: 'Far label', offset: { dx: 0, dy: -60 } },
527
- ]);
528
- const scales = computeScales(spec, chartArea, spec.data);
529
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
530
-
531
- const connector = annotations[0].label!.connector!;
532
- expect(connector).toBeDefined();
533
- const length = Math.hypot(connector.to.x - connector.from.x, connector.to.y - connector.from.y);
534
- expect(length).toBeGreaterThanOrEqual(MIN_CONNECTOR_LENGTH);
535
- });
536
- });
537
-
538
- describe('text annotation: connector config', () => {
539
- it('bare "curve" string defaults arrow to true', () => {
540
- const spec = makeSpec([
541
- {
542
- type: 'text',
543
- x: '2020-01-01',
544
- y: 20,
545
- text: 'Curve',
546
- connector: 'curve',
547
- offset: { dx: 0, dy: -60 },
548
- },
549
- ]);
550
- const scales = computeScales(spec, chartArea, spec.data);
551
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
552
-
553
- const c = annotations[0].label!.connector!;
554
- expect(c.style).toBe('curve');
555
- expect(c.arrow).toBe(true);
556
- });
557
-
558
- it('bare "straight" string defaults arrow to false', () => {
559
- const spec = makeSpec([
560
- {
561
- type: 'text',
562
- x: '2020-01-01',
563
- y: 20,
564
- text: 'Straight',
565
- connector: 'straight',
566
- offset: { dx: 0, dy: -60 },
567
- },
568
- ]);
569
- const scales = computeScales(spec, chartArea, spec.data);
570
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
571
-
572
- const c = annotations[0].label!.connector!;
573
- expect(c.style).toBe('straight');
574
- expect(c.arrow).toBe(false);
575
- });
576
-
577
- it('boolean true defaults to straight with no arrow', () => {
578
- const spec = makeSpec([
579
- {
580
- type: 'text',
581
- x: '2020-01-01',
582
- y: 20,
583
- text: 'Default',
584
- connector: true,
585
- offset: { dx: 0, dy: -60 },
586
- },
587
- ]);
588
- const scales = computeScales(spec, chartArea, spec.data);
589
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
590
-
591
- const c = annotations[0].label!.connector!;
592
- expect(c.style).toBe('straight');
593
- expect(c.arrow).toBe(false);
594
- });
595
-
596
- it('object form { type: "straight", arrow: true } enables arrow on straight', () => {
597
- const spec = makeSpec([
598
- {
599
- type: 'text',
600
- x: '2020-01-01',
601
- y: 20,
602
- text: 'Arrow straight',
603
- connector: { type: 'straight', arrow: true },
604
- offset: { dx: 0, dy: -60 },
605
- },
606
- ]);
607
- const scales = computeScales(spec, chartArea, spec.data);
608
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
609
-
610
- const c = annotations[0].label!.connector!;
611
- expect(c.style).toBe('straight');
612
- expect(c.arrow).toBe(true);
613
- });
614
-
615
- it('object form { type: "curve", arrow: false } disables arrow on curve', () => {
616
- const spec = makeSpec([
617
- {
618
- type: 'text',
619
- x: '2020-01-01',
620
- y: 20,
621
- text: 'No arrow curve',
622
- connector: { type: 'curve', arrow: false },
623
- offset: { dx: 0, dy: -60 },
624
- },
625
- ]);
626
- const scales = computeScales(spec, chartArea, spec.data);
627
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
628
-
629
- const c = annotations[0].label!.connector!;
630
- expect(c.style).toBe('curve');
631
- expect(c.arrow).toBe(false);
632
- });
633
-
634
- it('object form { type: "curve" } defaults arrow to true', () => {
635
- const spec = makeSpec([
636
- {
637
- type: 'text',
638
- x: '2020-01-01',
639
- y: 20,
640
- text: 'Default curve',
641
- connector: { type: 'curve' },
642
- offset: { dx: 0, dy: -60 },
643
- },
644
- ]);
645
- const scales = computeScales(spec, chartArea, spec.data);
646
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
647
-
648
- const c = annotations[0].label!.connector!;
649
- expect(c.style).toBe('curve');
650
- expect(c.arrow).toBe(true);
651
- });
652
-
653
- it('connector: false disables connector entirely', () => {
654
- const spec = makeSpec([
655
- { type: 'text', x: '2020-01-01', y: 20, text: 'No connector', connector: false },
656
- ]);
657
- const scales = computeScales(spec, chartArea, spec.data);
658
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
659
-
660
- expect(annotations[0].label!.connector).toBeUndefined();
661
- });
662
-
663
- it('drop-line string connector always has arrow: false', () => {
664
- const spec = makeSpec([
665
- { type: 'text', x: '2020-01-01', y: 20, text: 'Drop line', connector: 'drop-line' },
666
- ]);
667
- const scales = computeScales(spec, chartArea, spec.data);
668
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
669
-
670
- const c = annotations[0].label!.connector!;
671
- expect(c.style).toBe('drop-line');
672
- expect(c.arrow).toBe(false);
673
- });
674
-
675
- it('object form { type: "drop-line", arrow: true } clamps arrow to false', () => {
676
- const spec = makeSpec([
677
- {
678
- type: 'text',
679
- x: '2020-01-01',
680
- y: 20,
681
- text: 'Drop line forced',
682
- connector: { type: 'drop-line', arrow: true },
683
- },
684
- ]);
685
- const scales = computeScales(spec, chartArea, spec.data);
686
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
687
-
688
- const c = annotations[0].label!.connector!;
689
- expect(c.style).toBe('drop-line');
690
- expect(c.arrow).toBe(false);
691
- });
692
- });
693
-
694
- describe('text annotation: typography', () => {
695
- it('defaults to the regular annotation font size and weight', () => {
696
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'Plain' }]);
697
- const scales = computeScales(spec, chartArea, spec.data);
698
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
699
-
700
- const style = annotations[0].label!.style;
701
- expect(style.fontSize).toBe(DEFAULT_ANNOTATION_FONT_SIZE);
702
- expect(style.fontWeight).toBe(DEFAULT_ANNOTATION_FONT_WEIGHT);
703
- });
704
-
705
- // Lede rule: a subtitle turns the primary line into a lede, so it goes bold.
706
- it('promotes the primary text to the lede weight when a subtitle is present', () => {
707
- const spec = makeSpec([
708
- { type: 'text', x: '2020-01-01', y: 20, text: 'Feb. 25', subtitle: '2015 maximum' },
709
- ]);
710
- const scales = computeScales(spec, chartArea, spec.data);
711
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
712
-
713
- expect(annotations[0].label!.style.fontWeight).toBe(LEDE_FONT_WEIGHT);
714
- // The subtitle never inherits it.
715
- expect(annotations[0].subtitle!.style.fontWeight).toBe(SUBTITLE_FONT_WEIGHT);
716
- });
717
-
718
- it('an explicit fontWeight wins over the lede rule', () => {
719
- const spec = makeSpec([
720
- {
721
- type: 'text',
722
- x: '2020-01-01',
723
- y: 20,
724
- text: 'Feb. 25',
725
- subtitle: '2015 maximum',
726
- fontWeight: 500,
727
- },
728
- ]);
729
- const scales = computeScales(spec, chartArea, spec.data);
730
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
731
-
732
- expect(annotations[0].label!.style.fontWeight).toBe(500);
733
- });
734
-
735
- it('threads the theme font family into text, range, and refline labels', () => {
736
- const spec = makeSpec([
737
- { type: 'text', x: '2020-01-01', y: 20, text: 'Callout' },
738
- { type: 'range', x: ['2019-01-01', '2020-01-01'], label: 'Band' },
739
- { type: 'refline', y: 25, label: 'Target' },
740
- ]);
741
- const scales = computeScales(spec, chartArea, spec.data);
742
- const annotations = computeAnnotations(spec, {
743
- scales,
744
- chartArea,
745
- strategy: fullStrategy,
746
- isDark: false,
747
- obstacles: [],
748
- svg: { width: 600, height: 360 },
749
- measure: heuristicMeasure,
750
- fontFamily: 'Georgia, serif',
751
- });
752
-
753
- for (const ann of annotations) {
754
- expect(ann.label!.style.fontFamily).toBe('Georgia, serif');
755
- }
756
- });
757
-
758
- it('falls back to the default font stack when no theme font is threaded', () => {
759
- const spec = makeSpec([{ type: 'text', x: '2020-01-01', y: 20, text: 'Callout' }]);
760
- const scales = computeScales(spec, chartArea, spec.data);
761
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
762
-
763
- expect(annotations[0].label!.style.fontFamily).toBe(FALLBACK_FONT_FAMILY);
764
- });
765
- });
766
-
767
- describe('text annotation: alignment', () => {
768
- // Text blocks are never centered: they align on the edge facing the point.
769
- it('left-aligns multi-line text instead of centering it', () => {
770
- const spec = makeSpec([
771
- { type: 'text', x: '2020-01-01', y: 20, text: 'First line\nSecond line' },
772
- ]);
773
- const scales = computeScales(spec, chartArea, spec.data);
774
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
775
-
776
- expect(annotations[0].label!.style.textAnchor).toBe('start');
777
- });
778
-
779
- it('right-aligns the block when it sits to the left of the point', () => {
780
- const spec = makeSpec([
781
- { type: 'text', x: '2020-01-01', y: 20, text: 'First line\nSecond line', anchor: 'left' },
782
- ]);
783
- const scales = computeScales(spec, chartArea, spec.data);
784
- const annotations = computeAnnotations(spec, scales, chartArea, fullStrategy);
785
-
786
- expect(annotations[0].label!.style.textAnchor).toBe('end');
787
- });
788
- });