From fa98e9f411ed19a752a912e42b6ed9607d151159 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 25 Aug 2023 01:55:09 -0400
Subject: optimizations to speed up pie/table viz

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 177 +++++++++++------------
 1 file changed, 86 insertions(+), 91 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 284923092..f9f241234 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,3 +1,4 @@
+import { Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, ObservableMap } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -9,13 +10,11 @@ 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';
-import { Histogram } from './components/Histogram';
-import { PieChart } from './components/PieChart';
-import { Toggle, ToggleType, Type } from 'browndash-components';
-import { Utils } from '../../../../Utils';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -30,19 +29,29 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return FieldView.LayoutString(DataVizBox, fieldStr);
     }
 
-    // all data
-    static pairSet = new ObservableMap<string, { [key: string]: string }[]>();
-    @computed.struct get pairs() {
-        return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
-    }
+    // 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;
 
-    private _chartRenderer: LineChart | Histogram | PieChart | undefined;
+    // all CSV records in the dataset
+    @computed.struct get records() {
+        return DataVizBox.dataset.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
+    }
 
-    // current displayed chart type
+    // currently chosen visualization type: line, pie, histogram, table
     @computed get dataVizView(): 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);
+    }
+
+    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.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
@@ -56,7 +65,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this.layoutDoc['_' + key] = data[key];
             }
         });
-        const func = () => this._chartRenderer?.restoreView(data);
+        const func = () => this._vizRenderer?.restoreView(data);
         if (changedView || changedAxes) {
             setTimeout(func, 100);
             return true;
@@ -66,7 +75,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     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)
@@ -87,92 +96,78 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return anchor;
     };
 
-    @computed.struct get axes() {
-        return StrListCast(this.layoutDoc.dataViz_axes);
+    componentDidMount() {
+        this.props.setContentView?.(this);
+        if (!DataVizBox.dataset.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) this.fetchData();
     }
-    selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));
+
+    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.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res))));
+    };
 
     // toggles for user to decide which chart type to view the data in
-    @computed get selectView() {
+    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.pairs) return 'no data';
-        switch (this.dataVizView) {
-            case DataVizView.TABLE:
-                return <TableBox layoutDoc={this.layoutDoc} pairs={this.pairs} 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._chartRenderer = r ?? undefined)}
-                        height={height}
-                        width={width}
-                        fieldKey={this.fieldKey}
-                        margin={margin}
-                        rootDoc={this.rootDoc}
-                        axes={this.axes}
-                        pairs={this.pairs}
-                        dataDoc={this.dataDoc}
-                    />
-                );
-            case DataVizView.HISTOGRAM:
-                return (
-                    <Histogram
-                        layoutDoc={this.layoutDoc}
-                        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}
-                    />
-                );
-            case DataVizView.PIECHART:
-                return (
-                    <PieChart
-                        layoutDoc={this.layoutDoc}
-                        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}
-                    />
-                );
+        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}
+                        />
+                    );
+            }
         }
-    }
-
-    @computed get dataUrl() {
-        return Cast(this.dataDoc[this.fieldKey], CsvField);
-    }
-
-    componentDidMount() {
-        this.props.setContentView?.(this);
-        this.fetchData();
-        if (!this.layoutDoc._dataViz) this.layoutDoc._dataViz = this.dataVizView;
-    }
-
-    fetchData() {
-        if (DataVizBox.pairSet.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) return;
-        DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []);
-        fetch('/csvData?uri=' + this.dataUrl?.url.href) //
-            .then(res => res.json().then(action(res => {
-                if (!res.errno) {
-                    DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res);
-                    if (!this.dataDoc.dataViz_rowGuids && this.pairs)  this.dataDoc.dataViz_rowGuids = new List<string>(this.pairs.map(row => Utils.GenerateGuid()));
-                }
-            })));
-    }
+        return 'no data/visualization';
+    };
 
     render() {
-        return !this.pairs?.length ? (
+        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>
         ) : (
@@ -195,7 +190,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     <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.selectView}
+                {this.renderVizView()}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2

