Skip to content

Commit

Permalink
Some SVG export fixes; add PNG export; other misc. improvements (#384)
Browse files Browse the repository at this point in the history
* MNT: SVG export code tweaks

- Declare variables with "var"
- exportSvg() -> exportSVG()
- exportSVG_legend() -> exportSVGLegend()
- other minor things

* MNT/ENH: tidy export code; mv legends to viz right

Progress on #303

* STY: rm redundant declaration

* MNT/DOC: document legend svg; consistent var names

* MNT: don't draw a black circle @ root anymore

* MNT: remove apparently redundant dom arg in func

* init vb text method skeleton

* MNT/TST: Add "legendType" attr to Legend; test

will be useful when representing arbitrary legends in svg export

* MNT: Store main legend in Empress, not SidePanel

* TST: add legend-main to test index.html

should unbreak tests

* STY: pret

* MNT/TST: store title easily in legend obj & test

again, will help svg export

* MNT: Delegate legend SVG exporting to Legend

Still gotta, like, test the bulk of this tho. also code needs
formatting

* MNT/STY: make legend export work; prettify

Also changed exported legend appearance a bit (no rounded rects,
bg is white, color squares have a black border, etc)

* STY: pret

* MNT: refactor svg code;attempt to fix viewbox junk

legend still isn't properly accounted for in the viewbox; gotta
fix. code is a lot prettier tho

* STY: jshint thing

* MNT: use consistent legend font in svg; fix width?

the width is a sledgehammer solution; need to make it exactly
correct. idk whats wrong

* BUG: fix (most of the) viewbox pbms with svg

Now legends are fit in to the viewbox, and things seem mostly ok.

Turns out the problem was using tree width (and hgt, but the problems
i was seeing were just from width b/c legend wasn't fitting in on
the x-axis) alongside "max X" stuff. Just storing things in units
of "max X", etc. and THEN doing the -> width conversion after
computing the SVG of the tree and legends fixed things.

Remaining problems:

- Push down the legend a bit; it looks like there is like a pixel
  or two at the top of it (in the border) that isn't shown.
- Remove extra blank space on the right edge of the legend. Likely
  due to (2 * 4) (aka 2 * NODE_RADIUS) stuff being overzealous in
  how I'm using it.
- Remove extra blank space on bottom edge of the legend when it is
  taller than the tree svg (e.g. for moving pictures f.m. coloring
  on level 7 taxonomy). Likely for same reason as above.
- The tree is still flipped upside down. wat.

Also, I need to document the heck out of the new SVG functions (in
both empress.js and legend.js), and add tests preferably. Then
it's PR time?

* BUG???: Negate y-coords in SVG export: fix #334

I have NO IDEA why this works. Maybe ... is Empress itself flipping
the y-coordinates in the first place, and the SVG export was right
all along??? argh.

* STY: pret

* ENH: UI mockup of PNG export/export inclusion opts

Inclusion options discussed with @ElDeveloper this morning.

For an initial PR i'll probably comment out a lot of this stuff
(e.g. the barplots) but it should be adaptable as we continue to
support more exporting stuff

* ENH: more ui mockup stuff for exporting

* MNT: remove unused read() func + skbio imports

we still need to include skbio as a dep because _plot needs to
reference skbio.OrdinationResult or something (and also some of
the python tests load trees as skbio TreeNodes before converting them
to bp), but none of the main Empress python code touches TreeNode now
at least

* ENH: by dflt don't draw circles; draw forall nodes

even for nodes without a specified name.

Closes #349 and closes #348.

TODO: improve selection menu interface for no-name nodes

* ENH: Polish UI for unnamed nodes

- Add fancy description in selected node menu, rather than saying
  "Name: null"
- Add note to BPTree.getNodesWithName() about dangers of having
  null as a key in BPTree._nameToNodes. Would be nice to test this
  case eventuallly ...

* STY: prettify

* DOC: clarify getNodeCoords docs

* TST: unbreak getNodeCoords test for #348

* ENH: add early PNG export (close #330)

Also comment out some unused stuff from the export panel for now

* ENH: keep sel node shape as circle; node rad attrs

See comments -- basically, when node circles were turned off,
the selected node shape turned into a square. now it's always a circle
which is nice ux-wise

* ENH: p major refactor of svg stuff

-Splitting up tree and legend exporting, per convo with @ElDeveloper
-Make tree export (SVG and PNG) work nicely (SVG still needs barplot
 and collapsed clade support; that'll come soon!)
-Add docs

Just left is legend exporting, which shouldn't be too bad. knock
on wood. That'll also include barplot legends, I think!

* STY: prettify, and abstract rgb code to new func

* ENH: polish up legend svg export + clean code

fixed bug where all legends getting plomped on top of each other
b/c maxy wasn't updated properly.

still has slight bug where there's extra empty space below the
bottom legend. not sure where originator is but it seems to be
exactly 2*unit pixels

* MNT: clean up leg exporting code a tiny bit

* MNT: for now, don't export the same legend 3 times

(was doing that to test multi legend junk but for now it's ok)

* BUG: Fix extra v-space bug (kinda) in leg export

* STY/DOC: prettify and upd8 comment re vspace thing

* MNT: whoops only include one of the tree legend

* DOC: document a lotta stuff

* DOC: add note re negating

* DOC: fix wording

* DOC: document exporting fixes

* DOC: word unnamed node message better

* DOC: close a paren and add context to null thing

* DOC: tidy up some comments

* ENH: center legend titles in export

* ENH: make legend colors snug, like in-app legends

So, part of my confusion earlier was that rects (i.e. color
squares) have their x and y describe their top left, but BY DEFAULT
text tags in SVG have their x and y describe their bottom left.
And you gotta use dominant-baseline to change that. Not sure what
sadist approved that but ok

* BUG/DOC: fix legend svg width;add docs;abstr code

* STY: prettify~~~

* DOC: document funky legend stuff

tbh i'm still kinda lost on why this works as well as it does but
OH WELL :D

* MNT: use SVG styles to cut down on redundant code

* STY: prettify the styling

i warned you about styling bro / i told you dog

* MNT: Create context in Legend.exportSVG()

removes some work from empress.js, and lets us adjust context
based on legend font (shouldn't be dynamic but just in case :O)

* DOC: font family -> font style

more accurate description

* MNT: Move exporting code to export-util.js

* STY: preeettify

* TST: unbreak JS tests (point to export-util.js)

Also rm'd an extra log statement

* DOC: Point Legend.exportSVG docs to ExportUtil

due to code reorganization in PR

* BUG: fix Emperor selection callback

Noticed that double-clicking on a category didn't work with the
biplot generated for this PR. turns out that that's b/c I forgot
to update the way the legend is cleared in that file. should be
good now

* PERF: Clear node circle buffer with toggling

Pair-programmed with @kwcantrell

* BUG/MNT: rename drawer radii to diams & upd8 svg
  • Loading branch information
fedarko authored Sep 22, 2020
1 parent 22e2ae7 commit 3968bd4
Show file tree
Hide file tree
Showing 18 changed files with 758 additions and 365 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ 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 file by going to the *Export* section in the main menu and clicking on `Export tree as SVG`. Currently, certain elements of the display (e.g. barplots) are not exported; we're working on making this functionality more comprehensive.
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.

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.

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

Expand Down
16 changes: 3 additions & 13 deletions empress/support_files/js/animator.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,17 @@ define(["Colorer", "util"], function (Colorer, util) {
*
* @param{Empress} empress The core class. Entry point for all metadata and
* tree operations.
* @param{Legend} legend Display on the left side of screen. The legend will
* show the current time frame and the color assigned the
* trajectories.
*
* @returns{Animator}
* @constructs Animator
*/
function Animator(empress, legend) {
function Animator(empress) {
/**
* @type {Empress}
* The Empress state machine
*/
this.empress = empress;

/**
* @type {Legend}
* Used to display current time frame and the color assigned the
* trajectories.
*/
this.legend = legend;

/**
* @type {Object}
* Stores the legend info for each timeframe
Expand Down Expand Up @@ -227,7 +217,7 @@ define(["Colorer", "util"], function (Colorer, util) {
}

// draw new legend
this.legend.addCategoricalKey(name, keyInfo);
this.empress.updateLegendCategorical(name, keyInfo);

// draw tree
this.empress.resetTree();
Expand Down Expand Up @@ -329,7 +319,7 @@ define(["Colorer", "util"], function (Colorer, util) {
*/
Animator.prototype.stopAnimation = function () {
this.__resetParams();
this.legend.clear();
this.empress.clearLegend();
this.empress.resetTree();
this.empress.drawTree();
};
Expand Down
11 changes: 10 additions & 1 deletion empress/support_files/js/bp-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,8 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
return numTips;
};

/** True if name is in the names array for the tree
/**
* True if name is in the names array for the tree
*
* @param {String} name The name to search for.
* @return {Boolean} If the name is in the tree.
Expand All @@ -721,6 +722,14 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
* Returns all nodes with a given name. Once a name has been searched for,
* the returned object is cached in this._nameToNodes.
*
* NOTE: Care should be taken to make sure that this._nameToNodes is not
* populated with a literal null at any point, since Objects in JS store
* all keys as Strings (so having a literal null in this._nameToNodes [due
* to unnamed nodes] will cause this null to get confused with node(s)
* literally named "null" in the Newick file). I don't think this is
* currently possible in the code, but we should probably add tests that
* verify this.
*
* @param {String} name The name of node(s)
* @return {Array} An array of postorder positions of nodes with a given
* name. If no nodes have the specified name, this will be
Expand Down
30 changes: 21 additions & 9 deletions empress/support_files/js/drawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,15 @@ define(["glMatrix", "Camera"], function (gl, Camera) {
// the dimension of the canvas
this.dim = null;

this.showTreeNodes = true;
// Diameters of normal node circles and selected node circles. Note
// that, since these are constant values, they take up the same screen
// space regardless of zoom level. It would be possible to adjust these
// as the user zooms; would help unclutter the tree when it's zoomed
// out.
this.NODE_CIRCLE_DIAMETER = 4.0;
this.SELECTED_NODE_CIRCLE_DIAMETER = 9.0;

this.showTreeNodes = false;
}

/**
Expand Down Expand Up @@ -317,18 +325,17 @@ define(["glMatrix", "Camera"], function (gl, Camera) {
};

/**
* Display the tree nodes.
* Note: Currently Empress will only display the nodes that had an assigned
* name in the newick string.
* Determine whether or not to draw circles for each node in the tree.
*
* Note: this will only take effect after draw() is called.
*
* @param{Boolean} showTreeNodes If true the empress with display the tree
* nodes.
* @param{Boolean} showTreeNodes If true then Empress will draw node
* circles.
*/
Drawer.prototype.setTreeNodeVisibility = function (showTreeNodes) {
this.showTreeNodes = showTreeNodes;
};

/**
* Draws tree and other metadata
*/
Expand All @@ -351,16 +358,21 @@ define(["glMatrix", "Camera"], function (gl, Camera) {
// set the mvp attribute
c.uniformMatrix4fv(s.mvpMat, false, mvp);

// This seems to determine whether or not points are drawn as squares
// or as circles (1 = circle, 0 = square). We set it to 1 so that node
// circles and the selected node are both drawn as circles, and then
// set it to 0 afterwards.

c.uniform1i(s.isSingle, 1);
// draw tree node circles, if requested
if (this.showTreeNodes) {
c.uniform1i(s.isSingle, 1);
c.uniform1f(s.pointSize, 4.0);
c.uniform1f(s.pointSize, this.NODE_CIRCLE_DIAMETER);
this.bindBuffer(s.nodeVertBuff);
c.drawArrays(c.POINTS, 0, this.nodeSize);
}

// draw selected node
c.uniform1f(s.pointSize, 9.0);
c.uniform1f(s.pointSize, this.SELECTED_NODE_CIRCLE_DIAMETER);
this.bindBuffer(s.selectedNodeBuff);
c.drawArrays(gl.POINTS, 0, this.selectedNodeSize);

Expand Down
Loading

0 comments on commit 3968bd4

Please sign in to comment.