diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/northstar/utils/SizeConverter.ts | 82 | ||||
-rw-r--r-- | src/client/views/nodes/HistogramBox.scss | 22 | ||||
-rw-r--r-- | src/client/views/nodes/HistogramBox.tsx | 146 | ||||
-rw-r--r-- | src/client/views/nodes/HistogramBoxPrimitives.scss | 19 | ||||
-rw-r--r-- | src/client/views/nodes/HistogramBoxPrimitives.tsx | 106 |
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); } |