@internetarchive/histogram-date-range 0.1.0-beta → 0.1.3-alpha

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.
package/demo/index.css ADDED
@@ -0,0 +1,23 @@
1
+ html {
2
+ font-size: 10px;
3
+ font-family: sans-serif;
4
+ }
5
+ body {
6
+ background: white;
7
+ }
8
+ .container {
9
+ margin-top: 20px;
10
+ display: grid;
11
+ justify-content: center;
12
+ }
13
+ .description {
14
+ margin: 10px auto;
15
+ }
16
+ .received-events {
17
+ position: absolute;
18
+ top: 0;
19
+ }
20
+ button {
21
+ font-size: 100%;
22
+ margin: 10px auto;
23
+ }
package/demo/index.html CHANGED
@@ -3,49 +3,29 @@
3
3
  <head>
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1" />
5
5
  <meta charset="utf-8" />
6
- <style>
7
- html {
8
- font-size: 10px;
9
- font-family: sans-serif;
10
- }
11
- body {
12
- background: white;
13
- }
14
- .container {
15
- margin-top: 50px;
16
- display: grid;
17
- justify-content: center;
18
- }
19
- .description {
20
- margin: 10px auto;
21
- }
22
- .received-events {
23
- position: absolute;
24
- top: 0;
25
- }
26
- button {
27
- font-size: 100%;
28
- margin: 10px auto;
29
- }
30
- </style>
6
+ <link rel="stylesheet" href="index.css">
31
7
  </head>
32
8
 
33
9
  <script type="module">
34
10
  import '../dist/src/histogram-date-range.js';
11
+ let eventCount = 0;
35
12
  // listen to events from the component and display the data received from them
36
13
  document.addEventListener('histogramDateRangeUpdated', e => {
37
- document.querySelector('.received-events').innerHTML +=
38
- '\n' + JSON.stringify(e.detail);
14
+ document.querySelector('.received-events').innerHTML =
15
+ ++eventCount + ': ' + JSON.stringify(e.detail);
39
16
  });
40
17
  </script>
41
18
  <body>
42
19
  <pre class="received-events"></pre>
43
20
 
44
21
  <div class="container">
45
- <div class="description">pre-selected range with default date format</div>
22
+ <div class="description">
23
+ pre-selected range with 1000ms debounce delay
24
+ </div>
46
25
  <histogram-date-range
47
26
  minDate="1400"
48
27
  maxDate="2021"
28
+ updateDelay="1000"
49
29
  minSelectedDate="1800"
50
30
  maxSelectedDate="1900"
51
31
  bins="[ 74, 67, 17, 66, 49, 93, 47, 61, 32, 46, 53, 2,
@@ -57,19 +37,45 @@
57
37
  </div>
58
38
 
59
39
  <div class="container">
60
- <div class="description">default range with custom date format</div>
40
+ <div class="description">range spanning negative to positive years</div>
41
+ <histogram-date-range
42
+ mindate="-1050" maxdate="2200"
43
+ bins="[ 74, 67, 17, 66, 49, 93, 47, 61, 32, 46, 53, 2,
44
+ 13, 45, 28, 1, 8, 70, 37, 74, 67, 17, 66, 49, 93,
45
+ 47, 61, 70, 37, 74, 67, 17, 66, 49, 93, 47, 61, 32,
46
+ 32, 70, 37, 74, 67, 17, 66, 49, 93, 47, 61, 32
47
+ ]"
48
+ ></histogram-date-range>
49
+ </div>
50
+
51
+
52
+
53
+ <div class="container">
54
+ <div class="description">small year range and few bins</div>
55
+ <histogram-date-range width="175" tooltipwidth="120"
56
+ mindate="2008" maxdate="2016" bins="[76104,866978,1151617,986331,218672,107410,3324]">
57
+ </histogram-date-range>
58
+ </div>
59
+
60
+ <div class="container">
61
+ <div class="description">
62
+ default range with custom styling and date format
63
+ </div>
61
64
  <histogram-date-range
