|
4 | 4 | # pylint: disable=W0141
|
5 | 5 |
|
6 | 6 | import sys
|
| 7 | +import uuid |
7 | 8 |
|
8 | 9 | from pandas.core.base import PandasObject
|
9 | 10 | from pandas.core.common import adjoin, notnull
|
@@ -848,10 +849,9 @@ class HTMLFormatter(TableFormatter):
|
848 | 849 | indent_delta = 2
|
849 | 850 |
|
850 | 851 | def __init__(self, formatter, classes=None, max_rows=None, max_cols=None,
|
851 |
| - notebook=False, style=style): |
| 852 | + notebook=False): |
852 | 853 | self.fmt = formatter
|
853 | 854 | self.classes = classes
|
854 |
| - self.style = style |
855 | 855 |
|
856 | 856 | self.frame = self.fmt.frame
|
857 | 857 | self.columns = self.fmt.tr_frame.columns
|
@@ -1229,6 +1229,218 @@ def _write_hierarchical_rows(self, fmt_values, indent):
|
1229 | 1229 | nindex_levels=frame.index.nlevels)
|
1230 | 1230 |
|
1231 | 1231 |
|
| 1232 | + |
| 1233 | +class StyleFormatter(object): |
| 1234 | + |
| 1235 | + def __init__(self, data, rows=None, columns=None, table=None): |
| 1236 | + self.data = data |
| 1237 | + self.u = str(uuid.uuid1()).replace("-", "_") |
| 1238 | + rows, columns, table = self.format_style_args(data, rows, columns, |
| 1239 | + table) |
| 1240 | + self.rows = rows |
| 1241 | + self.columns = columns |
| 1242 | + self.table = table |
| 1243 | + |
| 1244 | + from jinja2 import Template |
| 1245 | + |
| 1246 | + self.t = Template(""" |
| 1247 | + <style type="text/css" > |
| 1248 | + #T_{{uuid}} tr { |
| 1249 | + border: none; |
| 1250 | + } |
| 1251 | + #T_{{uuid}} { |
| 1252 | + border: none; |
| 1253 | + } |
| 1254 | + #T_{{uuid}} th.blank { |
| 1255 | + border: none; |
| 1256 | + } |
| 1257 | +
|
| 1258 | + {% for s in style %} |
| 1259 | + #T_{{uuid}} {{s.selector}} { |
| 1260 | + {% for p,val in s.props %} |
| 1261 | + {{p}}: {{val}}; |
| 1262 | + {% endfor %} |
| 1263 | + } |
| 1264 | + {% endfor %} |
| 1265 | + {% for s in cellstyle %} |
| 1266 | + #T_{{uuid}}{{s.selector}} { |
| 1267 | + {% for p,val in s.props %} |
| 1268 | + {{p}}: {{val}}; |
| 1269 | + {% endfor %} |
| 1270 | + } |
| 1271 | + {% endfor %} |
| 1272 | + </style> |
| 1273 | +
|
| 1274 | + <table id="T_{{uuid}}"> |
| 1275 | + {% if caption %} |
| 1276 | + <caption>{{caption}}</caption> |
| 1277 | + {% endif %} |
| 1278 | +
|
| 1279 | + <thead> |
| 1280 | + {% for r in head %} |
| 1281 | + <tr> |
| 1282 | + {% for c in r %} |
| 1283 | + <{{c.type}} class="{{c.class}}">{{c.value}}</th> |
| 1284 | + {% endfor %} |
| 1285 | + </tr> |
| 1286 | + {% endfor %} |
| 1287 | + </thead> |
| 1288 | + <tbody> |
| 1289 | + {% for r in body %} |
| 1290 | + <tr> |
| 1291 | + {% for c in r %} |
| 1292 | + <{{c.type}} id="T_{{uuid}}{{c.id}}" class="{{c.class}}">{{c.value}}</th> |
| 1293 | + {% endfor %} |
| 1294 | + </tr> |
| 1295 | + {% endfor %} |
| 1296 | + </tbody> |
| 1297 | + </table> |
| 1298 | + """) |
| 1299 | + |
| 1300 | + @staticmethod |
| 1301 | + def format_style_args(data, rows=None, columns=None, table=None): |
| 1302 | + """ |
| 1303 | + rows/columns: |
| 1304 | + - function |
| 1305 | + - list |
| 1306 | + - dict |
| 1307 | + dict : {row/col}: [function] |
| 1308 | + """ |
| 1309 | + def cond(v): |
| 1310 | + return (callable(v) or com.is_list_like(v) |
| 1311 | + and not isinstance(v, dict)) |
| 1312 | + |
| 1313 | + if cond(rows): |
| 1314 | + rows = {r: rows for r in data.index} |
| 1315 | + if cond(columns): |
| 1316 | + columns = {c: columns for c in data.columns} |
| 1317 | + if callable(table) or com.is_list_like(table): |
| 1318 | + table = list(table) |
| 1319 | + |
| 1320 | + rows = rows or {} |
| 1321 | + columns = columns or {} |
| 1322 | + table = table or {} |
| 1323 | + |
| 1324 | + rows = {row: [x] if not com.is_list_like(x) else x |
| 1325 | + for row, x in rows.items()} |
| 1326 | + columns = {col: [x] if not com.is_list_like(x) else x |
| 1327 | + for col, x in columns.items()} |
| 1328 | + table = [table] if not com.is_list_like(table) else table |
| 1329 | + |
| 1330 | + return rows, columns, table |
| 1331 | + |
| 1332 | + def _build_styles(self, rows=None, columns=None, table=None): |
| 1333 | + """ |
| 1334 | + Build a DataFrame indexed like ``self.data`` containing the CSS |
| 1335 | + property: value pairs. |
| 1336 | + """ |
| 1337 | + from pandas.tools.merge import concat |
| 1338 | + data = self.data |
| 1339 | + final = [] # (row, col, prop, value) |
| 1340 | + |
| 1341 | + # rows, columns, table = self._format_style_args(rows, columns, table) |
| 1342 | + # TODO: ensure index handling is correct |
| 1343 | + if rows is not None: |
| 1344 | + for row in rows: |
| 1345 | + for func in rows[row]: |
| 1346 | + props, values = zip(*func(data.loc[row])) |
| 1347 | + final.append(data.__class__({'row': row, 'col': data.columns, |
| 1348 | + 'prop': props, 'value': values})) |
| 1349 | + if columns is not None: |
| 1350 | + for col in columns: |
| 1351 | + for func in columns[col]: |
| 1352 | + props, values = zip(*func(data[col])) |
| 1353 | + final.append(data.__class__({'row': data.index, 'col': col, |
| 1354 | + 'prop': props, 'value': values})) |
| 1355 | + |
| 1356 | + if table is not None: |
| 1357 | + for func in table: |
| 1358 | + r = func(data) |
| 1359 | + for col in r: |
| 1360 | + try: |
| 1361 | + prop, values = zip(*r[col]) |
| 1362 | + except ValueError: |
| 1363 | + props, values = '', '' |
| 1364 | + final.append(data.__clas__({'row': r.index, 'col': col, |
| 1365 | + 'prop': props, 'value': values})) |
| 1366 | + |
| 1367 | + r = concat(final)[['row', 'col', 'prop', 'value']] |
| 1368 | + # I don't feel great about this. |
| 1369 | + # Could use a MultiIndex of [(row/col), (prop, value)] |
| 1370 | + # Or a dumber structure like a dict. |
| 1371 | + r = (r['prop'] + ':' + r['value']).groupby([r['row'], r['col']]).agg( |
| 1372 | + lambda x: '; '.join(x)) |
| 1373 | + |
| 1374 | + return r.unstack() |
| 1375 | + |
| 1376 | + def translate(self): |
| 1377 | + """ |
| 1378 | + Convert the DataFrame in `self.data` and the attrs from `_build_styles` |
| 1379 | + into a dictionary of {head, body, uuid, cellstyle} |
| 1380 | + """ |
| 1381 | + attrs = self._build_styles(self.rows, self.columns, self.table) |
| 1382 | + ROW_HEADING_CLASS = "row_heading" |
| 1383 | + COL_HEADING_CLASS = "col_heading" |
| 1384 | + DATA_CLASS = "data" |
| 1385 | + BLANK_CLASS = "blank" |
| 1386 | + BLANK_VALUE = "" |
| 1387 | + |
| 1388 | + cell_context = dict() |
| 1389 | + |
| 1390 | + n_rlvls = self.data.index.nlevels |
| 1391 | + n_clvls = self.data.columns.nlevels |
| 1392 | + rlabels = self.data.index.tolist() |
| 1393 | + clabels = self.data.columns.tolist() |
| 1394 | + |
| 1395 | + if n_rlvls == 1: |
| 1396 | + rlabels = [[x] for x in rlabels] |
| 1397 | + if n_clvls == 1: |
| 1398 | + clabels = [[x] for x in clabels] |
| 1399 | + clabels=list(zip(*clabels)) |
| 1400 | + |
| 1401 | + style = [] |
| 1402 | + head = [] |
| 1403 | + |
| 1404 | + for r in range(n_clvls): |
| 1405 | + row_es = [{"type": "th", "value": BLANK_VALUE, |
| 1406 | + "class": " ".join([BLANK_CLASS])}] * n_rlvls |
| 1407 | + for c in range(len(clabels[0])): |
| 1408 | + cs = [COL_HEADING_CLASS, "level%s" % r, "col%s" % c] |
| 1409 | + cs.extend(cell_context.get("col_headings", {}).get(r, {}).get(c, [])) |
| 1410 | + row_es.append({"type": "th", "value": clabels[r][c], |
| 1411 | + "class": " ".join(cs)}) |
| 1412 | + head.append(row_es) |
| 1413 | + |
| 1414 | + body = [] |
| 1415 | + for r, idx in enumerate(self.data.index): |
| 1416 | + cs = [ROW_HEADING_CLASS, "level%s" % c, "row%s" % r] |
| 1417 | + cs.extend(cell_context.get("row_headings", {}).get(r, {}).get(c, [])) |
| 1418 | + row_es = [{"type": "th", "value": rlabels[r][c], "class": " ".join(cs)} |
| 1419 | + for c in range(len(rlabels[r]))] |
| 1420 | + |
| 1421 | + for c, col in enumerate(self.data.columns): |
| 1422 | + cs = [DATA_CLASS,"row%s" % r, "col%s" % c] |
| 1423 | + cs.extend(cell_context.get("data", {}).get(r, {}).get(c, [])) |
| 1424 | + row_es.append({"type": "td", "value": self.data.iloc[r][c], |
| 1425 | + "class": " ".join(cs), "id": "_".join(cs[1:])}) |
| 1426 | + try: |
| 1427 | + style.append( |
| 1428 | + {'props': [x.split(":") |
| 1429 | + for x in attrs.loc[idx, col].split('; ')], |
| 1430 | + 'selector': "row%s_col%s" % (r, c)}) |
| 1431 | + except KeyError: |
| 1432 | + pass |
| 1433 | + body.append(row_es) |
| 1434 | + |
| 1435 | + # uuid required to isolate table styling from others |
| 1436 | + # in same notebook in ipnb |
| 1437 | + u = str(uuid.uuid1()).replace("-","_") |
| 1438 | + return dict(head=head, cellstyle=style, body=body, uuid=u) |
| 1439 | + |
| 1440 | + def render(self): |
| 1441 | + return(self.t.render(**self.translate())) |
| 1442 | + |
| 1443 | + |
1232 | 1444 | def _get_level_lengths(levels, sentinel=''):
|
1233 | 1445 | from itertools import groupby
|
1234 | 1446 |
|
|
0 commit comments