Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support exporting all barplot legends, including color gradient and length legends; export collapsed clade shapes and barplots #392

Merged
merged 62 commits into from
Nov 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
46cd6a3
ENH: support collecting+exporting barplot legends
fedarko Sep 23, 2020
3d61289
BUG: Fix legend SVG title centering; <g> => <svg>
fedarko Sep 23, 2020
55a2ed1
BUG: Improve legend SVG for SVG importers
fedarko Sep 23, 2020
52f483e
STY: reorder/split up text and .title svg styles
fedarko Sep 23, 2020
77c555a
MNT: hang on to continuous props in Legend for SVG
fedarko Sep 24, 2020
e5a3e54
ENH: add newlines to within gradient svgs
fedarko Sep 24, 2020
d571dcd
MNT: abstract some code within legend export; grad svg work
fedarko Sep 24, 2020
fca7bae
MNT: Refactor colorer/legend handling of gradients
fedarko Sep 24, 2020
79d9602
ENH: Finish? gradient legend SVG exporting!
fedarko Sep 24, 2020
766158e
MNT: add explicit padding to right side of cat SVG
fedarko Sep 24, 2020
6c04c35
ENH: initial support for exporting length legends
fedarko Sep 24, 2020
c7f54c5
ENH: make exported length legend look purdy
fedarko Sep 24, 2020
49828bb
STY: pret
fedarko Sep 24, 2020
f6ceff3
Merge branch 'master' of https://github.com/biocore/empress into quic…
fedarko Sep 26, 2020
4789630
Merge branch 'master' of https://github.com/biocore/empress into quic…
fedarko Oct 8, 2020
93531a8
ENH: export collapsed clade shapes! #303
fedarko Oct 8, 2020
1e0f034
ENH: draw full rectangle for unrooted collapsing
fedarko Oct 8, 2020
ebd38a6
ENH/STY: use stroke on svg triangles; prettify
fedarko Oct 8, 2020
41f3219
MNT: also use stroke for unrooted clade export
fedarko Oct 8, 2020
a84d7e6
DOC: document todos for clade collapsing export
fedarko Oct 8, 2020
3a69fd8
ENH: support exporting barplots in SVG!
fedarko Oct 9, 2020
ad93456
Update readme re: #303 fixes :D
fedarko Oct 9, 2020
f368f2e
PERF: Only specify stroke width for thick lines
fedarko Oct 9, 2020
3bf35dc
MNT: apply shape-rendering to SVG
fedarko Oct 9, 2020
5c45cc9
Merge branch 'master' of https://github.com/biocore/empress into quic…
fedarko Oct 9, 2020
32d4185
MNT: make polygon exporting util func
fedarko Oct 10, 2020
f10dc88
MNT: attempt to fix bounding box stuff
fedarko Oct 10, 2020
5ed4f8a
STY: prettify
fedarko Oct 10, 2020
52c0587
negate y coords and declare maxy in bb
fedarko Oct 10, 2020
6575c6b
Space out adjacent polygon pts
fedarko Oct 10, 2020
5f8580b
PERF/BUG: don't even draw 0-length barplots
fedarko Oct 10, 2020
3e0d001
STY: prett
fedarko Oct 10, 2020
916261e
DOC: remove svg export disclaimer :D #303
fedarko Oct 10, 2020
878854a
BUG: Fix #421
fedarko Oct 14, 2020
57816f1
TST: fix a gradient svg test
fedarko Oct 14, 2020
ae1f875
specify combined svg
fedarko Oct 14, 2020
c810859
TST: split up test ref svgs by solo/html
fedarko Oct 14, 2020
bd57583
TST: Fix most of the colorer tests
fedarko Oct 14, 2020
7b05450
MNT: redo public attr stuff with a func approach
fedarko Oct 15, 2020
e7c0ac6
TST: unbreak colorer tests!
fedarko Oct 15, 2020
eb64e6d
Say missing and/or not numeric in warning
fedarko Oct 15, 2020
97508e4
TST: unbreak legend tests :D
fedarko Oct 15, 2020
cf2c3f1
STY: prettify tests
fedarko Oct 15, 2020
cf1573e
MNT: Drastically simplify legend exporting
fedarko Oct 15, 2020
d667cc4
STY: pret
fedarko Oct 15, 2020
9d54fa6
UNBREAK CONTINUOUS LEGENDS :D
fedarko Oct 15, 2020
1803ad3
Fix length legends
fedarko Oct 15, 2020
cbc27a9
ENH: show missing/nonnumeric warn in grad legends
fedarko Oct 15, 2020
6b94430
DOC: fix some docs stuff
fedarko Oct 15, 2020
b2044f1
fix stats format
fedarko Oct 15, 2020
b2c2860
populateTreeStats fixes
fedarko Oct 15, 2020
8f161cb
STY: whoops
fedarko Oct 15, 2020
2a8acf3
Merge branch 'master' of https://github.com/biocore/empress into quic…
fedarko Oct 27, 2020
5c94685
MNT: Move radius bb expansion to sep function
fedarko Oct 27, 2020
304a854
PERF: don't cache barplot buffer
fedarko Oct 27, 2020
f68dc64
BUG: fix reversed continuous color map legends
fedarko Oct 27, 2020
5d0a4be
simplify grad svg code a bit
fedarko Oct 27, 2020
622997c
TST: test reverse color gradient bug is fixed
fedarko Oct 27, 2020
23d4938
STY
fedarko Oct 27, 2020
367789a
MNT: Simplify gradient ID / suffix stuff
fedarko Nov 3, 2020
d83f9af
Merge branch 'master' of https://github.com/biocore/empress into quic…
fedarko Nov 5, 2020
033f420
DOC: fix documentation stuff wrt #400
fedarko Nov 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,10 @@ This was a brief introduction to some of the barplot functionality available in

