From b8c0dc61d535caeb2142678dffea2a841776e3a8 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 13 Mar 2019 16:39:45 -0400
Subject: moved DocumentContents into DocumentContentsView file.

---
 src/client/views/nodes/DocumentContentsView.tsx | 35 +++++++++++++++++++++++++
 1 file changed, 35 insertions(+)
 create mode 100644 src/client/views/nodes/DocumentContentsView.tsx

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
new file mode 100644
index 000000000..55b4938a0
--- /dev/null
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -0,0 +1,35 @@
+import { Document } from "../../../fields/Document";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { ImageBox } from "../nodes/ImageBox";
+import { VideoBox } from "../nodes/VideoBox";
+import { AudioBox } from "../nodes/AudioBox";
+import { KeyValueBox } from "./KeyValueBox"
+import { WebBox } from "../nodes/WebBox";
+import { PDFBox } from "../nodes/PDFBox";
+import "./DocumentView.scss";
+import React = require("react");
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+
+interface JsxBindings {
+    Document: Document;
+    layout: string;
+    [prop: string]: any;
+}
+
+export class DocumentContentsView extends React.PureComponent<JsxBindings> {
+    render() {
+        return <JsxParser
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
+            bindings={this.props}
+            jsx={this.props.layout}
+            showWarnings={true}
+            onError={(test: any) => { console.log(test) }}
+        />
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 1bf3a5a0108704a0204cedcbad6e6d52d19cf982 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 17 Mar 2019 22:04:03 -0400
Subject: moved DocumentContents to DocumentContentsView

---
 src/client/views/InkingControl.tsx                 |  8 +++
 .../views/collections/CollectionFreeFormView.tsx   | 34 ++----------
 src/client/views/collections/MarqueeView.tsx       |  2 +-
 src/client/views/collections/PreviewCursor.tsx     | 10 ++--
 src/client/views/nodes/DocumentContentsView.tsx    | 60 +++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            | 55 ++------------------
 6 files changed, 62 insertions(+), 107 deletions(-)

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index fb75ef2a5..6616f68d8 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -9,6 +9,8 @@ import "./InkingCanvas.scss"
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
+import { SelectionManager } from "../util/SelectionManager";
+import { KeyStore } from "../../fields/KeyStore";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -34,6 +36,12 @@ export class InkingControl extends React.Component {
     @action
     switchColor = (color: ColorResult): void => {
         this._selectedColor = color.hex;
+        if (SelectionManager.SelectedDocuments().length == 1) {
+            var sdoc = SelectionManager.SelectedDocuments()[0];
+            if (sdoc.props.ContainingCollectionView && sdoc.props.ContainingCollectionView) {
+                sdoc.props.Document.SetText(KeyStore.BackgroundColor, color.hex);
+            }
+        }
     }
 
     @action
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 988c9941f..cca57912c 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -8,28 +8,16 @@ import { TextField } from "../../../fields/TextField";
 import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
-import { CollectionView } from "../collections/CollectionView";
 import { InkingCanvas } from "../InkingCanvas";
-import { AudioBox } from "../nodes/AudioBox";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentView, DocumentViewProps, DocumentContents } from "../nodes/DocumentView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { KeyValueBox } from "../nodes/KeyValueBox";
-import { PDFBox } from "../nodes/PDFBox";
-import { VideoBox } from "../nodes/VideoBox";
-import { WebBox } from "../nodes/WebBox";
+import { DocumentContentsView } from "../nodes/DocumentContentsView";
+import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import "./CollectionFreeFormView.scss";
 import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
 import { CollectionViewBase } from "./CollectionViewBase";
 import { MarqueeView } from "./MarqueeView";
 import { PreviewCursor } from "./PreviewCursor";
 import React = require("react");
-const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
 
 @observer
 export class CollectionFreeFormView extends CollectionViewBase {
@@ -283,28 +271,14 @@ export class CollectionFreeFormView extends CollectionViewBase {
     @computed
     get backgroundView() {
         return !this.backgroundLayout ? (null) :
-            (<DocumentContents {...this.getDocumentViewProps(this.props.Document)}
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
                 layoutKey={KeyStore.BackgroundLayout} isSelected={() => false} select={() => { }} />);
-        // (<JsxParser
-        //     components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-        //     bindings={this.props.bindings}
-        //     jsx={this.backgroundLayout}
-        //     showWarnings={true}
-        //     onError={(test: any) => console.log(test)}
-        // />);
     }
     @computed
     get overlayView() {
         return !this.overlayLayout ? (null) :
-            (<DocumentContents {...this.getDocumentViewProps(this.props.Document)}
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
                 layoutKey={KeyStore.OverlayLayout} isSelected={() => false} select={() => { }} />);
-        // (<JsxParser
-        //     components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-        //     bindings={this.props.bindings}
-        //     jsx={this.overlayLayout}
-        //     showWarnings={true}
-        //     onError={(test: any) => console.log(test)}
-        // />);
     }
 
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
index 65aaa837f..f5c83a934 100644
--- a/src/client/views/collections/MarqueeView.tsx
+++ b/src/client/views/collections/MarqueeView.tsx
@@ -121,7 +121,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
         let centerShiftY = 0 - (selRect.top + selRect.height / 2);
         let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-        if (ink && ink != FieldWaiting) {
+        if (ink && ink != FieldWaiting && ink.Data) {
             let idata = new Map();
             ink.Data.forEach((value: StrokeData, key: string, map: any) => {
                 let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx
index a1411250a..cbcfa568d 100644
--- a/src/client/views/collections/PreviewCursor.tsx
+++ b/src/client/views/collections/PreviewCursor.tsx
@@ -1,16 +1,12 @@
-import { trace } from "mobx";
-import "./PreviewCursor.scss";
-import React = require("react");
 import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
-import { FieldWaiting, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
+import { Opt } from "../../../fields/Field";
 import { Documents } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./PreviewCursor.scss";
+import React = require("react");
 
 
 export interface PreviewCursorProps {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 55b4938a0..ce72ab64b 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,33 +1,55 @@
-import { Document } from "../../../fields/Document";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import { FieldWaiting } from "../../../fields/Field";
+import { Key } from "../../../fields/Key";
+import { KeyStore } from "../../../fields/KeyStore";
+import { ListField } from "../../../fields/ListField";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { VideoBox } from "../nodes/VideoBox";
-import { AudioBox } from "../nodes/AudioBox";
-import { KeyValueBox } from "./KeyValueBox"
-import { WebBox } from "../nodes/WebBox";
-import { PDFBox } from "../nodes/PDFBox";
+import { CollectionView } from "../collections/CollectionView";
+import { AudioBox } from "./AudioBox";
+import { DocumentViewProps, JsxBindings } from "./DocumentView";
 import "./DocumentView.scss";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { ImageBox } from "./ImageBox";
+import { KeyValueBox } from "./KeyValueBox";
+import { PDFBox } from "./PDFBox";
+import { VideoBox } from "./VideoBox";
+import { WebBox } from "./WebBox";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
-interface JsxBindings {
-    Document: Document;
-    layout: string;
-    [prop: string]: any;
-}
 
-export class DocumentContentsView extends React.PureComponent<JsxBindings> {
+@observer
+export class DocumentContentsView extends React.Component<DocumentViewProps & {
+    isSelected: () => boolean,
+    select: (ctrl: boolean) => void,
+    layoutKey: Key
+}> {
+    @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<p>Error loading layout data</p>"); }
+    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
+    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
+
+    CreateBindings(): JsxBindings {
+        let bindings: JsxBindings = { ...this.props, };
+        for (const key of this.layoutKeys) {
+            bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
+        }
+        for (const key of this.layoutFields) {
+            let field = this.props.Document.Get(key);
+            bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+        }
+        return bindings;
+    }
+
     render() {
         return <JsxParser
             components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-            bindings={this.props}
-            jsx={this.props.layout}
+            bindings={this.CreateBindings()}
+            jsx={this.layout}
             showWarnings={true}
             onError={(test: any) => { console.log(test) }}
         />
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3e691ee6d..b36d47b8b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,12 @@
 import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Document } from "../../../fields/Document";
-import { Field, FieldWaiting, Opt } from "../../../fields/Field";
+import { Field, Opt } from "../../../fields/Field";
 import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
 import { TextField } from "../../../fields/TextField";
+import { Utils } from "../../../Utils";
 import { Documents } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
@@ -14,22 +15,9 @@ import { Transform } from "../../util/Transform";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionView, CollectionViewType } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
+import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
-import { DocumentContentsView } from "./DocumentContentsView";
-import { Utils } from "../../../Utils";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { ImageBox } from "./ImageBox";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
-import { WebBox } from "./WebBox";
-import { KeyValueBox } from "./KeyValueBox";
-import { PDFBox } from "./PDFBox";
-import { VideoBox } from "./VideoBox";
-import { AudioBox } from "./AudioBox";
-const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 
 export interface DocumentViewProps {
@@ -87,7 +75,7 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
     return args;
 }
 
-interface JsxBindings {
+export interface JsxBindings {
     Document: Document;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
@@ -96,40 +84,7 @@ interface JsxBindings {
     [prop: string]: any;
 }
 
-@observer
-export class DocumentContents extends React.Component<DocumentViewProps & {
-    isSelected: () => boolean,
-    select: (ctrl: boolean) => void,
-    layoutKey: Key
-}> {
-    @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<p>Error loading layout data</p>"); }
-    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
-    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
-    CreateBindings(): JsxBindings {
-        let bindings: JsxBindings = {
-            ...this.props,
-        };
-        for (const key of this.layoutKeys) {
-            bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
-        }
-        for (const key of this.layoutFields) {
-            let field = this.props.Document.Get(key);
-            bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
-        }
-        return bindings;
-    }
 
-    render() {
-        return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-            bindings={this.CreateBindings()}
-            jsx={this.layout}
-            showWarnings={true}
-            onError={(test: any) => { console.log(test) }}
-        />
-    }
-}
 
 @observer
 export class DocumentView extends React.Component<DocumentViewProps> {
@@ -377,7 +332,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                 onDrop={this.onDrop}
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown} >
-                <DocumentContents {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />
+                <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />
             </div>
         )
     }
-- 
cgit v1.2.3-70-g09d2


From a16e6592caafb601b59c3d9f7609e8c1af231eba Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 20 Mar 2019 18:00:39 -0400
Subject: initial

---
 package.json                                       |   2 +
 src/client/documents/Documents.ts                  |  14 +-
 src/client/northstar/core/BaseObject.ts            |  12 ++
 .../northstar/core/attribute/AttributeModel.ts     | 111 ++++++++++
 .../core/attribute/AttributeTransformationModel.ts |  47 ++++
 .../core/attribute/CalculatedAttributeModel.ts     |  42 ++++
 .../northstar/core/brusher/BrushLinkModel.ts       |  40 ++++
 .../northstar/core/brusher/IBaseBrushable.ts       |  13 ++
 src/client/northstar/core/brusher/IBaseBrusher.ts  |  11 +
 src/client/northstar/core/filter/FilterModel.ts    |  81 +++++++
 src/client/northstar/core/filter/FilterOperand.ts  |   5 +
 src/client/northstar/core/filter/FilterType.ts     |   6 +
 .../northstar/core/filter/IBaseFilterConsumer.ts   |  10 +
 .../northstar/core/filter/IBaseFilterProvider.ts   |   8 +
 .../northstar/core/filter/ValueComparision.ts      |  74 +++++++
 src/client/northstar/model/ModelExtensions.ts      |  48 +++++
 src/client/northstar/model/ModelHelpers.ts         | 215 +++++++++++++++++++
 .../model/binRanges/AlphabeticVisualBinRange.ts    |  52 +++++
 .../model/binRanges/DateTimeVisualBinRange.ts      | 105 +++++++++
 .../model/binRanges/NominalVisualBinRange.ts       |  52 +++++
 .../model/binRanges/QuantitativeVisualBinRange.ts  |  90 ++++++++
 .../northstar/model/binRanges/VisualBinRange.ts    |  36 ++++
 .../model/binRanges/VisualBinRangeHelper.ts        |  71 +++++++
 src/client/northstar/operations/BaseOperation.ts   | 174 +++++++++++++++
 .../northstar/operations/HistogramOperation.ts     | 111 ++++++++++
 src/client/northstar/utils/ArrayUtil.ts            |  90 ++++++++
 src/client/northstar/utils/Extensions.ts           |  20 ++
 src/client/northstar/utils/GeometryUtil.ts         | 129 +++++++++++
 src/client/northstar/utils/IDisposable.ts          |   3 +
 src/client/northstar/utils/IEquatable.ts           |   3 +
 src/client/northstar/utils/KeyCodes.ts             | 137 ++++++++++++
 src/client/northstar/utils/MathUtil.ts             | 236 +++++++++++++++++++++
 src/client/northstar/utils/PartialClass.ts         |   7 +
 src/client/northstar/utils/Utils.ts                |  75 +++++++
 src/client/views/Main.tsx                          |  47 +++-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/FieldView.tsx               |   7 +-
 src/client/views/nodes/HistogramBox.scss           |   8 +
 src/client/views/nodes/HistogramBox.tsx            |  67 ++++++
 src/fields/KeyStore.ts                             |   1 +
 40 files changed, 2250 insertions(+), 14 deletions(-)
 create mode 100644 src/client/northstar/core/BaseObject.ts
 create mode 100644 src/client/northstar/core/attribute/AttributeModel.ts
 create mode 100644 src/client/northstar/core/attribute/AttributeTransformationModel.ts
 create mode 100644 src/client/northstar/core/attribute/CalculatedAttributeModel.ts
 create mode 100644 src/client/northstar/core/brusher/BrushLinkModel.ts
 create mode 100644 src/client/northstar/core/brusher/IBaseBrushable.ts
 create mode 100644 src/client/northstar/core/brusher/IBaseBrusher.ts
 create mode 100644 src/client/northstar/core/filter/FilterModel.ts
 create mode 100644 src/client/northstar/core/filter/FilterOperand.ts
 create mode 100644 src/client/northstar/core/filter/FilterType.ts
 create mode 100644 src/client/northstar/core/filter/IBaseFilterConsumer.ts
 create mode 100644 src/client/northstar/core/filter/IBaseFilterProvider.ts
 create mode 100644 src/client/northstar/core/filter/ValueComparision.ts
 create mode 100644 src/client/northstar/model/ModelExtensions.ts
 create mode 100644 src/client/northstar/model/ModelHelpers.ts
 create mode 100644 src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/NominalVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/VisualBinRange.ts
 create mode 100644 src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
 create mode 100644 src/client/northstar/operations/BaseOperation.ts
 create mode 100644 src/client/northstar/operations/HistogramOperation.ts
 create mode 100644 src/client/northstar/utils/ArrayUtil.ts
 create mode 100644 src/client/northstar/utils/Extensions.ts
 create mode 100644 src/client/northstar/utils/GeometryUtil.ts
 create mode 100644 src/client/northstar/utils/IDisposable.ts
 create mode 100644 src/client/northstar/utils/IEquatable.ts
 create mode 100644 src/client/northstar/utils/KeyCodes.ts
 create mode 100644 src/client/northstar/utils/MathUtil.ts
 create mode 100644 src/client/northstar/utils/PartialClass.ts
 create mode 100644 src/client/northstar/utils/Utils.ts
 create mode 100644 src/client/views/nodes/HistogramBox.scss
 create mode 100644 src/client/views/nodes/HistogramBox.tsx

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/package.json b/package.json
index c87e31bc4..33333a402 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
     "@types/connect-flash": "0.0.34",
     "@types/cookie-parser": "^1.4.1",
     "@types/cookie-session": "^2.0.36",
+    "@types/d3-format": "^1.3.1",
     "@types/express": "^4.16.1",
     "@types/express-flash": "0.0.0",
     "@types/express-session": "^1.15.12",
@@ -93,6 +94,7 @@
     "cookie-parser": "^1.4.4",
     "cookie-session": "^2.0.0-beta.3",
     "crypto-browserify": "^3.11.0",
+    "d3-format": "^1.3.2",
     "express": "^4.16.4",
     "express-flash": "0.0.2",
     "express-session": "^1.15.6",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4ed1fc3ed..b1e1f8c7b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -22,6 +22,7 @@ import { KeyValueBox } from "../views/nodes/KeyValueBox";
 import { PDFBox } from "../views/nodes/PDFBox";
 import { VideoBox } from "../views/nodes/VideoBox";
 import { WebBox } from "../views/nodes/WebBox";
+import { HistogramBox } from "../views/nodes/HistogramBox";
 
 export interface DocumentOptions {
     x?: number;
@@ -44,6 +45,7 @@ export interface DocumentOptions {
 
 export namespace Documents {
     let textProto: Document;
+    let histoProto: Document;
     let imageProto: Document;
     let webProto: Document;
     let collProto: Document;
@@ -52,6 +54,7 @@ export namespace Documents {
     let audioProto: Document;
     let pdfProto: Document;
     const textProtoId = "textProto";
+    const histoProtoId = "histoProto";
     const pdfProtoId = "pdfProto";
     const imageProtoId = "imageProto";
     const webProtoId = "webProto";
@@ -62,9 +65,10 @@ export namespace Documents {
 
     export function initProtos(callback: () => void) {
         Server.GetFields([collProtoId, textProtoId, imageProtoId], (fields) => {
+            textProto = fields[textProtoId] as Document;
+            histoProto = fields[histoProtoId] as Document;
             collProto = fields[collProtoId] as Document;
             imageProto = fields[imageProtoId] as Document;
-            textProto = fields[textProtoId] as Document;
             webProto = fields[webProtoId] as Document;
             kvpProto = fields[kvpProtoId] as Document;
             callback();
@@ -114,6 +118,11 @@ export namespace Documents {
         }
         return imageProto;
     }
+    function GetHistogramPrototype(): Document {
+        return histoProto ? histoProto :
+            histoProto = setupPrototypeOptions(textProtoId, "HISTO PROTOTYPE", HistogramBox.LayoutString(),
+                { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+    }
     function GetTextPrototype(): Document {
         return textProto ? textProto :
             textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
@@ -176,6 +185,9 @@ export namespace Documents {
         return assignToDelegate(SetInstanceOptions(GetAudioPrototype(), options, [new URL(url), AudioField]), options);
     }
 
+    export function HistogramDocument(options: DocumentOptions = {}) {
+        return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, ["", TextField]).MakeDelegate(), options);
+    }
     export function TextDocument(options: DocumentOptions = {}) {
         return assignToDelegate(SetInstanceOptions(GetTextPrototype(), options, ["", TextField]).MakeDelegate(), options);
     }
diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
new file mode 100644
index 000000000..e9e766e31
--- /dev/null
+++ b/src/client/northstar/core/BaseObject.ts
@@ -0,0 +1,12 @@
+import { IEquatable } from '../utils/IEquatable'
+import { IDisposable } from '../utils/IDisposable'
+
+export class BaseObject implements IEquatable, IDisposable {
+
+    public Equals(other: Object): boolean {
+        return this == other;
+    }
+
+    public Dispose(): void {
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts
new file mode 100644
index 000000000..124a5b45a
--- /dev/null
+++ b/src/client/northstar/core/attribute/AttributeModel.ts
@@ -0,0 +1,111 @@
+import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea'
+import { BaseObject } from '../BaseObject'
+import { observable } from "mobx";
+
+export abstract class AttributeModel extends BaseObject {
+    public abstract get DisplayName(): string;
+    public abstract get CodeName(): string;
+    public abstract get DataType(): DataType;
+    public abstract get VisualizationHints(): VisualizationHint[];
+}
+
+export class ColumnAttributeModel extends AttributeModel {
+    public Attribute: Attribute;
+
+    constructor(attribute: Attribute) {
+        super();
+        this.Attribute = attribute;
+    }
+
+    public get DataType(): DataType {
+        return this.Attribute.dataType ? this.Attribute.dataType : DataType.Undefined;
+    }
+
+    public get DisplayName(): string {
+        return this.Attribute.displayName ? this.Attribute.displayName.ReplaceAll("_", " ") : "";
+    }
+
+    public get CodeName(): string {
+        return this.Attribute.rawName ? this.Attribute.rawName : "";
+    }
+
+    public get VisualizationHints(): VisualizationHint[] {
+        return this.Attribute.visualizationHints ? this.Attribute.visualizationHints : [];
+    }
+
+    public Equals(other: ColumnAttributeModel): boolean {
+        return this.Attribute.rawName == other.Attribute.rawName;
+    }
+}
+
+export class CodeAttributeModel extends AttributeModel {
+    private _visualizationHints: VisualizationHint[];
+
+    public CodeName: string;
+
+    @observable
+    public Code: string;
+
+    constructor(code: string, codeName: string, displayName: string, visualizationHints: VisualizationHint[]) {
+        super();
+        this.Code = code;
+        this.CodeName = codeName;
+        this.DisplayName = displayName;
+        this._visualizationHints = visualizationHints;
+    }
+
+    public get DataType(): DataType {
+        return DataType.Undefined;
+    }
+
+    @observable
+    public DisplayName: string;
+
+    public get VisualizationHints(): VisualizationHint[] {
+        return this._visualizationHints;
+    }
+
+    public Equals(other: CodeAttributeModel): boolean {
+        return this.CodeName === other.CodeName;
+    }
+
+}
+
+export class BackendAttributeModel extends AttributeModel {
+    private _dataType: DataType;
+    private _displayName: string;
+    private _codeName: string;
+    private _visualizationHints: VisualizationHint[];
+
+    public Id: string;
+
+    constructor(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]) {
+        super();
+        this.Id = id;
+        this._dataType = dataType;
+        this._displayName = displayName;
+        this._codeName = codeName;
+        this._visualizationHints = visualizationHints;
+    }
+
+    public get DataType(): DataType {
+        return this._dataType;
+    }
+
+    public get DisplayName(): string {
+        return this._displayName.ReplaceAll("_", " ");;
+    }
+
+    public get CodeName(): string {
+        return this._codeName;
+    }
+
+    public get VisualizationHints(): VisualizationHint[] {
+        return this._visualizationHints;
+    }
+
+    public Equals(other: BackendAttributeModel): boolean {
+        return this.Id == other.Id;
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/attribute/AttributeTransformationModel.ts b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
new file mode 100644
index 000000000..cc5aa7154
--- /dev/null
+++ b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
@@ -0,0 +1,47 @@
+;
+import { computed, observable } from "mobx";
+import { AggregateFunction } from "../../model/idea/idea";
+import { AttributeModel } from "./AttributeModel";
+import { IEquatable } from "../../utils/IEquatable";
+
+export class AttributeTransformationModel implements IEquatable {
+
+    @observable public AggregateFunction: AggregateFunction;
+    @observable public AttributeModel: AttributeModel;
+
+    constructor(attributeModel: AttributeModel, aggregateFunction: AggregateFunction = AggregateFunction.None) {
+        this.AttributeModel = attributeModel;
+        this.AggregateFunction = aggregateFunction;
+    }
+
+    @computed
+    public get PresentedName(): string {
+        var displayName = this.AttributeModel.DisplayName;
+        if (this.AggregateFunction === AggregateFunction.Count) {
+            return "count";
+        }
+        if (this.AggregateFunction === AggregateFunction.Avg)
+            displayName = "avg(" + displayName + ")";
+        else if (this.AggregateFunction === AggregateFunction.Max)
+            displayName = "max(" + displayName + ")";
+        else if (this.AggregateFunction === AggregateFunction.Min)
+            displayName = "min(" + displayName + ")";
+        else if (this.AggregateFunction === AggregateFunction.Sum)
+            displayName = "sum(" + displayName + ")";
+        else if (this.AggregateFunction === AggregateFunction.SumE)
+            displayName = "sumE(" + displayName + ")";
+
+        return displayName;
+    }
+
+    public clone(): AttributeTransformationModel {
+        var clone = new AttributeTransformationModel(this.AttributeModel);
+        clone.AggregateFunction = this.AggregateFunction;
+        return clone;
+    }
+
+    public Equals(other: AttributeTransformationModel): boolean {
+        return this.AggregateFunction == other.AggregateFunction &&
+            this.AttributeModel.Equals(other.AttributeModel);
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
new file mode 100644
index 000000000..ab96c794d
--- /dev/null
+++ b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
@@ -0,0 +1,42 @@
+import { BackendAttributeModel, AttributeModel, CodeAttributeModel } from "./AttributeModel";
+import { DataType, VisualizationHint } from '../../model/idea/idea'
+
+export class CalculatedAttributeManager {
+    public static AllCalculatedAttributes: Array<AttributeModel> = new Array<AttributeModel>();
+
+    public static Clear() {
+        this.AllCalculatedAttributes = new Array<AttributeModel>();
+    }
+
+    public static CreateBackendAttributeModel(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]): BackendAttributeModel {
+        var filtered = this.AllCalculatedAttributes.filter(am => {
+            if (am instanceof BackendAttributeModel &&
+                am.Id == id) {
+                return true;
+            }
+            return false;
+        });
+        if (filtered.length > 0) {
+            return filtered[0] as BackendAttributeModel;
+        }
+        var newAttr = new BackendAttributeModel(id, dataType, displayName, codeName, visualizationHints);
+        this.AllCalculatedAttributes.push(newAttr);
+        return newAttr;
+    }
+
+    public static CreateCodeAttributeModel(code: string, codeName: string, visualizationHints: VisualizationHint[]): CodeAttributeModel {
+        var filtered = this.AllCalculatedAttributes.filter(am => {
+            if (am instanceof CodeAttributeModel &&
+                am.CodeName == codeName) {
+                return true;
+            }
+            return false;
+        });
+        if (filtered.length > 0) {
+            return filtered[0] as CodeAttributeModel;
+        }
+        var newAttr = new CodeAttributeModel(code, codeName, codeName.ReplaceAll("_", " "), visualizationHints);
+        this.AllCalculatedAttributes.push(newAttr);
+        return newAttr;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/brusher/BrushLinkModel.ts b/src/client/northstar/core/brusher/BrushLinkModel.ts
new file mode 100644
index 000000000..e3ac43367
--- /dev/null
+++ b/src/client/northstar/core/brusher/BrushLinkModel.ts
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 000000000..07d4e7580
--- /dev/null
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -0,0 +1,13 @@
+import { BrushLinkModel } from '../brusher/BrushLinkModel'
+import { PIXIPoint } from '../../utils/MathUtil'
+import { IEquatable } from '../../utils/IEquatable';
+
+export interface IBaseBrushable<T> extends IEquatable {
+    BrusherModels: Array<BrushLinkModel<T>>;
+    BrushColors: Array<number>;
+    Position: PIXIPoint;
+    Size: PIXIPoint;
+}
+export function instanceOfIBaseBrushable<T>(object: any): object is IBaseBrushable<T> {
+    return 'BrusherModels' in object;
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrusher.ts b/src/client/northstar/core/brusher/IBaseBrusher.ts
new file mode 100644
index 000000000..d7ae65464
--- /dev/null
+++ b/src/client/northstar/core/brusher/IBaseBrusher.ts
@@ -0,0 +1,11 @@
+import { PIXIPoint } from '../../utils/MathUtil'
+import { IEquatable } from '../../utils/IEquatable';
+
+
+export interface IBaseBrusher<T> extends IEquatable {
+    Position: PIXIPoint;
+    Size: PIXIPoint;
+}
+export function instanceOfIBaseBrusher<T>(object: any): object is IBaseBrusher<T> {
+    return 'BrushableModels' in object;
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
new file mode 100644
index 000000000..3c4cfc4a7
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -0,0 +1,81 @@
+import { ValueComparison } from "./ValueComparision";
+import { Utils } from "../../utils/Utils";
+
+export class FilterModel {
+    public ValueComparisons: ValueComparison[];
+    constructor() {
+        this.ValueComparisons = new Array<ValueComparison>();
+    }
+
+    public Equals(other: FilterModel): boolean {
+        if (!Utils.EqualityHelper(this, other)) return false;
+        if (!this.isSame(this.ValueComparisons, (other as FilterModel).ValueComparisons)) return false;
+        return true;
+    }
+
+    private isSame(a: ValueComparison[], b: ValueComparison[]): boolean {
+        if (a.length !== b.length) {
+            return false;
+        }
+        for (let i = 0; i < a.length; i++) {
+            let valueComp = a[i];
+            if (!valueComp.Equals(b[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public ToPythonString(): string {
+        let ret = "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
+        return ret;
+    }
+
+    public static And(filters: string[]): string {
+        let ret = filters.filter(f => f !== "").join(" && ");
+        return ret;
+    }
+
+    // public static GetFilterModelsRecursive(filterGraphNode: GraphNode<BaseOperationViewModel, FilterLinkViewModel>,
+    //     visitedFilterProviders: Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>, filterModels: FilterModel[], isFirst: boolean): string {
+    //     let ret = "";
+    //     if (Utils.isBaseFilterProvider(filterGraphNode.Data)) {
+    //         visitedFilterProviders.add(filterGraphNode);
+    //         let filtered = filterGraphNode.Data.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0);
+    //         if (!isFirst && filtered.length > 0) {
+    //             filterModels.push(...filtered);
+    //             ret = "(" + filterGraphNode.Data.FilterModels.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+    //         }
+    //     }
+    //     if (Utils.isBaseFilterConsumer(filterGraphNode.Data) && filterGraphNode.Links != null) {
+    //         let children = new Array<string>();
+    //         let linkedGraphNodes = filterGraphNode.Links.get(LinkType.Filter);
+    //         if (linkedGraphNodes != null) {
+    //             for (let i = 0; i < linkedGraphNodes.length; i++) {
+    //                 let linkVm = linkedGraphNodes[i].Data;
+    //                 let linkedGraphNode = linkedGraphNodes[i].Target;
+    //                 if (!visitedFilterProviders.has(linkedGraphNode)) {
+    //                     let child = FilterModel.GetFilterModelsRecursive(linkedGraphNode, visitedFilterProviders, filterModels, false);
+    //                     if (child !== "") {
+    //                         if (linkVm.IsInverted) {
+    //                             child = "! " + child;
+    //                         }
+    //                         children.push(child);
+    //                     }
+    //                 }
+    //             }
+    //         }
+
+    //         let childrenJoined = children.join(filterGraphNode.Data.FilterOperand === FilterOperand.AND ? " && " : " || ");
+    //         if (children.length > 0) {
+    //             if (ret !== "") {
+    //                 ret = "(" + ret + " &&  (" + childrenJoined + "))";
+    //             }
+    //             else {
+    //                 ret = "(" + childrenJoined + ")";
+    //             }
+    //         }
+    //     }
+    //     return ret;
+    // }
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterOperand.ts b/src/client/northstar/core/filter/FilterOperand.ts
new file mode 100644
index 000000000..2e8e8d6a0
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterOperand.ts
@@ -0,0 +1,5 @@
+export enum FilterOperand
+{
+    AND,
+    OR
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterType.ts b/src/client/northstar/core/filter/FilterType.ts
new file mode 100644
index 000000000..9adbc087f
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterType.ts
@@ -0,0 +1,6 @@
+export enum FilterType
+{
+    Filter,
+    Brush,
+    Slice
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
new file mode 100644
index 000000000..e687acb8a
--- /dev/null
+++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
@@ -0,0 +1,10 @@
+import { FilterOperand } from '../filter/FilterOperand'
+import { IEquatable } from '../../utils/IEquatable'
+
+export interface IBaseFilterConsumer extends IEquatable {
+    FilterOperand: FilterOperand;
+}
+
+export function instanceOfIBaseFilterConsumer(object: any): object is IBaseFilterConsumer {
+    return 'FilterOperand' in object;
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/IBaseFilterProvider.ts b/src/client/northstar/core/filter/IBaseFilterProvider.ts
new file mode 100644
index 000000000..d082bfe12
--- /dev/null
+++ b/src/client/northstar/core/filter/IBaseFilterProvider.ts
@@ -0,0 +1,8 @@
+import { FilterModel } from '../filter/FilterModel'
+
+export interface IBaseFilterProvider {
+    FilterModels: Array<FilterModel>;
+}
+export function instanceOfIBaseFilterProvider(object: any): object is IBaseFilterProvider {
+    return 'FilterModels' in object;
+}
\ No newline at end of file
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
new file mode 100644
index 000000000..1e729d06e
--- /dev/null
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -0,0 +1,74 @@
+import { Predicate } from '../../model/idea/idea'
+import { Utils } from '../../utils/Utils'
+import { AttributeModel } from '../attribute/AttributeModel';
+
+export class ValueComparison {
+
+    public attributeModel: AttributeModel;
+    public Value: any;
+    public Predicate: Predicate;
+
+    public constructor(attributeModel: AttributeModel, predicate: Predicate, value: any) {
+        this.attributeModel = attributeModel;
+        this.Value = value;
+        this.Predicate = predicate;
+    }
+
+    public Equals(other: Object): boolean {
+        if (!Utils.EqualityHelper(this, other))
+            return false;
+        if (this.Predicate !== (other as ValueComparison).Predicate)
+            return false;
+        let isComplex = (typeof this.Value === "object");
+        if (!isComplex && this.Value != (other as ValueComparison).Value)
+            return false;
+        if (isComplex && !this.Value.Equals((other as ValueComparison).Value))
+            return false;
+        return true;
+    }
+
+    public ToPythonString(): string {
+        var op = "";
+        switch (this.Predicate) {
+            case Predicate.EQUALS:
+                op = "==";
+                break;
+            case Predicate.GREATER_THAN:
+                op = ">";
+                break;
+            case Predicate.GREATER_THAN_EQUAL:
+                op = ">=";
+                break;
+            case Predicate.LESS_THAN:
+                op = "<";
+                break;
+            case Predicate.LESS_THAN_EQUAL:
+                op = "<=";
+                break;
+            default:
+                op = "==";
+                break;
+        }
+
+        var val = this.Value.toString();
+        if (typeof this.Value === 'string' || this.Value instanceof String) {
+            val = "\"" + val + "\"";
+        }
+        var ret = " ";
+        var rawName = this.attributeModel.CodeName;
+        switch (this.Predicate) {
+            case Predicate.STARTS_WITH:
+                ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") ";
+                return ret;
+            case Predicate.ENDS_WITH:
+                ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") ";
+                return ret;
+            case Predicate.CONTAINS:
+                ret += rawName + " != null && " + rawName + ".Contains(" + val + ") ";
+                return ret;
+            default:
+                ret += rawName + " " + op + " " + val + " ";
+                return ret;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
new file mode 100644
index 000000000..9fcba7f1c
--- /dev/null
+++ b/src/client/northstar/model/ModelExtensions.ts
@@ -0,0 +1,48 @@
+import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea'
+import { Utils } from '../utils/Utils'
+
+import { FilterModel } from '../core/filter/FilterModel'
+
+(SingleDimensionAggregateParameters as any).prototype["Equals"] = function (other: Object) {
+    if (!Utils.EqualityHelper(this, other)) return false;
+    if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
+        (other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
+    if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any)["Equals"]((other as SingleDimensionAggregateParameters).attributeParameters)) return false;
+    return true;
+}
+
+{
+    (AttributeParameters as any).prototype["Equals"] = function (other: AttributeParameters) {
+        return (<any>this).constructor.name === (<any>other).constructor.name &&
+            this.rawName === other.rawName;
+    }
+}
+
+{
+    (Solution as any).prototype["Equals"] = function (other: Object) {
+        if (!Utils.EqualityHelper(this, other)) return false;
+        if ((this as Solution).solutionId !== (other as Solution).solutionId) return false;
+        return true;
+    }
+}
+
+{
+    (MarginAggregateParameters as any).prototype["Equals"] = function (other: Object) {
+        if (!Utils.EqualityHelper(this, other)) return false;
+        if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
+            (other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
+        if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any)["Equals"]((other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
+
+        if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false;
+        return true;
+    }
+}
+
+{
+    (Brush as any).prototype["Equals"] = function (other: Object) {
+        if (!Utils.EqualityHelper(this, other)) return false;
+        if ((this as Brush).brushEnum !== (other as Brush).brushEnum) return false;
+        if ((this as Brush).brushIndex !== (other as Brush).brushIndex) return false;
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
new file mode 100644
index 000000000..e1241b3ef
--- /dev/null
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -0,0 +1,215 @@
+
+import { action } from "mobx";
+import { AggregateFunction, AggregateKey, AggregateParameters, AttributeColumnParameters, AttributeParameters, AverageAggregateParameters, Bin, BinningParameters, Brush, BrushEnum, CountAggregateParameters, DataType, EquiWidthBinningParameters, HistogramResult, MarginAggregateParameters, SingleBinBinningParameters, SingleDimensionAggregateParameters, SumAggregateParameters, AggregateBinRange, NominalBinRange, AlphabeticBinRange, Predicate, Schema, Attribute, AttributeGroup, Exception, AttributeBackendParameters, AttributeCodeParameters } from '../model/idea/idea';
+import { ValueComparison } from "../core/filter/ValueComparision";
+import { ArrayUtil } from "../utils/ArrayUtil";
+import { AttributeModel, ColumnAttributeModel, BackendAttributeModel, CodeAttributeModel } from "../core/attribute/AttributeModel";
+import { FilterModel } from "../core/filter/FilterModel";
+import { AlphabeticVisualBinRange } from "./binRanges/AlphabeticVisualBinRange";
+import { NominalVisualBinRange } from "./binRanges/NominalVisualBinRange";
+import { VisualBinRangeHelper } from "./binRanges/VisualBinRangeHelper";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
+import { Main } from "../../views/Main";
+
+export class ModelHelpers {
+
+    public static CreateAggregateKey(atm: AttributeTransformationModel, histogramResult: HistogramResult,
+        brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
+        {
+            if (aggParameters == undefined) {
+                aggParameters = ModelHelpers.GetAggregateParameter(atm);
+            }
+            else {
+                aggParameters.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+            }
+            return new AggregateKey(
+                {
+                    aggregateParameterIndex: ModelHelpers.GetAggregateParametersIndex(histogramResult, aggParameters),
+                    brushIndex: brushIndex
+                });
+        }
+    }
+
+    public static GetAggregateParametersIndex(histogramResult: HistogramResult, aggParameters?: AggregateParameters): number {
+        return ArrayUtil.IndexOfWithEqual(histogramResult.aggregateParameters!, aggParameters);
+    }
+
+    public static GetAggregateParameter(atm: AttributeTransformationModel): AggregateParameters | undefined {
+        var aggParam: AggregateParameters | undefined;
+        if (atm.AggregateFunction === AggregateFunction.Avg) {
+            var avg = new AverageAggregateParameters();
+            avg.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+            avg.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+            aggParam = avg;
+        }
+        else if (atm.AggregateFunction === AggregateFunction.Count) {
+            var cnt = new CountAggregateParameters();
+            cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+            cnt.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+            aggParam = cnt;
+        }
+        else if (atm.AggregateFunction === AggregateFunction.Sum) {
+            var sum = new SumAggregateParameters();
+            sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+            sum.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+            aggParam = sum;
+        }
+        return aggParam;
+    }
+
+    public static GetAggregateParametersWithMargins(atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
+        var aggregateParameters = new Array<AggregateParameters>();
+        atms.forEach(agg => {
+            var aggParams = ModelHelpers.GetAggregateParameter(agg);
+            if (aggParams) {
+                aggregateParameters.push(aggParams);
+
+                var margin = new MarginAggregateParameters();
+                margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
+                margin.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters;
+                margin.aggregateFunction = agg.AggregateFunction;
+                aggregateParameters.push(margin);
+            }
+        });
+
+        return aggregateParameters;
+    }
+
+    public static GetBinningParameters(attr: AttributeTransformationModel, nrOfBins: number, minvalue?: number, maxvalue?: number): BinningParameters {
+        if (attr.AggregateFunction === AggregateFunction.None) {
+            return new EquiWidthBinningParameters(
+                {
+                    attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel),
+                    requestedNrOfBins: nrOfBins,
+                    minValue: minvalue,
+                    maxValue: maxvalue
+                });
+        }
+        else {
+            return new SingleBinBinningParameters(
+                {
+                    attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel)
+                });
+        }
+    }
+
+    public static GetAttributeParametersFromAttributeModel(am: AttributeModel): AttributeParameters {
+        if (am instanceof ColumnAttributeModel) {
+            return new AttributeColumnParameters(
+                {
+                    rawName: am.CodeName,
+                    visualizationHints: am.VisualizationHints
+                });
+        }
+        else if (am instanceof BackendAttributeModel) {
+            return new AttributeBackendParameters(
+                {
+                    rawName: am.CodeName,
+                    visualizationHints: am.VisualizationHints,
+                    id: (am as BackendAttributeModel).Id
+                });
+        }
+        else if (am instanceof CodeAttributeModel) {
+            return new AttributeCodeParameters(
+                {
+                    rawName: am.CodeName,
+                    visualizationHints: am.VisualizationHints,
+                    code: (am as CodeAttributeModel).Code
+                });
+        }
+        else {
+            throw new Exception()
+        }
+    }
+
+    public static GetAttributeParameters(am: AttributeModel): AttributeParameters {
+        return this.GetAttributeParametersFromAttributeModel(am);
+    }
+
+    public static OverlapBrushIndex(histogramResult: HistogramResult): number {
+        var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.Overlap);
+        return ModelHelpers.GetBrushIndex(histogramResult, brush);
+    }
+
+    public static AllBrushIndex(histogramResult: HistogramResult): number {
+        var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.All);
+        return ModelHelpers.GetBrushIndex(histogramResult, brush);
+    }
+
+    public static RestBrushIndex(histogramResult: HistogramResult): number {
+        var brush = ArrayUtil.First(histogramResult.brushes!, (b: Brush) => b.brushEnum === BrushEnum.Rest);
+        return ModelHelpers.GetBrushIndex(histogramResult, brush);
+    }
+
+    public static GetBrushIndex(histogramResult: HistogramResult, brush: Brush): number {
+        return ArrayUtil.IndexOfWithEqual(histogramResult.brushes!, brush);
+    }
+
+    public static GetAggregateResult(bin: Bin, aggregateKey: AggregateKey) {
+        if (aggregateKey.aggregateParameterIndex == -1 || aggregateKey.brushIndex == -1) {
+            return null;
+        }
+        return bin.aggregateResults![aggregateKey.aggregateParameterIndex! * bin.ySize! + aggregateKey.brushIndex!];
+    }
+
+    @action
+    public static PossibleAggegationFunctions(atm: AttributeTransformationModel): Array<AggregateFunction> {
+        var ret = new Array<AggregateFunction>();
+        ret.push(AggregateFunction.None);
+        ret.push(AggregateFunction.Count);
+        if (atm.AttributeModel.DataType == DataType.Float ||
+            atm.AttributeModel.DataType == DataType.Double ||
+            atm.AttributeModel.DataType == DataType.Int) {
+            ret.push(AggregateFunction.Avg);
+            ret.push(AggregateFunction.Sum);
+        }
+        return ret;
+    }
+
+    public static GetBinFilterModel(
+        bin: Bin, brushIndex: number, histogramResult: HistogramResult,
+        xAom: AttributeTransformationModel, yAom: AttributeTransformationModel): FilterModel {
+        var dimensions: Array<AttributeTransformationModel> = [xAom, yAom];
+        var filterModel = new FilterModel();
+
+        for (var i = 0; i < histogramResult.binRanges!.length; i++) {
+            if (!(histogramResult.binRanges![i] instanceof AggregateBinRange)) {
+                var binRange = VisualBinRangeHelper.GetNonAggregateVisualBinRange(histogramResult.binRanges![i]);
+                var dataFrom = binRange.GetValueFromIndex(bin.binIndex!.indices![i]);
+                var dataTo = binRange.AddStep(dataFrom);
+
+                if (binRange instanceof NominalVisualBinRange) {
+                    var tt = binRange.GetLabel(dataFrom);
+                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.EQUALS, tt));
+                }
+                else if (binRange instanceof AlphabeticVisualBinRange) {
+                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.STARTS_WITH,
+                        binRange.GetLabel(dataFrom)));
+                }
+                else {
+                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.GREATER_THAN_EQUAL, dataFrom));
+                    filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.LESS_THAN, dataTo));
+                }
+            }
+        }
+
+        return filterModel;
+    }
+
+    public GetAllAttributes(schema: Schema) {
+        if (!schema || !schema.rootAttributeGroup) {
+            return [];
+        }
+        const recurs = (attrs: Attribute[], g: AttributeGroup) => {
+            if (g.attributes) {
+                attrs.push.apply(attrs, g.attributes);
+                if (g.attributeGroups) {
+                    g.attributeGroups.forEach(ng => recurs(attrs, ng));
+                }
+            }
+        };
+        const allAttributes: Attribute[] = new Array<Attribute>();
+        recurs(allAttributes, schema.rootAttributeGroup);
+        return allAttributes;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
new file mode 100644
index 000000000..995bf4e0b
--- /dev/null
+++ b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
@@ -0,0 +1,52 @@
+import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea'
+import { VisualBinRange } from './VisualBinRange'
+
+export class AlphabeticVisualBinRange extends VisualBinRange {
+    public DataBinRange: AlphabeticBinRange;
+
+    constructor(dataBinRange: AlphabeticBinRange) {
+        super();
+        this.DataBinRange = dataBinRange;
+    }
+
+    public AddStep(value: number): number {
+        return value + 1;
+    }
+
+    public GetValueFromIndex(index: number): number {
+        return index;
+    }
+
+    public GetBins(): number[] {
+        var bins = new Array<number>();
+        var idx = 0;
+        for (var key in this.DataBinRange.labelsValue) {
+            if (this.DataBinRange.labelsValue.hasOwnProperty(key)) {
+                bins.push(idx);
+                idx++;
+            }
+        }
+        return bins;
+    }
+
+    public GetLabel(value: number): string {
+        return this.DataBinRange.prefix + this.DataBinRange.valuesLabel![value];
+    }
+
+    public GetLabels(): Array<BinLabel> {
+        var labels = new Array<BinLabel>();
+        var count = 0;
+        for (var key in this.DataBinRange.valuesLabel) {
+            if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) {
+                var value = this.DataBinRange.valuesLabel[key];
+                labels.push(new BinLabel({
+                    value: parseFloat(key),
+                    minValue: count++,
+                    maxValue: count,
+                    label: this.DataBinRange.prefix + value
+                }));
+            }
+        }
+        return labels;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
new file mode 100644
index 000000000..9313fb1a7
--- /dev/null
+++ b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
@@ -0,0 +1,105 @@
+import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea'
+import { VisualBinRange } from './VisualBinRange'
+
+export class DateTimeVisualBinRange extends VisualBinRange {
+    public DataBinRange: DateTimeBinRange;
+
+    constructor(dataBinRange: DateTimeBinRange) {
+        super();
+        this.DataBinRange = dataBinRange;
+    }
+
+    public AddStep(value: number): number {
+        return DateTimeVisualBinRange.AddToDateTimeTicks(value, this.DataBinRange.step!);
+    }
+
+    public GetValueFromIndex(index: number): number {
+        var v = this.DataBinRange.minValue!;
+        for (var i = 0; i < index; i++) {
+            v = this.AddStep(v);
+        }
+        return v;
+    }
+
+    public GetBins(): number[] {
+        var bins = new Array<number>();
+        for (var v: number = this.DataBinRange.minValue!;
+            v < this.DataBinRange.maxValue!;
+            v = DateTimeVisualBinRange.AddToDateTimeTicks(v, this.DataBinRange.step!)) {
+            bins.push(v);
+        }
+        return bins;
+    }
+
+    private pad(n: number, size: number) {
+        var sign = n < 0 ? '-' : '';
+        return sign + new Array(size).concat([Math.abs(n)]).join('0').slice(-size);
+    }
+
+
+    public GetLabel(value: number): string {
+        var dt = DateTimeVisualBinRange.TicksToDate(value);
+        if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Second ||
+            this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Minute) {
+            return ("" + this.pad(dt.getMinutes(), 2) + ":" + this.pad(dt.getSeconds(), 2));
+            //return dt.ToString("mm:ss");
+        }
+        else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Hour) {
+            return (this.pad(dt.getHours(), 2) + ":" + this.pad(dt.getMinutes(), 2));
+            //return dt.ToString("HH:mm");
+        }
+        else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Day) {
+            return ((dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear());
+            //return dt.ToString("MM/dd/yyyy");
+        }
+        else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Month) {
+            //return dt.ToString("MM/yyyy");
+            return ((dt.getMonth() + 1) + "/" + dt.getFullYear());
+        }
+        else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Year) {
+            return "" + dt.getFullYear();
+        }
+        return "n/a";
+    }
+
+    public static TicksToDate(ticks: number): Date {
+        var dd = new Date((ticks - 621355968000000000) / 10000);
+        dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset());
+        return dd;
+    }
+
+
+    public static DateToTicks(date: Date): number {
+        var copiedDate = new Date(date.getTime());
+        copiedDate.setMinutes(copiedDate.getMinutes() - copiedDate.getTimezoneOffset());
+        var t = copiedDate.getTime() * 10000 + 621355968000000000;
+        /*var dd = new Date((ticks - 621355968000000000) / 10000);
+        dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset());
+        return dd;*/
+        return t;
+    }
+
+    public static AddToDateTimeTicks(ticks: number, dateTimeStep: DateTimeStep): number {
+        var copiedDate = DateTimeVisualBinRange.TicksToDate(ticks);
+        var returnDate: Date = new Date(Date.now());
+        if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Second) {
+            returnDate = new Date(copiedDate.setSeconds(copiedDate.getSeconds() + dateTimeStep.dateTimeStepValue!));
+        }
+        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Minute) {
+            returnDate = new Date(copiedDate.setMinutes(copiedDate.getMinutes() + dateTimeStep.dateTimeStepValue!));
+        }
+        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Hour) {
+            returnDate = new Date(copiedDate.setHours(copiedDate.getHours() + dateTimeStep.dateTimeStepValue!));
+        }
+        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Day) {
+            returnDate = new Date(copiedDate.setDate(copiedDate.getDate() + dateTimeStep.dateTimeStepValue!));
+        }
+        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Month) {
+            returnDate = new Date(copiedDate.setMonth(copiedDate.getMonth() + dateTimeStep.dateTimeStepValue!));
+        }
+        else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Year) {
+            returnDate = new Date(copiedDate.setFullYear(copiedDate.getFullYear() + dateTimeStep.dateTimeStepValue!));
+        }
+        return DateTimeVisualBinRange.DateToTicks(returnDate);
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
new file mode 100644
index 000000000..407ff3ea6
--- /dev/null
+++ b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
@@ -0,0 +1,52 @@
+import { NominalBinRange, BinLabel } from '../../model/idea/idea'
+import { VisualBinRange } from './VisualBinRange'
+
+export class NominalVisualBinRange extends VisualBinRange {
+    public DataBinRange: NominalBinRange;
+
+    constructor(dataBinRange: NominalBinRange) {
+        super();
+        this.DataBinRange = dataBinRange;
+    }
+
+    public AddStep(value: number): number {
+        return value + 1;
+    }
+
+    public GetValueFromIndex(index: number): number {
+        return index;
+    }
+
+    public GetBins(): number[] {
+        var bins = new Array<number>();
+        var idx = 0;
+        for (var key in this.DataBinRange.labelsValue) {
+            if (this.DataBinRange.labelsValue.hasOwnProperty(key)) {
+                bins.push(idx);
+                idx++;
+            }
+        }
+        return bins;
+    }
+
+    public GetLabel(value: number): string {
+        return this.DataBinRange.valuesLabel![value];
+    }
+
+    public GetLabels(): Array<BinLabel> {
+        var labels = new Array<BinLabel>();
+        var count = 0;
+        for (var key in this.DataBinRange.valuesLabel) {
+            if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) {
+                var value = this.DataBinRange.valuesLabel[key];
+                labels.push(new BinLabel({
+                    value: parseFloat(key),
+                    minValue: count++,
+                    maxValue: count,
+                    label: value
+                }));
+            }
+        }
+        return labels;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
new file mode 100644
index 000000000..80886416b
--- /dev/null
+++ b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
@@ -0,0 +1,90 @@
+import { QuantitativeBinRange } from '../idea/idea'
+import { VisualBinRange } from './VisualBinRange';
+import { format } from "d3-format";
+
+export class QuantitativeVisualBinRange extends VisualBinRange {
+
+    public DataBinRange: QuantitativeBinRange;
+
+    constructor(dataBinRange: QuantitativeBinRange) {
+        super();
+        this.DataBinRange = dataBinRange;
+    }
+
+    public AddStep(value: number): number {
+        return value + this.DataBinRange.step!;
+    }
+
+    public GetValueFromIndex(index: number): number {
+        return this.DataBinRange.minValue! + (index * this.DataBinRange.step!);
+    }
+
+    public GetLabel(value: number): string {
+        return QuantitativeVisualBinRange.NumberFormatter(value);
+    }
+
+    public static NumberFormatter(val: number): string {
+        if (val === 0) {
+            return "0";
+        }
+        if (val < 1) {
+            /*if (val < Math.abs(0.001))  {
+                return val.toExponential(2);
+            }*/
+            return format(".3")(val);
+        }
+        return format("~s")(val);
+    }
+
+    public GetBins(): number[] {
+        let bins = new Array<number>();
+
+        for (let v: number = this.DataBinRange.minValue!; v < this.DataBinRange.maxValue!; v += this.DataBinRange.step!) {
+            bins.push(v);
+        }
+        return bins;
+    }
+
+    public static Initialize(dataMinValue: number, dataMaxValue: number, targetBinNumber: number, isIntegerRange: boolean): QuantitativeVisualBinRange {
+        let extent = QuantitativeVisualBinRange.getExtent(dataMinValue, dataMaxValue, targetBinNumber, isIntegerRange);
+        let dataBinRange = new QuantitativeBinRange();
+        dataBinRange.minValue = extent[0];
+        dataBinRange.maxValue = extent[1];
+        dataBinRange.step = extent[2];
+
+        return new QuantitativeVisualBinRange(dataBinRange);
+    }
+
+    private static getExtent(dataMin: number, dataMax: number, m: number, isIntegerRange: boolean): number[] {
+        if (dataMin === dataMax) {
+            // dataMin -= 0.1;
+            dataMax += 0.1;
+        }
+        let span = dataMax - dataMin;
+
+        let step = Math.pow(10, Math.floor(Math.log10(span / m)));
+        let err = m / span * step;
+
+        if (err <= .15) {
+            step *= 10;
+        }
+        else if (err <= .35) {
+            step *= 5;
+        }
+        else if (err <= .75) {
+            step *= 2;
+        }
+
+        if (isIntegerRange) {
+            step = Math.ceil(step);
+        }
+        let ret: number[] = new Array<number>(3);
+        let minDivStep = Math.floor(dataMin / step);
+        let maxDivStep = Math.floor(dataMax / step);
+        ret[0] = minDivStep * step; // Math.floor(Math.Round(dataMin, 8)/step)*step;
+        ret[1] = maxDivStep * step + step; // Math.floor(Math.Round(dataMax, 8)/step)*step + step;
+        ret[2] = step;
+
+        return ret;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts
new file mode 100644
index 000000000..f53008f9a
--- /dev/null
+++ b/src/client/northstar/model/binRanges/VisualBinRange.ts
@@ -0,0 +1,36 @@
+import { BinLabel } from '../../model/idea/idea'
+
+export abstract class VisualBinRange {
+
+    constructor() {
+
+    }
+
+    public abstract AddStep(value: number): number;
+
+    public abstract GetValueFromIndex(index: number): number;
+
+    public abstract GetBins(): Array<number>;
+
+    public GetLabel(value: number): string {
+        return value.toString();
+    }
+
+    public GetLabels(): Array<BinLabel> {
+        var labels = new Array<BinLabel>();
+        var bins = this.GetBins();
+        bins.forEach(b => {
+            labels.push(new BinLabel({
+                value: b,
+                minValue: b,
+                maxValue: this.AddStep(b),
+                label: this.GetLabel(b)
+            }));
+        });
+        return labels;
+    }
+}
+
+export enum ChartType {
+    HorizontalBar = 0, VerticalBar = 1, HeatMap = 2, SinglePoint = 3
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
new file mode 100644
index 000000000..9eae39800
--- /dev/null
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -0,0 +1,71 @@
+import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult } from "../idea/idea";
+import { VisualBinRange, ChartType } from "./VisualBinRange";
+import { NominalVisualBinRange } from "./NominalVisualBinRange";
+import { QuantitativeVisualBinRange } from "./QuantitativeVisualBinRange";
+import { AlphabeticVisualBinRange } from "./AlphabeticVisualBinRange";
+import { DateTimeVisualBinRange } from "./DateTimeVisualBinRange";
+import { Settings } from "../../manager/Gateway";
+import { ModelHelpers } from "../ModelHelpers";
+import { AttributeTransformationModel } from "../../core/attribute/AttributeTransformationModel";
+
+export const SETTINGS_X_BINS = 15;
+export const SETTINGS_Y_BINS = 15;
+export const SETTINGS_SAMPLE_SIZE = 100000;
+
+export class VisualBinRangeHelper {
+
+    public static GetNonAggregateVisualBinRange(dataBinRange: BinRange): VisualBinRange {
+        if (dataBinRange instanceof NominalBinRange) {
+            return new NominalVisualBinRange(dataBinRange as NominalBinRange);
+        }
+        else if (dataBinRange instanceof QuantitativeBinRange) {
+            return new QuantitativeVisualBinRange(dataBinRange as QuantitativeBinRange);
+        }
+        else if (dataBinRange instanceof AlphabeticBinRange) {
+            return new AlphabeticVisualBinRange(dataBinRange as AlphabeticBinRange);
+        }
+        else if (dataBinRange instanceof DateTimeBinRange) {
+            return new DateTimeVisualBinRange(dataBinRange as DateTimeBinRange);
+        }
+        throw new Exception()
+    }
+
+    public static GetVisualBinRange(dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
+
+        if (!(dataBinRange instanceof AggregateBinRange)) {
+            return VisualBinRangeHelper.GetNonAggregateVisualBinRange(dataBinRange);
+        }
+        else {
+            var aggregateKey = ModelHelpers.CreateAggregateKey(attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
+            var minValue = Number.MAX_VALUE;
+            var maxValue = Number.MIN_VALUE;
+            for (var b = 0; b < histoResult.brushes!.length; b++) {
+                var brush = histoResult.brushes![b];
+                aggregateKey.brushIndex = brush.brushIndex;
+                for (var key in histoResult.bins) {
+                    if (histoResult.bins.hasOwnProperty(key)) {
+                        var bin = histoResult.bins[key];
+                        var res = <DoubleValueAggregateResult>ModelHelpers.GetAggregateResult(bin, aggregateKey);
+                        if (res && res.hasResult && res.result) {
+                            minValue = Math.min(minValue, res.result);
+                            maxValue = Math.max(maxValue, res.result);
+                        }
+                    }
+                }
+            };
+
+            let visualBinRange = QuantitativeVisualBinRange.Initialize(minValue, maxValue, 10, false);
+
+            if (chartType == ChartType.HorizontalBar || chartType == ChartType.VerticalBar) {
+                visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue),
+                    Math.max(0, (visualBinRange as QuantitativeVisualBinRange).DataBinRange.maxValue!),
+                    SETTINGS_X_BINS, false);
+            }
+            else if (chartType == ChartType.SinglePoint) {
+                visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue), Math.max(0, maxValue),
+                    SETTINGS_X_BINS, false);
+            }
+            return visualBinRange;
+        }
+    }
+}
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
new file mode 100644
index 000000000..4c0303a48
--- /dev/null
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -0,0 +1,174 @@
+import { FilterModel } from '../core/filter/FilterModel'
+import { ErrorResult, Exception, OperationParameters, OperationReference, Result, ResultParameters } from '../model/idea/idea';
+import { action, computed, observable } from "mobx";
+import { Gateway } from '../manager/Gateway';
+
+export abstract class BaseOperation {
+    private _interactionTimeoutId: number = 0;
+    private static _currentOperations: Map<number, PollPromise> = new Map<number, PollPromise>();
+    //public InteractionTimeout: EventDelegate<InteractionTimeoutEventArgs> = new EventDelegate<InteractionTimeoutEventArgs>();
+
+    @observable public Error: string = "";
+    @observable public OverridingFilters: FilterModel[] = [];
+    @observable public Result: Result | undefined;
+    @observable public ComputationStarted: boolean = false;
+    public OperationReference: OperationReference | undefined = undefined;
+
+    private static _nextId = 0;
+    public RequestSalt: string = "";
+    public Id: number;
+
+    constructor() {
+        this.Id = BaseOperation._nextId++;
+    }
+
+    @computed
+    public get FilterString(): string {
+
+        // if (this.OverridingFilters.length > 0) {
+        //     return "(" + this.OverridingFilters.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+        // }
+        // let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph;
+        // let sliceModel = this.TypedViewModel.IncomingSliceModel;
+        // if (sliceModel != null && sliceModel.Source != null && instanceOfIBaseFilterProvider(sliceModel.Source) && rdg.has(sliceModel.Source)) {
+        //     let filterModels = sliceModel.Source.FilterModels.map(f => f);
+        //     return FilterModel.GetFilterModelsRecursive(rdg.get(sliceModel.Source), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, false);
+        // }
+
+        // if (rdg.has(this.TypedViewModel)) {
+        //     let filterModels = [];
+        //     return FilterModel.GetFilterModelsRecursive(rdg.get(this.TypedViewModel), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, true)
+        // }
+        return "";
+    }
+
+
+    @action
+    public SetResult(result: Result): void {
+        this.Result = result;
+    }
+
+    public async Update(): Promise<void> {
+
+        try {
+            if (BaseOperation._currentOperations.has(this.Id)) {
+                BaseOperation._currentOperations.get(this.Id)!.Cancel();
+                if (this.OperationReference) {
+                    Gateway.Instance.PauseOperation(this.OperationReference.toJSON());
+                }
+            }
+
+            let operationParameters = this.CreateOperationParameters();
+            this.Result = undefined;
+            this.Error = "";
+            let salt = Math.random().toString();
+            this.RequestSalt = salt;
+
+            if (!operationParameters) {
+                this.ComputationStarted = false;
+                return;
+            }
+
+            this.ComputationStarted = true;
+            //let start = performance.now();
+            let promise = Gateway.Instance.StartOperation(operationParameters.toJSON());
+            promise.catch(err => {
+                action(() => {
+                    this.Error = err;
+                    console.error(err);
+                });
+            });
+            let operationReference = await promise;
+
+
+            if (operationReference) {
+                this.OperationReference = operationReference;
+
+                let resultParameters = new ResultParameters();
+                resultParameters.operationReference = operationReference;
+
+                let pollPromise = new PollPromise(salt, operationReference);
+                BaseOperation._currentOperations.set(this.Id, pollPromise);
+
+                pollPromise.Start(async () => {
+                    let result = await Gateway.Instance.GetResult(resultParameters.toJSON());
+                    if (result instanceof ErrorResult) {
+                        throw new Error((result as ErrorResult).message);
+                    }
+                    if (this.RequestSalt == pollPromise.RequestSalt) {
+                        if (result && (!this.Result || this.Result.progress != result.progress)) {
+                            /*if (operationViewModel.Result !== null && operationViewModel.Result !== undefined) {
+                                let t1 = performance.now();
+                                console.log((t1 - start) + " milliseconds.");
+                                start = performance.now();
+                            }*/
+                            this.SetResult(result);
+                        }
+
+                        if (!result || result.progress! < 1) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }, 100).catch((err: Error) => action(() => {
+                    this.Error = err.message;
+                    console.error(err.message);
+                })()
+                );
+            }
+        }
+        catch (err) {
+            console.error(err as Exception);
+            // ErrorDialog.Instance.HandleError(err, operationViewModel);
+        }
+    }
+
+    public CreateOperationParameters(): OperationParameters | undefined { return undefined; }
+
+    private interactionTimeout() {
+        // clearTimeout(this._interactionTimeoutId);
+        // this.InteractionTimeout.Fire(new InteractionTimeoutEventArgs(this.TypedViewModel, InteractionTimeoutType.Timeout));
+    }
+}
+
+export class PollPromise {
+    public RequestSalt: string;
+    public OperationReference: OperationReference;
+
+    private _notCanceled: boolean = true;
+    private _poll: undefined | (() => Promise<boolean>);
+    private _delay: number = 0;
+
+    public constructor(requestKey: string, operationReference: OperationReference) {
+        this.RequestSalt = requestKey;
+        this.OperationReference = operationReference;
+    }
+
+    public Cancel(): void {
+        this._notCanceled = false;
+    }
+
+    public Start(poll: () => Promise<boolean>, delay: number): Promise<void> {
+        this._poll = poll;
+        this._delay = delay;
+        return this.pollRecursive();
+    }
+
+    private pollRecursive = (): Promise<void> => {
+        return Promise.resolve().then(this._poll).then((flag) => {
+            this._notCanceled && flag && new Promise((res) => (setTimeout(res, this._delay)))
+                .then(this.pollRecursive);
+        });
+    }
+}
+
+
+export class InteractionTimeoutEventArgs {
+    constructor(public Sender: object, public Type: InteractionTimeoutType) {
+    }
+}
+
+export enum InteractionTimeoutType {
+    Reset = 0,
+    Timeout = 1
+}
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
new file mode 100644
index 000000000..a4f5cac70
--- /dev/null
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -0,0 +1,111 @@
+import { reaction, computed, action } from "mobx";
+import { Attribute, DataType, QuantitativeBinRange, HistogramOperationParameters, AggregateParameters, AggregateFunction, AverageAggregateParameters } from "../model/idea/idea";
+import { ArrayUtil } from "../utils/ArrayUtil";
+import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttributeModel";
+import { ModelHelpers } from "../model/ModelHelpers";
+import { SETTINGS_X_BINS, SETTINGS_Y_BINS, SETTINGS_SAMPLE_SIZE } from "../model/binRanges/VisualBinRangeHelper";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
+import { Main } from "../../views/Main";
+import { BaseOperation } from "./BaseOperation";
+
+
+export class HistogramOperation extends BaseOperation {
+    public X: AttributeTransformationModel;
+    public Y: AttributeTransformationModel;
+    public V: AttributeTransformationModel;
+    constructor(x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel) {
+        super();
+        this.X = x;
+        this.Y = y;
+        this.V = v;
+        reaction(() => this.createOperationParamsCache, () => this.Update());
+    }
+
+    @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.struct
+    public get SelectionString() {
+        return "";
+        // let filterModels = new Array<FilterModel>();
+        // let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph;
+        // let graphNode: GraphNode<BaseOperationViewModel, FilterLinkViewModel>;
+        // if (rdg.has(this.TypedViewModel)) {
+        //     graphNode = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph.get(this.TypedViewModel);
+        // }
+        // else {
+        //     graphNode = new GraphNode<BaseOperationViewModel, FilterLinkViewModel>(this.TypedViewModel);
+        // }
+        // return FilterModel.GetFilterModelsRecursive(graphNode, new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, false);
+    }
+
+    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));
+
+        let numericDataTypes = [DataType.Int, DataType.Double, DataType.Float];
+        let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(allAttributes);
+        let globalAggregateParameters: AggregateParameters[] = [];
+        [histoX, histoY]
+            .filter(a => a.AggregateFunction === AggregateFunction.None && ArrayUtil.Contains(numericDataTypes, a.AttributeModel.DataType))
+            .forEach(a => {
+                let avg = new AverageAggregateParameters();
+                avg.attributeParameters = ModelHelpers.GetAttributeParameters(a.AttributeModel);
+                globalAggregateParameters.push(avg);
+            });
+        return [perBinAggregateParameters, globalAggregateParameters];
+    }
+
+    @computed
+    get createOperationParamsCache() {
+        return this.CreateOperationParameters();
+    }
+
+    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);
+            return new HistogramOperationParameters({
+                enableBrushComputation: true,
+                adapterName: Main.Instance.ActiveSchema!.displayName,
+                filter: this.FilterString,
+                brushes: this.BrushString,
+                binningParameters: [ModelHelpers.GetBinningParameters(this.X, SETTINGS_X_BINS, this.QRange ? this.QRange.minValue : undefined, this.QRange ? this.QRange.maxValue : undefined),
+                ModelHelpers.GetBinningParameters(this.Y, SETTINGS_Y_BINS)],
+                sampleStreamBlockSize: SETTINGS_SAMPLE_SIZE,
+                perBinAggregateParameters: perBinAggregateParameters,
+                globalAggregateParameters: globalAggregateParameters,
+                sortPerBinAggregateParameter: undefined,
+                attributeCalculatedParameters: CalculatedAttributeManager
+                    .AllCalculatedAttributes.map(a => ModelHelpers.GetAttributeParametersFromAttributeModel(a)),
+                degreeOfParallism: 1, // Settings.Instance.DegreeOfParallelism,
+                isCachable: false
+            });
+        }
+    }
+
+
+    @action
+    public async Update(): Promise<void> {
+        // this.TypedViewModel.BrushColors = this.TypedViewModel.BrusherModels.map(e => e.Color);
+        return super.Update();
+    }
+}
+
+
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
new file mode 100644
index 000000000..f35c98317
--- /dev/null
+++ b/src/client/northstar/utils/ArrayUtil.ts
@@ -0,0 +1,90 @@
+import { Exception } from "../model/idea/idea";
+
+export class ArrayUtil {
+
+    public static Contains(arr1: any[], arr2: any): boolean {
+        if (arr1.length === 0) {
+            return false;
+        }
+        let isComplex = typeof arr1[0] === "object";
+        for (let i = 0; i < arr1.length; i++) {
+            if (isComplex && "Equals" in arr1[i]) {
+                if (arr1[i].Equals(arr2)) {
+                    return true;
+                }
+            }
+            else {
+                if (arr1[i] === arr2) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    public static RemoveMany(arr: any[], elements: Object[]) {
+        elements.forEach(e => ArrayUtil.Remove(arr, e));
+    }
+
+    public static AddMany(arr: any[], others: Object[]) {
+        arr.push(...others);
+    }
+
+    public static Clear(arr: any[]) {
+        arr.splice(0, arr.length);
+    }
+
+
+    public static Remove(arr: any[], other: Object) {
+        const index = ArrayUtil.IndexOfWithEqual(arr, other);
+        if (index === -1) {
+            return;
+        }
+        arr.splice(index, 1);
+    }
+
+
+    public static First<T>(arr: T[], predicate: (x: any) => boolean): T {
+        let filtered = arr.filter(predicate);
+        if (filtered.length > 0) {
+            return filtered[0];
+        }
+        throw new Exception()
+    }
+
+    public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined {
+        let filtered = arr.filter(predicate);
+        if (filtered.length > 0) {
+            return filtered[0];
+        }
+        return undefined;
+    }
+
+    public static Distinct(arr: any[]): any[] {
+        let ret = [];
+        for (let i = 0; i < arr.length; i++) {
+            if (!ArrayUtil.Contains(ret, arr[i])) {
+                ret.push(arr[i]);
+            }
+        }
+        return ret;
+    }
+
+    public static IndexOfWithEqual(arr: any[], other: any): number {
+        for (let i = 0; i < arr.length; i++) {
+            let isComplex = typeof arr[0] === "object";
+            if (isComplex && "Equals" in arr[i]) {
+                if (arr[i].Equals(other)) {
+                    return i;
+                }
+            }
+            else {
+                if (arr[i] === other) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts
new file mode 100644
index 000000000..71bcadf89
--- /dev/null
+++ b/src/client/northstar/utils/Extensions.ts
@@ -0,0 +1,20 @@
+interface String {
+    ReplaceAll(toReplace: string, replacement: string): string;
+}
+
+String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string {
+    var target = this;
+    return target.split(toReplace).join(replacement);
+}
+
+interface Math {
+    log10(val: number): number;
+}
+
+Math.log10 = function (val: number): number {
+    return Math.log(val) / Math.LN10;
+}
+
+declare interface ObjectConstructor {
+    assign(...objects: Object[]): Object;
+}
diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts
new file mode 100644
index 000000000..d5f3ba631
--- /dev/null
+++ b/src/client/northstar/utils/GeometryUtil.ts
@@ -0,0 +1,129 @@
+import { MathUtil, PIXIRectangle, PIXIPoint } from "./MathUtil";
+
+
+export class GeometryUtil {
+
+    public static ComputeBoundingBox(points: { x: number, y: number }[], scale = 1, padding: number = 0): PIXIRectangle {
+        let minX: number = Number.MAX_VALUE;
+        let minY: number = Number.MAX_VALUE;
+        let maxX: number = Number.MIN_VALUE;
+        let maxY: number = Number.MIN_VALUE;
+        for (var i = 0; i < points.length; i++) {
+            if (points[i].x < minX)
+                minX = points[i].x;
+            if (points[i].y < minY)
+                minY = points[i].y;
+            if (points[i].x > maxX)
+                maxX = points[i].x;
+            if (points[i].y > maxY)
+                maxY = points[i].y;
+        }
+        return new PIXIRectangle(minX * scale - padding, minY * scale - padding, (maxX - minX) * scale + padding * 2, (maxY - minY) * scale + padding * 2);
+    }
+
+    public static RectangleOverlap(rect1: PIXIRectangle, rect2: PIXIRectangle) {
+        let x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left));
+        let y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));
+        return x_overlap * y_overlap;
+    }
+
+    public static RotatePoints(center: { x: number, y: number }, points: { x: number, y: number }[], angle: number): PIXIPoint[] {
+        const rotate = (cx: number, cy: number, x: number, y: number, angle: number) => {
+            const radians = angle,
+                cos = Math.cos(radians),
+                sin = Math.sin(radians),
+                nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
+                ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
+            return new PIXIPoint(nx, ny);
+        }
+        return points.map(p => rotate(center.x, center.y, p.x, p.y, angle));
+    }
+
+    public static LineByLeastSquares(points: { x: number, y: number }[]): PIXIPoint[] {
+        let sum_x: number = 0;
+        let sum_y: number = 0;
+        let sum_xy: number = 0;
+        let sum_xx: number = 0;
+        let count: number = 0;
+
+        let x: number = 0;
+        let y: number = 0;
+
+
+        if (points.length === 0) {
+            return [];
+        }
+
+        for (let v = 0; v < points.length; v++) {
+            x = points[v].x;
+            y = points[v].y;
+            sum_x += x;
+            sum_y += y;
+            sum_xx += x * x;
+            sum_xy += x * y;
+            count++;
+        }
+
+        let m = (count * sum_xy - sum_x * sum_y) / (count * sum_xx - sum_x * sum_x);
+        let b = (sum_y / count) - (m * sum_x) / count;
+        let result: PIXIPoint[] = new Array<PIXIPoint>();
+
+        for (let v = 0; v < points.length; v++) {
+            x = points[v].x;
+            y = x * m + b;
+            result.push(new PIXIPoint(x, y));
+        }
+        return result;
+    }
+
+    // public static PointInsidePolygon(vs:Point[], x:number, y:number):boolean {
+    //     // ray-casting algorithm based on
+    //     // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+
+    //     var inside = false;
+    //     for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
+    //         var xi = vs[i].x, yi = vs[i].y;
+    //         var xj = vs[j].x, yj = vs[j].y;
+
+    //         var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+    //         if (intersect) 
+    //            inside = !inside;
+    //     }
+
+    //     return inside;
+    // };
+
+    public static IntersectLines(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number): boolean {
+        let a1: number, a2: number, b1: number, b2: number, c1: number, c2: number;
+        let r1: number, r2: number, r3: number, r4: number;
+        let denom: number, offset: number, num: number;
+
+        a1 = y2 - y1;
+        b1 = x1 - x2;
+        c1 = (x2 * y1) - (x1 * y2);
+        r3 = ((a1 * x3) + (b1 * y3) + c1);
+        r4 = ((a1 * x4) + (b1 * y4) + c1);
+
+        if ((r3 !== 0) && (r4 !== 0) && (MathUtil.Sign(r3) === MathUtil.Sign(r4))) {
+            return false;
+        }
+
+        a2 = y4 - y3;
+        b2 = x3 - x4;
+        c2 = (x4 * y3) - (x3 * y4);
+
+        r1 = (a2 * x1) + (b2 * y1) + c2;
+        r2 = (a2 * x2) + (b2 * y2) + c2;
+
+        if ((r1 !== 0) && (r2 !== 0) && (MathUtil.Sign(r1) === MathUtil.Sign(r2))) {
+            return false;
+        }
+
+        denom = (a1 * b2) - (a2 * b1);
+
+        if (denom === 0) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/utils/IDisposable.ts b/src/client/northstar/utils/IDisposable.ts
new file mode 100644
index 000000000..5e9843326
--- /dev/null
+++ b/src/client/northstar/utils/IDisposable.ts
@@ -0,0 +1,3 @@
+export interface IDisposable {
+    Dispose(): void;
+}
\ No newline at end of file
diff --git a/src/client/northstar/utils/IEquatable.ts b/src/client/northstar/utils/IEquatable.ts
new file mode 100644
index 000000000..2f81c2478
--- /dev/null
+++ b/src/client/northstar/utils/IEquatable.ts
@@ -0,0 +1,3 @@
+export interface IEquatable {
+    Equals(other: Object): boolean;
+}
\ No newline at end of file
diff --git a/src/client/northstar/utils/KeyCodes.ts b/src/client/northstar/utils/KeyCodes.ts
new file mode 100644
index 000000000..044569ffe
--- /dev/null
+++ b/src/client/northstar/utils/KeyCodes.ts
@@ -0,0 +1,137 @@
+/**
+ * Class contains the keycodes for keys on your keyboard.
+ *
+ * Useful for auto completion:
+ *
+ * ```
+ * switch (event.key)
+ * {
+ *      case KeyCode.UP:
+ *      {
+ *      	// Up key pressed
+ *       	break;
+ *      }
+ *      case KeyCode.DOWN:
+ *      {
+ *      	// Down key pressed
+ *       	break;
+ *      }
+ *      case KeyCode.LEFT:
+ *      {
+ *      	// Left key pressed
+ *       	break;
+ *      }
+ *      case KeyCode.RIGHT:
+ *      {
+ *      	// Right key pressed
+ *       	break;
+ *      }
+ *      default:
+ *      {
+ *      	// ignore
+ *      	break;
+ *      }
+ * }
+ * ```
+ */
+export class KeyCodes
+{
+	public static TAB:number = 9;
+	public static CAPS_LOCK:number = 20;
+	public static SHIFT:number = 16;
+	public static CONTROL:number = 17;
+	public static SPACE:number = 32;
+	public static DOWN:number = 40;
+	public static UP:number = 38;
+	public static LEFT:number = 37;
+	public static RIGHT:number = 39;
+	public static ESCAPE:number = 27;
+	public static F1:number = 112;
+	public static F2:number = 113;
+	public static F3:number = 114;
+	public static F4:number = 115;
+	public static F5:number = 116;
+	public static F6:number = 117;
+	public static F7:number = 118;
+	public static F8:number = 119;
+	public static F9:number = 120;
+	public static F10:number = 121;
+	public static F11:number = 122;
+	public static F12:number = 123;
+	public static INSERT:number = 45;
+	public static HOME:number = 36;
+	public static PAGE_UP:number = 33;
+	public static PAGE_DOWN:number = 34;
+	public static DELETE:number = 46;
+	public static END:number = 35;
+	public static ENTER:number = 13;
+	public static BACKSPACE:number = 8;
+	public static NUMPAD_0:number = 96;
+	public static NUMPAD_1:number = 97;
+	public static NUMPAD_2:number = 98;
+	public static NUMPAD_3:number = 99;
+	public static NUMPAD_4:number = 100;
+	public static NUMPAD_5:number = 101;
+	public static NUMPAD_6:number = 102;
+	public static NUMPAD_7:number = 103;
+	public static NUMPAD_8:number = 104;
+	public static NUMPAD_9:number = 105;
+	public static NUMPAD_DIVIDE:number = 111;
+	public static NUMPAD_ADD:number = 107;
+	public static NUMPAD_ENTER:number = 13;
+	public static NUMPAD_DECIMAL:number = 110;
+	public static NUMPAD_SUBTRACT:number = 109;
+	public static NUMPAD_MULTIPLY:number = 106;
+	public static SEMICOLON:number = 186;
+	public static EQUAL:number = 187;
+	public static COMMA:number = 188;
+	public static MINUS:number = 189;
+	public static PERIOD:number = 190;
+	public static SLASH:number = 191;
+	public static BACKQUOTE:number = 192;
+	public static LEFTBRACKET:number = 219;
+	public static BACKSLASH:number = 220;
+	public static RIGHTBRACKET:number = 221;
+	public static QUOTE:number = 222;
+	public static ALT:number = 18;
+	public static COMMAND:number = 15;
+	public static NUMPAD:number = 21;
+	public static A:number = 65;
+	public static B:number = 66;
+	public static C:number = 67;
+	public static D:number = 68;
+	public static E:number = 69;
+	public static F:number = 70;
+	public static G:number = 71;
+	public static H:number = 72;
+	public static I:number = 73;
+	public static J:number = 74;
+	public static K:number = 75;
+	public static L:number = 76;
+	public static M:number = 77;
+	public static N:number = 78;
+	public static O:number = 79;
+	public static P:number = 80;
+	public static Q:number = 81;
+	public static R:number = 82;
+	public static S:number = 83;
+	public static T:number = 84;
+	public static U:number = 85;
+	public static V:number = 86;
+	public static W:number = 87;
+	public static X:number = 88;
+	public static Y:number = 89;
+	public static Z:number = 90;
+	public static NUM_0:number = 48;
+	public static NUM_1:number = 49;
+	public static NUM_2:number = 50;
+	public static NUM_3:number = 51;
+	public static NUM_4:number = 52;
+	public static NUM_5:number = 53;
+	public static NUM_6:number = 54;
+	public static NUM_7:number = 55;
+	public static NUM_8:number = 56;
+	public static NUM_9:number = 57;
+	public static SUBSTRACT:number = 189;
+	public static ADD:number = 187;
+}
\ No newline at end of file
diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
new file mode 100644
index 000000000..3ed8628ee
--- /dev/null
+++ b/src/client/northstar/utils/MathUtil.ts
@@ -0,0 +1,236 @@
+
+
+export class PIXIPoint {
+    public x: number;
+    public y: number;
+    constructor(x: number, y: number) {
+        this.x = x;
+        this.y = y;
+    }
+}
+export class PIXIRectangle {
+    public x: number;
+    public y: number;
+    public width: number;
+    public height: number
+    public get left() { return this.x }
+    public get right() { return this.x + this.width; }
+    public get top() { return this.y }
+    public get bottom() { return this.top + this.height }
+    constructor(x: number, y: number, width: number, height: number) {
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+    }
+}
+
+export class MathUtil {
+
+    public static EPSILON: number = 0.001;
+
+    public static Sign(value: number): number {
+        return value >= 0 ? 1 : -1;
+    }
+
+    public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
+        if (inline) {
+            p1.x += p2.x;
+            p1.y += p2.y;
+            return p1;
+        }
+        else {
+            return new PIXIPoint(p1.x + p2.x, p1.y + p2.y);
+        }
+    }
+
+    public static Perp(p1: PIXIPoint): PIXIPoint {
+        return new PIXIPoint(-p1.y, p1.x);
+    }
+
+    public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint {
+        if (inline) {
+            p1.x /= by;
+            p1.y /= by;
+            return p1;
+        }
+        else {
+            return new PIXIPoint(p1.x / by, p1.y / by);
+        }
+    }
+
+    public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) {
+        if (inline) {
+            p1.x *= by;
+            p1.y *= by;
+            return p1;
+        }
+        else {
+            return new PIXIPoint(p1.x * by, p1.y * by);
+        }
+    }
+
+    public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
+        if (inline) {
+            p1.x -= p2.x;
+            p1.y -= p2.y;
+            return p1;
+        }
+        else {
+            return new PIXIPoint(p1.x - p2.x, p1.y - p2.y);
+        }
+    }
+
+    public static Area(rect: PIXIRectangle): number {
+        return rect.width * rect.height;
+    }
+
+    public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) {
+        // Return minimum distance between line segment vw and point p
+        var l2 = MathUtil.DistSquared(v, w);  // i.e. |w-v|^2 -  avoid a sqrt
+        if (l2 == 0.0) return MathUtil.Dist(p, v);   // v == w case
+        // Consider the line extending the segment, parameterized as v + t (w - v).
+        // We find projection of point p onto the line. 
+        // It falls where t = [(p-v) . (w-v)] / |w-v|^2
+        // We clamp t from [0,1] to handle points outside the segment vw.
+        var dot = MathUtil.Dot(
+            MathUtil.SubtractPoint(p, v),
+            MathUtil.SubtractPoint(w, v)) / l2;
+        var t = Math.max(0, Math.min(1, dot));
+        // Projection falls on the segment
+        var projection = MathUtil.AddPoint(v,
+            MathUtil.MultiplyConstant(
+                MathUtil.SubtractPoint(w, v), t));
+        return MathUtil.Dist(p, projection);
+    }
+
+    public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined {
+        var a1 = pe1.y - ps1.y;
+        var b1 = ps1.x - pe1.x;
+
+        var a2 = pe2.y - ps2.y;
+        var b2 = ps2.x - pe2.x;
+
+        var delta = a1 * b2 - a2 * b1;
+        if (delta == 0) {
+            return undefined;
+        }
+        var c2 = a2 * ps2.x + b2 * ps2.y;
+        var c1 = a1 * ps1.x + b1 * ps1.y;
+        var invdelta = 1 / delta;
+        return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta);
+    }
+
+    public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
+        if (p.x < rect.left - this.EPSILON)
+            return false;
+        if (p.x > rect.right + this.EPSILON)
+            return false;
+        if (p.y < rect.top - this.EPSILON)
+            return false;
+        if (p.y > rect.bottom + this.EPSILON)
+            return false;
+
+        return true;
+    }
+
+    public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> {
+        var r1 = new PIXIPoint(rect.left, rect.top);
+        var r2 = new PIXIPoint(rect.right, rect.top);
+        var r3 = new PIXIPoint(rect.right, rect.bottom);
+        var r4 = new PIXIPoint(rect.left, rect.bottom);
+        var ret = new Array<PIXIPoint>();
+        var dist = this.Dist(lineFrom, lineTo)
+        var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
+        if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            ret.push(inter);
+        inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3);
+        if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            ret.push(inter);
+        inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4);
+        if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            ret.push(inter);
+        inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1);
+        if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
+            this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+            ret.push(inter);
+        return ret;
+    }
+
+    public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle {
+        const left = Math.max(rect1.x, rect2.x);
+        const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width);
+        const top = Math.max(rect1.y, rect2.y);
+        const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height);
+        return new PIXIRectangle(left, top, right - left, bottom - top);
+    }
+
+    public static Dist(p1: PIXIPoint, p2: PIXIPoint): number {
+        return Math.sqrt(MathUtil.DistSquared(p1, p2));
+    }
+
+    public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
+        return p1.x * p2.x + p1.y * p2.y
+    }
+
+    public static Normalize(p1: PIXIPoint) {
+        var d = this.Length(p1);
+        return new PIXIPoint(p1.x / d, p1.y / d);
+    }
+
+    public static Length(p1: PIXIPoint): number {
+        return Math.sqrt(p1.x * p1.x + p1.y * p1.y);
+    }
+
+    public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number {
+        const a = p1.x - p2.x;
+        const b = p1.y - p2.y;
+        return (a * a + b * b)
+    }
+
+    public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
+        return !(r2.x > r1.x + r1.width ||
+            r2.x + r2.width < r1.x ||
+            r2.y > r1.y + r1.height ||
+            r2.y + r2.height < r1.y);
+    }
+
+    public static ArgMin(temp: number[]): number {
+        let index = 0;
+        let value = temp[0];
+        for (let i = 1; i < temp.length; i++) {
+            if (temp[i] < value) {
+                value = temp[i];
+                index = i;
+            }
+        }
+        return index;
+    }
+
+    public static ArgMax(temp: number[]): number {
+        let index = 0;
+        let value = temp[0];
+        for (let i = 1; i < temp.length; i++) {
+            if (temp[i] > value) {
+                value = temp[i];
+                index = i;
+            }
+        }
+        return index;
+    }
+
+    public static Combinations<T>(chars: T[]) {
+        let result = new Array<T>();
+        let f = (prefix: any, chars: any) => {
+            for (let i = 0; i < chars.length; i++) {
+                result.push(prefix.concat(chars[i]));
+                f(prefix.concat(chars[i]), chars.slice(i + 1));
+            }
+        };
+        f([], chars);
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/utils/PartialClass.ts b/src/client/northstar/utils/PartialClass.ts
new file mode 100644
index 000000000..2f20de96f
--- /dev/null
+++ b/src/client/northstar/utils/PartialClass.ts
@@ -0,0 +1,7 @@
+
+export class PartialClass<T> {
+
+    constructor(data?: Partial<T>) {
+        Object.assign(this, data);
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts
new file mode 100644
index 000000000..b35dce820
--- /dev/null
+++ b/src/client/northstar/utils/Utils.ts
@@ -0,0 +1,75 @@
+import { IBaseBrushable } from '../core/brusher/IBaseBrushable'
+import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer'
+import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider'
+import { AggregateFunction } from '../model/idea/idea'
+
+export class Utils {
+
+    public static EqualityHelper(a: Object, b: Object): boolean {
+        if (a === b) return true;
+        if (a === undefined && b !== undefined) return false;
+        if (a === null && b !== null) return false;
+        if (b === undefined && a !== undefined) return false;
+        if (b === null && a !== null) return false;
+        if ((<any>a).constructor.name !== (<any>b).constructor.name) return false;
+        return true;
+    }
+
+    public static LowercaseFirstLetter(str: string) {
+        return str.charAt(0).toUpperCase() + str.slice(1);
+    }
+
+    //
+    // this Type Guard tests if dropTarget is an IDropTarget.  If it is, it coerces the compiler
+    // to treat the dropTarget parameter as an IDropTarget *ouside* this function scope (ie, in
+    // the scope of where this function is called from).
+    //
+
+    public static isBaseBrushable<T>(obj: Object): obj is IBaseBrushable<T> {
+        let typed = <IBaseBrushable<T>>obj;
+        return typed != null && typed.BrusherModels !== undefined;
+    }
+
+    public static isBaseFilterProvider(obj: Object): obj is IBaseFilterProvider {
+        let typed = <IBaseFilterProvider>obj;
+        return typed != null && typed.FilterModels !== undefined;
+    }
+
+    public static isBaseFilterConsumer(obj: Object): obj is IBaseFilterConsumer {
+        let typed = <IBaseFilterConsumer>obj;
+        return typed != null && typed.FilterOperand !== undefined;
+    }
+
+    public static EncodeQueryData(data: any): string {
+        const ret = [];
+        for (let d in data) {
+            ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
+        }
+        return ret.join("&");
+    }
+
+    public static ToVegaAggregationString(agg: AggregateFunction): string {
+        if (agg === AggregateFunction.Avg) {
+            return "average";
+        }
+        else if (agg === AggregateFunction.Count) {
+            return "count";
+        }
+        else {
+            return "";
+        }
+    }
+
+    public static GetQueryVariable(variable: string) {
+        let query = window.location.search.substring(1);
+        let vars = query.split("&");
+        for (let i = 0; i < vars.length; i++) {
+            let pair = vars[i].split("=");
+            if (decodeURIComponent(pair[0]) == variable) {
+                return decodeURIComponent(pair[1]);
+            }
+        }
+        return undefined;
+    }
+}
+
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index ac51a7d87..b27f63e52 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,4 +1,4 @@
-import { action, configure, observable, runInAction, trace, computed } from 'mobx';
+import { action, configure, observable, runInAction, trace, computed, reaction } from 'mobx';
 import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
@@ -22,7 +22,6 @@ import "./Main.scss";
 import { observer } from 'mobx-react';
 import { InkingControl } from './InkingControl';
 import { RouteStore } from '../../server/RouteStore';
-import { json } from 'body-parser';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faFont } from '@fortawesome/free-solid-svg-icons';
@@ -42,9 +41,10 @@ import { ServerUtils } from '../../server/ServerUtil';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { Field, Opt } from '../../fields/Field';
 import { ListField } from '../../fields/ListField';
-import { map } from 'bluebird';
 import { Gateway, Settings } from '../northstar/manager/Gateway';
-import { Catalog } from '../northstar/model/idea/idea';
+import { Catalog, Schema, Attribute, AttributeGroup } from '../northstar/model/idea/idea';
+import { ArrayUtil } from '../northstar/utils/ArrayUtil';
+import '../northstar/model/ModelExtensions'
 
 @observer
 export class Main extends React.Component {
@@ -88,16 +88,45 @@ export class Main extends React.Component {
 
         this.initEventListeners();
         Documents.initProtos(() => this.initAuthenticationRouters());
+
+        reaction(() => [this.mainContainer, this.ActiveSchema],
+            () => {
+                if (this.mainContainer && this.ActiveSchema) {
+                    if (!this.mainContainer!.GetTAsync(KeyStore.ActiveDB, ListField, field => this.NorthstarCatalog = field!.Data as Document[])) {
+                        this.NorthstarCatalog = this.GetAllAttributes().map(a => Documents.HistogramDocument({ width: 200, height: 200, title: a.displayName! }));
+                        this.mainContainer!.SetData(KeyStore.ActiveDB, this.NorthstarCatalog, ListField);
+                    }
+                }
+            })
     }
 
+    NorthstarCatalog: Document[] = [];
+    @observable ActiveSchema: Schema | undefined;
     @action SetNorthstarCatalog(ctlog: Catalog) {
         this._northstarCatalog = ctlog;
-        if (this._northstarCatalog) {
+        if (this._northstarCatalog && this._northstarCatalog.schemas) {
             console.log("CATALOG " + this._northstarCatalog.schemas);
+            this.ActiveSchema = ArrayUtil.FirstOrDefault<Schema>(this._northstarCatalog.schemas!, (s: Schema) => s.displayName === "mimic");
         }
     }
+    public GetAllAttributes() {
+        if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) {
+            return [];
+        }
+        const recurs = (attrs: Attribute[], g: AttributeGroup) => {
+            if (g.attributes) {
+                attrs.push.apply(attrs, g.attributes);
+                if (g.attributeGroups) {
+                    g.attributeGroups.forEach(ng => recurs(attrs, ng));
+                }
+            }
+        };
+        const allAttributes: Attribute[] = new Array<Attribute>();
+        recurs(allAttributes, this.ActiveSchema.rootAttributeGroup);
+        return allAttributes;
+    }
     async initializeNorthstar(): Promise<void> {
-        let envPath = "assets/env.json";
+        let envPath = "/assets/env.json";
         const response = await fetch(envPath, {
             redirect: "follow",
             method: "GET",
@@ -251,7 +280,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 addSchemaNode = action(() => Documents.SchemaDocument(this.NorthstarCatalog, { width: 200, height: 200, title: "a schema collection" }));
         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" }));
@@ -269,7 +298,9 @@ export class Main extends React.Component {
             [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
         ]
 
-        let addClick = (creator: () => Document) => action(() => this.mainfreeform!.GetList<Document>(KeyStore.Data, []).push(creator()));
+        let addClick = (creator: () => Document) => action(() => {
+            this.mainfreeform!.GetList<Document>(KeyStore.Data, []).push(creator())
+        });
 
         return < div id="add-nodes-menu" >
             <input type="checkbox" id="add-menu-toggle" />
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index ce72ab64b..2f0459f88 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -19,6 +19,7 @@ import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
+import { HistogramBox } from "./HistogramBox";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -47,8 +48,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     render() {
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
-            bindings={this.CreateBindings()}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
             onError={(test: any) => { console.log(test) }}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b6d50bffb..f6343c631 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -7,7 +7,6 @@ import { TextField } from "../../../fields/TextField";
 import { NumberField } from "../../../fields/NumberField";
 import { RichTextField } from "../../../fields/RichTextField";
 import { ImageField } from "../../../fields/ImageField";
-import { WebField } from "../../../fields/WebField";
 import { VideoField } from "../../../fields/VideoField"
 import { Key } from "../../../fields/Key";
 import { FormattedTextBox } from "./FormattedTextBox";
@@ -64,9 +63,11 @@ export class FieldView extends React.Component<FieldViewProps> {
         }
         else if (field instanceof AudioField) {
             return <AudioBox {...this.props} />
-        } else if (field instanceof Document) {
+        }
+        else if (field instanceof Document) {
             return <div>{field.Title}</div>
-        } else if (field instanceof ListField) {
+        }
+        else if (field instanceof ListField) {
             return (<div>
                 {(field as ListField<Field>).Data.map(f => {
                     return f instanceof Document ? f.Title : f.GetValue().toString();
diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss
new file mode 100644
index 000000000..04bf1d732
--- /dev/null
+++ b/src/client/views/nodes/HistogramBox.scss
@@ -0,0 +1,8 @@
+.histogrambox-container {
+    padding: 0vw;
+    position: relative;
+    text-align: center;
+    width: 100%;
+    height: 100%;
+  }
+  
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
new file mode 100644
index 000000000..0fcc25e66
--- /dev/null
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -0,0 +1,67 @@
+import React = require("react")
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./VideoBox.scss";
+import { observable, reaction } from "mobx";
+import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
+import { Main } from "../Main";
+import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel";
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+import { AggregateFunction, HistogramResult, DoubleValueAggregateResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+
+@observer
+export class HistogramBox extends React.Component<FieldViewProps> {
+
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
+
+    constructor(props: FieldViewProps) {
+        super(props);
+    }
+
+    @observable _histoResult?: HistogramResult;
+    _histoOp?: HistogramOperation;
+
+    componentDidMount() {
+        Main.Instance.GetAllAttributes().map(a => {
+            if (a.displayName == this.props.doc.Title) {
+                var atmod = new ColumnAttributeModel(a);
+                this._histoOp = new HistogramOperation(new AttributeTransformationModel(atmod, AggregateFunction.None),
+                    new AttributeTransformationModel(atmod, AggregateFunction.Count),
+                    new AttributeTransformationModel(atmod, AggregateFunction.Count));
+                reaction(() => [this._histoOp && this._histoOp.Result],
+                    () => this._histoResult = this._histoOp ? this._histoOp.Result as HistogramResult : undefined
+                );
+                this._histoOp.Update();
+            }
+        })
+    }
+
+    twoString() {
+        let str = "";
+        if (this._histoResult && !this._histoResult.isEmpty) {
+            for (let key in this._histoResult.bins) {
+                if (this._histoResult.bins.hasOwnProperty(key)) {
+                    let bin = this._histoResult.bins[key];
+                    str += JSON.stringify(bin.binIndex!.toJSON()) + " = ";
+                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(this._histoOp!.V, this._histoResult, ModelHelpers.AllBrushIndex(this._histoResult));
+                    let value = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+                    if (value && value.hasResult && value.result) {
+                        str += value.result;
+                    }
+                }
+            }
+        }
+        return str;
+    }
+
+    render() {
+        if (!this._histoResult)
+            return (null);
+        return (
+            <div className="histogrambox-container">
+                `HISTOGRAM RESULT : ${this.twoString()}`
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 68883d6f1..319702cc4 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -28,6 +28,7 @@ export namespace KeyStore {
     export const SchemaSplitPercentage = new Key("SchemaSplitPercentage");
     export const Caption = new Key("Caption");
     export const ActiveFrame = new Key("ActiveFrame");
+    export const ActiveDB = new Key("ActiveDB");
     export const DocumentText = new Key("DocumentText");
     export const LinkedToDocs = new Key("LinkedToDocs");
     export const LinkedFromDocs = new Key("LinkedFromDocs");
-- 
cgit v1.2.3-70-g09d2


From 7ffbfd93f6c7c66993e0f447ba10a06386512c75 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Fri, 22 Mar 2019 02:59:11 -0400
Subject: Moved LayoutKeys check

---
 src/client/views/nodes/DocumentContentsView.tsx | 7 ++++++-
 src/client/views/nodes/DocumentView.tsx         | 4 ----
 2 files changed, 6 insertions(+), 5 deletions(-)

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 2f0459f88..12d14eb42 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -47,8 +47,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     render() {
+        let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
+        if (!lkeys || lkeys === FieldWaiting) {
+            return <p>Error loading layout keys</p>;
+        }
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
             onError={(test: any) => { console.log(test) }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fec451b09..cfe67c8fe 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -314,10 +314,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         if (!this.props.Document) {
             return (null);
         }
-        let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
-        if (!lkeys || lkeys === FieldWaiting) {
-            return <p>Error loading layout keys</p>;
-        }
         var scaling = this.props.ContentScaling();
         var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
         var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
-- 
cgit v1.2.3-70-g09d2


From e9826e0ac334bac28d173f67b4f0db0800b40680 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 26 Mar 2019 16:06:06 -0400
Subject: cleaned up a bit. got rid of blinking when histos change.

---
 src/client/northstar/core/filter/FilterModel.ts    |  50 +--
 src/client/northstar/operations/BaseOperation.ts   |   3 +-
 .../northstar/operations/HistogramOperation.ts     |   1 -
 src/client/views/nodes/DocumentContentsView.tsx    |   3 +-
 src/client/views/nodes/HistogramBox.tsx            | 360 ++------------------
 src/client/views/nodes/HistogramBoxPrimitives.tsx  | 373 +++++++++++++++++++++
 6 files changed, 399 insertions(+), 391 deletions(-)
 create mode 100644 src/client/views/nodes/HistogramBoxPrimitives.tsx

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index a9c79d245..bc7938947 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -1,11 +1,9 @@
 import { ValueComparison } from "./ValueComparision";
 import { Utils } from "../../utils/Utils";
-import { IBaseFilterProvider, instanceOfIBaseFilterProvider } from "./IBaseFilterProvider";
-import { BaseOperation } from "../../operations/BaseOperation";
+import { IBaseFilterProvider } from "./IBaseFilterProvider";
 import { FilterOperand } from "./FilterOperand";
 import { HistogramField } from "../../../../fields/HistogramField";
 import { KeyStore } from "../../../../fields/KeyStore";
-import { filter } from "bluebird";
 import { FieldWaiting } from "../../../../fields/Field";
 import { Document } from "../../../../fields/Document";
 
@@ -35,8 +33,7 @@ export class FilterModel {
     }
 
     public ToPythonString(): string {
-        let ret = "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
-        return ret;
+        return "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
     }
 
     public static And(filters: string[]): string {
@@ -84,47 +81,4 @@ export class FilterModel {
         }
         return ret;
     }
-
-    // public static GetFilterModelsRecursive(baseOperation: BaseOperation,
-    //     visitedFilterProviders: Set<IBaseFilterProvider>, filterModels: FilterModel[], isFirst: boolean): string {
-    //     let ret = "";
-    //     if (Utils.isBaseFilterProvider(baseOperation)) {
-    //         visitedFilterProviders.add(baseOperation);
-    //         let filtered = baseOperation.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0);
-    //         if (!isFirst && filtered.length > 0) {
-    //             filterModels.push(...filtered);
-    //             ret = "(" + baseOperation.FilterModels.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
-    //         }
-    //     }
-    //     if (Utils.isBaseFilterConsumer(baseOperation) && baseOperation.Links) {
-    //         let children = new Array<string>();
-    //         let linkedGraphNodes = baseOperation.Links.get(LinkType.Filter);
-    //         if (linkedGraphNodes != null) {
-    //             for (let i = 0; i < linkedGraphNodes.length; i++) {
-    //                 let linkVm = linkedGraphNodes[i].Data;
-    //                 let linkedGraphNode = linkedGraphNodes[i].Target;
-    //                 if (!visitedFilterProviders.has(linkedGraphNode)) {
-    //                     let child = FilterModel.GetFilterModelsRecursive(linkedGraphNode, visitedFilterProviders, filterModels, false);
-    //                     if (child !== "") {
-    //                         if (linkVm.IsInverted) {
-    //                             child = "! " + child;
-    //                         }
-    //                         children.push(child);
-    //                     }
-    //                 }
-    //             }
-    //         }
-
-    //         let childrenJoined = children.join(baseOperation.FilterOperand === FilterOperand.AND ? " && " : " || ");
-    //         if (children.length > 0) {
-    //             if (ret !== "") {
-    //                 ret = "(" + ret + " &&  (" + childrenJoined + "))";
-    //             }
-    //             else {
-    //                 ret = "(" + childrenJoined + ")";
-    //             }
-    //         }
-    //     }
-    //     return ret;
-    // }
 }
\ No newline at end of file
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index 4bc8873d5..7db6fcb91 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -61,7 +61,8 @@ export abstract class BaseOperation {
             }
 
             let operationParameters = this.CreateOperationParameters();
-            this.Result = undefined;
+            if (this.Result)
+                this.Result.progress = 0; // bcz: used to set Result to undefined, but that causes the display to blink
             this.Error = "";
             let salt = Math.random().toString();
             this.RequestSalt = salt;
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 93946b296..bceadb961 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -55,7 +55,6 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     public get FilterString(): string {
         let filterModels: FilterModel[] = [];
         let fstring = FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true)
-        console.log("Filter string " + this.X.AttributeModel.DisplayName + " = " + fstring);
         return fstring;
     }
     @computed
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 12d14eb42..40990b76a 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -20,6 +20,7 @@ import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "./HistogramBox";
+import { HistogramBoxPrimitives } from "./HistogramBoxPrimitives";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -52,7 +53,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             return <p>Error loading layout keys</p>;
         }
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, HistogramBoxPrimitives }}
             bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
index e21054e15..20a022227 100644
--- a/src/client/views/nodes/HistogramBox.tsx
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -13,16 +13,15 @@ import { FilterModel } from '../../northstar/core/filter/FilterModel';
 import { NominalVisualBinRange } from "../../northstar/model/binRanges/NominalVisualBinRange";
 import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
 import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, AggregateFunction, Bin, BinRange, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
+import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
 import { ModelHelpers } from "../../northstar/model/ModelHelpers";
 import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
-import { LABColor } from '../../northstar/utils/LABcolor';
 import { PIXIRectangle } from "../../northstar/utils/MathUtil";
 import { SizeConverter } from "../../northstar/utils/SizeConverter";
 import { StyleConstants } from "../../northstar/utils/StyleContants";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./HistogramBox.scss";
+import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
 
 @observer
 export class HistogramBox extends React.Component<FieldViewProps> {
@@ -41,6 +40,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
     @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
+    @computed get createOperationParamsCache() { return this.HistoOp!.CreateOperationParameters(); }
 
     componentDidMount() {
         reaction(() => [CurrentUserUtils.ActiveSchemaName, this.props.doc.GetText(KeyStore.NorthstarSchema, "?")],
@@ -57,8 +57,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                     binRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap;
 
                 this.VisualBinRanges.length = 0;
-                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[0], this.HistoOp.Result, this.HistoOp.X, this.ChartType));
-                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[1], this.HistoOp.Result, this.HistoOp.Y, this.ChartType));
+                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[0], this.HistoOp!.Result!, this.HistoOp!.X, this.ChartType));
+                this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[1], this.HistoOp!.Result!, this.HistoOp!.Y, this.ChartType));
 
                 if (!this.HistoOp.Result.isEmpty) {
                     this.MaxValue = Number.MIN_VALUE;
@@ -79,11 +79,6 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         );
     }
 
-    @computed
-    get createOperationParamsCache() {
-        return this.HistoOp!.CreateOperationParameters();
-    }
-
     activateHistogramOperation() {
         this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => {
             if (histoOp) {
@@ -91,7 +86,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                 this.HistoOp!.Update();
                 reaction(
                     () => this.createOperationParamsCache,
-                    () => this.HistoOp!.Update();
+                    () => this.HistoOp!.Update());
                 reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []),
                     (docs: Document[]) => {
                         this.HistoOp!.Links.length = 0;
@@ -107,22 +102,16 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         return <div key={DashUtils.GenerateGuid()} style={{ position: "absolute", width: `${width}px`, height: `${height}px`, background: "lightgray", transform: `translate(${xFrom}px, ${yFrom}px)` }} />;
     }
 
-    drawRect(r: PIXIRectangle, color: number, tapHandler: () => void) {
-        return <div key={DashUtils.GenerateGuid()} onPointerDown={tapHandler} style={{ position: "absolute", transform: `translate(${r.x}px,${r.y}px)`, width: `${r.width - 1}`, height: `${r.height}`, background: LABColor.RGBtoHexString(color) }} />
-    }
-
     private renderGridLinesAndLabels(axis: number) {
         let sc = this.SizeConverter!;
-        let labels = this.VisualBinRanges[axis].GetLabels();
-
-        let dim = sc.RenderSize[axis] / sc.MaxLabelSizes[axis].coords[axis] + 5;
-        let mod = Math.ceil(labels.length / dim);
+        if (!sc || !this.VisualBinRanges.length)
+            return (null);
+        let dim = sc.RenderSize[axis] / ((axis == 0 && this.VisualBinRanges[axis] instanceof NominalVisualBinRange) ?
+            (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
+            sc.MaxLabelSizes[axis].coords[axis] + 5);
 
-        if (axis == 0 && this.VisualBinRanges[axis] instanceof NominalVisualBinRange) {
-            mod = Math.ceil(
-                labels.length / (sc.RenderSize[0] / (12 + 5))); //  (<number>FontStyles.AxisLabel.fontSize + 5)));
-        }
         let prims: JSX.Element[] = [];
+        let labels = this.VisualBinRanges[axis].GetLabels();
         labels.map((binLabel, i) => {
             let xFrom = sc.DataToScreenX(axis === 0 ? binLabel.minValue! : sc.DataMins[0]);
             let xTo = sc.DataToScreenX(axis === 0 ? binLabel.maxValue! : sc.DataMaxs[0]);
@@ -133,7 +122,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
             if (i == labels.length - 1)
                 prims.push(this.drawLine(axis == 0 ? xTo : xFrom, axis == 0 ? yFrom : yTo, axis == 0 ? 1 : xTo - xFrom, axis == 0 ? yTo - yFrom : 1));
 
-            if (i % mod === 0 && binLabel.label) {
+            if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) {
                 let text = binLabel.label;
                 if (text.length >= StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS) {
                     text = text.slice(0, StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS - 3) + "...";
@@ -157,330 +146,21 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         return prims;
     }
 
-    @computed
-    get binPrimitives() {
-        if (!this.HistoOp || !(this.HistoOp.Result instanceof HistogramResult) || !this.SizeConverter)
-            return undefined;
-        let prims: JSX.Element[] = [];
-        let selectedBinPrimitiveCollections = new Array<HistogramBinPrimitiveCollection>();
-        let allBrushIndex = ModelHelpers.AllBrushIndex(this.HistoOp.Result);
-        for (let key in this.HistoOp.Result.bins) {
-            if (this.HistoOp.Result.bins.hasOwnProperty(key)) {
-                let drawPrims = new HistogramBinPrimitiveCollection(key, this);
-
-                let filterModel = ModelHelpers.GetBinFilterModel(this.HistoOp.Result.bins![key], allBrushIndex, this.HistoOp.Result, this.HistoOp.X, this.HistoOp.Y);
-
-                this.HitTargets.setValue(drawPrims.HitGeom, filterModel);
-
-                if (ArrayUtil.Contains(this.HistoOp.FilterModels, filterModel)) {
-                    selectedBinPrimitiveCollections.push(drawPrims);
-                }
-
-                drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(binPrimitive => {
-                    let toggleFilter = () => {
-                        if ([filterModel].filter(h => ArrayUtil.Contains(this.HistoOp!.FilterModels, h)).length > 0)
-                            this.HistoOp!.RemoveFilterModels([filterModel]);
-                        else this.HistoOp!.AddFilterModels([filterModel]);
-                    }
-                    prims.push(this.drawRect(binPrimitive.Rect, binPrimitive.Color, () => runInAction(toggleFilter)));
-                    prims.push(this.drawRect(binPrimitive.MarginRect, StyleConstants.MARGIN_BARS_COLOR, () => runInAction(toggleFilter)));
-                });
-            }
-        }
-        return prims;
-    }
-
     render() {
-        trace();
-        if (!this.binPrimitives || !this.VisualBinRanges.length) {
-            return (null);
-        }
-
+        let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>";
+        let xaxislines = this.xaxislines;
+        let yaxislines = this.yaxislines;
         return (
             <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(${-this.props.doc.GetNumber(KeyStore.Width, 0) / 2}px, ${-this.props.doc.GetNumber(KeyStore.Height, 0) / 2}px)` }}>
-                        {this.xaxislines}
-                        {this.yaxislines}
-                        {this.binPrimitives}
-                        <div className="histogrambox-xaxislabel">{this.HistoOp!.X.AttributeModel.DisplayName}</div>
+                        {xaxislines}
+                        {yaxislines}
+                        <HistogramBoxPrimitives HistoBox={this} />
+                        <div className="histogrambox-xaxislabel">{label}</div>
                     </div>
                 }
             </Measure>
         )
     }
-}
-
-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;
-}
-
-export class HistogramBinPrimitiveCollection {
-    private static TOLERANCE: number = 0.0001;
-
-    private _histoBox: HistogramBox;
-    private get histoOp() { return this._histoBox.HistoOp!; }
-    private get histoResult() { return this.histoOp.Result as HistogramResult; }
-    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
-    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
-
-    constructor(key: string, histoBox: HistogramBox) {
-        this._histoBox = histoBox;
-        let bin = this.histoResult.bins![key];
-
-        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
-        var orderedBrushes = new Array<Brush>();
-        orderedBrushes.push(this.histoResult.brushes![0]);
-        orderedBrushes.push(this.histoResult.brushes![overlapBrushIndex]);
-        for (var b = 0; b < this.histoResult.brushes!.length; b++) {
-            var brush = this.histoResult.brushes![b];
-            if (brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex) {
-                orderedBrushes.push(brush);
-            }
-        }
-        var binBrushMaxAxis = this.getBinBrushAxisRange(bin, orderedBrushes, this.histoOp.Normalization); // X= 0, Y = 1
-
-        var brushFactorSum: number = 0;
-        for (var b = 0; b < orderedBrushes.length; b++) {
-            var brush = orderedBrushes[b];
-            var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, brush.brushIndex!);
-            var doubleRes = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
-            var unNormalizedValue = (doubleRes != null && doubleRes.hasResult) ? doubleRes.result : null;
-            if (unNormalizedValue)
-                switch (histoBox.ChartType) {
-                    case ChartType.VerticalBar:
-                        this.createVerticalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!); // X = 0, Y = 1, NOne = -1
-                        break;
-                    case ChartType.HorizontalBar:
-                        this.createHorizontalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!);
-                        break;
-                    case ChartType.SinglePoint:
-                        this.createSinlgePointChartBinPrimitives(bin, brush, unNormalizedValue, histoBox.SizeConverter!);
-                        break;
-                    case ChartType.HeatMap:
-                        var normalizedValue = (unNormalizedValue - histoBox.MinValue) / (Math.abs((histoBox.MaxValue - histoBox.MinValue)) < HistogramBinPrimitiveCollection.TOLERANCE ?
-                            unNormalizedValue : histoBox.MaxValue - histoBox.MinValue);
-                        brushFactorSum = this.createHeatmapBinPrimitives(bin, brush, unNormalizedValue, brushFactorSum, normalizedValue, histoBox.SizeConverter!);
-                }
-        }
-
-        // adjust brush rects (stacking or not)
-        var sum: number = 0;
-        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
-        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
-        var count: number = filteredBinPrims.length;
-        filteredBinPrims.map(fbp => {
-            if (histoBox.ChartType == ChartType.VerticalBar) {
-                if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height);
-                    sum += fbp.Rect.height;
-                }
-                if (this.histoOp.Y.AggregateFunction == AggregateFunction.Avg) {
-                    var w = fbp.Rect.width / 2.0;
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / count, fbp.Rect.height);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
-                    sum += fbp.Rect.width;
-                }
-            }
-            else if (histoBox.ChartType == ChartType.HorizontalBar) {
-                if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
-                    sum += fbp.Rect.width;
-                }
-                if (this.histoOp.X.AggregateFunction == AggregateFunction.Avg) {
-                    var h = fbp.Rect.height / 2.0;
-                    fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / count);
-                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
-                    sum += fbp.Rect.height;
-                }
-            }
-        });
-        this.BinPrimitives = this.BinPrimitives.reverse();
-        var f = this.BinPrimitives.filter(b => b.BrushIndex == allBrushIndex);
-        this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY;
-    }
-    private getBinBrushAxisRange(bin: Bin, brushes: Array<Brush>, axis: number): number {
-        var binBrushMaxAxis = Number.MIN_VALUE;
-        brushes.forEach((Brush) => {
-            var maxAggregateKey = ModelHelpers.CreateAggregateKey(axis === 0 ? this.histoOp.Y : this.histoOp.X, this.histoResult, Brush.brushIndex!);
-            var aggResult = ModelHelpers.GetAggregateResult(bin, maxAggregateKey) as DoubleValueAggregateResult;
-            if (aggResult != null) {
-                if (aggResult.result! > binBrushMaxAxis)
-                    binBrushMaxAxis = aggResult.result!;
-            }
-        });
-        return binBrushMaxAxis;
-    }
-    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, brushFactorSum: number, normalizedValue: number, sizeConverter: SizeConverter): number {
-
-        var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult));
-        var allUnNormalizedValue = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
-
-        var tx = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0]);
-        var xFrom = sizeConverter.DataToScreenX(tx);
-        var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(tx));
-
-        var ty = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
-        var yFrom = sizeConverter.DataToScreenY(ty);
-        var yTo = sizeConverter.DataToScreenY(this._histoBox.VisualBinRanges[1].AddStep(ty));
-
-        var returnBrushFactorSum = brushFactorSum;
-        if (allUnNormalizedValue.hasResult) {
-            var brushFactor = (unNormalizedValue / allUnNormalizedValue.result!);
-            returnBrushFactorSum += brushFactor;
-            returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0);
-
-            var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo);
-            var ratio = (tempRect.width / tempRect.height);
-            var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum));
-            var newWidth = newHeight * ratio;
-
-            xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0);
-            yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0);
-            xTo = (xFrom + newWidth);
-            yFrom = (yTo + newHeight);
-        }
-        var alpha = 0.0;
-        var color = this.baseColorFromBrush(brush);
-        var lerpColor = LABColor.Lerp(
-            LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR),
-            LABColor.FromColor(color),
-            (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha)));
-        var dataColor = LABColor.ToColor(lerpColor);
-
-        this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
-        return returnBrushFactorSum;
-    }
-
-    private createSinlgePointChartBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, sizeConverter: SizeConverter): void {
-        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
-        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
-
-        var xValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
-        if (!xValue.hasResult)
-            return;
-        var xFrom = sizeConverter.DataToScreenX(xValue.result!) - 5;
-        var xTo = sizeConverter.DataToScreenX(xValue.result!) + 5;
-
-        var yValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;;
-        if (!yValue.hasResult)
-            return;
-        var yFrom = sizeConverter.DataToScreenY(yValue.result!) + 5;
-        var yTo = sizeConverter.DataToScreenY(yValue.result!);
-
-        this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
-    }
-
-    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
-        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
-        var marginParams = new MarginAggregateParameters();
-        marginParams.aggregateFunction = this.histoOp.Y.AggregateFunction;
-        var yMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult,
-            brush.brushIndex!, marginParams);
-        var dataValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;
-
-        if (dataValue != null && dataValue.hasResult) {
-            var yValue = normalization != 0 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[1];
-
-            var yFrom = sizeConverter.DataToScreenY(Math.min(0, yValue));
-            var yTo = sizeConverter.DataToScreenY(Math.max(0, yValue));;
-
-            var xValue = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0])!;
-            var xFrom = sizeConverter.DataToScreenX(xValue);
-            var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(xValue));
-
-            var marginResult = ModelHelpers.GetAggregateResult(bin, yMarginAggregateKey) as MarginAggregateResult;
-            var yMarginAbsolute = !marginResult ? 0 : marginResult.absolutMargin!;
-            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
-                sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
-                sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
-
-            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[1] + 0.4, dataValue.result!);
-        }
-    }
-
-    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
-        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
-        var marginParams = new MarginAggregateParameters();
-        marginParams.aggregateFunction = this.histoOp.X.AggregateFunction;
-        var xMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult,
-            brush.brushIndex!, marginParams);
-        var dataValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
-
-        if (dataValue != null && dataValue.hasResult) {
-            var xValue = normalization != 1 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[0];
-            var xFrom = sizeConverter.DataToScreenX(Math.min(0, xValue));
-            var xTo = sizeConverter.DataToScreenX(Math.max(0, xValue));
-
-            var yValue = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
-            var yFrom = yValue;
-            var yTo = this._histoBox.VisualBinRanges[1].AddStep(yValue);
-
-            var marginResult = ModelHelpers.GetAggregateResult(bin, xMarginAggregateKey) as MarginAggregateResult;
-            var xMarginAbsolute = sizeConverter.IsSmall || !marginResult ? 0 : marginResult.absolutMargin!;
-
-            var marginRect = new PIXIRectangle(sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                yTo + (yFrom - yTo) / 2.0 - 1,
-                sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
-                2.0);
-
-            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
-                this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[0] + 0.4, dataValue.result!);
-        }
-    }
-
-    private createBinPrimitive(bin: Bin, brush: Brush, marginRect: PIXIRectangle,
-        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
-        // hitgeom todo
-
-        var binPrimitive = new HistogramBinPrimitive(
-            {
-                Rect: new PIXIRectangle(
-                    xFrom,
-                    yTo,
-                    xTo - xFrom,
-                    yFrom - yTo),
-                MarginRect: marginRect,
-                MarginPercentage: marginPercentage,
-                BrushIndex: brush.brushIndex,
-                Color: color,
-                Opacity: opacity,
-                DataValue: dataValue
-            });
-        this.BinPrimitives.push(binPrimitive);
-    }
-
-    private baseColorFromBrush(brush: Brush): number {
-        var baseColor: number = StyleConstants.HIGHLIGHT_COLOR;
-        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
-            baseColor = StyleConstants.HIGHLIGHT_COLOR;
-        }
-        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
-            baseColor = StyleConstants.OVERLAP_COLOR;
-        }
-        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
-            baseColor = 0x00ff00;
-        }
-        else {
-            if (this._histoBox.HistoOp!.BrushColors.length > 0) {
-                baseColor = this._histoBox.HistoOp!.BrushColors[brush.brushIndex! % this._histoBox.HistoOp!.BrushColors.length];
-            }
-            else {
-                baseColor = StyleConstants.HIGHLIGHT_COLOR;
-            }
-        }
-        return baseColor;
-    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx
new file mode 100644
index 000000000..2f4a553b7
--- /dev/null
+++ b/src/client/views/nodes/HistogramBoxPrimitives.tsx
@@ -0,0 +1,373 @@
+import React = require("react")
+import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
+import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+import { LABColor } from '../../northstar/utils/LABcolor';
+import { PIXIRectangle } from "../../northstar/utils/MathUtil";
+import { SizeConverter } from "../../northstar/utils/SizeConverter";
+import { StyleConstants } from "../../northstar/utils/StyleContants";
+import "./HistogramBox.scss";
+import { HistogramBox } from "./HistogramBox";
+import { computed, runInAction, observable, trace } from "mobx";
+import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
+import { Utils as DashUtils } from '../../../Utils';
+import { observer } from "mobx-react";
+
+
+export interface HistogramBoxPrimitivesProps {
+    HistoBox: HistogramBox;
+}
+
+@observer
+export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitivesProps> {
+    @observable _selectedPrims: HistogramBinPrimitive[] = [];
+
+    @computed
+    get selectedPrimitives() {
+        return this._selectedPrims.map((bp) => this.drawBorder(bp.Rect, StyleConstants.OPERATOR_BACKGROUND_COLOR));
+    }
+    @computed
+    get binPrimitives() {
+        if (!this.props.HistoBox.HistoOp || !(this.props.HistoBox.HistoOp.Result instanceof HistogramResult) || !this.props.HistoBox.SizeConverter)
+            return (null);
+        let prims: JSX.Element[] = [];
+        let allBrushIndex = ModelHelpers.AllBrushIndex(this.props.HistoBox.HistoOp.Result);
+        for (let key in this.props.HistoBox.HistoOp.Result.bins) {
+            if (this.props.HistoBox.HistoOp.Result.bins.hasOwnProperty(key)) {
+                let drawPrims = new HistogramBinPrimitiveCollection(key, this.props.HistoBox);
+                let filterModel = ModelHelpers.GetBinFilterModel(this.props.HistoBox.HistoOp.Result.bins![key], allBrushIndex, this.props.HistoBox.HistoOp.Result, this.props.HistoBox.HistoOp.X, this.props.HistoBox.HistoOp.Y);
+
+                this.props.HistoBox.HitTargets.setValue(drawPrims.HitGeom, filterModel);
+
+                drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(binPrimitive => {
+                    let toggleFilter = () => {
+                        if ([filterModel].filter(h => ArrayUtil.Contains(this.props.HistoBox.HistoOp!.FilterModels, h)).length > 0) {
+                            let bp = ArrayUtil.FirstOrDefault<HistogramBinPrimitive>(drawPrims.BinPrimitives, (bp: HistogramBinPrimitive) => bp.BrushIndex == allBrushIndex);
+                            if (bp && bp.DataValue) {
+                                this._selectedPrims.splice(this._selectedPrims.indexOf(bp), 1);
+                            }
+                            this.props.HistoBox.HistoOp!.RemoveFilterModels([filterModel]);
+                        }
+                        else {
+                            let bp = ArrayUtil.FirstOrDefault<HistogramBinPrimitive>(drawPrims.BinPrimitives, (bp: HistogramBinPrimitive) => bp.BrushIndex == allBrushIndex);
+                            if (bp && bp.DataValue) {
+                                this._selectedPrims.push(bp!);
+                            }
+                            this.props.HistoBox.HistoOp!.AddFilterModels([filterModel]);
+                        }
+                    }
+                    prims.push(this.drawRect(binPrimitive.Rect, binPrimitive.Color, () => runInAction(toggleFilter)));
+                    prims.push(this.drawRect(binPrimitive.MarginRect, StyleConstants.MARGIN_BARS_COLOR, () => runInAction(toggleFilter)));
+                });
+            }
+        }
+        return prims;
+    }
+    drawBorder(r: PIXIRectangle, color: number) {
+        return <div key={DashUtils.GenerateGuid()} className="histogramboxprimitive-border"
+            style={{
+                position: "absolute",
+                transform: `translate(${r.x}px,${r.y}px)`,
+                width: `${r.width - 1}`,
+                height: `${r.height}`,
+                border: "1px",
+                borderStyle: "solid",
+                pointerEvents: "none",
+                borderColor: `${LABColor.RGBtoHexString(color)}`
+            }}
+        />
+    }
+
+    drawRect(r: PIXIRectangle, color: number, tapHandler: () => void) {
+        return <div key={DashUtils.GenerateGuid()} onPointerDown={tapHandler}
+            style={{
+                position: "absolute",
+                transform: `translate(${r.x}px,${r.y}px)`,
+                width: `${r.width - 1}`,
+                height: `${r.height}`,
+                background: `${LABColor.RGBtoHexString(color)}`
+            }}
+        />
+    }
+    render() {
+        return <div>
+            {this.binPrimitives}
+            {this.selectedPrimitives}
+        </div>
+    }
+}
+
+
+class HistogramBinPrimitive {
+    constructor(init?: Partial<HistogramBinPrimitive>) {
+        Object.assign(this, init);
+    }
+    public DataValue: number = 0;
+    public Rect: PIXIRectangle = PIXIRectangle.EMPTY;
+    public MarginRect: PIXIRectangle = PIXIRectangle.EMPTY;
+    public MarginPercentage: number = 0;
+    public Color: number = StyleConstants.WARNING_COLOR;
+    public Opacity: number = 1;
+    public BrushIndex: number = 0;
+}
+
+export class HistogramBinPrimitiveCollection {
+    private static TOLERANCE: number = 0.0001;
+
+    private _histoBox: HistogramBox;
+    private get histoOp() { return this._histoBox.HistoOp!; }
+    private get histoResult() { return this.histoOp.Result as HistogramResult; }
+    public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
+    public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
+
+    constructor(key: string, histoBox: HistogramBox) {
+        this._histoBox = histoBox;
+        let bin = this.histoResult.bins![key];
+
+        var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
+        var orderedBrushes = new Array<Brush>();
+        orderedBrushes.push(this.histoResult.brushes![0]);
+        orderedBrushes.push(this.histoResult.brushes![overlapBrushIndex]);
+        for (var b = 0; b < this.histoResult.brushes!.length; b++) {
+            var brush = this.histoResult.brushes![b];
+            if (brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex) {
+                orderedBrushes.push(brush);
+            }
+        }
+        var binBrushMaxAxis = this.getBinBrushAxisRange(bin, orderedBrushes, this.histoOp.Normalization); // X= 0, Y = 1
+
+        var brushFactorSum: number = 0;
+        for (var b = 0; b < orderedBrushes.length; b++) {
+            var brush = orderedBrushes[b];
+            var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, brush.brushIndex!);
+            var doubleRes = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+            var unNormalizedValue = (doubleRes != null && doubleRes.hasResult) ? doubleRes.result : null;
+            if (unNormalizedValue)
+                switch (histoBox.ChartType) {
+                    case ChartType.VerticalBar:
+                        this.createVerticalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!); // X = 0, Y = 1, NOne = -1
+                        break;
+                    case ChartType.HorizontalBar:
+                        this.createHorizontalBarChartBinPrimitives(bin, brush, binBrushMaxAxis, this.histoOp.Normalization, histoBox.SizeConverter!);
+                        break;
+                    case ChartType.SinglePoint:
+                        this.createSinlgePointChartBinPrimitives(bin, brush, unNormalizedValue, histoBox.SizeConverter!);
+                        break;
+                    case ChartType.HeatMap:
+                        var normalizedValue = (unNormalizedValue - histoBox.MinValue) / (Math.abs((histoBox.MaxValue - histoBox.MinValue)) < HistogramBinPrimitiveCollection.TOLERANCE ?
+                            unNormalizedValue : histoBox.MaxValue - histoBox.MinValue);
+                        brushFactorSum = this.createHeatmapBinPrimitives(bin, brush, unNormalizedValue, brushFactorSum, normalizedValue, histoBox.SizeConverter!);
+                }
+        }
+
+        // adjust brush rects (stacking or not)
+        var sum: number = 0;
+        var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
+        var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
+        var count: number = filteredBinPrims.length;
+        filteredBinPrims.map(fbp => {
+            if (histoBox.ChartType == ChartType.VerticalBar) {
+                if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
+                    fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height);
+                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height);
+                    sum += fbp.Rect.height;
+                }
+                if (this.histoOp.Y.AggregateFunction == AggregateFunction.Avg) {
+                    var w = fbp.Rect.width / 2.0;
+                    fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / count, fbp.Rect.height);
+                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
+                    sum += fbp.Rect.width;
+                }
+            }
+            else if (histoBox.ChartType == ChartType.HorizontalBar) {
+                if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
+                    fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height);
+                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
+                    sum += fbp.Rect.width;
+                }
+                if (this.histoOp.X.AggregateFunction == AggregateFunction.Avg) {
+                    var h = fbp.Rect.height / 2.0;
+                    fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / count);
+                    fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
+                    sum += fbp.Rect.height;
+                }
+            }
+        });
+        this.BinPrimitives = this.BinPrimitives.reverse();
+        var f = this.BinPrimitives.filter(b => b.BrushIndex == allBrushIndex);
+        this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY;
+    }
+    private getBinBrushAxisRange(bin: Bin, brushes: Array<Brush>, axis: number): number {
+        var binBrushMaxAxis = Number.MIN_VALUE;
+        brushes.forEach((Brush) => {
+            var maxAggregateKey = ModelHelpers.CreateAggregateKey(axis === 0 ? this.histoOp.Y : this.histoOp.X, this.histoResult, Brush.brushIndex!);
+            var aggResult = ModelHelpers.GetAggregateResult(bin, maxAggregateKey) as DoubleValueAggregateResult;
+            if (aggResult != null) {
+                if (aggResult.result! > binBrushMaxAxis)
+                    binBrushMaxAxis = aggResult.result!;
+            }
+        });
+        return binBrushMaxAxis;
+    }
+    private createHeatmapBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, brushFactorSum: number, normalizedValue: number, sizeConverter: SizeConverter): number {
+
+        var valueAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.V, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult));
+        var allUnNormalizedValue = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+
+        var tx = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0]);
+        var xFrom = sizeConverter.DataToScreenX(tx);
+        var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(tx));
+
+        var ty = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
+        var yFrom = sizeConverter.DataToScreenY(ty);
+        var yTo = sizeConverter.DataToScreenY(this._histoBox.VisualBinRanges[1].AddStep(ty));
+
+        var returnBrushFactorSum = brushFactorSum;
+        if (allUnNormalizedValue.hasResult) {
+            var brushFactor = (unNormalizedValue / allUnNormalizedValue.result!);
+            returnBrushFactorSum += brushFactor;
+            returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0);
+
+            var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo);
+            var ratio = (tempRect.width / tempRect.height);
+            var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum));
+            var newWidth = newHeight * ratio;
+
+            xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0);
+            yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0);
+            xTo = (xFrom + newWidth);
+            yFrom = (yTo + newHeight);
+        }
+        var alpha = 0.0;
+        var color = this.baseColorFromBrush(brush);
+        var lerpColor = LABColor.Lerp(
+            LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR),
+            LABColor.FromColor(color),
+            (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha)));
+        var dataColor = LABColor.ToColor(lerpColor);
+
+        this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
+        return returnBrushFactorSum;
+    }
+
+    private createSinlgePointChartBinPrimitives(bin: Bin, brush: Brush, unNormalizedValue: number, sizeConverter: SizeConverter): void {
+        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
+        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
+
+        var xValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
+        if (!xValue.hasResult)
+            return;
+        var xFrom = sizeConverter.DataToScreenX(xValue.result!) - 5;
+        var xTo = sizeConverter.DataToScreenX(xValue.result!) + 5;
+
+        var yValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;;
+        if (!yValue.hasResult)
+            return;
+        var yFrom = sizeConverter.DataToScreenY(yValue.result!) + 5;
+        var yTo = sizeConverter.DataToScreenY(yValue.result!);
+
+        this.createBinPrimitive(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+    }
+
+    private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
+        var yAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!);
+        var marginParams = new MarginAggregateParameters();
+        marginParams.aggregateFunction = this.histoOp.Y.AggregateFunction;
+        var yMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult,
+            brush.brushIndex!, marginParams);
+        var dataValue = ModelHelpers.GetAggregateResult(bin, yAggregateKey) as DoubleValueAggregateResult;
+
+        if (dataValue != null && dataValue.hasResult) {
+            var yValue = normalization != 0 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[1];
+
+            var yFrom = sizeConverter.DataToScreenY(Math.min(0, yValue));
+            var yTo = sizeConverter.DataToScreenY(Math.max(0, yValue));;
+
+            var xValue = this._histoBox.VisualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![0])!;
+            var xFrom = sizeConverter.DataToScreenX(xValue);
+            var xTo = sizeConverter.DataToScreenX(this._histoBox.VisualBinRanges[0].AddStep(xValue));
+
+            var marginResult = ModelHelpers.GetAggregateResult(bin, yMarginAggregateKey) as MarginAggregateResult;
+            var yMarginAbsolute = !marginResult ? 0 : marginResult.absolutMargin!;
+            var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
+                sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
+                sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
+
+            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[1] + 0.4, dataValue.result!);
+        }
+    }
+
+    private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number, sizeConverter: SizeConverter): void {
+        var xAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!);
+        var marginParams = new MarginAggregateParameters();
+        marginParams.aggregateFunction = this.histoOp.X.AggregateFunction;
+        var xMarginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult,
+            brush.brushIndex!, marginParams);
+        var dataValue = ModelHelpers.GetAggregateResult(bin, xAggregateKey) as DoubleValueAggregateResult;
+
+        if (dataValue != null && dataValue.hasResult) {
+            var xValue = normalization != 1 || binBrushMaxAxis == 0 ? dataValue.result! : (dataValue.result! - 0) / (binBrushMaxAxis - 0) * sizeConverter.DataRanges[0];
+            var xFrom = sizeConverter.DataToScreenX(Math.min(0, xValue));
+            var xTo = sizeConverter.DataToScreenX(Math.max(0, xValue));
+
+            var yValue = this._histoBox.VisualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![1]);
+            var yFrom = yValue;
+            var yTo = this._histoBox.VisualBinRanges[1].AddStep(yValue);
+
+            var marginResult = ModelHelpers.GetAggregateResult(bin, xMarginAggregateKey) as MarginAggregateResult;
+            var xMarginAbsolute = sizeConverter.IsSmall || !marginResult ? 0 : marginResult.absolutMargin!;
+
+            var marginRect = new PIXIRectangle(sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                yTo + (yFrom - yTo) / 2.0 - 1,
+                sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+                2.0);
+
+            this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+                this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / sizeConverter.DataRanges[0] + 0.4, dataValue.result!);
+        }
+    }
+
+    private createBinPrimitive(bin: Bin, brush: Brush, marginRect: PIXIRectangle,
+        marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
+        // hitgeom todo
+
+        var binPrimitive = new HistogramBinPrimitive(
+            {
+                Rect: new PIXIRectangle(
+                    xFrom,
+                    yTo,
+                    xTo - xFrom,
+                    yFrom - yTo),
+                MarginRect: marginRect,
+                MarginPercentage: marginPercentage,
+                BrushIndex: brush.brushIndex,
+                Color: color,
+                Opacity: opacity,
+                DataValue: dataValue
+            });
+        this.BinPrimitives.push(binPrimitive);
+    }
+
+    private baseColorFromBrush(brush: Brush): number {
+        var baseColor: number = StyleConstants.HIGHLIGHT_COLOR;
+        if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
+            baseColor = StyleConstants.HIGHLIGHT_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+            baseColor = StyleConstants.OVERLAP_COLOR;
+        }
+        else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
+            baseColor = 0x00ff00;
+        }
+        else {
+            if (this._histoBox.HistoOp!.BrushColors.length > 0) {
+                baseColor = this._histoBox.HistoOp!.BrushColors[brush.brushIndex! % this._histoBox.HistoOp!.BrushColors.length];
+            }
+            else {
+                baseColor = StyleConstants.HIGHLIGHT_COLOR;
+            }
+        }
+        return baseColor;
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 92aaac7cd78b9287d7f6ee85e73b3cad1c42d80c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 26 Mar 2019 16:44:53 -0400
Subject: fixed histos in docking pane and not selecting bars when dragging.

---
 src/client/views/collections/CollectionFreeFormView.tsx | 4 ++--
 src/client/views/nodes/DocumentContentsView.tsx         | 1 +
 src/client/views/nodes/HistogramBox.tsx                 | 4 +++-
 src/client/views/nodes/HistogramBoxPrimitives.tsx       | 2 +-
 4 files changed, 7 insertions(+), 4 deletions(-)

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 53fe969fe..1eab3e475 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -279,13 +279,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
     get backgroundView() {
         return !this.backgroundLayout ? (null) :
             (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.BackgroundLayout} isSelected={() => false} select={() => { }} />);
+                layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
     }
     @computed
     get overlayView() {
         return !this.overlayLayout ? (null) :
             (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.OverlayLayout} isSelected={() => false} select={() => { }} />);
+                layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
     }
 
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 40990b76a..f48be2047 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -35,6 +35,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
     @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
 
+
     CreateBindings(): JsxBindings {
         let bindings: JsxBindings = { ...this.props, };
         for (const key of this.layoutKeys) {
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
index 20a022227..4d7922c1b 100644
--- a/src/client/views/nodes/HistogramBox.tsx
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -150,10 +150,12 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>";
         let xaxislines = this.xaxislines;
         let yaxislines = this.yaxislines;
+        var h = this.props.isTopMost ? this._panelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
+        var w = this.props.isTopMost ? this._panelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
         return (
             <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(${-this.props.doc.GetNumber(KeyStore.Width, 0) / 2}px, ${-this.props.doc.GetNumber(KeyStore.Height, 0) / 2}px)` }}>
+                    <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-w / 2}px, ${-h / 2}px)` }}>
                         {xaxislines}
                         {yaxislines}
                         <HistogramBoxPrimitives HistoBox={this} />
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx
index 2f4a553b7..a8ada99a4 100644
--- a/src/client/views/nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/views/nodes/HistogramBoxPrimitives.tsx
@@ -79,7 +79,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitiv
     }
 
     drawRect(r: PIXIRectangle, color: number, tapHandler: () => void) {
-        return <div key={DashUtils.GenerateGuid()} onPointerDown={tapHandler}
+        return <div key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
             style={{
                 position: "absolute",
                 transform: `translate(${r.x}px,${r.y}px)`,
-- 
cgit v1.2.3-70-g09d2


From c3eea60fa586e8ff9cf59497c041044fd0143bb1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 28 Mar 2019 21:03:17 -0400
Subject: organized collectionfreeformview.  fixed issues with showing links

---
 src/client/util/DocumentManager.ts                 |  24 +-
 .../views/collections/CollectionFreeFormView.scss  | 116 -----
 .../views/collections/CollectionFreeFormView.tsx   | 525 ---------------------
 src/client/views/collections/CollectionView.tsx    |   7 +-
 src/client/views/collections/MarqueeView.scss      |   8 -
 src/client/views/collections/MarqueeView.tsx       | 175 -------
 src/client/views/collections/PreviewCursor.scss    |  18 -
 src/client/views/collections/PreviewCursor.tsx     |  76 ---
 .../CollectionFreeFormLinkView.scss                |   6 +
 .../CollectionFreeFormLinkView.tsx                 |  37 ++
 .../CollectionFreeFormLinksView.scss               |   7 +
 .../CollectionFreeFormLinksView.tsx                |  56 +++
 .../collectionFreeForm/CollectionFreeFormView.scss |  95 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 416 ++++++++++++++++
 .../collectionFreeForm/MarqueeView.scss            |   8 +
 .../collections/collectionFreeForm/MarqueeView.tsx | 175 +++++++
 .../collectionFreeForm/PreviewCursor.scss          |  18 +
 .../collectionFreeForm/PreviewCursor.tsx           |  76 +++
 src/client/views/nodes/DocumentContentsView.tsx    |   2 +-
 19 files changed, 924 insertions(+), 921 deletions(-)
 delete mode 100644 src/client/views/collections/CollectionFreeFormView.scss
 delete mode 100644 src/client/views/collections/CollectionFreeFormView.tsx
 delete mode 100644 src/client/views/collections/MarqueeView.scss
 delete mode 100644 src/client/views/collections/MarqueeView.tsx
 delete mode 100644 src/client/views/collections/PreviewCursor.scss
 delete mode 100644 src/client/views/collections/PreviewCursor.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/MarqueeView.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/MarqueeView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/PreviewCursor.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/PreviewCursor.tsx

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 28624dba8..bf59fbb43 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,10 +1,11 @@
 import React = require('react')
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import { observable, action, computed } from 'mobx';
 import { Document } from "../../fields/Document"
 import { DocumentView } from '../views/nodes/DocumentView';
 import { KeyStore } from '../../fields/KeyStore';
 import { FieldWaiting } from '../../fields/Field';
+import { ListField } from '../../fields/ListField';
 
 
 export class DocumentManager {
@@ -74,4 +75,25 @@ export class DocumentManager {
 
         return (toReturn);
     }
+
+    @computed
+    public get LinkedDocumentViews() {
+        return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
+            let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField);
+            if (linksList && linksList != FieldWaiting && linksList.Data.length) {
+                pairs.push(...linksList.Data.reduce((pairs, link) => {
+                    if (link instanceof Document) {
+                        let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
+                        if (linkToDoc && linkToDoc != FieldWaiting) {
+                            DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
+                                pairs.push({ a: dv, b: docView1, l: link })
+                            })
+                        }
+                    }
+                    return pairs;
+                }, [] as { a: DocumentView, b: DocumentView, l: Document }[]));
+            }
+            return pairs;
+        }, [] as { a: DocumentView, b: DocumentView, l: Document }[]);
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
deleted file mode 100644
index 7012ce6b9..000000000
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ /dev/null
@@ -1,116 +0,0 @@
-@import "../global_variables";
-
-.collectionfreeformview-linkLine {
-    stroke: black;
-    stroke-width: 3;
-    transform: translate(10000px,10000px);
-    pointer-events: all;
-}
-.collectionfreeformview-svgCanvas{
-  transform: translate(-10000px,-10000px);
-  position: absolute;
-  width: 20000px; 
-  height: 20000px;
-  pointer-events: none;
-}
-.collectionfreeformview-container {
-  .collectionfreeformview > .jsx-parser {
-    position: absolute;
-    height: 100%;
-    width: 100%;
-  }
-
-  .inking-canvas {
-    transform-origin: 50000px 50000px;
-  }
-  //nested freeform views
-  // .collectionfreeformview-container {
-    // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
-    //   linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
-    // background-size: 30px 30px;
-  // }
-
-  box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
-  border: 0px solid $light-color-secondary;
-  border-radius: $border-radius;
-  box-sizing: border-box;
-  position: relative;
-  overflow: hidden;
-  top: 0;
-  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 {
-    position: absolute;
-    height: 100%;
-  }
-  .formattedTextBox-cont {
-    background: $light-color-secondary;
-  }
-
-  .inking-canvas {
-    transform-origin: 50000px 50000px;
-  }
-
-  opacity: 0.99;
-  border: 0px solid transparent;
-  border-radius: $border-radius;
-  box-sizing: border-box;
-  position:relative;
-  overflow: hidden;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  .collectionfreeformview {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    .inking-canvas {
-      transform-origin: 50000px 50000px;
-    }
-    .formattedTextBox-cont {
-        background:yellow;
-    }
-  }
-}
-
-// selection border...?
-.border {
-  border-style: solid;
-  box-sizing: border-box;
-  width: 98%;
-  height: 98%;
-  border-radius: $border-radius;
-}
-
-//this is an animation for the blinking cursor!
-@keyframes blink {
-  0% {
-    opacity: 0;
-  }
-  49% {
-    opacity: 0;
-  }
-  50% {
-    opacity: 1;
-  }
-}
-
-#prevCursor {
-  animation: blink 1s infinite;
-}
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
deleted file mode 100644
index 44f4eadaa..000000000
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ /dev/null
@@ -1,525 +0,0 @@
-import { action, computed, observable, reaction, runInAction, trace, untracked, IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting, Field, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { TextField } from "../../../fields/TextField";
-import { DragManager } from "../../util/DragManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { InkingCanvas } from "../InkingCanvas";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "../nodes/DocumentContentsView";
-import { DocumentViewProps, DocumentView } from "../nodes/DocumentView";
-import "./CollectionFreeFormView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { CollectionViewBase, CollectionViewProps } from "./CollectionViewBase";
-import { MarqueeView } from "./MarqueeView";
-import { PreviewCursor } from "./PreviewCursor";
-import React = require("react");
-import v5 = require("uuid/v5");
-import { DocumentManager } from "../../util/DocumentManager";
-import { Utils } from "../../../Utils";
-import { Server } from "../../Server";
-import { AverageAggregateParameters } from "../../northstar/model/idea/idea";
-
-@observer
-export class CollectionFreeFormView extends CollectionViewBase {
-    public _canvasRef = React.createRef<HTMLDivElement>();
-    private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
-
-    public addLiveTextBox = (newBox: Document) => {
-        // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
-        this._selectOnLoaded = newBox.Id;
-        //set text to be the typed key and get focus on text box
-        this.props.addDocument(newBox, false);
-        //remove cursor from screen
-        this.PreviewCursorVisible = false;
-    }
-
-    public selectDocuments = (docs: Document[]) => {
-        this.props.CollectionView.SelectedDocs.length = 0;
-        docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
-    }
-
-    public getActiveDocuments = () => {
-        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
-        let active: Document[] = [];
-        if (lvalue && lvalue != FieldWaiting) {
-            lvalue.Data.map(doc => {
-                var page = doc.GetNumber(KeyStore.Page, -1);
-                if (page == curPage || page == -1) {
-                    active.push(doc);
-                }
-            })
-        }
-
-        return active;
-    }
-
-    //determines whether the blinking cursor for indicating whether a text will be made on key down is visible
-    @observable public PreviewCursorVisible: boolean = false;
-    @observable public MarqueeVisible = false;
-    @observable public DownX: number = 0;
-    @observable public DownY: number = 0;
-    @observable private _lastX: number = 0;
-    @observable private _lastY: number = 0;
-
-    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
-    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
-    @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
-    @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
-    @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
-    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
-    @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
-    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
-    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
-
-    @undoBatch
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
-            if (de.data instanceof DragManager.DocumentDragData) {
-                let screenX = de.x - (de.data.xOffset as number || 0);
-                let screenY = de.y - (de.data.yOffset as number || 0);
-                const [x, y] = this.getTransform().transformPoint(screenX, screenY);
-                de.data.droppedDocument.SetNumber(KeyStore.X, x);
-                de.data.droppedDocument.SetNumber(KeyStore.Y, y);
-                if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) {
-                    de.data.droppedDocument.SetNumber(KeyStore.Width, 300);
-                    de.data.droppedDocument.SetNumber(KeyStore.Height, 300);
-                }
-                this.bringToFront(de.data.droppedDocument);
-            }
-            return true;
-        }
-        return false;
-    }
-
-
-    @action
-    cleanupInteractions = () => {
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        this.MarqueeVisible = false;
-    }
-
-    @action
-    onPointerDown = (e: React.PointerEvent): void => {
-        this.PreviewCursorVisible = false;
-        if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-            this._lastX = this.DownX = e.pageX;
-            this._lastY = this.DownY = e.pageY;
-        }
-    }
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        e.stopPropagation();
-
-        if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) {
-            //show preview text cursor on tap
-            this.PreviewCursorVisible = true;
-            //select is not already selected
-            if (!this.props.isSelected()) {
-                this.props.select(false);
-            }
-        }
-        this.cleanupInteractions();
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble && this.props.active()) {
-            if (e.buttons == 1 && !e.altKey && !e.metaKey) {
-                this.MarqueeVisible = true;
-            }
-            if (this.MarqueeVisible) {
-                e.stopPropagation();
-                e.preventDefault();
-            }
-            else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
-                let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
-                let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
-                let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-                this.SetPan(x - dx, y - dy);
-                this._lastX = e.pageX;
-                this._lastY = e.pageY;
-                e.stopPropagation();
-                e.preventDefault();
-            }
-        }
-    }
-
-    @action
-    onPointerWheel = (e: React.WheelEvent): void => {
-        this.props.select(false);
-        e.stopPropagation();
-        e.preventDefault();
-        let coefficient = 1000;
-
-        if (e.ctrlKey) {
-            var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
-            var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
-            const coefficient = 1000;
-            let deltaScale = (1 - (e.deltaY / coefficient));
-            this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale);
-            this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale);
-            e.stopPropagation();
-            e.preventDefault();
-        } else {
-            // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
-            // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
-            let transform = this.getTransform();
-
-            let deltaScale = (1 - (e.deltaY / coefficient));
-            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
-                deltaScale = 1 / this.zoomScaling;
-            let [x, y] = transform.transformPoint(e.clientX, e.clientY);
-
-            let localTransform = this.getLocalTransform()
-            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
-            // console.log(localTransform)
-
-            this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
-            this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
-        }
-    }
-
-    @action
-    private SetPan(panX: number, panY: number) {
-        var x1 = this.getLocalTransform().inverse().Scale;
-        const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
-        const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
-        this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
-        this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
-    }
-
-    @action
-    onDrop = (e: React.DragEvent): void => {
-        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
-        super.onDrop(e, { x: pt[0], y: pt[1] });
-    }
-
-    onDragOver = (): void => {
-    }
-
-    @action
-    bringToFront(doc: Document) {
-        const { fieldKey: fieldKey, Document: Document } = this.props;
-
-        const value: Document[] = Document.GetList<Document>(fieldKey, []).slice();
-        value.sort((doc1, doc2) => {
-            if (doc1 === doc) {
-                return 1;
-            }
-            if (doc2 === doc) {
-                return -1;
-            }
-            return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
-        }).map((doc, index) => {
-            doc.SetNumber(KeyStore.ZIndex, index + 1)
-        });
-    }
-
-    @computed get backgroundLayout(): string | undefined {
-        let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
-        if (field && field !== FieldWaiting) {
-            return field.Data;
-        }
-    }
-    @computed get overlayLayout(): string | undefined {
-        let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField);
-        if (field && field !== FieldWaiting) {
-            return field.Data;
-        }
-    }
-
-    focusDocument = (doc: Document) => {
-        let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
-        let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
-        this.SetPan(x, y);
-        this.props.focus(this.props.Document);
-    }
-
-    getDocumentViewProps(document: Document): DocumentViewProps {
-        return {
-            Document: document,
-            AddDocument: this.props.addDocument,
-            RemoveDocument: this.props.removeDocument,
-            ScreenToLocalTransform: this.getTransform,
-            isTopMost: false,
-            SelectOnLoad: document.Id == this._selectOnLoaded,
-            PanelWidth: document.Width,
-            PanelHeight: document.Height,
-            ContentScaling: this.noScaling,
-            ContainingCollectionView: this.props.CollectionView,
-            focus: this.focusDocument
-        }
-    }
-
-    @computed
-    get views() {
-        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
-        if (lvalue && lvalue != FieldWaiting) {
-            return lvalue.Data.map(doc => {
-                if (!doc) return null;
-                var page = doc.GetNumber(KeyStore.Page, 0);
-                return (page != curPage && page != 0) ? (null) :
-                    (<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
-            })
-        }
-        return null;
-    }
-
-    @computed
-    get backgroundView() {
-        return !this.backgroundLayout ? (null) :
-            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
-    }
-    @computed
-    get overlayView() {
-        return !this.overlayLayout ? (null) :
-            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
-                layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
-    }
-
-    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
-    getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
-    getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
-    noScaling = () => 1;
-
-    //when focus is lost, this will remove the preview cursor
-    @action
-    onBlur = (): void => {
-        this.PreviewCursorVisible = false;
-    }
-
-    private crosshairs?: HTMLCanvasElement;
-    drawCrosshairs = (backgroundColor: string) => {
-        if (this.crosshairs) {
-            let c = this.crosshairs;
-            let ctx = c.getContext('2d');
-            if (ctx) {
-                ctx.fillStyle = backgroundColor;
-                ctx.fillRect(0, 0, 20, 20);
-
-                ctx.fillStyle = "black";
-                ctx.lineWidth = 0.5;
-
-                ctx.beginPath();
-
-                ctx.moveTo(10, 0);
-                ctx.lineTo(10, 8);
-
-                ctx.moveTo(10, 20);
-                ctx.lineTo(10, 12);
-
-                ctx.moveTo(0, 10);
-                ctx.lineTo(8, 10);
-
-                ctx.moveTo(20, 10);
-                ctx.lineTo(12, 10);
-
-                ctx.stroke();
-
-                // ctx.font = "10px Arial";
-                // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
-            }
-        }
-    }
-
-    render() {
-        let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
-
-        const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
-        const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
-        // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
-        // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
-        // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY));
-
-        return (
-            <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
-                onPointerDown={this.onPointerDown}
-                onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
-                onWheel={this.onPointerWheel}
-                onDrop={this.onDrop.bind(this)}
-                onDragOver={this.onDragOver}
-                onBlur={this.onBlur}
-                style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }}
-                tabIndex={0}
-                ref={this.createDropTarget}>
-                <div className="collectionfreeformview"
-                    style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
-                    ref={this._canvasRef}>
-                    {this.backgroundView}
-                    <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
-                    <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
-                    {this.views}
-                    <LinksView {...this.props} />
-                    {super.getCursors().map(entry => {
-                        if (entry.Data.length > 0) {
-                            let id = entry.Data[0][0];
-                            let email = entry.Data[0][1];
-                            let point = entry.Data[1];
-                            this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
-                            return (
-                                <div
-                                    key={id}
-                                    style={{
-                                        position: "absolute",
-                                        transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
-                                        zIndex: 10000,
-                                        transformOrigin: 'center center',
-                                    }}
-                                >
-                                    <canvas
-                                        ref={(el) => { if (el) this.crosshairs = el }}
-                                        width={20}
-                                        height={20}
-                                        style={{
-                                            position: 'absolute',
-                                            width: "20px",
-                                            height: "20px",
-                                            opacity: 0.5,
-                                            borderRadius: "50%",
-                                            border: "2px solid black"
-                                        }}
-                                    />
-                                    <p
-                                        style={{
-                                            fontSize: 14,
-                                            color: "black",
-                                            // fontStyle: "italic",
-                                            marginLeft: -12,
-                                            marginTop: 4
-                                        }}
-                                    >{email[0].toUpperCase()}</p>
-                                </div>
-                            );
-                        }
-                    })}
-                </div>
-                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
-                    addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
-                    getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
-                {this.overlayView}
-
-            </div>
-        );
-    }
-}
-
-@observer
-export class LinksView extends React.Component<CollectionViewProps> {
-    @observable _connections: { a: Document, b: Document, l: Document }[] = [];
-
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
-    componentDidMount() {
-        this._reactionDisposer = reaction(() => DocumentManager.Instance.DocumentViews.map(dv => [
-            dv.props.Document.Get(KeyStore.AnnotationOn, false),
-            dv.props.Document.Get(KeyStore.LinkedFromDocs, false),
-            dv.props.Document.Get(KeyStore.LinkedToDocs, false)
-        ]), () => runInAction(() => this._connections = this.findPairs()), { fireImmediately: true });
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this._reactionDisposer = undefined;
-    }
-
-    filtered(views: DocumentView[]) {
-        return views.filter(sv =>
-            sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document
-        );
-    }
-
-    findPairs() {
-        return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
-            untracked(() => {
-                let srcViews = [dv];
-                let srcAnnot = dv.props.Document.GetT(KeyStore.AnnotationOn, Document);
-                if (srcAnnot && srcAnnot != FieldWaiting && srcAnnot instanceof Document) {
-                    srcViews = DocumentManager.Instance.getDocumentViews(srcAnnot.GetPrototype() as Document)
-                }
-                let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField);
-                if (linksList && linksList != FieldWaiting && linksList.Data.length) {
-                    pairs.push(...linksList.Data.reduce((pairs, link) => {
-                        if (link instanceof Document) {
-                            let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
-                            if (linkToDoc && linkToDoc != FieldWaiting) {
-                                DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                                    let targetViews = [docView1];
-                                    let docAnnot = docView1.props.Document.GetT(KeyStore.AnnotationOn, Document);
-                                    if (docAnnot && docAnnot != FieldWaiting && docAnnot instanceof Document) {
-                                        targetViews = DocumentManager.Instance.getDocumentViews(docAnnot.GetPrototype() as Document)
-                                    }
-                                    this.filtered(targetViews).map(tv => this.filtered(srcViews).map(sv =>
-                                        pairs.push({ a: sv.props.Document, b: tv.props.Document, l: link })))
-                                })
-                            }
-                        }
-                        return pairs;
-                    }, [] as { a: Document, b: Document, l: Document }[]));
-                }
-            })
-            return pairs;
-        }, [] as { a: Document, b: Document, l: Document }[]);
-    }
-
-    onPointerDown(e: React.PointerEvent) {
-        let line = (e.nativeEvent as any).path[0];
-        line.style.stroke = "red";
-        Server.GetField(line.id, action((f: Opt<Field>) => {
-            if (f instanceof Document) {
-                console.log(f.Title);
-            }
-        }));
-    }
-
-    @computed
-    get uniqueConnections() {
-        return this._connections.reduce((y, t) => {
-            if (!y.reduce((found, yi) => {
-                if (yi.a == t.a && yi.b == t.b) {
-                    if (yi.l != t.l)
-                        yi.n++;
-                    return true;
-                }
-                return found;
-            }, false)) {
-                y.push({ a: t.a, b: t.b, l: t.l, n: 1 });
-            }
-            return y
-        }, [] as { a: Document, b: Document, l: Document, n: number }[]);
-    }
-
-    render() {
-        if (!this._connections.length)
-            return (null);
-        return <svg className="collectionfreeformview-svgCanvas">
-            {this.uniqueConnections.map(c => {
-                let x1 = c.a.GetNumber(KeyStore.X, 0) + c.a.GetNumber(KeyStore.Width, 0) / 2;
-                let y1 = c.a.GetNumber(KeyStore.Y, 0) + c.a.GetNumber(KeyStore.Height, 0) / 2;
-                let x2 = c.b.GetNumber(KeyStore.X, 0) + c.b.GetNumber(KeyStore.Width, 0) / 2;
-                let y2 = c.b.GetNumber(KeyStore.Y, 0) + c.b.GetNumber(KeyStore.Height, 0) / 2;
-                return (
-                    <line key={Utils.GenerateGuid()} id={c.l.Id} className="collectionfreeformview-linkLine" onPointerDown={this.onPointerDown}
-                        style={{ strokeWidth: `${c.n * 5}` }}
-                        x1={`${x1}`} y1={`${y1}`}
-                        x2={`${x2}`} y2={`${y2}`} />
-                )
-            })}
-        </svg>
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c804085df..a1498e0ae 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,7 +7,7 @@ import { ContextMenu } from "../ContextMenu";
 import React = require("react");
 import { KeyStore } from "../../../fields/KeyStore";
 import { NumberField } from "../../../fields/NumberField";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionDockingView } from "./CollectionDockingView";
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionViewProps } from "./CollectionViewBase";
@@ -104,6 +104,11 @@ export class CollectionView extends React.Component<CollectionViewProps> {
                 break;
             }
         }
+        doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => {
+            if (annotationOn == props.Document) {
+                doc.Set(KeyStore.AnnotationOn, undefined, true);
+            }
+        })
 
         if (index !== -1) {
             value.splice(index, 1)
diff --git a/src/client/views/collections/MarqueeView.scss b/src/client/views/collections/MarqueeView.scss
deleted file mode 100644
index 6d9a79344..000000000
--- a/src/client/views/collections/MarqueeView.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-
-.marqueeView {
-    border-style: dashed;
-    box-sizing: border-box;
-    position: absolute;
-    border-width: 1px;
-    border-color: black;
-}
\ No newline at end of file
diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
deleted file mode 100644
index b48ad9c64..000000000
--- a/src/client/views/collections/MarqueeView.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { Documents } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Transform } from "../../util/Transform";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
-import "./MarqueeView.scss";
-import React = require("react");
-import { InkField, StrokeData } from "../../../fields/InkField";
-import { Utils } from "../../../Utils";
-import { InkingCanvas } from "../InkingCanvas";
-
-interface MarqueeViewProps {
-    getMarqueeTransform: () => Transform;
-    getTransform: () => Transform;
-    container: CollectionFreeFormView;
-    addDocument: (doc: Document, allowDuplicates: false) => boolean;
-    activeDocuments: () => Document[];
-    selectDocuments: (docs: Document[]) => void;
-    removeDocument: (doc: Document) => boolean;
-}
-
-@observer
-export class MarqueeView extends React.Component<MarqueeViewProps>
-{
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
-    @observable _lastX: number = 0;
-    @observable _lastY: number = 0;
-    @observable _downX: number = 0;
-    @observable _downY: number = 0;
-
-    componentDidMount() {
-        this._reactionDisposer = reaction(
-            () => this.props.container.MarqueeVisible,
-            (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY))
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this.cleanupInteractions();
-    }
-
-    @action
-    cleanupInteractions = () => {
-        document.removeEventListener("pointermove", this.onPointerMove, true)
-        document.removeEventListener("pointerup", this.onPointerUp, true);
-        document.removeEventListener("keydown", this.marqueeCommand, true);
-    }
-
-    @action
-    onPointerDown = (visible: boolean, downX: number, downY: number): void => {
-        if (visible) {
-            this._downX = this._lastX = downX;
-            this._downY = this._lastY = downY;
-            document.addEventListener("pointermove", this.onPointerMove, true)
-            document.addEventListener("pointerup", this.onPointerUp, true);
-            document.addEventListener("keydown", this.marqueeCommand, true);
-        }
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        this._lastX = e.pageX;
-        this._lastY = e.pageY;
-    }
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        this.cleanupInteractions();
-        if (!e.shiftKey) {
-            SelectionManager.DeselectAll();
-        }
-        this.props.selectDocuments(this.marqueeSelect());
-    }
-
-    intersectRect(r1: { left: number, top: number, width: number, height: number },
-        r2: { left: number, top: number, width: number, height: number }) {
-        return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
-    }
-
-    get Bounds() {
-        let left = this._downX < this._lastX ? this._downX : this._lastX;
-        let top = this._downY < this._lastY ? this._downY : this._lastY;
-        let topLeft = this.props.getTransform().transformPoint(left, top);
-        let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
-    }
-
-    @action
-    marqueeCommand = (e: KeyboardEvent) => {
-        if (e.key == "Backspace" || e.key == "Delete") {
-            this.marqueeSelect().map(d => this.props.removeDocument(d));
-            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
-            this.cleanupInteractions();
-        }
-        if (e.key == "c") {
-            let bounds = this.Bounds;
-            let selected = this.marqueeSelect().map(d => {
-                this.props.removeDocument(d);
-                d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
-                d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
-                d.SetNumber(KeyStore.Page, 0);
-                d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
-                return d;
-            });
-            let liftedInk = this.marqueeInkSelect(true);
-            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
-            //setTimeout(() => {
-            let newCollection = Documents.FreeformDocument(selected, {
-                x: bounds.left,
-                y: bounds.top,
-                panx: 0,
-                pany: 0,
-                width: bounds.width,
-                height: bounds.height,
-                backgroundColor: "Transparent",
-                ink: liftedInk,
-                title: "a nested collection"
-            });
-            this.props.addDocument(newCollection, false);
-            // }, 100);
-            this.cleanupInteractions();
-        }
-    }
-    marqueeInkSelect(select: boolean) {
-        let selRect = this.Bounds;
-        let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
-        let centerShiftY = 0 - (selRect.top + selRect.height / 2);
-        let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-        if (ink && ink != FieldWaiting && ink.Data) {
-            let idata = new Map();
-            ink.Data.forEach((value: StrokeData, key: string, map: any) => {
-                let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
-                if (inside && select) {
-                    idata.set(key,
-                        {
-                            pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
-                            color: value.color,
-                            width: value.width,
-                            tool: value.tool,
-                            page: -1
-                        });
-                } else if (!inside && !select) {
-                    idata.set(key, value);
-                }
-            })
-            return idata;
-        }
-    }
-
-    marqueeSelect() {
-        let selRect = this.Bounds;
-        let selection: Document[] = [];
-        this.props.activeDocuments().map(doc => {
-            var x = doc.GetNumber(KeyStore.X, 0);
-            var y = doc.GetNumber(KeyStore.Y, 0);
-            var w = doc.GetNumber(KeyStore.Width, 0);
-            var h = doc.GetNumber(KeyStore.Height, 0);
-            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
-                selection.push(doc)
-        })
-        return selection;
-    }
-
-    render() {
-        let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
-        let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
-        return (!this.props.container.MarqueeVisible ? (null) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />);
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/PreviewCursor.scss b/src/client/views/collections/PreviewCursor.scss
deleted file mode 100644
index a797411f6..000000000
--- a/src/client/views/collections/PreviewCursor.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-
-.previewCursor {
-    color: black;
-    position: absolute;
-    transform-origin: left top;
-    pointer-events: none;
-}
-
-//this is an animation for the blinking cursor!
-@keyframes blink {
-	0% {opacity: 0}
-	49%{opacity: 0}
-	50% {opacity: 1}
-}
-
-#previewCursor {
-    animation: blink 1s infinite;
-}
\ No newline at end of file
diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx
deleted file mode 100644
index 8ae59a83a..000000000
--- a/src/client/views/collections/PreviewCursor.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { action, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { Opt } from "../../../fields/Field";
-import { Documents } from "../../documents/Documents";
-import { Transform } from "../../util/Transform";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
-import "./PreviewCursor.scss";
-import React = require("react");
-
-
-export interface PreviewCursorProps {
-    getTransform: () => Transform;
-    container: CollectionFreeFormView;
-    addLiveTextDocument: (doc: Document) => void;
-}
-
-@observer
-export class PreviewCursor extends React.Component<PreviewCursorProps>  {
-    private _reactionDisposer: Opt<IReactionDisposer>;
-
-    @observable _lastX: number = 0;
-    @observable _lastY: number = 0;
-
-    componentDidMount() {
-        this._reactionDisposer = reaction(
-            () => this.props.container.PreviewCursorVisible,
-            (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY))
-    }
-    componentWillUnmount() {
-        if (this._reactionDisposer) {
-            this._reactionDisposer();
-        }
-        this.cleanupInteractions();
-    }
-
-
-    @action
-    cleanupInteractions = () => {
-        document.removeEventListener("keypress", this.onKeyPress, false);
-    }
-
-    @action
-    onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
-        if (visible) {
-            document.addEventListener("keypress", this.onKeyPress, false);
-            this._lastX = downX;
-            this._lastY = downY;
-        } else
-            this.cleanupInteractions();
-    }
-
-    @action
-    onKeyPress = (e: KeyboardEvent) => {
-        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
-        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
-        // the keyPress here.
-        //if not these keys, make a textbox if preview cursor is active!
-        if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
-            //make textbox and add it to this collection
-            let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
-            let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
-            this.props.addLiveTextDocument(newBox);
-            e.stopPropagation();
-        }
-    }
-
-    render() {
-        //get local position and place cursor there!
-        let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
-        return (
-            !this.props.container.PreviewCursorVisible ? (null) :
-                <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
-
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
new file mode 100644
index 000000000..3b2f79be1
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -0,0 +1,6 @@
+.collectionfreeformlinkview-linkLine {
+    stroke: black;
+    stroke-width: 3;
+    transform: translate(10000px,10000px);
+    pointer-events: all;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
new file mode 100644
index 000000000..e84f0c5ad
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -0,0 +1,37 @@
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { Utils } from "../../../../Utils";
+import "./CollectionFreeFormLinkView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+
+export interface CollectionFreeFormLinkViewProps {
+    A: Document;
+    B: Document;
+    LinkDocs: Document[];
+}
+
+@observer
+export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
+
+    onPointerDown = (e: React.PointerEvent) => {
+        this.props.LinkDocs.map(l =>
+            console.log("Link:" + l.Title));
+    }
+    render() {
+        let l = this.props.LinkDocs;
+        let a = this.props.A;
+        let b = this.props.B;
+        let x1 = a.GetNumber(KeyStore.X, 0) + a.GetNumber(KeyStore.Width, 0) / 2;
+        let y1 = a.GetNumber(KeyStore.Y, 0) + a.GetNumber(KeyStore.Height, 0) / 2;
+        let x2 = b.GetNumber(KeyStore.X, 0) + b.GetNumber(KeyStore.Width, 0) / 2;
+        let y2 = b.GetNumber(KeyStore.Y, 0) + b.GetNumber(KeyStore.Height, 0) / 2;
+        return (
+            <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
+                style={{ strokeWidth: `${l.length * 5}` }}
+                x1={`${x1}`} y1={`${y1}`}
+                x2={`${x2}`} y2={`${y2}`} />
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
new file mode 100644
index 000000000..9af0df7f5
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -0,0 +1,7 @@
+.collectionfreeformlinksview-svgCanvas{
+    transform: translate(-10000px,-10000px);
+    position: absolute;
+    width: 20000px; 
+    height: 20000px;
+    pointer-events: none;
+  }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
new file mode 100644
index 000000000..4f28f43eb
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -0,0 +1,56 @@
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { Utils } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DocumentView } from "../../nodes/DocumentView";
+import { CollectionViewProps } from "../CollectionViewBase";
+import "./CollectionFreeFormLinksView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
+
+@observer
+export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
+
+    documentAnchors(view: DocumentView) {
+        let equalViews = [view];
+        let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
+        if (containerDoc && containerDoc != FieldWaiting && containerDoc instanceof Document) {
+            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype() as Document)
+        }
+        return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document);
+    }
+
+    @computed
+    get uniqueConnections() {
+        return DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
+            let srcViews = this.documentAnchors(connection.a);
+            let targetViews = this.documentAnchors(connection.b);
+            let possiblePairs: { a: Document, b: Document, }[] = [];
+            srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
+            possiblePairs.map(possiblePair => {
+                if (!drawnPairs.reduce((found, drawnPair) => {
+                    let match = (possiblePair.a == drawnPair.a && possiblePair.b == drawnPair.b);
+                    if (match) {
+                        if (!drawnPair.l.reduce((found, link) => found || link.Id == connection.l.Id, false))
+                            drawnPair.l.push(connection.l);
+                    }
+                    return match || found;
+                }, false)) {
+                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] as Document[] });
+                }
+            })
+            return drawnPairs
+        }, [] as { a: Document, b: Document, l: Document[] }[]);
+    }
+
+    render() {
+        return (
+            <svg className="collectionfreeformlinksview-svgCanvas">
+                {this.uniqueConnections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />)}
+            </svg>);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
new file mode 100644
index 000000000..9c5e98005
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -0,0 +1,95 @@
+@import "../../global_variables";
+
+.collectionfreeformview-container {
+  .collectionfreeformview > .jsx-parser {
+    position: absolute;
+    height: 100%;
+    width: 100%;
+  }
+
+  .inking-canvas {
+    transform-origin: 50000px 50000px;
+  }
+  //nested freeform views
+  // .collectionfreeformview-container {
+    // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
+    //   linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
+    // background-size: 30px 30px;
+  // }
+
+  box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+  border: 0px solid $light-color-secondary;
+  border-radius: $border-radius;
+  box-sizing: border-box;
+  position: relative;
+  overflow: hidden;
+  top: 0;
+  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 {
+    position: absolute;
+    height: 100%;
+  }
+  .formattedTextBox-cont {
+    background: $light-color-secondary;
+  }
+
+  .inking-canvas {
+    transform-origin: 50000px 50000px;
+  }
+
+  opacity: 0.99;
+  border: 0px solid transparent;
+  border-radius: $border-radius;
+  box-sizing: border-box;
+  position:relative;
+  overflow: hidden;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  .collectionfreeformview {
+    .formattedTextBox-cont {
+        background:yellow;
+    }
+  }
+}
+
+// selection border...?
+.border {
+  border-style: solid;
+  box-sizing: border-box;
+  width: 98%;
+  height: 98%;
+  border-radius: $border-radius;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+  0% {
+    opacity: 0;
+  }
+  49% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 1;
+  }
+}
+
+#prevCursor {
+  animation: blink 1s infinite;
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
new file mode 100644
index 000000000..1f90b0d46
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -0,0 +1,416 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { ListField } from "../../../../fields/ListField";
+import { TextField } from "../../../../fields/TextField";
+import { DragManager } from "../../../util/DragManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { InkingCanvas } from "../../InkingCanvas";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { DocumentContentsView } from "../../nodes/DocumentContentsView";
+import { DocumentViewProps } from "../../nodes/DocumentView";
+import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
+import { CollectionViewBase } from "../CollectionViewBase";
+import { MarqueeView } from "./MarqueeView";
+import { PreviewCursor } from "./PreviewCursor";
+import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import "./CollectionFreeFormView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+
+@observer
+export class CollectionFreeFormView extends CollectionViewBase {
+    public _canvasRef = React.createRef<HTMLDivElement>();
+    private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
+
+    public addLiveTextBox = (newBox: Document) => {
+        // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
+        this._selectOnLoaded = newBox.Id;
+        //set text to be the typed key and get focus on text box
+        this.props.addDocument(newBox, false);
+        //remove cursor from screen
+        this.PreviewCursorVisible = false;
+    }
+
+    public selectDocuments = (docs: Document[]) => {
+        this.props.CollectionView.SelectedDocs.length = 0;
+        docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
+    }
+
+    public getActiveDocuments = () => {
+        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
+        let active: Document[] = [];
+        if (lvalue && lvalue != FieldWaiting) {
+            lvalue.Data.map(doc => {
+                var page = doc.GetNumber(KeyStore.Page, -1);
+                if (page == curPage || page == -1) {
+                    active.push(doc);
+                }
+            })
+        }
+
+        return active;
+    }
+
+    //determines whether the blinking cursor for indicating whether a text will be made on key down is visible
+    @observable public PreviewCursorVisible: boolean = false;
+    @observable public MarqueeVisible = false;
+    @observable public DownX: number = 0;
+    @observable public DownY: number = 0;
+    @observable private _lastX: number = 0;
+    @observable private _lastY: number = 0;
+
+    @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
+    @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
+    @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
+    @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+    @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+    @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
+    @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; }  // shift so pan position is at center of window for non-overlay collections
+    @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
+
+    @undoBatch
+    @action
+    drop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.drop(e, de)) {
+            if (de.data instanceof DragManager.DocumentDragData) {
+                let screenX = de.x - (de.data.xOffset as number || 0);
+                let screenY = de.y - (de.data.yOffset as number || 0);
+                const [x, y] = this.getTransform().transformPoint(screenX, screenY);
+                de.data.droppedDocument.SetNumber(KeyStore.X, x);
+                de.data.droppedDocument.SetNumber(KeyStore.Y, y);
+                if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) {
+                    de.data.droppedDocument.SetNumber(KeyStore.Width, 300);
+                    de.data.droppedDocument.SetNumber(KeyStore.Height, 300);
+                }
+                this.bringToFront(de.data.droppedDocument);
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        this.MarqueeVisible = false;
+    }
+
+    @action
+    onPointerDown = (e: React.PointerEvent): void => {
+        this.PreviewCursorVisible = false;
+        if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
+            document.removeEventListener("pointermove", this.onPointerMove);
+            document.addEventListener("pointermove", this.onPointerMove);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointerup", this.onPointerUp);
+            this._lastX = this.DownX = e.pageX;
+            this._lastY = this.DownY = e.pageY;
+        }
+    }
+
+    @action
+    onPointerUp = (e: PointerEvent): void => {
+        e.stopPropagation();
+
+        if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) {
+            //show preview text cursor on tap
+            this.PreviewCursorVisible = true;
+            //select is not already selected
+            if (!this.props.isSelected()) {
+                this.props.select(false);
+            }
+        }
+        this.cleanupInteractions();
+    }
+
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        if (!e.cancelBubble && this.props.active()) {
+            if (e.buttons == 1 && !e.altKey && !e.metaKey) {
+                this.MarqueeVisible = true;
+            }
+            if (this.MarqueeVisible) {
+                e.stopPropagation();
+                e.preventDefault();
+            }
+            else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
+                let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
+                let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
+                let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+                this.SetPan(x - dx, y - dy);
+                this._lastX = e.pageX;
+                this._lastY = e.pageY;
+                e.stopPropagation();
+                e.preventDefault();
+            }
+        }
+    }
+
+    @action
+    onPointerWheel = (e: React.WheelEvent): void => {
+        this.props.select(false);
+        e.stopPropagation();
+        e.preventDefault();
+        let coefficient = 1000;
+
+        if (e.ctrlKey) {
+            var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+            var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+            const coefficient = 1000;
+            let deltaScale = (1 - (e.deltaY / coefficient));
+            this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale);
+            this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale);
+            e.stopPropagation();
+            e.preventDefault();
+        } else {
+            // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
+            // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
+            let transform = this.getTransform();
+
+            let deltaScale = (1 - (e.deltaY / coefficient));
+            if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
+                deltaScale = 1 / this.zoomScaling;
+            let [x, y] = transform.transformPoint(e.clientX, e.clientY);
+
+            let localTransform = this.getLocalTransform()
+            localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
+            // console.log(localTransform)
+
+            this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
+            this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
+        }
+    }
+
+    @action
+    private SetPan(panX: number, panY: number) {
+        var x1 = this.getLocalTransform().inverse().Scale;
+        const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
+        const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
+        this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
+        this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
+    }
+
+    @action
+    onDrop = (e: React.DragEvent): void => {
+        var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
+        super.onDrop(e, { x: pt[0], y: pt[1] });
+    }
+
+    onDragOver = (): void => {
+    }
+
+    @action
+    bringToFront(doc: Document) {
+        const { fieldKey: fieldKey, Document: Document } = this.props;
+
+        const value: Document[] = Document.GetList<Document>(fieldKey, []).slice();
+        value.sort((doc1, doc2) => {
+            if (doc1 === doc) {
+                return 1;
+            }
+            if (doc2 === doc) {
+                return -1;
+            }
+            return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
+        }).map((doc, index) => {
+            doc.SetNumber(KeyStore.ZIndex, index + 1)
+        });
+    }
+
+    @computed get backgroundLayout(): string | undefined {
+        let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
+        if (field && field !== FieldWaiting) {
+            return field.Data;
+        }
+    }
+    @computed get overlayLayout(): string | undefined {
+        let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField);
+        if (field && field !== FieldWaiting) {
+            return field.Data;
+        }
+    }
+
+    focusDocument = (doc: Document) => {
+        let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
+        let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
+        this.SetPan(x, y);
+        this.props.focus(this.props.Document);
+    }
+
+    getDocumentViewProps(document: Document): DocumentViewProps {
+        return {
+            Document: document,
+            AddDocument: this.props.addDocument,
+            RemoveDocument: this.props.removeDocument,
+            ScreenToLocalTransform: this.getTransform,
+            isTopMost: false,
+            SelectOnLoad: document.Id == this._selectOnLoaded,
+            PanelWidth: document.Width,
+            PanelHeight: document.Height,
+            ContentScaling: this.noScaling,
+            ContainingCollectionView: this.props.CollectionView,
+            focus: this.focusDocument
+        }
+    }
+
+    @computed
+    get views() {
+        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+        const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
+        if (lvalue && lvalue != FieldWaiting) {
+            return lvalue.Data.map(doc => {
+                if (!doc) return null;
+                var page = doc.GetNumber(KeyStore.Page, 0);
+                return (page != curPage && page != 0) ? (null) :
+                    (<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+            })
+        }
+        return null;
+    }
+
+    @computed
+    get backgroundView() {
+        return !this.backgroundLayout ? (null) :
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
+                layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
+    }
+    @computed
+    get overlayView() {
+        return !this.overlayLayout ? (null) :
+            (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
+                layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
+    }
+
+    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
+    getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
+    getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
+    noScaling = () => 1;
+
+    //when focus is lost, this will remove the preview cursor
+    @action
+    onBlur = (): void => {
+        this.PreviewCursorVisible = false;
+    }
+
+    private crosshairs?: HTMLCanvasElement;
+    drawCrosshairs = (backgroundColor: string) => {
+        if (this.crosshairs) {
+            let c = this.crosshairs;
+            let ctx = c.getContext('2d');
+            if (ctx) {
+                ctx.fillStyle = backgroundColor;
+                ctx.fillRect(0, 0, 20, 20);
+
+                ctx.fillStyle = "black";
+                ctx.lineWidth = 0.5;
+
+                ctx.beginPath();
+
+                ctx.moveTo(10, 0);
+                ctx.lineTo(10, 8);
+
+                ctx.moveTo(10, 20);
+                ctx.lineTo(10, 12);
+
+                ctx.moveTo(0, 10);
+                ctx.lineTo(8, 10);
+
+                ctx.moveTo(20, 10);
+                ctx.lineTo(12, 10);
+
+                ctx.stroke();
+
+                // ctx.font = "10px Arial";
+                // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+            }
+        }
+    }
+
+    render() {
+        let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
+
+        const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
+        const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
+        // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
+        // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
+        // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY));
+
+        return (
+            <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
+                onPointerDown={this.onPointerDown}
+                onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
+                onWheel={this.onPointerWheel}
+                onDrop={this.onDrop.bind(this)}
+                onDragOver={this.onDragOver}
+                onBlur={this.onBlur}
+                style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }}
+                tabIndex={0}
+                ref={this.createDropTarget}>
+                <div className="collectionfreeformview"
+                    style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
+                    ref={this._canvasRef}>
+                    {this.backgroundView}
+                    <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
+                    <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
+                    {this.views}
+                    <CollectionFreeFormLinksView {...this.props} />
+                    {super.getCursors().map(entry => {
+                        if (entry.Data.length > 0) {
+                            let id = entry.Data[0][0];
+                            let email = entry.Data[0][1];
+                            let point = entry.Data[1];
+                            this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+                            return (
+                                <div
+                                    key={id}
+                                    style={{
+                                        position: "absolute",
+                                        transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
+                                        zIndex: 10000,
+                                        transformOrigin: 'center center',
+                                    }}
+                                >
+                                    <canvas
+                                        ref={(el) => { if (el) this.crosshairs = el }}
+                                        width={20}
+                                        height={20}
+                                        style={{
+                                            position: 'absolute',
+                                            width: "20px",
+                                            height: "20px",
+                                            opacity: 0.5,
+                                            borderRadius: "50%",
+                                            border: "2px solid black"
+                                        }}
+                                    />
+                                    <p
+                                        style={{
+                                            fontSize: 14,
+                                            color: "black",
+                                            // fontStyle: "italic",
+                                            marginLeft: -12,
+                                            marginTop: 4
+                                        }}
+                                    >{email[0].toUpperCase()}</p>
+                                </div>
+                            );
+                        }
+                    })}
+                </div>
+                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+                    addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+                    getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
+                {this.overlayView}
+
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
new file mode 100644
index 000000000..6d9a79344
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -0,0 +1,8 @@
+
+.marqueeView {
+    border-style: dashed;
+    box-sizing: border-box;
+    position: absolute;
+    border-width: 1px;
+    border-color: black;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
new file mode 100644
index 000000000..2692690dd
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -0,0 +1,175 @@
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting, Opt } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { Documents } from "../../../documents/Documents";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { Transform } from "../../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./MarqueeView.scss";
+import React = require("react");
+import { InkField, StrokeData } from "../../../../fields/InkField";
+import { Utils } from "../../../../Utils";
+import { InkingCanvas } from "../../InkingCanvas";
+
+interface MarqueeViewProps {
+    getMarqueeTransform: () => Transform;
+    getTransform: () => Transform;
+    container: CollectionFreeFormView;
+    addDocument: (doc: Document, allowDuplicates: false) => boolean;
+    activeDocuments: () => Document[];
+    selectDocuments: (docs: Document[]) => void;
+    removeDocument: (doc: Document) => boolean;
+}
+
+@observer
+export class MarqueeView extends React.Component<MarqueeViewProps>
+{
+    private _reactionDisposer: Opt<IReactionDisposer>;
+
+    @observable _lastX: number = 0;
+    @observable _lastY: number = 0;
+    @observable _downX: number = 0;
+    @observable _downY: number = 0;
+
+    componentDidMount() {
+        this._reactionDisposer = reaction(
+            () => this.props.container.MarqueeVisible,
+            (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY))
+    }
+    componentWillUnmount() {
+        if (this._reactionDisposer) {
+            this._reactionDisposer();
+        }
+        this.cleanupInteractions();
+    }
+
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("pointermove", this.onPointerMove, true)
+        document.removeEventListener("pointerup", this.onPointerUp, true);
+        document.removeEventListener("keydown", this.marqueeCommand, true);
+    }
+
+    @action
+    onPointerDown = (visible: boolean, downX: number, downY: number): void => {
+        if (visible) {
+            this._downX = this._lastX = downX;
+            this._downY = this._lastY = downY;
+            document.addEventListener("pointermove", this.onPointerMove, true)
+            document.addEventListener("pointerup", this.onPointerUp, true);
+            document.addEventListener("keydown", this.marqueeCommand, true);
+        }
+    }
+
+    @action
+    onPointerMove = (e: PointerEvent): void => {
+        this._lastX = e.pageX;
+        this._lastY = e.pageY;
+    }
+
+    @action
+    onPointerUp = (e: PointerEvent): void => {
+        this.cleanupInteractions();
+        if (!e.shiftKey) {
+            SelectionManager.DeselectAll();
+        }
+        this.props.selectDocuments(this.marqueeSelect());
+    }
+
+    intersectRect(r1: { left: number, top: number, width: number, height: number },
+        r2: { left: number, top: number, width: number, height: number }) {
+        return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+    }
+
+    get Bounds() {
+        let left = this._downX < this._lastX ? this._downX : this._lastX;
+        let top = this._downY < this._lastY ? this._downY : this._lastY;
+        let topLeft = this.props.getTransform().transformPoint(left, top);
+        let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+        return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
+    }
+
+    @action
+    marqueeCommand = (e: KeyboardEvent) => {
+        if (e.key == "Backspace" || e.key == "Delete") {
+            this.marqueeSelect().map(d => this.props.removeDocument(d));
+            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
+            this.cleanupInteractions();
+        }
+        if (e.key == "c") {
+            let bounds = this.Bounds;
+            let selected = this.marqueeSelect().map(d => {
+                this.props.removeDocument(d);
+                d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
+                d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
+                d.SetNumber(KeyStore.Page, 0);
+                d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
+                return d;
+            });
+            let liftedInk = this.marqueeInkSelect(true);
+            this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
+            //setTimeout(() => {
+            let newCollection = Documents.FreeformDocument(selected, {
+                x: bounds.left,
+                y: bounds.top,
+                panx: 0,
+                pany: 0,
+                width: bounds.width,
+                height: bounds.height,
+                backgroundColor: "Transparent",
+                ink: liftedInk,
+                title: "a nested collection"
+            });
+            this.props.addDocument(newCollection, false);
+            // }, 100);
+            this.cleanupInteractions();
+        }
+    }
+    marqueeInkSelect(select: boolean) {
+        let selRect = this.Bounds;
+        let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+        let centerShiftY = 0 - (selRect.top + selRect.height / 2);
+        let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
+        if (ink && ink != FieldWaiting && ink.Data) {
+            let idata = new Map();
+            ink.Data.forEach((value: StrokeData, key: string, map: any) => {
+                let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
+                if (inside && select) {
+                    idata.set(key,
+                        {
+                            pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
+                            color: value.color,
+                            width: value.width,
+                            tool: value.tool,
+                            page: -1
+                        });
+                } else if (!inside && !select) {
+                    idata.set(key, value);
+                }
+            })
+            return idata;
+        }
+    }
+
+    marqueeSelect() {
+        let selRect = this.Bounds;
+        let selection: Document[] = [];
+        this.props.activeDocuments().map(doc => {
+            var x = doc.GetNumber(KeyStore.X, 0);
+            var y = doc.GetNumber(KeyStore.Y, 0);
+            var w = doc.GetNumber(KeyStore.Width, 0);
+            var h = doc.GetNumber(KeyStore.Height, 0);
+            if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
+                selection.push(doc)
+        })
+        return selection;
+    }
+
+    render() {
+        let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+        let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+        return (!this.props.container.MarqueeVisible ? (null) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
new file mode 100644
index 000000000..a797411f6
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
@@ -0,0 +1,18 @@
+
+.previewCursor {
+    color: black;
+    position: absolute;
+    transform-origin: left top;
+    pointer-events: none;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+	0% {opacity: 0}
+	49%{opacity: 0}
+	50% {opacity: 1}
+}
+
+#previewCursor {
+    animation: blink 1s infinite;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
new file mode 100644
index 000000000..95364f04b
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -0,0 +1,76 @@
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { Opt } from "../../../../fields/Field";
+import { Documents } from "../../../documents/Documents";
+import { Transform } from "../../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./PreviewCursor.scss";
+import React = require("react");
+
+
+export interface PreviewCursorProps {
+    getTransform: () => Transform;
+    container: CollectionFreeFormView;
+    addLiveTextDocument: (doc: Document) => void;
+}
+
+@observer
+export class PreviewCursor extends React.Component<PreviewCursorProps>  {
+    private _reactionDisposer: Opt<IReactionDisposer>;
+
+    @observable _lastX: number = 0;
+    @observable _lastY: number = 0;
+
+    componentDidMount() {
+        this._reactionDisposer = reaction(
+            () => this.props.container.PreviewCursorVisible,
+            (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY))
+    }
+    componentWillUnmount() {
+        if (this._reactionDisposer) {
+            this._reactionDisposer();
+        }
+        this.cleanupInteractions();
+    }
+
+
+    @action
+    cleanupInteractions = () => {
+        document.removeEventListener("keypress", this.onKeyPress, false);
+    }
+
+    @action
+    onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
+        if (visible) {
+            document.addEventListener("keypress", this.onKeyPress, false);
+            this._lastX = downX;
+            this._lastY = downY;
+        } else
+            this.cleanupInteractions();
+    }
+
+    @action
+    onKeyPress = (e: KeyboardEvent) => {
+        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
+        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
+        // the keyPress here.
+        //if not these keys, make a textbox if preview cursor is active!
+        if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+            //make textbox and add it to this collection
+            let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+            let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
+            this.props.addLiveTextDocument(newBox);
+            e.stopPropagation();
+        }
+    }
+
+    render() {
+        //get local position and place cursor there!
+        let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+        return (
+            !this.props.container.PreviewCursorVisible ? (null) :
+                <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
+
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f48be2047..b54744337 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -5,7 +5,7 @@ import { Key } from "../../../fields/Key";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
-- 
cgit v1.2.3-70-g09d2


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.

---
 src/client/documents/Documents.ts                  |  13 +-
 src/client/northstar/core/filter/FilterModel.ts    |   2 +-
 src/client/northstar/dash-fields/HistogramField.ts |  64 ++++
 src/client/northstar/dash-nodes/HistogramBox.scss  |  34 ++
 src/client/northstar/dash-nodes/HistogramBox.tsx   | 161 ++++++++++
 .../dash-nodes/HistogramBoxPrimitives.scss         |  26 ++
 .../dash-nodes/HistogramBoxPrimitives.tsx          | 341 +++++++++++++++++++++
 .../dash-nodes/HistogramLabelPrimitives.scss       |  13 +
 .../dash-nodes/HistogramLabelPrimitives.tsx        |  78 +++++
 src/client/northstar/model/ModelHelpers.ts         |  18 +-
 .../model/binRanges/VisualBinRangeHelper.ts        |   6 +-
 src/client/northstar/operations/BaseOperation.ts   |   4 +-
 .../northstar/operations/HistogramOperation.ts     |  13 +-
 src/client/northstar/utils/SizeConverter.ts        |   6 +-
 src/client/views/Main.tsx                          |  39 +--
 src/client/views/nodes/DocumentContentsView.tsx    |   5 +-
 src/client/views/nodes/DocumentView.tsx            |   3 -
 src/client/views/nodes/HistogramBox.scss           |  22 --
 src/client/views/nodes/HistogramBox.tsx            | 105 -------
 src/client/views/nodes/HistogramBoxPrimitives.scss |  25 --
 src/client/views/nodes/HistogramBoxPrimitives.tsx  | 336 --------------------
 .../views/nodes/HistogramLabelPrimitives.scss      |  12 -
 .../views/nodes/HistogramLabelPrimitives.tsx       |  78 -----
 src/fields/HistogramField.ts                       |  59 ----
 src/fields/KeyStore.ts                             |   1 -
 src/server/ServerUtil.ts                           |   3 +-
 .../authentication/models/current_user_utils.ts    |  27 +-
 27 files changed, 789 insertions(+), 705 deletions(-)
 create mode 100644 src/client/northstar/dash-fields/HistogramField.ts
 create mode 100644 src/client/northstar/dash-nodes/HistogramBox.scss
 create mode 100644 src/client/northstar/dash-nodes/HistogramBox.tsx
 create mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
 create mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
 create mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
 create mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
 delete mode 100644 src/client/views/nodes/HistogramBox.scss
 delete mode 100644 src/client/views/nodes/HistogramBox.tsx
 delete mode 100644 src/client/views/nodes/HistogramBoxPrimitives.scss
 delete mode 100644 src/client/views/nodes/HistogramBoxPrimitives.tsx
 delete mode 100644 src/client/views/nodes/HistogramLabelPrimitives.scss
 delete mode 100644 src/client/views/nodes/HistogramLabelPrimitives.tsx
 delete mode 100644 src/fields/HistogramField.ts

(limited to 'src/client/views/nodes/DocumentContentsView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 837dfe815..663ccae61 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,6 +1,6 @@
 import { AudioField } from "../../fields/AudioField";
 import { Document } from "../../fields/Document";
-import { Field, FieldWaiting } from "../../fields/Field";
+import { Field } from "../../fields/Field";
 import { HtmlField } from "../../fields/HtmlField";
 import { ImageField } from "../../fields/ImageField";
 import { InkField, StrokeData } from "../../fields/InkField";
@@ -11,6 +11,9 @@ import { PDFField } from "../../fields/PDFField";
 import { TextField } from "../../fields/TextField";
 import { VideoField } from "../../fields/VideoField";
 import { WebField } from "../../fields/WebField";
+import { HistogramField } from "../northstar/dash-fields/HistogramField";
+import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
+import { HistogramOperation } from "../northstar/operations/HistogramOperation";
 import { Server } from "../Server";
 import { CollectionPDFView } from "../views/collections/CollectionPDFView";
 import { CollectionVideoView } from "../views/collections/CollectionVideoView";
@@ -22,10 +25,6 @@ import { KeyValueBox } from "../views/nodes/KeyValueBox";
 import { PDFBox } from "../views/nodes/PDFBox";
 import { VideoBox } from "../views/nodes/VideoBox";
 import { WebBox } from "../views/nodes/WebBox";
-import { HistogramBox } from "../views/nodes/HistogramBox";
-import { FieldView } from "../views/nodes/FieldView";
-import { HistogramField } from "../../fields/HistogramField";
-import { HistogramOperation } from "../northstar/operations/HistogramOperation";
 
 export interface DocumentOptions {
     x?: number;
@@ -44,7 +43,6 @@ export interface DocumentOptions {
     layoutKeys?: Key[];
     viewType?: number;
     backgroundColor?: string;
-    northstarSchema?: string;
 }
 
 export namespace Documents {
@@ -88,7 +86,6 @@ export namespace Documents {
         if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
         if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
         if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
-        if (options.northstarSchema !== undefined) { doc.SetText(KeyStore.NorthstarSchema, options.northstarSchema); }
         return doc;
     }
 
@@ -126,7 +123,7 @@ export namespace Documents {
     function GetHistogramPrototype(): Document {
         if (!histoProto) {
             histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"),
-                { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+                { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
             histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
         }
         return histoProto;
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index bc7938947..aee99d2b6 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -2,10 +2,10 @@ import { ValueComparison } from "./ValueComparision";
 import { Utils } from "../../utils/Utils";
 import { IBaseFilterProvider } from "./IBaseFilterProvider";
 import { FilterOperand } from "./FilterOperand";
-import { HistogramField } from "../../../../fields/HistogramField";
 import { KeyStore } from "../../../../fields/KeyStore";
 import { FieldWaiting } from "../../../../fields/Field";
 import { Document } from "../../../../fields/Document";
+import { HistogramField } from "../../dash-fields/HistogramField";
 
 export class FilterModel {
     public ValueComparisons: ValueComparison[];
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
new file mode 100644
index 000000000..00912c595
--- /dev/null
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -0,0 +1,64 @@
+import { BasicField } from "../../../fields/BasicField";
+import { Field, FieldId } from "../../../fields/Field";
+import { Types } from "../../../server/Message";
+import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
+import { action } from "mobx";
+import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
+import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { ArrayUtil } from "../utils/ArrayUtil";
+import { Schema } from "../model/idea/idea";
+
+
+export class HistogramField extends BasicField<HistogramOperation> {
+    constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) {
+        super(data ? data : HistogramOperation.Empty, save, id);
+    }
+
+    toString(): string {
+        return JSON.stringify(this.Data);
+    }
+
+    Copy(): Field {
+        return new HistogramField(this.Data);
+    }
+
+    ToScriptString(): string {
+        return `new HistogramField("${this.Data}")`;
+    }
+
+    ToJson(): { type: Types, data: string, _id: string } {
+        return {
+            type: Types.HistogramOp,
+            data: JSON.stringify(this.Data),
+            _id: this.Id
+        }
+    }
+
+    @action
+    static FromJson(id: string, data: any): HistogramField {
+        let jp = JSON.parse(data);
+        let X: AttributeTransformationModel | undefined;
+        let Y: AttributeTransformationModel | undefined;
+        let V: AttributeTransformationModel | undefined;
+
+        let schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName);
+        if (schema) {
+            CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+                if (attr.displayName == jp.X.AttributeModel.Attribute.DisplayName) {
+                    X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction);
+                }
+                if (attr.displayName == jp.Y.AttributeModel.Attribute.DisplayName) {
+                    Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction);
+                }
+                if (attr.displayName == jp.V.AttributeModel.Attribute.DisplayName) {
+                    V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
+                }
+            });
+            if (X && Y && V) {
+                return new HistogramField(new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization), id, false);
+            }
+        }
+        return new HistogramField(HistogramOperation.Empty, id, false);
+    }
+}
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
new file mode 100644
index 000000000..b11840a65
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -0,0 +1,34 @@
+.histogrambox-container {
+    padding: 0vw;
+    position: absolute;
+    text-align: center;
+    width: 100%;
+    height: 100%;
+    background: black;
+  }
+  .histogrambox-xaxislabel {
+    position:absolute;
+    width:100%;
+    text-align: center;
+    bottom:0;
+    background: lightgray;
+    font-size: 14;
+    font-weight: bold;
+  }
+  .histogrambox-yaxislabel {
+    position:absolute;
+    height:100%;
+    width: 25px;
+    bottom:0;
+    background: lightgray;
+  }
+  .histogrambox-yaxislabel-text {
+    position:absolute;
+    transform-origin: left;
+    transform: rotate(-90deg);
+    text-align: center;
+    font-size: 14;
+    font-weight: bold;
+    bottom: calc(50% - 25px);
+  }
+  
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
new file mode 100644
index 000000000..9f8c2cfd0
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -0,0 +1,161 @@
+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 { 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 } 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 { HistogramField } from "../dash-fields/HistogramField";
+import "../utils/Extensions"
+import "./HistogramBox.scss";
+import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
+import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
+
+export interface HistogramPrimitivesProps {
+    HistoBox: HistogramBox;
+}
+
+@observer
+export class HistogramBox extends React.Component<FieldViewProps> {
+    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
+    private _dropXRef = React.createRef<HTMLDivElement>();
+    private _dropYRef = React.createRef<HTMLDivElement>();
+    private _dropXDisposer?: DragManager.DragDropDisposer;
+    private _dropYDisposer?: DragManager.DragDropDisposer;
+
+    @observable public PanelWidth: number = 100;
+    @observable public PanelHeight: number = 100;
+    @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
+    @observable public VisualBinRanges: VisualBinRange[] = [];
+    @observable public ValueRange: number[] = [];
+    @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 BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; }
+    @computed get ChartType() {
+        return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ?
+            (this.BinRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) :
+            this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap;
+    }
+
+    constructor(props: FieldViewProps) {
+        super(props);
+    }
+
+    @action
+    dropX = (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.DocumentDragData) {
+            let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField);
+            if (h && h != FieldWaiting) {
+                this.HistoOp.X = h.Data.X;
+            }
+            e.stopPropagation();
+            e.preventDefault();
+        }
+    }
+    @action
+    dropY = (e: Event, de: DragManager.DropEvent) => {
+        if (de.data instanceof DragManager.DocumentDragData) {
+            let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField);
+            if (h && h != FieldWaiting) {
+                this.HistoOp.Y = h.Data.X;
+            }
+            e.stopPropagation();
+            e.preventDefault();
+        }
+    }
+
+    componentDidMount() {
+        if (this._dropXRef.current) {
+            this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } });
+        }
+        if (this._dropYRef.current) {
+            this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, { handlers: { drop: this.dropY.bind(this) } });
+        }
+        reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
+        reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
+        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40))
+        reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
+            (binRanges: BinRange[] | undefined) => {
+                if (binRanges) {
+                    this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) =>
+                        VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult!, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType)));
+
+                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult!, ModelHelpers.AllBrushIndex(this.HistogramResult!));
+                    this.ValueRange = Object.values(this.HistogramResult!.bins!).reduce((prev, cur) => {
+                        let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult;
+                        return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev;
+                    }, [Number.MAX_VALUE, Number.MIN_VALUE]);
+                }
+            });
+    }
+
+    @action
+    xLabelPointerDown = (e: React.PointerEvent) => {
+
+    }
+
+    componentWillUnmount() {
+        if (this._dropXDisposer)
+            this._dropXDisposer();
+        if (this._dropYDisposer)
+            this._dropYDisposer();
+    }
+
+    activateHistogramOperation(catalog?: Catalog) {
+        if (catalog) {
+            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.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
+                }
+            }));
+        }
+    }
+    render() {
+        let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
+        let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
+        var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
+        var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
+        let loff = this.SizeConverter.LeftOffset;
+        let toff = this.SizeConverter.TopOffset;
+        let roff = this.SizeConverter.RightOffset;
+        let boff = this.SizeConverter.BottomOffset;
+        return (
+            <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} >
+                            <span className="histogrambox-yaxislabel-text">
+                                {labelY}
+                            </span>
+                        </div>
+                        <div style={{
+                            transform: `translate(${loff + 25}px, ${toff}px)`,
+                            width: `calc(100% - ${loff + roff + 25}px)`,
+                            height: `calc(100% - ${toff + boff}px)`,
+                        }}>
+                            <HistogramLabelPrimitives HistoBox={this} />
+                            <HistogramBoxPrimitives HistoBox={this} />
+                        </div>
+                        <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >{labelX}</div>
+                    </div>
+                }
+            </Measure>
+        )
+    }
+}
+
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
new file mode 100644
index 000000000..9d42219cc
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
@@ -0,0 +1,26 @@
+.histogramboxprimitives-border {
+    border: 3px;
+    border-style: solid;
+    border-color: white;
+    pointer-events: none;
+    position: absolute;
+}
+.histogramboxprimitives-bar {
+    position: absolute;
+    border: 1px;
+    border-style: solid;
+    border-color: #282828;
+    pointer-events: all;
+}
+
+.histogramboxprimitives-placer {
+    position: absolute;
+    pointer-events: none;
+    width: 100%;
+    height: 100%;
+}
+.histogramboxprimitives-line {
+    position: absolute;
+    background: darkGray;
+    opacity: 0.4;
+}
\ No newline at end of file
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
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
new file mode 100644
index 000000000..304d33771
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
@@ -0,0 +1,13 @@
+
+  .histogramLabelPrimitives-gridlabel {
+    position:absolute;
+    transform-origin: left top;
+    font-size: 11;
+    color:white;
+  }
+  .histogramLabelPrimitives-placer {
+    position:absolute;
+    width:100%;
+    height:100%;
+    pointer-events: none;
+  }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
new file mode 100644
index 000000000..45b23874d
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -0,0 +1,78 @@
+import React = require("react")
+import { action, computed, reaction } from "mobx";
+import { observer } from "mobx-react";
+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 "./HistogramLabelPrimitives.scss";
+
+@observer
+export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
+    componentDidMount() {
+        reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
+            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, this.props.HistoBox), { fireImmediately: true });
+    }
+
+    @action
+    static computeLabelAngle(panelWidth: number, leftOffset: number, histoBox: HistogramBox) {
+        const textWidth = 30;
+        if (panelWidth > 0 && histoBox.VisualBinRanges.length && histoBox.VisualBinRanges[0] instanceof NominalVisualBinRange) {
+            let space = (panelWidth - leftOffset * 2) / histoBox.VisualBinRanges[0].GetBins().length;
+            histoBox.SizeConverter.SetLabelAngle(Math.min(Math.PI / 2, Math.max(Math.PI / 6, textWidth / space * Math.PI / 2)));
+        } else if (histoBox.SizeConverter.LabelAngle) {
+            histoBox.SizeConverter.SetLabelAngle(0);
+        }
+    }
+    @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
+    @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
+
+    private renderGridLinesAndLabels(axis: number) {
+        let sc = this.props.HistoBox.SizeConverter;
+        let vb = this.props.HistoBox.VisualBinRanges;
+        if (!vb.length || !sc.Initialized)
+            return (null);
+        let dim = (axis == 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis == 0 && vb[axis] instanceof NominalVisualBinRange) ?
+            (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
+            sc.MaxLabelSizes[axis].coords[axis] + 5);
+
+        let labels = vb[axis].GetLabels();
+        return labels.reduce((prims, binLabel, i) => {
+            let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
+            if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) {
+                const label = binLabel.label.Truncate(StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS, "...");
+                const textHeight = 14; const textWidth = 30;
+                let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth);
+                let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom);
+
+                if (axis == 0 && vb[axis] instanceof NominalVisualBinRange) {
+                    let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.PanelWidth;
+                    xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
+                }
+
+                let xPercent = axis == 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`
+                let yPercent = axis == 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`
+
+                prims.push(
+                    <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
+                        <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis == 0 ? sc.LabelAngle : 0}rad)` }}>
+                            {label}
+                        </div>
+                    </div>
+                )
+            }
+            return prims;
+        }, [] as JSX.Element[]);
+    }
+
+    render() {
+        let xaxislines = this.xaxislines;
+        let yaxislines = this.yaxislines;
+        return <div className="histogramLabelPrimitives-container">
+            {xaxislines}
+            {yaxislines}
+        </div>
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index 8de0bd260..d0711fb69 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -13,11 +13,11 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
 
 export class ModelHelpers {
 
-    public static CreateAggregateKey(atm: AttributeTransformationModel, histogramResult: HistogramResult,
+    public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult,
         brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
         {
             if (aggParameters == undefined) {
-                aggParameters = ModelHelpers.GetAggregateParameter(atm);
+                aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm);
             }
             else {
                 aggParameters.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
@@ -34,40 +34,40 @@ export class ModelHelpers {
         return ArrayUtil.IndexOfWithEqual(histogramResult.aggregateParameters!, aggParameters);
     }
 
-    public static GetAggregateParameter(atm: AttributeTransformationModel): AggregateParameters | undefined {
+    public static GetAggregateParameter(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel): AggregateParameters | undefined {
         var aggParam: AggregateParameters | undefined;
         if (atm.AggregateFunction === AggregateFunction.Avg) {
             var avg = new AverageAggregateParameters();
             avg.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            avg.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+            avg.distinctAttributeParameters = distinctAttributeParameters;
             aggParam = avg;
         }
         else if (atm.AggregateFunction === AggregateFunction.Count) {
             var cnt = new CountAggregateParameters();
             cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            cnt.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+            cnt.distinctAttributeParameters = distinctAttributeParameters;
             aggParam = cnt;
         }
         else if (atm.AggregateFunction === AggregateFunction.Sum) {
             var sum = new SumAggregateParameters();
             sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
-            sum.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+            sum.distinctAttributeParameters = distinctAttributeParameters;
             aggParam = sum;
         }
         return aggParam;
     }
 
-    public static GetAggregateParametersWithMargins(atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
+    public static GetAggregateParametersWithMargins(distinctAttributeParameters: AttributeParameters | undefined, atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
         var aggregateParameters = new Array<AggregateParameters>();
         atms.forEach(agg => {
-            var aggParams = ModelHelpers.GetAggregateParameter(agg);
+            var aggParams = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, agg);
             if (aggParams) {
                 aggregateParameters.push(aggParams);
 
                 var margin = new MarginAggregateParameters()
                 margin.aggregateFunction = agg.AggregateFunction;
                 margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
-                margin.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters;
+                margin.distinctAttributeParameters = distinctAttributeParameters;
                 aggregateParameters.push(margin);
             }
         });
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
index 9eae39800..53d585bb4 100644
--- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -1,4 +1,4 @@
-import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult } from "../idea/idea";
+import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult, AttributeParameters } from "../idea/idea";
 import { VisualBinRange, ChartType } from "./VisualBinRange";
 import { NominalVisualBinRange } from "./NominalVisualBinRange";
 import { QuantitativeVisualBinRange } from "./QuantitativeVisualBinRange";
@@ -30,13 +30,13 @@ export class VisualBinRangeHelper {
         throw new Exception()
     }
 
-    public static GetVisualBinRange(dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
+    public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
 
         if (!(dataBinRange instanceof AggregateBinRange)) {
             return VisualBinRangeHelper.GetNonAggregateVisualBinRange(dataBinRange);
         }
         else {
-            var aggregateKey = ModelHelpers.CreateAggregateKey(attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
+            var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
             var minValue = Number.MAX_VALUE;
             var maxValue = Number.MIN_VALUE;
             for (var b = 0; b < histoResult.brushes!.length; b++) {
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index 7db6fcb91..94e0849af 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -10,9 +10,9 @@ 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 = undefined;
+    public OperationReference?: OperationReference = undefined;
 
     private static _nextId = 0;
     public RequestSalt: string = "";
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 6c7288d42..8a0f648f6 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -27,6 +27,8 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     @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); }
 
     @action
     public AddFilterModels(filterModels: FilterModel[]): void {
@@ -38,23 +40,24 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
     }
 
     public getValue(axis: number, bin: Bin, result: HistogramResult, brushIndex: number) {
-        var aggregateKey = ModelHelpers.CreateAggregateKey(axis == 0 ? this.X : axis == 1 ? this.Y : this.V, result, brushIndex);
+        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(new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())));
+    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(x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) {
+    constructor(schemaName: string, x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) {
         super();
         this.X = x;
         this.Y = y;
         this.V = v;
         this.Normalization = normalized ? normalized : -1;
+        this.SchemaName = schemaName;
     }
 
     @computed
@@ -93,7 +96,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None));
 
         let numericDataTypes = [DataType.Int, DataType.Double, DataType.Float];
-        let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(allAttributes);
+        let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(this.Schema!.distinctAttributeParameters, allAttributes);
         let globalAggregateParameters: AggregateParameters[] = [];
         [histoX, histoY]
             .filter(a => a.AggregateFunction === AggregateFunction.None && ArrayUtil.Contains(numericDataTypes, a.AttributeModel.DataType))
@@ -112,7 +115,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
             let [perBinAggregateParameters, globalAggregateParameters] = this.GetAggregateParameters(this.X, this.Y, this.V);
             return new HistogramOperationParameters({
                 enableBrushComputation: true,
-                adapterName: CurrentUserUtils.ActiveSchema!.displayName,
+                adapterName: this.SchemaName,
                 filter: this.FilterString,
                 brushes: this.BrushString,
                 binningParameters: [ModelHelpers.GetBinningParameters(this.X, SETTINGS_X_BINS, this.QRange ? this.QRange.minValue : undefined, this.QRange ? this.QRange.maxValue : undefined),
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index ffd162a83..30627dfd5 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -68,10 +68,14 @@ export class SizeConverter {
         return [undefined, undefined];
     }
 
-    public DataToScreenAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
+    public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
         var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]);
         return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))]
     }
+    public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
+        var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]);
+        return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))]
+    }
 
     public DataToScreenX(x: number): number {
         return ((x - this.DataMins[0]) / this.DataRanges[0]) * this.RenderDimension;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 09778ac77..2f20b102c 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -58,7 +58,7 @@ export class Main extends React.Component {
     @observable private mainfreeform?: Document;
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
-    private _northstarColumns: Document[] = [];
+    private _northstarSchemas: Document[] = [];
 
     @computed private get mainContainer(): Document | undefined {
         let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document);
@@ -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._northstarColumns, { width: 200, height: 200, title: "a tree collection" }));
+        let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 100, 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" }));
@@ -334,24 +334,27 @@ export class Main extends React.Component {
     // --------------- Northstar hooks ------------- /
 
     @action SetNorthstarCatalog(ctlog: Catalog) {
+        CurrentUserUtils.NorthstarDBCatalog = ctlog;
         if (ctlog && ctlog.schemas) {
-            CurrentUserUtils.ActiveSchema = ArrayUtil.FirstOrDefault<Schema>(ctlog.schemas!, (s: Schema) => s.displayName === "mimic");
-            CurrentUserUtils.GetAllNorthstarColumnAttributes().map(attr => {
-                Server.GetField(attr.displayName!, action((field: Opt<Field>) => {
-                    if (field instanceof Document) {
-                        this._northstarColumns.push(field);
-                    } else {
-                        var atmod = new ColumnAttributeModel(attr);
-                        let histoOp = new HistogramOperation(
-                            new AttributeTransformationModel(atmod, AggregateFunction.None),
-                            new AttributeTransformationModel(atmod, AggregateFunction.Count),
-                            new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                        this._northstarColumns.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName!, northstarSchema: CurrentUserUtils.ActiveSchema!.displayName! }, attr.displayName!));
-                    }
-                }));
+            this._northstarSchemas = ctlog.schemas.map(schema => {
+                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>) => {
+                        if (field instanceof Document) {
+                            schemaDocuments.push(field);
+                        } else {
+                            var atmod = new ColumnAttributeModel(attr);
+                            let histoOp = new HistogramOperation(schema!.displayName!,
+                                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!));
+                        }
+                    }));
+                });
+                return schemaDoc;
             })
-            console.log("Activating schema " + CurrentUserUtils.ActiveSchema!.displayName!)
-            CurrentUserUtils.ActiveSchemaName = CurrentUserUtils.ActiveSchema!.displayName!;
         }
     }
     async initializeNorthstar(): Promise<void> {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index b54744337..77551649c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -19,8 +19,7 @@ import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
-import { HistogramBox } from "./HistogramBox";
-import { HistogramBoxPrimitives } from "./HistogramBoxPrimitives";
+import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -54,7 +53,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             return <p>Error loading layout keys</p>;
         }
         return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, HistogramBoxPrimitives }}
+            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
             jsx={this.layout}
             showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 769d893f3..71613ca4f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -120,9 +120,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     private dropDisposer?: DragManager.DragDropDisposer;
-    protected createDropTarget = (ele: HTMLDivElement) => {
-
-    }
 
     componentDidMount() {
         if (this._mainCont.current) {
diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss
deleted file mode 100644
index 2660b1b75..000000000
--- a/src/client/views/nodes/HistogramBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.histogrambox-container {
-    padding: 0vw;
-    position: absolute;
-    text-align: center;
-    width: 100%;
-    height: 100%;
-  }
-  .histogrambox-xaxislabel {
-    position:absolute;
-    width:100%;
-    text-align: center;
-    bottom:0;
-    font-size: 14;
-    font-weight: bold;
-  }
-  
-  .histogrambox-container {
-    position:absolute;
-    width:100%;
-    height: 100%;
-  }
-  
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
deleted file mode 100644
index 3307925a2..000000000
--- a/src/client/views/nodes/HistogramBox.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React = require("react")
-import { computed, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import Measure from "react-measure";
-import { Dictionary } from "typescript-collections";
-import { Opt } from "../../../fields/Field";
-import { HistogramField } from "../../../fields/HistogramField";
-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 } 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 "./../../northstar/utils/Extensions";
-import { FieldView, FieldViewProps } from './FieldView';
-import "./HistogramBox.scss";
-import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
-import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
-
-export interface HistogramPrimitivesProps {
-    HistoBox: HistogramBox;
-}
-
-@observer
-export class HistogramBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
-    public HitTargets: Dictionary<PIXIRectangle, FilterModel> = new Dictionary<PIXIRectangle, FilterModel>();
-
-    @observable public PanelWidth: number = 100;
-    @observable public PanelHeight: number = 100;
-    @observable public HistoOp?: HistogramOperation;
-    @observable public VisualBinRanges: VisualBinRange[] = [];
-    @observable public ValueRange: number[] = [];
-    @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 BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; }
-    @computed get ChartType() {
-        return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ?
-            (this.BinRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) :
-            this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap;
-    }
-
-    componentDidMount() {
-        reaction(() => [CurrentUserUtils.ActiveSchemaName, this.props.doc.GetText(KeyStore.NorthstarSchema, "?")],
-            (params: string[]) => params[0] === params[1] && this.activateHistogramOperation(), { fireImmediately: true });
-        reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
-        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40))
-        reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
-            (binRanges: BinRange[] | undefined) => {
-                if (binRanges) {
-                    this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) =>
-                        VisualBinRangeHelper.GetVisualBinRange(br, this.HistogramResult!, ind ? this.HistoOp!.Y : this.HistoOp!.X, this.ChartType)));
-
-                    let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp!.V, this.HistogramResult!, ModelHelpers.AllBrushIndex(this.HistogramResult!));
-                    this.ValueRange = Object.values(this.HistogramResult!.bins!).reduce((prev, cur) => {
-                        let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult;
-                        return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev;
-                    }, [Number.MIN_VALUE, Number.MAX_VALUE]);
-                }
-            });
-    }
-
-    activateHistogramOperation() {
-        this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => {
-            if (histoOp) {
-                runInAction(() => this.HistoOp = histoOp.Data);
-                reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), docs => this.HistoOp!.Links.splice(0, this.HistoOp!.Links.length, ...docs), { fireImmediately: true });
-                reaction(() => this.createOperationParamsCache, () => this.HistoOp!.Update(), { fireImmediately: true });
-            }
-        })
-    }
-    render() {
-        let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>";
-        var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
-        var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
-        let loff = this.SizeConverter.LeftOffset;
-        let toff = this.SizeConverter.TopOffset;
-        let roff = this.SizeConverter.RightOffset;
-        let boff = this.SizeConverter.BottomOffset;
-        return (
-            <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 style={{
-                            transform: `translate(${loff}px, ${toff}px)`,
-                            width: `calc(100% - ${loff + roff}px)`,
-                            height: `calc(100% - ${toff + boff}px)`,
-                        }}>
-                            <HistogramLabelPrimitives HistoBox={this} />
-                            <HistogramBoxPrimitives HistoBox={this} />
-                        </div>
-                        <div className="histogrambox-xaxislabel">{label}</div>
-                    </div>
-                }
-            </Measure>
-        )
-    }
-}
-
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.scss b/src/client/views/nodes/HistogramBoxPrimitives.scss
deleted file mode 100644
index 85f2c092d..000000000
--- a/src/client/views/nodes/HistogramBoxPrimitives.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-.histogramboxprimitives-border {
-    border: 3px;
-    border-style: solid;
-    border-color: #282828;
-    pointer-events: none;
-    position: absolute;
-}
-.histogramboxprimitives-bar {
-    position: absolute;
-    border: 1px;
-    border-style: solid;
-    border-color: #282828;
-    pointer-events: all;
-}
-
-.histogramboxprimitives-placer {
-    position: absolute;
-    pointer-events: none;
-    width: 100%;
-    height: 100%;
-}
-.histogramboxprimitives-line {
-    position: absolute;
-    background: lightgray;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx
deleted file mode 100644
index f15cb5689..000000000
--- a/src/client/views/nodes/HistogramBoxPrimitives.tsx
+++ /dev/null
@@ -1,336 +0,0 @@
-import React = require("react")
-import { computed, observable, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import { Utils as DashUtils } from '../../../Utils';
-import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
-import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
-import { AggregateFunction, Bin, Brush, HistogramResult, MarginAggregateParameters, MarginAggregateResult, BinLabel } 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";
-import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { FilterModel } from "../../northstar/core/filter/FilterModel";
-
-
-@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 filterModel = ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp!.X, this.histoOp!.Y);
-
-            this.props.HistoBox.HitTargets.setValue(drawPrims.HitGeom, filterModel);
-
-            let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex, filterModel);
-            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 = () => { }) {
-        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.DataToScreenAxisRange(this._histoBox.VisualBinRanges, 0, bin);
-        let [yFrom, yTo] = this.sizeConverter.DataToScreenAxisRange(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.X, this.histoResult, brush.brushIndex!));
-            let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(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.DataToScreenAxisRange(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.DataToScreenAxisRange(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(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
diff --git a/src/client/views/nodes/HistogramLabelPrimitives.scss b/src/client/views/nodes/HistogramLabelPrimitives.scss
deleted file mode 100644
index d8ee88d72..000000000
--- a/src/client/views/nodes/HistogramLabelPrimitives.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-
-  .histogramLabelPrimitives-gridlabel {
-    position:absolute;
-    transform-origin: left top;
-    font-size: 11;
-  }
-  .histogramLabelPrimitives-placer {
-    position:absolute;
-    width:100%;
-    height:100%;
-    pointer-events: none;
-  }
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramLabelPrimitives.tsx b/src/client/views/nodes/HistogramLabelPrimitives.tsx
deleted file mode 100644
index 7f365e45b..000000000
--- a/src/client/views/nodes/HistogramLabelPrimitives.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import React = require("react")
-import { action, computed, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Utils as DashUtils } from '../../../Utils';
-import { NominalVisualBinRange } from "../../northstar/model/binRanges/NominalVisualBinRange";
-import { StyleConstants } from "../../northstar/utils/StyleContants";
-import "./../../northstar/utils/Extensions";
-import "./HistogramLabelPrimitives.scss";
-import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox";
-
-@observer
-export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
-    componentDidMount() {
-        reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
-            (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, this.props.HistoBox), { fireImmediately: true });
-    }
-
-    @action
-    static computeLabelAngle(panelWidth: number, leftOffset: number, histoBox: HistogramBox) {
-        const textWidth = 30;
-        if (panelWidth > 0 && histoBox.VisualBinRanges.length && histoBox.VisualBinRanges[0] instanceof NominalVisualBinRange) {
-            let space = (panelWidth - leftOffset * 2) / histoBox.VisualBinRanges[0].GetBins().length;
-            histoBox.SizeConverter.SetLabelAngle(Math.min(Math.PI / 2, Math.max(Math.PI / 6, textWidth / space * Math.PI / 2)));
-        } else if (histoBox.SizeConverter.LabelAngle) {
-            histoBox.SizeConverter.SetLabelAngle(0);
-        }
-    }
-    @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
-    @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
-
-    private renderGridLinesAndLabels(axis: number) {
-        let sc = this.props.HistoBox.SizeConverter;
-        let vb = this.props.HistoBox.VisualBinRanges;
-        if (!vb.length || !sc.Initialized)
-            return (null);
-        let dim = (axis == 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis == 0 && vb[axis] instanceof NominalVisualBinRange) ?
-            (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
-            sc.MaxLabelSizes[axis].coords[axis] + 5);
-
-        let labels = vb[axis].GetLabels();
-        return labels.reduce((prims, binLabel, i) => {
-            let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
-            if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) {
-                const label = binLabel.label.Truncate(StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS, "...");
-                const textHeight = 14; const textWidth = 30;
-                let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth);
-                let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom);
-
-                if (axis == 0 && vb[axis] instanceof NominalVisualBinRange) {
-                    let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.PanelWidth;
-                    xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
-                }
-
-                let xPercent = axis == 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`
-                let yPercent = axis == 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`
-
-                prims.push(
-                    <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
-                        <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis == 0 ? sc.LabelAngle : 0}rad)` }}>
-                            {label}
-                        </div>
-                    </div>
-                )
-            }
-            return prims;
-        }, [] as JSX.Element[]);
-    }
-
-    render() {
-        let xaxislines = this.xaxislines;
-        let yaxislines = this.yaxislines;
-        return <div className="histogramLabelPrimitives-container">
-            {xaxislines}
-            {yaxislines}
-        </div>
-    }
-
-}
\ No newline at end of file
diff --git a/src/fields/HistogramField.ts b/src/fields/HistogramField.ts
deleted file mode 100644
index bb0014ab3..000000000
--- a/src/fields/HistogramField.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-import { HistogramOperation } from "../client/northstar/operations/HistogramOperation";
-import { action } from "mobx";
-import { AttributeTransformationModel } from "../client/northstar/core/attribute/AttributeTransformationModel";
-import { ColumnAttributeModel } from "../client/northstar/core/attribute/AttributeModel";
-import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
-
-
-export class HistogramField extends BasicField<HistogramOperation> {
-    constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) {
-        super(data ? data : HistogramOperation.Empty, save, id);
-    }
-
-    toString(): string {
-        return JSON.stringify(this.Data);
-    }
-
-    Copy(): Field {
-        return new HistogramField(this.Data);
-    }
-
-    ToScriptString(): string {
-        return `new HistogramField("${this.Data}")`;
-    }
-
-    ToJson(): { type: Types, data: string, _id: string } {
-        return {
-            type: Types.HistogramOp,
-            data: JSON.stringify(this.Data),
-            _id: this.Id
-        }
-    }
-
-    @action
-    static FromJson(id: string, data: any): HistogramField {
-        let jp = JSON.parse(data);
-        let X: AttributeTransformationModel | undefined;
-        let Y: AttributeTransformationModel | undefined;
-        let V: AttributeTransformationModel | undefined;
-
-        CurrentUserUtils.GetAllNorthstarColumnAttributes().map(attr => {
-            if (attr.displayName == jp.X.AttributeModel.Attribute.DisplayName) {
-                X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction);
-            }
-            if (attr.displayName == jp.Y.AttributeModel.Attribute.DisplayName) {
-                Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction);
-            }
-            if (attr.displayName == jp.V.AttributeModel.Attribute.DisplayName) {
-                V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
-            }
-        });
-        if (X && Y && V) {
-            return new HistogramField(new HistogramOperation(X, Y, V, jp.Normalization), id, false);
-        }
-        return new HistogramField(HistogramOperation.Empty, id, false);
-    }
-}
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 09dddf962..aa0b9ce92 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -44,5 +44,4 @@ export namespace KeyStore {
     export const Archives = new Key("Archives");
     export const Updated = new Key("Updated");
     export const Workspaces = new Key("Workspaces");
-    export const NorthstarSchema = new Key("NorthstarSchema");
 }
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index f958df04b..98a7a1451 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -17,8 +17,7 @@ import { VideoField } from '../fields/VideoField';
 import { InkField } from '../fields/InkField';
 import { PDFField } from '../fields/PDFField';
 import { TupleField } from '../fields/TupleField';
-import { HistogramField } from '../fields/HistogramField';
-
+import { HistogramField } from '../client/northstar/dash-fields/HistogramField';
 
 
 
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 4b42e40b6..0ac85b446 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -7,8 +7,9 @@ import { Document } from "../../../fields/Document";
 import { KeyStore } from "../../../fields/KeyStore";
 import { ListField } from "../../../fields/ListField";
 import { Documents } from "../../../client/documents/Documents";
-import { Schema, Attribute, AttributeGroup } from "../../../client/northstar/model/idea/idea";
+import { Schema, Attribute, AttributeGroup, Catalog } from "../../../client/northstar/model/idea/idea";
 import { observable, computed, action } from "mobx";
+import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
 
 export class CurrentUserUtils {
     private static curr_email: string;
@@ -16,8 +17,7 @@ export class CurrentUserUtils {
     private static user_document: Document;
     //TODO tfs: these should be temporary...
     private static mainDocId: string | undefined;
-    private static activeSchema: Schema | undefined;
-    @observable public static ActiveSchemaName: string = "";
+    @observable private static catalog?: Catalog;
 
     public static get email(): string {
         return this.curr_email;
@@ -39,12 +39,18 @@ export class CurrentUserUtils {
         this.mainDocId = id;
     }
 
-
-    public static get ActiveSchema(): Schema | undefined {
-        return this.activeSchema;
+    @computed public static get NorthstarDBCatalog(): Catalog | undefined {
+        return this.catalog;
+    }
+    public static set NorthstarDBCatalog(ctlog: Catalog | undefined) {
+        this.catalog = ctlog;
     }
-    public static GetAllNorthstarColumnAttributes() {
-        if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) {
+    public static GetNorthstarSchema(name: string): Schema | undefined {
+        return !this.catalog || !this.catalog.schemas ? undefined :
+            ArrayUtil.FirstOrDefault<Schema>(this.catalog.schemas, (s: Schema) => s.displayName === name);
+    }
+    public static GetAllNorthstarColumnAttributes(schema: Schema) {
+        if (!schema || !schema.rootAttributeGroup) {
             return [];
         }
         const recurs = (attrs: Attribute[], g: AttributeGroup) => {
@@ -56,13 +62,10 @@ export class CurrentUserUtils {
             }
         };
         const allAttributes: Attribute[] = new Array<Attribute>();
-        recurs(allAttributes, this.ActiveSchema.rootAttributeGroup);
+        recurs(allAttributes, schema.rootAttributeGroup);
         return allAttributes;
     }
 
-    public static set ActiveSchema(id: Schema | undefined) {
-        this.activeSchema = id;
-    }
     private static createUserDocument(id: string): Document {
         let doc = new Document(id);
 
-- 
cgit v1.2.3-70-g09d2