62
65
  width="300"
63
66
  height="50"
64
67
  tooltipWidth="140"
65
- updateDelay="2000"
66
68
  dateFormat="DD MMM YYYY"
67
69
  style="
70
+ --histogramDateRangeSliderColor: #d8b384;
71
+ --histogramDateRangeSelectedRangeColor: #f3f0d7;
72
+ --histogramDateRangeTooltipFontFamily: serif;
73
+ --histogramDateRangeInputFontFamily: serif;
68
74
  --histogramDateRangeTooltipFontSize: 1rem;
69
75
  --histogramDateRangeInputWidth: 85px;
70
76
  "
71
- minDate="May 1, 1972"
72
- maxDate="12/21/1980"
77
+ minDate="05 May 1972"
78
+ maxDate="21 Dec 1980"
73
79
  bins="[ 85, 25, 200, 0, 0, 34, 0, 2, 5, 10, 0, 56, 10, 45, 100, 70, 50 ]"
74
80
  ></histogram-date-range>
75
81
  </div>
@@ -99,11 +105,8 @@
99
105
  </script>
100
106
 
101
107
  <div class="container">
102
- <div class="description">data set up with js; no debounce delay</div>
103
- <histogram-date-range
104
- id="js-setup"
105
- updateDelay="0"
106
- ></histogram-date-range>
108
+ <div class="description">data set up with js</div>
109
+ <histogram-date-range id="js-setup"></histogram-date-range>
107
110
  </div>
108
111
  <script>
109
112
  document.addEventListener('DOMContentLoaded', function () {
@@ -112,17 +115,23 @@
112
115
  );
113
116
  histogram.minDate = '1950';
114
117
  histogram.maxDate = '2000';
115
- // generate an array like [0, 1, 2, ... 49]
118
+ // generate array of [0, 1, 2, ... 49]
116
119
  histogram.bins = [...Array(50).keys()];
117
120
  });
118
121
  </script>
119
122
 
123
+ <div class="container">
124
+ <div class="description">
125
+ single bin
126
+ </div>
127
+ <histogram-date-range mindate="1926" maxdate="1926" bins="[8]">
128
+ </histogram-date-range>
129
+ </div>
130
+
120
131
  <div class="container">
121
132
  <div class="description">empty data</div>
122
- <histogram-date-range
123
- bins=""
124
- missingDataMessage="no data..."
125
- ></histogram-date-range>
133
+ <histogram-date-range missingDataMessage="no data..."></histoghistogram-date-range>
126
134
  </div>
135
+
127
136
  </body>
128
137
  </html>
