import { Colors, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox } from '@mui/material'; import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT'; import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { MarqueeAnnotator } from '../../MarqueeAnnotator'; import { PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { FocusViewOptions } from '../FocusViewOptions'; import './DataVizBox.scss'; import { Col, DocCreatorMenu } from './DocCreatorMenu/DocCreatorMenu'; import { TemplateFieldSize, TemplateFieldType } from './DocCreatorMenu/TemplateBackend'; import { Histogram } from './components/Histogram'; import { LineChart } from './components/LineChart'; import { PieChart } from './components/PieChart'; import { TableBox } from './components/TableBox'; export enum DataVizView { TABLE = 'table', LINECHART = 'lineChart', HISTOGRAM = 'histogram', PIECHART = 'pieChart', } @observer export class DataVizBox extends ViewBoxAnnotatableComponent() { private _urlError: boolean = false; private _mainCont: React.RefObject = React.createRef(); private _marqueeref = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); sidebarAddDoc: ((doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean) | undefined; crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined; @observable _marqueeing: number[] | undefined = undefined; @observable _savedAnnotations = new ObservableMap(); @observable _specialHighlightedRow: number | undefined = undefined; @observable GPTSummary: ObservableMap | undefined = undefined; @observable colsInfo: ObservableMap = new ObservableMap(); @observable _GPTLoading: boolean = false; constructor(props: FieldViewProps) { super(props); makeObservable(this); this._props.setContentViewBox?.(this); } @computed get annotationLayer() { TraceMobx(); return
; } 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() && Doc.ActiveTool !== InkTool.Ink) { setupMoveUpEvents( this, e, action(moveEv => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]); return true; }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false ); } }; @action finishMarquee = () => { this._marqueeref.current?.onTerminateSelection(); this._props.select(false); }; savedAnnotations = () => this._savedAnnotations; public static LayoutString(fieldStr: string) { return FieldView.LayoutString(DataVizBox, fieldStr); } // 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(); // when a dataset comes from schema view, this stores the original dataset to refer back to // href : dataset static datasetSchemaOG = new ObservableMap(); private _vizRenderer: LineChart | Histogram | PieChart | undefined; private _sidebarRef = React.createRef(); // all CSV records in the dataset (that aren't an empty row) @computed.struct get records() { try { const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href); this._urlError = false; return records?.filter(record => Object.keys(record).some(key => record[key])) ?? []; } catch { this._urlError = true; return [{ error: 'Data not found' }] as { [key: string]: string }[]; } } // currently chosen visualization type: line, pie, histogram, table @computed get dataVizView(): DataVizView { return StrCast(this.layoutDoc._dataViz, 'table') as DataVizView; } @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField); } @computed.struct get axes() { return StrListCast(this.layoutDoc._dataViz_axes); } selectAxes = (axes: string[]) => { this.layoutDoc._dataViz_axes = new List(axes); }; @computed.struct get titleCol() { return StrCast(this.layoutDoc._dataViz_titleCol); } selectTitleCol = (titleCol: string) => { this.layoutDoc._dataViz_titleCol = titleCol; }; @action setSpecialHighlightedRow = (row: number | undefined) => { this._specialHighlightedRow = row; }; @action setColumnType = (colTitle: string, type: TemplateFieldType) => { const colInfo = this.colsInfo.get(colTitle); if (colInfo) { colInfo.type = type; } else { this.colsInfo.set(colTitle, { title: colTitle, desc: '', type: type, sizes: [TemplateFieldSize.MEDIUM] }); } }; @action modifyColumnSizes = (colTitle: string, size: TemplateFieldSize, valid: boolean) => { const column = this.colsInfo.get(colTitle); if (column) { if (!valid && column.sizes.includes(size)) { column.sizes.splice(column.sizes.indexOf(size), 1); } else if (valid && !column.sizes.includes(size)) { column.sizes.push(size); } } else { this.colsInfo.set(colTitle, { title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [size] }); } }; @action setColumnTitle = (colTitle: string, newTitle: string) => { const colInfo = this.colsInfo.get(colTitle); if (colInfo) { colInfo.title = newTitle; } else { this.colsInfo.set(colTitle, { title: newTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [] }); } }; @action setColumnDesc = (colTitle: string, desc: string) => { const colInfo = this.colsInfo.get(colTitle); if (colInfo) { if (!desc) { colInfo.desc = this.GPTSummary?.get(colTitle)?.desc ?? ''; } else { colInfo.desc = desc; } } else { this.colsInfo.set(colTitle, { title: colTitle, desc: desc, type: TemplateFieldType.UNSET, sizes: [] }); } }; @action setColumnDefault = (colTitle: string, cont: string) => { const colInfo = this.colsInfo.get(colTitle); if (colInfo) { colInfo.defaultContent = cont; } else { this.colsInfo.set(colTitle, { title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [], defaultContent: cont }); } }; @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors restoreView = (viewData: Doc) => { // const changedView = data.config_dataViz && this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz); // const changedAxes = data.config_dataVizAxes && this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List(StrListCast(data.config_dataVizAxes))); this.layoutDoc.dataViz_selectedRows = Field.Copy(viewData.dataViz_selectedRows); this.layoutDoc.dataViz_histogram_barColors = Field.Copy(viewData.dataViz_histogram_barColors); this.layoutDoc.dataViz_histogram_defaultColor = viewData.dataViz_histogram_defaultColor; this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(viewData.dataViz_pie_sliceColors); Object.keys(this.layoutDoc).forEach(key => { if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) { this.layoutDoc['_' + key] = viewData[key]; } }); return true; // const func = () => this._vizRenderer?.restoreView(data); // if (changedView || changedAxes) { // setTimeout(func, 100); // return true; // } // return func() ?? false; }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const visibleAnchor = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation); const anchor = !pinProps ? this.Document : (this._vizRenderer?.getAnchor(pinProps) ?? visibleAnchor ?? Docs.Create.ConfigDocument({ title: 'ImgAnchor:' + this.Document.title, config_panX: NumCast(this.layoutDoc._freeform_panX), config_panY: NumCast(this.layoutDoc._freeform_panY), config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null), annotationOn: this.Document, // when we clear selection -> we should have it so chartBox getAnchor returns undefined // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) /* put in some options */ })); anchor.config_dataViz = this.dataVizView; anchor.config_dataVizAxes = this.axes.length ? new List(this.axes) : undefined; anchor.dataViz_selectedRows = Field.Copy(this.layoutDoc.dataViz_selectedRows); anchor.dataViz_histogram_barColors = Field.Copy(this.layoutDoc.dataViz_histogram_barColors); anchor.dataViz_histogram_defaultColor = this.layoutDoc.dataViz_histogram_defaultColor; anchor.dataViz_pie_sliceColors = Field.Copy(this.layoutDoc.dataViz_pie_sliceColors); Object.keys(this.layoutDoc).forEach(key => { if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) { anchor[key] = this.layoutDoc[key]; } }); this.addDocument(anchor); // addAsAnnotation && this.addDocument(anchor); return anchor; }; createNoteAnnotation = () => { const createFunc = undoable(() => { this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']); }, 'create note annotation'); if (!this.layoutDoc.layout_showSidebar) { this.toggleSidebar(); setTimeout(createFunc); } else createFunc(); }; @observable _showSidebar = false; @observable _previewNativeWidth: Opt = undefined; @observable _previewWidth: Opt = 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; } @computed get sidebarHandle() { return (
); } /** * Toggle sidebar onclick the tiny comment button on the top right corner * @param e */ sidebarBtnDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, (moveEv, down, delta) => runInAction(() => { const localDelta = this._props .ScreenToLocalTransform() .scale(this._props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); const fullWidth = NumCast(this.layoutDoc._width); const mapWidth = fullWidth - this.sidebarWidth(); if (this.sidebarWidth() + localDelta[0] > 0) { this.layoutDoc._layout_showSidebar = true; 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; this.layoutDoc._layout_sidebarWidthPercent = '0%'; } return false; }), emptyFunction, () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar') ); }; getView = (doc: Doc, options: FocusViewOptions) => { if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { options.didMove = true; this.toggleSidebar(); } return new Promise>(res => { DocumentView.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) => { if (!this.SidebarShown) this.toggleSidebar(); return this.addDocument(doc, sidebarKey); }; sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey); componentDidMount() { this._props.setContentViewBox?.(this); if (!this._urlError) { 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); if (!getFrom?.schema_columnKeys) return undefined; const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text'); const children = DocListCast(getFrom?.[Doc.LayoutDataKey(getFrom)]); const current: { [key: string]: string }[] = []; children .filter(child => child) .forEach(child => { const row: { [key: string]: string } = {}; keys.forEach(key => { let 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 const csvRows = []; csvRows.push(keys.join(',')); for (let i = 0; i < children.length - 1; i++) { const eachRow = []; for (let j = 0; j < keys.length; j++) { let 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; 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(jsonRes => !jsonRes.errno && DataVizBox.datasetSchemaOG.set(href, jsonRes)))); } return current; }, current => { if (current) { const { href } = CsvCast(this.Document[this.fieldKey]).url; if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current); else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!); } }, { fireImmediately: true } ); this._disposers.contentSummary = reaction( () => this.records, () => this.updateGPTSummary() ); } fetchData = () => { 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(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, jsonRes)))); } }; // toggles for user to decide which chart type to view the data in @computed get renderVizView() { const scale = this._props.NativeDimScaling?.() || 1; const sharedProps = { Document: this.Document, 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 - 55) /* height of 'change view' button */ * 0.8, 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 ; case DataVizView.LINECHART: return {this._vizRenderer = r ?? undefined;}} vizBox={this} />; case DataVizView.HISTOGRAM: return {this._vizRenderer = r ?? undefined;}} />; case DataVizView.PIECHART: return {this._vizRenderer = r ?? undefined;}} margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />; default: } // prettier-ignore return null; } @action onPointerDown = (e: React.PointerEvent): void => { if ((this.Document._freeform_scale || 1) !== 1) return; if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) { this._props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; const target = e.target as HTMLElement; if (e.target && (target.className.includes('endOfContent') || (target.parentElement?.className !== 'textLayer' && target.parentElement?.parentElement?.className !== 'textLayer'))) { /* empty */ } else { // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee. setTimeout( action(() => { this._marqueeing = undefined; }), 100 ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. document.addEventListener('pointerup', this.onSelectEnd); } } }; @action onSelectEnd = (e: PointerEvent): void => { this._props.select(false); document.removeEventListener('pointerup', this.onSelectEnd); const sel = window.getSelection(); if (sel) { AnchorMenu.Instance.setSelectedText(sel.toString()); } if (sel?.type === 'Range') { AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } // Changing which document to add the annotation to (the currently selected PDF) GPTPopup.Instance.setSidebarFieldKey('data_sidebar'); GPTPopup.Instance.addDoc = this.sidebarAddDocument; }; // represents whether or not a data viz box created from a schema table displays live updates to the canvas @action changeLiveSchemaCheckbox = () => { this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive; }; // represents whether or not clicking on a peice of data in the visualization // (i.e. a data point in a linechart, a bar on a histogram, or a slice of a pie chart) // filters the data onto a new data viz doc created off of this one @action changeFilteringCheckbox = () => { this.layoutDoc.dataViz_filterSelection = !this.layoutDoc.dataViz_filterSelection; }; openDocCreatorMenu = (x: number, y: number) => { DocCreatorMenu.Instance.toggleDisplay(x, y); DocCreatorMenu.Instance.setDataViz(this); }; specificContextMenu = (e: React.MouseEvent) => { const cm = ContextMenu.Instance; const options = cm.findByDescription('Options...'); const optionItems = options?.subitems ?? []; optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' }); optionItems.push({ description: `Create documents`, event: () => this.openDocCreatorMenu(e.pageX, e.pageY), icon: 'table-cells' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); }; askGPT = action(async () => { GPTPopup.Instance.setSidebarFieldKey('data_sidebar'); GPTPopup.Instance.addDoc = this.sidebarAddDocument; GPTPopup.Instance.createFilteredDoc = this.createFilteredDoc; GPTPopup.Instance.setDataJson(''); GPTPopup.Instance.setMode(GPTPopupMode.DATA); const csvdata = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href); GPTPopup.Instance.setDataJson(JSON.stringify(csvdata)); GPTPopup.Instance.generateDataAnalysis(); }); getColSummary = (): string => { const possibleIds: number[] = this.records.map((_, index) => index); for (let i = possibleIds.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [possibleIds[i], possibleIds[j]] = [possibleIds[j], possibleIds[i]]; } const rowsToCheck = possibleIds.slice(0, Math.min(10, this.records.length)); let prompt: string = 'Col titles: '; const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined); cols.forEach((col, i) => { prompt += `Col #${i}: ${col} ------`; }); prompt += '----------- Rows: '; rowsToCheck.forEach(row => { prompt += `Row #${row}: `; cols.forEach(col => { prompt += `${col}: ${this.records[row][col]} -----`; }); }); return prompt; }; updateColDefaults = () => { const possibleIds: number[] = this.records.map((_, index) => index); for (let i = possibleIds.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [possibleIds[i], possibleIds[j]] = [possibleIds[j], possibleIds[i]]; } const rowToCheck = possibleIds[0]; const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined); cols.forEach(col => this.setColumnDefault(col, `${this.records[rowToCheck][col]}`)); }; updateGPTSummary = async () => { this._GPTLoading = true; this.updateColDefaults(); const prompt = this.getColSummary(); const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined); cols.forEach(col => { if (!this.colsInfo.get(col)) this.colsInfo.set(col, { title: col, desc: '', sizes: [], type: TemplateFieldType.UNSET }); }); try { const [res1, res2] = await Promise.all([gptAPICall(prompt, GPTCallType.VIZSUM), gptAPICall('Info:' + prompt, GPTCallType.VIZSUM2)]); if (res1) { this.GPTSummary = new ObservableMap(); const descs: { [col: string]: string } = JSON.parse(res1); for (const [key, val] of Object.entries(descs)) { this.GPTSummary.set(key, { desc: val }); if (!this.colsInfo.get(key)?.desc) this.setColumnDesc(key, val); } } if (res2) { !this.GPTSummary && (this.GPTSummary = new ObservableMap()); const info: { [col: string]: { type: TemplateFieldType; size: TemplateFieldSize } } = JSON.parse(res2); for (const [key, val] of Object.entries(info)) { const colSummary = this.GPTSummary.get(key); if (colSummary) { colSummary.size = val.size; colSummary.type = val.type; this.setColumnType(key, val.type); this.modifyColumnSizes(key, val.size, true); } } } } catch (err) { console.error(err); } }; /** * creates a new dataviz document filter from this one * it appears to the right of this document, with the * parameters passed in being used to create an initial display */ createFilteredDoc = (axes?: string[]) => { const embedding = Doc.MakeEmbedding(this.Document!); embedding._layout_showSidebar = false; embedding._dataViz = DataVizView.LINECHART; embedding._dataViz_axes = new List(axes); embedding._dataViz_parentViz = this.Document; embedding.histogramBarColors = Field.Copy(this.layoutDoc.histogramBarColors); embedding.defaultHistogramColor = this.layoutDoc.defaultHistogramColor; embedding.pieSliceColors = Field.Copy(this.layoutDoc.pieSliceColors); embedding._layout_showSidebar = false; embedding.width = NumCast(this.layoutDoc._width) - this.sidebarWidth(); embedding._layout_sidebarWidthPercent = '0%'; this._props.addDocument?.(embedding); embedding._dataViz_axes = new List(axes); this.layoutDoc.dataViz_selectedRows = new List(this.records.map((rec, i) => i)); embedding.x = Number(embedding.x) + Number(this.Document.width); return true; }; render() { const scale = this._props.NativeDimScaling?.() || 1; const toggleBtn = (name: string, type: DataVizView) => ( { this.layoutDoc._dataViz = type; }} toggleStatus={this.layoutDoc._dataViz === type} /> ); return !this.records.length ? ( // displays how to get data into the DataVizBox if its empty
To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command (ctrl + p) to bring the data table to your canvas.
) : (
e.stopPropagation()} ref={this._mainCont}>
{toggleBtn(' TABLE ', DataVizView.TABLE)} {toggleBtn('LINECHART', DataVizView.LINECHART)} {toggleBtn('HISTOGRAM', DataVizView.HISTOGRAM)} {toggleBtn('PIE CHART', DataVizView.PIECHART)}
{this.layoutDoc && this.layoutDoc.dataViz_asSchema ? (
Display Live Updates to Canvas
) : null} {this.layoutDoc._dataViz !== DataVizView.TABLE ? (
Select data to filter
) : null} {this.renderVizView}
{this.sidebarHandle} {this.annotationLayer} {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : ( )}
); } } Docs.Prototypes.TemplateMap.set(DocumentType.DATAVIZ, { layout: { view: DataVizBox, dataField: 'data' }, options: { acl: '', dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, }, }); Docs.Prototypes.TemplateMap.set(DocumentType.DATAVIZ, { layout: { view: DataVizBox, dataField: 'data' }, options: { dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true }, });