diff options
author | bobzel <zzzman@gmail.com> | 2023-04-06 10:01:39 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2023-04-06 10:01:39 -0400 |
commit | 7975e3f4aaa3601a5512a7dce4c261e7f9411374 (patch) | |
tree | 94ab9fb90a9f87124ef97502197067938c98d735 /src | |
parent | a8343cad405a146fdff8fc2d66ef41fdaefb8bda (diff) |
added selection ui for choosing linechart axes.
Diffstat (limited to 'src')
4 files changed, 79 insertions, 35 deletions
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index e279a1262..0ce589a13 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,7 +1,8 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; @@ -26,7 +27,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // 2 ways of doing it // @observable private pairs: { [key: string]: number | string | undefined }[] = []; // @observable private pairs: { [key: string]: FieldResult }[] = []; - @observable private pairs: { x: number; y: number }[] = []; + @observable private pairs: { [key: string]: string }[] = []; private _chartRenderer: LineChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc @@ -51,16 +52,17 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops @computed get dataVizView(): DataVizView { - return StrCast(this.rootDoc._dataVizView, 'table') as DataVizView; + return StrCast(this.layoutDoc._dataVizView, 'table') as DataVizView; } @action restoreView = (data: Doc) => { - const changed = this.dataVizView !== data.presDataVizView && (this.rootDoc._dataVizView = data.presDataVizView); + const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); + const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._dataVizAxes = new List<string>(StrListCast(data.presDataVizAxes))); const func = () => this._chartRenderer?.restoreView(data); - if (changed) { + if (changedView || changedAxes) { setTimeout(func, 100); - return changed ? true : false; + return true; } return func() ?? false; }; @@ -75,26 +77,27 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }); anchor.presDataVizView = this.dataVizView; + anchor.presDataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined; this.addDocument(anchor); return anchor; }; + @computed.struct get axes() { + return StrListCast(this.layoutDoc.dataVizAxes); + } + selectAxes = (axes: string[]) => (this.layoutDoc.dataVizAxes = new List<string>(axes)); + @computed get selectView() { const width = NumCast(this.rootDoc._width) * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 50, bottom: 50, left: 50 }; // prettier-ignore switch (this.dataVizView) { - case DataVizView.TABLE: return <TableBox pairs={this.pairs} />; - case DataVizView.LINECHART: return <LineChart ref={r => (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />; + case DataVizView.TABLE: return <TableBox pairs={this.pairs} axes={this.axes} selectAxes={this.selectAxes}/>; + case DataVizView.LINECHART: return <LineChart ref={r => (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; } } - - @computed get pairVals() { - return fetch('/csvData?uri=' + this.dataUrl?.url.href).then(res => res.json()); - } - @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField); } @@ -113,7 +116,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; + this.layoutDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; } render() { diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 313164691..2357b7c69 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; // import d3 import * as d3 from 'd3'; import { Doc, DocListCast } from '../../../../../fields/Doc'; +import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; import { Cast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; @@ -32,7 +33,8 @@ export interface LineChartData { } export interface LineChartProps { rootDoc: Doc; - pairs: { x: number; y: number }[]; + axes: string[]; + pairs: { [key: string]: any }[]; width: number; height: number; dataDoc: Doc; @@ -67,8 +69,13 @@ export class LineChart extends React.Component<LineChartProps> { Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); } componentDidMount = () => { + this._disposers.chartdata = reaction( + () => this.props.axes.slice(), + axes => axes.length > 1 && this.generateChartData(), + { fireImmediately: true } + ); this._disposers.chartData = reaction( - () => ({ dataSet: this._lineChartData?.dataSet, w: this.props.width, h: this.props.height }), + () => ({ dataSet: this._lineChartData?.dataSet, axes: this.props.axes.slice(), w: this.props.width, h: this.props.height }), vals => { if (vals.dataSet) { this._rangeVals = minMaxRange(vals.dataSet); @@ -89,16 +96,15 @@ export class LineChart extends React.Component<LineChartProps> { }, { fireImmediately: true } ); - this.generateChartData(); }; @action generateChartData() { this._lineChartData = { - xLabel: 'x', - yLabel: 'y', + xLabel: this.props.axes[0], + yLabel: this.props.axes[1], // TODO: nda - add actual support for multiple sets of data - dataSet: [this.props.pairs?.map(pair => ({ x: pair.x, y: pair.y }))], + dataSet: [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) }))], }; } @@ -129,12 +135,17 @@ export class LineChart extends React.Component<LineChartProps> { // loop through and remove any annotations that no longer exist } + @action restoreView = (data: Doc) => { - const coords = Cast(data.presDataViz, listSpec('number'), null); - if ((coords && this._currSelected?.x !== coords[0]) || this._currSelected?.y !== coords[1]) { + const coords = Cast(data.presDataVizSelection, 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; } + if (this._currSelected) { + this._currSelected = undefined; + return true; + } return false; }; @@ -143,7 +154,8 @@ export class LineChart extends React.Component<LineChartProps> { const anchor = Docs.Create.TextanchorDocument({ title: 'line doc selection' + this._currSelected?.x, }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc); + 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; return anchor; }; @@ -277,9 +289,10 @@ export class LineChart extends React.Component<LineChartProps> { }; render() { + const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return ( <div ref={this._lineChartRef} className="chart-container"> - <span>Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'}</span> + <span> {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Curr Selected: ${selectedPt}`}</span> </div> ); } diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 28114036f..adefe90cd 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,28 +1,59 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../../Utils'; interface TableBoxProps { - pairs: { x: number; y: number }[]; + pairs: { [key: string]: any }[]; + selectAxes: (axes: string[]) => void; + axes: string[]; } +@observer export class TableBox extends React.Component<TableBoxProps> { + @computed get columns() { + return this.props.pairs.length ? Array.from(Object.keys(this.props.pairs[0])) : []; + } render() { return ( <div className="table-container"> <table className="table"> <thead> <tr className="table-row"> - <th>x</th> - <th>y</th> + {this.columns.map(col => ( + <th + style={{ color: this.props.axes.slice().reverse().lastElement() === col ? 'green' : this.props.axes.lastElement() === col ? 'red' : undefined, fontWeight: this.props.axes.includes(col) ? 'bolder' : 'normal' }} + onPointerDown={e => + setupMoveUpEvents( + {}, + e, + returnFalse, + emptyFunction, + action(e => { + const newAxes = this.props.axes; + if (newAxes.includes(col)) { + newAxes.splice(newAxes.indexOf(col), 1); + } else if (newAxes.length >= 1) { + newAxes[1] = col; + } else { + newAxes[0] = col; + } + this.props.selectAxes(newAxes); + }) + ) + }> + {col} + </th> + ))} </tr> </thead> <tbody> {this.props.pairs?.map(p => { return ( <tr className="table-row"> - <td>{p.x}</td> - <td>{p.y}</td> + {this.columns.map(col => ( + <td>{p[col]}</td> + ))} </tr> ); })} diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0e22ea3b1..3589a9065 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -492,10 +492,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { changed = true; } } - if (!pinDataTypes && activeItem.presDataViz !== undefined) { - if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { - changed = true; - } + if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { + changed = true; } if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) { @@ -654,7 +652,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; - if (pinProps.pinData.dataviz) pinDoc.presDataViz = new List<number>(pinProps.pinData.dataviz); if (pinProps.pinData.pannable) { pinDoc.presPanX = NumCast(targetDoc._panX); pinDoc.presPanY = NumCast(targetDoc._panY); |