@@ -42,14 +42,18 @@ export declare class HistogramDateRange extends LitElement {
42
42
  private calculateHistData;
43
43
  private get hasBinData();
44
44
  private get _numBins();
45
+ private get histogramLeftEdgeX();
46
+ private get histogramRightEdgeX();
45
47
  /** component's loading (and disabled) state */
46
48
  get loading(): boolean;
47
49
  set loading(value: boolean);
48
50
  /** formatted minimum date of selected date range */
49
51
  get minSelectedDate(): string;
52
+ /** updates minSelectedDate if new date is valid */
50
53
  set minSelectedDate(rawDate: string);
51
54
  /** formatted maximum date of selected date range */
52
55
  get maxSelectedDate(): string;
56
+ /** updates maxSelectedDate if new date is valid */
53
57
  set maxSelectedDate(rawDate: string);
54
58
  /** horizontal position of min date slider */
55
59
  get minSliderX(): number;
@@ -70,18 +74,18 @@ export declare class HistogramDateRange extends LitElement {
70
74
  * Constrain a proposed value for the minimum (left) slider
71
75
  *
72
76
  * If the value is less than the leftmost valid position, then set it to the
73
- * left edge of the widget (ie the slider width). If the value is greater than
74
- * the rightmost valid position (the position of the max slider), then set it
75
- * to the position of the max slider
77
+ * left edge of the histogram (ie the slider width). If the value is greater
78
+ * than the rightmost valid position (the position of the max slider), then
79
+ * set it to the position of the max slider
76
80
  */
77
81
  private validMinSliderX;
78
82
  /**
79
83
  * Constrain a proposed value for the maximum (right) slider
80
84
  *
81
85
  * If the value is greater than the rightmost valid position, then set it to
82
- * the right edge of the widget (ie widget width - slider width). If the value
83
- * is less than the leftmost valid position (the position of the min slider),
84
- * then set it to the position of the min slider
86
+ * the right edge of the histogram (ie histogram width - slider width). If the
87
+ * value is less than the leftmost valid position (the position of the min
88
+ * slider), then set it to the position of the min slider
85
89
  */
86
90
  private validMaxSliderX;
87
91
  private addListeners;
@@ -103,20 +107,25 @@ export declare class HistogramDateRange extends LitElement {
103
107
  */
104
108
  private translatePositionToDate;
105
109
  /**
106
- * Returns slider x-position corresponding to given date (or null if invalid
107
- * date)
110
+ * Returns slider x-position corresponding to given date
108
111
  *
109
112
  * @param date
110
113
  * @returns x-position of slider
111
114
  */
112
115
  private translateDateToPosition;
116
+ /** ensure that the returned value is between minValue and maxValue */
117
+ private clamp;
113
118
  private handleMinDateInput;
114
119
  private handleMaxDateInput;
120
+ private handleKeyUp;
115
121
  private get currentDateRangeString();
116
- /** minimum selected date in milliseconds */
117
- private get minSelectedDateMS();
118
- /** maximum selected date in milliseconds */
119
- private get maxSelectedDateMS();
122
+ private getMSFromString;
123
+ /**
124
+ * expand or narrow the selected range by moving the slider nearest the
125
+ * clicked bar to the outer edge of the clicked bar
126
+ *
127
+ * @param e Event click event from a histogram bar
128
+ */
120
129
  private handleBarClick;
121
130
  private get minSliderTemplate();
122
131
  private get maxSliderTemplate();
@@ -135,7 +144,7 @@ export declare class HistogramDateRange extends LitElement {
135
144
  get tooltipTemplate(): TemplateResult;
136
145
  private get noDataTemplate();
137
146
  private get activityIndicatorTemplate();
138
- static styles: import("lit").CSSResultGroup;
147
+ static styles: import("lit").CSSResult;
139
148
  render(): TemplateResult;
140
149
  }
141
150
  declare global {
@@ -1,8 +1,10 @@
1
1
  import { __decorate } from "tslib";
2
- import { css, html, LitElement, svg, } from 'lit';
2
+ import { css, html, nothing, LitElement, svg, } from 'lit';
3
3
  import { property, state, customElement } from 'lit/decorators.js';
4
4
  import { live } from 'lit/directives/live.js';
5
5
  import dayjs from 'dayjs/esm/index.js';
6
+ import customParseFormat from 'dayjs/esm/plugin/customParseFormat';
7
+ dayjs.extend(customParseFormat);
6
8
  import '@internetarchive/ia-activity-indicator/ia-activity-indicator';
7
9
  // these values can be overridden via the component's HTML (camelCased) attributes
8
10
  const WIDTH = 180;
@@ -12,21 +14,23 @@ const TOOLTIP_WIDTH = 125;
12
14
  const TOOLTIP_HEIGHT = 30;
13
15
  const DATE_FORMAT = 'YYYY';
14
16
  const MISSING_DATA = 'no data';
15
- const UPDATE_DEBOUNCE_DELAY_MS = 1000;
17
+ const UPDATE_DEBOUNCE_DELAY_MS = 0;
16
18
  // this constant is not set up to be overridden
17
19
  const SLIDER_CORNER_SIZE = 4;
18
20
  // these CSS custom props can be overridden from the HTML that is invoking this component
19
- const sliderFill = css `var(--histogramDateRangeSliderFill, #4B65FE)`;
20
- const selectedRangeFill = css `var(--histogramDateRangeSelectedRangeFill, #DBE0FF)`;
21
+ const sliderColor = css `var(--histogramDateRangeSliderColor, #4B65FE)`;
22
+ const selectedRangeColor = css `var(--histogramDateRangeSelectedRangeColor, #DBE0FF)`;
21
23
  const barIncludedFill = css `var(--histogramDateRangeBarIncludedFill, #2C2C2C)`;
22
24
  const activityIndicatorColor = css `var(--histogramDateRangeActivityIndicator, #2C2C2C)`;
23
25
  const barExcludedFill = css `var(--histogramDateRangeBarExcludedFill, #CCCCCC)`;
24
26
  const inputBorder = css `var(--histogramDateRangeInputBorder, 0.5px solid #2C2C2C)`;
25
27
  const inputWidth = css `var(--histogramDateRangeInputWidth, 35px)`;
26
28
  const inputFontSize = css `var(--histogramDateRangeInputFontSize, 1.2rem)`;
29
+ const inputFontFamily = css `var(--histogramDateRangeInputFontFamily, sans-serif)`;
27
30
  const tooltipBackgroundColor = css `var(--histogramDateRangeTooltipBackgroundColor, #2C2C2C)`;
28
31
  const tooltipTextColor = css `var(--histogramDateRangeTooltipTextColor, #FFFFFF)`;
29
32
  const tooltipFontSize = css `var(--histogramDateRangeTooltipFontSize, 1.1rem)`;
33
+ const tooltipFontFamily = css `var(--histogramDateRangeTooltipFontFamily, sans-serif)`;
30
34
  let HistogramDateRange = class HistogramDateRange extends LitElement {
31
35
  constructor() {
32
36
  /* eslint-disable lines-between-class-members */
@@ -89,12 +93,11 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
89
93
  this.move = (e) => {
90
94
  const newX = e.offsetX - this._dragOffset;
91
95
  const slider = this._currentSlider;
92
- const date = this.translatePositionToDate(newX);
93
96
  if (slider.id === 'slider-min') {
94
- this.minSelectedDate = date;
97
+ this.minSelectedDate = this.translatePositionToDate(this.validMinSliderX(newX));
95
98
  }
96
99
  else {
97
- this.maxSelectedDate = date;
100
+ this.maxSelectedDate = this.translatePositionToDate(this.validMaxSliderX(newX));
98
101
  }
99
102
  };
100
103
  }
@@ -125,8 +128,8 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
125
128
  return;
126
129
  }
127
130
  this._histWidth = this.width - this.sliderWidth * 2;
128
- this._minDateMS = dayjs(this.minDate).valueOf();
129
- this._maxDateMS = dayjs(this.maxDate).valueOf();
131
+ this._minDateMS = this.getMSFromString(this.minDate);
132
+ this._maxDateMS = this.getMSFromString(this.maxDate);
130
133
  this._binWidth = this._histWidth / this._numBins;
131
134
  this._previousDateRange = this.currentDateRangeString;
132
135
  this._histData = this.calculateHistData();
@@ -141,11 +144,16 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
141
144
  calculateHistData() {
142
145
  const minValue = Math.min(...this.bins);
143
146
  const maxValue = Math.max(...this.bins);
144
- const valueScale = this.height / Math.log1p(maxValue - minValue);
147
+ // if there is no difference between the min and max values, use a range of
148
+ // 1 because log scaling will fail if the range is 0
149
+ const valueRange = minValue === maxValue ? 1 : Math.log1p(maxValue - minValue);
150
+ const valueScale = this.height / valueRange;
145
151
  const dateScale = this.dateRangeMS / this._numBins;
146
152
  return this.bins.map((v, i) => {
147
153
  return {
148
154
  value: v,
155
+ // use log scaling for the height of the bar to prevent tall bars from
156
+ // making the smaller ones too small to see
149
157
  height: Math.floor(Math.log1p(v) * valueScale),
150
158
  binStart: `${this.formatDate(i * dateScale + this._minDateMS)}`,
151
159
  binEnd: `${this.formatDate((i + 1) * dateScale + this._minDateMS)}`,
@@ -161,6 +169,12 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
161
169
  }
162
170
  return this.bins.length;
163
171
  }
172
+ get histogramLeftEdgeX() {
173
+ return this.sliderWidth;
174
+ }
175
+ get histogramRightEdgeX() {
176
+ return this.width - this.sliderWidth;
177
+ }
164
178
  /** component's loading (and disabled) state */
165
179
  get loading() {
166
180
  return this._isLoading;
@@ -171,51 +185,55 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
171
185
  }
172
186
  /** formatted minimum date of selected date range */
173
187
  get minSelectedDate() {
174
- return this.formatDate(this._minSelectedDate);
188
+ return this.formatDate(this.getMSFromString(this._minSelectedDate));
175
189
  }
190
+ /** updates minSelectedDate if new date is valid */
176
191
  set minSelectedDate(rawDate) {
177
192
  if (!this._minSelectedDate) {
178
193
  // because the values needed to calculate valid max/min values are not
179
194
  // available during the lit init when it's populating properties from
180
195
  // attributes, fall back to just the raw date if nothing is already set
181
196
  this._minSelectedDate = rawDate;
197
+ return;
182
198
  }
183
- const x = this.translateDateToPosition(rawDate);
184
- if (x) {
185
- const validX = this.validMinSliderX(x);
186
- this._minSelectedDate = this.translatePositionToDate(validX);
199
+ const proposedDateMS = this.getMSFromString(rawDate);
200
+ const isValidDate = !Number.isNaN(proposedDateMS);
201
+ const isNotTooRecent = proposedDateMS <= this.getMSFromString(this.maxSelectedDate);
202
+ if (isValidDate && isNotTooRecent) {
203
+ this._minSelectedDate = this.formatDate(proposedDateMS);
187
204
  }
188
205
  this.requestUpdate();
189
206
  }
190
207
  /** formatted maximum date of selected date range */
191
208
  get maxSelectedDate() {
192
- return this.formatDate(this._maxSelectedDate);
209
+ return this.formatDate(this.getMSFromString(this._maxSelectedDate));
193
210
  }
211
+ /** updates maxSelectedDate if new date is valid */
194
212
  set maxSelectedDate(rawDate) {
195
213
  if (!this._maxSelectedDate) {
196
- // see comment above in the minSelectedDate setter
214
+ // because the values needed to calculate valid max/min values are not
215
+ // available during the lit init when it's populating properties from
216
+ // attributes, fall back to just the raw date if nothing is already set
197
217
  this._maxSelectedDate = rawDate;
218
+ return;
198
219
  }
199
- const x = this.translateDateToPosition(rawDate);
200
- if (x) {
201
- const validX = this.validMaxSliderX(x);
202
- this._maxSelectedDate = this.translatePositionToDate(validX);
220
+ const proposedDateMS = this.getMSFromString(rawDate);
221
+ const isValidDate = !Number.isNaN(proposedDateMS);
222
+ const isNotTooOld = proposedDateMS >= this.getMSFromString(this.minSelectedDate);
223
+ if (isValidDate && isNotTooOld) {
224
+ this._maxSelectedDate = this.formatDate(proposedDateMS);
203
225
  }
204
226
  this.requestUpdate();
205
227
  }
206
228
  /** horizontal position of min date slider */
207
229
  get minSliderX() {
208
- var _a;
209
- return (
210
- // default to leftmost position if missing or invalid min position
211
- (_a = this.translateDateToPosition(this.minSelectedDate)) !== null && _a !== void 0 ? _a : this.sliderWidth);
230
+ const x = this.translateDateToPosition(this.minSelectedDate);
231
+ return this.validMinSliderX(x);
212
232
  }
213
233
  /** horizontal position of max date slider */
214
234
  get maxSliderX() {
215
- var _a;
216
- return (
217
- // default to rightmost position if missing or invalid max position
218
- (_a = this.translateDateToPosition(this.maxSelectedDate)) !== null && _a !== void 0 ? _a : this.width - this.sliderWidth);
235
+ const x = this.translateDateToPosition(this.maxSelectedDate);
236
+ return this.validMaxSliderX(x);
219
237
  }
220
238
  get dateRangeMS() {
221
239
  return this._maxDateMS - this._minDateMS;
@@ -228,10 +246,11 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
228
246
  const x = target.x.baseVal.value + this.sliderWidth / 2;
229
247
  const dataset = target.dataset;
230
248
  const itemsText = `item${dataset.numItems !== '1' ? 's' : ''}`;
249
+ const formattedNumItems = Number(dataset.numItems).toLocaleString();
231
250
  this._tooltipOffset =
232
251
  x + (this._binWidth - this.sliderWidth - this.tooltipWidth) / 2;
233
252
  this._tooltipContent = html `
234
- ${dataset.numItems} ${itemsText}<br />
253
+ ${formattedNumItems} ${itemsText}<br />
235
254
  ${dataset.binStart} - ${dataset.binEnd}
236
255
  `;
237
256
  this._tooltipVisible = true;
@@ -244,25 +263,33 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
244
263
  * Constrain a proposed value for the minimum (left) slider
245
264
  *
246
265
  * If the value is less than the leftmost valid position, then set it to the
247
- * left edge of the widget (ie the slider width). If the value is greater than
248
- * the rightmost valid position (the position of the max slider), then set it
249
- * to the position of the max slider
266
+ * left edge of the histogram (ie the slider width). If the value is greater
267
+ * than the rightmost valid position (the position of the max slider), then
268
+ * set it to the position of the max slider
250
269
  */
251
270
  validMinSliderX(newX) {
252
- const validX = Math.max(newX, this.sliderWidth);
253
- return Math.min(validX, this.maxSliderX);
271
+ // allow the left slider to go right only to the right slider, even if the
272
+ // max selected date is out of range
273
+ const rightLimit = Math.min(this.translateDateToPosition(this.maxSelectedDate), this.histogramRightEdgeX);
274
+ newX = this.clamp(newX, this.histogramLeftEdgeX, rightLimit);
275
+ const isInvalid = Number.isNaN(newX) || rightLimit < this.histogramLeftEdgeX;
276
+ return isInvalid ? this.histogramLeftEdgeX : newX;
254
277
  }
255
278
  /**
256
279
  * Constrain a proposed value for the maximum (right) slider
257
280
  *
258
281
  * If the value is greater than the rightmost valid position, then set it to
259
- * the right edge of the widget (ie widget width - slider width). If the value
260
- * is less than the leftmost valid position (the position of the min slider),
261
- * then set it to the position of the min slider
282
+ * the right edge of the histogram (ie histogram width - slider width). If the
283
+ * value is less than the leftmost valid position (the position of the min
284
+ * slider), then set it to the position of the min slider
262
285
  */
263
286
  validMaxSliderX(newX) {
264
- const validX = Math.max(newX, this.minSliderX);
265
- return Math.min(validX, this.width - this.sliderWidth);
287
+ // allow the right slider to go left only to the left slider, even if the
288
+ // min selected date is out of range
289
+ const leftLimit = Math.max(this.histogramLeftEdgeX, this.translateDateToPosition(this.minSelectedDate));
290
+ newX = this.clamp(newX, leftLimit, this.histogramRightEdgeX);
291
+ const isInvalid = Number.isNaN(newX) || leftLimit > this.histogramRightEdgeX;
292
+ return isInvalid ? this.histogramRightEdgeX : newX;
266
293
  }
267
294
  addListeners() {
268
295
  window.addEventListener('pointermove', this.move);
@@ -332,17 +359,19 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
332
359
  return this.formatDate(this._minDateMS + milliseconds);
333
360
  }
334
361
  /**
335
- * Returns slider x-position corresponding to given date (or null if invalid
336
- * date)
362
+ * Returns slider x-position corresponding to given date
337
363
  *
338
364
  * @param date
339
365
  * @returns x-position of slider
340
366
  */
341
367
  translateDateToPosition(date) {
342
- const milliseconds = dayjs(date).valueOf();
343
- const xPosition = this.sliderWidth +
344
- ((milliseconds - this._minDateMS) * this._histWidth) / this.dateRangeMS;
345
- return isNaN(milliseconds) || isNaN(xPosition) ? null : xPosition;
368
+ const milliseconds = this.getMSFromString(date);
369
+ return (this.sliderWidth +
370
+ ((milliseconds - this._minDateMS) * this._histWidth) / this.dateRangeMS);
371
+ }
372
+ /** ensure that the returned value is between minValue and maxValue */
373
+ clamp(x, minValue, maxValue) {
374
+ return Math.min(Math.max(x, minValue), maxValue);
346
375
  }
347
376
  handleMinDateInput(e) {
348
377
  const target = e.currentTarget;
@@ -354,25 +383,51 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
354
383
  this.maxSelectedDate = target.value;
355
384
  this.beginEmitUpdateProcess();
356
385
  }
386
+ handleKeyUp(e) {
387
+ if (e.key === 'Enter') {
388
+ const target = e.currentTarget;
389
+ target.blur();
390
+ if (target.id === 'date-min') {
391
+ this.handleMinDateInput(e);
392
+ }
393
+ else if (target.id === 'date-max') {
394
+ this.handleMaxDateInput(e);
395
+ }
396
+ }
397
+ }
357
398
  get currentDateRangeString() {
358
399
  return `${this.minSelectedDate}:${this.maxSelectedDate}`;
359
400
  }
360
- /** minimum selected date in milliseconds */
361
- get minSelectedDateMS() {
362
- return dayjs(this.minSelectedDate).valueOf();
363
- }
364
- /** maximum selected date in milliseconds */
365
- get maxSelectedDateMS() {
366
- return dayjs(this.maxSelectedDate).valueOf();
401
+ getMSFromString(date) {
402
+ const digitGroupCount = (date.split(/(\d+)/).length - 1) / 2;
403
+ if (digitGroupCount === 1) {
404
+ // if there's just a single set of digits, assume it's a year
405
+ const dateObj = new Date(0, 0); // start at January 1, 1900
406
+ dateObj.setFullYear(Number(date)); // override year
407
+ return dateObj.getTime(); // get time in milliseconds
408
+ }
409
+ return dayjs(date, [this.dateFormat, DATE_FORMAT]).valueOf();
367
410
  }
411
+ /**
412
+ * expand or narrow the selected range by moving the slider nearest the
413
+ * clicked bar to the outer edge of the clicked bar
414
+ *
415
+ * @param e Event click event from a histogram bar
416
+ */
368
417
  handleBarClick(e) {
369
418
  const dataset = e.currentTarget.dataset;
370
- const binStartDateMS = dayjs(dataset.binStart).valueOf();
371
- if (binStartDateMS < this.minSelectedDateMS) {
419
+ // use the midpoint of the width of the clicked bar to determine which is
420
+ // the nearest slider
421
+ const clickPosition = (this.getMSFromString(dataset.binStart) +
422
+ this.getMSFromString(dataset.binEnd)) /
423
+ 2;
424
+ const distanceFromMinSlider = Math.abs(clickPosition - this.getMSFromString(this.minSelectedDate));
425
+ const distanceFromMaxSlider = Math.abs(clickPosition - this.getMSFromString(this.maxSelectedDate));
426
+ // update the selected range by moving the nearer slider
427
+ if (distanceFromMinSlider < distanceFromMaxSlider) {
372
428
  this.minSelectedDate = dataset.binStart;
373
429
  }
374
- const binEndDateMS = dayjs(dataset.binEnd).valueOf();
375
- if (binEndDateMS > this.maxSelectedDateMS) {
430
+ else {
376
431
  this.maxSelectedDate = dataset.binEnd;
377
432
  }
378
433
  this.beginEmitUpdateProcess();
@@ -416,7 +471,7 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
416
471
  ${this._isDragging ? 'dragging' : ''}"
417
472
  @pointerdown="${this.drag}"
418
473
  >
419
- <path d="${sliderShape} z" fill="${sliderFill}" />
474
+ <path d="${sliderShape} z" fill="${sliderColor}" />
420
475
  <rect
421
476
  x="${sliderPositionX - this.sliderWidth * k + this.sliderWidth * 0.4 * k}"
422
477
  y="${this.height / 3}"
@@ -441,7 +496,7 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
441
496
  y="0"
442
497
  width="${this.maxSliderX - this.minSliderX}"
443
498
  height="${this.height}"
444
- fill="${selectedRangeFill}"
499
+ fill="${selectedRangeColor}"
445
500
  />`;
446
501
  }
447
502
  get histogramTemplate() {
@@ -464,7 +519,7 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
464
519
  @pointerenter="${this.showTooltip}"
465
520
  @pointerleave="${this.hideTooltip}"
466
521
  @click="${this.handleBarClick}"
467
- fill="${x >= this.minSliderX && x <= this.maxSliderX
522
+ fill="${x + barWidth >= this.minSliderX && x <= this.maxSliderX
468
523
  ? barIncludedFill
469
524
  : barExcludedFill}"
470
525
  data-num-items="${data.value}"
@@ -475,9 +530,17 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
475
530
  return bar;
476
531
  });
477
532
  }
478
- formatDate(rawDate) {
479
- const date = dayjs(rawDate);
480
- return date.isValid() ? date.format(this.dateFormat) : '';
533
+ formatDate(dateMS) {
534
+ if (Number.isNaN(dateMS)) {
535
+ return '';
536
+ }
537
+ const date = dayjs(dateMS);
538
+ if (date.year() < 1000) {
539
+ // years before 1000 don't play well with dayjs custom formatting, so fall
540
+ // back to displaying only the year
541
+ return String(date.year());
542
+ }
543
+ return date.format(this.dateFormat);
481
544
  }
482
545
  /**
483
546
  * NOTE: we are relying on the lit `live` directive in the template to
@@ -493,6 +556,7 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
493
556
  type="text"
494
557
  @focus="${this.cancelPendingUpdateEvent}"
495
558
  @blur="${this.handleMinDateInput}"
559
+ @keyup="${this.handleKeyUp}"
496
560
  .value="${live(this.minSelectedDate)}"
497
561
  ?disabled="${this.disabled}"
498
562
  />
@@ -506,6 +570,7 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
506
570
  type="text"
507
571
  @focus="${this.cancelPendingUpdateEvent}"
508
572
  @blur="${this.handleMaxDateInput}"
573
+ @keyup="${this.handleKeyUp}"
509
574
  .value="${live(this.maxSelectedDate)}"
510
575
  ?disabled="${this.disabled}"
511
576
  />
@@ -535,7 +600,7 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
535
600
  }
536
601
  get activityIndicatorTemplate() {
537
602
  if (!this.loading) {
538
- return html ``;
603
+ return nothing;
539
604
  }
540
605
  return html `
541
606
  <ia-activity-indicator mode="processing"> </ia-activity-indicator>
@@ -635,7 +700,7 @@ HistogramDateRange.styles = css `
635
700
  border-radius: 3px;
636
701
  padding: 2px;
637
702
  font-size: ${tooltipFontSize};
638
- font-family: sans-serif;
703
+ font-family: ${tooltipFontFamily};
639
704
  touch-action: none;
640
705
  pointer-events: none;
641
706
  }
@@ -672,6 +737,7 @@ HistogramDateRange.styles = css `
672
737
  border-radius: 2px !important;
673
738
  text-align: center;
674
739
  font-size: ${inputFontSize};
740
+ font-family: ${inputFontFamily};
675
741
  }
676
742
  `;
677
743
  __decorate([