diff --git a/src/components/frequencies/functions.js b/src/components/frequencies/functions.js index 335d1db47..feecfdb8e 100644 --- a/src/components/frequencies/functions.js +++ b/src/components/frequencies/functions.js @@ -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"; @@ -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") @@ -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) => { @@ -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); @@ -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(`
${parseColorBy(colorBy, colorOptions)}: ${prettyString(labels[i])}
Time point: ${pivots[pivotIdx]}
-Frequency: ${freqVal}
`); +${frequencyText}: ${freqVal}
`); } @@ -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); }; diff --git a/src/components/frequencies/index.js b/src/components/frequencies/index.js index 70d50c037..5a78a0a42 100644 --- a/src/components/frequencies/index.js +++ b/src/components/frequencies/index.js @@ -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) => { @@ -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, @@ -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 */ @@ -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() { @@ -108,7 +113,15 @@ class Frequencies extends React.Component { fontSize: "14px" }} /> -