diff options
Diffstat (limited to 'src')
15 files changed, 133 insertions, 632 deletions
diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index f2450bc7c..a2e4fd73c 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -1,11 +1,10 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, HeightSym } from '../../../../fields/Doc'; -import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types'; import { action, computed, observable } from 'mobx'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; -import { CategoricalChartState } from 'recharts/types/chart/generateCategoricalChart'; -import { LineChart } from './LineChart'; +import { LineChart } from './components/LineChart'; +import { Chart, ChartData } from './ChartInterface'; export interface ChartBoxProps { rootDoc: Doc; @@ -15,26 +14,11 @@ export interface ChartBoxProps { getAnchor: () => Doc; } -const primaryColor = 'rgba(53, 162, 235, 0.5)'; -const selectedColor = 'rgba(255, 99, 132, 0.5)'; - -export interface RechartData { - name: string | number; - y: number; -} - export interface DataPoint { x: number; y: number; } -export interface ChartData { - xLabel: string; - yLabel: string; - data: DataPoint[]; - tooltipContent: (data: DataPoint) => string; -} - @observer export class ChartBox extends React.Component<ChartBoxProps> { @observable private _chartData: ChartData | undefined = undefined; @@ -55,7 +39,7 @@ export class ChartBox extends React.Component<ChartBoxProps> { } } - setCurrChart(chart: LineChart | undefined) { + setCurrChart(chart: Chart | undefined) { // this.currChart = chart; } @@ -69,17 +53,15 @@ export class ChartBox extends React.Component<ChartBoxProps> { const data: ChartData = { xLabel: '', yLabel: '', - data: [], - tooltipContent: (data: DataPoint) => { - return `<b>x: ${data.x} y: ${data.y}</b>`; - }, + data: [[]], }; if (this.props.pairs && this.props.pairs.length > 0) { data.xLabel = 'x'; data.yLabel = 'y'; this.props.pairs.forEach(pair => { - data.data.push({ x: pair.x, y: pair.y }); + // TODO: nda - add actual support for multiple sets of data + data.data[0].push({ x: pair.x, y: pair.y }); }); } @@ -87,30 +69,7 @@ export class ChartBox extends React.Component<ChartBoxProps> { this.props.rootDoc._chartData = JSON.stringify(data); } - // @action - // generateChartJsData() { - // if (this.props.rootDoc._chartData) { - // // parse the string into a json object - // this._chartJsData = JSON.parse(StrCast(this.props.rootDoc._chartData)); - // this._prevColor = StrCast(this.props.rootDoc._prevColor); - // this._prevIndex = JSON.parse(StrCast(this.props.rootDoc._prevIndex)); - // return; - // } - // const labels = this.props.pairs.map(p => p.x); - // const dataset = { - // label: 'Dataset 1', - // data: this.props.pairs.map(p => p.y), - // backgroundColor: this.props.pairs.map(p => primaryColor), - // }; - // const data = { - // labels, - // datasets: [dataset], - // }; - // this._chartJsData = data; - // } - componentDidMount() { - // this.generateChartJsData(); this.generateChartData(); // setting timeout to allow rerender cycle of the actual chart tof inish setTimeout(() => { @@ -127,21 +86,6 @@ export class ChartBox extends React.Component<ChartBoxProps> { this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase(); }; - onMouseMove = (e: CategoricalChartState) => { - console.log(e); - - // pops up at 526 when it should be at 263 - // at 100%scale it pops up at 526 but mouse shows 263 at 50% scale - if (e.chartX && e.chartY) { - e.chartX += 263; - e.chartY += 263; - } - // if (e.chartX && e.chartY) { - // e.chartX -= 200; - // e.chartY -= 200; - // } - }; - scrollFocus(doc: Doc, smooth: boolean) {} handleDotClick = (e: any) => { @@ -152,37 +96,6 @@ export class ChartBox extends React.Component<ChartBoxProps> { return this.currChart?._getAnchor(); } - // handleTextClick = (e: any) => { - // console.log(e); - // } - - // @computed get reLineChart() { - // return ( - // // <ResponsiveContainer width="80%" height="80%"> - // <LineChart - // width={500} - // height={300} - // data={this._chartData} - // margin={{ - // top: 5, - // right: 30, - // left: 20, - // bottom: 5, - // }} - // onMouseDown={e => console.log(e)} - // onMouseMove={e => this.onMouseMove(e)}> - // <CartesianGrid strokeDasharray="3 3" /> - // <XAxis dataKey="name" onMouseDown={e => console.log(e)} /> - // <YAxis /> - // <Tooltip /> - // <Legend /> - // <Line type="monotone" dataKey="y" stroke="#8884d8" activeDot={{ r: 8, onClick: this.handleDotClick }} /> - // {/* <Line type="monotone" dataKey="uv" stroke="#82ca9d" /> */} - // </LineChart> - // // </ResponsiveContainer> - // ); - // } - render() { if (this.props.pairs && this._chartData) { let width = NumCast(this.props.rootDoc._width); @@ -200,7 +113,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 margin={margin} width={width} height={height} data={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} /> + <LineChart margin={margin} width={width} height={height} chartData={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/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts new file mode 100644 index 000000000..6e37f966c --- /dev/null +++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts @@ -0,0 +1,33 @@ +import { Doc } from '../../../../fields/Doc'; +import { DataPoint } from './ChartBox'; +import { LineChart } from './components/LineChart'; + +export interface Chart { + tooltipContent: (data: DataPoint) => string; + drawChart: () => void; + height: number; + width: number; +} + +export interface ChartProps { + chartData: ChartData; + width: number; + height: number; + dataDoc: Doc; + fieldKey: string; + // returns linechart component but should be generic chart + setCurrChart: (chart: Chart) => void; + getAnchor: () => Doc; + margin: { + top: number; + right: number; + bottom: number; + left: number; + }; +} + +export interface ChartData { + xLabel: string; + yLabel: string; + data: DataPoint[][]; +} diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 4883385bb..1d9b309ce 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -9,7 +9,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent } from '../../DocComp import { FieldViewProps, FieldView } from '../FieldView'; import { ChartBox } from './ChartBox'; import './DataVizBox.scss'; -import { TableBox } from './TableBox'; +import { TableBox } from './components/TableBox'; enum DataVizView { TABLE = 'table', diff --git a/src/client/views/nodes/DataVizBox/DrawHelper.ts b/src/client/views/nodes/DataVizBox/DrawHelper.ts deleted file mode 100644 index 595cecebf..000000000 --- a/src/client/views/nodes/DataVizBox/DrawHelper.ts +++ /dev/null @@ -1,247 +0,0 @@ -export class PIXIPoint { - public get x() { return this.coords[0]; } - public get y() { return this.coords[1]; } - public set x(value: number) { this.coords[0] = value; } - public set y(value: number) { this.coords[1] = value; } - public coords: number[] = [0, 0]; - constructor(x: number, y: number) { - this.coords[0] = x; - this.coords[1] = y; - } -} - -export class PIXIRectangle { - public x: number; - public y: number; - public width: number; - public height: number; - public get left() { return this.x; } - public get right() { return this.x + this.width; } - public get top() { return this.y; } - public get bottom() { return this.top + this.height; } - public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); } - constructor(x: number, y: number, width: number, height: number) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } -} - -export class MathUtil { - - public static EPSILON: number = 0.001; - - public static Sign(value: number): number { - return value >= 0 ? 1 : -1; - } - - public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x += p2.x; - p1.y += p2.y; - return p1; - } - else { - return new PIXIPoint(p1.x + p2.x, p1.y + p2.y); - } - } - - public static Perp(p1: PIXIPoint): PIXIPoint { - return new PIXIPoint(-p1.y, p1.x); - } - - public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x /= by; - p1.y /= by; - return p1; - } - else { - return new PIXIPoint(p1.x / by, p1.y / by); - } - } - - public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) { - if (inline) { - p1.x *= by; - p1.y *= by; - return p1; - } - else { - return new PIXIPoint(p1.x * by, p1.y * by); - } - } - - public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { - if (inline) { - p1.x -= p2.x; - p1.y -= p2.y; - return p1; - } - else { - return new PIXIPoint(p1.x - p2.x, p1.y - p2.y); - } - } - - public static Area(rect: PIXIRectangle): number { - return rect.width * rect.height; - } - - public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) { - // Return minimum distance between line segment vw and point p - var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt - if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case - // Consider the line extending the segment, parameterized as v + t (w - v). - // We find projection of point p onto the line. - // It falls where t = [(p-v) . (w-v)] / |w-v|^2 - // We clamp t from [0,1] to handle points outside the segment vw. - var dot = MathUtil.Dot( - MathUtil.SubtractPoint(p, v), - MathUtil.SubtractPoint(w, v)) / l2; - var t = Math.max(0, Math.min(1, dot)); - // Projection falls on the segment - var projection = MathUtil.AddPoint(v, - MathUtil.MultiplyConstant( - MathUtil.SubtractPoint(w, v), t)); - return MathUtil.Dist(p, projection); - } - - public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined { - var a1 = pe1.y - ps1.y; - var b1 = ps1.x - pe1.x; - - var a2 = pe2.y - ps2.y; - var b2 = ps2.x - pe2.x; - - var delta = a1 * b2 - a2 * b1; - if (delta === 0) { - return undefined; - } - var c2 = a2 * ps2.x + b2 * ps2.y; - var c1 = a1 * ps1.x + b1 * ps1.y; - var invdelta = 1 / delta; - return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta); - } - - public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean { - if (p.x < rect.left - this.EPSILON) { - return false; - } - if (p.x > rect.right + this.EPSILON) { - return false; - } - if (p.y < rect.top - this.EPSILON) { - return false; - } - if (p.y > rect.bottom + this.EPSILON) { - return false; - } - - return true; - } - - public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> { - var r1 = new PIXIPoint(rect.left, rect.top); - var r2 = new PIXIPoint(rect.right, rect.top); - var r3 = new PIXIPoint(rect.right, rect.bottom); - var r4 = new PIXIPoint(rect.left, rect.bottom); - var ret = new Array<PIXIPoint>(); - var dist = this.Dist(lineFrom, lineTo); - var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1); - if (inter && this.PointInPIXIRectangle(inter, rect) && - this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { - ret.push(inter); - } - return ret; - } - - public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle { - const left = Math.max(rect1.x, rect2.x); - const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width); - const top = Math.max(rect1.y, rect2.y); - const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); - return new PIXIRectangle(left, top, right - left, bottom - top); - } - - public static Dist(p1: PIXIPoint, p2: PIXIPoint): number { - return Math.sqrt(MathUtil.DistSquared(p1, p2)); - } - - public static Dot(p1: PIXIPoint, p2: PIXIPoint): number { - return p1.x * p2.x + p1.y * p2.y; - } - - public static Normalize(p1: PIXIPoint) { - var d = this.Length(p1); - return new PIXIPoint(p1.x / d, p1.y / d); - } - - public static Length(p1: PIXIPoint): number { - return Math.sqrt(p1.x * p1.x + p1.y * p1.y); - } - - public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number { - const a = p1.x - p2.x; - const b = p1.y - p2.y; - return (a * a + b * b); - } - - public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean { - return !(r2.x > r1.x + r1.width || - r2.x + r2.width < r1.x || - r2.y > r1.y + r1.height || - r2.y + r2.height < r1.y); - } - - public static ArgMin(temp: number[]): number { - let index = 0; - let value = temp[0]; - for (let i = 1; i < temp.length; i++) { - if (temp[i] < value) { - value = temp[i]; - index = i; - } - } - return index; - } - - public static ArgMax(temp: number[]): number { - let index = 0; - let value = temp[0]; - for (let i = 1; i < temp.length; i++) { - if (temp[i] > value) { - value = temp[i]; - index = i; - } - } - return index; - } - - public static Combinations<T>(chars: T[]) { - let result = new Array<T>(); - let f = (prefix: any, chars: any) => { - for (let i = 0; i < chars.length; i++) { - result.push(prefix.concat(chars[i])); - f(prefix.concat(chars[i]), chars.slice(i + 1)); - } - }; - f([], chars); - return result; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.scss b/src/client/views/nodes/DataVizBox/HistogramBox.scss deleted file mode 100644 index 5aac9dc77..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramBox.scss +++ /dev/null @@ -1,18 +0,0 @@ -// change the stroke color of line-svg class -.svgLine { - position: absolute; - background: darkGray; - stroke: #000; - stroke-width: 1px; - width:100%; - height:100%; - opacity: 0.4; -} - -.svgContainer { - position: absolute; - top:0; - left:0; - width:100%; - height: 100%; -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx deleted file mode 100644 index 4488323fe..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramBox.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc } from "../../../../fields/Doc"; -import { NumCast } from "../../../../fields/Types"; -import { DragManager } from "../../../util/DragManager"; -import "./HistogramBox.scss"; - -interface HistogramBoxProps { - rootDoc: Doc; - pairs: { - x: number, - y: number - }[] -} - - -export class HistogramBox extends React.Component<HistogramBoxProps> { - private _dropXDispoer?: DragManager.DragDropDisposer; - private _dropYDispoer?: DragManager.DragDropDisposer; - - private origin = {x: 0.1 * this.width, y: 0.9 * this.height}; - - @computed get width() { - return NumCast(this.props.rootDoc.width); - } - - @computed get height() { - return NumCast(this.props.rootDoc.height); - } - - @computed get x() { - return NumCast(this.props.rootDoc.x); - } - - @computed get y() { - return NumCast(this.props.rootDoc.y); - } - - @computed get generatePoints() { - // evenly distribute points along the x axis - const xVals: number[] = this.props.pairs.map(p => p.x); - const yVals: number[] = this.props.pairs.map(p => p.y); - - const xMin = Math.min(...xVals); - const xMax = Math.max(...xVals); - const yMin = Math.min(...yVals); - const yMax = Math.max(...yVals); - - const xRange = xMax - xMin; - const yRange = yMax - yMin; - - const xScale = this.width / xRange; - const yScale = this.height / yRange; - - const xOffset = (this.x + (0.1 * this.width)) - xMin * xScale; - const yOffset = (this.y + (0.25 * this.height)) - yMin * yScale; - - const points: { - x: number, - y: number - }[] = this.props.pairs.map(p => { - return { - x: (p.x * xScale + xOffset) + this.origin.x, - y: (p.y * yScale + yOffset) - } - }); - - return points; - } - - @computed get generateGraphLine() { - const points = this.generatePoints; - // loop through points and create a line from each point to the next - let lines: { - x1: number, - y1: number, - x2: number, - y2: number - }[] = []; - for (let i = 0; i < points.length - 1; i++) { - lines.push({ - x1: points[i].x, - y1: points[i].y, - x2: points[i + 1].x, - y2: points[i + 1].y - }); - } - // generate array of svg with lines - let svgLines: JSX.Element[] = []; - for (let i = 0; i < lines.length; i++) { - svgLines.push( - <line - className="svgLine" - key={i} - x1={lines[i].x1} - y1={lines[i].y1} - x2={lines[i].x2} - y2={lines[i].y2} - stroke="black" - strokeWidth={2} - /> - ); - } - - let res = []; - for (let i = 0; i < svgLines.length; i++) { - res.push(<svg className="svgContainer">{svgLines[i]}</svg>) - } - return res; - } - - @computed get generateAxes() { - - const xAxis = { - x1: 0.1 * this.width, - x2: 0.9 * this.width, - y1: 0.9 * this.height, - y2: 0.9 * this.height, - }; - - const yAxis = { - x1: 0.1 * this.width, - x2: 0.1 * this.width, - y1: 0.25 * this.height, - y2: 0.9 * this.height, - }; - - - return ( - [ - (<svg className="svgContainer"> - {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={this.width - (0.1 * this.width)} y2={xAxis} /> */} - <line className="svgLine" x1={xAxis.x1} y1={xAxis.y1} x2={xAxis.x2} y2={xAxis.y2}/> - - {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */} - </svg>), - ( - <svg className="svgContainer"> - <line className="svgLine" x1={yAxis.x1} y1={yAxis.y1} x2={yAxis.x2} y2={yAxis.y2} /> - {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */} - </svg>) - ] - ) - } - - - render() { - return ( - <div>histogram box - {/* <svg className="svgContainer"> - {this.generateSVGLine} - </svg> */} - {this.generateAxes[0]} - {this.generateAxes[1]} - {this.generateGraphLine.map(line => line)} - </div> - ) - - } - -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss deleted file mode 100644 index e69de29bb..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss +++ /dev/null diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx deleted file mode 100644 index ac1b7699b..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { observer } from "mobx-react"; -import * as React from "react"; -import { HistogramBox } from "./HistogramBox"; - -export interface HistogramPrimitivesProps { - HistoBox: HistogramBox; -} - -@observer -export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> { - render() { - return <div className="histogramboxprimitives-container"> - - </div> - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss deleted file mode 100644 index e69de29bb..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss +++ /dev/null diff --git a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx deleted file mode 100644 index d3440dea1..000000000 --- a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { observer } from "mobx-react"; -import * as React from "react"; -import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives"; -import "./HistogramLabelPrimitives.scss"; - -@observer -export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> { - render() { - return <div className="histogramLabelPrimitives-container"> - - </div> - } -} diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss new file mode 100644 index 000000000..7792a2758 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -0,0 +1,10 @@ +.tooltip { + // make the height width bigger + width: 50px; + height: 50px; +} + +.highlight { + // change the color of the circle element to be red + fill: red; +} diff --git a/src/client/views/nodes/DataVizBox/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 0aeaacf34..88598be3d 100644 --- a/src/client/views/nodes/DataVizBox/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -1,29 +1,14 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ChartData, DataPoint } from './ChartBox'; +import { 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; - margin: { - top: number; - right: number; - bottom: number; - left: number; - }; -} +import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from '../utils/D3Utils'; +import { Docs } from '../../../../documents/Documents'; +import { Doc, DocListCast } from '../../../../../fields/Doc'; +import { Chart, ChartProps } from '../ChartInterface'; +import './Chart.scss'; type minMaxRange = { xMin: number | undefined; @@ -39,7 +24,7 @@ interface SelectedDataPoint { } @observer -export class LineChart extends React.Component<LineChartProps> { +export class LineChart extends React.Component<ChartProps> implements Chart { private _dataReactionDisposer: IReactionDisposer | undefined = undefined; private _heightReactionDisposer: IReactionDisposer | undefined = undefined; private _widthReactionDisposer: IReactionDisposer | undefined; @@ -55,17 +40,17 @@ export class LineChart extends React.Component<LineChartProps> { yMax: undefined, }; + private _chartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | 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() { - // this._rangeVals = minMaxRange(this.props.data.data); - // this.drawChart(); this._dataReactionDisposer = reaction( - () => this.props.data, - data => { - this._rangeVals = minMaxRange(data.data); + () => this.props.chartData, + chartData => { + this._rangeVals = minMaxRange(chartData.data); this.drawChart(); }, { fireImmediately: true } @@ -77,6 +62,7 @@ export class LineChart extends React.Component<LineChartProps> { 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(this.props.chartData.data[0][1].x, this.props.chartData.data[0][1].y); }, { fireImmediately: true } ); @@ -87,7 +73,23 @@ export class LineChart extends React.Component<LineChartProps> { } // gets called whenever the "data-annotations" fields gets updated - drawAnnotations() {} + drawAnnotations(dataX: number, dataY: number) { + // 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('highlight'); + } else { + element.classList.remove('highlight'); + } + } + } _getAnchor() { // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) @@ -128,22 +130,41 @@ export class LineChart extends React.Component<LineChartProps> { return this.props.width - this.props.margin.left - this.props.margin.right; } - @computed get svgContainer() { - const { margin } = this.props; - const svg = d3 - .create('svg') - .attr('width', `${this.width + margin.right + margin.left}`) - .attr('height', `${this.height + margin.top + margin.bottom}`) - .append('g') - .attr('transform', `translate(${margin.left}, ${margin.top})`); - return svg; + setupTooltip() { + const tooltip = d3 + .select(this._chartRef.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'); + return tooltip; + } + + drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) { + if (this._chartSvg) { + const circleClass = '.circle-' + idx; + this._chartSvg + .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); + } } // TODO: nda - can use d3.create() to create html element instead of appending drawChart() { - const { data, margin } = this.props; - console.log(this.height, this.width); - // clearing tooltip + const { chartData, margin } = this.props; + const data = chartData.data[0]; + // clearing tooltip and the current chart d3.select(this._chartRef.current).select('svg').remove(); d3.select(this._chartRef.current).select('.tooltip').remove(); @@ -152,7 +173,7 @@ export class LineChart extends React.Component<LineChartProps> { const { xMin, xMax, yMin, yMax } = this._rangeVals; // const svg = d3.select(this._chartRef.current).append(this.svgContainer.html()); // adding svg - const svg = d3 + this._chartSvg = d3 .select(this._chartRef.current) .append('svg') .attr('width', `${this.width + margin.right + margin.left}`) @@ -160,6 +181,8 @@ export class LineChart extends React.Component<LineChartProps> { .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`); + const svg = this._chartSvg; + if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) { // TODO: nda - error handle return; @@ -180,54 +203,34 @@ export class LineChart extends React.Component<LineChartProps> { yAxisCreator(svg.append('g'), this.width, yScale); // draw the line - drawLine(svg.append('path'), data.data, lineGen); + drawLine(svg.append('path'), data, lineGen); - // draw the datapoint circles - svg.selectAll('.circle-d1') - .data(data.data) - .join('circle') // enter append - .attr('class', 'circle-d1') - .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); + // draw the datapoint circle + this.drawDataPoints(data, 0, xScale, yScale); const focus = svg.append('g').attr('class', 'focus').style('display', 'none'); focus.append('circle').attr('r', 5).attr('class', 'circle'); - const tooltip = d3 - .select(this._chartRef.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'); + const tooltip = this.setupTooltip(); // add all the tooltipContent to the tooltip - // @action const mousemove = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data.data, xScale.invert(xPos)); - const d0 = data.data[x0]; + const x0 = bisect(data, xScale.invert(xPos)); + const d0 = data[x0]; this._x = d0.x; 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); // TODO: nda - updating the inner html could be deadly cause injection attacks! - // 0 = 30px => -597px (-this.width - margin.left - margin.right) - // 1 = - tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right)}px,${yScale(d0.y) + 30}px)`); + tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right) + 30}px,${yScale(d0.y) + 30}px)`); }); const onPointClick = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data.data, xScale.invert(xPos)); - const d0 = data.data[x0]; + const x0 = bisect(data, xScale.invert(xPos)); + const d0 = data[x0]; this._x = d0.x; this._y = d0.y; // find .circle-d1 with data-x = d0.x and data-y = d0.y @@ -239,7 +242,8 @@ export class LineChart extends React.Component<LineChartProps> { console.log(this._currSelected); }); - svg.append('rect') + this._chartSvg + .append('rect') .attr('class', 'overlay') .attr('width', this.width) .attr('height', this.height + margin.top + margin.bottom) @@ -259,10 +263,7 @@ export class LineChart extends React.Component<LineChartProps> { render() { return ( <div ref={this._chartRef} className="chart-container"> - <span> - x: {this._x} y: {this._y} - Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'} - </span> + <span>Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'}</span> </div> ); } diff --git a/src/client/views/nodes/DataVizBox/TableBox.scss b/src/client/views/nodes/DataVizBox/components/TableBox.scss index 1264d6a46..1264d6a46 100644 --- a/src/client/views/nodes/DataVizBox/TableBox.scss +++ b/src/client/views/nodes/DataVizBox/components/TableBox.scss diff --git a/src/client/views/nodes/DataVizBox/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index dfa8262d8..dfa8262d8 100644 --- a/src/client/views/nodes/DataVizBox/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts index 5c7f9bce1..2bb091999 100644 --- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -3,13 +3,13 @@ import { DataPoint } from '../ChartBox'; // TODO: nda - implement function that can handle range for strings -export const minMaxRange = (dataPts: DataPoint[]) => { - console.log(Number(dataPts[4].y)); - const yMin = d3.min(dataPts, d => Number(d.y)); - const yMax = d3.max(dataPts, d => Number(d.y)); +export const minMaxRange = (dataPts: DataPoint[][]) => { + // find the max and min of all the data points + const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y))); + const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y))); - const xMin = d3.min(dataPts, d => Number(d.x)); - const xMax = d3.max(dataPts, d => Number(d.x)); + const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x))); + const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x))); return { xMin, xMax, yMin, yMax }; }; |