import QtQuick 2.14 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 import StatusQ.Components 0.1 import StatusQ.Popups 0.1 import Storybook 1.0 import utils 1.0 SplitView { id: root orientation: Qt.Vertical QtObject { id: d //dummy data property real stepSize: 1000 property real minStep: 12000 property real maxStep: 22000 property var graphTabsModel: [{text: "Price", enabled: true}, {text: "Balance", enabled: true}] property var timeRangeTabsModel: [{text: "1H", enabled: true}, {text: "1D", enabled: true},{text: "7D", enabled: true}, {text: "1M", enabled: true}, {text: "6M", enabled: true}, {text: "1Y", enabled: true}, {text: "ALL", enabled: true}] function minutes(minutes = 0) { var newMinute = new Date(new Date().getTime() - (minutes * 60 * 1000)).toString(); if (newMinute.slice(10,12) === "00") { var dateToday = new Date(Date.now()).toString(); return dateToday.slice(4,7) + " " + dateToday.slice(8,10); } return newMinute.slice(10,16); } function hour(hours = 0) { var newHour = new Date(new Date().getTime() - (hours * 60 * 60 * 1000)).toString(); if (newHour.slice(10,12) === "00") { var dateToday = new Date(Date.now()).toString(); return dateToday.slice(4,7) + " " + dateToday.slice(8,10); } return newHour.slice(10,16); } function day(before = 0) { var newDay = new Date(Date.now() - before * 24 * 60 * 60 * 1000).toString(); return newDay.slice(4,7) + " " + newDay.slice(8,10); } function month(before = 0) { var newMonth = new Date(Date.now() - before * 24 * 60 * 60 * 1000).toString(); return newMonth.slice(4,7) + " '" + newMonth.slice(newMonth.indexOf("G")-3, newMonth.indexOf("G")-1); } property var timeRange: [ {'1H': [minutes(60), minutes(55), minutes(50), minutes(45), minutes(40), minutes(35), minutes(30), minutes(25), minutes(20), minutes(15), minutes(10), minutes(5), minutes()]}, {'1D': [hour(24), hour(23), hour(22), hour(21), hour(20), hour(19), hour(18), hour(17), hour(16), hour(15), hour(14), hour(13), hour(12), hour(11), hour(10), hour(9), hour(8), hour(7), hour(6), hour(5), hour(4), hour(3), hour(2), hour(1), hour()]}, {'7D': [day(6), day(5), day(4), day(3), day(2), day(1), day()]}, {'1M': [day(30), day(28), day(26), day(24), day(22), day(20), day(18), day(16), day(14), day(12), day(10), day(8), day(6), day(4), day()]}, {'6M': [month(150), month(120), month(90), month(60), month(30), month()]}, {'1Y': [month(330), month(300), month(270), month(240), month(210), month(180), month(150), month(120), month(90), month(60), month(30), month()]}, {'ALL': ['2016', '2017', '2018', '2019', '2020', '2021', '2022']} ] function generateData() { var result = []; for (var i = 0; i < timeRange[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange].length; ++i) { result[i] = Math.random() * (maxStep - minStep) + minStep; } return result; } readonly property var lineConfig: { return { type: 'line', labels: d.timeRange[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange], datasets: [{ label: 'Price', xAxisId: 'x-axis-1', yAxisId: 'y-axis-1', backgroundColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 0.2)' : 'rgba(67, 96, 223, 0.2)', borderColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 1)' : 'rgba(67, 96, 223, 1)', borderWidth: 3, pointRadius: 0, data: d.generateData() }], options: { maintainAspectRatio: false, responsive: true, legend: { display: false }, tooltips: { intersect: false, displayColors: false, callbacks: { footer: function(tooltipItem, data) { return 'Vol: $43,042,678,876'; }, label: function(tooltipItem, data) { let label = data.datasets[tooltipItem.datasetIndex].label || ''; if (label) { label += ': '; } label += tooltipItem.yLabel.toFixed(2); return label.slice(0,label.indexOf(":")+1)+ " $"+label.slice(label.indexOf(":")+2, label.length); } } }, scales: { xAxes: [{ id: 'x-axis-1', position: 'bottom', //type: 'linear', gridLines: { drawOnChartArea: false, drawBorder: false, drawTicks: false, }, ticks: { fontSize: 10, fontColor: (Theme.palette.name === "dark") ? '#909090' : '#939BA1', padding: 16 } }], yAxes: [{ position: 'left', id: 'y-axis-1', gridLines: { borderDash: [8, 4], drawBorder: false, drawTicks: false, color: (Theme.palette.name === "dark") ? '#909090' : '#939BA1' }, beforeDataLimits: (axis) => { axis.paddingTop = 25; axis.paddingBottom = 0; }, ticks: { fontSize: 10, fontColor: (Theme.palette.name === "dark") ? '#909090' : '#939BA1', padding: 8, min: d.minStep, max: d.maxStep, stepSize: d.stepSize, callback: function(value, index, ticks) { return '$' + value; }, } }] } } } } readonly property var barConfig: { return { type:"bar", options: { onHover: function(event, activeElements) { if (activeElements.length === 0) { toolTip.close() return } toolTip.text = "StatusMenu triggered by " + activeElements[0]._model.label toolTip.popup() toolTip.x += 10 toolTip.y -= toolTip.height + 10 }, tooltips: { enabled:false }, scales:{ xAxes:[{ id: "x-axis-1", position: "bottom", stacked: false, gridLines: { drawOnChartArea: false, drawBorder: false, drawTicks: false }, ticks: { fontSize:10, fontColor: "#939ba1", padding:16 } }], yAxes: [{ position: "left", id: "y-axis-1", stacked: false, gridLines: { borderDash: [5,3], lineWidth: 1, drawBorder: false, drawTicks: false, color: "#33939ba1" }, ticks: { fontSize: 10, fontColor: "#939ba1", padding: 8, maxTicksLimit: 10, beginAtZero: true, stepSize: 1 } }] } }, labels:["16:40","16:50","17:00","17:10","17:20","17:30"], datasets: [{ xAxisId: "x-axis-1", yAxisId: "y-axis-1", backgroundColor: "#334360df", pointRadius: 0, hoverBackgroundColor: "#334360df", hoverBorderColor: "#4360df", hoverBorderWidth: 2, data: [8,3,5,4,3,10] }] } } readonly property var crosshairConfig: { //binding to regenerate data and reset zoom chartType.currentText const generateDataset = (shift, label, color) => { var data = []; var x = 0; while (x < 30) { data.push({ x: x, y: Math.sin(shift + x / 3) }); x += Math.random(); } var dataset = { backgroundColor: color, borderColor: color, showLine: true, fill: false, pointRadius: 2, label: label, data: data, lineTension: 0, interpolate: true }; return dataset; } return { type: "scatter", options: { plugins: { crosshair: { enabled: true, sync: { enabled: false } } }, tooltips: { mode: "interpolate", intersect: true, }, scales: { xAxes: [{ id: 'x-axis-1', }], yAxes: [{ position: 'left', id: 'y-axis-1', }] } }, data: { datasets: [ generateDataset(0, "A", "red"), generateDataset(1, "B", "green"), generateDataset(2, "C", "blue") ] } }; } readonly property var minimisedConfig: { let config = Object.assign({}, d.lineConfig) config.datasets = [{ label: 'Price', xAxisId: 'x-axis-1', yAxisId: 'y-axis-1', backgroundColor: "transparent", borderColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 1)' : 'rgba(67, 96, 223, 1)', borderWidth: 3, pointRadius: 0, data: d.generateData() }] config.options = Object.assign({}, d.lineConfig.options) config.options.scales = { xAxes: [{ id: 'x-axis-1', display: false }], yAxes: [{ id: 'y-axis-1', display: false }] } return config; } readonly property var dataLabelsConfig: { var DATA_COUNT = 8; var labels = []; for (var i = 0; i < DATA_COUNT; ++i) { labels.push('' + i); } return { type: 'line', labels: labels, datasets: [{ backgroundColor: "blue", borderColor: "green", data: [5, 10, 15, 10, 5, 0, 5, 10], datalabels: { align: 'start', anchor: 'start' } }, { backgroundColor:"red", borderColor:"orance", data: [58, 80, 60, 70, 50, 60, 70, 80], }, { backgroundColor: "yellow", borderColor: "green", data: [30, 40, 30, 40, 30, 40, 30, 40], datalabels: { align: 'end', anchor: 'end' } }], options: { plugins: { datalabels: { backgroundColor: "red", borderRadius: 4, color: 'white', font: { weight: 'bold', size: 12 }, formatter: Math.round, padding: 6 } }, // Core options aspectRatio: 5 / 3, layout: { padding: { top: 32, right: 16, bottom: 16, left: 8 } }, elements: { line: { fill: false } }, scales: { yAxes: [{ stacked: true }] } } } } } StatusMenu { id: toolTip width: 243 //By design topPadding: Style.current.padding bottomPadding: topPadding leftPadding: topPadding rightPadding: topPadding parent: Overlay.overlay property alias text: label.text Label { id: label text: "Tooltip" anchors.centerIn: parent } } Item { SplitView.fillWidth: true SplitView.fillHeight: true StatusChartPanel { id: graphDetail height: 290 anchors.left: parent.left anchors.leftMargin: 24 anchors.right: parent.right anchors.rightMargin: 24 anchors.verticalCenter: parent.verticalCenter graphsModel: d.graphTabsModel timeRangeModel: d.timeRangeTabsModel chart.type: d.lineConfig.type chart.labels: d.lineConfig.labels chart.datasets: d.lineConfig.datasets chart.options: d.lineConfig.options } } LogsAndControlsPanel { id: logsAndControlsPanel SplitView.minimumHeight: 100 SplitView.preferredHeight: 150 ComboBox { id: chartType model: d.lineConfig ? ["line", "bar", "with crosshair", "line minimised", "data labels"] : [] currentIndex: 0 onCurrentTextChanged: { if (chartType.currentText === "line") { graphDetail.chart.type = d.lineConfig.type; graphDetail.chart.labels = d.lineConfig.labels; graphDetail.chart.datasets = d.lineConfig.datasets; graphDetail.chart.options = d.lineConfig.options; graphDetail.chart.plugins = [] graphDetail.chart.rebuild(); } else if (chartType.currentText === "bar") { graphDetail.chart.type = d.barConfig.type; graphDetail.chart.labels = d.barConfig.labels; graphDetail.chart.datasets = d.barConfig.datasets; graphDetail.chart.options = d.barConfig.options; graphDetail.chart.plugins = [] graphDetail.chart.rebuild(); } else if (chartType.currentText === "with crosshair") { graphDetail.chart.type = d.crosshairConfig.type; graphDetail.chart.options = d.crosshairConfig.options; graphDetail.chart.datasets = d.crosshairConfig.data.datasets; graphDetail.chart.plugins = [] graphDetail.chart.rebuild(); } else if (chartType.currentText === "line minimised") { graphDetail.chart.type = d.minimisedConfig.type; graphDetail.chart.labels = d.minimisedConfig.labels; graphDetail.chart.datasets = d.minimisedConfig.datasets; graphDetail.chart.options = d.minimisedConfig.options; graphDetail.chart.plugins = [] graphDetail.chart.rebuild(); } else if (chartType.currentText === "data labels") { graphDetail.chart.type = d.dataLabelsConfig.type; graphDetail.chart.datasets = d.dataLabelsConfig.datasets; graphDetail.chart.labels = d.dataLabelsConfig.labels; graphDetail.chart.options = d.dataLabelsConfig.options; graphDetail.chart.plugins = [graphDetail.chart.availablePlugins.datalabels] graphDetail.chart.rebuild(); } } } } } // category: Panels