aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DataVizBox
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-03-01 08:23:06 -0500
committerbobzel <zzzman@gmail.com>2024-03-01 08:23:06 -0500
commit25474b83f908732b2618cb7110f1e410030f9280 (patch)
treea942453765eb876ffaa3899d623fa77e13a196b4 /src/client/views/nodes/DataVizBox
parent4e837a73f5fae06368416f99c047d78f6b94565b (diff)
parent3179048be75fb7662fc472249798b2d103dc5544 (diff)
Merge branch 'master' into info-ui-observable
Diffstat (limited to 'src/client/views/nodes/DataVizBox')
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss4
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx184
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss3
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx28
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx90
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx21
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx38
-rw-r--r--src/client/views/nodes/DataVizBox/utils/D3Utils.ts4
8 files changed, 255 insertions, 117 deletions
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index a3132dc6e..6b5738790 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -29,6 +29,10 @@
}
}
+ .liveSchema-checkBox {
+ margin-bottom: -35px;
+ }
+
.dataviz-sidebar {
position: absolute;
right: 0;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 5a55ca764..66a08f13e 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
-import { ObservableMap, action, computed, observable, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
@@ -11,23 +11,23 @@ import { listSpec } from '../../../../fields/Schema';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
-import { Docs } from '../../../documents/Documents';
+import { DocUtils, Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { SidebarAnnos } from '../../SidebarAnnos';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { PinProps } from '../trails';
import './DataVizBox.scss';
import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
+import { Checkbox } from '@mui/material';
export enum DataVizView {
TABLE = 'table',
@@ -37,22 +37,28 @@ export enum DataVizView {
}
@observer
-export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _ffref = React.createRef<CollectionFreeFormView>();
private _marqueeref = React.createRef<MarqueeAnnotator>();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _disposers: { [name: string]: IReactionDisposer } = {};
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
- @observable schemaDataVizChildren: any = undefined;
@observable _marqueeing: number[] | undefined = undefined;
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ this._props.setContentViewBox?.(this);
+ }
+
@computed get annotationLayer() {
TraceMobx();
return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
}
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -80,6 +86,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// 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 }[]>();
+ // when a dataset comes from schema view, this stores the original dataset to refer back to
+ // href : dataset
+ static datasetSchemaOG = new ObservableMap<string, { [key: string]: string }[]>();
+
private _vizRenderer: LineChart | Histogram | PieChart | undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
@@ -98,10 +108,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return Cast(this.dataDoc[this.fieldKey], CsvField);
}
@computed.struct get axes() {
- return StrListCast(this.layoutDoc.dataViz_axes);
+ return StrListCast(this.layoutDoc._dataViz_axes);
}
-
- selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));
+ selectAxes = (axes: string[]) => (this.layoutDoc._dataViz_axes = new List<string>(axes));
+ @computed.struct get titleCol() {
+ return StrCast(this.layoutDoc._dataViz_titleCol);
+ }
+ selectTitleCol = (titleCol: string) => (this.layoutDoc._dataViz_titleCol = titleCol);
@action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
@@ -211,12 +224,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
.ScreenToLocalTransform()
.scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
- const fullWidth = this._props.width;
- const mapWidth = fullWidth! - this.sidebarWidth();
+ const fullWidth = NumCast(this.layoutDoc._width);
+ const mapWidth = fullWidth - this.sidebarWidth();
if (this.sidebarWidth() + localDelta[0] > 0) {
this.layoutDoc._layout_showSidebar = true;
- this.layoutDoc._width = fullWidth! + localDelta[0];
- this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth! + localDelta[0])).toString() + '%';
+ this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+ this.layoutDoc._width = fullWidth + localDelta[0];
} else {
this.layoutDoc._layout_showSidebar = false;
this.layoutDoc._width = mapWidth;
@@ -228,7 +241,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
() => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
);
};
- getView = async (doc: Doc, options: DocFocusOptions) => {
+ getView = async (doc: Doc, options: FocusViewOptions) => {
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
options.didMove = true;
this.toggleSidebar();
@@ -249,14 +262,75 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
+ this._disposers.datavis = reaction(
+ () => {
+ if (this.layoutDoc.dataViz_schemaLive==undefined) this.layoutDoc.dataViz_schemaLive = true;
+ const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
+ const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
+ if (!keys) return;
+ const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
+ var current: { [key: string]: string }[] = [];
+ children
+ .filter(child => child)
+ .forEach(child => {
+ const row: { [key: string]: string } = {};
+ keys.forEach(key => {
+ var cell = child[key];
+ if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ row[key] = StrCast(cell);
+ });
+ current.push(row);
+ });
+ if (!this.layoutDoc._dataViz_schemaOG){ // makes a copy of the original table for the "live" toggle
+ let csvRows = [];
+ csvRows.push(keys.join(','));
+ for (let i = 0; i < children.length-1; i++) {
+ let eachRow = [];
+ for (let j = 0; j < keys.length; j++) {
+ var cell = children[i][keys[j]];
+ if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ eachRow.push(cell);
+ }
+ csvRows.push(eachRow);
+ }
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
+ const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
+ const file = new File([blob], 'schemaTable for static dataviz', options);
+ const loading = Docs.Create.LoadingDocument(file, options);
+ DocUtils.uploadFileToDoc(file, {}, loading);
+ this.layoutDoc._dataViz_schemaOG = loading;
+ }
+ const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc
+ const ogHref = CsvCast(ogDoc[this.fieldKey])? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
+ const href = CsvCast(this.Document[this.fieldKey]).url.href
+ if (ogHref && !DataVizBox.datasetSchemaOG.has(href)){ // sets original dataset to the var
+ const lastRow = current.pop();
+ DataVizBox.datasetSchemaOG.set(href, current);
+ current.push(lastRow!);
+ fetch('/csvData?uri=' + ogHref)
+ .then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+ }
+ return current;
+ },
+ current => {
+ if (current) {
+ const href = CsvCast(this.Document[this.fieldKey]).url.href;
+ if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
+ else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
+ }
+ },
+ { fireImmediately: true }
+ );
}
fetchData = () => {
- DataVizBox.dataset.set(CsvCast(this.dataDoc[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.dataDoc[this.fieldKey]).url.href, res))));
+ if (!this.Document.dataViz_asSchema) {
+ DataVizBox.dataset.set(CsvCast(this.dataDoc[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.dataDoc[this.fieldKey]).url.href, res))));
+ }
};
// toggles for user to decide which chart type to view the data in
@@ -267,14 +341,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
layoutDoc: this.layoutDoc,
records: this.records,
axes: this.axes,
+ titleCol: this.titleCol,
//width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
- width: (this._props.PanelWidth() / scale) * 0.9,
+ width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
margin: { top: 10, right: 25, bottom: 75, left: 45 },
};
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
- case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this._props.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)}
@@ -285,7 +360,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
onPointerDown = (e: React.PointerEvent): void => {
if ((this.Document._freeform_scale || 1) !== 1) return;
- if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
this._props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
@@ -323,32 +398,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
@action
- updateSchemaViz = () => {
- const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
- const keys = Cast(getFrom.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
- if (!keys) return;
- const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
- var current: { [key: string]: string }[] = [];
- for (let i = 0; i < children.length; i++) {
- var row: { [key: string]: string } = {};
- if (children[i]) {
- for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
- row[keys[j]] = StrCast(cell);
- }
- }
- current.push(row);
- }
- DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current);
- };
+ changeLiveSchemaCheckbox = () => {
+ this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
+ }
render() {
- if (this.layoutDoc && this.layoutDoc.dataViz_asSchema) {
- this.schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length;
- this.updateSchemaViz();
- }
-
const scale = this._props.NativeDimScaling?.() || 1;
return !this.records.length ? (
// displays how to get data into the DataVizBox if its empty
@@ -366,34 +420,22 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}}
onWheel={e => e.stopPropagation()}
ref={this._mainCont}>
- <div className={'datatype-button'}>
+ <div className="datatype-button">
<Toggle text={' TABLE '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
<Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
<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>
- {/* <CollectionFreeFormView
- ref={this._ffref}
- {...this._props}
- setContentView={emptyFunction}
- renderDepth={this._props.renderDepth - 1}
- fieldKey={this.annotationKey}
- styleProvider={this._props.styleProvider}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={false}
- PanelWidth={this._props.PanelWidth}
- PanelHeight={this._props.PanelHeight}
- select={emptyFunction}
- isAnyChildContentActive={returnFalse}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}>
- {this.renderVizView}
- </CollectionFreeFormView> */}
+ {(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
+ <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
+ <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+ Display Live Updates to Canvas
+ </div>
+ ) : null}
{this.renderVizView}
+
<div className="dataviz-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }} onPointerDown={this.onPointerDown}>
<SidebarAnnos
ref={this._sidebarRef}
@@ -414,7 +456,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</div>
{this.sidebarHandle}
{this.annotationLayer}
- {!this._mainCont.current || !this._annotationLayer.current ? null : (
+ {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
Document={this.Document}
@@ -422,7 +464,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
scrollTop={0}
annotationLayerScrollTop={NumCast(this.Document._layout_scrollTop)}
scaling={returnOne}
- docView={this._props.DocumentView!}
+ docView={this.DocumentView}
addDocument={this.sidebarAddDocument}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 2f7dd0487..41ce637ac 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -19,8 +19,6 @@
margin-bottom: -20px;
}
.asHistogram-checkBox {
- // display: flex;
- // flex-direction: row;
align-items: left;
align-self: left;
align-content: left;
@@ -93,7 +91,6 @@
display: flex;
flex-direction: column;
cursor: default;
- margin-top: 30px;
height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows
.tableBox-container {
overflow: scroll;
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 4a1fb2ed1..6672603f3 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -20,6 +20,7 @@ export interface HistogramProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -63,17 +64,17 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
if (this._props.axes.length < 1) return [];
if (this._props.axes.length < 2) {
var ax0 = this._props.axes[0];
- if (/\d/.test(this._props.records[0][ax0])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])){
this.numericalXData = true;
}
return this._tableData.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.records[0][ax0])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
- if (/\d/.test(this._props.records[0][ax1])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax1])) {
this.numericalYData = true;
}
return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
@@ -89,9 +90,6 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
- // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
- // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -438,9 +436,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
this.updateBarColors();
this._histogramData;
var curSelectedBarName = '';
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_histogram_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
@@ -454,6 +452,18 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
: ''
);
selected = selected.substring(0, selected.length - 2) + ' }';
+ if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
+ selected+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
+ if (this._props.axes[1]){
+ if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
+ }
+ else selected+= each[this._props.titleCol] + ", ";
+ }
+ })
+ selected = selected.slice(0,-1).slice(0,-1);
+ }
}
var selectedBarColor;
var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 2a9a8b354..e093ec648 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -28,6 +28,7 @@ export interface LineChartProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -46,7 +47,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
- @observable _currSelected: SelectedDataPoint | undefined = undefined;
+ @observable _currSelected: any | undefined = undefined;
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
constructor(props: any) {
super(props);
@@ -235,21 +236,16 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
}
- // TODO: nda - can use d3.create() to create html element instead of appending
drawChart = (dataSet: any[][], 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 } = rangeVals;
+ var { xMin, xMax, yMin, yMax } = rangeVals;
if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
return;
}
- // creating the x and y scales
- 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
@@ -261,24 +257,71 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`));
+ var validSecondData;
+ if (this._props.axes.length>2){ // for when there are 2 lines on the chart
+ var next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
+ validSecondData = next.filter(d => {
+ if (!d.x || Number.isNaN(d.x) || !d.y || Number.isNaN(d.y)) return false;
+ return true;
+ });
+ var secondDataRange = minMaxRange([validSecondData]);
+ if (secondDataRange.xMax!>xMax) xMax = secondDataRange.xMax;
+ if (secondDataRange.yMax!>yMax) yMax = secondDataRange.yMax;
+ if (secondDataRange.xMin!<xMin) xMin = secondDataRange.xMin;
+ if (secondDataRange.yMin!<yMin) yMin = secondDataRange.yMin;
+ }
+
+ // creating the x and y scales
+ const xScale = scaleCreatorNumerical(xMin!, xMax!, 0, width);
+ const yScale = scaleCreatorNumerical(0, yMax!, height, 0);
+ const lineGen = createLineGenerator(xScale, yScale);
+
// create x and y grids
xGrid(svg.append('g'), height, xScale);
yGrid(svg.append('g'), width, yScale);
xAxisCreator(svg.append('g'), height, xScale);
yAxisCreator(svg.append('g'), width, yScale);
+ if (validSecondData) {
+ drawLine(svg.append('path'), validSecondData, lineGen, true);
+ this.drawDataPoints(validSecondData, 0, xScale, yScale);
+ svg.append('path').attr("stroke", "red");
+
+ // legend
+ var color = d3.scaleOrdinal()
+ .range(["black", "blue"])
+ .domain([this._props.axes[1], this._props.axes[2]])
+ svg.selectAll("mydots")
+ .data([this._props.axes[1], this._props.axes[2]])
+ .enter()
+ .append("circle")
+ .attr("cx", 5)
+ .attr("cy", function(d,i){ return -30 + i*15})
+ .attr("r", 7)
+ .style("fill", function(d){ return color(d)})
+ svg.selectAll("mylabels")
+ .data([this._props.axes[1], this._props.axes[2]])
+ .enter()
+ .append("text")
+ .attr("x", 25)
+ .attr("y", function(d,i){ return -30 + i*15})
+ .style("fill", function(d){ return color(d)})
+ .text(function(d){ return d})
+ .attr("text-anchor", "left")
+ .style("alignment-baseline", "middle")
+ }
+
// get valid data points
const data = dataSet[0];
- const lineGen = createLineGenerator(xScale, yScale);
var validData = data.filter(d => {
- var valid = true;
Object.keys(data[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) valid = false;
+ if (!d[key] || Number.isNaN(d[key])) return false;
});
- return valid;
+ return true;
});
+
// draw the plot line
- drawLine(svg.append('path'), validData, lineGen);
+ drawLine(svg.append('path'), validData, lineGen, false);
// draw the datapoint circle
this.drawDataPoints(validData, 0, xScale, yScale);
@@ -291,7 +334,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
const xPos = d3.pointer(e)[0];
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];
- if (!d0) return;
+ if (d0) this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
});
@@ -327,7 +370,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
svg.append('text')
.attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
.attr('x', -(height / 2))
- .attr('y', -20)
+ .attr('y', -30)
.attr('height', 20)
.attr('width', 20)
.style('text-anchor', 'middle')
@@ -351,11 +394,22 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
render() {
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_lineChart_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + 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';
+ var selectedTitle = "";
+ if (this._currSelected && this._props.titleCol){
+ selectedTitle+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ var mapThisEntry = false;
+ if (this._currSelected.x==each[this._props.axes[0]] && this._currSelected.y==each[this._props.axes[1]]) mapThisEntry = true;
+ else if (this._currSelected.y==each[this._props.axes[0]] && this._currSelected.x==each[this._props.axes[1]]) mapThisEntry = true;
+ if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ", ";
+ })
+ selectedTitle = selectedTitle.slice(0,-1).slice(0,-1);
+ }
if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
<div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
@@ -375,9 +429,9 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
{selectedPt != 'none' ? (
<div className={'selected-data'}>
{`Selected: ${selectedPt}`}
+ {`${selectedTitle}`}
<Button
onClick={e => {
- console.log('test plzz');
this._props.vizBox.sidebarBtnDown;
this._props.vizBox.sidebarAddDocument;
}}></Button>
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 1259a13ff..fc23f47de 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -19,6 +19,7 @@ export interface PieChartProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -331,22 +332,34 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
};
render() {
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_pie_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
var selected: string;
var curSelectedSliceName = '';
if (this._currSelected) {
+ selected = '{ ';
const sliceTitle = this._currSelected[this._props.axes[0]];
curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- selected = '{ ';
Object.keys(this._currSelected).map(key => {
key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
});
selected = selected.substring(0, selected.length - 2);
selected += ' }';
+ if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
+ selected+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
+ if (this._props.axes[1]){
+ if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
+ }
+ else selected+= each[this._props.titleCol] + ", ";
+ }
+ })
+ selected = selected.slice(0,-1).slice(0,-1);
+ }
} else selected = 'none';
var selectedSliceColor;
var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index ed44d9269..1b239b5e5 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -18,7 +18,9 @@ interface TableBoxProps {
layoutDoc: Doc;
records: { [key: string]: any }[];
selectAxes: (axes: string[]) => void;
+ selectTitleCol: (titleCol: string) => void;
axes: string[];
+ titleCol: string;
width: number;
height: number;
margin: {
@@ -83,14 +85,12 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
return this._props.docView?.()?.screenToViewTransform().Scale || 1;
}
@computed get rowHeight() {
- console.log('scale = ' + this.viewScale + ' table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
}
@computed get startID() {
return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight) - 1, 0) : 0;
}
@computed get endID() {
- console.log('start = ' + this.startID + ' container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
}
@action handleScroll = () => {
@@ -155,11 +155,18 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
},
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);
+ if (e.shiftKey){
+ if (this._props.titleCol == col) this._props.titleCol = "";
+ else this._props.titleCol = col;
+ this._props.selectTitleCol(this._props.titleCol);
+ }
+ else{
+ const newAxes = this._props.axes;
+ if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
+ else if (newAxes.length > 2) newAxes[newAxes.length-1] = col;
+ else newAxes.push(col);
+ this._props.selectAxes(newAxes);
+ }
})
);
};
@@ -213,8 +220,15 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
<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,
+ color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen'
+ : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? 'darkred'
+ : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? 'darkblue' : undefined,
+ background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb'
+ : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? '#Fbdbdb'
+ : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? '#c6ebf7' : undefined,
+ // blue: #ADD8E6
+ // green: #E3fbdb
+ // red: #Fbdbdb
fontWeight: 'bolder',
border: '3px solid black',
}}
@@ -236,7 +250,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
}}>
{this.columns.map(col => {
- 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;
+ var colSelected = false;
+ if (this._props.axes.length>2) colSelected = this._props.axes[0]==col || this._props.axes[1]==col || this._props.axes[2]==col;
+ else if (this._props.axes.length>1) colSelected = this._props.axes[0]==col || this._props.axes[1]==col;
+ else if (this._props.axes.length>0) colSelected = this._props.axes[0]==col;
+ if (this._props.titleCol==col) colSelected = true;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
<div className="tableBox-cell">{this._props.records[rowId][col]}</div>
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 10bfb0c64..336935d23 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -61,6 +61,6 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi
);
};
-export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>) => {
- p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('class', 'line').attr('d', lineGen);
+export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => {
+ p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen);
};