From eeb7d43e9eca66c8e3418e89492848589ee20aa3 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 24 Aug 2023 15:48:26 -0400
Subject: speedups for dataviz

---
 .../views/nodes/DataVizBox/components/TableBox.tsx    | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 70483ac6f..e1c5aa125 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -33,9 +33,11 @@ interface TableBoxProps {
 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.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)]));
+        if (this.incomingLinks.length <= 0) return this.props.pairs;
+        return this.props.pairs?.filter(pair => StrListCast(this.incomingLinks[0]?.dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)]));
+    }
+    @computed get rowGuids() {
+        return  StrListCast(this.props.layoutDoc.dataViz_rowGuids)
     }
 
     @computed get incomingLinks() {
@@ -47,9 +49,6 @@ export class TableBox extends React.Component<TableBoxProps> {
     }
 
     @computed get columns() {
-        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) : [];
     }
 
@@ -59,9 +58,9 @@ export class TableBox extends React.Component<TableBoxProps> {
         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
+            const newsel = selected.filter(guid => incomingSelected.includes(guid));// filters through selected to remove guids that were removed in the incoming data
+            selected.length = 0;
+            selected.push(...newsel);
         }
     }
 
@@ -145,7 +144,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                         <tbody>
                             {this._tableData?.map((p, i) => {
                                 var containsData = false;
-                                var guid = StrListCast(this.props.layoutDoc.dataViz_rowGuids)![this.props.pairs.indexOf(p)];
+                                var guid = this.rowGuids[this.props.pairs.indexOf(p)];
                                 this.columns.map(col => {
                                     if (p[col] != '' && p[col] != null && p[col] != undefined) containsData = true;
                                 });
-- 
cgit v1.2.3-70-g09d2


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/documents/Documents.ts                  |   2 +-
 .../CollectionFreeFormLinksView.tsx                |   5 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 177 +++++++--------
 .../nodes/DataVizBox/components/Histogram.tsx      | 164 ++++++--------
 .../nodes/DataVizBox/components/LineChart.tsx      |  30 +--
 .../views/nodes/DataVizBox/components/PieChart.tsx | 169 +++++++-------
 .../views/nodes/DataVizBox/components/TableBox.tsx | 247 ++++++++++-----------
 7 files changed, 369 insertions(+), 425 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e7d3e7efc..4933f0a7c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -680,7 +680,7 @@ export namespace Docs {
                 DocumentType.DATAVIZ,
                 {
                     layout: { view: DataVizBox, dataField: defaultDataKey },
-                    options: { dataViz_title: '', _layout_fitWidth: true, nativeDimModifiable: true },
+                    options: { dataViz_title: '', dataViz: 'table', _layout_fitWidth: true, nativeDimModifiable: true },
                 },
             ],
             [
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 7c869af24..420e6a318 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -12,10 +12,7 @@ export class CollectionFreeFormLinksView extends React.Component {
     @computed get uniqueConnections() {
         return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews))
             .filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath)))
-            .map(c => {
-            console.log("got a connectoin", c)
-            return <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />;
-    });
+            .map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
     }
 
     render() {
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>
         );
     }
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index b3bdccbbb..48cbe5c5f 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -1,26 +1,26 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
+import * as d3 from 'd3';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 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 './Chart.scss';
-import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
 import { FaFillDrip } from 'react-icons/fa';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Doc, StrListCast } from '../../../../../fields/Doc';
+import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { Docs } from '../../../../documents/Documents';
+import { LinkManager } from '../../../../util/LinkManager';
+import { undoable } from '../../../../util/UndoManager';
+import { PinProps, PresBox } from '../../trails';
 import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
-import { undoBatch, undoable } from '../../../../util/UndoManager';
+import './Chart.scss';
 
 export interface HistogramProps {
     rootDoc: Doc;
     layoutDoc: Doc;
     axes: string[];
-    pairs: { [key: string]: any }[];
+    records: { [key: string]: any }[];
     width: number;
     height: number;
     dataDoc: Doc;
@@ -48,41 +48,41 @@ 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.dataViz_rowGuids);
+        var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds);
         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])) {
+            if (/\d/.test(this.props.records[0][ax0])) {
                 this.numericalXData = true;
             }
-            return this.props.pairs
-                ?.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]] }));
+            return this.props.records
+                ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
+                .map(record => ({ [ax0]: record[this.props.axes[0]] }));
         }
         var ax0 = this.props.axes[0];
         var ax1 = this.props.axes[1];
