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

405 lines
18 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 utils 1.0
StatusChartPanel {
id: root
/**
* Flat model to use for the chart containing timestamps
* type: {Array}
* default: []
* example: {"communityId": "", "metricsType": "MessagesCount", "intervals": [{"startTimestamp": 1691047800000, "endTimestamp": 1691062200000, "timestamps": [], "count": 0}]}
*/
property var model: []
signal collectCommunityMetricsMessagesCount(var intervals)
function reset() {
d.now = Date.now()
d.requestCommunityMetrics()
}
onVisibleChanged: if(visible) d.resetWithSpamProtection()
onTimeRangeTabBarIndexChanged: reset()
onModelChanged: chart.refresh()
onCollectCommunityMetricsMessagesCount: d.lastRequestModelMetadata = d.selectedTabInfo.modelItems
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}]
property double now: Date.now()
property var lastRequestModelMetadata: null
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 resetWithSpamProtection() {
if(Date.now() - d.now > LocaleUtils.minutesToMs(5) || d.lastRequestModelMetadata != selectedTabInfo.modelItems) {
root.reset()
}
}
function requestCommunityMetrics() {
let intervals = d.selectedTabInfo.modelItems.map(item => {
return {
startTimestamp: item.start,
endTimestamp: item.end
}
})
root.collectCommunityMetricsMessagesCount(JSON.stringify(intervals))
}
function itemsCountInRange(model, start, end) {
if (model == undefined || model.intervals == undefined)
return 0
const interval = model.intervals.find(x => x.startTimestamp == start && x.endTimestamp == end)
if (!interval)
return 0
if(model.metricsType === "MessagesTimestamps")
return interval.timestamps.length
return interval.count
}
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 timeStamp = LocaleUtils.months(before, timeReference, roundCurrentTime)
return Qt.locale().standaloneMonthName(new Date(timeStamp).getMonth(), shortFormat ? Locale.ShortFormat : Locale.LongFormat)
}
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 default 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)
}
}
headerLeftPadding: 0
headerBottomPadding: Theme.bigPadding
graphsModel: d.graphTabsModel
timeRangeModel: d.modelMetadata
onHeaderTabClicked: {
chart.refresh()
}
/////////////////////////////
// Chartjs configuration //
/////////////////////////////
chart.type: 'bar'
chart.labels: d.labels
chart.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.options: {
return {
// Popup follows the cursor
onHover: function(arg1, hoveredItems) {
if(!arg1 || hoveredItems.length == 0) {
toolTip.close()
return
}
arg1.target = chart
d.hoveredBarIndex = hoveredItems[0]._index
d.hoveredBarValue = hoveredItems[0]._chart.config.data.datasets[0].data[hoveredItems[0]._index]
const position = d.getAdjustedTooltipPosition(arg1)
toolTip.popup(position.x, position.y)
},
tooltips: {
enabled: false,
},
legend: {
display: false
},
scales: {
xAxes: [{
id: 'x-axis-1',
position: 'bottom',
stacked: false,
gridLines: {
drawOnChartArea: false,
drawBorder: false,
drawTicks: false,
},
ticks: {
fontSize: Theme.asideTextFontSize,
fontColor: d.baseColor1,
padding: Theme.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: Theme.halfPadding,
maxTicksLimit: Theme.asideTextFontSize,
beginAtZero: true,
stepSize: 1,
callback: function(value, index, values) {
return LocaleUtils.numberToLocaleString(value)
}
}
}]
}
}
}
StatusMenu {
id: toolTip
width: 243 //By design
topPadding: Theme.padding
bottomPadding: topPadding
leftPadding: topPadding
rightPadding: topPadding
parent: Overlay.overlay
ColumnLayout {
spacing: Theme.padding
RowLayout {
Layout.fillWidth: true
StatusBaseText {
elide: Qt.ElideRight
color: Theme.palette.baseColor1
text: d.tooltipConfig.timeRangeString
}
Item {
Layout.fillWidth: true
}
StatusBaseText {
Layout.alignment: Qt.AlignRight
elide: Qt.ElideRight
text: d.hoveredModelMetadata ? d.tooltipConfig.timeRangeFormatter(d.hoveredModelMetadata.start, d.hoveredModelMetadata.end)
: ""
}
}
RowLayout {
Layout.fillWidth: true
StatusBaseText {
elide: Qt.ElideRight
font.pixelSize: Theme.primaryTextFontSize
color: Theme.palette.baseColor1
text: qsTr("No. of Messages")
}
Item { Layout.fillWidth: true }
StatusBaseText {
Layout.alignment: Qt.AlignRight
elide: Qt.ElideRight
font.pixelSize: Theme.primaryTextFontSize
color: Theme.palette.directColor1
text: LocaleUtils.numberToLocaleString(d.hoveredBarValue)
}
}
}
}
}