## Exporting Plots

Once you are done customizing your tree, you can export the tree as an SVG or PNG file by going to the *Export* section in the main menu and clicking on `Export tree as SVG` or `Export tree as PNG`. You can also export the legend used for tree coloring, if the tree has been colored, using the `Export legend as SVG` button.
Once you are done customizing your tree, you can export the current visualization of the tree as an SVG or PNG file by going to the *Export* section in the main menu and clicking on `Export tree as SVG` or `Export tree as PNG`. You can also export the legend(s) used for tree and/or barplot coloring, if applicable, using the `Export legends as SVG` button.

Currently, certain elements of the display (e.g. barplots, collapsed clades) are not included in the SVG export; we're working on making this functionality more comprehensive. Also, note that the SVG export does not change as you zoom / pan the tree, while the PNG export will change as you zoom / pan the tree.
Note that SVG export will always include the entire tree display, while the
contents of the PNG export will change as you zoom / pan the tree.

## Empire plots! Side-by-side integration of tree and PCoA plots

Expand Down
25 changes: 23 additions & 2 deletions empress/support_files/js/barplot-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -678,8 +678,7 @@ define([
this.colorByFMContinuous &&
!this.colorByFMColorMapDiscrete
) {
var gradInfo = colorer.getGradientSVG();
this.colorLegend.addContinuousKey(title, gradInfo[0], gradInfo[1]);
this.colorLegend.addContinuousKey(title, colorer.getGradientInfo());
} else {
this.colorLegend.addCategoricalKey(title, colorer.getMapHex());
}
Expand Down Expand Up @@ -745,6 +744,28 @@ define([
this.lengthLegend.clear();
};

/**
* Returns an Array containing all of the active legends in this layer.
*
* Currently, this just returns the color legend and length legends
* (assuming both are in use), since those are the only legends barplot
* layers own. However, if more sorts of encodings could be used at the
* same time (e.g. encoding bars' color, length, and ... opacity, I
* guess?), then this Array should be expanded.
*
* Inactive legends (e.g. the length legend, if no length encoding is in
* effect) will be excluded from the Array. However, if all legends are
* active, the order in the Array will always be color then length.
*
* @return {Array} Array of Legend objects
*/
BarplotLayer.prototype.getLegends = function () {
var containedLegends = [this.colorLegend, this.lengthLegend];
return _.filter(containedLegends, function (legend) {
return legend.isActive();
});
};

/**
* Updates the text in the layer's header based on the layer's number.
*
Expand Down
18 changes: 18 additions & 0 deletions empress/support_files/js/barplot-panel-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,24 @@ define([
});
};

/**
* Returns an Array containing all active legends owned by barplot layers.
*
* This Array is ordered (it starts with the first barplot layer's legends,
* then the second layer's legends, etc.). The ordering of legends within a
* layer (e.g. color then length legends) is dependent on how
* BarplotLayer.getLegends() works.
*
* @return {Array} Array of Legend objects
*/
BarplotPanel.prototype.getLegends = function () {
var legends = [];
_.each(this.layers, function (layer) {
legends.push(...layer.getLegends());
});
return legends;
};

/**
* Array containing the names of layouts compatible with barplots.
*/
Expand Down
172 changes: 108 additions & 64 deletions empress/support_files/js/colorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,37 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
// This object will describe a mapping of unique field values to colors
this.__valueToColor = {};

// Will be set to a string containing the SVG for a gradient, if
// continuous scaling is done (i.e. useQuantScale is true and the
// input color map is sequential / diverging)
/*** Continuous-scaling-specific things ***/

// We append a number to the gradient ID so that multiple gradients can
// be present on the same page without overriding each other. (It's the
// caller's responsibility to ensure that gradient ID suffixes are
// unique, at least across Colorers created for continuous scaling.)
this._gradientID = useQuantScale ? "Gradient" + gradientIDSuffix : null;

// Will be set to a string containing just the SVG defining the
// gradient (i.e. just the <defs>...</defs> stuff).
this._gradientSVG = null;

this._gradientIDSuffix = gradientIDSuffix;
// Will be set to a string describing the <rect> containing the
// gradient and the <text> representations of the min/mid/max values
// alongside it. Used to display the gradient in the page HTML.
this._pageSVG = null;

// Will be set to strings describing the minimum, middle, and maximum
// values along the gradient. Designed for use with this._gradientSVG
// (so they can be scaled as needed for SVG exporting).
this._minValStr = null;
this._midValStr = null;
this._maxValStr = null;

// Will be set to true if there were any non-numeric elements included
// in the input values that couldn't be assigned colors (only if
// continuous scaling was done)
// in the input values that couldn't be assigned colors (controls
// whether or not to show a warning)
this._missingNonNumerics = false;

/*** End continuous-scaling-specific things ***/

// Based on the color map, container, type and the value of
// useQuantScale, assign colors accordingly
if (_.isObject(this.color)) {
Expand Down Expand Up @@ -192,8 +211,9 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
* by this.color) for every value in this.sortedUniqueValues, taking into
* account the magnitudes/etc. of the numeric values in
* this.sortedUniqueValues. This will populate this.__valueToColor with
* this information. This will also populate this._gradientSVG with a
* String describing this gradient.
* this information. This will also populate this._gradientSVG,
* this._pageSVG, this._gradientID, this._missingNonNumerics,
* this._minValStr, this._midValStr, and this._maxValStr.
*
* Non-numeric values will not be assigned a color.
*
Expand All @@ -211,7 +231,6 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
" custom colormap"
);
}

