diff --git a/augur/data/schema-auspice-config-v2.json b/augur/data/schema-auspice-config-v2.json index fab639c92..a6311163c 100644 --- a/augur/data/schema-auspice-config-v2.json +++ b/augur/data/schema-auspice-config-v2.json @@ -35,6 +35,21 @@ "type": "string", "enum": ["continuous", "ordinal", "categorical", "boolean"] }, + "scale": { + "description": "Provided mapping between trait values & hex values", + "$comment": "NOTE: if supplied here, we will not use information supplied to `augur export` via `--colors` for this coloring.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": ["string", "number"], + "description": "For categorical/ordinal scales, this is the (string) value of the trait to associate with the colour. For continuous scales this is the (numeric) value to associate to with the colour, and interpolation will be used to span the domain" + }, + {"type": "string", "description": "color hex value", "pattern": "^#[0-9A-Fa-f]{6}$"} + ] + } + }, "legend": { "description": "Specify the entries displayed in the legend. This can be used to restrict the entries in the legend for display without otherwise affecting the data viz", "type": "array", diff --git a/augur/data/schema-export-v2.json b/augur/data/schema-export-v2.json index 1b3bec2a7..2727ba47d 100644 --- a/augur/data/schema-export-v2.json +++ b/augur/data/schema-export-v2.json @@ -180,11 +180,15 @@ }, "scale": { "description": "Provided mapping between trait values & hex values", + "$comment": "For continuous scales at least 2 items must be specified", "type": "array", "items": { "type": "array", "items": [ - {"type": "string", "description": "value of trait (should exist on >= 1 nodes)"}, + { + "type": ["string", "number"], + "description": "For categorical/ordinal scales, this is the (string) value of the trait to associate with the colour. For continuous scales this is the (numeric) value to associate to with the colour, and interpolation will be used to span the domain" + }, {"type": "string", "description": "color hex value", "pattern": "^#[0-9A-Fa-f]{6}$"} ] } diff --git a/augur/export_v2.py b/augur/export_v2.py index 0e9c35df3..8b13d13b3 100644 --- a/augur/export_v2.py +++ b/augur/export_v2.py @@ -6,6 +6,7 @@ import time from collections import defaultdict, deque import warnings +import numbers import re from Bio import Phylo from .utils import read_metadata, read_node_data, write_json, read_config, read_lat_longs, read_colors @@ -211,8 +212,38 @@ def _get_title(key): return key def _add_color_scale(coloring): + ## consider various sources to find any user-provided scale information key = coloring["key"] - if key.lower() in provided_colors: + scale_type = coloring["type"] + if scale_type is "continuous": + ## continuous scale information can only come from an auspice config JSON + if config.get(key, {}).get("scale"): + # enforce numeric values (we can't use the schema for this) + provided_scale = [s for s in config[key]['scale'] if isinstance(s[0], numbers.Number)] + if len(provided_scale)<2: + warn(f"The scale provided for {key} had fewer than 2 (valid numeric) entries. Skipping.") + return coloring + coloring["scale"] = provided_scale + return coloring + elif config.get(key, {}).get("scale"): + # If the auspice config JSON (`config`) explicitly defines a scale for this coloring + # then we use this instead of any colors provided via a TSV file (`provided_colors`) + values_in_tree = get_values_across_nodes(node_attrs, key) + scale = [] + provided_values_unseen_in_tree = [] + for info in config[key]['scale']: + if info[0] in values_in_tree: + scale.append(info) + else: + provided_values_unseen_in_tree.append(info[0]) + if len(scale): + coloring["scale"] = scale + if len(provided_values_unseen_in_tree): + warn(f"These values for \"{key}\" were not specified in the scale your configuration file provided:\n\t{', '.join(provided_values_unseen_in_tree)}.\n\tAuspice will create a greyscale colour ramp for them.") + return coloring + warn(f"The configuration JSON specifies a color scale for {key} however none of the values in the tree are in this scale! Auspice will generate its own color scale for this trait.") + elif key.lower() in provided_colors: + # `provided_colors` typically originates from a colors.tsv file scale = [] trait_values = {str(val).lower(): val for val in get_values_across_nodes(node_attrs, key)} trait_values_unseen = {k for k in trait_values} @@ -223,9 +254,10 @@ def _add_color_scale(coloring): if len(scale): coloring["scale"] = scale if len(trait_values_unseen): - warn("These values for trait {} were not specified in your provided color scale: {}. Auspice will create colors for them.".format(key, ", ".join(trait_values_unseen))) - else: - warn("You've specified a color scale for {} but none of the values found on the tree had associated colors. Auspice will generate its own color scale for this trait.".format(key)) + warn(f"These values for trait {key} were not specified in the colors file you provided:\n\t{', '.join(trait_values_unseen)}.\n\tAuspice will create colors for them.") + return coloring + warn(f"You've supplied a colors file with information for {key} but none of the values found on the tree had associated colors. Auspice will generate its own color scale for this trait.") + # Fallthrough (no scale information provided means that auspice will generate its own scale) return coloring def _add_legend(coloring):