diff options
Diffstat (limited to 'src')
7 files changed, 199 insertions, 13 deletions
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index 430446c06..84d4b21a6 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -7,6 +7,34 @@ display: flex; flex-direction: row; } + + .dataviz-overlayButton-sidebar { + background: #121721; + height: 25px; + width: 25px; + right: 5px; + display: flex; + position: absolute; + align-items: center; + justify-content: center; + border-radius: 3px; + pointer-events: all; + z-index: 1; // so it appears on top of the document's title, if shown + + // box-shadow: $standard-box-shadow; + // transition: 0.2s; + + &:hover { + filter: brightness(0.85); + } + } + + .dataviz-sidebar { + position: absolute; + right: 0; + top: 0; + height: 100%; + } .button-container { pointer-events: unset; } diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 299494c83..9692345a6 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,12 +1,12 @@ -import { Toggle, ToggleType, Type } from 'browndash-components'; -import { action, computed, ObservableMap } from 'mobx'; +import { Colors, Toggle, ToggleType, Type } from 'browndash-components'; +import { action, computed, observable, ObservableMap, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, Field, StrListCast } from '../../../../fields/Doc'; +import { Doc, Field, Opt, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; -import { Cast, CsvCast, StrCast } from '../../../../fields/Types'; +import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; -import { Docs } from '../../../documents/Documents'; +import { DocUtils, Docs } from '../../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import { PinProps } from '../trails'; @@ -15,6 +15,14 @@ import { LineChart } from './components/LineChart'; import { PieChart } from './components/PieChart'; import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; +import { UndoManager, undoable } from '../../../util/UndoManager'; +import { SidebarAnnos } from '../../SidebarAnnos'; +import { PDFBox } from '../PDFBox'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; +import { LinkManager } from '../../../util/LinkManager'; +import { DocumentView } from '../DocumentView'; +import { DocumentManager } from '../../../util/DocumentManager'; export enum DataVizView { TABLE = 'table', @@ -32,6 +40,7 @@ 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 }[]>(); private _vizRenderer: LineChart | Histogram | PieChart | undefined; + private _sidebarRef = React.createRef<SidebarAnnos>(); // all CSV records in the dataset (that aren't an empty row) @computed.struct get records() { @@ -97,6 +106,124 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return anchor; }; + createNoteAnnotation = () => { + const createFunc = undoable( + action(() => { + const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']); + // if (note && this.selectedPin) { + // note.latitude = this.selectedPin.latitude; + // note.longitude = this.selectedPin.longitude; + // note.map = this.selectedPin.map; + // } + }), + 'create note annotation' + ); + if (!this.layoutDoc.layout_showSidebar) { + this.toggleSidebar(); + setTimeout(createFunc); + } else createFunc(); + }; + + @observable _showSidebar = false; + @observable _previewNativeWidth: Opt<number> = undefined; + @observable _previewWidth: Opt<number> = undefined; + @action + toggleSidebar = () => { + const prevWidth = this.sidebarWidth(); + this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%'; + this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); + }; + @computed get SidebarShown() { + return this.layoutDoc._layout_showSidebar ? true : false; + } + @computed get sidebarHandle() { + return ( + <div + className="dataviz-overlayButton-sidebar" + key="sidebar" + title="Toggle Sidebar" + style={{ + display: !this.props.isContentActive() ? 'none' : undefined, + top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5, + backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, + }} + onPointerDown={this.sidebarBtnDown}> + <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" /> + </div> + ); + } + /** + * Toggle sidebar onclick the tiny comment button on the top right corner + * @param e + */ + sidebarBtnDown = (e: React.PointerEvent) => { + setupMoveUpEvents( + this, + e, + (e, down, delta) => + runInAction(() => { + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.NativeDimScaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const fullWidth = this.props.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() + '%'; + } else { + this.layoutDoc._layout_showSidebar = false; + this.layoutDoc._width = mapWidth; + this.layoutDoc._layout_sidebarWidthPercent = '0%'; + } + return false; + }), + emptyFunction, + () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar') + ); + }; + getView = async (doc: Doc) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); + return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + }; + @computed get sidebarWidthPercent() { + return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); + } + @computed get sidebarColor() { + return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); + } + sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); + sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { + console.log('sideBarAddDoc') + if (!this.SidebarShown) this.toggleSidebar(); + return this.addDocument(doc, sidebarKey); + // if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar(); + // const docs = doc instanceof Doc ? [doc] : doc; + // docs.forEach(doc => { + // let existingPin = Docs.Create.TextDocument("test"); + // //let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin; + // // if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) { + // // existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map)); + // // } + // if (existingPin) { + // setTimeout(() => { + // // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet + // if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) { + // // const anchor = this.getAnchor(true, undefined, existingPin); + // const anchor = this.getAnchor(true, undefined); + // anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to datapoint' }); + // // doc.latitude = existingPin?.latitude; + // // doc.longitude = existingPin?.longitude; + // } + // }); + // } + // }); //add to annotation list + + // return this.addDocument(doc, sidebarKey); // add to sidebar list + }; + sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey); + componentDidMount() { this.props.setContentView?.(this); if (!DataVizBox.dataset.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) this.fetchData(); @@ -116,7 +243,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { records: this.records, axes: this.axes, height: (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9, - width: this.props.PanelWidth() * 0.9, + width: this.SidebarShown? this.props.PanelWidth()*.9/1.2: this.props.PanelWidth() * 0.9, margin: { top: 10, right: 25, bottom: 75, left: 45 }, }; if (!this.records.length) return 'no data/visualization'; @@ -124,7 +251,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />; case DataVizView.LINECHART: - return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; + return <LineChart vizBox={this} {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; case DataVizView.PIECHART: @@ -160,6 +287,25 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <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.renderVizView()} + <div className="dataviz-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> + <SidebarAnnos + ref={this._sidebarRef} + {...this.props} + fieldKey={this.fieldKey} + rootDoc={this.rootDoc} + layoutDoc={this.layoutDoc} + dataDoc={this.dataDoc} + usePanelWidth={true} + showSidebar={this.SidebarShown} + nativeWidth={NumCast(this.layoutDoc._nativeWidth)} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + PanelWidth={this.sidebarWidth} + sidebarAddDocument={this.sidebarAddDocument} + moveDocument={this.moveDocument} + removeDocument={this.sidebarRemoveDocument} + /> + </div> + {this.sidebarHandle} </div> ); } diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index c788a64c2..c8ea88d63 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -111,6 +111,7 @@ white-space: pre; max-width: 150; overflow: hidden; + margin-left: 2px; } } } diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index e67e2bf31..eca77f0f3 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -456,7 +456,7 @@ export class Histogram extends React.Component<HistogramProps> { if (this._histogramData.length > 0 || !this.parentViz) { return this.props.axes.length >= 1 ? ( - <div className="chart-container"> + <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}> <div className="graph-title"> <EditableText val={StrCast(this.props.layoutDoc[titleAccessor])} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 3de7a0c4a..007d0259c 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -1,4 +1,4 @@ -import { EditableText, Size } from 'browndash-components'; +import { Button, EditableText, Size } from 'browndash-components'; import * as d3 from 'd3'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -23,6 +23,7 @@ export interface SelectedDataPoint extends DataPoint { elem?: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>; } export interface LineChartProps { + vizBox: DataVizBox; rootDoc: Doc; layoutDoc: Doc; axes: string[]; @@ -352,7 +353,7 @@ export class LineChart extends React.Component<LineChartProps> { 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.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"> + <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}> <div className="graph-title"> <EditableText val={StrCast(this.props.layoutDoc[titleAccessor])} @@ -366,7 +367,16 @@ export class LineChart extends React.Component<LineChartProps> { /> </div> <div ref={this._lineChartRef} /> - {selectedPt != 'none' ? <div className={'selected-data'}> {`Selected: ${selectedPt}`}</div> : null} + {selectedPt != 'none' ? + <div className={'selected-data'}> + {`Selected: ${selectedPt}`} + <Button onClick={e=>{ + console.log("test plzz") + this.props.vizBox.sidebarBtnDown; + this.props.vizBox.sidebarAddDocument;} + }></Button> + </div> + : null} </div> ) : ( <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span> diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 561f39141..df65810b5 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -353,7 +353,7 @@ export class PieChart extends React.Component<PieChartProps> { if (this._pieChartData.length > 0 || !this.parentViz) { return this.props.axes.length >= 1 ? ( - <div className="chart-container"> + <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}> <div className="graph-title"> <EditableText val={StrCast(this.props.layoutDoc[titleAccessor])} diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index b88389de6..3685a198e 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -82,7 +82,7 @@ export class TableBox extends React.Component<TableBoxProps> { return (this.viewScale * this._tableHeight) / this._tableDataIds.length; } @computed get startID() { - return this.rowHeight ? Math.floor(this._scrollTop / this.rowHeight) : 0; + return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight)-1, 0) : 0; } @computed get endID() { return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1)); @@ -163,6 +163,7 @@ export class TableBox extends React.Component<TableBoxProps> { return ( <div className="tableBox" + style={{width: this.props.width+this.props.margin.right}} tabIndex={0} onKeyDown={e => { if (this.props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) { |