aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-08-25 01:55:09 -0400
committerbobzel <zzzman@gmail.com>2023-08-25 01:55:09 -0400
commitfa98e9f411ed19a752a912e42b6ed9607d151159 (patch)
tree0ac3b2ca9b4bc3e2e27f5066a4e297f17d8c83c7 /src
parenteeb7d43e9eca66c8e3418e89492848589ee20aa3 (diff)
optimizations to speed up pie/table viz
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx5
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx177
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx164
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx30
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx169
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx247
7 files changed, 369 insertions, 425 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e7d3e7efc..4933f0a7c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -680,7 +680,7 @@ export namespace Docs {
DocumentType.DATAVIZ,
{
layout: { view: DataVizBox, dataField: defaultDataKey },
- options: { dataViz_title: '', _layout_fitWidth: true, nativeDimModifiable: true },
+ options: { dataViz_title: '', dataViz: 'table', _layout_fitWidth: true, nativeDimModifiable: true },
},
],
[
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 7c869af24..420e6a318 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -12,10 +12,7 @@ export class CollectionFreeFormLinksView extends React.Component {
@computed get uniqueConnections() {
return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews))
.filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath)))
- .map(c => {
- console.log("got a connectoin", c)
- return <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />;
- });
+ .map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
}
render() {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 284923092..f9f241234 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,3 +1,4 @@
+import { Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, ObservableMap } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -9,13 +10,11 @@ import { Docs } from '../../../documents/Documents';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
import { PinProps } from '../trails';
+import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
+import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
import './DataVizBox.scss';
-import { Histogram } from './components/Histogram';
-import { PieChart } from './components/PieChart';
-import { Toggle, ToggleType, Type } from 'browndash-components';
-import { Utils } from '../../../../Utils';
export enum DataVizView {
TABLE = 'table',
@@ -30,19 +29,29 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return FieldView.LayoutString(DataVizBox, fieldStr);
}
- // all data
- static pairSet = new ObservableMap<string, { [key: string]: string }[]>();
- @computed.struct get pairs() {
- return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
- }
+ // all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records
+ static dataset = new ObservableMap<string, { [key: string]: string }[]>();
+ private _vizRenderer: LineChart | Histogram | PieChart | undefined;
- private _chartRenderer: LineChart | Histogram | PieChart | undefined;
+ // all CSV records in the dataset
+ @computed.struct get records() {
+ return DataVizBox.dataset.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
+ }
- // current displayed chart type
+ // currently chosen visualization type: line, pie, histogram, table
@computed get dataVizView(): DataVizView {
return StrCast(this.layoutDoc._dataViz, 'table') as DataVizView;
}
+ @computed get dataUrl() {
+ return Cast(this.dataDoc[this.fieldKey], CsvField);
+ }
+ @computed.struct get axes() {
+ return StrListCast(this.layoutDoc.dataViz_axes);
+ }
+
+ selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));
+
@action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
const changedView = this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
@@ -56,7 +65,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.layoutDoc['_' + key] = data[key];
}
});
- const func = () => this._chartRenderer?.restoreView(data);
+ const func = () => this._vizRenderer?.restoreView(data);
if (changedView || changedAxes) {
setTimeout(func, 100);
return true;
@@ -66,7 +75,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
const anchor = !pinProps
? this.rootDoc
- : this._chartRenderer?.getAnchor(pinProps) ??
+ : this._vizRenderer?.getAnchor(pinProps) ??
Docs.Create.ConfigDocument({
// when we clear selection -> we should have it so chartBox getAnchor returns undefined
// this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
@@ -87,92 +96,78 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return anchor;
};
- @computed.struct get axes() {
- return StrListCast(this.layoutDoc.dataViz_axes);
+ componentDidMount() {
+ this.props.setContentView?.(this);
+ if (!DataVizBox.dataset.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) this.fetchData();
}
- selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));
+
+ fetchData = () => {
+ DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
+ fetch('/csvData?uri=' + this.dataUrl?.url.href) //
+ .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res))));
+ };
// toggles for user to decide which chart type to view the data in
- @computed get selectView() {
+ renderVizView = () => {
const width = this.props.PanelWidth() * 0.9;
const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9;
const margin = { top: 10, right: 25, bottom: 75, left: 45 };
- if (!this.pairs) return 'no data';
- switch (this.dataVizView) {
- case DataVizView.TABLE:
- return <TableBox layoutDoc={this.layoutDoc} pairs={this.pairs} axes={this.axes} height={height} width={width} margin={margin} rootDoc={this.rootDoc} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
- case DataVizView.LINECHART:
- return (
- <LineChart
- layoutDoc={this.layoutDoc}
- ref={r => (this._chartRenderer = r ?? undefined)}
- height={height}
- width={width}
- fieldKey={this.fieldKey}
- margin={margin}
- rootDoc={this.rootDoc}
- axes={this.axes}
- pairs={this.pairs}
- dataDoc={this.dataDoc}
- />
- );
- case DataVizView.HISTOGRAM:
- return (
- <Histogram
- layoutDoc={this.layoutDoc}
- ref={r => (this._chartRenderer = r ?? undefined)}
- height={height}
- width={width}
- fieldKey={this.fieldKey}
- margin={margin}
- rootDoc={this.rootDoc}
- axes={this.axes}
- pairs={this.pairs}
- dataDoc={this.dataDoc}
- />
- );
- case DataVizView.PIECHART:
- return (
- <PieChart
- layoutDoc={this.layoutDoc}
- ref={r => (this._chartRenderer = r ?? undefined)}
- height={height}
- width={width}
- fieldKey={this.fieldKey}
- margin={margin}
- rootDoc={this.rootDoc}
- axes={this.axes}
- pairs={this.pairs}
- dataDoc={this.dataDoc}
- />
- );
+ if (this.records) {
+ switch (this.dataVizView) {
+ case DataVizView.TABLE:
+ return <TableBox layoutDoc={this.layoutDoc} records={this.records} axes={this.axes} height={height} width={width} margin={margin} rootDoc={this.rootDoc} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.LINECHART:
+ return (
+ <LineChart
+ layoutDoc={this.layoutDoc}
+ ref={r => (this._vizRenderer = r ?? undefined)}
+ height={height}
+ width={width}
+ fieldKey={this.fieldKey}
+ margin={margin}
+ rootDoc={this.rootDoc}
+ axes={this.axes}
+ records={this.records}
+ dataDoc={this.dataDoc}
+ />
+ );
+ case DataVizView.HISTOGRAM:
+ return (
+ <Histogram
+ layoutDoc={this.layoutDoc}
+ ref={r => (this._vizRenderer = r ?? undefined)}
+ height={height}
+ width={width}
+ fieldKey={this.fieldKey}
+ margin={margin}
+ rootDoc={this.rootDoc}
+ axes={this.axes}
+ records={this.records}
+ dataDoc={this.dataDoc}
+ />
+ );
+ case DataVizView.PIECHART:
+ return (
+ <PieChart
+ layoutDoc={this.layoutDoc}
+ ref={r => (this._vizRenderer = r ?? undefined)}
+ height={height}
+ width={width}
+ fieldKey={this.fieldKey}
+ margin={margin}
+ rootDoc={this.rootDoc}
+ axes={this.axes}
+ records={this.records}
+ dataDoc={this.dataDoc}
+ />
+ );
+ }
}
- }
-
- @computed get dataUrl() {
- return Cast(this.dataDoc[this.fieldKey], CsvField);
- }
-
- componentDidMount() {
- this.props.setContentView?.(this);
- this.fetchData();
- if (!this.layoutDoc._dataViz) this.layoutDoc._dataViz = this.dataVizView;
- }
-
- fetchData() {
- if (DataVizBox.pairSet.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) return;
- DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []);
- fetch('/csvData?uri=' + this.dataUrl?.url.href) //
- .then(res => res.json().then(action(res => {
- if (!res.errno) {
- DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res);
- if (!this.dataDoc.dataViz_rowGuids && this.pairs) this.dataDoc.dataViz_rowGuids = new List<string>(this.pairs.map(row => Utils.GenerateGuid()));
- }
- })));
- }
+ return 'no data/visualization';
+ };
render() {
- return !this.pairs?.length ? (
+ return !this.records?.length ? (
// displays how to get data into the DataVizBox if its empty
<div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
) : (
@@ -195,7 +190,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz == DataVizView.HISTOGRAM} />
<Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.PIECHART} />
</div>
- {this.selectView}
+ {this.renderVizView()}
</div>
);
}
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index b3bdccbbb..48cbe5c5f 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -1,26 +1,26 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
+import * as d3 from 'd3';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, StrListCast } from '../../../../../fields/Doc';
import * as React from 'react';
-import * as d3 from 'd3';
-import { IReactionDisposer, action, computed, observable, reaction } from 'mobx';
-import { LinkManager } from '../../../../util/LinkManager';
-import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
-import { PinProps, PresBox } from '../../trails';
-import { Docs } from '../../../../documents/Documents';
-import { List } from '../../../../../fields/List';
-import './Chart.scss';
-import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
import { FaFillDrip } from 'react-icons/fa';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Doc, StrListCast } from '../../../../../fields/Doc';
+import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { Docs } from '../../../../documents/Documents';
+import { LinkManager } from '../../../../util/LinkManager';
+import { undoable } from '../../../../util/UndoManager';
+import { PinProps, PresBox } from '../../trails';
import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
-import { undoBatch, undoable } from '../../../../util/UndoManager';
+import './Chart.scss';
export interface HistogramProps {
rootDoc: Doc;
layoutDoc: Doc;
axes: string[];
- pairs: { [key: string]: any }[];
+ records: { [key: string]: any }[];
width: number;
height: number;
dataDoc: Doc;
@@ -48,41 +48,41 @@ export class Histogram extends React.Component<HistogramProps> {
// filters all data to just display selected data if brushed (created from an incoming link)
@computed get _histogramData() {
- var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids);
+ var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds);
if (this.props.axes.length < 1) return [];
if (this.props.axes.length < 2) {
var ax0 = this.props.axes[0];
- if (/\d/.test(this.props.pairs[0][ax0])) {
+ if (/\d/.test(this.props.records[0][ax0])) {
this.numericalXData = true;
}
- return this.props.pairs
- ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
- .map(pair => ({ [ax0]: pair[this.props.axes[0]] }));
+ return this.props.records
+ ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
+ .map(record => ({ [ax0]: record[this.props.axes[0]] }));
}
var ax0 = this.props.axes[0];
var ax1 = this.props.axes[1];
- if (/\d/.test(this.props.pairs[0][ax0])) {
+ if (/\d/.test(this.props.records[0][ax0])) {
this.numericalXData = true;
}
- if (/\d/.test(this.props.pairs[0][ax1])) {
+ if (/\d/.test(this.props.records[0][ax1])) {
this.numericalYData = true;
}
- return this.props.pairs
- ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
- .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] }));
+ return this.props.records
+ ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
+ .map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
}
@computed get defaultGraphTitle() {
var ax0 = this.props.axes[0];
var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
- if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.pairs[0][ax1]) || !this.numericalYData) {
+ if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.records[0][ax1]) || !this.numericalYData) {
return ax0 + ' Histogram';
} else return ax0 + ' by ' + ax1 + ' Histogram';
}
@computed get incomingLinks() {
return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link
+ .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
.map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -100,11 +100,7 @@ export class Histogram extends React.Component<HistogramProps> {
componentDidMount = () => {
this._disposers.chartData = reaction(
() => ({ dataSet: this._histogramData, w: this.width, h: this.height }),
- ({ dataSet, w, h }) => {
- if (dataSet!.length > 0) {
- this.drawChart(dataSet, w, h);
- }
- },
+ ({ dataSet, w, h }) => dataSet!.length > 0 && this.drawChart(dataSet, w, h),
{ fireImmediately: true }
);
};
@@ -114,7 +110,6 @@ export class Histogram extends React.Component<HistogramProps> {
// create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
getAnchor = (pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({
- //
title: 'histogram doc selection' + this._currSelected,
});
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
@@ -131,21 +126,15 @@ export class Histogram extends React.Component<HistogramProps> {
// cleans data by converting numerical data to numbers and taking out empty cells
data = (dataSet: any) => {
- var validData = dataSet.filter((d: { [x: string]: unknown }) => {
- var valid = true;
- Object.keys(dataSet[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) valid = false;
- });
- return valid;
- });
- var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
- const data = validData.map((d: { [x: string]: any }) => {
- if (this.numericalXData) {
- return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
- }
- return d[field!];
- });
- return data;
+ var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
+ return !field
+ ? []
+ : validData.map((d: { [x: string]: any }) =>
+ !this.numericalXData //
+ ? d[field]
+ : +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')
+ );
};
// outlines the bar selected / hovered over
@@ -185,24 +174,18 @@ export class Histogram extends React.Component<HistogramProps> {
d3.select(this._histogramRef.current).select('svg').remove();
d3.select(this._histogramRef.current).select('.tooltip').remove();
- var data = this.data(dataSet);
- var xAxisTitle = Object.keys(dataSet[0])[0];
- var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
- let uniqueArr: unknown[] = [...new Set(data)];
+ const data = this.data(dataSet);
+ const xAxisTitle = Object.keys(dataSet[0])[0];
+ const yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
+ const uniqueArr: unknown[] = [...new Set(data)];
var numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
var translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
if (numBins > this.maxBins) numBins = this.maxBins;
- var startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
- var endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
+ const startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
+ const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
// converts data into Objects
- var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => {
- var valid = true;
- Object.keys(dataSet[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) valid = false;
- });
- return valid;
- });
+ var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
if (!this.numericalXData) {
var histStringDataSet: { [x: string]: unknown }[] = [];
if (this.numericalYData) {
@@ -321,7 +304,7 @@ export class Histogram extends React.Component<HistogramProps> {
// click/hover
const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet));
const onHover = action((e: any) => {
- const selected = this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
+ this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
updateHighlights();
});
const mouseOut = action((e: any) => {
@@ -360,10 +343,10 @@ export class Histogram extends React.Component<HistogramProps> {
'transform',
this.numericalYData
? function (d) {
- var eachData = histDataSet.filter((data: { [x: string]: number }) => {
+ const eachData = histDataSet.filter((data: { [x: string]: number }) => {
return data[xAxisTitle] == d[0];
});
- var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
return 'translate(' + x(d.x0!) + ',' + y(length) + ')';
}
: function (d) {
@@ -374,10 +357,10 @@ export class Histogram extends React.Component<HistogramProps> {
'height',
this.numericalYData
? function (d) {
- var eachData = histDataSet.filter((data: { [x: string]: number }) => {
+ const eachData = histDataSet.filter((data: { [x: string]: number }) => {
return data[xAxisTitle] == d[0];
});
- var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
return height - y(length);
}
: function (d) {
@@ -389,7 +372,7 @@ export class Histogram extends React.Component<HistogramProps> {
'class',
selected
? function (d) {
- return selected && selected[0] == d[0] ? 'histogram-bar hover' : 'histogram-bar';
+ return selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar';
}
: function (d) {
return 'histogram-bar';
@@ -397,11 +380,11 @@ export class Histogram extends React.Component<HistogramProps> {
)
.attr('fill', d => {
var barColor;
- var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
- barColors.map(each => {
+ const barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+ barColors.forEach(each => {
if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
else {
- var range = StrCast(each[0]).split(' to ');
+ const range = StrCast(each[0]).split(' to ');
if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
}
});
@@ -411,23 +394,19 @@ export class Histogram extends React.Component<HistogramProps> {
@action changeSelectedColor = (color: string) => {
this.curBarSelected.attr('fill', color);
- var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ const barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec('string'), null);
- barColors.map(each => {
- if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1);
- });
+ barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
barColors.push(StrCast(barName + '::' + color));
};
@action eraseSelectedColor = () => {
this.curBarSelected.attr('fill', this.props.layoutDoc.defaultHistogramColor);
- var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ const barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec('string'), null);
- barColors.map(each => {
- if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1);
- });
+ barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
};
render() {
@@ -439,25 +418,22 @@ export class Histogram extends React.Component<HistogramProps> {
if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this.props.layoutDoc.defaultHistogramColor) this.props.layoutDoc.defaultHistogramColor = '#69b3a2';
if (!this.props.layoutDoc.histogramBarColors) this.props.layoutDoc.histogramBarColors = new List<string>();
- var selected: string;
+ var selected = 'none';
if (this._currSelected) {
curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
selected = '{ ';
- Object.keys(this._currSelected).map(key => {
- key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
- });
- selected = selected.substring(0, selected.length - 2);
- selected += ' }';
- } else selected = 'none';
+ Object.keys(this._currSelected).forEach(key =>
+ key //
+ ? (selected += key + ': ' + this._currSelected[key] + ', ')
+ : ''
+ );
+ selected = selected.substring(0, selected.length - 2) + ' }';
+ }
var selectedBarColor;
var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
- barColors.map(each => {
- if (each[0] == curSelectedBarName!) selectedBarColor = each[1];
- });
+ barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
- this.componentDidMount();
-
- if (this._histogramData.length > 0 || (!this.incomingLinks || this.incomingLinks.length==0)) {
+ if (this._histogramData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
return this.props.axes.length >= 1 ? (
<div className="chart-container">
<div className="graph-title">
@@ -514,10 +490,8 @@ export class Histogram extends React.Component<HistogramProps> {
) : (
<span className="chart-container"> {'first use table view to select a column to graph'}</span>
);
- } else
- return (
- // when it is a brushed table and the incoming table doesn't have any rows selected
- <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
- );
+ }
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ return <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 46cf27705..655c6de20 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -28,7 +28,7 @@ export interface LineChartProps {
rootDoc: Doc;
layoutDoc: Doc;
axes: string[];
- pairs: { [key: string]: any }[];
+ records: { [key: string]: any }[];
width: number;
height: number;
dataDoc: Doc;
@@ -50,11 +50,11 @@ export class LineChart extends React.Component<LineChartProps> {
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
@computed get _lineChartData() {
- var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids);
+ var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds);
if (this.props.axes.length <= 1) return [];
- return this.props.pairs
- ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
- .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) }))
+ return this.props.records
+ ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)])))
+ .map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[this.props.axes[1]]) }))
.sort((a, b) => (a.x < b.x ? -1 : 1));
}
@computed get graphTitle() {
@@ -63,7 +63,7 @@ export class LineChart extends React.Component<LineChartProps> {
@computed get incomingLinks() {
return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
.filter(link => {
- return link.link_anchor_1 == this.props.rootDoc.draggedFrom;
+ return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz;
}) // get links where this chart doc is the target of the link
.map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -73,7 +73,7 @@ export class LineChart extends React.Component<LineChartProps> {
return this.incomingLinks // all links that are pointing to this node
.map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes
.filter(dvb => dvb)
- .map(dvb => dvb.pairs?.filter(pair => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor
+ .map(dvb => dvb.records?.filter(record => record['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor
.lastElement();
}
@computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -91,7 +91,7 @@ export class LineChart extends React.Component<LineChartProps> {
// redraw annotations when the chart data has changed, or the local or inherited selection has changed
this.clearAnnotations();
this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true);
- this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
+ this.incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
}
},
{ fireImmediately: true }
@@ -117,7 +117,7 @@ export class LineChart extends React.Component<LineChartProps> {
// redraw annotations when the chart data has changed, or the local or inherited selection has changed
this.clearAnnotations();
selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true);
- incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
+ incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
},
{ fireImmediately: true }
);
@@ -193,7 +193,7 @@ export class LineChart extends React.Component<LineChartProps> {
@computed get defaultGraphTitle() {
var ax0 = this.props.axes[0];
var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
- if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) {
+ if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
return ax0 + ' Line Chart';
} else return ax1 + ' by ' + ax0 + ' Line Chart';
}
@@ -217,7 +217,7 @@ export class LineChart extends React.Component<LineChartProps> {
// TODO: nda - get rid of svg element in the list?
if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = undefined;
else this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
- this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true));
+ this.props.records.forEach(record => record[this.props.axes[0]] === x && record[this.props.axes[1]] === y && (record.selected = true));
}
drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -358,10 +358,10 @@ export class LineChart extends React.Component<LineChartProps> {
else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_lineChart_' + this.props.axes[0];
if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none';
- if (this._lineChartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){
- return this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? (
- <div className="chart-container" >
- <div className="graph-title">
+ if (this._lineChartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
+ return this.props.axes.length >= 2 && /\d/.test(this.props.records[0][this.props.axes[0]]) && /\d/.test(this.props.records[0][this.props.axes[1]]) ? (
+ <div className="chart-container">
+ <div className="graph-title">
<EditableText
val={StrCast(this.props.layoutDoc[titleAccessor])}
setVal={undoable(
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index e730d54f0..79d7b3f23 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -1,25 +1,24 @@
+import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
+import * as d3 from 'd3';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, StrListCast } from '../../../../../fields/Doc';
import * as React from 'react';
-import * as d3 from 'd3';
-import { IReactionDisposer, action, computed, observable, reaction } from 'mobx';
-import { LinkManager } from '../../../../util/LinkManager';
-import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
-import { PinProps, PresBox } from '../../trails';
-import { Docs } from '../../../../documents/Documents';
-import { List } from '../../../../../fields/List';
-import './Chart.scss';
-import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
import { FaFillDrip } from 'react-icons/fa';
+import { Doc, NumListCast, StrListCast } from '../../../../../fields/Doc';
+import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { Docs } from '../../../../documents/Documents';
+import { LinkManager } from '../../../../util/LinkManager';
import { undoable } from '../../../../util/UndoManager';
-import { Utils } from '../../../../../Utils';
+import { PinProps, PresBox } from '../../trails';
+import './Chart.scss';
export interface PieChartProps {
rootDoc: Doc;
layoutDoc: Doc;
axes: string[];
- pairs: { [key: string]: any }[];
+ records: { [key: string]: any }[];
width: number;
height: number;
dataDoc: Doc;
@@ -37,48 +36,51 @@ export class PieChart extends React.Component<PieChartProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _piechartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _piechartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
- private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios
- @observable _currSelected: any | undefined = undefined; // Object of selected slice
private curSliceSelected: any = undefined; // d3 data of selected slice
private selectedData: any = undefined; // Selection of selected slice
private hoverOverData: any = undefined; // Selection of slice being hovered over
+ @observable _currSelected: any | undefined = undefined; // Object of selected slice
- @computed get rowGuids() {
- return StrListCast(this.props.layoutDoc.dataViz_rowGuids)
+ @computed get _tableDataIds() {
+ return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows);
+ }
+ // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
+ @computed get _tableData() {
+ return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+ }
+
+ // organized by specified number percentages/ratios if one column is selected and it contains numbers
+ // otherwise, assume data is organized by categories
+ @computed get byCategory() {
+ if (this.props.axes.length === 1) {
+ return !/\d/.test(this.props.records[0][this.props.axes[0]]);
+ }
+ return true;
}
// filters all data to just display selected data if brushed (created from an incoming link)
@computed get _piechartData() {
if (this.props.axes.length < 1) return [];
+
+ const ax0 = this.props.axes[0];
if (this.props.axes.length < 2) {
- var ax0 = this.props.axes[0];
- if (/\d/.test(this.props.pairs[0][ax0])) {
- this.byCategory = false;
- }
- return this.props.pairs
- ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)])))
- .map(pair => ({ [ax0]: pair[this.props.axes[0]] }));
- }
- var ax0 = this.props.axes[0];
- var ax1 = this.props.axes[1];
- if (/\d/.test(this.props.pairs[0][ax0])) {
- this.byCategory = false;
+ return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] }));
}
- return this.props.pairs
- ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)])))
- .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] }));
+ const ax1 = this.props.axes[1];
+ return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
}
@computed get defaultGraphTitle() {
var ax0 = this.props.axes[0];
var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
- if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) {
+ if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
return ax0 + ' Pie Chart';
- } else return ax1 + ' by ' + ax0 + ' Pie Chart';
+ }
+ return ax1 + ' by ' + ax0 + ' Pie Chart';
}
@computed get incomingLinks() {
return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link
+ .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
.map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -119,21 +121,15 @@ export class PieChart extends React.Component<PieChartProps> {
// cleans data by converting numerical data to numbers and taking out empty cells
data = (dataSet: any) => {
- var validData = dataSet.filter((d: { [x: string]: unknown }) => {
- var valid = true;
- Object.keys(dataSet[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) valid = false;
- });
- return valid;
- });
- var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
- const data = validData.map((d: { [x: string]: any }) => {
- if (!this.byCategory) {
- return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
- }
- return d[field!];
- });
- return data;
+ const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+ const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
+ return !field
+ ? undefined
+ : validData.map((d: { [x: string]: any }) =>
+ this.byCategory
+ ? d[field] //
+ : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')
+ );
};
// outlines the slice selected / hovered over
@@ -195,13 +191,7 @@ export class PieChart extends React.Component<PieChartProps> {
// converts data into Objects
var data = this.data(dataSet);
- var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => {
- var valid = true;
- Object.keys(dataSet[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) valid = false;
- });
- return valid;
- });
+ var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
if (this.byCategory) {
let uniqueCategories = [...new Set(data)];
var pieStringDataSet: { frequency: number }[] = [];
@@ -235,7 +225,7 @@ export class PieChart extends React.Component<PieChartProps> {
// click/hover
const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet));
const onHover = action((e: any) => {
- const selected = this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
+ this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
updateHighlights();
});
const mouseOut = action((e: any) => {
@@ -255,16 +245,19 @@ export class PieChart extends React.Component<PieChartProps> {
// drawing the slices
var selected = this.selectedData;
var arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
+ const sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::'));
+ const possibleDataPointVals = pieDataSet.map((each: { [x: string]: any | { valueOf(): number } }) => {
+ try {
+ each[percentField] = Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ } catch (error) {
+ //return each[percentField] == d.data;
+ }
+ return each;
+ });
arcs.append('path')
.attr('fill', (d, i) => {
- var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => {
- try {
- return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data);
- } catch (error) {
- return each[percentField] == d.data;
- }
- });
var dataPoint;
+ const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
else {
dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
@@ -272,11 +265,8 @@ export class PieChart extends React.Component<PieChartProps> {
}
var sliceColor;
if (dataPoint) {
- var accessByName = dataPoint[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
- var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::'));
- sliceColors.map(each => {
- if (each[0] == StrCast(accessByName)) sliceColor = each[1];
- });
+ var accessByName = dataPoint[this.props.axes[0]];
+ sliceColors.forEach(each => each[0] == StrCast(accessByName) && (sliceColor = each[1]));
}
return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length];
})
@@ -298,29 +288,25 @@ export class PieChart extends React.Component<PieChartProps> {
// adding labels
trackDuplicates = {};
data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
- arcs.append('text')
- .attr('transform', function (d) {
- var centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
- var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
- return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')';
- })
- .attr('text-anchor', 'middle')
- .text(function (d) {
- var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => {
- try {
- return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data);
- } catch (error) {
- return each[percentField] == d.data;
+ arcs.size() < 100 &&
+ arcs
+ .append('text')
+ .attr('transform', function (d) {
+ var centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
+ var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
+ return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')';
+ })
+ .attr('text-anchor', 'middle')
+ .text(function (d) {
+ var dataPoint;
+ const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
+ if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
+ else {
+ dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
+ trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
}
+ return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : '';
});
- var dataPoint;
- if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
- else {
- dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
- trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
- }
- return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : '';
- });
};
@action changeSelectedColor = (color: string) => {
@@ -335,7 +321,6 @@ export class PieChart extends React.Component<PieChartProps> {
};
render() {
- this.componentDidMount();
var titleAccessor: any = '';
if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0] + '-' + this.props.axes[1];
else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0];
@@ -358,7 +343,7 @@ export class PieChart extends React.Component<PieChartProps> {
if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
});
- if (this._piechartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){
+ if (this._piechartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) {
return this.props.axes.length >= 1 ? (
<div className="chart-container">
<div className="graph-title">
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index e1c5aa125..e90b541ae 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,21 +1,21 @@
-import { action, computed } from 'mobx';
+import { action, computed, IReactionDisposer, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, Field, StrListCast } from '../../../../../fields/Doc';
+import { Doc, Field, NumListCast, StrListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
+import { listSpec } from '../../../../../fields/Schema';
+import { Cast, DocCast } from '../../../../../fields/Types';
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
import { DragManager } from '../../../../util/DragManager';
+import { LinkManager } from '../../../../util/LinkManager';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
-import { LinkManager } from '../../../../util/LinkManager';
-import { Cast, DocCast } from '../../../../../fields/Types';
import './Chart.scss';
-import { listSpec } from '../../../../../fields/Schema';
interface TableBoxProps {
rootDoc: Doc;
layoutDoc: Doc;
- pairs: { [key: string]: any }[];
+ records: { [key: string]: any }[];
selectAxes: (axes: string[]) => void;
axes: string[];
width: number;
@@ -31,20 +31,26 @@ interface TableBoxProps {
@observer
export class TableBox extends React.Component<TableBoxProps> {
- // filters all data to just display selected data if brushed (created from an incoming link)
- @computed get _tableData() {
- if (this.incomingLinks.length <= 0) return this.props.pairs;
- return this.props.pairs?.filter(pair => StrListCast(this.incomingLinks[0]?.dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)]));
+ _inputChangedDisposer?: IReactionDisposer;
+ componentDidMount() {
+ // if the tableData changes (ie., when records are selected by the parent (input) visulization),
+ // then we need to remove any selected rows that are no longer part of the visualized dataset.
+ this._inputChangedDisposer = reaction(() => this._tableData.slice(), this.filterSelectedRowsDown, { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ this._inputChangedDisposer?.();
+ }
+ @computed get _tableDataIds() {
+ return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows);
}
- @computed get rowGuids() {
- return StrListCast(this.props.layoutDoc.dataViz_rowGuids)
+ // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
+ @computed get _tableData() {
+ return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
}
@computed get incomingLinks() {
return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- .filter(link => {
- return link.link_anchor_1 == this.props.rootDoc.draggedFrom;
- }) // get links where this chart doc is the target of the link
+ .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
.map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@@ -53,128 +59,115 @@ export class TableBox extends React.Component<TableBoxProps> {
}
// updates the 'selected' field to no longer include rows that aren't in the table
- filterSelectedRowsDown() {
- if (!this.props.layoutDoc.dataViz_selectedRows) this.props.layoutDoc.dataViz_selectedRows = new List<string>();
- const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null);
- const incomingSelected = this.incomingLinks.length ? StrListCast(this.incomingLinks[0].dataViz_selectedRows) : undefined;
- if (incomingSelected) {
- const newsel = selected.filter(guid => incomingSelected.includes(guid));// filters through selected to remove guids that were removed in the incoming data
- selected.length = 0;
- selected.push(...newsel);
- }
- }
+ filterSelectedRowsDown = () => {
+ const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows);
+ this.props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
+ };
render() {
- this.filterSelectedRowsDown();
if (this._tableData.length > 0) {
return (
- <div className="table-container" style={{ height: this.props.height }}>
+ <div
+ className="table-container"
+ style={{ height: this.props.height }}
+ ref={r =>
+ r?.addEventListener(
+ 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
+ (e: WheelEvent) => {
+ if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
+ e.stopPropagation();
+ },
+ { passive: false }
+ )
+ }>
<table className="table">
<thead>
<tr className="table-row">
- {this.columns
- .filter(col => !col.startsWith('select'))
- .map(col => {
- const header = React.createRef<HTMLElement>();
- return (
- <th
- key={this.columns.indexOf(col)}
- ref={header as any}
- style={{
- color: this.props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this.props.axes.lastElement() === col ? 'darkred' : undefined,
- background: this.props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this.props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
- fontWeight: 'bolder',
- border: '3px solid black',
- }}
- onPointerDown={e => {
- const downX = e.clientX;
- const downY = e.clientY;
- setupMoveUpEvents(
- {},
- e,
- e => {
- // dragging off a column to create a brushed DataVizBox
- const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
- const targetCreator = (annotationOn: Doc | undefined) => {
- const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
- embedding._dataViz = DataVizView.TABLE;
- embedding._dataViz_axes = new List<string>([col, col]);
- embedding._draggedFrom = this.props.docView?.()!.rootDoc!;
- embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
- embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
- embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
- embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
- return embedding;
- };
- if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
- DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.linkDocument.link_displayLine = true;
- e.linkDocument.link_matchEmbeddings = true;
- // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
- // e.annoDragData.linkSourceDoc.followLinkZoom = false;
- }
- },
- });
- return true;
- }
- return false;
- },
- emptyFunction,
- action(e => {
- const newAxes = this.props.axes;
- if (newAxes.includes(col)) {
- newAxes.splice(newAxes.indexOf(col), 1);
- } else if (newAxes.length > 1) {
- newAxes[1] = col;
- } else {
- newAxes.push(col);
- }
- this.props.selectAxes(newAxes);
- })
- );
- }}>
- {col}
- </th>
- );
- })}
+ {this.columns.map(col => (
+ <th
+ key={this.columns.indexOf(col)}
+ style={{
+ color: this.props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this.props.axes.lastElement() === col ? 'darkred' : undefined,
+ background: this.props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this.props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
+ fontWeight: 'bolder',
+ border: '3px solid black',
+ }}
+ onPointerDown={e => {
+ const downX = e.clientX;
+ const downY = e.clientY;
+ setupMoveUpEvents(
+ {},
+ e,
+ e => {
+ // dragging off a column to create a brushed DataVizBox
+ const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
+ const targetCreator = (annotationOn: Doc | undefined) => {
+ const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
+ embedding._dataViz = DataVizView.TABLE;
+ embedding._dataViz_axes = new List<string>([col, col]);
+ embedding._dataViz_parentViz = this.props.docView?.()!.rootDoc!;
+ embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
+ embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
+ embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
+ embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
+ return embedding;
+ };
+ if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: e => {
+ if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+ e.linkDocument.link_displayLine = true;
+ e.linkDocument.link_matchEmbeddings = true;
+ // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+ // e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ }
+ },
+ });
+ return true;
+ }
+ return false;
+ },
+ emptyFunction,
+ action(e => {
+ const newAxes = this.props.axes;
+ if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
+ else if (newAxes.length > 1) newAxes[1] = col;
+ else newAxes.push(col);
+ this.props.selectAxes(newAxes);
+ })
+ );
+ }}>
+ {col}
+ </th>
+ ))}
</tr>
</thead>
<tbody>
- {this._tableData?.map((p, i) => {
- var containsData = false;
- var guid = this.rowGuids[this.props.pairs.indexOf(p)];
- this.columns.map(col => {
- if (p[col] != '' && p[col] != null && p[col] != undefined) containsData = true;
- });
- if (containsData) {
- return (
- <tr
- key={i}
- className="table-row"
- onClick={action(e => {
- // selecting a row
- const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null);
- if (selected.includes(guid)) selected.splice(selected.indexOf(guid), 1);
- else {
- selected.push(guid);
- }
- })}
- style={{ background: StrListCast(this.props.layoutDoc.dataViz_selectedRows).includes(guid) ? 'lightgrey' : '', width: '110%' }}>
- {this.columns.map(col => {
- // each cell
- var colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false;
- return (
- <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
- {p[col]}
- </td>
- );
- })}
- </tr>
- );
- }
- })}
+ {this._tableDataIds
+ ?.map(rowId => ({ record: this.props.records[rowId], rowId }))
+ .map(({ record, rowId }) => (
+ <tr
+ key={rowId}
+ className="table-row"
+ onClick={action(e => {
+ // selecting a row
+ const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+ if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1);
+ else selected?.push(rowId);
+ e.stopPropagation();
+ })}
+ style={{ background: NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '', width: '110%' }}>
+ {this.columns.map(col => {
+ // each cell
+ const colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false;
+ return (
+ <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
+ {record[col]}
+ </td>
+ );
+ })}
+ </tr>
+ ))}
</tbody>
</table>
</div>