From e6d1dd82f5d472bddaf66f9210142249d6fa09aa Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Wed, 6 Jul 2022 20:21:04 -0400
Subject: added endpoint routes for csv and integrated chartjs

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 67 +++++++++++++++++-------
 1 file changed, 47 insertions(+), 20 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 592723ee9..0c1063e0c 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,9 +1,12 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import * as React from "react";
-import { StrCast } from "../../../../fields/Types";
+import { Cast, StrCast } from "../../../../fields/Types";
+import { CsvField } from "../../../../fields/URLField";
+import { Utils } from "../../../../Utils";
 import { ViewBoxBaseComponent } from "../../DocComponent";
 import { FieldViewProps, FieldView } from "../FieldView";
+import { ChartBox } from "./ChartBox";
 import "./DataVizBox.scss";
 import { HistogramBox } from "./HistogramBox";
 import { TableBox } from "./TableBox";
@@ -16,7 +19,7 @@ enum DataVizView {
 
 @observer
 export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    @observable private pairs: {x: number, y:number}[] = [{x: 1, y:2}];
+    @observable private pairs: {x: number, y:number}[] = [];
 
     // TODO: nda - make this use enum values instead
     // @observable private currView: DataVizView = DataVizView.TABLE;
@@ -39,39 +42,63 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {  return FieldView.LayoutString(DataVizBox, fieldKey);  }
 
     @action
-    private createPairs() {
-        const xVals: number[] = [0, 1, 2, 3, 4, 5];
-        // const yVals: number[] = [10, 20, 30, 40, 50, 60];
-        const yVals: number[] = [1, 2, 3, 4, 5, 6];
-        let pairs: {
-            x: number,
-            y:number
-        }[] = [];
-        if (xVals.length != yVals.length) return pairs;
-        for (let i = 0; i < xVals.length; i++) {
-            pairs.push({x: xVals[i], y: yVals[i]});
-        }
-        this.pairs = pairs;
-        return pairs;
+    private async createPairs() {
+        // const xVals: number[] = [0, 1, 2, 3, 4, 5];
+        // // const yVals: number[] = [10, 20, 30, 40, 50, 60];
+        // const yVals: number[] = [1, 2, 3, 4, 5, 6];
+        // let pairs: {
+        //     x: number,
+        //     y:number
+        // }[] = [];
+        // if (xVals.length != yVals.length) return pairs;
+        // for (let i = 0; i < xVals.length; i++) {
+        //     pairs.push({x: xVals[i], y: yVals[i]});
+        // }
+        // this.pairs = pairs;
+        // this.pairs = await this.fetchData();
     }
 
     @computed get selectView() {
         switch(this.currView) {
             case "table":
-                return (<TableBox pairs={this.pairs} />)
+                return (<TableBox pairs={this.pairs} />);
             case "histogram":
-                return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)                
+                return (<ChartBox rootDoc={this.rootDoc} pairs={this.pairs}/>);
+            // case "histogram":
+            //     return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)                
         }
     }
 
     @computed get pairVals() {
-        return this.createPairs();
+        return fetch("/csvData?uri=" + this.dataUrl?.url.href).then(res => res.json());
     }
 
+    @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField) }
+
     componentDidMount() {
-        this.createPairs();
+        // this.createPairs();
+        this.fetchData();
     }
 
+    // async fetchData() {
+    //     console.log(Cast(this.dataDoc[this.fieldKey], CsvField));
+    //     const uri = this.dataUrl?.url.href;
+    //     console.log(uri);
+    //     const res = await fetch("/csvData?uri=" + uri);
+    //     return await res.json();
+    // }
+    
+    fetchData() {
+        const uri = this.dataUrl?.url.href;
+        fetch("/csvData?uri=" + uri).then(
+            res => res.json().then(
+                action(res => {
+                    this.pairs = res;
+                })
+            ))        
+    }
+
+
     // handle changing the view using a button
     @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) {
         e.preventDefault();
-- 
cgit v1.2.3-70-g09d2


From 1ccbb22c29ea406dae824b9163ddc676263bb83a Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Wed, 6 Jul 2022 20:41:27 -0400
Subject: cleaned up code for chartbox

---
 src/client/views/nodes/DataVizBox/ChartBox.tsx   | 85 +++++-------------------
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 22 ++----
 2 files changed, 21 insertions(+), 86 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index c5ec1716d..56f61474b 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -21,6 +21,15 @@ export interface ChartBoxProps {
     pairs: {x: number, y:number}[];
 }
 
+export interface ChartJsData {
+    labels: number[];
+    datasets: {
+        label: string;
+        data: number[];
+        backgroundColor: string[];
+    }[] 
+}
+
 ChartJS.register(
     CategoryScale,
     LinearScale,
@@ -30,37 +39,6 @@ ChartJS.register(
     Legend
   );
   
-//   export const options = {
-//     responsive: true,
-//     plugins: {
-//       legend: {
-//         position: 'top' as const,
-//       },
-//       title: {
-//         display: true,
-//         text: 'Chart.js Bar Chart',
-//       },
-//     },
-//   };
-  
-//   const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
-  
-//   export const data = {
-//     labels,
-//     datasets: [
-//       {
-//         label: 'Dataset 1',
-//         data: [100, 200, 300, 400, 500, 600, 700],
-//         backgroundColor: 'rgba(255, 99, 132, 0.5)',
-//       },
-//       {
-//         label: 'Dataset 2',
-//         data: [700, 600, 500, 400, 300, 200, 100],
-//         backgroundColor: 'rgba(53, 162, 235, 0.5)',
-//       },
-//     ],
-//   };
-
 const primaryColor = 'rgba(53, 162, 235, 0.5)';
 const selectedColor = 'rgba(255, 99, 132, 0.5)';
 
@@ -68,18 +46,9 @@ const selectedColor = 'rgba(255, 99, 132, 0.5)';
 export class ChartBox extends React.Component<ChartBoxProps> {
     private _chartRef: any = React.createRef<ChartJSOrUndefined<"bar", number[], string>>();
 
-    // TODO: add alternate | undefined to all these variables
-    @observable private _prevColor: string = undefined;
-    @observable private _prevIndex: { dIndex: number, index: number} = undefined;
-    // @observable private _chartJsData: {
-    //     labels: number[];
-    //     datasets: {
-    //         label: string;
-    //         data: number[];
-    //         backgroundColor: string;
-    //     }[];
-    // } = undefined;
-    @observable private _chartJsData: any = null;
+    @observable private _prevColor: string | undefined= undefined;
+    @observable private _prevIndex: { dIndex: number, index: number} | undefined = undefined;
+    @observable private _chartJsData: ChartJsData | undefined= undefined;
 
     @computed get options() {
         return {
@@ -102,7 +71,6 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         const dataset = {
             label: 'Dataset 1',
             data: this.props.pairs.map(p => p.y),
-            // backgroundColor: primaryColor 
             backgroundColor: this.props.pairs.map(p => primaryColor),
         }
         const data = {
@@ -110,39 +78,21 @@ export class ChartBox extends React.Component<ChartBoxProps> {
             datasets: [dataset]
         };
         this._chartJsData = data;
-        // this._chartRef.current.update()
     }
     
-    @computed get chartJsData() {
-        const labels = this.props.pairs.map(p => p.x);
-        const dataset = {
-            label: 'Dataset 1',
-            data: this.props.pairs.map(p => p.y),
-            backgroundColor: this.props.pairs.map(p => primaryColor),
-            // backgroundColor: primaryColor,
-        }
-        const data = {
-            labels,
-            datasets: [dataset]
-        };
-        return data;
+    componentDidMount() {
+        this.generateChartJsData();
     }
 
-    // componentDidMount() {
-    //     this.generateChartJsData();
-    // }
-
     @action
     onClick = (e: any) => {
         e.preventDefault();
-        console.log(getDatasetAtEvent(this._chartRef.current, e));
-        console.log(getElementAtEvent(this._chartRef.current, e));
-
+        if (getDatasetAtEvent(this._chartRef.current, e).length == 0) return;
+        if (!this._chartJsData) return;
         if (this._prevIndex && this._prevColor) {
             this._chartJsData.datasets[this._prevIndex.dIndex].backgroundColor[this._prevIndex.index] = this._prevColor;
         }
 
-
         const currSelected = getElementAtEvent(this._chartRef.current, e);
         const index = { datasetIndex: currSelected[0].datasetIndex, index: currSelected[0].index };
         this._prevIndex = { dIndex: index.datasetIndex, index: index.index };
@@ -150,12 +100,9 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor;
         // TODO: nda - send data back to the backend so that we know how to render the chart
         this._chartRef.current.update();
-        console.log(this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index]);
     }
 
     render() {
-        if (this.props.pairs && !this._chartJsData) this.generateChartJsData();
-            
         if (this.props.pairs && this._chartJsData) {
             return <Bar ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={(e) => this.onClick(e)} />
         } else {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 0c1063e0c..c5ad3c84d 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -41,23 +41,6 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     public static LayoutString(fieldKey: string) {  return FieldView.LayoutString(DataVizBox, fieldKey);  }
 
-    @action
-    private async createPairs() {
-        // const xVals: number[] = [0, 1, 2, 3, 4, 5];
-        // // const yVals: number[] = [10, 20, 30, 40, 50, 60];
-        // const yVals: number[] = [1, 2, 3, 4, 5, 6];
-        // let pairs: {
-        //     x: number,
-        //     y:number
-        // }[] = [];
-        // if (xVals.length != yVals.length) return pairs;
-        // for (let i = 0; i < xVals.length; i++) {
-        //     pairs.push({x: xVals[i], y: yVals[i]});
-        // }
-        // this.pairs = pairs;
-        // this.pairs = await this.fetchData();
-    }
-
     @computed get selectView() {
         switch(this.currView) {
             case "table":
@@ -107,6 +90,11 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     render() {
+
+        if (this.pairs.length == 0) {
+            return <div>Loading...</div>
+        }
+
         return (
             <div className="dataViz">
                 <button onClick={(e) => this.changeViewHandler(e)}>Change View</button>
-- 
cgit v1.2.3-70-g09d2


From 0b58be7abc9e477190b105fe9c1ecfcc9dd58e69 Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Tue, 12 Jul 2022 12:19:09 -0400
Subject: merged master

---
 package-lock.json                                | 91 +++++++++++++++++-------
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 80 ++++++++++-----------
 2 files changed, 101 insertions(+), 70 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index e0e83d4b8..71bac6958 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2455,7 +2455,7 @@
                 "textarea-caret": {
                     "version": "3.0.2",
                     "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz",
-                    "integrity": "sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg=="
+                    "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8="
                 }
             }
         },
@@ -2554,7 +2554,7 @@
         "after": {
             "version": "0.8.2",
             "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
-            "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA=="
+            "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
         },
         "agent-base": {
             "version": "6.0.2",
@@ -3346,7 +3346,7 @@
         "backo2": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
-            "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA=="
+            "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
         },
         "balanced-match": {
             "version": "1.0.2",
@@ -3411,7 +3411,7 @@
         "base64-arraybuffer": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
-            "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg=="
+            "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
         },
         "base64-js": {
             "version": "1.5.1",
@@ -4333,7 +4333,7 @@
         "component-bind": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
-            "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
+            "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
         },
         "component-emitter": {
             "version": "1.3.0",
@@ -4343,7 +4343,7 @@
         "component-inherit": {
             "version": "0.0.3",
             "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
-            "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
+            "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
         },
         "compress-brotli": {
             "version": "1.3.8",
@@ -5051,7 +5051,7 @@
         "custom-event": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
-            "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
+            "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU="
         },
         "cyclist": {
             "version": "1.0.1",
@@ -5059,6 +5059,16 @@
             "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
             "dev": true
         },
+        "d": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+            "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+            "dev": true,
+            "requires": {
+                "es5-ext": "^0.10.50",
+                "type": "^1.0.1"
+            }
+        },
         "d3-array": {
             "version": "2.12.1",
             "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
@@ -6069,7 +6079,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                 },
                 "ws": {
                     "version": "7.4.6",
@@ -6208,6 +6218,28 @@
                 "is-symbol": "^1.0.2"
             }
         },
+        "es5-ext": {
+            "version": "0.10.61",
+            "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz",
+            "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==",
+            "dev": true,
+            "requires": {
+                "es6-iterator": "^2.0.3",
+                "es6-symbol": "^3.1.3",
+                "next-tick": "^1.1.0"
+            }
+        },
+        "es6-iterator": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+            "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+            "dev": true,
+            "requires": {
+                "d": "1",
+                "es5-ext": "^0.10.35",
+                "es6-symbol": "^3.1.1"
+            }
+        },
         "es6-promise": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -6219,6 +6251,7 @@
             "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
             "dev": true,
             "requires": {
+                "d": "^1.0.1",
                 "ext": "^1.1.2"
             }
         },
@@ -8416,7 +8449,7 @@
         "fs-extra": {
             "version": "0.26.7",
             "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
-            "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
+            "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==",
             "requires": {
                 "graceful-fs": "^4.1.2",
                 "jsonfile": "^2.1.0",
@@ -9054,14 +9087,14 @@
                 "isarray": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-                    "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
+                    "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
                 }
             }
         },
         "has-cors": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
-            "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
+            "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
         },
         "has-flag": {
             "version": "3.0.0",
@@ -9557,7 +9590,7 @@
         "indexof": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
-            "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
+            "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
         },
         "inflight": {
             "version": "1.0.6",
@@ -10600,7 +10633,7 @@
         "jsonfile": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
-            "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
+            "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
             "requires": {
                 "graceful-fs": "^4.1.6"
             }
@@ -11019,7 +11052,7 @@
         "lodash.isequal": {
             "version": "4.5.0",
             "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
-            "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+            "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
         },
         "lodash.isplainobject": {
             "version": "4.0.6",
@@ -11270,7 +11303,7 @@
         "mathquill": {
             "version": "0.10.1-a",
             "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz",
-            "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==",
+            "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=",
             "requires": {
                 "jquery": "^1.12.3"
             },
@@ -11278,7 +11311,7 @@
                 "jquery": {
                     "version": "1.12.4",
                     "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz",
-                    "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg=="
+                    "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw="
                 }
             }
         },
@@ -12788,7 +12821,7 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
                             "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
                         },
                         "is-fullwidth-code-point": {
@@ -14287,7 +14320,7 @@
                 },
                 "minimist": {
                     "version": "1.2.6",
-                    "resolved": false,
+                    "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
                     "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
                 },
                 "minizlib": {
@@ -14336,7 +14369,7 @@
                     "dependencies": {
                         "minimist": {
                             "version": "1.2.6",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
                             "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
                         }
                     }
@@ -15709,7 +15742,7 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.1",
-                            "resolved": false,
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
                             "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
                         },
                         "is-fullwidth-code-point": {
@@ -19343,7 +19376,7 @@
                 "component-emitter": {
                     "version": "1.2.1",
                     "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
-                    "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
+                    "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA=="
                 },
                 "debug": {
                     "version": "4.1.1",
@@ -19356,7 +19389,7 @@
                 "isarray": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-                    "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+                    "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
                 }
             }
         },
@@ -20208,7 +20241,7 @@
         "to-array": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
-            "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
+            "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
         },
         "to-fast-properties": {
             "version": "2.0.0",
@@ -20720,6 +20753,12 @@
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
             "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
         },
+        "type": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+            "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+            "dev": true
+        },
         "type-check": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -21655,7 +21694,7 @@
                 "resolve-cwd": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
-                    "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+                    "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==",
                     "dev": true,
                     "requires": {
                         "resolve-from": "^3.0.0"
@@ -21664,7 +21703,7 @@
                 "resolve-from": {
                     "version": "3.0.0",
                     "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
-                    "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+                    "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==",
                     "dev": true
                 },
                 "semver": {
@@ -22205,7 +22244,7 @@
         "yeast": {
             "version": "0.1.2",
             "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
-            "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
+            "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
         },
         "yn": {
             "version": "2.0.0",
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index c5ad3c84d..a4a4028f2 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,25 +1,22 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { CsvField } from "../../../../fields/URLField";
-import { Utils } from "../../../../Utils";
-import { ViewBoxBaseComponent } from "../../DocComponent";
-import { FieldViewProps, FieldView } from "../FieldView";
-import { ChartBox } from "./ChartBox";
-import "./DataVizBox.scss";
-import { HistogramBox } from "./HistogramBox";
-import { TableBox } from "./TableBox";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Cast, StrCast } from '../../../../fields/Types';
+import { CsvField } from '../../../../fields/URLField';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { FieldViewProps, FieldView } from '../FieldView';
+import { ChartBox } from './ChartBox';
+import './DataVizBox.scss';
+import { TableBox } from './TableBox';
 
 enum DataVizView {
-    TABLE = "table",
-    HISTOGRAM= "histogram"
+    TABLE = 'table',
+    HISTOGRAM = 'histogram',
 }
 
-
 @observer
 export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    @observable private pairs: {x: number, y:number}[] = [];
+    @observable private pairs: { x: number; y: number }[] = [];
 
     // TODO: nda - make this use enum values instead
     // @observable private currView: DataVizView = DataVizView.TABLE;
@@ -27,7 +24,7 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this.rootDoc._dataVizView) {
             return StrCast(this.rootDoc._dataVizView);
         } else {
-            return "table";
+            return 'table';
         }
     }
 
@@ -35,71 +32,66 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
         super(props);
         if (!this.rootDoc._dataVizView) {
             // TODO: nda - this might not always want to default to "table"
-            this.rootDoc._dataVizView = "table";
+            this.rootDoc._dataVizView = 'table';
         }
     }
 
-    public static LayoutString(fieldKey: string) {  return FieldView.LayoutString(DataVizBox, fieldKey);  }
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(DataVizBox, fieldKey);
+    }
 
     @computed get selectView() {
-        switch(this.currView) {
-            case "table":
-                return (<TableBox pairs={this.pairs} />);
-            case "histogram":
-                return (<ChartBox rootDoc={this.rootDoc} pairs={this.pairs}/>);
+        switch (this.currView) {
+            case 'table':
+                return <TableBox pairs={this.pairs} />;
+            case 'histogram':
+                return <ChartBox rootDoc={this.rootDoc} pairs={this.pairs} />;
             // case "histogram":
-            //     return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)                
+            //     return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)
         }
     }
 
     @computed get pairVals() {
-        return fetch("/csvData?uri=" + this.dataUrl?.url.href).then(res => res.json());
+        return fetch('/csvData?uri=' + this.dataUrl?.url.href).then(res => res.json());
     }
 
-    @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField) }
+    @computed get dataUrl() {
+        return Cast(this.dataDoc[this.fieldKey], CsvField);
+    }
 
     componentDidMount() {
         // this.createPairs();
         this.fetchData();
     }
 
-    // async fetchData() {
-    //     console.log(Cast(this.dataDoc[this.fieldKey], CsvField));
-    //     const uri = this.dataUrl?.url.href;
-    //     console.log(uri);
-    //     const res = await fetch("/csvData?uri=" + uri);
-    //     return await res.json();
-    // }
-    
     fetchData() {
         const uri = this.dataUrl?.url.href;
-        fetch("/csvData?uri=" + uri).then(
-            res => res.json().then(
+        fetch('/csvData?uri=' + uri).then(res =>
+            res.json().then(
                 action(res => {
                     this.pairs = res;
                 })
-            ))        
+            )
+        );
     }
 
-
     // handle changing the view using a button
     @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) {
         e.preventDefault();
         e.stopPropagation();
-        this.rootDoc._dataVizView = this.currView == "table" ? "histogram" : "table";
+        this.rootDoc._dataVizView = this.currView == 'table' ? 'histogram' : 'table';
     }
 
     render() {
-
         if (this.pairs.length == 0) {
-            return <div>Loading...</div>
+            return <div>Loading...</div>;
         }
 
         return (
             <div className="dataViz">
-                <button onClick={(e) => this.changeViewHandler(e)}>Change View</button>
+                <button onClick={e => this.changeViewHandler(e)}>Change View</button>
                 {this.selectView}
             </div>
         );
     }
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2


From 3bc49b458c6f7275b5444553ce7e22306fec3ab7 Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Mon, 25 Jul 2022 15:11:29 -0400
Subject: fixed graph cut off bug - i hate javascript types

