From 1bd678851632bbbb302363574eb5e3e19dc343e9 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 29 Mar 2019 13:18:35 -0400
Subject: reorganized and mostly working northstar histograms.

---
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 341 +++++++++++++++++++++
 1 file changed, 341 insertions(+)
 create mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx

(limited to 'src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx')

diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
new file mode 100644
index 000000000..d2f1be4fd
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -0,0 +1,341 @@
+import React = require("react")
+import { computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Utils as DashUtils } from '../../../Utils';
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+import { FilterModel } from "../../northstar/core/filter/FilterModel";
+import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
+import { AggregateFunction, Bin, Brush, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
+import { LABColor } from '../../northstar/utils/LABcolor';
+import { PIXIRectangle } from "../../northstar/utils/MathUtil";
+import { StyleConstants } from "../../northstar/utils/StyleContants";
+import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox";
+import "./HistogramBoxPrimitives.scss";
+
+
+@observer
+export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> {
+    private get histoOp() { return this.props.HistoBox.HistoOp; }
+    private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; }
+    @observable _selectedPrims: HistogramBinPrimitive[] = [];
+    @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
+    @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
+    @computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); }
+    @computed get binPrimitives() {
+        let histoResult = this.props.HistoBox.HistogramResult;
+        if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length)
+            return (null);
+        let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
+        return Object.keys(histoResult.bins).reduce((prims, key) => {
+            let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox);
+
+            let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex,
+                ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp.X, this.histoOp.Y));
+            drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp =>
+                prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle))));
+            return prims;
+        }, [] as JSX.Element[]);
+    }
+
+    private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
+        let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex);
+        return !allBrushPrim ? () => { } : () => runInAction(() => {
+            if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) {
+                this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim!), 1);
+                this.histoOp.RemoveFilterModels([filterModel]);
+            }
+            else {
+                this._selectedPrims.push(allBrushPrim!);
+                this.histoOp.AddFilterModels([filterModel]);
+            }
+        })
+    }
+
+    private renderGridLinesAndLabels(axis: number) {
+        if (!this.props.HistoBox.SizeConverter.Initialized)
+            return (null);
+        let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels();
+        return labels.reduce((prims, binLabel, i) => {
+            let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
+            prims.push(this.drawLine(r.xFrom, r.yFrom, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0));
+            if (i == labels.length - 1)
+                prims.push(this.drawLine(axis == 0 ? r.xTo : r.xFrom, axis == 0 ? r.yFrom : r.yTo, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0));
+            return prims;
+        }, [] as JSX.Element[]);
+    }
+
+    drawEntity(xFrom: number, yFrom: number, entity: JSX.Element) {
+        let transXpercent = xFrom / this.renderDimension * 100;
+        let transYpercent = yFrom / this.renderDimension * 100;
+        return (<div key={DashUtils.GenerateGuid()} className={`histogramboxprimitives-placer`} style={{ transform: `translate(${transXpercent}%, ${transYpercent}%)` }}>
+            {entity}
+        </div>);
+    }
+    drawLine(xFrom: number, yFrom: number, width: number, height: number) {
+        if (height < 0) {
+            yFrom += height;
+            height = -height;
+        }
+        if (width < 0) {
+            xFrom += width;
+            width = -width;
+        }
+        let trans2Xpercent = width == 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`;
+        let trans2Ypercent = height == 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`;
+        let line = (<div className="histogramboxprimitives-line" style={{ width: trans2Xpercent, height: trans2Ypercent, }} />);
+        return this.drawEntity(xFrom, yFrom, line);
+    }
+
+    drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) {
+        if (r.height < 0) {
+            r.y += r.height;
+            r.height = -r.height;
+        }
+        if (r.width < 0) {
+            r.x += r.width;
+            r.width = -r.width;
+        }
+        let widthPercent = r.width / this.renderDimension * 100;
+        let heightPercent = r.height / this.renderDimension * 100;
+        let rect = (<div className={`histogramboxprimitives-${classExt}`} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
+            style={{
+                borderBottomStyle: barAxis == 1 ? "none" : "solid",
+                borderLeftStyle: barAxis == 0 ? "none" : "solid",
+                width: `${widthPercent}%`,
+                height: `${heightPercent}%`,
+                background: color ? `${LABColor.RGBtoHexString(color)}` : ""
+            }}
+        />);
+        return this.drawEntity(r.x, r.y, rect);
+    }
+    render() {
+        return <div className="histogramboxprimitives-container" style={{
+            width: "100%",
+            height: "100%",
+        }}>
+            {this.xaxislines}
+            {this.yaxislines}
+            {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;
+    public BarAxis: number = -1;
+}
+
+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; }
+    private get sizeConverter() { return this._histoBox.SizeConverter!; }
+    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
+    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
+
+    constructor(bin: Bin, histoBox: HistogramBox) {
+        this._histoBox = histoBox;
+        let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2
+
+        brushing.orderedBrushes.reduce((brushFactorSum, brush) => {
+            switch (histoBox.ChartType) {
+                case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
+                case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
+                case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush);
+                case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum);
+            }
+        }, 0);
+
+        // adjust brush rects (stacking or not)
+        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
+        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
+        filteredBinPrims.reduce((sum, 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);
+                    return 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 / filteredBinPrims.length, 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);
+                    return 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);
+                    return 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 / filteredBinPrims.length);
+                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
+                    return sum + fbp.Rect.height;
+                }
+            }
+            return 0;
+        }, 0);
+        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 setupBrushing(bin: Bin, normalization: number) {
+        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
+        var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]];
+        this.histoResult.brushes!.map(brush => brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex && orderedBrushes.push(brush));
+        return {
+            orderedBrushes,
+            maxAxis: orderedBrushes.reduce((prev, Brush) => {
+                let aggResult = this.histoOp.getValue(normalization, bin, this.histoResult, Brush.brushIndex!);
+                return aggResult != undefined && aggResult > prev ? aggResult : prev;
+            }, Number.MIN_VALUE)
+        };
+    }
+    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
+
+        let unNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, brush.brushIndex!);
+        if (unNormalizedValue == undefined)
+            return brushFactorSum;
+
+        var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
+            unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]);
+
+        let allUnNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult))
+
+        // bcz: are these calls needed?  
+        let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
+        let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
+
+        var returnBrushFactorSum = brushFactorSum;
+        if (allUnNormalizedValue != undefined) {
+            var brushFactor = (unNormalizedValue / allUnNormalizedValue);
+            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(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
+        return returnBrushFactorSum;
+    }
+
+    private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number {
+        let unNormalizedValue = this._histoBox.HistoOp.getValue(2, bin, this.histoResult, brush.brushIndex!);
+        if (unNormalizedValue != undefined) {
+            let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!));
+            let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!));
+
+            if (xFrom != undefined && yFrom != undefined && xTo != undefined && yTo != undefined)
+                this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+        }
+        return 0;
+    }
+
+    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
+        let dataValue = this.histoOp.getValue(1, bin, this.histoResult, brush.brushIndex!);
+        if (dataValue != undefined) {
+            let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
+            let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
+
+            var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y);
+            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
+                this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
+                this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
+
+            this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue);
+        }
+        return 0;
+    }
+
+    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
+        let dataValue = this.histoOp.getValue(0, bin, this.histoResult, brush.brushIndex!);
+        if (dataValue != undefined) {
+            let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis);
+            let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
+
+            var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X);
+            var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                yTo + (yFrom - yTo) / 2.0 - 1,
+                this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                2.0);
+
+            this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue);
+        }
+        return 0;
+    }
+
+
+    private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
+        var marginParams = new MarginAggregateParameters();
+        marginParams.aggregateFunction = axis.AggregateFunction;
+        var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams);
+        var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult;
+        return !marginResult ? 0 : marginResult.absolutMargin!;
+    }
+
+    private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle,
+        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
+        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,
+                BarAxis: barAxis
+            });
+        this.BinPrimitives.push(binPrimitive);
+    }
+
+    private baseColorFromBrush(brush: Brush): number {
+        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
+            return StyleConstants.HIGHLIGHT_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+            return StyleConstants.OVERLAP_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
+            return 0x00ff00;
+        }
+        else if (this.histoOp.BrushColors.length > 0) {
+            return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+        }
+        return StyleConstants.HIGHLIGHT_COLOR;
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From da7a12f9b49b43534658524b1da846948fbf3947 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 29 Mar 2019 14:37:26 -0400
Subject: fixed some bugs added some small features to histograms.