-        if (/\d/.test(this.props.pairs[0][ax0])) {
+        if (/\d/.test(this.props.records[0][ax0])) {
             this.numericalXData = true;
         }
-        if (/\d/.test(this.props.pairs[0][ax1])) {
+        if (/\d/.test(this.props.records[0][ax1])) {
             this.numericalYData = true;
         }
-        return this.props.pairs
-            ?.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]] }));
+        return this.props.records
+            ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
+            .map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
     }
 
     @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) {
+        if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.records[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
-            .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link
+            .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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
     }
 
@@ -100,11 +100,7 @@ export class Histogram extends React.Component<HistogramProps> {
     componentDidMount = () => {
         this._disposers.chartData = reaction(
             () => ({ dataSet: this._histogramData, w: this.width, h: this.height }),
-            ({ dataSet, w, h }) => {
-                if (dataSet!.length > 0) {
-                    this.drawChart(dataSet, w, h);
-                }
-            },
+            ({ dataSet, w, h }) => dataSet!.length > 0 && this.drawChart(dataSet, w, h),
             { fireImmediately: true }
         );
     };
@@ -114,7 +110,6 @@ export class Histogram extends React.Component<HistogramProps> {
     // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
     getAnchor = (pinProps?: PinProps) => {
         const anchor = Docs.Create.ConfigDocument({
-            //
             title: 'histogram doc selection' + this._currSelected,
         });
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
@@ -131,21 +126,15 @@ 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 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!];
-        });
-        return data;
+        var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+        const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
+        return !field
+            ? []
+            : validData.map((d: { [x: string]: any }) =>
+                  !this.numericalXData //
+                      ? d[field]
+                      : +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')
+              );
     };
 
     // outlines the bar selected / hovered over
@@ -185,24 +174,18 @@ export class Histogram extends React.Component<HistogramProps> {
         d3.select(this._histogramRef.current).select('svg').remove();
         d3.select(this._histogramRef.current).select('.tooltip').remove();
 
-        var data = this.data(dataSet);
-        var xAxisTitle = Object.keys(dataSet[0])[0];
-        var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
-        let uniqueArr: unknown[] = [...new Set(data)];
+        const data = this.data(dataSet);
+        const xAxisTitle = Object.keys(dataSet[0])[0];
+        const yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
+        const 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;
+        const startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
+        const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
 
         // converts data into Objects
-        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;
-        });
+        var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
         if (!this.numericalXData) {
             var histStringDataSet: { [x: string]: unknown }[] = [];
             if (this.numericalYData) {
@@ -321,7 +304,7 @@ export class Histogram extends React.Component<HistogramProps> {
         // 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);
+            this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
             updateHighlights();
         });
         const mouseOut = action((e: any) => {
@@ -360,10 +343,10 @@ export class Histogram extends React.Component<HistogramProps> {
                 'transform',
                 this.numericalYData
                     ? function (d) {
-                          var eachData = histDataSet.filter((data: { [x: string]: number }) => {
+                          const 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;
+                          const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
                           return 'translate(' + x(d.x0!) + ',' + y(length) + ')';
                       }
                     : function (d) {
@@ -374,10 +357,10 @@ export class Histogram extends React.Component<HistogramProps> {
                 'height',
                 this.numericalYData
                     ? function (d) {
-                          var eachData = histDataSet.filter((data: { [x: string]: number }) => {
+                          const 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;
+                          const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
                           return height - y(length);
                       }
                     : function (d) {
@@ -389,7 +372,7 @@ export class Histogram extends React.Component<HistogramProps> {
                 'class',
                 selected
                     ? function (d) {
-                          return selected && selected[0] == d[0] ? 'histogram-bar hover' : 'histogram-bar';
+                          return selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar';
                       }
                     : function (d) {
                           return 'histogram-bar';
@@ -397,11 +380,11 @@ export class Histogram extends React.Component<HistogramProps> {
             )
             .attr('fill', d => {
                 var barColor;
-                var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
-                barColors.map(each => {
+                const barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+                barColors.forEach(each => {
                     if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
                     else {
-                        var range = StrCast(each[0]).split(' to ');
+                        const range = StrCast(each[0]).split(' to ');
                         if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
                     }
                 });
@@ -411,23 +394,19 @@ export class Histogram extends React.Component<HistogramProps> {
 
     @action changeSelectedColor = (color: string) => {
         this.curBarSelected.attr('fill', color);
-        var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+        const 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);
-        });
+        barColors.forEach(each => 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 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);
-        });
+        barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
     };
 
     render() {
@@ -439,25 +418,22 @@ export class Histogram extends React.Component<HistogramProps> {
         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;
+        var selected = 'none';
         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);
-            selected += ' }';
-        } else selected = 'none';
+            Object.keys(this._currSelected).forEach(key =>
+                key //
+                    ? (selected += key + ': ' + this._currSelected[key] + ', ')
+                    : ''
+            );
+            selected = selected.substring(0, selected.length - 2) + ' }';
+        }
         var selectedBarColor;
         var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
-        barColors.map(each => {
-            if (each[0] == curSelectedBarName!) selectedBarColor = each[1];
-        });
+        barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
 
-        this.componentDidMount();
-
-        if (this._histogramData.length > 0 || (!this.incomingLinks || this.incomingLinks.length==0)) {
+        if (this._histogramData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
             return this.props.axes.length >= 1 ? (
                 <div className="chart-container">
                     <div className="graph-title">
@@ -514,10 +490,8 @@ export class Histogram extends React.Component<HistogramProps> {
             ) : (
                 <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>
-            );
+        }
+        // when it is a brushed table and the incoming table doesn't have any rows selected
+        return <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 46cf27705..655c6de20 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -28,7 +28,7 @@ export interface LineChartProps {
     rootDoc: Doc;
     layoutDoc: Doc;
     axes: string[];
-    pairs: { [key: string]: any }[];
+    records: { [key: string]: any }[];
     width: number;
     height: number;
     dataDoc: Doc;
@@ -50,11 +50,11 @@ 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);
+        var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds);
         if (this.props.axes.length <= 1) return [];
-        return this.props.pairs
-            ?.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]]) }))
+        return this.props.records
+            ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
+            .map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[this.props.axes[1]]) }))
             .sort((a, b) => (a.x < b.x ? -1 : 1));
     }
     @computed get graphTitle() {
@@ -63,7 +63,7 @@ export class LineChart extends React.Component<LineChartProps> {
     @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;
+                return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz;
             }) // 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
     }
@@ -73,7 +73,7 @@ export class LineChart extends React.Component<LineChartProps> {
         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)
-            .map(dvb => dvb.pairs?.filter(pair => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor
+            .map(dvb => dvb.records?.filter(record => record['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor
             .lastElement();
     }
     @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -91,7 +91,7 @@ export class LineChart extends React.Component<LineChartProps> {
                     // redraw annotations when the chart data has changed, or the local or inherited selection has changed
                     this.clearAnnotations();
                     this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true);
-                    this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
+                    this.incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
                 }
             },
             { fireImmediately: true }
@@ -117,7 +117,7 @@ export class LineChart extends React.Component<LineChartProps> {
                 // redraw annotations when the chart data has changed, or the local or inherited selection has changed
                 this.clearAnnotations();
                 selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true);
-                incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
+                incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
             },
             { fireImmediately: true }
         );
@@ -193,7 +193,7 @@ export class LineChart extends React.Component<LineChartProps> {
     @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) {
+        if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
             return ax0 + ' Line Chart';
         } else return ax1 + ' by ' + ax0 + ' Line Chart';
     }
