diff options
author | Naafiyan Ahmed <naafiyan@gmail.com> | 2022-07-14 16:38:00 -0400 |
---|---|---|
committer | Naafiyan Ahmed <naafiyan@gmail.com> | 2022-07-14 16:38:00 -0400 |
commit | 9136b17ed9021bd9435117f68ff66c4dbb1a1889 (patch) | |
tree | 1a06ebbf3bcd6a6fe637e580e4e4030599169bed | |
parent | ee2b15e0baa9cd3847d785c62d75fd82defaee08 (diff) |
refactored code into d3 utils
-rw-r--r-- | src/client/views/nodes/DataVizBox/ChartBox.tsx | 8 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/LineChart.tsx | 61 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/utils/D3Utils.ts | 59 |
3 files changed, 81 insertions, 47 deletions
diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index 7424fbe5f..a03aaae31 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -23,7 +23,7 @@ export interface RechartData { y: number; } -export interface DataPoints { +export interface DataPoint { x: number; y: number; } @@ -31,8 +31,8 @@ export interface DataPoints { export interface ChartData { xLabel: string; yLabel: string; - data: DataPoints[]; - tooltipContent: (data: DataPoints) => string; + data: DataPoint[]; + tooltipContent: (data: DataPoint) => string; } @observer @@ -65,7 +65,7 @@ export class ChartBox extends React.Component<ChartBoxProps> { xLabel: '', yLabel: '', data: [], - tooltipContent: (data: DataPoints) => { + tooltipContent: (data: DataPoint) => { return `<b>x: ${data.x} y: ${data.y}</b>`; }, }; diff --git a/src/client/views/nodes/DataVizBox/LineChart.tsx b/src/client/views/nodes/DataVizBox/LineChart.tsx index 5ad8f0846..8a757866e 100644 --- a/src/client/views/nodes/DataVizBox/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/LineChart.tsx @@ -1,9 +1,10 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ChartData, DataPoints } from './ChartBox'; +import { ChartData, DataPoint } from './ChartBox'; // import d3 import * as d3 from 'd3'; +import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, scaleCreator } from './utils/D3Utils'; interface LineChartProps { data: ChartData; @@ -23,6 +24,8 @@ export class LineChart extends React.Component<LineChartProps> { @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(); componentDidMount() { console.log('Getting to line chart'); @@ -36,7 +39,7 @@ export class LineChart extends React.Component<LineChartProps> { } } - tooltipContent(data: DataPoints) { + tooltipContent(data: DataPoint) { return `<b>x: ${data.x} y: ${data.y}</b>`; } @@ -48,16 +51,10 @@ export class LineChart extends React.Component<LineChartProps> { const margin = { top: 50, right: 50, bottom: 50, left: 50 }; const { data } = this.props; - const yMin = d3.min(data.data, d => d.y); - const yMax = d3.max(data.data, d => d.y); - - // TODO: nda - modify data.x to support strings - - const xMin = d3.min(data.data, d => d.x); - const xMax = d3.max(data.data, d => d.x); + const { xMin, xMax, yMin, yMax } = minMaxRange(data.data); // adding svg - const svg = d3.select('#chart-container').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', '100%').attr('height', '100%').append('g').attr('transform', `translate(${margin.left}, ${margin.top})`); // adding tooltip // const tooltip = d3.select('#chart-container').append('div').attr('class', 'tooltip'); @@ -67,41 +64,19 @@ export class LineChart extends React.Component<LineChartProps> { return; } // adding x axis - const xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, this.props.width]); - - const yScale = d3.scaleLinear().domain([0, yMax]).range([this.props.height, 0]); + const xScale = scaleCreator(xMin, xMax, 0, this.props.width); + const yScale = scaleCreator(0, yMax, this.props.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 = d3 - .line<DataPoints>() - .x(d => xScale(d.x)) - .y(d => yScale(d.y)) - .curve(d3.curveMonotoneX); - - // adding the line to the svg - svg.append('g') - .attr('class', 'grid') - .attr('transform', `translate(0,${this.props.height})`) - .call( - d3 - .axisBottom(xScale) - .tickSize(-this.props.height) - .tickFormat((a, b) => '') - ); - svg.append('g') - .attr('class', 'grid') - .call( - d3 - .axisLeft(yScale) - .tickSize(-this.props.width) - .tickFormat((a, b) => '') - ); - svg.append('g').attr('class', 'x-axis').attr('transform', `translate(0,${this.props.height})`).call(d3.axisBottom(xScale).tickSize(15)); - svg.append('g').attr('class', 'y-axis').call(d3.axisLeft(yScale)); + 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); // draw the line - svg.append('path').datum(data.data).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('class', 'line').attr('d', lineGen); + drawLine(svg.append('path'), data.data, lineGen); // draw the datapoint circles svg.selectAll('.circle-d1') @@ -132,7 +107,7 @@ export class LineChart extends React.Component<LineChartProps> { // add all the tooltipContent to the tooltip // @action const mousemove = action((e: any) => { - const bisect = d3.bisector((d: DataPoints) => d.x).left; + 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]; @@ -145,7 +120,7 @@ export class LineChart extends React.Component<LineChartProps> { }); const onPointClick = action((e: any) => { - const bisect = d3.bisector((d: DataPoints) => d.x).left; + 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]; @@ -175,7 +150,7 @@ export class LineChart extends React.Component<LineChartProps> { render() { return ( - <div id="chart-container"> + <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'} diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts new file mode 100644 index 000000000..285298472 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -0,0 +1,59 @@ +import d3 from 'd3'; +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); + + const xMin = d3.min(dataPts, d => d.x); + const xMax = d3.max(dataPts, d => d.x); + + return { xMin, xMax, yMin, yMax }; +}; + +export const scaleCreator = (domA: number, domB: number, rangeA: number, rangeB: number) => { + return d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]); +}; + +export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => { + // TODO: nda - look into the different types of curves + return d3 + .line<DataPoint>() + .x(d => xScale(d.x)) + .y(d => yScale(d.y)) + .curve(d3.curveMonotoneX); +}; + +export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, HTMLElement, any>, height: number, xScale: d3.ScaleLinear<number, number, never>) => { + g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15)); +}; + +export const yAxisCreator = (g: d3.Selection<SVGGElement, unknown, HTMLElement, any>, width: 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') + .attr('transform', `translate(0,${height})`) + .call( + d3 + .axisBottom(scale) + .tickSize(-height) + .tickFormat((a, b) => '') + ); +}; + +export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, width: number, scale: d3.ScaleLinear<number, number, never>) => { + g.attr('class', 'grid').call( + d3 + .axisLeft(scale) + .tickSize(-width) + .tickFormat((a, b) => '') + ); +}; + +export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>) => { + p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('class', 'line').attr('d', lineGen); +}; |