@inb/oeb_visualizations 0.0.8-beta → 0.1.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.
package/README.md CHANGED
@@ -14,7 +14,88 @@ To install the package run:
14
14
  ```bash
15
15
  npm install @inb/oeb_visualizations
16
16
  ```
17
+ ## How to contribute
18
+
19
+ If you want to contribute to this project, please read the [contributing guidelines](/CONTRIBUTING.md) first.
17
20
 
18
21
  ## Development
19
22
 
20
- 🚧 Work in progress 🚧
23
+ This project is built using [Vue.js](https://vuejs.org/). The components are written in [Vue](https://vuejs.org/) and the bundling is done using [Rollup](https://rollupjs.org/). The rollup configuration is in the `rollup.config.js` file and the main entry point is the `src/index.js` file, all the components are exported from this file.
24
+ The components are written in the `src/components` folder. Each component should be in a separate folder and should contain the `.vue` file and, optionally, a `.scss` file. In order to be used in other applications, the package should be built, which creates a `dist` folder with the compiled files.
25
+
26
+ The documentation is built using [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). The content of the documentation is written in markdown and should be added to the `docs` folder.
27
+ The configuration of the documentation is in the `mkdocs.yml` file. This file contains the structure of the documentation, including navigation, the theme used, etc. More information about how to configure the documentation can be found in the [documentation](https://squidfunk.github.io/mkdocs-material/reference/).
28
+
29
+ ### How to add a new visualization.
30
+
31
+ If you want to add a new visualization, you should create a new component for it. Follow the steps below:
32
+
33
+ - Create the new component in the `src/components` folder. A component can be written in only one file or, if it is complex, it can be divided into smaller components in a folder.
34
+ - Add the new component to the `index.js`, as following:
35
+
36
+ ```js
37
+ import newComponent from './components/newComponent.vue'
38
+
39
+ export {
40
+ newComponent,
41
+ ...
42
+ }
43
+ ```
44
+
45
+ > If the new component is a complex component, it should be divided into smaller components. In this case, the `index.js` file should export the smaller components and the main component.
46
+
47
+ > The documentation should be updated to include the new component.
48
+
49
+ ### How to document a new component.
50
+
51
+ To document a new component, add the new component to the documentation as a single file in the `docs/components` folder. Documentation is written in markdown and should contain at least the following sections:
52
+
53
+ - Description of how the data being represented.
54
+ - How to use it.
55
+ - Example of usage.
56
+ - Props and events.
57
+
58
+ ### How to build the package locally.
59
+
60
+ To build the package locally, run:
61
+ ```bash
62
+ npm run build
63
+ ```
64
+ This will create a `dist` folder with the compiled files.
65
+ The component will be available for other applications running locally to use.
66
+ To use the component in another application, you should link the package locally. To do so, run:
67
+ ```bash
68
+ npm link
69
+ ```
70
+ Then, in the application where you want to use the component, run:
71
+ ```bash
72
+ npm link @inb/oeb_visualizations
73
+ ```
74
+ This will link the local package to the application.
75
+
76
+ ### How to serve the documentation locally.
77
+
78
+ The documentation is built using [mkdocs](https://www.mkdocs.org/). To serve the documentation locally, run:
79
+ ```bash
80
+ mkdocs serve
81
+ ```
82
+ This will start a local server and the documentation will be available at `http://localhost:8000/`.
83
+
84
+
85
+ ## Publishing
86
+
87
+ ### Publishing a new version of the package to npm
88
+
89
+ To publish a new version of the package to [npm](https://www.npmjs.com), follow the steps below:
90
+
91
+ - Update the version in the `package.json` file. If all commit messages follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standard, you can use the `cz bump --version-scheme semver` command to update the version and the `CHANGELOG.md` file.
92
+ - Run the `npm run build` command to build the package.
93
+ - Run the `npm publish` command to publish the package to npmjs.
94
+
95
+ This package is published under the `@inb` scope as `@inb/oeb_visualizations`.
96
+ The package is available at [https://www.npmjs.com/package/@inb/oeb_visualizations](https://www.npmjs.com/package/@inb/oeb_visualizations).
97
+
98
+ ### Publishing the documentation
99
+
100
+ The documentation is built and deployed to the `gh-pages` branch using GitHub Actions. Each time a new commit is pushed to the `main` branch, the documentation is built and deployed to the `gh-pages` branch.
101
+ The documentation is available at [https://inab.github.io/oeb-visualizations/](https://inab.github.io/oeb-visualizations/).
@@ -13,6 +13,13 @@ var script$1 = {
13
13
  }
14
14
  }),
15
15
  props: {
16
+ xrange: {
17
+ /*
18
+ xrange is the range of the x axis in ms.
19
+ */
20
+ type: Array,
21
+ required: false
22
+ },
16
23
  dtick: {
17
24
  /*
18
25
  dtick is the interval between ticks on the x axis in ms.
@@ -72,6 +79,14 @@ var script$1 = {
72
79
  required: false,
73
80
  default: 350
74
81
  },
82
+ width: {
83
+ /*
84
+ width is the width of the plot in px.
85
+ */
86
+ type: Number,
87
+ required: false,
88
+ default: 700
89
+ },
75
90
  week: {
76
91
  /*
77
92
  whether the plot is used to show data of one week
@@ -123,9 +138,12 @@ var script$1 = {
123
138
  if (value[i].code !== null && typeof value[i].code !== 'number') {
124
139
  console.error(`[oeb-visualizations warn] code must be null or a number in dataItems prop item (at position ${i})`);
125
140
  }
126
- // Date must be a string containing a date
127
- if (typeof value[i].date !== 'string' || isNaN(Date.parse(value[i].date))) {
128
- console.error(`[oeb-visualizations warn] date must be a string containing a date in dataItems prop item (at position ${i})`);
141
+ // Date must be a string containing a parseable date, or a number (timestamp)
142
+ const validDateString = typeof value[i].date === 'string' && !isNaN(Date.parse(value[i].date));
143
+ const validDateNumber = typeof value[i].date === 'number';
144
+ if (!validDateString && !validDateNumber) {
145
+ console.error(`[oeb-visualizations warn] date must be a string containing a date or a number in dataItems prop item (at position ${i})`);
146
+ console.error(`[oeb-visualizations warn] date type is ${typeof value[i].date} and the value is ${value[i].date}`);
129
147
  }
130
148
 
131
149
  // And date cannot be null
@@ -138,7 +156,13 @@ var script$1 = {
138
156
  }
139
157
  },
140
158
  mounted() {
141
- this.max_access_time = Math.max(...this.dataItems.map(item => item.access_time));
159
+ // Compute the tallest online bar from real measurements only.
160
+ // Math.max over an array containing null coerces null -> 0, so a site
161
+ // that was down the whole window would yield 0 and render the
162
+ // offline/NA bars with zero height (invisible). Ignore nulls/NaN and
163
+ // apply a floor so down-only sites still draw full-height bars.
164
+ const times = this.dataItems.map(item => item.access_time).filter(t => typeof t === 'number' && !isNaN(t));
165
+ this.max_access_time = times.length ? Math.max(...times) : 100;
142
166
  var traces = this.buildOnlineTraces(this.dataItems); // generate line traces
143
167
 
144
168
  traces = traces.concat(this.buildOfflineNATraces(this.dataItems)); // generate bar traces
@@ -149,6 +173,7 @@ var script$1 = {
149
173
  showlegend: true,
150
174
  autosize: true,
151
175
  height: this.height,
176
+ width: this.width,
152
177
  margin: {
153
178
  l: 50,
154
179
  r: 50,
@@ -176,7 +201,9 @@ var script$1 = {
176
201
  tick0: this.xaxisTickZero(),
177
202
  dtick: this.xaxisTickD(),
178
203
  tickangle: this.xaxisTickAngle(),
179
- tickformat: this.xaxisTickFormat()
204
+ tickformat: this.xaxisTickFormat(),
205
+ tickvals: this.sixMonths ? this.monthTickVales() : null,
206
+ range: this.xaxisRange()
180
207
  },
181
208
  yaxis: {
182
209
  title: this.yaxisTitle,
@@ -253,7 +280,11 @@ var script$1 = {
253
280
  var sum = 0;
254
281
  var nonNullValues = 0;
255
282
  for (let i = 0; i < data.length; i++) {
256
- if (data[i].access_time !== null) {
283
+ // Only genuine successful measurements belong on the online
284
+ // line. An error code with a recorded access_time must not
285
+ // sneak in (it would draw green and skew the average); treat it
286
+ // as a break, just like a null access_time.
287
+ if (data[i].access_time !== null && !this.isErrorCode(data[i].code)) {
257
288
  // keep adding to subarray
258
289
  subarrayTime.push(data[i].access_time);
259
290
  subarrayDate.push(data[i].date);
@@ -380,15 +411,37 @@ var script$1 = {
380
411
  traces.push(this.buildAvgAccessTimeTrace(subarrays.average_access_time));
381
412
  return traces;
382
413
  },
414
+ isErrorCode(code) {
415
+ /*
416
+ Returns true if the HTTP code denotes the server being offline.
417
+ Single source of truth shared by the online-line and offline-bar
418
+ logic so redness is decided consistently by the response code.
419
+ */
420
+ const errorCodes = [400, 403, 404, 408, 500, 502, 503, 504];
421
+ return errorCodes.includes(code);
422
+ },
383
423
  extractOfflineNADates(data) {
424
+ /*
425
+ This function extracts dates with access_time null.
426
+ Depending on the code, the server is offline or NA on those dates.
427
+ Arguments:
428
+ - 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)
433
+ Classification is driven by the HTTP code, not by access_time
434
+ null-ness: an error response that happened to record an access_time
435
+ is still "offline", and a missing code is "no information available".
436
+ */
384
437
  const resultNA = [];
385
438
  const resultOffline = [];
386
439
  for (let i = 0; i < data.length; i++) {
387
- // if the access_time is null and the code is null, it means that the monitoring was down
388
- if (data[i].access_time === null && data[i].code === null) {
440
+ // no code at all -> grey "No information available"
441
+ if (data[i].code === null) {
389
442
  resultNA.push(data[i].date);
390
- // if the access_time is null and the code is in errorCodes, it means that the server is offline
391
- } else if (data[i].access_time === null) {
443
+ // an error code -> red "Offline"
444
+ } else if (this.isErrorCode(data[i].code)) {
392
445
  resultOffline.push(data[i].date);
393
446
  }
394
447
  }
@@ -489,7 +542,7 @@ var script$1 = {
489
542
  return "%A<br>%d %b";
490
543
  }
491
544
  if (this.sixMonths) {
492
- return "%b";
545
+ return "%d %b";
493
546
  } else {
494
547
  return "%d %b";
495
548
  }
@@ -504,7 +557,7 @@ var script$1 = {
504
557
  return 0;
505
558
  }
506
559
  if (this.sixMonths) {
507
- return 0;
560
+ return 45;
508
561
  } else {
509
562
  return 45;
510
563
  }
@@ -534,6 +587,24 @@ var script$1 = {
534
587
  } else {
535
588
  return "instant";
536
589
  }
590
+ },
591
+ xaxisRange() {
592
+ if (!this.xrange) {
593
+ return [this.dataItems[0], this.dataItems[this.dataItems.length - 1]];
594
+ } else {
595
+ return this.xrange;
596
+ }
597
+ },
598
+ monthTickVales() {
599
+ // First day of month of last date and previous five months
600
+ const lastDate = new Date(this.dataItems[this.dataItems.length - 1].date);
601
+ const lastMonth = lastDate.getMonth();
602
+ const lastYear = lastDate.getFullYear();
603
+ const tickValues = [];
604
+ for (let i = 0; i < 6; i++) {
605
+ tickValues.push(new Date(lastYear, lastMonth - i, 1));
606
+ }
607
+ return tickValues;
537
608
  }
538
609
  }
539
610
  };function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier
@@ -640,7 +711,7 @@ const __vue_inject_styles__$1 = undefined;
640
711
  /* scoped */
641
712
  const __vue_scope_id__$1 = undefined;
642
713
  /* module identifier */
643
- const __vue_module_identifier__$1 = "data-v-77c84891";
714
+ const __vue_module_identifier__$1 = "data-v-6f184d3f";
644
715
  /* functional template */
645
716
  const __vue_is_functional_template__$1 = false;
646
717
  /* style inject */
@@ -17,6 +17,13 @@ var script$1 = {
17
17
  }
18
18
  }),
19
19
  props: {
20
+ xrange: {
21
+ /*
22
+ xrange is the range of the x axis in ms.
23
+ */
24
+ type: Array,
25
+ required: false
26
+ },
20
27
  dtick: {
21
28
  /*
22
29
  dtick is the interval between ticks on the x axis in ms.
@@ -76,6 +83,14 @@ var script$1 = {
76
83
  required: false,
77
84
  default: 350
78
85
  },
86
+ width: {
87
+ /*
88
+ width is the width of the plot in px.
89
+ */
90
+ type: Number,
91
+ required: false,
92
+ default: 700
93
+ },
79
94
  week: {
80
95
  /*
81
96
  whether the plot is used to show data of one week
@@ -127,9 +142,12 @@ var script$1 = {
127
142
  if (value[i].code !== null && typeof value[i].code !== 'number') {
128
143
  console.error(`[oeb-visualizations warn] code must be null or a number in dataItems prop item (at position ${i})`);
129
144
  }
130
- // Date must be a string containing a date
131
- if (typeof value[i].date !== 'string' || isNaN(Date.parse(value[i].date))) {
132
- console.error(`[oeb-visualizations warn] date must be a string containing a date in dataItems prop item (at position ${i})`);
145
+ // Date must be a string containing a parseable date, or a number (timestamp)
146
+ const validDateString = typeof value[i].date === 'string' && !isNaN(Date.parse(value[i].date));
147
+ const validDateNumber = typeof value[i].date === 'number';
148
+ if (!validDateString && !validDateNumber) {
149
+ console.error(`[oeb-visualizations warn] date must be a string containing a date or a number in dataItems prop item (at position ${i})`);
150
+ console.error(`[oeb-visualizations warn] date type is ${typeof value[i].date} and the value is ${value[i].date}`);
133
151
  }
134
152
 
135
153
  // And date cannot be null
@@ -142,7 +160,13 @@ var script$1 = {
142
160
  }
143
161
  },
144
162
  mounted() {
145
- this.max_access_time = Math.max(...this.dataItems.map(item => item.access_time));
163
+ // Compute the tallest online bar from real measurements only.
164
+ // Math.max over an array containing null coerces null -> 0, so a site
165
+ // that was down the whole window would yield 0 and render the
166
+ // offline/NA bars with zero height (invisible). Ignore nulls/NaN and
167
+ // apply a floor so down-only sites still draw full-height bars.
168
+ const times = this.dataItems.map(item => item.access_time).filter(t => typeof t === 'number' && !isNaN(t));
169
+ this.max_access_time = times.length ? Math.max(...times) : 100;
146
170
  var traces = this.buildOnlineTraces(this.dataItems); // generate line traces
147
171
 
148
172
  traces = traces.concat(this.buildOfflineNATraces(this.dataItems)); // generate bar traces
@@ -153,6 +177,7 @@ var script$1 = {
153
177
  showlegend: true,
154
178
  autosize: true,
155
179
  height: this.height,
180
+ width: this.width,
156
181
  margin: {
157
182
  l: 50,
158
183
  r: 50,
@@ -180,7 +205,9 @@ var script$1 = {
180
205
  tick0: this.xaxisTickZero(),
181
206
  dtick: this.xaxisTickD(),
182
207
  tickangle: this.xaxisTickAngle(),
183
- tickformat: this.xaxisTickFormat()
208
+ tickformat: this.xaxisTickFormat(),
209
+ tickvals: this.sixMonths ? this.monthTickVales() : null,
210
+ range: this.xaxisRange()
184
211
  },
185
212
  yaxis: {
186
213
  title: this.yaxisTitle,
@@ -257,7 +284,11 @@ var script$1 = {
257
284
  var sum = 0;
258
285
  var nonNullValues = 0;
259
286
  for (let i = 0; i < data.length; i++) {
260
- if (data[i].access_time !== null) {
287
+ // Only genuine successful measurements belong on the online
288
+ // line. An error code with a recorded access_time must not
289
+ // sneak in (it would draw green and skew the average); treat it
290
+ // as a break, just like a null access_time.
291
+ if (data[i].access_time !== null && !this.isErrorCode(data[i].code)) {
261
292
  // keep adding to subarray
262
293
  subarrayTime.push(data[i].access_time);
263
294
  subarrayDate.push(data[i].date);
@@ -384,15 +415,37 @@ var script$1 = {
384
415
  traces.push(this.buildAvgAccessTimeTrace(subarrays.average_access_time));
385
416
  return traces;
386
417
  },
418
+ isErrorCode(code) {
419
+ /*
420
+ Returns true if the HTTP code denotes the server being offline.
421
+ Single source of truth shared by the online-line and offline-bar
422
+ logic so redness is decided consistently by the response code.
423
+ */
424
+ const errorCodes = [400, 403, 404, 408, 500, 502, 503, 504];
425
+ return errorCodes.includes(code);
426
+ },
387
427
  extractOfflineNADates(data) {
428
+ /*
429
+ This function extracts dates with access_time null.
430
+ Depending on the code, the server is offline or NA on those dates.
431
+ Arguments:
432
+ - 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)
437
+ Classification is driven by the HTTP code, not by access_time
438
+ null-ness: an error response that happened to record an access_time
439
+ is still "offline", and a missing code is "no information available".
440
+ */
388
441
  const resultNA = [];
389
442
  const resultOffline = [];
390
443
  for (let i = 0; i < data.length; i++) {
391
- // if the access_time is null and the code is null, it means that the monitoring was down
392
- if (data[i].access_time === null && data[i].code === null) {
444
+ // no code at all -> grey "No information available"
445
+ if (data[i].code === null) {
393
446
  resultNA.push(data[i].date);
394
- // if the access_time is null and the code is in errorCodes, it means that the server is offline
395
- } else if (data[i].access_time === null) {
447
+ // an error code -> red "Offline"
448
+ } else if (this.isErrorCode(data[i].code)) {
396
449
  resultOffline.push(data[i].date);
397
450
  }
398
451
  }
@@ -493,7 +546,7 @@ var script$1 = {
493
546
  return "%A<br>%d %b";
494
547
  }
495
548
  if (this.sixMonths) {
496
- return "%b";
549
+ return "%d %b";
497
550
  } else {
498
551
  return "%d %b";
499
552
  }
@@ -508,7 +561,7 @@ var script$1 = {
508
561
  return 0;
509
562
  }
510
563
  if (this.sixMonths) {
511
- return 0;
564
+ return 45;
512
565
  } else {
513
566
  return 45;
514
567
  }
@@ -538,6 +591,24 @@ var script$1 = {
538
591
  } else {
539
592
  return "instant";
540
593
  }
594
+ },
595
+ xaxisRange() {
596
+ if (!this.xrange) {
597
+ return [this.dataItems[0], this.dataItems[this.dataItems.length - 1]];
598
+ } else {
599
+ return this.xrange;
600
+ }
601
+ },
602
+ monthTickVales() {
603
+ // First day of month of last date and previous five months
604
+ const lastDate = new Date(this.dataItems[this.dataItems.length - 1].date);
605
+ const lastMonth = lastDate.getMonth();
606
+ const lastYear = lastDate.getFullYear();
607
+ const tickValues = [];
608
+ for (let i = 0; i < 6; i++) {
609
+ tickValues.push(new Date(lastYear, lastMonth - i, 1));
610
+ }
611
+ return tickValues;
541
612
  }
542
613
  }
543
614
  };
@@ -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,c){"boolean"!=typeof o&&(l=n,n=o,o=!1);var d,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?(d=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=d):t&&(d=o?function(){t.call(this,c(this.$root.$options.shadowRoot))}:function(e){t.call(this,n(e))}),d)if(h.functional){var u=h.render;h.render=function(e,t){return d.call(t),u(e,t)}}else{var m=h.beforeCreate;h.beforeCreate=m?[].concat(m,d):[d]}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:{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},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})`),("string"!=typeof e[t].date||isNaN(Date.parse(e[t].date)))&&console.error(`[oeb-visualizations warn] date must be a string containing a date in dataItems prop item (at position ${t})`),null===e[t].date&&console.error(`[oeb-visualizations warn] date cannot be null in dataItems prop item (at position ${t})`);return!0}}},mounted(){this.max_access_time=Math.max(...this.dataItems.map((e=>e.access_time)));var e=this.buildOnlineTraces(this.dataItems);e=e.concat(this.buildOfflineNATraces(this.dataItems));const t={bargap:0,barmode:"stack",showlegend:!0,autosize:!0,height:this.height,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()},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,e,t,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?(i.push(e[o].access_time),s.push(e[o].date),a+=e[o].access_time,r+=1):i.length>0&&(t.access_time.push(i),t.date.push(s),i=[],s=[]);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},extractOfflineNADates(e){const t=[],i=[];for(let s=0;s<e.length;s++)null===e[s].access_time&&null===e[s].code?t.push(e[s].date):null===e[s].access_time&&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?"%b":"%d %b"},xaxisTickAngle(){return this.week||this.sixMonths?0: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"}}},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 c=function(e){c.installed||(c.installed=!0,Object.entries(l).forEach((([t,i])=>{e.component(t,i)})))},d={install:c};{let e=null;"undefined"!=typeof window?e=window.Vue:"undefined"!=typeof global&&(e=global.Vue),e&&e.use(d)}return e.accessibilityPlot=o,e.citationsPlot=n,e.default=d,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(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);
@@ -13,6 +13,13 @@ var script$1 = {
13
13
  }
14
14
  }),
15
15
  props: {
16
+ xrange: {
17
+ /*
18
+ xrange is the range of the x axis in ms.
19
+ */
20
+ type: Array,
21
+ required: false
22
+ },
16
23
  dtick: {
17
24
  /*
18
25
  dtick is the interval between ticks on the x axis in ms.
@@ -72,6 +79,14 @@ var script$1 = {
72
79
  required: false,
73
80
  default: 350
74
81
  },
82
+ width: {
83
+ /*
84
+ width is the width of the plot in px.
85
+ */
86
+ type: Number,
87
+ required: false,
88
+ default: 700
89
+ },
75
90
  week: {
76
91
  /*
77
92
  whether the plot is used to show data of one week
@@ -123,9 +138,12 @@ var script$1 = {
123
138
  if (value[i].code !== null && typeof value[i].code !== 'number') {
124
139
  console.error(`[oeb-visualizations warn] code must be null or a number in dataItems prop item (at position ${i})`);
125
140
  }
126
- // Date must be a string containing a date
127
- if (typeof value[i].date !== 'string' || isNaN(Date.parse(value[i].date))) {
128
- console.error(`[oeb-visualizations warn] date must be a string containing a date in dataItems prop item (at position ${i})`);
141
+ // Date must be a string containing a parseable date, or a number (timestamp)
142
+ const validDateString = typeof value[i].date === 'string' && !isNaN(Date.parse(value[i].date));
143
+ const validDateNumber = typeof value[i].date === 'number';
144
+ if (!validDateString && !validDateNumber) {
145
+ console.error(`[oeb-visualizations warn] date must be a string containing a date or a number in dataItems prop item (at position ${i})`);
146
+ console.error(`[oeb-visualizations warn] date type is ${typeof value[i].date} and the value is ${value[i].date}`);
129
147
  }
130
148
 
131
149
  // And date cannot be null
@@ -138,7 +156,13 @@ var script$1 = {
138
156
  }
139
157
  },
140
158
  mounted() {
141
- this.max_access_time = Math.max(...this.dataItems.map(item => item.access_time));
159
+ // Compute the tallest online bar from real measurements only.
160
+ // Math.max over an array containing null coerces null -> 0, so a site
161
+ // that was down the whole window would yield 0 and render the
162
+ // offline/NA bars with zero height (invisible). Ignore nulls/NaN and
163
+ // apply a floor so down-only sites still draw full-height bars.
164
+ const times = this.dataItems.map(item => item.access_time).filter(t => typeof t === 'number' && !isNaN(t));
165
+ this.max_access_time = times.length ? Math.max(...times) : 100;
142
166
  var traces = this.buildOnlineTraces(this.dataItems); // generate line traces
143
167
 
144
168
  traces = traces.concat(this.buildOfflineNATraces(this.dataItems)); // generate bar traces
@@ -149,6 +173,7 @@ var script$1 = {
149
173
  showlegend: true,
150
174
  autosize: true,
151
175
  height: this.height,
176
+ width: this.width,
152
177
  margin: {
153
178
  l: 50,
154
179
  r: 50,
@@ -176,7 +201,9 @@ var script$1 = {
176
201
  tick0: this.xaxisTickZero(),
177
202
  dtick: this.xaxisTickD(),
178
203
  tickangle: this.xaxisTickAngle(),
179
- tickformat: this.xaxisTickFormat()
204
+ tickformat: this.xaxisTickFormat(),
205
+ tickvals: this.sixMonths ? this.monthTickVales() : null,
206
+ range: this.xaxisRange()
180
207
  },
181
208
  yaxis: {
182
209
  title: this.yaxisTitle,
@@ -253,7 +280,11 @@ var script$1 = {
253
280
  var sum = 0;
254
281
  var nonNullValues = 0;
255
282
  for (let i = 0; i < data.length; i++) {
256
- if (data[i].access_time !== null) {
283
+ // Only genuine successful measurements belong on the online
284
+ // line. An error code with a recorded access_time must not
285
+ // sneak in (it would draw green and skew the average); treat it
286
+ // as a break, just like a null access_time.
287
+ if (data[i].access_time !== null && !this.isErrorCode(data[i].code)) {
257
288
  // keep adding to subarray
258
289
  subarrayTime.push(data[i].access_time);
259
290
  subarrayDate.push(data[i].date);
@@ -380,15 +411,37 @@ var script$1 = {
380
411
  traces.push(this.buildAvgAccessTimeTrace(subarrays.average_access_time));
381
412
  return traces;
382
413
  },
414
+ isErrorCode(code) {
415
+ /*
416
+ Returns true if the HTTP code denotes the server being offline.
417
+ Single source of truth shared by the online-line and offline-bar
418
+ logic so redness is decided consistently by the response code.
419
+ */
420
+ const errorCodes = [400, 403, 404, 408, 500, 502, 503, 504];
421
+ return errorCodes.includes(code);
422
+ },
383
423
  extractOfflineNADates(data) {
424
+ /*
425
+ This function extracts dates with access_time null.
426
+ Depending on the code, the server is offline or NA on those dates.
427
+ Arguments:
428
+ - 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)
433
+ Classification is driven by the HTTP code, not by access_time
434
+ null-ness: an error response that happened to record an access_time
435
+ is still "offline", and a missing code is "no information available".
436
+ */
384
437
  const resultNA = [];
385
438
  const resultOffline = [];
386
439
  for (let i = 0; i < data.length; i++) {
387
- // if the access_time is null and the code is null, it means that the monitoring was down
388
- if (data[i].access_time === null && data[i].code === null) {
440
+ // no code at all -> grey "No information available"
441
+ if (data[i].code === null) {
389
442
  resultNA.push(data[i].date);
390
- // if the access_time is null and the code is in errorCodes, it means that the server is offline
391
- } else if (data[i].access_time === null) {
443
+ // an error code -> red "Offline"
444
+ } else if (this.isErrorCode(data[i].code)) {
392
445
  resultOffline.push(data[i].date);
393
446
  }
394
447
  }
@@ -489,7 +542,7 @@ var script$1 = {
489
542
  return "%A<br>%d %b";
490
543
  }
491
544
  if (this.sixMonths) {
492
- return "%b";
545
+ return "%d %b";
493
546
  } else {
494
547
  return "%d %b";
495
548
  }
@@ -504,7 +557,7 @@ var script$1 = {
504
557
  return 0;
505
558
  }
506
559
  if (this.sixMonths) {
507
- return 0;
560
+ return 45;
508
561
  } else {
509
562
  return 45;
510
563
  }
@@ -534,6 +587,24 @@ var script$1 = {
534
587
  } else {
535
588
  return "instant";
536
589
  }
590
+ },
591
+ xaxisRange() {
592
+ if (!this.xrange) {
593
+ return [this.dataItems[0], this.dataItems[this.dataItems.length - 1]];
594
+ } else {
595
+ return this.xrange;
596
+ }
597
+ },
598
+ monthTickVales() {
599
+ // First day of month of last date and previous five months
600
+ const lastDate = new Date(this.dataItems[this.dataItems.length - 1].date);
601
+ const lastMonth = lastDate.getMonth();
602
+ const lastYear = lastDate.getFullYear();
603
+ const tickValues = [];
604
+ for (let i = 0; i < 6; i++) {
605
+ tickValues.push(new Date(lastYear, lastMonth - i, 1));
606
+ }
607
+ return tickValues;
537
608
  }
538
609
  }
539
610
  };function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier
@@ -640,7 +711,7 @@ const __vue_inject_styles__$1 = undefined;
640
711
  /* scoped */
641
712
  const __vue_scope_id__$1 = undefined;
642
713
  /* module identifier */
643
- const __vue_module_identifier__$1 = "data-v-77c84891";
714
+ const __vue_module_identifier__$1 = "data-v-6f184d3f";
644
715
  /* functional template */
645
716
  const __vue_is_functional_template__$1 = false;
646
717
  /* style inject */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inb/oeb_visualizations",
3
- "version": "0.0.8-beta",
3
+ "version": "0.1.1",
4
4
  "description": "Collection of Vue components for data visualization in OpenEBench.",
5
5
  "author": "evamart",
6
6
  "license": "MIT",
@@ -23,7 +23,6 @@
23
23
  "files": [
24
24
  "dist/*"
25
25
  ],
26
-
27
26
  "vetur": {
28
27
  "tags": "tags.json",
29
28
  "attributes": "attributes.json"