@@ -217,7 +217,7 @@ export class LineChart extends React.Component<LineChartProps> {
         // TODO: nda - get rid of svg element in the list?
         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.records.forEach(record => record[this.props.axes[0]] === x && record[this.props.axes[1]] === y && (record.selected = true));
     }
 
     drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -358,10 +358,10 @@ export class LineChart extends React.Component<LineChartProps> {
         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"> 
+        if (this._lineChartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
+            return this.props.axes.length >= 2 && /\d/.test(this.props.records[0][this.props.axes[0]]) && /\d/.test(this.props.records[0][this.props.axes[1]]) ? (
+                <div className="chart-container">
+                    <div className="graph-title">
                         <EditableText
                             val={StrCast(this.props.layoutDoc[titleAccessor])}
                             setVal={undoable(
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index e730d54f0..79d7b3f23 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -1,25 +1,24 @@
+import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
+import * as d3 from 'd3';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 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 './Chart.scss';
-import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
 import { FaFillDrip } from 'react-icons/fa';
+import { Doc, NumListCast, StrListCast } from '../../../../../fields/Doc';
+import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { Docs } from '../../../../documents/Documents';
+import { LinkManager } from '../../../../util/LinkManager';
 import { undoable } from '../../../../util/UndoManager';
-import { Utils } from '../../../../../Utils';
+import { PinProps, PresBox } from '../../trails';
+import './Chart.scss';
 
 export interface PieChartProps {
     rootDoc: Doc;
     layoutDoc: Doc;
     axes: string[];
-    pairs: { [key: string]: any }[];
+    records: { [key: string]: any }[];
     width: number;
     height: number;
     dataDoc: Doc;
@@ -37,48 +36,51 @@ 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;
-    private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios
-    @observable _currSelected: any | undefined = undefined; // Object of selected slice
     private curSliceSelected: any = undefined; // d3 data of selected slice
     private selectedData: any = undefined; // Selection of selected slice
     private hoverOverData: any = undefined; // Selection of slice being hovered over
+    @observable _currSelected: any | undefined = undefined; // Object of selected slice
 
-    @computed get rowGuids() {
-        return  StrListCast(this.props.layoutDoc.dataViz_rowGuids)
+    @computed get _tableDataIds() {
+        return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows);
+    }
+    // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
+    @computed get _tableData() {
+        return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+    }
+
+    // organized by specified number percentages/ratios if one column is selected and it contains numbers
+    // otherwise, assume data is organized by categories
+    @computed get byCategory() {
+        if (this.props.axes.length === 1) {
+            return !/\d/.test(this.props.records[0][this.props.axes[0]]);
+        }
+        return true;
     }
     // filters all data to just display selected data if brushed (created from an incoming link)
     @computed get _piechartData() {
         if (this.props.axes.length < 1) return [];
+
+        const ax0 = this.props.axes[0];
         if (this.props.axes.length < 2) {
-            var ax0 = this.props.axes[0];
-            if (/\d/.test(this.props.pairs[0][ax0])) {
-                this.byCategory = false;
-            }
-            return this.props.pairs
-                ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[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;
+            return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] }));
         }
-        return this.props.pairs
-            ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)])))
-            .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] }));
+        const ax1 = this.props.axes[1];
+        return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
     }
 
     @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) {
+        if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
             return ax0 + ' Pie Chart';
-        } else return ax1 + ' by ' + ax0 + ' Pie Chart';
+        }
+        return ax1 + ' by ' + ax0 + ' Pie Chart';
     }
 
     @computed get incomingLinks() {
         return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
-            .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link
+            .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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
     }
 
