Skip to content

Commit f0c2fc3

Browse files
committed
FEAT: added eurostat index browser
1 parent 7e978cb commit f0c2fc3

File tree

2 files changed

+169
-2
lines changed

2 files changed

+169
-2
lines changed

larray_editor/editor.py

+98-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import io
22
import os
33
import re
4+
from datetime import datetime
45
import sys
56
from collections.abc import Sequence
67
from contextlib import redirect_stdout
@@ -26,6 +27,7 @@
2627
import matplotlib
2728
import matplotlib.axes
2829
import numpy as np
30+
import pandas as pd
2931

3032
import larray as la
3133

@@ -34,12 +36,14 @@
3436
get_versions, get_documentation_url, urls, RecentlyUsedList)
3537
from larray_editor.arraywidget import ArrayEditorWidget
3638
from larray_editor.commands import EditSessionArrayCommand, EditCurrentArrayCommand
39+
from larray_editor.treemodel import SimpleTreeNode, SimpleLazyTreeModel
3740

3841
from qtpy.QtCore import Qt, QUrl, QSettings
3942
from qtpy.QtGui import QDesktopServices, QKeySequence
4043
from qtpy.QtWidgets import (QMainWindow, QWidget, QListWidget, QListWidgetItem, QSplitter, QFileDialog, QPushButton,
4144
QDialogButtonBox, QShortcut, QVBoxLayout, QGridLayout, QLineEdit,
42-
QCheckBox, QComboBox, QMessageBox, QDialog, QInputDialog, QLabel, QGroupBox, QRadioButton)
45+
QCheckBox, QComboBox, QMessageBox, QDialog, QInputDialog, QLabel, QGroupBox, QRadioButton,
46+
QTreeView)
4347

4448
try:
4549
from qtpy.QtWidgets import QUndoStack
@@ -83,6 +87,87 @@
8387
DISPLAY_IN_GRID = (la.Array, np.ndarray)
8488

8589

90+
def num_leading_spaces(s):
91+
i = 0
92+
while s[i] == ' ':
93+
i += 1
94+
return i
95+
96+
97+
def indented_df_to_treenode(df, indent=4, indented_col=0, colnames=None, header=None):
98+
if colnames is None:
99+
colnames = df.columns.tolist()
100+
if header is None:
101+
header = [name.capitalize() for name in colnames]
102+
103+
root = SimpleTreeNode(None, header)
104+
df = df[colnames]
105+
parent_per_level = {0: root}
106+
# iterating on the df rows directly (via df.iterrows()) is too slow
107+
for row in df.values:
108+
row_data = row.tolist()
109+
indented_col_value = row_data[indented_col]
110+
level = num_leading_spaces(indented_col_value) // indent
111+
# remove indentation
112+
row_data[indented_col] = indented_col_value.strip()
113+
114+
parent_node = parent_per_level[level]
115+
node = SimpleTreeNode(parent_node, row_data)
116+
parent_node.children.append(node)
117+
parent_per_level[level + 1] = node
118+
return root
119+
120+
121+
class EurostatBrowserDialog(QDialog):
122+
def __init__(self, index, parent=None):
123+
super(EurostatBrowserDialog, self).__init__(parent)
124+
125+
assert isinstance(index, pd.DataFrame)
126+
127+
# drop unused/redundant "type" column
128+
index = index.drop('type', axis=1)
129+
130+
# display dates using locale
131+
# import locale
132+
# locale.setlocale(locale.LC_ALL, '')
133+
# datetime.date.strftime('%x')
134+
135+
# CHECK: this builds the whole (model) tree in memory eagerly, so it is not lazy despite using a
136+
# Simple*Lazy*TreeModel, but it is fast enough for now. *If* it ever becomes a problem,
137+
# we could make this lazy pretty easily (see treemodel.LazyDictTreeNode for an example).
138+
root = indented_df_to_treenode(index)
139+
model = SimpleLazyTreeModel(root)
140+
tree = QTreeView()
141+
tree.setModel(model)
142+
tree.setUniformRowHeights(True)
143+
tree.selectionModel().currentChanged.connect(self.view_eurostat_indicator)
144+
tree.setColumnWidth(0, 320)
145+
146+
self.resize(450, 600)
147+
self.setWindowTitle("Select dataset")
148+
149+
# set the layout
150+
layout = QVBoxLayout()
151+
# layout.addWidget(toolbar)
152+
layout.addWidget(tree)
153+
self.setLayout(layout)
154+
155+
def view_eurostat_indicator(self, index):
156+
from larray_eurostat import eurostat_get
157+
158+
node = index.internalPointer()
159+
title, code, last_update_of_data, last_table_structure_change, data_start, data_end = node.data
160+
if not node.children:
161+
last_update_of_data = datetime.strptime(last_update_of_data, "%d.%m.%Y")
162+
last_table_structure_change = datetime.strptime(last_table_structure_change, "%d.%m.%Y")
163+
last_change = max(last_update_of_data, last_table_structure_change)
164+
try:
165+
arr = eurostat_get(code, maxage=last_change, cache_dir='__array_cache__')
166+
except Exception:
167+
QMessageBox.critical(self, "Error", "Failed to load {}".format(code))
168+
self.parent().view_expr(arr, expr=code)
169+
170+
86171
class AbstractEditor(QMainWindow):
87172
"""Abstract Editor Window"""
88173

