diff options
author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-09-18 17:40:01 -0400 |
---|---|---|
committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-09-18 17:40:01 -0400 |
commit | 013f25f01e729feee5db94900c61f4be4dd46869 (patch) | |
tree | 765dd5f2e06d6217ca79438e1098cefc8da627bf /src/client/views/nodes/DataVizBox/DataVizBox.tsx | |
parent | f5e765adff1e7b32250eb503c9724a4ac99117f3 (diff) | |
parent | 84aa8806a62e2e957e8281d7d492139e3d8225f2 (diff) |
Merge branch 'master' into sophie-report-manager
Diffstat (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx')
-rw-r--r-- | src/client/views/nodes/DataVizBox/DataVizBox.tsx | 210 |
1 files changed, 130 insertions, 80 deletions
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index d548ab9f1..8f32e2ba4 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,137 +1,182 @@ -import { action, computed, observable, ObservableMap, ObservableSet } from 'mobx'; +import { Toggle, ToggleType, Type } from 'browndash-components'; +import { action, computed, ObservableMap } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, Field, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; -import { Cast, CsvCast, NumCast, StrCast } from '../../../../fields/Types'; +import { Cast, CsvCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import { PinProps } from '../trails'; +import { Histogram } from './components/Histogram'; import { LineChart } from './components/LineChart'; +import { PieChart } from './components/PieChart'; import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; export enum DataVizView { TABLE = 'table', LINECHART = 'lineChart', + HISTOGRAM = 'histogram', + PIECHART = 'pieChart', } @observer export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(DataVizBox, fieldKey); + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(DataVizBox, fieldStr); } - // says we have an object and any string - // 2 ways of doing it - // @observable private pairs: { [key: string]: number | string | undefined }[] = []; - // @observable private pairs: { [key: string]: FieldResult }[] = []; - static pairSet = new ObservableMap<string, { [key: string]: string }[]>(); - @computed.struct get pairs() { - return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); - } - private _chartRenderer: LineChart | undefined; - // // another way would be store a schema that defines the type of data we are expecting from an imported doc - - // method1() { - // this.pairs[0].x = 3; - // } - // method() { - // // this.pairs[0].x = 3; - // // go through the pairs - // const x = this.pairs[0].x; - // if (typeof x == 'number') { - // let x1 = Number(x); - // // let x1 = NumCast(x); - // } - // } + // all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records + static dataset = new ObservableMap<string, { [key: string]: string }[]>(); + private _vizRenderer: LineChart | Histogram | PieChart | undefined; - // could use field result - // [key: string]: FieldResult; - // instead of numeric x,y in there, - - // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops + // all CSV records in the dataset (that aren't an empty row) + @computed.struct get records() { + var records = DataVizBox.dataset.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); + return records?.filter(record => Object.keys(record).some(key => record[key])); + } + // currently chosen visualization type: line, pie, histogram, table @computed get dataVizView(): DataVizView { - return StrCast(this.layoutDoc._dataVizView, 'table') as DataVizView; + return StrCast(this.layoutDoc._dataViz, 'table') as DataVizView; + } + + @computed get dataUrl() { + return Cast(this.dataDoc[this.fieldKey], CsvField); + } + @computed.struct get axes() { + return StrListCast(this.layoutDoc.dataViz_axes); } - @action + selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes)); + + @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors restoreView = (data: Doc) => { - const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); - const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._data_vizAxes = new List<string>(StrListCast(data.presDataVizAxes))); - const func = () => this._chartRenderer?.restoreView(data); + const changedView = this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz); + const changedAxes = this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List<string>(StrListCast(data.config_dataVizAxes))); + this.layoutDoc.dataViz_selectedRows = Field.Copy(data.dataViz_selectedRows); + this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors); + this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor; + this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(data.dataViz_pie_sliceColors); + Object.keys(this.layoutDoc).map(key => { + if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) { + this.layoutDoc['_' + key] = data[key]; + } + }); + const func = () => this._vizRenderer?.restoreView(data); if (changedView || changedAxes) { setTimeout(func, 100); return true; } return func() ?? false; }; - getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { const anchor = !pinProps ? this.rootDoc - : this._chartRenderer?.getAnchor(pinProps) ?? + : this._vizRenderer?.getAnchor(pinProps) ?? Docs.Create.ConfigDocument({ // when we clear selection -> we should have it so chartBox getAnchor returns undefined // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) /*put in some options*/ }); - - anchor.presDataVizView = this.dataVizView; - anchor.presDataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined; - + anchor.config_dataViz = this.dataVizView; + anchor.config_dataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined; + anchor.dataViz_selectedRows = Field.Copy(this.layoutDoc.dataViz_selectedRows); + anchor.dataViz_histogram_barColors = Field.Copy(this.layoutDoc.dataViz_histogram_barColors); + anchor.dataViz_histogram_defaultColor = this.layoutDoc.dataViz_histogram_defaultColor; + anchor.dataViz_pie_sliceColors = Field.Copy(this.layoutDoc.dataViz_pie_sliceColors); + Object.keys(this.layoutDoc).map(key => { + if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) { + anchor[key] = this.layoutDoc[key]; + } + }); this.addDocument(anchor); return anchor; }; - @computed.struct get axes() { - return StrListCast(this.layoutDoc.data_vizAxes); - } - selectAxes = (axes: string[]) => (this.layoutDoc.data_vizAxes = new List<string>(axes)); - - @computed get selectView() { - const width = this.props.PanelWidth() * 0.9; - const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; - const margin = { top: 10, right: 25, bottom: 50, left: 25 }; - if (!this.pairs) return 'no data'; - // prettier-ignore - switch (this.dataVizView) { - case DataVizView.TABLE: return <TableBox pairs={this.pairs} axes={this.axes} docView={this.props.DocumentView} 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 dataUrl() { - return Cast(this.dataDoc[this.fieldKey], CsvField); - } - componentDidMount() { this.props.setContentView?.(this); - this.fetchData(); + if (!DataVizBox.dataset.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) this.fetchData(); } - fetchData() { - if (DataVizBox.pairSet.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) return; - DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); + fetchData = () => { + DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests fetch('/csvData?uri=' + this.dataUrl?.url.href) // - .then(res => res.json().then(action(res => !res.errno && DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res)))); - } + .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res)))); + }; - // handle changing the view using a button - @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) { - e.preventDefault(); - e.stopPropagation(); - this.layoutDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; - } + // toggles for user to decide which chart type to view the data in + renderVizView = () => { + const width = this.props.PanelWidth() * 0.9; + const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; + const margin = { top: 10, right: 25, bottom: 75, left: 45 }; + if (this.records) { + switch (this.dataVizView) { + case DataVizView.TABLE: + return <TableBox layoutDoc={this.layoutDoc} records={this.records} axes={this.axes} height={height} width={width} margin={margin} rootDoc={this.rootDoc} docView={this.props.DocumentView} selectAxes={this.selectAxes} />; + case DataVizView.LINECHART: + return ( + <LineChart + layoutDoc={this.layoutDoc} + ref={r => (this._vizRenderer = r ?? undefined)} + height={height} + width={width} + fieldKey={this.fieldKey} + margin={margin} + rootDoc={this.rootDoc} + axes={this.axes} + records={this.records} + dataDoc={this.dataDoc} + /> + ); + case DataVizView.HISTOGRAM: + return ( + <Histogram + layoutDoc={this.layoutDoc} + ref={r => (this._vizRenderer = r ?? undefined)} + height={height} + width={width} + fieldKey={this.fieldKey} + margin={margin} + rootDoc={this.rootDoc} + axes={this.axes} + records={this.records} + dataDoc={this.dataDoc} + /> + ); + case DataVizView.PIECHART: + return ( + <PieChart + layoutDoc={this.layoutDoc} + ref={r => (this._vizRenderer = r ?? undefined)} + height={height} + width={width} + fieldKey={this.fieldKey} + margin={margin} + rootDoc={this.rootDoc} + axes={this.axes} + records={this.records} + dataDoc={this.dataDoc} + /> + ); + } + } + return 'no data/visualization'; + }; render() { - return !this.pairs?.length ? ( - <div>Loading...</div> + return !this.records?.length ? ( + // displays how to get data into the DataVizBox if its empty + <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div> ) : ( <div className="dataViz" + style={{ + pointerEvents: this.props.isContentActive() === true ? "all" : "none" + }} onWheel={e => e.stopPropagation()} ref={r => r?.addEventListener( @@ -143,8 +188,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { { passive: false } ) }> - <button onClick={e => this.changeViewHandler(e)}>{this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE}</button> - {this.selectView} + <div className={'datatype-button'}> + <Toggle text={'TABLE'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz == DataVizView.TABLE} /> + <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.LINECHART} /> + <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz == DataVizView.HISTOGRAM} /> + <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.PIECHART} /> + </div> + {this.renderVizView()} </div> ); } |