aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DataVizBox/components/LineChart.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/DataVizBox/components/LineChart.tsx')
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx112
1 files changed, 89 insertions, 23 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 6b564b0c9..46cf27705 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,13 +1,12 @@
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-// import d3
import * as d3 from 'd3';
-import { Doc, DocListCast } from '../../../../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../../../../fields/Doc';
import { Id } from '../../../../../fields/FieldSymbols';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
-import { Cast, DocCast } from '../../../../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
import { DocumentManager } from '../../../../util/DocumentManager';
import { LinkManager } from '../../../../util/LinkManager';
@@ -15,16 +14,19 @@ import { PinProps, PresBox } from '../../trails';
import { DataVizBox } from '../DataVizBox';
import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
import './Chart.scss';
+import { EditableText, Size } from 'browndash-components';
+import { undoable } from '../../../../util/UndoManager';
export interface DataPoint {
x: number;
y: number;
}
-interface SelectedDataPoint extends DataPoint {
+export interface SelectedDataPoint extends DataPoint {
elem?: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
}
export interface LineChartProps {
rootDoc: Doc;
+ layoutDoc: Doc;
axes: string[];
pairs: { [key: string]: any }[];
width: number;
@@ -48,18 +50,26 @@ 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.dataViz_rowGuids);
if (this.props.axes.length <= 1) return [];
return this.props.pairs
- ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select'))))
+ ?.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 incomingLinks() {
return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- .filter(link => link.link_anchor_1 !== this.props.rootDoc) // get links where this chart doc is the target of the link
+ .filter(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() {
+ // return selected x and y axes
+ // otherwise, use the selection of whatever is linked to us
return this.incomingLinks // all links that are pointing to this node
.map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes
.filter(dvb => dvb)
@@ -149,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;
@@ -167,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;
};
@@ -180,6 +190,14 @@ export class LineChart extends React.Component<LineChartProps> {
return this.props.width - this.props.margin.left - this.props.margin.right;
}
+ @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';
+ }
+
setupTooltip() {
return d3
.select(this._lineChartRef.current)
@@ -197,9 +215,9 @@ export class LineChart extends React.Component<LineChartProps> {
@action
setCurrSelected(x?: number, y?: number) {
// TODO: nda - get rid of svg element in the list?
- this._currSelected = x !== undefined && y !== undefined ? { x, y } : 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));
- this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined));
}
drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -219,7 +237,7 @@ export class LineChart extends React.Component<LineChartProps> {
}
// TODO: nda - can use d3.create() to create html element instead of appending
- drawChart = (dataSet: DataPoint[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
+ drawChart = (dataSet: any[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
// clearing tooltip and the current chart
d3.select(this._lineChartRef.current).select('svg').remove();
d3.select(this._lineChartRef.current).select('.tooltip').remove();
@@ -238,6 +256,7 @@ export class LineChart extends React.Component<LineChartProps> {
const svg = (this._lineChartSvg = d3
.select(this._lineChartRef.current)
.append('svg')
+ .attr('class', 'graph')
.attr('width', `${width + margin.left + margin.right}`)
.attr('height', `${height + margin.top + margin.bottom}`)
.append('g')
@@ -249,13 +268,20 @@ export class LineChart extends React.Component<LineChartProps> {
xAxisCreator(svg.append('g'), height, xScale);
yAxisCreator(svg.append('g'), width, yScale);
- // draw the plot line
+ // get valid data points
const data = dataSet[0];
const lineGen = createLineGenerator(xScale, yScale);
- drawLine(svg.append('path'), data, lineGen);
-
+ 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
- this.drawDataPoints(data, 0, xScale, yScale);
+ this.drawDataPoints(validData, 0, xScale, yScale);
const higlightFocusPt = svg.append('g').style('display', 'none');
higlightFocusPt.append('circle').attr('r', 5).attr('class', 'circle');
@@ -293,6 +319,20 @@ export class LineChart extends React.Component<LineChartProps> {
.on('mouseout', () => tooltip.transition().duration(300).style('opacity', 0))
.on('mousemove', mousemove)
.on('click', onPointClick);
+
+ // axis titles
+ 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')
+ .text(this.props.axes[1]);
};
private updateTooltip(
@@ -308,15 +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 / 2}px,${yScale(d0.y) - 30}px)`);
+ .style('transform', `translate(${xScale(d0.x) - this.width}px,${yScale(d0.y)}px)`);
}
render() {
- const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none';
- return (
- <div ref={this._lineChartRef} className="chart-container">
- <span> {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Selected: ${selectedPt}`}</span>
- </div>
- );
+ this.componentDidMount();
+ 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]]) ? (
+ <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'}
+ size={Size.LARGE}
+ fillWidth
+ />
+ </div>
+ <div ref={this._lineChartRef} />
+ {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>
+ );
+ } 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>
+ );
}
}