---
 src/client/Server.ts                                 |  4 ++--
 src/client/documents/Documents.ts                    |  4 ++--
 src/client/northstar/dash-nodes/HistogramBox.tsx     | 17 ++++++++++++-----
 .../northstar/dash-nodes/HistogramBoxPrimitives.tsx  |  5 ++++-
 src/client/northstar/utils/SizeConverter.ts          |  2 +-
 src/client/util/DragManager.ts                       |  2 ++
 src/client/views/Main.tsx                            |  6 +++---
 .../collectionFreeForm/CollectionFreeFormView.scss   | 20 ++++++++++----------
 8 files changed, 36 insertions(+), 24 deletions(-)

(limited to 'src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx')

diff --git a/src/client/Server.ts b/src/client/Server.ts
index feafe9eb4..37e3c2c0d 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -75,7 +75,7 @@ export class Server {
                     existingFields[id] = field;
                 }
             }
-            SocketStub.SEND_FIELDS_REQUEST(neededFieldIds, (fields) => {
+            SocketStub.SEND_FIELDS_REQUEST(neededFieldIds, action((fields: FieldMap) => {
                 for (let id of neededFieldIds) {
                     let field = fields[id];
                     if (field) {
@@ -104,7 +104,7 @@ export class Server {
                         cb({ ...fields, ...existingFields })
                     }
                 }, { fireImmediately: true })
-            });
+            }));
         };
         if (callback) {
             fn(callback);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 663ccae61..0bf275df8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -190,8 +190,8 @@ export namespace Documents {
         return assignToDelegate(SetInstanceOptions(GetAudioPrototype(), options, [new URL(url), AudioField]), options);
     }
 
-    export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string) {
-        return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, [histoOp, HistogramField], id).MakeDelegate(), options);
+    export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string, delegId?: string) {
+        return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, [histoOp, HistogramField], id).MakeDelegate(delegId), options);
     }
     export function TextDocument(options: DocumentOptions = {}) {
         return assignToDelegate(SetInstanceOptions(GetTextPrototype(), options, ["", TextField]).MakeDelegate(), options);
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 9f8c2cfd0..dba4ce900 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -9,7 +9,7 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
 import { FilterModel } from '../../northstar/core/filter/FilterModel';
 import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
 import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult, Catalog } from "../../northstar/model/idea/idea";
+import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult, Catalog, AggregateFunction } from "../../northstar/model/idea/idea";
 import { ModelHelpers } from "../../northstar/model/ModelHelpers";
 import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
 import { PIXIRectangle } from "../../northstar/utils/MathUtil";
