From a5294e6ba7fcf82eb3d22c0c187ce351ee698ce5 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 6 Jul 2023 14:19:55 -0400 Subject: basic histogram --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 0fe24fe8d..38edf2dd9 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -12,10 +12,12 @@ import { PinProps } from '../trails'; import { LineChart } from './components/LineChart'; import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; +import { Histogram } from './components/Histogram'; export enum DataVizView { TABLE = 'table', LINECHART = 'lineChart', + HISTOGRAM = 'histogram' } @observer @@ -100,6 +102,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { switch (this.dataVizView) { case DataVizView.TABLE: return ; case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; + case DataVizView.HISTOGRAM: return ; } } @computed get dataUrl() { @@ -142,7 +145,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { { passive: false } ) }> - + {/* */} + + + {this.selectView} ); -- cgit v1.2.3-70-g09d2 From 217f656cd39701d87bc298717551ae78e5b9eb13 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 12 Jul 2023 14:40:31 -0400 Subject: start of pie charts --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 6 +- .../views/nodes/DataVizBox/components/PieChart.tsx | 292 +++++++++++++++++++++ 2 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 src/client/views/nodes/DataVizBox/components/PieChart.tsx (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index b8a448e46..2a0f6c17d 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -13,11 +13,13 @@ import { LineChart } from './components/LineChart'; import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; +import { PieChart } from './components/PieChart'; export enum DataVizView { TABLE = 'table', LINECHART = 'lineChart', - HISTOGRAM = 'histogram' + HISTOGRAM = 'histogram', + PIECHART = 'pieChart' } @observer @@ -104,6 +106,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { case DataVizView.TABLE: return ; case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; case DataVizView.HISTOGRAM: return ; + case DataVizView.PIECHART: return ; } } @computed get dataUrl() { @@ -150,6 +153,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { + {this.selectView} ); diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx new file mode 100644 index 000000000..c746e6fc1 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -0,0 +1,292 @@ +import { observer } from "mobx-react"; +import { Doc, DocListCast } from "../../../../../fields/Doc"; +import * as React from 'react'; +import * as d3 from 'd3'; +import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; +import { LinkManager } from "../../../../util/LinkManager"; +import { Cast, DocCast} from "../../../../../fields/Types"; +import { DataPoint, SelectedDataPoint } from "./LineChart"; +import { DocumentManager } from "../../../../util/DocumentManager"; +import { Id } from "../../../../../fields/FieldSymbols"; +import { DataVizBox } from "../DataVizBox"; +import { listSpec } from "../../../../../fields/Schema"; +import { PinProps, PresBox } from "../../trails"; +import { Docs } from "../../../../documents/Documents"; +import { List } from "../../../../../fields/List"; +import './Chart.scss'; +import { ChatCompletionResponseMessageRoleEnum } from "openai"; + +export interface PieChartProps { + rootDoc: Doc; + axes: string[]; + pairs: { [key: string]: any }[]; + width: number; + height: number; + dataDoc: Doc; + fieldKey: string; + margin: { + top: number; + right: number; + bottom: number; + left: number; + }; +} + +@observer +export class PieChart extends React.Component { + + private _disposers: { [key: string]: IReactionDisposer } = {}; + private _piechartRef: React.RefObject = React.createRef(); + private _piechartSvg: d3.Selection | undefined; + private numericalData: boolean = false; + @observable _currSelected: SelectedDataPoint | undefined = undefined; + // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates + + @computed get _piechartData() { + if (this.props.axes.length < 1) return []; + if (this.props.axes.length < 2) { + var ax0 = this.props.axes[0]; + if (/\d/.test(this.props.pairs[0][ax0])){ this.numericalData = true } + return this.props.pairs + ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) + }; + + var ax0 = this.props.axes[0]; + var ax1 = this.props.axes[1]; + if (/\d/.test(this.props.pairs[0][ax0])) { this.numericalData = true;} + return this.props.pairs + ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) + // .sort((a, b) => (a[ax0] < b[ax0] ? -1 : 1)); + return this.props.pairs + ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) + .sort((a, b) => (a.x < b.x ? -1 : 1)); + } + @computed get incomingLinks() { + return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + .filter(link => link.link_anchor_1 !== this.props.rootDoc) // 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 incomingSelected() { + // return selected x and y axes + // otherwise, use the selection of whatever is linked to us + return this.incomingLinks // all links that are pointing to this node + .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes + .filter(dvb => dvb) + .map(dvb => dvb.pairs?.filter((pair: { [x: string]: any; }) => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor + .lastElement(); + } + @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { + if (this.numericalData){ + const data = this.data(this._piechartData); + return {xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin:0, yMax:0} + } + return {xMin:0, xMax:0, yMin:0, yMax:0} + } + componentWillUnmount() { + Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); + } + componentDidMount = () => { + this._disposers.chartData = reaction( + () => ({ dataSet: this._piechartData, w: this.width, h: this.height }), + ({ dataSet, w, h }) => { + if (dataSet) { + this.drawChart(dataSet, w, h); + + // redraw annotations when the chart data has changed, or the local or inherited selection has changed + this.clearAnnotations(); + this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true); + this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); + } + }, + { fireImmediately: true } + ); + this._disposers.annos = reaction( + () => DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), + annotations => { + // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way + // could be blue colored to make it look like anchor + // this.drawAnnotations() + // loop through annotations and draw them + annotations.forEach(a => this.drawAnnotations(Number(a.x), Number(a.y))); + // this.drawAnnotations(annotations.x, annotations.y); + }, + { fireImmediately: true } + ); + this._disposers.highlights = reaction( + () => ({ + selected: this._currSelected, + incomingSelected: this.incomingSelected, + }), + ({ selected, incomingSelected }) => { + // redraw annotations when the chart data has changed, or the local or inherited selection has changed + this.clearAnnotations(); + selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true); + incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); + }, + { fireImmediately: true } + ); + }; + + // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that + + clearAnnotations = () => { + const elements = document.querySelectorAll('.datapoint'); + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + element.classList.remove('brushed'); + element.classList.remove('selected'); + } + }; + // gets called whenever the "data_annotations" fields gets updated + drawAnnotations = (dataX: number, dataY: number, selected?: boolean) => { + // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements + // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY + // if it exists, then highlight it + // if it doesn't exist, then remove the highlight + const elements = document.querySelectorAll('.datapoint'); + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + const x = element.getAttribute('data-x'); + const y = element.getAttribute('data-y'); + if (x === dataX.toString() && y === dataY.toString()) { + element.classList.add(selected ? 'selected' : 'brushed'); + } + // TODO: nda - this remove highlight code should go where we remove the links + // } else { + // } + } + }; + + removeAnnotations(dataX: number, dataY: number) { + // loop through and remove any annotations that no longer exist + } + + @action + restoreView = (data: Doc) => { + const coords = Cast(data.presDataVizSelection, listSpec('number'), null); + if (coords?.length > 1 && (this._currSelected?.x !== coords[0] || this._currSelected?.y !== coords[1])) { + this.setCurrSelected(coords[0], coords[1]); + return true; + } + if (this._currSelected) { + this.setCurrSelected(); + return true; + } + return false; + }; + + // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) + getAnchor = (pinProps?: PinProps) => { + const anchor = Docs.Create.ConfigDocument({ + // + title: 'line doc selection' + this._currSelected?.x, + }); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); + anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected.x, this._currSelected.y]) : undefined; + return anchor; + }; + + @computed get height() { + return this.props.height - this.props.margin.top - this.props.margin.bottom; + } + + @computed get width() { + return this.props.width - this.props.margin.left - this.props.margin.right; + } + + setupTooltip() { + return d3 + .select(this._piechartRef.current) + .append('div') + .attr('class', 'tooltip') + .style('opacity', 0) + .style('background', '#fff') + .style('border', '1px solid #ccc') + .style('padding', '5px') + .style('position', 'absolute') + .style('font-size', '12px'); + } + + // TODO: nda - use this everyewhere we update currSelected? + @action + setCurrSelected(x?: number, y?: number) { + // TODO: nda - get rid of svg element in the list? + this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined; + this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true)); + this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined)); + } + + data = (dataSet: any) => { + var validData = dataSet.filter((d: { [x: string]: unknown; }) => { + var valid = true; + Object.keys(dataSet[0]).map(key => { + if (!d[key] || Number.isNaN(d[key])) valid = false; + }) + return valid; + }) + var field = Object.keys(dataSet[0])[0] + const data = validData.map((d: { [x: string]: any; }) => { + if (this.numericalData) { return +d[field].replace(/\$/g, '').replace(/\%/g, '') } + return d[field] + }) + return data; + } + + drawChart = (dataSet: any, width: number, height: number) => { + d3.select(this._piechartRef.current).select('svg').remove(); + d3.select(this._piechartRef.current).select('.tooltip').remove(); + + var radius = Math.min(width, height) / 2 - Math.max(this.props.margin.top, this.props.margin.bottom, this.props.margin.left, this.props.margin.right) + var svg = (this._piechartSvg = d3 + .select(this._piechartRef.current) + .append("svg") + .attr("class", "graph") + .attr("width", width + this.props.margin.right + this.props.margin.left) + .attr("height", height + this.props.margin.top + this.props.margin.bottom) + .append("g")); + + let g = svg.append("g") + .attr("transform", + "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")"); + + var data = this.data(this._piechartData); + var pie = d3.pie(); + var arc = d3.arc() + .innerRadius(0) + .outerRadius(radius); + + //Generate groups + var percentField = Object.keys(dataSet[0])[0] + var descriptionField = Object.keys(dataSet[0])[1] + var arcs = g.selectAll("arc") + .data(pie(data)) + .enter() + .append("g") + arcs.append("path") + .attr("fill", (data, i)=>{ + let value=data.data; + return d3.schemeSet3[i]; + }) + .attr("d", arc); + arcs.append("text") + .attr("transform",function(d){ return "translate("+ (arc.centroid(d)) + ")"; }) + .attr("text-anchor", "middle") + .text(function(d){ return dataSet[data.indexOf(d.value)][percentField] + ' ' + dataSet[data.indexOf(d.value)][descriptionField]}) + + }; + + render() { + + return ( + this.props.axes.length >= 1 ? ( +
+ {`Selected: ${Object.keys(this._piechartData[0])[0]}`} +
+ ) : {'first use table view to select an axis to plot'} + ); + } + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From c5740deae879fffdc46a862b81be2c96ae9366b4 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 12 Jul 2023 17:28:20 -0400 Subject: graph type option ui + problem fixes --- src/client/views/nodes/DataVizBox/DataVizBox.scss | 5 +++++ src/client/views/nodes/DataVizBox/DataVizBox.tsx | 8 ++++---- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 2 +- src/client/views/nodes/DataVizBox/components/LineChart.tsx | 2 +- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index cd500e9ae..424f8b0f1 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -1,4 +1,9 @@ .dataviz { overflow: auto; height: 100%; + + .datatype-button{ + margin: 0; + border: 1px solid white; + } } diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 2a0f6c17d..e6d08d47f 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -150,10 +150,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { ) }> {/* */} - - - - + + + + {this.selectView} ); diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index e4267aee3..0ea492ff1 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -305,7 +305,7 @@ export class Histogram extends React.Component { bins.forEach(d => d.x0 = d.x0!) xAxis = d3.axisBottom(x) .ticks(numBins-1) - .tickFormat( i => uniqueArr[i]) + .tickFormat( i => uniqueArr[i.valueOf]) .tickPadding(10) translateXAxis = eachRectWidth / 2; } diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index d87f5ae1b..dfb9f54c1 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -221,7 +221,7 @@ export class LineChart extends React.Component { } // TODO: nda - can use d3.create() to create html element instead of appending - drawChart = (dataSet: DataPoint[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => { + drawChart = (dataSet: any[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => { // clearing tooltip and the current chart d3.select(this._lineChartRef.current).select('svg').remove(); d3.select(this._lineChartRef.current).select('.tooltip').remove(); diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index c746e6fc1..121e6db3d 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -272,7 +272,7 @@ export class PieChart extends React.Component { }) .attr("d", arc); arcs.append("text") - .attr("transform",function(d){ return "translate("+ (arc.centroid(d)) + ")"; }) + .attr("transform",function(d){ return "translate("+ (arc.centroid(d as unknown as d3.DefaultArcObject)) + ")"; }) .attr("text-anchor", "middle") .text(function(d){ return dataSet[data.indexOf(d.value)][percentField] + ' ' + dataSet[data.indexOf(d.value)][descriptionField]}) -- cgit v1.2.3-70-g09d2 From aa7642006598bd47b415d4d2452dd4d226ab3ac5 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 12 Jul 2023 22:59:46 -0400 Subject: brushing/linking for histograms + pie charts --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 1 + src/client/views/nodes/DataVizBox/components/Histogram.tsx | 6 +++--- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 5 ++--- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index e6d08d47f..25098baf1 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -133,6 +133,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } render() { + if (!this.layoutDoc._dataVizView) this.layoutDoc._dataVizView = this.dataVizView; return !this.pairs?.length ? (
Loading...
) : ( diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 0ea492ff1..b6ae15709 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -305,7 +305,7 @@ export class Histogram extends React.Component { bins.forEach(d => d.x0 = d.x0!) xAxis = d3.axisBottom(x) .ticks(numBins-1) - .tickFormat( i => uniqueArr[i.valueOf]) + .tickFormat( i => uniqueArr[i]) .tickPadding(10) translateXAxis = eachRectWidth / 2; } @@ -355,11 +355,11 @@ export class Histogram extends React.Component { render() { return ( - this.props.axes.length >= 1 ? ( + this.props.axes.length >= 1 && (this.incomingSelected? this.incomingSelected.length>0 : true) ? (
{`Selected: ${Object.keys(this._histogramData[0])[0]}`}
- ) : {'first use table view to select an axis to plot'} + ) : {'first use table view to select a column to graph'} ); } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 121e6db3d..3ca49a46c 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -14,7 +14,6 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; -import { ChatCompletionResponseMessageRoleEnum } from "openai"; export interface PieChartProps { rootDoc: Doc; @@ -281,11 +280,11 @@ export class PieChart extends React.Component { render() { return ( - this.props.axes.length >= 1 ? ( + this.props.axes.length >= 1 && (this.incomingSelected? this.incomingSelected.length>0 : true) ? (
{`Selected: ${Object.keys(this._piechartData[0])[0]}`}
- ) : {'first use table view to select an axis to plot'} + ) : {'first use table view to select a column to graph'} ); } -- cgit v1.2.3-70-g09d2 From 371f0309ec6b10a86b4a456d233be5b53cf93356 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 20 Jul 2023 17:58:24 -0400 Subject: components + links + brushed TableBox --- src/client/views/nodes/DataVizBox/DataVizBox.scss | 4 ++-- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 26 +++++++++++++++++----- .../nodes/DataVizBox/components/Histogram.tsx | 3 ++- .../views/nodes/DataVizBox/components/PieChart.tsx | 4 ++-- .../views/nodes/DataVizBox/components/TableBox.tsx | 22 ++++++++++++++++-- 5 files changed, 46 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index 5e7230271..32c0bbfc1 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -4,7 +4,7 @@ width: 100%; .datatype-button{ - margin: 0; - border: 1px solid white; + display: flex; + flex-direction: row; } } diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 25098baf1..4ddebb833 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -14,6 +14,7 @@ import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; import { PieChart } from './components/PieChart'; +import { Toggle, ToggleType } from 'browndash-components'; export enum DataVizView { TABLE = 'table', @@ -103,7 +104,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { if (!this.pairs) return 'no data'; // prettier-ignore switch (this.dataVizView) { - case DataVizView.TABLE: return ; + case DataVizView.TABLE: return ; case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; case DataVizView.HISTOGRAM: return ; case DataVizView.PIECHART: return ; @@ -150,11 +151,24 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { { passive: false } ) }> - {/* */} - - - - +
+ this.layoutDoc._dataVizView = DataVizView.TABLE} + toggleStatus={this.layoutDoc._dataVizView == DataVizView.TABLE} + /> + this.layoutDoc._dataVizView = DataVizView.LINECHART} + toggleStatus={this.layoutDoc._dataVizView == DataVizView.LINECHART} + /> + this.layoutDoc._dataVizView = DataVizView.HISTOGRAM} + toggleStatus={this.layoutDoc._dataVizView == DataVizView.HISTOGRAM} + /> + this.layoutDoc._dataVizView = DataVizView.PIECHART} + toggleStatus={this.layoutDoc._dataVizView == DataVizView.PIECHART} + /> +
{this.selectView} ); diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index b8be9bd13..01e6709fa 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -65,7 +65,8 @@ export class Histogram extends React.Component { } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 !== this.props.rootDoc) // get links where this chart doc is the target of the link + .filter(link => { + return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // 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 incomingSelected() { diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 6241e6221..05a2f1588 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -65,7 +65,8 @@ export class PieChart extends React.Component { } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 !== this.props.rootDoc) // get links where this chart doc is the target of the link + .filter(link => { + return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // 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 incomingSelected() { @@ -359,7 +360,6 @@ export class PieChart extends React.Component { }; render() { - var selected: string; if (this._currSelected){ selected = '{ '; diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index ca888e13f..500e7b639 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -8,8 +8,11 @@ import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../. import { DragManager } from '../../../../util/DragManager'; import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; +import { LinkManager } from '../../../../util/LinkManager'; +import { DocCast } from '../../../../../fields/Types'; interface TableBoxProps { + rootDoc: Doc; pairs: { [key: string]: any }[]; selectAxes: (axes: string[]) => void; axes: string[]; @@ -18,9 +21,24 @@ interface TableBoxProps { @observer export class TableBox extends React.Component { + + @computed get _tableData() { + if (this.incomingLinks.length! <= 0) return this.props.pairs; + return this.props.pairs?.filter(pair => (Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + } + + @computed get incomingLinks() { + return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + .filter(link => { + return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // 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.props.pairs.length ? Array.from(Object.keys(this.props.pairs[0])) : []; + return this._tableData.length ? Array.from(Object.keys(this._tableData[0])) : []; } + render() { return (
@@ -91,7 +109,7 @@ export class TableBox extends React.Component { - {this.props.pairs?.map((p, i) => { + {this._tableData?.map((p, i) => { return ( (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}> {this.columns.map(col => ( -- cgit v1.2.3-70-g09d2 From 6984267d0f70f080bc1e1e6397b2377145e3cae2 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 28 Jul 2023 12:13:52 -0400 Subject: DataVizBox from 'tools' instructions --- src/client/util/CurrentUserUtils.ts | 4 ++-- src/client/views/nodes/DataVizBox/DataVizBox.scss | 4 ++++ src/client/views/nodes/DataVizBox/DataVizBox.tsx | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2e4fb0f1c..64a056753 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -295,7 +295,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)}, { toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)}, { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)}, - { toolTip: "Tap or drag to create a physics simulation", title: "Simulation", icon: "atom", dragFactory: doc.emptySimulation as Doc, }, + { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation)}, { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, @@ -306,7 +306,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript)}, - { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, + { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true }, { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index 32c0bbfc1..ab2f19726 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -8,3 +8,7 @@ flex-direction: row; } } +.start-message { + margin: 10px; + align-self: baseline; +} diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 4ddebb833..d5e21ce0e 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -136,7 +136,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { render() { if (!this.layoutDoc._dataVizView) this.layoutDoc._dataVizView = this.dataVizView; return !this.pairs?.length ? ( -
Loading...
+
+ To create a DataViz box, either import / drag a CSV file into your canvas + or copy a data table and use the command 'ctrl + t' to bring the data table + to your canvas. +
) : (
Date: Fri, 28 Jul 2023 14:36:30 -0400 Subject: graph colorPickers --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 33 +++++++++++++--- .../views/nodes/DataVizBox/components/Chart.scss | 3 +- .../nodes/DataVizBox/components/Histogram.tsx | 46 +++++++++++++++++++++- .../views/nodes/DataVizBox/components/PieChart.tsx | 32 +++++++++++++-- 4 files changed, 102 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index d5e21ce0e..70fed91ef 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -97,17 +97,38 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } selectAxes = (axes: string[]) => (this.layoutDoc.data_vizAxes = new List(axes)); - @computed get selectView() { + @computed get table(){ + if (!this.pairs) return 'no data'; + return ; + } + @computed get lineChart(){ + const width = this.props.PanelWidth() * 0.9; + const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; + const margin = { top: 10, right: 25, bottom: 50, left: 25 }; + if (!this.pairs) return 'no data'; + return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; + } + @computed get histogram(){ const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 25, bottom: 50, left: 25 }; if (!this.pairs) return 'no data'; - // prettier-ignore + return ; + } + @computed get pieChart(){ + const width = this.props.PanelWidth() * 0.9; + const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; + const margin = { top: 10, right: 25, bottom: 50, left: 25 }; + if (!this.pairs) return 'no data'; + console.log('new pie') + return ; + } + @computed get selectView() { switch (this.dataVizView) { - case DataVizView.TABLE: return ; - case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; - case DataVizView.HISTOGRAM: return ; - case DataVizView.PIECHART: return ; + case DataVizView.TABLE: return this.table; + case DataVizView.LINECHART: return this.lineChart; + case DataVizView.HISTOGRAM: return this.histogram; + case DataVizView.PIECHART: return this.pieChart; } } @computed get dataUrl() { diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 5945840b5..da5a274a5 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -23,9 +23,8 @@ .histogram-bar{ outline: thin solid black; - fill: #69b3a2; &.hover{ - fill: grey; + outline: 5px solid black; } } diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index fdde29c81..998636a42 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -14,6 +14,8 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; +import { ColorPicker, Size, Type } from "browndash-components"; +import { FaFillDrip } from "react-icons/fa"; export interface HistogramProps { rootDoc: Doc; @@ -41,6 +43,9 @@ export class Histogram extends React.Component { private numericalYData: boolean = false; // whether the y axis is controlled by provided data rather than frequency private maxBins = 15; // maximum number of bins that is readable on a normal sized doc @observable _currSelected: any | undefined = undefined; + private curBarSelected: any = undefined; + private barColors: any = {}; + private defaultBarColor: string = '#69b3a2'; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _histogramData() { @@ -381,6 +386,11 @@ export class Histogram extends React.Component { elements[i].classList.remove('hover'); } if (!sameAsCurrent!) selected.attr('class', 'histogram-bar hover'); + if (sameAsCurrent!) this.curBarSelected = undefined; + else { + selected.attr('class', 'histogram-bar hover') + this.curBarSelected = selected; + } }); svg.on('click', onPointClick); @@ -415,6 +425,20 @@ export class Histogram extends React.Component { return height - y(d.length)}) .attr("width", eachRectWidth) .attr("class", 'histogram-bar') + .attr("fill", (d)=>{ return this.barColors[d[0]]? this.barColors[d[0]] : this.defaultBarColor}) + }; + + @action changeSelectedColor = (color: string) => { + this.curBarSelected.attr("fill", color); + this.barColors[this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { + const defaultColorBars = this._histogramSvg!.selectAll('.histogram-bar').filter((d: any) => { + if (this.barColors[d[0]]) return false; + else return true; + }) + defaultColorBars.attr("fill", color); + this.defaultBarColor = color; }; render() { @@ -433,7 +457,27 @@ export class Histogram extends React.Component { this.props.axes.length >= 1 ? (
{this.graphTitle}
-
{`Selected: ${selected}`}
+ } + selectedColor={this.defaultBarColor} + setSelectedColor={color => this.changeDefaultColor(color)} + size={Size.XSMALL} + /> + {selected != 'none' ? +
+ Selected: {selected} + } + selectedColor={this.curBarSelected.attr("fill")} + setSelectedColor={color => this.changeSelectedColor(color)} + size={Size.XSMALL} + /> +
+ : null}
) : {'first use table view to select a column to graph'} diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 27653b847..872bf9af1 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -14,6 +14,8 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; +import { ColorPicker, Size, Type } from "browndash-components"; +import { FaFillDrip } from "react-icons/fa"; export interface PieChartProps { rootDoc: Doc; @@ -39,6 +41,8 @@ export class PieChart extends React.Component { private _piechartSvg: d3.Selection | undefined; private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios @observable _currSelected: any | undefined = undefined; + private curSliceSelected: any = undefined; + private sliceColors: any = {}; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _piechartData() { @@ -332,7 +336,11 @@ export class PieChart extends React.Component { for (let i = 0; i < elements.length; i++) { elements[i].classList.remove('hover'); } - if (!sameAsCurrent!) selected.attr('class', 'slice hover'); + if (sameAsCurrent!) this.curSliceSelected = undefined; + else { + selected.attr('class', 'slice hover') + this.curSliceSelected = selected; + } }); var arcs = g.selectAll("arc") @@ -340,7 +348,7 @@ export class PieChart extends React.Component { .enter() .append("g") arcs.append("path") - .attr("fill", (data, i)=>{ return d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) + .attr("fill", (data, i)=>{ return this.sliceColors[data.data.valueOf()]? this.sliceColors[data.data.valueOf()] : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) .attr("class", 'slice') .attr("d", arc) .on('click', onPointClick) @@ -368,7 +376,13 @@ export class PieChart extends React.Component { }; + @action changeSelectedColor = (color: string) => { + this.curSliceSelected.attr("fill", color); + this.sliceColors[this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { this.props.axes.length >= 1 ? (
{this.graphTitle}
-
{`Selected: ${selected}`}
+ {selected != 'none' ? +
+ Selected: {selected} + } + selectedColor={this.curSliceSelected.attr("fill")} + setSelectedColor={color => this.changeSelectedColor(color)} + size={Size.XSMALL} + /> +
+ : null}
) : {'first use table view to select a column to graph'} -- cgit v1.2.3-70-g09d2 From 7da381226f86467729c4fcad685dac17e30c9bf9 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Mon, 31 Jul 2023 13:47:28 -0400 Subject: color ui fix + editable titles --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 1 - .../views/nodes/DataVizBox/components/Chart.scss | 14 +++++++- .../nodes/DataVizBox/components/Histogram.tsx | 41 ++++++++++++++++------ .../views/nodes/DataVizBox/components/PieChart.tsx | 22 +++++++++--- 4 files changed, 61 insertions(+), 17 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 70fed91ef..12aa2ae34 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -120,7 +120,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 25, bottom: 50, left: 25 }; if (!this.pairs) return 'no data'; - console.log('new pie') return ; } @computed get selectView() { diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index da5a274a5..fc0c4cea3 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -11,20 +11,32 @@ .graph-title{ align-items: center; font-size: larger; + display: flex; + flex-direction: row; + margin-top: -10px; + margin-bottom: -10px; } .selected-data{ align-items: center; + text-align: center; + display: flex; + flex-direction: row; + margin: 10px; + margin-top: 0px; + margin-bottom: -5px; } .slice { &.hover { stroke: black; + stroke-width: 2px; } } .histogram-bar{ outline: thin solid black; &.hover{ - outline: 5px solid black; + outline: 3px solid black; + outline-offset: -3px; } } diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 998636a42..6d0a8bf75 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -14,7 +14,7 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; -import { ColorPicker, Size, Type } from "browndash-components"; +import { ColorPicker, EditableText, Size, Type } from "browndash-components"; import { FaFillDrip } from "react-icons/fa"; export interface HistogramProps { @@ -46,6 +46,7 @@ export class Histogram extends React.Component { private curBarSelected: any = undefined; private barColors: any = {}; private defaultBarColor: string = '#69b3a2'; + @observable graphTitle: string = this.defaultGraphTitle; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _histogramData() { @@ -71,7 +72,7 @@ export class Histogram extends React.Component { .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) .sort((a, b) => (a.x < b.x ? -1 : 1)); } - @computed get graphTitle(){ + @computed get defaultGraphTitle(){ var ax0 = this.props.axes[0]; var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; if (this.props.axes.length<2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1){ @@ -432,6 +433,7 @@ export class Histogram extends React.Component { this.curBarSelected.attr("fill", color); this.barColors[this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { const defaultColorBars = this._histogramSvg!.selectAll('.histogram-bar').filter((d: any) => { if (this.barColors[d[0]]) return false; @@ -441,6 +443,19 @@ export class Histogram extends React.Component { this.defaultBarColor = color; }; + @computed get editableTitle() { + const title = this.graphTitle; + return ( + this.graphTitle = val as string)} + color={"black"} + size={Size.LARGE} + fillWidth + /> + ); + } + render() { var selected: string; @@ -456,18 +471,22 @@ export class Histogram extends React.Component { return ( this.props.axes.length >= 1 ? (
-
{this.graphTitle}
- } - selectedColor={this.defaultBarColor} - setSelectedColor={color => this.changeDefaultColor(color)} - size={Size.XSMALL} - /> +
+ {this.editableTitle} +     + } + selectedColor={this.defaultBarColor} + setSelectedColor={color => this.changeDefaultColor(color)} + size={Size.XSMALL} + /> +
{selected != 'none' ?
Selected: {selected} +     { @observable _currSelected: any | undefined = undefined; private curSliceSelected: any = undefined; private sliceColors: any = {}; + @observable graphTitle: string = this.defaultGraphTitle; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _piechartData() { @@ -67,7 +68,7 @@ export class PieChart extends React.Component { .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) .sort((a, b) => (a.x < b.x ? -1 : 1)); } - @computed get graphTitle(){ + @computed get defaultGraphTitle(){ var ax0 = this.props.axes[0]; var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; if (this.props.axes.length<2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1){ @@ -381,8 +382,20 @@ export class PieChart extends React.Component { this.sliceColors[this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ this.graphTitle = val as string)} + color={"black"} + size={Size.LARGE} + fillWidth + /> + ); + } + render() { - console.log(this.sliceColors) var selected: string; if (this._currSelected){ selected = '{ '; @@ -396,10 +409,11 @@ export class PieChart extends React.Component { return ( this.props.axes.length >= 1 ? (
-
{this.graphTitle}
+
{this.editableTitle}
{selected != 'none' ?
Selected: {selected} +     Date: Tue, 1 Aug 2023 17:41:09 -0400 Subject: things save: editable title for all 3 + color for histogram --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 42 ++++++---------------- .../views/nodes/DataVizBox/components/Chart.scss | 3 ++ .../nodes/DataVizBox/components/Histogram.tsx | 37 ++++++++++--------- .../nodes/DataVizBox/components/LineChart.tsx | 28 +++++++++++++-- .../views/nodes/DataVizBox/components/PieChart.tsx | 20 ++++++----- .../views/nodes/DataVizBox/components/TableBox.tsx | 26 +++++++++++--- 6 files changed, 95 insertions(+), 61 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 12aa2ae34..710c049a2 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -14,7 +14,7 @@ import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; import { PieChart } from './components/PieChart'; -import { Toggle, ToggleType } from 'browndash-components'; +import { Toggle, ToggleType, Type } from 'browndash-components'; export enum DataVizView { TABLE = 'table', @@ -25,6 +25,7 @@ export enum DataVizView { @observer export class DataVizBox extends ViewBoxAnnotatableComponent() { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } @@ -97,37 +98,16 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } selectAxes = (axes: string[]) => (this.layoutDoc.data_vizAxes = new List(axes)); - @computed get table(){ - if (!this.pairs) return 'no data'; - return ; - } - @computed get lineChart(){ - const width = this.props.PanelWidth() * 0.9; - const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; - const margin = { top: 10, right: 25, bottom: 50, left: 25 }; - if (!this.pairs) return 'no data'; - return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; - } - @computed get histogram(){ - const width = this.props.PanelWidth() * 0.9; - const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; - const margin = { top: 10, right: 25, bottom: 50, left: 25 }; - if (!this.pairs) return 'no data'; - return ; - } - @computed get pieChart(){ + @computed get selectView() { const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 25, bottom: 50, left: 25 }; if (!this.pairs) return 'no data'; - return ; - } - @computed get selectView() { switch (this.dataVizView) { - case DataVizView.TABLE: return this.table; - case DataVizView.LINECHART: return this.lineChart; - case DataVizView.HISTOGRAM: return this.histogram; - case DataVizView.PIECHART: return this.pieChart; + case DataVizView.TABLE: return ; + case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; + case DataVizView.HISTOGRAM: return ; + case DataVizView.PIECHART: return ; } } @computed get dataUrl() { @@ -176,19 +156,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { ) }>
- this.layoutDoc._dataVizView = DataVizView.TABLE} toggleStatus={this.layoutDoc._dataVizView == DataVizView.TABLE} /> - this.layoutDoc._dataVizView = DataVizView.LINECHART} toggleStatus={this.layoutDoc._dataVizView == DataVizView.LINECHART} /> - this.layoutDoc._dataVizView = DataVizView.HISTOGRAM} toggleStatus={this.layoutDoc._dataVizView == DataVizView.HISTOGRAM} /> - this.layoutDoc._dataVizView = DataVizView.PIECHART} toggleStatus={this.layoutDoc._dataVizView == DataVizView.PIECHART} /> diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index fc0c4cea3..6c87241b8 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -75,3 +75,6 @@ fill: red; } } +.table-container{ + overflow: scroll; +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 64e61fca8..740ee6e3a 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -1,10 +1,10 @@ import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../../../fields/Doc"; +import { Doc, DocListCast, FieldResult } from "../../../../../fields/Doc"; import * as React from 'react'; import * as d3 from 'd3'; import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; import { LinkManager } from "../../../../util/LinkManager"; -import { Cast, DocCast} from "../../../../../fields/Types"; +import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; import { DataPoint, SelectedDataPoint } from "./LineChart"; import { DocumentManager } from "../../../../util/DocumentManager"; import { Id } from "../../../../../fields/FieldSymbols"; @@ -19,6 +19,7 @@ import { FaFillDrip } from "react-icons/fa"; export interface HistogramProps { rootDoc: Doc; + layoutDoc: Doc; axes: string[]; pairs: { [key: string]: any }[]; width: number; @@ -44,9 +45,6 @@ export class Histogram extends React.Component { private maxBins = 15; // maximum number of bins that is readable on a normal sized doc @observable _currSelected: any | undefined = undefined; private curBarSelected: any = undefined; - private barColors: any = {}; - private defaultBarColor: string = '#69b3a2'; - @observable graphTitle: string = this.defaultGraphTitle; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _histogramData() { @@ -75,7 +73,7 @@ export class Histogram extends React.Component { @computed get defaultGraphTitle(){ var ax0 = this.props.axes[0]; var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; - if (this.props.axes.length<2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1){ + if (this.props.axes.length<2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1 || !this.numericalYData){ return ax0 + " Histogram"; } else return ax1 + " by " + ax0 + " Histogram"; @@ -285,8 +283,8 @@ export class Histogram extends React.Component { histStringDataSet.push({[yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i]}) } for (let i=0; i each[xAxisTitle]==data[i]) - sliceData[0][yAxisTitle] = sliceData[0][yAxisTitle] + 1; + let barData = histStringDataSet.filter(each => each[xAxisTitle]==data[i]) + barData[0][yAxisTitle] = barData[0][yAxisTitle] + 1; } } histDataSet = histStringDataSet @@ -426,27 +424,33 @@ export class Histogram extends React.Component { return height - y(d.length)}) .attr("width", eachRectWidth) .attr("class", 'histogram-bar') - .attr("fill", (d)=>{ return this.barColors[d[0]]? this.barColors[d[0]] : this.defaultBarColor}) + .attr("fill", (d)=>{ return this.props.layoutDoc['histogramBarColors-'+d[0]]? StrCast(this.props.layoutDoc['histogramBarColors-'+d[0]]) : this.props.layoutDoc.defaultHistogramColor? StrCast(this.props.layoutDoc.defaultHistogramColor): '#69b3a2'}) }; @action changeSelectedColor = (color: string) => { this.curBarSelected.attr("fill", color); - this.barColors[this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { const defaultColorBars = this._histogramSvg!.selectAll('.histogram-bar').filter((d: any) => { - if (this.barColors[d[0]]) return false; + if (this.props.layoutDoc['histogramBarColors-'+d[0]]) return false; else return true; }) defaultColorBars.attr("fill", color); - this.defaultBarColor = color; + this.props.layoutDoc.defaultHistogramColor = color; }; render() { - const title = this.graphTitle; + var curSelectedBarName; + var titleAccessor: any=''; + if (this.props.axes.length==2) titleAccessor = StrCast(this.props.layoutDoc['histogram-title-'+this.props.axes[0]+'-'+this.props.axes[1]]); + else if (this.props.axes.length>0) titleAccessor = StrCast(this.props.layoutDoc['histogram-title-'+this.props.axes[0]]); + const title = titleAccessor? titleAccessor : this.defaultGraphTitle; var selected: string; if (this._currSelected){ + curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { key!=''? selected += key + ': ' + this._currSelected[key] + ', ': ''; @@ -461,7 +465,8 @@ export class Histogram extends React.Component {
this.graphTitle = val as string)} + setVal={action(val => {this.props.axes.length>1? this.props.layoutDoc['histogram-title-'+this.props.axes[0]+"-"+this.props.axes[1]] = val as string + : this.props.layoutDoc['histogram-title-'+this.props.axes[0]] = val as string})} color={"black"} size={Size.LARGE} fillWidth @@ -471,7 +476,7 @@ export class Histogram extends React.Component { tooltip={'Change Default Bar Color'} type={Type.SEC} icon={} - selectedColor={this.defaultBarColor} + selectedColor={this.props.layoutDoc.defaultHistogramColor? StrCast(this.props.layoutDoc.defaultHistogramColor): '#69b3a2'} setSelectedColor={color => this.changeDefaultColor(color)} size={Size.XSMALL} /> @@ -484,7 +489,7 @@ export class Histogram extends React.Component { tooltip={'Change Slice Color'} type={Type.SEC} icon={} - selectedColor={this.curBarSelected.attr("fill")} + selectedColor={this.props.layoutDoc['histogramBarColors-'+curSelectedBarName]? this.props.layoutDoc['histogramBarColors-'+curSelectedBarName] : this.curBarSelected.attr("fill")} setSelectedColor={color => this.changeSelectedColor(color)} size={Size.XSMALL} /> diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index da79df476..0142e96ad 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -6,7 +6,7 @@ import { Doc, DocListCast } from '../../../../../fields/Doc'; import { Id } from '../../../../../fields/FieldSymbols'; import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; -import { Cast, DocCast } from '../../../../../fields/Types'; +import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; import { DocumentManager } from '../../../../util/DocumentManager'; import { LinkManager } from '../../../../util/LinkManager'; @@ -14,6 +14,7 @@ import { PinProps, PresBox } from '../../trails'; import { DataVizBox } from '../DataVizBox'; import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils'; import './Chart.scss'; +import { EditableText, Size } from 'browndash-components'; export interface DataPoint { x: number; @@ -24,6 +25,7 @@ export interface SelectedDataPoint extends DataPoint { } export interface LineChartProps { rootDoc: Doc; + layoutDoc: Doc; axes: string[]; pairs: { [key: string]: any }[]; width: number; @@ -185,6 +187,15 @@ export class LineChart extends React.Component { return this.props.width - this.props.margin.left - this.props.margin.right; } + @computed get defaultGraphTitle(){ + var ax0 = this.props.axes[0]; + var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; + if (this.props.axes.length<2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1){ + return ax0 + " Line Chart"; + } + else return ax1 + " by " + ax0 + " Line Chart"; + } + setupTooltip() { return d3 .select(this._lineChartRef.current) @@ -339,11 +350,24 @@ export class LineChart extends React.Component { } render() { + var titleAccessor:any = ''; + if (this.props.axes.length==2) titleAccessor = StrCast(this.props.layoutDoc['lineChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]]); + else if (this.props.axes.length>0) titleAccessor = StrCast(this.props.layoutDoc['lineChart-title-'+this.props.axes[0]]); + const title = titleAccessor? titleAccessor : this.defaultGraphTitle; const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return ( this.props.axes.length >= 2 ? (
-
{this.graphTitle}
+
+ {this.props.axes.length>1? this.props.layoutDoc['lineChart-title-'+this.props.axes[0]+"-"+this.props.axes[1]] = val as string + : this.props.layoutDoc['lineChart-title-'+this.props.axes[0]] = val as string})} + color={"black"} + size={Size.LARGE} + fillWidth + /> +
{`Selected: ${selectedPt}`}
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 47d4fb23e..f0c27866d 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import * as d3 from 'd3'; import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; import { LinkManager } from "../../../../util/LinkManager"; -import { Cast, DocCast} from "../../../../../fields/Types"; +import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; import { DataPoint, SelectedDataPoint } from "./LineChart"; import { DocumentManager } from "../../../../util/DocumentManager"; import { Id } from "../../../../../fields/FieldSymbols"; @@ -19,6 +19,7 @@ import { FaFillDrip } from "react-icons/fa"; export interface PieChartProps { rootDoc: Doc; + layoutDoc: Doc; axes: string[]; pairs: { [key: string]: any }[]; width: number; @@ -42,8 +43,6 @@ export class PieChart extends React.Component { private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios @observable _currSelected: any | undefined = undefined; private curSliceSelected: any = undefined; - private sliceColors: any = {}; - @observable graphTitle: string = this.defaultGraphTitle; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _piechartData() { @@ -254,7 +253,7 @@ export class PieChart extends React.Component { var percentField = Object.keys(dataSet[0])[0] var descriptionField = Object.keys(dataSet[0])[1]! - var radius = Math.min(width, height) / 2 - Math.max(this.props.margin.top, this.props.margin.bottom, this.props.margin.left, this.props.margin.right) + var radius = Math.min(width, height-this.props.margin.top-this.props.margin.bottom) /2 var svg = (this._piechartSvg = d3 .select(this._piechartRef.current) .append("svg") @@ -349,7 +348,7 @@ export class PieChart extends React.Component { .enter() .append("g") arcs.append("path") - .attr("fill", (data, i)=>{ return this.sliceColors[data.data.valueOf()]? this.sliceColors[data.data.valueOf()] : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) + .attr("fill", (data, i)=>{ return this.props.layoutDoc['pieSliceColors-'+data.data.valueOf()]? StrCast(this.props.layoutDoc['pieSliceColors-'+data.data.valueOf()]) : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) .attr("class", 'slice') .attr("d", arc) .on('click', onPointClick) @@ -379,11 +378,15 @@ export class PieChart extends React.Component { @action changeSelectedColor = (color: string) => { this.curSliceSelected.attr("fill", color); - this.sliceColors[this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\0) titleAccessor = StrCast(this.props.layoutDoc['pieChart-title-'+this.props.axes[0]]); + const title = titleAccessor? titleAccessor : this.defaultGraphTitle; var selected: string; if (this._currSelected){ selected = '{ '; @@ -400,7 +403,8 @@ export class PieChart extends React.Component {
this.graphTitle = val as string)} + setVal={action(val => {this.props.axes.length>1? this.props.layoutDoc['pieChart-title-'+this.props.axes[0]+"-"+this.props.axes[1]] = val as string + : this.props.layoutDoc['pieChart-title-'+this.props.axes[0]] = val as string})} color={"black"} size={Size.LARGE} fillWidth diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index aaedba202..64c6dc940 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -9,14 +9,24 @@ import { DragManager } from '../../../../util/DragManager'; import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; import { LinkManager } from '../../../../util/LinkManager'; -import { DocCast } from '../../../../../fields/Types'; +import { Cast, DocCast } from '../../../../../fields/Types'; import { EditableText, Size, Type } from 'browndash-components'; +import './Chart.scss'; +import { listSpec } from '../../../../../fields/Schema'; interface TableBoxProps { rootDoc: Doc; pairs: { [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; } @@ -27,6 +37,7 @@ export class TableBox extends React.Component { @computed get _tableData() { if (this.incomingLinks.length! <= 0) return this.props.pairs; + /// StrListCast(this.incomingLinks[0].anchor_1.selected) ==> list of guids that the parent has selected return this.props.pairs?.filter(pair => (Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) } @@ -44,7 +55,7 @@ export class TableBox extends React.Component { // render() { // return ( - //
+ //
// // // @@ -144,7 +155,7 @@ export class TableBox extends React.Component { render() { return ( -
+
@@ -214,7 +225,14 @@ export class TableBox extends React.Component { {this._tableData?.map((p, i) => { return ( - (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}> + { + // if (!this.props.docView?.()!.layoutDoc.selected) + // this.props.docView!.()!.layoutDoc.selected = new List(); + // const selected = Cast(this.props.docView?.()!.layoutDoc.selected, listSpec("string"), null); + // // StrListCast(this.props.docView?.()!.layoutDoc.selected) + // selected.push(p.guid); + (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]) + })}> {this.columns.map(col => ( {this._tableData?.map((p, i) => { var containsData = false; + var guid = StrListCast(this.props.layoutDoc.rowGuids)![this.props.pairs.indexOf(p)] this.columns.map(col => {if (p[col]!='' && p[col]!=null && p[col]!=undefined) containsData = true}) if (containsData){ return ( { if (!this.props.layoutDoc.selected) this.props.layoutDoc.selected = new List(); const selected = Cast(this.props.layoutDoc.selected, listSpec("string"), null); - if (selected.includes(p.guid)) selected.splice(selected.indexOf(p.guid), 1); + if (selected.includes(guid)) selected.splice(selected.indexOf(guid), 1); else { - selected.push(p.guid)}; + selected.push(guid)}; })} style={ - { fontWeight: StrListCast(this.props.layoutDoc.selected).includes(p.guid) ? 'bold' : '' , width: '110%', - background: StrListCast(this.props.layoutDoc.selected).includes(p.guid) ? 'lightgrey' : '' }}> + { fontWeight: StrListCast(this.props.layoutDoc.selected).includes(guid) ? 'bold' : '' , width: '110%', + background: StrListCast(this.props.layoutDoc.selected).includes(guid) ? 'lightgrey' : '' }}> {this.columns.map(col => ( (this.props.layoutDoc.selected)?
{p[col]} -- cgit v1.2.3-70-g09d2 From 719ac430e7f0e338cc8d911813f25a90696aba15 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 2 Aug 2023 11:12:50 -0400 Subject: linking+trails getAnchor+restoreView update --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 13 +++++++++++++ src/client/views/nodes/DataVizBox/components/Histogram.tsx | 1 + 2 files changed, 14 insertions(+) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 710c049a2..f167346de 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -68,6 +68,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { restoreView = (data: Doc) => { const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._data_vizAxes = new List(StrListCast(data.presDataVizAxes))); + Object.keys(this.layoutDoc).map(key => { + if (key.startsWith('histogram-title') || key.startsWith('histogramBarColors') || key.startsWith('defaultHistogramColor') + || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ + console.log(key) + this.layoutDoc['_'+key] = data[key]; + } + }) const func = () => this._chartRenderer?.restoreView(data); if (changedView || changedAxes) { setTimeout(func, 100); @@ -88,6 +95,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { anchor.presDataVizView = this.dataVizView; anchor.presDataVizAxes = this.axes.length ? new List(this.axes) : undefined; + Object.keys(this.layoutDoc).map(key => { + if (key.startsWith('histogram-title') || key.startsWith('histogramBarColors') || key.startsWith('defaultHistogramColor') + || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ + anchor[key] = this.layoutDoc[key]; + } + }) this.addDocument(anchor); return anchor; diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 740ee6e3a..c6e3b4cd1 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -443,6 +443,7 @@ export class Histogram extends React.Component { }; render() { + this.componentDidMount(); var curSelectedBarName; var titleAccessor: any=''; if (this.props.axes.length==2) titleAccessor = StrCast(this.props.layoutDoc['histogram-title-'+this.props.axes[0]+'-'+this.props.axes[1]]); -- cgit v1.2.3-70-g09d2 From 49fa6721e2a7af21db5da339cd3c7d90d3e8bf8b Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 2 Aug 2023 12:37:52 -0400 Subject: linking title bug fix --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 5 ++-- .../nodes/DataVizBox/components/Histogram.tsx | 11 ++++--- .../nodes/DataVizBox/components/LineChart.tsx | 12 ++++---- .../views/nodes/DataVizBox/components/PieChart.tsx | 35 +++++++++++++++++----- 4 files changed, 40 insertions(+), 23 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index f167346de..9a4de3c36 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -70,8 +70,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._data_vizAxes = new List(StrListCast(data.presDataVizAxes))); Object.keys(this.layoutDoc).map(key => { if (key.startsWith('histogram-title') || key.startsWith('histogramBarColors') || key.startsWith('defaultHistogramColor') - || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ - console.log(key) + || key.startsWith('lineChart-title') || key.startsWith('pieChart-title') || key.startsWith('pieSliceColors')){ this.layoutDoc['_'+key] = data[key]; } }) @@ -97,7 +96,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { anchor.presDataVizAxes = this.axes.length ? new List(this.axes) : undefined; Object.keys(this.layoutDoc).map(key => { if (key.startsWith('histogram-title') || key.startsWith('histogramBarColors') || key.startsWith('defaultHistogramColor') - || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ + || key.startsWith('lineChart-title') || key.startsWith('pieChart-title') || key.startsWith('pieSliceColors')){ anchor[key] = this.layoutDoc[key]; } }) diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index c6e3b4cd1..e5e3ccd53 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -446,9 +446,9 @@ export class Histogram extends React.Component { this.componentDidMount(); var curSelectedBarName; var titleAccessor: any=''; - if (this.props.axes.length==2) titleAccessor = StrCast(this.props.layoutDoc['histogram-title-'+this.props.axes[0]+'-'+this.props.axes[1]]); - else if (this.props.axes.length>0) titleAccessor = StrCast(this.props.layoutDoc['histogram-title-'+this.props.axes[0]]); - const title = titleAccessor? titleAccessor : this.defaultGraphTitle; + if (this.props.axes.length==2) titleAccessor = 'histogram-title-'+this.props.axes[0]+'-'+this.props.axes[1]; + else if (this.props.axes.length>0) titleAccessor = 'histogram-title-'+this.props.axes[0]; + if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; var selected: string; if (this._currSelected){ curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ {
{this.props.axes.length>1? this.props.layoutDoc['histogram-title-'+this.props.axes[0]+"-"+this.props.axes[1]] = val as string - : this.props.layoutDoc['histogram-title-'+this.props.axes[0]] = val as string})} + val={StrCast(this.props.layoutDoc[titleAccessor])} + setVal={action(val => this.props.layoutDoc[titleAccessor] = val as string)} color={"black"} size={Size.LARGE} fillWidth diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 0142e96ad..0e699bb99 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -350,19 +350,19 @@ export class LineChart extends React.Component { } render() { + this.componentDidMount(); var titleAccessor:any = ''; - if (this.props.axes.length==2) titleAccessor = StrCast(this.props.layoutDoc['lineChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]]); - else if (this.props.axes.length>0) titleAccessor = StrCast(this.props.layoutDoc['lineChart-title-'+this.props.axes[0]]); - const title = titleAccessor? titleAccessor : this.defaultGraphTitle; + if (this.props.axes.length==2) titleAccessor = 'lineChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]; + else if (this.props.axes.length>0) titleAccessor = 'lineChart-title-'+this.props.axes[0]; + if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return ( this.props.axes.length >= 2 ? (
{this.props.axes.length>1? this.props.layoutDoc['lineChart-title-'+this.props.axes[0]+"-"+this.props.axes[1]] = val as string - : this.props.layoutDoc['lineChart-title-'+this.props.axes[0]] = val as string})} + val={StrCast(this.props.layoutDoc[titleAccessor])} + setVal={action(val => this.props.layoutDoc[titleAccessor] = val as string)} color={"black"} size={Size.LARGE} fillWidth diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index f0c27866d..98c79f95a 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -348,10 +348,27 @@ export class PieChart extends React.Component { .enter() .append("g") arcs.append("path") - .attr("fill", (data, i)=>{ return this.props.layoutDoc['pieSliceColors-'+data.data.valueOf()]? StrCast(this.props.layoutDoc['pieSliceColors-'+data.data.valueOf()]) : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) + .attr("fill", (d, i)=>{ + var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number; }; }) => { + try { + return each[percentField].replace(/[^0-9]/g,"")==d.data.toString().replace(/[^0-9]/g,"") + } catch (error) { + return each[percentField]==d.data + }}) + var dataPoint; + if (possibleDataPoints.length==1) dataPoint = possibleDataPoints[0]; + else{ + dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]] + trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; + } + var accessByName = descriptionField? dataPoint[descriptionField] : dataPoint[percentField]; + return this.props.layoutDoc['pieSliceColors-'+accessByName]? StrCast(this.props.layoutDoc['pieSliceColors-'+accessByName]) : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) .attr("class", 'slice') .attr("d", arc) .on('click', onPointClick) + + trackDuplicates = {}; + data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) arcs.append("text") .attr("transform",function(d){ var centroid = arc.centroid(d as unknown as d3.DefaultArcObject) @@ -383,12 +400,15 @@ export class PieChart extends React.Component { }; render() { + this.componentDidMount(); var titleAccessor: any=''; - if (this.props.axes.length==2) titleAccessor = StrCast(this.props.layoutDoc['pieChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]]); - else if (this.props.axes.length>0) titleAccessor = StrCast(this.props.layoutDoc['pieChart-title-'+this.props.axes[0]]); - const title = titleAccessor? titleAccessor : this.defaultGraphTitle; + if (this.props.axes.length==2) titleAccessor = 'pieChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]; + else if (this.props.axes.length>0) titleAccessor = 'pieChart-title-'+this.props.axes[0]; + if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; var selected: string; + var curSelectedSliceName; if (this._currSelected){ + curSelectedSliceName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { key!=''? selected += key + ': ' + this._currSelected[key] + ', ': ''; @@ -402,9 +422,8 @@ export class PieChart extends React.Component {
{this.props.axes.length>1? this.props.layoutDoc['pieChart-title-'+this.props.axes[0]+"-"+this.props.axes[1]] = val as string - : this.props.layoutDoc['pieChart-title-'+this.props.axes[0]] = val as string})} + val={StrCast(this.props.layoutDoc[titleAccessor])} + setVal={action(val => this.props.layoutDoc[titleAccessor] = val as string)} color={"black"} size={Size.LARGE} fillWidth @@ -418,7 +437,7 @@ export class PieChart extends React.Component { tooltip={'Change Slice Color'} type={Type.SEC} icon={} - selectedColor={this.curSliceSelected.attr("fill")} + selectedColor={this.props.layoutDoc['pieSliceColors-'+curSelectedSliceName]? this.props.layoutDoc['pieSliceColors-'+curSelectedSliceName] : this.curSliceSelected.attr("fill")} setSelectedColor={color => this.changeSelectedColor(color)} size={Size.XSMALL} /> -- cgit v1.2.3-70-g09d2 From 23e0ee2dcad8df2bc3467647e05c433f27787d54 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 3 Aug 2023 14:38:47 -0400 Subject: table fixes (selected by guid not extra row + ui changes) --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 17 +- .../views/nodes/DataVizBox/components/Chart.scss | 1 + .../nodes/DataVizBox/components/Histogram.tsx | 56 +--- .../nodes/DataVizBox/components/LineChart.tsx | 4 +- .../views/nodes/DataVizBox/components/PieChart.tsx | 47 +--- .../views/nodes/DataVizBox/components/TableBox.tsx | 310 +++++++-------------- 6 files changed, 135 insertions(+), 300 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 9a4de3c36..80586d7c7 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,9 +1,9 @@ -import { action, computed, observable, ObservableMap, ObservableSet } from 'mobx'; +import { action, computed, ObservableMap, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; -import { Cast, CsvCast, NumCast, StrCast } from '../../../../fields/Types'; +import { Cast, CsvCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; @@ -15,6 +15,7 @@ import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; import { PieChart } from './components/PieChart'; import { Toggle, ToggleType, Type } from 'browndash-components'; +import { Utils } from '../../../../Utils'; export enum DataVizView { TABLE = 'table', @@ -35,9 +36,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // @observable private pairs: { [key: string]: FieldResult }[] = []; static pairSet = new ObservableMap(); @computed.struct get pairs() { - return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); + var pairs = DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); + pairs?.map(pair => {if (!pair.guid) pair.guid = Utils.GenerateGuid()}) + return pairs; } - private _chartRenderer: LineChart | undefined; + private _chartRenderer: LineChart | Histogram | PieChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc // method1() { @@ -116,10 +119,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { const margin = { top: 10, right: 25, bottom: 50, left: 25 }; if (!this.pairs) return 'no data'; switch (this.dataVizView) { - case DataVizView.TABLE: return ; + case DataVizView.TABLE: return ; case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; - case DataVizView.HISTOGRAM: return ; - case DataVizView.PIECHART: return ; + case DataVizView.HISTOGRAM: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; + case DataVizView.PIECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; } } @computed get dataUrl() { diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 6c87241b8..996183cb8 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -77,4 +77,5 @@ } .table-container{ overflow: scroll; + margin: 10px; } \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 89dcf87db..efe17297b 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -1,15 +1,13 @@ import { observer } from "mobx-react"; -import { Doc, DocListCast, FieldResult } from "../../../../../fields/Doc"; +import { Doc, DocListCast, StrListCast } from "../../../../../fields/Doc"; import * as React from 'react'; import * as d3 from 'd3'; import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; import { LinkManager } from "../../../../util/LinkManager"; -import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; -import { DataPoint, SelectedDataPoint } from "./LineChart"; +import { DocCast, StrCast} from "../../../../../fields/Types"; import { DocumentManager } from "../../../../util/DocumentManager"; import { Id } from "../../../../../fields/FieldSymbols"; import { DataVizBox } from "../DataVizBox"; -import { listSpec } from "../../../../../fields/Schema"; import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; @@ -54,7 +52,7 @@ export class Histogram extends React.Component { var ax0 = this.props.axes[0]; if (/\d/.test(this.props.pairs[0][ax0])){ this.numericalXData = true } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) }; @@ -63,13 +61,8 @@ export class Histogram extends React.Component { if (/\d/.test(this.props.pairs[0][ax0])) { this.numericalXData = true;} if (/\d/.test(this.props.pairs[0][ax1]) && this.props.pairs.length < this.maxBins) { this.numericalYData = true;} return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) - // .sort((a, b) => (a[ax0] < b[ax0] ? -1 : 1)); - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) - .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) - .sort((a, b) => (a.x < b.x ? -1 : 1)); } @computed get defaultGraphTitle(){ var ax0 = this.props.axes[0]; @@ -181,27 +174,16 @@ export class Histogram extends React.Component { } @action - restoreView = (data: Doc) => { - const coords = Cast(data.presDataVizSelection, listSpec('number'), null); - if (coords?.length > 1 && (this._currSelected?.x !== coords[0] || this._currSelected?.y !== coords[1])) { - this.setCurrSelected(coords[0], coords[1]); - return true; - } - if (this._currSelected) { - this.setCurrSelected(); - return true; - } - return false; - }; + restoreView = (data: Doc) => {}; // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ // - title: 'histogram doc selection' + this._currSelected?.x, + title: 'histogram doc selection' + this._currSelected, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); - anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected.x, this._currSelected.y]) : undefined; + anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; return anchor; }; @@ -213,28 +195,6 @@ export class Histogram extends React.Component { return this.props.width - this.props.margin.left - this.props.margin.right; } - setupTooltip() { - return d3 - .select(this._histogramRef.current) - .append('div') - .attr('class', 'tooltip') - .style('opacity', 0) - .style('background', '#fff') - .style('border', '1px solid #ccc') - .style('padding', '5px') - .style('position', 'absolute') - .style('font-size', '12px'); - } - - // TODO: nda - use this everyewhere we update currSelected? - @action - setCurrSelected(x?: number, y?: number) { - // TODO: nda - get rid of svg element in the list? - this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined; - this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true)); - this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined)); - } - data = (dataSet: any) => { var validData = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; @@ -364,7 +324,7 @@ export class Histogram extends React.Component { const selected = svg.selectAll('.histogram-bar').filter((d: any) => { barCounter++; if ((barCounter*eachRectWidth ) <= pointerX && pointerX <= ((barCounter+1)*eachRectWidth)){ - var showSelected = this.numericalYData? this.props.pairs.filter((data: { [x: string]: any; }) => data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { @computed get _lineChartData() { if (this.props.axes.length <= 1) return []; return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) .sort((a, b) => (a.x < b.x ? -1 : 1)); } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index d01d4429f..f3a72a53b 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -1,15 +1,13 @@ import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../../../fields/Doc"; +import { Doc, DocListCast, StrListCast } from "../../../../../fields/Doc"; import * as React from 'react'; import * as d3 from 'd3'; import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; import { LinkManager } from "../../../../util/LinkManager"; -import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; -import { DataPoint, SelectedDataPoint } from "./LineChart"; +import { DocCast, StrCast} from "../../../../../fields/Types"; import { DocumentManager } from "../../../../util/DocumentManager"; import { Id } from "../../../../../fields/FieldSymbols"; import { DataVizBox } from "../DataVizBox"; -import { listSpec } from "../../../../../fields/Schema"; import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; @@ -52,7 +50,7 @@ export class PieChart extends React.Component { var ax0 = this.props.axes[0]; if (/\d/.test(this.props.pairs[0][ax0])){ this.byCategory = false } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) }; @@ -60,13 +58,8 @@ export class PieChart extends React.Component { var ax1 = this.props.axes[1]; if (/\d/.test(this.props.pairs[0][ax0])) { this.byCategory = false; } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) - // .sort((a, b) => (a[ax0] < b[ax0] ? -1 : 1)); - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) - .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) - .sort((a, b) => (a.x < b.x ? -1 : 1)); } @computed get defaultGraphTitle(){ var ax0 = this.props.axes[0]; @@ -178,27 +171,16 @@ export class PieChart extends React.Component { } @action - restoreView = (data: Doc) => { - const coords = Cast(data.presDataVizSelection, listSpec('number'), null); - if (coords?.length > 1 && (this._currSelected?.x !== coords[0] || this._currSelected?.y !== coords[1])) { - this.setCurrSelected(coords[0], coords[1]); - return true; - } - if (this._currSelected) { - this.setCurrSelected(); - return true; - } - return false; - }; + restoreView = (data: Doc) => {}; // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ // - title: 'piechart doc selection' + this._currSelected?.x, + title: 'piechart doc selection' + this._currSelected, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); - anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected.x, this._currSelected.y]) : undefined; + anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; return anchor; }; @@ -210,19 +192,6 @@ export class PieChart extends React.Component { return this.props.width - this.props.margin.left - this.props.margin.right; } - setupTooltip() { - return d3 - .select(this._piechartRef.current) - .append('div') - .attr('class', 'tooltip') - .style('opacity', 0) - .style('background', '#fff') - .style('border', '1px solid #ccc') - .style('padding', '5px') - .style('position', 'absolute') - .style('font-size', '12px'); - } - // TODO: nda - use this everyewhere we update currSelected? @action setCurrSelected(x?: number, y?: number) { @@ -322,7 +291,7 @@ export class PieChart extends React.Component { if (Math.min(p4[1], p1[1])<=pointer[1] && pointer[1]<=Math.max(p4[1], p1[1])){ if (pointer[0] <= (pointer[1]-p4[1])*(p1[0]-p4[0])/(p1[1]-p4[1])+p4[0]) lineCrossCount++; } if (lineCrossCount % 2 != 0) { - var showSelected = this.byCategory? pieDataSet[index] : this.props.pairs[index]; + var showSelected = this.byCategory? pieDataSet[index] : this._piechartData[index]; sameAsCurrent = (this.byCategory && this._currSelected)? (showSelected[Object.keys(showSelected)[0]]==this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]]==this._currSelected![Object.keys(showSelected)[1]]) diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 64c6dc940..5653adbce 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,21 +1,20 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc } from '../../../../../fields/Doc'; -import { Id } from '../../../../../fields/FieldSymbols'; +import { Doc, StrListCast } from '../../../../../fields/Doc'; import { List } from '../../../../../fields/List'; -import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../../../Utils'; +import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils'; import { DragManager } from '../../../../util/DragManager'; import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; import { LinkManager } from '../../../../util/LinkManager'; import { Cast, DocCast } from '../../../../../fields/Types'; -import { EditableText, Size, Type } from 'browndash-components'; import './Chart.scss'; import { listSpec } from '../../../../../fields/Schema'; interface TableBoxProps { rootDoc: Doc; + layoutDoc: Doc; pairs: { [key: string]: any }[]; selectAxes: (axes: string[]) => void; axes: string[]; @@ -32,13 +31,10 @@ interface TableBoxProps { @observer export class TableBox extends React.Component { - @observable editableHeaders = this.columns; - @observable editableCells = this._tableData; @computed get _tableData() { if (this.incomingLinks.length! <= 0) return this.props.pairs; - /// StrListCast(this.incomingLinks[0].anchor_1.selected) ==> list of guids that the parent has selected - return this.props.pairs?.filter(pair => (Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + return this.props.pairs?.filter(pair => this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid)) } @computed get incomingLinks() { @@ -49,204 +45,110 @@ export class TableBox extends React.Component { } @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])) : []; + return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='guid' && header!='') : []; } - // render() { - // return ( - //
- // - // - // - // {this.editableHeaders - // .filter(col => !col.startsWith('select')) - // .map(col => { - // const header = React.createRef(); - // const displayColName = col; - // return ( - // - // ); - // })} - // - // - // - // {this._tableData?.map((p, i) => { - // return ( - // (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}> - // {this.editableHeaders.map(col => ( - // - // ))} - // - // ); - // })} - // - //
{ - // const downX = e.clientX; - // const downY = e.clientY; - // setupMoveUpEvents( - // {}, - // e, - // e => { - // 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([col, col]); - // embedding._draggedFrom = this.props.docView?.()!.rootDoc!; - // embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; - // 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.linkDocument.link_matchEmbeddings = 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[0] = col; - // } - // this.props.selectAxes(newAxes); - // }) - // ); - // }}> - // { - // this.editableHeaders[this.editableHeaders.indexOf(col)] = val as string - // this.props.pairs.map(pair => { - // pair[val as string] = pair[col]; - // delete pair[col] - // }) - // })} - // color={"black"} - // size={Size.LARGE} - // /> - //
- // {p[col]} - //
- //
- // ); - // } - - render() { - return ( -
- - - - {this.columns - .filter(col => !col.startsWith('select')) - .map(col => { - const header = React.createRef(); - return ( - - ); - })} - - - - {this._tableData?.map((p, i) => { - return ( - { - // if (!this.props.docView?.()!.layoutDoc.selected) - // this.props.docView!.()!.layoutDoc.selected = new List(); - // const selected = Cast(this.props.docView?.()!.layoutDoc.selected, listSpec("string"), null); - // // StrListCast(this.props.docView?.()!.layoutDoc.selected) - // selected.push(p.guid); - (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]) - })}> - {this.columns.map(col => ( - - ))} - - ); - })} - -
{ - const downX = e.clientX; - const downY = e.clientY; - setupMoveUpEvents( - {}, - e, - e => { - 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([col, col]); - embedding._draggedFrom = this.props.docView?.()!.rootDoc!; - embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; - 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.linkDocument.link_matchEmbeddings = 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[0] = col; - } - this.props.selectAxes(newAxes); - }) - ); - }}> - {col} -
- {p[col]} -
+ if (this._tableData.length>0){ + return ( +
+ + + + {this.columns + .filter(col => !col.startsWith('select')) + .map(col => { + const header = React.createRef(); + return ( + + ); + })} + + + + {this._tableData?.map((p, i) => { + return ( + { + if (!this.props.layoutDoc.selected) this.props.layoutDoc.selected = new List(); + const selected = Cast(this.props.layoutDoc.selected, listSpec("string"), null); + if (selected.includes(p.guid)) selected.splice(selected.indexOf(p.guid), 1); + else { + selected.push(p.guid)}; + })} style={ + { fontWeight: StrListCast(this.props.layoutDoc.selected).includes(p.guid) ? 'bold' : '' , width: '110%', + background: StrListCast(this.props.layoutDoc.selected).includes(p.guid) ? 'lightgrey' : '' }}> + {this.columns.map(col => ( + (this.props.layoutDoc.selected)? + + : + ))} + + ); + })} + +
{ + const downX = e.clientX; + const downY = e.clientY; + setupMoveUpEvents( + {}, + e, + e => { + const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!; + const targetCreator = (annotationOn: Doc | undefined) => { + const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!); + embedding._dataVizView = DataVizView.TABLE; + embedding._data_vizAxes = new List([col, col]); + embedding._draggedFrom = this.props.docView?.()!.rootDoc!; + embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; + 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.linkDocument.link_matchEmbeddings = 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[0] = col; + } + this.props.selectAxes(newAxes); + }) + ); + }}> + {col} +
+ {p[col]} + {p[col]}
+
+ ); + } + else return ( +
+ Selected rows of data from the incoming DataVizBox to display.
- ); + ) } - - - } -- cgit v1.2.3-70-g09d2 From acbfb6e690b98388d1f7d9a8544c89b2740adea4 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 3 Aug 2023 17:51:48 -0400 Subject: ability to revert histogram bar colors --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 19 ++++++---- .../nodes/DataVizBox/components/Histogram.tsx | 43 +++++++++++++++++----- 2 files changed, 44 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 80586d7c7..e08c55197 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -71,12 +71,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { restoreView = (data: Doc) => { const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._data_vizAxes = new List(StrListCast(data.presDataVizAxes))); + // this.layoutDoc.selected = data.selected; + // this.layoutDoc.histogramBarColors = data.histogramBarColors; + this.layoutDoc.defaultHistogramColor = data.defaultHistogramColor; + this.layoutDoc.pieSliceColors = data.pieSliceColors; Object.keys(this.layoutDoc).map(key => { - if (key.startsWith('histogram-title') || key.startsWith('histogramBarColors') || key.startsWith('defaultHistogramColor') - || key.startsWith('lineChart-title') || key.startsWith('pieChart-title') || key.startsWith('pieSliceColors')){ - this.layoutDoc['_'+key] = data[key]; - } + if (key.startsWith('histogram-title') || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ this.layoutDoc['_'+key] = data[key]; } }) + const func = () => this._chartRenderer?.restoreView(data); if (changedView || changedAxes) { setTimeout(func, 100); @@ -97,11 +99,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { anchor.presDataVizView = this.dataVizView; anchor.presDataVizAxes = this.axes.length ? new List(this.axes) : undefined; + // anchor.selected = this.layoutDoc.selected; + // anchor.histogramBarColors = this.layoutDoc.histogramBarColors; + anchor.defaultHistogramColor = this.layoutDoc.defaultHistogramColor; + anchor.pieSliceColors = this.layoutDoc.pieSliceColors; Object.keys(this.layoutDoc).map(key => { - if (key.startsWith('histogram-title') || key.startsWith('histogramBarColors') || key.startsWith('defaultHistogramColor') - || key.startsWith('lineChart-title') || key.startsWith('pieChart-title') || key.startsWith('pieSliceColors')){ - anchor[key] = this.layoutDoc[key]; - } + if (key.startsWith('histogram-title') || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ anchor[key] = this.layoutDoc[key]; } }) this.addDocument(anchor); diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 1077df844..b574a0062 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import * as d3 from 'd3'; import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; import { LinkManager } from "../../../../util/LinkManager"; -import { DocCast, StrCast} from "../../../../../fields/Types"; +import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; import { DocumentManager } from "../../../../util/DocumentManager"; import { Id } from "../../../../../fields/FieldSymbols"; import { DataVizBox } from "../DataVizBox"; @@ -12,8 +12,10 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; -import { ColorPicker, EditableText, Size, Type } from "browndash-components"; +import { ColorPicker, EditableText, IconButton, Size, Type } from "browndash-components"; import { FaFillDrip } from "react-icons/fa"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { listSpec } from "../../../../../fields/Schema"; export interface HistogramProps { rootDoc: Doc; @@ -183,7 +185,7 @@ export class Histogram extends React.Component { title: 'histogram doc selection' + this._currSelected, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); - anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; + // anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; return anchor; }; @@ -303,7 +305,7 @@ export class Histogram extends React.Component { xAxis = d3.axisBottom(x) .ticks(numBins-1) } - const maxFrequency = this.numericalYData? d3.max(histDataSet, function(d) { return d[yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { function(d) { return (selected && selected[0]==d[0])? 'histogram-bar hover' : 'histogram-bar'; }: function(d) {return 'histogram-bar'}) - .attr("fill", (d)=>{ return this.props.layoutDoc['histogramBarColors-'+d[0]]? StrCast(this.props.layoutDoc['histogramBarColors-'+d[0]]) : StrCast(this.props.layoutDoc['defaultHistogramColor'])}) + .attr("fill", (d)=>{ + var barColor; + var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); + barColors.map(each => {if (each[0]==StrCast(d[0])) barColor = each[1]}); + return barColor? StrCast(barColor) : StrCast(this.props.layoutDoc.defaultHistogramColor)}) }; @action changeSelectedColor = (color: string) => { this.curBarSelected.attr("fill", color); var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each)) }); + barColors.push(StrCast(barName + '::' + color)); }; @action changeDefaultColor = (color: string) => { @@ -397,7 +407,7 @@ export class Histogram extends React.Component { else return true; }) defaultColorBars.attr("fill", color); - this.props.layoutDoc['defaultHistogramColor'] = color; + this.props.layoutDoc.defaultHistogramColor = color; }; render() { @@ -406,7 +416,8 @@ export class Histogram extends React.Component { if (this.props.axes.length==2) titleAccessor = 'histogram-title-'+this.props.axes[0]+'-'+this.props.axes[1]; else if (this.props.axes.length>0) titleAccessor = 'histogram-title-'+this.props.axes[0]; if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; - if (!this.props.layoutDoc['defaultHistogramColor']) this.props.layoutDoc['defaultHistogramColor'] = '#69b3a2'; + if (!this.props.layoutDoc.defaultHistogramColor) this.props.layoutDoc.defaultHistogramColor = '#69b3a2'; + if (!this.props.layoutDoc.histogramBarColors) this.props.layoutDoc.histogramBarColors = new List(); var selected: string; if (this._currSelected){ curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { selected += ' }'; } else selected = 'none'; + var selectedBarColor; + var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); + barColors.map(each => {if (each[0]==curSelectedBarName!) selectedBarColor = each[1]}); this.componentDidMount(); return ( @@ -436,10 +450,19 @@ export class Histogram extends React.Component { tooltip={'Change Default Bar Color'} type={Type.SEC} icon={} - selectedColor={StrCast(this.props.layoutDoc['defaultHistogramColor'])} + selectedColor={StrCast(this.props.layoutDoc.defaultHistogramColor)} setSelectedColor={color => this.changeDefaultColor(color)} size={Size.XSMALL} /> +   + } + size={Size.XSMALL} + color={'black'} + type={Type.SEC} + tooltip={'Revert all bars to the default color'} + onClick={action(() => {this.props.layoutDoc.histogramBarColors = new List()})} + />
{selected != 'none' ?
@@ -449,7 +472,7 @@ export class Histogram extends React.Component { tooltip={'Change Slice Color'} type={Type.SEC} icon={} - selectedColor={this.props.layoutDoc['histogramBarColors-'+curSelectedBarName]? this.props.layoutDoc['histogramBarColors-'+curSelectedBarName] : this.curBarSelected.attr("fill")} + selectedColor={selectedBarColor? selectedBarColor : this.curBarSelected.attr("fill")} setSelectedColor={color => this.changeSelectedColor(color)} size={Size.XSMALL} /> -- cgit v1.2.3-70-g09d2 From f073fd90481d58f2eaccc1d546899dd157aff905 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 4 Aug 2023 11:25:36 -0400 Subject: selected rows + colored bars + colored slices updating with getAnchor / restoreView --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 14 +++++++------- .../nodes/DataVizBox/components/Histogram.tsx | 14 ++------------ .../views/nodes/DataVizBox/components/PieChart.tsx | 22 +++++++++++++++++----- 3 files changed, 26 insertions(+), 24 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index e08c55197..a92fc1eb9 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,7 +1,7 @@ import { action, computed, ObservableMap, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, Field, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { Cast, CsvCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; @@ -71,10 +71,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { restoreView = (data: Doc) => { const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._data_vizAxes = new List(StrListCast(data.presDataVizAxes))); - // this.layoutDoc.selected = data.selected; - // this.layoutDoc.histogramBarColors = data.histogramBarColors; + this.layoutDoc.selected = Field.Copy(data.selected); + this.layoutDoc.histogramBarColors = Field.Copy(data.histogramBarColors); this.layoutDoc.defaultHistogramColor = data.defaultHistogramColor; - this.layoutDoc.pieSliceColors = data.pieSliceColors; + this.layoutDoc.pieSliceColors = Field.Copy(data.pieSliceColors); Object.keys(this.layoutDoc).map(key => { if (key.startsWith('histogram-title') || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ this.layoutDoc['_'+key] = data[key]; } }) @@ -99,10 +99,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { anchor.presDataVizView = this.dataVizView; anchor.presDataVizAxes = this.axes.length ? new List(this.axes) : undefined; - // anchor.selected = this.layoutDoc.selected; - // anchor.histogramBarColors = this.layoutDoc.histogramBarColors; + anchor.selected = Field.Copy(this.layoutDoc.selected); + anchor.histogramBarColors = Field.Copy(this.layoutDoc.histogramBarColors); anchor.defaultHistogramColor = this.layoutDoc.defaultHistogramColor; - anchor.pieSliceColors = this.layoutDoc.pieSliceColors; + anchor.pieSliceColors = Field.Copy(this.layoutDoc.pieSliceColors); Object.keys(this.layoutDoc).map(key => { if (key.startsWith('histogram-title') || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ anchor[key] = this.layoutDoc[key]; } }) diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index b574a0062..02f1ddbbb 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -394,21 +394,11 @@ export class Histogram extends React.Component { @action changeSelectedColor = (color: string) => { this.curBarSelected.attr("fill", color); var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each)) }); + barColors.map(each => { if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1) }); barColors.push(StrCast(barName + '::' + color)); }; - - @action changeDefaultColor = (color: string) => { - const defaultColorBars = this._histogramSvg!.selectAll('.histogram-bar').filter((d: any) => { - if (this.props.layoutDoc['histogramBarColors-'+d[0]]) return false; - else return true; - }) - defaultColorBars.attr("fill", color); - this.props.layoutDoc.defaultHistogramColor = color; - }; render() { var curSelectedBarName; @@ -451,7 +441,7 @@ export class Histogram extends React.Component { type={Type.SEC} icon={} selectedColor={StrCast(this.props.layoutDoc.defaultHistogramColor)} - setSelectedColor={color => this.changeDefaultColor(color)} + setSelectedColor={color => this.props.layoutDoc.defaultHistogramColor = color} size={Size.XSMALL} />   diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index f3a72a53b..8fdead3d7 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import * as d3 from 'd3'; import { IReactionDisposer, action, computed, observable, reaction } from "mobx"; import { LinkManager } from "../../../../util/LinkManager"; -import { DocCast, StrCast} from "../../../../../fields/Types"; +import { Cast, DocCast, StrCast} from "../../../../../fields/Types"; import { DocumentManager } from "../../../../util/DocumentManager"; import { Id } from "../../../../../fields/FieldSymbols"; import { DataVizBox } from "../DataVizBox"; @@ -14,6 +14,7 @@ import { List } from "../../../../../fields/List"; import './Chart.scss'; import { ColorPicker, EditableText, Size, Type } from "browndash-components"; import { FaFillDrip } from "react-icons/fa"; +import { listSpec } from "../../../../../fields/Schema"; export interface PieChartProps { rootDoc: Doc; @@ -180,7 +181,7 @@ export class PieChart extends React.Component { title: 'piechart doc selection' + this._currSelected, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); - anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; + // anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; return anchor; }; @@ -327,7 +328,10 @@ export class PieChart extends React.Component { trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } var accessByName = descriptionField? dataPoint[descriptionField] : dataPoint[percentField]; - return this.props.layoutDoc['pieSliceColors-'+accessByName]? StrCast(this.props.layoutDoc['pieSliceColors-'+accessByName]) : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) + var sliceColor; + var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); + sliceColors.map(each => {if (each[0]==StrCast(accessByName)) sliceColor = each[1]}); + return sliceColor? StrCast(sliceColor) : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) .attr("class", selected? function(d) { return (selected && d.startAngle==selected.startAngle && d.endAngle==selected.endAngle)? 'slice hover' : 'slice'; @@ -364,7 +368,10 @@ export class PieChart extends React.Component { @action changeSelectedColor = (color: string) => { this.curSliceSelected.attr("fill", color); var sliceName = this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1) }); + sliceColors.push(StrCast(sliceName + '::' + color)); }; render() { @@ -373,6 +380,7 @@ export class PieChart extends React.Component { if (this.props.axes.length==2) titleAccessor = 'pieChart-title-'+this.props.axes[0]+'-'+this.props.axes[1]; else if (this.props.axes.length>0) titleAccessor = 'pieChart-title-'+this.props.axes[0]; if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; + if (!this.props.layoutDoc.pieSliceColors) this.props.layoutDoc.pieSliceColors = new List(); var selected: string; var curSelectedSliceName; if (this._currSelected){ @@ -385,6 +393,10 @@ export class PieChart extends React.Component { selected += ' }'; } else selected = 'none'; + var selectedSliceColor; + var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); + sliceColors.map(each => {if (each[0]==curSelectedSliceName!) selectedSliceColor = each[1]}); + return ( this.props.axes.length >= 1 ? (
@@ -405,7 +417,7 @@ export class PieChart extends React.Component { tooltip={'Change Slice Color'} type={Type.SEC} icon={} - selectedColor={this.props.layoutDoc['pieSliceColors-'+curSelectedSliceName]? this.props.layoutDoc['pieSliceColors-'+curSelectedSliceName] : this.curSliceSelected.attr("fill")} + selectedColor={selectedSliceColor? selectedSliceColor : this.curSliceSelected.attr("fill")} setSelectedColor={color => this.changeSelectedColor(color)} size={Size.XSMALL} /> -- cgit v1.2.3-70-g09d2 From bee66361d878c366e8c753ca844abc2f78fbf7f3 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 4 Aug 2023 14:52:35 -0400 Subject: better row guids so selected rows stay on refresh --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 5 +---- .../views/nodes/DataVizBox/components/Histogram.tsx | 5 +++-- .../views/nodes/DataVizBox/components/LineChart.tsx | 3 ++- .../views/nodes/DataVizBox/components/PieChart.tsx | 5 +++-- .../views/nodes/DataVizBox/components/TableBox.tsx | 17 +++++++++++------ 5 files changed, 20 insertions(+), 15 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index a92fc1eb9..8b951a002 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -15,7 +15,6 @@ import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; import { PieChart } from './components/PieChart'; import { Toggle, ToggleType, Type } from 'browndash-components'; -import { Utils } from '../../../../Utils'; export enum DataVizView { TABLE = 'table', @@ -36,9 +35,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // @observable private pairs: { [key: string]: FieldResult }[] = []; static pairSet = new ObservableMap(); @computed.struct get pairs() { - var pairs = DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); - pairs?.map(pair => {if (!pair.guid) pair.guid = Utils.GenerateGuid()}) - return pairs; + return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); } private _chartRenderer: LineChart | Histogram | PieChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 02f1ddbbb..a9be151bc 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -49,12 +49,13 @@ export class Histogram extends React.Component { // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _histogramData() { + var guids = StrListCast(this.props.layoutDoc.rowGuids); if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; if (/\d/.test(this.props.pairs[0][ax0])){ this.numericalXData = true } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) }; @@ -63,7 +64,7 @@ export class Histogram extends React.Component { if (/\d/.test(this.props.pairs[0][ax0])) { this.numericalXData = true;} if (/\d/.test(this.props.pairs[0][ax1]) && this.props.pairs.length < this.maxBins) { this.numericalYData = true;} return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) } @computed get defaultGraphTitle(){ diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index da5f7dbbb..77b3acf47 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -49,9 +49,10 @@ export class LineChart extends React.Component { // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _lineChartData() { + var guids = StrListCast(this.props.layoutDoc.rowGuids); if (this.props.axes.length <= 1) return []; return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) .sort((a, b) => (a.x < b.x ? -1 : 1)); } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 8fdead3d7..cc5cc231b 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -46,12 +46,13 @@ export class PieChart extends React.Component { // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _piechartData() { + var guids = StrListCast(this.props.layoutDoc.rowGuids); if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; if (/\d/.test(this.props.pairs[0][ax0])){ this.byCategory = false } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) }; @@ -59,7 +60,7 @@ export class PieChart extends React.Component { var ax1 = this.props.axes[1]; if (/\d/.test(this.props.pairs[0][ax0])) { this.byCategory = false; } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) } @computed get defaultGraphTitle(){ diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index f244502a4..7d6f934b9 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -34,7 +34,8 @@ export class TableBox extends React.Component { @computed get _tableData() { if (this.incomingLinks.length! <= 0) return this.props.pairs; - return this.props.pairs?.filter(pair => this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(pair.guid)) + var guids = StrListCast(this.props.layoutDoc.rowGuids); + return this.props.pairs?.filter(pair => this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)])) } @computed get incomingLinks() { @@ -45,7 +46,10 @@ export class TableBox extends React.Component { } @computed get columns() { - return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='guid' && header!='') : []; + if (!this.props.layoutDoc.rowGuids) this.props.layoutDoc.rowGuids = new List(); + const guids = Cast(this.props.layoutDoc.rowGuids, listSpec("string"), null); + if (guids.length==0) this.props.pairs.map(row => guids.push(Utils.GenerateGuid())); + return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='') : []; } render() { @@ -121,18 +125,19 @@ export class TableBox extends React.Component {
-- cgit v1.2.3-70-g09d2 From 85f91733a21c7b41829eb9280ce33e90783c926d Mon Sep 17 00:00:00 2001 From: srichman333 Date: Mon, 7 Aug 2023 13:30:40 -0400 Subject: selected data at bottom of graph --- src/client/views/nodes/DataVizBox/DataVizBox.scss | 2 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 2 +- src/client/views/nodes/DataVizBox/components/Chart.scss | 7 +++++-- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 2 +- src/client/views/nodes/DataVizBox/components/LineChart.tsx | 5 +++-- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 2 +- src/client/views/nodes/DataVizBox/components/TableBox.tsx | 4 ++-- 7 files changed, 14 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index ab2f19726..a69881b7c 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -1,5 +1,5 @@ .dataviz { - overflow: hidden; + overflow: scroll; height: 100%; width: 100%; diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 8b951a002..9a4546900 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -116,7 +116,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @computed get selectView() { const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; - const margin = { top: 10, right: 25, bottom: 50, left: 25 }; + const margin = { top: 10, right: 25, bottom: 75, left: 45 }; if (!this.pairs) return 'no data'; switch (this.dataVizView) { case DataVizView.TABLE: return ; diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 996183cb8..35e5187b2 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -4,6 +4,7 @@ align-items: center; cursor: default; margin-top: 10px; + overflow-y: visible; .graph{ overflow: visible; @@ -22,8 +23,8 @@ display: flex; flex-direction: row; margin: 10px; - margin-top: 0px; - margin-bottom: -5px; + margin-top: -25px; + margin-bottom: 5px; } .slice { &.hover { @@ -78,4 +79,6 @@ .table-container{ overflow: scroll; margin: 10px; + margin-left: 25px; + margin-top: 25px; } \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 2a47abf32..cb882cf4a 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -454,6 +454,7 @@ export class Histogram extends React.Component { size={Size.XSMALL} /> +
{selected != 'none' ?
Selected: {selected} @@ -477,7 +478,6 @@ export class Histogram extends React.Component { />
: null} -
) : {'first use table view to select a column to graph'} ); diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 77b3acf47..3a416c401 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -216,7 +216,6 @@ export class LineChart extends React.Component { // TODO: nda - get rid of svg element in the list? this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined; this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true)); - this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined)); } drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear, yScale: d3.ScaleLinear) { @@ -369,8 +368,10 @@ export class LineChart extends React.Component { fillWidth />
-
{`Selected: ${selectedPt}`}
+ {selectedPt!='none'? +
{`Selected: ${selectedPt}`}
+ : null}
) : {'first use table view to select two axes to plot'} ); diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index cc5cc231b..ca93a2942 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -410,6 +410,7 @@ export class PieChart extends React.Component { fillWidth /> +
{selected != 'none' ?
Selected: {selected} @@ -424,7 +425,6 @@ export class PieChart extends React.Component { />
: null} -
) : {'first use table view to select a column to graph'} ); diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index a7cc3f2fb..38dd62d8d 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -49,7 +49,7 @@ export class TableBox extends React.Component { if (!this.props.layoutDoc.rowGuids) this.props.layoutDoc.rowGuids = new List(); const guids = Cast(this.props.layoutDoc.rowGuids, listSpec("string"), null); if (guids.length==0) this.props.pairs.map(row => guids.push(Utils.GenerateGuid())); - return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='') : []; + return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='' && header!=undefined) : []; } filterSelectedRowsDown() { @@ -66,7 +66,7 @@ export class TableBox extends React.Component { this.filterSelectedRowsDown(); if (this._tableData.length>0){ return ( -
+
-- cgit v1.2.3-70-g09d2 From 318d56e1dff94204b100f5636e1a3288724aaffc Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 8 Aug 2023 16:15:36 -0400 Subject: comments + cleanups --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 47 +------ .../nodes/DataVizBox/components/Histogram.tsx | 120 ++++------------ .../views/nodes/DataVizBox/components/PieChart.tsx | 155 +++++---------------- .../views/nodes/DataVizBox/components/TableBox.tsx | 7 +- src/client/views/nodes/DataVizBox/utils/D3Utils.ts | 1 - 5 files changed, 78 insertions(+), 252 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 9a4546900..e71739231 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -26,45 +26,20 @@ export enum DataVizView { @observer export class DataVizBox extends ViewBoxAnnotatableComponent() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(DataVizBox, fieldKey); - } - // says we have an object and any string - // 2 ways of doing it - // @observable private pairs: { [key: string]: number | string | undefined }[] = []; - // @observable private pairs: { [key: string]: FieldResult }[] = []; + // all data static pairSet = new ObservableMap(); @computed.struct get pairs() { return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); } - private _chartRenderer: LineChart | Histogram | PieChart | undefined; - // // another way would be store a schema that defines the type of data we are expecting from an imported doc - - // method1() { - // this.pairs[0].x = 3; - // } - - // method() { - // // this.pairs[0].x = 3; - // // go through the pairs - // const x = this.pairs[0].x; - // if (typeof x == 'number') { - // let x1 = Number(x); - // // let x1 = NumCast(x); - // } - // } - // could use field result - // [key: string]: FieldResult; - // instead of numeric x,y in there, - - // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops + private _chartRenderer: LineChart | Histogram | PieChart | undefined; + // current displayed chart type @computed get dataVizView(): DataVizView { return StrCast(this.layoutDoc._dataVizView, 'table') as DataVizView; } - @action + @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors restoreView = (data: Doc) => { const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._data_vizAxes = new List(StrListCast(data.presDataVizAxes))); @@ -75,7 +50,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { Object.keys(this.layoutDoc).map(key => { if (key.startsWith('histogram-title') || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ this.layoutDoc['_'+key] = data[key]; } }) - const func = () => this._chartRenderer?.restoreView(data); if (changedView || changedAxes) { setTimeout(func, 100); @@ -83,7 +57,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } return func() ?? false; }; - getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { const anchor = !pinProps ? this.rootDoc @@ -93,7 +66,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) /*put in some options*/ }); - anchor.presDataVizView = this.dataVizView; anchor.presDataVizAxes = this.axes.length ? new List(this.axes) : undefined; anchor.selected = Field.Copy(this.layoutDoc.selected); @@ -103,7 +75,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { Object.keys(this.layoutDoc).map(key => { if (key.startsWith('histogram-title') || key.startsWith('lineChart-title') || key.startsWith('pieChart-title')){ anchor[key] = this.layoutDoc[key]; } }) - this.addDocument(anchor); return anchor; }; @@ -113,6 +84,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } selectAxes = (axes: string[]) => (this.layoutDoc.data_vizAxes = new List(axes)); + // toggles for user to decide which chart type to view the data in @computed get selectView() { const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; @@ -125,6 +97,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { case DataVizView.PIECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; } } + @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField); } @@ -141,16 +114,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { .then(res => res.json().then(action(res => !res.errno && DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res)))); } - // handle changing the view using a button - @action changeViewHandler(e: React.MouseEvent) { - e.preventDefault(); - e.stopPropagation(); - this.layoutDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; - } - render() { if (!this.layoutDoc._dataVizView) this.layoutDoc._dataVizView = this.dataVizView; return !this.pairs?.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 + t' to bring the data table diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 26c40045c..0b35f2856 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -44,12 +44,12 @@ export class Histogram extends React.Component { private numericalXData: boolean = false; // whether the data is organized by numbers rather than categoreis private numericalYData: boolean = false; // whether the y axis is controlled by provided data rather than frequency private maxBins = 15; // maximum number of bins that is readable on a normal sized doc - @observable _currSelected: any | undefined = undefined; - private curBarSelected: any = undefined; - private selectedData: any = undefined; - private hoverOverData: any = undefined; - // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates + @observable _currSelected: any | undefined = undefined; // Object of selected bar + private curBarSelected: any = undefined; // histogram bin of selected bar + private selectedData: any = undefined; // Selection of selected bar + private hoverOverData: any = undefined; // Selection of bar being hovered over + // filters all data to just display selected data if brushed (created from an incoming link) @computed get _histogramData() { var guids = StrListCast(this.props.layoutDoc.rowGuids); if (this.props.axes.length < 1) return []; @@ -60,7 +60,6 @@ export class Histogram extends React.Component { ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) }; - var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; if (/\d/.test(this.props.pairs[0][ax0])) { this.numericalXData = true;} @@ -69,6 +68,7 @@ export class Histogram extends React.Component { ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) } + @computed get defaultGraphTitle(){ var ax0 = this.props.axes[0]; var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; @@ -77,21 +77,13 @@ export class Histogram extends React.Component { } else return ax1 + " by " + ax0 + " Histogram"; } + @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => { - return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // get links where this chart doc is the target of the link + .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // 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 incomingSelected() { - // return selected x and y axes - // otherwise, use the selection of whatever is linked to us - return this.incomingLinks // all links that are pointing to this node - .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes - .filter(dvb => dvb) - .map(dvb => dvb.pairs?.filter((pair: { [x: string]: any; }) => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor - .lastElement(); - } + @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { if (this.numericalXData){ const data = this.data(this._histogramData); @@ -99,6 +91,7 @@ export class Histogram extends React.Component { } return {xMin:0, xMax:0, yMin:0, yMax:0} } + componentWillUnmount() { Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); } @@ -108,79 +101,14 @@ export class Histogram extends React.Component { ({ dataSet, w, h }) => { if (dataSet!.length>0) { this.drawChart(dataSet, w, h); - - // redraw annotations when the chart data has changed, or the local or inherited selection has changed - this.clearAnnotations(); - this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true); - this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); } }, { fireImmediately: true } ); - this._disposers.annos = reaction( - () => DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), - annotations => { - // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way - // could be blue colored to make it look like anchor - // this.drawAnnotations() - // loop through annotations and draw them - annotations.forEach(a => this.drawAnnotations(Number(a.x), Number(a.y))); - // this.drawAnnotations(annotations.x, annotations.y); - }, - { fireImmediately: true } - ); - this._disposers.highlights = reaction( - () => ({ - selected: this._currSelected, - incomingSelected: this.incomingSelected, - }), - ({ selected, incomingSelected }) => { - // redraw annotations when the chart data has changed, or the local or inherited selection has changed - this.clearAnnotations(); - selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true); - incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); - }, - { fireImmediately: true } - ); }; - // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that - - clearAnnotations = () => { - const elements = document.querySelectorAll('.datapoint'); - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - element.classList.remove('brushed'); - element.classList.remove('selected'); - } - }; - // gets called whenever the "data_annotations" fields gets updated - drawAnnotations = (dataX: number, dataY: number, selected?: boolean) => { - // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements - // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY - // if it exists, then highlight it - // if it doesn't exist, then remove the highlight - const elements = document.querySelectorAll('.datapoint'); - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - const x = element.getAttribute('data-x'); - const y = element.getAttribute('data-y'); - if (x === dataX.toString() && y === dataY.toString()) { - element.classList.add(selected ? 'selected' : 'brushed'); - } - // TODO: nda - this remove highlight code should go where we remove the links - // } else { - // } - } - }; - - removeAnnotations(dataX: number, dataY: number) { - // loop through and remove any annotations that no longer exist - } - @action restoreView = (data: Doc) => {}; - // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ @@ -188,7 +116,6 @@ export class Histogram extends React.Component { title: 'histogram doc selection' + this._currSelected, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); - // anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; return anchor; }; @@ -200,6 +127,7 @@ export class Histogram extends React.Component { return this.props.width - this.props.margin.left - this.props.margin.right; } + // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { var validData = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; @@ -216,21 +144,24 @@ export class Histogram extends React.Component { return data; } + // outlines the bar selected / hovered over highlightSelectedBar = (changeSelectedVariables: boolean, svg: any, eachRectWidth: any, pointerX: any, xAxisTitle: any, yAxisTitle: any, histDataSet: any) => { var sameAsCurrent: boolean; var barCounter = -1; const selected = svg.selectAll('.histogram-bar').filter((d: any) => { - barCounter++; + barCounter++; // uses the order of bars and width of each bar to find which one the pointer is over if ((barCounter*eachRectWidth ) <= pointerX && pointerX <= ((barCounter+1)*eachRectWidth)){ var showSelected = this.numericalYData? this._histogramData.filter((data: { [x: string]: any; }) => data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { } } + // draws the histogram drawChart = (dataSet: any, width: number, height: number) => { d3.select(this._histogramRef.current).select('svg').remove(); d3.select(this._histogramRef.current).select('.tooltip').remove(); var data = this.data(dataSet); - var xAxisTitle = Object.keys(dataSet[0])[0] var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency'; let uniqueArr: unknown[] = [...new Set(data)] @@ -263,6 +194,8 @@ export class Histogram extends React.Component { if (numBins>this.maxBins) numBins = this.maxBins; var startingPoint = this.numericalXData? this.rangeVals.xMin! : 0; var endingPoint = this.numericalXData? this.rangeVals.xMax! : numBins; + + // converts data into Objects var histDataSet = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; Object.keys(dataSet[0]).map(key => { @@ -289,6 +222,7 @@ export class Histogram extends React.Component { histDataSet = histStringDataSet } + // initial graph and binning data for histogram var svg = (this._histogramSvg = d3 .select(this._histogramRef.current) .append("svg") @@ -298,7 +232,6 @@ export class Histogram extends React.Component { .append("g") .attr("transform", "translate(" + this.props.margin.left + "," + this.props.margin.top + ")")); - var x = d3.scaleLinear() .domain(this.numericalXData? [startingPoint!, endingPoint!] : [0, numBins]) .range([0, width ]); @@ -314,6 +247,8 @@ export class Histogram extends React.Component { .range([0, Number.isInteger(this.rangeVals.xMin!)? (width-eachRectWidth) : width ]) var xAxis; + // more calculations based on bins + // x-axis if (!this.numericalXData) { // reorganize if the data is strings rather than numbers // uniqueArr.sort() histDataSet.sort() @@ -331,7 +266,7 @@ export class Histogram extends React.Component { bins.forEach(d => d.x0 = d.x0!) xAxis = d3.axisBottom(x) .ticks(bins.length-1) - .tickFormat( i => uniqueArr[i]) + .tickFormat( i => uniqueArr[i.valueOf()] as string) .tickPadding(10) x.range([0, width-eachRectWidth]) x.domain([0, bins.length-1]) @@ -343,10 +278,10 @@ export class Histogram extends React.Component { xAxis = d3.axisBottom(x) .ticks(numBins-1) } + // y-axis const maxFrequency = this.numericalYData? d3.max(histDataSet, function(d: any) { return Number(d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\ { svg.append("g") .attr("transform", "translate(" + translateXAxis + ", " + height + ")") .call(xAxis) - + + // click/hover const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet)); const onHover = action((e: any) => { const selected = this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet) @@ -378,12 +314,11 @@ export class Histogram extends React.Component { const selectedData = this.selectedData; svg.selectAll('rect').attr("class", function(d: any) { return ((hoverOverBar && hoverOverBar[0]==d[0]) || selectedData && selectedData[0]==d[0])? 'histogram-bar hover' : 'histogram-bar'; }) } - svg.on('click', onPointClick) .on('mouseover', onHover) .on('mouseout', mouseOut) - var selected = this.selectedData; + // axis titles svg.append("text") .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") .style("text-anchor", "middle") @@ -395,6 +330,9 @@ export class Histogram extends React.Component { .style("text-anchor", "middle") .text(yAxisTitle); d3.format('.0f') + + // draw bars + var selected = this.selectedData; svg.selectAll("rect") .data(bins) .enter() diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 0fd4f6b54..d4570dee2 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -40,12 +40,12 @@ export class PieChart extends React.Component { private _piechartRef: React.RefObject = React.createRef(); private _piechartSvg: d3.Selection | undefined; private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios - @observable _currSelected: any | undefined = undefined; - private curSliceSelected: any = undefined; - private selectedData: any = undefined; - private hoverOverData: any = undefined; - // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates + @observable _currSelected: any | undefined = undefined; // Object of selected slice + private curSliceSelected: any = undefined; // d3 data of selected slice + private selectedData: any = undefined; // Selection of selected slice + private hoverOverData: any = undefined; // Selection of slice being hovered over + // filters all data to just display selected data if brushed (created from an incoming link) @computed get _piechartData() { var guids = StrListCast(this.props.layoutDoc.rowGuids); if (this.props.axes.length < 1) return []; @@ -56,7 +56,6 @@ export class PieChart extends React.Component { ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) }; - var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; if (/\d/.test(this.props.pairs[0][ax0])) { this.byCategory = false; } @@ -64,6 +63,7 @@ export class PieChart extends React.Component { ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) } + @computed get defaultGraphTitle(){ var ax0 = this.props.axes[0]; var ax1 = (this.props.axes.length>1)? this.props.axes[1] : undefined; @@ -72,28 +72,13 @@ export class PieChart extends React.Component { } else return ax1 + " by " + ax0 + " Pie Chart"; } + @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => { - return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // get links where this chart doc is the target of the link + .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // 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 incomingSelected() { - // return selected x and y axes - // otherwise, use the selection of whatever is linked to us - return this.incomingLinks // all links that are pointing to this node - .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes - .filter(dvb => dvb) - .map(dvb => dvb.pairs?.filter((pair: { [x: string]: any; }) => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor - .lastElement(); - } - @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { - if (!this.byCategory){ - const data = this.data(this._piechartData); - return {xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin:0, yMax:0} - } - return {xMin:0, xMax:0, yMin:0, yMax:0} - } + componentWillUnmount() { Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); } @@ -103,79 +88,14 @@ export class PieChart extends React.Component { ({ dataSet, w, h }) => { if (dataSet!.length>0) { this.drawChart(dataSet, w, h); - - // redraw annotations when the chart data has changed, or the local or inherited selection has changed - this.clearAnnotations(); - this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true); - this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); } }, { fireImmediately: true } ); - this._disposers.annos = reaction( - () => DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), - annotations => { - // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way - // could be blue colored to make it look like anchor - // this.drawAnnotations() - // loop through annotations and draw them - annotations.forEach(a => this.drawAnnotations(Number(a.x), Number(a.y))); - // this.drawAnnotations(annotations.x, annotations.y); - }, - { fireImmediately: true } - ); - this._disposers.highlights = reaction( - () => ({ - selected: this._currSelected, - incomingSelected: this.incomingSelected, - }), - ({ selected, incomingSelected }) => { - // redraw annotations when the chart data has changed, or the local or inherited selection has changed - this.clearAnnotations(); - selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true); - incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); - }, - { fireImmediately: true } - ); - }; - - // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that - - clearAnnotations = () => { - const elements = document.querySelectorAll('.datapoint'); - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - element.classList.remove('brushed'); - element.classList.remove('selected'); - } - }; - // gets called whenever the "data_annotations" fields gets updated - drawAnnotations = (dataX: number, dataY: number, selected?: boolean) => { - // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements - // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY - // if it exists, then highlight it - // if it doesn't exist, then remove the highlight - const elements = document.querySelectorAll('.datapoint'); - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - const x = element.getAttribute('data-x'); - const y = element.getAttribute('data-y'); - if (x === dataX.toString() && y === dataY.toString()) { - element.classList.add(selected ? 'selected' : 'brushed'); - } - // TODO: nda - this remove highlight code should go where we remove the links - // } else { - // } - } }; - removeAnnotations(dataX: number, dataY: number) { - // loop through and remove any annotations that no longer exist - } - @action restoreView = (data: Doc) => {}; - // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ @@ -183,7 +103,6 @@ export class PieChart extends React.Component { title: 'piechart doc selection' + this._currSelected, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); - // anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected]) : undefined; return anchor; }; @@ -195,15 +114,7 @@ export class PieChart extends React.Component { return this.props.width - this.props.margin.left - this.props.margin.right; } - // TODO: nda - use this everyewhere we update currSelected? - @action - setCurrSelected(x?: number, y?: number) { - // TODO: nda - get rid of svg element in the list? - this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined; - this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true)); - this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined)); - } - + // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { var validData = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; @@ -220,15 +131,16 @@ export class PieChart extends React.Component { return data; } + // outlines the slice selected / hovered over highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => { var index = -1; var sameAsCurrent: boolean; const selected = svg.selectAll('.slice').filter((d: any) => { index++; - var p1 = [0,0]; - var p3 = [arc.centroid(d)[0]*2, arc.centroid(d)[1]*2]; - var p2 = [radius*Math.sin(d.startAngle), -radius*Math.cos(d.startAngle)]; - var p4 = [radius*Math.sin(d.endAngle), -radius*Math.cos(d.endAngle)]; + var p1 = [0,0]; // center of pie + var p3 = [arc.centroid(d)[0]*2, arc.centroid(d)[1]*2]; // outward peak of arc + var p2 = [radius*Math.sin(d.startAngle), -radius*Math.cos(d.startAngle)]; // start of arc + var p4 = [radius*Math.sin(d.endAngle), -radius*Math.cos(d.endAngle)]; // end of arc // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge var lineCrossCount = 0; @@ -241,10 +153,10 @@ export class PieChart extends React.Component { if (pointer[0] <= (pointer[1]-p3[1])*(p4[0]-p3[0])/(p4[1]-p3[1])+p3[0]) lineCrossCount++; } if (Math.min(p4[1], p1[1])<=pointer[1] && pointer[1]<=Math.max(p4[1], p1[1])){ if (pointer[0] <= (pointer[1]-p4[1])*(p1[0]-p4[0])/(p1[1]-p4[1])+p4[0]) lineCrossCount++; } - if (lineCrossCount % 2 != 0) { + if (lineCrossCount % 2 != 0) { // inside the slice of it crosses an odd number of edges var showSelected = this.byCategory? pieDataSet[index] : this._piechartData[index]; - if (changeSelectedVariables){ + // for when a bar is selected - not just hovered over sameAsCurrent = (this._currSelected)? (showSelected[Object.keys(showSelected)[0]]==this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]]==this._currSelected![Object.keys(showSelected)[1]]) @@ -264,6 +176,7 @@ export class PieChart extends React.Component { } } + // draws the pie chart drawChart = (dataSet: any, width: number, height: number) => { d3.select(this._piechartRef.current).select('svg').remove(); d3.select(this._piechartRef.current).select('.tooltip').remove(); @@ -271,18 +184,8 @@ export class PieChart extends React.Component { var percentField = Object.keys(dataSet[0])[0] var descriptionField = Object.keys(dataSet[0])[1]! var radius = Math.min(width, height-this.props.margin.top-this.props.margin.bottom) /2 - var svg = (this._piechartSvg = d3 - .select(this._piechartRef.current) - .append("svg") - .attr("class", "graph") - .attr("width", width + this.props.margin.right + this.props.margin.left) - .attr("height", height + this.props.margin.top + this.props.margin.bottom) - .append("g")); - - let g = svg.append("g") - .attr("transform", - "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")"); + // converts data into Objects var data = this.data(dataSet); var pieDataSet = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; @@ -293,12 +196,12 @@ export class PieChart extends React.Component { }); if (this.byCategory){ let uniqueCategories = [...new Set(data)] - var pieStringDataSet: { frequency: number, [percentField]: string }[] = []; + var pieStringDataSet: { frequency: number }[] = []; for (let i=0; i each[percentField]==data[i]) + let sliceData = pieStringDataSet.filter((each: any) => each[percentField]==data[i]) sliceData[0].frequency = sliceData[0].frequency + 1; } pieDataSet = pieStringDataSet @@ -309,11 +212,23 @@ export class PieChart extends React.Component { var trackDuplicates : {[key: string]: any} = {}; data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) + // initial chart + var svg = (this._piechartSvg = d3 + .select(this._piechartRef.current) + .append("svg") + .attr("class", "graph") + .attr("width", width + this.props.margin.right + this.props.margin.left) + .attr("height", height + this.props.margin.top + this.props.margin.bottom) + .append("g")); + let g = svg.append("g") + .attr("transform", + "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")"); var pie = d3.pie(); var arc = d3.arc() .innerRadius(0) .outerRadius(radius); + // click/hover const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet)); const onHover = action((e: any) => { const selected = this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet) @@ -331,6 +246,7 @@ export class PieChart extends React.Component { || ((hoverOverSlice && d.startAngle==hoverOverSlice.startAngle && d.endAngle==hoverOverSlice.endAngle)))? 'slice hover' : 'slice'; }) } + // drawing the slices var selected = this.selectedData; var arcs = g.selectAll("arc") .data(pie(data)) @@ -359,11 +275,12 @@ export class PieChart extends React.Component { function(d) { return (selected && d.startAngle==selected.startAngle && d.endAngle==selected.endAngle)? 'slice hover' : 'slice'; }: function(d) {return 'slice'}) - .attr("d", arc) + .attr('d', arc) .on('click', onPointClick) .on('mouseover', onHover) .on('mouseout', mouseOut); + // adding labels trackDuplicates = {}; data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) arcs.append("text") diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 38dd62d8d..277ee83ec 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -32,6 +32,7 @@ interface TableBoxProps { @observer export class TableBox extends React.Component { + // filters all data to just display selected data if brushed (created from an incoming link) @computed get _tableData() { if (this.incomingLinks.length! <= 0) return this.props.pairs; var guids = StrListCast(this.props.layoutDoc.rowGuids); @@ -52,6 +53,7 @@ export class TableBox extends React.Component { return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='' && header!=undefined) : []; } + // updates the 'selected' field to no longer include rows that aren't in the table filterSelectedRowsDown() { if (!this.props.layoutDoc.selected) this.props.layoutDoc.selected = new List(); const selected = Cast(this.props.layoutDoc.selected, listSpec("string"), null); @@ -88,7 +90,7 @@ export class TableBox extends React.Component { setupMoveUpEvents( {}, e, - 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!); @@ -144,6 +146,7 @@ export class TableBox extends React.Component { if (containsData){ return (
{ + // selecting a row const selected = Cast(this.props.layoutDoc.selected, listSpec("string"), null); if (selected.includes(guid)) selected.splice(selected.indexOf(guid), 1); else { @@ -153,6 +156,7 @@ export class TableBox extends React.Component { background: StrListCast(this.props.layoutDoc.selected).includes(guid) ? 'lightgrey' : '' }}> {this.columns.map(col => ( (this.props.layoutDoc.selected)? + // each cell @@ -168,6 +172,7 @@ export class TableBox extends React.Component { ); } else return ( + // when it is a brushed table and the incoming table doesn't have any rows selected
Selected rows of data from the incoming DataVizBox to display.
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts index e1ff6f8eb..10bfb0c64 100644 --- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -34,7 +34,6 @@ export const createLineGenerator = (xScale: d3.ScaleLinear, height: number, xScale: d3.ScaleLinear) => { - console.log('x axis creator being called'); g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15)); }; -- cgit v1.2.3-70-g09d2 From 51b525b5f727311628cba1809e078b67543ddfa4 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Mon, 14 Aug 2023 23:22:55 -0400 Subject: merge --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/SettingsManager.tsx | 2 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/DataVizBox/DataVizBox.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ef8b69d12..ae5d97082 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -306,7 +306,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript)}, - { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, + { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true }, { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index b2b5be070..6acba8af4 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -450,7 +450,7 @@ export class SettingsManager extends React.Component<{}> {
-
{DashVersion}
+
{DashVersion}
{Doc.CurrentUserEmail}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index e71739231..0cc73f32f 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -25,6 +25,9 @@ export enum DataVizView { @observer export class DataVizBox extends ViewBoxAnnotatableComponent() { + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(DataVizBox, fieldStr); + } // all data static pairSet = new ObservableMap(); @@ -120,7 +123,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // 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 + t' to bring the data table + or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.
) : ( -- cgit v1.2.3-70-g09d2
{p[col]}