diff --git a/client/src/components/continuous/continuous.js b/client/src/components/continuous/continuous.js index 8c12609f..2056b92d 100644 --- a/client/src/components/continuous/continuous.js +++ b/client/src/components/continuous/continuous.js @@ -5,17 +5,8 @@ import React from "react"; import _ from "lodash"; import { connect } from "react-redux"; -import styles from "./parallelCoordinates.css"; -import SectionHeader from "../framework/sectionHeader"; - -import setupParallelCoordinates from "./setupParallelCoordinates"; -import drawAxes from "./drawAxes"; -import drawLinesCanvas from "./drawLinesCanvas"; - import HistogramBrush from "./histogramBrush"; -import { margin, width, height, createDimensions } from "./util"; - @connect(state => { const metadata = _.get(state.controls.world, "obsAnnotations", null); const ranges = _.get(state.controls.world, "summary.obs", null); @@ -29,35 +20,29 @@ import { margin, width, height, createDimensions } from "./util"; }; }) class Continuous extends React.Component { - constructor(props) { - super(props); - this.state = { - svg: null, - ctx: null, - axes: null, - dimensions: null - }; - } - handleBrushAction(selection) { - this.props.dispatch({ + const { dispatch } = this.props; + dispatch({ type: "continuous selection using parallel coords brushing", data: selection }); } handleColorAction(key) { - this.props.dispatch({ + const { dispatch, ranges } = this.props; + dispatch({ type: "color by continuous metadata", colorAccessor: key, - rangeMaxForColorAccessor: this.props.ranges[key].range.max + rangeMaxForColorAccessor: ranges[key].range.max }); } render() { + const { ranges } = this.props; + return (
- {_.map(this.props.ranges, (value, key) => { + {_.map(ranges, (value, key) => { const isColorField = key.includes("color") || key.includes("Color"); if (value.range && key !== "name" && !isColorField) { return ( @@ -68,6 +53,7 @@ class Continuous extends React.Component { /> ); } + return null; })}
); @@ -75,5 +61,3 @@ class Continuous extends React.Component { } export default Continuous; - -// diff --git a/client/src/components/continuous/drawAxes.js b/client/src/components/continuous/drawAxes.js index 2ef6df4b..35aa43cf 100644 --- a/client/src/components/continuous/drawAxes.js +++ b/client/src/components/continuous/drawAxes.js @@ -1,4 +1,5 @@ // jshint esversion: 6 +import * as d3 from "d3"; import styles from "./parallelCoordinates.css"; import { yAxis, brushstart } from "./util"; @@ -19,13 +20,11 @@ const drawAxes = ( ******************************************/ function brush() { - var actives = []; + const actives = []; svg .selectAll(".parcoords_axis .parcoords_brush") - .filter(function(d) { - return d3.brushSelection(this); - }) - .each(function(d) { + .filter(() => d3.brushSelection(this)) + .each(d => { actives.push({ dimension: d, extent: d3.brushSelection(this) @@ -35,20 +34,18 @@ const drawAxes = ( handleBrushAction(actives); } - var axes = svg + const axes = svg .selectAll(".parcoords_axis") .data(dimensions) .enter() .append("g") .attr("class", `${styles.axis} parcoords_axis`) - .attr("transform", (d, i) => { - return "translate(" + xscale(i) + ")"; - }); + .attr("transform", (d, i) => `translate(${xscale(i)})`); axes .append("g") - .each(function(d) { - var renderAxis = + .each(d => { + const renderAxis = "axis" in d ? d.axis.scale(d.scale) // custom axis : yAxis.scale(d.scale); // default axis @@ -60,15 +57,13 @@ const drawAxes = ( }) .attr("class", styles.title) .attr("text-anchor", "start") - .text(function(d) { - return "description" in d ? d.description + " 🖌️" : d.key + " 🖌️"; - }); + .text(d => ("description" in d ? `${d.description} 🖌️` : `${d.key} 🖌️`)); // Add and store a brush for each axis. axes .append("g") .attr("class", `${styles.brush} parcoords_brush`) - .each(function(d) { + .each(d => { d3.select(this).call( (d.brush = d3 .brushY() diff --git a/client/src/components/continuous/drawLinesCanvas.js b/client/src/components/continuous/drawLinesCanvas.js index 05a23ae2..04d69b62 100644 --- a/client/src/components/continuous/drawLinesCanvas.js +++ b/client/src/components/continuous/drawLinesCanvas.js @@ -10,54 +10,46 @@ import renderQueue from "../../util/renderQueue"; ****************************************** ******************************************/ -const drawLinesCanvas = ( - ctx, - dimensions, - xscale, - colorAccessor, - colorScale -) => { - return d => { - ctx.globalAlpha = 0.1; +const drawLinesCanvas = (ctx, dimensions, xscale) => d => { + ctx.globalAlpha = 0.1; - if (d["__selected__"]) { - ctx.strokeStyle = d["__color__"]; - } else { + if (d.__selected__) { + ctx.strokeStyle = d.__color__; + } else { + return; + } + + ctx.beginPath(); + const coords = project(d, dimensions, xscale); + coords.forEach((p, i) => { + // this tricky bit avoids rendering null values as 0 + if (p === null) { + // this bit renders horizontal lines on the previous/next + // dimensions, so that sandwiched null values are visible + if (i > 0) { + const prev = coords[i - 1]; + if (prev !== null) { + ctx.moveTo(prev[0], prev[1]); + ctx.lineTo(prev[0] + 6, prev[1]); + } + } + if (i < coords.length - 1) { + const next = coords[i + 1]; + if (next !== null) { + ctx.moveTo(next[0] - 6, next[1]); + } + } return; } - ctx.beginPath(); - var coords = project(d, dimensions, xscale); - coords.forEach((p, i) => { - // this tricky bit avoids rendering null values as 0 - if (p === null) { - // this bit renders horizontal lines on the previous/next - // dimensions, so that sandwiched null values are visible - if (i > 0) { - var prev = coords[i - 1]; - if (prev !== null) { - ctx.moveTo(prev[0], prev[1]); - ctx.lineTo(prev[0] + 6, prev[1]); - } - } - if (i < coords.length - 1) { - var next = coords[i + 1]; - if (next !== null) { - ctx.moveTo(next[0] - 6, next[1]); - } - } - return; - } + if (i === 0) { + ctx.moveTo(p[0], p[1]); + return; + } - if (i == 0) { - ctx.moveTo(p[0], p[1]); - return; - } - - ctx.lineTo(p[0], p[1]); - }); - ctx.stroke(); - }; + ctx.lineTo(p[0], p[1]); + }); + ctx.stroke(); }; const drawCellLinesUsingRenderQueue = ( diff --git a/client/src/components/continuous/histogramBrush.js b/client/src/components/continuous/histogramBrush.js index dda1219d..059f73f4 100644 --- a/client/src/components/continuous/histogramBrush.js +++ b/client/src/components/continuous/histogramBrush.js @@ -12,14 +12,12 @@ import * as d3 from "d3"; import memoize from "memoize-one"; import * as globals from "../../globals"; -@connect(state => { - return { - initializeRanges: _.get(state.controls.world, "summary.obs"), - colorAccessor: state.controls.colorAccessor, - colorScale: state.controls.colorScale, - obsAnnotations: _.get(state.controls.world, "obsAnnotations", null) - }; -}) +@connect(state => ({ + initializeRanges: _.get(state.controls.world, "summary.obs"), + colorAccessor: state.controls.colorAccessor, + colorScale: state.controls.colorScale, + obsAnnotations: _.get(state.controls.world, "obsAnnotations", null) +})) class HistogramBrush extends React.Component { calcHistogramCache = memoize((obsAnnotations, metadataField, ranges) => { // recalculate expensive stuff @@ -101,18 +99,10 @@ class HistogramBrush extends React.Component { .enter() .append("rect") .attr("class", "bar") - .attr("x", function(d) { - return x(d.x0) + 1; - }) - .attr("y", function(d) { - return y(d.length / numValues); - }) - .attr("width", function(d) { - return Math.abs(x(d.x1) - x(d.x0) - 1); - }) - .attr("height", function(d) { - return y(0) - y(d.length / numValues); - }); + .attr("x", d => x(d.x0) + 1) + .attr("y", d => y(d.length / numValues)) + .attr("width", d => Math.abs(x(d.x1) - x(d.x0) - 1)) + .attr("height", d => y(0) - y(d.length / numValues)); if (!this.state.brush && !this.state.axis) { const brush = d3 @@ -122,20 +112,14 @@ class HistogramBrush extends React.Component { .call( d3 .brushX() - .on( - "end", - this.onBrush(this.props.metadataField, x.invert).bind(this) - ) + .on("end", this.onBrush(metadataField, x.invert).bind(this)) ); const xAxis = d3 .select(svgRef) .append("g") .attr("class", "axis axis--x") - .attr( - "transform", - "translate(0," + (this.height - this.marginBottom) + ")" - ) + .attr("transform", `translate(0,${this.height - this.marginBottom})`) .call(d3.axisBottom(x).ticks(5)) .append("text") .attr("x", this.width - 2) @@ -143,30 +127,30 @@ class HistogramBrush extends React.Component { .attr("fill", "#000") .attr("text-anchor", "end") .attr("font-weight", "bold") - .text(this.props.metadataField); + .text(metadataField); this.setState({ brush, xAxis }); } } handleColorAction() { - this.props.dispatch({ + const { dispatch, metadataField, initializeRanges } = this.props; + dispatch({ type: "color by continuous metadata", - colorAccessor: this.props.metadataField, - rangeMaxForColorAccessor: this.props.initializeRanges[ - this.props.metadataField - ].range.max + colorAccessor: metadataField, + rangeMaxForColorAccessor: initializeRanges[metadataField].range.max }); } render() { + const { metadataField, ranges, colorAccessor } = this.props; return (
- {this.props.ranges.min} + {ranges.min} {" to "} - {this.props.ranges.max} + {ranges.max} { - var container = d3.select("#parcoords"); +import * as d3 from "d3"; - var svg = container +const setupParallelCoordinates = (width, height, margin) => { + const container = d3.select("#parcoords"); + + const svg = container .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + .attr("transform", `translate(${margin.left},${margin.top})`); - var canvas = container + const canvas = container .append("canvas") .attr("width", width * devicePixelRatio) .attr("height", height * devicePixelRatio) - .style("width", width + "px") - .style("height", height + "px") - .style("margin-top", margin.top + "px") - .style("margin-left", margin.left + "px"); + .style("width", `${width}px`) + .style("height", `${height}px`) + .style("margin-top", `${margin.top}px`) + .style("margin-left", `${margin.left}px`); - var ctx = canvas.node().getContext("2d"); + const ctx = canvas.node().getContext("2d"); ctx.globalCompositeOperation = "darken"; ctx.globalAlpha = 0.15; ctx.lineWidth = 1.5; diff --git a/client/src/components/continuous/util.js b/client/src/components/continuous/util.js index 80b18baa..212fe177 100644 --- a/client/src/components/continuous/util.js +++ b/client/src/components/continuous/util.js @@ -18,11 +18,10 @@ export const createDimensions = data => { _.each(data, (value, key) => { if (value.range) { newArr.push({ - key: key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */, + key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */, type: { - within: (d, extent, dim) => { - return extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1]; - } + within: (d, extent, dim) => + extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1] }, scale: d3 .scaleSqrt() @@ -41,18 +40,13 @@ export const brushstart = () => { }; export const d3_functor = v => { - return typeof v === "function" - ? v - : () => { - return v; - }; + return typeof v === "function" ? v : () => v; }; -export const project = (d, dimensions, xscale) => { - return dimensions.map((p, i) => { +export const project = (d, dimensions, xscale) => + dimensions.map((p, i) => { // check if data element has property and contains a value if (!(p.key in d) || d[p.key] === null) return null; return [xscale(i), p.scale(d[p.key])]; }); -};