from jinja2 import Template # The baseline jinja2 template for HTML output. t=Template(""" {% if caption %} {% endif %} {% for r in head %} {% for c in r %} <{{c.type}} class="{{c.class}}">{{c.value}} {% endfor %} {% endfor %} {% for r in body %} {% for c in r %} <{{c.type}} class="{{c.class}}">{{c.value}} {% endfor %} {% endfor %}
{{caption}}
""") # the implementation code. very small. ROW_HEADING_CLASS="row_heading" COL_HEADING_CLASS="col_heading" DATA_CLASS="data" BLANK_CLASS="blank" BLANK_VALUE="" def translate(df,cell_context=None): import uuid cell_context = cell_context or dict() n_rlvls =df.index.nlevels n_clvls =df.columns.nlevels rlabels=df.index.tolist() clabels=df.columns.tolist() if n_rlvls == 1: rlabels = [[x] for x in rlabels] if n_clvls == 1: clabels = [[x] for x in clabels] clabels=zip(*clabels) head=[] for r in range(n_clvls): row_es = [{"type":"th","value":BLANK_VALUE ,"class": " ".join([BLANK_CLASS])}]*n_rlvls for c in range(len(clabels[0])): cs = [COL_HEADING_CLASS,"level%s" % r,"col%s" %c] cs.extend(cell_context.get("col_headings",{}).get(r,{}).get(c,[])) row_es.append({"type":"th","value": clabels[r][c],"class": " ".join(cs)}) head.append(row_es) body=[] for r in range(len(df)): cs = [ROW_HEADING_CLASS,"level%s" % c,"row%s" % r] cs.extend(cell_context.get("row_headings",{}).get(r,{}).get(c,[])) row_es = [{"type":"th","value": rlabels[r][c],"class": " ".join(cs)} for c in range(len(rlabels[r]))] for c in range(len(df.columns)): cs = [DATA_CLASS,"row%s" % r,"col%s" %c] cs.extend(cell_context.get("data",{}).get(r,{}).get(c,[])) row_es.append({"type":"td","value": df.iloc[r][c],"class": " ".join(cs)}) body.append(row_es) # uuid required to isolate table styling from others # in same notebook in ipnb u = str(uuid.uuid1()).replace("-","_") return dict(head=head, body=body,uuid=u) # first, vanilla df=mkdf(10,5,r_idx_nlevels=3,c_idx_nlevels=2) from IPython.display import HTML,display ctx= translate(df) ctx['caption']="Just a table, but rendered using a template with lots of classes to style against" display(HTML(t.render(**ctx))) def zebra(color1, color2): return [dict(selector="td.data:nth-child(2n)" , props=[("background-color",color1)]), dict(selector="td.data:nth-child(2n+1)" , props=[("background-color",color2)])] ctx= translate(df) style=[] style.extend(zebra("#aaa","#ddd")) ctx['style']=style ctx['caption']="A zebra table" display(HTML(t.render(**ctx))) def tag_col(n,c="grey10", with_headings=False): selector="td.col%d" % n if not with_headings: selector+=".data" return [dict(selector=selector, props=[("background-color",c)])] def tag_row(n,c="grey10", with_headings=False): selector="td.row%d" % n if not with_headings: selector+=".data" return [dict(selector=selector, props=[("background-color",c)])] ctx= translate(df) style=[] style.extend(tag_col(2,"beige")) style.extend(tag_row(3,"purple")) ctx['style']=style ctx['caption']="Highlight rows/cols by index" display(HTML(t.render(**ctx))) def round_corners(radius): props_bl=[ ("-moz-border-radius-bottomleft", "%dpx" % radius ), ("-webkit-border-bottom-left-radius", "%dpx" % radius ), ("border-bottom-left-radius", "%dpx" % radius ) ] props_br=[ ("-moz-border-radius-bottomright", "%dpx" % radius ), ("-webkit-border-bottom-right-radius", "%dpx" % radius ), ("border-bottom-right-radius", "%dpx" % radius ) ] props_tl=[ ("-moz-border-radius-topleft", "%dpx" % radius ), ("-webkit-border-top-left-radius", "%dpx" % radius ), ("border-top-left-radius", "%dpx" % radius ) ] props_tr=[ ("-moz-border-radius-topright", "%dpx" % radius ), ("-webkit-border-top-right-radius", "%dpx" % radius ), ("border-top-right-radius", "%dpx" % radius ) ] return [dict(selector="td", props=[("border-width","1px")]), dict(selector="", props=[("border-collapse","separate")]), dict(selector="tr:last-child th:first-child", props=props_bl), dict(selector="tr:last-child td:last-child", props=props_br), dict(selector="tr:first-child th.col0", props=props_tl), dict(selector="tr:first-child th.row0:first-child", props=props_tl), dict(selector="tr:first-child th:last-child", props=props_tr), ] ctx= translate(df) style=[] style.extend(round_corners(5)) ctx['caption']="Rounded corners. CSS skills beginning to fail." ctx['style']=style display(HTML(t.render(**ctx))) def color_class(cls, color): return [dict(selector="td.%s" % cls , props=[("background-color",color)])] def rank_col(n,ranking,u): data = {i: {n: ["%s-%s" % (u,ranking[i])]} for i in range(len(ranking))} return {"data": data} import uuid u = "U"+str(uuid.uuid1()).replace("-","_") df=mkdf(9,5,data_gen_f=lambda r,c:np.random.random()) ranking=df.iloc[:,1].argsort().tolist() cell_context=rank_col(1, ranking, u) ctx= translate(df,cell_context) style=[] # http://colorbrewer2.org/ color_scale=["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"] for intensity in range(9): style.extend(color_class("%s-%s" % (u,intensity),color_scale[intensity])) ctx['style']=style ctx['caption']="And finally, a heatmap based on values" display(HTML(t.render(**ctx)))