@teipublisher/pb-components 1.32.0 → 1.33.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.
@@ -0,0 +1,741 @@
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: var(--pb-timeline-height, 80px);
181
+ left: 0;
182
+ z-index: 1000;
183
+ }
184
+ #tooltip ul {
185
+ list-style: none;
186
+ margin: 0;
187
+ padding: 0;
188
+ }
189
+ #tooltip-close {
190
+ position: absolute;
191
+ top: -13px;
192
+ right: -10px;
193
+ }
194
+ #tooltip::after { /* small triangle that points to top */
195
+ content: "";
196
+ position: absolute;
197
+ bottom: 100%;
198
+ left: 10px;
199
+ margin-left: -5px;
200
+ border-width: 5px;
201
+ border-style: solid;
202
+ border-color: transparent transparent black transparent;
203
+ }
204
+ #tooltip.right::after {
205
+ right: 10px;
206
+ left: auto;
207
+ }
208
+ /* pure css close button for tooltip */
209
+ .close{
210
+ position: relative;
211
+ display: inline-block;
212
+ width: 50px;
213
+ height: 50px;
214
+ overflow: hidden;
215
+ transform: scale(0.25);
216
+ }
217
+ .close.rounded.black {
218
+ cursor: pointer;
219
+ }
220
+ .close::before, .close::after {
221
+ content: '';
222
+ position: absolute;
223
+ height: 2px;
224
+ width: 100%;
225
+ top: 50%;
226
+ left: 0;
227
+ margin-top: -1px;
228
+ background: #fff;
229
+ }
230
+ .close::before {
231
+ transform: rotate(45deg);
232
+ }
233
+ .close::after {
234
+ transform: rotate(-45deg);
235
+ }
236
+ .close.thick::before, .close.thick::after {
237
+ height: 4px;
238
+ margin-top: -2px;
239
+ }
240
+ .close.black::before, .close.black::after {
241
+ height: 8px;
242
+ margin-top: -4px;
243
+ }
244
+ .close.rounded::before, .close.rounded::after {
245
+ border-radius: 5px;
246
+ }
247
+ `;
248
+ }
249
+
250
+ static get properties() {
251
+ return {
252
+ ...super.properties,
253
+ /**
254
+ * start date for timeline to display
255
+ */
256
+ startDate:{
257
+ type: String,
258
+ reflect: true,
259
+ attribute: 'start-date'
260
+ },
261
+ /**
262
+ * endDate for timeline to display
263
+ */
264
+ endDate: {
265
+ type: String,
266
+ reflect: true,
267
+ attribute: 'end-date'
268
+ },
269
+ /**
270
+ * The scope for the timeline. Must be one of the pre-defined scopes.
271
+ * If not set, the component automatically tries to determine the best scope fitting the
272
+ * given time series.
273
+ */
274
+ scope:{
275
+ type: String
276
+ },
277
+ /**
278
+ * The scopes to consider for automatic scoping.
279
+ *
280
+ * Defaults to ["D", "W", "M", "Y", "5Y", "10Y"]
281
+ */
282
+ scopes: {
283
+ type: Array
284
+ },
285
+ /**
286
+ * Endpoint to load timeline data from. Expects response to be an
287
+ * object with key value pairs for (date, hits).
288
+ *
289
+ * Will be reloaded whenever 'start-date' or 'end-date' attributes change.
290
+ */
291
+ url:{
292
+ type: String
293
+ },
294
+ /**
295
+ * If set, data will be retrieved automatically on first load.
296
+ */
297
+ auto: {
298
+ type: Boolean
299
+ },
300
+ resettable: {
301
+ type: Boolean
302
+ }
303
+ };
304
+ }
305
+
306
+ constructor() {
307
+ super();
308
+ this.maxHeight = 80; // in pixels, has to be identical to the max-height specified in CSS
309
+ this.multiplier = 0.75; // max percentage of bin compared to the bin-conainer. Set 1 for full height (not recommended)
310
+ this.mousedown = false;
311
+ this.startDate = '';
312
+ this.endDate = '';
313
+ this.scope = '';
314
+ this.scopes = ["D", "W", "M", "Y", "5Y", "10Y"];
315
+ this.url = '';
316
+ this.auto = false;
317
+ this.resettable = false;
318
+ this._resetSelectionProperty();
319
+ }
320
+
321
+ connectedCallback() {
322
+ super.connectedCallback();
323
+
324
+ this.subscribeTo('pb-results-received', () => {
325
+ const loader = this.shadowRoot.getElementById('loadData');
326
+ const url = this.toAbsoluteURL(this.url, this.getEndpoint());
327
+ loader.url = url;
328
+ loader.generateRequest();
329
+ });
330
+ }
331
+
332
+ firstUpdated() {
333
+ this.bins = this.shadowRoot.querySelectorAll(".bin-container");
334
+ this.tooltip = this.shadowRoot.getElementById("tooltip");
335
+
336
+ // global mouseup event
337
+ document.addEventListener("mouseup", () => {
338
+ this._mouseUp();
339
+ })
340
+ // pb-timeline-daterange-changed event:
341
+ // changes daterange selection (marks bins on histogram)
342
+ // is triggered by the componeent itself but can be also triggered
343
+ // from outside by another component
344
+ document.addEventListener("pb-timeline-daterange-changed", (event) => {
345
+ const startDateStr = event.detail.startDateStr;
346
+ const endDateStr = event.detail.endDateStr;
347
+ if (this._fullRangeSelected(startDateStr, endDateStr)){
348
+ // do not mark the whole histogram, reset selection instead
349
+ console.log("_fullRangeSelected() is true");
350
+ this.resetSelection();
351
+ return;
352
+ }
353
+ this.select(startDateStr, endDateStr);
354
+ });
355
+ // pb-timeline-reset-selection:
356
+ // resets selection (remove marking of all selected bins)
357
+ // is triggered by the componeent itself but can be also triggered
358
+ // from outside by another component
359
+ document.addEventListener("pb-timeline-reset-selection", () => {
360
+ this.resetSelection();
361
+ this._hideTooltip();
362
+ });
363
+ }
364
+
365
+ /**
366
+ * checks if 'scope' has changed and re-applies dataset accordingly
367
+ *
368
+ * @param changedProperties
369
+ */
370
+ updated (changedProperties){
371
+ if(changedProperties.has('scope')){
372
+
373
+ if(this.searchResult){
374
+ if(this.scopes.includes(this.scope)){
375
+ this.setData(this.searchResult.export(this.scope));
376
+ }else{
377
+ console.error('unknown scope ', this.scope);
378
+ }
379
+ }
380
+
381
+ }
382
+ }
383
+
384
+
385
+ setData(dataObj) {
386
+ this.dataObj = dataObj;
387
+ this.maxValue = Math.max(...this.dataObj.data.map(binObj => binObj.value));
388
+ this.requestUpdate();
389
+ this.updateComplete.then(() => {
390
+ this.bins = this.shadowRoot.querySelectorAll(".bin-container");
391
+ this.resetSelection();
392
+ this._resetTooltip();
393
+ });
394
+ }
395
+
396
+ get label() {
397
+ if (!this.dataObj || this.dataObj.data.length === 0) {
398
+ return '';
399
+ }
400
+ if (this.dataObj.data.length === 1) {
401
+ return this.dataObj.data[0].category;
402
+ }
403
+ return `${this.dataObj.data[0].category} – ${this.dataObj.data[this.dataObj.data.length - 1].category}`;
404
+ }
405
+
406
+ getSelectedStartDateStr() {
407
+ return this.shadowRoot.querySelectorAll(".bin-container.selected")[0].dataset.selectionstart;
408
+ }
409
+
410
+ getSelectedEndDateStr() {
411
+ const selectedBins = this.shadowRoot.querySelectorAll(".bin-container.selected");
412
+ return selectedBins[selectedBins.length - 1].dataset.selectionend;
413
+ }
414
+
415
+ getSelectedCategories() {
416
+ const selectedBins = this.shadowRoot.querySelectorAll(".bin-container.selected");
417
+ const categories = [];
418
+ selectedBins.forEach((bin) => categories.push(bin.dataset.category));
419
+ return categories;
420
+ }
421
+
422
+ getSelectedItemCount() {
423
+ const selectedBins = this.shadowRoot.querySelectorAll(".bin-container.selected");
424
+ let count = 0;
425
+ selectedBins.forEach((bin) => { count += parseInt(bin.dataset.value); });
426
+ return count;
427
+ }
428
+
429
+ resetSelection() {
430
+ this.bins.forEach(bin => {
431
+ bin.classList.remove("selected");
432
+ });
433
+ this._resetSelectionProperty();
434
+ this._hideTooltip();
435
+ }
436
+
437
+ select(startDateStr, endDateStr) {
438
+ this.bins.forEach(bin => {
439
+ if (bin.dataset.isodatestr >= startDateStr && bin.dataset.isodatestr <= endDateStr) {
440
+ bin.classList.add("selected");
441
+ } else {bin.classList.remove("selected");
442
+ }
443
+ });
444
+ this._displayTooltip();
445
+ this._showtooltipSelection();
446
+ }
447
+
448
+ _fullRangeSelected(startDateStr, endDateStr) {
449
+ const matchingStartDate = startDateStr = this.bins[0].dataset.isodatestr;
450
+ const matchingEndDate = endDateStr === this.bins[this.bins.length - 1].dataset.isodatestr;
451
+ return matchingStartDate && matchingEndDate;
452
+ }
453
+
454
+ _mouseDown(event) {
455
+ this.resetSelection();
456
+ this.mousedown = true;
457
+ this.selection.start = this._getMousePosition(event).x;
458
+ this._applySelectionToBins();
459
+ }
460
+
461
+ _mouseUp() {
462
+ if (this.mousedown) {
463
+ this.mousedown = false;
464
+ const start = this.getSelectedStartDateStr();
465
+ const end = this.getSelectedEndDateStr();
466
+ if (start) {
467
+ const startDateStr = new ParseDateService().run(start);
468
+ const endDateStr = new ParseDateService().run(end);
469
+ const itemCount = this.getSelectedItemCount();
470
+ this._dispatchTimelineDaterangeChangedEvent(startDateStr, endDateStr, this.getSelectedCategories(), itemCount);
471
+ }
472
+ }
473
+ }
474
+
475
+ _mouseMove(event) {
476
+ if (this.mousedown) {
477
+ this._brushing(event);
478
+ this._showtooltipSelection();
479
+ } else if (this.selection.start === undefined) { // no selection currently made
480
+ this._showtooltip(event);
481
+ }
482
+ }
483
+
484
+ _mouseenter() {
485
+ if (this.dataObj) { // if data is loaded
486
+ this._displayTooltip();
487
+ }
488
+ }
489
+
490
+ _getMousePosition(mouseEvent) {
491
+ let rect = this.shadowRoot.querySelector(".wrapper").getBoundingClientRect();
492
+ let x = mouseEvent.clientX - rect.left + 1; //x position within the element.
493
+ let y = mouseEvent.clientY - rect.top + 1; //y position within the element.
494
+ return { x: x, y: y };
495
+ }
496
+
497
+ _brushing(event) {
498
+ this.selection.end = this._getMousePosition(event).x;
499
+ this._applySelectionToBins();
500
+ }
501
+
502
+ _dispatchTimelineDaterangeChangedEvent(startDateStr, endDateStr, categories, itemCount) {
503
+ if (startDateStr === '????-??-??') {
504
+ this.emitTo('pb-timeline-date-changed', { startDateStr: null, endDateStr: null, categories: ['?'], count: itemCount });
505
+ } else if(startDateStr === endDateStr) {
506
+ if (this.dataObj.scope !== 'D') {
507
+ this.emitTo('pb-timeline-daterange-changed', {
508
+ startDateStr,
509
+ endDateStr: this.searchResult.getEndOfRangeDate(this.dataObj.scope, endDateStr),
510
+ scope: this.dataObj.scope,
511
+ categories,
512
+ count: itemCount
513
+ });
514
+ } else {
515
+ this.emitTo('pb-timeline-date-changed', { startDateStr, endDateStr: null, scope: this.dataObj.scope, categories, count: itemCount });
516
+ }
517
+ } else {
518
+ this.emitTo('pb-timeline-daterange-changed', {
519
+ startDateStr,
520
+ endDateStr,
521
+ categories,
522
+ scope: this.dataObj.scope,
523
+ count: itemCount
524
+ });
525
+ }
526
+ }
527
+
528
+ _dispatchPbTimelineResetSelectionEvent() {
529
+ this.emitTo('pb-timeline-reset-selection');
530
+ }
531
+
532
+ _showtooltip(event) {
533
+ const interval = this._getElementInterval(event.currentTarget);
534
+ let offset;
535
+ if (interval[0] < interval[2]) {
536
+ offset = Math.round((((interval[0] + interval[1]) / 2)) - 10);
537
+ this.tooltip.classList.remove('right');
538
+ } else {
539
+ offset = Math.round((((interval[0] + interval[1]) / 2) - this.tooltip.offsetWidth)) + 10;
540
+ this.tooltip.classList.add('right');
541
+ }
542
+ this.tooltip.style.left = offset + "px";
543
+ const datestr = event.currentTarget.dataset.tooltip;
544
+ const value = this._numberWithCommas(event.currentTarget.dataset.value);
545
+ const info = event.currentTarget.querySelector('.info');
546
+ this.tooltip.querySelector("#tooltip-text").innerHTML =
547
+ `<div><strong>${datestr}</strong>: ${value}</div><ul>${info ? info.innerHTML : ''}</ul>`;
548
+ }
549
+
550
+ _showtooltipSelection() {
551
+ const selectedBins = this._getSelectedBins();
552
+ const intervalStart = this._getElementInterval(selectedBins[0])[0]; // get first selected element left boundary
553
+ const intervalEnd = this._getElementInterval(selectedBins[selectedBins.length-1])[1]; // get last selected element right boundary
554
+ const interval = [intervalStart, intervalEnd];
555
+ const label = `${selectedBins[0].dataset.selectionstart} - ${selectedBins[selectedBins.length-1].dataset.selectionend}`;
556
+ const value = selectedBins.map(bin => Number(bin.dataset.value)).reduce((a, b) => a + b);
557
+ const valueFormatted = this._numberWithCommas(value);
558
+ this.tooltip.querySelector("#tooltip-text").innerHTML = `<strong>${label}</strong>: ${valueFormatted}`;
559
+ this.tooltip.querySelector("#tooltip-close").classList.remove("hidden");
560
+ this.tooltip.classList.add("draggable");
561
+ const offset = Math.round((((interval[0] + interval[1]) / 2) - this.tooltip.offsetWidth / 2));
562
+ this.tooltip.style.left = offset + "px";
563
+ }
564
+
565
+ _resetTooltip() {
566
+ this._hideTooltip();
567
+ this.tooltip.style.left = '-1000px';
568
+ this.tooltip.querySelector("#tooltip-text").innerHTML = "";
569
+ }
570
+
571
+ _hideTooltip() {
572
+ if (this.selection.start === undefined) {
573
+ this.tooltip.classList.add("hidden");
574
+ this.tooltip.classList.remove("draggable");
575
+ this.tooltip.querySelector("#tooltip-close").classList.add("hidden");
576
+ }
577
+ }
578
+
579
+ _displayTooltip() {
580
+ this.tooltip.classList.remove("hidden");
581
+ }
582
+
583
+ _getElementInterval(nodeElement) {
584
+ let rect = this.shadowRoot.querySelector(".wrapper").getBoundingClientRect();
585
+ let bin = nodeElement;
586
+ let interval = [bin.getBoundingClientRect().x, bin.getBoundingClientRect().x + bin.getBoundingClientRect().width]
587
+ let x1 = interval[0] - rect.left + 1; //x position within the element.
588
+ let x2 = interval[1] - rect.left + 1; //x position within the element.
589
+ return [x1, x2, rect.width / 2];
590
+ }
591
+
592
+ _getSelectionInterval() {
593
+ return [this.selection.start, this.selection.end].sort((a, b) => a - b);
594
+ }
595
+
596
+ _getSelectedBins() {
597
+ return Array.prototype.slice.call(this.bins).filter(binContainer => {
598
+ return binContainer.classList.contains("selected");
599
+ });
600
+ }
601
+
602
+ _resetSelectionProperty() {
603
+ this.selection = {
604
+ start: undefined,
605
+ end: undefined
606
+ }
607
+ }
608
+
609
+ _applySelectionToBins() {
610
+ const selectionInterval = this._getSelectionInterval();
611
+ this.bins.forEach(bin => {
612
+ const elInterval = this._getElementInterval(bin);
613
+ // if (this.intervalsOverlapping(elInterval, selectionInterval)) {
614
+ if (this._areOverlapping(elInterval, selectionInterval)) {
615
+ bin.classList.add("selected");
616
+ } else {
617
+ bin.classList.remove("selected");
618
+ }
619
+ })
620
+ }
621
+
622
+ _numberWithCommas(input) {
623
+ return input.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, "'");
624
+ }
625
+
626
+ _areOverlapping(A, B) { // check if 2 intervals are overlapping
627
+ return B[0] < A[0] ? B[1] > A[0] : B[0] < A[1];
628
+ }
629
+
630
+ render() {
631
+ return html`
632
+ <div class="label" part="label">
633
+ <span class="label"><slot name="label"></slot>${this.label}</span>
634
+ ${
635
+ this.resettable ? html`
636
+ <paper-icon-button id="clear" icon="icons:clear" title="${translate('timeline.clear')}"
637
+ @click="${this._dispatchPbTimelineResetSelectionEvent}"></paper-icon-button>
638
+ ` : null
639
+ }
640
+ </div>
641
+ <div class="wrapper ${!this.dataObj || this.dataObj.data.length <= 1 ? 'empty' : ''}"
642
+ @mouseenter="${this._mouseenter}"
643
+ @mouseleave="${this._hideTooltip}">
644
+ ${this.dataObj ? this.renderBins() : ""}
645
+ ${this.renderTooltip()}
646
+ <iron-ajax
647
+ id="loadData"
648
+ verbose
649
+ handle-as="json"
650
+ method="get"
651
+ with-credentials
652
+ @response="${this._handleResponse}"
653
+ url="${this.url}?start=${this.startDate}&end=${this.endDate}"
654
+ ?auto="${this.auto}"></iron-ajax>
655
+ </div>
656
+ `;
657
+ }
658
+
659
+ renderTooltip() {
660
+ return html`
661
+ <div id="tooltip" class="hidden" part="tooltip">
662
+ <div id="tooltip-text"></div>
663
+ <div
664
+ id="tooltip-close"
665
+ class="hidden"
666
+ @click="${this._dispatchPbTimelineResetSelectionEvent}"
667
+ ><span class="close rounded black"></span>
668
+ </div>
669
+ </div>
670
+ `;
671
+ }
672
+
673
+ renderBins() {
674
+ return html`
675
+ ${this.dataObj.data.map((binObj, indx) => {
676
+ return html`
677
+ <div class="bin-container ${binObj.seperator ? "border-left" : ""}
678
+ ${indx % 2 === 0 ? "grey" : "white"} ${binObj.category === '?' ? 'unknown' : ''}"
679
+ data-tooltip="${binObj.tooltip}"
680
+ data-category="${binObj.category}"
681
+ data-selectionstart="${binObj.selectionStart}"
682
+ data-selectionend="${binObj.selectionEnd}"
683
+ data-isodatestr="${binObj.dateStr}"
684
+ data-datestr="${binObj.dateStr}"
685
+ data-value="${binObj.value}"
686
+ @mousemove="${this._mouseMove}"
687
+ @mousedown="${this._mouseDown}">
688
+ <div class="bin" style="height: ${(binObj.value / this.maxValue) * this.maxHeight * this.multiplier}px"></div>
689
+ <p class="bin-title
690
+ ${this.dataObj.binTitleRotated ? "rotated" : ""}
691
+ ${this.scope}"
692
+ >${binObj.binTitle ? binObj.binTitle : ""}
693
+ </p>
694
+ ${binObj.title ? html`
695
+ <p class="bins-title" part="title">${binObj.title}</p>
696
+ ` : ""}
697
+ ${this.renderInfo(binObj)}
698
+ </div>
699
+ `;
700
+ })}
701
+ `;
702
+ }
703
+
704
+ renderInfo(binObj) {
705
+ if (binObj.info && binObj.info.length > 0 && binObj.info.length <= 10) {
706
+ return html`
707
+ <ul class="info">
708
+ ${ binObj.info.map(info => html`<li>${unsafeHTML(info)}</li>`) }
709
+ </ul>
710
+ `;
711
+ }
712
+ return null;
713
+ }
714
+
715
+ async _handleResponse (){
716
+ await this.updateComplete;
717
+ const loader = this.shadowRoot.getElementById('loadData');
718
+ const data = loader.lastResponse;
719
+
720
+ let newJsonData = {};
721
+ if (this.startDate && this.endDate) {
722
+ Object.keys(data).filter(key => key >= this.startDate && key < this.endDate).forEach(key => {
723
+ newJsonData[key] = data[key];
724
+ });
725
+ } else {
726
+ newJsonData = data;
727
+ }
728
+ this.searchResult = new SearchResultService(newJsonData, 60, this.scopes);
729
+ this.setData(this.searchResult.export(this.scope));
730
+ this.dispatchEvent(new CustomEvent('pb-timeline-loaded', {
731
+ detail: {
732
+ value: true
733
+ },
734
+ composed: true,
735
+ bubbles: true
736
+ }));
737
+ }
738
+
739
+ }
740
+
741
+ customElements.define('pb-timeline', PbTimeline);