diff options
Diffstat (limited to 'src/client/views/nodes/DataVizBox/components/PieChart.tsx')
-rw-r--r-- | src/client/views/nodes/DataVizBox/components/PieChart.tsx | 175 |
1 files changed, 82 insertions, 93 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 213baa8a4..a8aa51897 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -1,24 +1,24 @@ +import { ColorPicker, EditableText, Size, Type } from 'browndash-components'; +import * as d3 from 'd3'; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, StrListCast } from '../../../../../fields/Doc'; import * as React from 'react'; -import * as d3 from 'd3'; -import { IReactionDisposer, action, computed, observable, reaction } from 'mobx'; -import { LinkManager } from '../../../../util/LinkManager'; -import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; -import { PinProps, PresBox } from '../../trails'; -import { Docs } from '../../../../documents/Documents'; -import { List } from '../../../../../fields/List'; -import './Chart.scss'; -import { ColorPicker, EditableText, Size, Type } from 'browndash-components'; import { FaFillDrip } from 'react-icons/fa'; +import { Doc, NumListCast, StrListCast } from '../../../../../fields/Doc'; +import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; +import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; +import { Docs } from '../../../../documents/Documents'; +import { LinkManager } from '../../../../util/LinkManager'; import { undoable } from '../../../../util/UndoManager'; +import { PinProps, PresBox } from '../../trails'; +import './Chart.scss'; export interface PieChartProps { rootDoc: Doc; layoutDoc: Doc; axes: string[]; - pairs: { [key: string]: any }[]; + records: { [key: string]: any }[]; width: number; height: number; dataDoc: Doc; @@ -36,47 +36,53 @@ export class PieChart extends React.Component<PieChartProps> { private _disposers: { [key: string]: IReactionDisposer } = {}; private _piechartRef: React.RefObject<HTMLDivElement> = React.createRef(); private _piechartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined; - private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios - @observable _currSelected: any | undefined = undefined; // Object of selected slice private curSliceSelected: any = undefined; // d3 data of selected slice private selectedData: any = undefined; // Selection of selected slice private hoverOverData: any = undefined; // Selection of slice being hovered over + @observable _currSelected: any | undefined = undefined; // Object of selected slice + @computed get _tableDataIds() { + return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows); + } + // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent) + @computed get _tableData() { + return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); + } + + // organized by specified number percentages/ratios if one column is selected and it contains numbers + // otherwise, assume data is organized by categories + @computed get byCategory() { + if (this.props.axes.length === 1) { + return !/\d/.test(this.props.records[0][this.props.axes[0]]); + } + return true; + } // filters all data to just display selected data if brushed (created from an incoming link) @computed get _piechartData() { - var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); if (this.props.axes.length < 1) return []; + + const ax0 = this.props.axes[0]; if (this.props.axes.length < 2) { - var ax0 = this.props.axes[0]; - if (/\d/.test(this.props.pairs[0][ax0])) { - this.byCategory = false; - } - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: pair[this.props.axes[0]] })); - } - var ax0 = this.props.axes[0]; - var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { - this.byCategory = false; + return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] })); } - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] })); + const ax1 = this.props.axes[1]; + return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] })); } @computed get defaultGraphTitle() { var ax0 = this.props.axes[0]; var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined; - if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) { + if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) { return ax0 + ' Pie Chart'; - } else return ax1 + ' by ' + ax0 + ' Pie Chart'; + } + return ax1 + ' by ' + ax0 + ' Pie Chart'; } - @computed get incomingLinks() { - return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // 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 parentViz() { + return DocCast(this.props.rootDoc.dataViz_parentViz); + // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + // .filter(link => link.link_anchor_1 == this.props.rootDoc.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() { @@ -116,21 +122,15 @@ export class PieChart extends React.Component<PieChartProps> { // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { - var validData = dataSet.filter((d: { [x: string]: unknown }) => { - var valid = true; - Object.keys(dataSet[0]).map(key => { - if (!d[key] || Number.isNaN(d[key])) valid = false; - }); - return valid; - }); - var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; - const data = validData.map((d: { [x: string]: any }) => { - if (!this.byCategory) { - return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''); - } - return d[field!]; - }); - return data; + const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); + const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; + return !field + ? undefined + : validData.map((d: { [x: string]: any }) => + this.byCategory + ? d[field] // + : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') + ); }; // outlines the slice selected / hovered over @@ -192,13 +192,7 @@ export class PieChart extends React.Component<PieChartProps> { // converts data into Objects var data = this.data(dataSet); - var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => { - var valid = true; - Object.keys(dataSet[0]).map(key => { - if (!d[key] || Number.isNaN(d[key])) valid = false; - }); - return valid; - }); + var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); if (this.byCategory) { let uniqueCategories = [...new Set(data)]; var pieStringDataSet: { frequency: number }[] = []; @@ -232,7 +226,7 @@ export class PieChart extends React.Component<PieChartProps> { // click/hover const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet)); const onHover = action((e: any) => { - const selected = this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet); + this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet); updateHighlights(); }); const mouseOut = action((e: any) => { @@ -252,16 +246,19 @@ export class PieChart extends React.Component<PieChartProps> { // drawing the slices var selected = this.selectedData; var arcs = g.selectAll('arc').data(pie(data)).enter().append('g'); + const sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); + const possibleDataPointVals = pieDataSet.map((each: { [x: string]: any | { valueOf(): number } }) => { + try { + each[percentField] = Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')); + } catch (error) { + //return each[percentField] == d.data; + } + return each; + }); arcs.append('path') .attr('fill', (d, i) => { - var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => { - try { - return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data); - } catch (error) { - return each[percentField] == d.data; - } - }); var dataPoint; + const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data)); if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; else { dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; @@ -269,11 +266,8 @@ export class PieChart extends React.Component<PieChartProps> { } var sliceColor; if (dataPoint) { - var accessByName = dataPoint[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''); - var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); - sliceColors.map(each => { - if (each[0] == StrCast(accessByName)) sliceColor = each[1]; - }); + var accessByName = dataPoint[this.props.axes[0]]; + sliceColors.forEach(each => each[0] == StrCast(accessByName) && (sliceColor = each[1])); } return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length]; }) @@ -295,29 +289,25 @@ export class PieChart extends React.Component<PieChartProps> { // adding labels trackDuplicates = {}; data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null)); - arcs.append('text') - .attr('transform', function (d) { - var centroid = arc.centroid(d as unknown as d3.DefaultArcObject); - var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]); - return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')'; - }) - .attr('text-anchor', 'middle') - .text(function (d) { - var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => { - try { - return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data); - } catch (error) { - return each[percentField] == d.data; + arcs.size() < 100 && + arcs + .append('text') + .attr('transform', function (d) { + var centroid = arc.centroid(d as unknown as d3.DefaultArcObject); + var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]); + return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')'; + }) + .attr('text-anchor', 'middle') + .text(function (d) { + var dataPoint; + const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data)); + if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; + else { + dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; + trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } + return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : ''; }); - var dataPoint; - if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; - else { - dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; - trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; - } - return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : ''; - }); }; @action changeSelectedColor = (color: string) => { @@ -332,7 +322,6 @@ export class PieChart extends React.Component<PieChartProps> { }; render() { - this.componentDidMount(); var titleAccessor: any = ''; if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0] + '-' + this.props.axes[1]; else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0]; @@ -355,7 +344,7 @@ export class PieChart extends React.Component<PieChartProps> { if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1]; }); - if (this._piechartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){ + if (this._piechartData.length > 0 || !this.parentViz) { return this.props.axes.length >= 1 ? ( <div className="chart-container"> <div className="graph-title"> |