@communitiesuk/svelte-component-library 0.1.19-beta.14 → 0.1.19-beta.16

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.
@@ -40,6 +40,8 @@
40
40
  paddingBottom = 100,
41
41
  paddingLeft = 0,
42
42
  paddingRight = 0,
43
+ tickStrokeWidth = 2,
44
+ axisStrokeWidth = 2,
43
45
 
44
46
  labelFormatter = undefined as LabelFormatter | undefined,
45
47
 
@@ -53,6 +55,7 @@
53
55
  range = undefined as [number, number] | undefined,
54
56
  fontSize = 19,
55
57
  polarity = "standard" as Polarity,
58
+ gridlines = false,
56
59
  }: {
57
60
  chartHeight?: number;
58
61
  chartWidth?: number;
@@ -74,6 +77,7 @@
74
77
  range?: [number, number];
75
78
  fontSize?: number;
76
79
  polarity?: Polarity;
80
+ gridlines?: Boolean;
77
81
  } = $props();
78
82
 
79
83
  // --- Helpers to compute default domain/range when not supplied ---
@@ -136,7 +140,7 @@
136
140
  x2={orientation.axis === "x" ? chartWidth : 0}
137
141
  y2={orientation.axis === "y" ? chartHeight : 0}
138
142
  stroke="darkgrey"
139
- stroke-width="2px"
143
+ stroke-width={axisStrokeWidth}
140
144
  ></line>
