@internetarchive/histogram-date-range 1.4.0-alpha-webdev7756.0 → 1.4.0-alpha-webdev7756.1

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.
@@ -20,6 +20,28 @@ import { styleMap } from 'lit/directives/style-map.js';
20
20
  dayjs.extend(customParseFormat);
21
21
  dayjs.extend(fixFirstCenturyYears);
22
22
 
23
+ type SliderId = 'slider-min' | 'slider-max';
24
+
25
+ export type BinSnappingInterval = 'none' | 'month' | 'year';
26
+
27
+ export type BarScalingPreset = 'linear' | 'logarithmic';
28
+ export type BarScalingFunction = (binValue: number) => number;
29
+ export type BarScalingOption = BarScalingPreset | BarScalingFunction;
30
+
31
+ interface HistogramItem {
32
+ value: number;
33
+ height: number;
34
+ binStart: string;
35
+ binEnd: string;
36
+ tooltip: string;
37
+ }
38
+
39
+ interface BarDataset extends DOMStringMap {
40
+ numItems: string;
41
+ binStart: string;
42
+ binEnd: string;
43
+ }
44
+
23
45
  // these values can be overridden via the component's HTML (camelCased) attributes
24
46
  const WIDTH = 180;
25
47
  const HEIGHT = 40;
@@ -31,12 +53,17 @@ const MISSING_DATA = 'no data';
31
53
  const UPDATE_DEBOUNCE_DELAY_MS = 0;
32
54
  const TOOLTIP_LABEL = 'item';
33
55
 
34
- // this function may be overridden only via Lit property expression or direct assignment
35
- const LOG_BAR_SCALING_FN = (binValue: number) => Math.log1p(binValue);
36
-
37
56
  // this constant is not set up to be overridden
38
57
  const SLIDER_CORNER_SIZE = 4;
39
58
 
59
+ /**
60
+ * Map from bar scaling preset options to the corresponding function they represent
61
+ */
62
+ const BAR_SCALING_PRESET_FNS: Record<BarScalingPreset, BarScalingFunction> = {
63
+ linear: (binValue: number) => binValue,
64
+ logarithmic: (binValue: number) => Math.log1p(binValue),
65
+ };
66
+
40
67
  // these CSS custom props can be overridden from the HTML that is invoking this component
41
68
  const sliderColor = css`var(--histogramDateRangeSliderColor, #4B65FE)`;
42
69
  const selectedRangeColor = css`var(--histogramDateRangeSelectedRangeColor, #DBE0FF)`;
@@ -53,24 +80,6 @@ const tooltipTextColor = css`var(--histogramDateRangeTooltipTextColor, #FFFFFF)`
53
80
  const tooltipFontSize = css`var(--histogramDateRangeTooltipFontSize, 1.1rem)`;
54
81
  const tooltipFontFamily = css`var(--histogramDateRangeTooltipFontFamily, sans-serif)`;
55
82
 
56
- type SliderId = 'slider-min' | 'slider-max';
57
-
58
- export type BinSnappingInterval = 'none' | 'month' | 'year';
59
-
60
- interface HistogramItem {
61
- value: number;
62
- height: number;
63
- binStart: string;
64
- binEnd: string;
65
- tooltip: string;
66
- }
67
-
68
- interface BarDataset extends DOMStringMap {
69
- numItems: string;
70
- binStart: string;
71
- binEnd: string;
72
- }
73
-
74
83
  @customElement('histogram-date-range')
