Compare commits
2 Commits
main
...
kate/dashboards
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b99bedd8d | |||
| d71fd4becf |
+475
-281
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SpeckleDashboard</class>
|
||||
<widget class="QDockWidget" name="SpeckleDashboard">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>575</width>
|
||||
<height>651</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
||||
<property name="windowTitle">
|
||||
<string>Speckle Dashboard</string>
|
||||
</property>
|
||||
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="5,1">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="chart">
|
||||
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
|
||||
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="streamListButtons">
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="streamBranchLabel">
|
||||
<property name="text">
|
||||
<string>Model properties</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="selectionDropdown"/>
|
||||
</item>
|
||||
|
||||
<item row="3" column="1">
|
||||
<widget class="QListWidget" name="dataWidget">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Fixed</enum>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::ListMode</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
|
||||
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -334,6 +334,23 @@
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
<item row="14" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
|
||||
<item>
|
||||
<widget class="QPushButton" name="open_dashboard">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open Dashboard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
import random
|
||||
from qgis.PyQt import QtWidgets, uic
|
||||
from qgis.PyQt import QtGui
|
||||
from qgis.PyQt.QtGui import QIcon, QPixmap
|
||||
from qgis.PyQt.QtWidgets import (
|
||||
QCheckBox,
|
||||
QListWidgetItem,
|
||||
QAction,
|
||||
QDockWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QWidget,
|
||||
QLabel,
|
||||
)
|
||||
from qgis.PyQt import QtCore
|
||||
from qgis.PyQt.QtCore import pyqtSignal, Qt
|
||||
|
||||
# from qgis.PyQt import QtCore, QtWidgets #, QtWebEngineWidgets
|
||||
from PyQt5 import *
|
||||
from PyQt5.QtCore import QUrl
|
||||
import plotly.express as px
|
||||
from PyQt5.QtWebKitWidgets import QWebView
|
||||
import pandas as pd
|
||||
import os
|
||||
|
||||
FORM_CLASS, _ = uic.loadUiType(
|
||||
os.path.join(os.path.join(os.path.dirname(__file__), "ui", "dashboard.ui"))
|
||||
)
|
||||
|
||||
|
||||
class SpeckleDashboard(QtWidgets.QDockWidget, FORM_CLASS):
|
||||
closingPlugin = pyqtSignal()
|
||||
dataStorage = None
|
||||
|
||||
dataNumeric: dict = {}
|
||||
dataText: dict = {}
|
||||
|
||||
current_filter: str = ""
|
||||
current_index: int = -1
|
||||
selectionDropdown: QtWidgets.QComboBox
|
||||
dataWidget: QtWidgets.QListWidget
|
||||
chart: QWidget
|
||||
browser = None
|
||||
existing_web: int = 0
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Constructor."""
|
||||
super(SpeckleDashboard, self).__init__(parent)
|
||||
# Set up the user interface from Designer through FORM_CLASS.
|
||||
# After self.setupUi() you can access any designer object by doing
|
||||
# self.<objectname>, and you can use autoconnect slots - see
|
||||
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
|
||||
# #widgets-and-dialogs-with-auto-connect
|
||||
|
||||
self.browser = QWebView(self) # https://github.com/qgis/QGIS/issues/26048
|
||||
self.setupUi(self)
|
||||
|
||||
self.selectionDropdown.clear()
|
||||
self.selectionDropdown.addItems(["area", "property value"])
|
||||
self.selectionDropdown.setCurrentIndex(0)
|
||||
|
||||
self.selectionDropdown.currentIndexChanged.connect(self.populateUI)
|
||||
|
||||
def setup(self):
|
||||
self.dataWidget.clear()
|
||||
for i, (key, val) in enumerate(self.dataNumeric.items()):
|
||||
# property_filter = self.selectionDropdown.currentText()
|
||||
if self.current_filter in key or key in self.current_filter:
|
||||
listItem = QListWidgetItem(f"{key}: {val}")
|
||||
self.dataWidget.addItem(listItem)
|
||||
# self.dataWidget.setMaximumHeight(50)
|
||||
|
||||
def populateUI(self, force=0):
|
||||
print(self.selectionDropdown.currentIndex())
|
||||
print(self.current_filter)
|
||||
if self.selectionDropdown.currentText() == "":
|
||||
self.current_filter = "area"
|
||||
self.current_index = 0
|
||||
self.setup()
|
||||
self.selectionDropdown.setIndex(0)
|
||||
|
||||
self.createChart()
|
||||
elif self.selectionDropdown.currentText() != self.current_filter:
|
||||
self.current_filter = self.selectionDropdown.currentText()
|
||||
self.current_index = self.selectionDropdown.currentIndex()
|
||||
self.setup()
|
||||
self.createChart()
|
||||
if force == 1:
|
||||
self.setup()
|
||||
self.createChart()
|
||||
|
||||
def update(self):
|
||||
self.dataNumeric = {}
|
||||
self.dataText = {}
|
||||
|
||||
for i in range(10):
|
||||
# keys = ["key1", "key2", "key3", "key4", "key5"]
|
||||
# key = keys[random.randint(0, 4)]
|
||||
value = random.randint(10, 100)
|
||||
self.dataNumeric.update({"index": i, f"area {random.randint(1, 4)}": value})
|
||||
for i in range(10):
|
||||
# keys = ["key1", "key2", "key3", "key4", "key5"]
|
||||
# key = keys[random.randint(0, 4)]
|
||||
value = random.randint(10, 100)
|
||||
self.dataNumeric.update(
|
||||
{
|
||||
"index": i + 10,
|
||||
f"property value {random.randint(1, 4)}": [
|
||||
value,
|
||||
value * 0.5,
|
||||
value * 2,
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
r"""
|
||||
layer = getLayerByName(self.dataStorage.project, "Speckle_dashboard")
|
||||
fields = layer.fields()
|
||||
for i, key in enumerate(fields.names()):
|
||||
if key in ["Branch URL", "commit_id", "updated"]:
|
||||
continue
|
||||
|
||||
value = None
|
||||
variant = fields.at(i).type()
|
||||
|
||||
if "value" in key:
|
||||
value = []
|
||||
if "area" in key:
|
||||
value = 0
|
||||
|
||||
for feat in layer.getFeatures():
|
||||
if isinstance(value, List):
|
||||
if feat[key] is not None:
|
||||
list_vals = (
|
||||
feat[key]
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace("'", "")
|
||||
.split(",")
|
||||
)
|
||||
value.extend([x for x in list_vals if x != ""])
|
||||
print(value)
|
||||
|
||||
elif isinstance(value, float) or isinstance(value, int):
|
||||
if feat[key] is not None:
|
||||
value += feat[key]
|
||||
|
||||
self.dataNumeric.update({key: value})
|
||||
"""
|
||||
|
||||
self.populateUI(force=1)
|
||||
|
||||
def createChart(self):
|
||||
# https://stackoverflow.com/questions/60522103/how-to-have-plotly-graph-as-pyqt5-widget
|
||||
r"""
|
||||
print("PRINT DATAFRAME")
|
||||
df = pd.DataFrame.from_dict(self.dataNumeric, orient="index", columns=["value"])
|
||||
df2 = df.reset_index()
|
||||
print(df2)
|
||||
|
||||
property_filter = str(self.selectionDropdown.currentText())
|
||||
if len(property_filter) <= 1:
|
||||
property_filter = "area"
|
||||
df2 = df2[df2["index"].str.lower().str.contains(property_filter)]
|
||||
print(df2)
|
||||
|
||||
if "area" in property_filter:
|
||||
fig = px.pie(
|
||||
df2,
|
||||
values="value",
|
||||
names="index",
|
||||
title="Land use distribution",
|
||||
hole=0.5,
|
||||
)
|
||||
|
||||
elif "value" in property_filter:
|
||||
all_column_vals = df2["value"].to_list()
|
||||
|
||||
all_column_vals_separated = []
|
||||
[all_column_vals_separated.extend(x) for x in all_column_vals]
|
||||
|
||||
all_vals = [float(x) for x in all_column_vals_separated]
|
||||
df2 = pd.DataFrame([{property_filter: val} for val in all_vals])
|
||||
fig = px.histogram(df2, x=property_filter, title="Property values")
|
||||
else:
|
||||
df2 = df2.loc["area" in df2["index"]]
|
||||
fig = px.pie(
|
||||
df2, values="value", names="index", title="Land use distribution"
|
||||
)
|
||||
print(df2)
|
||||
|
||||
# remove all buttons
|
||||
# try:
|
||||
# for i in reversed(range(self.chart.layout.count())):
|
||||
# self.chart.layout.itemAt(i).widget().setParent(None)
|
||||
# except: pass
|
||||
"""
|
||||
fig = self.assembleFigure()
|
||||
|
||||
if self.existing_web == 0:
|
||||
self.browser = QWebView(self)
|
||||
|
||||
self.browser.setHtml(
|
||||
fig.to_html(include_plotlyjs="cdn", config={"displayModeBar": False})
|
||||
)
|
||||
# self.browser.setUrl(QUrl("https://speckle.xyz/streams/e2effcfa27/commits/f76cedd9a6"))
|
||||
|
||||
self.chart.layout = QHBoxLayout(self.chart)
|
||||
# self.browser.setMaximumHeight(400)
|
||||
|
||||
if self.existing_web == 0:
|
||||
self.chart.layout.addWidget(self.browser)
|
||||
self.existing_web = 1
|
||||
|
||||
return
|
||||
# https://www.pythonguis.com/tutorials/plotting-pyqtgraph/
|
||||
|
||||
graphWidget = pg.PlotWidget()
|
||||
|
||||
hour = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]
|
||||
|
||||
# plot data: x, y values
|
||||
graphWidget.plot(hour, temperature)
|
||||
|
||||
# remove all buttons
|
||||
try:
|
||||
for i in reversed(range(self.chart.layout.count())):
|
||||
self.chart.layout.itemAt(i).widget().setParent(None)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.chart.layout = QHBoxLayout(self.chart)
|
||||
self.chart.layout.addWidget(graphWidget)
|
||||
|
||||
# set the QWebEngineView instance as main widget
|
||||
# self.setCentralWidget(plot_widget)
|
||||
|
||||
def assembleFigure(self):
|
||||
import plotly
|
||||
from plotly.subplots import make_subplots
|
||||
|
||||
cols = 1
|
||||
rows = 2 # len(TOTAL_SERVER_LIST) #math.ceil(len(TOTAL_SERVER_LIST)/cols)
|
||||
# specs_col = [{"type": "bar"}, {"type": "sunburst"}]
|
||||
specs = [[{"type": "sunburst"}], [{"type": "bar"}]]
|
||||
# specs = [ specs_col for r in range(rows)]
|
||||
# df = pd.DataFrame(columns=['Country', '', 'count'])
|
||||
# for i in range(5): df = df.append({'Name' : 'Ankit', 'Articles' : 97, 'Improved' : 2200},ignore_index = True)
|
||||
fig = make_subplots(rows=rows, cols=cols, specs=specs)
|
||||
|
||||
# dataframe
|
||||
df = pd.DataFrame.from_dict(self.dataNumeric, orient="index", columns=["value"])
|
||||
df = df.reset_index()
|
||||
print(df)
|
||||
|
||||
# plot - pie chart
|
||||
# df2 = df2.loc["area" in df2["index"]]
|
||||
property_filter = "area"
|
||||
df2 = df[df["index"].str.lower().str.contains(property_filter)].copy()
|
||||
fig2 = px.pie(
|
||||
df2,
|
||||
values="value",
|
||||
names="index",
|
||||
title="Land use distribution",
|
||||
hole=0.5,
|
||||
)
|
||||
fig.add_trace(fig2.data[0], row=1, col=1)
|
||||
|
||||
# plot - histogram
|
||||
property_filter = "value"
|
||||
# df2 = df[df["index"].str.lower().str.contains(property_filter)].copy()
|
||||
all_column_vals = df[df["index"].str.lower().str.contains(property_filter)][
|
||||
"value"
|
||||
].to_list()
|
||||
all_column_vals_separated = []
|
||||
[
|
||||
all_column_vals_separated.extend(x)
|
||||
for x in all_column_vals
|
||||
if isinstance(x, list)
|
||||
]
|
||||
|
||||
all_vals = [float(x) for x in all_column_vals_separated]
|
||||
df2 = pd.DataFrame([{property_filter: val} for val in all_vals])
|
||||
print(df2)
|
||||
fig2 = px.histogram(df2, x=property_filter, title="Property values")
|
||||
fig.add_trace(fig2.data[0], row=2, col=1)
|
||||
|
||||
# plot - sunburst
|
||||
# fig2 = px.sunburst(
|
||||
# df2,
|
||||
# path=["server_id", "isWebhook", "isMultiplayer"],
|
||||
# color="isMultiplayer",
|
||||
# values="count",
|
||||
# color_discrete_map={True: "blue", False: "red"},
|
||||
# )
|
||||
# fig.add_trace(fig2.data[0], row=2, col=1)
|
||||
|
||||
fig.update_layout(
|
||||
title="Some title",
|
||||
)
|
||||
|
||||
width = 300
|
||||
fig.update_layout(
|
||||
autosize=False,
|
||||
width=width,
|
||||
height=width / cols * rows,
|
||||
)
|
||||
|
||||
return fig
|
||||
# fig.update_yaxes(range = [0, 30], col=1) #, title_text=f"Total receives: {total_receives} by {people} people")
|
||||
# fig.update_xaxes(range = [0, 31*4], col=1)
|
||||
|
||||
# fig.show()
|
||||
|
||||
# plotly.offline.plot(fig, filename='Graphs/stats_perServer_pie.html')
|
||||
Reference in New Issue
Block a user