aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbob <bcz@cs.brown.edu>2019-03-27 16:12:36 -0400
committerbob <bcz@cs.brown.edu>2019-03-27 16:12:36 -0400
commitab78b1514ac16154d443d1d4a117a5fcaf891794 (patch)
treee1cebdf27124295c5b4ad726a37af500be25e01c /src
parente3ec05fbe6f3057fa33cb4d66025ff4c94f23f83 (diff)
converted histograms to % coords to avoid re-rendering.
Diffstat (limited to 'src')
-rw-r--r--src/client/northstar/utils/SizeConverter.ts82
-rw-r--r--src/client/views/nodes/HistogramBox.scss22
-rw-r--r--src/client/views/nodes/HistogramBox.tsx146
-rw-r--r--src/client/views/nodes/HistogramBoxPrimitives.scss19
-rw-r--r--src/client/views/nodes/HistogramBoxPrimitives.tsx106
5 files changed, 237 insertions, 138 deletions
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index 2dc2a7557..ffd162a83 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -1,68 +1,48 @@
import { PIXIPoint } from "./MathUtil";
-import { NominalVisualBinRange } from "../model/binRanges/NominalVisualBinRange";
import { VisualBinRange } from "../model/binRanges/VisualBinRange";
import { Bin, DoubleValueAggregateResult, AggregateKey } from "../model/idea/idea";
-import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
import { ModelHelpers } from "../model/ModelHelpers";
+import { observable, action, computed } from "mobx";
export class SizeConverter {
- public RenderSize: Array<number> = new Array<number>(2);
- public DataMins: Array<number> = new Array<number>(2);;
- public DataMaxs: Array<number> = new Array<number>(2);;
- public DataRanges: Array<number> = new Array<number>(2);;
- public MaxLabelSizes: Array<PIXIPoint> = new Array<PIXIPoint>(2);;
-
- public LeftOffset: number = 40;
- public RightOffset: number = 20;
- public TopOffset: number = 20;
- public BottomOffset: number = 45;
-
- public IsSmall: boolean = false;
-
- constructor(size: { x: number, y: number }, visualBinRanges: Array<VisualBinRange>, labelAngle: number) {
- this.LeftOffset = 40;
- this.RightOffset = 20;
- this.TopOffset = 20;
- this.BottomOffset = 45;
- this.IsSmall = false;
-
- if (visualBinRanges.length < 1)
- return;
-
+ public DataMins: Array<number> = new Array<number>(2);
+ public DataMaxs: Array<number> = new Array<number>(2);
+ public DataRanges: Array<number> = new Array<number>(2);
+ public MaxLabelSizes: Array<PIXIPoint> = new Array<PIXIPoint>(2);
+ public RenderDimension: number = 300;
+
+ @observable _leftOffset: number = 40;
+ @observable _rightOffset: number = 20;
+ @observable _topOffset: number = 20;
+ @observable _bottomOffset: number = 45;
+ @observable _labelAngle: number = 0;
+ @observable _isSmall: boolean = false;
+ @observable public Initialized = 0;
+
+ @action public SetIsSmall(isSmall: boolean) { this._isSmall = isSmall; }
+ @action public SetLabelAngle(angle: number) { this._labelAngle = angle; }
+ @computed public get IsSmall() { return this._isSmall; }
+ @computed public get LabelAngle() { return this._labelAngle; }
+ @computed public get LeftOffset() { return this.IsSmall ? 5 : this._leftOffset; }
+ @computed public get RightOffset() { return this.IsSmall ? 5 : !this._labelAngle ? this._bottomOffset : Math.max(this._rightOffset, Math.cos(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)); }
+ @computed public get TopOffset() { return this.IsSmall ? 5 : this._topOffset; }
+ @computed public get BottomOffset() { return this.IsSmall ? 25 : !this._labelAngle ? this._bottomOffset : Math.max(this._bottomOffset, Math.sin(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)) + 18; }
+
+ public SetVisualBinRanges(visualBinRanges: Array<VisualBinRange>) {
+ this.Initialized++;
var xLabels = visualBinRanges[0].GetLabels();
var yLabels = visualBinRanges[1].GetLabels();
var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length });
var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length });
- var metricsX = { width: 100 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
+ var metricsX = { width: 75 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
//xLabelStrings[0]!.slice(0, 20)) // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
var metricsY = { width: 22 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
// yLabelStrings[0]!.slice(0, 20)); // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
this.MaxLabelSizes[0] = new PIXIPoint(metricsX.width, 12);// FontStyles.AxisLabel.fontSize as number);
this.MaxLabelSizes[1] = new PIXIPoint(metricsY.width, 12); // FontStyles.AxisLabel.fontSize as number);
- this.LeftOffset = Math.max(10, metricsY.width + 10 + 20);
-
- if (visualBinRanges[0] instanceof NominalVisualBinRange) {
- var lw = this.MaxLabelSizes[0].x + 18;
- this.BottomOffset = Math.max(this.BottomOffset, Math.cos(labelAngle) * lw) + 5;
- this.RightOffset = Math.max(this.RightOffset, Math.sin(labelAngle) * lw);
- }
-
- this.RenderSize[0] = (size.x - this.LeftOffset - this.RightOffset);
- this.RenderSize[1] = (size.y - this.TopOffset - this.BottomOffset);
-
- //if (this.RenderSize.reduce((agg, cur) => Math.min(agg, cur), Number.MAX_VALUE) < 40) {
- if ((this.RenderSize[0] < 40 && this.RenderSize[1] < 40) ||
- (this.RenderSize[0] < 0 || this.RenderSize[1] < 0)) {
- this.LeftOffset = 5;
- this.RightOffset = 5;
- this.TopOffset = 5;
- this.BottomOffset = 25;
- this.IsSmall = true;
- this.RenderSize[0] = (size.x - this.LeftOffset - this.RightOffset);
- this.RenderSize[1] = (size.y - this.TopOffset - this.BottomOffset);
- }
+ this._leftOffset = Math.max(10, metricsY.width + 10 + 20);
this.DataMins[0] = xLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE);
this.DataMins[1] = yLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE);
@@ -94,11 +74,11 @@ export class SizeConverter {
}
public DataToScreenX(x: number): number {
- return (((x - this.DataMins[0]) / this.DataRanges[0]) * (this.RenderSize[0]) + (this.LeftOffset));
+ return ((x - this.DataMins[0]) / this.DataRanges[0]) * this.RenderDimension;
}
public DataToScreenY(y: number, flip: boolean = true) {
- var retY = ((y - this.DataMins[1]) / this.DataRanges[1]) * (this.RenderSize[1]);
- return flip ? (this.RenderSize[1]) - retY + (this.TopOffset) : retY + (this.TopOffset);
+ var retY = ((y - this.DataMins[1]) / this.DataRanges[1]) * this.RenderDimension;
+ return flip ? (this.RenderDimension) - retY : retY;
}
public DataToScreenCoord(v: number, axis: number) {
if (axis == 0)
diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss
index 00ad099ac..7c28fc99e 100644
--- a/src/client/views/nodes/HistogramBox.scss
+++ b/src/client/views/nodes/HistogramBox.scss
@@ -5,14 +5,28 @@
width: 100%;
height: 100%;
}
- .histogrambox-gridlabel {
- position:absolute;
- transform-origin: left top;
- }
.histogrambox-xaxislabel {
position:absolute;
width:100%;
text-align: center;
bottom:0;
+ font-size: 12;
+ }
+
+ .histogrambox-container {
+ position:absolute;
+ width:100%;
+ height: 100%;
+ }
+ .histogramLabelPrimitives-gridlabel {
+ position:absolute;
+ transform-origin: left top;
+ font-size: 12;
+ }
+ .histogramLabelPrimitives-placer {
+ position:absolute;
+ width:100%;
+ height:100%;
+ pointer-events: none;
}
\ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
index c9537bcf8..2291d1418 100644
--- a/src/client/views/nodes/HistogramBox.tsx
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -1,5 +1,5 @@
import React = require("react")
-import { computed, observable, reaction, runInAction } from "mobx";
+import { computed, observable, reaction, runInAction, trace, action } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import { Dictionary } from "typescript-collections";
@@ -21,22 +21,20 @@ import { StyleConstants } from "../../northstar/utils/StyleContants";
import "./../../northstar/utils/Extensions";
import { FieldView, FieldViewProps } from './FieldView';
import "./HistogramBox.scss";
-import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
+import { HistogramBoxPrimitives, HistogramBoxPrimitivesProps } from './HistogramBoxPrimitives';
@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 private _panelWidth: number = 100;
- @observable private _panelHeight: number = 100;
+ @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;
- public HitTargets: Dictionary<PIXIRectangle, FilterModel> = new Dictionary<PIXIRectangle, FilterModel>();
+ @observable public SizeConverter: SizeConverter = new SizeConverter();
- @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
- @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
@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; }
@@ -48,93 +46,127 @@ export class HistogramBox extends React.Component<FieldViewProps> {
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._panelHeight, this._panelWidth],
- () => this.SizeConverter = new SizeConverter({ x: this._panelWidth, y: this._panelHeight }, this.VisualBinRanges, Math.PI / 4));
- reaction(() => this.BinRanges, (binRanges: BinRange[] | undefined) => {
- if (binRanges && this.HistogramResult && !this.HistogramResult!.isEmpty && this.HistogramResult!.bins) {
- this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map(br =>
- VisualBinRangeHelper.GetVisualBinRange(br, this.HistogramResult!, this.HistoOp!.X, this.ChartType)));
+ (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]);
- }
- });
+ 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.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>
+ )
+ }
+}
+
+@observer
+export class HistogramLabelPrimitives extends React.Component<HistogramBoxPrimitivesProps> {
+ 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 });
+ }
- drawLine(xFrom: number, yFrom: number, width: number, height: number) {
- return <div key={DashUtils.GenerateGuid()} style={{ position: "absolute", width: `${width}px`, height: `${height}px`, background: "lightgray", transform: `translate(${xFrom}px, ${yFrom}px)` }} />;
+ @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.SizeConverter!;
- if (!sc || !this.VisualBinRanges.length)
+ let sc = this.props.HistoBox.SizeConverter;
+ let vb = this.props.HistoBox.VisualBinRanges;
+ if (!vb.length || !sc.Initialized)
return (null);
- let dim = sc.RenderSize[axis] / ((axis == 0 && this.VisualBinRanges[axis] instanceof NominalVisualBinRange) ?
+ 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 prims: JSX.Element[] = [];
- let labels = this.VisualBinRanges[axis].GetLabels();
+ let labels = vb[axis].GetLabels();
labels.map((binLabel, i) => {
let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
-
- prims.push(this.drawLine(r.xFrom, r.yFrom, axis == 0 ? 1 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 1));
- if (i == labels.length - 1)
- prims.push(this.drawLine(axis == 0 ? r.xTo : r.xFrom, axis == 0 ? r.yFrom : r.yTo, axis == 0 ? 1 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 1));
-
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);
- let rotation = 0;
- if (axis == 0 && this.VisualBinRanges[axis] instanceof NominalVisualBinRange) {
- rotation = Math.min(90, Math.max(30, textWidth / (r.xTo - r.xFrom) * 90));
- xStart += Math.max(textWidth / 2, (1 - textWidth / (r.xTo - r.xFrom)) * textWidth / 2) - textHeight / 2;
+ 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 key={DashUtils.GenerateGuid()} className="histogrambox-gridlabel" style={{ transform: `translate(${xStart}px, ${yStart}px) rotate(${rotation}deg)` }}>
- {label}
- </div>)
+ <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;
}
render() {
- 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(${-w / 2}px, ${-h / 2}px)` }}>
- {xaxislines}
- {yaxislines}
- <HistogramBoxPrimitives HistoBox={this} />
- <div className="histogrambox-xaxislabel">{label}</div>
- </div>
- }
- </Measure>
- )
+ return <div className="histogramLabelPrimitives-container">
+ {xaxislines}
+ {yaxislines}
+ </div>
}
+
} \ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBoxPrimitives.scss b/src/client/views/nodes/HistogramBoxPrimitives.scss
index c88d3a227..85f2c092d 100644
--- a/src/client/views/nodes/HistogramBoxPrimitives.scss
+++ b/src/client/views/nodes/HistogramBoxPrimitives.scss
@@ -1,10 +1,25 @@
.histogramboxprimitives-border {
- border: 1px;
+ border: 3px;
border-style: solid;
+ border-color: #282828;
pointer-events: none;
position: absolute;
- border-color: #282828;
}
.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
index 10f6515cf..06b318750 100644
--- a/src/client/views/nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/views/nodes/HistogramBoxPrimitives.tsx
@@ -1,5 +1,5 @@
import React = require("react")
-import { computed, observable, runInAction } from "mobx";
+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";
@@ -11,10 +11,9 @@ import { LABColor } from '../../northstar/utils/LABcolor';
import { PIXIRectangle } from "../../northstar/utils/MathUtil";
import { StyleConstants } from "../../northstar/utils/StyleContants";
import { HistogramBox } from "./HistogramBox";
-import "./HistogramBox.scss";
+import "./HistogramBoxPrimitives.scss";
import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
import { FilterModel } from "../../northstar/core/filter/FilterModel";
-import { jSXElement } from "babel-types";
export interface HistogramBoxPrimitivesProps {
HistoBox: HistogramBox;
@@ -24,9 +23,10 @@ export interface HistogramBoxPrimitivesProps {
export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitivesProps> {
@observable _selectedPrims: HistogramBinPrimitive[] = [];
- @computed
- get selectedPrimitives() {
- return this._selectedPrims.map((bp) => this.drawRect(bp.Rect, undefined, () => { }, "border"));
+ @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"));
}
private getSelectionToggle(histoOp: HistogramOperation, binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex);
@@ -45,7 +45,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitiv
get binPrimitives() {
let histoOp = this.props.HistoBox.HistoOp;
let histoResult = this.props.HistoBox.HistogramResult;
- if (!histoOp || !histoResult || !this.props.HistoBox.SizeConverter || !histoResult.bins)
+ if (!histoOp || !histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length)
return (null);
let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
return Object.keys(histoResult.bins).reduce((prims, key) => {
@@ -56,23 +56,79 @@ export class HistogramBoxPrimitives extends React.Component<HistogramBoxPrimitiv
let toggle = this.getSelectionToggle(histoOp!, 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, pair.c, toggle, "bar"))));
+ 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, toggle, "bar"))));
return prims;
}, [] as JSX.Element[]);
}
- drawRect(r: PIXIRectangle, color: number | undefined, tapHandler: () => void, classExt: string) {
- return <div key={DashUtils.GenerateGuid()} className={`histogramboxprimitives-+${classExt}`} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
- style={{
- transform: `translate(${r.x}px,${r.y}px)`,
- width: `${r.width - 1}`,
- height: `${r.height}`,
- background: color ? `${LABColor.RGBtoHexString(color)}` : ""
- }}
- />
+
+ private renderGridLinesAndLabels(axis: number) {
+ let sc = this.props.HistoBox.SizeConverter;
+ let vb = this.props.HistoBox.VisualBinRanges;
+ if (!vb.length || !sc.Initialized)
+ return (null);
+
+ let prims: JSX.Element[] = [];
+ let labels = vb[axis].GetLabels();
+ labels.map((binLabel, i) => {
+ let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
+
+ prims.push(this.drawLine(r.xFrom, r.yFrom, axis == 0 ? 1 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 1));
+ if (i == labels.length - 1)
+ prims.push(this.drawLine(axis == 0 ? r.xTo : r.xFrom, axis == 0 ? r.yFrom : r.yTo, axis == 0 ? 1 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 1));
+ });
+ return prims;
+ }
+
+ drawLine(xFrom: number, yFrom: number, width: number, height: number) {
+ if (height < 0) {
+ yFrom += height;
+ height = -height;
+ }
+ if (width < 0) {
+ xFrom += width;
+ width = -width;
+ }
+ let transXpercent = (xFrom) / this.props.HistoBox.SizeConverter.RenderDimension;
+ let transYpercent = (yFrom) / this.props.HistoBox.SizeConverter.RenderDimension;
+ let trans2Xpercent = width == 1 ? "1px" : `${(xFrom + width) / this.props.HistoBox.SizeConverter.RenderDimension * 100}%`;
+ let trans2Ypercent = height == 1 ? "1px" : `${(yFrom + height) / this.props.HistoBox.SizeConverter.RenderDimension * 100}%`;
+ return <div key={DashUtils.GenerateGuid()} className="histogramboxprimitives-placer" style={{ transform: `translate(${transXpercent * 100}%, ${transYpercent * 100}%)` }}>
+ <div className="histogramboxprimitives-line"
+ style={{
+ width: trans2Xpercent,
+ height: trans2Ypercent,
+ }}
+ /></div>;
+ }
+
+ drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, tapHandler: () => void, classExt: string) {
+ let widthPercent = (r.width - 0) / this.props.HistoBox.SizeConverter.RenderDimension;
+ let heightPercent = r.height / this.props.HistoBox.SizeConverter.RenderDimension;
+ let transXpercent = (r.x) / this.props.HistoBox.SizeConverter.RenderDimension;
+ let transYpercent = (r.y) / this.props.HistoBox.SizeConverter.RenderDimension;
+ return (<div key={DashUtils.GenerateGuid()} className={`histogramboxprimitives-placer`} style={{ transform: `translate(${transXpercent * 100}%, ${transYpercent * 100}%)` }}>
+ <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 * 100}%`,
+ height: `${heightPercent * 100}%`,
+ background: color ? `${LABColor.RGBtoHexString(color)}` : ""
+ }}
+ /></div>);
}
render() {
- return <div className="histogramboxprimitives-container">
+ if (!this.props.HistoBox.SizeConverter.Initialized)
+ return (null);
+ let xaxislines = this.xaxislines;
+ let yaxislines = this.yaxislines;
+ return <div className="histogramboxprimitives-container" style={{
+ width: "100%",
+ height: "100%",
+ }}>
+ {xaxislines}
+ {yaxislines}
{this.binPrimitives}
{this.selectedPrimitives}
</div>
@@ -90,6 +146,7 @@ class HistogramBinPrimitive {
public Color: number = StyleConstants.WARNING_COLOR;
public Opacity: number = 1;
public BrushIndex: number = 0;
+ public BarAxis: number = -1;
}
export class HistogramBinPrimitiveCollection {
@@ -202,7 +259,7 @@ export class HistogramBinPrimitiveCollection {
(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);
+ this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
return returnBrushFactorSum;
}
@@ -213,7 +270,7 @@ export class HistogramBinPrimitiveCollection {
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(bin, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+ this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
}
return 0;
}
@@ -229,7 +286,7 @@ export class HistogramBinPrimitiveCollection {
this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
- this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+ 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;
@@ -247,7 +304,7 @@ export class HistogramBinPrimitiveCollection {
this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
2.0);
- this.createBinPrimitive(bin, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+ 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;
@@ -262,7 +319,7 @@ export class HistogramBinPrimitiveCollection {
return !marginResult ? 0 : marginResult.absolutMargin!;
}
- private createBinPrimitive(bin: Bin, brush: Brush, marginRect: PIXIRectangle,
+ 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(
{
@@ -272,7 +329,8 @@ export class HistogramBinPrimitiveCollection {
BrushIndex: brush.brushIndex,
Color: color,
Opacity: opacity,
- DataValue: dataValue
+ DataValue: dataValue,
+ BarAxis: barAxis
});
this.BinPrimitives.push(binPrimitive);
}