var split = util.splitNumericValues(this.sortedUniqueValues);
if (split.numeric.length < 2) {
throw new Error("Category has less than 2 unique numeric values.");
Expand All @@ -229,23 +248,26 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
_.each(split.numeric, function (n) {
scope.__valueToColor[n] = interpolator(parseFloat(n));
});
// Figure out if we should show a warning message about missing values
// to the user
// Figure out if we should show a warning message about missing /
// non-numeric values to the user
this._missingNonNumerics = split.nonNumeric.length > 0;

// Create SVG describing the gradient
// Create SVG describing the gradient: basically, we sample the color
// map along the domain 101 times, and use these 101 colors to define
// the <linearGradient /> for each integer percentage in the inclusve
// range [0%, 100%]. See https://github.com/biocore/emperor/issues/788.
var mid = (min + max) / 2;
var step = (max - min) / 100;
var stopColors = [];
for (var s = min; s <= max; s += step) {
stopColors.push(interpolator(s).hex());
var stopColors = interpolator.colors(101);
// Calling interpolator.colors() returns the colors in order relative
// to the actual color map, which means that (if reverse is true) we
// need to reverse the color map itself.
if (this.reverse) {
stopColors.reverse();
}
var gradientSVG = "<defs>";
// We append a number to the gradient ID so that multiple gradients can
// be present on the same page without overriding each other
var gID = "Gradient" + this._gradientIDSuffix;
gradientSVG +=
'<linearGradient id="' + gID + '" x1="0" x2="0" y1="1" y2="0">';
this._gradientSVG =
'<defs><linearGradient id="' +
this._gradientID +
'" x1="0" x2="0" y1="1" y2="0">';
for (var pos = 0; pos < stopColors.length; pos++) {
// "stop" tags are written here as <stop .../>. This matches what
// Emperor does, and also the example on MDN here:
Expand All @@ -256,72 +278,94 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
// document.createElementNS(). Anyway, we keep things shorter for
// now for consistency, but replacing '"/>' with '"></stop>' works
// fine also.
gradientSVG +=
this._gradientSVG +=
'<stop offset="' +
pos +
'%" stop-color="' +
stopColors[pos] +
'"/>';
}
gradientSVG +=
"</linearGradient></defs><rect " +
'width="20" height="95%" fill="url(#' +
gID +
')"/>';

gradientSVG +=
this._gradientSVG += "</linearGradient></defs>\n";

// Add a rect containing the gradient. This'll only be used in the
// in-app representation of the gradient (not in the SVG export!)
this._pageSVG =
'<rect width="20" height="95%" fill="url(#' +
this._gradientID +
')"/>\n';

this._minValStr = min.toString();
this._midValStr = mid.toString();
this._maxValStr = max.toString();
this._pageSVG +=
'<text x="25" y="12px" font-family="sans-serif" ' +
'font-size="12px" text-anchor="start">' +
max +
"</text>";
gradientSVG +=
this._maxValStr +
"</text>\n";
this._pageSVG +=
'<text x="25" y="50%" font-family="sans-serif" ' +
'font-size="12px" text-anchor="start">' +
mid +
"</text>";
gradientSVG +=
this._midValStr +
"</text>\n";
this._pageSVG +=
'<text x="25" y="95%" font-family="sans-serif" ' +
'font-size="12px" text-anchor="start">' +
min +
"</text>";

this._gradientSVG = gradientSVG;
this._minValStr +
"</text>\n";
};

/**
* Returns an array containing SVG information and a flag about non-numeric
* values, to be used when creating a legend based on continuous scaling.
* Returns an Object containing gradient information for a Legend.
*
* This function should only be called if, on Colorer construction,
* useQuantScale is true and the input color map is sequential or
* diverging. If this is not the case (a.k.a. assignContinuousScaledColors
* was not called during construction), then this function will throw an
* error.
* The intent with this method is to make sure that the Legend holding a
* gradient has all of the information needed to export a SVG of this
* gradient (since this may involve rescaling or otherwise altering the
* way the legend is displayed, we pass things besides the SVG).
*
* @return{Array} gradientInfo An array containing two elements:
* 1. gradientSVG: a String containing the SVG
* describing this Colorer's gradient. This
* will include information about the
* input numeric values along this gradient.
* 2. missingNonNumerics: a Boolean value that
* is true if any of the values passed to
* this Colorer were not numeric (and
* therefore not assignable to any colors on
* the gradient), and false otherwise. If
* this is true, it's recommended that the
* caller show a warning along with the
* gradient that some value(s) have been
* omitted from the color map.
* @return {Object} gradientInfo An Object with the following entries:
* -gradientSVG: SVG String containing the
* <defs> and <linearGradient> that define
* the gradient.
* -pageSVG: SVG String containing the <rect>
* and <text>s that position the gradient
* within a rectangle and place min / mid
* / max value text along it. (This is used
* for displaying the gradient in the
* application interface, but not used for
* exporting.)
* -gradientID: String ID of the gradient
* defined in gradientSVG.
* -minValStr: String representation of the
* minimum numeric value.
* -midValStr: String representation of the
* middle numeric value (halfway between the
* min and max; not necessarily present
* within the input data).
* -midValStr: String representation of the
* maximum numeric value.
* -missingNonNumerics: Boolean describing
* whether or not missing / non-numeric
* values were provided to the Colorer.
* (If true, a warning about this should
* be shown in the legend about this.)
*/
Colorer.prototype.getGradientSVG = function () {
Colorer.prototype.getGradientInfo = function () {
if (_.isNull(this._gradientSVG)) {
throw new Error(
"No gradient defined for this Colorer; check that " +
"No gradient data defined for this Colorer; check that " +
"useQuantScale is true and that the selected color map " +
"is not discrete."
);
} else {
return [this._gradientSVG, this._missingNonNumerics];
return {
gradientSVG: this._gradientSVG,
pageSVG: this._pageSVG,
gradientID: this._gradientID,
minValStr: this._minValStr,
midValStr: this._midValStr,
maxValStr: this._maxValStr,
missingNonNumerics: this._missingNonNumerics,
};
}
};

Expand Down
Loading