@@ -119,21 +121,15 @@ 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 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!];
-        });
-        return data;
+        const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+        const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
+        return !field
+            ? undefined
+            : validData.map((d: { [x: string]: any }) =>
+                  this.byCategory
+                      ? d[field] //
+                      : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')
+              );
     };
 
     // outlines the slice selected / hovered over
@@ -195,13 +191,7 @@ export class PieChart extends React.Component<PieChartProps> {
 
         // converts data into Objects
         var data = this.data(dataSet);
-        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;
-        });
+        var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
         if (this.byCategory) {
             let uniqueCategories = [...new Set(data)];
             var pieStringDataSet: { frequency: number }[] = [];
@@ -235,7 +225,7 @@ export class PieChart extends React.Component<PieChartProps> {
         // 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);
+            this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
             updateHighlights();
         });
         const mouseOut = action((e: any) => {
@@ -255,16 +245,19 @@ export class PieChart extends React.Component<PieChartProps> {
         // drawing the slices
         var selected = this.selectedData;
         var arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
+        const sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::'));
+        const possibleDataPointVals = pieDataSet.map((each: { [x: string]: any | { valueOf(): number } }) => {
+            try {
+                each[percentField] = Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+            } catch (error) {
+                //return each[percentField] == d.data;
+            }
+            return each;
+        });
         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);
-                    } catch (error) {
-                        return each[percentField] == d.data;
-                    }
-                });
                 var dataPoint;
+                const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
                 if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
                 else {
                     dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
@@ -272,11 +265,8 @@ export class PieChart extends React.Component<PieChartProps> {
                 }
                 var sliceColor;
                 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];
-                    });
+                    var accessByName = dataPoint[this.props.axes[0]];
+                    sliceColors.forEach(each => each[0] == StrCast(accessByName) && (sliceColor = each[1]));
                 }
                 return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length];
             })
@@ -298,29 +288,25 @@ 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 * 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);
-                    } catch (error) {
-                        return each[percentField] == d.data;
+        arcs.size() < 100 &&
+            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 dataPoint;
+                    const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
+                    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])! : '';
                 });
-                var dataPoint;
-                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])! : '';
-            });
     };
 
     @action changeSelectedColor = (color: string) => {
@@ -335,7 +321,6 @@ export class PieChart extends React.Component<PieChartProps> {
     };
 
     render() {
-        this.componentDidMount();
         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];
@@ -358,7 +343,7 @@ export class PieChart extends React.Component<PieChartProps> {
             if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
         });
 
