Skip to content

Commit

Permalink
feat: add option to superimpose labels on wells
Browse files Browse the repository at this point in the history
This commit also adds some infrastructure to help with customizing the
appearance of layouts:

- The `extras` and `report_dependencies` arguments to `load()` are
  deprecated, and replaced by the new `meta` argument.

- Style objects can be specified in TOML files, in new [meta.style] and
  [meta.param_styles] tables.

- The `Style` class was rewritten to give it the ability to check for
  errors, and to merge styles.

Fixes #24
  • Loading branch information
kalekundert committed Sep 13, 2023
1 parent 35539f6 commit cca214e
Show file tree
Hide file tree
Showing 35 changed files with 2,351 additions and 539 deletions.
23 changes: 17 additions & 6 deletions docs/_ext/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import wellmap
import shlex
import matplotlib.pyplot as plt

from docutils import nodes
from docutils.statemachine import StringList
Expand All @@ -18,6 +19,7 @@ class Example(SphinxDirective):
final_argument_whitespace = True
option_spec = {
'params': lambda x: x.split(','),
'show': lambda x: int,
'colors': lambda x: x,
'no-figure': lambda x: x,
}
Expand All @@ -34,8 +36,14 @@ def run(self):
if len(contents) != len(rel_paths):
raise self.error(f"found {len(content)} TOML snippets, but {len(rel_paths)} paths.")

toml_paths = []
toml_abs_paths = []

for rel_path, content in zip(rel_paths, contents):
toml_path, toml_abs_path = self.env.relfn2path(rel_path)
toml_paths.append(toml_path)
toml_abs_paths.append(toml_abs_path)

name = os.path.basename(toml_path)

if content:
Expand All @@ -52,19 +60,22 @@ def run(self):

# Only make a figure for the last snippet.
if 'no-figure' not in self.options:
i = self.options.get('show', 0)
toml_path = toml_paths[i]
toml_abs_path = toml_abs_paths[i]
svg_path = change_ext(toml_path, '.svg')
svg_abs_path = change_ext(toml_abs_path, '.svg')

df, deps = wellmap.load(toml_abs_path, report_dependencies=True)
df, meta = wellmap.load(toml_abs_path, meta=True)

if any_deps_stale(svg_abs_path, deps):
if any_deps_stale(svg_abs_path, meta.dependencies):
logger.info(f"[example] rendering: {svg_path}")
params = self.options.get('params', [])
style = wellmap.Style(
color_scheme=self.options.get('color', 'rainbow'),
)
fig = wellmap.show_df(df, params, style=style)
if 'color' in self.options:
meta.style = self.options['color']
fig = wellmap.show_df(df, params, style=meta.style)
fig.savefig(svg_abs_path, bbox_inches='tight')
plt.close(fig)

