aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DataVizBox/components/TableBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx219
1 files changed, 166 insertions, 53 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index d84e34d52..4d6027ca4 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,42 +1,112 @@
-import { action, computed } from 'mobx';
+import { Button, Type } from 'browndash-components';
+import { action, computed, IReactionDisposer, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../../../../fields/Doc';
-import { Id } from '../../../../../fields/FieldSymbols';
+import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
-import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../../../Utils';
+import { listSpec } from '../../../../../fields/Schema';
+import { Cast, DocCast } from '../../../../../fields/Types';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
import { DragManager } from '../../../../util/DragManager';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
+import './Chart.scss';
interface TableBoxProps {
- pairs: { [key: string]: any }[];
+ rootDoc: Doc;
+ layoutDoc: Doc;
+ records: { [key: string]: any }[];
selectAxes: (axes: string[]) => void;
axes: string[];
+ width: number;
+ height: number;
+ margin: {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+ };
docView?: () => DocumentView | undefined;
}
@observer
export class TableBox extends React.Component<TableBoxProps> {
+ _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.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+ }
+ // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
+ @computed get _tableData() {
+ return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+ }
+
+ @computed get parentViz() {
+ return DocCast(this.props.rootDoc.dataViz_parentViz);
+ // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+ // .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
+ // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
+ }
+
@computed get columns() {
- return this.props.pairs.length ? Array.from(Object.keys(this.props.pairs[0])) : [];
+ return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : [];
}
+
+ // updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
+ filterSelectedRowsDown = () => {
+ const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows);
+ this.props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
+ const highlighted = NumListCast(this.props.layoutDoc.dataViz_highlitedRows);
+ this.props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through highlighted to remove guids that were removed in the incoming data
+ };
+
render() {
- return (
- <div className="table-container">
- <table className="table">
- <thead>
- <tr className="table-row">
- {this.columns
- .filter(col => !col.startsWith('select'))
- .map(col => {
- const header = React.createRef<HTMLElement>();
- return (
+ if (this._tableData.length > 0) {
+ return (
+ <div
+ className="tableBox"
+ tabIndex={0}
+ onKeyDown={e => {
+ if (this.props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
+ e.stopPropagation();
+ this.props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
+ }
+ }}>
+ <div className="selectAll-buttons">
+ <Button onClick={action(() => (this.props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
+ <Button onClick={action(() => (this.props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
+ </div>
+ <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.map(col => (
<th
- ref={header as any}
+ key={this.columns.indexOf(col)}
style={{
- color: this.props.axes.slice().reverse().lastElement() === col ? 'green' : this.props.axes.lastElement() === col ? 'red' : undefined,
- fontWeight: this.props.axes.includes(col) ? 'bolder' : 'normal',
+ 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;
@@ -45,24 +115,37 @@ export class TableBox extends React.Component<TableBoxProps> {
{},
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._dataVizView = DataVizView.LINECHART;
- embedding._data_vizAxes = new List<string>([col, col]);
+ embedding._dataViz = DataVizView.TABLE;
+ embedding._dataViz_axes = new List<string>([col, col]);
+ embedding._dataViz_parentViz = this.props.rootDoc;
embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
+ embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
+ embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
+ 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.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
- // e.annoDragData.linkSourceDoc.followLinkZoom = false;
- }
- },
- });
+ 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.linkDocument.link_displayArrow = true;
+ // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+ // e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ }
+ },
+ }
+ );
return true;
}
return false;
@@ -70,36 +153,66 @@ export class TableBox extends React.Component<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[0] = col;
- }
+ 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.props.pairs?.map((p, i) => {
- return (
- <tr className="table-row" onClick={action(e => (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}>
- {this.columns.map(col => (
- <td style={{ fontWeight: p['select' + this.props.docView?.()?.rootDoc![Id]] ? 'bold' : '' }}>{p[col]}</td>
))}
</tr>
- );
- })}
- </tbody>
- </table>
- </div>
- );
+ </thead>
+ <tbody>
+ {this._tableDataIds
+ ?.map(rowId => ({ record: this.props.records[rowId], rowId }))
+ .map(({ record, rowId }) => (
+ <tr
+ key={rowId}
+ className="table-row"
+ onClick={action(e => {
+ const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
+ const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+ if (e.metaKey) {
+ // highlighting a row
+ if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+ else highlited?.push(rowId);
+ if (!selected?.includes(rowId)) selected?.push(rowId);
+ } else {
+ // selecting a row
+ if (selected?.includes(rowId)) {
+ if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+ selected.splice(selected.indexOf(rowId), 1);
+ } else selected?.push(rowId);
+ }
+ e.stopPropagation();
+ })}
+ style={{
+ background: NumListCast(this.props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
+ width: '110%',
+ }}>
+ {this.columns.map(col => {
+ // each cell
+ const colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false;
+ 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>
+ </div>
+ );
+ } 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>
+ );
}
}