diff --git a/pandas/formats/style.py b/pandas/formats/style.py
index cf3f4bd6d6b14c..3faa729a778a26 100644
--- a/pandas/formats/style.py
+++ b/pandas/formats/style.py
@@ -10,7 +10,7 @@
from collections import defaultdict, MutableMapping
try:
- from jinja2 import Template
+ from jinja2 import Environment, PackageLoader
except ImportError:
msg = "pandas.HTMLStyler requires jinja2. "\
"Please install with `conda install Jinja2`\n"\
@@ -39,93 +39,7 @@ def _mpl(func):
raise ImportError(no_mpl_message.format(func.__name__))
-class HTMLStyler(object):
- """
- Helps style a DataFrame or Series according to the
- data with HTML and CSS.
-
- .. versionadded:: 0.17.1
-
- .. warning::
- This is a new feature and is under active development.
- We'll be adding features and possibly making breaking changes in future
- releases.
-
- Parameters
- ----------
- data: Series or DataFrame
- precision: int
- precision to round floats to, defaults to pd.options.display.precision
- table_styles: list-like, default None
- list of {selector: (attr, value)} dicts; see Notes
- uuid: str, default None
- a unique identifier to avoid CSS collisons; generated automatically
- caption: str, default None
- caption to attach to the table
-
- Attributes
- ----------
- template: Jinja Template
-
- Notes
- -----
- Most styling will be done by passing style functions into
- ``HTMLStyler.apply`` or ``Styler.applymap``. Style functions should
- return values with strings containing CSS ``'attr: value'`` that will
- be applied to the indicated cells.
-
- If using in the Jupyter notebook, HTMLStyler has defined a ``_repr_html_``
- to automatically render itself. Otherwise call HTMLStyler.render to get
- the genterated HTML.
-
- See Also
- --------
- pandas.DataFrame.style
- """
- template = Template("""
-
-
-
- {% if caption %}
- {{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}} id="T_{{uuid}}{{c.id}}" class="{{c.class}}">
- {{ c.display_value }}
- {% endfor %}
-
- {% endfor %}
-
-
- """)
+class BaseStyler(object):
def __init__(self, data, precision=None, table_styles=None, uuid=None,
caption=None, table_attributes=None):
@@ -160,117 +74,6 @@ def default_display_func(x):
self._display_funcs = defaultdict(lambda: default_display_func)
- def _repr_html_(self):
- """Hooks into Jupyter notebook rich display system."""
- return self.render()
-
- def _translate(self):
- """
- Convert the DataFrame in `self.data` and the attrs from `_build_styles`
- into a dictionary of {head, body, uuid, cellstyle}
- """
- table_styles = self.table_styles or []
- caption = self.caption
- ctx = self.ctx
- precision = self.precision
- uuid = self.uuid or str(uuid1()).replace("-", "_")
- ROW_HEADING_CLASS = "row_heading"
- COL_HEADING_CLASS = "col_heading"
- DATA_CLASS = "data"
- BLANK_CLASS = "blank"
- BLANK_VALUE = ""
-
- cell_context = dict()
-
- n_rlvls = self.data.index.nlevels
- n_clvls = self.data.columns.nlevels
- rlabels = self.data.index.tolist()
- clabels = self.data.columns.tolist()
-
- idx_values = self.data.index.format(sparsify=False, adjoin=False,
- names=False)
- idx_values = lzip(*idx_values)
-
- if n_rlvls == 1:
- rlabels = [[x] for x in rlabels]
- if n_clvls == 1:
- clabels = [[x] for x in clabels]
- clabels = list(zip(*clabels))
-
- cellstyle = []
- 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, []))
- value = clabels[r][c]
- row_es.append({"type": "th",
- "value": value,
- "display_value": value,
- "class": " ".join(cs)})
- head.append(row_es)
-
- if self.data.index.names and self.data.index.names != [None]:
- index_header_row = []
-
- for c, name in enumerate(self.data.index.names):
- cs = [COL_HEADING_CLASS,
- "level%s" % (n_clvls + 1),
- "col%s" % c]
- index_header_row.append({"type": "th", "value": name,
- "class": " ".join(cs)})
-
- index_header_row.extend(
- [{"type": "th",
- "value": BLANK_VALUE,
- "class": " ".join([BLANK_CLASS])
- }] * len(clabels[0]))
-
- head.append(index_header_row)
-
- body = []
- for r, idx in enumerate(self.data.index):
- 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),
- "display_value": rlabels[r][c]}
- for c in range(len(rlabels[r]))]
-
- for c, col in enumerate(self.data.columns):
- cs = [DATA_CLASS, "row%s" % r, "col%s" % c]
- cs.extend(cell_context.get("data", {}).get(r, {}).get(c, []))
- formatter = self._display_funcs[(r, c)]
- value = self.data.iloc[r, c]
- row_es.append({
- "type": "td",
- "value": value,
- "class": " ".join(cs),
- "id": "_".join(cs[1:]),
- "display_value": formatter(value)
- })
- props = []
- for x in ctx[r, c]:
- # have to handle empty styles like ['']
- if x.count(":"):
- props.append(x.split(":"))
- else:
- props.append(['', ''])
- cellstyle.append({'props': props,
- 'selector': "row%s_col%s" % (r, c)})
- body.append(row_es)
-
- return dict(head=head, cellstyle=cellstyle, body=body, uuid=uuid,
- precision=precision, table_styles=table_styles,
- caption=caption, table_attributes=self.table_attributes)
-
def format(self, formatter, subset=None):
"""
Format the text display value of cells.
@@ -520,25 +323,6 @@ def set_precision(self, precision):
self.precision = precision
return self
- def set_table_attributes(self, attributes):
- """
- Set the table attributes. These are the items
- that show up in the opening ```` tag in addition
- to to automatic (by default) id.
-
- .. versionadded:: 0.17.1
-
- Parameters
- ----------
- precision: int
-
- Returns
- -------
- self : HTMLStyler
- """
- self.table_attributes = attributes
- return self
-
def export(self):
"""
Export the styles to applied to the current HTMLStyler.
@@ -579,38 +363,243 @@ def use(self, styles):
self._todo.extend(styles)
return self
- def set_uuid(self, uuid):
+ def set_caption(self, caption):
"""
- Set the uuid for a HTMLStyler.
+ Se the caption on a HTMLStyler
.. versionadded:: 0.17.1
Parameters
----------
- uuid: str
+ caption: str
Returns
-------
self : HTMLStyler
"""
- self.uuid = uuid
+ self.caption = caption
return self
- def set_caption(self, caption):
+ def set_properties(self, subset=None, **kwargs):
"""
- Se the caption on a HTMLStyler
+ Convience method for setting one or more non-data dependent
+ properties or each cell.
.. versionadded:: 0.17.1
Parameters
----------
- caption: str
+ subset: IndexSlice
+ a valid slice for ``data`` to limit the style application to
+ kwargs: dict
+ property: value pairs to be set for each cell
Returns
-------
self : HTMLStyler
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(np.random.randn(10, 4))
+ >>> df.style.set_properties(color="white", align="right")
"""
- self.caption = caption
+ values = ';'.join('{p}: {v}'.format(p=p, v=v)
+ for p, v in kwargs.items())
+ f = lambda x: values
+ return self.applymap(f, subset=subset)
+
+class HTMLStyler(BaseStyler):
+ """
+ Helps style a DataFrame or Series according to the
+ data with HTML and CSS.
+
+ .. versionadded:: 0.17.1
+
+ .. warning::
+ This is a new feature and is under active development.
+ We'll be adding features and possibly making breaking changes in future
+ releases.
+
+ Parameters
+ ----------
+ data: Series or DataFrame
+ precision: int
+ precision to round floats to, defaults to pd.options.display.precision
+ table_styles: list-like, default None
+ list of {selector: (attr, value)} dicts; see Notes
+ uuid: str, default None
+ a unique identifier to avoid CSS collisons; generated automatically
+ caption: str, default None
+ caption to attach to the table
+
+ Attributes
+ ----------
+ template: Jinja Template
+
+ Notes
+ -----
+ Most styling will be done by passing style functions into
+ ``HTMLStyler.apply`` or ``Styler.applymap``. Style functions should
+ return values with strings containing CSS ``'attr: value'`` that will
+ be applied to the indicated cells.
+
+ If using in the Jupyter notebook, HTMLStyler has defined a ``_repr_html_``
+ to automatically render itself. Otherwise call HTMLStyler.render to get
+ the genterated HTML.
+
+ See Also
+ --------
+ pandas.DataFrame.style
+ """
+ environment = Environment(loader=PackageLoader('pandas',
+ 'formats/templates'))
+ template = environment.get_template('html.j2')
+
+ def _repr_html_(self):
+ """Hooks into Jupyter notebook rich display system."""
+ return self.render()
+
+ def _translate(self):
+ """
+ Convert the DataFrame in `self.data` and the attrs from `_build_styles`
+ into a dictionary of {head, body, uuid, cellstyle}
+ """
+ table_styles = self.table_styles or []
+ caption = self.caption
+ ctx = self.ctx
+ precision = self.precision
+ uuid = self.uuid or str(uuid1()).replace("-", "_")
+ ROW_HEADING_CLASS = "row_heading"
+ COL_HEADING_CLASS = "col_heading"
+ DATA_CLASS = "data"
+ BLANK_CLASS = "blank"
+ BLANK_VALUE = ""
+
+ cell_context = dict()
+
+ n_rlvls = self.data.index.nlevels
+ n_clvls = self.data.columns.nlevels
+ rlabels = self.data.index.tolist()
+ clabels = self.data.columns.tolist()
+
+ idx_values = self.data.index.format(sparsify=False, adjoin=False,
+ names=False)
+ idx_values = lzip(*idx_values)
+
+ if n_rlvls == 1:
+ rlabels = [[x] for x in rlabels]
+ if n_clvls == 1:
+ clabels = [[x] for x in clabels]
+ clabels = list(zip(*clabels))
+
+ cellstyle = []
+ 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, []))
+ value = clabels[r][c]
+ row_es.append({"type": "th",
+ "value": value,
+ "display_value": value,
+ "class": " ".join(cs)})
+ head.append(row_es)
+
+ if self.data.index.names and self.data.index.names != [None]:
+ index_header_row = []
+
+ for c, name in enumerate(self.data.index.names):
+ cs = [COL_HEADING_CLASS,
+ "level%s" % (n_clvls + 1),
+ "col%s" % c]
+ index_header_row.append({"type": "th", "value": name,
+ "class": " ".join(cs)})
+
+ index_header_row.extend(
+ [{"type": "th",
+ "value": BLANK_VALUE,
+ "class": " ".join([BLANK_CLASS])
+ }] * len(clabels[0]))
+
+ head.append(index_header_row)
+
+ body = []
+ for r, idx in enumerate(self.data.index):
+ 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),
+ "display_value": rlabels[r][c]}
+ for c in range(len(rlabels[r]))]
+
+ for c, col in enumerate(self.data.columns):
+ cs = [DATA_CLASS, "row%s" % r, "col%s" % c]
+ cs.extend(cell_context.get("data", {}).get(r, {}).get(c, []))
+ formatter = self._display_funcs[(r, c)]
+ value = self.data.iloc[r, c]
+ row_es.append({
+ "type": "td",
+ "value": value,
+ "class": " ".join(cs),
+ "id": "_".join(cs[1:]),
+ "display_value": formatter(value)
+ })
+ props = []
+ for x in ctx[r, c]:
+ # have to handle empty styles like ['']
+ if x.count(":"):
+ props.append(x.split(":"))
+ else:
+ props.append(['', ''])
+ cellstyle.append({'props': props,
+ 'selector': "row%s_col%s" % (r, c)})
+ body.append(row_es)
+
+ return dict(head=head, cellstyle=cellstyle, body=body, uuid=uuid,
+ precision=precision, table_styles=table_styles,
+ caption=caption, table_attributes=self.table_attributes)
+
+ def set_table_attributes(self, attributes):
+ """
+ Set the table attributes. These are the items
+ that show up in the opening ```` tag in addition
+ to to automatic (by default) id.
+
+ .. versionadded:: 0.17.1
+
+ Parameters
+ ----------
+ precision: int
+
+ Returns
+ -------
+ self : HTMLStyler
+ """
+ self.table_attributes = attributes
+ return self
+
+ def set_uuid(self, uuid):
+ """
+ Set the uuid for a HTMLStyler.
+
+ .. versionadded:: 0.17.1
+
+ Parameters
+ ----------
+ uuid: str
+
+ Returns
+ -------
+ self : HTMLStyler
+ """
+ self.uuid = uuid
return self
def set_table_styles(self, table_styles):
@@ -720,34 +709,6 @@ def _background_gradient(s, cmap='PuBu', low=0, high=0):
c = [colors.rgb2hex(x) for x in plt.cm.get_cmap(cmap)(normed)]
return ['background-color: %s' % color for color in c]
- def set_properties(self, subset=None, **kwargs):
- """
- Convience method for setting one or more non-data dependent
- properties or each cell.
-
- .. versionadded:: 0.17.1
-
- Parameters
- ----------
- subset: IndexSlice
- a valid slice for ``data`` to limit the style application to
- kwargs: dict
- property: value pairs to be set for each cell
-
- Returns
- -------
- self : HTMLStyler
-
- Examples
- --------
- >>> df = pd.DataFrame(np.random.randn(10, 4))
- >>> df.style.set_properties(color="white", align="right")
- """
- values = ';'.join('{p}: {v}'.format(p=p, v=v)
- for p, v in kwargs.items())
- f = lambda x: values
- return self.applymap(f, subset=subset)
-
@staticmethod
def _bar(s, color, width):
normed = width * (s - s.min()) / (s.max() - s.min())
diff --git a/pandas/formats/templates/html.j2 b/pandas/formats/templates/html.j2
new file mode 100644
index 00000000000000..f285e3dad0b417
--- /dev/null
+++ b/pandas/formats/templates/html.j2
@@ -0,0 +1,42 @@
+
+
+
+{% if caption %}
+ {{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}} id="T_{{uuid}}{{c.id}}" class="{{c.class}}">
+ {{ c.display_value }}
+ {% endfor %}
+
+ {% endfor %}
+
+
diff --git a/setup.py b/setup.py
index 596fe62ff07811..edce42a46de5eb 100755
--- a/setup.py
+++ b/setup.py
@@ -616,7 +616,8 @@ def pxd(name):
'pandas.tests.formats': ['data/*.csv'],
'pandas.tests.indexes': ['data/*.pickle'],
'pandas.tseries.tests': ['data/*.pickle',
- 'data/*.csv']
+ 'data/*.csv'],
+ 'pandas.formats.templates': ['*.j2'],
},
ext_modules=extensions,
maintainer_email=EMAIL,