75
84
  export class HistogramDateRange extends LitElement {
76
85
  /* eslint-disable lines-between-class-members */
@@ -111,20 +120,20 @@ export class HistogramDateRange extends LitElement {
111
120
  @property({ type: String }) tooltipLabel = TOOLTIP_LABEL;
112
121
 
113
122
  /**
114
- * A function mapping bin values to a new value that determines how tall each bar will
115
- * be in relation to the others.
123
+ * A function or preset value indicating how the height of each bar relates to its
124
+ * corresponding bin value. Current presets available are 'logarithmic' and 'linear',
125
+ * but a custom function may be provided instead if other behavior is desired.
116
126
  *
117
- * Default sizing function is the logarithm of the bin value, yielding more prominent
118
- * bars for smaller values, at the cost of bars not being directly proportional to their
119
- * values and having relatively smooth variation among values of a similar magnitude.
120
- * This ensures that when the difference between the min and max values is large, small
121
- * values are not as liable to completely disappear visually.
127
+ * The default scaling (`'logarithmic'`) uses the logarithm of each bin value, yielding
128
+ * more prominent bars for smaller values. This ensures that even when the difference
129
+ * between the min & max values is large, small values are unlikely to completely disappear
130
+ * visually. However, the cost is that bars have less noticeable variation among values of
131
+ * a similar magnitude, and their heights are not a direct representation of the bin values.
122
132
  *
123
- * For linearly-scaled bars, set this to the identity function.
133
+ * The `'linear'` preset option instead sizes the bars in linear proportion to their bin
134
+ * values.
124
135
  */
125
- @property({ attribute: false }) barScalingFunction: (
126
- binValue: number
127
- ) => number = LOG_BAR_SCALING_FN;
136
+ @property({ type: String }) barScaling: BarScalingOption = 'logarithmic';
128
137
 
129
138
  // internal reactive properties not exposed as attributes
130
139
  @state() private _tooltipOffsetX = 0;
@@ -166,7 +175,8 @@ export class HistogramDateRange extends LitElement {
166
175
  changedProps.has('maxSelectedDate') ||
167
176
  changedProps.has('width') ||
168
177
  changedProps.has('height') ||
169
- changedProps.has('binSnapping')
178
+ changedProps.has('binSnapping') ||
179
+ changedProps.has('barScaling')
170
180
  ) {
171
181
  this.handleDataUpdate();
172
182
  }
@@ -265,6 +275,17 @@ export class HistogramDateRange extends LitElement {
265
275
  }
266
276
  }
267
277
 
278
+ /**
279
+ * Function to scale bin values, whether from a preset or a provided custom function.
280
+ */
281
+ private get barScalingFunction(): BarScalingFunction {
282
+ if (typeof this.barScaling === 'string') {
283
+ return BAR_SCALING_PRESET_FNS[this.barScaling];
284
+ }
285
+
286
+ return this.barScaling;
287
+ }
288
+
268
289
  private calculateHistData(): HistogramItem[] {
269
290
  const { bins, height, dateRangeMS, _numBins, _minDateMS } = this;
270
291
  const minValue = Math.min(...this.bins);
@@ -295,8 +316,7 @@ export class HistogramDateRange extends LitElement {
295
316
 
296
317
  return {
297
318
  value: v,
298
- // use log scaling for the height of the bar to prevent tall bars from
299
- // making the smaller ones too small to see
319
+ // apply the configured scaling function to the bin value before determining bar height
300
320
  height: Math.floor(this.barScalingFunction(v) * valueScale),
301
321
  binStart,
302
322
  binEnd,
@@ -44,7 +44,7 @@ async function createCustomElementInHTMLContainer(): Promise<HistogramDateRange>
44
44
  }
45
45
 
46
46
  describe('HistogramDateRange', () => {
47
- it('shows scaled histogram bars when provided with data', async () => {
47
+ it('shows log-scaled histogram bars when provided with data', async () => {
48
48
  const el = await createCustomElementInHTMLContainer();
49
49
  const bars = el.shadowRoot?.querySelectorAll(
50
50
  '.bar'
@@ -54,6 +54,32 @@ describe('HistogramDateRange', () => {
54
54
  expect(heights).to.eql([38, 7, 50]);
55
55
  });
56
56
 
57
+ it('uses linear bar height scaling when specified', async () => {
58
+ const el = await createCustomElementInHTMLContainer();
59
+ el.barScaling = 'linear';
60
+ await el.updateComplete;
61
+
62
+ const bars = el.shadowRoot?.querySelectorAll(
63
+ '.bar'
64
+ ) as unknown as SVGRectElement[];
65
+ const heights = Array.from(bars, b => b.height.baseVal.value);
66
+
67
+ expect(heights).to.eql([16, 0, 50]);
68
+ });
69
+
70
+ it('uses custom bar height scaling when specified', async () => {
71
+ const el = await createCustomElementInHTMLContainer();
72
+ el.barScaling = (x: number) => Math.sqrt(x);
73
+ await el.updateComplete;
74
+
75
+ const bars = el.shadowRoot?.querySelectorAll(
76
+ '.bar'
77
+ ) as unknown as SVGRectElement[];
78
+ const heights = Array.from(bars, b => b.height.baseVal.value);
79
+
80
+ expect(heights).to.eql([28, 5, 50]);
81
+ });
82
+
57
83
  it('changes the position of the sliders and standardizes date format when dates are entered', async () => {
58
84
  const el = await createCustomElementInHTMLContainer();
59
85