|
2 | 2 |
|
3 | 3 | from aiida.orm import ArrayData |
4 | 4 | from ipywidgets import HTML, Dropdown, VBox |
| 5 | +from numpy import floating as npfloat |
5 | 6 |
|
6 | 7 |
|
7 | 8 | class XYZArrayDataTableWidget(VBox): |
8 | 9 | """ |
9 | | - Custom widget to display array data produced from ChemShell jobs. |
| 10 | + Custom widget to display array data associated with XYZ coordinates. |
10 | 11 |
|
11 | 12 | Create a table based widget for displaying different arrays within |
12 | 13 | an ArrayData object assuming that all the data is XYZ based i.e |
@@ -58,3 +59,74 @@ def _render_array(self, change) -> None: |
58 | 59 |
|
59 | 60 | self.children = [self.array_selector, HTML(html)] |
60 | 61 | return |
| 62 | + |
| 63 | + |
| 64 | +class GenericArrayDataTableWidget(VBox): |
| 65 | + """Custom widget to display generic array data as a table.""" |
| 66 | + |
| 67 | + def __init__(self, array: ArrayData, **kwargs): |
| 68 | + """GenericArrayDataTableWidget Constructor.""" |
| 69 | + super().__init__(**kwargs) |
| 70 | + self.array = array |
| 71 | + self.array_selector = Dropdown( |
| 72 | + options=self.array.get_arraynames(), |
| 73 | + description="Array Labels:", |
| 74 | + layout={"width": "50%"}, |
| 75 | + **kwargs, |
| 76 | + ) |
| 77 | + self._render_array({"new": self.array_selector.index, "old": -1}) |
| 78 | + self.array_selector.observe(self._render_array, "index") |
| 79 | + return |
| 80 | + |
| 81 | + def _render_array(self, change: dict) -> None: |
| 82 | + """Create a HTML table based on the currently selected array.""" |
| 83 | + index = change["new"] |
| 84 | + if index == change["old"]: |
| 85 | + return |
| 86 | + values = self.array.get_array(self.array.get_arraynames()[index]) |
| 87 | + if len(values.shape) > 2: |
| 88 | + self.children = [ |
| 89 | + self.array_selector, |
| 90 | + HTML("<p>To many dimension to create 2D table from array.</p>"), |
| 91 | + ] |
| 92 | + return |
| 93 | + if len(values.shape) == 1: |
| 94 | + nrows = values.shape[0] |
| 95 | + ncols = 1 |
| 96 | + else: |
| 97 | + ncols, nrows = values.shape # type: ignore |
| 98 | + values = values.reshape(1, -1) |
| 99 | + |
| 100 | + # Unique styling prefix to prevent CSS bleeding into other Jupyter elements |
| 101 | + |
| 102 | + # Build Table Header (Column Indices) |
| 103 | + html = "<table style='width:100%; border: 1px solid #ddd; text-align: left; " |
| 104 | + html += "border-collapse: collapse;'>" |
| 105 | + html += "<tr style='background-color: #2196F3; color: white;'>" |
| 106 | + html += "<th>Index</th>" |
| 107 | + if ncols > 1: |
| 108 | + for c in range(ncols): |
| 109 | + html += f"<th>{c}</th>" |
| 110 | + else: |
| 111 | + col_header = ( |
| 112 | + self.array.get_arraynames()[index].replace("_", " ").capitalize() |
| 113 | + ) |
| 114 | + html += f"<th>{col_header}</th>" |
| 115 | + html += "</tr></thead><tbody>" |
| 116 | + |
| 117 | + # Build Table Body (Row Index + Cell Data) |
| 118 | + for r in range(nrows): |
| 119 | + html += "<tr>" |
| 120 | + html += f'<th class="row-idx">{r}</th>' |
| 121 | + for c in range(ncols): |
| 122 | + val = values[c, r] |
| 123 | + # Format floats to 6 decimals, leave ints/strings as they are |
| 124 | + formatted_val = ( |
| 125 | + f"{val:.6f}" if isinstance(val, float | npfloat) else str(val) |
| 126 | + ) |
| 127 | + html += f"<td>{formatted_val}</td>" |
| 128 | + html += "</tr>" |
| 129 | + |
| 130 | + html += "</tbody></table></div>" |
| 131 | + self.children = [self.array_selector, HTML(html)] |
| 132 | + return |
0 commit comments