aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts4
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss5
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx50
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss14
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx101
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx125
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx98
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx9
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx27
9 files changed, 314 insertions, 119 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 30194f9f8..8747a00a6 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -24,7 +24,9 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
summary: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' },
edit: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' },
completion: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." },
- data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please keep your response short and to the point." },
+ // data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please keep your response short and to the point." },
+ data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please only return a JSON with a correlation column 1 propert, a correlation column 2 property, and an analysis property. " },
+
};
/**
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index e9a346fbe..9825d926f 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -30,8 +30,13 @@
}
.liveSchema-checkBox {
+ margin-left: 10px;
margin-bottom: -35px;
}
+ .filterData-checkBox {
+ margin-left: 10px;
+ margin-bottom: -10px;
+ }
.displaySchemaLive {
margin-bottom: 20px;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 60c5fdba2..01258a996 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -29,6 +29,7 @@ import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
import { Checkbox } from '@mui/material';
import { ContextMenu } from '../../ContextMenu';
+import { DragManager } from '../../../util/DragManager';
export enum DataVizView {
TABLE = 'table',
@@ -346,7 +347,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
axes: this.axes,
titleCol: this.titleCol,
//width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
- height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
+ height: (this._props.PanelHeight() / scale - 55) /* height of 'change view' button */ * 0.8,
width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
margin: { top: 10, right: 25, bottom: 75, left: 45 },
};
@@ -400,11 +401,20 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
};
+ // represents whether or not a data viz box created from a schema table displays live updates to the canvas
@action
changeLiveSchemaCheckbox = () => {
this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
}
+ // represents whether or not clicking on a peice of data in the visualization
+ // (i.e. a data point in a linechart, a bar on a histogram, or a slice of a pie chart)
+ // filters the data onto a new data viz doc created off of this one
+ @action
+ changeFilteringCheckbox = () => {
+ this.layoutDoc.dataViz_filterSelection = !this.layoutDoc.dataViz_filterSelection
+ }
+
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const options = cm.findByDescription('Options...');
@@ -417,6 +427,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
askGPT = action(async () => {
GPTPopup.Instance.setSidebarId('data_sidebar');
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+ GPTPopup.Instance.createFilteredDoc = this.createFilteredDoc;
GPTPopup.Instance.setDataJson("");
GPTPopup.Instance.setMode(GPTPopupMode.DATA);
let data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
@@ -425,6 +436,31 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
GPTPopup.Instance.generateDataAnalysis();
});
+ /**
+ * creates a new dataviz document filter from this one
+ * it appears to the right of this document, with the
+ * parameters passed in being used to create an initial display
+ */
+ createFilteredDoc = (axes?: any, type?: DataVizView) => {
+ const embedding = Doc.MakeEmbedding(this.Document!);
+ embedding._layout_showSidebar = false;
+ embedding._dataViz = type? type : DataVizView.LINECHART;
+ embedding._dataViz_axes = new List<string>(axes);
+ embedding._dataViz_parentViz = this.Document;
+ embedding.histogramBarColors = Field.Copy(this.layoutDoc.histogramBarColors);
+ embedding.defaultHistogramColor = this.layoutDoc.defaultHistogramColor;
+ embedding.pieSliceColors = Field.Copy(this.layoutDoc.pieSliceColors);
+ embedding._layout_showSidebar = false;
+ embedding.width = NumCast(this.layoutDoc._width)- this.sidebarWidth();
+ embedding._layout_sidebarWidthPercent = "0%";
+ this._props.addDocument?.(embedding);
+ embedding._dataViz_axes = new List<string>(axes)
+ this.layoutDoc.dataViz_selectedRows = new List<number>(this.records.map((rec, i) => i))
+ embedding.x = Number(embedding.x) + Number(this.Document.width);
+
+ return true;
+ };
+
render() {
const scale = this._props.NativeDimScaling?.() || 1;
return !this.records.length ? (
@@ -454,11 +490,17 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
{(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
<div className={'displaySchemaLive'}>
<div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
- <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
- Display Live Updates to Canvas
+ <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+ Display Live Updates to Canvas
+ </div>
</div>
- </div>
) : null}
+ {this.layoutDoc._dataViz != DataVizView.TABLE?(
+ <div className={'filterData-checkBox'}>
+ <Checkbox color="primary" onChange={this.changeFilteringCheckbox} checked={this.layoutDoc.dataViz_filterSelection as boolean} />
+ Select data to filter
+ </div>)
+ : null }
{this.renderVizView}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index cf0007cfd..0eb27b65b 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -15,18 +15,12 @@
font-size: larger;
display: flex;
flex-direction: row;
- margin-top: -20px;
- margin-bottom: -20px;
+ margin-top: -35px;
}
.asHistogram-checkBox {
- align-items: left;
- align-self: left;
- align-content: left;
- justify-content: flex-end;
- float: left;
- left: 0;
- position: relative;
- margin-bottom: -35px;
+ margin-left: 10px;
+ margin-bottom: -10px;
+ margin-top: -20px;
}
.selected-data {
align-items: center;
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 6672603f3..110626923 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -43,8 +43,8 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
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; // Object of selected bar
- private curBarSelected: any = undefined; // histogram bin of selected bar
- private selectedData: any = undefined; // Selection of selected bar
+ private curBarSelected: any = undefined; // histogram bin of selected bar for when just one bar is selected
+ private selectedData: any[] = []; // array of selected bars
private hoverOverData: any = undefined; // Selection of bar being hovered over
constructor(props: any) {
@@ -104,11 +104,29 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
}
componentDidMount() {
+ // draw histogram
this._disposers.chartData = reaction(
() => ({ dataSet: this._histogramData, w: this.width, h: this.height }),
({ dataSet, w, h }) => dataSet!.length > 0 && this.drawChart(dataSet, w, h),
{ fireImmediately: true }
);
+
+ // restore selected bars
+ var svg = this._histogramSvg;
+ if (svg) {
+ const selectedDataBars = StrListCast(this._props.layoutDoc.dataViz_histogram_selectedData)
+ svg.selectAll('rect').attr('class', (d: any) => {
+ let selected = false;
+ selectedDataBars.forEach(eachSelectedData => {
+ if (d[0]==eachSelectedData) selected = true;
+ })
+ if (selected){
+ this.selectedData.push(d);
+ return 'histogram-bar hover'
+ }
+ else return 'histogram-bar';
+ });
+ }
}
@action
@@ -162,16 +180,49 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
if (changeSelectedVariables) {
// for when a bar is selected - not just hovered over
sameAsCurrent = this._currSelected ? showSelected[xAxisTitle] == this._currSelected![xAxisTitle] && showSelected[yAxisTitle] == this._currSelected![yAxisTitle] : false;
- this._currSelected = sameAsCurrent ? undefined : showSelected;
- this.selectedData = sameAsCurrent ? undefined : d;
+ let sameAsAny = false;
+ const selectedDataBars = Cast(this._props.layoutDoc.dataViz_histogram_selectedData, listSpec('number'), null);
+ this.selectedData.forEach(eachData => {
+ if (!sameAsAny){
+ let match = true;
+ Object.keys(d).forEach(key => {
+ if (d[key] != eachData[key]) match = false;
+ })
+ if (match) {
+ sameAsAny = true;
+ let index = this.selectedData.indexOf(eachData)
+ this.selectedData.splice(index, 1);
+ selectedDataBars.splice(index, 1);
+ this._currSelected = undefined;
+ }
+ }
+ })
+ if(!sameAsAny) {
+ this.selectedData.push(d);
+ selectedDataBars.push(d[0]);
+ this._currSelected = this.selectedData.length>1? undefined : showSelected;
+ }
+
+ // for filtering child dataviz docs
+ if (this._props.layoutDoc.dataViz_filterSelection){
+ const selectedRows = Cast(this._props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+ this._tableDataIds.forEach(rowID => {
+ let match = false;
+ for (let i=0; i<d.length; i++){
+ if (this._props.records[rowID][xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[i]) match = true;
+ }
+ if (match && !selectedRows?.includes(rowID)) selectedRows?.push(rowID); // adding to filtered rows
+ else if (match && sameAsAny) selectedRows.splice(selectedRows.indexOf(rowID), 1); // removing from filtered rows
+ })
+ }
} else this.hoverOverData = d;
return true;
}
return false;
});
if (changeSelectedVariables) {
- if (sameAsCurrent!) this.curBarSelected = undefined;
- else this.curBarSelected = selected;
+ if (this._currSelected) this.curBarSelected = selected;
+ else this.curBarSelected = undefined;
}
};
@@ -321,7 +372,11 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const hoverOverBar = this.hoverOverData;
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';
+ let selected = false;
+ selectedData.forEach(eachSelectedData => {
+ if (d[0]==eachSelectedData[0]) selected = true;
+ })
+ return (hoverOverBar && hoverOverBar[0] == d[0]) || selected ? 'histogram-bar hover' : 'histogram-bar';
});
};
svg.on('click', onPointClick).on('mouseover', onHover).on('mouseout', mouseOut);
@@ -352,8 +407,8 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const eachData = histDataSet.filter((data: { [x: string]: number }) => {
return data[xAxisTitle] == d[0];
});
- const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
- return 'translate(' + x(d.x0!) + ',' + y(length) + ')';
+ const length = eachData.length ? StrCast(eachData[0][yAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ return 'translate(' + x(d.x0!) + ',' + y(Number(length)) + ')';
}
: function (d) {
return 'translate(' + x(d.x0!) + ',' + y(d.length) + ')';
@@ -366,8 +421,8 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
const eachData = histDataSet.filter((data: { [x: string]: number }) => {
return data[xAxisTitle] == d[0];
});
- const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
- return height - y(length);
+ const length = eachData.length ? StrCast(eachData[0][yAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ return height - y(Number(length));
}
: function (d) {
return height - y(d.length);
@@ -376,13 +431,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
.attr('width', eachRectWidth)
.attr(
'class',
- selected
- ? function (d) {
- return selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar';
- }
- : function (d) {
- return 'histogram-bar';
- }
+ function (d) {
+ let selectThisData = false;
+ selected.forEach(eachSelectedData => {
+ if (d[0]==eachSelectedData[0]) selectThisData = true;
+ })
+ return selectThisData ? 'histogram-bar hover' : 'histogram-bar';
+ }
)
.attr('fill', d => {
var barColor;
@@ -415,9 +470,11 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
};
- updateBarColors = () => {
+ // reloads the bar colors and selected bars
+ updateSavedUI = () => {
var svg = this._histogramSvg;
- if (svg)
+ if (svg) {
+ // bar color
svg.selectAll('rect').attr('fill', (d: any) => {
var barColor;
const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
@@ -430,10 +487,11 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
});
return barColor ? StrCast(barColor) : StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor);
});
+ }
};
render() {
- this.updateBarColors();
+ this.updateSavedUI();
this._histogramData;
var curSelectedBarName = '';
var titleAccessor: any = 'dataViz_histogram_title';
@@ -442,6 +500,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
+ if (!this._props.layoutDoc.dataViz_histogram_selectedData) this._props.layoutDoc.dataViz_histogram_selectedData = new List<string>();
var selected = 'none';
if (this._currSelected) {
curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index e093ec648..e79f4cde5 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -48,6 +48,8 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
@observable _currSelected: any | undefined = undefined;
+ private selectedData: any[] = []; // array of selected data points
+
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
constructor(props: any) {
super(props);
@@ -71,11 +73,6 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
- // .filter(link => {
- // return link.link_anchor_1 == this._props.Document.dataViz_parentViz;
- // }) // 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 incomingHighlited() {
// return selected x and y axes
@@ -91,6 +88,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
}
componentDidMount() {
+ // draw chart
this._disposers.chartData = reaction(
() => ({ dataSet: this._lineChartData, w: this.width, h: this.height }),
({ dataSet, w, h }) => {
@@ -100,31 +98,23 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
},
{ 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,
- incomingHighlited: this.incomingHighlited,
- }),
- ({ selected, incomingHighlited }) => {
- // 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);
- incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this._props.axes[0]]), Number(record[this._props.axes[1]])));
- },
- { fireImmediately: true }
- );
+
+ // coloring the selected point
+ const elements = document.querySelectorAll('.datapoint');
+ for (let i = 0; i < elements.length; i++) {
+ const x = Number(elements[i].getAttribute('data-x'));
+ const y = Number(elements[i].getAttribute('data-y'));
+ const selectedDataBars = StrListCast(this._props.layoutDoc.dataViz_lineChart_selectedData)
+ let selected = false;
+ selectedDataBars.forEach(eachSelectedData => {
+ // parse each selected point into x,y
+ let xy = eachSelectedData.split(',');
+ if (Number(xy[0])===x && Number(xy[1])===y) selected = true;
+ })
+ if (selected) {
+ this.drawAnnotations(x, y, false);
+ }
+ }
}
// 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
@@ -137,12 +127,9 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
element.classList.remove('selected');
}
};
- // gets called whenever the "data_annotations" fields gets updated
+
+ // draws red annotation on data points when selected
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];
@@ -151,25 +138,11 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
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 {
- // }
}
};
@action
- restoreView = (data: Doc) => {
- const coords = Cast(data.config_dataVizSelection, 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) => {
@@ -211,13 +184,49 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.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?
- 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.records.forEach(record => record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true));
+ setCurrSelected(d: DataPoint) {
+ let sameAsAny = false;
+ const selectedDatapoints = Cast(this._props.layoutDoc.dataViz_lineChart_selectedData, listSpec('string'), null);
+ this.selectedData.forEach(eachData => {
+ if (!sameAsAny){
+ if (eachData.x==d.x && eachData.y==d.y) {
+ sameAsAny = true;
+ let index = this.selectedData.indexOf(eachData)
+ this.selectedData.splice(index, 1);
+ selectedDatapoints.splice(index, 1);
+ this._currSelected = undefined;
+ }
+ }
+ })
+ if(!sameAsAny) {
+ this.selectedData.push(d);
+ selectedDatapoints.push(d.x + "," + d.y);
+ this._currSelected = this.selectedData.length>1? undefined : d;
+ }
+
+ // for filtering child dataviz docs
+ if (this._props.layoutDoc.dataViz_filterSelection){
+ const selectedRows = Cast(this._props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+ this._tableDataIds.forEach(rowID => {
+ if (this._props.records[rowID][this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')==d.x
+ && this._props.records[rowID][this._props.axes[1]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')==d.y) {
+ if (!selectedRows?.includes(rowID)) selectedRows?.push(rowID); // adding to filtered rows
+ else if (sameAsAny) selectedRows.splice(selectedRows.indexOf(rowID), 1); // removing from filtered rows
+ }
+ })
+ }
+
+ // coloring the selected point
+ const elements = document.querySelectorAll('.datapoint');
+ for (let i = 0; i < elements.length; i++) {
+ const x = Number(elements[i].getAttribute('data-x'));
+ const y = Number(elements[i].getAttribute('data-y'));
+ if (x===d.x && y===d.y) {
+ if (sameAsAny) elements[i].classList.remove('brushed');
+ else elements[i].classList.add('brushed');
+ }
+ }
}
drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -345,8 +354,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
const x0 = bisect(data, xScale.invert(xPos - 5)); // shift x by -5 so that you can reach points on the left-side axis
const d0 = data[x0];
// find .circle-d1 with data-x = d0.x and data-y = d0.y
- const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
- this.setCurrSelected(d0.x, d0.y);
+ this.setCurrSelected(d0);
this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
});
@@ -398,6 +406,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+ if (!this._props.layoutDoc.dataViz_lineChart_selectedData) this._props.layoutDoc.dataViz_lineChart_selectedData = new List<string>();
const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
var selectedTitle = "";
if (this._currSelected && this._props.titleCol){
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index fc23f47de..5c341e0b4 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -38,8 +38,8 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _piechartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _piechartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
- private curSliceSelected: any = undefined; // d3 data of selected slice
- private selectedData: any = undefined; // Selection of selected slice
+ private curSliceSelected: any = undefined; // d3 data of selected slice for when just one slice is selected
+ private selectedData: any[] = []; // array of selected slices
private hoverOverData: any = undefined; // Selection of slice being hovered over
@observable _currSelected: any | undefined = undefined; // Object of selected slice
@@ -84,20 +84,35 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
- // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // 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
}
componentWillUnmount() {
Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
}
componentDidMount() {
+ // draw chart
this._disposers.chartData = reaction(
() => ({ dataSet: this._pieChartData, w: this.width, h: this.height }),
({ dataSet, w, h }) => dataSet!.length > 0 && this.drawChart(dataSet, w, h),
{ fireImmediately: true }
);
+ // restore selected slices
+ var svg = this._piechartSvg;
+ if (svg && this._pieChartData[0]) {
+ let key = Object.keys(this._pieChartData[0])[0]
+ const selectedDataBars = StrListCast(this._props.layoutDoc.dataViz_pie_selectedData)
+ svg.selectAll('path').attr('class', (d: any) => {
+ let selected = false;
+ selectedDataBars.forEach(eachSelectedData => {
+ if (d[key]==eachSelectedData) selected = true;
+ })
+ if (selected){
+ this.selectedData.push(d);
+ return 'slice hover'
+ }
+ else return 'slice';
+ });
+ }
}
@action
@@ -163,21 +178,54 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
if (lineCrossCount % 2 != 0) {
// inside the slice of it crosses an odd number of edges
var showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
+ let key = 'data' // key that represents slice
+ if (Object.keys(showSelected)[0]=='frequency') key = Object.keys(showSelected)[1]
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]]
: this._currSelected === showSelected;
- this._currSelected = sameAsCurrent ? undefined : showSelected;
- this.selectedData = sameAsCurrent ? undefined : d;
+ let sameAsAny = false;
+ const selectedDataSlices = Cast(this._props.layoutDoc.dataViz_pie_selectedData, listSpec('number'), null);
+ this.selectedData.forEach(eachData => {
+ if (!sameAsAny){
+ let match = true;
+ Object.keys(d).forEach(key => {
+ if (d[key] != eachData[key]) match = false;
+ })
+ if (match) {
+ sameAsAny = true;
+ let index = this.selectedData.indexOf(eachData)
+ this.selectedData.splice(index, 1);
+ selectedDataSlices.splice(index, 1);
+ this._currSelected = undefined;
+ }
+ }
+ })
+ if(!sameAsAny) {
+ this.selectedData.push(d);
+ selectedDataSlices.push(d[key]);
+ this._currSelected = this.selectedData.length>1? undefined : showSelected;
+ }
+
+ // for filtering child dataviz docs
+ if (this._props.layoutDoc.dataViz_filterSelection){
+ const selectedRows = Cast(this._props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+ this._tableDataIds.forEach(rowID => {
+ let match = false;
+ if (this._props.records[rowID][key] == d[key]) match = true;
+ if (match && !selectedRows?.includes(rowID)) selectedRows?.push(rowID); // adding to filtered rows
+ else if (match && sameAsAny) selectedRows.splice(selectedRows.indexOf(rowID), 1); // removing from filtered rows
+ })
+ }
} else this.hoverOverData = d;
return true;
}
return false;
});
if (changeSelectedVariables) {
- if (sameAsCurrent!) this.curSliceSelected = undefined;
- else this.curSliceSelected = selected;
+ if (this._currSelected) this.curSliceSelected = selected;
+ else this.curSliceSelected = undefined;
}
};
@@ -237,9 +285,11 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
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';
+ let selected = false;
+ selectedData.forEach((eachSelectedData: any) => {
+ if (d.startAngle==eachSelectedData.startAngle) selected = true;
+ })
+ return selected || (hoverOverSlice && d.startAngle == hoverOverSlice.startAngle && d.endAngle == hoverOverSlice.endAngle) ? 'slice hover' : 'slice';
});
};
@@ -257,8 +307,14 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
possibleDataPointVals.push(dataPointVal);
});
const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+
+ // to make sure all important slice information is on 'd' object
+ let addKey: any = false;
+ if (Object.keys(pieDataSet[0])[0]=='frequency'){
+ addKey = Object.keys(pieDataSet[0])[1]
+ }
arcs.append('path')
- .attr('fill', (d, i) => {
+ .attr('fill', (d: any, i) => {
var dataPoint;
const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
@@ -268,6 +324,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
}
var sliceColor;
if (dataPoint) {
+ if (addKey) d[addKey] = dataPoint[addKey] // adding all slice information to d
const sliceTitle = dataPoint[this._props.axes[0]];
const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1]));
@@ -276,13 +333,13 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
})
.attr(
'class',
- selected
- ? function (d) {
- return selected && d.startAngle == selected.startAngle && d.endAngle == selected.endAngle ? 'slice hover' : 'slice';
- }
- : function (d) {
- return 'slice';
- }
+ function (d: any) {
+ let selectThisData = false;
+ selected.forEach((eachSelectedData: any) => {
+ if (d.startAngle==eachSelectedData.startAngle) selectThisData = true;
+ })
+ return selectThisData ? 'slice hover' : 'slice';
+ }
)
// @ts-ignore
.attr('d', arc)
@@ -337,6 +394,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
+ if (!this._props.layoutDoc.dataViz_pie_selectedData) this._props.layoutDoc.dataViz_pie_selectedData = new List<string>();
var selected: string;
var curSelectedSliceName = '';
if (this._currSelected) {
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 67e1c67bd..7ad5a0e6b 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -53,6 +53,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
makeObservable(this);
}
+ @action
componentDidMount() {
// if the tableData changes (ie., when records are selected by the parent (input) visulization),
// then we need to remove any selected rows that are no longer part of the visualized dataset.
@@ -137,7 +138,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
const targetCreator = (annotationOn: Doc | undefined) => {
const embedding = Doc.MakeEmbedding(this._props.docView?.()!.Document!);
embedding._dataViz = DataVizView.TABLE;
- embedding._dataViz_axes = new List<string>([col, col]);
+ embedding._dataViz_axes = new List<string>([col]);
embedding._dataViz_parentViz = this._props.Document;
embedding.annotationOn = annotationOn;
embedding.histogramBarColors = Field.Copy(this._props.layoutDoc.histogramBarColors);
@@ -185,9 +186,9 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
filter = undoable((e: any) => {
var start: any;
var end: any;
- if (this.filteringType == 'Range') {
- start = (this.filteringVal[0] as Number) ? Number(this.filteringVal[0]) : this.filteringVal[0];
- end = (this.filteringVal[1] as Number) ? Number(this.filteringVal[1]) : this.filteringVal[0];
+ if (this.filteringType === 'Range') {
+ start = Number.isNaN(Number(this.filteringVal[0])) ? this.filteringVal[0] : Number(this.filteringVal[0]);
+ end = Number.isNaN(Number(this.filteringVal[1])) ? this.filteringVal[1] : Number(this.filteringVal[1]);
}
this._tableDataIds.forEach(rowID => {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 29b1ca365..40946cd36 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -15,6 +15,7 @@ import { DocUtils, Docs } from '../../../documents/Documents';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { AnchorMenu } from '../AnchorMenu';
import './GPTPopup.scss';
+import { DataVizView } from '../../nodes/DataVizBox/DataVizBox';
export enum GPTPopupMode {
SUMMARY,
@@ -29,6 +30,7 @@ interface GPTPopupProps {}
export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
static Instance: GPTPopup;
@observable private chatMode: boolean = false;
+ private correlatedColumns: string[] = []
@observable
public visible: boolean = false;
@@ -121,6 +123,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
};
public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
+ public createFilteredDoc: (axes?: any, type?: DataVizView) => boolean = () => false;
public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
/**
@@ -149,6 +152,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
this.setLoading(false);
};
+ /**
+ * Completes an API call to generate a summary of
+ * this.selectedText in the popup.
+ */
generateSummary = async () => {
GPTPopup.Instance.setVisible(true);
GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
@@ -163,12 +170,22 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
GPTPopup.Instance.setLoading(false);
}
+ /**
+ * Completes an API call to generate an analysis of
+ * this.dataJson in the popup.
+ */
generateDataAnalysis = async () => {
GPTPopup.Instance.setVisible(true);
GPTPopup.Instance.setLoading(true);
try {
let res = await gptAPICall(this.dataJson, GPTCallType.DATA, this.dataChatPrompt);
- GPTPopup.Instance.setText(res || 'Something went wrong.');
+ console.log(res)
+ let json = JSON.parse(res! as string);
+ const keys = Object.keys(json)
+ this.correlatedColumns = []
+ this.correlatedColumns.push(json[keys[0]])
+ this.correlatedColumns.push(json[keys[1]])
+ GPTPopup.Instance.setText(json[keys[2]] || 'Something went wrong.');
} catch (err) {
console.error(err);
}
@@ -195,6 +212,13 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
};
/**
+ * Creates a histogram to show the correlation relationship that was found
+ */
+ private createVisualization = () => {
+ this.createFilteredDoc(this.correlatedColumns);
+ };
+
+ /**
* Transfers the image urls to actual image docs
*/
private transferToImage = (source: string) => {
@@ -362,6 +386,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
:(
<>
<Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Create a graph to visualize the correlation results" text="Visualize" onClick={this.createVisualization} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
<Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
</>
) : (