example_rst += f'''\
.. figure:: /{svg_path}
Expand Down
25 changes: 13 additions & 12 deletions docs/_static/css/corrections.css → docs/_static/css/tweaks.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
margin: 4px;
align-self: flex-start;
flex: 1 0 200px; /* Let the code grow, but don't let it take less than
* 300px. */
* 200px. */
min-width: 0; /* By default, flex-box items have "min-width: auto".
* This is in contrast to normal elements, which
* default to "min-width: 0". This difference prevents
Expand Down Expand Up @@ -78,6 +78,7 @@
}

/* Make inline code stand out less */

.rst-content code.literal {
color: #404040;
}
Expand All @@ -103,6 +104,7 @@
}

/* Make filename captions look nicer. */

.rst-content .code-block-caption {
padding-top: 0.5em;
padding-bottom: 0.5em;
Expand All @@ -116,24 +118,23 @@

/* Miscellaneous spacing fixes. */

.rst-content .section ol li ul {
margin-bottom: 12px;
}

.rst-content .field-list ul li ul>li>p {
margin-bottom: 0 !important;
}

.rst-content th p {
margin-bottom: 0px;
.rst-content .field-list ul.simple ul {
/* `merge_col` parameter of `wellmap.load()` */
/* This style would mess up the formatting of the "Precedence rules" section
* if applied to broadly. */
margin-bottom: 12px !important;
}

.rst-content .section ul li p:last-of-type {
/* Precedence rules */
margin-bottom: 0;
.rst-content th p {
margin-bottom: 0px;
}

.rst-content .section ul.paragraph-list li p {
.rst-content ul.paragraph-list li p {
/* Bradford example */
margin-bottom: 12px;
margin-bottom: 12px !important;
}

77 changes: 77 additions & 0 deletions docs/_static/js/tweaks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Show subsections in the navigation bar of the File Format document.
//
// It's helpful to understand how the navigation bar styling is supposed to
// work, to see why it doesn't normally show subsections. Here are the
// steps:
//
// - Sphinx RTD theme only includes in the navigation bar links that have a
// parent of class `.current`. The HTML for any other links is present,
// but hidden.
//
// - Sphinx add the `.current` class to links that point to the current page.
// All ancestors of a "current" link are also given the `.current` class.
// The idea is that themes might want to apply a unique style to the links
// leading up to the current page. Importantly, links that point within
// the current page, not to the page as a whole (i.e. sections and
// subsections), are not considered current.
//
// - Ultimately, this all means that only top-level sections are included in
// the navigation bar.
//
// I initially tried taking an approach where I manually added the `.current`
// class to the necessary elements to get the subsection links to appear.
// The upside was that I didn't have to hard-code the padding, but the
// downside was the font weight and background color were also (undesireably)
// affected. I decided that it was easier to just apply the specific styles
// I wanted.
//
// It might be possible to avoid the hard-coding by querying the style sheets
// somehow, e.g. `document.styleSheets`. But this seemed like to much effort
// for something that could break in many other ways anyways.

document.addEventListener("DOMContentLoaded", function(){
subsection_docs = [
'file_format.html',
'basic_usage_python.html',
'basic_usage_r.html',
]
is_subsection_doc = subsection_docs.some(
(p) => window.location.pathname.endsWith(p)
)
if(! is_subsection_doc) {
return
}

ul = document.querySelectorAll('.toctree-l2 > ul');
for(var i = 0; i < ul.length; i++) {
ul[i].style.display = 'block';

a = ul[i].querySelectorAll('.toctree-l3 > a');
for(var j = 0; j < a.length; j++) {
// The padding come from the following selector (note the `.current`):
// `.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a`
a[j].style.padding = '.4045em 1.618em .4045em 4.045em'
}
}
});

// Don't show subsections in the Python API docs.
//
// This is really a hacky work-around to an missing feature in autodoc.
// Directives like `.. function::` have the `:no-contents-entry:` option to
// prevent them from appearing in the TOC, but `.. autofunction::` doesn't
// expose this. So the only alternative I can think of is to hide these links
// after the fact.

document.addEventListener("DOMContentLoaded", function(){
api_pattern = /api\/wellmap.*html$/

if (! window.location.pathname.match(api_pattern)) {
return
}

ul = document.querySelector('.toctree-l2.current > ul');
ul.style.display = 'none';
});


29 changes: 29 additions & 0 deletions docs/_templates/autosummary/class.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{{ fullname | escape | underline}}

.. currentmodule:: {{ module }}

.. autoclass:: {{ objname }}

{% block methods %}
{% if methods %}
.. rubric:: {{ _('Methods') }}

.. autosummary::
{% for item in methods %}
~{{ name }}.{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}

{% block attributes %}
{% if attributes %}
.. rubric:: {{ _('Attributes') }}

.. autosummary::
{% for item in attributes %}
~{{ name }}.{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}

----
1 change: 1 addition & 0 deletions docs/api_python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Python API
wellmap.load
wellmap.show
wellmap.show_df
wellmap.Meta
wellmap.Style
wellmap.well_from_row_col
wellmap.well_from_ij
Expand Down
25 changes: 20 additions & 5 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
import sys, os
import wellmap
sys.path.append(os.path.dirname(__file__))

import sys, os
sys.path.append(os.path.dirname(__file__)) # custom sphinx extensions

import sphinx.ext.autosummary
sphinx.ext.autosummary.WELL_KNOWN_ABBREVIATIONS = ('i.e.', 'e.g.')

source_suffix = '.rst'
master_doc = 'index'
project = u'wellmap'
copyright = u'2015, Kale Kundert'
version = wellmap.__version__
release = wellmap.__version__
exclude_patterns = ['_build', '.*', 'slides']
exclude_patterns = ['_build', '.*', 'venv', 'slides', 'drafts']
templates_path = ['_templates']
html_static_path = ['_static']

extensions = [
'_ext.example',
'_ext.hidden_section',
#'show_nodes',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon',
'sphinx.ext.viewcode',
'sphinxcontrib.programoutput',
'sphinx_issues',
'myst_parser',
]
autodoc_default_options = {
'members': True,
'special-members': True,
'exclude-members': '__hash__,__weakref__,__getattribute__,__getattr__,__setattr__'
}
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'pd': ('https://pandas.pydata.org/pandas-docs/stable/', None),
Expand All @@ -33,6 +43,7 @@
add_function_parentheses = True
pygments_style = 'sphinx'
autosummary_generate = True
issues_github_path = 'kalekundert/wellmap'
rst_epilog = """\
.. |well| replace:: :ref:`well <well>`
.. |block| replace:: :ref:`block <block>`
Expand All @@ -42,8 +53,11 @@
.. |icol| replace:: :ref:`icol <icol>`
.. |plate| replace:: :ref:`plate <plate>`
.. |expt| replace:: :ref:`expt <expt>`
.. |extras| replace:: :ref:`extras <extras>`
.. _tidy: https://www.jstatsoft.org/article/view/v059i10
.. _issue: https://github.com/kalekundert/wellmap/issues
.. _pull requests: https://github.com/kalekundert/wellmap/pulls
"""

from sphinx_rtd_theme import get_html_theme_path
Expand All @@ -52,7 +66,8 @@
html_theme_options = {}

def setup(app):
app.add_css_file('css/corrections.css')
app.add_js_file('js/tweaks.js')
app.add_css_file('css/tweaks.css')

app.add_crossref_type(
'prog',
Expand Down
43 changes: 43 additions & 0 deletions docs/deprecations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
************
Deprecations
************

This pages lists features that have been slated for removal from wellmap. The
goal is to briefly explain the reason for removing each feature, and to show
how to update old code at a glance.

A `DeprecationWarning` is issued when any of these features is used. However,
be aware that python makes an effort to only show such warnings to "developers"
and not to "users". See :pep:`565` for details.

From the first release where a feature is deprecated, the deprecated behavior
is guaranteed to remain available for at least two years. The feature will be
removed in the next major release after that.

.. _load-extras-deps:

The *extras* and *report_dependencies* arguments to `wellmap.load()`
====================================================================
Both of these arguments request that the `load()` function return additional
information about the layout file. It seems likely that more and more similar
arguments will be added over time, so to avoid having to keep changing the
signature of `load()`, these arguments were consolidated into a single
:class:`~wellmap.Meta` object. See :issue:`38` for more information.

Old syntax (available until at least November 2025):

.. code::
df, extras, deps = wellmap.load(
'path/to/layout.toml',
extras=True,
report_dependencies=True,
)
New syntax (available since version 3.5):

.. code::
df, meta = wellmap.load('path/to/layout.toml', meta=True)
extras = meta.extras
deps = meta.dependencies
13 changes: 8 additions & 5 deletions docs/example_layouts/bradford_assay.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ example:
|row| and |col|. This makes it safe to include the standard curve in other
layouts, because the blocks won't grow as more wells are added to the layout.

- The ``[bradford]`` block provides information on how to parse and interpret
the data, e.g. what format the data is in and what wavelengths were measured.
This information can be accessed in analysis scripts via the **extras**
- The ``[bradford]`` table provides information on how to parse the data. In
particular, my lab has two different brands of plate reader, and they produce
output in different formats, so the analysis script needs to know which
format to expect. (The absorbance information is also needed to parse the
data file, frustratingly, because the BioTek output format is ridiculous.)
This information can be accessed in analysis scripts via the **meta**
argument to `load()`:

.. code-block:: pycon
>>> import wellmap
>>> df, ex = wellmap.load('bradford_assay.toml', extras=True)
>>> ex
>>> df, meta = wellmap.load('bradford_assay.toml', meta=True)
>>> meta.extras
{'bradford': {'format': 'biotek', 'absorbance': '595/450'}}
.. example:: bradford_standards.toml bradford_assay.toml
Loading

0 comments on commit cca214e

Please sign in to comment.