@communitiesuk/svelte-component-library 0.1.19-beta.27 → 0.1.19-beta.33

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.
@@ -1,5 +1,8 @@
1
1
  <script lang="ts">
2
2
  import Decimal from "decimal.js";
3
+ import { ticks, tickStep, range, nice } from "d3-array";
4
+ import { scaleLinear } from "d3-scale";
5
+ import Axis from "./Axis.svelte";
3
6
 
4
7
  // Types
5
8
  type Axis = "x" | "y";
@@ -20,95 +23,52 @@
20
23
  // Props with defaults (Svelte 5 runes)
21
24
  let {
22
25
  ticksArray = $bindable<number[]>(),
23
- chartWidth,
26
+ tickWidth,
24
27
  chartHeight,
25
- axisFunction,
26
28
  min,
27
29
  max,
28
30
  numberOfTicks,
29
- floor,
30
- ceiling,
31
31
  orientation,
32
32
  fontSize = 19,
33
33
  polarity = "standard",
34
34
  showGridlines = false,
35
35
  showTickMarks = false,
36
-
37
36
  strokeWidth = 2,
38
37
  labelFormatter = undefined as LabelFormatter | undefined,
39
38
  niceTicks = true,
39
+ leftPad = 0,
40
+ rightPad = 0,
40
41
  }: {
41
42
  ticksArray?: number[]; // bindable
42
- chartWidth: number;
43
+ tickWidth: number;
43
44
  chartHeight: number;
44
- axisFunction: any;
45
45
  min: number;
46
46
  max: number;
47
47
  numberOfTicks?: number;
48
- floor?: number;
49
- ceiling?: number;
50
48
  orientation: Orientation;
51
49
  fontSize?: number;
52
50
  polarity?: Polarity;
53
- showGridlines?: Boolean;
54
- showTickMarks?: Boolean;
55
-
51
+ showGridlines?: boolean;
52
+ showTickMarks?: boolean;
56
53
  strokeWidth?: number;
57
54
  labelFormatter?: LabelFormatter;
58
- niceTicks?: Boolean;
55
+ niceTicks?: boolean;
56
+ leftPad?: number;
57
+ rightPad?: number;
59
58
  } = $props();
60
- function axisValue(fn: any, tick: number): number {
61
- // Try single-call first: axisFunction(tick)
62
- try {
63
- const v = fn(tick);
64
- if (typeof v === "number") return v;
65
- } catch {
66
- // ignore
67
- }
68
-
69
- // Fallback: axisFunction()(tick)
70
- const inner = fn();
71
- return inner(tick);
72
- }
73
-
74
- function generateTicks(
75
- min: number,
76
- max: number,
77
- numTicks: number,
78
- floorVal?: number,
79
- ceilingVal?: number,
80
- ): number[] {
81
- let minVal =
82
- floorVal !== undefined ? new Decimal(floorVal) : new Decimal(min);
83
-
84
- let maxVal =
85
- ceilingVal !== undefined ? new Decimal(ceilingVal) : new Decimal(max);
86
-
87
- const rangeVal = maxVal.minus(minVal);
88
- const roughStep = rangeVal.div(numTicks - 1);
89
- const normalizedSteps = [
90
- 1, 2, 2.5, 3, 4, 5, 6, 8, 10, 12, 15, 25, 30, 35, 40, 45,
91
- ];
92
- const stepPower = Decimal.pow(
93
- 10,
94
- -Math.floor(Math.log10(roughStep.toNumber())),
95
- );
96
-
97
- const normalizedStep = roughStep.mul(stepPower);
98
- const chosen = normalizedSteps.find(
99
- (step) => step >= normalizedStep.toNumber(),
100
- );
101
- const optimalStep = new Decimal(chosen ?? 10).div(stepPower);
102
-
103
- const scaleMin = minVal.div(optimalStep).floor().mul(optimalStep);
104
- const scaleMax = maxVal.div(optimalStep).ceil().mul(optimalStep);
105
-
106
- const ticks: number[] = [];
107
- for (let i = scaleMin; i.lte(scaleMax); i = i.plus(optimalStep)) {
108
- ticks.push(i.toNumber());
109
- }
110
- return ticks;
111
- }
59
+ // function axisValue(fn: any, tick: number): number {
60
+ // // Try single-call first: axisFunction(tick)
61
+ // try {
62
+ // const v = fn(tick);
63
+ // if (typeof v === "number") return v;
64
+ // } catch {
65
+ // // ignore
66
+ // }
67
+
68
+ // // Fallback: axisFunction()(tick)
69
+ // const inner = fn();
70
+ // return inner(tick);
71
+ // }
112
72
 