@@ -21,6 +21,7 @@ import "../utils/Extensions"
 import "./HistogramBox.scss";
 import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
 import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
 
 export interface HistogramPrimitivesProps {
     HistoBox: HistogramBox;
@@ -104,7 +105,11 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     @action
     xLabelPointerDown = (e: React.PointerEvent) => {
-
+        this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+    }
+    @action
+    yLabelPointerDown = (e: React.PointerEvent) => {
+        this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
     }
 
     componentWillUnmount() {
@@ -138,12 +143,12 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}>
                 {({ measureRef }) =>
                     <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-w / 2}px, ${-h / 2}px)` }}>
-                        <div className="histogrambox-yaxislabel" ref={this._dropYRef} >
+                        <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
                             <span className="histogrambox-yaxislabel-text">
                                 {labelY}
                             </span>
                         </div>
-                        <div style={{
+                        <div className="histogrambox-primitives" style={{
                             transform: `translate(${loff + 25}px, ${toff}px)`,
                             width: `calc(100% - ${loff + roff + 25}px)`,
                             height: `calc(100% - ${toff + boff}px)`,
@@ -151,7 +156,9 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                             <HistogramLabelPrimitives HistoBox={this} />
                             <HistogramBoxPrimitives HistoBox={this} />
                         </div>
-                        <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >{labelX}</div>
+                        <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >
+                            {labelX}
+                        </div>
                     </div>
                 }
             </Measure>
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index d2f1be4fd..5e403eb9c 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -1,5 +1,5 @@
 import React = require("react")
-import { computed, observable, runInAction } from "mobx";
+import { computed, observable, runInAction, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
 import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
@@ -19,6 +19,9 @@ import "./HistogramBoxPrimitives.scss";
 export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> {
     private get histoOp() { return this.props.HistoBox.HistoOp; }
     private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; }
+    componentDidMount() {
+        reaction(() => this.props.HistoBox.HistogramResult, () => this._selectedPrims.length = 0);
+    }
     @observable _selectedPrims: HistogramBinPrimitive[] = [];
     @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
     @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index 30627dfd5..bb91ed4a7 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -62,7 +62,7 @@ export class SizeConverter {
 
     public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) {
         var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
-        if (value.hasResult)
+        if (value && value.hasResult)
             return [this.DataToScreenCoord(value.result!, axis) - 5,
             this.DataToScreenCoord(value.result!, axis) + 5];
         return [undefined, undefined];
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 661fa4dc8..70b1c9829 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -186,6 +186,8 @@ export namespace DragManager {
             e.preventDefault();
             x += e.movementX;
             y += e.movementY;
+            if (dragData instanceof DocumentDragData)
+                dragData.aliasOnDrop = e.ctrlKey || e.altKey;
             if (e.shiftKey) {
                 abortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 2f20b102c..75855c3d1 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -245,7 +245,7 @@ export class Main extends React.Component {
         let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
         let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
         let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
-        let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 100, height: 400, title: "northstar schemas" }));
+        let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas" }));
         let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
         let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
         let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
@@ -340,7 +340,7 @@ export class Main extends React.Component {
                 let schemaDoc = Documents.TreeDocument([], { width: 50, height: 100, title: schema.displayName! });
                 let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]);
                 CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-                    Server.GetField(attr.displayName!, action((field: Opt<Field>) => {
+                    Server.GetField(attr.displayName! + ".alias", action((field: Opt<Field>) => {
                         if (field instanceof Document) {
                             schemaDocuments.push(field);
                         } else {
@@ -349,7 +349,7 @@ export class Main extends React.Component {
                                 new AttributeTransformationModel(atmod, AggregateFunction.None),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                            schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, attr.displayName!));
+                            schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias"));
                         }
                     }));
                 });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 9c5e98005..215a75243 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,5 +1,15 @@
 @import "../../global_variables";
 
+.collectionfreeformview {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  .inking-canvas {
+    transform-origin: 50000px 50000px;
+  }
+}
 .collectionfreeformview-container {
   .collectionfreeformview > .jsx-parser {
     position: absolute;
@@ -27,16 +37,6 @@
   left: 0;
   width: 100%;
   height: 100%;
-  .collectionfreeformview {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    .inking-canvas {
-      transform-origin: 50000px 50000px;
-    }
-  }
 }
 .collectionfreeformview-overlay {
   .collectionfreeformview > .jsx-parser {
-- 
cgit v1.2.3-70-g09d2


From a10bbd686a825ee38caceb7ecfc388715c14a817 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 29 Mar 2019 16:18:55 -0400
Subject: added basic brushing placeholder

---
 .../northstar/core/brusher/BrushLinkModel.ts       | 40 ------------------
 .../northstar/core/brusher/IBaseBrushable.ts       |  4 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 19 +++++----
 .../dash-nodes/HistogramBoxPrimitives.tsx          |  5 ++-
 .../northstar/operations/HistogramOperation.ts     | 48 +++++++++++-----------
 5 files changed, 43 insertions(+), 73 deletions(-)
 delete mode 100644 src/client/northstar/core/brusher/BrushLinkModel.ts

(limited to 'src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx')

diff --git a/src/client/northstar/core/brusher/BrushLinkModel.ts b/src/client/northstar/core/brusher/BrushLinkModel.ts
deleted file mode 100644
index e3ac43367..000000000
--- a/src/client/northstar/core/brusher/BrushLinkModel.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { IBaseBrushable } from '../brusher/IBaseBrushable'
-import { IBaseBrusher } from '../brusher/IBaseBrusher'
-import { Utils } from '../../utils/Utils'
-import { IEquatable } from '../../utils/IEquatable';
-
-export class BrushLinkModel<T> implements IEquatable {
-
-    public From: IBaseBrusher<T>;
-
-    public To: IBaseBrushable<T>;
-
-    public Color: number = 0;
-
-    constructor(from: IBaseBrusher<T>, to: IBaseBrushable<T>) {
-        this.From = from;
-        this.To = to;
-    }
-
-    public static overlaps(start: number, end: number, otherstart: number, otherend: number): boolean {
-        if (start > otherend || otherstart > end)
-            return false;
-        return true;
-    }
-    public static Connected<T>(from: IBaseBrusher<T>, to: IBaseBrushable<T>): boolean {
-        var connected = (Math.abs(from.Position.x + from.Size.x - to.Position.x) <= 60 &&
-            this.overlaps(from.Position.y, from.Position.y + from.Size.y, to.Position.y, to.Position.y + to.Size.y)
-        ) ||
-            (Math.abs(to.Position.x + to.Size.x - from.Position.x) <= 60 &&
-                this.overlaps(to.Position.y, to.Position.y + to.Size.y, from.Position.y, from.Position.y + from.Size.y)
-            );
-        return connected;
-    }
-
-    public Equals(other: Object): boolean {
-        if (!Utils.EqualityHelper(this, other)) return false;
-        if (!this.From.Equals((other as BrushLinkModel<T>).From)) return false;
-        if (!this.To.Equals((other as BrushLinkModel<T>).To)) return false;
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
index 07d4e7580..99a36636f 100644
--- a/src/client/northstar/core/brusher/IBaseBrushable.ts
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -1,9 +1,9 @@
-import { BrushLinkModel } from '../brusher/BrushLinkModel'
 import { PIXIPoint } from '../../utils/MathUtil'
 import { IEquatable } from '../../utils/IEquatable';
+import { Document } from '../../../../fields/Document'
 
 export interface IBaseBrushable<T> extends IEquatable {
-    BrusherModels: Array<BrushLinkModel<T>>;
+    BrusherModels: Array<Document>;
     BrushColors: Array<number>;
     Position: PIXIPoint;
     Size: PIXIPoint;
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index dba4ce900..b464e125c 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -2,26 +2,25 @@ import React = require("react")
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import Measure from "react-measure";
-import { Dictionary } from "typescript-collections";
 import { FieldWaiting, Opt } from "../../../fields/Field";
+import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { FilterModel } from '../../northstar/core/filter/FilterModel';
 import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
 import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult, Catalog, AggregateFunction } from "../../northstar/model/idea/idea";
+import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
 import { ModelHelpers } from "../../northstar/model/ModelHelpers";
 import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { PIXIRectangle } from "../../northstar/utils/MathUtil";
 import { SizeConverter } from "../../northstar/utils/SizeConverter";
 import { DragManager } from "../../util/DragManager";
 import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
 import { HistogramField } from "../dash-fields/HistogramField";
-import "../utils/Extensions"
+import "../utils/Extensions";
 import "./HistogramBox.scss";
 import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
 import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
-import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
+import { StyleConstants } from "../utils/StyleContants";
 
 export interface HistogramPrimitivesProps {
     HistoBox: HistogramBox;
@@ -124,7 +123,13 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
                 if (this.HistoOp != HistogramOperation.Empty) {
-                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), docs => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []),
+                        (docs: Document[]) => {
+                            var availableColors = StyleConstants.BRUSH_COLORS.map(c => c);
+                            docs.map((brush, i) => brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]));
+                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...docs);
+                            //this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs)
+                        }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
                 }
             }));
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index 5e403eb9c..b8020c28c 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -168,7 +168,7 @@ export class HistogramBinPrimitiveCollection {
         var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
         filteredBinPrims.reduce((sum, fbp) => {
             if (histoBox.ChartType == ChartType.VerticalBar) {
-                if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
+                if (this.histoOp.Y.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);
                     return sum + fbp.Rect.height;
@@ -269,6 +269,9 @@ export class HistogramBinPrimitiveCollection {
     private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
         let dataValue = this.histoOp.getValue(1, bin, this.histoResult, brush.brushIndex!);
         if (dataValue != undefined) {
+            if (bin.binIndex!.indices![0] == 0 && bin.binIndex!.indices![1] == 0) {
+                console.log("DV = " + dataValue)
+            }
             let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
             let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
 
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 8a0f648f6..fa51e2a8c 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -4,29 +4,30 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
 import { ColumnAttributeModel } from "../core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
 import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttributeModel";
-import { BrushLinkModel } from "../core/brusher/BrushLinkModel";
 import { FilterModel } from "../core/filter/FilterModel";
 import { FilterOperand } from "../core/filter/FilterOperand";
 import { IBaseFilterConsumer } from "../core/filter/IBaseFilterConsumer";
-import { IBaseFilterProvider } from "../core/filter/IBaseFilterProvider";
+import { IBaseFilterProvider, instanceOfIBaseFilterProvider } from "../core/filter/IBaseFilterProvider";
 import { SETTINGS_SAMPLE_SIZE, SETTINGS_X_BINS, SETTINGS_Y_BINS } from "../model/binRanges/VisualBinRangeHelper";
 import { AggregateFunction, AggregateParameters, Attribute, AverageAggregateParameters, DataType, HistogramOperationParameters, QuantitativeBinRange, HistogramResult, Brush, DoubleValueAggregateResult, Bin } from "../model/idea/idea";
 import { ModelHelpers } from "../model/ModelHelpers";
 import { ArrayUtil } from "../utils/ArrayUtil";
 import { BaseOperation } from "./BaseOperation";
+import { KeyStore } from "../../../fields/KeyStore";
+import { HistogramField } from "../dash-fields/HistogramField";
+import { FieldWaiting } from "../../../fields/Field";
 
 
 export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider {
     @observable public FilterOperand: FilterOperand = FilterOperand.AND;
     @observable public Links: Document[] = [];
+    @observable public BrushLinks: Document[] = [];
     @observable public BrushColors: number[] = [];
     @observable public Normalization: number = -1;
     @observable public FilterModels: FilterModel[] = [];
     @observable public X: AttributeTransformationModel;
     @observable public Y: AttributeTransformationModel;
     @observable public V: AttributeTransformationModel;
-    @observable public BrusherModels: BrushLinkModel<HistogramOperation>[] = [];
-    @observable public BrushableModels: BrushLinkModel<HistogramOperation>[] = [];
     @observable public SchemaName: string;
     @computed public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); }
 
@@ -63,25 +64,24 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @computed
     public get FilterString(): string {
         let filterModels: FilterModel[] = [];
-        let fstring = FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true)
-        return fstring;
+        return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true)
     }
 
-    @computed.struct
-    public get BrushString() {
-        return [];
-        // let brushes = [];
-        // this.TypedViewModel.BrusherModels.map(brushLinkModel => {
-        //     if (instanceOfIBaseFilterProvider(brushLinkModel.From) && brushLinkModel.From.FilterModels.some && brushLinkModel.From instanceof BaseOperationViewModel) {
-        //         let brushFilterModels = [];
-        //         let gnode = MainManager.Instance.MainViewModel.FilterDependencyGraph.has(brushLinkModel.From) ?
-        //             MainManager.Instance.MainViewModel.FilterDependencyGraph.get(brushLinkModel.From) :
-        //             new GraphNode<BaseOperationViewModel, FilterLinkViewModel>(brushLinkModel.From);
-        //         let brush = FilterModel.GetFilterModelsRecursive(gnode, new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), brushFilterModels, false);
-        //         brushes.push(brush);
-        //     }
-        // });
-        // return brushes;
+    @computed
+    public get BrushString(): string[] {
+        let brushes: string[] = [];
+        this.BrushLinks.map(brushLink => {
+            let brusherDoc = brushLink.Get(KeyStore.LinkedFromDocs);
+            if (brusherDoc && brusherDoc != FieldWaiting && brusherDoc instanceof Document) {
+                let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField);
+                if (brushHistogram && brushHistogram != FieldWaiting) {
+                    let filterModels: FilterModel[] = [];
+                    let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set<IBaseFilterProvider>(), filterModels, false)
+                    brushes.push(brush);
+                }
+            }
+        });
+        return brushes;
     }
 
 
@@ -131,11 +131,13 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
             });
         }
     }
-
+    get_random_color(): number {
+        return (Math.floor(Math.random() * 256) << 16) + (Math.floor(Math.random() * 256) << 8) + (Math.floor(Math.random() * 256));
+    }
 
     @action
     public async Update(): Promise<void> {
-        this.BrushColors = this.BrusherModels.map(e => e.Color);
+        this.BrushColors = this.BrushLinks.map(e => e.GetNumber(KeyStore.BackgroundColor, 0));
         return super.Update();
     }
 }
-- 
cgit v1.2.3-70-g09d2


From 6e0439f36216af6ee25ff9a65d296e6f9ff28fd3 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 29 Mar 2019 16:24:44 -0400
Subject: from last

---
 src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx | 3 ---
 1 file changed, 3 deletions(-)

(limited to 'src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx')

diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index b8020c28c..648070241 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -269,9 +269,6 @@ export class HistogramBinPrimitiveCollection {
     private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
         let dataValue = this.histoOp.getValue(1, bin, this.histoResult, brush.brushIndex!);
         if (dataValue != undefined) {
-            if (bin.binIndex!.indices![0] == 0 && bin.binIndex!.indices![1] == 0) {
-                console.log("DV = " + dataValue)
-            }
             let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
             let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
 
-- 
cgit v1.2.3-70-g09d2


From 6e993fb5817e8ddce756396e53883a42530f52bb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 29 Mar 2019 18:49:22 -0400
Subject: brushes mostly working - some problems with cycles.

---
 src/client/northstar/dash-fields/HistogramField.ts | 17 +++++++-
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 27 ++++++++----
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 14 ++++---
 src/client/northstar/operations/BaseOperation.ts   |  6 ++-
 .../northstar/operations/HistogramOperation.ts     | 22 +++++-----
 .../CollectionFreeFormLinksView.tsx                | 49 +++++++++++++++++++++-
 src/fields/KeyStore.ts                             |  1 +
 7 files changed, 107 insertions(+), 29 deletions(-)

(limited to 'src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx')

diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 00912c595..1929f8dcd 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -16,7 +16,8 @@ export class HistogramField extends BasicField<HistogramOperation> {
     }
 
     toString(): string {
-        return JSON.stringify(this.Data);
+        let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']);
+        return JSON.stringify(omitted);
     }
 
     Copy(): Field {
@@ -27,10 +28,22 @@ export class HistogramField extends BasicField<HistogramOperation> {
         return `new HistogramField("${this.Data}")`;
     }
 
+    omitKeys(obj: any, keys: any) {
+        var dup: any = {};
+        for (var key in obj) {
+            if (keys.indexOf(key) == -1) {
+                dup[key] = obj[key];
+            }
+        }
+        return dup;
+    }
+
     ToJson(): { type: Types, data: string, _id: string } {
+        let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']);
         return {
             type: Types.HistogramOp,
-            data: JSON.stringify(this.Data),
+
+            data: JSON.stringify(omitted),
             _id: this.Id
         }
     }
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index b464e125c..9976ff6ad 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -1,5 +1,5 @@
 import React = require("react")
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import Measure from "react-measure";
 import { FieldWaiting, Opt } from "../../../fields/Field";
@@ -8,7 +8,7 @@ import { KeyStore } from "../../../fields/KeyStore";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
 import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
+import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult, Result } from "../../northstar/model/idea/idea";
 import { ModelHelpers } from "../../northstar/model/ModelHelpers";
 import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
 import { SizeConverter } from "../../northstar/utils/SizeConverter";
@@ -39,10 +39,10 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
     @observable public VisualBinRanges: VisualBinRange[] = [];
     @observable public ValueRange: number[] = [];
+    @observable public HistogramResult: HistogramResult = new HistogramResult();
     @observable public SizeConverter: SizeConverter = new SizeConverter();
 
-    @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); }
-    @computed get HistogramResult() { return this.HistoOp ? this.HistoOp.Result as HistogramResult : undefined; }
+    @computed get createOperationParamsCache() { trace(); return this.HistoOp.CreateOperationParameters(); }
     @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; }
     @computed get ChartType() {
         return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ?
@@ -123,12 +123,21 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
                 this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
                 if (this.HistoOp != HistogramOperation.Empty) {
-                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []),
-                        (docs: Document[]) => {
+                    this.HistoOp.YieldResult = (r: Result) => action(() => this.HistogramResult = r as HistogramResult)();
+                    reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length,
+                        () => {
+                            let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]);
                             var availableColors = StyleConstants.BRUSH_COLORS.map(c => c);
-                            docs.map((brush, i) => brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]));
-                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...docs);
-                            //this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs)
+                            let proto = this.props.doc.GetPrototype() as Document;
+                            let brushingLinks = brushingDocs.map((brush, i) => {
+                                // brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]);
+                                let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
+                                if (!brushed || brushed.length < 2)
+                                    return undefined;
+                                return { l: brush, b: brushed[0].Id == proto.Id ? brushed[1] : brushed[0] }
+                            }).filter(x => x != undefined) as { l: Document, b: Document }[];
+                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingLinks);
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
                 }
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index 648070241..c97acb064 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -1,5 +1,5 @@
 import React = require("react")
-import { computed, observable, runInAction, reaction } from "mobx";
+import { computed, observable, runInAction, reaction, untracked, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
 import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
@@ -20,7 +20,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
     private get histoOp() { return this.props.HistoBox.HistoOp; }
     private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; }
     componentDidMount() {
-        reaction(() => this.props.HistoBox.HistogramResult, () => this._selectedPrims.length = 0);
+        reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0);
     }
     @observable _selectedPrims: HistogramBinPrimitive[] = [];
     @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
@@ -30,10 +30,10 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         let histoResult = this.props.HistoBox.HistogramResult;
         if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length)
             return (null);
+        trace();
         let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
         return Object.keys(histoResult.bins).reduce((prims, key) => {
             let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox);
-
             let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex,
                 ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp.X, this.histoOp.Y));
             drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp =>
@@ -327,6 +327,7 @@ export class HistogramBinPrimitiveCollection {
     }
 
     private baseColorFromBrush(brush: Brush): number {
+        let bc = StyleConstants.BRUSH_COLORS;
         if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
             return StyleConstants.HIGHLIGHT_COLOR;
         }
@@ -336,9 +337,12 @@ export class HistogramBinPrimitiveCollection {
         else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
             return 0x00ff00;
         }
-        else if (this.histoOp.BrushColors.length > 0) {
-            return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+        else if (bc.length > 0) {
+            return bc[brush.brushIndex! % bc.length];
         }
+        // else if (this.histoOp.BrushColors.length > 0) {
+        //     return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+        // }
         return StyleConstants.HIGHLIGHT_COLOR;
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index 94e0849af..21d1db749 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -10,7 +10,8 @@ export abstract class BaseOperation {
 
     @observable public Error: string = "";
     @observable public OverridingFilters: FilterModel[] = [];
-    @observable public Result?: Result = undefined;
+    //@observable 
+    public Result?: Result = undefined;
     @observable public ComputationStarted: boolean = false;
     public OperationReference?: OperationReference = undefined;
 
@@ -45,8 +46,11 @@ export abstract class BaseOperation {
     }
 
 
+    public YieldResult: ((result: Result) => void) | undefined;
     @action
     public SetResult(result: Result): void {
+        if (this.YieldResult)
+            this.YieldResult(result);
         this.Result = result;
     }
 
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index fa51e2a8c..48bad73df 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
 import { Document } from "../../../fields/Document";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { ColumnAttributeModel } from "../core/attribute/AttributeModel";
@@ -16,12 +16,13 @@ import { BaseOperation } from "./BaseOperation";
 import { KeyStore } from "../../../fields/KeyStore";
 import { HistogramField } from "../dash-fields/HistogramField";
 import { FieldWaiting } from "../../../fields/Field";
+import { StyleConstants } from "../utils/StyleContants";
 
 
 export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider {
     @observable public FilterOperand: FilterOperand = FilterOperand.AND;
     @observable public Links: Document[] = [];
-    @observable public BrushLinks: Document[] = [];
+    @observable public BrushLinks: { l: Document, b: Document }[] = [];
     @observable public BrushColors: number[] = [];
     @observable public Normalization: number = -1;
     @observable public FilterModels: FilterModel[] = [];
@@ -69,16 +70,15 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
 
     @computed
     public get BrushString(): string[] {
+        trace();
         let brushes: string[] = [];
         this.BrushLinks.map(brushLink => {
-            let brusherDoc = brushLink.Get(KeyStore.LinkedFromDocs);
-            if (brusherDoc && brusherDoc != FieldWaiting && brusherDoc instanceof Document) {
-                let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField);
-                if (brushHistogram && brushHistogram != FieldWaiting) {
-                    let filterModels: FilterModel[] = [];
-                    let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set<IBaseFilterProvider>(), filterModels, false)
-                    brushes.push(brush);
-                }
+            let brusherDoc = brushLink.b;
+            let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField);
+            if (brushHistogram && brushHistogram != FieldWaiting) {
+                let filterModels: FilterModel[] = [];
+                let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set<IBaseFilterProvider>(), filterModels, false)
+                brushes.push(brush);
             }
         });
         return brushes;
@@ -137,7 +137,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
 
     @action
     public async Update(): Promise<void> {
-        this.BrushColors = this.BrushLinks.map(e => e.GetNumber(KeyStore.BackgroundColor, 0));
+        //this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0));
         return super.Update();
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 4f28f43eb..8dbf80533 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,4 +1,4 @@
-import { computed } from "mobx";
+import { computed, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../../fields/Document";
 import { FieldWaiting } from "../../../../fields/Field";
@@ -11,10 +11,57 @@ import "./CollectionFreeFormLinksView.scss";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
+import { ListField } from "../../../../fields/ListField";
+import { TextField } from "../../../../fields/TextField";
+import { StyleConstants } from "../../../northstar/utils/StyleContants";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
 
+    componentDidMount() {
+        reaction(() => {
+            return DocumentManager.Instance.getAllDocumentViews(this.props.Document).map(dv => dv.props.Document.GetNumber(KeyStore.X, 0))
+        }, () => {
+            let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document);
+            for (let i = 0; i < views.length; i++) {
+                for (let j = i + 1; j < views.length; j++) {
+                    let srcDoc = views[j].props.Document;
+                    let dstDoc = views[i].props.Document;
+                    let x1 = srcDoc.GetNumber(KeyStore.X, 0);
+                    let x1w = srcDoc.GetNumber(KeyStore.Width, 0);
+                    let x2 = dstDoc.GetNumber(KeyStore.X, 0);
+                    let x2w = dstDoc.GetNumber(KeyStore.Width, 0);
+                    if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) {
+                        let linkDoc: Document = new Document();
+                        dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
+                            srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
+                                linkDoc.Set(KeyStore.Title, new TextField("New Brush"));
+                                linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
+                                linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
+                                linkDoc.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[0]);
+
+                                let dstTarg = (protoDest ? protoDest : dstDoc);
+                                let srcTarg = (protoSrc ? protoSrc : srcDoc);
+                                linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
+                                dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
+                                srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
+                            }))
+                        )
+                    } else {
+                        dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
+                            srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
+
+                                let dstTarg = (protoDest ? protoDest : dstDoc);
+                                let srcTarg = (protoSrc ? protoSrc : srcDoc);
+                                dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.length = 0 })
+                                srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField<Document>).Data.length = 0 })
+                            }))
+                        )
+                    }
+                }
+            }
+        });
+    }
     documentAnchors(view: DocumentView) {
         let equalViews = [view];
         let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index aa0b9ce92..1f039e592 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -29,6 +29,7 @@ export namespace KeyStore {
     export const Caption = new Key("Caption");
     export const ActiveWorkspace = new Key("ActiveWorkspace");
     export const DocumentText = new Key("DocumentText");
+    export const BrushingDocs = new Key("BrushingDocs");
     export const LinkedToDocs = new Key("LinkedToDocs");
     export const LinkedFromDocs = new Key("LinkedFromDocs");
     export const LinkDescription = new Key("LinkDescription");
-- 
cgit v1.2.3-70-g09d2


From 79f9bc805f281a13fe59162f2e25dd1fdcefaae0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 29 Mar 2019 20:13:06 -0400
Subject: code cleanup

---
 src/client/northstar/dash-fields/HistogramField.ts |  24 +-
 .../dash-nodes/HistogramBinPrimitiveCollection.ts  | 238 ++++++++++++++++++++
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  21 +-
 .../dash-nodes/HistogramBoxPrimitives.scss         |   4 +
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 246 +--------------------
 .../dash-nodes/HistogramLabelPrimitives.tsx        |   3 +-
 .../northstar/operations/HistogramOperation.ts     |  73 +++---
 7 files changed, 301 insertions(+), 308 deletions(-)
 create mode 100644 src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts

(limited to 'src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx')

diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 1929f8dcd..ae83ea5ba 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -15,9 +15,17 @@ export class HistogramField extends BasicField<HistogramOperation> {
         super(data ? data : HistogramOperation.Empty, save, id);
     }
 
+    omitKeys(obj: any, keys: any) {
+        var dup: any = {};
+        for (var key in obj) {
+            if (keys.indexOf(key) == -1) {
+                dup[key] = obj[key];
+            }
+        }
+        return dup;
+    }
     toString(): string {
-        let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']);
-        return JSON.stringify(omitted);
+        return JSON.stringify(this.omitKeys(this.Data, ['Links', 'BrushLinks', 'Result']));
     }
 
     Copy(): Field {
@@ -28,22 +36,12 @@ export class HistogramField extends BasicField<HistogramOperation> {
         return `new HistogramField("${this.Data}")`;
     }
 
-    omitKeys(obj: any, keys: any) {
-        var dup: any = {};
-        for (var key in obj) {
-            if (keys.indexOf(key) == -1) {
-                dup[key] = obj[key];
-            }
-        }
-        return dup;
-    }
 
     ToJson(): { type: Types, data: string, _id: string } {
-        let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']);
         return {
             type: Types.HistogramOp,
 
-            data: JSON.stringify(omitted),
+            data: this.toString(),
             _id: this.Id
         }
     }
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
new file mode 100644
index 000000000..43e768c62
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
@@ -0,0 +1,238 @@
+import React = require("react")
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+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 { StyleConstants } from "../../northstar/utils/StyleContants";
+import { HistogramBox } from "./HistogramBox";
+import "./HistogramBoxPrimitives.scss";
+
+export 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;
+    public BarAxis: number = -1;
+}
+
+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; }
+    private get sizeConverter() { return this._histoBox.SizeConverter!; }
+    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
+    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
+
+    constructor(bin: Bin, histoBox: HistogramBox) {
+        this._histoBox = histoBox;
+        let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2
+
+        brushing.orderedBrushes.reduce((brushFactorSum, brush) => {
+            switch (histoBox.ChartType) {
+                case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
+                case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
+                case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush);
+                case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum);
+            }
+        }, 0);
+
+        // adjust brush rects (stacking or not)
+        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
+        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
+        filteredBinPrims.reduce((sum, fbp) => {
+            if (histoBox.ChartType == ChartType.VerticalBar) {
+                if (this.histoOp.Y.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);
+                    return 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 / filteredBinPrims.length, 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);
+                    return 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);
+                    return 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 / filteredBinPrims.length);
+                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
+                    return sum + fbp.Rect.height;
+                }
+            }
+            return 0;
+        }, 0);
+        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 setupBrushing(bin: Bin, normalization: number) {
+        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
+        var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]];
+        this.histoResult.brushes!.map(brush => brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex && orderedBrushes.push(brush));
+        return {
+            orderedBrushes,
+            maxAxis: orderedBrushes.reduce((prev, Brush) => {
+                let aggResult = this.getBinValue(normalization, bin, Brush.brushIndex!);
+                return aggResult != undefined && aggResult > prev ? aggResult : prev;
+            }, Number.MIN_VALUE)
+        };
+    }
+
+    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
+
+        let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
+        if (unNormalizedValue == undefined)
+            return brushFactorSum;
+
+        var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
+            unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]);
+
+        let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult))
+
+        // bcz: are these calls needed?  
+        let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
+        let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
+
+        var returnBrushFactorSum = brushFactorSum;
+        if (allUnNormalizedValue != undefined) {
+            var brushFactor = (unNormalizedValue / allUnNormalizedValue);
+            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(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
+        return returnBrushFactorSum;
+    }
+
+    private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number {
+        let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
+        if (unNormalizedValue != undefined) {
+            let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!));
+            let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!));
+
+            if (xFrom != undefined && yFrom != undefined && xTo != undefined && yTo != undefined)
+                this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+        }
+        return 0;
+    }
+
+    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
+        let dataValue = this.getBinValue(1, bin, brush.brushIndex!);
+        if (dataValue != undefined) {
+            let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
+            let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
+
+            var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y);
+            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
+                this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
+                this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
+
+            this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue);
+        }
+        return 0;
+    }
+
+    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
+        let dataValue = this.getBinValue(0, bin, brush.brushIndex!);
+        if (dataValue != undefined) {
+            let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis);
+            let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
+
+            var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X);
+            var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                yTo + (yFrom - yTo) / 2.0 - 1,
+                this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                2.0);
+
+            this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue);
+        }
+        return 0;
+    }
+
+    public getBinValue(axis: number, bin: Bin, brushIndex: number) {
+        var aggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis == 0 ? this.histoOp.X : axis == 1 ? this.histoOp.Y : this.histoOp.V, this.histoResult, brushIndex);
+        let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
+        return dataValue != null && dataValue.hasResult ? dataValue.result : undefined;
+    }
+
+    private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
+        var marginParams = new MarginAggregateParameters();
+        marginParams.aggregateFunction = axis.AggregateFunction;
+        var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams);
+        var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult;
+        return !marginResult ? 0 : marginResult.absolutMargin!;
+    }
+
+    private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle,
+        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
+        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,
+                BarAxis: barAxis
+            });
+        this.BinPrimitives.push(binPrimitive);
+    }
+
+    private baseColorFromBrush(brush: Brush): number {
+        let bc = StyleConstants.BRUSH_COLORS;
+        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
+            return StyleConstants.HIGHLIGHT_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+            return StyleConstants.OVERLAP_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
+            return 0x00ff00;
+        }
+        else if (bc.length > 0) {
+            return bc[brush.brushIndex! % bc.length];
+        }
+        // else if (this.histoOp.BrushColors.length > 0) {
+        //     return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+        // }
+        return StyleConstants.HIGHLIGHT_COLOR;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 9976ff6ad..6bf2697fc 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -22,9 +22,6 @@ import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
 import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
 
-export interface HistogramPrimitivesProps {
-    HistoBox: HistogramBox;
-}
 
 @observer
 export class HistogramBox extends React.Component<FieldViewProps> {
@@ -77,6 +74,15 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         }
     }
 
+    @action
+    xLabelPointerDown = (e: React.PointerEvent) => {
+        this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+    }
+    @action
+    yLabelPointerDown = (e: React.PointerEvent) => {
+        this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+    }
+
     componentDidMount() {
         if (this._dropXRef.current) {
             this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } });
@@ -102,15 +108,6 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             });
     }
 
-    @action
-    xLabelPointerDown = (e: React.PointerEvent) => {
-        this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
-    }
-    @action
-    yLabelPointerDown = (e: React.PointerEvent) => {
-        this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
-    }
-
     componentWillUnmount() {
         if (this._dropXDisposer)
             this._dropXDisposer();
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
index 9d42219cc..ce9edd65e 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
@@ -1,3 +1,7 @@
+.histogramboxprimitives-container {
+    width: 100%;
+    height: 100%;
+}
 .histogramboxprimitives-border {
     border: 3px;
     border-style: solid;
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index c97acb064..e9adb3ce5 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -1,27 +1,24 @@
 import React = require("react")
-import { computed, observable, runInAction, reaction, untracked, trace } from "mobx";
+import { computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Utils as DashUtils } from '../../../Utils';
-import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
 import { FilterModel } from "../../northstar/core/filter/FilterModel";
-import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
-import { AggregateFunction, Bin, Brush, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
 import { ModelHelpers } from "../../northstar/model/ModelHelpers";
 import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
 import { LABColor } from '../../northstar/utils/LABcolor';
 import { PIXIRectangle } from "../../northstar/utils/MathUtil";
 import { StyleConstants } from "../../northstar/utils/StyleContants";
-import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox";
+import { HistogramBinPrimitiveCollection, HistogramBinPrimitive } from "./HistogramBinPrimitiveCollection";
+import { HistogramBox } from "./HistogramBox";
 import "./HistogramBoxPrimitives.scss";
 
-
+export interface HistogramPrimitivesProps {
+    HistoBox: HistogramBox;
+}
 @observer
 export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> {
     private get histoOp() { return this.props.HistoBox.HistoOp; }
     private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; }
-    componentDidMount() {
-        reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0);
-    }
     @observable _selectedPrims: HistogramBinPrimitive[] = [];
     @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
     @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
@@ -42,6 +39,10 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         }, [] as JSX.Element[]);
     }
 
+    componentDidMount() {
+        reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0);
+    }
+
     private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
         let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex);
         return !allBrushPrim ? () => { } : () => runInAction(() => {
@@ -90,7 +91,6 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         let line = (<div className="histogramboxprimitives-line" style={{ width: trans2Xpercent, height: trans2Ypercent, }} />);
         return this.drawEntity(xFrom, yFrom, line);
     }
-
     drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) {
         if (r.height < 0) {
             r.y += r.height;
@@ -114,235 +114,11 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
         return this.drawEntity(r.x, r.y, rect);
     }
     render() {
-        return <div className="histogramboxprimitives-container" style={{
-            width: "100%",
-            height: "100%",
-        }}>
+        return <div className="histogramboxprimitives-container">
             {this.xaxislines}
             {this.yaxislines}
             {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;
-    public BarAxis: number = -1;
-}
-
-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; }
-    private get sizeConverter() { return this._histoBox.SizeConverter!; }
-    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
-    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
-
-    constructor(bin: Bin, histoBox: HistogramBox) {
-        this._histoBox = histoBox;
-        let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2
-
-        brushing.orderedBrushes.reduce((brushFactorSum, brush) => {
-            switch (histoBox.ChartType) {
-                case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
-                case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
-                case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush);
-                case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum);
-            }
-        }, 0);
-
-        // adjust brush rects (stacking or not)
-        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
-        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
-        filteredBinPrims.reduce((sum, fbp) => {
-            if (histoBox.ChartType == ChartType.VerticalBar) {
-                if (this.histoOp.Y.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);
-                    return 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 / filteredBinPrims.length, 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);
-                    return 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);
-                    return 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 / filteredBinPrims.length);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
-                    return sum + fbp.Rect.height;
-                }
-            }
-            return 0;
-        }, 0);
-        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 setupBrushing(bin: Bin, normalization: number) {
-        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
-        var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]];
-        this.histoResult.brushes!.map(brush => brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex && orderedBrushes.push(brush));
-        return {
-            orderedBrushes,
-            maxAxis: orderedBrushes.reduce((prev, Brush) => {
-                let aggResult = this.histoOp.getValue(normalization, bin, this.histoResult, Brush.brushIndex!);
-                return aggResult != undefined && aggResult > prev ? aggResult : prev;
-            }, Number.MIN_VALUE)
-        };
-    }
-    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
-
-        let unNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, brush.brushIndex!);
-        if (unNormalizedValue == undefined)
-            return brushFactorSum;
-
-        var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
-            unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]);
-
-        let allUnNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult))
-
-        // bcz: are these calls needed?  
-        let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
-        let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
-
-        var returnBrushFactorSum = brushFactorSum;
-        if (allUnNormalizedValue != undefined) {
-            var brushFactor = (unNormalizedValue / allUnNormalizedValue);
-            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(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
-        return returnBrushFactorSum;
-    }
-
-    private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number {
-        let unNormalizedValue = this._histoBox.HistoOp.getValue(2, bin, this.histoResult, brush.brushIndex!);
-        if (unNormalizedValue != undefined) {
-            let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!));
-            let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!));
-
-            if (xFrom != undefined && yFrom != undefined && xTo != undefined && yTo != undefined)
-                this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
-        }
-        return 0;
-    }
-
-    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
-        let dataValue = this.histoOp.getValue(1, bin, this.histoResult, brush.brushIndex!);
-        if (dataValue != undefined) {
-            let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
-            let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
-
-            var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y);
-            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
-                this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
-                this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
-
-            this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue);
-        }
-        return 0;
-    }
-
-    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
-        let dataValue = this.histoOp.getValue(0, bin, this.histoResult, brush.brushIndex!);
-        if (dataValue != undefined) {
-            let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis);
-            let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
-
-            var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X);
-            var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                yTo + (yFrom - yTo) / 2.0 - 1,
-                this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                2.0);
-
-            this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue);
-        }
-        return 0;
-    }
-
-
-    private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
-        var marginParams = new MarginAggregateParameters();
-        marginParams.aggregateFunction = axis.AggregateFunction;
-        var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams);
-        var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult;
-        return !marginResult ? 0 : marginResult.absolutMargin!;
-    }
-
-    private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle,
-        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
-        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,
-                BarAxis: barAxis
-            });
-        this.BinPrimitives.push(binPrimitive);
-    }
-
-    private baseColorFromBrush(brush: Brush): number {
-        let bc = StyleConstants.BRUSH_COLORS;
-        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
-            return StyleConstants.HIGHLIGHT_COLOR;
-        }
-        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
-            return StyleConstants.OVERLAP_COLOR;
-        }
-        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
-            return 0x00ff00;
-        }
-        else if (bc.length > 0) {
-            return bc[brush.brushIndex! % bc.length];
-        }
-        // else if (this.histoOp.BrushColors.length > 0) {
-        //     return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
-        // }
-        return StyleConstants.HIGHLIGHT_COLOR;
-    }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
index 45b23874d..93b237deb 100644
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -5,8 +5,9 @@ import { Utils as DashUtils } from '../../../Utils';
 import { NominalVisualBinRange } from "../model/binRanges/NominalVisualBinRange";
 import "../utils/Extensions";
 import { StyleConstants } from "../utils/StyleContants";
-import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox";
+import { HistogramBox } from "./HistogramBox";
 import "./HistogramLabelPrimitives.scss";
+import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
 
 @observer
 export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 48bad73df..4689cb233 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -1,5 +1,7 @@
 import { action, computed, observable, trace } from "mobx";
 import { Document } from "../../../fields/Document";
+import { FieldWaiting } from "../../../fields/Field";
+import { KeyStore } from "../../../fields/KeyStore";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { ColumnAttributeModel } from "../core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
@@ -7,52 +9,30 @@ import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttribut
 import { FilterModel } from "../core/filter/FilterModel";
 import { FilterOperand } from "../core/filter/FilterOperand";
 import { IBaseFilterConsumer } from "../core/filter/IBaseFilterConsumer";
-import { IBaseFilterProvider, instanceOfIBaseFilterProvider } from "../core/filter/IBaseFilterProvider";
+import { IBaseFilterProvider } from "../core/filter/IBaseFilterProvider";
+import { HistogramField } from "../dash-fields/HistogramField";
 import { SETTINGS_SAMPLE_SIZE, SETTINGS_X_BINS, SETTINGS_Y_BINS } from "../model/binRanges/VisualBinRangeHelper";
-import { AggregateFunction, AggregateParameters, Attribute, AverageAggregateParameters, DataType, HistogramOperationParameters, QuantitativeBinRange, HistogramResult, Brush, DoubleValueAggregateResult, Bin } from "../model/idea/idea";
+import { AggregateFunction, AggregateParameters, Attribute, AverageAggregateParameters, Bin, DataType, DoubleValueAggregateResult, HistogramOperationParameters, HistogramResult, QuantitativeBinRange } from "../model/idea/idea";
 import { ModelHelpers } from "../model/ModelHelpers";
 import { ArrayUtil } from "../utils/ArrayUtil";
 import { BaseOperation } from "./BaseOperation";
-import { KeyStore } from "../../../fields/KeyStore";
-import { HistogramField } from "../dash-fields/HistogramField";
-import { FieldWaiting } from "../../../fields/Field";
-import { StyleConstants } from "../utils/StyleContants";
-
 
 export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider {
+    public static Empty = new HistogramOperation("-empty schema-", new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())));
     @observable public FilterOperand: FilterOperand = FilterOperand.AND;
     @observable public Links: Document[] = [];
     @observable public BrushLinks: { l: Document, b: Document }[] = [];
     @observable public BrushColors: number[] = [];
-    @observable public Normalization: number = -1;
     @observable public FilterModels: FilterModel[] = [];
+
+    @observable public Normalization: number = -1;
     @observable public X: AttributeTransformationModel;
     @observable public Y: AttributeTransformationModel;
     @observable public V: AttributeTransformationModel;
     @observable public SchemaName: string;
+    @observable public QRange: QuantitativeBinRange | undefined;
     @computed public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); }
 
-    @action
-    public AddFilterModels(filterModels: FilterModel[]): void {
-        filterModels.filter(f => f !== null).forEach(fm => this.FilterModels.push(fm));
-    }
-    @action
-    public RemoveFilterModels(filterModels: FilterModel[]): void {
-        ArrayUtil.RemoveMany(this.FilterModels, filterModels);
-    }
-
-    public getValue(axis: number, bin: Bin, result: HistogramResult, brushIndex: number) {
-        var aggregateKey = ModelHelpers.CreateAggregateKey(this.Schema!.distinctAttributeParameters, axis == 0 ? this.X : axis == 1 ? this.Y : this.V, result, brushIndex);
-        let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
-        return dataValue != null && dataValue.hasResult ? dataValue.result : undefined;
-    }
-
-    public static Empty = new HistogramOperation("-empty schema-", new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())));
-
-    Equals(other: Object): boolean {
-        throw new Error("Method not implemented.");
-    }
-
     constructor(schemaName: string, x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) {
         super();
         this.X = x;
@@ -62,6 +42,19 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         this.SchemaName = schemaName;
     }
 
+    Equals(other: Object): boolean {
+        throw new Error("Method not implemented.");
+    }
+
+    @action
+    public AddFilterModels(filterModels: FilterModel[]): void {
+        filterModels.filter(f => f !== null).forEach(fm => this.FilterModels.push(fm));
+    }
+    @action
+    public RemoveFilterModels(filterModels: FilterModel[]): void {
+        ArrayUtil.RemoveMany(this.FilterModels, filterModels);
+    }
+
     @computed
     public get FilterString(): string {
         let filterModels: FilterModel[] = [];
@@ -73,25 +66,16 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         trace();
         let brushes: string[] = [];
         this.BrushLinks.map(brushLink => {
-            let brusherDoc = brushLink.b;
-            let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField);
+            let brushHistogram = brushLink.b.GetT(KeyStore.Data, HistogramField);
             if (brushHistogram && brushHistogram != FieldWaiting) {
                 let filterModels: FilterModel[] = [];
-                let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set<IBaseFilterProvider>(), filterModels, false)
-                brushes.push(brush);
+                brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.Data, new Set<IBaseFilterProvider>(), filterModels, false));
             }
         });
         return brushes;
     }
 
-
-    @computed.struct
-    public get SelectionString() {
-        let filterModels = new Array<FilterModel>();
-        return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, false);
-    }
-
-    GetAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) {
+    private getAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) {
         let allAttributes = new Array<AttributeTransformationModel>(histoX, histoY, histoValue);
         allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None));
 
@@ -108,11 +92,9 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         return [perBinAggregateParameters, globalAggregateParameters];
     }
 
-    public QRange: QuantitativeBinRange | undefined;
-
     public CreateOperationParameters(): HistogramOperationParameters | undefined {
         if (this.X && this.Y && this.V) {
-            let [perBinAggregateParameters, globalAggregateParameters] = this.GetAggregateParameters(this.X, this.Y, this.V);
+            let [perBinAggregateParameters, globalAggregateParameters] = this.getAggregateParameters(this.X, this.Y, this.V);
             return new HistogramOperationParameters({
                 enableBrushComputation: true,
                 adapterName: this.SchemaName,
@@ -131,9 +113,6 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
             });
         }
     }
-    get_random_color(): number {
-        return (Math.floor(Math.random() * 256) << 16) + (Math.floor(Math.random() * 256) << 8) + (Math.floor(Math.random() * 256));
-    }
 
     @action
     public async Update(): Promise<void> {
-- 
cgit v1.2.3-70-g09d2