-        if (this._piechartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){
+        if (this._piechartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
             return this.props.axes.length >= 1 ? (
                 <div className="chart-container">
                     <div className="graph-title">
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index e1c5aa125..e90b541ae 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,21 +1,21 @@
-import { action, computed } from 'mobx';
+import { action, computed, IReactionDisposer, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, Field, StrListCast } from '../../../../../fields/Doc';
+import { Doc, Field, NumListCast, StrListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
+import { listSpec } from '../../../../../fields/Schema';
+import { Cast, DocCast } from '../../../../../fields/Types';
 import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
 import { DragManager } from '../../../../util/DragManager';
+import { LinkManager } from '../../../../util/LinkManager';
 import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
-import { LinkManager } from '../../../../util/LinkManager';
-import { Cast, DocCast } from '../../../../../fields/Types';
 import './Chart.scss';
-import { listSpec } from '../../../../../fields/Schema';
 
 interface TableBoxProps {
     rootDoc: Doc;
     layoutDoc: Doc;
-    pairs: { [key: string]: any }[];
+    records: { [key: string]: any }[];
     selectAxes: (axes: string[]) => void;
     axes: string[];
     width: number;
@@ -31,20 +31,26 @@ 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;
-        return this.props.pairs?.filter(pair => StrListCast(this.incomingLinks[0]?.dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)]));
+    _inputChangedDisposer?: IReactionDisposer;
+    componentDidMount() {
+        // if the tableData changes (ie., when records are selected by the parent (input) visulization),
+        // then we need to remove any selected rows that are no longer part of the visualized dataset.
+        this._inputChangedDisposer = reaction(() => this._tableData.slice(), this.filterSelectedRowsDown, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        this._inputChangedDisposer?.();
+    }
+    @computed get _tableDataIds() {
+        return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows);
     }
-    @computed get rowGuids() {
-        return  StrListCast(this.props.layoutDoc.dataViz_rowGuids)
+    // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
+    @computed get _tableData() {
+        return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
     }
 
     @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
+            .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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
     }
 
@@ -53,128 +59,115 @@ export class TableBox extends React.Component<TableBoxProps> {
     }
 
     // updates the 'selected' field to no longer include rows that aren't in the table
-    filterSelectedRowsDown() {
-        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) {
-            const newsel = selected.filter(guid => incomingSelected.includes(guid));// filters through selected to remove guids that were removed in the incoming data
-            selected.length = 0;
-            selected.push(...newsel);
-        }
-    }
+    filterSelectedRowsDown = () => {
+        const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows);
+        this.props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
+    };
 
     render() {
-        this.filterSelectedRowsDown();
         if (this._tableData.length > 0) {
             return (
-                <div className="table-container" style={{ height: this.props.height }}>
+                <div
+                    className="table-container"
+                    style={{ height: this.props.height }}
+                    ref={r =>
+                        r?.addEventListener(
+                            'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
+                            (e: WheelEvent) => {
+                                if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
+                                e.stopPropagation();
+                            },
+                            { passive: false }
+                        )
+                    }>
                     <table className="table">
                         <thead>
                             <tr className="table-row">
-                                {this.columns
-                                    .filter(col => !col.startsWith('select'))
-                                    .map(col => {
-                                        const header = React.createRef<HTMLElement>();
-                                        return (
-                                            <th
-                                                key={this.columns.indexOf(col)}
-                                                ref={header as any}
-                                                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',
-                                                }}
-                                                onPointerDown={e => {
-                                                    const downX = e.clientX;
-                                                    const downY = e.clientY;
-                                                    setupMoveUpEvents(
-                                                        {},
-                                                        e,
-                                                        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._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);
-                                                                embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
-                                                                embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
-                                                                return embedding;
-                                                            };
-                                                            if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
-                                                                DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
-                                                                    dragComplete: e => {
-                                                                        if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                                                                            e.linkDocument.link_displayLine = true;
-                                                                            e.linkDocument.link_matchEmbeddings = true;
-                                                                            // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
-                                                                            // e.annoDragData.linkSourceDoc.followLinkZoom = false;
-                                                                        }
-                                                                    },
-                                                                });
-                                                                return true;
-                                                            }
-                                                            return false;
-                                                        },
-                                                        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.push(col);
-                                                            }
-                                                            this.props.selectAxes(newAxes);
-                                                        })
-                                                    );
-                                                }}>
-                                                {col}
-                                            </th>
-                                        );
-                                    })}
+                                {this.columns.map(col => (
+                                    <th
+                                        key={this.columns.indexOf(col)}
+                                        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',
+                                        }}
+                                        onPointerDown={e => {
+                                            const downX = e.clientX;
+                                            const downY = e.clientY;
+                                            setupMoveUpEvents(
+                                                {},
+                                                e,
+                                                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._dataViz = DataVizView.TABLE;
+                                                        embedding._dataViz_axes = new List<string>([col, col]);
+                                                        embedding._dataViz_parentViz = this.props.docView?.()!.rootDoc!;
+                                                        embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
+                                                        embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
+                                                        embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
+                                                        embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
+                                                        return embedding;
+                                                    };
+                                                    if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+                                                        DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+                                                            dragComplete: e => {
+                                                                if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+                                                                    e.linkDocument.link_displayLine = true;
+                                                                    e.linkDocument.link_matchEmbeddings = true;
+                                                                    // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+                                                                    // e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                                                                }
+                                                            },
+                                                        });
+                                                        return true;
+                                                    }
+                                                    return false;
+                                                },
+                                                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.push(col);
+                                                    this.props.selectAxes(newAxes);
+                                                })
+                                            );
+                                        }}>
+                                        {col}
+                                    </th>
+                                ))}
                             </tr>
                         </thead>
                         <tbody>