---
 src/client/views/nodes/DataVizBox/ChartBox.tsx     |  59 +++++-----
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  67 ++++++++++-
 src/client/views/nodes/DataVizBox/LineChart.tsx    | 128 ++++++++++++++++++---
 src/client/views/nodes/DataVizBox/utils/D3Utils.ts |  15 +--
 4 files changed, 209 insertions(+), 60 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index a03aaae31..92ad76e61 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -1,18 +1,18 @@
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc } from '../../../../fields/Doc';
-// import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, InteractionItem, PointElement, LineElement } from 'chart.js';
-// import { Bar, getDatasetAtEvent, getElementAtEvent, Line } from 'react-chartjs-2';
+import { Doc, HeightSym } from '../../../../fields/Doc';
 import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types';
 import { action, computed, observable } from 'mobx';
-import { Cast, StrCast } from '../../../../fields/Types';
-// import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
 import { CategoricalChartState } from 'recharts/types/chart/generateCategoricalChart';
 import { LineChart } from './LineChart';
 
 export interface ChartBoxProps {
     rootDoc: Doc;
+    dataDoc: Doc;
     pairs: { x: number; y: number }[];
+    setChartBox: (chartBox: ChartBox) => void;
+    getAnchor: () => Doc;
 }
 
 const primaryColor = 'rgba(53, 162, 235, 0.5)';
