status-desktop/ui/app/AppLayouts/Communities/panels/OverviewSettingsChart.qml

388 lines
17 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import utils 1.0
StatusChartPanel {
id: root
/**
* Flat model to use for the chart containing timestamps
* type: {Array}
*/
property var model: []
signal collectCommunityMetricsMessagesTimestamps(var intervals)
function requestCommunityMetrics() {
let intervals = d.selectedTabInfo.modelItems.map(item => {
return {
startTimestamp: item.start,
endTimestamp: item.end
}
})
collectCommunityMetricsMessagesTimestamps(JSON.stringify(intervals))
}
onVisibleChanged: if (visible) requestCommunityMetrics()
QtObject {
id: d
//visual properties
readonly property string baseColor1: Theme.palette.baseColor1
readonly property string twentyPercentBaseColor1: Theme.palette.alphaColor(baseColor1, 0.2)
readonly property string barColor: Theme.palette.primaryColor2
readonly property string barBorderColor: Theme.palette.primaryColor1
readonly property string messagesLabel: qsTr("Messages")
property int hoveredBarIndex: 0
property int hoveredBarValue: 0
readonly property var hoveredModelMetadata: modelMetadata[root.timeRangeTabBarIndex].modelItems[hoveredBarIndex]
readonly property var tooltipConfig: modelMetadata[root.timeRangeTabBarIndex].tooltipConfig
readonly property var graphTabsModel: [{text: messagesLabel, enabled: true}]
readonly property var now: Date.now()
readonly property var chartData: selectedTabInfo.modelItems.map(x => d.itemsCountInRange(root.model, x.start, x.end))
readonly property var labels: selectedTabInfo.modelItems.map(x => x.label)
readonly property var selectedTabInfo: modelMetadata[root.timeRangeTabBarIndex]
readonly property var modelMetadata: [
{
text: qsTr("1H"),
modelItems: [
{ start: LocaleUtils.minutes(60, now), end: LocaleUtils.minutes(50, now), label: minutesStr(55)},
{ start: LocaleUtils.minutes(50, now), end: LocaleUtils.minutes(40, now), label: minutesStr(45)},
{ start: LocaleUtils.minutes(40, now), end: LocaleUtils.minutes(30, now), label: minutesStr(35)},
{ start: LocaleUtils.minutes(30, now), end: LocaleUtils.minutes(20, now), label: minutesStr(25)},
{ start: LocaleUtils.minutes(20, now), end: LocaleUtils.minutes(10, now), label: minutesStr(15)},
{ start: LocaleUtils.minutes(10, now), end: LocaleUtils.minutes(0, now, false), label: minutesStr(5)}
],
tooltipConfig: {
timeRangeString: qsTr("Time period"),
timeRangeFormatter: d.hoursRangeStr
},
},
{
text: qsTr("1D"),
modelItems: [
{ start: LocaleUtils.hours(24, now), end: LocaleUtils.hours(20, now), label: hourStr(22)},
{ start: LocaleUtils.hours(20, now), end: LocaleUtils.hours(16, now), label: hourStr(18)},
{ start: LocaleUtils.hours(16, now), end: LocaleUtils.hours(12, now), label: hourStr(14)},
{ start: LocaleUtils.hours(12, now), end: LocaleUtils.hours(8, now), label: hourStr(10)},
{ start: LocaleUtils.hours(8, now), end: LocaleUtils.hours(4, now), label: hourStr(6)},
{ start: LocaleUtils.hours(4, now), end: LocaleUtils.hours(0, now, false), label: hourStr(2)}
],
tooltipConfig: {
timeRangeString: qsTr("Time period"),
timeRangeFormatter: d.hoursRangeStr
},
},
{
text: qsTr("7D"),
modelItems: [
{ start: LocaleUtils.days(6, now), end: LocaleUtils.days(5, now), label: dayStr(6)},
{ start: LocaleUtils.days(5, now), end: LocaleUtils.days(4, now), label: dayStr(5)},
{ start: LocaleUtils.days(4, now), end: LocaleUtils.days(3, now), label: dayStr(4)},
{ start: LocaleUtils.days(3, now), end: LocaleUtils.days(2, now), label: dayStr(3)},
{ start: LocaleUtils.days(2, now), end: LocaleUtils.days(1, now), label: dayStr(2)},
{ start: LocaleUtils.days(1, now), end: LocaleUtils.days(0, now), label: dayStr(1)},
{ start: LocaleUtils.days(0, now), end: LocaleUtils.days(0, now, false), label: dayStr(0)}
],
tooltipConfig: {
timeRangeString: qsTr("Date"),
timeRangeFormatter: d.daysRangeStr
},
},
{
text: qsTr("1M"),
modelItems: [
{ start: LocaleUtils.days(30, now), end: LocaleUtils.days(25, now), label: dayStr(30)},
{ start: LocaleUtils.days(25, now), end: LocaleUtils.days(20, now), label: dayStr(25)},
{ start: LocaleUtils.days(20, now), end: LocaleUtils.days(15, now), label: dayStr(20)},
{ start: LocaleUtils.days(15, now), end: LocaleUtils.days(10, now), label: dayStr(15)},
{ start: LocaleUtils.days(10, now), end: LocaleUtils.days(5, now), label: dayStr(10)},
{ start: LocaleUtils.days(5, now), end: LocaleUtils.days(0, now, false), label: dayStr(5)}
],
tooltipConfig: {
timeRangeString: qsTr("Time period"),
timeRangeFormatter: d.daysRangeStr
},
},
{
text: qsTr("6M"),
modelItems: [
{ start: LocaleUtils.months(5, now), end: LocaleUtils.months(4, now), label: monthStr(5)},
{ start: LocaleUtils.months(4, now), end: LocaleUtils.months(3, now), label: monthStr(4)},
{ start: LocaleUtils.months(3, now), end: LocaleUtils.months(2, now), label: monthStr(3)},
{ start: LocaleUtils.months(2, now), end: LocaleUtils.months(1, now), label: monthStr(2)},
{ start: LocaleUtils.months(1, now), end: LocaleUtils.months(0, now), label: monthStr(1)},
{ start: LocaleUtils.months(0, now), end: LocaleUtils.months(0, now, false), label: monthStr(0)}
],
tooltipConfig: {
timeRangeString: qsTr("Month"),
timeRangeFormatter: d.monthsRangeStr
},
},
{
text: qsTr("1Y"),
modelItems: [
{ start: LocaleUtils.months(12, now), end: LocaleUtils.months(10, now), label: monthStr(11)},
{ start: LocaleUtils.months(10, now), end: LocaleUtils.months(8, now), label: monthStr(9)},
{ start: LocaleUtils.months(8, now), end: LocaleUtils.months(6, now), label: monthStr(7)},
{ start: LocaleUtils.months(6, now), end: LocaleUtils.months(4, now), label: monthStr(5)},
{ start: LocaleUtils.months(4, now), end: LocaleUtils.months(2, now), label: monthStr(3)},
{ start: LocaleUtils.months(2, now), end: LocaleUtils.months(0, now, false), label: monthStr(1)}
],
tooltipConfig: {
timeRangeString: qsTr("Time period"),
timeRangeFormatter: d.monthsRangeStr
},
},
{
text: qsTr("ALL"),
modelItems: [
{ start: LocaleUtils.years(7, now), end: LocaleUtils.years(6, now), label: yearsStr(7) },
{ start: LocaleUtils.years(6, now), end: LocaleUtils.years(5, now), label: yearsStr(6) },
{ start: LocaleUtils.years(5, now), end: LocaleUtils.years(4, now), label: yearsStr(5) },
{ start: LocaleUtils.years(4, now), end: LocaleUtils.years(3, now), label: yearsStr(4) },
{ start: LocaleUtils.years(3, now), end: LocaleUtils.years(2, now), label: yearsStr(3) },
{ start: LocaleUtils.years(2, now), end: LocaleUtils.years(1, now), label: yearsStr(2) },
{ start: LocaleUtils.years(1, now), end: LocaleUtils.years(0, now), label: yearsStr(1) },
{ start: LocaleUtils.years(0, now), end: LocaleUtils.years(0, now, false), label: yearsStr(0) }
],
tooltipConfig: {
timeRangeString: qsTr("Year"),
timeRangeFormatter: d.yearsRangeStr
},
}
]
function itemsCountInRange(array, start, end) {
return array ? array.filter(x => x <= end && x > start).length : 0
}
function minutesStr(before = 0, timeReference = now, roundCurrentTime = true) {
return LocaleUtils.formatTime(LocaleUtils.minutes(before, timeReference, roundCurrentTime), Locale.ShortFormat)
}
function hourStr(before = 0, timeReference = now, roundCurrentTime = true) {
return LocaleUtils.formatTime(LocaleUtils.hours(before, timeReference, roundCurrentTime), Locale.ShortFormat)
}
function dayStr(before = 0, timeReference = now, roundCurrentTime = true) {
return LocaleUtils.getDayMonth(LocaleUtils.days(before, timeReference, roundCurrentTime), Locale.ShortFormat)
}
function monthStr(before = 0, timeReference = now, roundCurrentTime = true, shortFormat = true) {
const format = shortFormat ? "MMM" : "MMMM"
const timeStamp = LocaleUtils.months(before, timeReference, roundCurrentTime)
return LocaleUtils.formatDate(timeStamp, format)
}
function yearsStr(before = 0, timeReference = now, roundCurrentTime = true) {
return LocaleUtils.formatDate(LocaleUtils.years(before, timeReference, roundCurrentTime), "yyyy");
}
function hoursRangeStr(start, end) {
return "%1 - %2".arg(hourStr(0, start, false)).arg(hourStr(0, end, false))
}
function daysRangeStr(start, end) {
return (end - start > LocaleUtils.daysToMs(1)) ?
"%1 - %2".arg(dayStr(0, start, false)).arg(dayStr(0, end, false)) :
dayStr(0, start, false)
}
function monthsRangeStr(start, end) {
//End date excluded
//Adjust by one ms to exclude the end date
//To avoid considering the end date as a new month
end = end - 1
const startDate = monthStr(0, start, false)
const endDate = monthStr(0, end, false)
return (startDate !== endDate) ?
"%1 - %2".arg(startDate).arg(endDate) :
monthStr(0, start, false, false)
}
function yearsRangeStr(start, end) {
//End date excluded
//Adjust by one ms to exclude the end date
//To avoid considering the end date as a new year
end = end - 1
const startYear = yearsStr(0, start, false)
const endYear = yearsStr(0, end, false)
return (startYear !== endYear) ?
"%1 - %2".arg(startYear).arg(endYear) :
startYear
}
function getAdjustedTooltipPosition(event) {
// By defaullt the popup is displayed on the right of the cursor
// If there is not enough space on the right, display it on the left
const relativeMousePoint = event.target.mapToItem(toolTip.parent, event.x, event.y) // relative to tooltip parent
const leftPositon = (toolTip.parent.width - (toolTip.width + toolTip.rightPadding + relativeMousePoint.x + 15)) < 0
return leftPositon ? Qt.point(relativeMousePoint.x - toolTip.width - 15, relativeMousePoint.y - 5)
: Qt.point(relativeMousePoint.x + 15, relativeMousePoint.y - 5)
}
onSelectedTabInfoChanged: root.requestCommunityMetrics()
}
headerLeftPadding: 0
headerBottomPadding: Style.current.bigPadding
graphsModel: d.graphTabsModel
timeRangeModel: d.modelMetadata
onHeaderTabClicked: {
root.chart.animateToNewData();
}
/////////////////////////////
// Chartjs configuration //
/////////////////////////////
chart.chartType: 'bar'
chart.chartData: {
return {
labels: d.labels,
datasets: [{
xAxisId: 'x-axis-1',
yAxisId: 'y-axis-1',
backgroundColor: d.barColor,
pointRadius: 0,
hoverBackgroundColor: d.barColor,
hoverBorderColor: d.barBorderColor,
hoverBorderWidth: 2,
data: d.chartData
}]
}
}
chart.chartOptions: {
return {
maintainAspectRatio: false,
responsive: true,
legend: {
display: false
},
// Popup follows the cursor
onHover: function(arg1, hoveredItems, event) {
if(!event || hoveredItems.length == 0) {
toolTip.close()
return
}
d.hoveredBarIndex = hoveredItems[0]._index
d.hoveredBarValue = hoveredItems[0]._chart.config.data.datasets[0].data[hoveredItems[0]._index]
const position = d.getAdjustedTooltipPosition(event)
toolTip.popup(position.x, position.y)
},
tooltips: {
enabled: false,
},
scales: {
xAxes: [{
id: 'x-axis-1',
position: 'bottom',
stacked: false,
gridLines: {
drawOnChartArea: false,
drawBorder: false,
drawTicks: false,
},
ticks: {
fontSize: Style.current.asideTextFontSize,
fontColor: d.baseColor1,
padding: Style.current.padding,
}
}],
yAxes: [{
position: 'left',
id: 'y-axis-1',
stacked: false,
gridLines: {
borderDash: [5, 3],
lineWidth: 1,
drawBorder: false,
drawTicks: false,
color: d.twentyPercentBaseColor1,
},
beforeDataLimits: (axis) => {
axis.paddingTop = 25;
axis.paddingBottom = 0;
},
ticks: {
fontSize: 10,
fontColor: d.baseColor1,
padding: Style.current.halfPadding,
maxTicksLimit: Style.current.asideTextFontSize,
beginAtZero: true,
stepSize: 1,
callback: function(value, index, values) {
return LocaleUtils.numberToLocaleString(value)
}
}
}]
}
}
}
StatusMenu {
id: toolTip
width: 243 //By design
topPadding: Style.current.padding
bottomPadding: topPadding
leftPadding: topPadding
rightPadding: topPadding
parent: Overlay.overlay
ColumnLayout {
spacing: Style.current.padding
RowLayout {
Layout.fillWidth: true
StatusBaseText {
elide: Qt.ElideRight
font.pixelSize: Style.current.primaryTextFontSize
color: Theme.palette.baseColor1
text: d.tooltipConfig.timeRangeString
}
Item {
Layout.fillWidth: true
}
StatusBaseText {
Layout.alignment: Qt.AlignRight
elide: Qt.ElideRight
font.pixelSize: Style.current.primaryTextFontSize
color: Theme.palette.directColor1
text: d.hoveredModelMetadata ? d.tooltipConfig.timeRangeFormatter(d.hoveredModelMetadata.start, d.hoveredModelMetadata.end)
: ""
}
}
RowLayout {
Layout.fillWidth: true
StatusBaseText {
elide: Qt.ElideRight
font.pixelSize: Style.current.primaryTextFontSize
color: Theme.palette.baseColor1
text: qsTr("No. of Messages")
}
Item { Layout.fillWidth: true }
StatusBaseText {
Layout.alignment: Qt.AlignRight
elide: Qt.ElideRight
font.pixelSize: Style.current.primaryTextFontSize
color: Theme.palette.directColor1
text: LocaleUtils.numberToLocaleString(d.hoveredBarValue)
}
}
}
}
}