141
145
  {#if values}
142
146
  {#key numberOfTicks}
@@ -153,6 +157,8 @@
153
157
  {labelFormatter}
154
158
  {fontSize}
155
159
  {polarity}
160
+ {tickStrokeWidth}
161
+ {gridlines}
156
162
  />
157
163
  {/key}
158
164
  {/if}
@@ -26,6 +26,7 @@ type $$ComponentProps = {
26
26
  range?: [number, number];
27
27
  fontSize?: number;
28
28
  polarity?: Polarity;
29
+ gridlines?: Boolean;
29
30
  };
30
31
  declare const Axis: import("svelte").Component<$$ComponentProps, {}, "ticksArray" | "chartWidth">;
31
32
  type Axis = ReturnType<typeof Axis>;
@@ -19,6 +19,7 @@
19
19
  axisFunction,
20
20
  values,
21
21
  numberOfTicks,
22
+ tickStrokeWidth,
22
23
  floor,
23
24
  ceiling,
24
25
  orientation,
@@ -26,6 +27,7 @@
26
27
  fontSize = 19,
27
28
  clamp = false,
28
29
  polarity = "standard",
30
+ gridlines = false,
29
31
  }: {
30
32
  ticksArray?: number[];
31
33
  chartWidth: number;
@@ -33,6 +35,8 @@
33
35
  axisFunction: any;
34
36
  values: number[];
35
37
  numberOfTicks?: number;
38
+ tickStrokeWidth?: number;
39
+
36
40
  floor?: number | null;
37
41
  ceiling?: number | null;
38
42
  orientation: Orientation;
@@ -45,6 +49,7 @@
45
49
  fontSize?: number;
46
50
  clamp: boolean;
47
51
  polarity: Polarity;
52
+ gridlines: boolean;
48
53
  } = $props();
49
54
 
50
55
  // Axis value helper
@@ -92,10 +97,26 @@
92
97
  const chosen = normalizedSteps.find(
93
98
  (step) => step >= normalizedStep.toNumber(),
94
99
  );
95
- const optimalStep = new Decimal(chosen ?? 10).div(stepPower);
96
100
 
97
- const scaleMin = minVal.div(optimalStep).floor().mul(optimalStep);
98
- const scaleMax = maxVal.div(optimalStep).ceil().mul(optimalStep);
101
+ let chosenIdx = normalizedSteps.findIndex(
102
+ (step) => step >= normalizedStep.toNumber(),
103
+ );
104
+
105
+ let optimalStep: Decimal;
106
+ let scaleMin: Decimal;
107
+ let scaleMax: Decimal;
108
+
109
+ for (let i = chosenIdx; i >= 0; i--) {
110
+ optimalStep = new Decimal(normalizedSteps[i]).div(stepPower);
111
+ scaleMin = minVal.div(optimalStep).floor().mul(optimalStep);
112
+ scaleMax = maxVal.div(optimalStep).ceil().mul(optimalStep);
113
+
114
+ const axisRange = scaleMax.minus(scaleMin);
115
+ const dataRange = maxVal.minus(minVal);
116
+ const paddingRatio = axisRange.minus(dataRange).div(axisRange).toNumber();
117
+
118
+ if (paddingRatio <= 0.3) break;
119
+ }
99
120
 
100
121
  const ticks: number[] = [];
101
122
  for (let i = scaleMin; i.lte(scaleMax); i = i.plus(optimalStep)) {
@@ -155,13 +176,13 @@
155
176
  <path
156
177
  d={orientation.axis === "y"
157
178
  ? orientation.position === "left"
158
- ? "M0 0 l-8 0"
179
+ ? `M0 0 l${gridlines ? chartWidth : -8} 0`
159
180
  : "M0 0 l8 0"
160
181
  : orientation.position === "top"
161
182
  ? "M0 0 l0 -8"
162
183
  : "M0 0 l0 8"}
163
184
  stroke="darkgrey"
164
- stroke-width="2px"
185
+ stroke-width={tickStrokeWidth}
165
186
  ></path>
166
187
  <text
167
188
  transform="translate(
@@ -12,6 +12,7 @@ type $$ComponentProps = {
12
12
  axisFunction: any;
13
13
  values: number[];
14
14
  numberOfTicks?: number;
15
+ tickStrokeWidth?: number;
15
16
  floor?: number | null;
16
17
  ceiling?: number | null;
17
18
  orientation: Orientation;
@@ -19,6 +20,7 @@ type $$ComponentProps = {
19
20
  fontSize?: number;
20
21
  clamp: boolean;
21
22
  polarity: Polarity;
23
+ gridlines: boolean;
22
24
  };
23
25
  declare const Ticks: import("svelte").Component<$$ComponentProps, {}, "ticksArray">;
24
26
  type Ticks = ReturnType<typeof Ticks>;
@@ -6,7 +6,8 @@
6
6
  color = "grey",
7
7
  highlightColor = "black",
8
8
  highlightValue = undefined,
9
- showAxis = true,
9
+ showXAxis = true,
10
+ showYAxis = true,
10
11
  showArrows = true,
11
12
  midColor = "#DDDDDD",
12
13
  startColor = "#B70000",
@@ -46,6 +47,11 @@
46
47
  let yTickMin = $derived(yTicks.length ? Math.min(...yTicks) : 0);
47
48
  let yTickMax = $derived(yTicks.length ? Math.max(...yTicks) : 1);
48
49
 
50
+ import { median, quantile } from "d3-array";
51
+
52
+ let distSorted = $derived(dist.slice().sort((a, b) => a - b));
53
+ let rawMedian = $derived(median(distSorted));
54
+
49
55
  // min and max at extremes
50
56
  // let histogram = bin().domain([domainMin, domainMax]).thresholds(thresholds);
51
57
 
@@ -70,7 +76,11 @@
70
76
  // won't get xTick and xTickMax if showAxis is false
71
77
  // run it anyway inside an else block
72
78
  let bins = $derived(createEqualWidthBins(xTickMin, xTickMax, 10, dist));
73
- $inspect({ xTickMin, xTickMax, bins });
79
+
80
+ let proportionInExtremeBins = $derived([
81
+ bins[0]["count"] / distSorted.length,
82
+ bins.at(-1)["count"] / distSorted.length,
83
+ ]);
74
84
 
75
85
  let nBins = $derived(bins.length);
76
86
 
@@ -101,17 +111,59 @@
101
111
  bins.length ? xScale(bins[0].x1) - xScale(bins[0].x0) : 0,
102
112
  );
103
113
 
114
+ import chroma from "chroma-js";
115
+
116
+ let midPosition = $derived((rawMedian - xTickMin) / (xTickMax - xTickMin));
117
+
118
+ let c1000 = $derived(
119
+ chroma
120
+ .scale([startColor, midColor, endColor])
121
+ .domain([0, midPosition, 1])
122
+ .colors(1000),
123
+ );
124
+
104
125
  let colors1000 = $derived(
105
126
  polarity === "reverse"
106
- ? interpolateColors(startColor, endColor, 1000, midColor).reverse() // only done once
107
- : interpolateColors(startColor, endColor, 1000, midColor),
127
+ ? c1000.reverse() // only done once
128
+ : c1000,
129
+ );
130
+
131
+ let thisDomain = $derived(
132
+ polarity === "reverse"
133
+ ? [0, midPosition, 1].reverse() // only done once
134
+ : [0, midPosition, 1],
135
+ );
136
+
137
+ // let colors1000 = $derived(
138
+ // polarity === "reverse"
139
+ // ? interpolateColors(startColor, endColor, 1000, midColor).reverse() // only done once
140
+ // : interpolateColors(startColor, endColor, 1000, midColor),
141
+ // );
142
+
143
+ let averagesForSegments = $derived(
144
+ splitGroupsAndAverages(dist, nSegments).averages,
145
+ );
146
+
147
+ let preBin = $derived(
148
+ chroma
149
+ .scale([startColor, midColor, endColor])
150
+ .domain(thisDomain)
151
+ .padding([proportionInExtremeBins[0] / 2, proportionInExtremeBins[1] / 2])
152
+ .colors(10),
108
153
  );
109
154
 
110
155
  let binColors = $derived(
111
156
  polarity === "reverse"
112
- ? assignBinColors(bins, colors1000) // only done once
113
- : assignBinColors(bins, colors1000),
157
+ ? preBin.reverse() // only done once
158
+ : preBin,
114
159
  );
160
+
161
+ // let binColors = $derived(
162
+ // polarity === "reverse"
163
+ // ? assignBinColors(bins, colors1000).reverse() // only done once
164
+ // : assignBinColors(bins, colors1000),
165
+ // );
166
+
115
167
  let interpolatedColors = $derived(binColors);
116
168
 
117
169
  function findBinIndex(binned, value) {
@@ -124,17 +176,23 @@
124
176
  </script>
125
177
 
126
178
  {#key containerWidth}
127
- <div
128
- class="scale-container"
129
- bind:clientWidth={containerWidth}
130
- style="height={height * 2}"
131
- >
179
+ <div class="scale-container" bind:clientWidth={containerWidth}>
132
180
  <svg
133
- class="chart-container"
134
181
  width={containerWidth - padding}
135
- {height}
182
+ height={height + (annotationText ? 25 : 0) + (showXAxis ? 25 : 0)}
136
183
  transform="translate({padding / 2},0)"
137
184
  >
185
+ <g transform="translate({xScale(highlightValue)},0)">
186
+ <text
187
+ fill="#555555"
188
+ font-size="0.8em"
189
+ text-anchor="middle"
190
+ dominant-baseline="hanging"
191
+ >
192
+ <tspan x="0" dy="0">{annotationText}</tspan>
193
+ <tspan x="0" dy="12">▼</tspan>
194
+ </text>
195
+ </g>
138
196
  <defs>
139
197
  <marker
140
198
  id="arrow-down"
@@ -150,34 +208,42 @@
150
208
  <path d="M 0 0 L 6 3 L 0 6 z"></path>
151
209
  </marker>
152
210
  </defs>
153
- <g style="display: {showAxis ? 'block' : 'none'}">
154
- <Axis
155
- bind:ticksArray={xTicks}
156
- chartHeight={height}
157
- chartWidth={containerWidth - padding}
158
- orientation={{ axis: "x", position: "bottom" }}
159
- domain={[xTickMin, xTickMax]}
160
- range={useRange}
161
- values={dist}
162
- fontSize={14}
163
- {floor}
164
- {ceiling}
165
- {labelFormatter}
166
- ></Axis>
167
- <Axis
168
- bind:ticksArray={yTicks}
169
- chartHeight={height}
170
- chartWidth={containerWidth - padding}
171
- orientation={{ axis: "y", position: "left" }}
172
- domain={[0, yTickMax]}
173
- range={[height, 0]}
174
- values={dist}
175
- fontSize={0}
176
- {floor}
177
- {ceiling}
178
- ></Axis>
179
- </g>
180
- <g transform="translate(0,0)">
211
+
212
+ <g transform="translate(0,{annotationText ? 25 : 0})">
213
+ <g>
214
+ {#if showXAxis}
215
+ <Axis
216
+ bind:ticksArray={xTicks}
217
+ chartHeight={height}
218
+ chartWidth={containerWidth - padding}
219
+ orientation={{ axis: "x", position: "bottom" }}
220
+ domain={[xTickMin, xTickMax]}
221
+ range={useRange}
222
+ values={dist}
223
+ fontSize={14}
224
+ {floor}
225
+ {ceiling}
226
+ {labelFormatter}
227
+ numberOfTicks={2}
228
+ ></Axis>
229
+ {/if}
230
+ {#if showYAxis}
231
+ <Axis
232
+ bind:ticksArray={yTicks}
233
+ chartHeight={height}
234
+ chartWidth={containerWidth - padding}
235
+ orientation={{ axis: "y", position: "left" }}
236
+ domain={[0, yTickMax]}
237
+ range={[height, 0]}
238
+ values={[...dist, 0]}
239
+ fontSize={0}
240
+ numberOfTicks={5}
241
+ tickStrokeWidth={0.25}
242
+ axisStrokeWidth={0}
243
+ gridlines={true}
244
+ ></Axis>
245
+ {/if}
246
+ </g>
181
247
  {#each bins as bin, i}
182
248
  {#key bin.x0}
183
249
  <rect
@@ -213,19 +279,8 @@
213
279
  height={yScale(bins[highlightIndex].count)}
214
280
  fill={interpolatedColors[highlightIndex]}
215
281
  stroke={highlightColor}
216
- stroke-width={1}
282
+ stroke-width={0}
217
283
  ></rect>
218
-
219
- <g
220
- transform="translate({xScale(highlightValue)},{height -
221
- yScale(bins[highlightIndex].count) -
222
- 15})"
223
- >
224
- <text fill="#555555" font-size="0.8em" text-anchor="middle">
225
- <tspan x="0" dy="0">{annotationText}</tspan>
226
- <tspan x="0" dy="12">▼</tspan>
227
- </text>
228
- </g>
229
284
  {/key}
230
285
  {/if}
231
286
  </g>
@@ -244,6 +299,34 @@
244
299
  </div>
245
300
  {/key}
246
301
 
302
+ <g style="display: 'none'">
303
+ <Axis
304
+ bind:ticksArray={xTicks}
305
+ chartHeight={height}
306
+ chartWidth={containerWidth - padding}
307
+ orientation={{ axis: "x", position: "bottom" }}
308
+ domain={[xTickMin, xTickMax]}
309
+ range={useRange}
310
+ values={dist}
311
+ fontSize={14}
312
+ {floor}
313
+ {ceiling}
314
+ {labelFormatter}
315
+ ></Axis>
316
+ <Axis
317
+ bind:ticksArray={yTicks}
318
+ chartHeight={height}
319
+ chartWidth={containerWidth - padding}
320
+ orientation={{ axis: "y", position: "left" }}
321
+ domain={[0, yTickMax]}
322
+ range={[height, 0]}
323
+ values={dist}
324
+ fontSize={0}
325
+ {floor}
326
+ {ceiling}
327
+ ></Axis>
328
+ </g>
329
+
247
330
  <style>
248
331
  .scale-container svg {
249
332
  overflow: visible;
@@ -10,7 +10,8 @@ declare const Histogram: import("svelte").Component<{
10
10
  color?: string;
11
11
  highlightColor?: string;
12
12
  highlightValue?: any;
13
- showAxis?: boolean;
13
+ showXAxis?: boolean;
14
+ showYAxis?: boolean;
14
15
  showArrows?: boolean;
15
16
  midColor?: string;
16
17
  startColor?: string;
@@ -31,7 +32,8 @@ type $$ComponentProps = {
31
32
  color?: string;
32
33
  highlightColor?: string;
33
34
  highlightValue?: any;
34
- showAxis?: boolean;
35
+ showXAxis?: boolean;
36
+ showYAxis?: boolean;
35
37
  showArrows?: boolean;
36
38
  midColor?: string;
37
39
  startColor?: string;
@@ -205,28 +205,62 @@
205
205
  createEqualWidthBins(xTickMin, xTickMax, nSegments, dist),
206
206
  );
207
207
 
208
+ let proportionInExtremeBins = $derived([
209
+ bins[0]["count"] / distSorted.length,
210
+ bins.at(-1)["count"] / distSorted.length,
211
+ ]);
212
+
208
213
  const range = $derived(
209
214
  Array.from({ length: nSegments }, (_, i) => nSegments - 1 - i),
210
215
  );
211
216
 
217
+ import chroma from "chroma-js";
218
+
219
+ let midPosition = $derived((rawMedian - newMin) / (newMax - newMin));
220
+
221
+ let c1000 = $derived(
222
+ chroma
223
+ .scale([startColor, midColor, endColor])
224
+ .domain([0, midPosition, 1])
225
+ .colors(1000),
226
+ );
227
+
212
228
  let colors1000 = $derived(
213
229
  polarity === "reverse"
214
- ? interpolateColors(startColor, endColor, 1000, midColor).reverse() // only done once
215
- : interpolateColors(startColor, endColor, 1000, midColor),
230
+ ? c1000.reverse() // only done once
231
+ : c1000,
232
+ );
233
+
234
+ let thisDomain = $derived(
235
+ polarity === "reverse"
236
+ ? [0, midPosition, 1].reverse() // only done once
237
+ : [0, midPosition, 1],
216
238
  );
217
239
 
240
+ // let colors1000 = $derived(
241
+ // polarity === "reverse"
242
+ // ? interpolateColors(startColor, endColor, 1000, midColor).reverse() // only done once
243
+ // : interpolateColors(startColor, endColor, 1000, midColor),
244
+ // );
245
+
218
246
  let averagesForSegments = $derived(
219
247
  splitGroupsAndAverages(dist, nSegments).averages,
220
248
  );
221
249
 
222
- // let binColors = $derived(assignBinColors(bins, colors1000));
223
-
224
250
  let binColors = $derived(
225
- polarity === "reverse"
226
- ? assignBinColors(bins, colors1000).reverse() // only done once
227
- : assignBinColors(bins, colors1000),
251
+ chroma
252
+ .scale([startColor, midColor, endColor])
253
+ .domain(thisDomain)
254
+ .padding([proportionInExtremeBins[0] / 2, proportionInExtremeBins[1] / 2])
255
+ .colors(10),
228
256
  );
229
257
 
258
+ // let binColors = $derived(
259
+ // polarity === "reverse"
260
+ // ? assignBinColors(bins, colors1000).reverse() // only done once
261
+ // : assignBinColors(bins, colors1000),
262
+ // );
263
+
230
264
  let interpolatedColors = $derived(
231
265
  skew
232
266
  ? binColors
@@ -675,7 +709,7 @@
675
709
  )
676
710
  : rowValue.color}
677
711
  stroke="white"
678
- stroke-width="2"
712
+ stroke-width="5"
679
713
  opacity={rowValue.opacity}
680
714
  pointer-events={rowValue.pointerEvents}
681
715
  ></circle>
@@ -691,7 +725,8 @@
691
725
  activeColors,
692
726
  )
693
727
  : rowValue.color}
694
- stroke="black"
728
+ stroke="#333333"
729
+ stroke-width="2"
695
730
  opacity={rowValue.opacity}
696
731
  pointer-events={rowValue.pointerEvents}
697
732
  ></circle>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@communitiesuk/svelte-component-library",
3
- "version": "0.1.19-beta.14",
3
+ "version": "0.1.19-beta.16",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/communitiesuk/mhclg_svelte_component_library.git"
@@ -101,6 +101,7 @@
101
101
  "@types/dompurify": "^3.0.5",
102
102
  "accessible-autocomplete": "^3.0.1",
103
103
  "choices.js": "^11.1.0",
104
+ "chroma-js": "^3.2.0",
104
105
  "csv-parser": "^3.0.0",
105
106
  "d3": "^7.9.0",
106
107
  "decimal.js": "^10.5.0",