113
73
  // Default label when no labelFormatter is supplied
114
74
  function defaultLabel(tick: number): string {
@@ -116,52 +76,49 @@
116
76
  }
117
77
 
118
78
  function tickCount(w: number, h: number): number {
119
- // Keep behavior aligned with your original code.
120
79
  const tickNum = orientation.axis === "y" ? h / 50 : w / 50;
121
80
  return tickNum;
122
81
  }
123
- function clampTickEnds(
124
- ticks: number[],
125
- floor?: number,
126
- ceiling?: number,
127
- ): number[] {
128
- if (!ticks || ticks.length === 0) return ticks;
129
-
130
- const out = ticks.slice();
131
-
132
- if (floor !== undefined && out[0] <= floor) {
133
- out[0] = floor;
134
- }
135
- if (ceiling !== undefined && out[out.length - 1] >= ceiling) {
136
- out[out.length - 1] = ceiling;
137
- }
138
- return out;
139
- }
140
82
 
141
- // Compute ticks
142
83
  let computedTickCount = $derived(
143
- numberOfTicks ?? tickCount(chartWidth, chartHeight),
84
+ numberOfTicks ?? tickCount(tickWidth, chartHeight),
144
85
  );
145
86
 
146
87
  let rawTicks = $derived(
147
88
  niceTicks
148
- ? generateTicks(min, max, computedTickCount, floor, ceiling)
149
- : [min, max],
89
+ ? ticks(...nice(min, max, computedTickCount), computedTickCount)
90
+ : leftPad || rightPad
91
+ ? polarity === "standard"
92
+ ? [min + (max - min) * leftPad, max - (max - min) * rightPad]
93
+ : [min + (max - min) * rightPad, max - (max - min) * leftPad]
94
+ : [min, max],
150
95
  );
151
96
 
152
97
  let ticksOrdered = $derived(
153
- polarity === "standard" ? rawTicks : rawTicks.reverse(),
98
+ polarity === "standard" ? rawTicks : [...rawTicks].toReversed(),
154
99
  );
155
100
 
156
- ticksArray = ticksOrdered;
101
+ $effect(() => {
102
+ ticksArray = ticksOrdered;
103
+ });
104
+
105
+ let ticksDomain = $derived(
106
+ polarity === "standard"
107
+ ? [Math.min(...ticksArray), Math.max(...ticksArray)]
108
+ : [Math.max(...ticksArray), Math.min(...ticksArray)],
109
+ );
110
+
111
+ let axisFunction = $derived(
112
+ scaleLinear().domain(ticksDomain).range([0, tickWidth]),
113
+ );
157
114
  </script>
158
115
 