@@ -553,6 +638,7 @@ def _setup_file_menu(self, menu_bar):
553638
# ============= #
554639
file_menu.addSeparator()
555640
file_menu.addAction(create_action(self, _('&Load Example Dataset'), triggered=self.load_example))
641+
file_menu.addAction(create_action(self, _('&Browse Eurostat Datasets'), triggered=self.browse_eurostat))
556642
# ============= #
557643
# SCRIPTS #
558644
# ============= #
@@ -675,7 +761,7 @@ def line_edit_update(self):
675761

676762
def view_expr(self, array, expr):
677763
self._listwidget.clearSelection()
678-
self.set_current_array(array, expr)
764+
self.set_current_array(array, name=expr)
679765

680766
def _display_in_grid(self, k, v):
681767
return not k.startswith('__') and isinstance(v, DISPLAY_IN_GRID)
@@ -1153,6 +1239,16 @@ def load_example(self):
11531239
filepath = AVAILABLE_EXAMPLE_DATA[dataset_name]
11541240
self._open_file(filepath)
11551241

1242+
def browse_eurostat(self):
1243+
from larray_eurostat import get_index
1244+
try:
1245+
df = get_index(cache_dir='__array_cache__', maxage=None)
1246+
except:
1247+
QMessageBox.critical(self, "Error", "Failed to fetch Eurostat dataset index")
1248+
return
1249+
dialog = EurostatBrowserDialog(df, parent=self)
1250+
dialog.show()
1251+
11561252

11571253
class ArrayEditor(AbstractEditor):
11581254
"""Array Editor Dialog"""

larray_editor/treemodel.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from qtpy.QtCore import Qt, QModelIndex, QAbstractItemModel
2+
3+
4+
class SimpleTreeNode(object):
5+
__slots__ = ['parent', 'data', 'children']
6+
7+
def __init__(self, parent, data):
8+
self.parent = parent
9+
self.data = data
10+
self.children = []
11+
12+
13+
class SimpleLazyTreeModel(QAbstractItemModel):
14+
def __init__(self, root, parent=None):
15+
super(SimpleLazyTreeModel, self).__init__(parent)
16+
assert isinstance(root, SimpleTreeNode)
17+
self.root = root
18+
19+
def columnCount(self, index):
20+
node = index.internalPointer() if index.isValid() else self.root
21+
return len(node.data)
22+
23+
def data(self, index, role):
24+
if not index.isValid():
25+
return None
26+
27+
if role != Qt.DisplayRole:
28+
return None
29+
30+
node = index.internalPointer()
31+
return node.data[index.column()]
32+
33+
def flags(self, index):
34+
if not index.isValid():
35+
return Qt.NoItemFlags
36+
37+
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
38+
39+
def headerData(self, section, orientation, role):
40+
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
41+
return self.root.data[section]
42+
return None
43+
44+
def index(self, row, column, parent_index):
45+
if not self.hasIndex(row, column, parent_index):
46+
return QModelIndex()
47+
48+
parent_node = parent_index.internalPointer() if parent_index.isValid() else self.root
49+
child_node = parent_node.children[row]
50+
return self.createIndex(row, column, child_node)
51+
52+
def parent(self, index):
53+
if not index.isValid():
54+
return QModelIndex()
55+
56+
child_node = index.internalPointer()
57+
parent_node = child_node.parent
58+
59+
if parent_node == self.root:
60+
return QModelIndex()
61+
62+
grand_parent = parent_node.parent
63+
parent_row = grand_parent.children.index(parent_node)
64+
return self.createIndex(parent_row, 0, parent_node)
65+
66+
def rowCount(self, index):
67+
if index.column() > 0:
68+
return 0
69+
70+
node = index.internalPointer() if index.isValid() else self.root
71+
return len(node.children)

0 commit comments

Comments
 (0)