@@ -38,6 +38,7 @@ export interface ChartData {
 @observer
 export class ChartBox extends React.Component<ChartBoxProps> {
     @observable private _chartData: ChartData | undefined = undefined;
+    @observable private currChart: LineChart | undefined;
 
     @computed get currView() {
         if (this.props.rootDoc._dataVizView) {
@@ -54,6 +55,10 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         }
     }
 
+    setCurrChart(chart: LineChart | undefined) {
+        // this.currChart = chart;
+    }
+
     @action
     generateChartData() {
         if (this.props.rootDoc._chartData) {
@@ -107,6 +112,11 @@ export class ChartBox extends React.Component<ChartBoxProps> {
     componentDidMount() {
         // this.generateChartJsData();
         this.generateChartData();
+        // setting timeout to allow rerender cycle of the actual chart tof inish
+        setTimeout(() => {
+            this.props.setChartBox(this);
+        });
+        // this.props.setChartBox(this);
     }
 
     @action
@@ -117,32 +127,6 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase();
     };
 
-    // @action
-    // onClick = (e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
-    //     e.preventDefault();
-    //     console.log(e);
-
-    //     if (getDatasetAtEvent(this._chartRef.current, e).length == 0) return;
-    //     if (!this._chartJsData) return;
-    //     if (this._prevIndex && this._prevColor) {
-    //         this._chartJsData.datasets[this._prevIndex.dIndex].backgroundColor[this._prevIndex.index] = this._prevColor;
-    //     }
-
-    //     const currSelected = getElementAtEvent(this._chartRef.current, e);
-    //     // TODO - nda: the currSelected might not have the updated color variables so look into that
-    //     this._currSelected = currSelected;
-    //     const index = { datasetIndex: currSelected[0].datasetIndex, index: currSelected[0].index };
-    //     this._prevIndex = { dIndex: index.datasetIndex, index: index.index };
-    //     this._prevColor = this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index];
-    //     this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor;
-    //     this._chartRef.current.update();
-    //     // stringify this._chartJsData
-    //     const strData = JSON.stringify(this._chartJsData);
-    //     this.props.rootDoc._chartData = strData;
-    //     this.props.rootDoc._prevColor = this._prevColor;
-    //     this.props.rootDoc._prevIndex = JSON.stringify(this._prevIndex);
-    // };
-
     onMouseMove = (e: CategoricalChartState) => {
         console.log(e);
 
@@ -158,10 +142,16 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         // }
     };
 
+    scrollFocus(doc: Doc, smooth: boolean) {}
+
     handleDotClick = (e: any) => {
         console.log(e);
     };
 
+    _getAnchor() {
+        return this.currChart?._getAnchor();
+    }
+
     // handleTextClick = (e: any) => {
     //     console.log(e);
     // }
@@ -195,6 +185,11 @@ export class ChartBox extends React.Component<ChartBoxProps> {
 
     render() {
         if (this.props.pairs && this._chartData) {
+            let width = NumCast(this.props.rootDoc._width);
+            width = width * 0.7;
+            let height = NumCast(this.props.rootDoc._height);
+            height = height * 0.7;
+            console.log(width, height);
             return (
                 <div>
                     <div>
@@ -204,7 +199,7 @@ export class ChartBox extends React.Component<ChartBoxProps> {
                             <Bar ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} />
                         )} */}
                         {/* {this.reLineChart} */}
-                        <LineChart width={500} height={300} data={this._chartData} />
+                        <LineChart width={width} height={height} data={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />
                     </div>
                     <button onClick={e => this.onClickChangeChart(e)} value="line">
                         Line
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index a4a4028f2..4883385bb 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,9 +1,11 @@
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { Doc, FieldResult } from '../../../../fields/Doc';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
-import { ViewBoxBaseComponent } from '../../DocComponent';
+import { Docs } from '../../../documents/Documents';
+import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent } from '../../DocComponent';
 import { FieldViewProps, FieldView } from '../FieldView';
 import { ChartBox } from './ChartBox';
 import './DataVizBox.scss';
@@ -15,11 +17,42 @@ enum DataVizView {
 }
 
 @observer
-export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+    // says we have an object and any string
+    // 2 ways of doing it
+    // @observable private pairs: { [key: string]: number | string | undefined }[] = [];
+    // @observable private pairs: { [key: string]: FieldResult }[] = [];
     @observable private pairs: { x: number; y: number }[] = [];
+    private _chartBox: ChartBox | undefined;
+    // // another way would be store a schema that defines the type of data we are expecting from an imported doc
+
+    // method1() {
+    //     this.pairs[0].x = 3;
+    // }
+
+    // method() {
+    //     // this.pairs[0].x = 3;
+    //     // go through the pairs
+    //     const x = this.pairs[0].x;
+    //     if (typeof x == 'number') {
+    //         let x1 = Number(x);
+    //         // let x1 = NumCast(x);
+    //     }
+    // }
+
+    // could use field result
+    // [key: string]: FieldResult;
+    // instead of numeric x,y in there,
 
     // TODO: nda - make this use enum values instead
     // @observable private currView: DataVizView = DataVizView.TABLE;
+
+    // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops
+
+    setChartBox = (chartBox: ChartBox) => {
+        this._chartBox = chartBox;
+    };
+
     @computed get currView() {
         if (this.rootDoc._dataVizView) {
             return StrCast(this.rootDoc._dataVizView);
@@ -28,6 +61,32 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     }
 
+    scrollFocus = (doc: Doc, smooth: boolean) => {
+        // reconstruct the view based on the anchor ->
+        // keep track of the datavizbox view and the type of chart we're rendering
+
+        // use dataview to restore part of it and then pass on the rest to the chartbox
+
+        // pseudocode
+        setTimeout(() => this._chartBox?.scrollFocus(doc, smooth)); /* smooth parameter true = do animations */
+        return 0;
+    };
+
+    getAnchor() {
+        // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
+        const anchor =
+            // this._COMPONENTNAME._getAnchor() ??
+            this._chartBox?._getAnchor() ??
+            Docs.Create.TextanchorDocument({
+                /*put in some options*/
+            });
+
+        anchor.dataView = this.currView;
+
+        this.addDocument(anchor);
+        return anchor;
+        // have some other function in code that
+    }
     constructor(props: any) {
         super(props);
         if (!this.rootDoc._dataVizView) {
@@ -45,7 +104,7 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
             case 'table':
                 return <TableBox pairs={this.pairs} />;
             case 'histogram':
-                return <ChartBox rootDoc={this.rootDoc} pairs={this.pairs} />;
+                return <ChartBox getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
             // case "histogram":
             //     return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)
         }
diff --git a/src/client/views/nodes/DataVizBox/LineChart.tsx b/src/client/views/nodes/DataVizBox/LineChart.tsx
index 42e9da3d7..89fb6e20b 100644
--- a/src/client/views/nodes/DataVizBox/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/LineChart.tsx
@@ -5,13 +5,27 @@ import { ChartData, DataPoint } from './ChartBox';
 // import d3
 import * as d3 from 'd3';
 import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from './utils/D3Utils';
+import { Docs } from '../../../documents/Documents';
+import { Doc, DocListCast } from '../../../../fields/Doc';
 
 interface LineChartProps {
     data: ChartData;
     width: number;
     height: number;
+    dataDoc: Doc;
+    fieldKey: string;
+    // returns linechart component but should be generic chart
+    setCurrChart: (chart: LineChart | undefined) => void;
+    getAnchor: () => Doc;
 }
 
+type minMaxRange = {
+    xMin: number | undefined;
+    xMax: number | undefined;
+    yMin: number | undefined;
+    yMax: number | undefined;
+};
+
 interface SelectedDataPoint {
     x: number;
     y: number;
@@ -21,61 +35,137 @@ interface SelectedDataPoint {
 @observer
 export class LineChart extends React.Component<LineChartProps> {
     private _dataReactionDisposer: IReactionDisposer | undefined = undefined;
+    private _heightReactionDisposer: IReactionDisposer | undefined = undefined;
+    private _widthReactionDisposer: IReactionDisposer | undefined;
     @observable private _x: number = 0;
     @observable private _y: number = 0;
     @observable private _currSelected: SelectedDataPoint | undefined = undefined;
     // create ref for the div
     private _chartRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _rangeVals: minMaxRange = {
+        xMin: undefined,
+        xMax: undefined,
+        yMin: undefined,
+        yMax: undefined,
+    };
+
+    // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that
+
+    // write the getanchor function that gets whatever I want as the link anchor
 
     componentDidMount() {
-        console.log('Getting to line chart');
-        this.drawChart();
-        this._dataReactionDisposer = reaction(() => this.props.data, this.drawChart);
+        // this._rangeVals = minMaxRange(this.props.data.data);
+        // this.drawChart();
+        this._dataReactionDisposer = reaction(
+            () => this.props.data,
+            data => {
+                this._rangeVals = minMaxRange(data.data);
+                this.drawChart();
+            },
+            { fireImmediately: true }
+        );
+        this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this));
+        this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this));
+        reaction(
+            () => DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']),
+            annotations => {
+                // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
+                // could be blue colored to make it look like anchor
+            },
+            { fireImmediately: true }
+        );
+        setTimeout(() => {
+            this.props.setCurrChart(this);
+        });
+    }
+
+    // gets called whenever the "data-annotations" fields gets updated
+    drawAnnotations() {}
+
+    _getAnchor() {
+        // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
+
+        // TODO: nda - look at pdfviewer get anchor for args
+        const doc = Docs.Create.TextanchorDocument({
+            /*put in some options*/
+        });
+        // access curr selected from the charts
+        doc.x = this._currSelected?.x;
+        doc.y = this._currSelected?.y;
+        doc.chartType = 'line';
+        return doc;
+        // have some other function in code that
     }
 
     componentWillUnmount() {
         if (this._dataReactionDisposer) {
             this._dataReactionDisposer();
         }
+        if (this._heightReactionDisposer) {
+            this._heightReactionDisposer();
+        }
+        if (this._widthReactionDisposer) {
+            this._widthReactionDisposer();
+        }
     }
 
     tooltipContent(data: DataPoint) {
         return `<b>x: ${data.x} y: ${data.y}</b>`;
     }
 
+    // TODO: nda - can use d3.create() to create html element instead of appending
     drawChart() {
+        console.log('Drawing chart');
         // clearing tooltip
         d3.select(this._chartRef.current).select('svg').remove();
         d3.select(this._chartRef.current).select('.tooltip').remove();
 
+        // TODO: nda - refactor code so that it only recalculates min max and things related to data on data update
         const margin = { top: 50, right: 50, bottom: 50, left: 50 };
         const { data } = this.props;
 
-        const { xMin, xMax, yMin, yMax } = minMaxRange(data.data);
+        const { xMin, xMax, yMin, yMax } = this._rangeVals;
+        console.log('rangeVals', this._rangeVals);
+
+        const width = this.props.width - margin.left - margin.right;
+        const height = this.props.height - margin.top - margin.bottom;
+        console.log('height, width', this.props.height, this.props.width);
 
         // adding svg
-        const svg = d3.select(this._chartRef.current).append('svg').attr('width', '100%').attr('height', '100%').append('g').attr('transform', `translate(${margin.left}, ${margin.top})`);
+        const svg = d3
+            .select(this._chartRef.current)
+            .append('svg')
+            .attr('width', `${width + margin.right + margin.left}`)
+            .attr('height', `${height + margin.top + margin.bottom}`)
+            .append('g')
+            .attr('transform', `translate(${margin.left}, ${margin.top})`);
 
         // adding tooltip
         // const tooltip = d3.select('#chart-container').append('div').attr('class', 'tooltip');
+        console.log('xMinMax:', xMin, xMax);
+        console.log('yMinMax:', yMin, yMax);
 
-        if (!xMin || !xMax || !yMin || !yMax) {
+        if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) {
+            console.log('getting here');
             // TODO: nda - error handle
             return;
         }
-        const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.props.width);
-        // adding x axis
-        const yScale = scaleCreatorNumerical(0, yMax, this.props.height, 0);
+
+        console.log('getting here');
+
+        // creating the x and y scales
+        const xScale = scaleCreatorNumerical(xMin, xMax, 0, width);
+        const yScale = scaleCreatorNumerical(0, yMax, height, 0);
 
         // create a line function that takes in the data.data.x and data.data.y
         // TODO: nda - fix the types for the d here
         const lineGen = createLineGenerator(xScale, yScale);
 
         // create x and y grids
-        xGrid(svg.append('g'), this.props.height, xScale);
-        yGrid(svg.append('g'), this.props.width, yScale);
-        xAxisCreator(svg.append('g'), this.props.height, xScale);
-        yAxisCreator(svg.append('g'), this.props.width, yScale);
+        xGrid(svg.append('g'), height, xScale);
+        yGrid(svg.append('g'), width, yScale);
+        xAxisCreator(svg.append('g'), height, xScale);
+        yAxisCreator(svg.append('g'), width, yScale);
 
         // draw the line
         drawLine(svg.append('path'), data.data, lineGen);
@@ -117,8 +207,8 @@ export class LineChart extends React.Component<LineChartProps> {
             this._y = d0.y;
             focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`);
             // TODO: nda - implement tooltips
-            // tooltip.transition().duration(300).style('opacity', 0.9);
-            // tooltip.html(() => this.tooltipContent(d0)).attr('transform', `translate(${xScale(d0.x) + 30}px,${yScale(d0.y) + 30}px)`);
+            tooltip.transition().duration(300).style('opacity', 0.9);
+            tooltip.html(() => this.tooltipContent(d0)).attr('transform', `translate(${xScale(d0.x) + 30}px,${yScale(d0.y) + 30}px)`);
         });
 
         const onPointClick = action((e: any) => {
@@ -132,13 +222,17 @@ export class LineChart extends React.Component<LineChartProps> {
             const selected = svg.selectAll('.circle-d1').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
             this._currSelected = { x: d0.x, y: d0.y, elem: selected };
             console.log('Getting here');
+            setTimeout(() => this.props.getAnchor());
+            // this.props.getAnchor();
             console.log(this._currSelected);
         });
 
         svg.append('rect')
             .attr('class', 'overlay')
-            .attr('width', this.props.width)
-            .attr('height', this.props.height)
+            .attr('width', width)
+            .attr('height', height + margin.top + margin.bottom)
+            .attr('fill', 'none')
+            .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`)
             .style('opacity', 0)
             .on('mouseover', () => {
                 focus.style('display', null);
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 90ec35f5c..5c7f9bce1 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -4,11 +4,12 @@ import { DataPoint } from '../ChartBox';
 // TODO: nda - implement function that can handle range for strings
 
 export const minMaxRange = (dataPts: DataPoint[]) => {
-    const yMin = d3.min(dataPts, d => d.y);
-    const yMax = d3.max(dataPts, d => d.y);
+    console.log(Number(dataPts[4].y));
+    const yMin = d3.min(dataPts, d => Number(d.y));
+    const yMax = d3.max(dataPts, d => Number(d.y));
 
-    const xMin = d3.min(dataPts, d => d.x);
-    const xMax = d3.max(dataPts, d => d.x);
+    const xMin = d3.min(dataPts, d => Number(d.x));
+    const xMax = d3.max(dataPts, d => Number(d.x));
 
     return { xMin, xMax, yMin, yMax };
 };
@@ -37,12 +38,12 @@ export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefin
     g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15));
 };
 
-export const yAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, width: number, yScale: d3.ScaleLinear<number, number, never>) => {
+export const yAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, marginLeft: number, yScale: d3.ScaleLinear<number, number, never>) => {
     g.attr('class', 'y-axis').call(d3.axisLeft(yScale));
 };
 
 export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, scale: d3.ScaleLinear<number, number, never>) => {
-    g.attr('class', 'grid')
+    g.attr('class', 'xGrid')
         .attr('transform', `translate(0,${height})`)
         .call(
             d3
@@ -53,7 +54,7 @@ export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, he
 };
 
 export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, width: number, scale: d3.ScaleLinear<number, number, never>) => {
-    g.attr('class', 'grid').call(
+    g.attr('class', 'yGrid').call(
         d3
             .axisLeft(scale)
             .tickSize(-width)
-- 
cgit v1.2.3-70-g09d2


From ba8cb4194062b0f939afd136a269625f9f26dcaa Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Mon, 25 Jul 2022 19:10:55 -0400
Subject: cleaned up code and moved files around

---
 src/client/views/nodes/DataVizBox/ChartBox.tsx     | 101 +-------
 .../views/nodes/DataVizBox/ChartInterface.ts       |  33 +++
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   2 +-
 src/client/views/nodes/DataVizBox/DrawHelper.ts    | 247 -------------------
 .../views/nodes/DataVizBox/HistogramBox.scss       |  18 --
 src/client/views/nodes/DataVizBox/HistogramBox.tsx | 162 -------------
 .../nodes/DataVizBox/HistogramBoxPrimitives.scss   |   0
 .../nodes/DataVizBox/HistogramBoxPrimitives.tsx    |  16 --
 .../nodes/DataVizBox/HistogramLabelPrimitives.scss |   0
 .../nodes/DataVizBox/HistogramLabelPrimitives.tsx  |  13 -
 src/client/views/nodes/DataVizBox/LineChart.tsx    | 269 --------------------
 src/client/views/nodes/DataVizBox/TableBox.scss    |  22 --
 src/client/views/nodes/DataVizBox/TableBox.tsx     |  37 ---
 .../views/nodes/DataVizBox/components/Chart.scss   |  10 +
 .../nodes/DataVizBox/components/LineChart.tsx      | 270 +++++++++++++++++++++
 .../nodes/DataVizBox/components/TableBox.scss      |  22 ++
 .../views/nodes/DataVizBox/components/TableBox.tsx |  37 +++
 src/client/views/nodes/DataVizBox/utils/D3Utils.ts |  12 +-
 18 files changed, 386 insertions(+), 885 deletions(-)
 create mode 100644 src/client/views/nodes/DataVizBox/ChartInterface.ts
 delete mode 100644 src/client/views/nodes/DataVizBox/DrawHelper.ts
 delete mode 100644 src/client/views/nodes/DataVizBox/HistogramBox.scss
 delete mode 100644 src/client/views/nodes/DataVizBox/HistogramBox.tsx
 delete mode 100644 src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss
 delete mode 100644 src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx
 delete mode 100644 src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss
 delete mode 100644 src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx
 delete mode 100644 src/client/views/nodes/DataVizBox/LineChart.tsx
 delete mode 100644 src/client/views/nodes/DataVizBox/TableBox.scss
 delete mode 100644 src/client/views/nodes/DataVizBox/TableBox.tsx
 create mode 100644 src/client/views/nodes/DataVizBox/components/Chart.scss
 create mode 100644 src/client/views/nodes/DataVizBox/components/LineChart.tsx
 create mode 100644 src/client/views/nodes/DataVizBox/components/TableBox.scss
 create mode 100644 src/client/views/nodes/DataVizBox/components/TableBox.tsx

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

diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index f2450bc7c..a2e4fd73c 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -1,11 +1,10 @@
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, HeightSym } from '../../../../fields/Doc';
-import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types';
 import { action, computed, observable } from 'mobx';
 import { Cast, NumCast, StrCast } from '../../../../fields/Types';
-import { CategoricalChartState } from 'recharts/types/chart/generateCategoricalChart';
-import { LineChart } from './LineChart';
+import { LineChart } from './components/LineChart';
+import { Chart, ChartData } from './ChartInterface';
 
 export interface ChartBoxProps {
     rootDoc: Doc;
@@ -15,26 +14,11 @@ export interface ChartBoxProps {
     getAnchor: () => Doc;
 }
 
-const primaryColor = 'rgba(53, 162, 235, 0.5)';
-const selectedColor = 'rgba(255, 99, 132, 0.5)';
-
-export interface RechartData {
-    name: string | number;
-    y: number;
-}
-
 export interface DataPoint {
     x: number;
     y: number;
 }
 
-export interface ChartData {
-    xLabel: string;
-    yLabel: string;
-    data: DataPoint[];
-    tooltipContent: (data: DataPoint) => string;
-}
-
 @observer
 export class ChartBox extends React.Component<ChartBoxProps> {
     @observable private _chartData: ChartData | undefined = undefined;
@@ -55,7 +39,7 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         }
     }
 
-    setCurrChart(chart: LineChart | undefined) {
+    setCurrChart(chart: Chart | undefined) {
         // this.currChart = chart;
     }
 
@@ -69,17 +53,15 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         const data: ChartData = {
             xLabel: '',
             yLabel: '',
-            data: [],
-            tooltipContent: (data: DataPoint) => {
-                return `<b>x: ${data.x} y: ${data.y}</b>`;
-            },
+            data: [[]],
         };
 
         if (this.props.pairs && this.props.pairs.length > 0) {
             data.xLabel = 'x';
             data.yLabel = 'y';
             this.props.pairs.forEach(pair => {
-                data.data.push({ x: pair.x, y: pair.y });
+                // TODO: nda - add actual support for multiple sets of data
+                data.data[0].push({ x: pair.x, y: pair.y });
             });
         }
 
@@ -87,30 +69,7 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this.props.rootDoc._chartData = JSON.stringify(data);
     }
 
-    // @action
-    // generateChartJsData() {
-    //     if (this.props.rootDoc._chartData) {
-    //         // parse the string into a json object
-    //         this._chartJsData = JSON.parse(StrCast(this.props.rootDoc._chartData));
-    //         this._prevColor = StrCast(this.props.rootDoc._prevColor);
-    //         this._prevIndex = JSON.parse(StrCast(this.props.rootDoc._prevIndex));
-    //         return;
-    //     }
-    //     const labels = this.props.pairs.map(p => p.x);
-    //     const dataset = {
-    //         label: 'Dataset 1',
-    //         data: this.props.pairs.map(p => p.y),
-    //         backgroundColor: this.props.pairs.map(p => primaryColor),
-    //     };
-    //     const data = {
-    //         labels,
-    //         datasets: [dataset],
-    //     };
-    //     this._chartJsData = data;
-    // }
-
     componentDidMount() {
-        // this.generateChartJsData();
         this.generateChartData();
         // setting timeout to allow rerender cycle of the actual chart tof inish
         setTimeout(() => {
@@ -127,21 +86,6 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase();
     };
 
-    onMouseMove = (e: CategoricalChartState) => {
-        console.log(e);
-
-        // pops up at 526 when it should be at 263
-        // at 100%scale it pops up at 526 but mouse shows 263 at 50% scale
-        if (e.chartX && e.chartY) {
-            e.chartX += 263;
-            e.chartY += 263;
-        }
-        // if (e.chartX && e.chartY) {
-        //     e.chartX -= 200;
-        //     e.chartY -= 200;
-        // }
-    };
-
     scrollFocus(doc: Doc, smooth: boolean) {}
 
     handleDotClick = (e: any) => {
@@ -152,37 +96,6 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         return this.currChart?._getAnchor();
     }
 
-    // handleTextClick = (e: any) => {
-    //     console.log(e);
-    // }
-
-    // @computed get reLineChart() {
-    //     return (
-    //         // <ResponsiveContainer width="80%" height="80%">
-    //         <LineChart
-    //             width={500}
-    //             height={300}
-    //             data={this._chartData}
-    //             margin={{
-    //                 top: 5,
-    //                 right: 30,
-    //                 left: 20,
-    //                 bottom: 5,
-    //             }}
-    //             onMouseDown={e => console.log(e)}
-    //             onMouseMove={e => this.onMouseMove(e)}>
-    //             <CartesianGrid strokeDasharray="3 3" />
-    //             <XAxis dataKey="name" onMouseDown={e => console.log(e)} />
-    //             <YAxis />
-    //             <Tooltip />
-    //             <Legend />
-    //             <Line type="monotone" dataKey="y" stroke="#8884d8" activeDot={{ r: 8, onClick: this.handleDotClick }} />
-    //             {/* <Line type="monotone" dataKey="uv" stroke="#82ca9d" /> */}
-    //         </LineChart>
-    //         // </ResponsiveContainer>
-    //     );
-    // }
-
     render() {
         if (this.props.pairs && this._chartData) {
             let width = NumCast(this.props.rootDoc._width);
@@ -200,7 +113,7 @@ export class ChartBox extends React.Component<ChartBoxProps> {
                             <Bar ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} />
                         )} */}
                         {/* {this.reLineChart} */}
-                        <LineChart margin={margin} width={width} height={height} data={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />
+                        <LineChart margin={margin} width={width} height={height} chartData={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />
                     </div>
                     <button onClick={e => this.onClickChangeChart(e)} value="line">
                         Line
diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts
new file mode 100644
index 000000000..6e37f966c
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts
@@ -0,0 +1,33 @@
+import { Doc } from '../../../../fields/Doc';
+import { DataPoint } from './ChartBox';
+import { LineChart } from './components/LineChart';
+
+export interface Chart {
+    tooltipContent: (data: DataPoint) => string;
+    drawChart: () => void;
+    height: number;
+    width: number;
+}
+
+export interface ChartProps {
+    chartData: ChartData;
+    width: number;
+    height: number;
+    dataDoc: Doc;
+    fieldKey: string;
+    // returns linechart component but should be generic chart
+    setCurrChart: (chart: Chart) => void;
+    getAnchor: () => Doc;
+    margin: {
+        top: number;
+        right: number;
+        bottom: number;
+        left: number;
+    };
+}
+
+export interface ChartData {
+    xLabel: string;
+    yLabel: string;
+    data: DataPoint[][];
+}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 4883385bb..1d9b309ce 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -9,7 +9,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent } from '../../DocComp
 import { FieldViewProps, FieldView } from '../FieldView';
 import { ChartBox } from './ChartBox';
 import './DataVizBox.scss';
-import { TableBox } from './TableBox';
+import { TableBox } from './components/TableBox';
 
 enum DataVizView {
     TABLE = 'table',
diff --git a/src/client/views/nodes/DataVizBox/DrawHelper.ts b/src/client/views/nodes/DataVizBox/DrawHelper.ts
deleted file mode 100644
index 595cecebf..000000000
--- a/src/client/views/nodes/DataVizBox/DrawHelper.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-export class PIXIPoint {
-    public get x() { return this.coords[0]; }
-    public get y() { return this.coords[1]; }
-    public set x(value: number) { this.coords[0] = value; }
-    public set y(value: number) { this.coords[1] = value; }
-    public coords: number[] = [0, 0];
-    constructor(x: number, y: number) {
-        this.coords[0] = x;
-        this.coords[1] = y;
-    }
-}
-
-export class PIXIRectangle {
-    public x: number;
-    public y: number;
-    public width: number;
-    public height: number;
-    public get left() { return this.x; }
-    public get right() { return this.x + this.width; }
-    public get top() { return this.y; }
-    public get bottom() { return this.top + this.height; }
-    public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); }
-    constructor(x: number, y: number, width: number, height: number) {
-        this.x = x;
-        this.y = y;
-        this.width = width;
-        this.height = height;
-    }
-}
-
-export class MathUtil {
-
-    public static EPSILON: number = 0.001;
-
-    public static Sign(value: number): number {
-        return value >= 0 ? 1 : -1;
-    }
-
-    public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
-        if (inline) {
-            p1.x += p2.x;
-            p1.y += p2.y;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x + p2.x, p1.y + p2.y);
-        }
-    }
-
-    public static Perp(p1: PIXIPoint): PIXIPoint {
-        return new PIXIPoint(-p1.y, p1.x);
-    }
-
-    public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint {
-        if (inline) {
-            p1.x /= by;
-            p1.y /= by;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x / by, p1.y / by);
-        }
-    }
-
-    public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) {
-        if (inline) {
-            p1.x *= by;
-            p1.y *= by;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x * by, p1.y * by);
-        }
-    }
-
-    public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
-        if (inline) {
-            p1.x -= p2.x;
-            p1.y -= p2.y;
-            return p1;
-        }
-        else {
-            return new PIXIPoint(p1.x - p2.x, p1.y - p2.y);
-        }
-    }
-
-    public static Area(rect: PIXIRectangle): number {
-        return rect.width * rect.height;
-    }
-
-    public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) {
-        // Return minimum distance between line segment vw and point p
-        var l2 = MathUtil.DistSquared(v, w);  // i.e. |w-v|^2 -  avoid a sqrt
-        if (l2 === 0.0) return MathUtil.Dist(p, v);   // v === w case
-        // Consider the line extending the segment, parameterized as v + t (w - v).
-        // We find projection of point p onto the line. 
-        // It falls where t = [(p-v) . (w-v)] / |w-v|^2
-        // We clamp t from [0,1] to handle points outside the segment vw.
-        var dot = MathUtil.Dot(
-            MathUtil.SubtractPoint(p, v),
-            MathUtil.SubtractPoint(w, v)) / l2;
-        var t = Math.max(0, Math.min(1, dot));
-        // Projection falls on the segment
-        var projection = MathUtil.AddPoint(v,
-            MathUtil.MultiplyConstant(
-                MathUtil.SubtractPoint(w, v), t));
-        return MathUtil.Dist(p, projection);
-    }
-
-    public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined {
-        var a1 = pe1.y - ps1.y;
-        var b1 = ps1.x - pe1.x;
-
-        var a2 = pe2.y - ps2.y;
-        var b2 = ps2.x - pe2.x;
-
-        var delta = a1 * b2 - a2 * b1;
-        if (delta === 0) {
-            return undefined;
-        }
-        var c2 = a2 * ps2.x + b2 * ps2.y;
-        var c1 = a1 * ps1.x + b1 * ps1.y;
-        var invdelta = 1 / delta;
-        return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta);
-    }
-
-    public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
-        if (p.x < rect.left - this.EPSILON) {
-            return false;
-        }
-        if (p.x > rect.right + this.EPSILON) {
-            return false;
-        }
-        if (p.y < rect.top - this.EPSILON) {
-            return false;
-        }
-        if (p.y > rect.bottom + this.EPSILON) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> {
-        var r1 = new PIXIPoint(rect.left, rect.top);
-        var r2 = new PIXIPoint(rect.right, rect.top);
-        var r3 = new PIXIPoint(rect.right, rect.bottom);
-        var r4 = new PIXIPoint(rect.left, rect.bottom);
-        var ret = new Array<PIXIPoint>();
-        var dist = this.Dist(lineFrom, lineTo);
-        var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1);
-        if (inter && this.PointInPIXIRectangle(inter, rect) &&
-            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
-            ret.push(inter);
-        }
-        return ret;
-    }
-
-    public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle {
-        const left = Math.max(rect1.x, rect2.x);
-        const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width);
-        const top = Math.max(rect1.y, rect2.y);
-        const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height);
-        return new PIXIRectangle(left, top, right - left, bottom - top);
-    }
-
-    public static Dist(p1: PIXIPoint, p2: PIXIPoint): number {
-        return Math.sqrt(MathUtil.DistSquared(p1, p2));
-    }
-
-    public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
-        return p1.x * p2.x + p1.y * p2.y;
-    }
-
-    public static Normalize(p1: PIXIPoint) {
-        var d = this.Length(p1);
-        return new PIXIPoint(p1.x / d, p1.y / d);
-    }
-
-    public static Length(p1: PIXIPoint): number {
-        return Math.sqrt(p1.x * p1.x + p1.y * p1.y);
-    }
-
-    public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number {
-        const a = p1.x - p2.x;
-        const b = p1.y - p2.y;
-        return (a * a + b * b);
-    }
-
-    public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
-        return !(r2.x > r1.x + r1.width ||
-            r2.x + r2.width < r1.x ||
-            r2.y > r1.y + r1.height ||
-            r2.y + r2.height < r1.y);
-    }
-
-    public static ArgMin(temp: number[]): number {
-        let index = 0;
-        let value = temp[0];
-        for (let i = 1; i < temp.length; i++) {
-            if (temp[i] < value) {
-                value = temp[i];
-                index = i;
-            }
-        }
-        return index;
-    }
-
-    public static ArgMax(temp: number[]): number {
-        let index = 0;
-        let value = temp[0];
-        for (let i = 1; i < temp.length; i++) {
-            if (temp[i] > value) {
-                value = temp[i];
-                index = i;
-            }
-        }
-        return index;
-    }
-
-    public static Combinations<T>(chars: T[]) {
-        let result = new Array<T>();
-        let f = (prefix: any, chars: any) => {
-            for (let i = 0; i < chars.length; i++) {
-                result.push(prefix.concat(chars[i]));
-                f(prefix.concat(chars[i]), chars.slice(i + 1));
-            }
-        };
-        f([], chars);
-        return result;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.scss b/src/client/views/nodes/DataVizBox/HistogramBox.scss
deleted file mode 100644
index 5aac9dc77..000000000
--- a/src/client/views/nodes/DataVizBox/HistogramBox.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-// change the stroke color of line-svg class
-.svgLine {
-    position: absolute;
-    background: darkGray;
-    stroke: #000;
-    stroke-width: 1px;
-    width:100%;
-    height:100%;
-    opacity: 0.4;
-}
-
-.svgContainer {
-    position: absolute;
-    top:0;
-    left:0;
-    width:100%;
-    height: 100%;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx
deleted file mode 100644
index 4488323fe..000000000
--- a/src/client/views/nodes/DataVizBox/HistogramBox.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc } from "../../../../fields/Doc";
-import { NumCast } from "../../../../fields/Types";
-import { DragManager } from "../../../util/DragManager";
-import "./HistogramBox.scss";
-
-interface HistogramBoxProps {
-    rootDoc: Doc;
-    pairs: {
-        x: number,
-        y: number
-    }[]
-}
-
-
-export class HistogramBox extends React.Component<HistogramBoxProps> {
-    private _dropXDispoer?: DragManager.DragDropDisposer;
-    private _dropYDispoer?: DragManager.DragDropDisposer;
-
-    private origin = {x: 0.1 * this.width, y: 0.9 * this.height};
-
-    @computed get width() {
-        return NumCast(this.props.rootDoc.width);
-    }
-
-    @computed get height() {
-        return NumCast(this.props.rootDoc.height);
-    }
-
-    @computed get x() {
-        return NumCast(this.props.rootDoc.x);
-    }
-
-    @computed get y() {
-        return NumCast(this.props.rootDoc.y);
-    }
-
-    @computed get generatePoints() {
-        // evenly distribute points along the x axis
-        const xVals: number[] = this.props.pairs.map(p => p.x);
-        const yVals: number[] = this.props.pairs.map(p => p.y);
-
-        const xMin = Math.min(...xVals);
-        const xMax = Math.max(...xVals);
-        const yMin = Math.min(...yVals);
-        const yMax = Math.max(...yVals);
-
-        const xRange = xMax - xMin;
-        const yRange = yMax - yMin;
-
-        const xScale = this.width / xRange;
-        const yScale = this.height / yRange;
-
-        const xOffset = (this.x + (0.1 * this.width)) - xMin * xScale;
-        const yOffset = (this.y + (0.25 * this.height)) - yMin * yScale;
-
-        const points: {
-            x: number,
-            y: number
-        }[] = this.props.pairs.map(p => {
-            return {
-                x: (p.x * xScale + xOffset) + this.origin.x,
-                y: (p.y * yScale + yOffset)
-            }
-        });
-
-        return points;
-    }
-
-    @computed get generateGraphLine() {
-        const points = this.generatePoints;
-        // loop through points and create a line from each point to the next
-        let lines: {
-            x1: number,
-            y1: number,
-            x2: number,
-            y2: number
-        }[] = [];
-        for (let i = 0; i < points.length - 1; i++) {
-            lines.push({
-                x1: points[i].x,
-                y1: points[i].y,
-                x2: points[i + 1].x,
-                y2: points[i + 1].y
-            });
-        }
-        // generate array of svg with lines
-        let svgLines: JSX.Element[] = [];
-        for (let i = 0; i < lines.length; i++) {
-            svgLines.push(
-                <line
-                    className="svgLine"
-                    key={i}
-                    x1={lines[i].x1}
-                    y1={lines[i].y1}
-                    x2={lines[i].x2}
-                    y2={lines[i].y2}
-                    stroke="black"
-                    strokeWidth={2}
-                />
-            );
-        }
-
-        let res = [];
-        for (let i = 0; i < svgLines.length; i++) {
-            res.push(<svg className="svgContainer">{svgLines[i]}</svg>)
-        }
-        return res;
-    }
-
-    @computed get generateAxes() {       
-
-        const xAxis = {
-            x1: 0.1 * this.width,
-            x2: 0.9 * this.width,
-            y1: 0.9 * this.height,
-            y2: 0.9 * this.height,
-        };
-
-        const yAxis = {
-            x1: 0.1 * this.width,
-            x2: 0.1 * this.width,
-            y1: 0.25 * this.height,
-            y2: 0.9 * this.height,
-        };
-
-
-        return (
-            [
-                (<svg className="svgContainer">
-                    {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={this.width - (0.1 * this.width)} y2={xAxis} /> */}
-                    <line className="svgLine" x1={xAxis.x1} y1={xAxis.y1} x2={xAxis.x2} y2={xAxis.y2}/>
-
-                    {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */}
-                </svg>),
-                (
-                <svg className="svgContainer">
-                    <line className="svgLine" x1={yAxis.x1} y1={yAxis.y1} x2={yAxis.x2} y2={yAxis.y2} />
-                {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */}
-                </svg>)
-            ]
-        )
-    }
-
-
-    render() {
-        return (
-            <div>histogram box
-                {/* <svg className="svgContainer">
-                    {this.generateSVGLine}
-                </svg> */}
-                {this.generateAxes[0]}
-                {this.generateAxes[1]}
-                {this.generateGraphLine.map(line => line)}
-            </div>
-        )
-            
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx
deleted file mode 100644
index ac1b7699b..000000000
--- a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { observer } from "mobx-react";
-import * as React from "react";
-import { HistogramBox } from "./HistogramBox";
-
-export interface HistogramPrimitivesProps {
-    HistoBox: HistogramBox;
-}
-
-@observer
-export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> {
-    render() {
-        return <div className="histogramboxprimitives-container">
-            
-        </div>
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx
deleted file mode 100644
index d3440dea1..000000000
--- a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { observer } from "mobx-react";
-import * as React from "react";
-import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
-import "./HistogramLabelPrimitives.scss";
-
-@observer
-export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
-    render() {
-        return <div className="histogramLabelPrimitives-container">
-
-        </div>
-    }    
-}
diff --git a/src/client/views/nodes/DataVizBox/LineChart.tsx b/src/client/views/nodes/DataVizBox/LineChart.tsx
deleted file mode 100644
index 0aeaacf34..000000000
--- a/src/client/views/nodes/DataVizBox/LineChart.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { ChartData, DataPoint } from './ChartBox';
-// import d3
-import * as d3 from 'd3';
-import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from './utils/D3Utils';
-import { Docs } from '../../../documents/Documents';
-import { Doc, DocListCast } from '../../../../fields/Doc';
-
-interface LineChartProps {
-    data: ChartData;
-    width: number;
-    height: number;
-    dataDoc: Doc;
-    fieldKey: string;
-    // returns linechart component but should be generic chart
-    setCurrChart: (chart: LineChart | undefined) => void;
-    getAnchor: () => Doc;
-    margin: {
-        top: number;
-        right: number;
-        bottom: number;
-        left: number;
-    };
-}
-
-type minMaxRange = {
-    xMin: number | undefined;
-    xMax: number | undefined;
-    yMin: number | undefined;
-    yMax: number | undefined;
-};
-
-interface SelectedDataPoint {
-    x: number;
-    y: number;
-    elem: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
-}
-
-@observer
-export class LineChart extends React.Component<LineChartProps> {
-    private _dataReactionDisposer: IReactionDisposer | undefined = undefined;
-    private _heightReactionDisposer: IReactionDisposer | undefined = undefined;
-    private _widthReactionDisposer: IReactionDisposer | undefined;
-    @observable private _x: number = 0;
-    @observable private _y: number = 0;
-    @observable private _currSelected: SelectedDataPoint | undefined = undefined;
-    // create ref for the div
-    private _chartRef: React.RefObject<HTMLDivElement> = React.createRef();
-    private _rangeVals: minMaxRange = {
-        xMin: undefined,
-        xMax: undefined,
-        yMin: undefined,
-        yMax: undefined,
-    };
-
-    // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that
-
-    // write the getanchor function that gets whatever I want as the link anchor
-
-    componentDidMount() {
-        // this._rangeVals = minMaxRange(this.props.data.data);
-        // this.drawChart();
-        this._dataReactionDisposer = reaction(
-            () => this.props.data,
-            data => {
-                this._rangeVals = minMaxRange(data.data);
-                this.drawChart();
-            },
-            { fireImmediately: true }
-        );
-        this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this));
-        this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this));
-        reaction(
-            () => DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']),
-            annotations => {
-                // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
-                // could be blue colored to make it look like anchor
-            },
-            { fireImmediately: true }
-        );
-
-        setTimeout(() => {
-            this.props.setCurrChart(this);
-        });
-    }
-
-    // gets called whenever the "data-annotations" fields gets updated
-    drawAnnotations() {}
-
-    _getAnchor() {
-        // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
-
-        // TODO: nda - look at pdfviewer get anchor for args
-        const doc = Docs.Create.TextanchorDocument({
-            /*put in some options*/
-        });
-        // access curr selected from the charts
-        doc.x = this._currSelected?.x;
-        doc.y = this._currSelected?.y;
-        doc.chartType = 'line';
-        return doc;
-        // have some other function in code that
-    }
-
-    componentWillUnmount() {
-        if (this._dataReactionDisposer) {
-            this._dataReactionDisposer();
-        }
-        if (this._heightReactionDisposer) {
-            this._heightReactionDisposer();
-        }
-        if (this._widthReactionDisposer) {
-            this._widthReactionDisposer();
-        }
-    }
-
-    tooltipContent(data: DataPoint) {
-        return `<b>x: ${data.x} y: ${data.y}</b>`;
-    }
-
-    @computed get height(): number {
-        return this.props.height - this.props.margin.top - this.props.margin.bottom;
-    }
-
-    @computed get width(): number {
-        return this.props.width - this.props.margin.left - this.props.margin.right;
-    }
-
-    @computed get svgContainer() {
-        const { margin } = this.props;
-        const svg = d3
-            .create('svg')
-            .attr('width', `${this.width + margin.right + margin.left}`)
-            .attr('height', `${this.height + margin.top + margin.bottom}`)
-            .append('g')
-            .attr('transform', `translate(${margin.left}, ${margin.top})`);
-        return svg;
-    }
-
-    // TODO: nda - can use d3.create() to create html element instead of appending
-    drawChart() {
-        const { data, margin } = this.props;
-        console.log(this.height, this.width);
-        // clearing tooltip
-        d3.select(this._chartRef.current).select('svg').remove();
-        d3.select(this._chartRef.current).select('.tooltip').remove();
-
-        // TODO: nda - refactor code so that it only recalculates min max and things related to data on data update
-
-        const { xMin, xMax, yMin, yMax } = this._rangeVals;
-        // const svg = d3.select(this._chartRef.current).append(this.svgContainer.html());
-        // adding svg
-        const svg = d3
-            .select(this._chartRef.current)
-            .append('svg')
-            .attr('width', `${this.width + margin.right + margin.left}`)
-            .attr('height', `${this.height + margin.top + margin.bottom}`)
-            .append('g')
-            .attr('transform', `translate(${margin.left}, ${margin.top})`);
-
-        if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) {
-            // TODO: nda - error handle
-            return;
-        }
-
-        // creating the x and y scales
-        const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.width);
-        const yScale = scaleCreatorNumerical(0, yMax, this.height, 0);
-
-        // create a line function that takes in the data.data.x and data.data.y
-        // TODO: nda - fix the types for the d here
-        const lineGen = createLineGenerator(xScale, yScale);
-
-        // create x and y grids
-        xGrid(svg.append('g'), this.height, xScale);
-        yGrid(svg.append('g'), this.width, yScale);
-        xAxisCreator(svg.append('g'), this.height, xScale);
-        yAxisCreator(svg.append('g'), this.width, yScale);
-
-        // draw the line
-        drawLine(svg.append('path'), data.data, lineGen);
-
-        // draw the datapoint circles
-        svg.selectAll('.circle-d1')
-            .data(data.data)
-            .join('circle') // enter append
-            .attr('class', 'circle-d1')
-            .attr('r', '3') // radius
-            .attr('cx', d => xScale(d.x))
-            .attr('cy', d => yScale(d.y))
-            .attr('data-x', d => d.x)
-            .attr('data-y', d => d.y);
-
-        const focus = svg.append('g').attr('class', 'focus').style('display', 'none');
-        focus.append('circle').attr('r', 5).attr('class', 'circle');
-        const tooltip = d3
-            .select(this._chartRef.current)
-            .append('div')
-            .attr('class', 'tooltip')
-            .style('opacity', 0)
-            .style('background', '#fff')
-            .style('border', '1px solid #ccc')
-            .style('padding', '5px')
-            .style('position', 'absolute')
-            .style('font-size', '12px');
-        // add all the tooltipContent to the tooltip
-        // @action
-        const mousemove = action((e: any) => {
-            const bisect = d3.bisector((d: DataPoint) => d.x).left;
-            const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data.data, xScale.invert(xPos));
-            const d0 = data.data[x0];
-            this._x = d0.x;
-            this._y = d0.y;
-            focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`);
-            // TODO: nda - implement tooltips
-            tooltip.transition().duration(300).style('opacity', 0.9);
-            // TODO: nda - updating the inner html could be deadly cause injection attacks!
-            // 0 = 30px => -597px (-this.width - margin.left - margin.right)
-            // 1 =
-            tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right)}px,${yScale(d0.y) + 30}px)`);
-        });
-
-        const onPointClick = action((e: any) => {
-            const bisect = d3.bisector((d: DataPoint) => d.x).left;
-            const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data.data, xScale.invert(xPos));
-            const d0 = data.data[x0];
-            this._x = d0.x;
-            this._y = d0.y;
-            // find .circle-d1 with data-x = d0.x and data-y = d0.y
-            const selected = svg.selectAll('.circle-d1').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
-            this._currSelected = { x: d0.x, y: d0.y, elem: selected };
-            console.log('Getting here');
-            setTimeout(() => this.props.getAnchor());
-            // this.props.getAnchor();
-            console.log(this._currSelected);
-        });
-
-        svg.append('rect')
-            .attr('class', 'overlay')
-            .attr('width', this.width)
-            .attr('height', this.height + margin.top + margin.bottom)
-            .attr('fill', 'none')
-            .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`)
-            .style('opacity', 0)
-            .on('mouseover', () => {
-                focus.style('display', null);
-            })
-            .on('mouseout', () => {
-                tooltip.transition().duration(300).style('opacity', 0);
-            })
-            .on('mousemove', mousemove)
-            .on('click', onPointClick);
-    }
-
-    render() {
-        return (
-            <div ref={this._chartRef} className="chart-container">
-                <span>
-                    x: {this._x} y: {this._y}
-                    Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'}
-                </span>
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/nodes/DataVizBox/TableBox.scss b/src/client/views/nodes/DataVizBox/TableBox.scss
deleted file mode 100644
index 1264d6a46..000000000
--- a/src/client/views/nodes/DataVizBox/TableBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.table {
-    margin-top: 10px;
-    margin-bottom: 10px;
-    margin-left: 10px;
-    margin-right: 10px;
-}
-
-.table-row {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-    align-items: center;
-    padding: 5px;
-    border-bottom: 1px solid #ccc;
-}
-
-.table-container {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    align-items: center;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/TableBox.tsx b/src/client/views/nodes/DataVizBox/TableBox.tsx
deleted file mode 100644
index dfa8262d8..000000000
--- a/src/client/views/nodes/DataVizBox/TableBox.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-
-interface TableBoxProps {
-    pairs: {x: number, y:number}[]
-}
-
-
-export class TableBox extends React.Component<TableBoxProps> {
-
-
-
-    render() {
-        return (
-            <div className="table-container">
-                <table className="table">
-                    <thead>
-                        <tr className="table-row">
-                            <th>x</th>
-                            <th>y</th>
-                        </tr>
-                    </thead>
-                    <tbody>
-                    {this.props.pairs.map(p => {
-                        return (<tr className="table-row">
-                            <td>{p.x}</td>
-                            <td>{p.y}</td>
-                        </tr>)
-                    })}
-                    </tbody>
-                </table>
-            </div>
-        )
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
new file mode 100644
index 000000000..7792a2758
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -0,0 +1,10 @@
+.tooltip {
+    // make the height width bigger
+    width: 50px;
+    height: 50px;
+}
+
+.highlight {
+    // change the color of the circle element to be red
+    fill: red;
+}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
new file mode 100644
index 000000000..88598be3d
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -0,0 +1,270 @@
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { DataPoint } from '../ChartBox';
+// import d3
+import * as d3 from 'd3';
+import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from '../utils/D3Utils';
+import { Docs } from '../../../../documents/Documents';
+import { Doc, DocListCast } from '../../../../../fields/Doc';
+import { Chart, ChartProps } from '../ChartInterface';
+import './Chart.scss';
+
+type minMaxRange = {
+    xMin: number | undefined;
+    xMax: number | undefined;
+    yMin: number | undefined;
+    yMax: number | undefined;
+};
+
+interface SelectedDataPoint {
+    x: number;
+    y: number;
+    elem: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
+}
+
+@observer
+export class LineChart extends React.Component<ChartProps> implements Chart {
+    private _dataReactionDisposer: IReactionDisposer | undefined = undefined;
+    private _heightReactionDisposer: IReactionDisposer | undefined = undefined;
+    private _widthReactionDisposer: IReactionDisposer | undefined;
+    @observable private _x: number = 0;
+    @observable private _y: number = 0;
+    @observable private _currSelected: SelectedDataPoint | undefined = undefined;
+    // create ref for the div
+    private _chartRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _rangeVals: minMaxRange = {
+        xMin: undefined,
+        xMax: undefined,
+        yMin: undefined,
+        yMax: undefined,
+    };
+
+    private _chartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
+
+    // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that
+
+    // write the getanchor function that gets whatever I want as the link anchor
+
+    componentDidMount() {
+        this._dataReactionDisposer = reaction(
+            () => this.props.chartData,
+            chartData => {
+                this._rangeVals = minMaxRange(chartData.data);
+                this.drawChart();
+            },
+            { fireImmediately: true }
+        );
+        this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this));
+        this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this));
+        reaction(
+            () => DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']),
+            annotations => {
+                // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
+                // could be blue colored to make it look like anchor
+                this.drawAnnotations(this.props.chartData.data[0][1].x, this.props.chartData.data[0][1].y);
+            },
+            { fireImmediately: true }
+        );
+
+        setTimeout(() => {
+            this.props.setCurrChart(this);
+        });
+    }
+
+    // gets called whenever the "data-annotations" fields gets updated
+    drawAnnotations(dataX: number, dataY: number) {
+        // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements
+        // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY
+        // if it exists, then highlight it
+        // if it doesn't exist, then remove the highlight
+        const elements = document.querySelectorAll('datapoint');
+        for (let i = 0; i < elements.length; i++) {
+            const element = elements[i];
+            const x = element.getAttribute('data-x');
+            const y = element.getAttribute('data-y');
+            if (x === dataX.toString() && y === dataY.toString()) {
+                element.classList.add('highlight');
+            } else {
+                element.classList.remove('highlight');
+            }
+        }
+    }
+
+    _getAnchor() {
+        // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
+
+        // TODO: nda - look at pdfviewer get anchor for args
+        const doc = Docs.Create.TextanchorDocument({
+            /*put in some options*/
+        });
+        // access curr selected from the charts
+        doc.x = this._currSelected?.x;
+        doc.y = this._currSelected?.y;
+        doc.chartType = 'line';
+        return doc;
+        // have some other function in code that
+    }
+
+    componentWillUnmount() {
+        if (this._dataReactionDisposer) {
+            this._dataReactionDisposer();
+        }
+        if (this._heightReactionDisposer) {
+            this._heightReactionDisposer();
+        }
+        if (this._widthReactionDisposer) {
+            this._widthReactionDisposer();
+        }
+    }
+
+    tooltipContent(data: DataPoint) {
+        return `<b>x: ${data.x} y: ${data.y}</b>`;
+    }
+
+    @computed get height(): number {
+        return this.props.height - this.props.margin.top - this.props.margin.bottom;
+    }
+
+    @computed get width(): number {
+        return this.props.width - this.props.margin.left - this.props.margin.right;
+    }
+
+    setupTooltip() {
+        const tooltip = d3
+            .select(this._chartRef.current)
+            .append('div')
+            .attr('class', 'tooltip')
+            .style('opacity', 0)
+            .style('background', '#fff')
+            .style('border', '1px solid #ccc')
+            .style('padding', '5px')
+            .style('position', 'absolute')
+            .style('font-size', '12px');
+        return tooltip;
+    }
+
+    drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
+        if (this._chartSvg) {
+            const circleClass = '.circle-' + idx;
+            this._chartSvg
+                .selectAll(circleClass)
+                .data(data)
+                .join('circle') // enter append
+                .attr('class', `${circleClass} datapoint`)
+                .attr('r', '3') // radius
+                .attr('cx', d => xScale(d.x))
+                .attr('cy', d => yScale(d.y))
+                .attr('data-x', d => d.x)
+                .attr('data-y', d => d.y);
+        }
+    }
+
+    // TODO: nda - can use d3.create() to create html element instead of appending
+    drawChart() {
+        const { chartData, margin } = this.props;
+        const data = chartData.data[0];
+        // clearing tooltip and the current chart
+        d3.select(this._chartRef.current).select('svg').remove();
+        d3.select(this._chartRef.current).select('.tooltip').remove();
+
+        // TODO: nda - refactor code so that it only recalculates min max and things related to data on data update
+
+        const { xMin, xMax, yMin, yMax } = this._rangeVals;
+        // const svg = d3.select(this._chartRef.current).append(this.svgContainer.html());
+        // adding svg
+        this._chartSvg = d3
+            .select(this._chartRef.current)
+            .append('svg')
+            .attr('width', `${this.width + margin.right + margin.left}`)
+            .attr('height', `${this.height + margin.top + margin.bottom}`)
+            .append('g')
+            .attr('transform', `translate(${margin.left}, ${margin.top})`);
+
+        const svg = this._chartSvg;
+
+        if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) {
+            // TODO: nda - error handle
+            return;
+        }
+
+        // creating the x and y scales
+        const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.width);
+        const yScale = scaleCreatorNumerical(0, yMax, this.height, 0);
+
+        // create a line function that takes in the data.data.x and data.data.y
+        // TODO: nda - fix the types for the d here
+        const lineGen = createLineGenerator(xScale, yScale);
+
+        // create x and y grids
+        xGrid(svg.append('g'), this.height, xScale);
+        yGrid(svg.append('g'), this.width, yScale);
+        xAxisCreator(svg.append('g'), this.height, xScale);
+        yAxisCreator(svg.append('g'), this.width, yScale);
+
+        // draw the line
+        drawLine(svg.append('path'), data, lineGen);
+
+        // draw the datapoint circle
+        this.drawDataPoints(data, 0, xScale, yScale);
+
+        const focus = svg.append('g').attr('class', 'focus').style('display', 'none');
+        focus.append('circle').attr('r', 5).attr('class', 'circle');
+        const tooltip = this.setupTooltip();
+        // add all the tooltipContent to the tooltip
+        const mousemove = action((e: any) => {
+            const bisect = d3.bisector((d: DataPoint) => d.x).left;
+            const xPos = d3.pointer(e)[0];
+            const x0 = bisect(data, xScale.invert(xPos));
+            const d0 = data[x0];
+            this._x = d0.x;
+            this._y = d0.y;
+            focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`);
+            // TODO: nda - implement tooltips
+            tooltip.transition().duration(300).style('opacity', 0.9);
+            // TODO: nda - updating the inner html could be deadly cause injection attacks!
+            tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right) + 30}px,${yScale(d0.y) + 30}px)`);
+        });
+
+        const onPointClick = action((e: any) => {
+            const bisect = d3.bisector((d: DataPoint) => d.x).left;
+            const xPos = d3.pointer(e)[0];
+            const x0 = bisect(data, xScale.invert(xPos));
+            const d0 = data[x0];
+            this._x = d0.x;
+            this._y = d0.y;
+            // find .circle-d1 with data-x = d0.x and data-y = d0.y
+            const selected = svg.selectAll('.circle-d1').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
+            this._currSelected = { x: d0.x, y: d0.y, elem: selected };
+            console.log('Getting here');
+            setTimeout(() => this.props.getAnchor());
+            // this.props.getAnchor();
+            console.log(this._currSelected);
+        });
+
+        this._chartSvg
+            .append('rect')
+            .attr('class', 'overlay')
+            .attr('width', this.width)
+            .attr('height', this.height + margin.top + margin.bottom)
+            .attr('fill', 'none')
+            .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`)
+            .style('opacity', 0)
+            .on('mouseover', () => {
+                focus.style('display', null);
+            })
+            .on('mouseout', () => {
+                tooltip.transition().duration(300).style('opacity', 0);
+            })
+            .on('mousemove', mousemove)
+            .on('click', onPointClick);
+    }
+
+    render() {
+        return (
+            <div ref={this._chartRef} className="chart-container">
+                <span>Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'}</span>
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.scss b/src/client/views/nodes/DataVizBox/components/TableBox.scss
new file mode 100644
index 000000000..1264d6a46
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.scss
@@ -0,0 +1,22 @@
+.table {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    margin-left: 10px;
+    margin-right: 10px;
+}
+
+.table-row {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    padding: 5px;
+    border-bottom: 1px solid #ccc;
+}
+
+.table-container {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
new file mode 100644
index 000000000..dfa8262d8
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -0,0 +1,37 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+
+interface TableBoxProps {
+    pairs: {x: number, y:number}[]
+}
+
+
+export class TableBox extends React.Component<TableBoxProps> {
+
+
+
+    render() {
+        return (
+            <div className="table-container">
+                <table className="table">
+                    <thead>
+                        <tr className="table-row">
+                            <th>x</th>
+                            <th>y</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                    {this.props.pairs.map(p => {
+                        return (<tr className="table-row">
+                            <td>{p.x}</td>
+                            <td>{p.y}</td>
+                        </tr>)
+                    })}
+                    </tbody>
+                </table>
+            </div>
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 5c7f9bce1..2bb091999 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -3,13 +3,13 @@ import { DataPoint } from '../ChartBox';
 
 // TODO: nda - implement function that can handle range for strings
 
-export const minMaxRange = (dataPts: DataPoint[]) => {
-    console.log(Number(dataPts[4].y));
-    const yMin = d3.min(dataPts, d => Number(d.y));
-    const yMax = d3.max(dataPts, d => Number(d.y));
+export const minMaxRange = (dataPts: DataPoint[][]) => {
+    // find the max and min of all the data points
+    const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y)));
+    const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y)));
 
-    const xMin = d3.min(dataPts, d => Number(d.x));
-    const xMax = d3.max(dataPts, d => Number(d.x));
+    const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x)));
+    const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x)));
 
     return { xMin, xMax, yMin, yMax };
 };
