aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/HistogramBoxPrimitives.tsx
diff options
context:
space:
mode:
authorbob <bcz@cs.brown.edu>2019-03-26 16:06:06 -0400
committerbob <bcz@cs.brown.edu>2019-03-26 16:06:06 -0400
commite9826e0ac334bac28d173f67b4f0db0800b40680 (patch)
tree445801d2f3971243045682e13c1cd6bf0ac371a8 /src/client/views/nodes/HistogramBoxPrimitives.tsx
parentb9d23e5adde2da01708cc8501ca375726d232d06 (diff)
cleaned up a bit. got rid of blinking when histos change.
Diffstat (limited to 'src/client/views/nodes/HistogramBoxPrimitives.tsx')
-rw-r--r--src/client/views/nodes/HistogramBoxPrimitives.tsx373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx
new file mode 100644
index 000000000..2f4a553b7
--- /dev/null
+++ b/src/client/views/nodes/HistogramBoxPrimitives.tsx
@@ -0,0 +1,373 @@
+import React = require("react")
+import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
+import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+import { LABColor } from '../../northstar/utils/LABcolor';
+import { PIXIRectangle } from "../../northstar/utils/MathUtil";
+import { SizeConverter } from "../../northstar/utils/SizeConverter";
+import { StyleConstants } from "../../northstar/utils/StyleContants";
+import "./HistogramBox.scss";
+import { HistogramBox } from "./HistogramBox";
+import { computed, runInAction, observable, trace } from "mobx";
+import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
+import { Utils as DashUtils } from '../../../Utils';
+import { observer } from "mobx-react";
+
+
+export interface HistogramBoxPrimitivesProps {
+ HistoBox: HistogramBox;
+}
+
+@observer
+export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitivesProps> {
+ @observable _selectedPrims: HistogramBinPrimitive[] = [];
+
+ @computed
+ get selectedPrimitives() {
+ return this._selectedPrims.map((bp) => this.drawBorder(bp.Rect, StyleConstants.OPERATOR_BACKGROUND_COLOR));
+ }
+ @computed
+ get binPrimitives() {
+ if (!this.props.HistoBox.HistoOp || !(this.props.HistoBox.HistoOp.Result instanceof HistogramResult) || !this.props.HistoBox.SizeConverter)
+ return (null);
+ let prims: JSX.Element[] = [];
+ let allBrushIndex = ModelHelpers.AllBrushIndex(this.props.HistoBox.HistoOp.Result);
+ for (let key in this.props.HistoBox.HistoOp.Result.bins) {
+ if (this.props.HistoBox.HistoOp.Result.bins.hasOwnProperty(key)) {
+ let drawPrims = new HistogramBinPrimitiveCollection(key, this.props.HistoBox);
+ let filterModel = ModelHelpers.GetBinFilterModel(this.props.HistoBox.HistoOp.Result.bins![key], allBrushIndex, this.props.HistoBox.HistoOp.Result, this.props.HistoBox.HistoOp.X, this.props.HistoBox.HistoOp.Y);
+
+ this.props.HistoBox.HitTargets.setValue(drawPrims.HitGeom, filterModel);
+
+ drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(binPrimitive => {
+ let toggleFilter = () => {
+ if ([filterModel].filter(h => ArrayUtil.Contains(this.props.HistoBox.HistoOp!.FilterModels, h)).length > 0) {
+ let bp = ArrayUtil.FirstOrDefault<HistogramBinPrimitive>(drawPrims.BinPrimitives, (bp: HistogramBinPrimitive) => bp.BrushIndex == allBrushIndex);
+ if (bp && bp.DataValue) {
+ this._selectedPrims.splice(this._selectedPrims.indexOf(bp), 1);
+ }
+ this.props.HistoBox.HistoOp!.RemoveFilterModels([filterModel]);
+ }
+ else {
+ let bp = ArrayUtil.FirstOrDefault<HistogramBinPrimitive>(drawPrims.BinPrimitives, (bp: HistogramBinPrimitive) => bp.BrushIndex == allBrushIndex);
+ if (bp && bp.DataValue) {
+ this._selectedPrims.push(bp!);
+ }
+ this.props.HistoBox.HistoOp!.AddFilterModels([filterModel]);
+ }
+ }
+ prims.push(this.drawRect(binPrimitive.Rect, binPrimitive.Color, () => runInAction(toggleFilter)));
+ prims.push(this.drawRect(binPrimitive.MarginRect, StyleConstants.MARGIN_BARS_COLOR, () => runInAction(toggleFilter)));
+ });
+ }
+ }
+ return prims;
+ }
+ drawBorder(r: PIXIRectangle, color: number) {
+ return <div key={DashUtils.GenerateGuid()} className="histogramboxprimitive-border"
+ style={{
+ position: "absolute",
+ transform: `translate(${r.x}px,${r.y}px)`,
+ width: `${r.width - 1}`,
+ height: `${r.height}`,
+ border: "1px",
+ borderStyle: "solid",
+ pointerEvents: "none",
+ borderColor: `${LABColor.RGBtoHexString(color)}`
+ }}
+ />
+ }
+
+ drawRect(r: PIXIRectangle, color: number, tapHandler: () => void) {
+ return <div key={DashUtils.GenerateGuid()} onPointerDown={tapHandler}
+ style={{
+ position: "absolute",
+ transform: `translate(${r.x}px,${r.y}px)`,
+ width: `${r.width - 1}`,
+ height: `${r.height}`,
+ background: `${LABColor.RGBtoHexString(color)}`
+ }}
+ />
+ }
+ render() {
+ return <div>
+ {this.binPrimitives}
+ {this.selectedPrimitives}
+ </div>
+ }
+}
+
+
+class HistogramBinPrimitive {
+ constructor(init?: Partial<HistogramBinPrimitive>) {
+ Object.assign(this, init);
+ }
+ public DataValue: number = 0;
+ public Rect: PIXIRectangle = PIXIRectangle.EMPTY;
+ public MarginRect: PIXIRectangle = PIXIRectangle.EMPTY;
+ public MarginPercentage: number = 0;
+ public Color: number = StyleConstants.WARNING_COLOR;
+ public Opacity: number = 1;
+ public BrushIndex: number = 0;
+}
+
+export class HistogramBinPrimitiveCollection {
+ private static TOLERANCE: number = 0.0001;
+
+ private _histoBox: HistogramBox;
+ private get histoOp() { return this._histoBox.HistoOp!; }
+ private get histoResult() { return this.histoOp.Result as HistogramResult; }
+ public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
+ public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
+
+ constructor(key: string, histoBox: HistogramBox) {
+ this._histoBox = histoBox;
+ let bin = this.histoResult.bins![key];
+
+ var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
+ var orderedBrushes = new Array<Brush>();
+ orderedBrushes.push(this.histoResult.brushes![0]);
+ orderedBrushes.push(this.histoResult.brushes![overlapBrushIndex]);
+ for (var b = 0; b < this.histoResult.brushes!.length; b++) {
+ var brush = this.histoResult.brushes![b];
+ if (brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex) {
+ orderedBrushes.push(brush);
+ }
+ }
+ var binBrushMaxAxis = this.getBinBrushAxisRange(bin, orderedBrushes, this.histoOp.Normalization); // X= 0, Y = 1
+
+ var brushFactorSum: number = 0;
+ for (var b = 0; b < orderedBrushes.length; b++) {
+ var brush = orderedBrushes[b];
+ var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, brush.brushIndex!);
+ var doubleRes = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+ var unNormalizedValue = (doubleRes != null && doubleRes.hasResult) ? doubleRes.result : null;
+ if (unNormalizedValue)
+ switch (histoBox.ChartType) {
+ case ChartType.VerticalBar:
+ this.createVerticalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!); // X = 0, Y = 1, NOne = -1
+ break;
+ case ChartType.HorizontalBar:
+ this.createHorizontalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!);
+ break;
+ case ChartType.SinglePoint:
+ this.createSinlgePointChartBinPrimitives(bin, brush, unNormalizedValue, histoBox.SizeConverter!);
+ break;
+ case ChartType.HeatMap:
+ var normalizedValue = (unNormalizedValue - histoBox.MinValue) / (Math.abs((histoBox.MaxValue - histoBox.MinValue)) < HistogramBinPrimitiveCollection.TOLERANCE ?
+ unNormalizedValue : histoBox.MaxValue - histoBox.MinValue);
+ brushFactorSum = this.createHeatmapBinPrimitives(bin, brush, unNormalizedValue, brushFactorSum, normalizedValue, histoBox.SizeConverter!);
+ }
+ }
+
+ // adjust brush rects (stacking or not)
+ var sum: number = 0;
+ var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
+ var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
+ var count: number = filteredBinPrims.length;
+ filteredBinPrims.map(fbp => {
+ if (histoBox.ChartType == ChartType.VerticalBar) {
+ if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height);
+ sum += fbp.Rect.height;
+ }
+ if (this.histoOp.Y.AggregateFunction == AggregateFunction.Avg) {
+ var w = fbp.Rect.width / 2.0;
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / count, fbp.Rect.height);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
+ sum += fbp.Rect.width;
+ }
+ }
+ else if (histoBox.ChartType == ChartType.HorizontalBar) {
+ if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
+ sum += fbp.Rect.width;
+ }
+ if (this.histoOp.X.AggregateFunction == AggregateFunction.Avg) {
+ var h = fbp.Rect.height / 2.0;
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / count);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
+ sum += fbp.Rect.height;
+ }
+ }
+ });
+ this.BinPrimitives = this.BinPrimitives.reverse();
+ var f = this.BinPrimitives.filter(b => b.BrushIndex == allBrushIndex);
+ this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY;
+ }
+ private getBinBrushAxisRange(bin: Bin, brushes: Array<Brush>, axis: number): number {
+ var binBrushMaxAxis = Number.MIN_VALUE;
+ brushes.forEach((Brush) => {
+ var maxAggregateKey = ModelHelpers.CreateAggregateKey(axis === 0 ? this.histoOp.Y : this.histoOp.X, this.histoResult, Brush.brushIndex!);
+ var aggResult = ModelHelpers.GetAggregateResult(bin, maxAggregateKey) as DoubleValueAggregateResult;
+ if (aggResult != null) {
+ if (aggResult.result! > binBrushMaxAxis)
+ binBrushMaxAxis = aggResult.result!;
+ }
+ });
+ return binBrushMaxAxis;
+ }
+ private createHeatmapBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, brushFactorSum: number, normalizedValue: number, sizeConverter: SizeConverter): number {
+
+ var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult));
+ var allUnNormalizedValue = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+
+ var tx = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0]);
+ var xFrom = sizeConverter.DataToScreenX(tx);
+ var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(tx));
+
+ var ty = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
+ var yFrom = sizeConverter.DataToScreenY(ty);
+ var yTo = sizeConverter.DataToScreenY(this._histoBox.VisualBinRanges[1].AddStep(ty));
+
+ var returnBrushFactorSum = brushFactorSum;
+ if (allUnNormalizedValue.hasResult) {
+ var brushFactor = (unNormalizedValue / allUnNormalizedValue.result!);
+ returnBrushFactorSum += brushFactor;
+ returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0);
+
+ var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo);
+ var ratio = (tempRect.width / tempRect.height);
+ var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum));
+ var newWidth = newHeight * ratio;
+
+ xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0);
+ yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0);
+ xTo = (xFrom + newWidth);
+ yFrom = (yTo + newHeight);
+ }
+ var alpha = 0.0;
+ var color = this.baseColorFromBrush(brush);
+ var lerpColor = LABColor.Lerp(
+ LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR),
+ LABColor.FromColor(color),
+ (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha)));
+ var dataColor = LABColor.ToColor(lerpColor);
+
+ this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
+ return returnBrushFactorSum;
+ }
+
+ private createSinlgePointChartBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, sizeConverter: SizeConverter): void {
+ var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
+ var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
+
+ var xValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
+ if (!xValue.hasResult)
+ return;
+ var xFrom = sizeConverter.DataToScreenX(xValue.result!) - 5;
+ var xTo = sizeConverter.DataToScreenX(xValue.result!) + 5;
+
+ var yValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;;
+ if (!yValue.hasResult)
+ return;
+ var yFrom = sizeConverter.DataToScreenY(yValue.result!) + 5;
+ var yTo = sizeConverter.DataToScreenY(yValue.result!);
+
+ this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+ }
+
+ private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
+ var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
+ var marginParams = new MarginAggregateParameters();
+ marginParams.aggregateFunction = this.histoOp.Y.AggregateFunction;
+ var yMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult,
+ brush.brushIndex!, marginParams);
+ var dataValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;
+
+ if (dataValue != null && dataValue.hasResult) {
+ var yValue = normalization != 0 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[1];
+
+ var yFrom = sizeConverter.DataToScreenY(Math.min(0, yValue));
+ var yTo = sizeConverter.DataToScreenY(Math.max(0, yValue));;
+
+ var xValue = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0])!;
+ var xFrom = sizeConverter.DataToScreenX(xValue);
+ var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(xValue));
+
+ var marginResult = ModelHelpers.GetAggregateResult(bin, yMarginAggregateKey) as MarginAggregateResult;
+ var yMarginAbsolute = !marginResult ? 0 : marginResult.absolutMargin!;
+ var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
+ sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
+ sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
+
+ this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+ this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[1] + 0.4, dataValue.result!);
+ }
+ }
+
+ private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
+ var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
+ var marginParams = new MarginAggregateParameters();
+ marginParams.aggregateFunction = this.histoOp.X.AggregateFunction;
+ var xMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult,
+ brush.brushIndex!, marginParams);
+ var dataValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
+
+ if (dataValue != null && dataValue.hasResult) {
+ var xValue = normalization != 1 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[0];
+ var xFrom = sizeConverter.DataToScreenX(Math.min(0, xValue));
+ var xTo = sizeConverter.DataToScreenX(Math.max(0, xValue));
+
+ var yValue = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
+ var yFrom = yValue;
+ var yTo = this._histoBox.VisualBinRanges[1].AddStep(yValue);
+
+ var marginResult = ModelHelpers.GetAggregateResult(bin, xMarginAggregateKey) as MarginAggregateResult;
+ var xMarginAbsolute = sizeConverter.IsSmall || !marginResult ? 0 : marginResult.absolutMargin!;
+
+ var marginRect = new PIXIRectangle(sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+ yTo + (yFrom - yTo) / 2.0 - 1,
+ sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+ 2.0);
+
+ this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+ this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[0] + 0.4, dataValue.result!);
+ }
+ }
+
+ private createBinPrimitive(bin: Bin, brush: Brush, marginRect: PIXIRectangle,
+ marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
+ // hitgeom todo
+
+ var binPrimitive = new HistogramBinPrimitive(
+ {
+ Rect: new PIXIRectangle(
+ xFrom,
+ yTo,
+ xTo - xFrom,
+ yFrom - yTo),
+ MarginRect: marginRect,
+ MarginPercentage: marginPercentage,
+ BrushIndex: brush.brushIndex,
+ Color: color,
+ Opacity: opacity,
+ DataValue: dataValue
+ });
+ this.BinPrimitives.push(binPrimitive);
+ }
+
+ private baseColorFromBrush(brush: Brush): number {
+ var baseColor: number = StyleConstants.HIGHLIGHT_COLOR;
+ if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
+ baseColor = StyleConstants.HIGHLIGHT_COLOR;
+ }
+ else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+ baseColor = StyleConstants.OVERLAP_COLOR;
+ }
+ else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
+ baseColor = 0x00ff00;
+ }
+ else {
+ if (this._histoBox.HistoOp!.BrushColors.length > 0) {
+ baseColor = this._histoBox.HistoOp!.BrushColors[brush.brushIndex! % this._histoBox.HistoOp!.BrushColors.length];
+ }
+ else {
+ baseColor = StyleConstants.HIGHLIGHT_COLOR;
+ }
+ }
+ return baseColor;
+ }
+} \ No newline at end of file