diff options
| author | bobzel <zzzman@gmail.com> | 2023-10-10 14:54:18 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-10 14:54:18 -0400 |
| commit | d929c0511cae863412a398f426d9e5b7ca64e6d9 (patch) | |
| tree | 7c077af3a03a17a8ffb0e9fbb45e7d71a8372cc5 /src/client/views/nodes/DataVizBox/components | |
| parent | c9d83841221620137e89920198ffaeab2677b439 (diff) | |
| parent | 5eca36aa3e775646d96671b922bf7205074ab135 (diff) | |
Merge pull request #282 from brown-dash/dataViz-scroll
Data viz scroll
Diffstat (limited to 'src/client/views/nodes/DataVizBox/components')
3 files changed, 142 insertions, 117 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 50dfe7f05..a6ce0b88c 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -1,3 +1,4 @@ +@import '../../../global/globalCssVariables'; .chart-container { display: flex; flex-direction: column; @@ -6,10 +7,10 @@ margin-top: 10px; overflow-y: visible; - .graph{ + .graph { overflow: visible; } - .graph-title{ + .graph-title { align-items: center; font-size: larger; display: flex; @@ -29,7 +30,7 @@ position: relative; margin-bottom: -35px; } - .selected-data{ + .selected-data { align-items: center; text-align: center; display: flex; @@ -44,10 +45,10 @@ stroke-width: 2px; } } - - .histogram-bar{ + + .histogram-bar { outline: thin solid black; - &.hover{ + &.hover { outline: 3px solid black; outline-offset: -3px; } @@ -91,13 +92,22 @@ .tableBox { display: flex; flex-direction: column; + height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows + .table { + height: 100%; + } } -.table-container{ +.table-container { overflow: scroll; margin: 5px; margin-left: 25px; margin-right: 10px; margin-bottom: 0; + tr td { + height: $DATA_VIZ_TABLE_ROW_HEIGHT !important; // bcz: hack. you can't set a <tr> height directly, but you can set the height of all of it's <td>s. So this is the height of a tableBox row. + padding: 0 !important; + vertical-align: middle !important; + } } .selectAll-buttons { display: flex; @@ -106,4 +116,4 @@ margin-top: 5px; margin-right: 10px; float: right; -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.scss b/src/client/views/nodes/DataVizBox/components/TableBox.scss deleted file mode 100644 index 1264d6a46..000000000 --- a/src/client/views/nodes/DataVizBox/components/TableBox.scss +++ /dev/null @@ -1,22 +0,0 @@ -.table { - margin-top: 10px; - margin-bottom: 10px; - margin-left: 10px; - margin-right: 10px; -} - -.table-row { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 5px; - border-bottom: 1px solid #ccc; -} - -.table-container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 4d6027ca4..8f16df1dc 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={`table-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="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,29 @@ export class TableBox extends React.Component<TableBoxProps> { </thead> <tbody> {this._tableDataIds + .filter(rowId => this.startID <= rowId && rowId <= this.endID) ?.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(); - })} + className={`table-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 style={{ textOverflow: 'ellipsis', width: '100%', whiteSpace: 'pre', maxWidth: 150, overflow: 'hidden' }}>{record[col]}</div> </td> ); })} </tr> ))} </tbody> + <div style={{ height: (this._tableDataIds.length - this.endID) * 40 }} /> </table> </div> </div> |
