aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DataVizBox/components/TableBox.tsx
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-10-19 00:58:50 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-10-19 00:58:50 -0400
commit1efba5a6a80cba09633426fc8cb42be4bf9b2e74 (patch)
treeabd78b4818115b300fb934e343f41417ac13e19f /src/client/views/nodes/DataVizBox/components/TableBox.tsx
parent612f3d05927113c7b010861c17765fcead4752e5 (diff)
parentabf40af6dd617de6486a97e8b5f276db232119ed (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx215
1 files changed, 125 insertions, 90 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 4d6027ca4..b88389de6 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,5 +1,5 @@
import { Button, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
@@ -10,6 +10,7 @@ import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
import { DragManager } from '../../../../util/DragManager';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
+import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss';
import './Chart.scss';
interface TableBoxProps {
@@ -32,10 +33,17 @@ interface TableBoxProps {
@observer
export class TableBox extends React.Component<TableBoxProps> {
_inputChangedDisposer?: IReactionDisposer;
+ _containerRef: HTMLDivElement | null = null;
+
+ @observable _scrollTop = -1;
+ @observable _tableHeight = 0;
+ @observable _tableContainerHeight = 0;
+
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 });
+ this.handleScroll();
}
componentWillUnmount() {
this._inputChangedDisposer?.();
@@ -67,6 +75,89 @@ export class TableBox extends React.Component<TableBoxProps> {
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
};
+ @computed get viewScale() {
+ return this.props.docView?.()?.props.ScreenToLocalTransform().Scale || 1;
+ }
+ @computed get rowHeight() {
+ return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
+ }
+ @computed get startID() {
+ return this.rowHeight ? Math.floor(this._scrollTop / this.rowHeight) : 0;
+ }
+ @computed get endID() {
+ return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
+ }
+ @action handleScroll = () => {
+ if (!this.props.docView?.()?.ContentDiv?.hidden) {
+ this._scrollTop = this._containerRef?.scrollTop ?? 0;
+ }
+ };
+ @action
+ tableRowClick = (e: React.MouseEvent, rowId: number) => {
+ 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();
+ };
+
+ columnPointerDown = (e: React.PointerEvent, col: string) => {
+ 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.rootDoc;
+ embedding.annotationOn = annotationOn;
+ 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.linkDocument.link_displayArrow = 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);
+ })
+ );
+ };
+
render() {
if (this._tableData.length > 0) {
return (
@@ -84,21 +175,33 @@ export class TableBox extends React.Component<TableBoxProps> {
<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">
+ className={`tableBox-container ${this.columns[0]}`}
+ style={{ height: '100%', overflow: 'auto' }}
+ onScroll={this.handleScroll}
+ ref={action((r: HTMLDivElement | null) => {
+ this._containerRef = r;
+ if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+ this._tableContainerHeight = r.getBoundingClientRect().height ?? 0;
+ 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="tableBox-table"
+ ref={action((r: HTMLTableElement | null) => {
+ if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+ this._tableHeight = r?.getBoundingClientRect().height ?? 0;
+ }
+ })}>
+ <div style={{ height: this.startID * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
<thead>
- <tr className="table-row">
+ <tr>
{this.columns.map(col => (
<th
key={this.columns.indexOf(col)}
@@ -108,58 +211,7 @@ export class TableBox extends React.Component<TableBoxProps> {
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.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.linkDocument.link_displayArrow = 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);
- })
- );
- }}>
+ onPointerDown={e => this.columnPointerDown(e, col)}>
{col}
</th>
))}
@@ -167,44 +219,27 @@ export class TableBox extends React.Component<TableBoxProps> {
</thead>
<tbody>
{this._tableDataIds
- ?.map(rowId => ({ record: this.props.records[rowId], rowId }))
- .map(({ record, rowId }) => (
+ .filter(rowId => this.startID <= rowId && rowId <= this.endID)
+ ?.map(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();
- })}
+ className={`tableBox-row ${this.columns[0]}`}
+ onClick={e => this.tableRowClick(e, rowId)}
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]}
+ <div className="tableBox-cell">{this.props.records[rowId][col]}</div>
</td>
);
})}
</tr>
))}
</tbody>
+ <div style={{ height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
</table>
</div>
</div>