aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/nodes/DataVizBox/ChartBox.tsx59
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx67
-rw-r--r--src/client/views/nodes/DataVizBox/LineChart.tsx128
-rw-r--r--src/client/views/nodes/DataVizBox/utils/D3Utils.ts15
4 files changed, 209 insertions, 60 deletions
diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index a03aaae31..92ad76e61 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -1,18 +1,18 @@
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../../../fields/Doc';
-// import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, InteractionItem, PointElement, LineElement } from 'chart.js';
-// import { Bar, getDatasetAtEvent, getElementAtEvent, Line } from 'react-chartjs-2';
+import { Doc, HeightSym } from '../../../../fields/Doc';
import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types';
import { action, computed, observable } from 'mobx';
-import { Cast, StrCast } from '../../../../fields/Types';
-// import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { CategoricalChartState } from 'recharts/types/chart/generateCategoricalChart';
import { LineChart } from './LineChart';
export interface ChartBoxProps {
rootDoc: Doc;
+ dataDoc: Doc;
pairs: { x: number; y: number }[];
+ setChartBox: (chartBox: ChartBox) => void;
+ getAnchor: () => Doc;
}
const primaryColor = 'rgba(53, 162, 235, 0.5)';
@@ -38,6 +38,7 @@ export interface ChartData {
@observer
export class ChartBox extends React.Component<ChartBoxProps> {
@observable private _chartData: ChartData | undefined = undefined;
+ @observable private currChart: LineChart | undefined;
@computed get currView() {
if (this.props.rootDoc._dataVizView) {
@@ -54,6 +55,10 @@ export class ChartBox extends React.Component<ChartBoxProps> {
}
}
+ setCurrChart(chart: LineChart | undefined) {
+ // this.currChart = chart;
+ }
+
@action
generateChartData() {
if (this.props.rootDoc._chartData) {
@@ -107,6 +112,11 @@ export class ChartBox extends React.Component<ChartBoxProps> {
componentDidMount() {
// this.generateChartJsData();
this.generateChartData();
+ // setting timeout to allow rerender cycle of the actual chart tof inish
+ setTimeout(() => {
+ this.props.setChartBox(this);
+ });
+ // this.props.setChartBox(this);
}
@action
@@ -117,32 +127,6 @@ export class ChartBox extends React.Component<ChartBoxProps> {
this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase();
};
- // @action
- // onClick = (e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
- // e.preventDefault();
- // console.log(e);
-
- // if (getDatasetAtEvent(this._chartRef.current, e).length == 0) return;
- // if (!this._chartJsData) return;
- // if (this._prevIndex && this._prevColor) {
- // this._chartJsData.datasets[this._prevIndex.dIndex].backgroundColor[this._prevIndex.index] = this._prevColor;
- // }
-
- // const currSelected = getElementAtEvent(this._chartRef.current, e);
- // // TODO - nda: the currSelected might not have the updated color variables so look into that
- // this._currSelected = currSelected;
- // const index = { datasetIndex: currSelected[0].datasetIndex, index: currSelected[0].index };
- // this._prevIndex = { dIndex: index.datasetIndex, index: index.index };
- // this._prevColor = this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index];
- // this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor;
- // this._chartRef.current.update();
- // // stringify this._chartJsData
- // const strData = JSON.stringify(this._chartJsData);
- // this.props.rootDoc._chartData = strData;
- // this.props.rootDoc._prevColor = this._prevColor;
- // this.props.rootDoc._prevIndex = JSON.stringify(this._prevIndex);
- // };
-
onMouseMove = (e: CategoricalChartState) => {
console.log(e);
@@ -158,10 +142,16 @@ export class ChartBox extends React.Component<ChartBoxProps> {
// }
};
+ scrollFocus(doc: Doc, smooth: boolean) {}
+
handleDotClick = (e: any) => {
console.log(e);
};
+ _getAnchor() {
+ return this.currChart?._getAnchor();
+ }
+
// handleTextClick = (e: any) => {
// console.log(e);
// }
@@ -195,6 +185,11 @@ export class ChartBox extends React.Component<ChartBoxProps> {
render() {
if (this.props.pairs && this._chartData) {
+ let width = NumCast(this.props.rootDoc._width);
+ width = width * 0.7;
+ let height = NumCast(this.props.rootDoc._height);
+ height = height * 0.7;
+ console.log(width, height);
return (
<div>
<div>
@@ -204,7 +199,7 @@ export class ChartBox extends React.Component<ChartBoxProps> {
<Bar ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} />
)} */}
{/* {this.reLineChart} */}
- <LineChart width={500} height={300} data={this._chartData} />
+ <LineChart width={width} height={height} data={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />
</div>
<button onClick={e => this.onClickChangeChart(e)} value="line">
Line
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index a4a4028f2..4883385bb 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,9 +1,11 @@
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { Doc, FieldResult } from '../../../../fields/Doc';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
-import { ViewBoxBaseComponent } from '../../DocComponent';
+import { Docs } from '../../../documents/Documents';
+import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent } from '../../DocComponent';
import { FieldViewProps, FieldView } from '../FieldView';
import { ChartBox } from './ChartBox';
import './DataVizBox.scss';
@@ -15,11 +17,42 @@ enum DataVizView {
}
@observer
-export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+ // 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 }[] = [];
@observable private pairs: { x: number; y: number }[] = [];
+ private _chartBox: ChartBox | 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 - make this use enum values instead
// @observable private currView: DataVizView = DataVizView.TABLE;
+
+ // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops
+
+ setChartBox = (chartBox: ChartBox) => {
+ this._chartBox = chartBox;
+ };
+
@computed get currView() {
if (this.rootDoc._dataVizView) {
return StrCast(this.rootDoc._dataVizView);
@@ -28,6 +61,32 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
+ scrollFocus = (doc: Doc, smooth: boolean) => {
+ // reconstruct the view based on the anchor ->
+ // keep track of the datavizbox view and the type of chart we're rendering
+
+ // use dataview to restore part of it and then pass on the rest to the chartbox
+
+ // pseudocode
+ setTimeout(() => this._chartBox?.scrollFocus(doc, smooth)); /* smooth parameter true = do animations */
+ return 0;
+ };
+
+ getAnchor() {
+ // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
+ const anchor =
+ // this._COMPONENTNAME._getAnchor() ??
+ this._chartBox?._getAnchor() ??
+ Docs.Create.TextanchorDocument({
+ /*put in some options*/
+ });
+
+ anchor.dataView = this.currView;
+
+ this.addDocument(anchor);
+ return anchor;
+ // have some other function in code that
+ }
constructor(props: any) {
super(props);
if (!this.rootDoc._dataVizView) {
@@ -45,7 +104,7 @@ export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
case 'table':
return <TableBox pairs={this.pairs} />;
case 'histogram':
- return <ChartBox rootDoc={this.rootDoc} pairs={this.pairs} />;
+ return <ChartBox getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
// case "histogram":
// return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)
}
diff --git a/src/client/views/nodes/DataVizBox/LineChart.tsx b/src/client/views/nodes/DataVizBox/LineChart.tsx
index 42e9da3d7..89fb6e20b 100644
--- a/src/client/views/nodes/DataVizBox/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/LineChart.tsx
@@ -5,13 +5,27 @@ import { ChartData, DataPoint } from './ChartBox';
// import d3
import * as d3 from 'd3';
import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from './utils/D3Utils';
+import { Docs } from '../../../documents/Documents';
+import { Doc, DocListCast } from '../../../../fields/Doc';
interface LineChartProps {
data: ChartData;
width: number;
height: number;
+ dataDoc: Doc;
+ fieldKey: string;
+ // returns linechart component but should be generic chart
+ setCurrChart: (chart: LineChart | undefined) => void;
+ getAnchor: () => Doc;
}
+type minMaxRange = {
+ xMin: number | undefined;
+ xMax: number | undefined;
+ yMin: number | undefined;
+ yMax: number | undefined;
+};
+
interface SelectedDataPoint {
x: number;
y: number;
@@ -21,61 +35,137 @@ interface SelectedDataPoint {
@observer
export class LineChart extends React.Component<LineChartProps> {
private _dataReactionDisposer: IReactionDisposer | undefined = undefined;
+ private _heightReactionDisposer: IReactionDisposer | undefined = undefined;
+ private _widthReactionDisposer: IReactionDisposer | undefined;
@observable private _x: number = 0;
@observable private _y: number = 0;
@observable private _currSelected: SelectedDataPoint | undefined = undefined;
// create ref for the div
private _chartRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _rangeVals: minMaxRange = {
+ xMin: undefined,
+ xMax: undefined,
+ yMin: undefined,
+ yMax: undefined,
+ };
+
+ // 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
+
+ // write the getanchor function that gets whatever I want as the link anchor
componentDidMount() {
- console.log('Getting to line chart');
- this.drawChart();
- this._dataReactionDisposer = reaction(() => this.props.data, this.drawChart);
+ // this._rangeVals = minMaxRange(this.props.data.data);
+ // this.drawChart();
+ this._dataReactionDisposer = reaction(
+ () => this.props.data,
+ data => {
+ this._rangeVals = minMaxRange(data.data);
+ this.drawChart();
+ },
+ { fireImmediately: true }
+ );
+ this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this));
+ this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this));
+ 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
+ },
+ { fireImmediately: true }
+ );
+ setTimeout(() => {
+ this.props.setCurrChart(this);
+ });
+ }
+
+ // gets called whenever the "data-annotations" fields gets updated
+ drawAnnotations() {}
+
+ _getAnchor() {
+ // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
+
+ // TODO: nda - look at pdfviewer get anchor for args
+ const doc = Docs.Create.TextanchorDocument({
+ /*put in some options*/
+ });
+ // access curr selected from the charts
+ doc.x = this._currSelected?.x;
+ doc.y = this._currSelected?.y;
+ doc.chartType = 'line';
+ return doc;
+ // have some other function in code that
}
componentWillUnmount() {
if (this._dataReactionDisposer) {
this._dataReactionDisposer();
}
+ if (this._heightReactionDisposer) {
+ this._heightReactionDisposer();
+ }
+ if (this._widthReactionDisposer) {
+ this._widthReactionDisposer();
+ }
}
tooltipContent(data: DataPoint) {
return `<b>x: ${data.x} y: ${data.y}</b>`;
}
+ // TODO: nda - can use d3.create() to create html element instead of appending
drawChart() {
+ console.log('Drawing chart');
// clearing tooltip
d3.select(this._chartRef.current).select('svg').remove();
d3.select(this._chartRef.current).select('.tooltip').remove();
+ // TODO: nda - refactor code so that it only recalculates min max and things related to data on data update
const margin = { top: 50, right: 50, bottom: 50, left: 50 };
const { data } = this.props;
- const { xMin, xMax, yMin, yMax } = minMaxRange(data.data);
+ const { xMin, xMax, yMin, yMax } = this._rangeVals;
+ console.log('rangeVals', this._rangeVals);
+
+ const width = this.props.width - margin.left - margin.right;
+ const height = this.props.height - margin.top - margin.bottom;
+ console.log('height, width', this.props.height, this.props.width);
// adding svg
- const svg = d3.select(this._chartRef.current).append('svg').attr('width', '100%').attr('height', '100%').append('g').attr('transform', `translate(${margin.left}, ${margin.top})`);
+ const svg = d3
+ .select(this._chartRef.current)
+ .append('svg')
+ .attr('width', `${width + margin.right + margin.left}`)
+ .attr('height', `${height + margin.top + margin.bottom}`)
+ .append('g')
+ .attr('transform', `translate(${margin.left}, ${margin.top})`);
// adding tooltip
// const tooltip = d3.select('#chart-container').append('div').attr('class', 'tooltip');
+ console.log('xMinMax:', xMin, xMax);
+ console.log('yMinMax:', yMin, yMax);
- if (!xMin || !xMax || !yMin || !yMax) {
+ if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) {
+ console.log('getting here');
// TODO: nda - error handle
return;
}
- const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.props.width);
- // adding x axis
- const yScale = scaleCreatorNumerical(0, yMax, this.props.height, 0);
+
+ console.log('getting here');
+
+ // creating the x and y scales
+ const xScale = scaleCreatorNumerical(xMin, xMax, 0, width);
+ const yScale = scaleCreatorNumerical(0, yMax, height, 0);
// create a line function that takes in the data.data.x and data.data.y
// TODO: nda - fix the types for the d here
const lineGen = createLineGenerator(xScale, yScale);
// create x and y grids
- xGrid(svg.append('g'), this.props.height, xScale);
- yGrid(svg.append('g'), this.props.width, yScale);
- xAxisCreator(svg.append('g'), this.props.height, xScale);
- yAxisCreator(svg.append('g'), this.props.width, yScale);
+ xGrid(svg.append('g'), height, xScale);
+ yGrid(svg.append('g'), width, yScale);
+ xAxisCreator(svg.append('g'), height, xScale);
+ yAxisCreator(svg.append('g'), width, yScale);
// draw the line
drawLine(svg.append('path'), data.data, lineGen);
@@ -117,8 +207,8 @@ export class LineChart extends React.Component<LineChartProps> {
this._y = d0.y;
focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`);
// TODO: nda - implement tooltips
- // tooltip.transition().duration(300).style('opacity', 0.9);
- // tooltip.html(() => this.tooltipContent(d0)).attr('transform', `translate(${xScale(d0.x) + 30}px,${yScale(d0.y) + 30}px)`);
+ tooltip.transition().duration(300).style('opacity', 0.9);
+ tooltip.html(() => this.tooltipContent(d0)).attr('transform', `translate(${xScale(d0.x) + 30}px,${yScale(d0.y) + 30}px)`);
});
const onPointClick = action((e: any) => {
@@ -132,13 +222,17 @@ export class LineChart extends React.Component<LineChartProps> {
const selected = svg.selectAll('.circle-d1').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
this._currSelected = { x: d0.x, y: d0.y, elem: selected };
console.log('Getting here');
+ setTimeout(() => this.props.getAnchor());
+ // this.props.getAnchor();
console.log(this._currSelected);
});
svg.append('rect')
.attr('class', 'overlay')
- .attr('width', this.props.width)
- .attr('height', this.props.height)
+ .attr('width', width)
+ .attr('height', height + margin.top + margin.bottom)
+ .attr('fill', 'none')
+ .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`)
.style('opacity', 0)
.on('mouseover', () => {
focus.style('display', null);
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 90ec35f5c..5c7f9bce1 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -4,11 +4,12 @@ import { DataPoint } from '../ChartBox';
// TODO: nda - implement function that can handle range for strings
export const minMaxRange = (dataPts: DataPoint[]) => {
- const yMin = d3.min(dataPts, d => d.y);
- const yMax = d3.max(dataPts, d => d.y);
+ console.log(Number(dataPts[4].y));
+ const yMin = d3.min(dataPts, d => Number(d.y));
+ const yMax = d3.max(dataPts, d => Number(d.y));
- const xMin = d3.min(dataPts, d => d.x);
- const xMax = d3.max(dataPts, d => d.x);
+ const xMin = d3.min(dataPts, d => Number(d.x));
+ const xMax = d3.max(dataPts, d => Number(d.x));
return { xMin, xMax, yMin, yMax };
};
@@ -37,12 +38,12 @@ export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefin
g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15));
};
-export const yAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, width: number, yScale: d3.ScaleLinear<number, number, never>) => {
+export const yAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, marginLeft: number, yScale: d3.ScaleLinear<number, number, never>) => {
g.attr('class', 'y-axis').call(d3.axisLeft(yScale));
};
export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, scale: d3.ScaleLinear<number, number, never>) => {
- g.attr('class', 'grid')
+ g.attr('class', 'xGrid')
.attr('transform', `translate(0,${height})`)
.call(
d3
@@ -53,7 +54,7 @@ export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, he
};
export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, width: number, scale: d3.ScaleLinear<number, number, never>) => {
- g.attr('class', 'grid').call(
+ g.attr('class', 'yGrid').call(
d3
.axisLeft(scale)
.tickSize(-width)