diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/DataVizBox/ChartBox.tsx | 59 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/DataVizBox.tsx | 67 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/LineChart.tsx | 128 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/utils/D3Utils.ts | 15 |
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) |