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

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.
@@ -177,7 +177,6 @@
177
177
  {#if topLabel && layout.topLabelY !== null}
178
178
  <text x={0} y={layout.topLabelY + 14} fill="#666" font-size="0.75em">
179
179
  Number of areas
180
- {minX}, {maxX}
181
180
  </text>
182
181
  {/if}
183
182
 
@@ -201,28 +200,14 @@
201
200
 
202
201
  <g transform="translate(0, {layout.chartY})">
203
202
  {#if showYAxis}
204
- <!-- <Axis
205
- bind:axisDomain
206
- bind:ticksArray={ticksDomain}
207
- {chartHeight}
208
- chartWidth={chartWidth - markerRadius * 2}
209
- orientation={{ axis: "x", position: "bottom" }}
210
- range={[markerRadius, chartWidth - markerRadius]}
211
- domain={[xValueFirst, xValueLast]}
212
- {min}
213
- {max}
214
- fontSize={14}
215
- {floor}
216
- {ceiling}
217
- {numberOfTicks}
218
- {polarity}
219
- {showTickMarks}
220
- {showGridlines}
221
- {labelFormatter}
222
- {niceTicks}
223
- {markerRadius}
224
- {distribution}
225
- ></Axis> -->
203
+ {#each [3, 6, 9] as x, i}
204
+ <path
205
+ transform="translate(0, {scaleLinear([0, 9], [height, 0])(x)})"
206
+ d={`M0 0 l${containerWidth - 25} 0`}
207
+ stroke="grey"
208
+ stroke-width={0.25}
209
+ ></path>
210
+ {/each}
226
211
  {/if}
227
212
 
228
213
  {#each bins as bin, i}
@@ -85,7 +85,7 @@
85
85
  onMouseLeaveMarker = (event, marker, dataId) => {
86
86
  activeMarkerId = null;
87
87
  },
88
- activeMarkerId = undefined,
88
+ activeMarkerId = $bindable(undefined),
89
89
  distribution = [],
90
90
  floor = undefined,
91
91
  ceiling = undefined,
@@ -225,7 +225,7 @@
225
225
  .colors(2);
226
226
 
227
227
  const averageNormalised =
228
- (averageValue - xTickFirst) / (xTickLast - xTickFirst);
228
+ (averageValue - chartDomain[0]) / (chartDomain[1] - chartDomain[0]);
229
229
 
230
230
  const binColors = chroma
231
231
  .scale([extremeColors[0], midColor, extremeColors[1]])
@@ -275,7 +275,8 @@
275
275
  Math.min(
276
276
  nSegments - 1,
277
277
  Math.floor(
278
- (nSegments * (value - xTickFirst)) / (xTickLast - xTickFirst),
278
+ (nSegments * (value - chartDomain[0])) /
279
+ (chartDomain[1] - chartDomain[0]),
279
280
  ),
280
281
  ),
281
282
  );
@@ -391,6 +392,30 @@
391
392
  fill={colorScale[number]}
392
393
  ></rect></g
393
394
  >{/each}
395
+ {#if showAxis}
396
+ <Axis
397
+ bind:axisDomain
398
+ bind:ticksArray={ticksDomain}
399
+ {chartHeight}
400
+ chartWidth={chartWidth - markerRadius * 2}
401
+ orientation={{ axis: "x", position: "bottom" }}
402
+ range={[markerRadius, chartWidth - markerRadius]}
403
+ domain={[xTickFirst, xTickLast]}
404
+ {min}
405
+ {max}
406
+ fontSize={14}
407
+ {floor}
408
+ {ceiling}
409
+ {numberOfTicks}
410
+ {polarity}
411
+ {showTickMarks}
412
+ {showGridlines}
413
+ {labelFormatter}
414
+ {niceTicks}
415
+ {markerRadius}
416
+ {distribution}
417
+ ></Axis>
418
+ {/if}
394
419
  {#each Object.entries(positionChart.rowData) as [tier, points]}
395
420
  {#each points as rowValue, i}
396
421
  {#if !isNaN(Number(rowValue.value))}
@@ -422,6 +447,7 @@
422
447
  markerRadius},{positionChart.chartHeight / 2})"
423
448
  >
424
449
  {#if rowValue.shape === "line"}
450
+ {console.log("seg", segmentIndex(rowValue.value))}
425
451
  <line
426
452
  x1={0}
427
453
  x2={0}
@@ -430,7 +456,7 @@
430
456
  stroke={rowValue.color === "inherit"
431
457
  ? colorScale[segmentIndex(rowValue.value)]
432
458
  : rowValue.color}
433
- stroke-width={rowValue.markerRadius * 5}
459
+ stroke-width={rowValue.markerRadius * 7}
434
460
  opacity={0}
435
461
  pointer-events={rowValue.pointerEvents}
436
462
  ></line>
@@ -455,7 +481,7 @@
455
481
  fill={rowValue.color === "inherit"
456
482
  ? colorScale[segmentIndex(rowValue.value)]
457
483
  : rowValue.color}
458
- stroke-width={3}
484
+ stroke-width={5}
459
485
  opacity={rowValue.opacity}
460
486
  ></circle>
461
487
  <circle
@@ -465,8 +491,8 @@
465
491
  fill={rowValue.color === "inherit"
466
492
  ? colorScale[segmentIndex(rowValue.value)]
467
493
  : rowValue.color}
468
- stroke="black"
469
- stroke-width={1.5}
494
+ stroke="#111"
495
+ stroke-width={3}
470
496
  opacity={rowValue.opacity}
471
497
  ></circle>
472
498
  {/if}
@@ -474,30 +500,7 @@
474
500
  {/if}
475
501
  {/each}
476
502
  {/each}
477
- {#if showAxis}
478
- <Axis
479
- bind:axisDomain
480
- bind:ticksArray={ticksDomain}
481
- {chartHeight}
482
- chartWidth={chartWidth - markerRadius * 2}
483
- orientation={{ axis: "x", position: "bottom" }}
484
- range={[markerRadius, chartWidth - markerRadius]}
485
- domain={[xTickFirst, xTickLast]}
486
- {min}
487
- {max}
488
- fontSize={14}
489
- {floor}
490
- {ceiling}
491
- {numberOfTicks}
492
- {polarity}
493
- {showTickMarks}
494
- {showGridlines}
495
- {labelFormatter}
496
- {niceTicks}
497
- {markerRadius}
498
- {distribution}
499
- ></Axis>
500
- {/if}
503
+
501
504
  {#if showAverage}
502
505
  <g
503
506
  transform="translate({xFunction(chartDomain)(averageValue) +
@@ -511,7 +514,8 @@
511
514
  >
512
515
  <tspan x="1" dy="15">▲</tspan>
513
516
  <tspan x="1" dy="4">|</tspan>
514
- <tspan font-family="GDS Transport" x="1" dy="13">Average</tspan>
517
+
518
+ <tspan font-family="GDS Transport" x="1" dy="15">Average</tspan>
515
519
  </text>
516
520
  </g>
517
521
  {/if}
@@ -58,7 +58,7 @@ declare const PositionChart: import("svelte").Component<{
58
58
  niceTicks?: boolean;
59
59
  ticksDomain?: any[];
60
60
  axisDomain?: any[];
61
- }, {}, "chartWidth" | "axisDomain" | "ticksDomain">;
61
+ }, {}, "chartWidth" | "axisDomain" | "activeMarkerId" | "ticksDomain">;
62
62
  type $$ComponentProps = {
63
63
  value?: any;
64
64
  min?: any;
@@ -2,48 +2,63 @@
2
2
  import Button from "./../../ui/Button.svelte";
3
3
 
4
4
  let {
5
- data = undefined,
6
- metaData = undefined,
5
+ data = [],
6
+ metadata = {},
7
7
  caption = undefined,
8
8
  colourScale = undefined,
9
9
  } = $props();
10
10
 
11
- let localCopyOfData = $derived([...data]);
11
+ let localCopyOfData = $state([...(data ?? [])]);
12
+
13
+ let sortState = $state({
14
+ column: null,
15
+ order: null, // "ascending" | "descending" | null
16
+ });
17
+
18
+ $effect(() => {
19
+ localCopyOfData = [...(data ?? [])];
20
+ });
12
21
 
13
22
  function hasUniqueValues(array, key) {
14
23
  const seen = new Set();
15
24
  for (const obj of array) {
16
25
  if (seen.has(obj[key])) {
17
- return false; // Duplicate found
26
+ return false;
18
27
  }
19
28
  seen.add(obj[key]);
20
29
  }
21
- return true; // All values are unique
30
+ return true;
22
31
  }
23
32
 
24
- let columns = [];
25
-
26
- for (const column in localCopyOfData[0]) {
27
- // create a variable to store whether the key is unique or not
28
- const keyIsUnique = hasUniqueValues(localCopyOfData, column);
29
- // get data type of each column
30
- const columnDataType = typeof localCopyOfData[0][column];
31
- // for each one create an object and push it into the array
32
- const columnObject = {
33
- key: column,
34
- isUnique: keyIsUnique,
35
- dataType: columnDataType,
36
- };
37
- columns.push(columnObject);
38
- }
33
+ let columns = $derived.by(() => {
34
+ if (!data || data.length === 0) return [];
35
+
36
+ const derivedColumns = [];
39
37
 
40
- $inspect("columns array is ", columns);
38
+ for (const column in data[0]) {
39
+ const keyIsUnique = hasUniqueValues(data, column);
40
+ const columnDataType = typeof data[0][column];
41
+
42
+ derivedColumns.push({
43
+ key: column,
44
+ isUnique: keyIsUnique,
45
+ dataType: columnDataType,
46
+ id: metadata[column].order,
47
+ });
48
+ }
49
+
50
+ const orderedColumns = derivedColumns.sort((a, b) => a.id - b.id);
51
+
52
+ return orderedColumns;
53
+ });
41
54
 
42
- const metrics = columns
43
- .filter((column) => column.dataType === "number")
44
- .map((column) => column.key);
55
+ $inspect(columns);
45
56
 
46
- let sortState = $state({ column: "sortedColumn", order: "ascending" });
57
+ const metrics = $derived(
58
+ columns
59
+ .filter((column) => column.dataType === "number")
60
+ .map((column) => column.key),
61
+ );
47
62
 
48
63
  function updateSortState(columnToSort, sortOrder) {
49
64
  sortState.column = columnToSort;
@@ -51,61 +66,63 @@
51
66
  }
52
67
 
53
68
  function sortFunction() {
54
- if (typeof localCopyOfData[0][sortState["column"]] === "number") {
69
+ if (!localCopyOfData.length || !sortState.column || !sortState.order)
70
+ return;
71
+
72
+ const sorted = [...localCopyOfData];
73
+
74
+ if (typeof sorted[0][sortState.column] === "number") {
55
75
  if (sortState.order === "ascending") {
56
- localCopyOfData.sort(
57
- (a, b) => a[sortState.column] - b[sortState.column],
58
- );
76
+ sorted.sort((a, b) => a[sortState.column] - b[sortState.column]);
59
77
  } else {
60
- localCopyOfData.sort(
61
- (a, b) => b[sortState.column] - a[sortState.column],
62
- );
78
+ sorted.sort((a, b) => b[sortState.column] - a[sortState.column]);
63
79
  }
64
80
  }
65
- if (typeof localCopyOfData[0][sortState["column"]] === "string") {
81
+
82
+ if (typeof sorted[0][sortState.column] === "string") {
66
83
  if (sortState.order === "ascending") {
67
- localCopyOfData.sort((a, b) =>
68
- a[sortState["column"]].localeCompare(b[sortState["column"]]),
84
+ sorted.sort((a, b) =>
85
+ a[sortState.column].localeCompare(b[sortState.column]),
69
86
  );
70
87
  } else {
71
- localCopyOfData.sort((a, b) =>
72
- b[sortState["column"]].localeCompare(a[sortState["column"]]),
88
+ sorted.sort((a, b) =>
89
+ b[sortState.column].localeCompare(a[sortState.column]),
73
90
  );
74
91
  }
75
92
  }
76
- }
77
-
78
- $inspect(
79
- sortState,
80
- localCopyOfData[0],
81
- typeof localCopyOfData[0][sortState["column"]],
82
- );
83
93
 
84
- // heat map
85
-
86
- // calculate the min and max of each metric
87
- const minAndMaxValues = {}; // create an empty object to store them in
88
- for (const metric of metrics) {
89
- // get the values
90
- const metricValues = localCopyOfData.map((item) => item[metric]);
91
- const min = Math.min(...metricValues);
92
- const max = Math.max(...metricValues);
93
- // store them
94
- minAndMaxValues[metric] = { min, max };
94
+ localCopyOfData = sorted;
95
95
  }
96
96
 
97
- localCopyOfData = localCopyOfData.map((row) => {
98
- const rowWithNorms = { ...row };
97
+ const minAndMaxValues = $derived.by(() => {
98
+ const result = {};
99
99
 
100
100
  for (const metric of metrics) {
101
- const { min, max } = minAndMaxValues[metric];
102
- const value = row[metric];
103
- const normalisedValue = (value - min) / (max - min);
101
+ const metricValues = localCopyOfData.map((item) => item[metric]);
102
+ const min = Math.min(...metricValues);
103
+ const max = Math.max(...metricValues);
104
104
 
105
- rowWithNorms[`${metric}__normalised`] = normalisedValue;
105
+ result[metric] = { min, max };
106
106
  }
107
107
 
108
- return rowWithNorms;
108
+ return result;
109
+ });
110
+
111
+ const displayRows = $derived.by(() => {
112
+ return localCopyOfData.map((row) => {
113
+ const rowWithNorms = { ...row };
114
+
115
+ for (const metric of metrics) {
116
+ const { min, max } = minAndMaxValues[metric];
117
+ const value = row[metric];
118
+
119
+ const normalisedValue = max === min ? 0.5 : (value - min) / (max - min);
120
+
121
+ rowWithNorms[`${metric}__normalised`] = normalisedValue;
122
+ }
123
+
124
+ return rowWithNorms;
125
+ });
109
126
  });
110
127
 
111
128
  function normToColor(norm) {
@@ -119,6 +136,27 @@
119
136
  }
120
137
 
121
138
  const colorKey = Object.entries({ Good: 1, Ok: 0.5, Bad: 0 });
139
+
140
+ function handleSort(columnKey) {
141
+ let newDirection = "ascending";
142
+
143
+ if (sortState.column === columnKey) {
144
+ if (sortState.order === "ascending") {
145
+ newDirection = "descending";
146
+ } else if (sortState.order === "descending") {
147
+ newDirection = null;
148
+ }
149
+ }
150
+
151
+ if (newDirection === null) {
152
+ updateSortState(null, null);
153
+ localCopyOfData = [...(data ?? [])];
154
+ return;
155
+ }
156
+
157
+ updateSortState(columnKey, newDirection);
158
+ sortFunction();
159
+ }
122
160
  </script>
123
161
 
124
162
  <div class="p-4">
@@ -126,7 +164,10 @@
126
164
  <div class="legend">
127
165
  <div>Colour key:</div>
128
166
  {#each colorKey as key}
129
- <div class="color-keys" style="background-color: {normToColor(key[1])}">
167
+ <div
168
+ class="color-keys"
169
+ style={`background-color: ${normToColor(key[1])}`}
170
+ >
130
171
  {key[0]}
131
172
  </div>
132
173
  {/each}
@@ -135,62 +176,65 @@
135
176
 
136
177
  <div class="table-container">
137
178
  <div id="table-caption" class="sticky-caption">{caption}</div>
179
+
138
180
  <table class="govuk-table" data-module="moj-sortable-table">
139
- <thead class="govuk-table__head"
140
- ><tr class="govuk-table__row">
181
+ <thead class="govuk-table__head">
182
+ <tr class="govuk-table__row">
141
183
  {#each columns as column}
142
184
  <th
143
185
  scope="col"
144
186
  class={`govuk-table__header ${column.dataType === "number" ? "govuk-table__header--numeric" : ""}`}
145
- title={metaData[column.key].explainer}
146
- aria-sort={sortState.column !== column.key
187
+ title={metadata[column.key]?.explainer}
188
+ style={metadata[column.key]?.width
189
+ ? `min-width: ${metadata[column.key].width}`
190
+ : undefined}
191
+ aria-sort={sortState.column !== column.key ||
192
+ sortState.order === null
147
193
  ? "none"
148
- : sortState.column === column.key &&
149
- sortState.order === "descending"
194
+ : sortState.order === "descending"
150
195
  ? "descending"
151
196
  : "ascending"}
152
197
  >
153
198
  <div class="header">
154
199
  <Button
155
- textContent={metaData[column.key].shortLabel}
200
+ textContent={metadata[column.key]?.shortLabel ?? column.key}
156
201
  buttonType={"table header"}
157
- onClickFunction={() => {
158
- const newDirection =
159
- sortState.column === column.key &&
160
- sortState.order === "ascending"
161
- ? "descending"
162
- : "ascending";
163
-
164
- updateSortState(column.key, newDirection);
165
- sortFunction();
166
- }}
202
+ direction={sortState.column === column.key
203
+ ? sortState.order
204
+ : null}
205
+ onClickFunction={() => handleSort(column.key)}
167
206
  ></Button>
168
- </div></th
169
- >
207
+ </div>
208
+ </th>
170
209
  {/each}
171
- </tr></thead
172
- >
210
+ </tr>
211
+ </thead>
212
+
173
213
  <tbody class="govuk-table__body">
174
- {#each localCopyOfData as row}
214
+ {#each displayRows as row}
175
215
  <tr class="govuk-table__row">
176
216
  {#each columns as column}
177
217
  {#if column.dataType === "number"}
178
218
  {#if colourScale === "On"}
179
219
  <td
180
220
  class="govuk-table__cell govuk-table__cell--numeric"
181
- style="background-color: {metaData[column.key].direction ===
182
- 'Higher is better'
183
- ? normToColor(row[column.key + '__normalised'])
184
- : normToColorReverse(row[column.key + '__normalised'])}"
185
- >{row[column.key]}</td
221
+ style={`background-color: ${
222
+ metadata[column.key]?.direction === "Higher is better"
223
+ ? normToColor(row[column.key + "__normalised"])
224
+ : normToColorReverse(row[column.key + "__normalised"])
225
+ }`}
186
226
  >
227
+ {@html row[column.key]}
228
+ </td>
187
229
  {:else}
188
- <td class="govuk-table__cell govuk-table__cell--numeric"
189
- >{row[column.key]}</td
190
- >
230
+ <td class="govuk-table__cell govuk-table__cell--numeric">
231
+ {@html row[column.key]}
232
+ </td>
191
233
  {/if}
192
234
  {:else}
193
- <td class="govuk-table__cell">{row[column.key]}</td>
235
+ <td class="govuk-table__cell">
236
+ {@html row[column.key]}
237
+ </td>
194
238
  {/if}
195
239
  {/each}
196
240
  </tr>
@@ -202,7 +246,7 @@
202
246
 
203
247
  <style>
204
248
  .table-container {
205
- max-height: 85vh;
249
+ max-height: 60vh;
206
250
  overflow-y: auto;
207
251
  overflow-x: scroll;
208
252
  width: 100%;
@@ -228,8 +272,14 @@
228
272
  gap: 20px;
229
273
  margin: 10px;
230
274
  }
275
+
231
276
  .color-keys {
232
277
  border-radius: 10%;
233
278
  padding: 6px;
234
279
  }
280
+
281
+ .header {
282
+ display: flex;
283
+ align-items: center;
284
+ }
235
285
  </style>
@@ -4,14 +4,14 @@ type Table = {
4
4
  $set?(props: Partial<$$ComponentProps>): void;
5
5
  };
6
6
  declare const Table: import("svelte").Component<{
7
- data?: any;
8
- metaData?: any;
7
+ data?: any[];
8
+ metadata?: Record<string, any>;
9
9
  caption?: any;
10
10
  colourScale?: any;
11
11
  }, {}, "">;
12
12
  type $$ComponentProps = {
13
- data?: any;
14
- metaData?: any;
13
+ data?: any[];
14
+ metadata?: Record<string, any>;
15
15
  caption?: any;
16
16
  colourScale?: any;
17
17
  };