159
116
  {#if axisFunction && ticksArray && orientation.axis && orientation.position}
160
117
  {#each ticksArray as tick, index}
161
118
  <g
162
119
  transform="translate(
163
- {orientation.axis === 'x' ? axisValue(axisFunction, tick) : 0},
164
- {orientation.axis === 'y' ? axisValue(axisFunction, tick) : 0}
120
+ {orientation.axis === 'x' ? axisFunction(tick) : 0},
121
+ {orientation.axis === 'y' ? axisFunction(tick) : 0}
165
122
  )"
166
123
  >
167
124
  {#if showTickMarks}
@@ -181,8 +138,8 @@
181
138
  <path
182
139
  d={orientation.axis === "y"
183
140
  ? orientation.position === "left"
184
- ? `M0 0 l${chartWidth} 0`
185
- : `M0 0 l-${chartWidth} 0`
141
+ ? `M0 0 l${tickWidth} 0`
142
+ : `M0 0 l-${tickWidth} 0`
186
143
  : orientation.position === "top"
187
144
  ? `M0 0 l0 ${chartHeight}`
188
145
  : `M0 0 l0 -${chartHeight}`}
@@ -1,3 +1,4 @@
1
+ import Axis from "./Axis.svelte";
1
2
  type Axis = "x" | "y";
2
3
  type Position = "left" | "right" | "top" | "bottom";
3
4
  interface Orientation {
@@ -8,22 +9,21 @@ type Polarity = "standard" | "reverse";
8
9
  type LabelFormatter = (tick: number, index: number, ticksArrayLength: number) => string | number;
9
10
  type $$ComponentProps = {
10
11
  ticksArray?: number[];
11
- chartWidth: number;
12
+ tickWidth: number;
12
13
  chartHeight: number;
13
- axisFunction: any;
14
14
  min: number;
15
15
  max: number;
16
16
  numberOfTicks?: number;
17
- floor?: number;
18
- ceiling?: number;
19
17
  orientation: Orientation;
20
18
  fontSize?: number;
21
19
  polarity?: Polarity;
22
- showGridlines?: Boolean;
23
- showTickMarks?: Boolean;
20
+ showGridlines?: boolean;
21
+ showTickMarks?: boolean;
24
22
  strokeWidth?: number;
25
23
  labelFormatter?: LabelFormatter;
26
- niceTicks?: Boolean;
24
+ niceTicks?: boolean;
25
+ leftPad?: number;
26
+ rightPad?: number;
27
27
  };
28
28
  declare const Ticks: import("svelte").Component<$$ComponentProps, {}, "ticksArray">;
29
29
  type Ticks = ReturnType<typeof Ticks>;
@@ -14,6 +14,7 @@
14
14
  label = undefined,
15
15
  showAxis = true,
16
16
  showArrows = false,
17
+ arrowTextSize = "s",
17
18
  showAverage = false,
18
19
  chartWidth = $bindable(500), // the 'chart' is the bar and the marker
19
20
  chartHeight = 24,
@@ -97,15 +98,18 @@
97
98
  return tick;
98
99
  },
99
100
  niceTicks = true,
101
+ ticksDomain = $bindable([[]]),
102
+ axisDomain = $bindable([[]]),
100
103
  } = $props();
101
104
 
102
- let xTicks = $state([]);
105
+ let xTickFirst = $derived(ticksDomain.length ? ticksDomain[0] : 0);
106
+ let xTickLast = $derived(ticksDomain.length ? ticksDomain.at(-1) : 1);
103
107
 
104
- let xTickFirst = $derived(xTicks.length ? xTicks[0] : 0);
105
- let xTickLast = $derived(xTicks.length ? xTicks.at(-1) : 1);
106
-
107
- let domainMin = $derived(Math.min(xTickFirst, xTickLast));
108
- let domainMax = $derived(Math.max(xTickFirst, xTickLast));
108
+ let domainMin = $derived(Math.min(...axisDomain));
109
+ let domainMax = $derived(Math.max(...axisDomain));
110
+ let chartDomain = $derived(
111
+ polarity === "standard" ? [domainMin, domainMax] : [domainMax, domainMin],
112
+ );
109
113
 
110
114
  const segmentScale = $derived(
111
115
  scaleLinear().domain([0, nSegments]).range([domainMin, domainMax]),
@@ -241,8 +245,8 @@
241
245
  let barWidth = $derived(chartWidth - markerRadius * 2);
242
246
  let barHeight = $derived((chartHeight * 5) / 6);
243
247
 
244
- function xFunction(min, max) {
245
- return scaleLinear().domain([min, max]).range([0, barWidth]).clamp(true);
248
+ function xFunction(chartDomain) {
249
+ return scaleLinear().domain(chartDomain).range([0, barWidth]).clamp(true);
246
250
  }
247
251
 
248
252
  let annotations = $derived(
@@ -325,7 +329,7 @@
325
329
  </marker>
326
330
  </defs>
327
331
  <path
328
- d="M 4 25 v 10 h {xFunction(min, max)(d.value) +
332
+ d="M 4 25 v 10 h {xFunction(chartDomain)(d.value) +
329
333
  markerRadius -
330
334
  4 +
331
335
  (topWidth - chartWidth)} v 15"
@@ -414,11 +418,8 @@
414
418
  ? (e) => e.key === "Enter" && onClickMarker(e, value)
415
419
  : null}
416
420
  pointer-events={interactiveMarkers ? null : "none"}
417
- transform="translate({xFunction(
418
- xTickFirst,
419
- xTickLast,
420
- )(rowValue.value) + markerRadius},{positionChart.chartHeight /
421
- 2})"
421
+ transform="translate({xFunction(chartDomain)(rowValue.value) +
422
+ markerRadius},{positionChart.chartHeight / 2})"
422
423
  >
