diff options
-rw-r--r-- | package-lock.json | 31 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | src/.DS_Store | bin | 10244 -> 10244 bytes | |||
-rw-r--r-- | src/client/views/nodes/DataVizBox/ChartBox.tsx | 162 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/DataVizBox.tsx | 61 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/HistogramBox.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss | 0 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx | 16 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss | 0 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx | 13 | ||||
-rw-r--r-- | src/server/ApiManagers/DataVizManager.ts | 26 | ||||
-rw-r--r-- | src/server/DataVizUtils.ts | 6 | ||||
-rw-r--r-- | src/server/index.ts | 2 |
13 files changed, 303 insertions, 23 deletions
diff --git a/package-lock.json b/package-lock.json index 3b9cda8bb..414b8abdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2468,6 +2468,9 @@ "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", +<<<<<<< HEAD + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" +======= "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, @@ -2480,6 +2483,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/D/-/D-1.0.0.tgz", "integrity": "sha512-nQvrCBu7K2pSSEtIM0EEF03FVjcczCXInMt3moLNFbjlWx6bZrX72uT6/1uAXDbnzGUAx9gTyDiQ+vrFi663oA==" +>>>>>>> master }, "abab": { "version": "2.0.6", @@ -5059,6 +5063,14 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, +<<<<<<< HEAD + "D": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/D/-/D-1.0.0.tgz", + "integrity": "sha512-nQvrCBu7K2pSSEtIM0EEF03FVjcczCXInMt3moLNFbjlWx6bZrX72uT6/1uAXDbnzGUAx9gTyDiQ+vrFi663oA==" + }, +======= +>>>>>>> master "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -7442,8 +7454,12 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" +======= "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true +>>>>>>> master } } }, @@ -9476,7 +9492,11 @@ "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { +<<<<<<< HEAD + "image-size": "git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", +======= "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", +>>>>>>> master "readable-stream": "^1.0.33", "tryit": "^1.0.1" }, @@ -12471,6 +12491,8 @@ "write-file-atomic": "^2.4.3" }, "dependencies": { +<<<<<<< HEAD +======= "JSONStream": { "version": "1.3.5", "bundled": true, @@ -12479,6 +12501,7 @@ "through": ">=2.2.7 <3" } }, +>>>>>>> master "abbrev": { "version": "1.1.1", "resolved": false, @@ -15338,6 +15361,8 @@ } } }, +<<<<<<< HEAD +======= "string_decoder": { "version": "1.3.0", "bundled": true, @@ -15351,6 +15376,7 @@ } } }, +>>>>>>> master "stringify-package": { "version": "1.0.1", "resolved": false, @@ -17506,6 +17532,11 @@ "use-memo-one": "^1.1.1" } }, + "react-chartjs-2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.2.0.tgz", + "integrity": "sha512-9Vm9Sg9XAKiR579/FnBkesofjW9goaaFLfS7XlGTzUJlWFZGSE6A/pBI6+i/bP3pobKZoFcWJdFnjShytToqXw==" + }, "react-color": { "version": "2.19.3", "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", diff --git a/package.json b/package.json index 2e3ef0a07..87cd1c074 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,12 @@ "bootstrap": "^4.6.1", "browser-assert": "^1.2.1", "bson": "^4.6.1", +<<<<<<< HEAD + "canvas": "^2.9.0", + "chart.js": "^3.8.0", +======= "canvas": "^2.9.3", +>>>>>>> master "child_process": "^1.0.2", "chrome": "^0.1.0", "class-transformer": "^0.2.0", @@ -264,6 +269,7 @@ "react-audio-waveform": "0.0.5", "react-autosuggest": "^9.4.3", "react-beautiful-dnd": "^13.1.0", + "react-chartjs-2": "^4.2.0", "react-color": "^2.19.3", "react-compound-slider": "^2.5.0", "react-datepicker": "^3.8.0", diff --git a/src/.DS_Store b/src/.DS_Store Binary files differindex 4751acf44..69407d6c2 100644 --- a/src/.DS_Store +++ b/src/.DS_Store diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx new file mode 100644 index 000000000..07f754637 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -0,0 +1,162 @@ +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 { ChartJSOrUndefined } from "react-chartjs-2/dist/types"; +import { action, computed, observable } from "mobx"; +import { Cast, StrCast } from "../../../../fields/Types"; + + +export interface ChartBoxProps { + rootDoc: Doc; + pairs: {x: number, y:number}[]; +} + +export interface ChartJsData { + labels: number[]; + datasets: { + label: string; + data: number[]; + backgroundColor: string[]; + }[] +} + +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, + PointElement, + LineElement + ); + +const primaryColor = 'rgba(53, 162, 235, 0.5)'; +const selectedColor = 'rgba(255, 99, 132, 0.5)'; + +@observer +export class ChartBox extends React.Component<ChartBoxProps> { + private _chartRef: any = React.createRef<ChartJSOrUndefined<"bar", number[], string>>(); + + @observable private _prevColor: string | undefined= undefined; + @observable private _prevIndex: { dIndex: number, index: number} | undefined = undefined; + @observable private _chartJsData: ChartJsData | undefined= 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"; + } + } + + @computed get options() { + return { + responsive: true, + plugins: { + legend: { + position: 'top' as const, + }, + title: { + display: true, + text: 'Bar Chart', + }, + }, + } + } + + @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(); + } + + @action + onClickChangeChart = (e: React.MouseEvent<HTMLButtonElement>) => { + e.preventDefault(); + e.stopPropagation(); + console.log(e.currentTarget.value); + this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase(); + } + + @action + onClick = (e: any) => { + e.preventDefault(); + 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 }; + 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); + } + + render() { + if (this.props.pairs && this._chartJsData) { + 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)} />) + } + </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> + } + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 592723ee9..c5ad3c84d 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; @@ -38,40 +41,47 @@ 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; - } - @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(); @@ -80,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> diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx index 00dc2ef46..4488323fe 100644 --- a/src/client/views/nodes/DataVizBox/HistogramBox.tsx +++ b/src/client/views/nodes/DataVizBox/HistogramBox.tsx @@ -3,6 +3,7 @@ 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 { @@ -15,6 +16,8 @@ interface HistogramBoxProps { 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}; diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx new file mode 100644 index 000000000..ac1b7699b --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx @@ -0,0 +1,16 @@ +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 new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss diff --git a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx new file mode 100644 index 000000000..d3440dea1 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx @@ -0,0 +1,13 @@ +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/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts new file mode 100644 index 000000000..0d43130d1 --- /dev/null +++ b/src/server/ApiManagers/DataVizManager.ts @@ -0,0 +1,26 @@ +import { csvParser, csvToString } from "../DataVizUtils"; +import { Method, _success } from "../RouteManager"; +import ApiManager, { Registration } from "./ApiManager"; +import { Directory, serverPathToFile } from "./UploadManager"; +import * as path from 'path'; + +export default class DataVizManager extends ApiManager { + protected initialize(register: Registration): void { + register({ + method: Method.GET, + subscription: "/csvData", + secureHandler: async ({ req, res }) => { + const uri = req.query.uri as string; + + return new Promise<void>(resolve => { + const name = path.basename(uri); + const sPath = serverPathToFile(Directory.csv, name); + const parsedCsv = csvParser(csvToString(sPath)); + _success(res, parsedCsv); + resolve(); + }); + } + }); + } + +}
\ No newline at end of file diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts index 4fd0ca6ff..2528fb1ab 100644 --- a/src/server/DataVizUtils.ts +++ b/src/server/DataVizUtils.ts @@ -1,3 +1,5 @@ +import { readFileSync } from "fs"; + export function csvParser(csv: string) { const lines = csv.split("\n"); const headers = lines[0].split(","); @@ -10,4 +12,8 @@ export function csvParser(csv: string) { return obj; }); return data; +} + +export function csvToString(path: string) { + return readFileSync(path, 'utf8'); }
\ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index f8c32103b..0acb7f20f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -4,6 +4,7 @@ import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import * as qs from 'query-string'; import { log_execution } from "./ActionUtilities"; +import DataVizManager from "./ApiManagers/DataVizManager"; import DeleteManager from "./ApiManagers/DeleteManager"; import DownloadManager from './ApiManagers/DownloadManager'; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; @@ -73,6 +74,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: new UtilManager(), new GeneralGoogleManager(), new GooglePhotosManager(), + new DataVizManager(), ]; // initialize API Managers |