@inb/oeb_visualizations 0.1.1 → 0.1.2

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.
@@ -285,9 +285,11 @@ var script$1 = {
285
285
  // sneak in (it would draw green and skew the average); treat it
286
286
  // as a break, just like a null access_time.
287
287
  if (data[i].access_time !== null && !this.isErrorCode(data[i].code)) {
288
- // keep adding to subarray
288
+ // keep adding to subarray. Snap the x to the day centre so
289
+ // the online marker sits in the middle of its day, on the
290
+ // same grid as the offline/NA bars.
289
291
  subarrayTime.push(data[i].access_time);
290
- subarrayDate.push(data[i].date);
292
+ subarrayDate.push(this.dayCenter(data[i].date));
291
293
  sum += data[i].access_time;
292
294
  nonNullValues += 1;
293
295
  } else {
@@ -420,29 +422,62 @@ var script$1 = {
420
422
  const errorCodes = [400, 403, 404, 408, 500, 502, 503, 504];
421
423
  return errorCodes.includes(code);
422
424
  },
425
+ dayCenter(date) {
426
+ /*
427
+ Snaps a date to the centre (12:00) of its UTC calendar day, returned
428
+ as ms since epoch. The whole chart is a daily grid: null placeholders
429
+ arrive at 00:00Z and real probes mid-afternoon, so aligning every
430
+ point and every bar to the day centre keeps them on one grid. That is
431
+ what makes blocks sit centred on their day and prevents bars from
432
+ bleeding into a neighbouring day's measurement.
433
+ */
434
+ const d = new Date(date);
435
+ d.setUTCHours(0, 0, 0, 0);
436
+ return d.getTime() + 1000 * 3600 * 24 / 2;
437
+ },
438
+ computeDaySlots(data) {
439
+ /*
440
+ Returns, for every data point, the bar slot it should occupy: a full
441
+ calendar day centred on that day (see dayCenter). Consecutive
442
+ offline/NA days therefore tile into a single block, and because every
443
+ online point is snapped to the same daily grid a bar never overlaps a
444
+ measurement.
445
+ Arguments:
446
+ - data: array of objects with a "date" key
447
+ Returns an array (parallel to data) of objects with keys:
448
+ - center: slot centre (day centre), in ms since epoch
449
+ - width: slot width, in ms (one full day)
450
+ */
451
+ const DAY = 1000 * 3600 * 24;
452
+ return data.map(item => ({
453
+ center: this.dayCenter(item.date),
454
+ width: DAY
455
+ }));
456
+ },
423
457
  extractOfflineNADates(data) {
424
458
  /*
425
- This function extracts dates with access_time null.
426
- Depending on the code, the server is offline or NA on those dates.
459
+ This function extracts the bar slots of points with no successful
460
+ measurement. Depending on the code, the server is offline or NA on
461
+ those dates.
427
462
  Arguments:
428
463
  - data: array of objects with keys "access_time", "date" and "code". access_time and code can be null
429
-
430
- Returns an object with keys:
431
- - NA: array of dates with code null (no information available)
432
- - down: array of dates with code in errorCodes (server offline)
464
+ Returns an object with keys:
465
+ - NA: array of slots (see computeDaySlots) for code null (no information available)
466
+ - down: array of slots for code in errorCodes (server offline)
433
467
  Classification is driven by the HTTP code, not by access_time
434
468
  null-ness: an error response that happened to record an access_time
435
469
  is still "offline", and a missing code is "no information available".
436
470
  */
471
+ const slots = this.computeDaySlots(data);
437
472
  const resultNA = [];
438
473
  const resultOffline = [];
439
474
  for (let i = 0; i < data.length; i++) {
440
475
  // no code at all -> grey "No information available"
441
476
  if (data[i].code === null) {
442
- resultNA.push(data[i].date);
477
+ resultNA.push(slots[i]);
443
478
  // an error code -> red "Offline"
444
479
  } else if (this.isErrorCode(data[i].code)) {
445
- resultOffline.push(data[i].date);
480
+ resultOffline.push(slots[i]);
446
481
  }
447
482
  }
448
483
  return {
@@ -454,10 +489,12 @@ var script$1 = {
454
489
  /*
455
490
  This function builds a bar trace.
456
491
  Arguments:
457
- - data: object with keys "dates" and "access_times"
458
- - dates: array of dates
492
+ - data: object with keys "dates", "access_times", "colors" and "widths"
493
+ - dates: array of dates (slot centres, in ms)
459
494
  - access_times: array of access_times
460
495
  - colors: array of colors in rgba format (eg:"rgba(255, 153, 145,0.8)")
496
+ - widths: array of per-bar widths in ms, so each bar fills its
497
+ own time slot (see computeDaySlots) instead of a fixed day
461
498
  - name: name of the trace
462
499
  */
463
500
  const trace = {
@@ -472,35 +509,43 @@ var script$1 = {
472
509
  showlegend: showlegend,
473
510
  hoverinfo: hoverinfo,
474
511
  hovertemplate: hovertemplate,
475
- width: 1000 * 3600 * 24 * 1 // 100% of the space between two dates
512
+ width: data.widths
476
513
  };
477
-
478
- console.log(trace);
479
514
  return trace;
480
515
  },
481
- buildBarTraces(dates, color, label, group) {
516
+ buildBarTraces(slots, color, label, group) {
482
517
  /*
483
518
  This function builds the series used to display the offline/NA bars.
484
- For each date, two columns are created: one very short and one tall.
485
- These columns cover the whole plot height.
486
- The tall column is colored with colorStrong and the short one with colorLight.
519
+ For each slot two stacked columns are created that together span the
520
+ whole plot height: a thin strong-coloured band sitting on the axis,
521
+ and a light-coloured column filling the rest of the height above it.
487
522
  Arguments:
488
- - dates: array of dates
523
+ - slots: array of slots ({ center, width }), see computeDaySlots
489
524
  - color: color of the bars
490
525
  Returns array with offline and NA traces
491
526
  */
492
527
 
493
528
  const colorStrong = `rgba(${color},.8)`;
494
529
  const colorLight = `rgba(${color},.2)`;
530
+ const centers = slots.map(s => s.center);
531
+ const widths = slots.map(s => s.width);
532
+
533
+ // Total background height (keeps the previous y-axis extent). The
534
+ // strong colour is a thin band on the axis; the light colour fills
535
+ // the remaining height stacked on top of it.
536
+ const totalHeight = 2 + this.max_access_time * 1.1;
537
+ const baseHeight = totalHeight * 0.03;
495
538
  const dataShort = {
496
- dates: dates,
497
- access_times: Array(dates.length).fill(2),
498
- colors: Array(dates.length).fill(colorStrong)
539
+ dates: centers,
540
+ access_times: Array(slots.length).fill(baseHeight),
541
+ colors: Array(slots.length).fill(colorStrong),
542
+ widths: widths
499
543
  };
500
544
  const dataTall = {
501
- dates: dates,
502
- access_times: Array(dates.length).fill(this.max_access_time * 1.1),
503
- colors: Array(dates.length).fill(colorLight)
545
+ dates: centers,
546
+ access_times: Array(slots.length).fill(totalHeight - baseHeight),
547
+ colors: Array(slots.length).fill(colorLight),
548
+ widths: widths
504
549
  };
505
550
  const hovertemplate = `<b>${label}</b><br>%{x|%d %b %Y}<br>%{y} ms <extra></extra>`;
506
551
  const traceShort = this.barTrace(dataShort, label, false, 'skip', '', group);
@@ -522,12 +567,10 @@ var script$1 = {
522
567
  // Down arrays
523
568
  if (arrays.down.length > 0) {
524
569
  traces = traces.concat(this.buildBarTraces(arrays.down, this.colorOffline, 'Offline', 'down'));
525
- console.log(arrays.down);
526
570
  }
527
571
  // NA arrays
528
572
  if (arrays.NA.length > 0) {
529
573
  traces = traces.concat(this.buildBarTraces(arrays.NA, this.colorNA, 'No information available', 'na'));
530
- console.log(arrays.NA);
531
574
  }
532
575
  return traces;
533
576
  },
@@ -578,7 +621,10 @@ var script$1 = {
578
621
  console.log('six months ago: ' + new Date(lastDate.setMonth(lastDate.getMonth() - 6)));
579
622
  return new Date(lastDate.setMonth(lastDate.getMonth() - 6));
580
623
  } else {
581
- return this.dataItems[0];
624
+ // Anchor day ticks at the day centre (noon) so each label sits
625
+ // under the middle of its day block, aligned with the online dot
626
+ // (which is snapped to the same day centre).
627
+ return this.dayCenter(this.dataItems[0].date);
582
628
  }
583
629
  },
584
630
  xaxisMode() {
@@ -589,11 +635,17 @@ var script$1 = {
589
635
  }
590
636
  },
591
637
  xaxisRange() {
592
- if (!this.xrange) {
593
- return [this.dataItems[0], this.dataItems[this.dataItems.length - 1]];
594
- } else {
638
+ if (this.xrange) {
595
639
  return this.xrange;
596
640
  }
641
+ // Span whole calendar days so the first and last day blocks (which
642
+ // are centred on their day, one full day wide) are fully visible.
643
+ const DAY = 1000 * 3600 * 24;
644
+ const firstDay = new Date(this.dataItems[0].date);
645
+ firstDay.setUTCHours(0, 0, 0, 0);
646
+ const lastDay = new Date(this.dataItems[this.dataItems.length - 1].date);
647
+ lastDay.setUTCHours(0, 0, 0, 0);
648
+ return [firstDay.getTime(), lastDay.getTime() + DAY];
597
649
  },
598
650
  monthTickVales() {
599
651
  // First day of month of last date and previous five months
@@ -711,7 +763,7 @@ const __vue_inject_styles__$1 = undefined;
711
763
  /* scoped */
712
764
  const __vue_scope_id__$1 = undefined;
713
765
  /* module identifier */
714
- const __vue_module_identifier__$1 = "data-v-6f184d3f";
766
+ const __vue_module_identifier__$1 = "data-v-bab9d544";
715
767
  /* functional template */
716
768
  const __vue_is_functional_template__$1 = false;
717
769
  /* style inject */
@@ -289,9 +289,11 @@ var script$1 = {
289
289
  // sneak in (it would draw green and skew the average); treat it
290
290
  // as a break, just like a null access_time.
291
291
  if (data[i].access_time !== null && !this.isErrorCode(data[i].code)) {
292
- // keep adding to subarray
292
+ // keep adding to subarray. Snap the x to the day centre so
293
+ // the online marker sits in the middle of its day, on the
294
+ // same grid as the offline/NA bars.
293
295
  subarrayTime.push(data[i].access_time);
294
- subarrayDate.push(data[i].date);
296
+ subarrayDate.push(this.dayCenter(data[i].date));
295
297
  sum += data[i].access_time;
296
298
  nonNullValues += 1;
297
299
  } else {
@@ -424,29 +426,62 @@ var script$1 = {
424
426
  const errorCodes = [400, 403, 404, 408, 500, 502, 503, 504];
425
427
  return errorCodes.includes(code);
426
428
  },
429
+ dayCenter(date) {
430
+ /*
431
+ Snaps a date to the centre (12:00) of its UTC calendar day, returned
432
+ as ms since epoch. The whole chart is a daily grid: null placeholders
433
+ arrive at 00:00Z and real probes mid-afternoon, so aligning every
434
+ point and every bar to the day centre keeps them on one grid. That is
435
+ what makes blocks sit centred on their day and prevents bars from
436
+ bleeding into a neighbouring day's measurement.
437
+ */
438
+ const d = new Date(date);
439
+ d.setUTCHours(0, 0, 0, 0);
440
+ return d.getTime() + 1000 * 3600 * 24 / 2;
441
+ },
442
+ computeDaySlots(data) {
443
+ /*
444
+ Returns, for every data point, the bar slot it should occupy: a full
445
+ calendar day centred on that day (see dayCenter). Consecutive
446
+ offline/NA days therefore tile into a single block, and because every
447
+ online point is snapped to the same daily grid a bar never overlaps a
448
+ measurement.
449
+ Arguments:
450
+ - data: array of objects with a "date" key
451
+ Returns an array (parallel to data) of objects with keys:
452
+ - center: slot centre (day centre), in ms since epoch
453
+ - width: slot width, in ms (one full day)
454
+ */
455
+ const DAY = 1000 * 3600 * 24;
456
+ return data.map(item => ({
457
+ center: this.dayCenter(item.date),
458
+ width: DAY
459
+ }));
460
+ },
427
461
  extractOfflineNADates(data) {
428
462
  /*
429
- This function extracts dates with access_time null.
430
- Depending on the code, the server is offline or NA on those dates.
463
+ This function extracts the bar slots of points with no successful
464
+ measurement. Depending on the code, the server is offline or NA on
465
+ those dates.
431
466
  Arguments:
432
467
  - data: array of objects with keys "access_time", "date" and "code". access_time and code can be null
433
-
434
- Returns an object with keys:
435
- - NA: array of dates with code null (no information available)
436
- - down: array of dates with code in errorCodes (server offline)
468
+ Returns an object with keys:
469
+ - NA: array of slots (see computeDaySlots) for code null (no information available)
470
+ - down: array of slots for code in errorCodes (server offline)
437
471
  Classification is driven by the HTTP code, not by access_time
438
472
  null-ness: an error response that happened to record an access_time
439
473
  is still "offline", and a missing code is "no information available".
440
474
  */
475
+ const slots = this.computeDaySlots(data);
441
476
  const resultNA = [];
442
477
  const resultOffline = [];
443
478
  for (let i = 0; i < data.length; i++) {
444
479
  // no code at all -> grey "No information available"
445
480
  if (data[i].code === null) {
446
- resultNA.push(data[i].date);
481
+ resultNA.push(slots[i]);
447
482
  // an error code -> red "Offline"
448
483
  } else if (this.isErrorCode(data[i].code)) {
449
- resultOffline.push(data[i].date);
484
+ resultOffline.push(slots[i]);
450
485
  }
451
486
  }
452
487
  return {
@@ -458,10 +493,12 @@ var script$1 = {
458
493
  /*
459
494
  This function builds a bar trace.
460
495
  Arguments:
461
- - data: object with keys "dates" and "access_times"
462
- - dates: array of dates
496
+ - data: object with keys "dates", "access_times", "colors" and "widths"
497
+ - dates: array of dates (slot centres, in ms)
463
498
  - access_times: array of access_times
464
499
  - colors: array of colors in rgba format (eg:"rgba(255, 153, 145,0.8)")
500
+ - widths: array of per-bar widths in ms, so each bar fills its
501
+ own time slot (see computeDaySlots) instead of a fixed day
465
502
  - name: name of the trace
466
503
  */
467
504
  const trace = {
@@ -476,35 +513,43 @@ var script$1 = {
476
513
  showlegend: showlegend,
477
514
  hoverinfo: hoverinfo,
478
515
  hovertemplate: hovertemplate,
479
- width: 1000 * 3600 * 24 * 1 // 100% of the space between two dates
516
+ width: data.widths
480
517
  };
481
-
482
- console.log(trace);
483
518
  return trace;
484
519
  },
485
- buildBarTraces(dates, color, label, group) {
520
+ buildBarTraces(slots, color, label, group) {
486
521
  /*
487
522
  This function builds the series used to display the offline/NA bars.
488
- For each date, two columns are created: one very short and one tall.
489
- These columns cover the whole plot height.
490
- The tall column is colored with colorStrong and the short one with colorLight.
523
+ For each slot two stacked columns are created that together span the
524
+ whole plot height: a thin strong-coloured band sitting on the axis,
525
+ and a light-coloured column filling the rest of the height above it.
491
526
  Arguments:
492
- - dates: array of dates
527
+ - slots: array of slots ({ center, width }), see computeDaySlots
493
528
  - color: color of the bars
494
529
  Returns array with offline and NA traces
495
530
  */
496
531
 
497
532
  const colorStrong = `rgba(${color},.8)`;
498
533
  const colorLight = `rgba(${color},.2)`;
534
+ const centers = slots.map(s => s.center);
535
+ const widths = slots.map(s => s.width);
536
+
537
+ // Total background height (keeps the previous y-axis extent). The
538
+ // strong colour is a thin band on the axis; the light colour fills
539
+ // the remaining height stacked on top of it.
540
+ const totalHeight = 2 + this.max_access_time * 1.1;
541
+ const baseHeight = totalHeight * 0.03;
499
542
  const dataShort = {
500
- dates: dates,
501
- access_times: Array(dates.length).fill(2),
502
- colors: Array(dates.length).fill(colorStrong)
543
+ dates: centers,
544
+ access_times: Array(slots.length).fill(baseHeight),
545
+ colors: Array(slots.length).fill(colorStrong),
546
+ widths: widths
503
547
  };
504
548
  const dataTall = {
505
- dates: dates,
506
- access_times: Array(dates.length).fill(this.max_access_time * 1.1),
507
- colors: Array(dates.length).fill(colorLight)
549
+ dates: centers,
550
+ access_times: Array(slots.length).fill(totalHeight - baseHeight),
551
+ colors: Array(slots.length).fill(colorLight),
552
+ widths: widths
508
553
  };
509
554
  const hovertemplate = `<b>${label}</b><br>%{x|%d %b %Y}<br>%{y} ms <extra></extra>`;
510
555
  const traceShort = this.barTrace(dataShort, label, false, 'skip', '', group);
@@ -526,12 +571,10 @@ var script$1 = {
526
571
  // Down arrays
527
572
  if (arrays.down.length > 0) {
528
573
  traces = traces.concat(this.buildBarTraces(arrays.down, this.colorOffline, 'Offline', 'down'));
529
- console.log(arrays.down);
530
574
  }
531
575
  // NA arrays
532
576
  if (arrays.NA.length > 0) {
533
577
  traces = traces.concat(this.buildBarTraces(arrays.NA, this.colorNA, 'No information available', 'na'));
534
- console.log(arrays.NA);
535
578
  }
536
579
  return traces;
537
580
  },
@@ -582,7 +625,10 @@ var script$1 = {
582
625
  console.log('six months ago: ' + new Date(lastDate.setMonth(lastDate.getMonth() - 6)));
583
626
  return new Date(lastDate.setMonth(lastDate.getMonth() - 6));
584
627
  } else {
585
- return this.dataItems[0];
628
+ // Anchor day ticks at the day centre (noon) so each label sits
629
+ // under the middle of its day block, aligned with the online dot
630
+ // (which is snapped to the same day centre).
631
+ return this.dayCenter(this.dataItems[0].date);
586
632
  }
587
633
  },
588
634
  xaxisMode() {
@@ -593,11 +639,17 @@ var script$1 = {
593
639
  }
594
640
  },
595
641
  xaxisRange() {
596
- if (!this.xrange) {
597
- return [this.dataItems[0], this.dataItems[this.dataItems.length - 1]];
598
- } else {
642
+ if (this.xrange) {
599
643
  return this.xrange;
600
644
  }
645
+ // Span whole calendar days so the first and last day blocks (which
646
+ // are centred on their day, one full day wide) are fully visible.
647
+ const DAY = 1000 * 3600 * 24;
648
+ const firstDay = new Date(this.dataItems[0].date);
649
+ firstDay.setUTCHours(0, 0, 0, 0);
650
+ const lastDay = new Date(this.dataItems[this.dataItems.length - 1].date);
651
+ lastDay.setUTCHours(0, 0, 0, 0);
652
+ return [firstDay.getTime(), lastDay.getTime() + DAY];
601
653
  },
602
654
  monthTickVales() {
603
655
  // First day of month of last date and previous five months
@@ -1 +1 @@
1
- var oeb_visualizations=function(e,t){"use strict";function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var s=i(t);function a(e){return Math.random().toString(36).replace("0.",e||"")}var r=function(e,t,i,s,a,r,o,n,l,d){"boolean"!=typeof o&&(l=n,n=o,o=!1);var c,h="function"==typeof i?i.options:i;if(e&&e.render&&(h.render=e.render,h.staticRenderFns=e.staticRenderFns,h._compiled=!0,a&&(h.functional=!0)),s&&(h._scopeId=s),r?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),t&&t.call(this,l(e)),e&&e._registeredComponents&&e._registeredComponents.add(r)},h._ssrRegister=c):t&&(c=o?function(){t.call(this,d(this.$root.$options.shadowRoot))}:function(e){t.call(this,n(e))}),c)if(h.functional){var u=h.render;h.render=function(e,t){return c.call(t),u(e,t)}}else{var m=h.beforeCreate;h.beforeCreate=m?[].concat(m,c):[c]}return i};var o=r({render:function(){var e=this,t=e.$createElement;return(e._self._c||t)("div",{attrs:{id:e.divId}})},staticRenderFns:[]},undefined,{name:"accessibilityPlot",data:()=>({divId:a("acc_plot_"),max_access_time:0,config:{displaylogo:!1,responsive:!0,modeBarButtonsToRemove:["lasso"]}}),props:{xrange:{type:Array,required:!1},dtick:{type:Number,required:!1,default:864e5},xaxisTitle:{type:String,required:!1,default:"Date"},yaxisTitle:{type:String,required:!1,default:"Access time (ms)"},colorOnline:{type:String,required:!1,default:"111, 176, 129"},colorOffline:{type:String,required:!1,default:"255, 153, 145"},colorNA:{type:String,required:!1,default:"204,204,204"},height:{type:Number,required:!1,default:350},width:{type:Number,required:!1,default:700},week:{type:Boolean,required:!1,default:!1},sixMonths:{type:Boolean,required:!1,default:!1},dataItems:{type:Array,required:!0,validator:function(e){for(let t=0;t<e.length;t++){e[t].hasOwnProperty("access_time")?e[t].hasOwnProperty("date")?e[t].hasOwnProperty("code")||console.error(`[oeb-visualizations warn] code key is missing in dataItems prop item (at position ${t})`):console.error(`[oeb-visualizations warn] date key is missing in dataItems prop item (at position ${t})`):console.error(`[oeb-visualizations warn] access_time key is missing in dataItems prop item (at position ${t})`),null!==e[t].access_time&&"number"!=typeof e[t].access_time&&console.error(`[oeb-visualizations warn] access_time must be null or a number in dataItems prop item (at position ${t})`),null!==e[t].code&&"number"!=typeof e[t].code&&console.error(`[oeb-visualizations warn] code must be null or a number in dataItems prop item (at position ${t})`);const i="string"==typeof e[t].date&&!isNaN(Date.parse(e[t].date)),s="number"==typeof e[t].date;i||s||(console.error(`[oeb-visualizations warn] date must be a string containing a date or a number in dataItems prop item (at position ${t})`),console.error(`[oeb-visualizations warn] date type is ${typeof e[t].date} and the value is ${e[t].date}`)),null===e[t].date&&console.error(`[oeb-visualizations warn] date cannot be null in dataItems prop item (at position ${t})`)}return!0}}},mounted(){const e=this.dataItems.map((e=>e.access_time)).filter((e=>"number"==typeof e&&!isNaN(e)));this.max_access_time=e.length?Math.max(...e):100;var t=this.buildOnlineTraces(this.dataItems);t=t.concat(this.buildOfflineNATraces(this.dataItems));const i={bargap:0,barmode:"stack",showlegend:!0,autosize:!0,height:this.height,width:this.width,margin:{l:50,r:50,b:50,t:20,pad:4},xaxis:{type:"date",ticklabelmode:this.xaxisMode(),title:this.xaxisTitle,font:{size:10},tickfont:{size:10},showgrid:!!this.sixMonths,griddash:"dot",gridwidth:1,gridcolor:"#d9d7d7",showspikes:!0,spikedash:"4px",spikethickness:1,tick0:this.xaxisTickZero(),dtick:this.xaxisTickD(),tickangle:this.xaxisTickAngle(),tickformat:this.xaxisTickFormat(),tickvals:this.sixMonths?this.monthTickVales():null,range:this.xaxisRange()},yaxis:{title:this.yaxisTitle,titlefont:{size:10},tickfont:{size:10}},template:"plotly_white",legend:{orientation:"h",yanchor:"bottom",y:-.5,xanchor:"left",x:.05,font:{size:8}},hoverlabel:{bgcolor:"#FFF"},hovermode:"closest"};console.log(this.divId),s.default.newPlot(this.divId,t,i,this.config)},methods:{generateColor(e,t){let i=[];for(let s=0;s<30;s++)switch(e[s]){case"up":i.push(`rgba(${this.colorOnline},0)`);break;case"down":i.push(`rgba(${this.colorOffline},${t})`);break;case"NA":i.push(`rgba(${this.colorNA},${t})`)}return i},extractSubarraysBetweenNullValues(e){var t={access_time:[],date:[],average_access_time:0},i=[],s=[],a=0,r=0;for(let o=0;o<e.length;o++)null===e[o].access_time||this.isErrorCode(e[o].code)?i.length>0&&(t.access_time.push(i),t.date.push(s),i=[],s=[]):(i.push(e[o].access_time),s.push(e[o].date),a+=e[o].access_time,r+=1);return t.access_time.push(i),t.date.push(s),t.average_access_time=a/r,t},buildAccessTimeTraces(e){var t=[];for(let i=0;i<e.access_time.length;i++){const s=e.access_time[i],a={x:e.date[i],y:s,name:"Online",legendgroup:"up",showlegend:0===i,mode:"markers+lines",type:"scatter",fill:"tozeroy",fillcolor:`rgba(${this.colorOnline},.2)`,connectgaps:!1,line:{color:`rgba(${this.colorOnline},.8)`,width:1.5},marker:{size:5},hovertemplate:"<b>Online</b><br>%{x|%d %b %Y}<br>%{y} ms <extra></extra>",hoveron:"points+fills"};t.push(a)}return t},buildAvgAccessTimeTrace(e){const t=new Date(this.dataItems[0].date);this.week?t.setDate(t.getDate()-.3):t.setDate(t.getDate()-1);const i=new Date(this.dataItems[this.dataItems.length-1].date);this.week?i.setDate(i.getDate()+.3):i.setDate(i.getDate()+1);return{x:[t,i],y:[e,e],name:"Average access time",showlegend:!0,mode:"lines",type:"scatter",line:{color:`rgba(${this.colorOnline},.6)`,width:1.5,dash:"4px"},marker:{size:5},hovertemplate:"<b>Average access time</b><br>%{y:.2f} ms <extra></extra>"}},buildOnlineTraces(e){const t=this.extractSubarraysBetweenNullValues(e);var i=this.buildAccessTimeTraces(t);return i.push(this.buildAvgAccessTimeTrace(t.average_access_time)),i},isErrorCode:e=>[400,403,404,408,500,502,503,504].includes(e),extractOfflineNADates(e){const t=[],i=[];for(let s=0;s<e.length;s++)null===e[s].code?t.push(e[s].date):this.isErrorCode(e[s].code)&&i.push(e[s].date);return{NA:t,down:i}},barTrace(e,t,i,s,a,r){const o={x:e.dates,y:e.access_times,marker:{color:e.colors},name:t,type:"bar",legendgroup:r,showlegend:i,hoverinfo:s,hovertemplate:a,width:864e5};return console.log(o),o},buildBarTraces(e,t,i,s){const a=`rgba(${t},.8)`,r=`rgba(${t},.2)`,o={dates:e,access_times:Array(e.length).fill(2),colors:Array(e.length).fill(a)},n={dates:e,access_times:Array(e.length).fill(1.1*this.max_access_time),colors:Array(e.length).fill(r)},l=`<b>${i}</b><br>%{x|%d %b %Y}<br>%{y} ms <extra></extra>`;return[this.barTrace(o,i,!1,"skip","",s),this.barTrace(n,i,!0,"all",l,s)]},buildOfflineNATraces(e){var t=[];const i=this.extractOfflineNADates(e);return i.down.length>0&&(t=t.concat(this.buildBarTraces(i.down,this.colorOffline,"Offline","down")),console.log(i.down)),i.NA.length>0&&(t=t.concat(this.buildBarTraces(i.NA,this.colorNA,"No information available","na")),console.log(i.NA)),t},xaxisTickFormat(){return this.week?"%A<br>%d %b":(this.sixMonths,"%d %b")},xaxisTickAngle(){return this.week?0:(this.sixMonths,45)},xaxisTickD(){return!0===this.sixMonths?"M1":this.dtick},xaxisTickZero(){if(!0===this.sixMonths){console.log("zero six months ago");const e=new Date(this.dataItems[this.dataItems.length-1].date);return console.log(e),console.log("six months ago: "+new Date(e.setMonth(e.getMonth()-6))),new Date(e.setMonth(e.getMonth()-6))}return this.dataItems[0]},xaxisMode(){return!0===this.sixMonths?"period":"instant"},xaxisRange(){return this.xrange?this.xrange:[this.dataItems[0],this.dataItems[this.dataItems.length-1]]},monthTickVales(){const e=new Date(this.dataItems[this.dataItems.length-1].date),t=e.getMonth(),i=e.getFullYear(),s=[];for(let e=0;e<6;e++)s.push(new Date(i,t-e,1));return s}}},undefined,!1,undefined,void 0,void 0);var n=r({render:function(){var e=this,t=e.$createElement;return(e._self._c||t)("div",{attrs:{id:e.divId}})},staticRenderFns:[]},undefined,{name:"citationsPlot",data:()=>({divId:a("cit_plot_")}),props:{dataTraces:{type:Array,required:!0},stack:{type:Boolean,required:!1,default:!1},colors:{type:Array,required:!1,default:()=>["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd"]},height:{type:Number,required:!1,default:400},showlegend:{type:Boolean,required:!1,default:!0},title:{type:String,required:!1,default:""},xaxisTitle:{type:String,required:!1,default:"Year"},yaxisTitle:{type:String,required:!1,default:"Number of citations"},darkMode:{type:Boolean,required:!1,default:!1}},mounted(){const e=this.buildTraces(),t={showlegend:this.showlegend,autosize:!0,height:this.height,margin:{l:50,r:50,b:70,t:50,pad:4},xaxis:{title:this.xaxisTitle,font:{size:10},tickfont:{size:10},tickmode:"linear",color:this.darkMode?"white":"black"},yaxis:{title:this.yaxisTitle,titlefont:{size:10},tickfont:{size:10},font:{size:10},color:this.darkMode?"white":"black"},legend:{orientation:"h",yanchor:"bottom",y:1.02,xanchor:"right",x:1,font:{size:8,color:this.darkMode?"white":"black"}},hoverlabel:{color:this.darkMode?"white":"black"},hovermode:this.stack?"x unified":"closest",hoverdistance:70,plot_bgcolor:this.darkMode?"rgb(38, 50, 56)":"white",paper_bgcolor:this.darkMode?"rgb(38, 50, 56)":"white"};s.default.newPlot(this.divId,e,t)},methods:{buildTraces(){const e=[];for(let t=0;t<this.dataTraces.length;t++){const i={x:this.dataTraces[t].data.map((e=>e.year)),y:this.dataTraces[t].data.map((e=>e.citations)),mode:"lines+markers",name:this.dataTraces[t].label,hovertemplate:this.hoverTemplate(),marker:{size:5},line:{color:this.colors[t],width:1.8},stackgroup:this.stack?"one":null};e.push(i)}return e},hoverTemplate(){return this.stack?"%{y} citations <extra></extra>":"%{y} citations in %{x} <extra></extra>"}}},undefined,!1,undefined,void 0,void 0),l=Object.freeze({__proto__:null,accessibilityPlot:o,citationsPlot:n});const d=function(e){d.installed||(d.installed=!0,Object.entries(l).forEach((([t,i])=>{e.component(t,i)})))},c={install:d};{let e=null;"undefined"!=typeof window?e=window.Vue:"undefined"!=typeof global&&(e=global.Vue),e&&e.use(c)}return e.accessibilityPlot=o,e.citationsPlot=n,e.default=c,Object.defineProperty(e,"__esModule",{value:!0}),e}({},Plotly);
1
+ var oeb_visualizations=function(e,t){"use strict";function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var s=i(t);function a(e){return Math.random().toString(36).replace("0.",e||"")}var r=function(e,t,i,s,a,r,o,n,l,d){"boolean"!=typeof o&&(l=n,n=o,o=!1);var c,h="function"==typeof i?i.options:i;if(e&&e.render&&(h.render=e.render,h.staticRenderFns=e.staticRenderFns,h._compiled=!0,a&&(h.functional=!0)),s&&(h._scopeId=s),r?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),t&&t.call(this,l(e)),e&&e._registeredComponents&&e._registeredComponents.add(r)},h._ssrRegister=c):t&&(c=o?function(){t.call(this,d(this.$root.$options.shadowRoot))}:function(e){t.call(this,n(e))}),c)if(h.functional){var u=h.render;h.render=function(e,t){return c.call(t),u(e,t)}}else{var m=h.beforeCreate;h.beforeCreate=m?[].concat(m,c):[c]}return i};var o=r({render:function(){var e=this,t=e.$createElement;return(e._self._c||t)("div",{attrs:{id:e.divId}})},staticRenderFns:[]},undefined,{name:"accessibilityPlot",data:()=>({divId:a("acc_plot_"),max_access_time:0,config:{displaylogo:!1,responsive:!0,modeBarButtonsToRemove:["lasso"]}}),props:{xrange:{type:Array,required:!1},dtick:{type:Number,required:!1,default:864e5},xaxisTitle:{type:String,required:!1,default:"Date"},yaxisTitle:{type:String,required:!1,default:"Access time (ms)"},colorOnline:{type:String,required:!1,default:"111, 176, 129"},colorOffline:{type:String,required:!1,default:"255, 153, 145"},colorNA:{type:String,required:!1,default:"204,204,204"},height:{type:Number,required:!1,default:350},width:{type:Number,required:!1,default:700},week:{type:Boolean,required:!1,default:!1},sixMonths:{type:Boolean,required:!1,default:!1},dataItems:{type:Array,required:!0,validator:function(e){for(let t=0;t<e.length;t++){e[t].hasOwnProperty("access_time")?e[t].hasOwnProperty("date")?e[t].hasOwnProperty("code")||console.error(`[oeb-visualizations warn] code key is missing in dataItems prop item (at position ${t})`):console.error(`[oeb-visualizations warn] date key is missing in dataItems prop item (at position ${t})`):console.error(`[oeb-visualizations warn] access_time key is missing in dataItems prop item (at position ${t})`),null!==e[t].access_time&&"number"!=typeof e[t].access_time&&console.error(`[oeb-visualizations warn] access_time must be null or a number in dataItems prop item (at position ${t})`),null!==e[t].code&&"number"!=typeof e[t].code&&console.error(`[oeb-visualizations warn] code must be null or a number in dataItems prop item (at position ${t})`);const i="string"==typeof e[t].date&&!isNaN(Date.parse(e[t].date)),s="number"==typeof e[t].date;i||s||(console.error(`[oeb-visualizations warn] date must be a string containing a date or a number in dataItems prop item (at position ${t})`),console.error(`[oeb-visualizations warn] date type is ${typeof e[t].date} and the value is ${e[t].date}`)),null===e[t].date&&console.error(`[oeb-visualizations warn] date cannot be null in dataItems prop item (at position ${t})`)}return!0}}},mounted(){const e=this.dataItems.map((e=>e.access_time)).filter((e=>"number"==typeof e&&!isNaN(e)));this.max_access_time=e.length?Math.max(...e):100;var t=this.buildOnlineTraces(this.dataItems);t=t.concat(this.buildOfflineNATraces(this.dataItems));const i={bargap:0,barmode:"stack",showlegend:!0,autosize:!0,height:this.height,width:this.width,margin:{l:50,r:50,b:50,t:20,pad:4},xaxis:{type:"date",ticklabelmode:this.xaxisMode(),title:this.xaxisTitle,font:{size:10},tickfont:{size:10},showgrid:!!this.sixMonths,griddash:"dot",gridwidth:1,gridcolor:"#d9d7d7",showspikes:!0,spikedash:"4px",spikethickness:1,tick0:this.xaxisTickZero(),dtick:this.xaxisTickD(),tickangle:this.xaxisTickAngle(),tickformat:this.xaxisTickFormat(),tickvals:this.sixMonths?this.monthTickVales():null,range:this.xaxisRange()},yaxis:{title:this.yaxisTitle,titlefont:{size:10},tickfont:{size:10}},template:"plotly_white",legend:{orientation:"h",yanchor:"bottom",y:-.5,xanchor:"left",x:.05,font:{size:8}},hoverlabel:{bgcolor:"#FFF"},hovermode:"closest"};console.log(this.divId),s.default.newPlot(this.divId,t,i,this.config)},methods:{generateColor(e,t){let i=[];for(let s=0;s<30;s++)switch(e[s]){case"up":i.push(`rgba(${this.colorOnline},0)`);break;case"down":i.push(`rgba(${this.colorOffline},${t})`);break;case"NA":i.push(`rgba(${this.colorNA},${t})`)}return i},extractSubarraysBetweenNullValues(e){var t={access_time:[],date:[],average_access_time:0},i=[],s=[],a=0,r=0;for(let o=0;o<e.length;o++)null===e[o].access_time||this.isErrorCode(e[o].code)?i.length>0&&(t.access_time.push(i),t.date.push(s),i=[],s=[]):(i.push(e[o].access_time),s.push(this.dayCenter(e[o].date)),a+=e[o].access_time,r+=1);return t.access_time.push(i),t.date.push(s),t.average_access_time=a/r,t},buildAccessTimeTraces(e){var t=[];for(let i=0;i<e.access_time.length;i++){const s=e.access_time[i],a={x:e.date[i],y:s,name:"Online",legendgroup:"up",showlegend:0===i,mode:"markers+lines",type:"scatter",fill:"tozeroy",fillcolor:`rgba(${this.colorOnline},.2)`,connectgaps:!1,line:{color:`rgba(${this.colorOnline},.8)`,width:1.5},marker:{size:5},hovertemplate:"<b>Online</b><br>%{x|%d %b %Y}<br>%{y} ms <extra></extra>",hoveron:"points+fills"};t.push(a)}return t},buildAvgAccessTimeTrace(e){const t=new Date(this.dataItems[0].date);this.week?t.setDate(t.getDate()-.3):t.setDate(t.getDate()-1);const i=new Date(this.dataItems[this.dataItems.length-1].date);this.week?i.setDate(i.getDate()+.3):i.setDate(i.getDate()+1);return{x:[t,i],y:[e,e],name:"Average access time",showlegend:!0,mode:"lines",type:"scatter",line:{color:`rgba(${this.colorOnline},.6)`,width:1.5,dash:"4px"},marker:{size:5},hovertemplate:"<b>Average access time</b><br>%{y:.2f} ms <extra></extra>"}},buildOnlineTraces(e){const t=this.extractSubarraysBetweenNullValues(e);var i=this.buildAccessTimeTraces(t);return i.push(this.buildAvgAccessTimeTrace(t.average_access_time)),i},isErrorCode:e=>[400,403,404,408,500,502,503,504].includes(e),dayCenter(e){const t=new Date(e);return t.setUTCHours(0,0,0,0),t.getTime()+432e5},computeDaySlots(e){return e.map((e=>({center:this.dayCenter(e.date),width:864e5})))},extractOfflineNADates(e){const t=this.computeDaySlots(e),i=[],s=[];for(let a=0;a<e.length;a++)null===e[a].code?i.push(t[a]):this.isErrorCode(e[a].code)&&s.push(t[a]);return{NA:i,down:s}},barTrace:(e,t,i,s,a,r)=>({x:e.dates,y:e.access_times,marker:{color:e.colors},name:t,type:"bar",legendgroup:r,showlegend:i,hoverinfo:s,hovertemplate:a,width:e.widths}),buildBarTraces(e,t,i,s){const a=`rgba(${t},.8)`,r=`rgba(${t},.2)`,o=e.map((e=>e.center)),n=e.map((e=>e.width)),l=2+1.1*this.max_access_time,d=.03*l,c={dates:o,access_times:Array(e.length).fill(d),colors:Array(e.length).fill(a),widths:n},h={dates:o,access_times:Array(e.length).fill(l-d),colors:Array(e.length).fill(r),widths:n},u=`<b>${i}</b><br>%{x|%d %b %Y}<br>%{y} ms <extra></extra>`;return[this.barTrace(c,i,!1,"skip","",s),this.barTrace(h,i,!0,"all",u,s)]},buildOfflineNATraces(e){var t=[];const i=this.extractOfflineNADates(e);return i.down.length>0&&(t=t.concat(this.buildBarTraces(i.down,this.colorOffline,"Offline","down"))),i.NA.length>0&&(t=t.concat(this.buildBarTraces(i.NA,this.colorNA,"No information available","na"))),t},xaxisTickFormat(){return this.week?"%A<br>%d %b":(this.sixMonths,"%d %b")},xaxisTickAngle(){return this.week?0:(this.sixMonths,45)},xaxisTickD(){return!0===this.sixMonths?"M1":this.dtick},xaxisTickZero(){if(!0===this.sixMonths){console.log("zero six months ago");const e=new Date(this.dataItems[this.dataItems.length-1].date);return console.log(e),console.log("six months ago: "+new Date(e.setMonth(e.getMonth()-6))),new Date(e.setMonth(e.getMonth()-6))}return this.dayCenter(this.dataItems[0].date)},xaxisMode(){return!0===this.sixMonths?"period":"instant"},xaxisRange(){if(this.xrange)return this.xrange;const e=new Date(this.dataItems[0].date);e.setUTCHours(0,0,0,0);const t=new Date(this.dataItems[this.dataItems.length-1].date);return t.setUTCHours(0,0,0,0),[e.getTime(),t.getTime()+864e5]},monthTickVales(){const e=new Date(this.dataItems[this.dataItems.length-1].date),t=e.getMonth(),i=e.getFullYear(),s=[];for(let e=0;e<6;e++)s.push(new Date(i,t-e,1));return s}}},undefined,!1,undefined,void 0,void 0);var n=r({render:function(){var e=this,t=e.$createElement;return(e._self._c||t)("div",{attrs:{id:e.divId}})},staticRenderFns:[]},undefined,{name:"citationsPlot",data:()=>({divId:a("cit_plot_")}),props:{dataTraces:{type:Array,required:!0},stack:{type:Boolean,required:!1,default:!1},colors:{type:Array,required:!1,default:()=>["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd"]},height:{type:Number,required:!1,default:400},showlegend:{type:Boolean,required:!1,default:!0},title:{type:String,required:!1,default:""},xaxisTitle:{type:String,required:!1,default:"Year"},yaxisTitle:{type:String,required:!1,default:"Number of citations"},darkMode:{type:Boolean,required:!1,default:!1}},mounted(){const e=this.buildTraces(),t={showlegend:this.showlegend,autosize:!0,height:this.height,margin:{l:50,r:50,b:70,t:50,pad:4},xaxis:{title:this.xaxisTitle,font:{size:10},tickfont:{size:10},tickmode:"linear",color:this.darkMode?"white":"black"},yaxis:{title:this.yaxisTitle,titlefont:{size:10},tickfont:{size:10},font:{size:10},color:this.darkMode?"white":"black"},legend:{orientation:"h",yanchor:"bottom",y:1.02,xanchor:"right",x:1,font:{size:8,color:this.darkMode?"white":"black"}},hoverlabel:{color:this.darkMode?"white":"black"},hovermode:this.stack?"x unified":"closest",hoverdistance:70,plot_bgcolor:this.darkMode?"rgb(38, 50, 56)":"white",paper_bgcolor:this.darkMode?"rgb(38, 50, 56)":"white"};s.default.newPlot(this.divId,e,t)},methods:{buildTraces(){const e=[];for(let t=0;t<this.dataTraces.length;t++){const i={x:this.dataTraces[t].data.map((e=>e.year)),y:this.dataTraces[t].data.map((e=>e.citations)),mode:"lines+markers",name:this.dataTraces[t].label,hovertemplate:this.hoverTemplate(),marker:{size:5},line:{color:this.colors[t],width:1.8},stackgroup:this.stack?"one":null};e.push(i)}return e},hoverTemplate(){return this.stack?"%{y} citations <extra></extra>":"%{y} citations in %{x} <extra></extra>"}}},undefined,!1,undefined,void 0,void 0),l=Object.freeze({__proto__:null,accessibilityPlot:o,citationsPlot:n});const d=function(e){d.installed||(d.installed=!0,Object.entries(l).forEach((([t,i])=>{e.component(t,i)})))},c={install:d};{let e=null;"undefined"!=typeof window?e=window.Vue:"undefined"!=typeof global&&(e=global.Vue),e&&e.use(c)}return e.accessibilityPlot=o,e.citationsPlot=n,e.default=c,Object.defineProperty(e,"__esModule",{value:!0}),e}({},Plotly);
@@ -285,9 +285,11 @@ var script$1 = {
285
285
  // sneak in (it would draw green and skew the average); treat it
286
286
  // as a break, just like a null access_time.
287
287
  if (data[i].access_time !== null && !this.isErrorCode(data[i].code)) {
288
- // keep adding to subarray
288
+ // keep adding to subarray. Snap the x to the day centre so
289
+ // the online marker sits in the middle of its day, on the
290
+ // same grid as the offline/NA bars.
289
291
  subarrayTime.push(data[i].access_time);
290
- subarrayDate.push(data[i].date);
292
+ subarrayDate.push(this.dayCenter(data[i].date));
291
293
  sum += data[i].access_time;
292
294
  nonNullValues += 1;
293
295
  } else {
@@ -420,29 +422,62 @@ var script$1 = {
420
422
  const errorCodes = [400, 403, 404, 408, 500, 502, 503, 504];
421
423
  return errorCodes.includes(code);
422
424
  },
425
+ dayCenter(date) {
426
+ /*
427
+ Snaps a date to the centre (12:00) of its UTC calendar day, returned
428
+ as ms since epoch. The whole chart is a daily grid: null placeholders
429
+ arrive at 00:00Z and real probes mid-afternoon, so aligning every
430
+ point and every bar to the day centre keeps them on one grid. That is
431
+ what makes blocks sit centred on their day and prevents bars from
432
+ bleeding into a neighbouring day's measurement.
433
+ */
434
+ const d = new Date(date);
435
+ d.setUTCHours(0, 0, 0, 0);
436
+ return d.getTime() + 1000 * 3600 * 24 / 2;
437
+ },
438
+ computeDaySlots(data) {
439
+ /*
440
+ Returns, for every data point, the bar slot it should occupy: a full
441
+ calendar day centred on that day (see dayCenter). Consecutive
442
+ offline/NA days therefore tile into a single block, and because every
443
+ online point is snapped to the same daily grid a bar never overlaps a
444
+ measurement.
445
+ Arguments:
446
+ - data: array of objects with a "date" key
447
+ Returns an array (parallel to data) of objects with keys:
448
+ - center: slot centre (day centre), in ms since epoch
449
+ - width: slot width, in ms (one full day)
450
+ */
451
+ const DAY = 1000 * 3600 * 24;
452
+ return data.map(item => ({
453
+ center: this.dayCenter(item.date),
454
+ width: DAY
455
+ }));
456
+ },
423
457
  extractOfflineNADates(data) {
424
458
  /*
425
- This function extracts dates with access_time null.
426
- Depending on the code, the server is offline or NA on those dates.
459
+ This function extracts the bar slots of points with no successful
460
+ measurement. Depending on the code, the server is offline or NA on
461
+ those dates.
427
462
  Arguments:
428
463
  - data: array of objects with keys "access_time", "date" and "code". access_time and code can be null
429
-
430
- Returns an object with keys:
431
- - NA: array of dates with code null (no information available)
432
- - down: array of dates with code in errorCodes (server offline)
464
+ Returns an object with keys:
465
+ - NA: array of slots (see computeDaySlots) for code null (no information available)
466
+ - down: array of slots for code in errorCodes (server offline)
433
467
  Classification is driven by the HTTP code, not by access_time
434
468
  null-ness: an error response that happened to record an access_time
435
469
  is still "offline", and a missing code is "no information available".
436
470
  */
471
+ const slots = this.computeDaySlots(data);
437
472
  const resultNA = [];
438
473
  const resultOffline = [];
439
474
  for (let i = 0; i < data.length; i++) {
440
475
  // no code at all -> grey "No information available"
441
476
  if (data[i].code === null) {
442
- resultNA.push(data[i].date);
477
+ resultNA.push(slots[i]);
443
478
  // an error code -> red "Offline"
444
479
  } else if (this.isErrorCode(data[i].code)) {
445
- resultOffline.push(data[i].date);
480
+ resultOffline.push(slots[i]);
446
481
  }
447
482
  }
448
483
  return {
@@ -454,10 +489,12 @@ var script$1 = {
454
489
  /*
455
490
  This function builds a bar trace.
456
491
  Arguments:
457
- - data: object with keys "dates" and "access_times"
458
- - dates: array of dates
492
+ - data: object with keys "dates", "access_times", "colors" and "widths"
493
+ - dates: array of dates (slot centres, in ms)
459
494
  - access_times: array of access_times
460
495
  - colors: array of colors in rgba format (eg:"rgba(255, 153, 145,0.8)")
496
+ - widths: array of per-bar widths in ms, so each bar fills its
497
+ own time slot (see computeDaySlots) instead of a fixed day
461
498
  - name: name of the trace
462
499
  */
463
500
  const trace = {
@@ -472,35 +509,43 @@ var script$1 = {
472
509
  showlegend: showlegend,
473
510
  hoverinfo: hoverinfo,
474
511
  hovertemplate: hovertemplate,
475
- width: 1000 * 3600 * 24 * 1 // 100% of the space between two dates
512
+ width: data.widths
476
513
  };
477
-
478
- console.log(trace);
479
514
  return trace;
480
515
  },
481
- buildBarTraces(dates, color, label, group) {
516
+ buildBarTraces(slots, color, label, group) {
482
517
  /*
483
518
  This function builds the series used to display the offline/NA bars.
484
- For each date, two columns are created: one very short and one tall.
485
- These columns cover the whole plot height.
486
- The tall column is colored with colorStrong and the short one with colorLight.
519
+ For each slot two stacked columns are created that together span the
520
+ whole plot height: a thin strong-coloured band sitting on the axis,
521
+ and a light-coloured column filling the rest of the height above it.
487
522
  Arguments:
488
- - dates: array of dates
523
+ - slots: array of slots ({ center, width }), see computeDaySlots
489
524
  - color: color of the bars
490
525
  Returns array with offline and NA traces
491
526
  */
492
527
 
493
528
  const colorStrong = `rgba(${color},.8)`;
494
529
  const colorLight = `rgba(${color},.2)`;
530
+ const centers = slots.map(s => s.center);
531
+ const widths = slots.map(s => s.width);
532
+
533
+ // Total background height (keeps the previous y-axis extent). The
534
+ // strong colour is a thin band on the axis; the light colour fills
535
+ // the remaining height stacked on top of it.
536
+ const totalHeight = 2 + this.max_access_time * 1.1;
537
+ const baseHeight = totalHeight * 0.03;
495
538
  const dataShort = {
496
- dates: dates,
497
- access_times: Array(dates.length).fill(2),
498
- colors: Array(dates.length).fill(colorStrong)
539
+ dates: centers,
540
+ access_times: Array(slots.length).fill(baseHeight),
541
+ colors: Array(slots.length).fill(colorStrong),
542
+ widths: widths
499
543
  };
500
544
  const dataTall = {
501
- dates: dates,
502
- access_times: Array(dates.length).fill(this.max_access_time * 1.1),
503
- colors: Array(dates.length).fill(colorLight)
545
+ dates: centers,
546
+ access_times: Array(slots.length).fill(totalHeight - baseHeight),
547
+ colors: Array(slots.length).fill(colorLight),
548
+ widths: widths
504
549
  };
505
550
  const hovertemplate = `<b>${label}</b><br>%{x|%d %b %Y}<br>%{y} ms <extra></extra>`;
506
551
  const traceShort = this.barTrace(dataShort, label, false, 'skip', '', group);
@@ -522,12 +567,10 @@ var script$1 = {
522
567
  // Down arrays
523
568
  if (arrays.down.length > 0) {
524
569
  traces = traces.concat(this.buildBarTraces(arrays.down, this.colorOffline, 'Offline', 'down'));
525
- console.log(arrays.down);
526
570
  }
527
571
  // NA arrays
528
572
  if (arrays.NA.length > 0) {
529
573
  traces = traces.concat(this.buildBarTraces(arrays.NA, this.colorNA, 'No information available', 'na'));
530
- console.log(arrays.NA);
531
574
  }
532
575
  return traces;
533
576
  },
@@ -578,7 +621,10 @@ var script$1 = {
578
621
  console.log('six months ago: ' + new Date(lastDate.setMonth(lastDate.getMonth() - 6)));
579
622
  return new Date(lastDate.setMonth(lastDate.getMonth() - 6));
580
623
  } else {
581
- return this.dataItems[0];
624
+ // Anchor day ticks at the day centre (noon) so each label sits
625
+ // under the middle of its day block, aligned with the online dot
626
+ // (which is snapped to the same day centre).
627
+ return this.dayCenter(this.dataItems[0].date);
582
628
  }
583
629
  },
584
630
  xaxisMode() {
@@ -589,11 +635,17 @@ var script$1 = {
589
635
  }
590
636
  },
591
637
  xaxisRange() {
592
- if (!this.xrange) {
593
- return [this.dataItems[0], this.dataItems[this.dataItems.length - 1]];
594
- } else {
638
+ if (this.xrange) {
595
639
  return this.xrange;
596
640
  }
641
+ // Span whole calendar days so the first and last day blocks (which
642
+ // are centred on their day, one full day wide) are fully visible.
643
+ const DAY = 1000 * 3600 * 24;
644
+ const firstDay = new Date(this.dataItems[0].date);
645
+ firstDay.setUTCHours(0, 0, 0, 0);
646
+ const lastDay = new Date(this.dataItems[this.dataItems.length - 1].date);
647
+ lastDay.setUTCHours(0, 0, 0, 0);
648
+ return [firstDay.getTime(), lastDay.getTime() + DAY];
597
649
  },
598
650
  monthTickVales() {
599
651
  // First day of month of last date and previous five months
@@ -711,7 +763,7 @@ const __vue_inject_styles__$1 = undefined;
711
763
  /* scoped */
712
764
  const __vue_scope_id__$1 = undefined;
713
765
  /* module identifier */
714
- const __vue_module_identifier__$1 = "data-v-6f184d3f";
766
+ const __vue_module_identifier__$1 = "data-v-bab9d544";
715
767
  /* functional template */
716
768
  const __vue_is_functional_template__$1 = false;
717
769
  /* style inject */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inb/oeb_visualizations",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Collection of Vue components for data visualization in OpenEBench.",
5
5
  "author": "evamart",
6
6
  "license": "MIT",