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.tsx155
1 files changed, 36 insertions, 119 deletions
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 0fd4f6b54..d4570dee2 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -40,12 +40,12 @@ export class PieChart extends React.Component<PieChartProps> {
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;
- private curSliceSelected: any = undefined;
- private selectedData: any = undefined;
- private hoverOverData: any = undefined;
- // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
+ @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
+ // filters all data to just display selected data if brushed (created from an incoming link)
@computed get _piechartData() {
var guids = StrListCast(this.props.layoutDoc.rowGuids);
if (this.props.axes.length < 1) return [];
@@ -56,7 +56,6 @@ export class PieChart extends React.Component<PieChartProps> {
?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).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; }
@@ -64,6 +63,7 @@ export class PieChart extends React.Component<PieChartProps> {
?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.selected && StrListCast(this.incomingLinks[0].selected).includes(guids[this.props.pairs.indexOf(pair)])))
.map(pair => ({ [ax0]: (pair[this.props.axes[0]]), [ax1]: (pair[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;
@@ -72,28 +72,13 @@ export class PieChart extends React.Component<PieChartProps> {
}
else return ax1 + " by " + ax0 + " Pie Chart";
}
+
@computed get incomingLinks() {
return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- .filter(link => {
- return link.link_anchor_1 == this.props.rootDoc.draggedFrom}) // get links where this chart doc is the target of the link
+ .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 incomingSelected() {
- // return selected x and y axes
- // otherwise, use the selection of whatever is linked to us
- return this.incomingLinks // all links that are pointing to this node
- .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes
- .filter(dvb => dvb)
- .map(dvb => dvb.pairs?.filter((pair: { [x: string]: any; }) => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor
- .lastElement();
- }
- @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
- if (!this.byCategory){
- const data = this.data(this._piechartData);
- return {xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin:0, yMax:0}
- }
- return {xMin:0, xMax:0, yMin:0, yMax:0}
- }
+
componentWillUnmount() {
Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
}
@@ -103,79 +88,14 @@ export class PieChart extends React.Component<PieChartProps> {
({ dataSet, w, h }) => {
if (dataSet!.length>0) {
this.drawChart(dataSet, w, h);
-
- // redraw annotations when the chart data has changed, or the local or inherited selection has changed
- this.clearAnnotations();
- this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true);
- this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
}
},
{ fireImmediately: true }
);
- this._disposers.annos = 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
- // this.drawAnnotations()
- // loop through annotations and draw them
- annotations.forEach(a => this.drawAnnotations(Number(a.x), Number(a.y)));
- // this.drawAnnotations(annotations.x, annotations.y);
- },
- { fireImmediately: true }
- );
- this._disposers.highlights = reaction(
- () => ({
- selected: this._currSelected,
- incomingSelected: this.incomingSelected,
- }),
- ({ selected, incomingSelected }) => {
- // redraw annotations when the chart data has changed, or the local or inherited selection has changed
- this.clearAnnotations();
- selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true);
- incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]])));
- },
- { fireImmediately: true }
- );
- };
-
- // 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
-
- clearAnnotations = () => {
- const elements = document.querySelectorAll('.datapoint');
- for (let i = 0; i < elements.length; i++) {
- const element = elements[i];
- element.classList.remove('brushed');
- element.classList.remove('selected');
- }
- };
- // gets called whenever the "data_annotations" fields gets updated
- drawAnnotations = (dataX: number, dataY: number, selected?: boolean) => {
- // 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(selected ? 'selected' : 'brushed');
- }
- // TODO: nda - this remove highlight code should go where we remove the links
- // } else {
- // }
- }
};
- removeAnnotations(dataX: number, dataY: number) {
- // loop through and remove any annotations that no longer exist
- }
-
@action
restoreView = (data: Doc) => {};
-
// create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
getAnchor = (pinProps?: PinProps) => {
const anchor = Docs.Create.ConfigDocument({
@@ -183,7 +103,6 @@ export class PieChart extends React.Component<PieChartProps> {
title: 'piechart doc selection' + this._currSelected,
});
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc);
- // anchor.presDataVizSelection = this._currSelected ? new List<number>([this._currSelected]) : undefined;
return anchor;
};
@@ -195,15 +114,7 @@ export class PieChart extends React.Component<PieChartProps> {
return this.props.width - this.props.margin.left - this.props.margin.right;
}
- // TODO: nda - use this everyewhere we update currSelected?
- @action
- setCurrSelected(x?: number, y?: number) {
- // TODO: nda - get rid of svg element in the list?
- this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
- this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true));
- this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined));
- }
-
+ // 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;
@@ -220,15 +131,16 @@ export class PieChart extends React.Component<PieChartProps> {
return data;
}
+ // outlines the slice selected / hovered over
highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => {
var index = -1;
var sameAsCurrent: boolean;
const selected = svg.selectAll('.slice').filter((d: any) => {
index++;
- var p1 = [0,0];
- var p3 = [arc.centroid(d)[0]*2, arc.centroid(d)[1]*2];
- var p2 = [radius*Math.sin(d.startAngle), -radius*Math.cos(d.startAngle)];
- var p4 = [radius*Math.sin(d.endAngle), -radius*Math.cos(d.endAngle)];
+ var p1 = [0,0]; // center of pie
+ var p3 = [arc.centroid(d)[0]*2, arc.centroid(d)[1]*2]; // outward peak of arc
+ var p2 = [radius*Math.sin(d.startAngle), -radius*Math.cos(d.startAngle)]; // start of arc
+ var p4 = [radius*Math.sin(d.endAngle), -radius*Math.cos(d.endAngle)]; // end of arc
// draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge
var lineCrossCount = 0;
@@ -241,10 +153,10 @@ export class PieChart extends React.Component<PieChartProps> {
if (pointer[0] <= (pointer[1]-p3[1])*(p4[0]-p3[0])/(p4[1]-p3[1])+p3[0]) lineCrossCount++; }
if (Math.min(p4[1], p1[1])<=pointer[1] && pointer[1]<=Math.max(p4[1], p1[1])){
if (pointer[0] <= (pointer[1]-p4[1])*(p1[0]-p4[0])/(p1[1]-p4[1])+p4[0]) lineCrossCount++; }
- if (lineCrossCount % 2 != 0) {
+ if (lineCrossCount % 2 != 0) { // inside the slice of it crosses an odd number of edges
var showSelected = this.byCategory? pieDataSet[index] : this._piechartData[index];
-
if (changeSelectedVariables){
+ // for when a bar is selected - not just hovered over
sameAsCurrent = (this._currSelected)?
(showSelected[Object.keys(showSelected)[0]]==this._currSelected![Object.keys(showSelected)[0]]
&& showSelected[Object.keys(showSelected)[1]]==this._currSelected![Object.keys(showSelected)[1]])
@@ -264,6 +176,7 @@ export class PieChart extends React.Component<PieChartProps> {
}
}
+ // draws the pie chart
drawChart = (dataSet: any, width: number, height: number) => {
d3.select(this._piechartRef.current).select('svg').remove();
d3.select(this._piechartRef.current).select('.tooltip').remove();
@@ -271,18 +184,8 @@ export class PieChart extends React.Component<PieChartProps> {
var percentField = Object.keys(dataSet[0])[0]
var descriptionField = Object.keys(dataSet[0])[1]!
var radius = Math.min(width, height-this.props.margin.top-this.props.margin.bottom) /2
- var svg = (this._piechartSvg = d3
- .select(this._piechartRef.current)
- .append("svg")
- .attr("class", "graph")
- .attr("width", width + this.props.margin.right + this.props.margin.left)
- .attr("height", height + this.props.margin.top + this.props.margin.bottom)
- .append("g"));
-
- let g = svg.append("g")
- .attr("transform",
- "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")");
+ // converts data into Objects
var data = this.data(dataSet);
var pieDataSet = dataSet.filter((d: { [x: string]: unknown; }) => {
var valid = true;
@@ -293,12 +196,12 @@ export class PieChart extends React.Component<PieChartProps> {
});
if (this.byCategory){
let uniqueCategories = [...new Set(data)]
- var pieStringDataSet: { frequency: number, [percentField]: string }[] = [];
+ var pieStringDataSet: { frequency: number }[] = [];
for (let i=0; i<uniqueCategories.length; i++){
pieStringDataSet.push({frequency: 0, [percentField]: uniqueCategories[i]})
}
for (let i=0; i<data.length; i++){
- let sliceData = pieStringDataSet.filter(each => each[percentField]==data[i])
+ let sliceData = pieStringDataSet.filter((each: any) => each[percentField]==data[i])
sliceData[0].frequency = sliceData[0].frequency + 1;
}
pieDataSet = pieStringDataSet
@@ -309,11 +212,23 @@ export class PieChart extends React.Component<PieChartProps> {
var trackDuplicates : {[key: string]: any} = {};
data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null)
+ // initial chart
+ var svg = (this._piechartSvg = d3
+ .select(this._piechartRef.current)
+ .append("svg")
+ .attr("class", "graph")
+ .attr("width", width + this.props.margin.right + this.props.margin.left)
+ .attr("height", height + this.props.margin.top + this.props.margin.bottom)
+ .append("g"));
+ let g = svg.append("g")
+ .attr("transform",
+ "translate(" + (width/2 + this.props.margin.left) + "," + height/2 + ")");
var pie = d3.pie();
var arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
+ // 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)
@@ -331,6 +246,7 @@ export class PieChart extends React.Component<PieChartProps> {
|| ((hoverOverSlice && d.startAngle==hoverOverSlice.startAngle && d.endAngle==hoverOverSlice.endAngle)))? 'slice hover' : 'slice'; })
}
+ // drawing the slices
var selected = this.selectedData;
var arcs = g.selectAll("arc")
.data(pie(data))
@@ -359,11 +275,12 @@ export class PieChart extends React.Component<PieChartProps> {
function(d) {
return (selected && d.startAngle==selected.startAngle && d.endAngle==selected.endAngle)? 'slice hover' : 'slice';
}: function(d) {return 'slice'})
- .attr("d", arc)
+ .attr('d', arc)
.on('click', onPointClick)
.on('mouseover', onHover)
.on('mouseout', mouseOut);
+ // adding labels
trackDuplicates = {};
data.forEach((eachData: any) => !trackDuplicates[eachData]? trackDuplicates[eachData] = 0: null)
arcs.append("text")