Skip to content

Commit 08836db

Browse files
committed
Use table format instead
1 parent 060d163 commit 08836db

File tree

2 files changed

+322
-84
lines changed

2 files changed

+322
-84
lines changed

lib/iris/experimental/representation.py

Lines changed: 163 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from __future__ import (absolute_import, division, print_function)
2424
from six.moves import (filter, input, map, range, zip) # noqa
2525

26+
import re
27+
2628

2729
class CubeRepresentation(object):
2830
"""
@@ -31,23 +33,49 @@ class CubeRepresentation(object):
3133
This includes:
3234
3335
* ``_html_repr_``: a representation of the cube as an html object,
34-
available in jupyter notebooks.
36+
available in Jupyter notebooks. Specifically, this is presented as an
37+
html table.
3538
3639
"""
40+
3741
_template = """
3842
<style>
3943
a.iris {{
4044
text-decoration: none !important;
4145
}}
42-
.iris {{
46+
table.iris {{
4347
white-space: pre;
44-
}}
45-
.iris-panel-group {{
46-
display: block;
47-
overflow: visible;
48-
width: max-content;
48+
border: 1px solid;
49+
border-color: #9c9c9c;
4950
font-family: monaco, monospace;
5051
}}
52+
th.iris {{
53+
background: #303f3f;
54+
color: #e0e0e0;
55+
border-left: 1px solid;
56+
border-color: #9c9c9c;
57+
font-size: 1.05em;
58+
min-width: 50px;
59+
max-width: 125px;
60+
}}
61+
tr.iris :first-child {{
62+
border-right: 1px solid #9c9c9c !important;
63+
}}
64+
td.iris-title {{
65+
background: #d5dcdf;
66+
border-top: 1px solid #9c9c9c;
67+
font-weight: bold;
68+
}}
69+
.iris-word-cell {{
70+
text-align: left !important;
71+
white-space: pre;
72+
}}
73+
.iris-subheading-cell {{
74+
padding-left: 2em !important;
75+
}}
76+
.iris-inclusion-cell {{
77+
padding-right: 1em !important;
78+
}}
5179
.iris-panel-body {{
5280
padding-top: 0px;
5381
}}
@@ -58,55 +86,19 @@ class CubeRepresentation(object):
5886
margin-top: 7px;
5987
}}
6088
</style>
61-
<div class="panel-group iris-panel-group">
62-
<div class="panel panel-default">
63-
<div class="panel-heading">
64-
<h4 class="panel-title">
65-
<a class="iris" data-toggle="collapse" href="#collapse1-{obj_id}">
66-
{summary}
67-
</a>
68-
</h4>
69-
</div>
70-
<div id="collapse1-{obj_id}" class="panel-collapse collapse in">
71-
{content}
72-
</div>
73-
</div>
74-
</div>
89+
<table class="iris" id="{id}">
90+
{header}
91+
{shape}
92+
{content}
93+
</table>
7594
"""
7695

77-
# Need to format the keywords:
78-
# `emt_id`, `obj_id`, `str_heading`, `opened`, `content`.
79-
_insert_content = """
80-
<div class="panel-body iris-panel-body">
81-
<h4 class="panel-title iris-panel-title">
82-
<a class="iris" data-toggle="collapse" href="#{emt_id}-{obj_id}">
83-
{str_heading}
84-
</a>
85-
</h4>
86-
</div>
87-
<div id="{emt_id}-{obj_id}" class="panel-collapse collapse{opened}">
88-
<div class="panel-body iris-panel-body">
89-
<p class="iris">{content}</p>
90-
</div>
91-
</div>
92-
"""
93-
9496
def __init__(self, cube):
95-
"""
96-
Produce different representations of a :class:`~iris.cube.Cube`.
97-
98-
Args:
99-
100-
* cube
101-
the cube to produce representations of.
102-
103-
"""
104-
10597
self.cube = cube
10698
self.cube_id = id(self.cube)
10799
self.cube_str = str(self.cube)
108100

109-
self.summary = None
101+
self.summary = self.cube.summary(True)
110102
self.str_headings = {
111103
'Dimension coordinates:': None,
112104
'Auxiliary coordinates:': None,
@@ -115,18 +107,32 @@ def __init__(self, cube):
115107
'Attributes:': None,
116108
'Cell methods:': None,
117109
}
118-
self.major_headings = ['Dimension coordinates:',
119-
'Auxiliary coordinates:',
120-
'Attributes:']
110+
self.dim_desc_coords = ['Dimension coordinates:',
111+
'Auxiliary coordinates:',
112+
'Derived coordinates:']
121113

122-
def _get_bits(self):
114+
def _summary_content(self):
123115
"""
124-
Parse the str representation of the cube to retrieve the elements
125-
to add to an html representation of the cube.
116+
Deal with the content in the summary (the first line of printout).
117+
118+
This contains:
119+
* name (unit),
120+
* dim name (len of dim) for each dim.
126121
127122
"""
123+
emts = re.findall(r'\w+', self.summary)
124+
self.names = [' '.join(name.split('_')) for name in emts[::2]]
125+
self.shapes = emts[1::2]
126+
127+
# Name and unit are the first item in names and descs respectively.
128+
self.name = self.names.pop(0).title()
129+
self.units = self.shapes.pop(0)
130+
self.ndims = self.cube.ndim
131+
132+
def _get_bits(self):
128133
bits = self.cube_str.split('\n')
129-
self.summary = bits[0]
134+
# self.summary = bits[0]
135+
self._summary_content()
130136
left_indent = bits[1].split('D')[0]
131137