-- 
cgit v1.2.3-70-g09d2


From 50797032a67c6e3920edd8ab38e6453a17674cb8 Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Wed, 27 Jul 2022 13:57:26 -0400
Subject: this.addDocument does not exist

---
 src/client/views/nodes/DataVizBox/ChartBox.tsx             | 10 +++++-----
 src/client/views/nodes/DataVizBox/ChartInterface.ts        |  2 ++
 src/client/views/nodes/DataVizBox/DataVizBox.tsx           |  6 +++---
 src/client/views/nodes/DataVizBox/components/LineChart.tsx |  6 +++---
 4 files changed, 13 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index a2e4fd73c..34eee8ee8 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -1,8 +1,8 @@
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, HeightSym } from '../../../../fields/Doc';
+import { Doc } from '../../../../fields/Doc';
 import { action, computed, observable } from 'mobx';
-import { Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { NumCast, StrCast } from '../../../../fields/Types';
 import { LineChart } from './components/LineChart';
 import { Chart, ChartData } from './ChartInterface';
 
@@ -22,7 +22,7 @@ export interface DataPoint {
 @observer
 export class ChartBox extends React.Component<ChartBoxProps> {
     @observable private _chartData: ChartData | undefined = undefined;
-    @observable private currChart: LineChart | undefined;
+    private currChart: Chart | undefined;
 
     @computed get currView() {
         if (this.props.rootDoc._dataVizView) {
@@ -39,8 +39,8 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         }
     }
 
-    setCurrChart(chart: Chart | undefined) {
-        // this.currChart = chart;
+    setCurrChart(chart: Chart) {
+        this.currChart = chart;
     }
 
     @action
diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts
index 6e37f966c..05ef35b95 100644
--- a/src/client/views/nodes/DataVizBox/ChartInterface.ts
+++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts
@@ -7,6 +7,8 @@ export interface Chart {
     drawChart: () => void;
     height: number;
     width: number;
+    // TODO: nda - probably want to get rid of this to keep charts more generic
+    _getAnchor: () => Doc;
 }
 
 export interface ChartProps {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 1d9b309ce..944468a43 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,11 +1,11 @@
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, FieldResult } from '../../../../fields/Doc';
-import { Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { Doc } from '../../../../fields/Doc';
+import { Cast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
 import { Docs } from '../../../documents/Documents';
-import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { FieldViewProps, FieldView } from '../FieldView';
 import { ChartBox } from './ChartBox';
 import './DataVizBox.scss';
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 88598be3d..458ff5f23 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -62,7 +62,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             annotations => {
                 // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
                 // could be blue colored to make it look like anchor
-                this.drawAnnotations(this.props.chartData.data[0][1].x, this.props.chartData.data[0][1].y);
+                this.drawAnnotations(this.props.chartData.data[0][4].x, this.props.chartData.data[0][4].y);
             },
             { fireImmediately: true }
         );
@@ -78,7 +78,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY
         // if it exists, then highlight it
         // if it doesn't exist, then remove the highlight
-        const elements = document.querySelectorAll('datapoint');
+        const elements = document.querySelectorAll('.datapoint');
         for (let i = 0; i < elements.length; i++) {
             const element = elements[i];
             const x = element.getAttribute('data-x');
@@ -234,7 +234,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             this._x = d0.x;
             this._y = d0.y;
             // find .circle-d1 with data-x = d0.x and data-y = d0.y
-            const selected = svg.selectAll('.circle-d1').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
+            const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
             this._currSelected = { x: d0.x, y: d0.y, elem: selected };
             console.log('Getting here');
             setTimeout(() => this.props.getAnchor());
-- 
cgit v1.2.3-70-g09d2


From c80d0763c87d1965f451bbd7b31d8da8228dc048 Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Wed, 27 Jul 2022 23:04:22 -0400
Subject: got basic scroll focus to work

---
 src/client/views/nodes/DataVizBox/ChartBox.tsx     | 38 +++++++++++++-------
 .../views/nodes/DataVizBox/ChartInterface.ts       |  1 +
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 22 +++++++++---
 .../nodes/DataVizBox/components/LineChart.tsx      | 40 ++++++++++++++++------
 4 files changed, 75 insertions(+), 26 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index 34eee8ee8..c229e5471 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -39,9 +39,9 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         }
     }
 
-    setCurrChart(chart: Chart) {
+    setCurrChart = (chart: Chart) => {
         this.currChart = chart;
-    }
+    };
 
     @action
     generateChartData() {
@@ -69,14 +69,14 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this.props.rootDoc._chartData = JSON.stringify(data);
     }
 
-    componentDidMount() {
+    componentDidMount = () => {
         this.generateChartData();
         // setting timeout to allow rerender cycle of the actual chart tof inish
-        setTimeout(() => {
-            this.props.setChartBox(this);
-        });
+        // setTimeout(() => {
+        this.props.setChartBox(this);
+        // });
         // this.props.setChartBox(this);
-    }
+    };
 
     @action
     onClickChangeChart = (e: React.MouseEvent<HTMLButtonElement>) => {
@@ -86,11 +86,25 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase();
     };
 
-    scrollFocus(doc: Doc, smooth: boolean) {}
-
-    handleDotClick = (e: any) => {
-        console.log(e);
-    };
+    scrollFocus(doc: Doc, smooth: boolean): number | undefined {
+        // if x and y exist on this
+        // then highlight/focus the x and y position (datapoint)
+        // highlight for a few seconds and then remove (set a timer to unhighlight after a period of time,
+        //  to be consistent, use 5 seconds and highlight color is orange)
+        // if x and y don't exist => link to whole doc => dash will take care of focusing on the entire doc
+        // return value is how long this action takes
+        //  undefined => immediately, 0 => introduces a timeout
+        //  number in ms => won't do any of the focus or call the automated highlighting thing until this timeout expires
+        //      in this case probably number in ms doesn't matter
+
+        // for now could just update the currSelected in linechart to be what doc.x and doc.y
+
+        if (this.currChart && doc.x && doc.y) {
+            // update curr selected
+            this.currChart.setCurrSelected(Number(doc.x), Number(doc.y));
+        }
+        return 0;
+    }
 
     _getAnchor() {
         return this.currChart?._getAnchor();
diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts
index 05ef35b95..494242ac5 100644
--- a/src/client/views/nodes/DataVizBox/ChartInterface.ts
+++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts
@@ -9,6 +9,7 @@ export interface Chart {
     width: number;
     // TODO: nda - probably want to get rid of this to keep charts more generic
     _getAnchor: () => Doc;
+    setCurrSelected: (x: number, y: number) => void;
 }
 
 export interface ChartProps {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 944468a43..db677ff61 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -68,16 +68,29 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         // use dataview to restore part of it and then pass on the rest to the chartbox
 
         // pseudocode
-        setTimeout(() => this._chartBox?.scrollFocus(doc, smooth)); /* smooth parameter true = do animations */
-        return 0;
+        return this._chartBox?.scrollFocus(doc, smooth);
     };
 
-    getAnchor() {
+    getAnchor = () => {
+        // TODO: nda - look into DocumentButtonBar and DocumentLinksButton
+
+        /*
+        if nothing selected:
+            return this.rootDoc
+        this part does not specify view type (doesn't change type when following the link)
+
+            // this slightly diff than the stuff below: 
+                below part returns an anchor that specifies the viewtype for the whole doc and nothing else
+                
+        */
+
         // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
         const anchor =
             // this._COMPONENTNAME._getAnchor() ??
             this._chartBox?._getAnchor() ??
             Docs.Create.TextanchorDocument({
+                // when we clear selection -> we should have it so chartBox getAnchor returns undefined
+                // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
                 /*put in some options*/
             });
 
@@ -86,7 +99,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.addDocument(anchor);
         return anchor;
         // have some other function in code that
-    }
+    };
     constructor(props: any) {
         super(props);
         if (!this.rootDoc._dataVizView) {
@@ -119,6 +132,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     componentDidMount() {
+        this.props.setContentView?.(this);
         // this.createPairs();
         this.fetchData();
     }
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 458ff5f23..d893b3e12 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -20,7 +20,7 @@ type minMaxRange = {
 interface SelectedDataPoint {
     x: number;
     y: number;
-    elem: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
+    elem?: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
 }
 
 @observer
@@ -39,6 +39,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         yMin: undefined,
         yMax: undefined,
     };
+    // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
 
     private _chartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
 
@@ -46,7 +47,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
 
     // write the getanchor function that gets whatever I want as the link anchor
 
-    componentDidMount() {
+    componentDidMount = () => {
         this._dataReactionDisposer = reaction(
             () => this.props.chartData,
             chartData => {
@@ -55,6 +56,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             },
             { fireImmediately: true }
         );
+        // DocumentDecorations.Instance.Interacting
         this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this));
         this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this));
         reaction(
@@ -62,15 +64,19 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             annotations => {
                 // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
                 // could be blue colored to make it look like anchor
-                this.drawAnnotations(this.props.chartData.data[0][4].x, this.props.chartData.data[0][4].y);
+                console.log(annotations);
+                // this.drawAnnotations()
+                // loop through annotations and draw them
+                annotations.forEach(a => {
+                    this.drawAnnotations(Number(a.x), Number(a.y));
+                });
+                // this.drawAnnotations(annotations.x, annotations.y);
             },
             { fireImmediately: true }
         );
 
-        setTimeout(() => {
-            this.props.setCurrChart(this);
-        });
-    }
+        this.props.setCurrChart(this);
+    };
 
     // gets called whenever the "data-annotations" fields gets updated
     drawAnnotations(dataX: number, dataY: number) {
@@ -85,18 +91,25 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             const y = element.getAttribute('data-y');
             if (x === dataX.toString() && y === dataY.toString()) {
                 element.classList.add('highlight');
-            } else {
-                element.classList.remove('highlight');
             }
+            // TODO: nda - this remove highlight code should go where we remove the links
+            // } else {
+            //     element.classList.remove('highlight');
+            // }
         }
     }
 
+    removeAnnotations(dataX: number, dataY: number) {
+        // loop through and remove any annotations that no longer exist
+    }
+
     _getAnchor() {
         // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
 
         // TODO: nda - look at pdfviewer get anchor for args
         const doc = Docs.Create.TextanchorDocument({
             /*put in some options*/
+            title: 'line doc selection' + this._currSelected?.x,
         });
         // access curr selected from the charts
         doc.x = this._currSelected?.x;
@@ -144,6 +157,13 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         return tooltip;
     }
 
+    // TODO: nda - use this everyewhere we update currSelected?
+    @action
+    setCurrSelected(x: number, y: number) {
+        // TODO: nda - get rid of svg element in the list?
+        this._currSelected = { x: x, y: y };
+    }
+
     drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
         if (this._chartSvg) {
             const circleClass = '.circle-' + idx;
@@ -237,7 +257,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
             this._currSelected = { x: d0.x, y: d0.y, elem: selected };
             console.log('Getting here');
-            setTimeout(() => this.props.getAnchor());
+            // this.drawAnnotations(this._x, this._y);
             // this.props.getAnchor();
             console.log(this._currSelected);
         });
-- 
cgit v1.2.3-70-g09d2


From 99fba6d6e99da0154d40d2e3e87e120d8e6f2810 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Apr 2023 22:44:38 -0400
Subject: fixed up dataviz to work again.  fixed point selection, tooltips,
 making and following links

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/nodes/DataVizBox/ChartBox.tsx     | 62 ++++-----------------
 .../views/nodes/DataVizBox/ChartInterface.ts       |  6 +--
 src/client/views/nodes/DataVizBox/DataVizBox.scss  |  4 ++
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 63 ++++++++++------------
 .../views/nodes/DataVizBox/components/Chart.scss   |  9 +++-
 .../nodes/DataVizBox/components/LineChart.tsx      | 41 ++++++++------
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/trails/PresBox.tsx          |  9 +++-
 src/server/DataVizUtils.ts                         | 22 ++++----
 11 files changed, 98 insertions(+), 125 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c4014a752..1f09cdd3d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1148,8 +1148,8 @@ export namespace Docs {
             return Prototypes.get(DocumentType.PRESELEMENT);
         }
 
-        export function DataVizDocument(url: string, options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options });
+        export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) {
+            return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc);
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9aceed366..623aaf357 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -273,7 +273,7 @@ export class CurrentUserUtils {
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List<string>(["system"]) }},
             {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
-            // {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument(opts),         opts: { _width: 300, _height: 300 }},
+            {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}},
             {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }},
diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index c229e5471..ee577b9fd 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -5,10 +5,12 @@ import { action, computed, observable } from 'mobx';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { LineChart } from './components/LineChart';
 import { Chart, ChartData } from './ChartInterface';
+import { PinProps } from '../trails';
 
 export interface ChartBoxProps {
     rootDoc: Doc;
     dataDoc: Doc;
+    height: number;
     pairs: { x: number; y: number }[];
     setChartBox: (chartBox: ChartBox) => void;
     getAnchor: () => Doc;
@@ -71,11 +73,7 @@ export class ChartBox extends React.Component<ChartBoxProps> {
 
     componentDidMount = () => {
         this.generateChartData();
-        // setting timeout to allow rerender cycle of the actual chart tof inish
-        // setTimeout(() => {
         this.props.setChartBox(this);
-        // });
-        // this.props.setChartBox(this);
     };
 
     @action
@@ -86,59 +84,17 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase();
     };
 
-    scrollFocus(doc: Doc, smooth: boolean): number | undefined {
-        // if x and y exist on this
-        // then highlight/focus the x and y position (datapoint)
-        // highlight for a few seconds and then remove (set a timer to unhighlight after a period of time,
-        //  to be consistent, use 5 seconds and highlight color is orange)
-        // if x and y don't exist => link to whole doc => dash will take care of focusing on the entire doc
-        // return value is how long this action takes
-        //  undefined => immediately, 0 => introduces a timeout
-        //  number in ms => won't do any of the focus or call the automated highlighting thing until this timeout expires
-        //      in this case probably number in ms doesn't matter
+    getAnchor = (pinProps?: PinProps) => this.currChart?._getAnchor(pinProps);
 
-        // for now could just update the currSelected in linechart to be what doc.x and doc.y
-
-        if (this.currChart && doc.x && doc.y) {
-            // update curr selected
-            this.currChart.setCurrSelected(Number(doc.x), Number(doc.y));
-        }
-        return 0;
-    }
-
-    _getAnchor() {
-        return this.currChart?._getAnchor();
-    }
+    restoreView = (data: Doc) => this.currChart?.restoreView(data);
 
     render() {
         if (this.props.pairs && this._chartData) {
-            let width = NumCast(this.props.rootDoc._width);
-            width = width * 0.7;
-            let height = NumCast(this.props.rootDoc._height);
-            height = height * 0.7;
-            console.log(width, height);
-            const margin = { top: 50, right: 50, bottom: 50, left: 50 };
-            return (
-                <div>
-                    <div>
-                        {/*{this.props.rootDoc._currChartView == 'line' ? (
-                            <Line ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} />
-                        ) : (
-                            <Bar ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} />
-                        )} */}
-                        {/* {this.reLineChart} */}
-                        <LineChart margin={margin} width={width} height={height} chartData={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />
-                    </div>
-                    <button onClick={e => this.onClickChangeChart(e)} value="line">
-                        Line
-                    </button>
-                    <button onClick={e => this.onClickChangeChart(e)} value="bar">
-                        Bar
-                    </button>
-                </div>
-            );
-        } else {
-            return <div></div>;
+            const width = NumCast(this.props.rootDoc._width) * 0.9;
+            const height = this.props.height * 0.9;
+            const margin = { top: 10, right: 50, bottom: 50, left: 50 };
+            return <LineChart margin={margin} width={width} height={height} chartData={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />;
         }
+        return <div />;
     }
 }
diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts
index 494242ac5..8ebcc2a5f 100644
--- a/src/client/views/nodes/DataVizBox/ChartInterface.ts
+++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts
@@ -1,15 +1,15 @@
 import { Doc } from '../../../../fields/Doc';
+import { PinProps } from '../trails';
 import { DataPoint } from './ChartBox';
-import { LineChart } from './components/LineChart';
 
 export interface Chart {
     tooltipContent: (data: DataPoint) => string;
     drawChart: () => void;
+    restoreView: (data: any) => boolean;
     height: number;
     width: number;
     // TODO: nda - probably want to get rid of this to keep charts more generic
-    _getAnchor: () => Doc;
-    setCurrSelected: (x: number, y: number) => void;
+    _getAnchor: (pinProps?: PinProps) => Doc;
 }
 
 export interface ChartProps {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index e69de29bb..cd500e9ae 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -0,0 +1,4 @@
+.dataviz {
+    overflow: auto;
+    height: 100%;
+}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index db677ff61..68766e8dc 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -2,7 +2,7 @@ import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
 import { Docs } from '../../../documents/Documents';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -10,6 +10,7 @@ import { FieldViewProps, FieldView } from '../FieldView';
 import { ChartBox } from './ChartBox';
 import './DataVizBox.scss';
 import { TableBox } from './components/TableBox';
+import { PinProps } from '../trails';
 
 enum DataVizView {
     TABLE = 'table',
@@ -54,40 +55,23 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     @computed get currView() {
-        if (this.rootDoc._dataVizView) {
-            return StrCast(this.rootDoc._dataVizView);
-        } else {
-            return 'table';
-        }
+        return StrCast(this.rootDoc._dataVizView, 'table');
     }
 
-    scrollFocus = (doc: Doc, smooth: boolean) => {
-        // reconstruct the view based on the anchor ->
-        // keep track of the datavizbox view and the type of chart we're rendering
-
-        // use dataview to restore part of it and then pass on the rest to the chartbox
-
-        // pseudocode
-        return this._chartBox?.scrollFocus(doc, smooth);
+    @action
+    restoreView = (data: Doc) => {
+        const changed = this.currView !== data.dataView && (this.rootDoc._dataVizView = data.dataView);
+        const func = () => this._chartBox?.restoreView(data);
+        if (changed) {
+            setTimeout(func);
+            return changed ? true : false;
+        }
+        return func() ?? false;
     };
 
-    getAnchor = () => {
-        // TODO: nda - look into DocumentButtonBar and DocumentLinksButton
-
-        /*
-        if nothing selected:
-            return this.rootDoc
-        this part does not specify view type (doesn't change type when following the link)
-
-            // this slightly diff than the stuff below: 
-                below part returns an anchor that specifies the viewtype for the whole doc and nothing else
-                
-        */
-
-        // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
+    getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
         const anchor =
-            // this._COMPONENTNAME._getAnchor() ??
-            this._chartBox?._getAnchor() ??
+            this._chartBox?.getAnchor(pinProps) ??
             Docs.Create.TextanchorDocument({
                 // 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)
@@ -98,7 +82,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         this.addDocument(anchor);
         return anchor;
-        // have some other function in code that
     };
     constructor(props: any) {
         super(props);
@@ -117,7 +100,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             case 'table':
                 return <TableBox pairs={this.pairs} />;
             case 'histogram':
-                return <ChartBox getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
+                return <ChartBox height={this.props.PanelHeight() - 32 /* height of 'change view' button */} getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
             // case "histogram":
             //     return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)
         }
@@ -152,7 +135,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) {
         e.preventDefault();
         e.stopPropagation();
-        this.rootDoc._dataVizView = this.currView == 'table' ? 'histogram' : 'table';
+        this.rootDoc._dataVizView = this.currView === 'table' ? 'histogram' : 'table';
     }
 
     render() {
@@ -161,7 +144,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
 
         return (
-            <div className="dataViz">
+            <div
+                className="dataViz"
+                onWheel={e => e.stopPropagation()}
+                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 }
+                    )
+                }>
                 <button onClick={e => this.changeViewHandler(e)}>Change View</button>
                 {this.selectView}
             </div>
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 7792a2758..2d6c5f0f2 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -1,10 +1,15 @@
 .tooltip {
     // make the height width bigger
-    width: 50px;
-    height: 50px;
+    width: fit-content;
+    height: fit-content;
 }
 
 .highlight {
     // change the color of the circle element to be red
     fill: red;
 }
+.chart-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index d893b3e12..e5f7dc4f4 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -9,6 +9,10 @@ import { Docs } from '../../../../documents/Documents';
 import { Doc, DocListCast } from '../../../../../fields/Doc';
 import { Chart, ChartProps } from '../ChartInterface';
 import './Chart.scss';
+import { List } from '../../../../../fields/List';
+import { PinProps, PresBox } from '../../trails';
+import { listSpec } from '../../../../../fields/Schema';
+import { Cast } from '../../../../../fields/Types';
 
 type minMaxRange = {
     xMin: number | undefined;
@@ -103,21 +107,25 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         // loop through and remove any annotations that no longer exist
     }
 
-    _getAnchor() {
+    restoreView = (data: Doc) => {
+        const coords = Cast(data.presDataViz, listSpec('number'), null);
+        if ((coords && this._currSelected?.x !== coords[0]) || this._currSelected?.y !== coords[1]) {
+            this.setCurrSelected(coords[0], coords[1]);
+            return true;
+        }
+        return false;
+    };
+    _getAnchor = (pinProps?: PinProps) => {
         // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
 
-        // TODO: nda - look at pdfviewer get anchor for args
-        const doc = Docs.Create.TextanchorDocument({
+        const anchor = Docs.Create.TextanchorDocument({
             /*put in some options*/
             title: 'line doc selection' + this._currSelected?.x,
         });
-        // access curr selected from the charts
-        doc.x = this._currSelected?.x;
-        doc.y = this._currSelected?.y;
-        doc.chartType = 'line';
-        return doc;
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc);
+        return anchor;
         // have some other function in code that
-    }
+    };
 
     componentWillUnmount() {
         if (this._dataReactionDisposer) {
@@ -132,7 +140,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
     }
 
     tooltipContent(data: DataPoint) {
-        return `<b>x: ${data.x} y: ${data.y}</b>`;
+        return `<b>(${data.x},${data.y})</b>`;
     }
 
     @computed get height(): number {
@@ -235,7 +243,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         const mousemove = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos));
+            const x0 = bisect(data, xScale.invert(xPos - 25));
             const d0 = data[x0];
             this._x = d0.x;
             this._y = d0.y;
@@ -243,23 +251,22 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             // TODO: nda - implement tooltips
             tooltip.transition().duration(300).style('opacity', 0.9);
             // TODO: nda - updating the inner html could be deadly cause injection attacks!
-            tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right) + 30}px,${yScale(d0.y) + 30}px)`);
+            tooltip
+                .html(() => this.tooltipContent(d0))
+                .style('pointer-events', 'none')
+                .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`);
         });
 
         const onPointClick = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos));