423
424
  {#if rowValue.shape === "line"}
424
425
  <line
@@ -475,7 +476,8 @@
475
476
  {/each}
476
477
  {#if showAxis}
477
478
  <Axis
478
- bind:ticksArray={xTicks}
479
+ bind:axisDomain
480
+ bind:ticksArray={ticksDomain}
479
481
  {chartHeight}
480
482
  chartWidth={chartWidth - markerRadius * 2}
481
483
  orientation={{ axis: "x", position: "bottom" }}
@@ -492,15 +494,14 @@
492
494
  {showGridlines}
493
495
  {labelFormatter}
494
496
  {niceTicks}
497
+ {markerRadius}
498
+ {distribution}
495
499
  ></Axis>
496
500
  {/if}
497
501
  {#if showAverage}
498
502
  <g
499
- transform="translate({xFunction(
500
- xTickFirst,
501
- xTickLast,
502
- )(averageValue) + markerRadius}, {chartHeight +
503
- (showAxis ? 20 : 0)})"
503
+ transform="translate({xFunction(chartDomain)(averageValue) +
504
+ markerRadius}, {chartHeight + (showAxis ? 20 : 0)})"
504
505
  >
505
506
  <text
506
507
  fill="#444"
@@ -516,13 +517,12 @@
516
517
  {/if}
517
518
  </svg>