132138
# Get heading indices within the printout.
@@ -139,41 +145,123 @@ def _get_bits(self):
139145
continue
140146
else:
141147
start_inds.append(start_ind)
142-
# Make sure the indices are in order.
143-
start_inds = sorted(start_inds)
144148
# Mark the end of the file.
145-
start_inds.append(None)
149+
start_inds.append(0)
146150

147151
# Retrieve info for each heading from the printout.
148152
for i0, i1 in zip(start_inds[:-1], start_inds[1:]):
149153
str_heading_name = bits[i0].strip()
150-
if i1 is not None:
154+
if i1 != 0:
151155
content = bits[i0 + 1: i1]
152156
else:
153157
content = bits[i0 + 1:]
154158
self.str_headings[str_heading_name] = content
155159

160+
def _make_header(self):
161+
"""
162+
Make the table header. This is similar to the summary of the cube,
163+
but does not include dim shapes. These are included on the next table
164+
row down, and produced with `make_shapes_row`.
165+
166+
"""
167+
# Header row.
168+
tlc_template = \
169+
'<th class="iris iris-word-cell">{self.name} ({self.units})</th>'
170+
top_left_cell = tlc_template.format(self=self)
171+
cells = ['<tr class="iris">', top_left_cell]
172+
for dim_name in self.names:
173+
cells.append(
174+
'<th class="iris iris-word-cell">{}</th>'.format(dim_name))
175+
cells.append('</tr>')
176+
return '\n'.join(cell for cell in cells)
177+
178+
def _make_shapes_row(self):
179+
"""Add a row to show data / dimensions shape."""
180+
title_cell = \
181+
'<td class="iris-word-cell iris-subheading-cell">Shape</td>'
182+
cells = ['<tr class="iris">', title_cell]
183+
for shape in self.shapes:
184+
cells.append(
185+
'<td class="iris iris-inclusion-cell">{}</td>'.format(shape))
186+
cells.append('</td>')
187+
return '\n'.join(cell for cell in cells)
188+
189+
def _make_row(self, title, body=None, col_span=0):
190+
"""
191+
Produce one row for the table body; i.e.
192+
<tr><td>Coord name</td><td>x</td><td>-</td>...</tr>
193+
194+
`body` contains the content for each cell not in the left-most (title)
195+
column.
196+
If None, indicates this row is a title row (see below).
197+
`title` contains the row heading. If `body` is None, indicates
198+
that the row contains a sub-heading;
199+
e.g. 'Dimension coordinates:'.
200+
`col_span` indicates how many columns the string should span.
201+
202+
"""
203+
row = ['<tr class="iris">']
204+
template = ' <td{html_cls}>{content}</td>'
205+
if body is None:
206+
# This is a title row.
207+
# Strip off the trailing ':' from the title string.
208+
title = title.strip()[:-1]
209+
row.append(
210+
template.format(html_cls=' class="iris-title iris-word-cell"',
211+
content=title))
212+
# Add blank cells for the rest of the rows.
213+
for _ in range(self.ndims):
214+
row.append(template.format(html_cls=' class="iris-title"',
215+
content=''))
216+
else:
217+
# This is not a title row.
218+
# Deal with name of coord/attr etc. first.
219+
sub_title = '\t{}'.format(title)
220+
row.append(template.format(
221+
html_cls=' class="iris-word-cell iris-subheading-cell"',
222+
content=sub_title))
223+
# One further item or more than that?
224+
if col_span != 0:
225+
html_cls = ' class="{}" colspan="{}"'.format('iris-word-cell',
226+
col_span)
227+
row.append(template.format(html_cls=html_cls, content=body))
228+
else:
229+
# "Inclusion" - `x` or `-`.
230+
for itm in body:
231+
row.append(template.format(
232+
html_cls=' class="iris-inclusion-cell"',
233+
content=itm))
234+
row.append('</tr>')
235+
return row
236+
156237
def _make_content(self):
157238
elements = []
158239
for k, v in self.str_headings.items():
159240
if v is not None:
160-
html_id = k.split(' ')[0].lower().strip(':')
161-
content = '\n'.join(line for line in v)
162-
collapse = ' in' if k in self.major_headings else ''
163-
element = self._insert_content.format(emt_id=html_id,
164-
obj_id=self.cube_id,
165-
str_heading=k,
166-
opened=collapse,
167-
content=content)
168-
elements.append(element)
241+
# Add the sub-heading title.
242+
elements.extend(self._make_row(k))
243+
for line in v:
244+
# Add every other row in the sub-heading.
245+
if k in self.dim_desc_coords:
246+
body = re.findall(r'[\w-]+', line)
247+
title = body.pop(0)
248+
colspan = 0
249+
else:
250+
split_point = line.index(':')
251+
title = line[:split_point].strip()
252+
body = line[split_point + 2:].strip()
253+
colspan = self.ndims
254+
elements.extend(
255+
self._make_row(title, body=body, col_span=colspan))
169256
return '\n'.join(element for element in elements)
170257

171258
def repr_html(self):
172-
"""Produce an html representation of a cube and return it."""
259+
"""The `repr` interface to Jupyter."""
173260
self._get_bits()
174-
summary = self.summary
261+
header = self._make_header()
262+
shape = self._make_shapes_row()
175263
content = self._make_content()
176-
return self._template.format(summary=summary,
177-
content=content,
178-
obj_id=self.cube_id,
179-
)
264+
return self._template.format(header=header,
265+
id=self.cube_id,
266+
shape=shape,
267+
content=content)

0 commit comments

Comments
 (0)