Skip to content

Commit

Permalink
Merge pull request #777 from nextstrain/projection-pivot
Browse files Browse the repository at this point in the history
Update frequencies tooltips and include projection pivot
  • Loading branch information
trvrb authored Aug 28, 2019
2 parents 8a69557 + ae29fe3 commit 7d1d54e
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 19 deletions.
91 changes: 77 additions & 14 deletions src/components/frequencies/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { scaleLinear } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis";
import { rgb } from "d3-color";
import { area } from "d3-shape";
import { format } from "d3-format";
import { dataFont } from "../../globalStyles";
import { prettyString } from "../../util/stringHelpers";
import { unassigned_label } from "../../util/processFrequencies";
Expand Down Expand Up @@ -74,6 +75,11 @@ const removeYAxis = (svg) => {
svg.selectAll(".y.axis").remove();
};

const removeProjectionInfo = (svg) => {
svg.selectAll(".projection-pivot").remove();
svg.selectAll(".projection-text").remove();
};

export const drawXAxis = (svg, chartGeom, scales) => {
removeXAxis(svg);
svg.append("g")
Expand All @@ -83,14 +89,50 @@ export const drawXAxis = (svg, chartGeom, scales) => {
.style("font-size", "12px")
.call(axisBottom(scales.x).ticks(scales.numTicksX, ".1f"));
};

export const drawYAxis = (svg, chartGeom, scales) => {
removeYAxis(svg);
const formatPercent = format(".0%");
svg.append("g")
.attr("class", "y axis")
.attr("transform", `translate(${chartGeom.spaceLeft},0)`)
.style("font-family", dataFont)
.style("font-size", "12px")
.call(axisLeft(scales.y).ticks(scales.numTicksY));
.call(axisLeft(scales.y).ticks(scales.numTicksY).tickFormat(formatPercent));
};

export const drawProjectionInfo = (svg, scales, projection_pivot) => {
if (projection_pivot) {

removeProjectionInfo(svg);

svg.append("g")
.attr("class", "projection-pivot")
.append("line")
.attr("x1", scales.x(parseFloat(projection_pivot)))
.attr("x2", scales.x(parseFloat(projection_pivot)))
.attr("y1", scales.y(1))
.attr("y2", scales.y(0))
.style("visibility", "visible")
.style("stroke", "rgba(55,55,55,0.9)")
.style("stroke-width", "2")
.style("stroke-dasharray", "4 4");

const midPoint = 0.5 * (scales.x(parseFloat(projection_pivot)) + scales.x.range()[1]);
svg.append("g")
.attr("class", "projection-text")
.append("text")
.attr("x", midPoint)
.attr("y", scales.y(1) - 3)
.style("pointer-events", "none")
.style("fill", "#555")
.style("font-family", dataFont)
.style("font-size", 12)
.style("alignment-baseline", "bottom")
.style("text-anchor", "middle")
.text("Projection");

}
};

const turnMatrixIntoSeries = (categories, nPivots, matrix) => {
Expand Down Expand Up @@ -197,7 +239,7 @@ export const processMatrix = ({matrix, pivots, colorScale}) => {
};

export const drawStream = (
svgStreamGroup, scales, {categories, series}, {colorBy, colorScale, colorOptions, pivots}
svgStreamGroup, scales, {categories, series}, {colorBy, colorScale, colorOptions, pivots, projection_pivot}
) => {
removeStream(svgStreamGroup);
const colourer = generateColorScaleD3(categories, colorScale);
Expand All @@ -216,23 +258,46 @@ export const drawStream = (
const date = scales.x.invert(mousex);
const pivotIdx = pivots.reduce((closestIdx, val, idx, arr) => Math.abs(val - date) < Math.abs(arr[closestIdx] - date) ? idx : closestIdx, 0);
const freqVal = Math.round((d[pivotIdx][1] - d[pivotIdx][0]) * 100) + "%";
const xvalueOfPivot = scales.x(pivots[pivotIdx]);
const xValueOfPivot = scales.x(pivots[pivotIdx]);
const y1ValueOfPivot = scales.y(d[pivotIdx][1]);
const y2ValueOfPivot = scales.y(d[pivotIdx][0]);

select("#vline")
.style("visibility", "visible")
.attr("x1", xvalueOfPivot)
.attr("x2", xvalueOfPivot);
.attr("x1", xValueOfPivot)
.attr("x2", xValueOfPivot)
.attr("y1", y1ValueOfPivot)
.attr("y2", y2ValueOfPivot);

const left = xValueOfPivot > 0.5 * scales.x.range()[1] ? "" : `${xValueOfPivot + 25}px`;
const right = xValueOfPivot > 0.5 * scales.x.range()[1] ? `${scales.x.range()[1] - xValueOfPivot + 25}px` : "";
const top = y1ValueOfPivot > 0.5 * scales.y(0) ? `${scales.y(0) - 50}px` : `${y1ValueOfPivot + 25}px`;

let frequencyText = "Frequency";
if (projection_pivot) {
if (pivots[pivotIdx] > projection_pivot) {
frequencyText = "Projected frequency";
}
}

const left = mousex > 0.5 * scales.x.range()[1] ? "" : `${mousex + 4}px`;
const right = mousex > 0.5 * scales.x.range()[1] ? `${scales.x.range()[1] - mousex - 4}px` : "";
select("#freqinfo")
.style("left", left)
.style("right", right)
.style("top", `${70}px`)
.style("top", top)
.style("padding-left", "10px")
.style("padding-right", "10px")
.style("padding-top", "0px")
.style("padding-bottom", "0px")
.style("visibility", "visible")
.style("background-color", "rgba(55,55,55,0.9)")
.style("color", "white")
.style("font-family", dataFont)
.style("font-size", 18)
.style("line-height", 1)
.style("font-weight", 300)
.html(`<p>${parseColorBy(colorBy, colorOptions)}: ${prettyString(labels[i])}</p>
<p>Time point: ${pivots[pivotIdx]}</p>
<p>Frequency: ${freqVal}</p>`);
<p>${frequencyText}: ${freqVal}</p>`);
}


Expand All @@ -248,15 +313,13 @@ export const drawStream = (
.on("mouseout", handleMouseOut)
.on("mousemove", handleMouseMove);

/* the vertical line to indicate the pivot point */
/* the vertical line to indicate the highlighted frequency interval */
svgStreamGroup.append("line")
.attr("id", "vline")
.attr("y1", scales.y(1))
.attr("y2", scales.y(0))
.style("visibility", "hidden")
.style("pointer-events", "none")
.style("stroke", "hsla(0,0%,100%,.9)")
.style("stroke-width", "5");
.style("stroke", "rgba(55,55,55,0.9)")
.style("stroke-width", 4);

drawLabelsOverStream(svgStreamGroup, series, pivots, labels, scales);
};
19 changes: 16 additions & 3 deletions src/components/frequencies/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { select } from "d3-selection";
import 'd3-transition'
import { connect } from "react-redux";
import Card from "../framework/card";
import { calcXScale, calcYScale, drawXAxis, drawYAxis, areListsEqual,
drawStream, processMatrix, parseColorBy } from "./functions";
import { calcXScale, calcYScale, drawXAxis, drawYAxis, drawProjectionInfo,
areListsEqual, drawStream, processMatrix, parseColorBy } from "./functions";
import "../../css/entropy.css";

@connect((state) => {
Expand All @@ -13,6 +13,7 @@ import "../../css/entropy.css";
pivots: state.frequencies.pivots,
ticks: state.frequencies.ticks,
matrix: state.frequencies.matrix,
projection_pivot: state.frequencies.projection_pivot,
version: state.frequencies.version,
browserDimensions: state.browserDimensions.browserDimensions,
colorBy: state.controls.colorBy,
Expand Down Expand Up @@ -40,6 +41,7 @@ class Frequencies extends React.Component {
drawXAxis(newState.svg, chartGeom, scalesX);
drawYAxis(newState.svg, chartGeom, scalesY);
drawStream(newState.svgStreamGroup, newState.scales, data, {...props});
drawProjectionInfo(newState.svg, newState.scales, props.projection_pivot);
}
recomputeRedrawPartial(oldState, oldProps, newProps) {
/* we don't have to check width / height changes here - that's done in componentDidUpdate */
Expand All @@ -59,6 +61,9 @@ class Frequencies extends React.Component {
}
/* if !catChange we could transition the streams instead of redrawing them... */
drawStream(oldState.svgStreamGroup, newScales, data, {...newProps});
if (maxYChange) {
drawProjectionInfo(oldState.svg, newScales, newProps.projection_pivot);
}
return {...oldState, scales: newScales, maxY: data.maxY, categories: data.categories};
}
componentDidMount() {
Expand Down Expand Up @@ -108,7 +113,15 @@ class Frequencies extends React.Component {
fontSize: "14px"
}}
/>
<svg style={{pointerEvents: "auto"}} width={this.props.width} height={this.props.height} id="d3frequenciesSVG">
<svg
id="d3frequenciesSVG"
width={this.props.width}
height={this.props.height}
style={{
pointerEvents: "auto",
overflow: "visible"
}}
>
<g ref={(c) => { this.domRef = c; }} id="d3frequencies"/>
</svg>
</Card>
Expand Down
3 changes: 2 additions & 1 deletion src/reducers/frequencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const frequencies = (state = {
pivots: undefined,
ticks: undefined,
matrix: undefined,
projection_pivot: undefined,
version: 0
}, action) => {
switch (action.type) {
Expand All @@ -17,7 +18,7 @@ const frequencies = (state = {
return Object.assign({}, state, {loaded: true, matrix: action.matrix, version: state.version + 1});
}
case types.DATA_INVALID: {
return {loaded: false, data: undefined, pivots: undefined, ticks: undefined, matrix: undefined, version: 0};
return {loaded: false, data: undefined, pivots: undefined, ticks: undefined, matrix: undefined, projection_pivot: undefined, version: 0};
}
default:
return state;
Expand Down
7 changes: 6 additions & 1 deletion src/util/processFrequencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export const processFrequenciesJSON = (rawJSON, tree, controls) => {
while (ticks[ticks.length - 1] < pivots[pivots.length - 1]) {
ticks.push((ticks[ticks.length - 1] + tick_step) * 10 / 10);
}
let projection_pivot = null;
if ("projection_pivot" in rawJSON) {
projection_pivot = Math.round(parseFloat(rawJSON.projection_pivot) * 100) / 100;
}
if (!tree.loaded) {
throw new Error("tree not loaded");
}
Expand Down Expand Up @@ -101,6 +105,7 @@ export const processFrequenciesJSON = (rawJSON, tree, controls) => {
data,
pivots,
ticks,
matrix
matrix,
projection_pivot
};
};

0 comments on commit 7d1d54e

Please sign in to comment.