@teipublisher/pb-components 1.31.0 → 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.
- package/CHANGELOG.md +42 -0
- package/css/leaflet/MarkerCluster.Default.css +60 -0
- package/css/leaflet/MarkerCluster.css +14 -0
- package/dist/demo/demos.json +4 -1
- package/dist/demo/pb-leaflet-map.html +9 -7
- package/dist/demo/pb-leaflet-map2.html +2 -2
- package/dist/demo/pb-leaflet-map3.html +3 -5
- package/dist/demo/pb-timeline.html +122 -0
- package/dist/demo/pb-timeline2.html +94 -0
- package/dist/demo/timeline-dev-data.json +3 -0
- package/dist/es-global-bridge-6abe3a88.js +5 -0
- package/dist/pb-components-bundle.js +487 -243
- package/dist/pb-elements.json +303 -3
- package/dist/pb-leaflet-map.js +4 -8
- package/i18n/common/de.json +4 -0
- package/i18n/common/en.json +4 -0
- package/lib/leaflet-src.js +14062 -0
- package/lib/leaflet.markercluster-src.js +2718 -0
- package/package.json +2 -1
- package/pb-elements.json +303 -3
- package/src/parse-date-service.js +266 -0
- package/src/pb-browse-docs.js +1 -0
- package/src/pb-components.js +1 -0
- package/src/pb-geolocation.js +18 -0
- package/src/pb-leaflet-map.js +113 -40
- package/src/pb-split-list.js +5 -13
- package/src/pb-timeline.js +729 -0
- package/src/search-result-service.js +512 -0
|
@@ -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);
|