-                            {this._tableData?.map((p, i) => {
-                                var containsData = false;
-                                var guid = this.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 => {
-                                                // selecting a row
-                                                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.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;
-                                                return (
-                                                    <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
-                                                        {p[col]}
-                                                    </td>
-                                                );
-                                            })}
-                                        </tr>
-                                    );
-                                }
-                            })}
+                            {this._tableDataIds
+                                ?.map(rowId => ({ record: this.props.records[rowId], rowId }))
+                                .map(({ record, rowId }) => (
+                                    <tr
+                                        key={rowId}
+                                        className="table-row"
+                                        onClick={action(e => {
+                                            // selecting a row
+                                            const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+                                            if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1);
+                                            else selected?.push(rowId);
+                                            e.stopPropagation();
+                                        })}
+                                        style={{ background: NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '', width: '110%' }}>
+                                        {this.columns.map(col => {
+                                            // each cell
+                                            const 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;
+                                            return (
+                                                <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
+                                                    {record[col]}
+                                                </td>
+                                            );
+                                        })}
+                                    </tr>
+                                ))}
                         </tbody>
                     </table>
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 6f2fc57f29a90b02dad0bc98c20ec69ef90c39ef Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 25 Aug 2023 11:58:14 -0400
Subject: simplification of vizBoxes to use parentViz instead of going through
 links

---
 .../nodes/DataVizBox/components/Histogram.tsx      | 29 +++++++++--------
 .../nodes/DataVizBox/components/LineChart.tsx      | 36 ++++++++++++----------
 .../views/nodes/DataVizBox/components/PieChart.tsx | 15 ++++-----
 .../views/nodes/DataVizBox/components/TableBox.tsx | 15 ++++-----
 4 files changed, 51 insertions(+), 44 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 48cbe5c5f..50facf03e 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
-import { Doc, StrListCast } from '../../../../../fields/Doc';
+import { Doc, NumListCast, StrListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
@@ -46,18 +46,22 @@ export class Histogram extends React.Component<HistogramProps> {
     private selectedData: any = undefined; // Selection of selected bar
     private hoverOverData: any = undefined; // Selection of bar being hovered over
 
+    @computed get _tableDataIds() {
+        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+    }
+    // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
+    @computed get _tableData() {
+        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+    }
     // filters all data to just display selected data if brushed (created from an incoming link)
     @computed get _histogramData() {
-        var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds);
         if (this.props.axes.length < 1) return [];
         if (this.props.axes.length < 2) {
             var ax0 = this.props.axes[0];
             if (/\d/.test(this.props.records[0][ax0])) {
                 this.numericalXData = true;
             }
-            return this.props.records
-                ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
-                .map(record => ({ [ax0]: record[this.props.axes[0]] }));
+            return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] }));
         }
         var ax0 = this.props.axes[0];
         var ax1 = this.props.axes[1];
@@ -67,9 +71,7 @@ export class Histogram extends React.Component<HistogramProps> {
         if (/\d/.test(this.props.records[0][ax1])) {
             this.numericalYData = true;
         }
-        return this.props.records
-            ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
-            .map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
+        return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
     }
 
     @computed get defaultGraphTitle() {
@@ -80,10 +82,11 @@ export class Histogram extends React.Component<HistogramProps> {
         } else return ax0 + ' by ' + ax1 + ' Histogram';
     }
 
