import { shortcut, createWidget, toPix, parseFloat, Layout, AbsoluteLayout, clamp, VerticalLayout, isEmptyString, isNumeric, isNull, isInteger, i18nText, size, parseInt, isNotEmptyString, MouseMoveTracker } from "@/core"; import { Single, Editor } from "@/base"; import { AccurateCalculationModel } from "./model.accuratecalculation"; import { SignTextEditor, SliderIconButton } from "../singleslider"; @shortcut() export class IntervalSlider extends Single { static xtype = "bi.interval_slider"; _constant = { EDITOR_WIDTH: 58, EDITOR_R_GAP: 60, EDITOR_HEIGHT: 20, SLIDER_WIDTH_HALF: 15, SLIDER_WIDTH: 30, SLIDER_HEIGHT: 30, TRACK_HEIGHT: 24, } props = { baseCls: "bi-interval-slider bi-slider-track", digit: false, unit: "", min: 0, max: 100, value: { min: "", max: "" }, }; static EVENT_CHANGE = "EVENT_CHANGE"; beforeMount() { const { value, min, max } = this.options; this._setMinAndMax({ min, max, }); this.setValue(value); this.populate(); } render() { const c = this._constant; this.enable = false; this.valueOne = ""; this.valueTwo = ""; this.calculation = new AccurateCalculationModel(); this.grayTrack = createWidget({ type: Layout.xtype, cls: "gray-track", height: 6, }); this.blueTrack = createWidget({ type: Layout.xtype, cls: "blue-track bi-high-light-background", height: 6, }); this.track = this._createTrackWrapper(); this.labelOne = createWidget({ type: SignTextEditor.xtype, cls: "slider-editor-button", text: this.options.unit, allowBlank: false, width: toPix(c.EDITOR_WIDTH, 2), height: toPix(c.EDITOR_HEIGHT, 2), validationChecker: v => this._checkValidation(v), }); this.labelOne.element.hover( () => { this.labelOne.element .removeClass("bi-border") .addClass("bi-border"); }, () => { this.labelOne.element.removeClass("bi-border"); } ); this.labelOne.on(Editor.EVENT_CONFIRM, () => { const oldValueOne = this.valueOne; const v = parseFloat(this.getValue()); this.valueOne = v; const percent = this._getPercentByValue(v); const significantPercent = parseFloat(percent.toFixed(1)); // 分成1000份 this._setSliderOnePosition(significantPercent); this._setBlueTrack(); this._checkLabelPosition( oldValueOne, this.valueTwo, this.valueOne, this.valueTwo ); this.fireEvent(IntervalSlider.EVENT_CHANGE); }); this.labelTwo = createWidget({ type: SignTextEditor.xtype, cls: "slider-editor-button", text: this.options.unit, allowBlank: false, width: toPix(c.EDITOR_WIDTH, 2), height: toPix(c.EDITOR_HEIGHT, 2), validationChecker: v => this._checkValidation(v), }); this.labelTwo.element.hover( () => { this.labelTwo.element .removeClass("bi-border") .addClass("bi-border"); }, () => { this.labelTwo.element.removeClass("bi-border"); } ); this.labelTwo.on(Editor.EVENT_CONFIRM, () => { const oldValueTwo = this.valueTwo; const v = parseFloat(this.getValue()); this.valueTwo = v; const percent = this._getPercentByValue(v); const significantPercent = parseFloat(percent.toFixed(1)); this._setSliderTwoPosition(significantPercent); this._setBlueTrack(); this._checkLabelPosition( this.valueOne, oldValueTwo, this.valueOne, this.valueTwo ); this.fireEvent(IntervalSlider.EVENT_CHANGE); }); this.sliderOne = createWidget({ type: SliderIconButton.xtype, }); this.sliderTwo = createWidget({ type: SliderIconButton.xtype, }); this._draggable(this.sliderOne, true); this._draggable(this.sliderTwo, false); this._setVisible(false); return { type: "bi.vertical_fill", rowSize: [30, 30], items: [ this._createLabelWrapper(), { type: AbsoluteLayout.xtype, items: [ { el: { type: "bi.horizontal", horizontalAlign: "stretch", verticalAlign: "middle", columnSize: ["fill"], items: [ { el: this.track, } ], hgap: 10, }, inset: 0, }, this._createSliderWrapper() ], } ], }; } _rePosBySizeAfterMove(size, isLeft) { const o = this.options; const percent = (size * 100) / this._getGrayTrackLength(); const significantPercent = parseFloat(percent.toFixed(1)); let v = this._getValueByPercent(significantPercent); v = this._assertValue(v); v = o.digit === false ? v : v.toFixed(o.digit); const oldValueOne = this.valueOne, oldValueTwo = this.valueTwo; if (isLeft) { this._setSliderOnePosition(significantPercent); this.labelOne.setValue(v); this.valueOne = v; this._checkLabelPosition( oldValueOne, oldValueTwo, v, this.valueTwo ); } else { this._setSliderTwoPosition(significantPercent); this.labelTwo.setValue(v); this.valueTwo = v; this._checkLabelPosition( oldValueOne, oldValueTwo, this.valueOne, v ); } this._setBlueTrack(); } _rePosBySizeAfterStop(size, isLeft) { const percent = (size * 100) / this._getGrayTrackLength(); const significantPercent = parseFloat(percent.toFixed(1)); isLeft ? this._setSliderOnePosition(significantPercent) : this._setSliderTwoPosition(significantPercent); } _draggable(widget, isLeft) { let startDrag = false; let size = 0, offset = 0, defaultSize = 0; const mouseMoveTracker = new MouseMoveTracker( (deltaX => { if (mouseMoveTracker.isDragging()) { startDrag = true; offset += deltaX; size = optimizeSize(defaultSize + offset); widget.element.addClass("dragging"); this._rePosBySizeAfterMove(size, isLeft); } }), (() => { if (startDrag === true) { size = optimizeSize(size); this._rePosBySizeAfterStop(size, isLeft); size = 0; offset = 0; defaultSize = size; startDrag = false; } widget.element.removeClass("dragging"); mouseMoveTracker.releaseMouseMoves(); this.fireEvent(IntervalSlider.EVENT_CHANGE); }), window ); widget.element.on("mousedown", function (event) { if (!widget.isEnabled()) { return; } defaultSize = this.offsetLeft; optimizeSize(defaultSize); mouseMoveTracker.captureMouseMoves(event); }); const optimizeSize = s => clamp(s, 0, this._getGrayTrackLength()); } _createLabelWrapper() { const c = this._constant; return { el: { type: VerticalLayout.xtype, items: [ { type: AbsoluteLayout.xtype, items: [ { el: this.labelOne, top: 0, left: 0, } ], }, { type: AbsoluteLayout.xtype, items: [ { el: this.labelTwo, top: 0, right: 0, } ], } ], rgap: c.EDITOR_R_GAP, height: c.SLIDER_HEIGHT, }, top: 0, left: 0, width: "100%", }; } _createSliderWrapper() { return { el: { type: "bi.horizontal", horizontalAlign: "stretch", verticalAlign: "middle", items: [ { type: AbsoluteLayout.xtype, height: 12, width: "fill", items: [ { el: this.sliderOne, top: 0, bottom: 0, left: 0, }, { el: this.sliderTwo, top: 0, bottom: 0, left: "100%", } ], } ], hgap: 10, }, inset: 0, }; } _createTrackWrapper() { return createWidget({ type: "bi.horizontal", cls: "track-wrapper", horizontalAlign: "stretch", verticalAlign: "middle", columnSize: ["fill"], scrollx: false, items: [ { type: AbsoluteLayout.xtype, height: 6, items: [ { el: this.grayTrack, top: 0, left: 0, bottom: 0, width: "100%", }, { el: this.blueTrack, top: 0, left: 0, bottom: 0, width: "0%", } ], } ], }); } _checkValidation(v) { const o = this.options; let valid = false; // 像90.这样的既不属于整数又不属于小数,是不合法的值 let dotText = (`${v}`).split(".")[1]; // eslint-disable-next-line no-empty if (isEmptyString(dotText)) { } else { if (isNumeric(v) && !(isNull(v) || v < this.min || v > this.max)) { // 虽然规定了所填写的小数位数,但是我们认为所有的整数都是满足设置的小数位数的 // 100等价于100.0 100.00 100.000 if (o.digit === false || isInteger(v)) { valid = true; } else { dotText = dotText || ""; valid = dotText.length === o.digit; } } } return valid; } _checkOverlap() { const labelOneLeft = this.labelOne.element[0].offsetLeft; const labelTwoLeft = this.labelTwo.element[0].offsetLeft; if (labelOneLeft <= labelTwoLeft) { if (labelTwoLeft - labelOneLeft < 90) { this.labelTwo.element.css({ top: 40 }); } else { this.labelTwo.element.css({ top: 0 }); } } else { if (labelOneLeft - labelTwoLeft < 90) { this.labelTwo.element.css({ top: 40 }); } else { this.labelTwo.element.css({ top: 0 }); } } } _checkLabelPosition(oldValueOne, oldValueTwo, valueOne, valueTwo, isLeft) { oldValueOne = parseFloat(oldValueOne); oldValueTwo = parseFloat(oldValueTwo); valueOne = parseFloat(valueOne); valueTwo = parseFloat(valueTwo); if ( (oldValueOne <= oldValueTwo && valueOne > valueTwo) || (oldValueOne >= oldValueTwo && valueOne < valueTwo) ) { const isSliderOneLeft = parseFloat(this.labelOne.getValue()) < parseFloat(this.labelTwo.getValue()); this._resetLabelPosition(!isSliderOneLeft); } } _resetLabelPosition(needReverse) { this.labelOne.element.css({ left: needReverse ? "100%" : "0%" }); this.labelTwo.element.css({ left: needReverse ? "0%" : "100%" }); } _setSliderOnePosition(percent) { this.sliderOne.element.css({ left: `${percent}%` }); } _setSliderTwoPosition(percent) { this.sliderTwo.element.css({ left: `${percent}%` }); } _setBlueTrackLeft(percent) { this.blueTrack.element.css({ left: `${percent}%` }); } _setBlueTrackWidth(percent) { this.blueTrack.element.css({ width: `${percent}%` }); } _setBlueTrack() { const percentOne = this._getPercentByValue(this.labelOne.getValue()); const percentTwo = this._getPercentByValue(this.labelTwo.getValue()); if (percentOne <= percentTwo) { this._setBlueTrackLeft(percentOne); this._setBlueTrackWidth(percentTwo - percentOne); } else { this._setBlueTrackLeft(percentTwo); this._setBlueTrackWidth(percentOne - percentTwo); } } _setAllPosition(one, two) { this._setSliderOnePosition(one); this._setSliderTwoPosition(two); this._setBlueTrack(); } _setVisible(visible) { this.sliderOne.setVisible(visible); this.sliderTwo.setVisible(visible); this.labelOne.setVisible(visible); this.labelTwo.setVisible(visible); } _setErrorText() { const errorText = i18nText( "BI-Basic_Please_Enter_Number_Between", this.min, this.max ); this.labelOne.setErrorText(errorText); this.labelTwo.setErrorText(errorText); } _getGrayTrackLength() { return this.grayTrack.element[0].scrollWidth; } _getValueByPercent(percent) { // return (((max-min)*percent)/100+min) if (percent === 0) { return this.min; } if (percent === 100) { return this.max; } const sub = this.calculation.accurateSubtraction(this.max, this.min); const mul = this.calculation.accurateMultiplication(sub, percent); const div = this.calculation.accurateDivisionTenExponent(mul, 2); if (this.precision < 0) { const value = parseFloat( this.calculation.accurateAddition(div, this.min) ); const reduceValue = Math.round( this.calculation.accurateDivisionTenExponent( value, -this.precision ) ); return this.calculation.accurateMultiplication( reduceValue, Math.pow(10, -this.precision) ); } return parseFloat( this.calculation .accurateAddition(div, this.min) .toFixed(this.precision) ); } _getPercentByValue(v) { return ((v - this.min) * 100) / (this.max - this.min); } _getPrecision() { // 计算每一份值的精度(最大值和最小值的差值保留4为有效数字后的精度) // 如果差值的整数位数大于4,toPrecision(4)得到的是科学计数法123456 => 1.235e+5 // 返回非负值: 保留的小数位数 // 返回负值: 保留的10^n精度中的n const sub = this.calculation.accurateSubtraction(this.max, this.min); const pre = sub.toPrecision(4); // 科学计数法 const eIndex = pre.indexOf("e"); let arr = []; if (eIndex > -1) { arr = pre.split("e"); const decimalPartLength = size(arr[0].split(".")[1]); const sciencePartLength = parseInt(arr[1].substring(1)); return decimalPartLength - sciencePartLength; } arr = pre.split("."); return arr.length > 1 ? arr[1].length : 0; } _assertValue(value) { if (value <= this.min) { return this.min; } if (value >= this.max) { return this.max; } return value; } _setEnable(b) { super._setEnable.apply(this, [b]); if (b) { this.blueTrack.element .removeClass("disabled-blue-track") .addClass("blue-track"); } else { this.blueTrack.element .removeClass("blue-track") .addClass("disabled-blue-track"); } } getValue() { if (this.valueOne <= this.valueTwo) { return { min: this.valueOne, max: this.valueTwo }; } return { min: this.valueTwo, max: this.valueOne }; } _setMinAndMax(v) { const minNumber = parseFloat(v.min); const maxNumber = parseFloat(v.max); if (!isNaN(minNumber) && !isNaN(maxNumber) && maxNumber >= minNumber) { this.min = minNumber; this.max = maxNumber; this.valueOne = minNumber; this.valueTwo = maxNumber; this.precision = this._getPrecision(); } } setMinAndMax(v) { this._setMinAndMax(v); this.setEnable(v.min <= v.max); } setValue(v) { const o = this.options; let valueOne = parseFloat(v.min); let valueTwo = parseFloat(v.max); valueOne = o.digit === false ? valueOne : parseFloat(valueOne.toFixed(o.digit)); valueTwo = o.digit === false ? valueTwo : parseFloat(valueTwo.toFixed(o.digit)); if (!isNaN(valueOne) && !isNaN(valueTwo)) { if (this._checkValidation(valueOne)) { this.valueOne = this.valueOne <= this.valueTwo ? valueOne : valueTwo; } if (this._checkValidation(valueTwo)) { this.valueTwo = this.valueOne <= this.valueTwo ? valueTwo : valueOne; } if (valueOne < this.min) { this.valueOne = this.min; } if (valueTwo > this.max) { this.valueTwo = this.max; } } } reset() { this._setVisible(false); this.enable = false; this.valueOne = ""; this.valueTwo = ""; this.min = NaN; this.max = NaN; this._setBlueTrackWidth(0); } populate() { const o = this.options; if (!isNaN(this.min) && !isNaN(this.max)) { this.enable = true; this._setVisible(true); this._setErrorText(); if ( (isNumeric(this.valueOne) || isNotEmptyString(this.valueOne)) && (isNumeric(this.valueTwo) || isNotEmptyString(this.valueTwo)) ) { this.labelOne.setValue( o.digit === false ? this.valueOne : parseFloat(this.valueOne).toFixed(o.digit) ); this.labelTwo.setValue( o.digit === false ? this.valueTwo : parseFloat(this.valueTwo).toFixed(o.digit) ); this._setAllPosition( this._getPercentByValue(this.valueOne), this._getPercentByValue(this.valueTwo) ); } else { this.labelOne.setValue(this.min); this.labelTwo.setValue(this.max); this._setAllPosition(0, 100); } this._resetLabelPosition(this.valueOne > this.valueTwo); } } }