/*
* Copyright (C) 2020 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import cockpit from "cockpit";
import React, { useState, useRef, useLayoutEffect } from 'react';
import { useEvent } from "hooks.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Dropdown, DropdownItem, DropdownList } from '@patternfly/react-core/dist/esm/components/Dropdown/index.js';
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider/index.js';
import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggle/index.js';
import { AngleLeftIcon, AngleRightIcon, SearchMinusIcon } from '@patternfly/react-icons';
import * as timeformat from "timeformat";
import '@patternfly/patternfly/patternfly-charts.scss';
import "cockpit-components-plot.scss";
const _ = cockpit.gettext;
function time_ticks(data) {
const first_plot = data[0].data;
const start_ms = first_plot[0][0];
const end_ms = first_plot[first_plot.length - 1][0];
// Determine size between ticks
const sizes_in_seconds = [
60, // minute
5 * 60, // 5 minutes
10 * 60, // 10 minutes
30 * 60, // half hour
60 * 60, // hour
6 * 60 * 60, // quarter day
12 * 60 * 60, // half day
24 * 60 * 60, // day
7 * 24 * 60 * 60, // week
30 * 24 * 60 * 60, // month
183 * 24 * 60 * 60, // half a year
365 * 24 * 60 * 60, // year
10 * 365 * 24 * 60 * 60 // 10 years
];
let size;
for (let i = 0; i < sizes_in_seconds.length; i++) {
if (((end_ms - start_ms) / 1000) / sizes_in_seconds[i] < 10 || i === sizes_in_seconds.length - 1) {
size = sizes_in_seconds[i] * 1000;
break;
}
}
// Determine what to omit from the tick label. If it's all in the
// current year, we don't need to include the year; and if it's
// all happening today, we don't include the month and date.
const now_date = new Date();
const start_date = new Date(start_ms);
let include_year = true;
let include_month_and_day = true;
if (start_date.getFullYear() === now_date.getFullYear()) {
include_year = false;
if (start_date.getMonth() === now_date.getMonth() && start_date.getDate() === now_date.getDate())
include_month_and_day = false;
}
// Compute the actual ticks
const ticks = [];
let t = Math.ceil(start_ms / size) * size;
while (t < end_ms) {
ticks.push(t);
t += size;
}
// Render the label
function pad(n) {
let str = n.toFixed();
if (str.length === 1)
str = '0' + str;
return str;
}
function format_tick(val, index, ticks) {
const d = new Date(val);
let label = ' ';
if (include_month_and_day) {
if (include_year)
label += timeformat.date(d) + '\n';
else
label += timeformat.formatter({ month: "long" }).format(d) + ' ' + d.getDate().toFixed() + '\n';
}
label += pad(d.getHours()) + ':' + pad(d.getMinutes());
return label;
}
return {
ticks,
formatter: format_tick,
start: start_ms,
end: end_ms
};
}
function value_ticks(data, config) {
let max = config.min_max;
const last_plot = data[data.length - 1].data;
for (let i = 0; i < last_plot.length; i++) {
const s = last_plot[i][1] || last_plot[i][2];
if (s > max)
max = s;
}
// Find the highest power of the base unit that is still below
// MAX.
//
// For example, if the base unit is 1000 and MAX is 402,345,765
// this will set UNIT to 1,000,000, aka "Mega".
//
let unit = 1;
while (config.base_unit && max > unit * config.base_unit)
unit *= config.base_unit;
// Find the highest power of 10 that is below the maximum number
// on a tick label. If we use that as the distance between ticks,
// we get at most 10 ticks.
//
// To continue the example, MAX is 402,345,765 and UNIT is thus
// 1,000,000. The highest number on a tick label would be MAX /
// UNIT = 402ish. The highest power of 10 below that is 100. Thus
// the size between ticks is 100*UNIT = 100,000,000. Ticks would
// thus be "100 Mega" apart.
//
// If the highest number of would be only, say, 81, then we would get
// a highest power of 10, and ticks would be 10 units apart.
//
let size = Math.pow(10, Math.floor(Math.log10(max / unit))) * unit;
// Get the number of ticks to be around 4, but don't produce
// fractional numbers. This is done by doubling or halving the
// size between ticks until we get MAX / SIZE to be less than 8 or
// greater than 2.
//
// In the example, MAX / SIZE is already in range, so nothing
// changes here.
//
// If MAX / UNIT is close to the next power of ten, such as 999, we
// would end up with a doubled SIZE of 200,000,000.
//
// If on the other hand MAX / UNIT would be closer to the next
// lower power of 10, like say 110, then we would half the SIZE to
// get moreticks. With 110, it will happen twice and SIZE ends up
// being 25,000,000.
//
// However, if we only have single digit tick labels, we don't
// want to halve them any further, since we don't want tick labels
// like "0.75".
//
while (max / size > 7)
size *= 2;
while (max / size < 3 && size / unit >= 10)
size /= 2;
// Make a list of tick values, each SIZE apart until we are just
// above MAX.
//
// In the example, we get
//
// [ 0, 100000000, 200000000, 300000000, 400000000, 500000000 ]
//
const ticks = [];
for (let t = 0; t < max + size; t += size)
ticks.push(t);
if (config.pull_out_unit) {
const unit_str = config.formatter(unit, config.base_unit, true)[1];
return {
ticks,
formatter: (val) => config.formatter(val, unit_str, true)[0],
unit: unit_str,
max: ticks[ticks.length - 1]
};
} else {
return {
ticks,
formatter: config.formatter,
max: ticks[ticks.length - 1]
};
}
}
export const ZoomControls = ({ plot_state }) => {
function format_range(seconds) {
let n;
if (seconds >= 365 * 24 * 60 * 60) {
n = Math.ceil(seconds / (365 * 24 * 60 * 60));
return cockpit.format(cockpit.ngettext("$0 year", "$0 years", n), n);
} else if (seconds >= 30 * 24 * 60 * 60) {
n = Math.ceil(seconds / (30 * 24 * 60 * 60));
return cockpit.format(cockpit.ngettext("$0 month", "$0 months", n), n);
} else if (seconds >= 7 * 24 * 60 * 60) {
n = Math.ceil(seconds / (7 * 24 * 60 * 60));
return cockpit.format(cockpit.ngettext("$0 week", "$0 weeks", n), n);
} else if (seconds >= 24 * 60 * 60) {
n = Math.ceil(seconds / (24 * 60 * 60));
return cockpit.format(cockpit.ngettext("$0 day", "$0 days", n), n);
} else if (seconds >= 60 * 60) {
n = Math.ceil(seconds / (60 * 60));
return cockpit.format(cockpit.ngettext("$0 hour", "$0 hours", n), n);
} else {
n = Math.ceil(seconds / 60);
return cockpit.format(cockpit.ngettext("$0 minute", "$0 minutes", n), n);
}
}
const zoom_state = plot_state.zoom_state;
const [isOpen, setIsOpen] = useState(false);
useEvent(plot_state, "changed");
useEvent(zoom_state, "changed");
function range_item(seconds, title) {
return (