518
519
  {#if showArrows}
519
- <PositionChartAxis
520
- markerRadius={10}
521
- barWidth={chartWidth}
522
- textSize="xs"
523
- {chartWidth}
524
- axisLabels={["Worse than average", "Better than average"]}
525
- ></PositionChartAxis>
520
+ <div class="axis-container" style="margin: 0px {markerRadius}px">
521
+ <PositionChartAxis
522
+ textSize={arrowTextSize}
523
+ axisLabels={["Worse than average", "Better than average"]}
524
+ ></PositionChartAxis>
525
+ </div>
526
526
  {/if}
527
527
  </div>
528
528
  {#if moreInfoTogglesArray[i]}
@@ -574,7 +574,8 @@
574
574
  <div style="content-visibility: hidden;">
575
575
  {#if !showAxis}
576
576
  <Axis
577
- bind:ticksArray={xTicks}
577
+ bind:axisDomain
578
+ bind:ticksArray={ticksDomain}
578
579
  {chartHeight}
579
580
  chartWidth={chartWidth - markerRadius * 2}
580
581
  orientation={{ axis: "x", position: "bottom" }}
@@ -591,6 +592,8 @@
591
592
  {showGridlines}
592
593
  {labelFormatter}
593
594
  {niceTicks}
595
+ {markerRadius}
596
+ {distribution}
594
597
  ></Axis>
595
598
  {/if}
596
599
  </div>
@@ -10,6 +10,7 @@ declare const PositionChart: import("svelte").Component<{
10
10
  label?: any;
11
11
  showAxis?: boolean;
12
12
  showArrows?: boolean;
13
+ arrowTextSize?: string;
13
14
  showAverage?: boolean;
14
15
  chartWidth?: number;
15
16
  chartHeight?: number;
@@ -55,7 +56,9 @@ declare const PositionChart: import("svelte").Component<{
55
56
  showGridlines?: boolean;
56
57
  labelFormatter?: Function;
57
58
  niceTicks?: boolean;
58
- }, {}, "chartWidth">;
59
+ ticksDomain?: any[];
60
+ axisDomain?: any[];
61
+ }, {}, "chartWidth" | "axisDomain" | "ticksDomain">;
59
62
  type $$ComponentProps = {
60
63
  value?: any;
61
64
  min?: any;
@@ -63,6 +66,7 @@ type $$ComponentProps = {
63
66
  label?: any;
64
67
  showAxis?: boolean;
65
68
  showArrows?: boolean;
69
+ arrowTextSize?: string;
66
70
  showAverage?: boolean;
67
71
  chartWidth?: number;
68
72
  chartHeight?: number;
@@ -108,4 +112,6 @@ type $$ComponentProps = {
108
112
  showGridlines?: boolean;
109
113
  labelFormatter?: Function;
110
114
  niceTicks?: boolean;
115
+ ticksDomain?: any[];
116
+ axisDomain?: any[];
111
117
  };
@@ -1,47 +1,47 @@
1
1
  <script>
2
- let {
3
- markerRadius = undefined,
4
- textSize = "s",
5
- chartWidth,
6
- axisLabels = ["Left", "Right"],
7
- } = $props();
2
+ let { textSize = "s", axisLabels = ["Left", "Right"] } = $props();
8
3
  </script>
9
4
 
10
- <div
11
- class="axis govuk-body-{textSize}"
12
- style="--axis-padding:{markerRadius}px; width: {chartWidth}"
13
- >
14
- <div class="right-label">
15
- <svg
16
- xmlns="http://www.w3.org/2000/svg"
17
- width="18"
18
- height="16"
19
- viewBox="0 0 11 20"
20
- fill="none"
21
- >
22
- <path
23
- transform="translate(20,21)rotate(180)"
24
- d="M 19.7071 12.7071 C 20.0976 12.3166 20.0976 11.6834 19.7071 11.2929 L 13.3431 4.9289 C 12.9526 4.5384 12.3195 4.5384 11.9289 4.9289 C 11.5384 5.3195 11.5384 5.9526 11.9289 6.3431 L 17.5858 12 L 11.9289 17.6568 C 11.5384 18.0474 11.5384 18.6805 11.9289 19.0711 C 12.3195 19.4616 12.9526 19.4616 13.3431 19.0711 L 19.7071 12.7071 Z M 4 12 L 4 13 L 19 13 L 19 12 L 19 11 L 9 11 L 4 11 Z"
25
- fill="darkgrey"
26
- ></path>
27
- </svg>
5
+ <div class="axis govuk-body-{textSize}">
6
+ <div class="left-label">
7
+ <div class="arrow-container">
8
+ <svg
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ width="18"
11
+ height="16"
12
+ viewBox="0 0 18 16"
13
+ >
14
+ <path
15
+ d="M8 1L1 8L8 15M1 8H17"
16
+ stroke="darkgrey"
17
+ stroke-width="2"
18
+ fill="none"
19
+ stroke-linecap="round"
20
+ stroke-linejoin="round"
21
+ ></path>
22
+ </svg>
23
+ </div>
28
24
  <span class="axis-text">{axisLabels[0]}</span>
29
25
  </div>
30
- <div class="left-label">
26
+ <div class="right-label">
31
27
  <span class="axis-text">{axisLabels[1]}</span>
32
- <svg
33
- xmlns="http://www.w3.org/2000/svg"
34
- width="18"
35
- height="16"
36
- viewBox="0 0 11 20"
37
- fill="none"
38
- >
39
- <path
40
- transform="translate(-5,-3)"
41
- d="M 19.7071 12.7071 C 20.0976 12.3166 20.0976 11.6834 19.7071 11.2929 L 13.3431 4.9289 C 12.9526 4.5384 12.3195 4.5384 11.9289 4.9289 C 11.5384 5.3195 11.5384 5.9526 11.9289 6.3431 L 17.5858 12 L 11.9289 17.6568 C 11.5384 18.0474 11.5384 18.6805 11.9289 19.0711 C 12.3195 19.4616 12.9526 19.4616 13.3431 19.0711 L 19.7071 12.7071 Z M 4 12 L 4 13 L 19 13 L 19 12 L 19 11 L 9 11 L 4 11 Z"
42
- fill="darkgrey"
43
- ></path>
44
- </svg>
28
+ <div class="arrow-container">
29
+ <svg
30
+ xmlns="http://www.w3.org/2000/svg"
31
+ width="18"
32
+ height="16"
33
+ viewBox="0 0 18 16"
34
+ >
35
+ <path
36
+ d="M10 1L17 8L10 15M17 8H1"
37
+ stroke="darkgrey"
38
+ stroke-width="2"
39
+ fill="none"
40
+ stroke-linecap="round"
41
+ stroke-linejoin="round"
42
+ ></path>
43
+ </svg>
44
+ </div>
45
45
  </div>
46
46
  </div>
47
47
 
@@ -59,10 +59,11 @@
59
59
  .left-label,
60
60
  .right-label {
61
61
  display: flex;
62
+ gap: 4px;
62
63
  max-width: 120px;
63
64
  }
64
65
 
65
- .left-label {
66
+ .right-label {
66
67
  text-align: end;
67
68
  }
68
69
 
@@ -72,4 +73,9 @@
72
73
  color: #666666;
73
74
  font-style: italic;
74
75
  }
76
+
77
+ .arrow-container {
78
+ display: flex;
79
+ align-items: center;
80
+ }
75
81
  </style>
@@ -4,14 +4,10 @@ type PositionChartAxis = {
4
4
  $set?(props: Partial<$$ComponentProps>): void;
5
5
  };
6
6
  declare const PositionChartAxis: import("svelte").Component<{
7
- markerRadius?: any;
8
7
  textSize?: string;
9
- chartWidth: any;
10
8
  axisLabels?: any[];
11
9
  }, {}, "">;
12
10
  type $$ComponentProps = {
13
- markerRadius?: any;
14
11
  textSize?: string;
15
- chartWidth: any;
16
12
  axisLabels?: any[];
17
13
  };