+            const x0 = bisect(data, xScale.invert(xPos - 25));
             const d0 = data[x0];
             this._x = d0.x;
             this._y = d0.y;
             // find .circle-d1 with data-x = d0.x and data-y = d0.y
             const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
             this._currSelected = { x: d0.x, y: d0.y, elem: selected };
-            console.log('Getting here');
-            // this.drawAnnotations(this._x, this._y);
-            // this.props.getAnchor();
-            console.log(this._currSelected);
         });
 
         this._chartSvg
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0e0e22b84..e24bf35f5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -108,6 +108,7 @@ export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, p
 export interface DocComponentView {
     updateIcon?: () => void; // updates the icon representation of the document
     getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
+    restoreView?: (viewSpec: Doc) => boolean;
     scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
     brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void;
     getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index bfaae8069..0e22ea3b1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
+import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -39,6 +39,7 @@ const { Howl } = require('howler');
 
 export interface pinDataTypes {
     scrollable?: boolean;
+    dataviz?: number[];
     pannable?: boolean;
     viewType?: boolean;
     inkable?: boolean;
@@ -491,6 +492,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 changed = true;
             }
         }
+        if (!pinDataTypes && activeItem.presDataViz !== undefined) {
+            if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) {
+                changed = true;
+            }
+        }
 
         if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) {
             if (bestTarget._scrollTop !== activeItem.presViewScroll) {
@@ -648,6 +654,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType;
             if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField);
             if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField;
+            if (pinProps.pinData.dataviz) pinDoc.presDataViz = new List<number>(pinProps.pinData.dataviz);
             if (pinProps.pinData.pannable) {
                 pinDoc.presPanX = NumCast(targetDoc._panX);
                 pinDoc.presPanY = NumCast(targetDoc._panY);
diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts
index 2528fb1ab..15f03b319 100644
--- a/src/server/DataVizUtils.ts
+++ b/src/server/DataVizUtils.ts
@@ -1,19 +1,17 @@
-import { readFileSync } from "fs";
+import { readFileSync } from 'fs';
 
 export function csvParser(csv: string) {
-    const lines = csv.split("\n");
-    const headers = lines[0].split(",");
-    const data = lines.slice(1).map(line => {
-        const values = line.split(",");
-        const obj: any = {};
-        for (let i = 0; i < headers.length; i++) {
-            obj[headers[i]] = values[i];
-        }
-        return obj;
-    });
+    const lines = csv.split('\n');
+    const headers = lines[0].split(',').map(header => header.trim());
+    const data = lines.slice(1).map(line =>
+        line.split(',').reduce((last, value, i) => {
+            last[headers[i]] = value.trim();
+            return last;
+        }, {} as any)
+    );
     return data;
 }
 
 export function csvToString(path: string) {
     return readFileSync(path, 'utf8');
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2


From a8343cad405a146fdff8fc2d66ef41fdaefb8bda Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 6 Apr 2023 00:14:24 -0400
Subject: more simplification/cleanup of dataviz

---
 src/client/views/nodes/DataVizBox/ChartBox.tsx     | 100 -----------
 .../views/nodes/DataVizBox/ChartInterface.ts       |  36 ----
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  84 ++++-----
 .../nodes/DataVizBox/components/LineChart.tsx      | 187 ++++++++++-----------
 .../views/nodes/DataVizBox/components/TableBox.tsx |  31 ++--
 src/client/views/nodes/DataVizBox/utils/D3Utils.ts |   2 +-
 6 files changed, 133 insertions(+), 307 deletions(-)
 delete mode 100644 src/client/views/nodes/DataVizBox/ChartBox.tsx
 delete mode 100644 src/client/views/nodes/DataVizBox/ChartInterface.ts

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

diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
deleted file mode 100644
index ee577b9fd..000000000
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../../../fields/Doc';
-import { action, computed, observable } from 'mobx';
-import { NumCast, StrCast } from '../../../../fields/Types';
-import { LineChart } from './components/LineChart';
-import { Chart, ChartData } from './ChartInterface';
-import { PinProps } from '../trails';
-
-export interface ChartBoxProps {
-    rootDoc: Doc;
-    dataDoc: Doc;
-    height: number;
-    pairs: { x: number; y: number }[];
-    setChartBox: (chartBox: ChartBox) => void;
-    getAnchor: () => Doc;
-}
-
-export interface DataPoint {
-    x: number;
-    y: number;
-}
-
-@observer
-export class ChartBox extends React.Component<ChartBoxProps> {
-    @observable private _chartData: ChartData | undefined = undefined;
-    private currChart: Chart | undefined;
-
-    @computed get currView() {
-        if (this.props.rootDoc._dataVizView) {
-            return StrCast(this.props.rootDoc._currChartView);
-        } else {
-            return 'table';
-        }
-    }
-
-    constructor(props: any) {
-        super(props);
-        if (!this.props.rootDoc._currChartView) {
-            this.props.rootDoc._currChartView = 'bar';
-        }
-    }
-
-    setCurrChart = (chart: Chart) => {
-        this.currChart = chart;
-    };
-
-    @action
-    generateChartData() {
-        if (this.props.rootDoc._chartData) {
-            this._chartData = JSON.parse(StrCast(this.props.rootDoc._chartData));
-            return;
-        }
-
-        const data: ChartData = {
-            xLabel: '',
-            yLabel: '',
-            data: [[]],
-        };
-
-        if (this.props.pairs && this.props.pairs.length > 0) {
-            data.xLabel = 'x';
-            data.yLabel = 'y';
-            this.props.pairs.forEach(pair => {
-                // TODO: nda - add actual support for multiple sets of data
-                data.data[0].push({ x: pair.x, y: pair.y });
-            });
-        }
-
-        this._chartData = data;
-        this.props.rootDoc._chartData = JSON.stringify(data);
-    }
-
-    componentDidMount = () => {
-        this.generateChartData();
-        this.props.setChartBox(this);
-    };
-
-    @action
-    onClickChangeChart = (e: React.MouseEvent<HTMLButtonElement>) => {
-        e.preventDefault();
-        e.stopPropagation();
-        console.log(e.currentTarget.value);
-        this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase();
-    };
-
-    getAnchor = (pinProps?: PinProps) => this.currChart?._getAnchor(pinProps);
-
-    restoreView = (data: Doc) => this.currChart?.restoreView(data);
-
-    render() {
-        if (this.props.pairs && this._chartData) {
-            const width = NumCast(this.props.rootDoc._width) * 0.9;
-            const height = this.props.height * 0.9;
-            const margin = { top: 10, right: 50, bottom: 50, left: 50 };
-            return <LineChart margin={margin} width={width} height={height} chartData={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />;
-        }
-        return <div />;
-    }
-}
diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts
deleted file mode 100644
index 8ebcc2a5f..000000000
--- a/src/client/views/nodes/DataVizBox/ChartInterface.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Doc } from '../../../../fields/Doc';
-import { PinProps } from '../trails';
-import { DataPoint } from './ChartBox';
-
-export interface Chart {
-    tooltipContent: (data: DataPoint) => string;
-    drawChart: () => void;
-    restoreView: (data: any) => boolean;
-    height: number;
-    width: number;
-    // TODO: nda - probably want to get rid of this to keep charts more generic
-    _getAnchor: (pinProps?: PinProps) => Doc;
-}
-
-export interface ChartProps {
-    chartData: ChartData;
-    width: number;
-    height: number;
-    dataDoc: Doc;
-    fieldKey: string;
-    // returns linechart component but should be generic chart
-    setCurrChart: (chart: Chart) => void;
-    getAnchor: () => Doc;
-    margin: {
-        top: number;
-        right: number;
-        bottom: number;
-        left: number;
-    };
-}
-
-export interface ChartData {
-    xLabel: string;
-    yLabel: string;
-    data: DataPoint[][];
-}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 68766e8dc..e279a1262 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -2,29 +2,32 @@ import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
-import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
 import { Docs } from '../../../documents/Documents';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { FieldViewProps, FieldView } from '../FieldView';
-import { ChartBox } from './ChartBox';
-import './DataVizBox.scss';
-import { TableBox } from './components/TableBox';
+import { FieldView, FieldViewProps } from '../FieldView';
 import { PinProps } from '../trails';
+import { LineChart } from './components/LineChart';
+import { TableBox } from './components/TableBox';
+import './DataVizBox.scss';
 
 enum DataVizView {
     TABLE = 'table',
-    HISTOGRAM = 'histogram',
+    LINECHART = 'lineChart',
 }
 
 @observer
 export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(DataVizBox, fieldKey);
+    }
     // says we have an object and any string
     // 2 ways of doing it
     // @observable private pairs: { [key: string]: number | string | undefined }[] = [];
     // @observable private pairs: { [key: string]: FieldResult }[] = [];
     @observable private pairs: { x: number; y: number }[] = [];
-    private _chartBox: ChartBox | undefined;
+    private _chartRenderer: LineChart | undefined;
     // // another way would be store a schema that defines the type of data we are expecting from an imported doc
 
     // method1() {
@@ -45,25 +48,18 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // [key: string]: FieldResult;
     // instead of numeric x,y in there,
 
-    // TODO: nda - make this use enum values instead
-    // @observable private currView: DataVizView = DataVizView.TABLE;
-
     // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops
 
-    setChartBox = (chartBox: ChartBox) => {
-        this._chartBox = chartBox;
-    };
-
-    @computed get currView() {
-        return StrCast(this.rootDoc._dataVizView, 'table');
+    @computed get dataVizView(): DataVizView {
+        return StrCast(this.rootDoc._dataVizView, 'table') as DataVizView;
     }
 
     @action
     restoreView = (data: Doc) => {
-        const changed = this.currView !== data.dataView && (this.rootDoc._dataVizView = data.dataView);
-        const func = () => this._chartBox?.restoreView(data);
+        const changed = this.dataVizView !== data.presDataVizView && (this.rootDoc._dataVizView = data.presDataVizView);
+        const func = () => this._chartRenderer?.restoreView(data);
         if (changed) {
-            setTimeout(func);
+            setTimeout(func, 100);
             return changed ? true : false;
         }
         return func() ?? false;
@@ -71,38 +67,27 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
         const anchor =
-            this._chartBox?.getAnchor(pinProps) ??
+            this._chartRenderer?.getAnchor(pinProps) ??
             Docs.Create.TextanchorDocument({
                 // when we clear selection -> we should have it so chartBox getAnchor returns undefined
                 // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
                 /*put in some options*/
             });
 
-        anchor.dataView = this.currView;
+        anchor.presDataVizView = this.dataVizView;
 
         this.addDocument(anchor);
         return anchor;
     };
-    constructor(props: any) {
-        super(props);
-        if (!this.rootDoc._dataVizView) {
-            // TODO: nda - this might not always want to default to "table"
-            this.rootDoc._dataVizView = 'table';
-        }
-    }
-
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(DataVizBox, fieldKey);
-    }
 
     @computed get selectView() {
-        switch (this.currView) {
-            case 'table':
-                return <TableBox pairs={this.pairs} />;
-            case 'histogram':
-                return <ChartBox height={this.props.PanelHeight() - 32 /* height of 'change view' button */} getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
-            // case "histogram":
-            //     return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)
+        const width = NumCast(this.rootDoc._width) * 0.9;
+        const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9;
+        const margin = { top: 10, right: 50, bottom: 50, left: 50 };
+        // prettier-ignore
+        switch (this.dataVizView) {
+            case DataVizView.TABLE:     return <TableBox pairs={this.pairs} />;
+            case DataVizView.LINECHART: return <LineChart ref={r => (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
         }
     }
 
@@ -116,34 +101,25 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     componentDidMount() {
         this.props.setContentView?.(this);
-        // this.createPairs();
         this.fetchData();
     }
 
     fetchData() {
-        const uri = this.dataUrl?.url.href;
-        fetch('/csvData?uri=' + uri).then(res =>
-            res.json().then(
-                action(res => {
-                    this.pairs = res;
-                })
-            )
-        );
+        fetch('/csvData?uri=' + this.dataUrl?.url.href) //
+            .then(res => res.json().then(action(res => !res.errno && (this.pairs = res))));
     }
 
     // handle changing the view using a button
     @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) {
         e.preventDefault();
         e.stopPropagation();
-        this.rootDoc._dataVizView = this.currView === 'table' ? 'histogram' : 'table';
+        this.rootDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE;
     }
 
     render() {
-        if (this.pairs.length == 0) {
-            return <div>Loading...</div>;
-        }
-
-        return (
+        return this.pairs.length == 0 ? (
+            <div>Loading...</div>
+        ) : (
             <div
                 className="dataViz"
                 onWheel={e => e.stopPropagation()}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index e5f7dc4f4..313164691 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,18 +1,15 @@
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DataPoint } from '../ChartBox';
 // import d3
 import * as d3 from 'd3';
-import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from '../utils/D3Utils';
-import { Docs } from '../../../../documents/Documents';
 import { Doc, DocListCast } from '../../../../../fields/Doc';
-import { Chart, ChartProps } from '../ChartInterface';
-import './Chart.scss';
-import { List } from '../../../../../fields/List';
-import { PinProps, PresBox } from '../../trails';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast } from '../../../../../fields/Types';
+import { Docs } from '../../../../documents/Documents';
+import { PinProps, PresBox } from '../../trails';
+import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
+import './Chart.scss';
 
 type minMaxRange = {
     xMin: number | undefined;
@@ -21,22 +18,43 @@ type minMaxRange = {
     yMax: number | undefined;
 };
 
-interface SelectedDataPoint {
+export interface DataPoint {
     x: number;
     y: number;
+}
+interface SelectedDataPoint extends DataPoint {
     elem?: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
 }
+export interface LineChartData {
+    xLabel: string;
+    yLabel: string;
+    dataSet: DataPoint[][];
+}
+export interface LineChartProps {
+    rootDoc: Doc;
+    pairs: { x: number; y: number }[];
+    width: number;
+    height: number;
+    dataDoc: Doc;
+    fieldKey: string;
+    margin: {
+        top: number;
+        right: number;
+        bottom: number;
+        left: number;
+    };
+}
 
 @observer
-export class LineChart extends React.Component<ChartProps> implements Chart {
-    private _dataReactionDisposer: IReactionDisposer | undefined = undefined;
-    private _heightReactionDisposer: IReactionDisposer | undefined = undefined;
-    private _widthReactionDisposer: IReactionDisposer | undefined;
+export class LineChart extends React.Component<LineChartProps> {
+    private _disposers: { [key: string]: IReactionDisposer } = {};
     @observable private _x: number = 0;
     @observable private _y: number = 0;
     @observable private _currSelected: SelectedDataPoint | undefined = undefined;
+    @observable private _lineChartData: LineChartData | undefined = undefined;
     // create ref for the div
-    private _chartRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
     private _rangeVals: minMaxRange = {
         xMin: undefined,
         xMax: undefined,
@@ -45,43 +63,47 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
     };
     // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
 
-    private _chartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
-
-    // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that
-
-    // write the getanchor function that gets whatever I want as the link anchor
-
+    componentWillUnmount() {
+        Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
+    }
     componentDidMount = () => {
-        this._dataReactionDisposer = reaction(
-            () => this.props.chartData,
-            chartData => {
-                this._rangeVals = minMaxRange(chartData.data);
-                this.drawChart();
+        this._disposers.chartData = reaction(
+            () => ({ dataSet: this._lineChartData?.dataSet, w: this.props.width, h: this.props.height }),
+            vals => {
+                if (vals.dataSet) {
+                    this._rangeVals = minMaxRange(vals.dataSet);
+                    this.drawChart(vals.dataSet);
+                }
             },
             { fireImmediately: true }
         );
-        // DocumentDecorations.Instance.Interacting
-        this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this));
-        this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this));
-        reaction(
+        this._disposers.annos = reaction(
             () => DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']),
             annotations => {
                 // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
                 // could be blue colored to make it look like anchor
-                console.log(annotations);
                 // this.drawAnnotations()
                 // loop through annotations and draw them
-                annotations.forEach(a => {
-                    this.drawAnnotations(Number(a.x), Number(a.y));
-                });
+                annotations.forEach(a => this.drawAnnotations(Number(a.x), Number(a.y)));
                 // this.drawAnnotations(annotations.x, annotations.y);
             },
             { fireImmediately: true }
         );
-
-        this.props.setCurrChart(this);
+        this.generateChartData();
     };
 
+    @action
+    generateChartData() {
+        this._lineChartData = {
+            xLabel: 'x',
+            yLabel: 'y',
+            // TODO: nda - add actual support for multiple sets of data
+            dataSet: [this.props.pairs?.map(pair => ({ x: pair.x, y: pair.y }))],
+        };
+    }
+
+    // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that
+
     // gets called whenever the "data-annotations" fields gets updated
     drawAnnotations(dataX: number, dataY: number) {
         // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements
@@ -115,45 +137,27 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         }
         return false;
     };
-    _getAnchor = (pinProps?: PinProps) => {
-        // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
 
+    // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
+    getAnchor = (pinProps?: PinProps) => {
         const anchor = Docs.Create.TextanchorDocument({
-            /*put in some options*/
             title: 'line doc selection' + this._currSelected?.x,
         });
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc);
         return anchor;
-        // have some other function in code that
     };
 
-    componentWillUnmount() {
-        if (this._dataReactionDisposer) {
-            this._dataReactionDisposer();
-        }
-        if (this._heightReactionDisposer) {
-            this._heightReactionDisposer();
-        }
-        if (this._widthReactionDisposer) {
-            this._widthReactionDisposer();
-        }
-    }
-
-    tooltipContent(data: DataPoint) {
-        return `<b>(${data.x},${data.y})</b>`;
-    }
-
-    @computed get height(): number {
+    @computed get height() {
         return this.props.height - this.props.margin.top - this.props.margin.bottom;
     }
 
-    @computed get width(): number {
+    @computed get width() {
         return this.props.width - this.props.margin.left - this.props.margin.right;
     }
 
     setupTooltip() {
         const tooltip = d3
-            .select(this._chartRef.current)
+            .select(this._lineChartRef.current)
             .append('div')
             .attr('class', 'tooltip')
             .style('opacity', 0)
@@ -169,13 +173,13 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
     @action
     setCurrSelected(x: number, y: number) {
         // TODO: nda - get rid of svg element in the list?
-        this._currSelected = { x: x, y: y };
+        this._currSelected = { x, y };
     }
 
     drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
-        if (this._chartSvg) {
+        if (this._lineChartSvg) {
             const circleClass = '.circle-' + idx;
-            this._chartSvg
+            this._lineChartSvg
                 .selectAll(circleClass)
                 .data(data)
                 .join('circle') // enter append
@@ -189,30 +193,13 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
     }
 
     // TODO: nda - can use d3.create() to create html element instead of appending
-    drawChart() {
-        const { chartData, margin } = this.props;
-        const data = chartData.data[0];
+    drawChart = (dataSet: DataPoint[][]) => {
         // clearing tooltip and the current chart
-        d3.select(this._chartRef.current).select('svg').remove();
-        d3.select(this._chartRef.current).select('.tooltip').remove();
-
-        // TODO: nda - refactor code so that it only recalculates min max and things related to data on data update
+        d3.select(this._lineChartRef.current).select('svg').remove();
+        d3.select(this._lineChartRef.current).select('.tooltip').remove();
 
         const { xMin, xMax, yMin, yMax } = this._rangeVals;
-        // const svg = d3.select(this._chartRef.current).append(this.svgContainer.html());
-        // adding svg
-        this._chartSvg = d3
-            .select(this._chartRef.current)
-            .append('svg')
-            .attr('width', `${this.width + margin.right + margin.left}`)
-            .attr('height', `${this.height + margin.top + margin.bottom}`)
-            .append('g')
-            .attr('transform', `translate(${margin.left}, ${margin.top})`);
-
-        const svg = this._chartSvg;
-
-        if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) {
-            // TODO: nda - error handle
+        if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
             return;
         }
 
@@ -220,9 +207,15 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.width);
         const yScale = scaleCreatorNumerical(0, yMax, this.height, 0);
 
-        // create a line function that takes in the data.data.x and data.data.y
-        // TODO: nda - fix the types for the d here
-        const lineGen = createLineGenerator(xScale, yScale);
+        // adding svg
+        const margin = this.props.margin;
+        const svg = (this._lineChartSvg = d3
+            .select(this._lineChartRef.current)
+            .append('svg')
+            .attr('width', `${this.width + margin.right + margin.left}`)
+            .attr('height', `${this.height + margin.top + margin.bottom}`)
+            .append('g')
+            .attr('transform', `translate(${margin.left}, ${margin.top})`));
 
         // create x and y grids
         xGrid(svg.append('g'), this.height, xScale);
@@ -230,7 +223,9 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         xAxisCreator(svg.append('g'), this.height, xScale);
         yAxisCreator(svg.append('g'), this.width, yScale);
 
-        // draw the line
+        // draw the plot line
+        const data = dataSet[0];
+        const lineGen = createLineGenerator(xScale, yScale);
         drawLine(svg.append('path'), data, lineGen);
 
         // draw the datapoint circle
@@ -243,16 +238,15 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         const mousemove = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos - 25));
+            const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis
             const d0 = data[x0];
             this._x = d0.x;
             this._y = d0.y;
             focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`);
-            // TODO: nda - implement tooltips
             tooltip.transition().duration(300).style('opacity', 0.9);
             // TODO: nda - updating the inner html could be deadly cause injection attacks!
             tooltip
-                .html(() => this.tooltipContent(d0))
+                .html(() => `<b>(${d0.x},${d0.y})</b>`) // text content for tooltip
                 .style('pointer-events', 'none')
                 .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`);
         });
@@ -260,7 +254,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         const onPointClick = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos - 25));
+            const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis
             const d0 = data[x0];
             this._x = d0.x;
             this._y = d0.y;
@@ -269,27 +263,22 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             this._currSelected = { x: d0.x, y: d0.y, elem: selected };
         });
 
-        this._chartSvg
-            .append('rect')
+        svg.append('rect')
             .attr('class', 'overlay')
             .attr('width', this.width)
             .attr('height', this.height + margin.top + margin.bottom)
             .attr('fill', 'none')
             .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`)
             .style('opacity', 0)
-            .on('mouseover', () => {
-                focus.style('display', null);
-            })
-            .on('mouseout', () => {
-                tooltip.transition().duration(300).style('opacity', 0);
-            })
+            .on('mouseover', () => focus.style('display', null))
+            .on('mouseout', () => tooltip.transition().duration(300).style('opacity', 0))
             .on('mousemove', mousemove)
             .on('click', onPointClick);
-    }
+    };
 
     render() {
         return (
-            <div ref={this._chartRef} className="chart-container">
+            <div ref={this._lineChartRef} className="chart-container">
                 <span>Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'}</span>
             </div>
         );
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index dfa8262d8..28114036f 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,16 +1,12 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 
 interface TableBoxProps {
-    pairs: {x: number, y:number}[]
+    pairs: { x: number; y: number }[];
 }
 
-
 export class TableBox extends React.Component<TableBoxProps> {
-
-
-
     render() {
         return (
             <div className="table-container">
@@ -22,16 +18,17 @@ export class TableBox extends React.Component<TableBoxProps> {
                         </tr>
                     </thead>
                     <tbody>
-                    {this.props.pairs.map(p => {
-                        return (<tr className="table-row">
-                            <td>{p.x}</td>
-                            <td>{p.y}</td>
-                        </tr>)
-                    })}
+                        {this.props.pairs?.map(p => {
+                            return (
+                                <tr className="table-row">
+                                    <td>{p.x}</td>
+                                    <td>{p.y}</td>
+                                </tr>
+                            );
+                        })}
                     </tbody>
                 </table>
             </div>
-        )
+        );
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 2bb091999..e1ff6f8eb 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -1,5 +1,5 @@
 import * as d3 from 'd3';
-import { DataPoint } from '../ChartBox';
+import { DataPoint } from '../components/LineChart';
 
 // TODO: nda - implement function that can handle range for strings
 
-- 
cgit v1.2.3-70-g09d2


From 7975e3f4aaa3601a5512a7dce4c261e7f9411374 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 6 Apr 2023 10:01:39 -0400
Subject: added selection ui for choosing linechart axes.

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 31 +++++++++-------
 .../nodes/DataVizBox/components/LineChart.tsx      | 33 ++++++++++++-----
 .../views/nodes/DataVizBox/components/TableBox.tsx | 43 +++++++++++++++++++---
 src/client/views/nodes/trails/PresBox.tsx          |  7 +---
 4 files changed, 79 insertions(+), 35 deletions(-)

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

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


From 986c5bdc899954c4609f71405d3334147be721fe Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 7 Apr 2023 10:42:16 -0400
Subject: experimetnal changes to datavizbox to allow brushing data items and
 better highlighting of selections.  Also working on drawing link lines
 between chart aliases.

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  11 ++-
 .../views/nodes/DataVizBox/components/Chart.scss   |  20 ++++
 .../nodes/DataVizBox/components/LineChart.tsx      | 105 +++++++++++++++------
 .../views/nodes/DataVizBox/components/TableBox.tsx |  98 +++++++++++++------
 src/fields/Doc.ts                                  |   1 +
 5 files changed, 170 insertions(+), 65 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 0ce589a13..aaa8c3c53 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -13,7 +13,7 @@ import { LineChart } from './components/LineChart';
 import { TableBox } from './components/TableBox';
 import './DataVizBox.scss';
 
-enum DataVizView {
+export enum DataVizView {
     TABLE = 'table',
     LINECHART = 'lineChart',
 }
@@ -27,7 +27,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // 2 ways of doing it
     // @observable private pairs: { [key: string]: number | string | undefined }[] = [];
     // @observable private pairs: { [key: string]: FieldResult }[] = [];
-    @observable private pairs: { [key: string]: string }[] = [];
+    @observable pairs: { [key: string]: string }[] = [];
     private _chartRenderer: LineChart | undefined;
     // // another way would be store a schema that defines the type of data we are expecting from an imported doc
 
@@ -71,6 +71,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const anchor =
             this._chartRenderer?.getAnchor(pinProps) ??
             Docs.Create.TextanchorDocument({
+                unrendered: true,
                 // when we clear selection -> we should have it so chartBox getAnchor returns undefined
                 // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
                 /*put in some options*/
@@ -89,12 +90,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     selectAxes = (axes: string[]) => (this.layoutDoc.dataVizAxes = new List<string>(axes));
 
     @computed get selectView() {
-        const width = NumCast(this.rootDoc._width) * 0.9;
+        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: 50, bottom: 50, left: 50 };
         // prettier-ignore
         switch (this.dataVizView) {
-            case DataVizView.TABLE:     return <TableBox pairs={this.pairs} axes={this.axes} selectAxes={this.selectAxes}/>;
+            case DataVizView.TABLE:     return <TableBox pairs={this.pairs} axes={this.axes} docView={this.props.DocumentView} selectAxes={this.selectAxes}/>;
             case DataVizView.LINECHART: return <LineChart ref={r => (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />;
         }
     }
@@ -136,7 +137,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         { passive: false }
                     )
                 }>
-                <button onClick={e => this.changeViewHandler(e)}>Change View</button>
+                <button onClick={e => this.changeViewHandler(e)}>{this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE}</button>
                 {this.selectView}
             </div>
         );
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 2d6c5f0f2..c8daf7c90 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -8,8 +8,28 @@
     // change the color of the circle element to be red
     fill: red;
 }
+.focus-selected,
+.selected {
+    // change the color of the circle element to be red
+    fill: lightblue;
+    position: absolute;
+    transform-box: fill-box;
+    scale: 2;
+    transform-origin: center;
+}
+.focus {
+    fill: transparent;
+    outline: black solid 1px;
+    border-radius: 100%;
+}
+.focus-selected {
+    scale: 1;
+    outline: black solid 1px;
+    border-radius: 100%;
+}
 .chart-container {
     display: flex;
     flex-direction: column;
     align-items: center;
+    cursor: default;
 }
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index e6a06a454..6a223e683 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -6,11 +6,15 @@ import * as d3 from 'd3';
 import { Doc, DocListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
-import { Cast } from '../../../../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { PinProps, PresBox } from '../../trails';
 import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
 import './Chart.scss';
+import { LinkManager } from '../../../../util/LinkManager';
+import { DocumentManager } from '../../../../util/DocumentManager';
+import { Id } from '../../../../../fields/FieldSymbols';
+import { DataVizBox } from '../DataVizBox';
 
 export interface DataPoint {
     x: number;
@@ -40,7 +44,7 @@ export class LineChart extends React.Component<LineChartProps> {
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
-    private _rangeVals: { xMin?: number; xMax?: number;yMin?: number;yMax?: number;}= {};
+    private _rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number } = {};
     @observable _currSelected: SelectedDataPoint | undefined = undefined;
     @observable _lineChartData: DataPoint[][] | undefined = undefined;
     // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
@@ -51,9 +55,11 @@ export class LineChart extends React.Component<LineChartProps> {
     componentDidMount = () => {
         this._disposers.chartdata = reaction(
             () => this.props.axes.slice(),
-            axes => {if (axes.length > 1) {
-                this._lineChartData = [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })).sort((a,b) => a.x < b.x ? 1:-1)]
-            }},
+            axes => {
+                if (axes.length > 1) {
+                    this._lineChartData = [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1))];
+                }
+            },
             { fireImmediately: true }
         );
         this._disposers.chartData = reaction(
@@ -78,12 +84,38 @@ export class LineChart extends React.Component<LineChartProps> {
             },
             { fireImmediately: true }
         );
+        this._disposers.highlights = reaction(
+            () => ({
+                selected: this._currSelected,
+                pairs: LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc)
+                    .filter(link => link.anchor1 !== this.props.rootDoc) // all links that are pointing to this node
+                    .map(link => DocCast(link.anchor1)) // get the documents 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
+                    .lastElement(),
+            }),
+            ({ selected, pairs }) => {
+                this.clearAnnoations();
+                selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true);
+                pairs.forEach(pair => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
+            },
+            { fireImmediately: true }
+        );
     };
 
     // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that
 
+    clearAnnoations = () => {
+        const elements = document.querySelectorAll('.datapoint');
+        for (let i = 0; i < elements.length; i++) {
+            const element = elements[i];
+            element.classList.remove('highlight');
+            element.classList.remove('selected');
+        }
+    };
     // gets called whenever the "data-annotations" fields gets updated
-    drawAnnotations(dataX: number, dataY: number) {
+    drawAnnotations = (dataX: number, dataY: number, selected?: boolean) => {
         // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements
         // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY
         // if it exists, then highlight it
@@ -94,14 +126,13 @@ export class LineChart extends React.Component<LineChartProps> {
             const x = element.getAttribute('data-x');
             const y = element.getAttribute('data-y');
             if (x === dataX.toString() && y === dataY.toString()) {
-                element.classList.add('highlight');
+                element.classList.add(selected ? 'selected' : 'highlight');
             }
             // TODO: nda - this remove highlight code should go where we remove the links
             // } else {
-            //     element.classList.remove('highlight');
             // }
         }
-    }
+    };
 
     removeAnnotations(dataX: number, dataY: number) {
         // loop through and remove any annotations that no longer exist
@@ -115,7 +146,7 @@ export class LineChart extends React.Component<LineChartProps> {
             return true;
         }
         if (this._currSelected) {
-            this._currSelected = undefined;
+            this.setCurrSelected();
             return true;
         }
         return false;
@@ -123,9 +154,7 @@ export class LineChart extends React.Component<LineChartProps> {
 
     // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
     getAnchor = (pinProps?: PinProps) => {
-        const anchor = Docs.Create.TextanchorDocument({
-            title: 'line doc selection' + this._currSelected?.x,
-        });
+        const anchor = Docs.Create.TextanchorDocument({ title: 'line doc selection' + this._currSelected?.x, unrendered: true });
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc);
         anchor.presDataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
         return anchor;
@@ -140,7 +169,7 @@ export class LineChart extends React.Component<LineChartProps> {
     }
 
     setupTooltip() {
-        const tooltip = d3
+        return d3
             .select(this._lineChartRef.current)
             .append('div')
             .attr('class', 'tooltip')
@@ -150,14 +179,15 @@ export class LineChart extends React.Component<LineChartProps> {
             .style('padding', '5px')
             .style('position', 'absolute')
             .style('font-size', '12px');
-        return tooltip;
     }
 
     // TODO: nda - use this everyewhere we update currSelected?
     @action
-    setCurrSelected(x: number, y: number) {
+    setCurrSelected(x?: number, y?: number) {
         // TODO: nda - get rid of svg element in the list?
-        this._currSelected = { x, y };
+        this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
+        this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true));
+        this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined));
     }
 
     drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -215,32 +245,29 @@ export class LineChart extends React.Component<LineChartProps> {
         // draw the datapoint circle
         this.drawDataPoints(data, 0, xScale, yScale);
 
-        const focus = svg.append('g').attr('class', 'focus').style('display', 'none');
-        focus.append('circle').attr('r', 5).attr('class', 'circle');
+        const higlightFocusPt = svg.append('g').style('display', 'none');
+        higlightFocusPt.append('circle').attr('r', 5).attr('class', 'circle');
         const tooltip = this.setupTooltip();
         // add all the tooltipContent to the tooltip
         const mousemove = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis
+            const x0 = Math.min(data.length - 1, bisect(data, xScale.invert(xPos - 5))); // shift x by -5 so that you can reach points on the left-side axis
             const d0 = data[x0];
-            focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`);
-            tooltip.transition().duration(300).style('opacity', 0.9);
-            // TODO: nda - updating the inner html could be deadly cause injection attacks!
-            tooltip
-                .html(() => `<b>(${d0.x},${d0.y})</b>`) // text content for tooltip
-                .style('pointer-events', 'none')
-                .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`);
+            if (!d0) return;
+
+            this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
         });
 
         const onPointClick = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis
+            const x0 = bisect(data, xScale.invert(xPos - 5)); // shift x by -5 so that you can reach points on the left-side axis
             const d0 = data[x0];
             // find .circle-d1 with data-x = d0.x and data-y = d0.y
             const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
-            this._currSelected = { x: d0.x, y: d0.y, elem: selected };
+            this.setCurrSelected(d0.x, d0.y);
+            this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
         });
 
         svg.append('rect')
@@ -250,17 +277,33 @@ export class LineChart extends React.Component<LineChartProps> {
             .attr('fill', 'none')
             .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`)
             .style('opacity', 0)
