diff options
| author | srichman333 <sarah_n_richman@brown.edu> | 2023-08-17 10:54:35 -0400 |
|---|---|---|
| committer | srichman333 <sarah_n_richman@brown.edu> | 2023-08-17 10:54:35 -0400 |
| commit | ce7d1b25a384a3919ce547e5dd33fd2a0ebd3111 (patch) | |
| tree | d10387bb81d83172fa380e9805a9861e12ffb5f0 /src/client/views/nodes/DataVizBox/components | |
| parent | 18b3b9e5eab4a7902390033feb0a1c7e379cf7d6 (diff) | |
| parent | 0eb9d37a9dd1a6539f331f953d5f20c761d5f940 (diff) | |
Merge branch 'master' into data-visualization-sarah
Diffstat (limited to 'src/client/views/nodes/DataVizBox/components')
4 files changed, 582 insertions, 520 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index e078043c1..954569910 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -1,20 +1,20 @@ -import { observer } from "mobx-react"; -import { Doc, StrListCast } from "../../../../../fields/Doc"; +import { observer } from 'mobx-react'; +import { Doc, StrListCast } from '../../../../../fields/Doc'; import * as React from 'react'; import * as d3 from 'd3'; -import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; -import { LinkManager } from "../../../../util/LinkManager"; -import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; -import { PinProps, PresBox } from "../../trails"; -import { Docs } from "../../../../documents/Documents"; -import { List } from "../../../../../fields/List"; +import { IReactionDisposer, action, computed, observable, reaction } from 'mobx'; +import { LinkManager } from '../../../../util/LinkManager'; +import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; +import { PinProps, PresBox } from '../../trails'; +import { Docs } from '../../../../documents/Documents'; +import { List } from '../../../../../fields/List'; import './Chart.scss'; -import { ColorPicker, EditableText, IconButton, Size, Type } from "browndash-components"; -import { FaFillDrip } from "react-icons/fa"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { listSpec } from "../../../../../fields/Schema"; -import { scaleCreatorNumerical, yAxisCreator } from "../utils/D3Utils"; -import { undoBatch, undoable } from "../../../../util/UndoManager"; +import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components'; +import { FaFillDrip } from 'react-icons/fa'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { listSpec } from '../../../../../fields/Schema'; +import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils'; +import { undoBatch, undoable } from '../../../../util/UndoManager'; export interface HistogramProps { rootDoc: Doc; @@ -35,7 +35,6 @@ export interface HistogramProps { @observer export class Histogram extends React.Component<HistogramProps> { - private _disposers: { [key: string]: IReactionDisposer } = {}; private _histogramRef: React.RefObject<HTMLDivElement> = React.createRef(); private _histogramSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined; @@ -49,32 +48,37 @@ export class Histogram extends React.Component<HistogramProps> { // filters all data to just display selected data if brushed (created from an incoming link) @computed get _histogramData() { - var guids = StrListCast(this.props.layoutDoc.rowGuids); + var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; - if (/\d/.test(this.props.pairs[0][ax0])){ this.numericalXData = true } + if (/\d/.test(this.props.pairs[0][ax0])) { + this.numericalXData = true; + } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) - }; + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) + .map(pair => ({ [ax0]: pair[this.props.axes[0]] })); + } var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { this.numericalXData = true;} - if (/\d/.test(this.props.pairs[0][ax1]) ) { this.numericalYData = true;} + if (/\d/.test(this.props.pairs[0][ax0])) { + this.numericalXData = true; + } + if (/\d/.test(this.props.pairs[0][ax1])) { + this.numericalYData = true; + } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) + .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] })); } - @computed get defaultGraphTitle(){ + @computed get defaultGraphTitle() { var ax0 = this.props.axes[0]; - var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; - if (this.props.axes.length<2 || !ax1 || !/\d/.test(this.props.pairs[0][ax1]) || !this.numericalYData){ - return ax0 + " Histogram"; - } - else return ax0 + " by " + ax1 + " Histogram"; - } + var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined; + if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.pairs[0][ax1]) || !this.numericalYData) { + return ax0 + ' Histogram'; + } else return ax0 + ' by ' + ax1 + ' Histogram'; + } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links @@ -83,11 +87,11 @@ export class Histogram extends React.Component<HistogramProps> { } @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { - if (this.numericalXData){ + if (this.numericalXData) { const data = this.data(this._histogramData); - return {xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin:0, yMax:0} + return { xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin: 0, yMax: 0 }; } - return {xMin:0, xMax:0, yMin:0, yMax:0} + return { xMin: 0, xMax: 0, yMin: 0, yMax: 0 }; } componentWillUnmount() { @@ -97,7 +101,7 @@ export class Histogram extends React.Component<HistogramProps> { this._disposers.chartData = reaction( () => ({ dataSet: this._histogramData, w: this.width, h: this.height }), ({ dataSet, w, h }) => { - if (dataSet!.length>0) { + if (dataSet!.length > 0) { this.drawChart(dataSet, w, h); } }, @@ -113,7 +117,7 @@ export class Histogram extends React.Component<HistogramProps> { // title: 'histogram doc selection' + this._currSelected, }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc); return anchor; }; @@ -127,20 +131,22 @@ export class Histogram extends React.Component<HistogramProps> { // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { - var validData = dataSet.filter((d: { [x: string]: unknown; }) => { + var validData = dataSet.filter((d: { [x: string]: unknown }) => { var valid = true; Object.keys(dataSet[0]).map(key => { if (!d[key] || Number.isNaN(d[key])) valid = false; - }) + }); return valid; - }) - var field = dataSet[0]? Object.keys(dataSet[0])[0] : undefined; - const data = validData.map((d: { [x: string]: any; }) => { - if (this.numericalXData) { return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') } - return d[field!] - }) + }); + var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; + const data = validData.map((d: { [x: string]: any }) => { + if (this.numericalXData) { + return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''); + } + return d[field!]; + }); return data; - } + }; // outlines the bar selected / hovered over highlightSelectedBar = (changeSelectedVariables: boolean, svg: any, eachRectWidth: any, pointerX: any, xAxisTitle: any, yAxisTitle: any, histDataSet: any) => { @@ -148,35 +154,31 @@ export class Histogram extends React.Component<HistogramProps> { var barCounter = -1; const selected = svg.selectAll('.histogram-bar').filter((d: any) => { barCounter++; // uses the order of bars and width of each bar to find which one the pointer is over - if ((barCounter*eachRectWidth ) <= pointerX && pointerX <= ((barCounter+1)*eachRectWidth)){ - var showSelected = this.numericalYData? this._histogramData.filter((data: { [x: string]: any; }) => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')==d[0])[0] - : histDataSet.filter((data: { [x: string]: any; }) => data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')==d[0])[0]; - if (this.numericalXData){ + if (barCounter * eachRectWidth <= pointerX && pointerX <= (barCounter + 1) * eachRectWidth) { + var showSelected = this.numericalYData + ? this._histogramData.filter((data: { [x: string]: any }) => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0] + : histDataSet.filter((data: { [x: string]: any }) => data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0]; + if (this.numericalXData) { // calculating frequency - if (d[0] && d[1] && d[0]!=d[1]){ - showSelected = {[xAxisTitle]: (d3.min(d) + " to " + d3.max(d)), frequency: d.length} - } - else if (!this.numericalYData) showSelected = {[xAxisTitle]: showSelected[xAxisTitle], frequency: d.length} - } - if (changeSelectedVariables){ - // for when a bar is selected - not just hovered over - sameAsCurrent = this._currSelected? - (showSelected[xAxisTitle]==this._currSelected![xAxisTitle] - && showSelected[yAxisTitle]==this._currSelected![yAxisTitle]) - : false; - this._currSelected = sameAsCurrent? undefined: showSelected; - this.selectedData = sameAsCurrent? undefined: d; + if (d[0] && d[1] && d[0] != d[1]) { + showSelected = { [xAxisTitle]: d3.min(d) + ' to ' + d3.max(d), frequency: d.length }; + } else if (!this.numericalYData) showSelected = { [xAxisTitle]: showSelected[xAxisTitle], frequency: d.length }; } - else this.hoverOverData = d; - return true + if (changeSelectedVariables) { + // for when a bar is selected - not just hovered over + sameAsCurrent = this._currSelected ? showSelected[xAxisTitle] == this._currSelected![xAxisTitle] && showSelected[yAxisTitle] == this._currSelected![yAxisTitle] : false; + this._currSelected = sameAsCurrent ? undefined : showSelected; + this.selectedData = sameAsCurrent ? undefined : d; + } else this.hoverOverData = d; + return true; } return false; }); - if (changeSelectedVariables){ + if (changeSelectedVariables) { if (sameAsCurrent!) this.curBarSelected = undefined; else this.curBarSelected = selected; } - } + }; // draws the histogram drawChart = (dataSet: any, width: number, height: number) => { @@ -184,248 +186,274 @@ export class Histogram extends React.Component<HistogramProps> { d3.select(this._histogramRef.current).select('.tooltip').remove(); var data = this.data(dataSet); - var xAxisTitle = Object.keys(dataSet[0])[0] + var xAxisTitle = Object.keys(dataSet[0])[0]; var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency'; - let uniqueArr: unknown[] = [...new Set(data)] - var numBins = (this.numericalXData && Number.isInteger(data[0]))? (this.rangeVals.xMax! - this.rangeVals.xMin! ) : uniqueArr.length - var translateXAxis = !this.numericalXData || numBins<this.maxBins ? width/(numBins+1)/2 : 0; - if (numBins>this.maxBins) numBins = this.maxBins; - var startingPoint = this.numericalXData? this.rangeVals.xMin! : 0; - var endingPoint = this.numericalXData? this.rangeVals.xMax! : numBins; + let uniqueArr: unknown[] = [...new Set(data)]; + var numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length; + var translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0; + if (numBins > this.maxBins) numBins = this.maxBins; + var startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0; + var endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins; // converts data into Objects - var histDataSet = dataSet.filter((d: { [x: string]: unknown; }) => { + var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => { var valid = true; Object.keys(dataSet[0]).map(key => { if (!d[key] || Number.isNaN(d[key])) valid = false; - }) + }); return valid; }); if (!this.numericalXData) { - var histStringDataSet: { [x: string]: unknown; }[] = []; - if (this.numericalYData){ - for (let i=0; i<dataSet.length; i++){ - histStringDataSet.push({[yAxisTitle]: dataSet[i][yAxisTitle], [xAxisTitle]: dataSet[i][xAxisTitle]}) + var histStringDataSet: { [x: string]: unknown }[] = []; + if (this.numericalYData) { + for (let i = 0; i < dataSet.length; i++) { + histStringDataSet.push({ [yAxisTitle]: dataSet[i][yAxisTitle], [xAxisTitle]: dataSet[i][xAxisTitle] }); } - } - else{ - for (let i=0; i<uniqueArr.length; i++){ - histStringDataSet.push({[yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i]}) + } else { + for (let i = 0; i < uniqueArr.length; i++) { + histStringDataSet.push({ [yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i] }); } - for (let i=0; i<data.length; i++){ - let barData = histStringDataSet.filter(each => each[xAxisTitle]==data[i]) - histStringDataSet.filter(each => each[xAxisTitle]==data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1; + for (let i = 0; i < data.length; i++) { + let barData = histStringDataSet.filter(each => each[xAxisTitle] == data[i]); + histStringDataSet.filter(each => each[xAxisTitle] == data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1; } } - histDataSet = histStringDataSet + histDataSet = histStringDataSet; } // initial graph and binning data for histogram var svg = (this._histogramSvg = d3 .select(this._histogramRef.current) - .append("svg") - .attr("class", "graph") - .attr("width", width + this.props.margin.right + this.props.margin.left) - .attr("height", height + this.props.margin.top + this.props.margin.bottom) - .append("g") - .attr("transform", - "translate(" + this.props.margin.left + "," + this.props.margin.top + ")")); - var x = d3.scaleLinear() - .domain(this.numericalXData? [startingPoint!, endingPoint!] : [0, numBins]) - .range([0, width ]); - var histogram = d3.histogram() - .value(function(d) {return d}) + .append('svg') + .attr('class', 'graph') + .attr('width', width + this.props.margin.right + this.props.margin.left) + .attr('height', height + this.props.margin.top + this.props.margin.bottom) + .append('g') + .attr('transform', 'translate(' + this.props.margin.left + ',' + this.props.margin.top + ')')); + var x = d3 + .scaleLinear() + .domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins]) + .range([0, width]); + var histogram = d3 + .histogram() + .value(function (d) { + return d; + }) .domain([startingPoint!, endingPoint!]) - .thresholds(x.ticks(numBins)) - var bins = histogram(data) - var eachRectWidth = width/(bins.length) - var graphStartingPoint = (bins[0].x1 && bins[1])? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0; + .thresholds(x.ticks(numBins)); + var bins = histogram(data); + var eachRectWidth = width / bins.length; + var graphStartingPoint = bins[0].x1 && bins[1] ? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0; bins[0].x0 = graphStartingPoint; - x = x.domain([graphStartingPoint, endingPoint]) - .range([0, Number.isInteger(this.rangeVals.xMin!)? (width-eachRectWidth) : width ]) + x = x.domain([graphStartingPoint, endingPoint]).range([0, Number.isInteger(this.rangeVals.xMin!) ? width - eachRectWidth : width]); var xAxis; // more calculations based on bins // x-axis - if (!this.numericalXData) { // reorganize to match data if the data is strings rather than numbers + if (!this.numericalXData) { + // reorganize to match data if the data is strings rather than numbers // uniqueArr.sort() - histDataSet.sort() - for (let i=0; i<data.length; i++){ - var index = 0 - for (let j=0; j<uniqueArr.length; j++){ - if (uniqueArr[j] == data[i]){ + histDataSet.sort(); + for (let i = 0; i < data.length; i++) { + var index = 0; + for (let j = 0; j < uniqueArr.length; j++) { + if (uniqueArr[j] == data[i]) { index = j; } } - if (bins[index]) bins[index].push(data[i]) + if (bins[index]) bins[index].push(data[i]); } bins.pop(); - eachRectWidth = width/(bins.length) - bins.forEach(d => d.x0 = d.x0!) - xAxis = d3.axisBottom(x) - .ticks(bins.length>1? bins.length-1: 1) - .tickFormat( i => uniqueArr[i.valueOf()] as string) - .tickPadding(10) - x.range([0, width-eachRectWidth]) - x.domain([0, bins.length-1]) + eachRectWidth = width / bins.length; + bins.forEach(d => (d.x0 = d.x0!)); + xAxis = d3 + .axisBottom(x) + .ticks(bins.length > 1 ? bins.length - 1 : 1) + .tickFormat(i => uniqueArr[i.valueOf()] as string) + .tickPadding(10); + x.range([0, width - eachRectWidth]); + x.domain([0, bins.length - 1]); translateXAxis = eachRectWidth / 2; - } - else { + } else { var allSame = true; - for (var i=0; i<bins.length; i++){ - if (bins[i] && bins[i][0]){ + for (var i = 0; i < bins.length; i++) { + if (bins[i] && bins[i][0]) { var compare = bins[i][0]; - for (let j=1; j<bins[i].length; j++){ + for (let j = 1; j < bins[i].length; j++) { if (bins[i][j] != compare) allSame = false; } } } - if (allSame){ + if (allSame) { translateXAxis = eachRectWidth / 2; - eachRectWidth = width/(bins.length) - } - else { - eachRectWidth = width/(bins.length+1) - var tickDiff = (bins.length>=2? (bins[bins.length-2].x1!-bins[bins.length-2].x0!): 0) + eachRectWidth = width / bins.length; + } else { + eachRectWidth = width / (bins.length + 1); + var tickDiff = bins.length >= 2 ? bins[bins.length - 2].x1! - bins[bins.length - 2].x0! : 0; var curDomain = x.domain(); - x.domain([curDomain[0], curDomain[0] + tickDiff*bins.length]) + x.domain([curDomain[0], curDomain[0] + tickDiff * bins.length]); } - xAxis = d3.axisBottom(x) - .ticks(bins.length-1) - x.range([0, width-eachRectWidth]) + xAxis = d3.axisBottom(x).ticks(bins.length - 1); + x.range([0, width - eachRectWidth]); } // y-axis - const maxFrequency = this.numericalYData? d3.max(histDataSet, function(d: any) { - return d[yAxisTitle]? Number(d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) : 0}) - : d3.max(bins, function(d) { return d.length; }) - var y = d3.scaleLinear() - .range([height, 0]); - y.domain([0, +maxFrequency!]); - var yAxis = d3.axisLeft(y) - .ticks(maxFrequency!) - if (this.numericalYData){ + const maxFrequency = this.numericalYData + ? d3.max(histDataSet, function (d: any) { + return d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) : 0; + }) + : d3.max(bins, function (d) { + return d.length; + }); + var y = d3.scaleLinear().range([height, 0]); + y.domain([0, +maxFrequency!]); + var yAxis = d3.axisLeft(y).ticks(maxFrequency!); + if (this.numericalYData) { const yScale = scaleCreatorNumerical(0, Number(maxFrequency), height, 0); yAxisCreator(svg.append('g'), width, yScale); + } else { + svg.append('g').call(yAxis); } - else{ - svg.append("g") - .call(yAxis); - } - svg.append("g") - .attr("transform", "translate(" + translateXAxis + ", " + height + ")") - .call(xAxis) + svg.append('g') + .attr('transform', 'translate(' + translateXAxis + ', ' + height + ')') + .call(xAxis); // click/hover const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet)); const onHover = action((e: any) => { - const selected = this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet) + const selected = this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet); updateHighlights(); - }) + }); const mouseOut = action((e: any) => { this.hoverOverData = undefined; updateHighlights(); - }) + }); const updateHighlights = () => { const hoverOverBar = this.hoverOverData; const selectedData = this.selectedData; - svg.selectAll('rect').attr("class", function(d: any) { return ((hoverOverBar && hoverOverBar[0]==d[0]) || selectedData && selectedData[0]==d[0])? 'histogram-bar hover' : 'histogram-bar'; }) - } - svg.on('click', onPointClick) - .on('mouseover', onHover) - .on('mouseout', mouseOut) + svg.selectAll('rect').attr('class', function (d: any) { + return (hoverOverBar && hoverOverBar[0] == d[0]) || (selectedData && selectedData[0] == d[0]) ? 'histogram-bar hover' : 'histogram-bar'; + }); + }; + svg.on('click', onPointClick).on('mouseover', onHover).on('mouseout', mouseOut); // axis titles - svg.append("text") - .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") - .style("text-anchor", "middle") + svg.append('text') + .attr('transform', 'translate(' + width / 2 + ' ,' + (height + 40) + ')') + .style('text-anchor', 'middle') .text(xAxisTitle); - svg.append("text") - .attr("transform", "rotate(-90)" + " " + "translate( 0, " + -10 + ")") - .attr("x", -(height/2)) - .attr("y", -20) - .style("text-anchor", "middle") + svg.append('text') + .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')') + .attr('x', -(height / 2)) + .attr('y', -20) + .style('text-anchor', 'middle') .text(yAxisTitle); - d3.format('.0f') + d3.format('.0f'); // draw bars var selected = this.selectedData; - svg.selectAll("rect") + svg.selectAll('rect') .data(bins) .enter() - .append("rect") - .attr("transform", this.numericalYData? - function (d) { - var eachData = histDataSet.filter((data: { [x: string]: number; }) => {return data[xAxisTitle]==d[0]}) - var length = eachData.length? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0; - return "translate(" + x(d.x0!) + "," + y(length) + ")"; - } - : function(d) { return "translate(" + x(d.x0!) + "," + y(d.length) + ")"; }) - .attr("height", this.numericalYData? - function(d) { - var eachData = histDataSet.filter((data: { [x: string]: number; }) => {return data[xAxisTitle]==d[0]}) - var length = eachData.length? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0; - return height-y(length)} - : function(d) { - return height - y(d.length)}) - .attr("width", eachRectWidth) - .attr("class", selected? - function(d) { - return (selected && selected[0]==d[0])? 'histogram-bar hover' : 'histogram-bar'; - }: function(d) {return 'histogram-bar'}) - .attr("fill", (d)=>{ + .append('rect') + .attr( + 'transform', + this.numericalYData + ? function (d) { + var eachData = histDataSet.filter((data: { [x: string]: number }) => { + return data[xAxisTitle] == d[0]; + }); + var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0; + return 'translate(' + x(d.x0!) + ',' + y(length) + ')'; + } + : function (d) { + return 'translate(' + x(d.x0!) + ',' + y(d.length) + ')'; + } + ) + .attr( + 'height', + this.numericalYData + ? function (d) { + var eachData = histDataSet.filter((data: { [x: string]: number }) => { + return data[xAxisTitle] == d[0]; + }); + var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0; + return height - y(length); + } + : function (d) { + return height - y(d.length); + } + ) + .attr('width', eachRectWidth) + .attr( + 'class', + selected + ? function (d) { + return selected && selected[0] == d[0] ? 'histogram-bar hover' : 'histogram-bar'; + } + : function (d) { + return 'histogram-bar'; + } + ) + .attr('fill', d => { var barColor; var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); - barColors.map(each => { - if (d[0] && d[0].toString() && each[0]==d[0].toString()) barColor = each[1]; + barColors.map(each => { + if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1]; else { - var range = StrCast(each[0]).split(" to "); - if (Number(range[0])<=d[0] && d[0]<=Number(range[1])) barColor = each[1]; + var range = StrCast(each[0]).split(' to '); + if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1]; } }); - return barColor? StrCast(barColor) : StrCast(this.props.layoutDoc.defaultHistogramColor)}) + return barColor ? StrCast(barColor) : StrCast(this.props.layoutDoc.defaultHistogramColor); + }); }; @action changeSelectedColor = (color: string) => { - this.curBarSelected.attr("fill", color); - var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) + this.curBarSelected.attr('fill', color); + var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')); - const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec("string"), null); - barColors.map(each => { if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1) }); + const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec('string'), null); + barColors.map(each => { + if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1); + }); barColors.push(StrCast(barName + '::' + color)); }; - - @action eraseSelectedColor= () => { - this.curBarSelected.attr("fill", this.props.layoutDoc.defaultHistogramColor); - var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) - const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec("string"), null); - barColors.map(each => { if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1) }); + @action eraseSelectedColor = () => { + this.curBarSelected.attr('fill', this.props.layoutDoc.defaultHistogramColor); + var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')); + + const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec('string'), null); + barColors.map(each => { + if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1); + }); }; render() { - this._histogramData - var curSelectedBarName; - var titleAccessor: any=''; - if (this.props.axes.length==2) titleAccessor = 'histogram-title-'+this.props.axes[0]+'-'+this.props.axes[1]; - else if (this.props.axes.length>0) titleAccessor = 'histogram-title-'+this.props.axes[0]; + this._histogramData; + var curSelectedBarName = ''; + var titleAccessor: any = ''; + if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_histogram_' + this.props.axes[0] + '-' + this.props.axes[1]; + else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_histogram_' + this.props.axes[0]; if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; if (!this.props.layoutDoc.defaultHistogramColor) this.props.layoutDoc.defaultHistogramColor = '#69b3a2'; if (!this.props.layoutDoc.histogramBarColors) this.props.layoutDoc.histogramBarColors = new List<string>(); var selected: string; - if (this._currSelected){ - curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) + if (this._currSelected) { + curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')); selected = '{ '; Object.keys(this._currSelected).map(key => { - key!=''? selected += key + ': ' + this._currSelected[key] + ', ': ''; - }) - selected = selected.substring(0, selected.length-2); + key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : ''; + }); + selected = selected.substring(0, selected.length - 2); selected += ' }'; - } - else selected = 'none'; + } else selected = 'none'; var selectedBarColor; var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); - barColors.map(each => {if (each[0]==curSelectedBarName!) selectedBarColor = each[1]}); + barColors.map(each => { + if (each[0] == curSelectedBarName!) selectedBarColor = each[1]; + }); this.componentDidMount(); @@ -436,54 +464,61 @@ export class Histogram extends React.Component<HistogramProps> { <div className="graph-title"> <EditableText val={StrCast(this.props.layoutDoc[titleAccessor])} - setVal={undoable (action(val => this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} - color={"black"} + setVal={undoable( + action(val => (this.props.layoutDoc[titleAccessor] = val as string)), + 'Change Graph Title' + )} + color={'black'} size={Size.LARGE} fillWidth /> - + <ColorPicker - tooltip={'Change Default Bar Color'} - type={Type.SEC} - icon={<FaFillDrip/>} + tooltip={'Change Default Bar Color'} + type={Type.SEC} + icon={<FaFillDrip />} selectedColor={StrCast(this.props.layoutDoc.defaultHistogramColor)} - setSelectedColor={undoable (color => this.props.layoutDoc.defaultHistogramColor = color, "Change Default Bar Color")} + setFinalColor={undoable(color => (this.props.layoutDoc.defaultHistogramColor = color), 'Change Default Bar Color')} + setSelectedColor={undoable(color => (this.props.layoutDoc.defaultHistogramColor = color), 'Change Default Bar Color')} size={Size.XSMALL} /> </div> <div ref={this._histogramRef} /> - {selected != 'none' ? + {selected != 'none' ? ( <div className={'selected-data'}> Selected: {selected} <ColorPicker - tooltip={'Change Bar Color'} - type={Type.SEC} - icon={<FaFillDrip/>} - selectedColor={selectedBarColor? selectedBarColor : this.curBarSelected.attr("fill")} - setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Bar Color")} + tooltip={'Change Bar Color'} + type={Type.SEC} + icon={<FaFillDrip />} + selectedColor={selectedBarColor ? selectedBarColor : this.curBarSelected.attr('fill')} + setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')} + setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')} size={Size.XSMALL} /> <IconButton - icon={<FontAwesomeIcon icon={'eraser'} />} - size={Size.XSMALL} - color={'black'} + icon={<FontAwesomeIcon icon={'eraser'} />} + size={Size.XSMALL} + color={'black'} type={Type.SEC} tooltip={'Revert to the default bar color'} - onClick={undoable (action(() => this.eraseSelectedColor()), "Change Selected Bar Color")} + onClick={undoable( + action(() => this.eraseSelectedColor()), + 'Change Selected Bar Color' + )} /> </div> - : null} + ) : null} </div> - ) : <span className="chart-container"> {'first use table view to select a column to graph'}</span> + ) : ( + <span className="chart-container"> {'first use table view to select a column to graph'}</span> + ); + } else + return ( + // when it is a brushed table and the incoming table doesn't have any rows selected + <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div> ); - } - else return ( - // when it is a brushed table and the incoming table doesn't have any rows selected - <div className='chart-container'> - Selected rows of data from the incoming DataVizBox to display. - </div> - ) } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 468780185..46cf27705 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -50,20 +50,21 @@ export class LineChart extends React.Component<LineChartProps> { // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _lineChartData() { - var guids = StrListCast(this.props.layoutDoc.rowGuids); + var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); if (this.props.axes.length <= 1) return []; return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) .sort((a, b) => (a.x < b.x ? -1 : 1)); } - @computed get graphTitle(){ - return this.props.axes[1] + " vs. " + this.props.axes[0] + " Line Chart"; - } + @computed get graphTitle() { + return this.props.axes[1] + ' vs. ' + this.props.axes[0] + ' Line Chart'; + } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links .filter(link => { - return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // get links where this chart doc is the target of the link + return link.link_anchor_1 == this.props.rootDoc.draggedFrom; + }) // get links where this chart doc is the target of the link .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @computed get incomingSelected() { @@ -158,7 +159,7 @@ export class LineChart extends React.Component<LineChartProps> { @action restoreView = (data: Doc) => { - const coords = Cast(data.presDataVizSelection, listSpec('number'), null); + const coords = Cast(data.config_dataVizSelection, listSpec('number'), null); if (coords?.length > 1 && (this._currSelected?.x !== coords[0] || this._currSelected?.y !== coords[1])) { this.setCurrSelected(coords[0], coords[1]); return true; @@ -176,8 +177,8 @@ export class LineChart extends React.Component<LineChartProps> { // title: 'line doc selection' + this._currSelected?.x, }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); - anchor.presDataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined; + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc); + anchor.config_dataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined; return anchor; }; @@ -189,14 +190,13 @@ export class LineChart extends React.Component<LineChartProps> { return this.props.width - this.props.margin.left - this.props.margin.right; } - @computed get defaultGraphTitle(){ + @computed get defaultGraphTitle() { var ax0 = this.props.axes[0]; - var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; - if (this.props.axes.length<2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1){ - return ax0 + " Line Chart"; - } - else return ax1 + " by " + ax0 + " Line Chart"; - } + var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined; + if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) { + return ax0 + ' Line Chart'; + } else return ax1 + ' by ' + ax0 + ' Line Chart'; + } setupTooltip() { return d3 @@ -215,7 +215,7 @@ export class LineChart extends React.Component<LineChartProps> { @action setCurrSelected(x?: number, y?: number) { // TODO: nda - get rid of svg element in the list? - if (this._currSelected && this._currSelected.x==x && this._currSelected.y==y) this._currSelected = undefined; + if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = undefined; else this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined; this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true)); } @@ -256,7 +256,7 @@ export class LineChart extends React.Component<LineChartProps> { const svg = (this._lineChartSvg = d3 .select(this._lineChartRef.current) .append('svg') - .attr("class", "graph") + .attr('class', 'graph') .attr('width', `${width + margin.left + margin.right}`) .attr('height', `${height + margin.top + margin.bottom}`) .append('g') @@ -271,13 +271,13 @@ export class LineChart extends React.Component<LineChartProps> { // get valid data points const data = dataSet[0]; const lineGen = createLineGenerator(xScale, yScale); - var validData = data.filter((d => { + var validData = data.filter(d => { var valid = true; Object.keys(data[0]).map(key => { if (!d[key] || Number.isNaN(d[key])) valid = false; - }) + }); return valid; - })) + }); // draw the plot line drawLine(svg.append('path'), validData, lineGen); // draw the datapoint circle @@ -321,17 +321,17 @@ export class LineChart extends React.Component<LineChartProps> { .on('click', onPointClick); // axis titles - svg.append("text") - .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") - .style("text-anchor", "middle") + svg.append('text') + .attr('transform', 'translate(' + width / 2 + ' ,' + (height + 40) + ')') + .style('text-anchor', 'middle') .text(this.props.axes[0]); - svg.append("text") - .attr("transform", "rotate(-90)" + " " + "translate( 0, " + -10 + ")") - .attr("x", -(height/2)) - .attr("y", -20) - .attr("height", 20) - .attr("width", 20) - .style("text-anchor", "middle") + svg.append('text') + .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')') + .attr('x', -(height / 2)) + .attr('y', -20) + .attr('height', 20) + .attr('width', 20) + .style('text-anchor', 'middle') .text(this.props.axes[1]); }; @@ -348,42 +348,41 @@ export class LineChart extends React.Component<LineChartProps> { tooltip .html(() => `<b>(${d0.x},${d0.y})</b>`) // text content for tooltip .style('pointer-events', 'none') - .style('transform', `translate(${xScale(d0.x)-this.width}px,${yScale(d0.y)}px)`); + .style('transform', `translate(${xScale(d0.x) - this.width}px,${yScale(d0.y)}px)`); } render() { this.componentDidMount(); - var titleAccessor:any = ''; - if (this.props.axes.length==2) titleAccessor = 'lineChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]; - else if (this.props.axes.length>0) titleAccessor = 'lineChart-title-'+this.props.axes[0]; + var titleAccessor: any = ''; + if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_lineChart_' + this.props.axes[0] + '-' + this.props.axes[1]; + else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_lineChart_' + this.props.axes[0]; if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none'; if (this._lineChartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){ - return ( - this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? ( + return this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? ( <div className="chart-container" > <div className="graph-title"> <EditableText val={StrCast(this.props.layoutDoc[titleAccessor])} - setVal={undoable (action(val => this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} - color={"black"} + setVal={undoable( + action(val => (this.props.layoutDoc[titleAccessor] = val as string)), + 'Change Graph Title' + )} + color={'black'} size={Size.LARGE} fillWidth /> </div> <div ref={this._lineChartRef} /> - {selectedPt!='none'? - <div className={'selected-data'}> {`Selected: ${selectedPt}`}</div> - : null} + {selectedPt != 'none' ? <div className={'selected-data'}> {`Selected: ${selectedPt}`}</div> : null} </div> - ) : <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span> + ) : ( + <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span> + ); + } else + return ( + // when it is a brushed table and the incoming table doesn't have any rows selected + <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div> ); - } - else return ( - // when it is a brushed table and the incoming table doesn't have any rows selected - <div className='chart-container'> - Selected rows of data from the incoming DataVizBox to display. - </div> - ) } } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 657d496aa..213baa8a4 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -1,18 +1,18 @@ -import { observer } from "mobx-react"; -import { Doc, StrListCast } from "../../../../../fields/Doc"; +import { observer } from 'mobx-react'; +import { Doc, StrListCast } from '../../../../../fields/Doc'; import * as React from 'react'; import * as d3 from 'd3'; -import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; -import { LinkManager } from "../../../../util/LinkManager"; -import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; -import { PinProps, PresBox } from "../../trails"; -import { Docs } from "../../../../documents/Documents"; -import { List } from "../../../../../fields/List"; +import { IReactionDisposer, action, computed, observable, reaction } from 'mobx'; +import { LinkManager } from '../../../../util/LinkManager'; +import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; +import { PinProps, PresBox } from '../../trails'; +import { Docs } from '../../../../documents/Documents'; +import { List } from '../../../../../fields/List'; import './Chart.scss'; -import { ColorPicker, EditableText, Size, Type } from "browndash-components"; -import { FaFillDrip } from "react-icons/fa"; -import { listSpec } from "../../../../../fields/Schema"; -import { undoable } from "../../../../util/UndoManager"; +import { ColorPicker, EditableText, Size, Type } from 'browndash-components'; +import { FaFillDrip } from 'react-icons/fa'; +import { listSpec } from '../../../../../fields/Schema'; +import { undoable } from '../../../../util/UndoManager'; export interface PieChartProps { rootDoc: Doc; @@ -33,7 +33,6 @@ export interface PieChartProps { @observer export class PieChart extends React.Component<PieChartProps> { - private _disposers: { [key: string]: IReactionDisposer } = {}; private _piechartRef: React.RefObject<HTMLDivElement> = React.createRef(); private _piechartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined; @@ -45,31 +44,34 @@ export class PieChart extends React.Component<PieChartProps> { // filters all data to just display selected data if brushed (created from an incoming link) @computed get _piechartData() { - var guids = StrListCast(this.props.layoutDoc.rowGuids); + var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; - if (/\d/.test(this.props.pairs[0][ax0])){ this.byCategory = false } + if (/\d/.test(this.props.pairs[0][ax0])) { + this.byCategory = false; + } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) - }; + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) + .map(pair => ({ [ax0]: pair[this.props.axes[0]] })); + } var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { this.byCategory = false; } + if (/\d/.test(this.props.pairs[0][ax0])) { + this.byCategory = false; + } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) + .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] })); } - @computed get defaultGraphTitle(){ + @computed get defaultGraphTitle() { var ax0 = this.props.axes[0]; - var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; - if (this.props.axes.length<2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1){ - return ax0 + " Pie Chart"; - } - else return ax1 + " by " + ax0 + " Pie Chart"; - } + var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined; + if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) { + return ax0 + ' Pie Chart'; + } else return ax1 + ' by ' + ax0 + ' Pie Chart'; + } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links @@ -84,7 +86,7 @@ export class PieChart extends React.Component<PieChartProps> { this._disposers.chartData = reaction( () => ({ dataSet: this._piechartData, w: this.width, h: this.height }), ({ dataSet, w, h }) => { - if (dataSet!.length>0) { + if (dataSet!.length > 0) { this.drawChart(dataSet, w, h); } }, @@ -100,7 +102,7 @@ export class PieChart extends React.Component<PieChartProps> { // title: 'piechart doc selection' + this._currSelected, }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc); return anchor; }; @@ -114,167 +116,177 @@ export class PieChart extends React.Component<PieChartProps> { // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { - var validData = dataSet.filter((d: { [x: string]: unknown; }) => { + var validData = dataSet.filter((d: { [x: string]: unknown }) => { var valid = true; Object.keys(dataSet[0]).map(key => { if (!d[key] || Number.isNaN(d[key])) valid = false; - }) + }); return valid; - }) - var field = dataSet[0]? Object.keys(dataSet[0])[0] : undefined; - const data = validData.map((d: { [x: string]: any; }) => { - if (!this.byCategory) { return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') } - return d[field!] - }) + }); + var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; + const data = validData.map((d: { [x: string]: any }) => { + if (!this.byCategory) { + return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''); + } + return d[field!]; + }); return data; - } + }; // outlines the slice selected / hovered over highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => { - var index = -1; - var sameAsCurrent: boolean; - const selected = svg.selectAll('.slice').filter((d: any) => { - index++; - var p1 = [0,0]; // center of pie - var p3 = [arc.centroid(d)[0]*2, arc.centroid(d)[1]*2]; // outward peak of arc - var p2 = [radius*Math.sin(d.startAngle), -radius*Math.cos(d.startAngle)]; // start of arc - var p4 = [radius*Math.sin(d.endAngle), -radius*Math.cos(d.endAngle)]; // end of arc + var index = -1; + var sameAsCurrent: boolean; + const selected = svg.selectAll('.slice').filter((d: any) => { + index++; + var p1 = [0, 0]; // center of pie + var p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc + var p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc + var p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc - // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge - var lineCrossCount = 0; - // if for all 4 lines - if (Math.min(p1[1], p2[1])<=pointer[1] && pointer[1]<=Math.max(p1[1], p2[1])){ // within y bounds - if (pointer[0] <= (pointer[1]-p1[1])*(p2[0]-p1[0])/(p2[1]-p1[1])+p1[0]) lineCrossCount++; } // intercepts x - if (Math.min(p2[1], p3[1])<=pointer[1] && pointer[1]<=Math.max(p2[1], p3[1])){ - if (pointer[0] <= (pointer[1]-p2[1])*(p3[0]-p2[0])/(p3[1]-p2[1])+p2[0]) lineCrossCount++; } - if (Math.min(p3[1], p4[1])<=pointer[1] && pointer[1]<=Math.max(p3[1], p4[1])){ - if (pointer[0] <= (pointer[1]-p3[1])*(p4[0]-p3[0])/(p4[1]-p3[1])+p3[0]) lineCrossCount++; } - if (Math.min(p4[1], p1[1])<=pointer[1] && pointer[1]<=Math.max(p4[1], p1[1])){ - if (pointer[0] <= (pointer[1]-p4[1])*(p1[0]-p4[0])/(p1[1]-p4[1])+p4[0]) lineCrossCount++; } - if (lineCrossCount % 2 != 0) { // inside the slice of it crosses an odd number of edges - var showSelected = this.byCategory? pieDataSet[index] : this._piechartData[index]; - if (changeSelectedVariables){ - // for when a bar is selected - not just hovered over - sameAsCurrent = (this._currSelected)? - (showSelected[Object.keys(showSelected)[0]]==this._currSelected![Object.keys(showSelected)[0]] - && showSelected[Object.keys(showSelected)[1]]==this._currSelected![Object.keys(showSelected)[1]]) - : - this._currSelected===showSelected; - this._currSelected = sameAsCurrent? undefined: showSelected; - this.selectedData = sameAsCurrent? undefined: d; - } - else this.hoverOverData = d; - return true; - } - return false; - }); - if (changeSelectedVariables){ - if (sameAsCurrent!) this.curSliceSelected = undefined; - else this.curSliceSelected = selected; + // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge + var lineCrossCount = 0; + // if for all 4 lines + if (Math.min(p1[1], p2[1]) <= pointer[1] && pointer[1] <= Math.max(p1[1], p2[1])) { + // within y bounds + if (pointer[0] <= ((pointer[1] - p1[1]) * (p2[0] - p1[0])) / (p2[1] - p1[1]) + p1[0]) lineCrossCount++; + } // intercepts x + if (Math.min(p2[1], p3[1]) <= pointer[1] && pointer[1] <= Math.max(p2[1], p3[1])) { + if (pointer[0] <= ((pointer[1] - p2[1]) * (p3[0] - p2[0])) / (p3[1] - p2[1]) + p2[0]) lineCrossCount++; } - } + if (Math.min(p3[1], p4[1]) <= pointer[1] && pointer[1] <= Math.max(p3[1], p4[1])) { + if (pointer[0] <= ((pointer[1] - p3[1]) * (p4[0] - p3[0])) / (p4[1] - p3[1]) + p3[0]) lineCrossCount++; + } + if (Math.min(p4[1], p1[1]) <= pointer[1] && pointer[1] <= Math.max(p4[1], p1[1])) { + if (pointer[0] <= ((pointer[1] - p4[1]) * (p1[0] - p4[0])) / (p1[1] - p4[1]) + p4[0]) lineCrossCount++; + } + if (lineCrossCount % 2 != 0) { + // inside the slice of it crosses an odd number of edges + var showSelected = this.byCategory ? pieDataSet[index] : this._piechartData[index]; + if (changeSelectedVariables) { + // for when a bar is selected - not just hovered over + sameAsCurrent = this._currSelected + ? showSelected[Object.keys(showSelected)[0]] == this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] == this._currSelected![Object.keys(showSelected)[1]] + : this._currSelected === showSelected; + this._currSelected = sameAsCurrent ? undefined : showSelected; + this.selectedData = sameAsCurrent ? undefined : d; + } else this.hoverOverData = d; + return true; + } + return false; + }); + if (changeSelectedVariables) { + if (sameAsCurrent!) this.curSliceSelected = undefined; + else this.curSliceSelected = selected; + } + }; // draws the pie chart drawChart = (dataSet: any, width: number, height: number) => { d3.select(this._piechartRef.current).select('svg').remove(); d3.select(this._piechartRef.current).select('.tooltip').remove(); - - var percentField = Object.keys(dataSet[0])[0] - var descriptionField = Object.keys(dataSet[0])[1]! - var radius = Math.min(width, height-this.props.margin.top-this.props.margin.bottom) /2 + + var percentField = Object.keys(dataSet[0])[0]; + var descriptionField = Object.keys(dataSet[0])[1]!; + var radius = Math.min(width, height - this.props.margin.top - this.props.margin.bottom) / 2; // converts data into Objects var data = this.data(dataSet); - var pieDataSet = dataSet.filter((d: { [x: string]: unknown; }) => { + var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => { var valid = true; Object.keys(dataSet[0]).map(key => { if (!d[key] || Number.isNaN(d[key])) valid = false; - }) + }); return valid; }); - if (this.byCategory){ - let uniqueCategories = [...new Set(data)] + if (this.byCategory) { + let uniqueCategories = [...new Set(data)]; var pieStringDataSet: { frequency: number }[] = []; - for (let i=0; i<uniqueCategories.length; i++){ - pieStringDataSet.push({frequency: 0, [percentField]: uniqueCategories[i]}) + for (let i = 0; i < uniqueCategories.length; i++) { + pieStringDataSet.push({ frequency: 0, [percentField]: uniqueCategories[i] }); } - for (let i=0; i<data.length; i++){ - let sliceData = pieStringDataSet.filter((each: any) => each[percentField]==data[i]) + for (let i = 0; i < data.length; i++) { + let sliceData = pieStringDataSet.filter((each: any) => each[percentField] == data[i]); sliceData[0].frequency = sliceData[0].frequency + 1; } - pieDataSet = pieStringDataSet - percentField = Object.keys(pieDataSet[0])[0] - descriptionField = Object.keys(pieDataSet[0])[1]! - data = this.data(pieStringDataSet) + pieDataSet = pieStringDataSet; + percentField = Object.keys(pieDataSet[0])[0]; + descriptionField = Object.keys(pieDataSet[0])[1]!; + data = this.data(pieStringDataSet); } - var trackDuplicates : {[key: string]: any} = {}; - data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) + var trackDuplicates: { [key: string]: any } = {}; + data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null)); // initial chart var svg = (this._piechartSvg = d3 .select(this._piechartRef.current) - .append("svg") - .attr("class", "graph") - .attr("width", width + this.props.margin.right + this.props.margin.left) - .attr("height", height + this.props.margin.top + this.props.margin.bottom) - .append("g")); - let g = svg.append("g") - .attr("transform", - "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")"); + .append('svg') + .attr('class', 'graph') + .attr('width', width + this.props.margin.right + this.props.margin.left) + .attr('height', height + this.props.margin.top + this.props.margin.bottom) + .append('g')); + let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this.props.margin.left) + ',' + height / 2 + ')'); var pie = d3.pie(); - var arc = d3.arc() - .innerRadius(0) - .outerRadius(radius); + var arc = d3.arc().innerRadius(0).outerRadius(radius); // click/hover const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet)); const onHover = action((e: any) => { - const selected = this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet) + const selected = this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet); updateHighlights(); - }) + }); const mouseOut = action((e: any) => { this.hoverOverData = undefined; updateHighlights(); - }) + }); const updateHighlights = () => { const hoverOverSlice = this.hoverOverData; const selectedData = this.selectedData; - svg.selectAll('path').attr("class", function(d: any) { - return ((selectedData && d.startAngle==selectedData.startAngle && d.endAngle==selectedData.endAngle) - || ((hoverOverSlice && d.startAngle==hoverOverSlice.startAngle && d.endAngle==hoverOverSlice.endAngle)))? 'slice hover' : 'slice'; }) - } + svg.selectAll('path').attr('class', function (d: any) { + return (selectedData && d.startAngle == selectedData.startAngle && d.endAngle == selectedData.endAngle) || (hoverOverSlice && d.startAngle == hoverOverSlice.startAngle && d.endAngle == hoverOverSlice.endAngle) + ? 'slice hover' + : 'slice'; + }); + }; // drawing the slices var selected = this.selectedData; - var arcs = g.selectAll("arc") - .data(pie(data)) - .enter() - .append("g") - arcs.append("path") - .attr("fill", (d, i)=>{ - var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number; }; }) => { + var arcs = g.selectAll('arc').data(pie(data)).enter().append('g'); + arcs.append('path') + .attr('fill', (d, i) => { + var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => { try { - return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''))==Number(d.data) + return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data); } catch (error) { - return each[percentField]==d.data - }}) + return each[percentField] == d.data; + } + }); var dataPoint; - if (possibleDataPoints.length==1) dataPoint = possibleDataPoints[0]; - else{ - dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]] + if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; + else { + dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } var sliceColor; - if (dataPoint){ + if (dataPoint) { var accessByName = dataPoint[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''); var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); - sliceColors.map(each => {if (each[0]==StrCast(accessByName)) sliceColor = each[1]}); + sliceColors.map(each => { + if (each[0] == StrCast(accessByName)) sliceColor = each[1]; + }); } - return sliceColor? StrCast(sliceColor) : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%(d3.schemeSet3.length)] }) - .attr("class", selected? - function(d) { - return (selected && d.startAngle==selected.startAngle && d.endAngle==selected.endAngle)? 'slice hover' : 'slice'; - }: function(d) {return 'slice'}) + return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length]; + }) + .attr( + 'class', + selected + ? function (d) { + return selected && d.startAngle == selected.startAngle && d.endAngle == selected.endAngle ? 'slice hover' : 'slice'; + } + : function (d) { + return 'slice'; + } + ) .attr('d', arc) .on('click', onPointClick) .on('mouseover', onHover) @@ -282,99 +294,106 @@ export class PieChart extends React.Component<PieChartProps> { // adding labels trackDuplicates = {}; - data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) - arcs.append("text") - .attr("transform",function(d){ - var centroid = arc.centroid(d as unknown as d3.DefaultArcObject) - var heightOffset = (centroid[1]/radius) * Math.abs(centroid[1]) - return "translate("+ (centroid[0]+centroid[0]/(radius*.02)) + "," + (centroid[1]+heightOffset) + ")"; }) - .attr("text-anchor", "middle") - .text(function(d){ - var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number; }; }) => { + data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null)); + arcs.append('text') + .attr('transform', function (d) { + var centroid = arc.centroid(d as unknown as d3.DefaultArcObject); + var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]); + return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')'; + }) + .attr('text-anchor', 'middle') + .text(function (d) { + var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => { try { - return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''))==Number(d.data) + return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data); } catch (error) { - return each[percentField]==d.data - }}) + return each[percentField] == d.data; + } + }); var dataPoint; - if (possibleDataPoints.length==1) dataPoint = possibleDataPoints[0]; - else{ - dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]] + if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; + else { + dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } - return dataPoint? dataPoint[percentField]! + (!descriptionField? '' : (' - ' + dataPoint[descriptionField]))! : ''}) + return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : ''; + }); }; @action changeSelectedColor = (color: string) => { - this.curSliceSelected.attr("fill", color); - var sliceName = this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') - - const sliceColors = Cast(this.props.layoutDoc.pieSliceColors, listSpec("string"), null); - sliceColors.map(each => { if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1) }); + this.curSliceSelected.attr('fill', color); + var sliceName = this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''); + + const sliceColors = Cast(this.props.layoutDoc.pieSliceColors, listSpec('string'), null); + sliceColors.map(each => { + if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1); + }); sliceColors.push(StrCast(sliceName + '::' + color)); }; render() { this.componentDidMount(); - var titleAccessor: any=''; - if (this.props.axes.length==2) titleAccessor = 'pieChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]; - else if (this.props.axes.length>0) titleAccessor = 'pieChart-title-'+this.props.axes[0]; + var titleAccessor: any = ''; + if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0] + '-' + this.props.axes[1]; + else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0]; if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; if (!this.props.layoutDoc.pieSliceColors) this.props.layoutDoc.pieSliceColors = new List<string>(); var selected: string; - var curSelectedSliceName; - if (this._currSelected){ - curSelectedSliceName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) + var curSelectedSliceName = ''; + if (this._currSelected) { + curSelectedSliceName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')); selected = '{ '; Object.keys(this._currSelected).map(key => { - key!=''? selected += key + ': ' + this._currSelected[key] + ', ': ''; - }) - selected = selected.substring(0, selected.length-2); + key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : ''; + }); + selected = selected.substring(0, selected.length - 2); selected += ' }'; - } - else selected = 'none'; + } else selected = 'none'; var selectedSliceColor; var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); - sliceColors.map(each => {if (each[0]==curSelectedSliceName!) selectedSliceColor = each[1]}); + sliceColors.map(each => { + if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1]; + }); if (this._piechartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){ - return ( - this.props.axes.length >= 1 ? ( + return this.props.axes.length >= 1 ? ( <div className="chart-container"> - <div className="graph-title"> + <div className="graph-title"> <EditableText val={StrCast(this.props.layoutDoc[titleAccessor])} - setVal={undoable (action(val => this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} - color={"black"} + setVal={undoable( + action(val => (this.props.layoutDoc[titleAccessor] = val as string)), + 'Change Graph Title' + )} + color={'black'} size={Size.LARGE} fillWidth /> </div> <div ref={this._piechartRef} /> - {selected != 'none' ? + {selected != 'none' ? ( <div className={'selected-data'}> Selected: {selected} <ColorPicker - tooltip={'Change Slice Color'} - type={Type.SEC} - icon={<FaFillDrip/>} - selectedColor={selectedSliceColor? selectedSliceColor : this.curSliceSelected.attr("fill")} - setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Slice Color")} + tooltip={'Change Slice Color'} + type={Type.SEC} + icon={<FaFillDrip />} + selectedColor={selectedSliceColor ? selectedSliceColor : this.curSliceSelected.attr('fill')} + setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')} + setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')} size={Size.XSMALL} /> </div> - : null} + ) : null} </div> - ) : <span className="chart-container"> {'first use table view to select a column to graph'}</span> + ) : ( + <span className="chart-container"> {'first use table view to select a column to graph'}</span> + ); + } else + return ( + // when it is a brushed table and the incoming table doesn't have any rows selected + <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div> ); - } - else return ( - // when it is a brushed table and the incoming table doesn't have any rows selected - <div className='chart-container'> - Selected rows of data from the incoming DataVizBox to display. - </div> - ) } - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 7b3e700ac..edec72cc5 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,4 +1,4 @@ -import { action, computed, } from 'mobx'; +import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, Field, StrListCast } from '../../../../../fields/Doc'; @@ -31,44 +31,45 @@ interface TableBoxProps { @observer export class TableBox extends React.Component<TableBoxProps> { - // filters all data to just display selected data if brushed (created from an incoming link) @computed get _tableData() { if (this.incomingLinks.length! <= 0) return this.props.pairs; - var guids = StrListCast(this.props.layoutDoc.rowGuids); - return this.props.pairs?.filter(pair => this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)])) + var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); + return this.props.pairs?.filter(pair => this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])); } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links .filter(link => { - return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // get links where this chart doc is the target of the link + return link.link_anchor_1 == this.props.rootDoc.draggedFrom; + }) // get links where this chart doc is the target of the link .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @computed get columns() { - if (!this.props.layoutDoc.rowGuids) this.props.layoutDoc.rowGuids = new List<string>(); - const guids = Cast(this.props.layoutDoc.rowGuids, listSpec("string"), null); - if (guids.length==0) this.props.pairs.map(row => guids.push(Utils.GenerateGuid())); - return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='' && header!=undefined) : []; + if (!this.props.layoutDoc.dataViz_rowGuids) this.props.layoutDoc.dataViz_rowGuids = new List<string>(); + const guids = Cast(this.props.layoutDoc.dataViz_rowGuids, listSpec('string'), null); + if (guids.length == 0) this.props.pairs.map(row => guids.push(Utils.GenerateGuid())); + return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : []; } // updates the 'selected' field to no longer include rows that aren't in the table filterSelectedRowsDown() { - if (!this.props.layoutDoc.selected) this.props.layoutDoc.selected = new List<string>(); - const selected = Cast(this.props.layoutDoc.selected, listSpec("string"), null); - const incomingSelected = this.incomingLinks.length? StrListCast(this.incomingLinks[0].selected) : undefined; - if (incomingSelected){ + if (!this.props.layoutDoc.dataViz_selectedRows) this.props.layoutDoc.dataViz_selectedRows = new List<string>(); + const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null); + const incomingSelected = this.incomingLinks.length ? StrListCast(this.incomingLinks[0].dataViz_selectedRows) : undefined; + if (incomingSelected) { selected.map(guid => { - if (!incomingSelected.includes(guid)) selected.splice(selected.indexOf(guid), 1)}); // filters through selected to remove guids that were removed in the incoming data + if (!incomingSelected.includes(guid)) selected.splice(selected.indexOf(guid), 1); + }); // filters through selected to remove guids that were removed in the incoming data } } render() { this.filterSelectedRowsDown(); - if (this._tableData.length>0){ + if (this._tableData.length > 0) { return ( - <div className="table-container" style={{height: this.props.height}}> + <div className="table-container" style={{ height: this.props.height }}> <table className="table"> <thead> <tr className="table-row"> @@ -83,7 +84,8 @@ export class TableBox extends React.Component<TableBoxProps> { style={{ color: this.props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this.props.axes.lastElement() === col ? 'darkred' : undefined, background: this.props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this.props.axes.lastElement() === col ? '#Fbdbdb' : undefined, - fontWeight: 'bolder', border: '3px solid black' + fontWeight: 'bolder', + border: '3px solid black', }} onPointerDown={e => { const downX = e.clientX; @@ -91,12 +93,13 @@ export class TableBox extends React.Component<TableBoxProps> { setupMoveUpEvents( {}, e, - e => { // dragging off a column to create a brushed DataVizBox + e => { + // dragging off a column to create a brushed DataVizBox const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!; const targetCreator = (annotationOn: Doc | undefined) => { const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!); - embedding._dataVizView = DataVizView.TABLE; - embedding._data_vizAxes = new List<string>([col, col]); + embedding._dataViz = DataVizView.TABLE; + embedding._dataViz_axes = new List<string>([col, col]); embedding._draggedFrom = this.props.docView?.()!.rootDoc!; embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors); @@ -142,17 +145,24 @@ export class TableBox extends React.Component<TableBoxProps> { <tbody> {this._tableData?.map((p, i) => { var containsData = false; - var guid = StrListCast(this.props.layoutDoc.rowGuids)![this.props.pairs.indexOf(p)] - this.columns.map(col => {if (p[col]!='' && p[col]!=null && p[col]!=undefined) containsData = true}) - if (containsData){ + var guid = StrListCast(this.props.layoutDoc.dataViz_rowGuids)![this.props.pairs.indexOf(p)]; + this.columns.map(col => { + if (p[col] != '' && p[col] != null && p[col] != undefined) containsData = true; + }); + if (containsData) { return ( - <tr key={i} className="table-row" onClick={action(e => { + <tr + key={i} + className="table-row" + onClick={action(e => { // selecting a row - const selected = Cast(this.props.layoutDoc.selected, listSpec("string"), null); + const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null); if (selected.includes(guid)) selected.splice(selected.indexOf(guid), 1); else { - selected.push(guid)}; - })} style={{ background: StrListCast(this.props.layoutDoc.selected).includes(guid) ? 'lightgrey' : '', width: '110%' }}> + selected.push(guid); + } + })} + style={{ background: StrListCast(this.props.layoutDoc.dataViz_selectedRows).includes(guid) ? 'lightgrey' : '', width: '110%' }}> {this.columns.map(col => { // each cell var colSelected = this.props.axes.length>1? (this.props.axes[0]==col || this.props.axes[1]==col) : this.props.axes.length>0? this.props.axes[0]==col : false; @@ -160,7 +170,8 @@ export class TableBox extends React.Component<TableBoxProps> { <td key={this.columns.indexOf(col)} style={{border: colSelected? '3px solid black' : '1px solid black', fontWeight: colSelected? 'bolder' : 'normal'}}> {p[col]} </td> - )})} + ); + })} </tr> ); } @@ -169,12 +180,10 @@ export class TableBox extends React.Component<TableBoxProps> { </table> </div> ); - } - else return ( - // when it is a brushed table and the incoming table doesn't have any rows selected - <div className='chart-container'> - Selected rows of data from the incoming DataVizBox to display. - </div> - ) + } else + return ( + // when it is a brushed table and the incoming table doesn't have any rows selected + <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div> + ); } } |
