@teipublisher/pb-components 1.32.2 → 1.33.0

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.
@@ -0,0 +1,729 @@
1
+ import { LitElement, html, css } from 'lit-element';
2
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
3
+ import { SearchResultService } from "./search-result-service.js";
4
+ import { ParseDateService } from "./parse-date-service.js";
5
+ import { pbMixin } from "./pb-mixin.js";
6
+ import '@polymer/iron-ajax';
7
+ import '@polymer/iron-icons';
8
+ import '@polymer/paper-icon-button';
9
+ import { translate } from "./pb-i18n.js";
10
+
11
+ /**
12
+ * A timeline component to display time series data in a bar chart like view.
13
+ *
14
+ * Time series data can be displayed in one of 6 different scales:
15
+ *
16
+ * - by decade (10Y)
17
+ * - by 5 years (5Y)
18
+ * - by years (Y)
19
+ * - by month (M)
20
+ * - by week (W)
21
+ * - by day (D)
22
+ *
23
+ * The endpoint is expected to return a JSON object. Each property should either be a date or the special
24
+ * marker `?`, which indicates undated resources.
25
+ * The value associated with each entry
26
+ * should either correspond to a count of resources or an object with properties `count` and `info`.
27
+ * `info` should be an array, containing HTML to be shown in a list within the tooltips.
28
+ *
29
+ * @slot label - Inserted before the label showing the currently displayed time range
30
+ *
31
+ * @fires pb-timeline-date-changed - Triggered when user clicks on a single entry
32
+ * @fires pb-timeline-daterange-changed - Triggered when user selects a range of entries
33
+ * @fires pb-timeline-reset-selection - Requests that the timeline is reset to initial state
34
+ * @fires pb-timeline-loaded - Timeline was loaded
35
+ *
36
+ * @cssprop --pb-timeline-height
37
+ * @cssprop --pb-timeline-padding
38
+ * @cssprop --pb-timeline-color-highlight
39
+ * @cssprop --pb-timeline-color-light
40
+ * @cssprop --pb-timeline-color-dark
41
+ * @cssprop --pb-timeline-color-selected
42
+ * @cssprop --pb-timeline-color-bin
43
+ * @cssprop --pb-timeline-title-font-size
44
+ * @cssprop --pb-timeline-tooltip-font-size
45
+ *
46
+ * @csspart label
47
+ * @csspart tooltip
48
+ * @csspart title
49
+ */
50
+ export class PbTimeline extends pbMixin(LitElement) {
51
+
52
+
53
+ static get styles() {
54
+ return css`
55
+ :host{
56
+ display: block;
57
+ }
58
+ .hidden {
59
+ visibility: hidden;
60
+ }
61
+ .draggable {
62
+ cursor: grab;
63
+ user-select: none;
64
+ padding-right: 30px !important;
65
+ }
66
+ .wrapper {
67
+ margin: 0 auto;
68
+ padding: var(--pb-timeline-padding);
69
+ width: auto;
70
+ height: var(--pb-timeline-height, 80px);
71
+ display: flex;
72
+ position: relative;
73
+ }
74
+ .wrapper.empty {
75
+ display: none;
76
+ }
77
+
78
+ .label {
79
+ display: flex;
80
+ align-items: center;
81
+ }
82
+ .bin-container {
83
+ cursor: crosshair;
84
+ margin-top: 20px;
85
+ min-width: var(--pb-timeline-min-width, 14px);
86
+ max-width: var(--pb-timeline-max-width, 20px);
87
+ flex-grow: 1;
88
+ flex-basis: 0;
89
+ display: flex;
90
+ align-items: flex-end;
91
+ // justify-content: center;
92
+ position: relative;
93
+ }
94
+ .bin-container.border-left, .bin-container.unknown {
95
+ border-left: 1px solid rgba(0,0,0,0.4);
96
+ }
97
+ .bin-container.unknown {
98
+ margin-left: 40px;
99
+ }
100
+ .bin-container:hover .bin {
101
+ background-color: var(--pb-timeline-color-highlight, #3f52b5);
102
+ }
103
+ .bin-container.selected > .bin {
104
+ background-color: var(--pb-timeline-color-highlight, #3f52b5);
105
+ }
106
+ .bin-container.selected p {
107
+ font-weight: bold;
108
+ }
109
+ .bin-container.white {
110
+ background-color: var(--pb-timeline-color-light, white);
111
+ }
112
+ .bin-container.grey {
113
+ background-color: var(--pb-timeline-color-dark, #f1f1f1);
114
+ }
115
+ .bin-container.selected {
116
+ background-color: var(--pb-timeline-color-selected, #e6eaff) !important;
117
+ }
118
+ .bin {
119
+ width: 80%;
120
+ background-color: var(--pb-timeline-color-bin, #ccc);
121
+ border-radius: 2px;
122
+ user-select: none;
123
+ }
124
+ p.bin-title {
125
+ pointer-events: none;
126
+ position: absolute;
127
+ top: 5px;
128
+ z-index: 10;
129
+ margin: 0;
130
+ font-size: var(--pb-timeline-title-font-size, 12px);
131
+ user-select: none;
132
+ white-space: nowrap;
133
+ }
134
+ p.bin-title.months {
135
+ top: -1px;
136
+ }
137
+ p.bin-title.weeks {
138
+ top: 3px;
139
+ }
140
+ p.bin-title.days {
141
+ top: -1px;
142
+ }
143
+ p.bin-title.rotated {
144
+ transform: rotate(-90deg);
145
+ }
146
+ .bins-title {
147
+ cursor: auto;
148
+ font-weight: normal !important;
149
+ margin: 0;
150
+ white-space: nowrap;
151
+ z-index: 200;
152
+ position: absolute;
153
+ left: 0;
154
+ top: -20px;
155
+ font-size: var(--pb-timeline-title-font-size, 12px);
156
+ background-color: var(--pb-timeline-background-color-title, #535353);
157
+ color: var(--pb-timeline-color-title, #ffffff);
158
+ padding: 2px 4px;
159
+ border-radius: 5px;
160
+ height: var(--pb-timeline-title-font-size, 12px);
161
+ line-height: var(--pb-timeline-title-font-size, 12px);
162
+ user-select: none;
163
+ }
164
+ .info {
165
+ display: none;
166
+ }
167
+
168
+ /* TOOLTIP */
169
+ #tooltip {
170
+ display: inline-block;
171
+ position: absolute;
172
+ min-width: var(--pb-timeline-tooltip-min-width, 200px);
173
+ font-size: var(--pb-timeline-tooltip-font-size, 11px);
174
+ line-height: 1.25;
175
+ background: var(--pb-timeline-background-color-title, #535353);
176
+ color: var(--pb-timeline-color-title, #ffffff);
177
+ text-align: left;
178
+ border-radius: 6px;
179
+ padding: 5px 10px;
180
+ top: calc(var(--pb-timeline-height, 80px) - 5px);
181
+ left: 0;
182
+ }
183
+ #tooltip ul {
184
+ list-style: none;
185
+ margin: 0;
186
+ padding: 0;
187
+ }
188
+ #tooltip-close {
189
+ position: absolute;
190
+ top: -13px;
191
+ right: -10px;
192
+ }
193
+ #tooltip::after { /* small triangle that points to top */
194
+ content: "";
195
+ position: absolute;
196
+ bottom: 100%;
197
+ left: 50%;
198
+ margin-left: -5px;
199
+ border-width: 5px;
200
+ border-style: solid;
201
+ border-color: transparent transparent black transparent;
202
+ }
203
+ /* pure css close button for tooltip */
204
+ .close{
205
+ position: relative;
206
+ display: inline-block;
207
+ width: 50px;
208
+ height: 50px;
209
+ overflow: hidden;
210
+ transform: scale(0.25);
211
+ }
212
+ .close.rounded.black {
213
+ cursor: pointer;
214
+ }
215
+ .close::before, .close::after {
216
+ content: '';
217
+ position: absolute;
218
+ height: 2px;
219
+ width: 100%;
220
+ top: 50%;
221
+ left: 0;
222
+ margin-top: -1px;
223
+ background: #fff;
224
+ }
225
+ .close::before {
226
+ transform: rotate(45deg);
227
+ }
228
+ .close::after {
229
+ transform: rotate(-45deg);
230
+ }
231
+ .close.thick::before, .close.thick::after {
232
+ height: 4px;
233
+ margin-top: -2px;
234
+ }
235
+ .close.black::before, .close.black::after {
236
+ height: 8px;
237
+ margin-top: -4px;
238
+ }
239
+ .close.rounded::before, .close.rounded::after {
240
+ border-radius: 5px;
241
+ }
242
+ `;
243
+ }
244
+
245
+ static get properties() {
246
+ return {
247
+ ...super.properties,
248
+ /**
249
+ * start date for timeline to display
250
+ */
251
+ startDate:{
252
+ type: String,
253
+ reflect: true,
254
+ attribute: 'start-date'
255
+ },
256
+ /**
257
+ * endDate for timeline to display
258
+ */
259
+ endDate: {
260
+ type: String,
261
+ reflect: true,
262
+ attribute: 'end-date'
263
+ },
264
+ /**
265
+ * The scope for the timeline. Must be one of the pre-defined scopes.
266
+ * If not set, the component automatically tries to determine the best scope fitting the
267
+ * given time series.
268
+ */
269
+ scope:{
270
+ type: String
271
+ },
272
+ /**
273
+ * The scopes to consider for automatic scoping.
274
+ *
275
+ * Defaults to ["D", "W", "M", "Y", "5Y", "10Y"]
276
+ */
277
+ scopes: {
278
+ type: Array
279
+ },
280
+ /**
281
+ * Endpoint to load timeline data from. Expects response to be an
282
+ * object with key value pairs for (date, hits).
283
+ *
284
+ * Will be reloaded whenever 'start-date' or 'end-date' attributes change.
285
+ */
286
+ url:{
287
+ type: String
288
+ },
289
+ /**
290
+ * If set, data will be retrieved automatically on first load.
291
+ */
292
+ auto: {
293
+ type: Boolean
294
+ },
295
+ resettable: {
296
+ type: Boolean
297
+ }
298
+ };
299
+ }
300
+
301
+ constructor() {
302
+ super();
303
+ this.maxHeight = 80; // in pixels, has to be identical to the max-height specified in CSS
304
+ this.multiplier = 0.75; // max percentage of bin compared to the bin-conainer. Set 1 for full height (not recommended)
305
+ this.mousedown = false;
306
+ this.startDate = '';
307
+ this.endDate = '';
308
+ this.scope = '';
309
+ this.scopes = ["D", "W", "M", "Y", "5Y", "10Y"];
310
+ this.url = '';
311
+ this.auto = false;
312
+ this.resettable = false;
313
+ this._resetSelectionProperty();
314
+ }
315
+
316
+ connectedCallback() {
317
+ super.connectedCallback();
318
+
319
+ this.subscribeTo('pb-results-received', () => {
320
+ const loader = this.shadowRoot.getElementById('loadData');
321
+ const url = this.toAbsoluteURL(this.url, this.getEndpoint());
322
+ loader.url = url;
323
+ loader.generateRequest();
324
+ });
325
+ }
326
+
327
+ firstUpdated() {
328
+ this.bins = this.shadowRoot.querySelectorAll(".bin-container");
329
+ this.tooltip = this.shadowRoot.getElementById("tooltip");
330
+
331
+ // global mouseup event
332
+ document.addEventListener("mouseup", () => {
333
+ this._mouseUp();
334
+ })
335
+ // pb-timeline-daterange-changed event:
336
+ // changes daterange selection (marks bins on histogram)
337
+ // is triggered by the componeent itself but can be also triggered
338
+ // from outside by another component
339
+ document.addEventListener("pb-timeline-daterange-changed", (event) => {
340
+ const startDateStr = event.detail.startDateStr;
341
+ const endDateStr = event.detail.endDateStr;
342
+ if (this._fullRangeSelected(startDateStr, endDateStr)){
343
+ // do not mark the whole histogram, reset selection instead
344
+ console.log("_fullRangeSelected() is true");
345
+ this.resetSelection();
346
+ return;
347
+ }
348
+ this.select(startDateStr, endDateStr);
349
+ });
350
+ // pb-timeline-reset-selection:
351
+ // resets selection (remove marking of all selected bins)
352
+ // is triggered by the componeent itself but can be also triggered
353
+ // from outside by another component
354
+ document.addEventListener("pb-timeline-reset-selection", () => {
355
+ this.resetSelection();
356
+ this._hideTooltip();
357
+ });
358
+ }
359
+
360
+ /**
361
+ * checks if 'scope' has changed and re-applies dataset accordingly
362
+ *
363
+ * @param changedProperties
364
+ */
365
+ updated (changedProperties){
366
+ if(changedProperties.has('scope')){
367
+
368
+ if(this.searchResult){
369
+ if(this.scopes.includes(this.scope)){
370
+ this.setData(this.searchResult.export(this.scope));
371
+ }else{
372
+ console.error('unknown scope ', this.scope);
373
+ }
374
+ }
375
+
376
+ }
377
+ }
378
+
379
+
380
+ setData(dataObj) {
381
+ this.dataObj = dataObj;
382
+ this.maxValue = Math.max(...this.dataObj.data.map(binObj => binObj.value));
383
+ this.requestUpdate();
384
+ this.updateComplete.then(() => {
385
+ this.bins = this.shadowRoot.querySelectorAll(".bin-container");
386
+ this.resetSelection();
387
+ this._resetTooltip();
388
+ });
389
+ }
390
+
391
+ get label() {
392
+ if (!this.dataObj || this.dataObj.data.length === 0) {
393
+ return '';
394
+ }
395
+ if (this.dataObj.data.length === 1) {
396
+ return this.dataObj.data[0].category;
397
+ }
398
+ return `${this.dataObj.data[0].category} – ${this.dataObj.data[this.dataObj.data.length - 1].category}`;
399
+ }
400
+
401
+ getSelectedStartDateStr() {
402
+ return this.shadowRoot.querySelectorAll(".bin-container.selected")[0].dataset.selectionstart;
403
+ }
404
+
405
+ getSelectedEndDateStr() {
406
+ const selectedBins = this.shadowRoot.querySelectorAll(".bin-container.selected");
407
+ return selectedBins[selectedBins.length - 1].dataset.selectionend;
408
+ }
409
+
410
+ getSelectedCategories() {
411
+ const selectedBins = this.shadowRoot.querySelectorAll(".bin-container.selected");
412
+ const categories = [];
413
+ selectedBins.forEach((bin) => categories.push(bin.dataset.category));
414
+ return categories;
415
+ }
416
+
417
+ getSelectedItemCount() {
418
+ const selectedBins = this.shadowRoot.querySelectorAll(".bin-container.selected");
419
+ let count = 0;
420
+ selectedBins.forEach((bin) => { count += parseInt(bin.dataset.value); });
421
+ return count;
422
+ }
423
+
424
+ resetSelection() {
425
+ this.bins.forEach(bin => {
426
+ bin.classList.remove("selected");
427
+ });
428
+ this._resetSelectionProperty();
429
+ this._hideTooltip();
430
+ }
431
+
432
+ select(startDateStr, endDateStr) {
433
+ this.bins.forEach(bin => {
434
+ if (bin.dataset.isodatestr >= startDateStr && bin.dataset.isodatestr <= endDateStr) {
435
+ bin.classList.add("selected");
436
+ } else {bin.classList.remove("selected");
437
+ }
438
+ });
439
+ this._displayTooltip();
440
+ this._showtooltipSelection();
441
+ }
442
+
443
+ _fullRangeSelected(startDateStr, endDateStr) {
444
+ const matchingStartDate = startDateStr = this.bins[0].dataset.isodatestr;
445
+ const matchingEndDate = endDateStr === this.bins[this.bins.length - 1].dataset.isodatestr;
446
+ return matchingStartDate && matchingEndDate;
447
+ }
448
+
449
+ _mouseDown(event) {
450
+ this.resetSelection();
451
+ this.mousedown = true;
452
+ this.selection.start = this._getMousePosition(event).x;
453
+ this._applySelectionToBins();
454
+ }
455
+
456
+ _mouseUp() {
457
+ if (this.mousedown) {
458
+ this.mousedown = false;
459
+ const start = this.getSelectedStartDateStr();
460
+ const end = this.getSelectedEndDateStr();
461
+ if (start) {
462
+ const startDateStr = new ParseDateService().run(start);
463
+ const endDateStr = new ParseDateService().run(end);
464
+ const itemCount = this.getSelectedItemCount();
465
+ this._dispatchTimelineDaterangeChangedEvent(startDateStr, endDateStr, this.getSelectedCategories(), itemCount);
466
+ }
467
+ }
468
+ }
469
+
470
+ _mouseMove(event) {
471
+ if (this.mousedown) {
472
+ this._brushing(event);
473
+ this._showtooltipSelection();
474
+ } else if (this.selection.start === undefined) { // no selection currently made
475
+ this._showtooltip(event);
476
+ }
477
+ }
478
+
479
+ _mouseenter() {
480
+ if (this.dataObj) { // if data is loaded
481
+ this._displayTooltip();
482
+ }
483
+ }
484
+
485
+ _getMousePosition(mouseEvent) {
486
+ let rect = this.shadowRoot.querySelector(".wrapper").getBoundingClientRect();
487
+ let x = mouseEvent.clientX - rect.left + 1; //x position within the element.
488
+ let y = mouseEvent.clientY - rect.top + 1; //y position within the element.
489
+ return { x: x, y: y };
490
+ }
491
+
492
+ _brushing(event) {
493
+ this.selection.end = this._getMousePosition(event).x;
494
+ this._applySelectionToBins();
495
+ }
496
+
497
+ _dispatchTimelineDaterangeChangedEvent(startDateStr, endDateStr, categories, itemCount) {
498
+ if (startDateStr === '????-??-??') {
499
+ this.emitTo('pb-timeline-date-changed', { startDateStr: null, endDateStr: null, categories: ['?'], count: itemCount });
500
+ } else if(startDateStr === endDateStr) {
501
+ if (this.dataObj.scope !== 'D') {
502
+ this.emitTo('pb-timeline-daterange-changed', {
503
+ startDateStr,
504
+ endDateStr: this.searchResult.getEndOfRangeDate(this.dataObj.scope, endDateStr),
505
+ scope: this.dataObj.scope,
506
+ categories,
507
+ count: itemCount
508
+ });
509
+ } else {
510
+ this.emitTo('pb-timeline-date-changed', { startDateStr, endDateStr: null, scope: this.dataObj.scope, categories, count: itemCount });
511
+ }
512
+ } else {
513
+ this.emitTo('pb-timeline-daterange-changed', {
514
+ startDateStr,
515
+ endDateStr,
516
+ categories,
517
+ scope: this.dataObj.scope,
518
+ count: itemCount
519
+ });
520
+ }
521
+ }
522
+
523
+ _dispatchPbTimelineResetSelectionEvent() {
524
+ this.emitTo('pb-timeline-reset-selection');
525
+ }
526
+
527
+ _showtooltip(event) {
528
+ const interval = this._getElementInterval(event.currentTarget);
529
+ const offset = Math.round((((interval[0] + interval[1]) / 2) - this.tooltip.offsetWidth / 2));
530
+ this.tooltip.style.left = offset + "px";
531
+ const datestr = event.currentTarget.dataset.tooltip;
532
+ const value = this._numberWithCommas(event.currentTarget.dataset.value);
533
+ const info = event.currentTarget.querySelector('.info');
534
+ this.tooltip.querySelector("#tooltip-text").innerHTML =
535
+ `<div><strong>${datestr}</strong>: ${value}</div><ul>${info ? info.innerHTML : ''}</ul>`;
536
+ }
537
+
538
+ _showtooltipSelection() {
539
+ const selectedBins = this._getSelectedBins();
540
+ const intervalStart = this._getElementInterval(selectedBins[0])[0]; // get first selected element left boundary
541
+ const intervalEnd = this._getElementInterval(selectedBins[selectedBins.length-1])[1]; // get last selected element right boundary
542
+ const interval = [intervalStart, intervalEnd];
543
+ const label = `${selectedBins[0].dataset.selectionstart} - ${selectedBins[selectedBins.length-1].dataset.selectionend}`;
544
+ const value = selectedBins.map(bin => Number(bin.dataset.value)).reduce((a, b) => a + b);
545
+ const valueFormatted = this._numberWithCommas(value);
546
+ this.tooltip.querySelector("#tooltip-text").innerHTML = `<strong>${label}</strong>: ${valueFormatted}`;
547
+ this.tooltip.querySelector("#tooltip-close").classList.remove("hidden");
548
+ this.tooltip.classList.add("draggable");
549
+ const offset = Math.round((((interval[0] + interval[1]) / 2) - this.tooltip.offsetWidth / 2));
550
+ this.tooltip.style.left = offset + "px";
551
+ }
552
+
553
+ _resetTooltip() {
554
+ this._hideTooltip();
555
+ this.tooltip.style.left = '-1000px';
556
+ this.tooltip.querySelector("#tooltip-text").innerHTML = "";
557
+ }
558
+
559
+ _hideTooltip() {
560
+ if (this.selection.start === undefined) {
561
+ this.tooltip.classList.add("hidden");
562
+ this.tooltip.classList.remove("draggable");
563
+ this.tooltip.querySelector("#tooltip-close").classList.add("hidden");
564
+ }
565
+ }
566
+
567
+ _displayTooltip() {
568
+ this.tooltip.classList.remove("hidden");
569
+ }
570
+
571
+ _getElementInterval(nodeElement) {
572
+ let rect = this.shadowRoot.querySelector(".wrapper").getBoundingClientRect();
573
+ let bin = nodeElement;
574
+ let interval = [bin.getBoundingClientRect().x, bin.getBoundingClientRect().x + bin.getBoundingClientRect().width]
575
+ let x1 = interval[0] - rect.left + 1; //x position within the element.
576
+ let x2 = interval[1] - rect.left + 1; //x position within the element.
577
+ return [x1, x2];
578
+ }
579
+
580
+ _getSelectionInterval() {
581
+ return [this.selection.start, this.selection.end].sort((a, b) => a - b);
582
+ }
583
+
584
+ _getSelectedBins() {
585
+ return Array.prototype.slice.call(this.bins).filter(binContainer => {
586
+ return binContainer.classList.contains("selected");
587
+ });
588
+ }
589
+
590
+ _resetSelectionProperty() {
591
+ this.selection = {
592
+ start: undefined,
593
+ end: undefined
594
+ }
595
+ }
596
+
597
+ _applySelectionToBins() {
598
+ const selectionInterval = this._getSelectionInterval();
599
+ this.bins.forEach(bin => {
600
+ const elInterval = this._getElementInterval(bin);
601
+ // if (this.intervalsOverlapping(elInterval, selectionInterval)) {
602
+ if (this._areOverlapping(elInterval, selectionInterval)) {
603
+ bin.classList.add("selected");
604
+ } else {
605
+ bin.classList.remove("selected");
606
+ }
607
+ })
608
+ }
609
+
610
+ _numberWithCommas(input) {
611
+ return input.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, "'");
612
+ }
613
+
614
+ _areOverlapping(A, B) { // check if 2 intervals are overlapping
615
+ return B[0] < A[0] ? B[1] > A[0] : B[0] < A[1];
616
+ }
617
+
618
+ render() {
619
+ return html`
620
+ <div class="label" part="label">
621
+ <span class="label"><slot name="label"></slot>${this.label}</span>
622
+ ${
623
+ this.resettable ? html`
624
+ <paper-icon-button id="clear" icon="icons:clear" title="${translate('timeline.clear')}"
625
+ @click="${this._dispatchPbTimelineResetSelectionEvent}"></paper-icon-button>
626
+ ` : null
627
+ }
628
+ </div>
629
+ <div class="wrapper ${!this.dataObj || this.dataObj.data.length <= 1 ? 'empty' : ''}"
630
+ @mouseenter="${this._mouseenter}"
631
+ @mouseleave="${this._hideTooltip}">
632
+ ${this.dataObj ? this.renderBins() : ""}
633
+ ${this.renderTooltip()}
634
+ <iron-ajax
635
+ id="loadData"
636
+ verbose
637
+ handle-as="json"
638
+ method="get"
639
+ with-credentials
640
+ @response="${this._handleResponse}"
641
+ url="${this.url}?start=${this.startDate}&end=${this.endDate}"
642
+ ?auto="${this.auto}"></iron-ajax>
643
+ </div>
644
+ `;
645
+ }
646
+
647
+ renderTooltip() {
648
+ return html`
649
+ <div id="tooltip" class="hidden" part="tooltip">
650
+ <div id="tooltip-text"></div>
651
+ <div
652
+ id="tooltip-close"
653
+ class="hidden"
654
+ @click="${this._dispatchPbTimelineResetSelectionEvent}"
655
+ ><span class="close rounded black"></span>
656
+ </div>
657
+ </div>
658
+ `;
659
+ }
660
+
661
+ renderBins() {
662
+ return html`
663
+ ${this.dataObj.data.map((binObj, indx) => {
664
+ return html`
665
+ <div class="bin-container ${binObj.seperator ? "border-left" : ""}
666
+ ${indx % 2 === 0 ? "grey" : "white"} ${binObj.category === '?' ? 'unknown' : ''}"
667
+ data-tooltip="${binObj.tooltip}"
668
+ data-category="${binObj.category}"
669
+ data-selectionstart="${binObj.selectionStart}"
670
+ data-selectionend="${binObj.selectionEnd}"
671
+ data-isodatestr="${binObj.dateStr}"
672
+ data-datestr="${binObj.dateStr}"
673
+ data-value="${binObj.value}"
674
+ @mousemove="${this._mouseMove}"
675
+ @mousedown="${this._mouseDown}">
676
+ <div class="bin" style="height: ${(binObj.value / this.maxValue) * this.maxHeight * this.multiplier}px"></div>
677
+ <p class="bin-title
678
+ ${this.dataObj.binTitleRotated ? "rotated" : ""}
679
+ ${this.scope}"
680
+ >${binObj.binTitle ? binObj.binTitle : ""}
681
+ </p>
682
+ ${binObj.title ? html`
683
+ <p class="bins-title" part="title">${binObj.title}</p>
684
+ ` : ""}
685
+ ${this.renderInfo(binObj)}
686
+ </div>
687
+ `;
688
+ })}
689
+ `;
690
+ }
691
+
692
+ renderInfo(binObj) {
693
+ if (binObj.info && binObj.info.length > 0 && binObj.info.length < 6) {
694
+ return html`
695
+ <ul class="info">
696
+ ${ binObj.info.map(info => html`<li>${unsafeHTML(info)}</li>`) }
697
+ </ul>
698
+ `;
699
+ }
700
+ return null;
701
+ }
702
+
703
+ async _handleResponse (){
704
+ await this.updateComplete;
705
+ const loader = this.shadowRoot.getElementById('loadData');
706
+ const data = loader.lastResponse;
707
+
708
+ let newJsonData = {};
709
+ if (this.startDate && this.endDate) {
710
+ Object.keys(data).filter(key => key >= this.startDate && key < this.endDate).forEach(key => {
711
+ newJsonData[key] = data[key];
712
+ });
713
+ } else {
714
+ newJsonData = data;
715
+ }
716
+ this.searchResult = new SearchResultService(newJsonData, 60, this.scopes);
717
+ this.setData(this.searchResult.export(this.scope));
718
+ this.dispatchEvent(new CustomEvent('pb-timeline-loaded', {
719
+ detail: {
720
+ value: true
721
+ },
722
+ composed: true,
723
+ bubbles: true
724
+ }));
725
+ }
726
+
727
+ }
728
+
729
+ customElements.define('pb-timeline', PbTimeline);