|
1 | 1 | import io
|
2 | 2 | import os
|
3 | 3 | import re
|
| 4 | +from datetime import datetime |
4 | 5 | import sys
|
5 | 6 | from collections.abc import Sequence
|
6 | 7 | from contextlib import redirect_stdout
|
|
26 | 27 | import matplotlib
|
27 | 28 | import matplotlib.axes
|
28 | 29 | import numpy as np
|
| 30 | +import pandas as pd |
29 | 31 |
|
30 | 32 | import larray as la
|
31 | 33 |
|
|
34 | 36 | get_versions, get_documentation_url, urls, RecentlyUsedList)
|
35 | 37 | from larray_editor.arraywidget import ArrayEditorWidget
|
36 | 38 | from larray_editor.commands import EditSessionArrayCommand, EditCurrentArrayCommand
|
| 39 | +from larray_editor.treemodel import SimpleTreeNode, SimpleLazyTreeModel |
37 | 40 |
|
38 | 41 | from qtpy.QtCore import Qt, QUrl, QSettings
|
39 | 42 | from qtpy.QtGui import QDesktopServices, QKeySequence
|
40 | 43 | from qtpy.QtWidgets import (QMainWindow, QWidget, QListWidget, QListWidgetItem, QSplitter, QFileDialog, QPushButton,
|
41 | 44 | 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) |
43 | 47 |
|
44 | 48 | try:
|
45 | 49 | from qtpy.QtWidgets import QUndoStack
|
|
83 | 87 | DISPLAY_IN_GRID = (la.Array, np.ndarray)
|
84 | 88 |
|
85 | 89 |
|
| 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 | + |
86 | 171 | class AbstractEditor(QMainWindow):
|
87 | 172 | """Abstract Editor Window"""
|
88 | 173 |
|
@@ -553,6 +638,7 @@ def _setup_file_menu(self, menu_bar):
|
553 | 638 | # ============= #
|
554 | 639 | file_menu.addSeparator()
|
555 | 640 | 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)) |
556 | 642 | # ============= #
|
557 | 643 | # SCRIPTS #
|
558 | 644 | # ============= #
|
@@ -675,7 +761,7 @@ def line_edit_update(self):
|
675 | 761 |
|
676 | 762 | def view_expr(self, array, expr):
|
677 | 763 | self._listwidget.clearSelection()
|
678 |
| - self.set_current_array(array, expr) |
| 764 | + self.set_current_array(array, name=expr) |
679 | 765 |
|
680 | 766 | def _display_in_grid(self, k, v):
|
681 | 767 | return not k.startswith('__') and isinstance(v, DISPLAY_IN_GRID)
|
@@ -1153,6 +1239,16 @@ def load_example(self):
|
1153 | 1239 | filepath = AVAILABLE_EXAMPLE_DATA[dataset_name]
|
1154 | 1240 | self._open_file(filepath)
|
1155 | 1241 |
|
| 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 | + |
1156 | 1252 |
|
1157 | 1253 | class ArrayEditor(AbstractEditor):
|
1158 | 1254 | """Array Editor Dialog"""
|
|
0 commit comments