-            .on('mouseover', () => focus.style('display', null))
+            .on('mouseover', () => higlightFocusPt.style('display', null))
             .on('mouseout', () => tooltip.transition().duration(300).style('opacity', 0))
             .on('mousemove', mousemove)
             .on('click', onPointClick);
     };
 
+    private updateTooltip(
+        higlightFocusPt: d3.Selection<SVGGElement, unknown, null, undefined>,
+        xScale: d3.ScaleLinear<number, number, never>,
+        d0: DataPoint,
+        yScale: d3.ScaleLinear<number, number, never>,
+        tooltip: d3.Selection<HTMLDivElement, unknown, null, undefined>
+    ) {
+        higlightFocusPt.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`).attr('class', this._currSelected?.x === d0.x && this._currSelected?.y === d0.y ? 'focus-selected' : 'focus');
+        tooltip.transition().duration(300).style('opacity', 0.9);
+        // TODO: nda - updating the inner html could be deadly cause injection attacks!
+        tooltip
+            .html(() => `<b>(${d0.x},${d0.y})</b>`) // text content for tooltip
+            .style('pointer-events', 'none')
+            .style('transform', `translate(${xScale(d0.x) - this.width / 2}px,${yScale(d0.y) - 30}px)`);
+    }
+
     render() {
         const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none';
         return (
             <div ref={this._lineChartRef} className="chart-container">
-                <span> {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Curr Selected: ${selectedPt}`}</span>
+                <span> {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Selected: ${selectedPt}`}</span>
             </div>
         );
     }
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index adefe90cd..0d69ac890 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,12 +1,19 @@
 import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../../Utils';
+import { AnimationSym, Doc } from '../../../../../fields/Doc';
+import { Id } from '../../../../../fields/FieldSymbols';
+import { List } from '../../../../../fields/List';
+import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../../../Utils';
+import { DragManager } from '../../../../util/DragManager';
+import { DocumentView } from '../../DocumentView';
+import { DataVizView } from '../DataVizBox';
 
 interface TableBoxProps {
     pairs: { [key: string]: any }[];
     selectAxes: (axes: string[]) => void;
     axes: string[];
+    docView?: () => DocumentView | undefined;
 }
 
 @observer
@@ -20,39 +27,72 @@ export class TableBox extends React.Component<TableBoxProps> {
                 <table className="table">
                     <thead>
                         <tr className="table-row">
-                            {this.columns.map(col => (
-                                <th
-                                    style={{ color: this.props.axes.slice().reverse().lastElement() === col ? 'green' : this.props.axes.lastElement() === col ? 'red' : undefined, fontWeight: this.props.axes.includes(col) ? 'bolder' : 'normal' }}
-                                    onPointerDown={e =>
-                                        setupMoveUpEvents(
-                                            {},
-                                            e,
-                                            returnFalse,
-                                            emptyFunction,
-                                            action(e => {
-                                                const newAxes = this.props.axes;
-                                                if (newAxes.includes(col)) {
-                                                    newAxes.splice(newAxes.indexOf(col), 1);
-                                                } else if (newAxes.length >= 1) {
-                                                    newAxes[1] = col;
-                                                } else {
-                                                    newAxes[0] = col;
-                                                }
-                                                this.props.selectAxes(newAxes);
-                                            })
-                                        )
-                                    }>
-                                    {col}
-                                </th>
-                            ))}
+                            {this.columns
+                                .filter(col => !col.startsWith('select'))
+                                .map(col => {
+                                    const header = React.createRef<HTMLElement>();
+                                    return (
+                                        <th
+                                            ref={header as any}
+                                            style={{
+                                                color: this.props.axes.slice().reverse().lastElement() === col ? 'green' : this.props.axes.lastElement() === col ? 'red' : undefined,
+                                                fontWeight: this.props.axes.includes(col) ? 'bolder' : 'normal',
+                                            }}
+                                            onPointerDown={e => {
+                                                const downX = e.clientX;
+                                                const downY = e.clientY;
+                                                setupMoveUpEvents(
+                                                    {},
+                                                    e,
+                                                    e => {
+                                                        const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
+                                                        const targetCreator = (annotationOn: Doc | undefined) => {
+                                                            const alias = Doc.MakeAlias(this.props.docView?.()!.rootDoc!);
+                                                            alias._dataVizView = DataVizView.LINECHART;
+                                                            alias._dataVizAxes = new List<string>([col, col]);
+                                                            alias.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
+                                                            return alias;
+                                                        };
+                                                        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.linkDisplay = 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[0] = col;
+                                                        }
+                                                        this.props.selectAxes(newAxes);
+                                                    })
+                                                );
+                                            }}>
+                                            {col}
+                                        </th>
+                                    );
+                                })}
                         </tr>
                     </thead>
                     <tbody>
-                        {this.props.pairs?.map(p => {
+                        {this.props.pairs?.map((p, i) => {
                             return (
-                                <tr className="table-row">
+                                <tr className="table-row" onClick={action(e => (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}>
                                     {this.columns.map(col => (
-                                        <td>{p[col]}</td>
+                                        <td style={{ fontWeight: p['select' + this.props.docView?.()?.rootDoc![Id]] ? 'bold' : '' }}>{p[col]}</td>
                                     ))}
                                 </tr>
                             );
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index cc024d83a..e89f5db52 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1335,6 +1335,7 @@ export namespace Doc {
     }
 
     export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
+        if (linkDoc.anchor2 === anchorDoc || (linkDoc.anchor2 as Doc).annotationOn) return '2';
         return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? '1' : '2';
     }
 
-- 
cgit v1.2.3-70-g09d2


From 68c6c36af823255824a6b0692e8c33618c2d7ca2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 10 Apr 2023 11:32:44 -0400
Subject: fixed brushing of fonticon boxes with dropdowns.   made line charts
 use computed values instead of observables

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 14 +++-
 .../views/nodes/DataVizBox/components/Chart.scss   |  2 +-
 .../nodes/DataVizBox/components/LineChart.tsx      | 89 ++++++++++++----------
 src/client/views/nodes/button/FontIconBox.tsx      | 33 ++++++--
 .../nodes/button/colorDropdown/ColorDropdown.tsx   | 57 +++++++-------
 src/fields/Doc.ts                                  |  2 +-
 7 files changed, 120 insertions(+), 81 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9fe8f5f49..9d4a788db 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -664,8 +664,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     ActiveIsInkMask(),
                     {
                         title: 'ink stroke',
-                        x: B.x - ActiveInkWidth() / 2,
-                        y: B.y - ActiveInkWidth() / 2,
+                        x: B.x - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
+                        y: B.y - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
                         _width: B.width + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
                         _height: B.height + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
                     }
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index aaa8c3c53..8593c8994 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, ObservableMap, ObservableSet } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, StrListCast } from '../../../../fields/Doc';
@@ -27,7 +27,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // 2 ways of doing it
     // @observable private pairs: { [key: string]: number | string | undefined }[] = [];
     // @observable private pairs: { [key: string]: FieldResult }[] = [];
-    @observable pairs: { [key: string]: string }[] = [];
+    static pairSet = new ObservableMap<string, { [key: string]: string }[]>();
+    @computed.struct get pairs() {
+        return DataVizBox.pairSet.get(StrCast(this.rootDoc.fileUpload));
+    }
     private _chartRenderer: LineChart | undefined;
     // // another way would be store a schema that defines the type of data we are expecting from an imported doc
 
@@ -93,6 +96,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         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: 50, bottom: 50, left: 50 };
+        if (!this.pairs) return 'no data';
         // prettier-ignore
         switch (this.dataVizView) {
             case DataVizView.TABLE:     return <TableBox pairs={this.pairs} axes={this.axes} docView={this.props.DocumentView} selectAxes={this.selectAxes}/>;
@@ -109,8 +113,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     fetchData() {
+        if (DataVizBox.pairSet.has(StrCast(this.rootDoc.fileUpload))) return;
+        DataVizBox.pairSet.set(StrCast(this.rootDoc.fileUpload), []);
         fetch('/csvData?uri=' + this.dataUrl?.url.href) //
-            .then(res => res.json().then(action(res => !res.errno && (this.pairs = res))));
+            .then(res => res.json().then(action(res => !res.errno && DataVizBox.pairSet.set(StrCast(this.rootDoc.fileUpload), res))));
     }
 
     // handle changing the view using a button
@@ -121,7 +127,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     render() {
-        return this.pairs.length == 0 ? (
+        return !this.pairs?.length ? (
             <div>Loading...</div>
         ) : (
             <div
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 56fcb1fcf..f1d09d2e7 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -8,7 +8,7 @@
 .selected {
     // change the color of the circle element to be red
     fill: transparent;
-    outline: lightblue solid 2px;
+    outline: red solid 2px;
     border-radius: 100%;
     position: absolute;
     transform-box: fill-box;
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 308ef4cba..bc9f0be77 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -4,17 +4,17 @@ import * as React from 'react';
 // import d3
 import * as d3 from 'd3';
 import { Doc, DocListCast } from '../../../../../fields/Doc';
+import { Id } from '../../../../../fields/FieldSymbols';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
-import { Cast, DocCast, NumCast } from '../../../../../fields/Types';
+import { Cast, DocCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
+import { DocumentManager } from '../../../../util/DocumentManager';
+import { LinkManager } from '../../../../util/LinkManager';
 import { PinProps, PresBox } from '../../trails';
+import { DataVizBox } from '../DataVizBox';
 import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
 import './Chart.scss';
-import { LinkManager } from '../../../../util/LinkManager';
-import { DocumentManager } from '../../../../util/DocumentManager';
-import { Id } from '../../../../../fields/FieldSymbols';
-import { DataVizBox } from '../DataVizBox';
 
 export interface DataPoint {
     x: number;
@@ -44,30 +44,44 @@ export class LineChart extends React.Component<LineChartProps> {
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
-    private _rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number } = {};
     @observable _currSelected: SelectedDataPoint | undefined = undefined;
-    @observable _lineChartData: DataPoint[][] | 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 _lineChartData() {
+        if (this.props.axes.length <= 1) return [];
+        return this.props.pairs
+            ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select'))))
+            .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) }))
+            .sort((a, b) => (a.x < b.x ? -1 : 1));
+    }
+    @computed get incomingLinks() {
+        return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+            .filter(link => link.anchor1 !== this.props.rootDoc) // get links where this chart doc is the target of the link
+            .map(link => DocCast(link.anchor1)); // then return the source of the link
+    }
+    @computed get incomingSelected() {
+        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
+            .lastElement();
+    }
+    @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
+        return minMaxRange([this._lineChartData]);
+    }
     componentWillUnmount() {
         Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
     }
     componentDidMount = () => {
-        this._disposers.chartdata = reaction(
-            () => this.props.axes.slice(),
-            axes => {
-                if (axes.length > 1) {
-                    this._lineChartData = [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1))];
-                }
-            },
-            { fireImmediately: true }
-        );
         this._disposers.chartData = reaction(
-            () => ({ dataSet: this._lineChartData, axes: this.props.axes.slice(), w: this.props.width, h: this.props.height }),
-            vals => {
-                if (vals.dataSet) {
-                    this._rangeVals = minMaxRange(vals.dataSet);
-                    this.drawChart(vals.dataSet);
+            () => ({ dataSet: this._lineChartData, w: this.props.width, h: this.props.height }),
+            ({ dataSet, w, h }) => {
+                if (dataSet) {
+                    this.drawChart([dataSet], this.rangeVals, w, h);
+                    // 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]])));
                 }
             },
             { fireImmediately: true }
@@ -87,18 +101,13 @@ export class LineChart extends React.Component<LineChartProps> {
         this._disposers.highlights = reaction(
             () => ({
                 selected: this._currSelected,
-                pairs: LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc)
-                    .filter(link => link.anchor1 !== this.props.rootDoc) // all links that are pointing to this node
-                    .map(link => DocCast(link.anchor1)) // get the documents 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
-                    .lastElement(),
+                incomingSelected: this.incomingSelected,
             }),
-            ({ selected, pairs }) => {
-                this.clearAnnoations();
+            ({ selected, incomingSelected }) => {
+                // 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);
-                pairs.forEach(pair => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
+                incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
             },
             { fireImmediately: true }
         );
@@ -106,7 +115,7 @@ export class LineChart extends React.Component<LineChartProps> {
 
     // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that
 
-    clearAnnoations = () => {
+    clearAnnotations = () => {
         const elements = document.querySelectorAll('.datapoint');
         for (let i = 0; i < elements.length; i++) {
             const element = elements[i];
@@ -207,12 +216,12 @@ export class LineChart extends React.Component<LineChartProps> {
     }
 
     // TODO: nda - can use d3.create() to create html element instead of appending
-    drawChart = (dataSet: DataPoint[][]) => {
+    drawChart = (dataSet: DataPoint[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
         // clearing tooltip and the current chart
         d3.select(this._lineChartRef.current).select('svg').remove();
         d3.select(this._lineChartRef.current).select('.tooltip').remove();
 
-        const { xMin, xMax, yMin, yMax } = this._rangeVals;
+        const { xMin, xMax, yMin, yMax } = rangeVals;
         if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
             return;
         }
@@ -226,16 +235,16 @@ export class LineChart extends React.Component<LineChartProps> {
         const svg = (this._lineChartSvg = d3
             .select(this._lineChartRef.current)
             .append('svg')
-            .attr('width', `${this.width + margin.right + margin.left}`)
-            .attr('height', `${this.height + margin.top + margin.bottom}`)
+            .attr('width', `${width + margin.right + margin.left}`)
+            .attr('height', `${height + margin.top + margin.bottom}`)
             .append('g')
             .attr('transform', `translate(${margin.left}, ${margin.top})`));
 
         // create x and y grids
-        xGrid(svg.append('g'), this.height, xScale);
-        yGrid(svg.append('g'), this.width, yScale);
-        xAxisCreator(svg.append('g'), this.height, xScale);
-        yAxisCreator(svg.append('g'), this.width, yScale);
+        xGrid(svg.append('g'), height, xScale);
+        yGrid(svg.append('g'), width, yScale);
+        xAxisCreator(svg.append('g'), height, xScale);
+        yAxisCreator(svg.append('g'), width, yScale);
 
         // draw the plot line
         const data = dataSet[0];
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 8410fda18..8eacfbc51 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -161,7 +161,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 </div>
             );
             return (
-                <div className={`menuButton ${this.type} ${numBtnType}`} onPointerDown={e => e.stopPropagation()} onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
+                <div
+                    className={`menuButton ${this.type} ${numBtnType}`}
+                    onPointerDown={e => e.stopPropagation()}
+                    onClick={action(() => {
+                        this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
+                        Doc.UnBrushAllDocs();
+                    })}>
                     {checkResult}
                     {label}
                     {this.rootDoc.dropDownOpen ? dropdown : null}
@@ -198,7 +204,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                             e.stopPropagation();
                             e.preventDefault();
                         }}
-                        onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
+                        onClick={action(() => {
+                            this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
+                            Doc.UnBrushAllDocs();
+                        })}>
                         <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={undoBatch(action(e => setValue(Number(e.target.value))))} />
                     </div>
                     <div className={`button`} onClick={action(e => setValue(Number(checkResult) + 1))}>
@@ -215,6 +224,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                                 onClick={e => {
                                     e.stopPropagation();
                                     this.rootDoc.dropDownOpen = false;
+                                    Doc.UnBrushAllDocs();
                                 }}
                             />
                         </div>
@@ -237,7 +247,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             <div
                 className={`menuButton ${this.type} ${active}`}
                 style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
-                onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
+                onClick={action(() => {
+                    this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
+                    Doc.UnBrushAllDocs();
+                })}>
                 {this.Icon(color)}
                 {!this.label || !FontIconBox.GetShowLabels() ? null : (
                     <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
@@ -316,7 +329,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             <div
                 className={`menuButton ${this.type} ${active}`}
                 style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : 'flex' }}
-                onClick={dropdown ? () => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen) : undefined}>
+                onClick={
+                    dropdown
+                        ? () => {
+                              this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
+                              Doc.UnBrushAllDocs();
+                          }
+                        : undefined
+                }>
                 {dropdown ? null : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
                 <div className="menuButton-dropdown-header">{text && text[0].toUpperCase() + text.slice(1)}</div>
                 {label}
@@ -335,6 +355,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                             onClick={e => {
                                 e.stopPropagation();
                                 this.rootDoc.dropDownOpen = false;
+                                Doc.UnBrushAllDocs();
                             }}
                         />
                     </div>
@@ -379,6 +400,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
                 onClick={action(e => {
                     this.colorPickerClosed = !this.colorPickerClosed;
+                    setTimeout(() => Doc.UnBrushAllDocs());
                     e.stopPropagation();
                 })}
                 onPointerDown={e => e.stopPropagation()}>
@@ -397,6 +419,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                                 e.preventDefault();
                                 e.stopPropagation();
                                 this.colorPickerClosed = true;
+                                Doc.UnBrushAllDocs();
                             })}
                         />
                     </div>
@@ -816,7 +839,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
             setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
         }],
         ['fillColor', {
-            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ? Colors.MEDIUM_BLUE : 'transparent'),
+            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"),
             setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
             setMode: () => SetActiveFillColor(StrCast(value)),
         }],
diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
index 7f414ddbb..74c3c563c 100644
--- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
+++ b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
@@ -12,7 +12,7 @@ export class ColorDropdown extends Component<IButtonProps> {
         const active: string = StrCast(this.props.rootDoc.dropDownOpen);
 
         const script: string = StrCast(this.props.rootDoc.script);
-        const scriptCheck: string = script + "(undefined, true)";
+        const scriptCheck: string = script + '(undefined, true)';
         const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
 
         const stroke: boolean = false;
@@ -24,24 +24,21 @@ export class ColorDropdown extends Component<IButtonProps> {
         //     strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />);
         // }
 
-        const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
-            '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
-            '#FFFFFF', '#f1efeb'];
+        const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb'];
 
-        const colorBox = (func: (color: ColorState) => void) => <SketchPicker
-            disableAlpha={!stroke}
-            onChange={func} color={boolResult ? boolResult : "#FFFFFF"}
-            presetColors={colorOptions} />;
-        const label = !this.props.label || !FontIconBox.GetShowLabels() ? (null) :
-            <div className="fontIconBox-label" style={{ color: this.props.color, backgroundColor: this.props.backgroundColor, position: "absolute" }}>
-                {this.props.label}
-            </div>;
+        const colorBox = (func: (color: ColorState) => void) => <SketchPicker disableAlpha={!stroke} onChange={func} color={boolResult ? boolResult : '#FFFFFF'} presetColors={colorOptions} />;
+        const label =
+            !this.props.label || !FontIconBox.GetShowLabels() ? null : (
+                <div className="fontIconBox-label" style={{ color: this.props.color, backgroundColor: this.props.backgroundColor, position: 'absolute' }}>
+                    {this.props.label}
+                </div>
+            );
 
-        const dropdownCaret = <div
-            className="menuButton-dropDown"
-            style={{ borderBottomRightRadius: active ? 0 : undefined }}>
-            <FontAwesomeIcon icon={'caret-down'} color={this.props.color} size="sm" />
-        </div>;
+        const dropdownCaret = (
+            <div className="menuButton-dropDown" style={{ borderBottomRightRadius: active ? 0 : undefined }}>
+                <FontAwesomeIcon icon={'caret-down'} color={this.props.color} size="sm" />
+            </div>
+        );
 
         const click = (value: ColorState) => {
             const hex: string = value.hex;
@@ -51,26 +48,30 @@ export class ColorDropdown extends Component<IButtonProps> {
             }
         };
         return (
-            <div className={`menuButton ${this.props.type} ${active}`}
+            <div
+                className={`menuButton ${this.props.type} ${active}`}
                 style={{ color: this.props.color, borderBottomLeftRadius: active ? 0 : undefined }}
-                onClick={() => this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen}
+                onClick={() => (this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen)}
                 onPointerDown={e => e.stopPropagation()}>
                 <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
-                <div className="colorButton-color"
-                    style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />
+                <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : '#FFFFFF' }} />
                 {label}
                 {/* {dropdownCaret} */}
-                {this.props.rootDoc.dropDownOpen ?
+                {this.props.rootDoc.dropDownOpen ? (
                     <div>
-                        <div className="menuButton-dropdownBox"
-                            onPointerDown={e => e.stopPropagation()}
-                            onClick={e => e.stopPropagation()}>
+                        <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()}>
                             {colorBox(click)}
                         </div>
-                        <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.props.rootDoc.dropDownOpen = false; }} />
+                        <div
+                            className="dropbox-background"
+                            onClick={e => {
+                                e.stopPropagation();
+                                this.props.rootDoc.dropDownOpen = false;
+                            }}
+                        />
                     </div>
-                    : null}
+                ) : null}
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e89f5db52..1db56cbdd 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1394,7 +1394,7 @@ export namespace Doc {
         });
     }
     export function UnBrushAllDocs() {
-        brushManager.BrushedDoc.clear();
+        runInAction(() => brushManager.BrushedDoc.clear());
     }
 
     export function getDocTemplate(doc?: Doc) {
-- 
cgit v1.2.3-70-g09d2


From 6520bb8594b6cee9a4df09b25adf7aa590ef29e4 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 10 Apr 2023 13:49:28 -0400
Subject: fixed linechart margins

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx           | 2 +-
 src/client/views/nodes/DataVizBox/components/LineChart.tsx | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 8593c8994..2666787c5 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -95,7 +95,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @computed get selectView() {
         const width = this.props.PanelWidth() * 0.9;
         const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9;
-        const margin = { top: 10, right: 50, bottom: 50, left: 50 };
+        const margin = { top: 10, right: 10, bottom: 10, left: 10 };
         if (!this.pairs) return 'no data';
         // prettier-ignore
         switch (this.dataVizView) {
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index bc9f0be77..ecb06f64f 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -235,8 +235,8 @@ export class LineChart extends React.Component<LineChartProps> {
         const svg = (this._lineChartSvg = d3
             .select(this._lineChartRef.current)
             .append('svg')
-            .attr('width', `${width + margin.right + margin.left}`)
-            .attr('height', `${height + margin.top + margin.bottom}`)
+            .attr('width', `${width - margin.right - margin.left}`)
+            .attr('height', `${height - margin.top - margin.bottom}`)
             .append('g')
             .attr('transform', `translate(${margin.left}, ${margin.top})`));
 
-- 
cgit v1.2.3-70-g09d2


From 3cb7f85b23eb0ae3a432bbe15b8a2cda37290ce2 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Mon, 10 Apr 2023 15:32:06 -0400
Subject: fixed linechart margins

---
 package-lock.json                                          |  8 ++++----
 src/client/views/nodes/DataVizBox/DataVizBox.tsx           |  2 +-
 src/client/views/nodes/DataVizBox/components/LineChart.tsx | 12 ++++++------
 3 files changed, 11 insertions(+), 11 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 83bb8cd9c..4b83d07f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10498,14 +10498,14 @@
             "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz",
             "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=",
             "requires": {
-                "image-size": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1",
+                "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1",
                 "readable-stream": "^1.0.33",
                 "tryit": "^1.0.1"
             },
             "dependencies": {
                 "image-size": {
-                    "version": "git+ssh://git@github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1",
-                    "from": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1"
+                    "version": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1",
+                    "from": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1"
                 },
                 "isarray": {
                     "version": "0.0.1",
@@ -21004,7 +21004,7 @@
         "sparse-bitfield": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
-            "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+            "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
             "optional": true,
             "requires": {
                 "memory-pager": "^1.0.2"
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 2666787c5..eb25d3264 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -95,7 +95,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @computed get selectView() {
         const width = this.props.PanelWidth() * 0.9;
         const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9;
-        const margin = { top: 10, right: 10, bottom: 10, left: 10 };
+        const margin = { top: 10, right: 25, bottom: 50, left:25};
         if (!this.pairs) return 'no data';
         // prettier-ignore
         switch (this.dataVizView) {
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index ecb06f64f..777bf2f66 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -74,7 +74,7 @@ export class LineChart extends React.Component<LineChartProps> {
     }
     componentDidMount = () => {
         this._disposers.chartData = reaction(
-            () => ({ dataSet: this._lineChartData, w: this.props.width, h: this.props.height }),
+            () => ({ dataSet: this._lineChartData, w: this.width, h: this.height }),
             ({ dataSet, w, h }) => {
                 if (dataSet) {
                     this.drawChart([dataSet], this.rangeVals, w, h);
@@ -227,16 +227,16 @@ export class LineChart extends React.Component<LineChartProps> {
         }
 
         // creating the x and y scales
-        const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.width);
-        const yScale = scaleCreatorNumerical(0, yMax, this.height, 0);
+        const xScale = scaleCreatorNumerical(xMin, xMax, 0, width);
+        const yScale = scaleCreatorNumerical(0, yMax,height, 0);
 
         // adding svg
         const margin = this.props.margin;
         const svg = (this._lineChartSvg = d3
             .select(this._lineChartRef.current)
             .append('svg')
-            .attr('width', `${width - margin.right - margin.left}`)
-            .attr('height', `${height - margin.top - margin.bottom}`)
+            .attr('width', `${width +margin.left + margin.right}`)
+            .attr('height', `${height + margin.top + margin.bottom }`)
             .append('g')
             .attr('transform', `translate(${margin.left}, ${margin.top})`));
 
@@ -281,7 +281,7 @@ export class LineChart extends React.Component<LineChartProps> {
 
         svg.append('rect')
             .attr('class', 'overlay')
-            .attr('width', this.width)
+            .attr('width', width)
             .attr('height', this.height + margin.top + margin.bottom)
             .attr('fill', 'none')
             .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`)
-- 
cgit v1.2.3-70-g09d2

