aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DataVizBox/components/PieChart.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/DataVizBox/components/PieChart.tsx')
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx210
1 files changed, 106 insertions, 104 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 213baa8a4..561f39141 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 { undoable } from '../../../../util/UndoManager';
+import { PinProps, PresBox } from '../../trails';
+import './Chart.scss';
+import { Checkbox } from '@material-ui/core';
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,50 @@ 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() {
+ return !/\d/.test(this.props.records[0][this.props.axes[0]]) || this.props.layoutDoc.dataViz_pie_asHistogram;
+ }
// 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);
+ @computed get _pieChartData() {
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() {
@@ -84,7 +87,7 @@ export class PieChart extends React.Component<PieChartProps> {
}
componentDidMount = () => {
this._disposers.chartData = reaction(
- () => ({ dataSet: this._piechartData, w: this.width, h: this.height }),
+ () => ({ dataSet: this._pieChartData, w: this.width, h: this.height }),
({ dataSet, w, h }) => {
if (dataSet!.length > 0) {
this.drawChart(dataSet, w, h);
@@ -116,21 +119,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, '').replace(/\</g, '')
+ );
};
// outlines the slice selected / hovered over
@@ -162,7 +159,7 @@ export class PieChart extends React.Component<PieChartProps> {
}
if (lineCrossCount % 2 != 0) {
// inside the slice of it crosses an odd number of edges
- var showSelected = this.byCategory ? pieDataSet[index] : this._piechartData[index];
+ var showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
if (changeSelectedVariables) {
// for when a bar is selected - not just hovered over
sameAsCurrent = this._currSelected
@@ -192,13 +189,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 +223,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 +243,21 @@ 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 possibleDataPointVals: { [x: string]: any }[] = [];
+ pieDataSet.forEach((each: { [x: string]: any | { valueOf(): number } }) => {
+ var dataPointVal: { [x: string]: any } = {};
+ dataPointVal[percentField] = each[percentField];
+ if (descriptionField) dataPointVal[descriptionField] = each[descriptionField];
+ try {
+ dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, ''));
+ } catch (error) {}
+ possibleDataPointVals.push(dataPointVal);
+ });
+ const sliceColors = StrListCast(this.props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
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 +265,9 @@ 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];
- });
+ const sliceTitle = dataPoint[this.props.axes[0]];
+ const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
+ sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1]));
}
return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length];
})
@@ -295,53 +289,55 @@ 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 = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
+ else {
+ dataPoint = pieDataSet[possibleDataPointVals.indexOf(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) => {
this.curSliceSelected.attr('fill', color);
- var sliceName = this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
+ const sliceTitle = this._currSelected[this.props.axes[0]];
+ const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- const sliceColors = Cast(this.props.layoutDoc.pieSliceColors, listSpec('string'), null);
+ const sliceColors = Cast(this.props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null);
sliceColors.map(each => {
if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
});
sliceColors.push(StrCast(sliceName + '::' + color));
};
+ @action changeHistogramCheckBox = () => {
+ this.props.layoutDoc.dataViz_pie_asHistogram = !this.props.layoutDoc.dataViz_pie_asHistogram;
+ this.drawChart(this._pieChartData, this.width, this.height);
+ };
+
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];
+ if (this.props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this.props.axes[0] + '-' + this.props.axes[1];
+ else if (this.props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + this.props.axes[0];
if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
- if (!this.props.layoutDoc.pieSliceColors) this.props.layoutDoc.pieSliceColors = new List<string>();
+ if (!this.props.layoutDoc.dataViz_pie_sliceColors) this.props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
var selected: string;
var curSelectedSliceName = '';
if (this._currSelected) {
- curSelectedSliceName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ const sliceTitle = this._currSelected[this.props.axes[0]];
+ curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
selected = '{ ';
Object.keys(this._currSelected).map(key => {
key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
@@ -350,12 +346,12 @@ export class PieChart extends React.Component<PieChartProps> {
selected += ' }';
} else selected = 'none';
var selectedSliceColor;
- var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::'));
- sliceColors.map(each => {
+ var sliceColors = StrListCast(this.props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+ sliceColors.forEach(each => {
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">
@@ -370,6 +366,12 @@ export class PieChart extends React.Component<PieChartProps> {
fillWidth
/>
</div>
+ {this.props.axes.length === 1 && /\d/.test(this.props.records[0][this.props.axes[0]]) ? (
+ <div className={'asHistogram-checkBox'} style={{ width: this.props.width }}>
+ <Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this.props.layoutDoc.dataViz_pie_asHistogram as boolean} />
+ Organize data as histogram
+ </div>
+ ) : null}
<div ref={this._piechartRef} />
{selected != 'none' ? (
<div className={'selected-data'}>