diff --git a/tools/oversight/charts/skyline.js b/tools/oversight/charts/skyline.js
index 713ed657..d8263610 100644
--- a/tools/oversight/charts/skyline.js
+++ b/tools/oversight/charts/skyline.js
@@ -42,16 +42,20 @@ export default class SkylineChart extends AbstractChart {
groupFn.fillerFn = (existing) => {
const endDate = this.chartConfig.endDate ? new Date(this.chartConfig.endDate) : new Date();
- // set start date depending on the unit
- let startDate = new Date(endDate);
- // roll back to beginning of time
- if (this.chartConfig.unit === 'day') startDate.setDate(endDate.getDate() - 30);
- if (this.chartConfig.unit === 'hour') startDate.setDate(endDate.getDate() - 7);
- if (this.chartConfig.unit === 'week') startDate.setMonth(endDate.getMonth() - 12);
- if (this.chartConfig.startDate) {
- // nevermind, we have a start date in the config, let's use that
+
+ let startDate;
+ if (!this.chartConfig.startDate) {
+ // set start date depending on the unit
+ startDate = new Date(endDate);
+ // roll back to beginning of time
+ if (this.chartConfig.unit === 'day') startDate.setDate(endDate.getDate() - 30);
+ if (this.chartConfig.unit === 'hour') startDate.setDate(endDate.getDate() - 7);
+ if (this.chartConfig.unit === 'week') startDate.setMonth(endDate.getMonth() - 12);
+ if (this.chartConfig.unit === 'month') startDate.setMonth(endDate.getMonth() - 1);
+ } else {
startDate = new Date(this.chartConfig.startDate);
}
+
const slots = new Set(existing);
const slotTime = new Date(startDate);
// return Array.from(slots);
@@ -62,6 +66,7 @@ export default class SkylineChart extends AbstractChart {
if (this.chartConfig.unit === 'day') slotTime.setDate(slotTime.getDate() + 1);
if (this.chartConfig.unit === 'hour') slotTime.setHours(slotTime.getHours() + 1);
if (this.chartConfig.unit === 'week') slotTime.setDate(slotTime.getDate() + 7);
+ if (this.chartConfig.unit === 'month') slotTime.setMonth(slotTime.getMonth() + 1);
maxSlots -= 1;
if (maxSlots < 0) {
// eslint-disable-next-line no-console
@@ -452,12 +457,41 @@ export default class SkylineChart extends AbstractChart {
async draw() {
const params = new URL(window.location).searchParams;
- const view = ['week', 'month', 'year'].indexOf(params.get('view')) !== -1
- ? params.get('view')
- : 'week';
- // TODO re-add. I think this should be a filter
+ const view = params.get('view');
+
// eslint-disable-next-line no-unused-vars
- const endDate = params.get('endDate') ? `${params.get('endDate')}T00:00:00` : null;
+ const startDate = params.get('startDate');
+ const endDate = params.get('endDate');
+
+ let customView = 'year';
+ let unit = 'month';
+ let units = 12;
+ if (view === 'custom') {
+ const diff = endDate ? new Date(endDate).getTime() - new Date(startDate).getTime() : 0;
+ if (diff < (1000 * 60 * 60 * 24)) {
+ // less than a day
+ customView = 'hour';
+ unit = 'hour';
+ units = 24;
+ } else if (diff <= (1000 * 60 * 60 * 24 * 7)) {
+ // less than a week
+ customView = 'week';
+ unit = 'hour';
+ units = Math.round(diff / (1000 * 60 * 60));
+ } else if (diff <= (1000 * 60 * 60 * 24 * 31)) {
+ // less than a month
+ customView = 'month';
+ unit = 'day';
+ units = 30;
+ } else if (diff <= (1000 * 60 * 60 * 24 * 365 * 3)) {
+ // less than 3 years
+ customView = 'week';
+ unit = 'week';
+ units = Math.round(diff / (1000 * 60 * 60 * 24 * 7));
+ }
+ }
+
+ const focus = params.get('focus');
if (this.dataChunks.filtered.length < 1000) {
this.elems.lowDataWarning.ariaHidden = 'false';
@@ -470,18 +504,32 @@ export default class SkylineChart extends AbstractChart {
view,
unit: 'day',
units: 30,
+ focus,
+ startDate,
endDate,
},
week: {
view,
unit: 'hour',
units: 24 * 7,
+ focus,
+ startDate,
endDate,
},
year: {
view,
unit: 'week',
units: 52,
+ focus,
+ startDate,
+ endDate,
+ },
+ custom: {
+ view: customView,
+ unit,
+ units,
+ focus,
+ startDate,
endDate,
},
};
@@ -559,6 +607,7 @@ export default class SkylineChart extends AbstractChart {
this.stepSize = undefined;
this.clsAlreadyLabeled = false;
this.lcpAlreadyLabeled = false;
+
this.chart.update();
// add trend indicators
diff --git a/tools/oversight/elements/daterange-picker.js b/tools/oversight/elements/daterange-picker.js
new file mode 100644
index 00000000..80c50667
--- /dev/null
+++ b/tools/oversight/elements/daterange-picker.js
@@ -0,0 +1,392 @@
+function debounce(func, wait) {
+ let timeout;
+ // eslint-disable-next-line func-names
+ return function (...args) {
+ const context = this;
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(context, args), wait);
+ };
+}
+
+// date management
+function pad(number) {
+ return number.toString().padStart(2, '0');
+}
+
+function toDateString(date) {
+ // convert date
+ const year = date.getFullYear();
+ const month = pad(date.getMonth() + 1);
+ const day = pad(date.getDate());
+
+ return `${year}-${month}-${day}`;
+}
+
+const STYLES = `
+ .daterange-wrapper {
+ display: grid;
+ align-items: end;
+ gap: var(--spacing-l);
+ font-size: var(--type-body-s-size);
+ }
+
+ .input-wrapper, .daterange-wrapper {
+ position: relative;
+ display: block;
+ }
+
+ input {
+ width: 100%;
+ font: inherit;
+ border-color: var(--gray-100);
+ border: 2px solid var(--gray-300);
+ padding: 0.4em 0.85em 0.1em;
+ background-color: var(--gray-100);
+ cursor: pointer;
+ transition: border-color 0.2s, background-color 0.2s;
+ border-radius: 4px;
+ }
+
+ input ~ ul li {
+ padding: 0.4em 0;
+ padding-left: 2rem;
+ cursor: pointer;
+ }
+
+ ul.menu li:last-child {
+ position: relative;
+ margin-top: 16px;
+ }
+
+ ul.menu li:last-child::before {
+ content: '';
+ position: absolute;
+ top: calc((-0.5 * 16px) - (2px / 2));
+ left: 0;
+ right: 0;
+ height: 2px;
+ background-color: var(--gray-200);
+ }
+
+ .input-wrapper {
+ display: none;
+ background-color: white;
+ }
+
+ input[data-custom='true'] ~ .input-wrapper {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 16px 12px;
+ right: 0;
+ margin-top: 4px;
+ border-radius: 4px;
+ padding: calc(0.4em + 2px);
+ background-color: white;
+ border: 1px solid var(--gray-100);
+ box-shadow: 5px 5px 5px var(--gray-700);
+ z-index: 10;
+ }
+
+ ul {
+ position: relative;
+ list-style: none;
+ left: 0;
+ right: 0;
+ margin: 0;
+ border-radius: 8px;
+ padding: calc(0.4em + 2px);
+ background-color: white;
+ border: 1px solid var(--gray-100);
+ box-shadow: 5px 5px 5px var(--gray-700);
+ z-index: 20;
+ }
+
+ ul.menu:not([hidden]) + .input-wrapper {
+ display: none;
+ }
+
+ .date-field {
+ display: block;
+ margin-top: 0;
+ }
+
+ .date-field label {
+ display: block;
+ }
+
+ @media (width >= 740px) {
+ .input-wrapper {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ input[data-custom='true'] ~ .input-wrapper {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ }
+
+ @media (width >= 900px) {
+ .daterange-wrapper {
+ font-size: var(--type-body-l-size);
+ }
+
+ ul {
+ position: absolute;
+ }
+
+ input {
+ min-width: 200px;
+ }
+
+ .input-wrapper {
+ min-width: 600px;
+ }
+
+ input[data-custom='true'] ~ .input-wrapper {
+ grid-template-columns: minmax(0, 1fr);
+ }
+
+ .date-field {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ }
+
+ .date-field label {
+ margin-top: 4px;
+ }
+ }
+
+ @media (width >= 1200px) {
+ input[data-custom='true'] ~ .input-wrapper {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ position: absolute;
+ }
+ }
+`;
+
+const TEMPLATE = `
+