-    @computed get incomingLinks() {
-        return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
-            .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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 parentViz() {
+        return DocCast(this.props.rootDoc.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+        //     .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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 rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -433,7 +436,7 @@ export class Histogram extends React.Component<HistogramProps> {
         var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
         barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
 
-        if (this._histogramData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
+        if (this._histogramData.length > 0 || !this.parentViz) {
             return this.props.axes.length >= 1 ? (
                 <div className="chart-container">
                     <div className="graph-title">
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 655c6de20..3ef2bd8b0 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as d3 from 'd3';
-import { Doc, DocListCast, StrListCast } from '../../../../../fields/Doc';
+import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
 import { Id } from '../../../../../fields/FieldSymbols';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
@@ -49,32 +49,34 @@ export class LineChart extends React.Component<LineChartProps> {
     @observable _currSelected: SelectedDataPoint | undefined = undefined;
     // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
 
+    @computed get _tableDataIds() {
+        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+    }
+    // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
+    @computed get _tableData() {
+        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+    }
     @computed get _lineChartData() {
         var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds);
         if (this.props.axes.length <= 1) return [];
-        return this.props.records
-            ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
-            .map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[this.props.axes[1]]) }))
-            .sort((a, b) => (a.x < b.x ? -1 : 1));
+        return this._tableData.map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[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 => {
-                return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz;
-            }) // 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 parentViz() {
+        return DocCast(this.props.rootDoc.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+        //     .filter(link => {
+        //         return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz;
+        //     }) // 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)
-            .map(dvb => dvb.records?.filter(record => record['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor
-            .lastElement();
+        const incomingVizBox = DocumentManager.Instance.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
+        return incomingVizBox.records?.filter(record => record['select' + incomingVizBox.rootDoc[Id]]); // get all the datapoints they have selected field set by incoming anchor
     }
     @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
         return minMaxRange([this._lineChartData]);
@@ -358,7 +360,7 @@ export class LineChart extends React.Component<LineChartProps> {
         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) {
+        if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
             return this.props.axes.length >= 2 && /\d/.test(this.props.records[0][this.props.axes[0]]) && /\d/.test(this.props.records[0][this.props.axes[1]]) ? (
                 <div className="chart-container">
                     <div className="graph-title">
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 79d7b3f23..a8aa51897 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -42,11 +42,11 @@ export class PieChart extends React.Component<PieChartProps> {
     @observable _currSelected: any | undefined = undefined; // Object of selected slice
 
     @computed get _tableDataIds() {
-        return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows);
+        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
     // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
     @computed get _tableData() {
-        return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
     }
 
     // organized by specified number percentages/ratios if one column is selected and it contains numbers
@@ -78,10 +78,11 @@ export class PieChart extends React.Component<PieChartProps> {
         return ax1 + ' by ' + ax0 + ' Pie Chart';
     }
 
-    @computed get incomingLinks() {
-        return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
-            .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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 parentViz() {
+        return DocCast(this.props.rootDoc.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+        //     .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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
     }
 
     componentWillUnmount() {
@@ -343,7 +344,7 @@ export class PieChart extends React.Component<PieChartProps> {
             if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
         });
 
-        if (this._piechartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
+        if (this._piechartData.length > 0 || !this.parentViz) {
             return this.props.axes.length >= 1 ? (
                 <div className="chart-container">
                     <div className="graph-title">
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index e90b541ae..7bca08c15 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -41,17 +41,18 @@ export class TableBox extends React.Component<TableBoxProps> {
         this._inputChangedDisposer?.();
     }
     @computed get _tableDataIds() {
-        return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows);
+        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
     // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
     @computed get _tableData() {
-        return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
     }
 
-    @computed get incomingLinks() {
-        return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
-            .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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 parentViz() {
+        return DocCast(this.props.rootDoc.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+        //     .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // 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() {
@@ -105,7 +106,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                                                         const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
                                                         embedding._dataViz = DataVizView.TABLE;
                                                         embedding._dataViz_axes = new List<string>([col, col]);
-                                                        embedding._dataViz_parentViz = this.props.docView?.()!.rootDoc!;
+                                                        embedding._dataViz_parentViz = this.props.rootDoc;
                                                         embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
                                                         embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
                                                         embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
-- 
cgit v1.2.3-70-g09d2


From 25d1448fa5d840e2e9475f088748f62d6aed0248 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 25 Aug 2023 12:25:45 -0400
Subject: added cmdKey clicking on table rows to 'highlight' them by setting
 dataViz_highlitedRows, and changed lineChart's to read dataViz_highlitedRows
 to highlight data points

---
 .../views/nodes/DataVizBox/components/LineChart.tsx | 13 +++++++------
 .../views/nodes/DataVizBox/components/TableBox.tsx  | 21 ++++++++++++++++-----
 2 files changed, 23 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 3ef2bd8b0..6c9922c0a 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -72,11 +72,12 @@ export class LineChart extends React.Component<LineChartProps> {
         //     }) // 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() {
+    @computed get incomingHighlited() {
         // return selected x and y axes
         // otherwise, use the selection of whatever is linked to us
         const incomingVizBox = DocumentManager.Instance.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
-        return incomingVizBox.records?.filter(record => record['select' + incomingVizBox.rootDoc[Id]]); // get all the datapoints they have selected field set by incoming anchor
+        const highlitedRowIds = NumListCast(incomingVizBox.rootDoc.dataViz_highlitedRows);
+        return this._tableData.filter((record, i) => highlitedRowIds.includes(this._tableDataIds[i])); // get all the datapoints they have selected field set by incoming anchor
     }
     @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
         return minMaxRange([this._lineChartData]);
@@ -93,7 +94,7 @@ export class LineChart extends React.Component<LineChartProps> {
                     // redraw annotations when the chart data has changed, or the local or inherited selection has changed
                     this.clearAnnotations();
                     this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true);
-                    this.incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
+                    this.incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
                 }
             },
             { fireImmediately: true }
@@ -113,13 +114,13 @@ export class LineChart extends React.Component<LineChartProps> {
         this._disposers.highlights = reaction(
             () => ({
                 selected: this._currSelected,
-                incomingSelected: this.incomingSelected,
+                incomingHighlited: this.incomingHighlited,
             }),
-            ({ selected, incomingSelected }) => {
+            ({ selected, incomingHighlited }) => {
                 // redraw annotations when the chart data has changed, or the local or inherited selection has changed
                 this.clearAnnotations();
                 selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true);
-                incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
+                incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
             },
             { fireImmediately: true }
         );
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 7bca08c15..9e2ce1c80 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -59,10 +59,12 @@ export class TableBox extends React.Component<TableBoxProps> {
         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
+    // updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
     filterSelectedRowsDown = () => {
         const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows);
         this.props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
+        const highlighted = NumListCast(this.props.layoutDoc.dataViz_highlitedRows);
+        this.props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
     };
 
     render() {
@@ -152,12 +154,21 @@ export class TableBox extends React.Component<TableBoxProps> {
                                         className="table-row"
                                         onClick={action(e => {
                                             // selecting a row
-                                            const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
-                                            if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1);
-                                            else selected?.push(rowId);
+                                            if (e.metaKey) {
+                                                const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
+                                                if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+                                                else highlited?.push(rowId);
+                                            } else {
+                                                const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+                                                if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1);
+                                                else selected?.push(rowId);
+                                            }
                                             e.stopPropagation();
                                         })}
-                                        style={{ background: NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '', width: '110%' }}>
+                                        style={{
+                                            background: NumListCast(this.props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
+                                            width: '110%',
+                                        }}>
                                         {this.columns.map(col => {
                                             // each cell
                                             const 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;
-- 
cgit v1.2.3-70-g09d2


From a8cde5790f1f388dfed4dcf863d5284942773c94 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 25 Aug 2023 12:31:37 -0400
Subject: from last

---
 src/client/views/nodes/DataVizBox/components/TableBox.tsx | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 9e2ce1c80..01f33dd80 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -153,15 +153,18 @@ export class TableBox extends React.Component<TableBoxProps> {
                                         key={rowId}
                                         className="table-row"
                                         onClick={action(e => {
-                                            // selecting a row
+                                            const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
                                             if (e.metaKey) {
-                                                const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
+                                                // highlighting a row
                                                 if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
                                                 else highlited?.push(rowId);
                                             } else {
+                                                // selecting a row
                                                 const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
-                                                if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1);
-                                                else selected?.push(rowId);
+                                                if (selected?.includes(rowId)) {
+                                                    if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+                                                    selected.splice(selected.indexOf(rowId), 1);
+                                                } else selected?.push(rowId);
                                             }
                                             e.stopPropagation();
                                         })}
-- 
cgit v1.2.3-70-g09d2


From dca2fef8f8d4e068d9a5ab9ef6cd5636190d2ceb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 25 Aug 2023 12:35:46 -0400
Subject: from last

---
 src/client/views/nodes/DataVizBox/components/TableBox.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 01f33dd80..067dff07a 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -154,13 +154,14 @@ export class TableBox extends React.Component<TableBoxProps> {
                                         className="table-row"
                                         onClick={action(e => {
                                             const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
+                                            const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
                                             if (e.metaKey) {
                                                 // highlighting a row
                                                 if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
                                                 else highlited?.push(rowId);
+                                                if (!selected?.includes(rowId)) selected?.push(rowId);
                                             } else {
                                                 // selecting a row
-                                                const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
                                                 if (selected?.includes(rowId)) {
                                                     if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
                                                     selected.splice(selected.indexOf(rowId), 1);
-- 
cgit v1.2.3-70-g09d2

