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 +- .../nodes/DataVizBox/components/Histogram.tsx | 326 +++++++++++++++++++++ .../nodes/DataVizBox/components/LineChart.tsx | 4 +- 3 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 src/client/views/nodes/DataVizBox/components/Histogram.tsx (limited to 'src') 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} ); diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx new file mode 100644 index 000000000..cb0b8cd9a --- /dev/null +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -0,0 +1,326 @@ +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, StrCast } from "../../../../../fields/Types"; +import { useMemo } from "react"; +import { DataPoint, SelectedDataPoint } from "./LineChart"; +import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from "../utils/D3Utils"; +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 { isNaN } from "lodash"; + +export interface HistogramProps { + 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 Histogram extends React.Component { + + private _disposers: { [key: string]: IReactionDisposer } = {}; + private _histogramRef: React.RefObject = React.createRef(); + private _histogramSvg: d3.Selection | undefined; + @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 _histogramData() { + if (this.props.axes.length < 1) return []; + if (this.props.axes.length < 2) { + var ax0 = this.props.axes[0]; + 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]; + 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(); + } + componentWillUnmount() { + Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); + } + componentDidMount = () => { + this._disposers.chartData = reaction( + () => ({ dataSet: this._histogramData, 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._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)); + } + + drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear, yScale: d3.ScaleLinear) { + if (this._histogramSvg) { + const circleClass = '.circle-' + idx; + this._histogramSvg + .selectAll(circleClass) + .data(data) + .join('circle') // enter append + .attr('class', `${circleClass} datapoint`) + .attr('r', '3') // radius + .attr('cx', d => xScale(d.x)) + .attr('cy', d => yScale(d.y)) + .attr('data-x', d => d.x) + .attr('data-y', d => d.y); + } + } + + drawChart = (dataSet: any, width: number, height: number) => { + + 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 (+d[field]) {return +d[field] } + return d[field] + }) + + let uniqueArr = [...new Set(data)] + var numBins = uniqueArr.length + if (+d3.max(data)!) numBins = +d3.max(data)! + // var numBins = (+d3.max(data)!)? +d3.max(data)! : 3; + + const svg = d3.select(this._histogramRef.current) + .append("svg") + .attr("width", width + this.props.margin.right + this.props.margin.left) + .attr("height", height + this.props.margin.top + this.props.margin.bottom) + .append("g") + .attr("transform", + "translate(" + this.props.margin.left + "," + this.props.margin.top + ")"); + + var x = d3.scaleLinear() + .domain([0, numBins]) + .range([0, width]); + var xAxis = d3.axisBottom(x) + .ticks(numBins) + var translateXAxis = 0; + + var histogram = d3.histogram() + .value(function(d) {return d}) + .domain([0, numBins]) + .thresholds(x.ticks(numBins)) + var bins = histogram(data) + if (!+d3.max(data)!) { // if the data is strings rather than numbers + uniqueArr.sort() + for (let i=0; i d.x0 = d.x0!+1) + xAxis = d3.axisBottom(x) + .ticks(numBins) + .tickValues([0, 1, 2, 3, 4]) + .tickFormat( i => uniqueArr[i]) + .tickPadding(10) + translateXAxis = (width/numBins) / 2; + } + + var y = d3.scaleLinear() + .range([height, 0]); + y.domain([0, d3.max(bins, function(d) { return d.length; })!]); + var yAxis = d3.axisLeft(y) + .ticks(d3.max(bins, function(d) { return d.length; })!) + svg.append("g") + .call(yAxis); + svg.append("g") + .attr("transform", "translate(" + translateXAxis + ", " + height + ")") + .call(xAxis) + + d3.format('.0f') + + svg.selectAll("rect") + .data(bins) + .enter() + .append("rect") + .attr("x", 1) + .attr("transform", function(d) { return "translate(" + x(d.x0! - 1) + "," + y(d.length) + ")"; }) + // .attr("width", function(d) { return x(d.x1!) - x(d.x0! ) ; }) + .attr("width", width/(numBins)) + .attr("height", function(d) { return height - y(d.length); }) + .attr("style", "outline: thin solid black;") + .style("fill", "#69b3a2") + }; + + render() { + + return ( + this.props.axes.length >= 1 ? ( +
+ {`Selected: ${Object.keys(this._histogramData[0])[0]}`} +
+ ) : {'first use table view to select an axis to plot'} + ); + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 6b564b0c9..289cecb6b 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -20,7 +20,7 @@ export interface DataPoint { x: number; y: number; } -interface SelectedDataPoint extends DataPoint { +export interface SelectedDataPoint extends DataPoint { elem?: d3.Selection; } export interface LineChartProps { @@ -60,6 +60,8 @@ export class LineChart extends React.Component { .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) -- cgit v1.2.3-70-g09d2 From 9418db69bad9e6cc862ccccb95e04d9a9430c283 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Sun, 9 Jul 2023 12:28:51 -0400 Subject: chart axis labels --- src/client/views/nodes/DataVizBox/components/Chart.scss | 5 +++++ .../views/nodes/DataVizBox/components/Histogram.tsx | 15 +++++++++++++-- .../views/nodes/DataVizBox/components/LineChart.tsx | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index d4f7bfb32..05bb1655d 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -3,6 +3,11 @@ flex-direction: column; align-items: center; cursor: default; + margin-top: 10px; + + .graph{ + overflow: visible; + } .tooltip { // make the height width bigger diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index cb0b8cd9a..1da803076 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -244,10 +244,10 @@ export class Histogram extends React.Component { let uniqueArr = [...new Set(data)] var numBins = uniqueArr.length if (+d3.max(data)!) numBins = +d3.max(data)! - // var numBins = (+d3.max(data)!)? +d3.max(data)! : 3; const svg = d3.select(this._histogramRef.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") @@ -297,6 +297,18 @@ export class Histogram extends React.Component { .attr("transform", "translate(" + translateXAxis + ", " + height + ")") .call(xAxis) + // axis titles + svg.append("text") + .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") + .style("text-anchor", "middle") + .text(field); + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("x", -(height/2)) + .attr("y", -20) + .style("text-anchor", "middle") + .text('frequency'); + d3.format('.0f') svg.selectAll("rect") @@ -305,7 +317,6 @@ export class Histogram extends React.Component { .append("rect") .attr("x", 1) .attr("transform", function(d) { return "translate(" + x(d.x0! - 1) + "," + y(d.length) + ")"; }) - // .attr("width", function(d) { return x(d.x1!) - x(d.x0! ) ; }) .attr("width", width/(numBins)) .attr("height", function(d) { return height - y(d.length); }) .attr("style", "outline: thin solid black;") diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 289cecb6b..b1a759c1f 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -240,6 +240,7 @@ export class LineChart extends React.Component { const svg = (this._lineChartSvg = d3 .select(this._lineChartRef.current) .append('svg') + .attr("class", "graph") .attr('width', `${width + margin.left + margin.right}`) .attr('height', `${height + margin.top + margin.bottom}`) .append('g') @@ -295,6 +296,20 @@ export class LineChart extends React.Component { .on('mouseout', () => tooltip.transition().duration(300).style('opacity', 0)) .on('mousemove', mousemove) .on('click', onPointClick); + + // axis titles + svg.append("text") + .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") + .style("text-anchor", "middle") + .text(this.props.axes[0]); + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("x", -(height/2)) + .attr("y", -20) + .attr("height", 20) + .attr("width", 20) + .style("text-anchor", "middle") + .text(this.props.axes[1]); }; private updateTooltip( -- cgit v1.2.3-70-g09d2 From 6220445cc7832039029b74bc683f21f850daab8f Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 11 Jul 2023 10:23:08 -0400 Subject: importing tables from wikipedia -> DataVizBox --- .../collections/collectionFreeForm/MarqueeView.tsx | 62 +++++++++------------- 1 file changed, 26 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5febbe83e..35b3ca5d8 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -170,6 +170,14 @@ export class MarqueeView extends React.Component { + const text: string = await navigator.clipboard.readText(); + const ns = text.split('\n').filter(t => t.trim() !== '\r' && t.trim() !== ''); + this.pasteTable(ns, x, y); + })(); + e.stopPropagation(); } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : ''; FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note'); @@ -185,44 +193,26 @@ export class MarqueeView extends React.Component 0 && ns[0].split('\t').length < 2) { - ns.splice(0, 1); - } - if (ns.length > 0) { - const columns = ns[0].split('\t'); - const docList: Doc[] = []; - let groupAttr: string | number = ''; - const rowProto = new Doc(); - rowProto.title = rowProto.Id; - rowProto._width = 200; - rowProto.isDataDoc = true; - for (let i = 1; i < ns.length - 1; i++) { - const values = ns[i].split('\t'); - if (values.length === 1 && columns.length > 1) { - groupAttr = values[0]; - continue; - } - const docDataProto = Doc.MakeDelegate(rowProto); - docDataProto.isDataDoc = true; - columns.forEach((col, i) => (docDataProto[columns[i]] = values.length > i ? (values[i].indexOf(Number(values[i]).toString()) !== -1 ? Number(values[i]) : values[i]) : undefined)); - if (groupAttr) { - docDataProto._group = groupAttr; - } - docDataProto.title = i.toString(); - const doc = Doc.MakeDelegate(docDataProto); - doc._width = 200; - docList.push(doc); + let csvRows = []; + const headers = ns[0].split('\t'); + csvRows.push(headers.join(',')); + ns[0] = ''; + const eachCell = ns.join('\t').split('\t') + let eachRow = [] + for (let i=1; i c).map(c => new SchemaHeaderField(c, '#f1efeb'))], docList, { - x: x, - y: y, - title: 'droppedTable', - _width: 300, - _height: 100, - }); - - this.props.addDocument?.(newCol); } + + const blob = new Blob([csvRows.join('\n')], {type: 'text/csv'}) + const options = { x: x, y: y, title: 'droppedTable', _width: 300, _height: 100, type:'text/csv'} + const file = new File([blob], 'droppedTable', options); + const loading = Docs.Create.LoadingDocument(file, options); + DocUtils.uploadFileToDoc(file, {}, loading); + this.props.addDocument?.(loading); } @action -- cgit v1.2.3-70-g09d2 From 32f8cd2c70e77fa13f6b9d1fed73dc5c1af11189 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 11 Jul 2023 10:30:51 -0400 Subject: multiple histogram bug fix --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 1da803076..14063a068 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -4,10 +4,8 @@ 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 { useMemo } from "react"; +import { Cast, DocCast} from "../../../../../fields/Types"; import { DataPoint, SelectedDataPoint } from "./LineChart"; -import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from "../utils/D3Utils"; import { DocumentManager } from "../../../../util/DocumentManager"; import { Id } from "../../../../../fields/FieldSymbols"; import { DataVizBox } from "../DataVizBox"; @@ -16,7 +14,6 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; -import { isNaN } from "lodash"; export interface HistogramProps { rootDoc: Doc; @@ -227,6 +224,8 @@ export class Histogram extends React.Component { } drawChart = (dataSet: any, width: number, height: number) => { + d3.select(this._histogramRef.current).select('svg').remove(); + d3.select(this._histogramRef.current).select('.tooltip').remove(); var validData = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; @@ -245,14 +244,15 @@ export class Histogram extends React.Component { var numBins = uniqueArr.length if (+d3.max(data)!) numBins = +d3.max(data)! - const svg = d3.select(this._histogramRef.current) + const svg = (this._histogramSvg = d3 + .select(this._histogramRef.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") .attr("transform", - "translate(" + this.props.margin.left + "," + this.props.margin.top + ")"); + "translate(" + this.props.margin.left + "," + this.props.margin.top + ")")); var x = d3.scaleLinear() .domain([0, numBins]) -- cgit v1.2.3-70-g09d2 From e3b5e9fd819a394051c760b37ca14bb8c759f72b Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 11 Jul 2023 12:49:18 -0400 Subject: cleaner + bottom axis shows bigger numbers (like years) better --- .../nodes/DataVizBox/components/Histogram.tsx | 83 +++++++++++++--------- 1 file changed, 50 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 14063a068..c7850d8c1 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 { minMaxRange } from "../utils/D3Utils"; +import e from "connect-flash"; export interface HistogramProps { rootDoc: Doc; @@ -37,6 +39,7 @@ export class Histogram extends React.Component { private _disposers: { [key: string]: IReactionDisposer } = {}; private _histogramRef: React.RefObject = React.createRef(); private _histogramSvg: 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 @@ -44,12 +47,15 @@ export class Histogram extends React.Component { if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; + if (+this.props.pairs[0][this.props.axes[0]]) { 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 (+this.props.pairs[0][this.props.axes[0]]) { 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]]) })) @@ -73,6 +79,13 @@ export class Histogram extends React.Component { .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._histogramData); + 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]()); } @@ -207,26 +220,7 @@ export class Histogram extends React.Component { 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) { - if (this._histogramSvg) { - const circleClass = '.circle-' + idx; - this._histogramSvg - .selectAll(circleClass) - .data(data) - .join('circle') // enter append - .attr('class', `${circleClass} datapoint`) - .attr('r', '3') // radius - .attr('cx', d => xScale(d.x)) - .attr('cy', d => yScale(d.y)) - .attr('data-x', d => d.x) - .attr('data-y', d => d.y); - } - } - - drawChart = (dataSet: any, width: number, height: number) => { - d3.select(this._histogramRef.current).select('svg').remove(); - d3.select(this._histogramRef.current).select('.tooltip').remove(); - + data = (dataSet: any) => { var validData = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; Object.keys(dataSet[0]).map(key => { @@ -236,13 +230,24 @@ export class Histogram extends React.Component { }) var field = Object.keys(dataSet[0])[0] const data = validData.map((d: { [x: string]: any; }) => { - if (+d[field]) {return +d[field] } + if (this.numericalData) {return +d[field] } return d[field] }) + return data; + } + + drawChart = (dataSet: any, width: number, height: number) => { + d3.select(this._histogramRef.current).select('svg').remove(); + d3.select(this._histogramRef.current).select('.tooltip').remove(); + var field = Object.keys(dataSet[0])[0] + const data = this.data(dataSet); + let uniqueArr = [...new Set(data)] var numBins = uniqueArr.length - if (+d3.max(data)!) numBins = +d3.max(data)! + if (this.numericalData) numBins = this.rangeVals.xMax! - this.rangeVals.xMin! + 1; + var startingPoint = this.numericalData==true? this.rangeVals.xMin: 0; + console.log(startingPoint) const svg = (this._histogramSvg = d3 .select(this._histogramRef.current) @@ -254,19 +259,32 @@ export class Histogram extends React.Component { .attr("transform", "translate(" + this.props.margin.left + "," + this.props.margin.top + ")")); - var x = d3.scaleLinear() - .domain([0, numBins]) - .range([0, width]); + var x: any; + if (this.numericalData){ + x = d3.scaleLinear() + .domain([this.rangeVals.xMin!, this.rangeVals.xMax!]) + .range([0, width-(width/numBins)]); + } + else { + x = d3.scaleLinear() + .domain([0, numBins]) + .range([0, width]); + } + var xAxis = d3.axisBottom(x) - .ticks(numBins) - var translateXAxis = 0; + .ticks(numBins-1) + var translateXAxis = (width/numBins) / 2; var histogram = d3.histogram() .value(function(d) {return d}) - .domain([0, numBins]) + .domain([startingPoint!, numBins+startingPoint!]) .thresholds(x.ticks(numBins)) var bins = histogram(data) - if (!+d3.max(data)!) { // if the data is strings rather than numbers + console.log(this._histogramData) + console.log(data) + console.log(bins) + console.log(this.rangeVals.xMin, this.rangeVals.xMax, numBins) + if (!this.numericalData) { // if the data is strings rather than numbers uniqueArr.sort() for (let i=0; i { } bins[index].push(data[i]) } - bins.forEach(d => d.x0 = d.x0!+1) + bins.forEach(d => d.x0 = d.x0!) xAxis = d3.axisBottom(x) - .ticks(numBins) - .tickValues([0, 1, 2, 3, 4]) + .ticks(numBins-1) .tickFormat( i => uniqueArr[i]) .tickPadding(10) translateXAxis = (width/numBins) / 2; @@ -316,7 +333,7 @@ export class Histogram extends React.Component { .enter() .append("rect") .attr("x", 1) - .attr("transform", function(d) { return "translate(" + x(d.x0! - 1) + "," + y(d.length) + ")"; }) + .attr("transform", function(d) { return "translate(" + x(d.x0!) + "," + y(d.length) + ")"; }) .attr("width", width/(numBins)) .attr("height", function(d) { return height - y(d.length); }) .attr("style", "outline: thin solid black;") -- cgit v1.2.3-70-g09d2 From 144375b1b8b3fb8119997a9c691b501cfae39826 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 11 Jul 2023 13:07:10 -0400 Subject: drawing line chart fix --- .../views/nodes/DataVizBox/components/LineChart.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index b1a759c1f..d87f5ae1b 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -252,13 +252,20 @@ export class LineChart extends React.Component { xAxisCreator(svg.append('g'), height, xScale); yAxisCreator(svg.append('g'), width, yScale); - // draw the plot line + // get valid data points const data = dataSet[0]; const lineGen = createLineGenerator(xScale, yScale); - drawLine(svg.append('path'), data, lineGen); - + var validData = data.filter((d => { + var valid = true; + Object.keys(data[0]).map(key => { + if (!d[key] || Number.isNaN(d[key])) valid = false; + }) + return valid; + })) + // draw the plot line + drawLine(svg.append('path'), validData, lineGen); // draw the datapoint circle - this.drawDataPoints(data, 0, xScale, yScale); + this.drawDataPoints(validData, 0, xScale, yScale); const higlightFocusPt = svg.append('g').style('display', 'none'); higlightFocusPt.append('circle').attr('r', 5).attr('class', 'circle'); -- cgit v1.2.3-70-g09d2 From 0694f00a07801a579ae1a7b0f84e0aec852a2a22 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 11 Jul 2023 14:51:30 -0400 Subject: histogram grouped bins for lots / spread out data --- .../nodes/DataVizBox/components/Histogram.tsx | 50 ++++++++++++++-------- 1 file changed, 31 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index c7850d8c1..623453ea3 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -14,7 +14,6 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; -import { minMaxRange } from "../utils/D3Utils"; import e from "connect-flash"; export interface HistogramProps { @@ -245,9 +244,17 @@ export class Histogram extends React.Component { let uniqueArr = [...new Set(data)] var numBins = uniqueArr.length - if (this.numericalData) numBins = this.rangeVals.xMax! - this.rangeVals.xMin! + 1; - var startingPoint = this.numericalData==true? this.rangeVals.xMin: 0; - console.log(startingPoint) + var startingPoint = 0; + var endingPoint = numBins; + var translateXAxis = 0; + if (this.numericalData) { + numBins = this.rangeVals.xMax! - this.rangeVals.xMin! + 1; + startingPoint = this.rangeVals.xMin!; + endingPoint = this.rangeVals.xMax!; + if (numBins>15) numBins = 15; + else translateXAxis = width/(numBins+1) / 2; + } + else translateXAxis = width/(numBins+1) / 2; const svg = (this._histogramSvg = d3 .select(this._histogramRef.current) @@ -262,28 +269,27 @@ export class Histogram extends React.Component { var x: any; if (this.numericalData){ x = d3.scaleLinear() - .domain([this.rangeVals.xMin!, this.rangeVals.xMax!]) - .range([0, width-(width/numBins)]); + .domain([startingPoint!, endingPoint!]) + .range([0, width ]); } else { x = d3.scaleLinear() .domain([0, numBins]) .range([0, width]); } - - var xAxis = d3.axisBottom(x) - .ticks(numBins-1) - var translateXAxis = (width/numBins) / 2; var histogram = d3.histogram() .value(function(d) {return d}) - .domain([startingPoint!, numBins+startingPoint!]) + .domain([startingPoint!, endingPoint!]) .thresholds(x.ticks(numBins)) var bins = histogram(data) - console.log(this._histogramData) - console.log(data) - console.log(bins) - console.log(this.rangeVals.xMin, this.rangeVals.xMax, numBins) + var eachRectWidth = width/(bins.length) + var graphStartingPoint = bins[0].x1! - (bins[1].x1! - bins[1].x0!) + bins[0].x0 = graphStartingPoint; + x = x.domain([graphStartingPoint, endingPoint]) + .range([0, width - eachRectWidth]) + var xAxis; + if (!this.numericalData) { // if the data is strings rather than numbers uniqueArr.sort() for (let i=0; i { .ticks(numBins-1) .tickFormat( i => uniqueArr[i]) .tickPadding(10) - translateXAxis = (width/numBins) / 2; + translateXAxis = eachRectWidth / 2; + } + else { + xAxis = d3.axisBottom(x) + .ticks(numBins-1) } + const maxFrequency = d3.max(bins, function(d) { return d.length; }) + var y = d3.scaleLinear() .range([height, 0]); - y.domain([0, d3.max(bins, function(d) { return d.length; })!]); + y.domain([0, maxFrequency!]); var yAxis = d3.axisLeft(y) - .ticks(d3.max(bins, function(d) { return d.length; })!) + .ticks(maxFrequency!) svg.append("g") .call(yAxis); svg.append("g") @@ -334,7 +346,7 @@ export class Histogram extends React.Component { .append("rect") .attr("x", 1) .attr("transform", function(d) { return "translate(" + x(d.x0!) + "," + y(d.length) + ")"; }) - .attr("width", width/(numBins)) + .attr("width", eachRectWidth) .attr("height", function(d) { return height - y(d.length); }) .attr("style", "outline: thin solid black;") .style("fill", "#69b3a2") -- cgit v1.2.3-70-g09d2 From cbe80a7b0fa7a887596859f74f0461a7c7f376d3 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 12 Jul 2023 12:09:53 -0400 Subject: histogram decimals + prices --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 623453ea3..6047e6424 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -14,7 +14,6 @@ import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; import './Chart.scss'; -import e from "connect-flash"; export interface HistogramProps { rootDoc: Doc; @@ -46,7 +45,7 @@ export class Histogram extends React.Component { if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; - if (+this.props.pairs[0][this.props.axes[0]]) { this.numericalData = true;} + 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]])})) @@ -54,7 +53,7 @@ export class Histogram extends React.Component { var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; - if (+this.props.pairs[0][this.props.axes[0]]) { this.numericalData = true;} + 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]]) })) @@ -229,7 +228,7 @@ export class Histogram extends React.Component { }) var field = Object.keys(dataSet[0])[0] const data = validData.map((d: { [x: string]: any; }) => { - if (this.numericalData) {return +d[field] } + if (this.numericalData) { return +d[field].replace(/\$/g, '') } return d[field] }) return data; @@ -248,7 +247,9 @@ export class Histogram extends React.Component { var endingPoint = numBins; var translateXAxis = 0; if (this.numericalData) { - numBins = this.rangeVals.xMax! - this.rangeVals.xMin! + 1; + if (Number.isInteger(this.rangeVals.xMin!)){ + numBins = this.rangeVals.xMax! - this.rangeVals.xMin! + 1; + } startingPoint = this.rangeVals.xMin!; endingPoint = this.rangeVals.xMax!; if (numBins>15) numBins = 15; @@ -283,7 +284,7 @@ export class Histogram extends React.Component { .domain([startingPoint!, endingPoint!]) .thresholds(x.ticks(numBins)) var bins = histogram(data) - var eachRectWidth = width/(bins.length) + var eachRectWidth = width/(bins.length+1) var graphStartingPoint = bins[0].x1! - (bins[1].x1! - bins[1].x0!) bins[0].x0 = graphStartingPoint; x = x.domain([graphStartingPoint, endingPoint]) -- cgit v1.2.3-70-g09d2 From a6ff4b6481a4490351f715eed4f26b34cbbc5e03 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 12 Jul 2023 12:20:27 -0400 Subject: percentages --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 6047e6424..f9410a580 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -228,7 +228,7 @@ export class Histogram extends React.Component { }) var field = Object.keys(dataSet[0])[0] const data = validData.map((d: { [x: string]: any; }) => { - if (this.numericalData) { return +d[field].replace(/\$/g, '') } + if (this.numericalData) { return +d[field].replace(/\$/g, '').replace(/\%/g, '') } return d[field] }) return data; -- 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') 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 d20a4b3791c79325c709c42f797452275f2da312 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 12 Jul 2023 16:25:22 -0400 Subject: histogram bar width fix --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index f9410a580..e4267aee3 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -278,17 +278,17 @@ export class Histogram extends React.Component { .domain([0, numBins]) .range([0, width]); } - + var histogram = d3.histogram() .value(function(d) {return d}) .domain([startingPoint!, endingPoint!]) - .thresholds(x.ticks(numBins)) + .thresholds(x.ticks(numBins-1)) var bins = histogram(data) - var eachRectWidth = width/(bins.length+1) + var eachRectWidth = width/(bins.length) var graphStartingPoint = bins[0].x1! - (bins[1].x1! - bins[1].x0!) bins[0].x0 = graphStartingPoint; x = x.domain([graphStartingPoint, endingPoint]) - .range([0, width - eachRectWidth]) + .range([0, Number.isInteger(this.rangeVals.xMin!)? (width-eachRectWidth) : width ]) var xAxis; if (!this.numericalData) { // if the data is strings rather than numbers @@ -345,7 +345,6 @@ export class Histogram extends React.Component { .data(bins) .enter() .append("rect") - .attr("x", 1) .attr("transform", function(d) { return "translate(" + x(d.x0!) + "," + y(d.length) + ")"; }) .attr("width", eachRectWidth) .attr("height", function(d) { return height - y(d.length); }) -- 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') 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') 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 e1e74c5e35c5eafa6b4fadc4df2173ba09e235ec Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 12 Jul 2023 23:48:46 -0400 Subject: pie chart axis labels --- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 3ca49a46c..8f7fadfa5 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -228,7 +228,7 @@ export class PieChart extends React.Component { }) 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, '') } + if (this.numericalData) { return +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { //Generate groups var percentField = Object.keys(dataSet[0])[0] - var descriptionField = Object.keys(dataSet[0])[1] + var descriptionField = Object.keys(dataSet[0])[1]! var arcs = g.selectAll("arc") .data(pie(data)) .enter() @@ -273,12 +273,14 @@ export class PieChart extends React.Component { arcs.append("text") .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]}) + .text(function(d){ + return dataSet[data.indexOf(d.value)][percentField] + + (!descriptionField? '' : (' ' + dataSet[data.indexOf(d.value)][descriptionField]))}) }; render() { - + return ( this.props.axes.length >= 1 && (this.incomingSelected? this.incomingSelected.length>0 : true) ? (
-- cgit v1.2.3-70-g09d2 From 9cfd160f8e1b6caf4711bc034f9bed850eb4b12b Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 13 Jul 2023 12:00:32 -0400 Subject: pie chart readability --- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 8f7fadfa5..5b6bdc4cf 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -267,15 +267,18 @@ export class PieChart extends React.Component { arcs.append("path") .attr("fill", (data, i)=>{ let value=data.data; - return d3.schemeSet3[i]; + return d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12]; }) .attr("d", arc); arcs.append("text") - .attr("transform",function(d){ return "translate("+ (arc.centroid(d as unknown as d3.DefaultArcObject)) + ")"; }) + .attr("transform",function(d){ + var centroid = arc.centroid(d as unknown as d3.DefaultArcObject) + var heightOffset = (centroid[1]/radius) * Math.abs(centroid[1]) + return "translate("+ (centroid[0]+centroid[0]/(radius*.02)) + "," + (centroid[1]+heightOffset) + ")"; }) .attr("text-anchor", "middle") .text(function(d){ return dataSet[data.indexOf(d.value)][percentField] - + (!descriptionField? '' : (' ' + dataSet[data.indexOf(d.value)][descriptionField]))}) + + (!descriptionField? '' : (' - ' + dataSet[data.indexOf(d.value)][descriptionField]))}) }; -- cgit v1.2.3-70-g09d2 From 258d9f39bb2eb8b871ad01070eeab033b30c397b Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 14 Jul 2023 15:17:03 -0400 Subject: pie chart groupings --- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../nodes/DataVizBox/components/Histogram.tsx | 10 ++-- .../views/nodes/DataVizBox/components/PieChart.tsx | 62 +++++++++++++++------- 3 files changed, 50 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 35b3ca5d8..fc737c4b9 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -200,7 +200,7 @@ export class MarqueeView extends React.Component { this._disposers.chartData = reaction( () => ({ dataSet: this._histogramData, w: this.width, h: this.height }), ({ dataSet, w, h }) => { - if (dataSet) { + 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 @@ -226,10 +226,10 @@ export class Histogram extends React.Component { }) return valid; }) - var field = Object.keys(dataSet[0])[0] + var field = dataSet[0]? Object.keys(dataSet[0])[0]: undefined; const data = validData.map((d: { [x: string]: any; }) => { - if (this.numericalData) { return +d[field].replace(/\$/g, '').replace(/\%/g, '') } - return d[field] + if (this.numericalData) { return +d[field!].replace(/\$/g, '').replace(/\%/g, '') } + return d[field!] }) return data; } @@ -241,7 +241,7 @@ export class Histogram extends React.Component { var field = Object.keys(dataSet[0])[0] const data = this.data(dataSet); - let uniqueArr = [...new Set(data)] + let uniqueArr: unknown[] = [...new Set(data)] var numBins = uniqueArr.length var startingPoint = 0; var endingPoint = numBins; diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 5b6bdc4cf..7d532e790 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -37,7 +37,7 @@ 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; + private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios @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 @@ -45,7 +45,7 @@ export class PieChart extends React.Component { 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 } + 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')))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) @@ -53,7 +53,7 @@ export class PieChart extends React.Component { var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { this.numericalData = true;} + 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')))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) @@ -78,7 +78,7 @@ export class PieChart extends React.Component { .lastElement(); } @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { - if (this.numericalData){ + 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} } @@ -91,7 +91,7 @@ export class PieChart extends React.Component { this._disposers.chartData = reaction( () => ({ dataSet: this._piechartData, w: this.width, h: this.height }), ({ dataSet, w, h }) => { - if (dataSet) { + 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 @@ -226,10 +226,10 @@ export class PieChart extends React.Component { }) return valid; }) - var field = Object.keys(dataSet[0])[0] + var field = dataSet[0]? Object.keys(dataSet[0])[0] : undefined; const data = validData.map((d: { [x: string]: any; }) => { - if (this.numericalData) { return +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { .attr("transform", "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")"); - var data = this.data(this._piechartData); + var data = this.data(dataSet); + var pieDataSet = dataSet; + var pie = d3.pie(); var arc = d3.arc() .innerRadius(0) .outerRadius(radius); + + if (this.byCategory){ + let uniqueCategories = [...new Set(data)] + var pieStringDataSet: { frequency: any, label: any }[] = []; + for (let i=0; i each.label==data[i]) + sliceData[0].frequency = sliceData[0].frequency + 1; + } + pieDataSet = pieStringDataSet + data = this.data(pieStringDataSet) + } + var trackDuplicates : {[key: string]: any} = {}; + data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) - //Generate groups - var percentField = Object.keys(dataSet[0])[0] - var descriptionField = Object.keys(dataSet[0])[1]! + var percentField = Object.keys(pieDataSet[0])[0] + var descriptionField = Object.keys(pieDataSet[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]? d3.schemeSet3[i]: d3.schemeSet3[i%12]; - }) + .attr("fill", (data, i)=>{ return d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) .attr("d", arc); arcs.append("text") .attr("transform",function(d){ @@ -277,8 +291,20 @@ export class PieChart extends React.Component { return "translate("+ (centroid[0]+centroid[0]/(radius*.02)) + "," + (centroid[1]+heightOffset) + ")"; }) .attr("text-anchor", "middle") .text(function(d){ - return dataSet[data.indexOf(d.value)][percentField] - + (!descriptionField? '' : (' - ' + dataSet[data.indexOf(d.value)][descriptionField]))}) + 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; + } + return dataPoint[percentField]! + + (!descriptionField? '' : (' - ' + dataPoint[descriptionField]))!}) }; -- cgit v1.2.3-70-g09d2 From 84b3012d81b403c4a2a7e7517f4fdfe727464a57 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Mon, 17 Jul 2023 15:00:52 -0400 Subject: start of click on pie charts --- .../views/nodes/DataVizBox/components/Chart.scss | 6 +++ .../views/nodes/DataVizBox/components/PieChart.tsx | 46 +++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 05bb1655d..aa005ea66 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -8,6 +8,12 @@ .graph{ overflow: visible; } + .slice { + + } + .hover { + stroke: black; + } .tooltip { // make the height width bigger diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 7d532e790..0d3c74c32 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -252,7 +252,13 @@ export class PieChart extends React.Component { "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")"); var data = this.data(dataSet); - var pieDataSet = dataSet; + var pieDataSet = 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 pie = d3.pie(); var arc = d3.arc() @@ -275,6 +281,40 @@ export class PieChart extends React.Component { var trackDuplicates : {[key: string]: any} = {}; data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) + const onPointClick = action((e: any) => { + // check the 4 'corners' of each slice and see if the pointer is within those bounds to get the slice the user clicked on + const pointer = d3.pointer(e); + var selectedSlice; + var index = -1; + 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)]; + + // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge + var lineCrossCount = 0; + // if for all 4 lines + if (Math.min(p1[1], p2[1])<=pointer[1] && pointer[1]<=Math.max(p1[1], p2[1])){ // within y bounds + if (pointer[0] <= (pointer[1]-p1[1])*(p2[0]-p1[0])/(p2[1]-p1[1])+p1[0]) lineCrossCount++; } // intercepts x + if (Math.min(p2[1], p3[1])<=pointer[1] && pointer[1]<=Math.max(p2[1], p3[1])){ + if (pointer[0] <= (pointer[1]-p2[1])*(p3[0]-p2[0])/(p3[1]-p2[1])+p2[0]) lineCrossCount++; } + if (Math.min(p3[1], p4[1])<=pointer[1] && pointer[1]<=Math.max(p3[1], p4[1])){ + 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) { + selectedSlice = pieDataSet[index]; + return true; + } + return false; + }); + console.log('selectedSlice', selectedSlice) + + selected.attr('class')=='slice hover'? selected.attr('class', 'slice'): selected.attr('class', 'slice hover') + }); + var percentField = Object.keys(pieDataSet[0])[0] var descriptionField = Object.keys(pieDataSet[0])[1]! var arcs = g.selectAll("arc") @@ -283,7 +323,9 @@ export class PieChart extends React.Component { .append("g") arcs.append("path") .attr("fill", (data, i)=>{ return d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) - .attr("d", arc); + .attr("class", `${pieDataSet[0][percentField]} slice`) + .attr("d", arc) + .on('click', onPointClick) arcs.append("text") .attr("transform",function(d){ var centroid = arc.centroid(d as unknown as d3.DefaultArcObject) -- cgit v1.2.3-70-g09d2 From 65ab8c985a86f18ebb51a269f20d7865dcfc6589 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 18 Jul 2023 10:50:16 -0400 Subject: click on histograms --- .../views/nodes/DataVizBox/components/Chart.scss | 15 +++++++++++---- .../views/nodes/DataVizBox/components/Histogram.tsx | 19 ++++++++++++++++--- .../views/nodes/DataVizBox/components/TableBox.tsx | 5 +++-- 3 files changed, 30 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index aa005ea66..6c3d59879 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -9,10 +9,17 @@ overflow: visible; } .slice { - - } - .hover { - stroke: black; + &.hover { + stroke: black; + } + } + + .histogram-bar{ + outline: thin solid black; + fill: #69b3a2; + &.hover{ + fill: grey; + } } .tooltip { diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 7a866d742..34fc9ce82 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -315,7 +315,7 @@ export class Histogram extends React.Component { } const maxFrequency = d3.max(bins, function(d) { return d.length; }) - + var y = d3.scaleLinear() .range([height, 0]); y.domain([0, maxFrequency!]); @@ -327,6 +327,20 @@ export class Histogram extends React.Component { .attr("transform", "translate(" + translateXAxis + ", " + height + ")") .call(xAxis) + const onPointClick = action((e: any) => { + var pointerX = d3.pointer(e)[0]; + const selected = svg.selectAll('.histogram-bar').filter((d: any) => { + if ((d.x0*eachRectWidth ) <= pointerX && pointerX <= (d.x1*eachRectWidth )){ + console.log(d) + return true + } + return false; + }); + selected.attr('class')=='histogram-bar hover'? selected.attr('class', 'histogram-bar'): selected.attr('class', 'histogram-bar hover') + }); + + svg.on('click', onPointClick); + // axis titles svg.append("text") .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") @@ -348,8 +362,7 @@ export class Histogram extends React.Component { .attr("transform", function(d) { return "translate(" + x(d.x0!) + "," + y(d.length) + ")"; }) .attr("width", eachRectWidth) .attr("height", function(d) { return height - y(d.length); }) - .attr("style", "outline: thin solid black;") - .style("fill", "#69b3a2") + .attr("class", 'histogram-bar') }; render() { diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index d84e34d52..8c8264861 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -33,6 +33,7 @@ export class TableBox extends React.Component { const header = React.createRef(); return ( { {this.props.pairs?.map((p, i) => { return ( - (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}> + (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}> {this.columns.map(col => ( - {p[col]} + {p[col]} ))} ); -- cgit v1.2.3-70-g09d2 From 51718316b592e86c0009b7a27e1e32ba74d2488b Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 18 Jul 2023 13:22:04 -0400 Subject: click to select for pie charts + some histograms --- src/client/views/nodes/DataVizBox/DataVizBox.scss | 2 +- .../views/nodes/DataVizBox/components/Chart.scss | 4 +++ .../nodes/DataVizBox/components/Histogram.tsx | 39 +++++++++++++++++++--- .../nodes/DataVizBox/components/LineChart.tsx | 2 +- .../views/nodes/DataVizBox/components/PieChart.tsx | 32 ++++++++++++++---- 5 files changed, 65 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index 424f8b0f1..b3cbc89aa 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -1,5 +1,5 @@ .dataviz { - overflow: auto; + overflow: hidden; height: 100%; .datatype-button{ diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 6c3d59879..808300c2c 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -56,4 +56,8 @@ // change the color of the circle element to be red fill: red; } + + .selected-data{ + text-align: center; + } } diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 34fc9ce82..b8be9bd13 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -38,7 +38,7 @@ export class Histogram extends React.Component { private _histogramRef: React.RefObject = React.createRef(); private _histogramSvg: d3.Selection | undefined; private numericalData: boolean = false; - @observable _currSelected: SelectedDataPoint | undefined = undefined; + @observable _currSelected: any | 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 _histogramData() { @@ -329,14 +329,33 @@ export class Histogram extends React.Component { const onPointClick = action((e: any) => { var pointerX = d3.pointer(e)[0]; + var index = -1; + var sameAsCurrent: boolean; const selected = svg.selectAll('.histogram-bar').filter((d: any) => { - if ((d.x0*eachRectWidth ) <= pointerX && pointerX <= (d.x1*eachRectWidth )){ - console.log(d) + index++; + var left = this.numericalData? d.x0-1: d.x0; + var right = (this.numericalData && d.x0!=d.x1)? d.x1-1: d.x1; + if ((left*eachRectWidth ) <= pointerX && pointerX <= (right*eachRectWidth )){ + // var showSelected = !this.numericalData? dataSet[index] : this.props.pairs[index]; + var showSelected = dataSet[index] + showSelected['frequency'] = d.length; + console.log('showSelected', showSelected) + console.log('current', this._currSelected) + 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]]) + : false; + this._currSelected = sameAsCurrent? undefined: showSelected; return true } return false; }); - selected.attr('class')=='histogram-bar hover'? selected.attr('class', 'histogram-bar'): selected.attr('class', 'histogram-bar hover') + // selected.attr('class')=='histogram-bar hover'? selected.attr('class', 'histogram-bar'): selected.attr('class', 'histogram-bar hover') + const elements = document.querySelectorAll('.histogram-bar'); + for (let i = 0; i < elements.length; i++) { + elements[i].classList.remove('hover'); + } + if (!sameAsCurrent!) selected.attr('class', 'histogram-bar hover'); }); svg.on('click', onPointClick); @@ -367,10 +386,20 @@ export class Histogram extends React.Component { render() { + var selected: string; + if (this._currSelected){ + selected = '{ '; + Object.keys(this._currSelected).map(key => { + key!=''? selected += key + ': ' + this._currSelected[key] + ', ': ''; + }) + selected = selected.substring(0, selected.length-2); + selected += ' }'; + } + else selected = 'none'; return ( this.props.axes.length >= 1 && (this.incomingSelected? this.incomingSelected.length>0 : true) ? (
- {`Selected: ${Object.keys(this._histogramData[0])[0]}`} + {`Selected: ${selected}`}
) : {'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 dfb9f54c1..cb6ba6fe7 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -339,7 +339,7 @@ export class LineChart extends React.Component { const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return (
- {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Selected: ${selectedPt}`} + {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Selected: ${selectedPt}`}
); } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 0d3c74c32..6241e6221 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -38,7 +38,7 @@ 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: SelectedDataPoint | undefined = undefined; + @observable _currSelected: any | 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() { @@ -284,8 +284,8 @@ export class PieChart extends React.Component { const onPointClick = action((e: any) => { // check the 4 'corners' of each slice and see if the pointer is within those bounds to get the slice the user clicked on const pointer = d3.pointer(e); - var selectedSlice; var index = -1; + var sameAsCurrent: boolean; const selected = svg.selectAll('.slice').filter((d: any) => { index++; var p1 = [0,0]; @@ -305,14 +305,22 @@ 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) { - selectedSlice = pieDataSet[index]; + var showSelected = this.byCategory? pieDataSet[index] : this.props.pairs[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]]) + : + this._currSelected===showSelected; + this._currSelected = sameAsCurrent? undefined: showSelected; return true; } return false; }); - console.log('selectedSlice', selectedSlice) - - selected.attr('class')=='slice hover'? selected.attr('class', 'slice'): selected.attr('class', 'slice hover') + const elements = document.querySelectorAll('.slice'); + for (let i = 0; i < elements.length; i++) { + elements[i].classList.remove('hover'); + } + if (!sameAsCurrent!) selected.attr('class', 'slice hover'); }); var percentField = Object.keys(pieDataSet[0])[0] @@ -352,10 +360,20 @@ export class PieChart extends React.Component { render() { + var selected: string; + if (this._currSelected){ + selected = '{ '; + Object.keys(this._currSelected).map(key => { + key!=''? selected += key + ': ' + this._currSelected[key] + ', ': ''; + }) + selected = selected.substring(0, selected.length-2); + selected += ' }'; + } + else selected = 'none'; return ( this.props.axes.length >= 1 && (this.incomingSelected? this.incomingSelected.length>0 : true) ? (
- {`Selected: ${Object.keys(this._piechartData[0])[0]}`} + {`Selected: ${selected}`}
) : {'first use table view to select a column to graph'} ); -- cgit v1.2.3-70-g09d2 From 900efdb5c08397e53e1d00555fc6eac208eff5a1 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 18 Jul 2023 16:24:57 -0400 Subject: brushed graph has can have multiple links + still link to the correct graph for data --- src/client/views/nodes/DataVizBox/DataVizBox.scss | 1 + src/client/views/nodes/DataVizBox/components/LineChart.tsx | 4 ++-- src/client/views/nodes/DataVizBox/components/TableBox.tsx | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index b3cbc89aa..5e7230271 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -1,6 +1,7 @@ .dataviz { overflow: hidden; height: 100%; + width: 100%; .datatype-button{ margin: 0; diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index cb6ba6fe7..637ed0ead 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -1,7 +1,6 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -// import d3 import * as d3 from 'd3'; import { Doc, DocListCast } from '../../../../../fields/Doc'; import { Id } from '../../../../../fields/FieldSymbols'; @@ -56,7 +55,8 @@ export class LineChart 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/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 8c8264861..ff43f67d9 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -51,6 +51,7 @@ export class TableBox extends React.Component { 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; }; -- cgit v1.2.3-70-g09d2 From 27441b60e7af8ea93f277ab3bdc2990ad25ea326 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 19 Jul 2023 19:03:59 -0400 Subject: fixed showing of link lines for data visualization views. --- src/client/views/nodes/DataVizBox/components/TableBox.tsx | 5 ++++- src/client/views/nodes/DocumentView.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index ff43f67d9..ca888e13f 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -60,6 +60,7 @@ export class TableBox extends React.Component { 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; } @@ -94,7 +95,9 @@ export class TableBox extends React.Component { return ( (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}> {this.columns.map(col => ( - {p[col]} + + {p[col]} + ))} ); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d7b4ca317..4211b8a81 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -975,8 +975,8 @@ export class DocumentViewInternal extends DocComponent - Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.rootDoc) || - Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.rootDoc) || + (link.link_matchEmbeddings ? link.link_anchor_1 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.rootDoc)) || + (link.link_matchEmbeddings ? link.link_anchor_2 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.rootDoc)) || ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.rootDoc)) || ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.rootDoc)) ); @@ -1237,7 +1237,7 @@ export class DocumentViewInternal extends DocComponent { return this.docView?._componentView; } get allLinks() { - return this.docView?.allLinks || []; + return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.rootDoc || link.link_anchor_2 === this.rootDoc); } get LayoutFieldKey() { return this.docView?.LayoutFieldKey || 'layout'; -- 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') 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 27bf26fe688baabff93ab6ebb6b75af4043d29d3 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 21 Jul 2023 11:34:51 -0400 Subject: brushed lightbox fix --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 2 +- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 01e6709fa..c07c85f7e 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -398,7 +398,7 @@ export class Histogram extends React.Component { } else selected = 'none'; return ( - this.props.axes.length >= 1 && (this.incomingSelected? this.incomingSelected.length>0 : true) ? ( + this.props.axes.length >= 1 ? (
{`Selected: ${selected}`}
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 05a2f1588..37c435411 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -371,7 +371,7 @@ export class PieChart extends React.Component { } else selected = 'none'; return ( - this.props.axes.length >= 1 && (this.incomingSelected? this.incomingSelected.length>0 : true) ? ( + this.props.axes.length >= 1 ? (
{`Selected: ${selected}`}
-- cgit v1.2.3-70-g09d2 From 3a177316c1f104da538ed4a53c6a442c6f3fcdc4 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 26 Jul 2023 16:27:12 -0400 Subject: histogram reorganized to accommodate more data types --- src/client/documents/Documents.ts | 2 +- .../nodes/DataVizBox/components/Histogram.tsx | 134 ++++++++++++--------- .../nodes/DataVizBox/components/LineChart.tsx | 2 +- .../views/nodes/DataVizBox/components/PieChart.tsx | 13 +- 4 files changed, 86 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5ef033e35..426eaa14d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1143,7 +1143,7 @@ export namespace Docs { } export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc); + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', type: 'dataviz', ...options }, undefined, undefined, undefined, overwriteDoc); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index c07c85f7e..5fbe92563 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -38,6 +38,9 @@ export class Histogram extends React.Component { private _histogramRef: React.RefObject = React.createRef(); private _histogramSvg: d3.Selection | undefined; private numericalData: boolean = false; + 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; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @@ -45,7 +48,7 @@ export class Histogram extends React.Component { 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 } + 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')))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]])})) @@ -53,7 +56,8 @@ export class Histogram extends React.Component { var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { this.numericalData = true;} + 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')))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) @@ -79,7 +83,7 @@ export class Histogram extends React.Component { .lastElement(); } @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { - if (this.numericalData){ + if (this.numericalXData){ const data = this.data(this._histogramData); return {xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin:0, yMax:0} } @@ -182,7 +186,7 @@ export class Histogram extends React.Component { getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ // - title: 'line doc selection' + this._currSelected?.x, + title: 'histogram 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; @@ -227,9 +231,9 @@ export class Histogram extends React.Component { }) return valid; }) - var field = dataSet[0]? Object.keys(dataSet[0])[0]: undefined; + var field = dataSet[0]? Object.keys(dataSet[0])[0] : undefined; const data = validData.map((d: { [x: string]: any; }) => { - if (this.numericalData) { return +d[field!].replace(/\$/g, '').replace(/\%/g, '') } + if (this.numericalXData) { return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { d3.select(this._histogramRef.current).select('svg').remove(); d3.select(this._histogramRef.current).select('.tooltip').remove(); - var field = Object.keys(dataSet[0])[0] - const data = this.data(dataSet); + var data = this.data(dataSet); let uniqueArr: unknown[] = [...new Set(data)] - var numBins = uniqueArr.length - var startingPoint = 0; - var endingPoint = numBins; - var translateXAxis = 0; - if (this.numericalData) { - if (Number.isInteger(this.rangeVals.xMin!)){ - numBins = this.rangeVals.xMax! - this.rangeVals.xMin! + 1; + var numBins = this.numericalXData? (this.rangeVals.xMax! - this.rangeVals.xMin! + 1) : uniqueArr.length + var translateXAxis = !this.numericalXData || numBinsthis.maxBins) numBins = this.maxBins; + var startingPoint = this.numericalXData? this.rangeVals.xMin! : 0; + var endingPoint = this.numericalXData? this.rangeVals.xMax! : numBins; + var xAxisTitle = Object.keys(dataSet[0])[0] + var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency'; + var histDataSet = 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; + }); + if (!this.numericalXData) { + var histStringDataSet: { frequency: any, label: any }[] = []; + if (this.numericalYData){ + for (let i=0; i each.label==data[i]) + sliceData[0].frequency = sliceData[0].frequency + 1; + } } - startingPoint = this.rangeVals.xMin!; - endingPoint = this.rangeVals.xMax!; - if (numBins>15) numBins = 15; - else translateXAxis = width/(numBins+1) / 2; + histDataSet = histStringDataSet } - else translateXAxis = width/(numBins+1) / 2; - const svg = (this._histogramSvg = d3 + var svg = (this._histogramSvg = d3 .select(this._histogramRef.current) .append("svg") .attr("class", "graph") @@ -268,32 +289,24 @@ export class Histogram extends React.Component { .attr("transform", "translate(" + this.props.margin.left + "," + this.props.margin.top + ")")); - var x: any; - if (this.numericalData){ - x = d3.scaleLinear() - .domain([startingPoint!, endingPoint!]) + var x = d3.scaleLinear() + .domain(this.numericalXData? [startingPoint!, endingPoint!] : [0, numBins]) .range([0, width ]); - } - else { - x = d3.scaleLinear() - .domain([0, numBins]) - .range([0, width]); - } - var histogram = d3.histogram() .value(function(d) {return d}) .domain([startingPoint!, endingPoint!]) .thresholds(x.ticks(numBins-1)) var bins = histogram(data) var eachRectWidth = width/(bins.length) - var graphStartingPoint = bins[0].x1! - (bins[1].x1! - bins[1].x0!) + var graphStartingPoint = bins[0].x1? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0; bins[0].x0 = graphStartingPoint; x = x.domain([graphStartingPoint, endingPoint]) .range([0, Number.isInteger(this.rangeVals.xMin!)? (width-eachRectWidth) : width ]) var xAxis; - if (!this.numericalData) { // if the data is strings rather than numbers - uniqueArr.sort() + if (!this.numericalXData) { // reorganize if the data is strings rather than numbers + // uniqueArr.sort() + histDataSet.sort() for (let i=0; i { } bins[index].push(data[i]) } + bins.pop(); + eachRectWidth = width/(bins.length) bins.forEach(d => d.x0 = d.x0!) xAxis = d3.axisBottom(x) - .ticks(numBins-1) + .ticks(bins.length-1) .tickFormat( i => uniqueArr[i]) .tickPadding(10) + x.range([0, width-eachRectWidth]) + x.domain([0, bins.length-1]) translateXAxis = eachRectWidth / 2; } else { xAxis = d3.axisBottom(x) .ticks(numBins-1) } - - const maxFrequency = d3.max(bins, function(d) { return d.length; }) + const maxFrequency = this.numericalYData? d3.max(histDataSet, function(d) {return d.frequency.replace(/\$/g, '').replace(/\%/g, '').replace(/\ { var left = this.numericalData? d.x0-1: d.x0; var right = (this.numericalData && d.x0!=d.x1)? d.x1-1: d.x1; if ((left*eachRectWidth ) <= pointerX && pointerX <= (right*eachRectWidth )){ - // var showSelected = !this.numericalData? dataSet[index] : this.props.pairs[index]; - var showSelected = dataSet[index] - showSelected['frequency'] = d.length; - console.log('showSelected', showSelected) - console.log('current', this._currSelected) + var showSelected = histDataSet[index] + var selectedDisplay = {[xAxisTitle]: showSelected.label, [yAxisTitle]: showSelected.frequency} + // showSelected['frequency'] = d.length; 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]]) + (selectedDisplay[xAxisTitle]==this._currSelected![xAxisTitle] + && selectedDisplay[yAxisTitle]==this._currSelected![yAxisTitle]) : false; - this._currSelected = sameAsCurrent? undefined: showSelected; + this._currSelected = sameAsCurrent? undefined: selectedDisplay; return true } return false; @@ -358,30 +373,37 @@ export class Histogram extends React.Component { } if (!sameAsCurrent!) selected.attr('class', 'histogram-bar hover'); }); - svg.on('click', onPointClick); - // axis titles svg.append("text") .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") .style("text-anchor", "middle") - .text(field); + .text(xAxisTitle); svg.append("text") - .attr("transform", "rotate(-90)") + .attr("transform", "rotate(-90)" + " " + "translate( 0, " + -10 + ")") .attr("x", -(height/2)) .attr("y", -20) .style("text-anchor", "middle") - .text('frequency'); - + .text(yAxisTitle); d3.format('.0f') - svg.selectAll("rect") .data(bins) .enter() .append("rect") - .attr("transform", function(d) { return "translate(" + x(d.x0!) + "," + y(d.length) + ")"; }) + .attr("transform", this.numericalYData? + function (d) { + var eachData = histDataSet.filter((data: { label: number; }) => {return data.label==d[0]}) + var length = eachData[0].frequency.replace(/\$/g, '').replace(/\%/g, '').replace(/\ {return data.label==d[0]}) + var length = eachData[0].frequency.replace(/\$/g, '').replace(/\%/g, '').replace(/\ { .style("text-anchor", "middle") .text(this.props.axes[0]); svg.append("text") - .attr("transform", "rotate(-90)") + .attr("transform", "rotate(-90)" + " " + "translate( 0, " + -10 + ")") .attr("x", -(height/2)) .attr("y", -20) .attr("height", 20) diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 37c435411..fc24e5821 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -182,7 +182,7 @@ export class PieChart extends React.Component { getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ // - title: 'line doc selection' + this._currSelected?.x, + title: 'piechart 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; @@ -260,12 +260,6 @@ export class PieChart extends React.Component { }) return valid; }); - - var pie = d3.pie(); - var arc = d3.arc() - .innerRadius(0) - .outerRadius(radius); - if (this.byCategory){ let uniqueCategories = [...new Set(data)] var pieStringDataSet: { frequency: any, label: any }[] = []; @@ -282,6 +276,11 @@ export class PieChart extends React.Component { var trackDuplicates : {[key: string]: any} = {}; data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) + var pie = d3.pie(); + var arc = d3.arc() + .innerRadius(0) + .outerRadius(radius); + const onPointClick = action((e: any) => { // check the 4 'corners' of each slice and see if the pointer is within those bounds to get the slice the user clicked on const pointer = d3.pointer(e); -- cgit v1.2.3-70-g09d2 From 16d543ae2c2c3b2beca593db7ec503af88ee727b Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 26 Jul 2023 23:09:28 -0400 Subject: clicking on histogram data fix --- .../nodes/DataVizBox/components/Histogram.tsx | 38 ++++++++++------------ 1 file changed, 17 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 5fbe92563..7b9eba1cd 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -37,7 +37,6 @@ export class Histogram extends React.Component { private _disposers: { [key: string]: IReactionDisposer } = {}; private _histogramRef: React.RefObject = React.createRef(); private _histogramSvg: d3.Selection | undefined; - private numericalData: boolean = false; 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 @@ -261,19 +260,19 @@ export class Histogram extends React.Component { return valid; }); if (!this.numericalXData) { - var histStringDataSet: { frequency: any, label: any }[] = []; + var histStringDataSet: { [x: string]: unknown; }[] = []; if (this.numericalYData){ for (let i=0; i each.label==data[i]) - sliceData[0].frequency = sliceData[0].frequency + 1; + let sliceData = histStringDataSet.filter(each => each[xAxisTitle]==data[i]) + sliceData[0][yAxisTitle] = sliceData[0][yAxisTitle] + 1; } } histDataSet = histStringDataSet @@ -331,7 +330,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.frequency.replace(/\$/g, '').replace(/\%/g, '').replace(/\ { const onPointClick = action((e: any) => { var pointerX = d3.pointer(e)[0]; - var index = -1; var sameAsCurrent: boolean; const selected = svg.selectAll('.histogram-bar').filter((d: any) => { - index++; - var left = this.numericalData? d.x0-1: d.x0; - var right = (this.numericalData && d.x0!=d.x1)? d.x1-1: d.x1; - if ((left*eachRectWidth ) <= pointerX && pointerX <= (right*eachRectWidth )){ - var showSelected = histDataSet[index] - var selectedDisplay = {[xAxisTitle]: showSelected.label, [yAxisTitle]: showSelected.frequency} - // showSelected['frequency'] = d.length; + var left = this.numericalXData? d.x0-1: d.x0; + var right = (this.numericalXData && d.x0!=d.x1)? d.x1-1: d.x1; + if ((left*eachRectWidth ) <= pointerX && pointerX <= (right*eachRectWidth)){ + var showSelected = histDataSet.filter((data: { [x: string]: any; }) => data[xAxisTitle]==d[0])[0]; + var selectedDisplay = {[xAxisTitle]: showSelected[xAxisTitle], [yAxisTitle]: this.numericalXData? d.length : showSelected[yAxisTitle]} sameAsCurrent = this._currSelected? (selectedDisplay[xAxisTitle]==this._currSelected![xAxisTitle] && selectedDisplay[yAxisTitle]==this._currSelected![yAxisTitle]) @@ -366,7 +362,6 @@ export class Histogram extends React.Component { } return false; }); - // selected.attr('class')=='histogram-bar hover'? selected.attr('class', 'histogram-bar'): selected.attr('class', 'histogram-bar hover') const elements = document.querySelectorAll('.histogram-bar'); for (let i = 0; i < elements.length; i++) { elements[i].classList.remove('hover'); @@ -392,17 +387,18 @@ export class Histogram extends React.Component { .append("rect") .attr("transform", this.numericalYData? function (d) { - var eachData = histDataSet.filter((data: { label: number; }) => {return data.label==d[0]}) - var length = eachData[0].frequency.replace(/\$/g, '').replace(/\%/g, '').replace(/\ {return data[xAxisTitle]==d[0]}) + var length = eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ {return data.label==d[0]}) - var length = eachData[0].frequency.replace(/\$/g, '').replace(/\%/g, '').replace(/\ {return data[xAxisTitle]==d[0]}) + var length = eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ Date: Thu, 27 Jul 2023 11:28:43 -0400 Subject: more info on selected histogram bars + better labels for selected pie chart slices --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 9 ++++----- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 12 +++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 7b9eba1cd..c9cd49aa1 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -351,13 +351,12 @@ export class Histogram extends React.Component { var left = this.numericalXData? d.x0-1: d.x0; var right = (this.numericalXData && d.x0!=d.x1)? d.x1-1: d.x1; if ((left*eachRectWidth ) <= pointerX && pointerX <= (right*eachRectWidth)){ - var showSelected = histDataSet.filter((data: { [x: string]: any; }) => data[xAxisTitle]==d[0])[0]; - var selectedDisplay = {[xAxisTitle]: showSelected[xAxisTitle], [yAxisTitle]: this.numericalXData? d.length : showSelected[yAxisTitle]} + var showSelected = this.numericalYData? this.props.pairs.filter((data: { [x: string]: any; }) => data[xAxisTitle]==d[0])[0] : histDataSet.filter((data: { [x: string]: any; }) => data[xAxisTitle]==d[0])[0]; sameAsCurrent = this._currSelected? - (selectedDisplay[xAxisTitle]==this._currSelected![xAxisTitle] - && selectedDisplay[yAxisTitle]==this._currSelected![yAxisTitle]) + (showSelected[xAxisTitle]==this._currSelected![xAxisTitle] + && showSelected[yAxisTitle]==this._currSelected![yAxisTitle]) : false; - this._currSelected = sameAsCurrent? undefined: selectedDisplay; + this._currSelected = sameAsCurrent? undefined: showSelected; return true } return false; diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index fc24e5821..cead40d92 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -239,6 +239,8 @@ export class PieChart extends React.Component { d3.select(this._piechartRef.current).select('svg').remove(); d3.select(this._piechartRef.current).select('.tooltip').remove(); + 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 svg = (this._piechartSvg = d3 .select(this._piechartRef.current) @@ -262,15 +264,17 @@ export class PieChart extends React.Component { }); if (this.byCategory){ let uniqueCategories = [...new Set(data)] - var pieStringDataSet: { frequency: any, label: any }[] = []; + var pieStringDataSet: { frequency: number, [percentField]: string }[] = []; for (let i=0; i each.label==data[i]) + let sliceData = pieStringDataSet.filter(each => each[percentField]==data[i]) sliceData[0].frequency = sliceData[0].frequency + 1; } pieDataSet = pieStringDataSet + percentField = Object.keys(pieDataSet[0])[0] + descriptionField = Object.keys(pieDataSet[0])[1]! data = this.data(pieStringDataSet) } var trackDuplicates : {[key: string]: any} = {}; @@ -323,8 +327,6 @@ export class PieChart extends React.Component { if (!sameAsCurrent!) selected.attr('class', 'slice hover'); }); - var percentField = Object.keys(pieDataSet[0])[0] - var descriptionField = Object.keys(pieDataSet[0])[1]! var arcs = g.selectAll("arc") .data(pie(data)) .enter() -- cgit v1.2.3-70-g09d2 From ad6cbd1e4abf97cff81d160b3b3afa0bc9b8c204 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 27 Jul 2023 13:55:56 -0400 Subject: titles --- .../views/nodes/DataVizBox/components/Chart.scss | 11 +++++++---- .../views/nodes/DataVizBox/components/Histogram.tsx | 14 ++++++++++++-- .../views/nodes/DataVizBox/components/LineChart.tsx | 11 +++++++++-- .../views/nodes/DataVizBox/components/PieChart.tsx | 18 ++++++++++++++---- 4 files changed, 42 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 808300c2c..5945840b5 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -8,6 +8,13 @@ .graph{ overflow: visible; } + .graph-title{ + align-items: center; + font-size: larger; + } + .selected-data{ + align-items: center; + } .slice { &.hover { stroke: black; @@ -56,8 +63,4 @@ // change the color of the circle element to be red fill: red; } - - .selected-data{ - text-align: center; - } } diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index c9cd49aa1..479f6584c 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -66,6 +66,14 @@ 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(){ + 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 + " Histogram"; + } + else return ax1 + " by " + ax0 + " Histogram"; + } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links .filter(link => { @@ -416,8 +424,10 @@ export class Histogram extends React.Component { else selected = 'none'; return ( this.props.axes.length >= 1 ? ( -
- {`Selected: ${selected}`} +
+
{this.graphTitle}
+
{`Selected: ${selected}`}
+
) : {'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 91baf095d..da79df476 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -53,6 +53,9 @@ export class LineChart 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(){ + return this.props.axes[1] + " vs. " + this.props.axes[0] + " Line Chart"; + } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links .filter(link => { @@ -338,9 +341,13 @@ export class LineChart extends React.Component { render() { const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return ( -
- {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Selected: ${selectedPt}`} + this.props.axes.length >= 2 ? ( +
+
{this.graphTitle}
+
{`Selected: ${selectedPt}`}
+
+ ) : {'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 cead40d92..27653b847 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -53,7 +53,7 @@ export class PieChart extends React.Component { var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { this.byCategory = false;} + 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')))) .map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[this.props.axes[1]]) })) @@ -63,6 +63,14 @@ 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(){ + 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 + " Pie Chart"; + } + else return ax1 + " by " + ax0 + " Pie Chart"; + } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links .filter(link => { @@ -333,7 +341,7 @@ export class PieChart extends React.Component { .append("g") arcs.append("path") .attr("fill", (data, i)=>{ return d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%12] }) - .attr("class", `${pieDataSet[0][percentField]} slice`) + .attr("class", 'slice') .attr("d", arc) .on('click', onPointClick) arcs.append("text") @@ -373,8 +381,10 @@ export class PieChart extends React.Component { else selected = 'none'; return ( this.props.axes.length >= 1 ? ( -
- {`Selected: ${selected}`} +
+
{this.graphTitle}
+
{`Selected: ${selected}`}
+
) : {'first use table view to select a column to graph'} ); -- cgit v1.2.3-70-g09d2 From 7bdc4799c2546de168cc74d536463d898673afc6 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 27 Jul 2023 15:19:14 -0400 Subject: numerical histogram fix --- .../nodes/DataVizBox/components/Histogram.tsx | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 479f6584c..fdde29c81 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -252,14 +252,14 @@ export class Histogram extends React.Component { 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)] - var numBins = this.numericalXData? (this.rangeVals.xMax! - this.rangeVals.xMin! + 1) : uniqueArr.length + var numBins = (this.numericalXData && Number.isInteger(data[0][xAxisTitle]))? (this.rangeVals.xMax! - this.rangeVals.xMin! + 1) : uniqueArr.length var translateXAxis = !this.numericalXData || numBinsthis.maxBins) numBins = this.maxBins; var startingPoint = this.numericalXData? this.rangeVals.xMin! : 0; var endingPoint = this.numericalXData? this.rangeVals.xMax! : numBins; - var xAxisTitle = Object.keys(dataSet[0])[0] - var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency'; var histDataSet = dataSet.filter((d: { [x: string]: unknown; }) => { var valid = true; Object.keys(dataSet[0]).map(key => { @@ -351,15 +351,22 @@ export class Histogram extends React.Component { svg.append("g") .attr("transform", "translate(" + translateXAxis + ", " + height + ")") .call(xAxis) - + const onPointClick = action((e: any) => { var pointerX = d3.pointer(e)[0]; var sameAsCurrent: boolean; + var barCounter = -1; const selected = svg.selectAll('.histogram-bar').filter((d: any) => { - var left = this.numericalXData? d.x0-1: d.x0; - var right = (this.numericalXData && d.x0!=d.x1)? d.x1-1: d.x1; - if ((left*eachRectWidth ) <= pointerX && pointerX <= (right*eachRectWidth)){ - var showSelected = this.numericalYData? this.props.pairs.filter((data: { [x: string]: any; }) => data[xAxisTitle]==d[0])[0] : histDataSet.filter((data: { [x: string]: any; }) => data[xAxisTitle]==d[0])[0]; + 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(/\ Date: Thu, 27 Jul 2023 15:23:00 -0400 Subject: merge --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 7e574e839..ba8585749 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -170,7 +170,7 @@ export class MarqueeView extends React.Component { const text: string = await navigator.clipboard.readText(); @@ -178,7 +178,7 @@ export class MarqueeView extends React.Component 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') 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') 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') 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: Mon, 31 Jul 2023 17:18:48 -0400 Subject: import table on windows --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ba8585749..158588e8a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -170,7 +170,7 @@ export class MarqueeView extends React.Component { const text: string = await navigator.clipboard.readText(); -- cgit v1.2.3-70-g09d2 From 710cb3aa93ea30799479ca7c79444f05aeab2209 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Mon, 31 Jul 2023 18:49:44 -0400 Subject: TableBox editable headers start --- .../nodes/DataVizBox/components/Histogram.tsx | 23 ++--- .../views/nodes/DataVizBox/components/PieChart.tsx | 24 ++--- .../views/nodes/DataVizBox/components/TableBox.tsx | 108 ++++++++++++++++++++- 3 files changed, 125 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 6d0a8bf75..64e61fca8 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -443,21 +443,8 @@ 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() { - + const title = this.graphTitle; var selected: string; if (this._currSelected){ selected = '{ '; @@ -472,7 +459,13 @@ export class Histogram extends React.Component { this.props.axes.length >= 1 ? (
- {this.editableTitle} + this.graphTitle = val as string)} + color={"black"} + size={Size.LARGE} + fillWidth + />     { 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() { + const title = this.graphTitle; var selected: string; if (this._currSelected){ selected = '{ '; @@ -409,7 +397,15 @@ export class PieChart extends React.Component { return ( this.props.axes.length >= 1 ? (
-
{this.editableTitle}
+
+ this.graphTitle = val as string)} + color={"black"} + size={Size.LARGE} + fillWidth + /> +
{selected != 'none' ?
Selected: {selected} diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 500e7b639..aaedba202 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,4 +1,4 @@ -import { action, computed } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../../../fields/Doc'; @@ -10,6 +10,7 @@ import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; import { LinkManager } from '../../../../util/LinkManager'; import { DocCast } from '../../../../../fields/Types'; +import { EditableText, Size, Type } from 'browndash-components'; interface TableBoxProps { rootDoc: Doc; @@ -21,6 +22,8 @@ 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; @@ -39,6 +42,106 @@ export class TableBox extends React.Component { return this._tableData.length ? Array.from(Object.keys(this._tableData[0])) : []; } + // 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 (
@@ -125,4 +228,7 @@ export class TableBox extends React.Component {
); } + + + } -- cgit v1.2.3-70-g09d2 From 591533a40c847f84e23428ab757b8822edbc2a61 Mon Sep 17 00:00:00 2001 From: srichman333 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') 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) => { - 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)? - - : - ))} - - ); + var containsData = false; + 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); + 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)? + + : + ))} + + ); + } })}
{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') 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') 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 e94534bc9f9dd44980e7574b860ea0b7912371bd Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 2 Aug 2023 13:00:18 -0400 Subject: linking default histogram color bug fix --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index e5e3ccd53..46a264386 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -424,7 +424,7 @@ export class Histogram extends React.Component { return height - y(d.length)}) .attr("width", eachRectWidth) .attr("class", 'histogram-bar') - .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'}) + .attr("fill", (d)=>{ return this.props.layoutDoc['histogramBarColors-'+d[0]]? StrCast(this.props.layoutDoc['histogramBarColors-'+d[0]]) : StrCast(this.props.layoutDoc['defaultHistogramColor'])}) }; @action changeSelectedColor = (color: string) => { @@ -439,16 +439,16 @@ export class Histogram extends React.Component { else return true; }) defaultColorBars.attr("fill", color); - this.props.layoutDoc.defaultHistogramColor = color; + this.props.layoutDoc['defaultHistogramColor'] = color; }; render() { - this.componentDidMount(); var curSelectedBarName; var titleAccessor: any=''; 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'; var selected: string; if (this._currSelected){ curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { selected += ' }'; } else selected = 'none'; + + this.componentDidMount(); return ( this.props.axes.length >= 1 ? (
@@ -476,7 +478,7 @@ export class Histogram extends React.Component { tooltip={'Change Default Bar Color'} type={Type.SEC} icon={} - selectedColor={this.props.layoutDoc.defaultHistogramColor? StrCast(this.props.layoutDoc.defaultHistogramColor): '#69b3a2'} + selectedColor={StrCast(this.props.layoutDoc['defaultHistogramColor'])} setSelectedColor={color => this.changeDefaultColor(color)} size={Size.XSMALL} /> -- cgit v1.2.3-70-g09d2 From 9fc748e6b09783f6bb375a68d10040141ac502f2 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 2 Aug 2023 15:02:26 -0400 Subject: last --- .../views/nodes/DataVizBox/components/Histogram.tsx | 18 ++++++++++++++++-- .../views/nodes/DataVizBox/components/PieChart.tsx | 8 +++++++- 2 files changed, 23 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 46a264386..68cb768d1 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -45,6 +45,7 @@ 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 selectTry: any = 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 _histogramData() { @@ -376,23 +377,32 @@ export class Histogram extends React.Component { && showSelected[yAxisTitle]==this._currSelected![yAxisTitle]) : false; this._currSelected = sameAsCurrent? undefined: showSelected; + this.selectTry = sameAsCurrent? undefined: d; return true } return false; }); + // console.log(this.curBarSelected.groups[0][0].data==selected) const elements = document.querySelectorAll('.histogram-bar'); for (let i = 0; i < elements.length; i++) { elements[i].classList.remove('hover'); } if (!sameAsCurrent!) selected.attr('class', 'histogram-bar hover'); - if (sameAsCurrent!) this.curBarSelected = undefined; + if (sameAsCurrent!) { + selected.attr('class', 'histogram-bar') + this.curBarSelected = undefined; + } else { selected.attr('class', 'histogram-bar hover') + selected.attr('stroke', 'blue') this.curBarSelected = selected; } + console.log(this._currSelected) + console.log(this.selectTry) }); svg.on('click', onPointClick); + var selected = this.selectTry; svg.append("text") .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") .style("text-anchor", "middle") @@ -423,7 +433,11 @@ export class Histogram extends React.Component { : function(d) { return height - y(d.length)}) .attr("width", eachRectWidth) - .attr("class", 'histogram-bar') + .attr("class", selected? + function(d) { + console.log(d[0]==selected[0], 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'])}) }; diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 98c79f95a..d3d16aa4c 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -43,6 +43,7 @@ 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 selectedTry: any = 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() { @@ -328,6 +329,7 @@ export class PieChart extends React.Component { : this._currSelected===showSelected; this._currSelected = sameAsCurrent? undefined: showSelected; + this.selectedTry = sameAsCurrent? undefined: d; return true; } return false; @@ -343,6 +345,7 @@ export class PieChart extends React.Component { } }); + var selected = this.selectedTry; var arcs = g.selectAll("arc") .data(pie(data)) .enter() @@ -363,7 +366,10 @@ export class PieChart extends React.Component { } 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("class", selected? + function(d) { + return (selected && d.startAngle==selected.startAngle && d.endAngle==selected.endAngle)? 'slice hover' : 'slice'; + }: function(d) {return 'slice'}) .attr("d", arc) .on('click', onPointClick) -- cgit v1.2.3-70-g09d2 From de064a6b0acf7f3dff04c579afc2562d782472d2 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 2 Aug 2023 17:50:25 -0400 Subject: selected fix --- .../nodes/DataVizBox/components/Histogram.tsx | 26 +++++----------------- .../views/nodes/DataVizBox/components/PieChart.tsx | 15 ++++--------- 2 files changed, 9 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 68cb768d1..89dcf87db 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -45,7 +45,7 @@ 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 selectTry: any = undefined; + private selectedData: any = 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 _histogramData() { @@ -377,32 +377,17 @@ export class Histogram extends React.Component { && showSelected[yAxisTitle]==this._currSelected![yAxisTitle]) : false; this._currSelected = sameAsCurrent? undefined: showSelected; - this.selectTry = sameAsCurrent? undefined: d; + this.selectedData = sameAsCurrent? undefined: d; return true } return false; }); - // console.log(this.curBarSelected.groups[0][0].data==selected) - const elements = document.querySelectorAll('.histogram-bar'); - for (let i = 0; i < elements.length; i++) { - elements[i].classList.remove('hover'); - } - if (!sameAsCurrent!) selected.attr('class', 'histogram-bar hover'); - if (sameAsCurrent!) { - selected.attr('class', 'histogram-bar') - this.curBarSelected = undefined; - } - else { - selected.attr('class', 'histogram-bar hover') - selected.attr('stroke', 'blue') - this.curBarSelected = selected; - } - console.log(this._currSelected) - console.log(this.selectTry) + if (sameAsCurrent!) this.curBarSelected = undefined; + else this.curBarSelected = selected; }); svg.on('click', onPointClick); - var selected = this.selectTry; + var selected = this.selectedData; svg.append("text") .attr("transform", "translate(" + (width/2) + " ," + (height+40) + ")") .style("text-anchor", "middle") @@ -435,7 +420,6 @@ export class Histogram extends React.Component { .attr("width", eachRectWidth) .attr("class", selected? function(d) { - console.log(d[0]==selected[0], 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'])}) diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index d3d16aa4c..d01d4429f 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -43,7 +43,7 @@ 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 selectedTry: any = undefined; + private selectedData: any = 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() { @@ -329,23 +329,16 @@ export class PieChart extends React.Component { : this._currSelected===showSelected; this._currSelected = sameAsCurrent? undefined: showSelected; - this.selectedTry = sameAsCurrent? undefined: d; + this.selectedData = sameAsCurrent? undefined: d; return true; } return false; }); - const elements = document.querySelectorAll('.slice'); - for (let i = 0; i < elements.length; i++) { - elements[i].classList.remove('hover'); - } if (sameAsCurrent!) this.curSliceSelected = undefined; - else { - selected.attr('class', 'slice hover') - this.curSliceSelected = selected; - } + else this.curSliceSelected = selected; }); - var selected = this.selectedTry; + var selected = this.selectedData; var arcs = g.selectAll("arc") .data(pie(data)) .enter() -- 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') 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 8bdae208c4ae44766241c285f2f7234027b9b80f Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 3 Aug 2023 15:56:11 -0400 Subject: histogram small data fix --- .../nodes/DataVizBox/components/Histogram.tsx | 6 ++-- .../views/nodes/DataVizBox/components/TableBox.tsx | 42 ++++++++++++---------- 2 files changed, 26 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index efe17297b..1077df844 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -267,10 +267,10 @@ export class Histogram extends React.Component { var histogram = d3.histogram() .value(function(d) {return d}) .domain([startingPoint!, endingPoint!]) - .thresholds(x.ticks(numBins-1)) + .thresholds(x.ticks(numBins)) var bins = histogram(data) var eachRectWidth = width/(bins.length) - var graphStartingPoint = bins[0].x1? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0; + var graphStartingPoint = (bins[0].x1 && bins[1])? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0; bins[0].x0 = graphStartingPoint; x = x.domain([graphStartingPoint, endingPoint]) .range([0, Number.isInteger(this.rangeVals.xMin!)? (width-eachRectWidth) : width ]) @@ -286,7 +286,7 @@ export class Histogram extends React.Component { index = j; } } - bins[index].push(data[i]) + if (bins[index]) bins[index].push(data[i]) } bins.pop(); eachRectWidth = width/(bins.length) diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 5653adbce..f244502a4 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -120,25 +120,29 @@ export class TableBox extends React.Component {
- {p[col]} - {p[col]}
+ {p[col]} + {p[col]}
-- 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') 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') 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') 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 { {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)? -- cgit v1.2.3-70-g09d2 From 8cbfb72751a3f8814c0dbda54c8ed22c8bb58783 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 4 Aug 2023 15:17:25 -0400 Subject: color stays on dragged charts + erase bar color is on individual bars --- .../nodes/DataVizBox/components/Histogram.tsx | 28 ++++++++++++++-------- .../views/nodes/DataVizBox/components/TableBox.tsx | 5 +++- 2 files changed, 22 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index a9be151bc..2a47abf32 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -400,6 +400,14 @@ export class Histogram extends React.Component { barColors.map(each => { if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1) }); barColors.push(StrCast(barName + '::' + color)); }; + + @action eraseSelectedColor= () => { + this.curBarSelected.attr("fill", this.props.layoutDoc.defaultHistogramColor); + var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1) }); + }; render() { var curSelectedBarName; @@ -445,28 +453,28 @@ export class Histogram extends React.Component { setSelectedColor={color => this.props.layoutDoc.defaultHistogramColor = 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' ?
Selected: {selected}     } selectedColor={selectedBarColor? selectedBarColor : this.curBarSelected.attr("fill")} setSelectedColor={color => this.changeSelectedColor(color)} size={Size.XSMALL} /> +   + } + size={Size.XSMALL} + color={'black'} + type={Type.SEC} + tooltip={'Revert to the default bar color'} + onClick={action(() => this.eraseSelectedColor())} + />
: null}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 7d6f934b9..8a99d332f 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,7 +1,7 @@ import { action, computed, } 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 { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils'; import { DragManager } from '../../../../util/DragManager'; @@ -85,6 +85,9 @@ export class TableBox extends React.Component { embedding._data_vizAxes = new List([col, col]); embedding._draggedFrom = this.props.docView?.()!.rootDoc!; embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; + embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors); + embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor; + embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors); return embedding; }; if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { -- cgit v1.2.3-70-g09d2 From 1bfa61ae441458cdcd860eb39e617417d013f1fa Mon Sep 17 00:00:00 2001 From: srichman333 Date: Mon, 7 Aug 2023 10:53:09 -0400 Subject: Issues when you remove a highlighted row more than 1 chart up the tree fixed --- src/client/views/nodes/DataVizBox/components/TableBox.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 8a99d332f..a7cc3f2fb 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -52,7 +52,18 @@ export class TableBox extends React.Component { return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header!='') : []; } + filterSelectedRowsDown() { + if (!this.props.layoutDoc.selected) this.props.layoutDoc.selected = new List(); + const selected = Cast(this.props.layoutDoc.selected, listSpec("string"), null); + const incomingSelected = this.incomingLinks.length? StrListCast(this.incomingLinks[0].selected) : undefined; + if (incomingSelected){ + selected.map(guid => { + if (!incomingSelected.includes(guid)) selected.splice(selected.indexOf(guid), 1)}); // filters through selected to remove guids that were removed in the incoming data + } + } + render() { + this.filterSelectedRowsDown(); if (this._tableData.length>0){ return (
@@ -133,7 +144,6 @@ export class TableBox extends React.Component { 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(guid)) selected.splice(selected.indexOf(guid), 1); else { -- 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') 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 36a34ed8b3be3f0db156fcce09842a2c45193cea Mon Sep 17 00:00:00 2001 From: srichman333 Date: Mon, 7 Aug 2023 15:03:45 -0400 Subject: histogram hover vs click --- .../nodes/DataVizBox/components/Histogram.tsx | 81 ++++++++++++++-------- 1 file changed, 52 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index cb882cf4a..092718e17 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -46,6 +46,7 @@ export class Histogram extends React.Component { @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 @computed get _histogramData() { @@ -214,6 +215,39 @@ export class Histogram extends React.Component { return data; } + 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++; + 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(/\ { d3.select(this._histogramRef.current).select('svg').remove(); d3.select(this._histogramRef.current).select('.tooltip').remove(); @@ -320,35 +354,24 @@ export class Histogram extends React.Component { .attr("transform", "translate(" + translateXAxis + ", " + height + ")") .call(xAxis) - const onPointClick = action((e: any) => { - var pointerX = d3.pointer(e)[0]; - var sameAsCurrent: boolean; - var barCounter = -1; - const selected = svg.selectAll('.histogram-bar').filter((d: any) => { - barCounter++; - 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(/\ 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) + updateHighlights(); + }) + const mouseOut = action((e: any) => { + this.hoverOverData = undefined; + updateHighlights(); + }) + const updateHighlights = () => { + const hoverOverBar = this.hoverOverData; + const selectedData = this.selectedData; + svg.selectAll('rect').attr("class", function(d) { 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; svg.append("text") -- cgit v1.2.3-70-g09d2 From b44722cc4e35b994ad50d47cf2586785b3e0ab2d Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 8 Aug 2023 11:19:54 -0400 Subject: hover vs click working for all 3 graph types --- .../nodes/DataVizBox/components/Histogram.tsx | 6 +- .../nodes/DataVizBox/components/LineChart.tsx | 3 +- .../views/nodes/DataVizBox/components/PieChart.tsx | 102 +++++++++++++-------- 3 files changed, 68 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 092718e17..6591581f7 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -282,7 +282,7 @@ export class Histogram extends React.Component { } for (let i=0; i each[xAxisTitle]==data[i]) - barData[0][yAxisTitle] = barData[0][yAxisTitle] + 1; + barData[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1; } } histDataSet = histStringDataSet @@ -340,7 +340,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(/\ { const updateHighlights = () => { const hoverOverBar = this.hoverOverData; const selectedData = this.selectedData; - svg.selectAll('rect').attr("class", function(d) { return ((hoverOverBar && hoverOverBar[0]==d[0]) || selectedData && selectedData[0]==d[0])? 'histogram-bar hover' : 'histogram-bar'; }) + 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) diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 3a416c401..16794886e 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -214,7 +214,8 @@ export class LineChart extends React.Component { @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; + if (this._currSelected && this._currSelected.x==x && this._currSelected.y==y) this._currSelected = undefined; + else 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)); } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index ca93a2942..913bdd9a4 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -43,6 +43,7 @@ export class PieChart extends React.Component { @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 @computed get _piechartData() { @@ -219,6 +220,50 @@ export class PieChart extends React.Component { return data; } + 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)]; + + // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge + var lineCrossCount = 0; + // if for all 4 lines + if (Math.min(p1[1], p2[1])<=pointer[1] && pointer[1]<=Math.max(p1[1], p2[1])){ // within y bounds + if (pointer[0] <= (pointer[1]-p1[1])*(p2[0]-p1[0])/(p2[1]-p1[1])+p1[0]) lineCrossCount++; } // intercepts x + if (Math.min(p2[1], p3[1])<=pointer[1] && pointer[1]<=Math.max(p2[1], p3[1])){ + if (pointer[0] <= (pointer[1]-p2[1])*(p3[0]-p2[0])/(p3[1]-p2[1])+p2[0]) lineCrossCount++; } + if (Math.min(p3[1], p4[1])<=pointer[1] && pointer[1]<=Math.max(p3[1], p4[1])){ + 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) { + var showSelected = this.byCategory? pieDataSet[index] : this._piechartData[index]; + + if (changeSelectedVariables){ + 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]]) + : + this._currSelected===showSelected; + this._currSelected = sameAsCurrent? undefined: showSelected; + this.selectedData = sameAsCurrent? undefined: d; + } + else this.hoverOverData = d; + return true; + } + return false; + }); + if (changeSelectedVariables){ + if (sameAsCurrent!) this.curSliceSelected = undefined; + else this.curSliceSelected = selected; + } + } + drawChart = (dataSet: any, width: number, height: number) => { d3.select(this._piechartRef.current).select('svg').remove(); d3.select(this._piechartRef.current).select('.tooltip').remove(); @@ -269,45 +314,22 @@ export class PieChart extends React.Component { .innerRadius(0) .outerRadius(radius); - const onPointClick = action((e: any) => { - // check the 4 'corners' of each slice and see if the pointer is within those bounds to get the slice the user clicked on - const pointer = d3.pointer(e); - 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)]; - - // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge - var lineCrossCount = 0; - // if for all 4 lines - if (Math.min(p1[1], p2[1])<=pointer[1] && pointer[1]<=Math.max(p1[1], p2[1])){ // within y bounds - if (pointer[0] <= (pointer[1]-p1[1])*(p2[0]-p1[0])/(p2[1]-p1[1])+p1[0]) lineCrossCount++; } // intercepts x - if (Math.min(p2[1], p3[1])<=pointer[1] && pointer[1]<=Math.max(p2[1], p3[1])){ - if (pointer[0] <= (pointer[1]-p2[1])*(p3[0]-p2[0])/(p3[1]-p2[1])+p2[0]) lineCrossCount++; } - if (Math.min(p3[1], p4[1])<=pointer[1] && pointer[1]<=Math.max(p3[1], p4[1])){ - 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) { - 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]]) - : - this._currSelected===showSelected; - this._currSelected = sameAsCurrent? undefined: showSelected; - this.selectedData = sameAsCurrent? undefined: d; - return true; - } - return false; - }); - if (sameAsCurrent!) this.curSliceSelected = undefined; - else this.curSliceSelected = selected; - }); + 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) + updateHighlights(); + }) + const mouseOut = action((e: any) => { + this.hoverOverData = undefined; + updateHighlights(); + }) + const updateHighlights = () => { + const hoverOverSlice = this.hoverOverData; + const selectedData = this.selectedData; + svg.selectAll('path').attr("class", function(d: any) { + return ((selectedData && d.startAngle==selectedData.startAngle && d.endAngle==selectedData.endAngle) + || ((hoverOverSlice && d.startAngle==hoverOverSlice.startAngle && d.endAngle==hoverOverSlice.endAngle)))? 'slice hover' : 'slice'; }) + } var selected = this.selectedData; var arcs = g.selectAll("arc") @@ -339,6 +361,8 @@ export class PieChart extends React.Component { }: function(d) {return 'slice'}) .attr("d", arc) .on('click', onPointClick) + .on('mouseover', onHover) + .on('mouseout', mouseOut); trackDuplicates = {}; data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null) -- cgit v1.2.3-70-g09d2 From 5614478b54b754665faff41c9a09b9bd0535e2f5 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 8 Aug 2023 14:25:18 -0400 Subject: histogram bug fixes --- .../views/nodes/DataVizBox/components/Histogram.tsx | 18 ++++++++++++++---- .../views/nodes/DataVizBox/components/PieChart.tsx | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 6591581f7..26c40045c 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -16,6 +16,7 @@ import { ColorPicker, EditableText, IconButton, Size, Type } from "browndash-com import { FaFillDrip } from "react-icons/fa"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { listSpec } from "../../../../../fields/Schema"; +import { scaleCreatorNumerical, yAxisCreator } from "../utils/D3Utils"; export interface HistogramProps { rootDoc: Doc; @@ -63,7 +64,7 @@ export class Histogram extends React.Component { var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; 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;} + if (/\d/.test(this.props.pairs[0][ax1]) ) { this.numericalYData = true;} return this.props.pairs ?.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]]) })) @@ -282,7 +283,7 @@ export class Histogram extends React.Component { } for (let i=0; i each[xAxisTitle]==data[i]) - barData[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1; + histStringDataSet.filter(each => each[xAxisTitle]==data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1; } } histDataSet = histStringDataSet @@ -337,10 +338,13 @@ export class Histogram extends React.Component { translateXAxis = eachRectWidth / 2; } else { + eachRectWidth = width/(bins.length+1) + x.range([0, width-eachRectWidth]) xAxis = d3.axisBottom(x) .ticks(numBins-1) } - const maxFrequency = this.numericalYData? d3.max(histDataSet, function(d: any) { return d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\ { y.domain([0, +maxFrequency!]); var yAxis = d3.axisLeft(y) .ticks(maxFrequency!) - svg.append("g") + if (this.numericalYData){ + const yScale = scaleCreatorNumerical(0, Number(maxFrequency), height, 0); + yAxisCreator(svg.append('g'), width, yScale); + } + else{ + svg.append("g") .call(yAxis); + } svg.append("g") .attr("transform", "translate(" + translateXAxis + ", " + height + ")") .call(xAxis) diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 913bdd9a4..0fd4f6b54 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -245,7 +245,7 @@ export class PieChart extends React.Component { var showSelected = this.byCategory? pieDataSet[index] : this._piechartData[index]; if (changeSelectedVariables){ - sameAsCurrent = (this.byCategory && this._currSelected)? + 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]]) : -- 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') 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 5a77db9f5f1e842c59d071f230c09f5f94018ea1 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 9 Aug 2023 11:25:10 -0400 Subject: histogram numerical bars bug fix --- .../nodes/DataVizBox/components/Histogram.tsx | 22 ++++++++++++++++++---- .../views/nodes/DataVizBox/components/PieChart.tsx | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 0b35f2856..7bf546a62 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -189,7 +189,7 @@ export class Histogram extends React.Component { var xAxisTitle = Object.keys(dataSet[0])[0] var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency'; let uniqueArr: unknown[] = [...new Set(data)] - var numBins = (this.numericalXData && Number.isInteger(data[0][xAxisTitle]))? (this.rangeVals.xMax! - this.rangeVals.xMin! + 1) : uniqueArr.length + var numBins = (this.numericalXData && Number.isInteger(data[0]))? (this.rangeVals.xMax! - this.rangeVals.xMin! ) : uniqueArr.length var translateXAxis = !this.numericalXData || numBinsthis.maxBins) numBins = this.maxBins; var startingPoint = this.numericalXData? this.rangeVals.xMin! : 0; @@ -249,7 +249,7 @@ export class Histogram extends React.Component { // more calculations based on bins // x-axis - if (!this.numericalXData) { // reorganize if the data is strings rather than numbers + if (!this.numericalXData) { // reorganize to match data if the data is strings rather than numbers // uniqueArr.sort() histDataSet.sort() for (let i=0; i { translateXAxis = eachRectWidth / 2; } else { - eachRectWidth = width/(bins.length+1) + var allSame = true; + for (var i=0; i { 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] }) + return sliceColor? StrCast(sliceColor) : d3.schemeSet3[i]? d3.schemeSet3[i]: d3.schemeSet3[i%(d3.schemeSet3.length)] }) .attr("class", selected? function(d) { return (selected && d.startAngle==selected.startAngle && d.endAngle==selected.endAngle)? 'slice hover' : 'slice'; -- cgit v1.2.3-70-g09d2 From d70e2a955633734d5c19c2e76efb075ddf33327b Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 9 Aug 2023 12:29:12 -0400 Subject: more histogram cleanup --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 7bf546a62..e04d37094 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -286,11 +286,16 @@ export class Histogram extends React.Component { translateXAxis = eachRectWidth / 2; eachRectWidth = width/(bins.length) } - else eachRectWidth = width/(bins.length+1) + else { + eachRectWidth = width/(bins.length+1) + var tickDiff = (bins.length>=2? (bins[bins.length-2].x1!-bins[bins.length-2].x0!): 0) + var curDomain = x.domain(); + x.domain([curDomain[0], curDomain[0] + tickDiff*bins.length]) + } - x.range([0, width-eachRectWidth]) xAxis = d3.axisBottom(x) .ticks(bins.length-1) + x.range([0, width-eachRectWidth]) } // y-axis const maxFrequency = this.numericalYData? d3.max(histDataSet, function(d: any) { -- cgit v1.2.3-70-g09d2 From 79a5cddf969a027811a9b7069f8ae8614b825a05 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 9 Aug 2023 12:58:59 -0400 Subject: display clarification for when a string column is given to a LineChart --- src/client/views/nodes/DataVizBox/components/LineChart.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 16794886e..053696807 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -358,7 +358,7 @@ export class LineChart extends React.Component { 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>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? (
{
{`Selected: ${selectedPt}`}
: null}
- ) : {'first use table view to select two axes to plot'} + ) : {'first use table view to select two numerical axes to plot'} ); } } -- cgit v1.2.3-70-g09d2 From 481a847b1074c9544e947e409b96778ac4d4f852 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 9 Aug 2023 15:24:03 -0400 Subject: numerical y axis data fix --- .../views/nodes/DataVizBox/components/Histogram.tsx | 11 ++++------- .../views/nodes/DataVizBox/components/PieChart.tsx | 21 +++++++++------------ .../views/nodes/DataVizBox/components/TableBox.tsx | 4 ++-- 3 files changed, 15 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index e04d37094..ed663006f 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -1,13 +1,10 @@ import { observer } from "mobx-react"; -import { Doc, DocListCast, StrListCast } from "../../../../../fields/Doc"; +import { Doc, 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 { DocumentManager } from "../../../../util/DocumentManager"; -import { Id } from "../../../../../fields/FieldSymbols"; -import { DataVizBox } from "../DataVizBox"; import { PinProps, PresBox } from "../../trails"; import { Docs } from "../../../../documents/Documents"; import { List } from "../../../../../fields/List"; @@ -299,7 +296,7 @@ export class Histogram extends React.Component { } // y-axis const maxFrequency = this.numericalYData? d3.max(histDataSet, function(d: any) { - return Number(d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\ { .attr("transform", this.numericalYData? function (d) { var eachData = histDataSet.filter((data: { [x: string]: number; }) => {return data[xAxisTitle]==d[0]}) - var length = eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ {return data[xAxisTitle]==d[0]}) - var length = eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { .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,"") + return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]] trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } - var accessByName = descriptionField? dataPoint[descriptionField] : dataPoint[percentField]; var sliceColor; - var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); - sliceColors.map(each => {if (each[0]==StrCast(accessByName)) sliceColor = each[1]}); + if (dataPoint){ + var accessByName = descriptionField? dataPoint[descriptionField] : dataPoint[percentField]; + 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%(d3.schemeSet3.length)] }) .attr("class", selected? function(d) { @@ -292,7 +291,7 @@ export class PieChart extends React.Component { .text(function(d){ 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,"") + return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]] trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } - return dataPoint[percentField]! - + (!descriptionField? '' : (' - ' + dataPoint[descriptionField]))!}) - + return dataPoint? dataPoint[percentField]! + (!descriptionField? '' : (' - ' + dataPoint[descriptionField]))! : ''}) }; @action changeSelectedColor = (color: string) => { diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 277ee83ec..f80cbdf99 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -123,10 +123,10 @@ export class TableBox extends React.Component { const newAxes = this.props.axes; if (newAxes.includes(col)) { newAxes.splice(newAxes.indexOf(col), 1); - } else if (newAxes.length >= 1) { + } else if (newAxes.length > 1) { newAxes[1] = col; } else { - newAxes[0] = col; + newAxes.push(col); } this.props.selectAxes(newAxes); }) -- cgit v1.2.3-70-g09d2 From a89ba9afe33d973c50c0a66aec73db1e22367913 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 10 Aug 2023 11:28:09 -0400 Subject: undo/redo for colors + titles --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 9 +++++---- src/client/views/nodes/DataVizBox/components/LineChart.tsx | 3 ++- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index ed663006f..883cc006b 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -14,6 +14,7 @@ import { FaFillDrip } from "react-icons/fa"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { listSpec } from "../../../../../fields/Schema"; import { scaleCreatorNumerical, yAxisCreator } from "../utils/D3Utils"; +import { undoBatch, undoable } from "../../../../util/UndoManager"; export interface HistogramProps { rootDoc: Doc; @@ -426,7 +427,7 @@ export class Histogram extends React.Component {
this.props.layoutDoc[titleAccessor] = val as string)} + setVal={undoable (action(val => this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} color={"black"} size={Size.LARGE} fillWidth @@ -437,7 +438,7 @@ export class Histogram extends React.Component { type={Type.SEC} icon={} selectedColor={StrCast(this.props.layoutDoc.defaultHistogramColor)} - setSelectedColor={color => this.props.layoutDoc.defaultHistogramColor = color} + setSelectedColor={undoable (color => this.props.layoutDoc.defaultHistogramColor = color, "Change Default Bar Color")} size={Size.XSMALL} />
@@ -451,7 +452,7 @@ export class Histogram extends React.Component { type={Type.SEC} icon={} selectedColor={selectedBarColor? selectedBarColor : this.curBarSelected.attr("fill")} - setSelectedColor={color => this.changeSelectedColor(color)} + setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Bar Color")} size={Size.XSMALL} />   @@ -461,7 +462,7 @@ export class Histogram extends React.Component { color={'black'} type={Type.SEC} tooltip={'Revert to the default bar color'} - onClick={action(() => this.eraseSelectedColor())} + onClick={undoable (action(() => this.eraseSelectedColor()), "Change Selected Bar Color")} />
: null} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 053696807..49ef577e4 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -15,6 +15,7 @@ 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'; +import { undoable } from '../../../../util/UndoManager'; export interface DataPoint { x: number; @@ -363,7 +364,7 @@ export class LineChart extends React.Component {
this.props.layoutDoc[titleAccessor] = val as string)} + setVal={undoable (action(val => this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} 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 6581b6b75..de725209d 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -12,6 +12,7 @@ import './Chart.scss'; import { ColorPicker, EditableText, Size, Type } from "browndash-components"; import { FaFillDrip } from "react-icons/fa"; import { listSpec } from "../../../../../fields/Schema"; +import { undoable } from "../../../../util/UndoManager"; export interface PieChartProps { rootDoc: Doc; @@ -342,7 +343,7 @@ export class PieChart extends React.Component {
this.props.layoutDoc[titleAccessor] = val as string)} + setVal={undoable (action(val => this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} color={"black"} size={Size.LARGE} fillWidth @@ -358,7 +359,7 @@ export class PieChart extends React.Component { type={Type.SEC} icon={} selectedColor={selectedSliceColor? selectedSliceColor : this.curSliceSelected.attr("fill")} - setSelectedColor={color => this.changeSelectedColor(color)} + setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Slice Color")} size={Size.XSMALL} />
-- cgit v1.2.3-70-g09d2 From 5a1bf41682f641967304c8a7fa7df9b6b343b7b2 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 10 Aug 2023 13:21:10 -0400 Subject: changing selected slice/bar color w different data types bug fix --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 8 +++++++- src/client/views/nodes/DataVizBox/components/PieChart.tsx | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 883cc006b..8161a12c9 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -376,7 +376,13 @@ export class Histogram extends React.Component { .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]}); + barColors.map(each => { + if (d[0] && d[0].toString() && each[0]==d[0].toString()) barColor = each[1]; + else { + var range = StrCast(each[0]).split(" to "); + if (Number(range[0])<=d[0] && d[0]<=Number(range[1])) barColor = each[1]; + } + }); return barColor? StrCast(barColor) : StrCast(this.props.layoutDoc.defaultHistogramColor)}) }; diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index de725209d..03c9efdd1 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -266,7 +266,7 @@ export class PieChart extends React.Component { } var sliceColor; if (dataPoint){ - var accessByName = descriptionField? dataPoint[descriptionField] : dataPoint[percentField]; + var accessByName = dataPoint[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ each.split('::')); sliceColors.map(each => {if (each[0]==StrCast(accessByName)) sliceColor = each[1]}); } -- cgit v1.2.3-70-g09d2 From 5417c64b0723df1bc011efbea2f0f286100d9bf3 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 10 Aug 2023 17:22:16 -0400 Subject: histogram numerical Y axis fixes --- src/client/views/nodes/DataVizBox/components/Histogram.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 8161a12c9..df713c462 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -70,10 +70,10 @@ 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 || !this.numericalYData){ + if (this.props.axes.length<2 || !ax1 || !/\d/.test(this.props.pairs[0][ax1]) || !this.numericalYData){ return ax0 + " Histogram"; } - else return ax1 + " by " + ax0 + " Histogram"; + else return ax0 + " by " + ax1 + " Histogram"; } @computed get incomingLinks() { @@ -149,7 +149,7 @@ export class Histogram extends React.Component { const selected = svg.selectAll('.histogram-bar').filter((d: any) => { 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(/\ StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\ data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { }; render() { + this._histogramData var curSelectedBarName; var titleAccessor: any=''; if (this.props.axes.length==2) titleAccessor = 'histogram-title-'+this.props.axes[0]+'-'+this.props.axes[1]; -- cgit v1.2.3-70-g09d2 From da893c2739c9589bac04c00df3d22ebbfd78c09d Mon Sep 17 00:00:00 2001 From: srichman333 Date: Sun, 13 Aug 2023 17:13:31 -0400 Subject: LineChart ui fixes + selected columns bolded --- .../views/nodes/DataVizBox/components/LineChart.tsx | 4 ++-- .../views/nodes/DataVizBox/components/TableBox.tsx | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 49ef577e4..d27aaafbc 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -348,7 +348,7 @@ export class LineChart extends React.Component { tooltip .html(() => `(${d0.x},${d0.y})`) // text content for tooltip .style('pointer-events', 'none') - .style('transform', `translate(${xScale(d0.x) - this.width / 2}px,${yScale(d0.y) - 30}px)`); + .style('transform', `translate(${xScale(d0.x)-this.width}px,${yScale(d0.y)}px)`); } render() { @@ -357,7 +357,7 @@ export class LineChart extends React.Component { 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'; + const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none'; return ( this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? (
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index f80cbdf99..f56d34fa6 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -81,7 +81,8 @@ export class TableBox extends React.Component { key={this.columns.indexOf(col)} ref={header as any} style={{ - color: this.props.axes.slice().reverse().lastElement() === col ? 'green' : this.props.axes.lastElement() === col ? 'red' : undefined, + color: this.props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this.props.axes.lastElement() === col ? 'darkred' : undefined, + background: this.props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this.props.axes.lastElement() === col ? '#Fbdbdb' : undefined, fontWeight: 'bolder', border: '3px solid black' }} onPointerDown={e => { @@ -151,17 +152,15 @@ export class TableBox extends React.Component { if (selected.includes(guid)) selected.splice(selected.indexOf(guid), 1); else { selected.push(guid)}; - })} style={ - { 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)? + })} style={{ background: StrListCast(this.props.layoutDoc.selected).includes(guid) ? 'lightgrey' : '', width: '110%' }}> + {this.columns.map(col => { // each cell -
- : - ))} + )})} ); } -- cgit v1.2.3-70-g09d2 From 9dbd9efeb0080418fcb44c5c78511fffa09ad229 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Sun, 13 Aug 2023 17:37:02 -0400 Subject: just one histogram bar bug fix + message displayed when graphs have no incoming data rows selected --- .../nodes/DataVizBox/components/Histogram.tsx | 94 ++++++++++++---------- .../nodes/DataVizBox/components/LineChart.tsx | 42 ++++++---- .../views/nodes/DataVizBox/components/PieChart.tsx | 62 +++++++------- 3 files changed, 111 insertions(+), 87 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index df713c462..df6aac6bc 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -263,7 +263,7 @@ export class Histogram extends React.Component { eachRectWidth = width/(bins.length) bins.forEach(d => d.x0 = d.x0!) xAxis = d3.axisBottom(x) - .ticks(bins.length-1) + .ticks(bins.length>1? bins.length-1: 1) .tickFormat( i => uniqueArr[i.valueOf()] as string) .tickPadding(10) x.range([0, width-eachRectWidth]) @@ -428,54 +428,62 @@ export class Histogram extends React.Component { barColors.map(each => {if (each[0]==curSelectedBarName!) selectedBarColor = each[1]}); this.componentDidMount(); - return ( - this.props.axes.length >= 1 ? ( -
-
- this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} - color={"black"} - size={Size.LARGE} - fillWidth - /> -     - } - selectedColor={StrCast(this.props.layoutDoc.defaultHistogramColor)} - setSelectedColor={undoable (color => this.props.layoutDoc.defaultHistogramColor = color, "Change Default Bar Color")} - size={Size.XSMALL} - /> -
-
- {selected != 'none' ? -
- Selected: {selected} -     + + if (this._histogramData.length>0){ + return ( + this.props.axes.length >= 1 ? ( +
+
+ this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} + color={"black"} + size={Size.LARGE} + fillWidth + /> +     } - selectedColor={selectedBarColor? selectedBarColor : this.curBarSelected.attr("fill")} - setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Bar Color")} + selectedColor={StrCast(this.props.layoutDoc.defaultHistogramColor)} + setSelectedColor={undoable (color => this.props.layoutDoc.defaultHistogramColor = color, "Change Default Bar Color")} size={Size.XSMALL} /> -   - } - size={Size.XSMALL} - color={'black'} - type={Type.SEC} - tooltip={'Revert to the default bar color'} - onClick={undoable (action(() => this.eraseSelectedColor()), "Change Selected Bar Color")} - />
- : null} +
+ {selected != 'none' ? +
+ Selected: {selected} +     + } + selectedColor={selectedBarColor? selectedBarColor : this.curBarSelected.attr("fill")} + setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Bar Color")} + size={Size.XSMALL} + /> +   + } + size={Size.XSMALL} + color={'black'} + type={Type.SEC} + tooltip={'Revert to the default bar color'} + onClick={undoable (action(() => this.eraseSelectedColor()), "Change Selected Bar Color")} + /> +
+ : null} +
+ ) : {'first use table view to select a column to graph'} + ); + } + 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.
- ) : {'first use table view to select a column to graph'} - ); + ) } - } \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index d27aaafbc..8bace941f 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -358,24 +358,32 @@ export class LineChart extends React.Component { 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 ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none'; - return ( - this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? ( -
-
- this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} - color={"black"} - size={Size.LARGE} - fillWidth - /> + if (this._lineChartData.length>0){ + return ( + this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? ( +
+
+ this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} + color={"black"} + size={Size.LARGE} + fillWidth + /> +
+
+ {selectedPt!='none'? +
{`Selected: ${selectedPt}`}
+ : null}
-
- {selectedPt!='none'? -
{`Selected: ${selectedPt}`}
- : null} + ) : {'first use table view to select two numerical axes to plot'} + ); + } + 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.
- ) : {'first use table view to select two numerical axes to plot'} - ); + ) } } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 03c9efdd1..0c54d0a4e 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -337,36 +337,44 @@ export class PieChart extends React.Component { 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 ? ( -
-
- this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} - color={"black"} - size={Size.LARGE} - fillWidth - /> -
-
- {selected != 'none' ? -
- Selected: {selected} -     - } - selectedColor={selectedSliceColor? selectedSliceColor : this.curSliceSelected.attr("fill")} - setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Slice Color")} - size={Size.XSMALL} + if (this._piechartData.length>0){ + return ( + this.props.axes.length >= 1 ? ( +
+
+ this.props.layoutDoc[titleAccessor] = val as string), "Change Graph Title")} + color={"black"} + size={Size.LARGE} + fillWidth />
- : null} +
+ {selected != 'none' ? +
+ Selected: {selected} +     + } + selectedColor={selectedSliceColor? selectedSliceColor : this.curSliceSelected.attr("fill")} + setSelectedColor={undoable (color => this.changeSelectedColor(color), "Change Selected Slice Color")} + size={Size.XSMALL} + /> +
+ : null} +
+ ) : {'first use table view to select a column to graph'} + ); + } + 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.
- ) : {'first use table view to select a column to graph'} - ); + ) } } \ No newline at end of file -- 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') 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 From 61fb855ec92540c48cc4cc844d3b21728e8a4754 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Tue, 15 Aug 2023 19:21:30 -0400 Subject: DataViz node documentation link --- src/client/views/nodes/DocumentView.tsx | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4beaeb10e..90fb55290 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -846,6 +846,10 @@ export class DocumentViewInternal extends DocComponent
{p[col]} + var colSelected = this.props.axes[0]==col || this.props.axes[1]==col; + return ( + {p[col]} {p[col]}