@visactor/vutils 1.0.24 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.zh-CN.md +12 -0
- package/REGRESSION.md +36 -0
- package/cjs/angle.d.ts +61 -0
- package/cjs/angle.js +73 -7
- package/cjs/angle.js.map +1 -1
- package/cjs/color/Color.d.ts +3 -0
- package/cjs/color/Color.js +64 -10
- package/cjs/color/Color.js.map +1 -1
- package/cjs/color/hexToRgb.d.ts +5 -0
- package/cjs/color/hexToRgb.js +5 -0
- package/cjs/color/hexToRgb.js.map +1 -1
- package/cjs/color/rgbToHsl.js +5 -1
- package/cjs/color/rgbToHsl.js.map +1 -1
- package/cjs/common/array.d.ts +43 -0
- package/cjs/common/array.js +44 -0
- package/cjs/common/array.js.map +1 -1
- package/cjs/common/bisect.d.ts +21 -0
- package/cjs/common/bisect.js +29 -0
- package/cjs/common/bisect.js.map +1 -1
- package/cjs/common/bootstrap.d.ts +5 -0
- package/cjs/common/bootstrap.js +22 -0
- package/cjs/common/bootstrap.js.map +1 -0
- package/cjs/common/clone.js +6 -0
- package/cjs/common/clone.js.map +1 -1
- package/cjs/common/cloneDeep.js +4 -1
- package/cjs/common/cloneDeep.js.map +1 -1
- package/cjs/common/correlation.d.ts +3 -0
- package/cjs/common/correlation.js +83 -0
- package/cjs/common/correlation.js.map +1 -0
- package/cjs/common/debounce.js +22 -4
- package/cjs/common/debounce.js.map +1 -1
- package/cjs/common/deviation.js +1 -2
- package/cjs/common/ecdf.d.ts +8 -0
- package/cjs/common/ecdf.js +11 -2
- package/cjs/common/ecdf.js.map +1 -1
- package/cjs/common/extent.d.ts +4 -0
- package/cjs/common/extent.js +4 -2
- package/cjs/common/extent.js.map +1 -1
- package/cjs/common/field.d.ts +6 -0
- package/cjs/common/field.js +3 -0
- package/cjs/common/field.js.map +1 -1
- package/cjs/common/has.d.ts +17 -0
- package/cjs/common/has.js +18 -1
- package/cjs/common/has.js.map +1 -1
- package/cjs/common/index.d.ts +5 -0
- package/cjs/common/index.js +34 -6
- package/cjs/common/index.js.map +1 -1
- package/cjs/common/interpolate.js +16 -3
- package/cjs/common/interpolate.js.map +1 -1
- package/cjs/common/isBase64.d.ts +6 -0
- package/cjs/common/isBase64.js +6 -0
- package/cjs/common/isBase64.js.map +1 -1
- package/cjs/common/isBoolean.d.ts +13 -0
- package/cjs/common/isBoolean.js +13 -1
- package/cjs/common/isBoolean.js.map +1 -1
- package/cjs/common/isEmpty.js +11 -1
- package/cjs/common/isEmpty.js.map +1 -1
- package/cjs/common/isEqual.js +19 -7
- package/cjs/common/isEqual.js.map +1 -1
- package/cjs/common/isFunction.d.ts +25 -0
- package/cjs/common/isFunction.js +27 -2
- package/cjs/common/isFunction.js.map +1 -1
- package/cjs/common/isNull.js +2 -1
- package/cjs/common/isNumber.d.ts +23 -0
- package/cjs/common/isNumber.js +23 -1
- package/cjs/common/isNumber.js.map +1 -1
- package/cjs/common/isNumeric.d.ts +25 -0
- package/cjs/common/isNumeric.js +25 -0
- package/cjs/common/isNumeric.js.map +1 -1
- package/cjs/common/isObject.d.ts +21 -0
- package/cjs/common/isObject.js +22 -0
- package/cjs/common/isObject.js.map +1 -1
- package/cjs/common/isObjectLike.d.ts +20 -0
- package/cjs/common/isObjectLike.js +21 -0
- package/cjs/common/isObjectLike.js.map +1 -1
- package/cjs/common/isPlainObject.d.ts +25 -0
- package/cjs/common/isPlainObject.js.map +1 -1
- package/cjs/common/isPrototype.js +1 -1
- package/cjs/common/isRegExp.js +1 -1
- package/cjs/common/isShallowEqual.js +1 -0
- package/cjs/common/isShallowEqual.js.map +1 -1
- package/cjs/common/isString.d.ts +13 -0
- package/cjs/common/isString.js +13 -1
- package/cjs/common/isString.js.map +1 -1
- package/cjs/common/isType.d.ts +6 -0
- package/cjs/common/isType.js +6 -0
- package/cjs/common/isType.js.map +1 -1
- package/cjs/common/isValidNumber.js.map +1 -1
- package/cjs/common/isValidUrl.js.map +1 -1
- package/cjs/common/kde.d.ts +28 -0
- package/cjs/common/kde.js +29 -5
- package/cjs/common/kde.js.map +1 -1
- package/cjs/common/mean.d.ts +2 -0
- package/cjs/common/mean.js +25 -0
- package/cjs/common/mean.js.map +1 -0
- package/cjs/common/merge.js +20 -4
- package/cjs/common/merge.js.map +1 -1
- package/cjs/common/mixin.js +14 -3
- package/cjs/common/mixin.js.map +1 -1
- package/cjs/common/normalQuantile.d.ts +5 -0
- package/cjs/common/normalQuantile.js +21 -0
- package/cjs/common/normalQuantile.js.map +1 -0
- package/cjs/common/number.d.ts +24 -0
- package/cjs/common/number.js +24 -1
- package/cjs/common/number.js.map +1 -1
- package/cjs/common/pad.d.ts +21 -0
- package/cjs/common/pad.js +21 -1
- package/cjs/common/pad.js.map +1 -1
- package/cjs/common/random.d.ts +3 -0
- package/cjs/common/random.js +13 -0
- package/cjs/common/random.js.map +1 -1
- package/cjs/common/regression-bootstrap.d.ts +14 -0
- package/cjs/common/regression-bootstrap.js +37 -0
- package/cjs/common/regression-bootstrap.js.map +1 -0
- package/cjs/common/regression-linear.d.ts +5 -0
- package/cjs/common/regression-linear.js +28 -39
- package/cjs/common/regression-linear.js.map +1 -1
- package/cjs/common/regression-logistic.d.ts +6 -0
- package/cjs/common/regression-logistic.js +25 -14
- package/cjs/common/regression-logistic.js.map +1 -1
- package/cjs/common/regression-lowess.d.ts +11 -1
- package/cjs/common/regression-lowess.js +125 -118
- package/cjs/common/regression-lowess.js.map +1 -1
- package/cjs/common/regression-polynomial.d.ts +1 -0
- package/cjs/common/regression-polynomial.js +24 -94
- package/cjs/common/regression-polynomial.js.map +1 -1
- package/cjs/common/regression-solver.d.ts +26 -0
- package/cjs/common/regression-solver.js +180 -0
- package/cjs/common/regression-solver.js.map +1 -0
- package/cjs/common/regression-utils.d.ts +13 -1
- package/cjs/common/regression-utils.js +27 -12
- package/cjs/common/regression-utils.js.map +1 -1
- package/cjs/common/throttle.js +3 -0
- package/cjs/common/throttle.js.map +1 -1
- package/cjs/common/toDate.js +22 -3
- package/cjs/common/toDate.js.map +1 -1
- package/cjs/common/uuid.d.ts +7 -0
- package/cjs/common/uuid.js +15 -1
- package/cjs/common/uuid.js.map +1 -1
- package/cjs/data-structure/bounds.d.ts +101 -0
- package/cjs/data-structure/bounds.js +74 -7
- package/cjs/data-structure/bounds.js.map +1 -1
- package/cjs/data-structure/hashTable.d.ts +4 -0
- package/cjs/data-structure/hashTable.js +11 -2
- package/cjs/data-structure/hashTable.js.map +1 -1
- package/cjs/data-structure/index.js +6 -2
- package/cjs/data-structure/index.js.map +1 -1
- package/cjs/data-structure/matrix.d.ts +91 -0
- package/cjs/data-structure/matrix.js +52 -4
- package/cjs/data-structure/matrix.js.map +1 -1
- package/cjs/data-structure/point.d.ts +3 -0
- package/cjs/data-structure/point.js +5 -1
- package/cjs/data-structure/point.js.map +1 -1
- package/cjs/dom.d.ts +36 -0
- package/cjs/dom.js +43 -1
- package/cjs/dom.js.map +1 -1
- package/cjs/fmin/blas1.js +10 -1
- package/cjs/fmin/blas1.js.map +1 -1
- package/cjs/fmin/conjugate-gradient.js +18 -3
- package/cjs/fmin/conjugate-gradient.js.map +1 -1
- package/cjs/fmin/linesearch.js +15 -1
- package/cjs/fmin/linesearch.js.map +1 -1
- package/cjs/fmin/nelder-mead.d.ts +1 -0
- package/cjs/fmin/nelder-mead.js +30 -10
- package/cjs/fmin/nelder-mead.js.map +1 -1
- package/cjs/format/number/formatDecimal.js +9 -1
- package/cjs/format/number/formatDecimal.js.map +1 -1
- package/cjs/format/number/formatGroup.js +7 -0
- package/cjs/format/number/formatGroup.js.map +1 -1
- package/cjs/format/number/formatPrefixAuto.js +8 -0
- package/cjs/format/number/formatPrefixAuto.js.map +1 -1
- package/cjs/format/number/formatRounded.js +7 -0
- package/cjs/format/number/formatRounded.js.map +1 -1
- package/cjs/format/number/formatTrim.js +7 -0
- package/cjs/format/number/formatTrim.js.map +1 -1
- package/cjs/format/number/number.js +39 -9
- package/cjs/format/number/number.js.map +1 -1
- package/cjs/format/time.js +23 -6
- package/cjs/format/time.js.map +1 -1
- package/cjs/geo/circle-intersection.d.ts +12 -0
- package/cjs/geo/circle-intersection.js +54 -9
- package/cjs/geo/circle-intersection.js.map +1 -1
- package/cjs/geo/constant.js +8 -1
- package/cjs/geo/constant.js.map +1 -1
- package/cjs/geo/invariant.d.ts +22 -0
- package/cjs/geo/invariant.js +24 -1
- package/cjs/geo/invariant.js.map +1 -1
- package/cjs/graphics/algorithm/aabb.js +7 -3
- package/cjs/graphics/algorithm/aabb.js.map +1 -1
- package/cjs/graphics/algorithm/intersect.d.ts +48 -0
- package/cjs/graphics/algorithm/intersect.js +75 -7
- package/cjs/graphics/algorithm/intersect.js.map +1 -1
- package/cjs/graphics/bounds-util.js +4 -2
- package/cjs/graphics/bounds-util.js.map +1 -1
- package/cjs/graphics/graph-util.js +4 -0
- package/cjs/graphics/graph-util.js.map +1 -1
- package/cjs/graphics/index.js +7 -4
- package/cjs/graphics/index.js.map +1 -1
- package/cjs/graphics/polygon.d.ts +12 -0
- package/cjs/graphics/polygon.js +28 -5
- package/cjs/graphics/polygon.js.map +1 -1
- package/cjs/graphics/text/measure/interface.d.ts +12 -0
- package/cjs/graphics/text/measure/textMeasure.d.ts +23 -0
- package/cjs/graphics/text/measure/textMeasure.js +50 -22
- package/cjs/graphics/text/measure/textMeasure.js.map +1 -1
- package/cjs/graphics/text/measure/util.js +1 -0
- package/cjs/graphics/text/measure/util.js.map +1 -1
- package/cjs/graphics/text/stringWidth.js +7 -3
- package/cjs/graphics/text/stringWidth.js.map +1 -1
- package/cjs/index.d.ts +6 -0
- package/cjs/index.js +19 -4
- package/cjs/index.js.map +1 -1
- package/cjs/logger.js +12 -2
- package/cjs/logger.js.map +1 -1
- package/cjs/lru.js +14 -6
- package/cjs/lru.js.map +1 -1
- package/cjs/math.d.ts +9 -0
- package/cjs/math.js +10 -0
- package/cjs/math.js.map +1 -1
- package/cjs/padding.d.ts +5 -0
- package/cjs/padding.js +5 -0
- package/cjs/padding.js.map +1 -1
- package/cjs/time/formatUtils.d.ts +6 -0
- package/cjs/time/formatUtils.js +7 -0
- package/cjs/time/formatUtils.js.map +1 -1
- package/dist/index.js +2035 -411
- package/dist/index.min.js +128 -1
- package/es/angle.d.ts +61 -0
- package/es/angle.js +66 -10
- package/es/angle.js.map +1 -1
- package/es/color/Color.d.ts +3 -0
- package/es/color/Color.js +64 -9
- package/es/color/Color.js.map +1 -1
- package/es/color/hexToRgb.d.ts +5 -0
- package/es/color/hexToRgb.js +5 -0
- package/es/color/hexToRgb.js.map +1 -1
- package/es/color/rgbToHsl.js +5 -1
- package/es/color/rgbToHsl.js.map +1 -1
- package/es/common/array.d.ts +43 -0
- package/es/common/array.js +44 -8
- package/es/common/array.js.map +1 -1
- package/es/common/bisect.d.ts +21 -0
- package/es/common/bisect.js +29 -4
- package/es/common/bisect.js.map +1 -1
- package/es/common/bootstrap.d.ts +5 -0
- package/es/common/bootstrap.js +14 -0
- package/es/common/bootstrap.js.map +1 -0
- package/es/common/clone.js +6 -0
- package/es/common/clone.js.map +1 -1
- package/es/common/cloneDeep.js +4 -1
- package/es/common/cloneDeep.js.map +1 -1
- package/es/common/correlation.d.ts +3 -0
- package/es/common/correlation.js +77 -0
- package/es/common/correlation.js.map +1 -0
- package/es/common/debounce.js +22 -4
- package/es/common/debounce.js.map +1 -1
- package/es/common/deviation.js +1 -2
- package/es/common/ecdf.d.ts +8 -0
- package/es/common/ecdf.js +11 -1
- package/es/common/ecdf.js.map +1 -1
- package/es/common/extent.d.ts +4 -0
- package/es/common/extent.js +8 -3
- package/es/common/extent.js.map +1 -1
- package/es/common/field.d.ts +6 -0
- package/es/common/field.js +3 -1
- package/es/common/field.js.map +1 -1
- package/es/common/has.d.ts +17 -0
- package/es/common/has.js +18 -1
- package/es/common/has.js.map +1 -1
- package/es/common/index.d.ts +5 -0
- package/es/common/index.js +24 -0
- package/es/common/index.js.map +1 -1
- package/es/common/interpolate.js +16 -3
- package/es/common/interpolate.js.map +1 -1
- package/es/common/isBase64.d.ts +6 -0
- package/es/common/isBase64.js +6 -0
- package/es/common/isBase64.js.map +1 -1
- package/es/common/isBoolean.d.ts +13 -0
- package/es/common/isBoolean.js +13 -1
- package/es/common/isBoolean.js.map +1 -1
- package/es/common/isEmpty.js +11 -1
- package/es/common/isEmpty.js.map +1 -1
- package/es/common/isEqual.js +19 -7
- package/es/common/isEqual.js.map +1 -1
- package/es/common/isFunction.d.ts +25 -0
- package/es/common/isFunction.js +27 -2
- package/es/common/isFunction.js.map +1 -1
- package/es/common/isNull.js +2 -1
- package/es/common/isNumber.d.ts +23 -0
- package/es/common/isNumber.js +23 -1
- package/es/common/isNumber.js.map +1 -1
- package/es/common/isNumeric.d.ts +25 -0
- package/es/common/isNumeric.js +25 -0
- package/es/common/isNumeric.js.map +1 -1
- package/es/common/isObject.d.ts +21 -0
- package/es/common/isObject.js +22 -0
- package/es/common/isObject.js.map +1 -1
- package/es/common/isObjectLike.d.ts +20 -0
- package/es/common/isObjectLike.js +21 -0
- package/es/common/isObjectLike.js.map +1 -1
- package/es/common/isPlainObject.d.ts +25 -0
- package/es/common/isPlainObject.js +26 -0
- package/es/common/isPlainObject.js.map +1 -1
- package/es/common/isPrototype.js +1 -1
- package/es/common/isRegExp.js +1 -1
- package/es/common/isShallowEqual.js +1 -0
- package/es/common/isShallowEqual.js.map +1 -1
- package/es/common/isString.d.ts +13 -0
- package/es/common/isString.js +13 -1
- package/es/common/isString.js.map +1 -1
- package/es/common/isType.d.ts +6 -0
- package/es/common/isType.js +6 -0
- package/es/common/isType.js.map +1 -1
- package/es/common/isValidNumber.js.map +1 -1
- package/es/common/isValidUrl.js.map +1 -1
- package/es/common/kde.d.ts +28 -0
- package/es/common/kde.js +29 -7
- package/es/common/kde.js.map +1 -1
- package/es/common/mean.d.ts +2 -0
- package/es/common/mean.js +19 -0
- package/es/common/mean.js.map +1 -0
- package/es/common/merge.js +22 -5
- package/es/common/merge.js.map +1 -1
- package/es/common/mixin.js +14 -3
- package/es/common/mixin.js.map +1 -1
- package/es/common/normalQuantile.d.ts +5 -0
- package/es/common/normalQuantile.js +15 -0
- package/es/common/normalQuantile.js.map +1 -0
- package/es/common/number.d.ts +24 -0
- package/es/common/number.js +24 -2
- package/es/common/number.js.map +1 -1
- package/es/common/pad.d.ts +21 -0
- package/es/common/pad.js +21 -1
- package/es/common/pad.js.map +1 -1
- package/es/common/random.d.ts +3 -0
- package/es/common/random.js +13 -1
- package/es/common/random.js.map +1 -1
- package/es/common/regression-bootstrap.d.ts +14 -0
- package/es/common/regression-bootstrap.js +31 -0
- package/es/common/regression-bootstrap.js.map +1 -0
- package/es/common/regression-linear.d.ts +5 -0
- package/es/common/regression-linear.js +25 -39
- package/es/common/regression-linear.js.map +1 -1
- package/es/common/regression-logistic.d.ts +6 -0
- package/es/common/regression-logistic.js +24 -16
- package/es/common/regression-logistic.js.map +1 -1
- package/es/common/regression-lowess.d.ts +11 -1
- package/es/common/regression-lowess.js +120 -118
- package/es/common/regression-lowess.js.map +1 -1
- package/es/common/regression-polynomial.d.ts +1 -0
- package/es/common/regression-polynomial.js +21 -89
- package/es/common/regression-polynomial.js.map +1 -1
- package/es/common/regression-solver.d.ts +26 -0
- package/es/common/regression-solver.js +168 -0
- package/es/common/regression-solver.js.map +1 -0
- package/es/common/regression-utils.d.ts +13 -1
- package/es/common/regression-utils.js +18 -9
- package/es/common/regression-utils.js.map +1 -1
- package/es/common/throttle.js +3 -0
- package/es/common/throttle.js.map +1 -1
- package/es/common/toDate.js +23 -3
- package/es/common/toDate.js.map +1 -1
- package/es/common/uuid.d.ts +7 -0
- package/es/common/uuid.js +15 -1
- package/es/common/uuid.js.map +1 -1
- package/es/data-structure/bounds.d.ts +101 -0
- package/es/data-structure/bounds.js +74 -8
- package/es/data-structure/bounds.js.map +1 -1
- package/es/data-structure/hashTable.d.ts +4 -0
- package/es/data-structure/hashTable.js +11 -2
- package/es/data-structure/hashTable.js.map +1 -1
- package/es/data-structure/index.js +2 -0
- package/es/data-structure/index.js.map +1 -1
- package/es/data-structure/matrix.d.ts +91 -0
- package/es/data-structure/matrix.js +52 -5
- package/es/data-structure/matrix.js.map +1 -1
- package/es/data-structure/point.d.ts +3 -0
- package/es/data-structure/point.js +5 -1
- package/es/data-structure/point.js.map +1 -1
- package/es/dom.d.ts +36 -0
- package/es/dom.js +43 -8
- package/es/dom.js.map +1 -1
- package/es/fmin/blas1.js +10 -0
- package/es/fmin/blas1.js.map +1 -1
- package/es/fmin/conjugate-gradient.js +18 -2
- package/es/fmin/conjugate-gradient.js.map +1 -1
- package/es/fmin/linesearch.js +15 -0
- package/es/fmin/linesearch.js.map +1 -1
- package/es/fmin/nelder-mead.d.ts +1 -0
- package/es/fmin/nelder-mead.js +29 -9
- package/es/fmin/nelder-mead.js.map +1 -1
- package/es/format/number/formatDecimal.js +9 -1
- package/es/format/number/formatDecimal.js.map +1 -1
- package/es/format/number/formatGroup.js +7 -0
- package/es/format/number/formatGroup.js.map +1 -1
- package/es/format/number/formatPrefixAuto.js +8 -0
- package/es/format/number/formatPrefixAuto.js.map +1 -1
- package/es/format/number/formatRounded.js +7 -0
- package/es/format/number/formatRounded.js.map +1 -1
- package/es/format/number/formatTrim.js +7 -0
- package/es/format/number/formatTrim.js.map +1 -1
- package/es/format/number/number.js +40 -8
- package/es/format/number/number.js.map +1 -1
- package/es/format/time.js +23 -5
- package/es/format/time.js.map +1 -1
- package/es/geo/circle-intersection.d.ts +12 -0
- package/es/geo/circle-intersection.js +54 -14
- package/es/geo/circle-intersection.js.map +1 -1
- package/es/geo/constant.js +8 -0
- package/es/geo/constant.js.map +1 -1
- package/es/geo/invariant.d.ts +22 -0
- package/es/geo/invariant.js +24 -0
- package/es/geo/invariant.js.map +1 -1
- package/es/graphics/algorithm/aabb.js +7 -3
- package/es/graphics/algorithm/aabb.js.map +1 -1
- package/es/graphics/algorithm/intersect.d.ts +48 -0
- package/es/graphics/algorithm/intersect.js +75 -12
- package/es/graphics/algorithm/intersect.js.map +1 -1
- package/es/graphics/bounds-util.js +4 -2
- package/es/graphics/bounds-util.js.map +1 -1
- package/es/graphics/graph-util.js +4 -0
- package/es/graphics/graph-util.js.map +1 -1
- package/es/graphics/index.js +2 -0
- package/es/graphics/index.js.map +1 -1
- package/es/graphics/polygon.d.ts +12 -0
- package/es/graphics/polygon.js +28 -6
- package/es/graphics/polygon.js.map +1 -1
- package/es/graphics/text/measure/interface.d.ts +12 -0
- package/es/graphics/text/measure/textMeasure.d.ts +23 -0
- package/es/graphics/text/measure/textMeasure.js +46 -21
- package/es/graphics/text/measure/textMeasure.js.map +1 -1
- package/es/graphics/text/measure/util.js +1 -0
- package/es/graphics/text/measure/util.js.map +1 -1
- package/es/graphics/text/stringWidth.js +7 -3
- package/es/graphics/text/stringWidth.js.map +1 -1
- package/es/index.d.ts +6 -0
- package/es/index.js +15 -1
- package/es/index.js.map +1 -1
- package/es/logger.js +12 -1
- package/es/logger.js.map +1 -1
- package/es/lru.js +14 -6
- package/es/lru.js.map +1 -1
- package/es/math.d.ts +9 -0
- package/es/math.js +10 -1
- package/es/math.js.map +1 -1
- package/es/padding.d.ts +5 -0
- package/es/padding.js +5 -1
- package/es/padding.js.map +1 -1
- package/es/time/formatUtils.d.ts +6 -0
- package/es/time/formatUtils.js +7 -1
- package/es/time/formatUtils.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -325,8 +325,27 @@
|
|
|
325
325
|
var eventemitter3Exports = eventemitter3.exports;
|
|
326
326
|
var index$1 = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
|
|
327
327
|
|
|
328
|
+
/**
|
|
329
|
+
* check value type
|
|
330
|
+
* @param value the value to check
|
|
331
|
+
* @param type type
|
|
332
|
+
* @returns
|
|
333
|
+
*/
|
|
328
334
|
const isType = (value, type) => Object.prototype.toString.call(value) === `[object ${type}]`;
|
|
329
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Checks if `value` is classified as a boolean primitive or object.
|
|
338
|
+
* @param {*} value The value to check.
|
|
339
|
+
* @param {boolean} fuzzy Whether to perform fuzzy judgment, for better performance,default to `false`.
|
|
340
|
+
* @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
|
|
341
|
+
* @example
|
|
342
|
+
*
|
|
343
|
+
* isBoolean(false)
|
|
344
|
+
* // => true
|
|
345
|
+
*
|
|
346
|
+
* isBoolean(null)
|
|
347
|
+
* // => false
|
|
348
|
+
*/
|
|
330
349
|
const isBoolean = (value, fuzzy = false) => {
|
|
331
350
|
if (fuzzy) {
|
|
332
351
|
return typeof value === 'boolean';
|
|
@@ -334,6 +353,32 @@
|
|
|
334
353
|
return value === true || value === false || isType(value, 'Boolean');
|
|
335
354
|
};
|
|
336
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Checks if `value` is classified as a `Function` object.
|
|
358
|
+
*
|
|
359
|
+
* @param {*} value The value to check.
|
|
360
|
+
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
|
|
361
|
+
* @example
|
|
362
|
+
*
|
|
363
|
+
* isFunction(class Any{})
|
|
364
|
+
* // => true
|
|
365
|
+
*
|
|
366
|
+
* isFunction(() => {})
|
|
367
|
+
* // => true
|
|
368
|
+
*
|
|
369
|
+
* isFunction(async () => {})
|
|
370
|
+
* // => true
|
|
371
|
+
*
|
|
372
|
+
* isFunction(function * Any() {})
|
|
373
|
+
* // => true
|
|
374
|
+
*
|
|
375
|
+
* isFunction(Math.round)
|
|
376
|
+
* // => true
|
|
377
|
+
*
|
|
378
|
+
* isFunction(/abc/)
|
|
379
|
+
* // => false
|
|
380
|
+
*/
|
|
381
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
337
382
|
const isFunction = (value) => {
|
|
338
383
|
return typeof value === 'function';
|
|
339
384
|
};
|
|
@@ -350,15 +395,84 @@
|
|
|
350
395
|
return value !== null && value !== undefined;
|
|
351
396
|
};
|
|
352
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Checks if `value` is the
|
|
400
|
+
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
|
|
401
|
+
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
402
|
+
*
|
|
403
|
+
* @param {*} value The value to check.
|
|
404
|
+
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
405
|
+
* @example
|
|
406
|
+
*
|
|
407
|
+
* isObject({})
|
|
408
|
+
* // => true
|
|
409
|
+
*
|
|
410
|
+
* isObject([1, 2, 3])
|
|
411
|
+
* // => true
|
|
412
|
+
*
|
|
413
|
+
* isObject(Function)
|
|
414
|
+
* // => true
|
|
415
|
+
*
|
|
416
|
+
* isObject(null)
|
|
417
|
+
* // => false
|
|
418
|
+
*/
|
|
419
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
353
420
|
const isObject = (value) => {
|
|
354
421
|
const type = typeof value;
|
|
355
422
|
return (value !== null && type === 'object') || type === 'function';
|
|
356
423
|
};
|
|
357
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Checks if `value` is object-like. A value is object-like if it's not `null`
|
|
427
|
+
* and has a `typeof` result of "object".
|
|
428
|
+
*
|
|
429
|
+
* @param {*} value The value to check.
|
|
430
|
+
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
431
|
+
* @example
|
|
432
|
+
*
|
|
433
|
+
* isObjectLike({})
|
|
434
|
+
* // => true
|
|
435
|
+
*
|
|
436
|
+
* isObjectLike([1, 2, 3])
|
|
437
|
+
* // => true
|
|
438
|
+
*
|
|
439
|
+
* isObjectLike(Function)
|
|
440
|
+
* // => false
|
|
441
|
+
*
|
|
442
|
+
* isObjectLike(null)
|
|
443
|
+
* // => false
|
|
444
|
+
*/
|
|
445
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
358
446
|
const isObjectLike = (value) => {
|
|
359
447
|
return typeof value === 'object' && value !== null;
|
|
360
448
|
};
|
|
361
449
|
|
|
450
|
+
/**
|
|
451
|
+
* @see https://github.com/lodash/lodash/blob/master/isPlainObject.js
|
|
452
|
+
* Checks if `value` is a plain object, that is, an object created by the
|
|
453
|
+
* `Object` constructor or one with a `[[Prototype]]` of `null`.
|
|
454
|
+
*
|
|
455
|
+
* @param {*} value The value to check.
|
|
456
|
+
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
|
|
457
|
+
* @example
|
|
458
|
+
*
|
|
459
|
+
* function Foo() {
|
|
460
|
+
* this.a = 1
|
|
461
|
+
* }
|
|
462
|
+
*
|
|
463
|
+
* isPlainObject(new Foo)
|
|
464
|
+
* // => false
|
|
465
|
+
*
|
|
466
|
+
* isPlainObject([1, 2, 3])
|
|
467
|
+
* // => false
|
|
468
|
+
*
|
|
469
|
+
* isPlainObject({ 'x': 0, 'y': 0 })
|
|
470
|
+
* // => true
|
|
471
|
+
*
|
|
472
|
+
* isPlainObject(Object.create(null))
|
|
473
|
+
* // => true
|
|
474
|
+
*/
|
|
475
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
362
476
|
const isPlainObject = function (value) {
|
|
363
477
|
if (!isObjectLike(value) || !isType(value, 'Object')) {
|
|
364
478
|
return false;
|
|
@@ -377,6 +491,19 @@
|
|
|
377
491
|
return value === undefined;
|
|
378
492
|
};
|
|
379
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Checks if `value` is classified as a `String` primitive or object.
|
|
496
|
+
* @param {*} value The value to check.
|
|
497
|
+
* @param {boolean} fuzzy Whether to perform fuzzy judgment, for better performance,default to `false`.
|
|
498
|
+
* @returns {boolean} Returns `true` if `value` is a string, else `false`.
|
|
499
|
+
* @example
|
|
500
|
+
*
|
|
501
|
+
* isString('abc')
|
|
502
|
+
* // => true
|
|
503
|
+
*
|
|
504
|
+
* isString(1)
|
|
505
|
+
* // => false
|
|
506
|
+
*/
|
|
380
507
|
const isString = (value, fuzzy = false) => {
|
|
381
508
|
const type = typeof value;
|
|
382
509
|
if (fuzzy) {
|
|
@@ -397,6 +524,29 @@
|
|
|
397
524
|
return isType(value, 'Date');
|
|
398
525
|
};
|
|
399
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Checks if `value` is classified as a `Number` primitive or object.
|
|
529
|
+
*
|
|
530
|
+
* **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
|
|
531
|
+
* classified as numbers, use the `Number.isFinite` method.
|
|
532
|
+
*
|
|
533
|
+
* @param {*} value The value to check.
|
|
534
|
+
* @param {boolean} fuzzy Whether to perform fuzzy judgment, for better performance,default to `false`.
|
|
535
|
+
* @returns {boolean} Returns `true` if `value` is a number, else `false`.
|
|
536
|
+
* @example
|
|
537
|
+
*
|
|
538
|
+
* isNumber(3)
|
|
539
|
+
* // => true
|
|
540
|
+
*
|
|
541
|
+
* isNumber(Number.MIN_VALUE)
|
|
542
|
+
* // => true
|
|
543
|
+
*
|
|
544
|
+
* isNumber(Infinity)
|
|
545
|
+
* // => true
|
|
546
|
+
*
|
|
547
|
+
* isNumber('3')
|
|
548
|
+
* // => false
|
|
549
|
+
*/
|
|
400
550
|
const isNumber = (value, fuzzy = false) => {
|
|
401
551
|
const type = typeof value;
|
|
402
552
|
if (fuzzy) {
|
|
@@ -405,6 +555,31 @@
|
|
|
405
555
|
return type === 'number' || isType(value, 'Number');
|
|
406
556
|
};
|
|
407
557
|
|
|
558
|
+
/**
|
|
559
|
+
* Checks if `value` is classified as a legal string number
|
|
560
|
+
*
|
|
561
|
+
* @param {*} value The string value to check.
|
|
562
|
+
* @returns {boolean} Returns `true` if `value` is a legal string number, else `false`.
|
|
563
|
+
* @example
|
|
564
|
+
*
|
|
565
|
+
* isNumeric(1)
|
|
566
|
+
* // => false
|
|
567
|
+
*
|
|
568
|
+
* isNumeric('2.0')
|
|
569
|
+
* // => true
|
|
570
|
+
*
|
|
571
|
+
* isNumeric('3a')
|
|
572
|
+
* // => false
|
|
573
|
+
*
|
|
574
|
+
* isNumeric('4.a')
|
|
575
|
+
* // => false
|
|
576
|
+
*
|
|
577
|
+
* isNumeric(Infinity)
|
|
578
|
+
* // => false
|
|
579
|
+
*
|
|
580
|
+
* isNumeric('01')
|
|
581
|
+
* // => true
|
|
582
|
+
*/
|
|
408
583
|
const isNumeric = (value) => {
|
|
409
584
|
if (typeof value !== 'string') {
|
|
410
585
|
return false;
|
|
@@ -413,10 +588,12 @@
|
|
|
413
588
|
};
|
|
414
589
|
|
|
415
590
|
const isValidNumber = (value) => {
|
|
591
|
+
// isFinate 包含来 isNaN 的判断
|
|
416
592
|
return isNumber(value) && Number.isFinite(value);
|
|
417
593
|
};
|
|
418
594
|
|
|
419
595
|
const isValidUrl = (value) => {
|
|
596
|
+
// TODO: 匹配其他形式的 url
|
|
420
597
|
const exp = /^(http(s)?:\/\/)\w+[^\s]+(\.[^\s]+){1,}$/;
|
|
421
598
|
const urlPattern = new RegExp(exp);
|
|
422
599
|
return urlPattern.test(value);
|
|
@@ -426,6 +603,12 @@
|
|
|
426
603
|
return isType(value, 'RegExp');
|
|
427
604
|
};
|
|
428
605
|
|
|
606
|
+
/**
|
|
607
|
+
* 迁移至 sirius
|
|
608
|
+
* Checks if `value` is classified as a `Base64` string;
|
|
609
|
+
* @param {string} The value to check.
|
|
610
|
+
* @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
|
|
611
|
+
*/
|
|
429
612
|
const isBase64 = (value) => {
|
|
430
613
|
const exp = /^data:image\/(?:gif|png|jpeg|bmp|webp|svg\+xml)(?:;charset=utf-8)?;base64,(?:[A-Za-z0-9]|[+/])+={0,2}/g;
|
|
431
614
|
const urlPattern = new RegExp(exp);
|
|
@@ -448,12 +631,22 @@
|
|
|
448
631
|
|
|
449
632
|
const hasOwnProperty$2 = Object.prototype.hasOwnProperty;
|
|
450
633
|
function isEmpty(value) {
|
|
634
|
+
/**
|
|
635
|
+
* isEmpty(null) => true
|
|
636
|
+
* isEmpty() => true
|
|
637
|
+
* isEmpty(true) => true
|
|
638
|
+
* isEmpty(1) => true
|
|
639
|
+
* isEmpty([1, 2, 3]) => false
|
|
640
|
+
* isEmpty('abc') => false
|
|
641
|
+
* isEmpty({ a: 1 }) => false
|
|
642
|
+
*/
|
|
451
643
|
if (isNil(value)) {
|
|
452
644
|
return true;
|
|
453
645
|
}
|
|
454
646
|
if (isArrayLike(value)) {
|
|
455
647
|
return !value.length;
|
|
456
648
|
}
|
|
649
|
+
// TODO: 这里需要优化下
|
|
457
650
|
const type = getType(value);
|
|
458
651
|
if (type === 'Map' || type === 'Set') {
|
|
459
652
|
return !value.size;
|
|
@@ -477,7 +670,25 @@
|
|
|
477
670
|
return obj === undefined ? defaultValue : obj;
|
|
478
671
|
};
|
|
479
672
|
|
|
673
|
+
/** Used to check objects for own properties. */
|
|
480
674
|
const hasOwnProperty$1 = Object.prototype.hasOwnProperty;
|
|
675
|
+
/**
|
|
676
|
+
* Checks if `key` is a direct property of `object`.
|
|
677
|
+
*
|
|
678
|
+
* @param {Object} object The object to query.
|
|
679
|
+
* @param {string} key The key to check.
|
|
680
|
+
* @returns {boolean} Returns `true` if `key` exists, else `false`.
|
|
681
|
+
* @example
|
|
682
|
+
*
|
|
683
|
+
* const object = { 'a': { 'b': 2 } }
|
|
684
|
+
* const other = create({ 'a': create({ 'b': 2 }) })
|
|
685
|
+
*
|
|
686
|
+
* has(object, 'a')
|
|
687
|
+
* // => true
|
|
688
|
+
*
|
|
689
|
+
* has(other, 'a')
|
|
690
|
+
* // => false
|
|
691
|
+
*/
|
|
481
692
|
const has = (object, key) => {
|
|
482
693
|
return object != null && hasOwnProperty$1.call(object, key);
|
|
483
694
|
};
|
|
@@ -489,7 +700,11 @@
|
|
|
489
700
|
re.multiline && (flags += 'm');
|
|
490
701
|
return flags;
|
|
491
702
|
}
|
|
703
|
+
// Adapted from https://github.com/pvorb/clone by Paul Vorbach
|
|
704
|
+
// License: https://github.com/pvorb/clone/blob/master/LICENSE
|
|
492
705
|
function clone(parent, circular = false, depth = 0, prototype = undefined) {
|
|
706
|
+
// maintain two arrays for circular references, where corresponding parents
|
|
707
|
+
// and children have the same index
|
|
493
708
|
const allParents = [];
|
|
494
709
|
const allChildren = [];
|
|
495
710
|
if (typeof circular === 'undefined') {
|
|
@@ -498,7 +713,9 @@
|
|
|
498
713
|
if (typeof depth === 'undefined') {
|
|
499
714
|
depth = Infinity;
|
|
500
715
|
}
|
|
716
|
+
// recurse this function so we don't reset allParents and allChildren
|
|
501
717
|
function _clone(parent, depth) {
|
|
718
|
+
// cloning null always returns null
|
|
502
719
|
if (parent === null) {
|
|
503
720
|
return null;
|
|
504
721
|
}
|
|
@@ -552,21 +769,27 @@
|
|
|
552
769
|
}
|
|
553
770
|
const isArr = isArray(value);
|
|
554
771
|
const length = value.length;
|
|
772
|
+
// 不考虑特殊数组的额外处理
|
|
555
773
|
if (isArr) {
|
|
556
774
|
result = new Array(length);
|
|
557
775
|
}
|
|
776
|
+
// 不考虑 buffer / arguments 类型的处理以及 prototype 的额外处理
|
|
558
777
|
else if (typeof value === 'object') {
|
|
559
778
|
result = {};
|
|
560
779
|
}
|
|
780
|
+
// 不建议使用作为 Boolean / Number / String 作为构造器
|
|
561
781
|
else if (isBoolean(value) || isNumber(value) || isString(value)) {
|
|
562
782
|
result = value;
|
|
563
783
|
}
|
|
564
784
|
else if (isDate(value)) {
|
|
565
785
|
result = new Date(+value);
|
|
566
786
|
}
|
|
787
|
+
// 不考虑 ArrayBuffer / DataView / TypedArray / map / set / regexp / symbol 类型
|
|
567
788
|
else {
|
|
568
789
|
result = undefined;
|
|
569
790
|
}
|
|
791
|
+
// 不考虑 map / set / TypedArray 类型的赋值
|
|
792
|
+
// 不考虑对象的 symbol 属性
|
|
570
793
|
const props = isArr ? undefined : Object.keys(Object(value));
|
|
571
794
|
let index = -1;
|
|
572
795
|
if (result) {
|
|
@@ -590,8 +813,10 @@
|
|
|
590
813
|
return;
|
|
591
814
|
}
|
|
592
815
|
if (isValid(source) && typeof source === 'object') {
|
|
816
|
+
// baseFor
|
|
593
817
|
const iterable = Object(source);
|
|
594
818
|
const props = [];
|
|
819
|
+
// keysIn
|
|
595
820
|
for (const key in iterable) {
|
|
596
821
|
props.push(key);
|
|
597
822
|
}
|
|
@@ -599,6 +824,7 @@
|
|
|
599
824
|
let propIndex = -1;
|
|
600
825
|
while (length--) {
|
|
601
826
|
const key = props[++propIndex];
|
|
827
|
+
// skipArray 这个是vchart spec的特有逻辑
|
|
602
828
|
if (isValid(iterable[key]) &&
|
|
603
829
|
typeof iterable[key] === 'object' &&
|
|
604
830
|
(!skipTargetArray || !isArray(target[key]))) {
|
|
@@ -611,19 +837,24 @@
|
|
|
611
837
|
}
|
|
612
838
|
}
|
|
613
839
|
}
|
|
840
|
+
// 由于目前 ChartSpace 内部对 spec 会先执行一次深拷贝,merge 暂时不考虑 source 中有环的问题
|
|
841
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
614
842
|
function baseMergeDeep(target, source, key, shallowArray = false, skipTargetArray = false) {
|
|
615
843
|
const objValue = target[key];
|
|
616
844
|
const srcValue = source[key];
|
|
617
845
|
let newValue = source[key];
|
|
618
846
|
let isCommon = true;
|
|
847
|
+
// 不考虑 buffer / typedArray 类型
|
|
619
848
|
if (isArray(srcValue)) {
|
|
620
849
|
if (shallowArray) {
|
|
850
|
+
// 依据参数对数组做浅拷贝
|
|
621
851
|
newValue = [];
|
|
622
852
|
}
|
|
623
853
|
else if (isArray(objValue)) {
|
|
624
854
|
newValue = objValue;
|
|
625
855
|
}
|
|
626
856
|
else if (isArrayLike(objValue)) {
|
|
857
|
+
// 如果 source 为数组,则 target 的 arrayLike 对象也视作为数组处理
|
|
627
858
|
newValue = new Array(objValue.length);
|
|
628
859
|
let index = -1;
|
|
629
860
|
const length = objValue.length;
|
|
@@ -632,8 +863,13 @@
|
|
|
632
863
|
}
|
|
633
864
|
}
|
|
634
865
|
}
|
|
866
|
+
// else if (isArray(srcValue) && shallowArray) {
|
|
867
|
+
// newValue = [];
|
|
868
|
+
// }
|
|
869
|
+
// 不考虑 argument 类型
|
|
635
870
|
else if (isPlainObject(srcValue)) {
|
|
636
871
|
newValue = objValue !== null && objValue !== void 0 ? objValue : {};
|
|
872
|
+
// 不考虑 prototype 的额外处理
|
|
637
873
|
if (typeof objValue === 'function' || typeof objValue !== 'object') {
|
|
638
874
|
newValue = {};
|
|
639
875
|
}
|
|
@@ -641,19 +877,24 @@
|
|
|
641
877
|
else {
|
|
642
878
|
isCommon = false;
|
|
643
879
|
}
|
|
880
|
+
// 对 class 等复杂对象或者浅拷贝的 array 不做拷贝处理
|
|
644
881
|
if (isCommon) {
|
|
645
882
|
baseMerge(newValue, srcValue, shallowArray, skipTargetArray);
|
|
646
883
|
}
|
|
647
884
|
assignMergeValue(target, key, newValue);
|
|
648
885
|
}
|
|
886
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
649
887
|
function assignMergeValue(target, key, value) {
|
|
650
888
|
if ((value !== undefined && !eq(target[key], value)) || (value === undefined && !(key in target))) {
|
|
889
|
+
// 不考虑 __proto__ 的赋值处理
|
|
651
890
|
target[key] = value;
|
|
652
891
|
}
|
|
653
892
|
}
|
|
654
893
|
function eq(value, other) {
|
|
655
894
|
return value === other || (Number.isNaN(value) && Number.isNaN(other));
|
|
656
895
|
}
|
|
896
|
+
// 与原生的 lodash merge 差异在于对数组是否应用最后一个 source 的结果
|
|
897
|
+
// 例如 lineDash 等 spec 的 merge 使用这一操作更合适
|
|
657
898
|
function merge(target, ...sources) {
|
|
658
899
|
let sourceIndex = -1;
|
|
659
900
|
const length = sources.length;
|
|
@@ -707,6 +948,8 @@
|
|
|
707
948
|
function objectKeys(obj) {
|
|
708
949
|
return Object.keys(obj);
|
|
709
950
|
}
|
|
951
|
+
// Adapted from https://github.com/antvis/F2/blob/master/packages/f2/src/base/equal.ts by zengyue
|
|
952
|
+
// License: https://github.com/antvis/F2/blob/master/packages/f2/LICENSE
|
|
710
953
|
function isEqual(a, b, options) {
|
|
711
954
|
if (a === b) {
|
|
712
955
|
return true;
|
|
@@ -714,18 +957,22 @@
|
|
|
714
957
|
if (typeof a !== typeof b) {
|
|
715
958
|
return false;
|
|
716
959
|
}
|
|
960
|
+
// null 和 undefined
|
|
717
961
|
if (a == null || b == null) {
|
|
718
962
|
return false;
|
|
719
963
|
}
|
|
964
|
+
// 特殊处理NaN
|
|
720
965
|
if (Number.isNaN(a) && Number.isNaN(b)) {
|
|
721
966
|
return true;
|
|
722
967
|
}
|
|
723
968
|
if (objToString(a) !== objToString(b)) {
|
|
724
969
|
return false;
|
|
725
970
|
}
|
|
971
|
+
// 如果是function,则不相等
|
|
726
972
|
if (isFunction(a)) {
|
|
727
973
|
return !!(options === null || options === void 0 ? void 0 : options.skipFunction);
|
|
728
974
|
}
|
|
975
|
+
// 值类型,Number String Boolean
|
|
729
976
|
if (typeof a !== 'object') {
|
|
730
977
|
return false;
|
|
731
978
|
}
|
|
@@ -745,16 +992,21 @@
|
|
|
745
992
|
}
|
|
746
993
|
const ka = objectKeys(a);
|
|
747
994
|
const kb = objectKeys(b);
|
|
995
|
+
// having the same number of owned properties (keys incorporates hasOwnProperty)
|
|
748
996
|
if (ka.length !== kb.length) {
|
|
749
997
|
return false;
|
|
750
998
|
}
|
|
999
|
+
// the same set of keys (although not necessarily the same order),
|
|
751
1000
|
ka.sort();
|
|
752
1001
|
kb.sort();
|
|
1002
|
+
// ~~~cheap key test
|
|
753
1003
|
for (let i = ka.length - 1; i >= 0; i--) {
|
|
1004
|
+
// eslint-disable-next-line eqeqeq
|
|
754
1005
|
if (ka[i] != kb[i]) {
|
|
755
1006
|
return false;
|
|
756
1007
|
}
|
|
757
1008
|
}
|
|
1009
|
+
// equivalent values for every corresponding key, and ~~~possibly expensive deep test
|
|
758
1010
|
for (let i = ka.length - 1; i >= 0; i--) {
|
|
759
1011
|
const key = ka[i];
|
|
760
1012
|
if (!isEqual(a[key], b[key], options)) {
|
|
@@ -768,7 +1020,8 @@
|
|
|
768
1020
|
if (x === y) {
|
|
769
1021
|
return x !== 0 || y !== 0 || 1 / x === 1 / y;
|
|
770
1022
|
}
|
|
771
|
-
|
|
1023
|
+
// eslint-disable-next-line no-self-compare
|
|
1024
|
+
return x !== x && y !== y; // NaN == NaN
|
|
772
1025
|
}
|
|
773
1026
|
function length(obj) {
|
|
774
1027
|
if (isArray(obj)) {
|
|
@@ -804,6 +1057,7 @@
|
|
|
804
1057
|
return ret;
|
|
805
1058
|
}
|
|
806
1059
|
|
|
1060
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
|
|
807
1061
|
function keys(obj) {
|
|
808
1062
|
if (!obj) {
|
|
809
1063
|
return [];
|
|
@@ -813,6 +1067,7 @@
|
|
|
813
1067
|
}
|
|
814
1068
|
const keyList = [];
|
|
815
1069
|
for (const key in obj) {
|
|
1070
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
816
1071
|
if (obj.hasOwnProperty(key)) {
|
|
817
1072
|
keyList.push(key);
|
|
818
1073
|
}
|
|
@@ -829,9 +1084,16 @@
|
|
|
829
1084
|
}
|
|
830
1085
|
return target;
|
|
831
1086
|
}
|
|
1087
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
832
1088
|
function mixin(target, source, override = true) {
|
|
1089
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1090
|
+
// @ts-ignore
|
|
833
1091
|
target = 'prototype' in target ? target.prototype : target;
|
|
1092
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1093
|
+
// @ts-ignore
|
|
834
1094
|
source = 'prototype' in source ? source.prototype : source;
|
|
1095
|
+
// If build target is ES6 class. prototype methods is not enumerable. Use getOwnPropertyNames instead
|
|
1096
|
+
// TODO: Determine if source is ES6 class?
|
|
835
1097
|
if (Object.getOwnPropertyNames) {
|
|
836
1098
|
const keyList = Object.getOwnPropertyNames(source);
|
|
837
1099
|
for (let i = 0; i < keyList.length; i++) {
|
|
@@ -848,12 +1110,22 @@
|
|
|
848
1110
|
}
|
|
849
1111
|
}
|
|
850
1112
|
|
|
1113
|
+
/**
|
|
1114
|
+
* 将传入数据转换为数组格式
|
|
1115
|
+
* @param arr 入参。
|
|
1116
|
+
* @returns 数组
|
|
1117
|
+
*/
|
|
851
1118
|
function array(arr) {
|
|
852
1119
|
if (isValid(arr)) {
|
|
853
1120
|
return isArray(arr) ? arr : [arr];
|
|
854
1121
|
}
|
|
855
1122
|
return [];
|
|
856
1123
|
}
|
|
1124
|
+
/**
|
|
1125
|
+
* 获取数组的最后一个值,如果传入的参数为非数组,则返回 undefined
|
|
1126
|
+
* @param val any
|
|
1127
|
+
* @returns
|
|
1128
|
+
*/
|
|
857
1129
|
function last(val) {
|
|
858
1130
|
if (isArrayLike(val)) {
|
|
859
1131
|
const arr = val;
|
|
@@ -861,12 +1133,23 @@
|
|
|
861
1133
|
}
|
|
862
1134
|
return undefined;
|
|
863
1135
|
}
|
|
1136
|
+
/**
|
|
1137
|
+
* 获取数组第一项与最后一项的差值,如果数组为空则返回 0
|
|
1138
|
+
* @param arr
|
|
1139
|
+
* @returns
|
|
1140
|
+
*/
|
|
864
1141
|
const span = (arr) => {
|
|
865
1142
|
if (arr.length <= 1) {
|
|
866
1143
|
return 0;
|
|
867
1144
|
}
|
|
868
1145
|
return last(arr) - arr[0];
|
|
869
1146
|
};
|
|
1147
|
+
/**
|
|
1148
|
+
* 获取数组中的最大值
|
|
1149
|
+
* @param arr 数组
|
|
1150
|
+
* @param compareFn 比较函数,返回正数表示 a > b,返回负数表示 a < b,返回 0 表示相等
|
|
1151
|
+
* @returns
|
|
1152
|
+
*/
|
|
870
1153
|
const maxInArray = (arr, compareFn) => {
|
|
871
1154
|
var _a;
|
|
872
1155
|
if (arr.length === 0) {
|
|
@@ -881,6 +1164,12 @@
|
|
|
881
1164
|
}
|
|
882
1165
|
return max;
|
|
883
1166
|
};
|
|
1167
|
+
/**
|
|
1168
|
+
* 获取数组中的最小值
|
|
1169
|
+
* @param arr 数组
|
|
1170
|
+
* @param compareFn 比较函数,返回正数表示 a > b,返回负数表示 a < b,返回 0 表示相等
|
|
1171
|
+
* @returns
|
|
1172
|
+
*/
|
|
884
1173
|
const minInArray = (arr, compareFn) => {
|
|
885
1174
|
var _a;
|
|
886
1175
|
if (arr.length === 0) {
|
|
@@ -895,6 +1184,12 @@
|
|
|
895
1184
|
}
|
|
896
1185
|
return min;
|
|
897
1186
|
};
|
|
1187
|
+
/**
|
|
1188
|
+
* 判断两个数组是否相同
|
|
1189
|
+
* @param a
|
|
1190
|
+
* @param b
|
|
1191
|
+
* @returns
|
|
1192
|
+
*/
|
|
898
1193
|
function arrayEqual(a, b) {
|
|
899
1194
|
if (!isArray(a) || !isArray(b)) {
|
|
900
1195
|
return false;
|
|
@@ -909,12 +1204,18 @@
|
|
|
909
1204
|
}
|
|
910
1205
|
return true;
|
|
911
1206
|
}
|
|
1207
|
+
/**
|
|
1208
|
+
* 封装并返回无重复的数组
|
|
1209
|
+
* @param arr
|
|
1210
|
+
* @returns
|
|
1211
|
+
*/
|
|
912
1212
|
function uniqArray(arr) {
|
|
913
1213
|
if (!arr || !isArray(arr)) {
|
|
914
1214
|
return arr;
|
|
915
1215
|
}
|
|
916
1216
|
return Array.from(new Set(array(arr)));
|
|
917
1217
|
}
|
|
1218
|
+
// Based on http://jsfromhell.com/array/shuffle
|
|
918
1219
|
function shuffleArray(arr, random = Math.random) {
|
|
919
1220
|
let j;
|
|
920
1221
|
let x;
|
|
@@ -927,6 +1228,11 @@
|
|
|
927
1228
|
}
|
|
928
1229
|
return arr;
|
|
929
1230
|
}
|
|
1231
|
+
/**
|
|
1232
|
+
* 展开多层数组,非数组内容将变为 [input]
|
|
1233
|
+
* @param arr
|
|
1234
|
+
* @returns
|
|
1235
|
+
*/
|
|
930
1236
|
function flattenArray(arr) {
|
|
931
1237
|
if (!isArray(arr)) {
|
|
932
1238
|
return [arr];
|
|
@@ -981,11 +1287,74 @@
|
|
|
981
1287
|
return value0 + (value1 - value0) * (i - i0);
|
|
982
1288
|
}
|
|
983
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* Approximate inverse normal CDF (probit) using Acklam's algorithm.
|
|
1292
|
+
* Returns z such that P(Z <= z) = p for standard normal Z.
|
|
1293
|
+
*/
|
|
1294
|
+
function normalQuantile(p) {
|
|
1295
|
+
if (p === 0) {
|
|
1296
|
+
return -Infinity;
|
|
1297
|
+
}
|
|
1298
|
+
if (p === 1) {
|
|
1299
|
+
return Infinity;
|
|
1300
|
+
}
|
|
1301
|
+
if (!Number.isFinite(p) || p < 0 || p > 1) {
|
|
1302
|
+
return NaN;
|
|
1303
|
+
}
|
|
1304
|
+
const a1 = -39.6968302866538;
|
|
1305
|
+
const a2 = 220.946098424521;
|
|
1306
|
+
const a3 = -275.928510446969;
|
|
1307
|
+
const a4 = 138.357751867269;
|
|
1308
|
+
const a5 = -30.6647980661472;
|
|
1309
|
+
const a6 = 2.50662827745924;
|
|
1310
|
+
const b1 = -54.4760987982241;
|
|
1311
|
+
const b2 = 161.585836858041;
|
|
1312
|
+
const b3 = -155.698979859887;
|
|
1313
|
+
const b4 = 66.8013118877197;
|
|
1314
|
+
const b5 = -13.2806815528857;
|
|
1315
|
+
const c1 = -0.00778489400243029;
|
|
1316
|
+
const c2 = -0.322396458041136;
|
|
1317
|
+
const c3 = -2.40075827716184;
|
|
1318
|
+
const c4 = -2.54973253934373;
|
|
1319
|
+
const c5 = 4.37466414146497;
|
|
1320
|
+
const c6 = 2.93816398269878;
|
|
1321
|
+
const d1 = 0.00778469570904146;
|
|
1322
|
+
const d2 = 0.32246712907004;
|
|
1323
|
+
const d3 = 2.445134137143;
|
|
1324
|
+
const d4 = 3.75440866190742;
|
|
1325
|
+
const pLow = 0.02425;
|
|
1326
|
+
const pHigh = 1 - pLow;
|
|
1327
|
+
let q;
|
|
1328
|
+
let r;
|
|
1329
|
+
if (p < pLow) {
|
|
1330
|
+
q = Math.sqrt(-2 * Math.log(p));
|
|
1331
|
+
return (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
|
|
1332
|
+
}
|
|
1333
|
+
if (p <= pHigh) {
|
|
1334
|
+
q = p - 0.5;
|
|
1335
|
+
r = q * q;
|
|
1336
|
+
return (((((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q) /
|
|
1337
|
+
(((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1));
|
|
1338
|
+
}
|
|
1339
|
+
q = Math.sqrt(-2 * Math.log(1 - p));
|
|
1340
|
+
return -((((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1));
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/* Adapted from vega by University of Washington Interactive Data Lab
|
|
1344
|
+
* https://vega.github.io/vega/
|
|
1345
|
+
* Licensed under the BSD-3-Clause
|
|
1346
|
+
|
|
1347
|
+
* url: https://github.com/vega/vega/blob/main/packages/vega-util/src/logger.js
|
|
1348
|
+
* License: https://github.com/vega/vega/blob/main/LICENSE
|
|
1349
|
+
* @license
|
|
1350
|
+
*/
|
|
1351
|
+
// check console
|
|
984
1352
|
const hasConsole = typeof console !== 'undefined';
|
|
985
1353
|
function log(method, level, input) {
|
|
986
1354
|
const args = [level].concat([].slice.call(input));
|
|
987
1355
|
if (hasConsole) {
|
|
988
|
-
|
|
1356
|
+
// eslint-disable-next-line prefer-spread, no-undef
|
|
1357
|
+
console[method].apply(console, args); // eslint-disable-line no-console
|
|
989
1358
|
}
|
|
990
1359
|
}
|
|
991
1360
|
exports.LoggerLevel = void 0;
|
|
@@ -1093,6 +1462,14 @@
|
|
|
1093
1462
|
}
|
|
1094
1463
|
Logger._instance = null;
|
|
1095
1464
|
|
|
1465
|
+
/**
|
|
1466
|
+
* 通过二分法,查找数组a中大于数值x的第一个元素的序号
|
|
1467
|
+
* @param a
|
|
1468
|
+
* @param x
|
|
1469
|
+
* @param lo
|
|
1470
|
+
* @param hi
|
|
1471
|
+
* @returns
|
|
1472
|
+
*/
|
|
1096
1473
|
function bisect(a, x, lo = 0, hi) {
|
|
1097
1474
|
if (isNil(hi)) {
|
|
1098
1475
|
hi = a.length;
|
|
@@ -1108,6 +1485,16 @@
|
|
|
1108
1485
|
}
|
|
1109
1486
|
return lo;
|
|
1110
1487
|
}
|
|
1488
|
+
/* Adapted from fmin by Ben Frederickson
|
|
1489
|
+
* https://github.com/benfred/fmin
|
|
1490
|
+
* Licensed under the BSD-3-Clause
|
|
1491
|
+
|
|
1492
|
+
* url: https://github.com/benfred/fmin/blob/master/src/bisect.js
|
|
1493
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
1494
|
+
* @license
|
|
1495
|
+
*/
|
|
1496
|
+
/** finds the zeros of a function, given two starting points (which must
|
|
1497
|
+
* have opposite signs */
|
|
1111
1498
|
function findZeroOfFunction(f, a, b, parameters) {
|
|
1112
1499
|
var _a, _b;
|
|
1113
1500
|
const maxIterations = (_a = parameters === null || parameters === void 0 ? void 0 : parameters.maxIterations) !== null && _a !== void 0 ? _a : 100;
|
|
@@ -1139,16 +1526,27 @@
|
|
|
1139
1526
|
}
|
|
1140
1527
|
return a + delta;
|
|
1141
1528
|
}
|
|
1529
|
+
/**
|
|
1530
|
+
* 二分靠近框架,返回数组中第一个大于等于目标值的数的索引
|
|
1531
|
+
* @param arr 数组
|
|
1532
|
+
* @param compareFn 比较函数,返回(当前值-目标值)
|
|
1533
|
+
*/
|
|
1142
1534
|
const binaryFuzzySearch = (arr, compareFn) => {
|
|
1143
1535
|
return binaryFuzzySearchInNumberRange(0, arr.length, value => compareFn(arr[value]));
|
|
1144
1536
|
};
|
|
1537
|
+
/**
|
|
1538
|
+
* 二分靠近框架,返回数字区间中第一个大于等于目标值的数字
|
|
1539
|
+
* @param x1 区间上界
|
|
1540
|
+
* @param x2 区间下界(不包含)
|
|
1541
|
+
* @param compareFn 比较函数,返回(当前值-目标值)
|
|
1542
|
+
*/
|
|
1145
1543
|
const binaryFuzzySearchInNumberRange = (x1, x2, compareFn) => {
|
|
1146
1544
|
let left = x1;
|
|
1147
1545
|
let right = x2;
|
|
1148
1546
|
while (left < right) {
|
|
1149
1547
|
const mid = Math.floor((left + right) / 2);
|
|
1150
1548
|
if (compareFn(mid) >= 0) {
|
|
1151
|
-
right = mid;
|
|
1549
|
+
right = mid; // 第一个大于等于目标值的数
|
|
1152
1550
|
}
|
|
1153
1551
|
else {
|
|
1154
1552
|
left = mid + 1;
|
|
@@ -1219,16 +1617,40 @@
|
|
|
1219
1617
|
return stop < start ? -step1 : step1;
|
|
1220
1618
|
}
|
|
1221
1619
|
|
|
1620
|
+
/**
|
|
1621
|
+
* @description 处理数值相关的方法
|
|
1622
|
+
*/
|
|
1222
1623
|
const DEFAULT_ABSOLUTE_TOLERATE = 1e-10;
|
|
1223
1624
|
const DEFAULT_RELATIVE_TOLERATE = 1e-10;
|
|
1625
|
+
/**
|
|
1626
|
+
* 判断两数是否接近相等,函数参数参照 python isClose 方法
|
|
1627
|
+
* @param {number} a
|
|
1628
|
+
* @param {number} b
|
|
1629
|
+
* @param refTol 指定的相对容差比例,将乘以两数的最大值作为相对容差
|
|
1630
|
+
* @param absTol 指定的绝对容差
|
|
1631
|
+
*/
|
|
1224
1632
|
function isNumberClose(a, b, relTol = DEFAULT_RELATIVE_TOLERATE, absTol = DEFAULT_ABSOLUTE_TOLERATE) {
|
|
1225
1633
|
const abs = absTol;
|
|
1226
1634
|
const rel = relTol * Math.max(a, b);
|
|
1227
1635
|
return Math.abs(a - b) <= Math.max(abs, rel);
|
|
1228
1636
|
}
|
|
1637
|
+
/**
|
|
1638
|
+
* 判断 a 是否大于 b,并排除容差范围
|
|
1639
|
+
* @param a
|
|
1640
|
+
* @param b
|
|
1641
|
+
* @param relTol 指定的相对容差比例,将乘以两数的最大值作为相对容差
|
|
1642
|
+
* @param absTol 指定的绝对容差
|
|
1643
|
+
*/
|
|
1229
1644
|
function isGreater(a, b, relTol, absTol) {
|
|
1230
1645
|
return a > b && !isNumberClose(a, b, relTol, absTol);
|
|
1231
1646
|
}
|
|
1647
|
+
/**
|
|
1648
|
+
* 判断 a 是否小于 b,并排除容差范围
|
|
1649
|
+
* @param a
|
|
1650
|
+
* @param b
|
|
1651
|
+
* @param relTol 指定的相对容差比例,将乘以两数的最大值作为相对容差
|
|
1652
|
+
* @param absTol 指定的绝对容差
|
|
1653
|
+
*/
|
|
1232
1654
|
function isLess(a, b, relTol, absTol) {
|
|
1233
1655
|
return a < b && !isNumberClose(a, b, relTol, absTol);
|
|
1234
1656
|
}
|
|
@@ -1263,6 +1685,27 @@
|
|
|
1263
1685
|
}
|
|
1264
1686
|
return s;
|
|
1265
1687
|
};
|
|
1688
|
+
/**
|
|
1689
|
+
* Pads `string` on the left and right, left or right sides if it's shorter than `length`.
|
|
1690
|
+
* Padding characters are truncated if they can't be evenly divided by `length`.
|
|
1691
|
+
*
|
|
1692
|
+
* @since 3.0.0
|
|
1693
|
+
* @category String
|
|
1694
|
+
* @param {string} [string=''] The string to pad.
|
|
1695
|
+
* @param {number} [length=0] The padding length.
|
|
1696
|
+
* @param {string} [chars=' '] The string used as padding.
|
|
1697
|
+
* @returns {string} Returns the padded string.
|
|
1698
|
+
* @example
|
|
1699
|
+
*
|
|
1700
|
+
* pad('abc', 8)
|
|
1701
|
+
* // => ' abc '
|
|
1702
|
+
*
|
|
1703
|
+
* pad('abc', 8, '_-')
|
|
1704
|
+
* // => '_-abc_-_'
|
|
1705
|
+
*
|
|
1706
|
+
* pad('abc', 2)
|
|
1707
|
+
* // => 'abc'
|
|
1708
|
+
*/
|
|
1266
1709
|
const pad = (str, length, padChar = ' ', align = 'right') => {
|
|
1267
1710
|
const c = padChar;
|
|
1268
1711
|
const s = str + '';
|
|
@@ -1290,20 +1733,33 @@
|
|
|
1290
1733
|
return align === 'center' ? s.slice(0, Math.ceil(l / 2)) + e + s.slice(n - Math.floor(l / 2)) : s.slice(0, l) + e;
|
|
1291
1734
|
};
|
|
1292
1735
|
|
|
1736
|
+
// TODO: 性能不是很好,需要优化,后续升级到最新版本的 canvas 之后就可以删除了
|
|
1737
|
+
/**
|
|
1738
|
+
* 生成uuid字符串.
|
|
1739
|
+
* 参考https://github.com/yangjunning/issues/issues/56
|
|
1740
|
+
* @param len
|
|
1741
|
+
* @param radix
|
|
1742
|
+
* @returns
|
|
1743
|
+
*/
|
|
1293
1744
|
const uuid = (len, radix) => {
|
|
1294
1745
|
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
|
|
1295
1746
|
const uuid = [];
|
|
1296
1747
|
let i;
|
|
1297
1748
|
radix = radix || chars.length;
|
|
1298
1749
|
if (len) {
|
|
1750
|
+
// Compact form
|
|
1299
1751
|
for (i = 0; i < len; i++) {
|
|
1300
1752
|
uuid[i] = chars[0 | (Math.random() * radix)];
|
|
1301
1753
|
}
|
|
1302
1754
|
}
|
|
1303
1755
|
else {
|
|
1756
|
+
// rfc4122, version 4 form
|
|
1304
1757
|
let r;
|
|
1758
|
+
// rfc4122 requires these characters
|
|
1305
1759
|
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
|
|
1306
1760
|
uuid[14] = '4';
|
|
1761
|
+
// Fill in random data. At i==19 set the high bits of clock sequence as
|
|
1762
|
+
// per rfc4122, sec. 4.1.5
|
|
1307
1763
|
for (i = 0; i < 36; i++) {
|
|
1308
1764
|
if (!uuid[i]) {
|
|
1309
1765
|
r = 0 | (Math.random() * 16);
|
|
@@ -1350,6 +1806,9 @@
|
|
|
1350
1806
|
};
|
|
1351
1807
|
}
|
|
1352
1808
|
|
|
1809
|
+
/**
|
|
1810
|
+
* @see https://github.com/lodash/lodash/blob/master/debounce.js
|
|
1811
|
+
*/
|
|
1353
1812
|
let hasRaf = false;
|
|
1354
1813
|
try {
|
|
1355
1814
|
hasRaf = typeof requestAnimationFrame === 'function' && typeof cancelAnimationFrame === 'function';
|
|
@@ -1363,12 +1822,14 @@
|
|
|
1363
1822
|
let lastThis;
|
|
1364
1823
|
let maxWait;
|
|
1365
1824
|
let result;
|
|
1825
|
+
// eslint-disable-next-line no-undef
|
|
1366
1826
|
let timerId;
|
|
1367
1827
|
let lastCallTime;
|
|
1368
1828
|
let lastInvokeTime = 0;
|
|
1369
1829
|
let leading = false;
|
|
1370
1830
|
let maxing = false;
|
|
1371
1831
|
let trailing = true;
|
|
1832
|
+
// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
|
|
1372
1833
|
const useRAF = !wait && wait !== 0 && hasRaf;
|
|
1373
1834
|
if (typeof func !== 'function') {
|
|
1374
1835
|
throw new TypeError('Expected a function');
|
|
@@ -1404,8 +1865,11 @@
|
|
|
1404
1865
|
clearTimeout(id);
|
|
1405
1866
|
}
|
|
1406
1867
|
function leadingEdge(time) {
|
|
1868
|
+
// Reset any `maxWait` timer.
|
|
1407
1869
|
lastInvokeTime = time;
|
|
1870
|
+
// Start the timer for the trailing edge.
|
|
1408
1871
|
timerId = startTimer(timerExpired, wait);
|
|
1872
|
+
// Invoke the leading edge.
|
|
1409
1873
|
return leading ? invokeFunc(time) : result;
|
|
1410
1874
|
}
|
|
1411
1875
|
function remainingWait(time) {
|
|
@@ -1417,6 +1881,9 @@
|
|
|
1417
1881
|
function shouldInvoke(time) {
|
|
1418
1882
|
const timeSinceLastCall = time - lastCallTime;
|
|
1419
1883
|
const timeSinceLastInvoke = time - lastInvokeTime;
|
|
1884
|
+
// Either this is the first call, activity has stopped and we're at the
|
|
1885
|
+
// trailing edge, the system time has gone backwards and we're treating
|
|
1886
|
+
// it as the trailing edge, or we've hit the `maxWait` limit.
|
|
1420
1887
|
return (lastCallTime === undefined ||
|
|
1421
1888
|
timeSinceLastCall >= wait ||
|
|
1422
1889
|
timeSinceLastCall < 0 ||
|
|
@@ -1427,10 +1894,13 @@
|
|
|
1427
1894
|
if (shouldInvoke(time)) {
|
|
1428
1895
|
return trailingEdge(time);
|
|
1429
1896
|
}
|
|
1897
|
+
// Restart the timer.
|
|
1430
1898
|
timerId = startTimer(timerExpired, remainingWait(time));
|
|
1431
1899
|
}
|
|
1432
1900
|
function trailingEdge(time) {
|
|
1433
1901
|
timerId = undefined;
|
|
1902
|
+
// Only invoke if we have `lastArgs` which means `func` has been
|
|
1903
|
+
// debounced at least once.
|
|
1434
1904
|
if (trailing && lastArgs) {
|
|
1435
1905
|
return invokeFunc(time);
|
|
1436
1906
|
}
|
|
@@ -1454,6 +1924,7 @@
|
|
|
1454
1924
|
const time = Date.now();
|
|
1455
1925
|
const isInvoking = shouldInvoke(time);
|
|
1456
1926
|
lastArgs = args;
|
|
1927
|
+
// @ts-ignore
|
|
1457
1928
|
lastThis = this;
|
|
1458
1929
|
lastCallTime = time;
|
|
1459
1930
|
if (isInvoking) {
|
|
@@ -1461,6 +1932,7 @@
|
|
|
1461
1932
|
return leadingEdge(lastCallTime);
|
|
1462
1933
|
}
|
|
1463
1934
|
if (maxing) {
|
|
1935
|
+
// Handle invocations in a tight loop.
|
|
1464
1936
|
timerId = startTimer(timerExpired, wait);
|
|
1465
1937
|
return invokeFunc(lastCallTime);
|
|
1466
1938
|
}
|
|
@@ -1476,6 +1948,9 @@
|
|
|
1476
1948
|
return debounced;
|
|
1477
1949
|
}
|
|
1478
1950
|
|
|
1951
|
+
/**
|
|
1952
|
+
* @see https://github.com/lodash/lodash/blob/master/throttle.js
|
|
1953
|
+
*/
|
|
1479
1954
|
function throttle(func, wait, options) {
|
|
1480
1955
|
let leading = true;
|
|
1481
1956
|
let trailing = true;
|
|
@@ -1525,47 +2000,55 @@
|
|
|
1525
2000
|
};
|
|
1526
2001
|
}
|
|
1527
2002
|
function interpolateString(a, b) {
|
|
1528
|
-
let bi = (reA.lastIndex = reB.lastIndex = 0);
|
|
1529
|
-
let am;
|
|
1530
|
-
let bm;
|
|
1531
|
-
let bs;
|
|
1532
|
-
let i = -1;
|
|
1533
|
-
const s = [];
|
|
1534
|
-
const q = [];
|
|
2003
|
+
let bi = (reA.lastIndex = reB.lastIndex = 0); // scan index for next number in b
|
|
2004
|
+
let am; // current match in a
|
|
2005
|
+
let bm; // current match in b
|
|
2006
|
+
let bs; // string preceding current number in b, if any
|
|
2007
|
+
let i = -1; // index in s
|
|
2008
|
+
const s = []; // string constants and placeholders
|
|
2009
|
+
const q = []; // number interpolators
|
|
2010
|
+
// Coerce inputs to strings.
|
|
1535
2011
|
(a = a + ''), (b = b + '');
|
|
2012
|
+
// Interpolate pairs of numbers in a & b.
|
|
1536
2013
|
while ((am = reA.exec(a)) && (bm = reB.exec(b))) {
|
|
1537
2014
|
if ((bs = bm.index) > bi) {
|
|
2015
|
+
// a string precedes the next number in b
|
|
1538
2016
|
bs = b.slice(bi, bs);
|
|
1539
2017
|
if (s[i]) {
|
|
1540
2018
|
s[i] += bs;
|
|
1541
|
-
}
|
|
2019
|
+
} // coalesce with previous string
|
|
1542
2020
|
else {
|
|
1543
2021
|
s[++i] = bs;
|
|
1544
2022
|
}
|
|
1545
2023
|
}
|
|
1546
2024
|
if ((am = am[0]) === (bm = bm[0])) {
|
|
2025
|
+
// numbers in a & b match
|
|
1547
2026
|
if (s[i]) {
|
|
1548
2027
|
s[i] += bm;
|
|
1549
|
-
}
|
|
2028
|
+
} // coalesce with previous string
|
|
1550
2029
|
else {
|
|
1551
2030
|
s[++i] = bm;
|
|
1552
2031
|
}
|
|
1553
2032
|
}
|
|
1554
2033
|
else {
|
|
2034
|
+
// interpolate non-matching numbers
|
|
1555
2035
|
s[++i] = null;
|
|
1556
2036
|
q.push({ i: i, x: interpolateNumber(am, bm) });
|
|
1557
2037
|
}
|
|
1558
2038
|
bi = reB.lastIndex;
|
|
1559
2039
|
}
|
|
2040
|
+
// Add remains of b.
|
|
1560
2041
|
if (bi < b.length) {
|
|
1561
2042
|
bs = b.slice(bi);
|
|
1562
2043
|
if (s[i]) {
|
|
1563
2044
|
s[i] += bs;
|
|
1564
|
-
}
|
|
2045
|
+
} // coalesce with previous string
|
|
1565
2046
|
else {
|
|
1566
2047
|
s[++i] = bs;
|
|
1567
2048
|
}
|
|
1568
2049
|
}
|
|
2050
|
+
// Special optimization for only a single match.
|
|
2051
|
+
// Otherwise, interpolate each of the numbers and rejoin the string.
|
|
1569
2052
|
return s.length < 2
|
|
1570
2053
|
? q[0]
|
|
1571
2054
|
? one(q[0].x)
|
|
@@ -1579,19 +2062,36 @@
|
|
|
1579
2062
|
});
|
|
1580
2063
|
}
|
|
1581
2064
|
|
|
1582
|
-
|
|
2065
|
+
// eslint-disable-next-line no-useless-escape
|
|
2066
|
+
const TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d{1,2})(?::(\d{1,2})(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // eslint-disable-line
|
|
1583
2067
|
function toDate(val) {
|
|
1584
2068
|
if (val instanceof Date) {
|
|
1585
2069
|
return val;
|
|
1586
2070
|
}
|
|
1587
2071
|
else if (isString(val)) {
|
|
2072
|
+
// Different browsers parse date in different way, so we parse it manually.
|
|
2073
|
+
// Some other issues:
|
|
2074
|
+
// new Date('1970-01-01') is UTC,
|
|
2075
|
+
// new Date('1970/01/01') and new Date('1970-1-01') is local.
|
|
2076
|
+
// See issue #3623
|
|
1588
2077
|
const match = TIME_REG.exec(val);
|
|
1589
2078
|
if (!match) {
|
|
2079
|
+
// return Invalid Date.
|
|
1590
2080
|
return new Date(NaN);
|
|
1591
2081
|
}
|
|
2082
|
+
// Use local time when no timezone offset specifed.
|
|
1592
2083
|
if (!match[8]) {
|
|
2084
|
+
// match[n] can only be string or undefined.
|
|
2085
|
+
// But take care of '12' + 1 => '121'.
|
|
1593
2086
|
return new Date(+match[1], +(match[2] || 1) - 1, +match[3] || 1, +match[4] || 0, +(match[5] || 0), +match[6] || 0, match[7] ? +match[7].substring(0, 3) : 0);
|
|
1594
2087
|
}
|
|
2088
|
+
// Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time,
|
|
2089
|
+
// https://tc39.github.io/ecma262/#sec-daylight-saving-time-adjustment).
|
|
2090
|
+
// For example, system timezone is set as "Time Zone: America/Toronto",
|
|
2091
|
+
// then these code will get different result:
|
|
2092
|
+
// `new Date(1478411999999).getTimezoneOffset(); // get 240`
|
|
2093
|
+
// `new Date(1478412000000).getTimezoneOffset(); // get 300`
|
|
2094
|
+
// So we should not use `new Date`, but use `Date.UTC`.
|
|
1595
2095
|
let hour = +match[4] || 0;
|
|
1596
2096
|
if (match[8].toUpperCase() !== 'Z') {
|
|
1597
2097
|
hour -= +match[8].slice(0, 3);
|
|
@@ -1635,13 +2135,25 @@
|
|
|
1635
2135
|
function seedRandom(seed) {
|
|
1636
2136
|
return parseFloat('0.' + Math.sin(seed).toString().substring(6));
|
|
1637
2137
|
}
|
|
2138
|
+
/* Adapted from vega by University of Washington Interactive Data Lab
|
|
2139
|
+
* https://vega.github.io/vega/
|
|
2140
|
+
* Licensed under the BSD-3-Clause
|
|
2141
|
+
|
|
2142
|
+
* url: https://github.com/vega/vega/blob/main/packages/vega-statistics/src/lcg.js
|
|
2143
|
+
* License: https://github.com/vega/vega/blob/main/LICENSE
|
|
2144
|
+
* @license
|
|
2145
|
+
*/
|
|
2146
|
+
// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use
|
|
1638
2147
|
const a = 1664525;
|
|
1639
2148
|
const c = 1013904223;
|
|
1640
|
-
const m = 4294967296;
|
|
2149
|
+
const m = 4294967296; // 2^32
|
|
1641
2150
|
function randomLCG(initS = 1) {
|
|
1642
2151
|
let s = initS;
|
|
1643
2152
|
return () => (s = (a * s + c) % m) / m;
|
|
1644
2153
|
}
|
|
2154
|
+
/**
|
|
2155
|
+
* 随机拟合
|
|
2156
|
+
*/
|
|
1645
2157
|
const fakeRandom = () => {
|
|
1646
2158
|
let i = -1;
|
|
1647
2159
|
const arr = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
|
|
@@ -1670,6 +2182,9 @@
|
|
|
1670
2182
|
}
|
|
1671
2183
|
return fieldSingle(fieldStr, opt);
|
|
1672
2184
|
};
|
|
2185
|
+
/**
|
|
2186
|
+
* 取数逻辑
|
|
2187
|
+
*/
|
|
1673
2188
|
const simpleField = (option) => {
|
|
1674
2189
|
if (!option) {
|
|
1675
2190
|
return null;
|
|
@@ -1692,12 +2207,17 @@
|
|
|
1692
2207
|
|
|
1693
2208
|
const zero = (_) => 0;
|
|
1694
2209
|
|
|
2210
|
+
/**
|
|
2211
|
+
* Return an array with minimum and maximum values, in the
|
|
2212
|
+
* form [min, max]. Ignores null, undefined, and NaN values.
|
|
2213
|
+
*/
|
|
1695
2214
|
const extent = (array, func) => {
|
|
1696
2215
|
const valueGetter = isFunction(func) ? func : (val) => val;
|
|
1697
2216
|
let min;
|
|
1698
2217
|
let max;
|
|
1699
2218
|
if (array && array.length) {
|
|
1700
2219
|
const n = array.length;
|
|
2220
|
+
// find first valid value
|
|
1701
2221
|
for (let i = 0; i < n; i += 1) {
|
|
1702
2222
|
let value = valueGetter(array[i]);
|
|
1703
2223
|
if (!isNil(value) && isNumber((value = +value)) && !Number.isNaN(value)) {
|
|
@@ -1716,49 +2236,233 @@
|
|
|
1716
2236
|
return [min, max];
|
|
1717
2237
|
};
|
|
1718
2238
|
|
|
1719
|
-
function
|
|
1720
|
-
|
|
1721
|
-
|
|
2239
|
+
function regressionPoints(data, x, y) {
|
|
2240
|
+
const points = [];
|
|
2241
|
+
data.forEach(d => {
|
|
2242
|
+
const a = x(d);
|
|
2243
|
+
const b = y(d);
|
|
2244
|
+
// 保留既有 accessor 对缺失观测的排除行为;无穷大不是可拟合观测。
|
|
2245
|
+
if (a != null && b != null && Number.isFinite(+a) && Number.isFinite(+b)) {
|
|
2246
|
+
points.push({ x: +a, y: +b });
|
|
2247
|
+
}
|
|
2248
|
+
});
|
|
2249
|
+
return points;
|
|
2250
|
+
}
|
|
2251
|
+
function normalizeRegression(value, center, scale) {
|
|
2252
|
+
const delta = value - center;
|
|
2253
|
+
return Number.isFinite(delta) ? delta / scale : value / scale - center / scale;
|
|
2254
|
+
}
|
|
2255
|
+
function regressionBasis(points) {
|
|
2256
|
+
let xMin = Infinity;
|
|
2257
|
+
let xMax = -Infinity;
|
|
2258
|
+
let yMin = Infinity;
|
|
2259
|
+
let yMax = -Infinity;
|
|
2260
|
+
for (const p of points) {
|
|
2261
|
+
xMin = Math.min(xMin, p.x);
|
|
2262
|
+
xMax = Math.max(xMax, p.x);
|
|
2263
|
+
yMin = Math.min(yMin, p.y);
|
|
2264
|
+
yMax = Math.max(yMax, p.y);
|
|
2265
|
+
}
|
|
2266
|
+
if (!points.length) {
|
|
2267
|
+
return { xCenter: 0, xScale: 1, yCenter: 0, yScale: 1 };
|
|
2268
|
+
}
|
|
2269
|
+
const xCenter = xMin / 2 + xMax / 2;
|
|
2270
|
+
const yCenter = yMin / 2 + yMax / 2;
|
|
2271
|
+
return {
|
|
2272
|
+
xCenter,
|
|
2273
|
+
yCenter,
|
|
2274
|
+
xScale: Math.max(Math.abs(xMin - xCenter), Math.abs(xMax - xCenter)) || 1,
|
|
2275
|
+
yScale: Math.max(Math.abs(yMin - yCenter), Math.abs(yMax - yCenter)) || 1
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
/** 秩不足时对 QR 的小型 R 做单边 Jacobi SVD,获得明确的最小范数解。 */
|
|
2279
|
+
function minimumNorm(r, rhs) {
|
|
2280
|
+
const p = rhs.length;
|
|
2281
|
+
const a = r.map(row => row.slice());
|
|
2282
|
+
const v = Array.from({ length: p }, (_, i) => Array.from({ length: p }, (_, j) => +(i === j)));
|
|
2283
|
+
for (let sweep = 0; sweep < 50; sweep++) {
|
|
2284
|
+
let changed = false;
|
|
2285
|
+
for (let j = 0; j < p - 1; j++) {
|
|
2286
|
+
for (let k = j + 1; k < p; k++) {
|
|
2287
|
+
let aa = 0;
|
|
2288
|
+
let bb = 0;
|
|
2289
|
+
let ab = 0;
|
|
2290
|
+
for (let i = 0; i < p; i++) {
|
|
2291
|
+
aa += a[i][j] ** 2;
|
|
2292
|
+
bb += a[i][k] ** 2;
|
|
2293
|
+
ab += a[i][j] * a[i][k];
|
|
2294
|
+
}
|
|
2295
|
+
if (Math.abs(ab) <= Number.EPSILON * 8 * Math.sqrt(aa * bb)) {
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
changed = true;
|
|
2299
|
+
const tau = (bb - aa) / (2 * ab);
|
|
2300
|
+
const t = (tau >= 0 ? 1 : -1) / (Math.abs(tau) + Math.hypot(1, tau));
|
|
2301
|
+
const c = 1 / Math.hypot(1, t);
|
|
2302
|
+
const s = c * t;
|
|
2303
|
+
for (let i = 0; i < p; i++) {
|
|
2304
|
+
const aj = a[i][j];
|
|
2305
|
+
const ak = a[i][k];
|
|
2306
|
+
const vj = v[i][j];
|
|
2307
|
+
const vk = v[i][k];
|
|
2308
|
+
a[i][j] = c * aj - s * ak;
|
|
2309
|
+
a[i][k] = s * aj + c * ak;
|
|
2310
|
+
v[i][j] = c * vj - s * vk;
|
|
2311
|
+
v[i][k] = s * vj + c * vk;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
if (!changed) {
|
|
2316
|
+
break;
|
|
2317
|
+
}
|
|
1722
2318
|
}
|
|
1723
|
-
const
|
|
1724
|
-
const
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
const d1 = 0.00778469570904146;
|
|
1741
|
-
const d2 = 0.32246712907004;
|
|
1742
|
-
const d3 = 2.445134137143;
|
|
1743
|
-
const d4 = 3.75440866190742;
|
|
1744
|
-
const pLow = 0.02425;
|
|
1745
|
-
const pHigh = 1 - pLow;
|
|
1746
|
-
let q;
|
|
1747
|
-
let r;
|
|
1748
|
-
if (p < pLow) {
|
|
1749
|
-
q = Math.sqrt(-2 * Math.log(p));
|
|
1750
|
-
return (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
|
|
2319
|
+
const norms = Array.from({ length: p }, (_, j) => Math.hypot(...a.map(row => row[j])));
|
|
2320
|
+
const tolerance = Math.max(...norms) * Number.EPSILON * p * 64;
|
|
2321
|
+
const coef = new Array(p).fill(0);
|
|
2322
|
+
let rank = 0;
|
|
2323
|
+
for (let j = 0; j < p; j++) {
|
|
2324
|
+
if (norms[j] <= tolerance) {
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
rank++;
|
|
2328
|
+
let projection = 0;
|
|
2329
|
+
for (let i = 0; i < p; i++) {
|
|
2330
|
+
projection += a[i][j] * rhs[i];
|
|
2331
|
+
}
|
|
2332
|
+
projection /= norms[j] ** 2;
|
|
2333
|
+
for (let i = 0; i < p; i++) {
|
|
2334
|
+
coef[i] += v[i][j] * projection;
|
|
2335
|
+
}
|
|
1751
2336
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2337
|
+
return { coef, rank };
|
|
2338
|
+
}
|
|
2339
|
+
function fitRegression(points, degree, basis = regressionBasis(points)) {
|
|
2340
|
+
const p = degree + 1;
|
|
2341
|
+
if (degree === 1) {
|
|
2342
|
+
let mx = 0;
|
|
2343
|
+
let my = 0;
|
|
2344
|
+
let n = 0;
|
|
2345
|
+
for (const row of points) {
|
|
2346
|
+
n++;
|
|
2347
|
+
mx += (normalizeRegression(row.x, basis.xCenter, basis.xScale) - mx) / n;
|
|
2348
|
+
my += (normalizeRegression(row.y, basis.yCenter, basis.yScale) - my) / n;
|
|
2349
|
+
}
|
|
2350
|
+
let sxx = 0;
|
|
2351
|
+
let sxy = 0;
|
|
2352
|
+
for (const row of points) {
|
|
2353
|
+
const dx = normalizeRegression(row.x, basis.xCenter, basis.xScale) - mx;
|
|
2354
|
+
sxx += dx * dx;
|
|
2355
|
+
sxy += dx * (normalizeRegression(row.y, basis.yCenter, basis.yScale) - my);
|
|
2356
|
+
}
|
|
2357
|
+
const b = sxx === 0 ? 0 : sxy / sxx;
|
|
2358
|
+
const coef = sxx === 0 ? [my / (1 + mx * mx), (mx * my) / (1 + mx * mx)] : [my - b * mx, b];
|
|
2359
|
+
return { coef, rank: n === 0 ? 0 : sxx === 0 ? 1 : 2, basis };
|
|
2360
|
+
}
|
|
2361
|
+
const r = Array.from({ length: p }, () => new Array(p).fill(0));
|
|
2362
|
+
const rhs = new Array(p).fill(0);
|
|
2363
|
+
const row = new Array(p);
|
|
2364
|
+
for (const point of points) {
|
|
2365
|
+
const z = normalizeRegression(point.x, basis.xCenter, basis.xScale);
|
|
2366
|
+
row[0] = 1;
|
|
2367
|
+
for (let j = 1; j < p; j++) {
|
|
2368
|
+
row[j] = row[j - 1] * z;
|
|
2369
|
+
}
|
|
2370
|
+
let value = normalizeRegression(point.y, basis.yCenter, basis.yScale);
|
|
2371
|
+
// 增量 Givens QR 仅保留 p×p 的 R,bootstrap 不分配 N×p 矩阵。
|
|
2372
|
+
for (let j = 0; j < p; j++) {
|
|
2373
|
+
const norm = Math.hypot(r[j][j], row[j]);
|
|
2374
|
+
if (norm === 0) {
|
|
2375
|
+
continue;
|
|
2376
|
+
}
|
|
2377
|
+
const c = r[j][j] / norm;
|
|
2378
|
+
const s = row[j] / norm;
|
|
2379
|
+
r[j][j] = norm;
|
|
2380
|
+
for (let k = j + 1; k < p; k++) {
|
|
2381
|
+
const previous = r[j][k];
|
|
2382
|
+
r[j][k] = c * previous + s * row[k];
|
|
2383
|
+
row[k] = -s * previous + c * row[k];
|
|
2384
|
+
}
|
|
2385
|
+
const previous = rhs[j];
|
|
2386
|
+
rhs[j] = c * previous + s * value;
|
|
2387
|
+
value = -s * previous + c * value;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
const tolerance = Math.max(...r.map((row, i) => Math.abs(row[i]))) * Number.EPSILON * p * 64;
|
|
2391
|
+
if (r.some((row, i) => Math.abs(row[i]) <= tolerance)) {
|
|
2392
|
+
return Object.assign(Object.assign({}, minimumNorm(r, rhs)), { basis });
|
|
2393
|
+
}
|
|
2394
|
+
const coef = new Array(p);
|
|
2395
|
+
for (let i = p - 1; i >= 0; i--) {
|
|
2396
|
+
let value = rhs[i];
|
|
2397
|
+
for (let j = i + 1; j < p; j++) {
|
|
2398
|
+
value -= r[i][j] * coef[j];
|
|
2399
|
+
}
|
|
2400
|
+
coef[i] = value / r[i][i];
|
|
2401
|
+
}
|
|
2402
|
+
return { coef, rank: p, basis };
|
|
2403
|
+
}
|
|
2404
|
+
function regressionPrediction(model, x) {
|
|
2405
|
+
const z = normalizeRegression(x, model.basis.xCenter, model.basis.xScale);
|
|
2406
|
+
let value = 0;
|
|
2407
|
+
for (let j = model.coef.length - 1; j >= 0; j--) {
|
|
2408
|
+
value = value * z + model.coef[j];
|
|
2409
|
+
}
|
|
2410
|
+
return model.basis.yCenter + model.basis.yScale * value;
|
|
2411
|
+
}
|
|
2412
|
+
/** 保留公开的升幂 coef;预测始终使用稳定基底。 */
|
|
2413
|
+
function regressionCoefficients(model) {
|
|
2414
|
+
let coef = [model.coef[model.coef.length - 1]];
|
|
2415
|
+
const a = -model.basis.xCenter / model.basis.xScale;
|
|
2416
|
+
const b = 1 / model.basis.xScale;
|
|
2417
|
+
for (let j = model.coef.length - 2; j >= 0; j--) {
|
|
2418
|
+
const next = new Array(coef.length + 1).fill(0);
|
|
2419
|
+
coef.forEach((c, i) => {
|
|
2420
|
+
next[i] += a * c;
|
|
2421
|
+
next[i + 1] += b * c;
|
|
2422
|
+
});
|
|
2423
|
+
next[0] += model.coef[j];
|
|
2424
|
+
coef = next;
|
|
1757
2425
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
2426
|
+
coef = coef.map(c => c * model.basis.yScale);
|
|
2427
|
+
coef[0] += model.basis.yCenter;
|
|
2428
|
+
if (coef.some(c => !Number.isFinite(c))) {
|
|
2429
|
+
throw new RangeError('Regression coefficients are not finite');
|
|
2430
|
+
}
|
|
2431
|
+
return coef;
|
|
1760
2432
|
}
|
|
2433
|
+
function regressionGrid(min, max, count, predict) {
|
|
2434
|
+
if (!Number.isInteger(count) || count <= 0 || !Number.isFinite(min) || !Number.isFinite(max)) {
|
|
2435
|
+
return [];
|
|
2436
|
+
}
|
|
2437
|
+
const width = max - min;
|
|
2438
|
+
const step = count === 1 ? 0 : width / (count - 1);
|
|
2439
|
+
return Array.from({ length: count }, (_, i) => {
|
|
2440
|
+
const t = count === 1 ? 0 : i / (count - 1);
|
|
2441
|
+
const x = i === 0 ? min : i === count - 1 ? max : Number.isFinite(width) ? min + i * step : (1 - t) * min + t * max;
|
|
2442
|
+
return { x, y: predict(x) };
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
/** Compute a two-sided normal critical value without subtracting a small tail probability from one. */
|
|
2447
|
+
function confidenceCriticalValue(alpha) {
|
|
2448
|
+
if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
|
2449
|
+
return NaN;
|
|
2450
|
+
}
|
|
2451
|
+
return -normalQuantile(alpha / 2);
|
|
2452
|
+
}
|
|
2453
|
+
/** A zero standard error gives a degenerate interval even when the critical value is infinite. */
|
|
2454
|
+
function confidenceHalfWidth(z, standardError) {
|
|
2455
|
+
if (Number.isNaN(z)) {
|
|
2456
|
+
return NaN;
|
|
2457
|
+
}
|
|
2458
|
+
return standardError === 0 ? 0 : z * standardError;
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* Compute basic components used for linear regression confidence / prediction intervals.
|
|
2462
|
+
* - scans data to compute min/max of x, n, mean X, SSE and Sxx (sum (xi - X)^2).
|
|
2463
|
+
*/
|
|
1761
2464
|
function computeLinearCIComponents(data, x, y, predict) {
|
|
2465
|
+
// simple local scanner to avoid circular imports
|
|
1762
2466
|
let min = Infinity;
|
|
1763
2467
|
let max = -Infinity;
|
|
1764
2468
|
let n = 0;
|
|
@@ -1797,6 +2501,9 @@
|
|
|
1797
2501
|
}
|
|
1798
2502
|
return { min, max, n, X, SSE, Sxx };
|
|
1799
2503
|
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Compute standard errors for mean and prediction at px using components.
|
|
2506
|
+
*/
|
|
1800
2507
|
function stdErrorsAt(px, comps) {
|
|
1801
2508
|
const { n, X, Sxx, SSE } = comps;
|
|
1802
2509
|
const s2 = n > 2 ? SSE / (n - 2) : 0;
|
|
@@ -1805,6 +2512,10 @@
|
|
|
1805
2512
|
return { seMean, sePred };
|
|
1806
2513
|
}
|
|
1807
2514
|
|
|
2515
|
+
/**
|
|
2516
|
+
* Linear regression utilities (single clean implementation).
|
|
2517
|
+
* Exports: ordinaryLeastSquares, visitPoints, rSquared, regressionLinear
|
|
2518
|
+
*/
|
|
1808
2519
|
function ordinaryLeastSquares(uX, uY, uXY, uX2) {
|
|
1809
2520
|
const denom = uX2 - uX * uX;
|
|
1810
2521
|
if (Math.abs(denom) < Number.EPSILON) {
|
|
@@ -1843,56 +2554,39 @@
|
|
|
1843
2554
|
function regressionLinear(data, x = d => d.x, y = d => d.y, options) {
|
|
1844
2555
|
var _a;
|
|
1845
2556
|
const alpha = (_a = options === null || options === void 0 ? void 0 : options.alpha) !== null && _a !== void 0 ? _a : 0.05;
|
|
1846
|
-
|
|
1847
|
-
|
|
2557
|
+
const points = regressionPoints(data, x, y);
|
|
2558
|
+
const model = fitRegression(points, 1);
|
|
2559
|
+
const coef = regressionCoefficients(model);
|
|
2560
|
+
const a = coef[0];
|
|
2561
|
+
const b = coef[1];
|
|
2562
|
+
const predict = (xx) => regressionPrediction(model, xx);
|
|
2563
|
+
const comps = computeLinearCIComponents(data, x, y, predict);
|
|
1848
2564
|
let meanY = 0;
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
visitPoints(data, x, y, (xi, yi) => {
|
|
1852
|
-
n++;
|
|
1853
|
-
meanX += (xi - meanX) / n;
|
|
1854
|
-
meanY += (yi - meanY) / n;
|
|
1855
|
-
meanXY += (xi * yi - meanXY) / n;
|
|
1856
|
-
meanX2 += (xi * xi - meanX2) / n;
|
|
2565
|
+
points.forEach((p, i) => {
|
|
2566
|
+
meanY += (p.y - meanY) / (i + 1);
|
|
1857
2567
|
});
|
|
1858
|
-
const { a, b } = ordinaryLeastSquares(meanX, meanY, meanXY, meanX2);
|
|
1859
|
-
const predict = (xx) => a + b * xx;
|
|
1860
|
-
const comps = computeLinearCIComponents(data, x, y, predict);
|
|
1861
2568
|
function evaluateGrid(N) {
|
|
1862
|
-
|
|
1863
|
-
if (comps.n === 0 || N <= 0) {
|
|
1864
|
-
return out;
|
|
1865
|
-
}
|
|
1866
|
-
if (comps.min === comps.max) {
|
|
1867
|
-
for (let i = 0; i < N; i++) {
|
|
1868
|
-
out.push({ x: comps.min, y: predict(comps.min) });
|
|
1869
|
-
}
|
|
1870
|
-
return out;
|
|
1871
|
-
}
|
|
1872
|
-
const step = (comps.max - comps.min) / (N - 1);
|
|
1873
|
-
for (let i = 0; i < N; i++) {
|
|
1874
|
-
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
1875
|
-
out.push({ x: px, y: predict(px) });
|
|
1876
|
-
}
|
|
1877
|
-
return out;
|
|
2569
|
+
return regressionGrid(comps.min, comps.max, N, predict);
|
|
1878
2570
|
}
|
|
1879
2571
|
function confidenceInterval(N = 50) {
|
|
1880
2572
|
const out = [];
|
|
1881
2573
|
if (comps.n === 0 || N <= 0) {
|
|
1882
2574
|
return out;
|
|
1883
2575
|
}
|
|
1884
|
-
const z =
|
|
2576
|
+
const z = confidenceCriticalValue(alpha);
|
|
1885
2577
|
if (comps.min === comps.max) {
|
|
1886
2578
|
const m = predict(comps.min);
|
|
1887
2579
|
const errs = stdErrorsAt(comps.min, comps);
|
|
2580
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2581
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
1888
2582
|
for (let i = 0; i < N; i++) {
|
|
1889
2583
|
out.push({
|
|
1890
2584
|
x: comps.min,
|
|
1891
2585
|
mean: m,
|
|
1892
|
-
lower: m -
|
|
1893
|
-
upper: m +
|
|
1894
|
-
predLower: m -
|
|
1895
|
-
predUpper: m +
|
|
2586
|
+
lower: m - meanHalfWidth,
|
|
2587
|
+
upper: m + meanHalfWidth,
|
|
2588
|
+
predLower: m - predHalfWidth,
|
|
2589
|
+
predUpper: m + predHalfWidth
|
|
1896
2590
|
});
|
|
1897
2591
|
}
|
|
1898
2592
|
return out;
|
|
@@ -1902,19 +2596,22 @@
|
|
|
1902
2596
|
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
1903
2597
|
const m = predict(px);
|
|
1904
2598
|
const errs = stdErrorsAt(px, comps);
|
|
2599
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2600
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
1905
2601
|
out.push({
|
|
1906
2602
|
x: px,
|
|
1907
2603
|
mean: m,
|
|
1908
|
-
lower: m -
|
|
1909
|
-
upper: m +
|
|
1910
|
-
predLower: m -
|
|
1911
|
-
predUpper: m +
|
|
2604
|
+
lower: m - meanHalfWidth,
|
|
2605
|
+
upper: m + meanHalfWidth,
|
|
2606
|
+
predLower: m - predHalfWidth,
|
|
2607
|
+
predUpper: m + predHalfWidth
|
|
1912
2608
|
});
|
|
1913
2609
|
}
|
|
1914
2610
|
return out;
|
|
1915
2611
|
}
|
|
1916
2612
|
return {
|
|
1917
2613
|
coef: { a, b },
|
|
2614
|
+
rank: model.rank,
|
|
1918
2615
|
predict,
|
|
1919
2616
|
rSquared: rSquared(data, x, y, meanY, predict),
|
|
1920
2617
|
evaluateGrid,
|
|
@@ -1922,11 +2619,18 @@
|
|
|
1922
2619
|
};
|
|
1923
2620
|
}
|
|
1924
2621
|
|
|
2622
|
+
/**
|
|
2623
|
+
* Simple logistic regression (binary) using Newton-Raphson (IRLS).
|
|
2624
|
+
* Returns { coef, predict, evaluateGrid }
|
|
2625
|
+
* - coef: [intercept, beta]
|
|
2626
|
+
* - predict(x): probability p(y=1|x)
|
|
2627
|
+
*/
|
|
1925
2628
|
function regressionLogistic(data, x = d => d.x, y = d => d.y, options) {
|
|
1926
2629
|
var _a, _b, _c;
|
|
1927
2630
|
const maxIter = (_a = options === null || options === void 0 ? void 0 : options.maxIteration) !== null && _a !== void 0 ? _a : 25;
|
|
1928
2631
|
const tol = (_b = options === null || options === void 0 ? void 0 : options.tol) !== null && _b !== void 0 ? _b : 1e-6;
|
|
1929
2632
|
const alpha = (_c = options === null || options === void 0 ? void 0 : options.alpha) !== null && _c !== void 0 ? _c : 0.05;
|
|
2633
|
+
// build arrays
|
|
1930
2634
|
const xs = [];
|
|
1931
2635
|
const ys = [];
|
|
1932
2636
|
visitPoints(data, x, y, (dx, dy) => {
|
|
@@ -1942,6 +2646,7 @@
|
|
|
1942
2646
|
confidenceInterval: (N = 50) => []
|
|
1943
2647
|
};
|
|
1944
2648
|
}
|
|
2649
|
+
// initial coef via linear regression rough guess
|
|
1945
2650
|
let intercept = 0;
|
|
1946
2651
|
let beta = 0;
|
|
1947
2652
|
for (let iter = 0; iter < maxIter; iter++) {
|
|
@@ -1952,6 +2657,7 @@
|
|
|
1952
2657
|
const pi = 1 / (1 + Math.exp(-z));
|
|
1953
2658
|
p[i] = pi;
|
|
1954
2659
|
}
|
|
2660
|
+
// compute gradient and hessian
|
|
1955
2661
|
let g0 = 0;
|
|
1956
2662
|
let g1 = 0;
|
|
1957
2663
|
let h00 = 0;
|
|
@@ -1966,6 +2672,7 @@
|
|
|
1966
2672
|
h01 += wi * xs[i];
|
|
1967
2673
|
h11 += wi * xs[i] * xs[i];
|
|
1968
2674
|
}
|
|
2675
|
+
// solve 2x2 system H * delta = g
|
|
1969
2676
|
const det = h00 * h11 - h01 * h01;
|
|
1970
2677
|
if (Math.abs(det) < 1e-12) {
|
|
1971
2678
|
break;
|
|
@@ -2026,18 +2733,20 @@
|
|
|
2026
2733
|
if (comps.n === 0) {
|
|
2027
2734
|
return out;
|
|
2028
2735
|
}
|
|
2029
|
-
const z =
|
|
2736
|
+
const z = confidenceCriticalValue(alpha);
|
|
2030
2737
|
if (comps.min === comps.max) {
|
|
2031
2738
|
const v = predict(comps.min);
|
|
2032
2739
|
const errs = stdErrorsAt(comps.min, comps);
|
|
2740
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2741
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2033
2742
|
for (let i = 0; i < N; i++) {
|
|
2034
2743
|
out.push({
|
|
2035
2744
|
x: comps.min,
|
|
2036
2745
|
mean: v,
|
|
2037
|
-
lower: v -
|
|
2038
|
-
upper: v +
|
|
2039
|
-
predLower: v -
|
|
2040
|
-
predUpper: v +
|
|
2746
|
+
lower: v - meanHalfWidth,
|
|
2747
|
+
upper: v + meanHalfWidth,
|
|
2748
|
+
predLower: v - predHalfWidth,
|
|
2749
|
+
predUpper: v + predHalfWidth
|
|
2041
2750
|
});
|
|
2042
2751
|
}
|
|
2043
2752
|
return out;
|
|
@@ -2047,13 +2756,15 @@
|
|
|
2047
2756
|
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
2048
2757
|
const yh = predict(px);
|
|
2049
2758
|
const errs = stdErrorsAt(px, comps);
|
|
2759
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2760
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2050
2761
|
out.push({
|
|
2051
2762
|
x: px,
|
|
2052
2763
|
mean: yh,
|
|
2053
|
-
lower: yh -
|
|
2054
|
-
upper: yh +
|
|
2055
|
-
predLower: yh -
|
|
2056
|
-
predUpper: yh +
|
|
2764
|
+
lower: yh - meanHalfWidth,
|
|
2765
|
+
upper: yh + meanHalfWidth,
|
|
2766
|
+
predLower: yh - predHalfWidth,
|
|
2767
|
+
predUpper: yh + predHalfWidth
|
|
2057
2768
|
});
|
|
2058
2769
|
}
|
|
2059
2770
|
return out;
|
|
@@ -2066,193 +2777,226 @@
|
|
|
2066
2777
|
};
|
|
2067
2778
|
}
|
|
2068
2779
|
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
const t = 1 - uu * uu * uu;
|
|
2075
|
-
return t * t * t;
|
|
2076
|
-
}
|
|
2077
|
-
function stratifiedSample(sortedData, maxSamples) {
|
|
2078
|
-
const n = sortedData.length;
|
|
2079
|
-
if (n <= maxSamples) {
|
|
2080
|
-
return sortedData;
|
|
2081
|
-
}
|
|
2082
|
-
const sampled = [];
|
|
2083
|
-
const step = n / maxSamples;
|
|
2084
|
-
for (let i = 0; i < maxSamples; i++) {
|
|
2085
|
-
const idx = Math.min(Math.floor(i * step), n - 1);
|
|
2086
|
-
sampled.push(sortedData[idx]);
|
|
2087
|
-
}
|
|
2088
|
-
return sampled;
|
|
2089
|
-
}
|
|
2780
|
+
/**
|
|
2781
|
+
* 局部线性 LOWESS。默认全量观测、span=2/3、三次稳健重加权。
|
|
2782
|
+
* delta 显式插值加速;maxSamples 仅保留显式选择的历史抽样用途。
|
|
2783
|
+
* predict 与 evaluateGrid 查询构建时固定的同一模型,不触发再拟合。
|
|
2784
|
+
*/
|
|
2090
2785
|
function regressionLowess(data, x = d => d.x, y = d => d.y, options = {}) {
|
|
2091
|
-
var _a;
|
|
2092
|
-
const span = options.span
|
|
2093
|
-
const degree = options.degree
|
|
2094
|
-
const
|
|
2095
|
-
const
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2786
|
+
var _a, _b, _c, _d, _e;
|
|
2787
|
+
const span = (_a = options.span) !== null && _a !== void 0 ? _a : 2 / 3;
|
|
2788
|
+
const degree = (_b = options.degree) !== null && _b !== void 0 ? _b : 1;
|
|
2789
|
+
const iterations = (_c = options.iterations) !== null && _c !== void 0 ? _c : 3;
|
|
2790
|
+
const delta = (_d = options.delta) !== null && _d !== void 0 ? _d : 0;
|
|
2791
|
+
const alpha = (_e = options.alpha) !== null && _e !== void 0 ? _e : 0.05;
|
|
2792
|
+
if (!(span > 0 && span <= 1) ||
|
|
2793
|
+
!Number.isInteger(iterations) ||
|
|
2794
|
+
iterations < 0 ||
|
|
2795
|
+
!Number.isFinite(delta) ||
|
|
2796
|
+
delta < 0 ||
|
|
2797
|
+
(degree !== 0 && degree !== 1) ||
|
|
2798
|
+
(options.maxSamples != null && (!Number.isInteger(options.maxSamples) || options.maxSamples < 2))) {
|
|
2799
|
+
throw new RangeError('Invalid LOWESS span, degree, iterations, delta or maxSamples');
|
|
2800
|
+
}
|
|
2801
|
+
let pts = regressionPoints(data, x, y).sort((a, b) => a.x - b.x || a.y - b.y);
|
|
2802
|
+
if (options.maxSamples != null && pts.length > options.maxSamples) {
|
|
2803
|
+
const full = pts;
|
|
2804
|
+
const count = options.maxSamples;
|
|
2805
|
+
pts = Array.from({ length: count }, (_, i) => full[Math.floor((i * (full.length - 1)) / (count - 1))]);
|
|
2806
|
+
}
|
|
2807
|
+
const n = pts.length;
|
|
2808
|
+
const ptsX = pts.map(p => p.x);
|
|
2809
|
+
const ptsY = pts.map(p => p.y);
|
|
2810
|
+
const basis = regressionBasis(pts);
|
|
2811
|
+
const normalizedX = ptsX.map(v => normalizeRegression(v, basis.xCenter, basis.xScale));
|
|
2812
|
+
const normalizedY = ptsY.map(v => normalizeRegression(v, basis.yCenter, basis.yScale));
|
|
2813
|
+
const count = Math.min(n, Math.max(2, Math.floor(span * n + 1e-10)));
|
|
2814
|
+
let robustWeights;
|
|
2815
|
+
function exact(x0) {
|
|
2816
|
+
if (!n) {
|
|
2112
2817
|
return 0;
|
|
2113
2818
|
}
|
|
2819
|
+
const query = normalizeRegression(x0, basis.xCenter, basis.xScale);
|
|
2820
|
+
// 找到连续的最近邻窗口;相等距离选择较小的 x(ties 的 y 已排序)。
|
|
2114
2821
|
let left = 0;
|
|
2115
|
-
let right = n;
|
|
2822
|
+
let right = n - count;
|
|
2116
2823
|
while (left < right) {
|
|
2117
|
-
const mid = (left + right)
|
|
2118
|
-
|
|
2824
|
+
const mid = (left + right) >>> 1;
|
|
2825
|
+
const lowerDistance = x0 - ptsX[mid];
|
|
2826
|
+
const upperDistance = ptsX[mid + count] - x0;
|
|
2827
|
+
if (Number.isFinite(lowerDistance) && Number.isFinite(upperDistance)
|
|
2828
|
+
? lowerDistance > upperDistance
|
|
2829
|
+
: query - normalizedX[mid] > normalizedX[mid + count] - query) {
|
|
2119
2830
|
left = mid + 1;
|
|
2120
2831
|
}
|
|
2121
2832
|
else {
|
|
2122
2833
|
right = mid;
|
|
2123
2834
|
}
|
|
2124
2835
|
}
|
|
2125
|
-
|
|
2126
|
-
let
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
lo--;
|
|
2836
|
+
let start = left;
|
|
2837
|
+
let end = left + count;
|
|
2838
|
+
if (ptsX[start] === ptsX[end - 1]) {
|
|
2839
|
+
while (start > 0 && ptsX[start - 1] === ptsX[start]) {
|
|
2840
|
+
start--;
|
|
2131
2841
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
const actualStart = lo + 1;
|
|
2137
|
-
const end = hi;
|
|
2138
|
-
let maxDist = 0;
|
|
2139
|
-
const windowSize = end - actualStart;
|
|
2140
|
-
const distances = new Array(windowSize);
|
|
2141
|
-
for (let i = actualStart; i < end; i++) {
|
|
2142
|
-
const dist = Math.abs(ptsX[i] - x0);
|
|
2143
|
-
distances[i - actualStart] = dist;
|
|
2144
|
-
if (dist > maxDist) {
|
|
2145
|
-
maxDist = dist;
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
let sumw = 0;
|
|
2149
|
-
const w = new Array(windowSize);
|
|
2150
|
-
for (let i = 0; i < windowSize; i++) {
|
|
2151
|
-
const u = maxDist === 0 ? 0 : distances[i] / maxDist;
|
|
2152
|
-
let wi = tricube(u);
|
|
2153
|
-
if (robustWeights && robustWeights[actualStart + i] != null) {
|
|
2154
|
-
wi *= robustWeights[actualStart + i];
|
|
2155
|
-
}
|
|
2156
|
-
w[i] = wi;
|
|
2157
|
-
sumw += wi;
|
|
2158
|
-
}
|
|
2159
|
-
if (sumw === 0) {
|
|
2160
|
-
let nearestIdx = 0;
|
|
2161
|
-
let nearestDistance = Math.abs(ptsX[0] - x0);
|
|
2162
|
-
for (let i = 1; i < n; i++) {
|
|
2163
|
-
const distance = Math.abs(ptsX[i] - x0);
|
|
2164
|
-
if (distance < nearestDistance ||
|
|
2165
|
-
(distance === nearestDistance && sampledPoints[i].index < sampledPoints[nearestIdx].index)) {
|
|
2166
|
-
nearestIdx = i;
|
|
2167
|
-
nearestDistance = distance;
|
|
2168
|
-
}
|
|
2842
|
+
while (end < n && ptsX[end] === ptsX[start]) {
|
|
2843
|
+
end++;
|
|
2169
2844
|
}
|
|
2170
|
-
return ptsY[nearestIdx];
|
|
2171
2845
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2846
|
+
const rawRadius = Math.max(Math.abs(ptsX[start] - x0), Math.abs(ptsX[end - 1] - x0));
|
|
2847
|
+
const scaled = !Number.isFinite(rawRadius);
|
|
2848
|
+
const radius = scaled
|
|
2849
|
+
? Math.max(Math.abs(normalizedX[start] - query), Math.abs(normalizedX[end - 1] - query))
|
|
2850
|
+
: rawRadius;
|
|
2851
|
+
// 局部响应坐标不能由远处、未参与该邻域的观测决定精度。
|
|
2852
|
+
let yMin = Infinity;
|
|
2853
|
+
let yMax = -Infinity;
|
|
2854
|
+
for (let i = start; i < end; i++) {
|
|
2855
|
+
yMin = Math.min(yMin, ptsY[i]);
|
|
2856
|
+
yMax = Math.max(yMax, ptsY[i]);
|
|
2178
2857
|
}
|
|
2858
|
+
const yCenter = yMin / 2 + yMax / 2;
|
|
2859
|
+
const yScale = Math.max(Math.abs(yMin - yCenter), Math.abs(yMax - yCenter)) || 1;
|
|
2179
2860
|
let sw = 0;
|
|
2180
2861
|
let sx = 0;
|
|
2181
2862
|
let sy = 0;
|
|
2182
2863
|
let sxx = 0;
|
|
2183
2864
|
let sxy = 0;
|
|
2184
|
-
for (let i =
|
|
2185
|
-
const
|
|
2186
|
-
const
|
|
2187
|
-
const
|
|
2188
|
-
const
|
|
2189
|
-
sw +=
|
|
2190
|
-
sx +=
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2865
|
+
for (let i = start; i < end; i++) {
|
|
2866
|
+
const z = radius === 0 ? 0 : (scaled ? normalizedX[i] - query : ptsX[i] - x0) / radius;
|
|
2867
|
+
const u = Math.abs(z);
|
|
2868
|
+
const t = u >= 1 ? 0 : 1 - u * u * u;
|
|
2869
|
+
const w = t * t * t * (robustWeights ? robustWeights[i] : 1);
|
|
2870
|
+
sw += w;
|
|
2871
|
+
sx += w * z;
|
|
2872
|
+
const response = normalizeRegression(ptsY[i], yCenter, yScale);
|
|
2873
|
+
sy += w * response;
|
|
2874
|
+
sxx += w * z * z;
|
|
2875
|
+
sxy += w * z * response;
|
|
2876
|
+
}
|
|
2877
|
+
if (sw === 0) {
|
|
2878
|
+
// 核边界零权重时使用最近有效观测的加权常数;等距离取均值。
|
|
2879
|
+
let distance = Infinity;
|
|
2880
|
+
let weight = 0;
|
|
2881
|
+
let value = 0;
|
|
2882
|
+
for (let i = 0; i < n; i++) {
|
|
2883
|
+
const w = robustWeights ? robustWeights[i] : 1;
|
|
2884
|
+
if (w === 0) {
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
const d = Math.abs(scaled ? normalizedX[i] - query : ptsX[i] - x0);
|
|
2888
|
+
if (d < distance) {
|
|
2889
|
+
distance = d;
|
|
2890
|
+
weight = w;
|
|
2891
|
+
value = ptsY[i];
|
|
2892
|
+
}
|
|
2893
|
+
else if (d === distance) {
|
|
2894
|
+
value = value * (weight / (weight + w)) + ptsY[i] * (w / (weight + w));
|
|
2895
|
+
weight += w;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
if (weight === 0) {
|
|
2899
|
+
throw new RangeError('LOWESS has no effective local observations');
|
|
2900
|
+
}
|
|
2901
|
+
return value;
|
|
2902
|
+
}
|
|
2903
|
+
const mx = sx / sw;
|
|
2904
|
+
const my = sy / sw;
|
|
2905
|
+
const variance = sxx / sw - mx * mx;
|
|
2906
|
+
const slope = degree === 0 || variance <= Number.EPSILON * 32 ? 0 : (sxy / sw - mx * my) / variance;
|
|
2907
|
+
return yCenter + yScale * (my - slope * mx);
|
|
2908
|
+
}
|
|
2909
|
+
// delta 不删除观测。只减少局部回归求值位置,全部残差参与重加权。
|
|
2910
|
+
const anchors = [];
|
|
2911
|
+
for (let i = 0; i < n;) {
|
|
2912
|
+
anchors.push(i);
|
|
2913
|
+
if (i === n - 1) {
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
let next = i + 1;
|
|
2917
|
+
if (delta > 0) {
|
|
2918
|
+
while (next + 1 < n && ptsX[next + 1] - ptsX[i] <= delta) {
|
|
2919
|
+
next++;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
// 重复 x 一起使用同一预测;端点不能丢失。
|
|
2923
|
+
while (next + 1 < n && ptsX[next + 1] === ptsX[next]) {
|
|
2924
|
+
next++;
|
|
2925
|
+
}
|
|
2926
|
+
i = next;
|
|
2201
2927
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2928
|
+
let fits = new Array(n);
|
|
2929
|
+
function fitObservations() {
|
|
2930
|
+
const out = new Array(n);
|
|
2931
|
+
for (let a = 0; a < anchors.length; a++) {
|
|
2932
|
+
const index = anchors[a];
|
|
2933
|
+
out[index] = exact(ptsX[index]);
|
|
2934
|
+
if (a === 0) {
|
|
2935
|
+
continue;
|
|
2936
|
+
}
|
|
2937
|
+
const previous = anchors[a - 1];
|
|
2938
|
+
const rawWidth = ptsX[index] - ptsX[previous];
|
|
2939
|
+
const width = Number.isFinite(rawWidth) ? rawWidth : normalizedX[index] - normalizedX[previous];
|
|
2940
|
+
for (let i = previous + 1; i < index; i++) {
|
|
2941
|
+
const t = width === 0
|
|
2942
|
+
? 0
|
|
2943
|
+
: (Number.isFinite(rawWidth) ? ptsX[i] - ptsX[previous] : normalizedX[i] - normalizedX[previous]) / width;
|
|
2944
|
+
out[i] = out[previous] * (1 - t) + out[index] * t;
|
|
2208
2945
|
}
|
|
2209
|
-
return out;
|
|
2210
2946
|
}
|
|
2211
|
-
return
|
|
2947
|
+
return out;
|
|
2212
2948
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2949
|
+
for (let iteration = 0; iteration <= iterations; iteration++) {
|
|
2950
|
+
fits = fitObservations();
|
|
2951
|
+
if (iteration === iterations) {
|
|
2952
|
+
break;
|
|
2216
2953
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2954
|
+
const residuals = fits.map((v, i) => {
|
|
2955
|
+
const residual = ptsY[i] - v;
|
|
2956
|
+
return Number.isFinite(residual)
|
|
2957
|
+
? Math.abs(residual / basis.yScale)
|
|
2958
|
+
: Math.abs(normalizedY[i] - normalizeRegression(v, basis.yCenter, basis.yScale));
|
|
2959
|
+
});
|
|
2960
|
+
const med = median(residuals.slice());
|
|
2961
|
+
const currentFits = fits;
|
|
2962
|
+
robustWeights = residuals.map((r, i) => {
|
|
2963
|
+
if (med === 0) {
|
|
2964
|
+
const tolerance = Number.EPSILON * 64 * (Math.max(Math.abs(ptsY[i]), Math.abs(currentFits[i])) / basis.yScale);
|
|
2965
|
+
return r <= tolerance ? 1 : 0;
|
|
2966
|
+
}
|
|
2967
|
+
const u = r / (6 * med);
|
|
2968
|
+
return u >= 1 ? 0 : (1 - u * u) ** 2;
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
function predictSingle(x0) {
|
|
2972
|
+
if (delta === 0 || n < 2 || x0 < ptsX[0] || x0 > ptsX[n - 1]) {
|
|
2973
|
+
return exact(x0);
|
|
2219
2974
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
out[i] = { x: min, y: v };
|
|
2975
|
+
let lo = 0;
|
|
2976
|
+
let hi = n - 1;
|
|
2977
|
+
while (hi - lo > 1) {
|
|
2978
|
+
const mid = (lo + hi) >>> 1;
|
|
2979
|
+
if (ptsX[mid] <= x0) {
|
|
2980
|
+
lo = mid;
|
|
2227
2981
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
const step = (max - min) / (N - 1);
|
|
2231
|
-
let robustWeights;
|
|
2232
|
-
const effectiveIterations = options.iterations != null ? iterations : n > 500 ? 0 : iterations;
|
|
2233
|
-
if (effectiveIterations > 0) {
|
|
2234
|
-
for (let iter = 0; iter < effectiveIterations; iter++) {
|
|
2235
|
-
const fits = new Array(n);
|
|
2236
|
-
const res = new Array(n);
|
|
2237
|
-
for (let i = 0; i < n; i++) {
|
|
2238
|
-
fits[i] = predictSingle(ptsX[i], robustWeights);
|
|
2239
|
-
res[i] = Math.abs(ptsY[i] - fits[i]);
|
|
2240
|
-
}
|
|
2241
|
-
const sortedRes = res.slice().sort((a, b) => a - b);
|
|
2242
|
-
const med = sortedRes[Math.floor(n / 2)] || 0;
|
|
2243
|
-
robustWeights = new Array(n);
|
|
2244
|
-
for (let i = 0; i < n; i++) {
|
|
2245
|
-
const u = med === 0 ? 0 : res[i] / (6 * med);
|
|
2246
|
-
const w = Math.abs(u) >= 1 ? 0 : (1 - u * u) * (1 - u * u);
|
|
2247
|
-
robustWeights[i] = w;
|
|
2248
|
-
}
|
|
2982
|
+
else {
|
|
2983
|
+
hi = mid;
|
|
2249
2984
|
}
|
|
2250
2985
|
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
out[i] = { x: px, y: predictSingle(px, robustWeights) };
|
|
2986
|
+
if (ptsX[hi] === ptsX[lo]) {
|
|
2987
|
+
return fits[lo];
|
|
2254
2988
|
}
|
|
2255
|
-
|
|
2989
|
+
const width = ptsX[hi] - ptsX[lo];
|
|
2990
|
+
const t = Number.isFinite(width)
|
|
2991
|
+
? (x0 - ptsX[lo]) / width
|
|
2992
|
+
: (normalizeRegression(x0, basis.xCenter, basis.xScale) - normalizedX[lo]) / (normalizedX[hi] - normalizedX[lo]);
|
|
2993
|
+
return fits[lo] * (1 - t) + fits[hi] * t;
|
|
2994
|
+
}
|
|
2995
|
+
function predict(x0) {
|
|
2996
|
+
return Array.isArray(x0) ? x0.map(predictSingle) : predictSingle(x0);
|
|
2997
|
+
}
|
|
2998
|
+
function evaluateGrid(N) {
|
|
2999
|
+
return regressionGrid(ptsX[0], ptsX[n - 1], N, predictSingle);
|
|
2256
3000
|
}
|
|
2257
3001
|
function confidenceInterval(N = 50) {
|
|
2258
3002
|
const out = [];
|
|
@@ -2262,6 +3006,7 @@
|
|
|
2262
3006
|
if (n === 0) {
|
|
2263
3007
|
return out;
|
|
2264
3008
|
}
|
|
3009
|
+
// use data x-range (already sorted)
|
|
2265
3010
|
const min = ptsX[0];
|
|
2266
3011
|
const max = ptsX[n - 1];
|
|
2267
3012
|
if (min === undefined || max === undefined || min === Infinity || max === -Infinity) {
|
|
@@ -2271,18 +3016,20 @@
|
|
|
2271
3016
|
if (comps.n === 0) {
|
|
2272
3017
|
return out;
|
|
2273
3018
|
}
|
|
2274
|
-
const z =
|
|
3019
|
+
const z = confidenceCriticalValue(alpha);
|
|
2275
3020
|
if (comps.min === comps.max) {
|
|
2276
3021
|
const v = predictSingle(comps.min);
|
|
2277
3022
|
const errs = stdErrorsAt(comps.min, comps);
|
|
3023
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3024
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2278
3025
|
for (let i = 0; i < N; i++) {
|
|
2279
3026
|
out.push({
|
|
2280
3027
|
x: comps.min,
|
|
2281
3028
|
mean: v,
|
|
2282
|
-
lower: v -
|
|
2283
|
-
upper: v +
|
|
2284
|
-
predLower: v -
|
|
2285
|
-
predUpper: v +
|
|
3029
|
+
lower: v - meanHalfWidth,
|
|
3030
|
+
upper: v + meanHalfWidth,
|
|
3031
|
+
predLower: v - predHalfWidth,
|
|
3032
|
+
predUpper: v + predHalfWidth
|
|
2286
3033
|
});
|
|
2287
3034
|
}
|
|
2288
3035
|
return out;
|
|
@@ -2292,13 +3039,15 @@
|
|
|
2292
3039
|
const px = i === N - 1 ? max : min + step * i;
|
|
2293
3040
|
const yh = predictSingle(px);
|
|
2294
3041
|
const errs = stdErrorsAt(px, comps);
|
|
3042
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3043
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2295
3044
|
out.push({
|
|
2296
3045
|
x: px,
|
|
2297
3046
|
mean: yh,
|
|
2298
|
-
lower: yh -
|
|
2299
|
-
upper: yh +
|
|
2300
|
-
predLower: yh -
|
|
2301
|
-
predUpper: yh +
|
|
3047
|
+
lower: yh - meanHalfWidth,
|
|
3048
|
+
upper: yh + meanHalfWidth,
|
|
3049
|
+
predLower: yh - predHalfWidth,
|
|
3050
|
+
predUpper: yh + predHalfWidth
|
|
2302
3051
|
});
|
|
2303
3052
|
}
|
|
2304
3053
|
return out;
|
|
@@ -2307,106 +3056,30 @@
|
|
|
2307
3056
|
predict,
|
|
2308
3057
|
evaluate: predict,
|
|
2309
3058
|
evaluateGrid,
|
|
2310
|
-
confidenceInterval
|
|
3059
|
+
confidenceInterval,
|
|
3060
|
+
// 节点值按 x 排序,重复观测仍保持其统计权重。
|
|
3061
|
+
fitted: pts.map((p, i) => ({ x: p.x, y: fits[i] }))
|
|
2311
3062
|
};
|
|
2312
3063
|
}
|
|
2313
3064
|
|
|
2314
|
-
function solveLinearSystem(A, b) {
|
|
2315
|
-
const n = b.length;
|
|
2316
|
-
const M = new Array(n);
|
|
2317
|
-
for (let i = 0; i < n; i++) {
|
|
2318
|
-
M[i] = A[i].slice();
|
|
2319
|
-
M[i].push(b[i]);
|
|
2320
|
-
}
|
|
2321
|
-
for (let k = 0; k < n; k++) {
|
|
2322
|
-
let maxRow = k;
|
|
2323
|
-
let maxVal = Math.abs(M[k][k]);
|
|
2324
|
-
for (let i = k + 1; i < n; i++) {
|
|
2325
|
-
const v = Math.abs(M[i][k]);
|
|
2326
|
-
if (v > maxVal) {
|
|
2327
|
-
maxVal = v;
|
|
2328
|
-
maxRow = i;
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
if (maxRow !== k) {
|
|
2332
|
-
const tmp = M[k];
|
|
2333
|
-
M[k] = M[maxRow];
|
|
2334
|
-
M[maxRow] = tmp;
|
|
2335
|
-
}
|
|
2336
|
-
if (Math.abs(M[k][k]) < 1e-12) {
|
|
2337
|
-
const res = new Array(n).fill(0);
|
|
2338
|
-
return res;
|
|
2339
|
-
}
|
|
2340
|
-
for (let j = k + 1; j <= n; j++) {
|
|
2341
|
-
M[k][j] = M[k][j] / M[k][k];
|
|
2342
|
-
}
|
|
2343
|
-
M[k][k] = 1;
|
|
2344
|
-
for (let i = 0; i < n; i++) {
|
|
2345
|
-
if (i === k) {
|
|
2346
|
-
continue;
|
|
2347
|
-
}
|
|
2348
|
-
const factor = M[i][k];
|
|
2349
|
-
if (factor === 0) {
|
|
2350
|
-
continue;
|
|
2351
|
-
}
|
|
2352
|
-
for (let j = k + 1; j <= n; j++) {
|
|
2353
|
-
M[i][j] -= factor * M[k][j];
|
|
2354
|
-
}
|
|
2355
|
-
M[i][k] = 0;
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
const x = new Array(n);
|
|
2359
|
-
for (let i = 0; i < n; i++) {
|
|
2360
|
-
x[i] = M[i][n];
|
|
2361
|
-
}
|
|
2362
|
-
return x;
|
|
2363
|
-
}
|
|
2364
3065
|
function regressionPolynomial(data, x = d => d.x, y = d => d.y, options = {}) {
|
|
2365
3066
|
var _a, _b;
|
|
2366
|
-
|
|
2367
|
-
if (degree < 0) {
|
|
2368
|
-
degree
|
|
3067
|
+
const degree = (_a = options.degree) !== null && _a !== void 0 ? _a : 0;
|
|
3068
|
+
if (!Number.isInteger(degree) || degree < 0) {
|
|
3069
|
+
throw new RangeError('Polynomial degree must be an nonnegative integer');
|
|
2369
3070
|
}
|
|
2370
3071
|
const alpha = (_b = options.alpha) !== null && _b !== void 0 ? _b : 0.05;
|
|
2371
|
-
const
|
|
2372
|
-
const
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
for (let k = 0; k < sums.length; k++) {
|
|
2376
|
-
sums[k] += xp;
|
|
2377
|
-
xp *= dx;
|
|
2378
|
-
}
|
|
2379
|
-
});
|
|
2380
|
-
const A = new Array(m);
|
|
2381
|
-
for (let i = 0; i < m; i++) {
|
|
2382
|
-
A[i] = new Array(m).fill(0);
|
|
2383
|
-
for (let j = 0; j < m; j++) {
|
|
2384
|
-
A[i][j] = sums[i + j];
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
const B = new Array(m).fill(0);
|
|
2388
|
-
visitPoints(data, x, y, (dx, dy) => {
|
|
2389
|
-
let xp = 1;
|
|
2390
|
-
for (let k = 0; k < m; k++) {
|
|
2391
|
-
B[k] += dy * xp;
|
|
2392
|
-
xp *= dx;
|
|
2393
|
-
}
|
|
2394
|
-
});
|
|
2395
|
-
const coef = solveLinearSystem(A, B);
|
|
2396
|
-
const predict = (xx) => {
|
|
2397
|
-
let xp = 1;
|
|
2398
|
-
let v = 0;
|
|
2399
|
-
for (let k = 0; k < coef.length; k++) {
|
|
2400
|
-
v += coef[k] * xp;
|
|
2401
|
-
xp *= xx;
|
|
2402
|
-
}
|
|
2403
|
-
return v;
|
|
2404
|
-
};
|
|
3072
|
+
const points = regressionPoints(data, x, y);
|
|
3073
|
+
const model = fitRegression(points, degree);
|
|
3074
|
+
const coef = regressionCoefficients(model);
|
|
3075
|
+
const predict = (xx) => regressionPrediction(model, xx);
|
|
2405
3076
|
return {
|
|
2406
3077
|
degree,
|
|
3078
|
+
rank: model.rank,
|
|
2407
3079
|
coef,
|
|
2408
3080
|
predict,
|
|
2409
3081
|
rSquared: rSquared(data, x, y, (() => {
|
|
3082
|
+
// compute mean y
|
|
2410
3083
|
let sum = 0;
|
|
2411
3084
|
let cnt = 0;
|
|
2412
3085
|
visitPoints(data, x, y, (_dx, dy) => {
|
|
@@ -2416,38 +3089,13 @@
|
|
|
2416
3089
|
return cnt === 0 ? 0 : sum / cnt;
|
|
2417
3090
|
})(), predict),
|
|
2418
3091
|
evaluateGrid(N) {
|
|
2419
|
-
const out = [];
|
|
2420
|
-
if (N <= 0) {
|
|
2421
|
-
return out;
|
|
2422
|
-
}
|
|
2423
3092
|
let min = Infinity;
|
|
2424
3093
|
let max = -Infinity;
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
min = dx;
|
|
2429
|
-
}
|
|
2430
|
-
if (dx > max) {
|
|
2431
|
-
max = dx;
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
3094
|
+
points.forEach(p => {
|
|
3095
|
+
min = Math.min(min, p.x);
|
|
3096
|
+
max = Math.max(max, p.x);
|
|
2434
3097
|
});
|
|
2435
|
-
|
|
2436
|
-
return out;
|
|
2437
|
-
}
|
|
2438
|
-
if (min === max) {
|
|
2439
|
-
const v = predict(min);
|
|
2440
|
-
for (let i = 0; i < N; i++) {
|
|
2441
|
-
out.push({ x: min, y: v });
|
|
2442
|
-
}
|
|
2443
|
-
return out;
|
|
2444
|
-
}
|
|
2445
|
-
const step = (max - min) / (N - 1);
|
|
2446
|
-
for (let i = 0; i < N; i++) {
|
|
2447
|
-
const px = i === N - 1 ? max : min + step * i;
|
|
2448
|
-
out.push({ x: px, y: predict(px) });
|
|
2449
|
-
}
|
|
2450
|
-
return out;
|
|
3098
|
+
return regressionGrid(min, max, N, predict);
|
|
2451
3099
|
},
|
|
2452
3100
|
confidenceInterval(N = 50) {
|
|
2453
3101
|
const out = [];
|
|
@@ -2458,18 +3106,20 @@
|
|
|
2458
3106
|
if (comps.n === 0) {
|
|
2459
3107
|
return out;
|
|
2460
3108
|
}
|
|
2461
|
-
const z =
|
|
3109
|
+
const z = confidenceCriticalValue(alpha);
|
|
2462
3110
|
if (comps.min === comps.max) {
|
|
2463
3111
|
const v = predict(comps.min);
|
|
2464
3112
|
const errs = stdErrorsAt(comps.min, comps);
|
|
3113
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3114
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2465
3115
|
for (let i = 0; i < N; i++) {
|
|
2466
3116
|
out.push({
|
|
2467
3117
|
x: comps.min,
|
|
2468
3118
|
mean: v,
|
|
2469
|
-
lower: v -
|
|
2470
|
-
upper: v +
|
|
2471
|
-
predLower: v -
|
|
2472
|
-
predUpper: v +
|
|
3119
|
+
lower: v - meanHalfWidth,
|
|
3120
|
+
upper: v + meanHalfWidth,
|
|
3121
|
+
predLower: v - predHalfWidth,
|
|
3122
|
+
predUpper: v + predHalfWidth
|
|
2473
3123
|
});
|
|
2474
3124
|
}
|
|
2475
3125
|
return out;
|
|
@@ -2479,13 +3129,15 @@
|
|
|
2479
3129
|
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
2480
3130
|
const yh = predict(px);
|
|
2481
3131
|
const errs = stdErrorsAt(px, comps);
|
|
3132
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3133
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2482
3134
|
out.push({
|
|
2483
3135
|
x: px,
|
|
2484
3136
|
mean: yh,
|
|
2485
|
-
lower: yh -
|
|
2486
|
-
upper: yh +
|
|
2487
|
-
predLower: yh -
|
|
2488
|
-
predUpper: yh +
|
|
3137
|
+
lower: yh - meanHalfWidth,
|
|
3138
|
+
upper: yh + meanHalfWidth,
|
|
3139
|
+
predLower: yh - predHalfWidth,
|
|
3140
|
+
predUpper: yh + predHalfWidth
|
|
2489
3141
|
});
|
|
2490
3142
|
}
|
|
2491
3143
|
return out;
|
|
@@ -2493,14 +3145,29 @@
|
|
|
2493
3145
|
};
|
|
2494
3146
|
}
|
|
2495
3147
|
|
|
3148
|
+
/**
|
|
3149
|
+
* Kernel Density Estimation (KDE) utilities
|
|
3150
|
+
*
|
|
3151
|
+
* Exports:
|
|
3152
|
+
* - Kernel functions: gaussian, epanechnikov
|
|
3153
|
+
* - bandwidth selectors: scott, silverman
|
|
3154
|
+
* - kde: main function to compute density estimates at provided points
|
|
3155
|
+
*/
|
|
3156
|
+
/** Gaussian kernel (standard normal) */
|
|
2496
3157
|
const gaussian = (u) => {
|
|
2497
3158
|
const invSqrt2Pi = 1 / Math.sqrt(2 * Math.PI);
|
|
2498
3159
|
return invSqrt2Pi * Math.exp(-0.5 * u * u);
|
|
2499
3160
|
};
|
|
3161
|
+
/** Epanechnikov kernel (compact support) */
|
|
2500
3162
|
const epanechnikov = (u) => {
|
|
2501
3163
|
const absu = Math.abs(u);
|
|
2502
3164
|
return absu <= 1 ? 0.75 * (1 - u * u) : 0;
|
|
2503
3165
|
};
|
|
3166
|
+
/**
|
|
3167
|
+
* Bandwidth selectors
|
|
3168
|
+
* - scott: h = n^{-1/(d+4)} * std
|
|
3169
|
+
* - silverman: h = ( (4/(d+2))^{1/(d+4)} ) * n^{-1/(d+4)} * std
|
|
3170
|
+
*/
|
|
2504
3171
|
function scott(n, std, dim = 1) {
|
|
2505
3172
|
if (n <= 0 || std === 0) {
|
|
2506
3173
|
return 0;
|
|
@@ -2514,6 +3181,9 @@
|
|
|
2514
3181
|
const factor = Math.pow(4 / (dim + 2), 1 / (dim + 4));
|
|
2515
3182
|
return factor * std * Math.pow(n, -1 / (dim + 4));
|
|
2516
3183
|
}
|
|
3184
|
+
/**
|
|
3185
|
+
* Compute standard deviation for numeric array
|
|
3186
|
+
*/
|
|
2517
3187
|
function std(values) {
|
|
2518
3188
|
const n = values.length;
|
|
2519
3189
|
if (n === 0) {
|
|
@@ -2531,6 +3201,10 @@
|
|
|
2531
3201
|
}
|
|
2532
3202
|
return Math.sqrt(s / n);
|
|
2533
3203
|
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Factory: create a KDE evaluator for given data and options.
|
|
3206
|
+
* Usage: const model = kde(data, options); model.evaluate(x) or model.evaluate([x1,x2])
|
|
3207
|
+
*/
|
|
2534
3208
|
function kde(data, options = {}) {
|
|
2535
3209
|
const n = data.length;
|
|
2536
3210
|
const kernel = options.kernel || gaussian;
|
|
@@ -2545,6 +3219,7 @@
|
|
|
2545
3219
|
h = scott(n, sd, 1);
|
|
2546
3220
|
}
|
|
2547
3221
|
}
|
|
3222
|
+
// if still zero (constant data), evaluator returns zeros
|
|
2548
3223
|
if (!h || h <= 0) {
|
|
2549
3224
|
const zerosEval = (x) => {
|
|
2550
3225
|
if (Array.isArray(x)) {
|
|
@@ -2561,6 +3236,7 @@
|
|
|
2561
3236
|
if (N <= 0) {
|
|
2562
3237
|
return out;
|
|
2563
3238
|
}
|
|
3239
|
+
// compute data min (use data range if available)
|
|
2564
3240
|
let min = Infinity;
|
|
2565
3241
|
let max = -Infinity;
|
|
2566
3242
|
for (let j = 0; j < n; j++) {
|
|
@@ -2617,6 +3293,7 @@
|
|
|
2617
3293
|
if (N <= 0) {
|
|
2618
3294
|
return out;
|
|
2619
3295
|
}
|
|
3296
|
+
// compute data range
|
|
2620
3297
|
let min = Infinity;
|
|
2621
3298
|
let max = -Infinity;
|
|
2622
3299
|
for (let i = 0; i < n; i++) {
|
|
@@ -2632,6 +3309,7 @@
|
|
|
2632
3309
|
return out;
|
|
2633
3310
|
}
|
|
2634
3311
|
if (min === max) {
|
|
3312
|
+
// single point repeated
|
|
2635
3313
|
for (let i = 0; i < N; i++) {
|
|
2636
3314
|
out.push({ x: min, y: evalPoint(min) });
|
|
2637
3315
|
}
|
|
@@ -2647,6 +3325,14 @@
|
|
|
2647
3325
|
};
|
|
2648
3326
|
}
|
|
2649
3327
|
|
|
3328
|
+
/**
|
|
3329
|
+
* Empirical Cumulative Distribution Function (ECDF)
|
|
3330
|
+
* Returns a factory that builds an evaluator over provided samples.
|
|
3331
|
+
* The evaluator exposes:
|
|
3332
|
+
* - evaluate(x:number) => proportion of samples <= x
|
|
3333
|
+
* - evaluate(xs:number[]) => number[]
|
|
3334
|
+
* - evaluateGrid(N:number) => { points: number[], cdf: number[] } over sample range
|
|
3335
|
+
*/
|
|
2650
3336
|
function ecdf(data) {
|
|
2651
3337
|
const n = data.length;
|
|
2652
3338
|
const sorted = data.slice().sort((a, b) => a - b);
|
|
@@ -2654,8 +3340,9 @@
|
|
|
2654
3340
|
if (n === 0) {
|
|
2655
3341
|
return 0;
|
|
2656
3342
|
}
|
|
3343
|
+
// count of values <= x (upper bound index)
|
|
2657
3344
|
let lo = 0;
|
|
2658
|
-
let hi = n;
|
|
3345
|
+
let hi = n; // exclusive
|
|
2659
3346
|
while (lo < hi) {
|
|
2660
3347
|
const mid = (lo + hi) >>> 1;
|
|
2661
3348
|
if (sorted[mid] <= x) {
|
|
@@ -2707,8 +3394,241 @@
|
|
|
2707
3394
|
};
|
|
2708
3395
|
}
|
|
2709
3396
|
|
|
3397
|
+
function pearson(x, y) {
|
|
3398
|
+
let sx = 0;
|
|
3399
|
+
let sy = 0;
|
|
3400
|
+
for (let i = 0; i < x.length; i++) {
|
|
3401
|
+
sx = Math.max(sx, Math.abs(x[i]));
|
|
3402
|
+
sy = Math.max(sy, Math.abs(y[i]));
|
|
3403
|
+
}
|
|
3404
|
+
if (!sx || !sy) {
|
|
3405
|
+
return undefined;
|
|
3406
|
+
}
|
|
3407
|
+
let mx = 0;
|
|
3408
|
+
let my = 0;
|
|
3409
|
+
let cx = 0;
|
|
3410
|
+
let cy = 0;
|
|
3411
|
+
for (let i = 0; i < x.length; i++) {
|
|
3412
|
+
const vx = x[i] / sx - cx;
|
|
3413
|
+
const vy = y[i] / sy - cy;
|
|
3414
|
+
const tx = mx + vx;
|
|
3415
|
+
const ty = my + vy;
|
|
3416
|
+
cx = tx - mx - vx;
|
|
3417
|
+
cy = ty - my - vy;
|
|
3418
|
+
mx = tx;
|
|
3419
|
+
my = ty;
|
|
3420
|
+
}
|
|
3421
|
+
mx /= x.length;
|
|
3422
|
+
my /= x.length;
|
|
3423
|
+
let xx = 0;
|
|
3424
|
+
let yy = 0;
|
|
3425
|
+
let xy = 0;
|
|
3426
|
+
for (let i = 0; i < x.length; i++) {
|
|
3427
|
+
const dx = x[i] / sx - mx;
|
|
3428
|
+
const dy = y[i] / sy - my;
|
|
3429
|
+
xx += dx * dx;
|
|
3430
|
+
yy += dy * dy;
|
|
3431
|
+
xy += dx * dy;
|
|
3432
|
+
}
|
|
3433
|
+
if (xx === 0 || yy === 0) {
|
|
3434
|
+
return undefined;
|
|
3435
|
+
}
|
|
3436
|
+
const result = xy / Math.sqrt(xx * yy);
|
|
3437
|
+
// 仅修正浮点舍入越过理论端点的几个 ulp。
|
|
3438
|
+
if (Math.abs(result) > 1 && Math.abs(result) <= 1 + 8 * Number.EPSILON) {
|
|
3439
|
+
return Math.sign(result);
|
|
3440
|
+
}
|
|
3441
|
+
return result;
|
|
3442
|
+
}
|
|
3443
|
+
function ranks(values) {
|
|
3444
|
+
const order = values.map((value, index) => ({ value, index })).sort((a, b) => a.value - b.value);
|
|
3445
|
+
const result = new Array(values.length);
|
|
3446
|
+
for (let start = 0; start < order.length;) {
|
|
3447
|
+
let end = start + 1;
|
|
3448
|
+
while (end < order.length && order[end].value === order[start].value) {
|
|
3449
|
+
end++;
|
|
3450
|
+
}
|
|
3451
|
+
const rank = (start + end + 1) / 2;
|
|
3452
|
+
for (let i = start; i < end; i++) {
|
|
3453
|
+
result[order[i].index] = rank;
|
|
3454
|
+
}
|
|
3455
|
+
start = end;
|
|
3456
|
+
}
|
|
3457
|
+
return result;
|
|
3458
|
+
}
|
|
3459
|
+
function kendall(x, y) {
|
|
3460
|
+
const pairs = x.map((value, index) => ({ x: value, y: y[index] })).sort((a, b) => a.x - b.x || a.y - b.y);
|
|
3461
|
+
const sortedY = [...y].sort((a, b) => a - b);
|
|
3462
|
+
const yRanks = new Map();
|
|
3463
|
+
let tiesY = 0;
|
|
3464
|
+
for (let start = 0; start < sortedY.length;) {
|
|
3465
|
+
let end = start + 1;
|
|
3466
|
+
while (end < sortedY.length && sortedY[end] === sortedY[start]) {
|
|
3467
|
+
end++;
|
|
3468
|
+
}
|
|
3469
|
+
tiesY += ((end - start) * (end - start - 1)) / 2;
|
|
3470
|
+
yRanks.set(sortedY[start], yRanks.size + 1);
|
|
3471
|
+
start = end;
|
|
3472
|
+
}
|
|
3473
|
+
const tree = new Float64Array(yRanks.size + 1);
|
|
3474
|
+
let tiesX = 0;
|
|
3475
|
+
let tiesBoth = 0;
|
|
3476
|
+
let discordant = 0;
|
|
3477
|
+
for (let start = 0; start < pairs.length;) {
|
|
3478
|
+
let end = start + 1;
|
|
3479
|
+
while (end < pairs.length && pairs[end].x === pairs[start].x) {
|
|
3480
|
+
end++;
|
|
3481
|
+
}
|
|
3482
|
+
tiesX += ((end - start) * (end - start - 1)) / 2;
|
|
3483
|
+
for (let i = start; i < end;) {
|
|
3484
|
+
let j = i + 1;
|
|
3485
|
+
while (j < end && pairs[j].y === pairs[i].y) {
|
|
3486
|
+
j++;
|
|
3487
|
+
}
|
|
3488
|
+
tiesBoth += ((j - i) * (j - i - 1)) / 2;
|
|
3489
|
+
i = j;
|
|
3490
|
+
}
|
|
3491
|
+
// 同 x 组先查询再更新,避免将 x ties 算成逆序。
|
|
3492
|
+
for (let i = start; i < end; i++) {
|
|
3493
|
+
let lessOrEqual = 0;
|
|
3494
|
+
for (let rank = yRanks.get(pairs[i].y); rank > 0; rank -= rank & -rank) {
|
|
3495
|
+
lessOrEqual += tree[rank];
|
|
3496
|
+
}
|
|
3497
|
+
discordant += start - lessOrEqual;
|
|
3498
|
+
}
|
|
3499
|
+
for (let i = start; i < end; i++) {
|
|
3500
|
+
for (let rank = yRanks.get(pairs[i].y); rank < tree.length; rank += rank & -rank) {
|
|
3501
|
+
tree[rank]++;
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
start = end;
|
|
3505
|
+
}
|
|
3506
|
+
const total = (x.length * (x.length - 1)) / 2;
|
|
3507
|
+
if (total === tiesX || total === tiesY) {
|
|
3508
|
+
return undefined;
|
|
3509
|
+
}
|
|
3510
|
+
return (total - tiesX - tiesY + tiesBoth - 2 * discordant) / Math.sqrt((total - tiesX) * (total - tiesY));
|
|
3511
|
+
}
|
|
3512
|
+
/** 等长有限样本的相关系数;常量/不足两条返回 undefined,不修改数组。 */
|
|
3513
|
+
function correlation(x, y, method = 'pearson') {
|
|
3514
|
+
if (x.length !== y.length ||
|
|
3515
|
+
!['pearson', 'spearman', 'kendall'].includes(method) ||
|
|
3516
|
+
x.some(value => !Number.isFinite(value)) ||
|
|
3517
|
+
y.some(value => !Number.isFinite(value))) {
|
|
3518
|
+
throw new RangeError('correlation requires equal finite arrays and a supported method');
|
|
3519
|
+
}
|
|
3520
|
+
if (x.length < 2) {
|
|
3521
|
+
return undefined;
|
|
3522
|
+
}
|
|
3523
|
+
return method === 'kendall' ? kendall(x, y) : method === 'spearman' ? pearson(ranks(x), ranks(y)) : pearson(x, y);
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
/** 有限数值的均值;空输入没有均值,不修改输入。 */
|
|
3527
|
+
function mean(values) {
|
|
3528
|
+
if (!values.length) {
|
|
3529
|
+
return undefined;
|
|
3530
|
+
}
|
|
3531
|
+
let scale = 0;
|
|
3532
|
+
for (const value of values) {
|
|
3533
|
+
if (!Number.isFinite(value)) {
|
|
3534
|
+
throw new RangeError('mean requires finite numbers');
|
|
3535
|
+
}
|
|
3536
|
+
scale = Math.max(scale, Math.abs(value));
|
|
3537
|
+
}
|
|
3538
|
+
if (scale === 0) {
|
|
3539
|
+
return 0;
|
|
3540
|
+
}
|
|
3541
|
+
let sum = 0;
|
|
3542
|
+
let correction = 0;
|
|
3543
|
+
for (const value of values) {
|
|
3544
|
+
const next = value / scale;
|
|
3545
|
+
const total = sum + next;
|
|
3546
|
+
// Neumaier compensation retains small contributions even when larger values cancel.
|
|
3547
|
+
correction += Math.abs(sum) >= Math.abs(next) ? sum - total + next : next - total + sum;
|
|
3548
|
+
sum = total;
|
|
3549
|
+
}
|
|
3550
|
+
return ((sum + correction) / values.length) * scale;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
/** 有放回重采样;回调不得修改或持有复用的只读样本缓冲区。 */
|
|
3554
|
+
function bootstrapEstimate(values, estimator, options) {
|
|
3555
|
+
if (!values.length ||
|
|
3556
|
+
values.some(value => !Number.isFinite(value)) ||
|
|
3557
|
+
!Number.isInteger(options.resamples) ||
|
|
3558
|
+
options.resamples < 1 ||
|
|
3559
|
+
!Number.isInteger(options.seed) ||
|
|
3560
|
+
options.seed < 0 ||
|
|
3561
|
+
options.seed > 0xffffffff) {
|
|
3562
|
+
throw new RangeError('bootstrap requires finite observations, positive resamples and a uint32 seed');
|
|
3563
|
+
}
|
|
3564
|
+
const random = randomLCG(options.seed);
|
|
3565
|
+
const sample = new Array(values.length);
|
|
3566
|
+
const estimates = new Array(options.resamples);
|
|
3567
|
+
for (let b = 0; b < options.resamples; b++) {
|
|
3568
|
+
for (let i = 0; i < sample.length; i++) {
|
|
3569
|
+
sample[i] = values[Math.floor(random() * values.length)];
|
|
3570
|
+
}
|
|
3571
|
+
const estimate = estimator(sample);
|
|
3572
|
+
if (!Number.isFinite(estimate)) {
|
|
3573
|
+
throw new RangeError('bootstrap estimator must return a finite number');
|
|
3574
|
+
}
|
|
3575
|
+
estimates[b] = estimate;
|
|
3576
|
+
}
|
|
3577
|
+
return estimates;
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
/** 成对 percentile bootstrap;每次重采样仅拟合一次,固定原始坐标基底。 */
|
|
3581
|
+
function regressionBootstrap(data, options) {
|
|
3582
|
+
var _a;
|
|
3583
|
+
const degree = options.method === 'linear' ? 1 : (_a = options.degree) !== null && _a !== void 0 ? _a : NaN;
|
|
3584
|
+
if ((options.method !== 'linear' && options.method !== 'polynomial') ||
|
|
3585
|
+
!Number.isInteger(degree) ||
|
|
3586
|
+
degree < 1 ||
|
|
3587
|
+
!data.length ||
|
|
3588
|
+
data.some(p => !Number.isFinite(p.x) || !Number.isFinite(p.y)) ||
|
|
3589
|
+
options.grid.some(x => !Number.isFinite(x)) ||
|
|
3590
|
+
!(options.level > 0 && options.level < 1) ||
|
|
3591
|
+
!Number.isInteger(options.resamples) ||
|
|
3592
|
+
options.resamples < 1 ||
|
|
3593
|
+
!Number.isInteger(options.seed) ||
|
|
3594
|
+
options.seed < 0 ||
|
|
3595
|
+
options.seed > 0xffffffff) {
|
|
3596
|
+
throw new RangeError('Invalid paired regression bootstrap inputs');
|
|
3597
|
+
}
|
|
3598
|
+
if (data.length <= degree + 1) {
|
|
3599
|
+
throw new RangeError('Regression bootstrap requires positive residual degrees of freedom');
|
|
3600
|
+
}
|
|
3601
|
+
const sorted = data.slice().sort((a, b) => a.x - b.x || a.y - b.y);
|
|
3602
|
+
const basis = regressionBasis(sorted);
|
|
3603
|
+
if (fitRegression(sorted, degree, basis).rank < degree + 1) {
|
|
3604
|
+
throw new RangeError('Original regression design is rank deficient');
|
|
3605
|
+
}
|
|
3606
|
+
const random = randomLCG(options.seed);
|
|
3607
|
+
const sample = new Array(sorted.length);
|
|
3608
|
+
const values = options.grid.map(() => new Array(options.resamples));
|
|
3609
|
+
for (let b = 0; b < options.resamples; b++) {
|
|
3610
|
+
for (let i = 0; i < sample.length; i++) {
|
|
3611
|
+
sample[i] = sorted[Math.floor(random() * sorted.length)];
|
|
3612
|
+
}
|
|
3613
|
+
const model = fitRegression(sample, degree, basis);
|
|
3614
|
+
for (let j = 0; j < options.grid.length; j++) {
|
|
3615
|
+
const value = regressionPrediction(model, options.grid[j]);
|
|
3616
|
+
if (!Number.isFinite(value)) {
|
|
3617
|
+
throw new RangeError('Regression bootstrap prediction is not finite');
|
|
3618
|
+
}
|
|
3619
|
+
values[j][b] = value;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
const tail = (1 - options.level) / 2;
|
|
3623
|
+
return options.grid.map((x, j) => {
|
|
3624
|
+
values[j].sort((a, b) => a - b);
|
|
3625
|
+
return { x, lower: quantileSorted(values[j], tail), upper: quantileSorted(values[j], 1 - tail) };
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
|
|
2710
3629
|
class HashValue {
|
|
2711
3630
|
}
|
|
3631
|
+
// 基于 array 和 object的 HashTable
|
|
2712
3632
|
class HashTable {
|
|
2713
3633
|
constructor() {
|
|
2714
3634
|
this.items = {};
|
|
@@ -2744,6 +3664,10 @@
|
|
|
2744
3664
|
this.resetIndex();
|
|
2745
3665
|
}
|
|
2746
3666
|
}
|
|
3667
|
+
/**
|
|
3668
|
+
* 不包含当前index
|
|
3669
|
+
* @param index
|
|
3670
|
+
*/
|
|
2747
3671
|
delFrom(index) {
|
|
2748
3672
|
for (let i = index + 1; i < this.count(); i++) {
|
|
2749
3673
|
const key = this.itemList[i].key;
|
|
@@ -2787,6 +3711,7 @@
|
|
|
2787
3711
|
getKeyByIndex(index) {
|
|
2788
3712
|
return this.itemList[index].key;
|
|
2789
3713
|
}
|
|
3714
|
+
//遍历 扩展
|
|
2790
3715
|
foreach(callback) {
|
|
2791
3716
|
for (const key in this.items) {
|
|
2792
3717
|
const returnVal = callback(key, this.items[key].value);
|
|
@@ -2813,12 +3738,14 @@
|
|
|
2813
3738
|
}
|
|
2814
3739
|
return false;
|
|
2815
3740
|
}
|
|
3741
|
+
//获取index
|
|
2816
3742
|
indexOf(key) {
|
|
2817
3743
|
if (this.has(key)) {
|
|
2818
3744
|
return this.items[key].index;
|
|
2819
3745
|
}
|
|
2820
3746
|
return -1;
|
|
2821
3747
|
}
|
|
3748
|
+
//插入
|
|
2822
3749
|
insertAt(index, value, key) {
|
|
2823
3750
|
const hashV = new HashValue();
|
|
2824
3751
|
hashV.index = index;
|
|
@@ -2851,6 +3778,7 @@
|
|
|
2851
3778
|
return returnArr;
|
|
2852
3779
|
}
|
|
2853
3780
|
toImmutableMap() {
|
|
3781
|
+
// do nothing
|
|
2854
3782
|
}
|
|
2855
3783
|
}
|
|
2856
3784
|
|
|
@@ -2877,6 +3805,15 @@
|
|
|
2877
3805
|
function asin(x) {
|
|
2878
3806
|
return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x);
|
|
2879
3807
|
}
|
|
3808
|
+
/**
|
|
3809
|
+
* 根据比例获直线上的点
|
|
3810
|
+
* @param {number} x1 起始点 x
|
|
3811
|
+
* @param {number} y1 起始点 y
|
|
3812
|
+
* @param {number} x2 结束点 x
|
|
3813
|
+
* @param {number} y2 结束点 y
|
|
3814
|
+
* @param {number} t 指定比例
|
|
3815
|
+
* @return {object} 包含 x, y 的点
|
|
3816
|
+
*/
|
|
2880
3817
|
function pointAt(x1, y1, x2, y2, t) {
|
|
2881
3818
|
let x = x2;
|
|
2882
3819
|
let y = y2;
|
|
@@ -2892,6 +3829,7 @@
|
|
|
2892
3829
|
};
|
|
2893
3830
|
}
|
|
2894
3831
|
function lengthFromPointToLine(point, point1, point2) {
|
|
3832
|
+
// 面积
|
|
2895
3833
|
const dir1X = point2.x - point1.x;
|
|
2896
3834
|
const dir1Y = point2.y - point1.y;
|
|
2897
3835
|
const dir2X = point.x - point1.x;
|
|
@@ -2936,6 +3874,9 @@
|
|
|
2936
3874
|
|
|
2937
3875
|
class Point {
|
|
2938
3876
|
constructor(x = 0, y = 0, x1, y1) {
|
|
3877
|
+
/**
|
|
3878
|
+
* 面积图的下点
|
|
3879
|
+
*/
|
|
2939
3880
|
this.x = 0;
|
|
2940
3881
|
this.y = 0;
|
|
2941
3882
|
this.x = x;
|
|
@@ -2988,6 +3929,7 @@
|
|
|
2988
3929
|
}
|
|
2989
3930
|
}
|
|
2990
3931
|
class PointService {
|
|
3932
|
+
/* distance */
|
|
2991
3933
|
static distancePP(p1, p2) {
|
|
2992
3934
|
return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
|
|
2993
3935
|
}
|
|
@@ -2997,6 +3939,7 @@
|
|
|
2997
3939
|
static distancePN(point, x, y) {
|
|
2998
3940
|
return sqrt(pow(x - point.x, 2) + pow(y - point.y, 2));
|
|
2999
3941
|
}
|
|
3942
|
+
/* point at */
|
|
3000
3943
|
static pointAtPP(p1, p2, t) {
|
|
3001
3944
|
return new Point((p2.x - p1.x) * t + p1.x, (p2.y - p1.y) * t + p1.y);
|
|
3002
3945
|
}
|
|
@@ -3045,12 +3988,30 @@
|
|
|
3045
3988
|
return new ImageData(flipClampBuffer, width, height);
|
|
3046
3989
|
};
|
|
3047
3990
|
|
|
3991
|
+
/**
|
|
3992
|
+
* 处理角度相关的数据计算
|
|
3993
|
+
*/
|
|
3994
|
+
/**
|
|
3995
|
+
* degree -> radian
|
|
3996
|
+
* @param degree
|
|
3997
|
+
* @returns radian
|
|
3998
|
+
*/
|
|
3048
3999
|
function degreeToRadian(degree) {
|
|
3049
4000
|
return degree * (Math.PI / 180);
|
|
3050
4001
|
}
|
|
4002
|
+
/**
|
|
4003
|
+
* radian -> degree
|
|
4004
|
+
* @param radian
|
|
4005
|
+
* @returns
|
|
4006
|
+
*/
|
|
3051
4007
|
function radianToDegree(radian) {
|
|
3052
4008
|
return (radian * 180) / Math.PI;
|
|
3053
4009
|
}
|
|
4010
|
+
/**
|
|
4011
|
+
* 对弧度进行格式化
|
|
4012
|
+
* @param a 传入的弧度
|
|
4013
|
+
* @returns
|
|
4014
|
+
*/
|
|
3054
4015
|
const clampRadian = (angle = 0) => {
|
|
3055
4016
|
if (angle < 0) {
|
|
3056
4017
|
while (angle < -tau) {
|
|
@@ -3064,14 +4025,28 @@
|
|
|
3064
4025
|
}
|
|
3065
4026
|
return angle;
|
|
3066
4027
|
};
|
|
4028
|
+
// alias of clampRadian
|
|
3067
4029
|
const clampAngleByRadian = clampRadian;
|
|
4030
|
+
/**
|
|
4031
|
+
* 对角度进行格式化
|
|
4032
|
+
* @param a 角度
|
|
4033
|
+
* @returns
|
|
4034
|
+
*/
|
|
3068
4035
|
const clampDegree = (a = 0) => {
|
|
3069
4036
|
if (a > 360 || a < -360) {
|
|
3070
4037
|
return a % 360;
|
|
3071
4038
|
}
|
|
3072
4039
|
return a;
|
|
3073
4040
|
};
|
|
4041
|
+
// alias of clampDegree
|
|
3074
4042
|
const clampAngleByDegree = clampDegree;
|
|
4043
|
+
/**
|
|
4044
|
+
* 根据弧度及半径计算极坐标系下的坐标点
|
|
4045
|
+
* @param centerX 圆心坐标
|
|
4046
|
+
* @param radius 半径
|
|
4047
|
+
* @param angleInRadian 弧度
|
|
4048
|
+
* @returns 返回笛卡尔坐标点
|
|
4049
|
+
*/
|
|
3075
4050
|
function polarToCartesian(center, radius, angleInRadian) {
|
|
3076
4051
|
if (!radius) {
|
|
3077
4052
|
return { x: center.x, y: center.y };
|
|
@@ -3081,6 +4056,14 @@
|
|
|
3081
4056
|
y: center.y + radius * Math.sin(angleInRadian)
|
|
3082
4057
|
};
|
|
3083
4058
|
}
|
|
4059
|
+
/**
|
|
4060
|
+
* 根据笛卡尔坐标系坐标计算极坐标坐标点
|
|
4061
|
+
* @param point
|
|
4062
|
+
* @param center 极坐标系中心点坐标
|
|
4063
|
+
* @param startAngle 极坐标系起始角度
|
|
4064
|
+
* @param endAngle 极坐标系结束角度
|
|
4065
|
+
* @returns 极坐标系坐标点
|
|
4066
|
+
*/
|
|
3084
4067
|
function cartesianToPolar(point, center = { x: 0, y: 0 }, startAngle = 0, endAngle = 2 * Math.PI) {
|
|
3085
4068
|
const { x, y } = point;
|
|
3086
4069
|
const { x: centerX, y: centerY } = center;
|
|
@@ -3111,9 +4094,19 @@
|
|
|
3111
4094
|
angle: radian
|
|
3112
4095
|
};
|
|
3113
4096
|
}
|
|
4097
|
+
/**
|
|
4098
|
+
* 根据点的笛卡尔坐标获取该点与圆心的连线同正 x 轴方向的夹角
|
|
4099
|
+
* @param center 圆心坐标
|
|
4100
|
+
* @param point 待求的点坐标
|
|
4101
|
+
* @returns 返回夹角对应的弧度值
|
|
4102
|
+
*/
|
|
3114
4103
|
function getAngleByPoint(center, point) {
|
|
3115
4104
|
return Math.atan2(point.y - center.y, point.x - center.x);
|
|
3116
4105
|
}
|
|
4106
|
+
/**
|
|
4107
|
+
* 角度标准化处理
|
|
4108
|
+
* @param angle 弧度角
|
|
4109
|
+
*/
|
|
3117
4110
|
function normalizeAngle(angle) {
|
|
3118
4111
|
while (angle < 0) {
|
|
3119
4112
|
angle += Math.PI * 2;
|
|
@@ -3123,6 +4116,14 @@
|
|
|
3123
4116
|
}
|
|
3124
4117
|
return angle;
|
|
3125
4118
|
}
|
|
4119
|
+
/**
|
|
4120
|
+
* 指定开始角度和结束角度,计算这个范围内的边界角度,
|
|
4121
|
+
* 即起始角度以及经过的东、南、西、北四个方向的角度
|
|
4122
|
+
* 计算这个角度,可以用于计算一个弧度的bounding box 等
|
|
4123
|
+
* @param startAngle 起始角度的弧度值
|
|
4124
|
+
* @param endAngle 结束角度的弧度值
|
|
4125
|
+
* @returns 边界角度数组
|
|
4126
|
+
*/
|
|
3126
4127
|
function findBoundaryAngles(startAngle, endAngle) {
|
|
3127
4128
|
const deltaAngle = Math.abs(endAngle - startAngle);
|
|
3128
4129
|
if (deltaAngle >= 2 * Math.PI || 2 * Math.PI - deltaAngle < 1e-6) {
|
|
@@ -3141,6 +4142,14 @@
|
|
|
3141
4142
|
}
|
|
3142
4143
|
return steps;
|
|
3143
4144
|
}
|
|
4145
|
+
/**
|
|
4146
|
+
* 计算指定范围内,指定中心的情况下,不超出边界的最大可用半径
|
|
4147
|
+
* @param rect 矩形的大小
|
|
4148
|
+
* @param center 中心点
|
|
4149
|
+
* @param startAngle 起始角度的弧度值
|
|
4150
|
+
* @param endAngle 结束角度的弧度值
|
|
4151
|
+
* @returns 最大半径
|
|
4152
|
+
*/
|
|
3144
4153
|
function calculateMaxRadius(rect, center, startAngle, endAngle) {
|
|
3145
4154
|
const { x, y } = center;
|
|
3146
4155
|
const steps = findBoundaryAngles(startAngle, endAngle);
|
|
@@ -3192,10 +4201,18 @@
|
|
|
3192
4201
|
return 1;
|
|
3193
4202
|
}
|
|
3194
4203
|
|
|
4204
|
+
/* 用于判断2d相交 */
|
|
3195
4205
|
function sub(out, v1, v2) {
|
|
3196
4206
|
out[0] = v1[0] - v2[0];
|
|
3197
4207
|
out[1] = v1[1] - v2[1];
|
|
3198
4208
|
}
|
|
4209
|
+
/**
|
|
4210
|
+
* 判断直线是否相交,投影法
|
|
4211
|
+
* @param left1
|
|
4212
|
+
* @param right1
|
|
4213
|
+
* @param left2
|
|
4214
|
+
* @param right2
|
|
4215
|
+
*/
|
|
3199
4216
|
function isIntersect(left1, right1, left2, right2) {
|
|
3200
4217
|
let min1 = left1[0];
|
|
3201
4218
|
let max1 = right1[0];
|
|
@@ -3222,6 +4239,15 @@
|
|
|
3222
4239
|
}
|
|
3223
4240
|
return true;
|
|
3224
4241
|
}
|
|
4242
|
+
/**
|
|
4243
|
+
* 获取直线交点
|
|
4244
|
+
* 不相交返回false,共线返回true,相交返回交点
|
|
4245
|
+
* https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
|
|
4246
|
+
* @param left1
|
|
4247
|
+
* @param right1
|
|
4248
|
+
* @param left2
|
|
4249
|
+
* @param right2
|
|
4250
|
+
*/
|
|
3225
4251
|
function getIntersectPoint(left1, right1, left2, right2) {
|
|
3226
4252
|
if (!isIntersect(left1, right1, left2, right2)) {
|
|
3227
4253
|
return false;
|
|
@@ -3231,9 +4257,16 @@
|
|
|
3231
4257
|
const tempVec = [0, 0];
|
|
3232
4258
|
sub(dir1, right1, left1);
|
|
3233
4259
|
sub(dir2, right2, left2);
|
|
4260
|
+
// 判断共线
|
|
3234
4261
|
if (fuzzyEqualVec(dir1, dir2)) {
|
|
3235
4262
|
return true;
|
|
3236
4263
|
}
|
|
4264
|
+
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
|
|
4265
|
+
// line1: left1 + dir1 * t
|
|
4266
|
+
// line2: left2 + dir2 * u
|
|
4267
|
+
// 当 left1 + dir1 * t = left2 + dir2 * u => (left1 + dir1 * t) x dir2 = (left2 + dir2 * u) x dir2
|
|
4268
|
+
// => dir1 x dir2 * t = (left2 - left1) x dir2 => t = (left2 - left1) x dir2 / (dir1 x dir2)
|
|
4269
|
+
// 直线不平行,dir1 x dir2 ≠ 0,当0 <= t <= 1时,可以求出交点
|
|
3237
4270
|
sub(tempVec, left2, left1);
|
|
3238
4271
|
const t = crossProduct(tempVec, dir2) / crossProduct(dir1, dir2);
|
|
3239
4272
|
if (t >= 0 && t <= 1) {
|
|
@@ -3241,6 +4274,13 @@
|
|
|
3241
4274
|
}
|
|
3242
4275
|
return false;
|
|
3243
4276
|
}
|
|
4277
|
+
/**
|
|
4278
|
+
* 获取两个rect的相交部分
|
|
4279
|
+
* 如果有bbox为null,返回null,如果不相交返回{x1: 0, y1: 0, x2: 0, y2: 0}
|
|
4280
|
+
* @param bbox1
|
|
4281
|
+
* @param bbox2
|
|
4282
|
+
* @param format
|
|
4283
|
+
*/
|
|
3244
4284
|
function getRectIntersect(bbox1, bbox2, format) {
|
|
3245
4285
|
if (bbox1 === null) {
|
|
3246
4286
|
return bbox2;
|
|
@@ -3285,19 +4325,35 @@
|
|
|
3285
4325
|
}
|
|
3286
4326
|
return { x11, x12, y11, y12, x21, x22, y21, y22 };
|
|
3287
4327
|
};
|
|
4328
|
+
/**
|
|
4329
|
+
* 矩形是否在另一个矩形内部
|
|
4330
|
+
* 返回InnerBBox
|
|
4331
|
+
* @param bbox1
|
|
4332
|
+
* @param bbox2
|
|
4333
|
+
* @param format
|
|
4334
|
+
*/
|
|
3288
4335
|
function rectInsideAnotherRect(bbox1, bbox2, format) {
|
|
3289
4336
|
if (!bbox1 || !bbox2) {
|
|
3290
4337
|
return exports.InnerBBox.NONE;
|
|
3291
4338
|
}
|
|
3292
4339
|
const { x11, x12, y11, y12, x21, x22, y21, y22 } = formatTwoBBox(bbox1, bbox2, format);
|
|
4340
|
+
// bbox1在bbox2内部
|
|
3293
4341
|
if (x11 > x21 && x12 < x22 && y11 > y21 && y12 < y22) {
|
|
3294
4342
|
return exports.InnerBBox.BBOX1;
|
|
3295
4343
|
}
|
|
4344
|
+
// bbox2在bbox1内部
|
|
3296
4345
|
if (x21 > x11 && x22 < x12 && y21 > y11 && y22 < y12) {
|
|
3297
4346
|
return exports.InnerBBox.BBOX2;
|
|
3298
4347
|
}
|
|
3299
4348
|
return exports.InnerBBox.NONE;
|
|
3300
4349
|
}
|
|
4350
|
+
/**
|
|
4351
|
+
* 两个矩形是否相交
|
|
4352
|
+
* 如果有矩形为null,判断为相交
|
|
4353
|
+
* @param bbox1
|
|
4354
|
+
* @param bbox2
|
|
4355
|
+
* @param format
|
|
4356
|
+
*/
|
|
3301
4357
|
function isRectIntersect(bbox1, bbox2, format) {
|
|
3302
4358
|
if (bbox1 && bbox2) {
|
|
3303
4359
|
if (!format) {
|
|
@@ -3314,6 +4370,12 @@
|
|
|
3314
4370
|
}
|
|
3315
4371
|
return true;
|
|
3316
4372
|
}
|
|
4373
|
+
/**
|
|
4374
|
+
* 点在box内部
|
|
4375
|
+
* 如果bbox为null返回true
|
|
4376
|
+
* @param point
|
|
4377
|
+
* @param bbox
|
|
4378
|
+
*/
|
|
3317
4379
|
function pointInRect(point, bbox, format) {
|
|
3318
4380
|
if (!bbox) {
|
|
3319
4381
|
return true;
|
|
@@ -3333,9 +4395,20 @@
|
|
|
3333
4395
|
}
|
|
3334
4396
|
return point.x >= x11 && point.x <= x12 && point.y >= y11 && point.y <= y12;
|
|
3335
4397
|
}
|
|
4398
|
+
// 参考https://github.com/francecil/leetcode/issues/1
|
|
4399
|
+
/**
|
|
4400
|
+
* 计算投影半径
|
|
4401
|
+
* @param {Array(Number)} checkAxis 检测轴 [cosθ,sinθ]
|
|
4402
|
+
* @param {Array} axis 目标轴 [x,y]
|
|
4403
|
+
*/
|
|
3336
4404
|
function getProjectionRadius(checkAxis, axis) {
|
|
3337
4405
|
return Math.abs(axis[0] * checkAxis[0] + axis[1] * checkAxis[1]);
|
|
3338
4406
|
}
|
|
4407
|
+
/**
|
|
4408
|
+
* 逆时针旋转矩阵(从正 x 轴开始,旋转到正 y 轴的方向)
|
|
4409
|
+
* [ cos(θ) -sin(θ) ]
|
|
4410
|
+
* [ sin(θ) cos(θ) ]
|
|
4411
|
+
*/
|
|
3339
4412
|
function rotatePoint({ x, y }, rad, origin = { x: 0, y: 0 }) {
|
|
3340
4413
|
return {
|
|
3341
4414
|
x: (x - origin.x) * Math.cos(rad) - (y - origin.y) * Math.sin(rad) + origin.x,
|
|
@@ -3348,6 +4421,10 @@
|
|
|
3348
4421
|
y: (box.y1 + box.y2) / 2
|
|
3349
4422
|
};
|
|
3350
4423
|
}
|
|
4424
|
+
/**
|
|
4425
|
+
* 转化为顶点坐标数组
|
|
4426
|
+
* @param {Object} box
|
|
4427
|
+
*/
|
|
3351
4428
|
function toRect(box, isDeg) {
|
|
3352
4429
|
const deg = isDeg ? degreeToRadian(box.angle) : box.angle;
|
|
3353
4430
|
const cp = getCenterPoint(box);
|
|
@@ -3376,15 +4453,21 @@
|
|
|
3376
4453
|
const vector = (start, end) => {
|
|
3377
4454
|
return [end.x - start.x, end.y - start.y];
|
|
3378
4455
|
};
|
|
4456
|
+
// 两个矩形的中心点
|
|
3379
4457
|
const p1 = getCenterPoint(box1);
|
|
3380
4458
|
const p2 = getCenterPoint(box2);
|
|
4459
|
+
// 向量 p1p2
|
|
3381
4460
|
const vp1p2 = vector(p1, p2);
|
|
4461
|
+
//矩形1的两边向量
|
|
3382
4462
|
const AB = vector(rect1[0], rect1[1]);
|
|
3383
4463
|
const BC = vector(rect1[1], rect1[2]);
|
|
4464
|
+
//矩形2的两边向量
|
|
3384
4465
|
const A1B1 = vector(rect2[0], rect2[1]);
|
|
3385
4466
|
const B1C1 = vector(rect2[1], rect2[2]);
|
|
4467
|
+
// 矩形1 的两个弧度
|
|
3386
4468
|
const deg11 = isDeg ? degreeToRadian(box1.angle) : box1.angle;
|
|
3387
4469
|
let deg12 = isDeg ? degreeToRadian(90 - box1.angle) : box1.angle + halfPi;
|
|
4470
|
+
// 矩形2 的两个弧度
|
|
3388
4471
|
const deg21 = isDeg ? degreeToRadian(box2.angle) : box2.angle;
|
|
3389
4472
|
let deg22 = isDeg ? degreeToRadian(90 - box2.angle) : box2.angle + halfPi;
|
|
3390
4473
|
if (deg12 > pi2) {
|
|
@@ -3393,10 +4476,12 @@
|
|
|
3393
4476
|
if (deg22 > pi2) {
|
|
3394
4477
|
deg22 -= pi2;
|
|
3395
4478
|
}
|
|
4479
|
+
// 投影重叠
|
|
3396
4480
|
const isCover = (checkAxisRadius, deg, targetAxis1, targetAxis2) => {
|
|
3397
4481
|
const checkAxis = [Math.cos(deg), Math.sin(deg)];
|
|
3398
4482
|
const targetAxisRadius = (getProjectionRadius(checkAxis, targetAxis1) + getProjectionRadius(checkAxis, targetAxis2)) / 2;
|
|
3399
4483
|
const centerPointRadius = getProjectionRadius(checkAxis, vp1p2);
|
|
4484
|
+
// console.log('abc', `checkAxis:${checkAxis},三个投影:${checkAxisRadius}, ${targetAxisRadius}, ${centerPointRadius}`)
|
|
3400
4485
|
return checkAxisRadius + targetAxisRadius > centerPointRadius;
|
|
3401
4486
|
};
|
|
3402
4487
|
return (isCover((box1.x2 - box1.x1) / 2, deg11, A1B1, B1C1) &&
|
|
@@ -3411,6 +4496,7 @@
|
|
|
3411
4496
|
let y2;
|
|
3412
4497
|
function getAABBFromPoints(points) {
|
|
3413
4498
|
(x1 = Infinity), (y1 = Infinity), (x2 = -Infinity), (y2 = -Infinity);
|
|
4499
|
+
// todo 有优化空间
|
|
3414
4500
|
points.forEach(point => {
|
|
3415
4501
|
if (x1 > point.x) {
|
|
3416
4502
|
x1 = point.x;
|
|
@@ -3462,6 +4548,7 @@
|
|
|
3462
4548
|
y22 = temp;
|
|
3463
4549
|
}
|
|
3464
4550
|
}
|
|
4551
|
+
// 不相交直接跳过
|
|
3465
4552
|
if (x11 >= x22 || x12 <= x21 || y11 >= y22 || y12 <= y21) {
|
|
3466
4553
|
return [bounds1, bounds2];
|
|
3467
4554
|
}
|
|
@@ -3483,6 +4570,7 @@
|
|
|
3483
4570
|
const l = [];
|
|
3484
4571
|
list.forEach(b => {
|
|
3485
4572
|
let arr;
|
|
4573
|
+
// 相交
|
|
3486
4574
|
if ((arr = unionAABB(baseBound, b)).length > 1) {
|
|
3487
4575
|
l.push(b);
|
|
3488
4576
|
return;
|
|
@@ -3596,8 +4684,10 @@
|
|
|
3596
4684
|
};
|
|
3597
4685
|
}
|
|
3598
4686
|
estimate({ text, fontSize }) {
|
|
3599
|
-
|
|
3600
|
-
let
|
|
4687
|
+
// 假设只有英文和中文字符
|
|
4688
|
+
let eCharLen = 0; // 英文字符
|
|
4689
|
+
let cCharLen = 0; // 中文字符
|
|
4690
|
+
// 判断ascii码,如果是
|
|
3601
4691
|
for (let i = 0; i < text.length; i++) {
|
|
3602
4692
|
text.charCodeAt(i) < 128 ? eCharLen++ : cCharLen++;
|
|
3603
4693
|
}
|
|
@@ -3615,6 +4705,14 @@
|
|
|
3615
4705
|
}
|
|
3616
4706
|
|
|
3617
4707
|
const EPSILON = 1e-8;
|
|
4708
|
+
// Adapted from https://github.com/apache/echarts/blob/master/src/util/graphic.ts by striezel
|
|
4709
|
+
// License: https://github.com/apache/echarts/blob/master/LICENSE
|
|
4710
|
+
/**
|
|
4711
|
+
* Return `true` if the given line (line `a`) and the given polygon
|
|
4712
|
+
* are intersect.
|
|
4713
|
+
* Note that we do not count colinear as intersect here because no
|
|
4714
|
+
* requirement for that. We could do that if required in future.
|
|
4715
|
+
*/
|
|
3618
4716
|
function lineIntersectPolygon(a1x, a1y, a2x, a2y, points) {
|
|
3619
4717
|
for (let i = 0, p2 = points[points.length - 1]; i < points.length; i++) {
|
|
3620
4718
|
const p = points[i];
|
|
@@ -3625,6 +4723,8 @@
|
|
|
3625
4723
|
}
|
|
3626
4724
|
return false;
|
|
3627
4725
|
}
|
|
4726
|
+
// Adapted from https://github.com/ecomfe/zrender/blob/master/src/contain/polygon.ts by pissang
|
|
4727
|
+
// License: https://github.com/ecomfe/zrender/blob/master/LICENSE
|
|
3628
4728
|
function polygonContainPoint(points, x, y) {
|
|
3629
4729
|
let w = 0;
|
|
3630
4730
|
let p = points[0];
|
|
@@ -3636,30 +4736,44 @@
|
|
|
3636
4736
|
w += isPointInLine(p.x, p.y, p2.x, p2.y, x, y);
|
|
3637
4737
|
p = p2;
|
|
3638
4738
|
}
|
|
4739
|
+
// Close polygon
|
|
3639
4740
|
const p0 = points[0];
|
|
3640
4741
|
if (!isAroundEqual(p.x, p0.x) || !isAroundEqual(p.y, p0.y)) {
|
|
3641
4742
|
w += isPointInLine(p.x, p.y, p0.x, p0.y, x, y);
|
|
3642
4743
|
}
|
|
3643
4744
|
return w !== 0;
|
|
3644
4745
|
}
|
|
4746
|
+
// Adapted from https://github.com/ecomfe/zrender/blob/master/src/contain/windingLine.ts by pissang
|
|
4747
|
+
// License: https://github.com/ecomfe/zrender/blob/master/LICENSE
|
|
3645
4748
|
function isPointInLine(x0, y0, x1, y1, x, y) {
|
|
3646
4749
|
if ((y > y0 && y > y1) || (y < y0 && y < y1)) {
|
|
3647
4750
|
return 0;
|
|
3648
4751
|
}
|
|
4752
|
+
// Ignore horizontal line
|
|
3649
4753
|
if (y1 === y0) {
|
|
3650
4754
|
return 0;
|
|
3651
4755
|
}
|
|
3652
4756
|
const t = (y - y0) / (y1 - y0);
|
|
3653
4757
|
let dir = y1 < y0 ? 1 : -1;
|
|
4758
|
+
// Avoid winding error when intersection point is the connect point of two line of polygon
|
|
3654
4759
|
if (t === 1 || t === 0) {
|
|
3655
4760
|
dir = y1 < y0 ? 0.5 : -0.5;
|
|
3656
4761
|
}
|
|
3657
4762
|
const x_ = t * (x1 - x0) + x0;
|
|
4763
|
+
// If (x, y) on the line, considered as "contain".
|
|
3658
4764
|
return x_ === x ? Infinity : x_ > x ? dir : 0;
|
|
3659
4765
|
}
|
|
4766
|
+
// Adapted from https://github.com/ecomfe/zrender/blob/master/src/contain/polygon.ts by pissang
|
|
4767
|
+
// License: https://github.com/ecomfe/zrender/blob/master/LICENSE
|
|
3660
4768
|
function isAroundEqual(a, b) {
|
|
3661
4769
|
return Math.abs(a - b) < EPSILON;
|
|
3662
4770
|
}
|
|
4771
|
+
/**
|
|
4772
|
+
* polygon图元之间是否相交
|
|
4773
|
+
* @param pointsA 图元A的points
|
|
4774
|
+
* @param pointsB 图元B的points
|
|
4775
|
+
* @returns 是否相交
|
|
4776
|
+
*/
|
|
3663
4777
|
function polygonIntersectPolygon(pointsA, pointsB) {
|
|
3664
4778
|
for (let i = 0; i < pointsB.length; i++) {
|
|
3665
4779
|
if (polygonContainPoint(pointsA, pointsB[i].x, pointsB[i].y)) {
|
|
@@ -3716,9 +4830,11 @@
|
|
|
3716
4830
|
let width = 0;
|
|
3717
4831
|
for (const character of string) {
|
|
3718
4832
|
const codePoint = character.codePointAt(0);
|
|
4833
|
+
// Ignore control characters
|
|
3719
4834
|
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
3720
4835
|
continue;
|
|
3721
4836
|
}
|
|
4837
|
+
// Ignore combining characters
|
|
3722
4838
|
if (codePoint >= 0x300 && codePoint <= 0x36f) {
|
|
3723
4839
|
continue;
|
|
3724
4840
|
}
|
|
@@ -3745,6 +4861,7 @@
|
|
|
3745
4861
|
};
|
|
3746
4862
|
const ansiRegex = ({ onlyFirst = false } = {}) => {
|
|
3747
4863
|
const pattern = [
|
|
4864
|
+
// eslint-disable-next-line max-len
|
|
3748
4865
|
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
3749
4866
|
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
|
|
3750
4867
|
].join('|');
|
|
@@ -4001,9 +5118,12 @@
|
|
|
4001
5118
|
return 'N';
|
|
4002
5119
|
};
|
|
4003
5120
|
const emojiRegex = () => {
|
|
5121
|
+
// https://mths.be/emoji
|
|
5122
|
+
// eslint-disable-next-line max-len
|
|
4004
5123
|
return /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC3\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC08\uDC26](?:\u200D\u2B1B)?|[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;
|
|
4005
5124
|
};
|
|
4006
5125
|
|
|
5126
|
+
// FIXME: from VRender
|
|
4007
5127
|
function getContextFont(text, defaultAttr = {}, fontSizeScale) {
|
|
4008
5128
|
if (!fontSizeScale) {
|
|
4009
5129
|
fontSizeScale = 1;
|
|
@@ -4020,16 +5140,25 @@
|
|
|
4020
5140
|
|
|
4021
5141
|
class TextMeasure {
|
|
4022
5142
|
constructor(option, textSpec) {
|
|
5143
|
+
/** 数字大小缓存 */
|
|
4023
5144
|
this._numberCharSize = null;
|
|
5145
|
+
/** 全角字符大小缓存 */
|
|
4024
5146
|
this._fullCharSize = null;
|
|
5147
|
+
/** 字母大小缓存 */
|
|
4025
5148
|
this._letterCharSize = null;
|
|
5149
|
+
/** 特殊字符缓存 */
|
|
4026
5150
|
this._specialCharSizeMap = {};
|
|
5151
|
+
/** 内置测量 canvas */
|
|
4027
5152
|
this._canvas = null;
|
|
4028
5153
|
this._context = null;
|
|
4029
5154
|
this._contextSaved = false;
|
|
5155
|
+
/** 是否不支持 canvas */
|
|
4030
5156
|
this._notSupportCanvas = false;
|
|
5157
|
+
/** 是否不支持 vrender */
|
|
4031
5158
|
this._notSupportVRender = false;
|
|
5159
|
+
/** 文字 spec */
|
|
4032
5160
|
this._userSpec = {};
|
|
5161
|
+
/** 特殊字符集 */
|
|
4033
5162
|
this.specialCharSet = '-/: .,@%\'"~';
|
|
4034
5163
|
this._option = option;
|
|
4035
5164
|
this._userSpec = textSpec !== null && textSpec !== void 0 ? textSpec : {};
|
|
@@ -4041,6 +5170,7 @@
|
|
|
4041
5170
|
? this.fullMeasure.bind(this)
|
|
4042
5171
|
: this.measureWithNaiveCanvas.bind(this);
|
|
4043
5172
|
}
|
|
5173
|
+
/** 初始化测量相关上下文 */
|
|
4044
5174
|
initContext() {
|
|
4045
5175
|
if (this._notSupportCanvas) {
|
|
4046
5176
|
return false;
|
|
@@ -4054,6 +5184,7 @@
|
|
|
4054
5184
|
typeof window.document !== 'undefined' &&
|
|
4055
5185
|
globalThis &&
|
|
4056
5186
|
isValid(globalThis.document)) {
|
|
5187
|
+
// 默认创建方法
|
|
4057
5188
|
this._canvas = globalThis.document.createElement('canvas');
|
|
4058
5189
|
}
|
|
4059
5190
|
}
|
|
@@ -4094,6 +5225,7 @@
|
|
|
4094
5225
|
lineHeight
|
|
4095
5226
|
};
|
|
4096
5227
|
}
|
|
5228
|
+
/** 计算文本宽高 */
|
|
4097
5229
|
measure(text, method) {
|
|
4098
5230
|
switch (method) {
|
|
4099
5231
|
case 'vrender':
|
|
@@ -4108,12 +5240,13 @@
|
|
|
4108
5240
|
return this.quickMeasure(text);
|
|
4109
5241
|
}
|
|
4110
5242
|
}
|
|
5243
|
+
/** 精确计算文本宽高 */
|
|
4111
5244
|
fullMeasure(text) {
|
|
4112
5245
|
if (isNil(text)) {
|
|
4113
5246
|
return { width: 0, height: 0 };
|
|
4114
5247
|
}
|
|
4115
5248
|
if (isNil(this._option.getTextBounds) || !this._notSupportVRender) {
|
|
4116
|
-
return this.measureWithNaiveCanvas(text);
|
|
5249
|
+
return this.measureWithNaiveCanvas(text); // 降级
|
|
4117
5250
|
}
|
|
4118
5251
|
const { fontFamily, fontSize, fontWeight, textAlign, textBaseline, ellipsis, limit, lineHeight } = this.textSpec;
|
|
4119
5252
|
let size;
|
|
@@ -4133,17 +5266,18 @@
|
|
|
4133
5266
|
}
|
|
4134
5267
|
catch (e) {
|
|
4135
5268
|
this._notSupportVRender = true;
|
|
4136
|
-
size = this.measureWithNaiveCanvas(text);
|
|
5269
|
+
size = this.measureWithNaiveCanvas(text); // 降级
|
|
4137
5270
|
}
|
|
4138
5271
|
return size;
|
|
4139
5272
|
}
|
|
5273
|
+
/** 通过原生 canvas 精确计算文本宽度,高度为估算 */
|
|
4140
5274
|
measureWithNaiveCanvas(text) {
|
|
4141
5275
|
return this._measureReduce(text, this._measureWithNaiveCanvas.bind(this));
|
|
4142
5276
|
}
|
|
4143
5277
|
_measureWithNaiveCanvas(text) {
|
|
4144
5278
|
var _a;
|
|
4145
5279
|
if (!this.initContext()) {
|
|
4146
|
-
return this._quickMeasureWithoutCanvas(text);
|
|
5280
|
+
return this._quickMeasureWithoutCanvas(text); // 降级
|
|
4147
5281
|
}
|
|
4148
5282
|
const metrics = this._context.measureText(text);
|
|
4149
5283
|
const { fontSize, lineHeight } = this.textSpec;
|
|
@@ -4154,6 +5288,7 @@
|
|
|
4154
5288
|
fontBoundingBoxDescent: metrics.fontBoundingBoxDescent
|
|
4155
5289
|
};
|
|
4156
5290
|
}
|
|
5291
|
+
/** 快速估算文本宽高 */
|
|
4157
5292
|
quickMeasure(text) {
|
|
4158
5293
|
return this._measureReduce(text, this._quickMeasure.bind(this));
|
|
4159
5294
|
}
|
|
@@ -4164,13 +5299,17 @@
|
|
|
4164
5299
|
};
|
|
4165
5300
|
for (let i = 0; i < text.length; i++) {
|
|
4166
5301
|
const char = text[i];
|
|
5302
|
+
// 先判断是否特殊字符
|
|
4167
5303
|
let size = this._measureSpecialChar(char);
|
|
5304
|
+
// 再判断是否数字
|
|
4168
5305
|
if (isNil(size) && TextMeasure.NUMBERS_CHAR_SET.includes(char)) {
|
|
4169
5306
|
size = this._measureNumberChar();
|
|
4170
5307
|
}
|
|
5308
|
+
// 再判断全角字符
|
|
4171
5309
|
if (isNil(size) && ['F', 'W'].includes(eastAsianCharacterInfo(char))) {
|
|
4172
5310
|
size = this._measureFullSizeChar();
|
|
4173
5311
|
}
|
|
5312
|
+
// 最后按半角字符处理
|
|
4174
5313
|
if (isNil(size)) {
|
|
4175
5314
|
size = this._measureLetterChar();
|
|
4176
5315
|
}
|
|
@@ -4181,6 +5320,7 @@
|
|
|
4181
5320
|
}
|
|
4182
5321
|
return totalSize;
|
|
4183
5322
|
}
|
|
5323
|
+
/** 不使用 canvas 来快速估算文本宽高 */
|
|
4184
5324
|
quickMeasureWithoutCanvas(text) {
|
|
4185
5325
|
return this._measureReduce(text, this._quickMeasureWithoutCanvas.bind(this));
|
|
4186
5326
|
}
|
|
@@ -4193,6 +5333,7 @@
|
|
|
4193
5333
|
const { fontSize, lineHeight } = this.textSpec;
|
|
4194
5334
|
for (let i = 0; i < text.length; i++) {
|
|
4195
5335
|
const char = text[i];
|
|
5336
|
+
// 判断全角字符
|
|
4196
5337
|
const size = ['F', 'W'].includes(eastAsianCharacterInfo(char)) ? 1 : 0.53;
|
|
4197
5338
|
totalSize.width += size * fontSize;
|
|
4198
5339
|
}
|
|
@@ -4216,11 +5357,12 @@
|
|
|
4216
5357
|
}
|
|
4217
5358
|
return {
|
|
4218
5359
|
width: textArr.reduce((maxWidth, cur) => Math.max(maxWidth, processor(cur).width), 0),
|
|
4219
|
-
height: textArr.length * (((_a = lineHeight) !== null && _a !== void 0 ? _a : fontSize) + 1) + 1
|
|
5360
|
+
height: textArr.length * (((_a = lineHeight) !== null && _a !== void 0 ? _a : fontSize) + 1) + 1 // 经验值
|
|
4220
5361
|
};
|
|
4221
5362
|
}
|
|
4222
5363
|
return processor(text.toString());
|
|
4223
5364
|
}
|
|
5365
|
+
/** 测量一个数字 */
|
|
4224
5366
|
_measureNumberChar() {
|
|
4225
5367
|
if (isNil(this._numberCharSize)) {
|
|
4226
5368
|
const numberBounds = this._standardMethod(TextMeasure.NUMBERS_CHAR_SET);
|
|
@@ -4233,12 +5375,14 @@
|
|
|
4233
5375
|
}
|
|
4234
5376
|
return this._numberCharSize;
|
|
4235
5377
|
}
|
|
5378
|
+
/** 测量一个全角字符 */
|
|
4236
5379
|
_measureFullSizeChar() {
|
|
4237
5380
|
if (isNil(this._fullCharSize)) {
|
|
4238
5381
|
this._fullCharSize = this._standardMethod(TextMeasure.FULL_SIZE_CHAR);
|
|
4239
5382
|
}
|
|
4240
5383
|
return this._fullCharSize;
|
|
4241
5384
|
}
|
|
5385
|
+
/** 测量一个英文字符 */
|
|
4242
5386
|
_measureLetterChar() {
|
|
4243
5387
|
if (isNil(this._letterCharSize)) {
|
|
4244
5388
|
const alphabetBounds = this._standardMethod(TextMeasure.ALPHABET_CHAR_SET);
|
|
@@ -4251,6 +5395,7 @@
|
|
|
4251
5395
|
}
|
|
4252
5396
|
return this._letterCharSize;
|
|
4253
5397
|
}
|
|
5398
|
+
/** 测量一个特殊字符 */
|
|
4254
5399
|
_measureSpecialChar(char) {
|
|
4255
5400
|
if (isValid(this._specialCharSizeMap[char])) {
|
|
4256
5401
|
return this._specialCharSizeMap[char];
|
|
@@ -4274,8 +5419,11 @@
|
|
|
4274
5419
|
}
|
|
4275
5420
|
}
|
|
4276
5421
|
}
|
|
5422
|
+
/** 英文字母集 */
|
|
4277
5423
|
TextMeasure.ALPHABET_CHAR_SET = 'abcdefghijklmnopqrstuvwxyz';
|
|
5424
|
+
/** 数字集 */
|
|
4278
5425
|
TextMeasure.NUMBERS_CHAR_SET = '0123456789';
|
|
5426
|
+
/** 全角字符 */
|
|
4279
5427
|
TextMeasure.FULL_SIZE_CHAR = '字';
|
|
4280
5428
|
|
|
4281
5429
|
const calculateAnchorOfBounds = (bounds, anchorType) => {
|
|
@@ -4331,26 +5479,28 @@
|
|
|
4331
5479
|
{ x: Math.cos(a.angle), y: Math.sin(a.angle) },
|
|
4332
5480
|
{ x: -Math.sin(a.angle), y: Math.cos(a.angle) },
|
|
4333
5481
|
{ x: Math.cos(b.angle), y: Math.sin(a.angle) },
|
|
4334
|
-
{ x: -Math.sin(b.angle), y: Math.cos(a.angle) }
|
|
5482
|
+
{ x: -Math.sin(b.angle), y: Math.cos(a.angle) } // Rect B's second axis
|
|
4335
5483
|
];
|
|
5484
|
+
// calculate the projection range of a rectangle on a given axis
|
|
4336
5485
|
function getProjectionRange(obb, axisX, axisY) {
|
|
4337
5486
|
const corners = obb.getRotatedCorners();
|
|
4338
5487
|
const projections = corners.map(p => p.x * axisX + p.y * axisY);
|
|
4339
5488
|
return { min: Math.min(...projections), max: Math.max(...projections) };
|
|
4340
5489
|
}
|
|
5490
|
+
// Calculate distances for all axes
|
|
4341
5491
|
let maxDistance = 0;
|
|
4342
5492
|
for (const axis of axes) {
|
|
4343
5493
|
const rangeA = getProjectionRange(a, axis.x, axis.y);
|
|
4344
5494
|
const rangeB = getProjectionRange(b, axis.x, axis.y);
|
|
4345
5495
|
let distance;
|
|
4346
5496
|
if (rangeA.max < rangeB.min) {
|
|
4347
|
-
distance = rangeB.min - rangeA.max;
|
|
5497
|
+
distance = rangeB.min - rangeA.max; // B is to the right of A
|
|
4348
5498
|
}
|
|
4349
5499
|
else if (rangeB.max < rangeA.min) {
|
|
4350
|
-
distance = rangeA.min - rangeB.max;
|
|
5500
|
+
distance = rangeA.min - rangeB.max; // A is to the right of B
|
|
4351
5501
|
}
|
|
4352
5502
|
else {
|
|
4353
|
-
distance = 0;
|
|
5503
|
+
distance = 0; // Overlapping
|
|
4354
5504
|
}
|
|
4355
5505
|
maxDistance = Math.max(maxDistance, distance);
|
|
4356
5506
|
}
|
|
@@ -4359,6 +5509,7 @@
|
|
|
4359
5509
|
|
|
4360
5510
|
function transformBoundsWithMatrix(out, bounds, matrix) {
|
|
4361
5511
|
const { x1, y1, x2, y2 } = bounds;
|
|
5512
|
+
// 如果没有旋转和缩放,那就直接translate
|
|
4362
5513
|
if (matrix.onlyTranslate()) {
|
|
4363
5514
|
if (out !== bounds) {
|
|
4364
5515
|
out.setValue(bounds.x1, bounds.y1, bounds.x2, bounds.y2);
|
|
@@ -4373,6 +5524,16 @@
|
|
|
4373
5524
|
out.add(matrix.a * x1 + matrix.c * y2 + matrix.e, matrix.b * x1 + matrix.d * y2 + matrix.f);
|
|
4374
5525
|
return bounds;
|
|
4375
5526
|
}
|
|
5527
|
+
/**
|
|
5528
|
+
* 依据graphic的属性对bounds进行变换
|
|
5529
|
+
* @param bounds
|
|
5530
|
+
* @param x
|
|
5531
|
+
* @param y
|
|
5532
|
+
* @param scaleX
|
|
5533
|
+
* @param scaleY
|
|
5534
|
+
* @param angle
|
|
5535
|
+
* @param rotateCenter
|
|
5536
|
+
*/
|
|
4376
5537
|
function transformBounds(bounds, x, y, scaleX, scaleY, angle, rotateCenter) {
|
|
4377
5538
|
if (abs(scaleX) <= epsilon || abs(scaleY) <= epsilon) {
|
|
4378
5539
|
return;
|
|
@@ -4390,6 +5551,37 @@
|
|
|
4390
5551
|
}
|
|
4391
5552
|
bounds.translate(x, y);
|
|
4392
5553
|
}
|
|
5554
|
+
/**
|
|
5555
|
+
Copyright (c) 2015-2021, University of Washington Interactive Data Lab
|
|
5556
|
+
All rights reserved.
|
|
5557
|
+
|
|
5558
|
+
Redistribution and use in source and binary forms, with or without
|
|
5559
|
+
modification, are permitted provided that the following conditions are met:
|
|
5560
|
+
|
|
5561
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
5562
|
+
list of conditions and the following disclaimer.
|
|
5563
|
+
|
|
5564
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
5565
|
+
this list of conditions and the following disclaimer in the documentation
|
|
5566
|
+
and/or other materials provided with the distribution.
|
|
5567
|
+
|
|
5568
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
5569
|
+
may be used to endorse or promote products derived from this software
|
|
5570
|
+
without specific prior written permission.
|
|
5571
|
+
|
|
5572
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
5573
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
5574
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
5575
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
5576
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
5577
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
5578
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
5579
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
5580
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
5581
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
5582
|
+
*/
|
|
5583
|
+
// 基于Vega的Bounds重构
|
|
5584
|
+
// https://github.com/vega/vega/blob/825bfaba6ccfe3306183df22b8c955a07bb30714/packages/vega-scenegraph/src/Bounds.js
|
|
4393
5585
|
class Bounds {
|
|
4394
5586
|
constructor(bounds) {
|
|
4395
5587
|
if (bounds) {
|
|
@@ -4496,6 +5688,11 @@
|
|
|
4496
5688
|
const p = this.scalePoints(sx, sy, x, y);
|
|
4497
5689
|
return this.clear().add(p[0], p[1]).add(p[2], p[3]);
|
|
4498
5690
|
}
|
|
5691
|
+
/**
|
|
5692
|
+
* 并集
|
|
5693
|
+
* @param b
|
|
5694
|
+
* @returns
|
|
5695
|
+
*/
|
|
4499
5696
|
union(b) {
|
|
4500
5697
|
if (b.x1 < this.x1) {
|
|
4501
5698
|
this.x1 = b.x1;
|
|
@@ -4511,6 +5708,11 @@
|
|
|
4511
5708
|
}
|
|
4512
5709
|
return this;
|
|
4513
5710
|
}
|
|
5711
|
+
/**
|
|
5712
|
+
* 交集
|
|
5713
|
+
* @param b
|
|
5714
|
+
* @returns
|
|
5715
|
+
*/
|
|
4514
5716
|
intersect(b) {
|
|
4515
5717
|
if (b.x1 > this.x1) {
|
|
4516
5718
|
this.x1 = b.x1;
|
|
@@ -4526,15 +5728,36 @@
|
|
|
4526
5728
|
}
|
|
4527
5729
|
return this;
|
|
4528
5730
|
}
|
|
5731
|
+
/**
|
|
5732
|
+
* 是否包含b
|
|
5733
|
+
* @param b
|
|
5734
|
+
* @returns
|
|
5735
|
+
*/
|
|
4529
5736
|
encloses(b) {
|
|
4530
5737
|
return b && this.x1 <= b.x1 && this.x2 >= b.x2 && this.y1 <= b.y1 && this.y2 >= b.y2;
|
|
4531
5738
|
}
|
|
5739
|
+
/**
|
|
5740
|
+
* 是否共边
|
|
5741
|
+
* @param b
|
|
5742
|
+
* @returns
|
|
5743
|
+
*/
|
|
4532
5744
|
alignsWith(b) {
|
|
4533
5745
|
return b && (this.x1 === b.x1 || this.x2 === b.x2 || this.y1 === b.y1 || this.y2 === b.y2);
|
|
4534
5746
|
}
|
|
5747
|
+
/**
|
|
5748
|
+
* 是否相交
|
|
5749
|
+
* @param b
|
|
5750
|
+
* @returns
|
|
5751
|
+
*/
|
|
4535
5752
|
intersects(b) {
|
|
4536
5753
|
return b && !(this.x2 < b.x1 || this.x1 > b.x2 || this.y2 < b.y1 || this.y1 > b.y2);
|
|
4537
5754
|
}
|
|
5755
|
+
/**
|
|
5756
|
+
* 是否包含
|
|
5757
|
+
* @param x
|
|
5758
|
+
* @param y
|
|
5759
|
+
* @returns
|
|
5760
|
+
*/
|
|
4538
5761
|
contains(x = 0, y = 0) {
|
|
4539
5762
|
return !(x < this.x1 || x > this.x2 || y < this.y1 || y > this.y2);
|
|
4540
5763
|
}
|
|
@@ -4640,6 +5863,7 @@
|
|
|
4640
5863
|
this.f = f;
|
|
4641
5864
|
}
|
|
4642
5865
|
equalToMatrix(m2) {
|
|
5866
|
+
// ef -> ad -> bc
|
|
4643
5867
|
return !(this.e !== m2.e ||
|
|
4644
5868
|
this.f !== m2.f ||
|
|
4645
5869
|
this.a !== m2.a ||
|
|
@@ -4648,6 +5872,7 @@
|
|
|
4648
5872
|
this.c !== m2.c);
|
|
4649
5873
|
}
|
|
4650
5874
|
equalTo(a, b, c, d, e, f) {
|
|
5875
|
+
// ef -> ad -> bc
|
|
4651
5876
|
return !(this.e !== e || this.f !== f || this.a !== a || this.d !== d || this.b !== b || this.c !== c);
|
|
4652
5877
|
}
|
|
4653
5878
|
setValue(a, b, c, d, e, f) {
|
|
@@ -4668,6 +5893,9 @@
|
|
|
4668
5893
|
this.f = 0;
|
|
4669
5894
|
return this;
|
|
4670
5895
|
}
|
|
5896
|
+
/**
|
|
5897
|
+
* 获取当前矩阵的逆矩阵
|
|
5898
|
+
*/
|
|
4671
5899
|
getInverse() {
|
|
4672
5900
|
const a = this.a;
|
|
4673
5901
|
const b = this.b;
|
|
@@ -4699,6 +5927,17 @@
|
|
|
4699
5927
|
return this;
|
|
4700
5928
|
}
|
|
4701
5929
|
rotateByCenter(rad, cx, cy) {
|
|
5930
|
+
/** rotate matrix
|
|
5931
|
+
* | cos -sin (1-cos)*cx+sin*cy |
|
|
5932
|
+
* | sin cos (1-cos)*cy-sin*cx |
|
|
5933
|
+
* | 0 0 1 |
|
|
5934
|
+
* after multiply
|
|
5935
|
+
* m13 = (1-cos)*cx+sin*cy;
|
|
5936
|
+
* m23 = (1-cos)*cy-sin*cx;
|
|
5937
|
+
* | cos*a-sin*b cos*c-sin*d cos*e-sin*f+m13 |
|
|
5938
|
+
* | sin*a+cos*b sin*c+cos*d sin*e+cos*f+m23 |
|
|
5939
|
+
* | 0 0 1 |
|
|
5940
|
+
*/
|
|
4702
5941
|
const cos = Math.cos(rad);
|
|
4703
5942
|
const sin = Math.sin(rad);
|
|
4704
5943
|
const rotateM13 = (1 - cos) * cx + sin * cy;
|
|
@@ -4741,6 +5980,15 @@
|
|
|
4741
5980
|
return this;
|
|
4742
5981
|
}
|
|
4743
5982
|
transpose() {
|
|
5983
|
+
/** transpose matrix
|
|
5984
|
+
* | 0 1 0 |
|
|
5985
|
+
* | 1 0 0 |
|
|
5986
|
+
* | 0 0 1 |
|
|
5987
|
+
* after multiply
|
|
5988
|
+
* | b d f |
|
|
5989
|
+
* | a c e |
|
|
5990
|
+
* | 0 0 1 |
|
|
5991
|
+
*/
|
|
4744
5992
|
const { a, b, c, d, e, f } = this;
|
|
4745
5993
|
this.a = b;
|
|
4746
5994
|
this.b = a;
|
|
@@ -4750,6 +5998,10 @@
|
|
|
4750
5998
|
this.f = e;
|
|
4751
5999
|
return this;
|
|
4752
6000
|
}
|
|
6001
|
+
/**
|
|
6002
|
+
* 矩阵相乘
|
|
6003
|
+
* @param matrix
|
|
6004
|
+
*/
|
|
4753
6005
|
multiply(a2, b2, c2, d2, e2, f2) {
|
|
4754
6006
|
const a1 = this.a;
|
|
4755
6007
|
const b1 = this.b;
|
|
@@ -4771,6 +6023,11 @@
|
|
|
4771
6023
|
this.f = dy;
|
|
4772
6024
|
return this;
|
|
4773
6025
|
}
|
|
6026
|
+
/**
|
|
6027
|
+
* 插值计算
|
|
6028
|
+
* @param m2
|
|
6029
|
+
* @param t
|
|
6030
|
+
*/
|
|
4774
6031
|
interpolate(m2, t) {
|
|
4775
6032
|
const m = new Matrix();
|
|
4776
6033
|
m.a = this.a + (m2.a - this.a) * t;
|
|
@@ -4781,6 +6038,11 @@
|
|
|
4781
6038
|
m.f = this.f + (m2.f - this.f) * t;
|
|
4782
6039
|
return m;
|
|
4783
6040
|
}
|
|
6041
|
+
/**
|
|
6042
|
+
* 将point转到当前矩阵的坐标空间中
|
|
6043
|
+
* @param source
|
|
6044
|
+
* @param target
|
|
6045
|
+
*/
|
|
4784
6046
|
transformPoint(source, target) {
|
|
4785
6047
|
const { a, b, c, d, e, f } = this;
|
|
4786
6048
|
const dt = a * d - b * c;
|
|
@@ -4794,6 +6056,7 @@
|
|
|
4794
6056
|
target.x = x * nextA + y * nextC + nextE;
|
|
4795
6057
|
target.y = x * nextB + y * nextD + nextF;
|
|
4796
6058
|
}
|
|
6059
|
+
// 只有translate
|
|
4797
6060
|
onlyTranslate(scale = 1) {
|
|
4798
6061
|
return this.a === scale && this.b === 0 && this.c === 0 && this.d === scale;
|
|
4799
6062
|
}
|
|
@@ -4817,6 +6080,7 @@
|
|
|
4817
6080
|
skewX: 0,
|
|
4818
6081
|
skewY: 0
|
|
4819
6082
|
};
|
|
6083
|
+
// Apply the QR-like decomposition.
|
|
4820
6084
|
if (a !== 0 || b !== 0) {
|
|
4821
6085
|
const r = Math.sqrt(a * a + b * b);
|
|
4822
6086
|
result.rotateDeg = b > 0 ? Math.acos(a / r) : -Math.acos(a / r);
|
|
@@ -4838,6 +6102,17 @@
|
|
|
4838
6102
|
return result;
|
|
4839
6103
|
}
|
|
4840
6104
|
}
|
|
6105
|
+
/**
|
|
6106
|
+
* 对matrix进行通常的变换(基于rotateCenter进行旋转的同时进行translate和scale)
|
|
6107
|
+
* @param out
|
|
6108
|
+
* @param origin
|
|
6109
|
+
* @param x
|
|
6110
|
+
* @param y
|
|
6111
|
+
* @param scaleX
|
|
6112
|
+
* @param scaleY
|
|
6113
|
+
* @param angle
|
|
6114
|
+
* @param rotateCenter
|
|
6115
|
+
*/
|
|
4841
6116
|
function normalTransform(out, origin, x, y, scaleX, scaleY, angle, rotateCenter) {
|
|
4842
6117
|
const oa = origin.a;
|
|
4843
6118
|
const ob = origin.b;
|
|
@@ -4871,18 +6146,20 @@
|
|
|
4871
6146
|
out.f = of + ob * rotateCenterX + od * rotateCenterY - b1 * offsetX - d1 * offsetY;
|
|
4872
6147
|
}
|
|
4873
6148
|
|
|
6149
|
+
// LRU清除
|
|
4874
6150
|
class LRU {
|
|
4875
6151
|
constructor() {
|
|
4876
6152
|
this.CLEAN_THRESHOLD = 1e3;
|
|
4877
|
-
this.L_TIME = 1000;
|
|
4878
|
-
this.R_COUNT = 1;
|
|
4879
|
-
this.R_TIMESTAMP_MAX_SIZE = 20;
|
|
6153
|
+
this.L_TIME = 1000; // 1000ms以内
|
|
6154
|
+
this.R_COUNT = 1; // 使用次数为0
|
|
6155
|
+
this.R_TIMESTAMP_MAX_SIZE = 20; // timestamp最大长度
|
|
4880
6156
|
}
|
|
4881
6157
|
clearCache(cache, params) {
|
|
4882
6158
|
const { CLEAN_THRESHOLD = this.CLEAN_THRESHOLD, L_TIME = this.L_TIME, R_COUNT = this.R_COUNT } = params;
|
|
4883
6159
|
if (cache.size < CLEAN_THRESHOLD) {
|
|
4884
6160
|
return 0;
|
|
4885
6161
|
}
|
|
6162
|
+
// 开始清理,清理最近1000ms内使用次数为0的缓存
|
|
4886
6163
|
let clearNum = 0;
|
|
4887
6164
|
const clear = (key) => {
|
|
4888
6165
|
clearNum++;
|
|
@@ -4892,7 +6169,7 @@
|
|
|
4892
6169
|
cache.forEach((item, key) => {
|
|
4893
6170
|
if (item.timestamp.length < R_COUNT) {
|
|
4894
6171
|
return clear(key);
|
|
4895
|
-
}
|
|
6172
|
+
} // 最近使用次数小于R_COUNT,清除
|
|
4896
6173
|
let useCount = 0;
|
|
4897
6174
|
while (now - item.timestamp[item.timestamp.length - 1 - useCount] < L_TIME) {
|
|
4898
6175
|
useCount++;
|
|
@@ -4902,7 +6179,8 @@
|
|
|
4902
6179
|
}
|
|
4903
6180
|
if (useCount < R_COUNT) {
|
|
4904
6181
|
return clear(key);
|
|
4905
|
-
}
|
|
6182
|
+
} // 最近L_TIME以内使用次数小于R_COUNT,清除
|
|
6183
|
+
// 清除L_TIME以外的key,避免数组过长
|
|
4906
6184
|
while (now - item.timestamp[0] > L_TIME) {
|
|
4907
6185
|
item.timestamp.shift();
|
|
4908
6186
|
}
|
|
@@ -4913,6 +6191,7 @@
|
|
|
4913
6191
|
addLimitedTimestamp(cacheItem, t, params) {
|
|
4914
6192
|
const { R_TIMESTAMP_MAX_SIZE = this.R_TIMESTAMP_MAX_SIZE } = params;
|
|
4915
6193
|
if (cacheItem.timestamp.length > R_TIMESTAMP_MAX_SIZE) {
|
|
6194
|
+
// cacheItem.timestamp[cacheItem.timestamp.length-1] =
|
|
4916
6195
|
cacheItem.timestamp.shift();
|
|
4917
6196
|
}
|
|
4918
6197
|
cacheItem.timestamp.push(t);
|
|
@@ -4984,6 +6263,7 @@
|
|
|
4984
6263
|
r /= 255;
|
|
4985
6264
|
g /= 255;
|
|
4986
6265
|
b /= 255;
|
|
6266
|
+
// Find greatest and smallest channel values
|
|
4987
6267
|
const cMin = Math.min(r, g, b);
|
|
4988
6268
|
const cMax = Math.max(r, g, b);
|
|
4989
6269
|
const delta = cMax - cMin;
|
|
@@ -4993,12 +6273,15 @@
|
|
|
4993
6273
|
if (delta === 0) {
|
|
4994
6274
|
h = 0;
|
|
4995
6275
|
}
|
|
6276
|
+
// Red is max
|
|
4996
6277
|
else if (cMax === r) {
|
|
4997
6278
|
h = ((g - b) / delta) % 6;
|
|
4998
6279
|
}
|
|
6280
|
+
// Green is max
|
|
4999
6281
|
else if (cMax === g) {
|
|
5000
6282
|
h = (b - r) / delta + 2;
|
|
5001
6283
|
}
|
|
6284
|
+
// Blue is max
|
|
5002
6285
|
else {
|
|
5003
6286
|
h = (r - g) / delta + 4;
|
|
5004
6287
|
}
|
|
@@ -5007,7 +6290,9 @@
|
|
|
5007
6290
|
h += 360;
|
|
5008
6291
|
}
|
|
5009
6292
|
l = (cMax + cMin) / 2;
|
|
6293
|
+
// Calculate saturation
|
|
5010
6294
|
s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
|
|
6295
|
+
// Multiply l and s by 100
|
|
5011
6296
|
s = +(s * 100).toFixed(1);
|
|
5012
6297
|
l = +(l * 100).toFixed(1);
|
|
5013
6298
|
return {
|
|
@@ -5017,6 +6302,34 @@
|
|
|
5017
6302
|
};
|
|
5018
6303
|
}
|
|
5019
6304
|
|
|
6305
|
+
/*
|
|
6306
|
+
Copyright (c) 2013, Jason Davies.
|
|
6307
|
+
All rights reserved.
|
|
6308
|
+
|
|
6309
|
+
Redistribution and use in source and binary forms, with or without
|
|
6310
|
+
modification, are permitted provided that the following conditions are met:
|
|
6311
|
+
|
|
6312
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
6313
|
+
list of conditions and the following disclaimer.
|
|
6314
|
+
|
|
6315
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
6316
|
+
this list of conditions and the following disclaimer in the documentation
|
|
6317
|
+
and/or other materials provided with the distribution.
|
|
6318
|
+
|
|
6319
|
+
* The name Jason Davies may not be used to endorse or promote products
|
|
6320
|
+
derived from this software without specific prior written permission.
|
|
6321
|
+
|
|
6322
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
6323
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
6324
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
6325
|
+
DISCLAIMED. IN NO EVENT SHALL JASON DAVIES BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
6326
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
6327
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
6328
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
6329
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
6330
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
6331
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
6332
|
+
*/
|
|
5020
6333
|
const REG_HEX = /^#([0-9a-f]{3,8})$/;
|
|
5021
6334
|
const DEFAULT_COLORS_OPACITY = {
|
|
5022
6335
|
transparent: 0xffffff00
|
|
@@ -5182,6 +6495,7 @@
|
|
|
5182
6495
|
else if (isArray(value)) {
|
|
5183
6496
|
return new RGB(value[0], value[1], value[2]);
|
|
5184
6497
|
}
|
|
6498
|
+
// default return
|
|
5185
6499
|
return new RGB(255, 255, 255);
|
|
5186
6500
|
}
|
|
5187
6501
|
function rgba(value) {
|
|
@@ -5191,6 +6505,7 @@
|
|
|
5191
6505
|
else if (isArray(value)) {
|
|
5192
6506
|
return new RGB(value[0], value[1], value[2], value[3]);
|
|
5193
6507
|
}
|
|
6508
|
+
// default return
|
|
5194
6509
|
return new RGB(255, 255, 255, 1);
|
|
5195
6510
|
}
|
|
5196
6511
|
function SRGBToLinear(c) {
|
|
@@ -5204,12 +6519,15 @@
|
|
|
5204
6519
|
if (forceHex || isHex) {
|
|
5205
6520
|
const hex = parseInt(isHex[1], 16);
|
|
5206
6521
|
const hexLength = isHex[1].length;
|
|
6522
|
+
// #fff
|
|
5207
6523
|
if (hexLength === 3) {
|
|
5208
6524
|
return new RGB(((hex >> 8) & 0xf) + (((hex >> 8) & 0xf) << 4), ((hex >> 4) & 0xf) + (((hex >> 4) & 0xf) << 4), (hex & 0xf) + ((hex & 0xf) << 4), 1);
|
|
5209
6525
|
}
|
|
6526
|
+
// #ffffff
|
|
5210
6527
|
if (hexLength === 6) {
|
|
5211
6528
|
return rgb(hex);
|
|
5212
6529
|
}
|
|
6530
|
+
// #ffffffaa
|
|
5213
6531
|
else if (hexLength === 8) {
|
|
5214
6532
|
return new RGB((hex >> 24) & 0xff, (hex >> 16) & 0xff, (hex >> 8) & 0xff, (hex & 0xff) / 0xff);
|
|
5215
6533
|
}
|
|
@@ -5250,21 +6568,26 @@
|
|
|
5250
6568
|
}
|
|
5251
6569
|
}
|
|
5252
6570
|
static parseColorString(value) {
|
|
6571
|
+
// case0: transparent
|
|
5253
6572
|
if (isValid(DEFAULT_COLORS_OPACITY[value])) {
|
|
5254
6573
|
return rgba(DEFAULT_COLORS_OPACITY[value]);
|
|
5255
6574
|
}
|
|
6575
|
+
// case1: 简写 ['blue','red']
|
|
5256
6576
|
if (isValid(DEFAULT_COLORS[value])) {
|
|
5257
6577
|
return rgb(DEFAULT_COLORS[value]);
|
|
5258
6578
|
}
|
|
5259
6579
|
const formatValue = `${value}`.trim().toLowerCase();
|
|
6580
|
+
// case2: 16进制
|
|
5260
6581
|
const hexRes = setHex(formatValue);
|
|
5261
6582
|
if (hexRes !== undefined) {
|
|
5262
6583
|
return hexRes;
|
|
5263
6584
|
}
|
|
6585
|
+
// case3 : rgb
|
|
5264
6586
|
if (/^(rgb|RGB|rgba|RGBA)/.test(formatValue)) {
|
|
5265
6587
|
const aColor = formatValue.replace(/(?:\(|\)|rgba|RGBA|rgb|RGB)*/g, '').split(',');
|
|
5266
6588
|
return new RGB(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10), parseFloat(aColor[3]));
|
|
5267
6589
|
}
|
|
6590
|
+
// case4: hsl
|
|
5268
6591
|
if (/^(hsl|HSL|hsla|HSLA)/.test(formatValue)) {
|
|
5269
6592
|
const aColor = formatValue.replace(/(?:\(|\)|hsla|HSLA|hsl|HSL)*/g, '').split(',');
|
|
5270
6593
|
const rgb = hslToRgb(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10));
|
|
@@ -5274,10 +6597,12 @@
|
|
|
5274
6597
|
}
|
|
5275
6598
|
constructor(value) {
|
|
5276
6599
|
const color = Color.parseColorString(value);
|
|
6600
|
+
// case0: transparent
|
|
5277
6601
|
if (color) {
|
|
5278
6602
|
this.color = color;
|
|
5279
6603
|
}
|
|
5280
6604
|
else {
|
|
6605
|
+
// default: 白色
|
|
5281
6606
|
console.warn(`Warn: 传入${value}无法解析为Color`);
|
|
5282
6607
|
this.color = new RGB(255, 255, 255);
|
|
5283
6608
|
}
|
|
@@ -5321,6 +6646,7 @@
|
|
|
5321
6646
|
this.color.b = Math.max(0, Math.min(255, Math.floor(b * color.color.b)));
|
|
5322
6647
|
return this;
|
|
5323
6648
|
}
|
|
6649
|
+
// todo: 这里亮度计算都没考虑alpha通道
|
|
5324
6650
|
getHSVBrightness() {
|
|
5325
6651
|
return Math.max(this.color.r, this.color.g, this.color.b) / 255;
|
|
5326
6652
|
}
|
|
@@ -5348,11 +6674,14 @@
|
|
|
5348
6674
|
return res !== null && res !== void 0 ? res : this;
|
|
5349
6675
|
}
|
|
5350
6676
|
setColorName(name) {
|
|
6677
|
+
// color keywords
|
|
5351
6678
|
const hex = DEFAULT_COLORS[name.toLowerCase()];
|
|
5352
6679
|
if (typeof hex !== 'undefined') {
|
|
6680
|
+
// red
|
|
5353
6681
|
this.setHex(hex);
|
|
5354
6682
|
}
|
|
5355
6683
|
else {
|
|
6684
|
+
// unknown color
|
|
5356
6685
|
console.warn('THREE.Color: Unknown color ' + name);
|
|
5357
6686
|
}
|
|
5358
6687
|
return this;
|
|
@@ -5376,6 +6705,9 @@
|
|
|
5376
6705
|
getLuminance3() {
|
|
5377
6706
|
return (0.299 * this.color.r + 0.587 * this.color.g + 0.114 * this.color.b) / 255;
|
|
5378
6707
|
}
|
|
6708
|
+
/**
|
|
6709
|
+
* https://www.w3.org/TR/WCAG21/#use-of-color
|
|
6710
|
+
*/
|
|
5379
6711
|
getLuminanceWCAG() {
|
|
5380
6712
|
const RsRGB = this.color.r / 255;
|
|
5381
6713
|
const GsRGB = this.color.g / 255;
|
|
@@ -5461,6 +6793,7 @@
|
|
|
5461
6793
|
this.opacity = 1;
|
|
5462
6794
|
}
|
|
5463
6795
|
}
|
|
6796
|
+
// 转为16进制
|
|
5464
6797
|
formatHex() {
|
|
5465
6798
|
return `#${hex(this.r) + hex(this.g) + hex(this.b) + (this.opacity === 1 ? '' : hex(this.opacity * 255))}`;
|
|
5466
6799
|
}
|
|
@@ -5473,11 +6806,17 @@
|
|
|
5473
6806
|
const { h, s, l } = rgbToHsl(this.r, this.g, this.b);
|
|
5474
6807
|
return `${opacity === 1 ? 'hsl(' : 'hsla('}${h},${s}%,${l}%${opacity === 1 ? ')' : `,${opacity})`}`;
|
|
5475
6808
|
}
|
|
6809
|
+
// 转为16进制
|
|
5476
6810
|
toString() {
|
|
5477
6811
|
return this.formatHex();
|
|
5478
6812
|
}
|
|
5479
6813
|
}
|
|
5480
6814
|
|
|
6815
|
+
/**
|
|
6816
|
+
* 将 hex 格式颜色转换为 rgb 格式
|
|
6817
|
+
* @param str hex 格式的颜色值
|
|
6818
|
+
* @returns rgb 格式
|
|
6819
|
+
*/
|
|
5481
6820
|
function hexToRgb(str) {
|
|
5482
6821
|
let r = '';
|
|
5483
6822
|
let g = '';
|
|
@@ -5539,6 +6878,11 @@
|
|
|
5539
6878
|
rgbToHsl: rgbToHsl
|
|
5540
6879
|
});
|
|
5541
6880
|
|
|
6881
|
+
/**
|
|
6882
|
+
* 将 padding 转换为通用的格式([上,右,下,左])
|
|
6883
|
+
* @param padding
|
|
6884
|
+
* @return [ top, right, bottom, left ]
|
|
6885
|
+
*/
|
|
5542
6886
|
function normalizePadding(padding) {
|
|
5543
6887
|
if (isValidNumber(padding)) {
|
|
5544
6888
|
return [padding, padding, padding, padding];
|
|
@@ -5610,6 +6954,12 @@
|
|
|
5610
6954
|
function millisecondsSetterName(isUTC) {
|
|
5611
6955
|
return isUTC ? 'setUTCMilliseconds' : 'setMilliseconds';
|
|
5612
6956
|
}
|
|
6957
|
+
/**
|
|
6958
|
+
* 根据时间推断格式化字符串
|
|
6959
|
+
* @param value
|
|
6960
|
+
* @param isUTC
|
|
6961
|
+
* @returns
|
|
6962
|
+
*/
|
|
5613
6963
|
function getFormatFromValue(value, isUTC) {
|
|
5614
6964
|
const date = toDate(value);
|
|
5615
6965
|
const M = date[monthGetterName(isUTC)]() + 1;
|
|
@@ -5657,15 +7007,20 @@
|
|
|
5657
7007
|
const m = date[minutesGetterName(isUTC)]();
|
|
5658
7008
|
const s = date[secondsGetterName(isUTC)]();
|
|
5659
7009
|
const S = date[millisecondsGetterName(isUTC)]();
|
|
7010
|
+
// 月份,周等需要管理管理locale的时间格式化暂时不支持
|
|
5660
7011
|
return ((template || '')
|
|
5661
7012
|
.replace(/YYYY/g, pad(y + '', 4, '0', 'left'))
|
|
5662
7013
|
.replace(/yyyy/g, y + '')
|
|
5663
7014
|
.replace(/yy/g, (y % 100) + '')
|
|
5664
7015
|
.replace(/Q/g, q + '')
|
|
7016
|
+
// .replace(/{MMMM}/g, month[M - 1])
|
|
7017
|
+
// .replace(/{MMM}/g, monthAbbr[M - 1])
|
|
5665
7018
|
.replace(/MM/g, pad(M, 2, '0', 'left'))
|
|
5666
7019
|
.replace(/M/g, M + '')
|
|
5667
7020
|
.replace(/dd/g, pad(d, 2, '0', 'left'))
|
|
5668
7021
|
.replace(/d/g, d + '')
|
|
7022
|
+
// .replace(/{eeee}/g, dayOfWeek[e])
|
|
7023
|
+
// .replace(/{ee}/g, dayOfWeekAbbr[e])
|
|
5669
7024
|
.replace(/e/g, e + '')
|
|
5670
7025
|
.replace(/HH/g, pad(H, 2, '0', 'left'))
|
|
5671
7026
|
.replace(/H/g, H + '')
|
|
@@ -5967,9 +7322,14 @@
|
|
|
5967
7322
|
};
|
|
5968
7323
|
}
|
|
5969
7324
|
const style = getComputedStyle(el);
|
|
7325
|
+
// clientWidth/clientHeight: 默认整数,会向上取整,导致canvas > container
|
|
7326
|
+
// getBoundingClientRect:默认小数,但是在container上有css类似 transform: scale(0.5)时,获取结果不对
|
|
7327
|
+
// getComputedStyle:默认小数,获取最终结果,但是会包含padding;
|
|
5970
7328
|
if (/^(\d*\.?\d+)(px)$/.exec(style.width)) {
|
|
7329
|
+
// 当dom元素的 display: 'none' 的时候,获取到的宽高会变成原始的样式配置,而不是计算后的像素值
|
|
5971
7330
|
const computedWidth = parseFloat(style.width) - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight) || el.clientWidth - 1;
|
|
5972
7331
|
const computedHeight = parseFloat(style.height) - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom) || el.clientHeight - 1;
|
|
7332
|
+
// 理论上不用向下取整,目前没加。
|
|
5973
7333
|
return {
|
|
5974
7334
|
width: computedWidth <= 0 ? defaultWidth : computedWidth,
|
|
5975
7335
|
height: computedHeight <= 0 ? defaultHeight : computedHeight
|
|
@@ -5977,10 +7337,20 @@
|
|
|
5977
7337
|
}
|
|
5978
7338
|
return { width: defaultWidth, height: defaultHeight };
|
|
5979
7339
|
}
|
|
7340
|
+
/**
|
|
7341
|
+
* 获取元素的绝对位置坐标(相对于页面左上角)
|
|
7342
|
+
* @param element
|
|
7343
|
+
* @returns
|
|
7344
|
+
*/
|
|
5980
7345
|
function getElementAbsolutePosition(element) {
|
|
5981
7346
|
const { x, y } = element.getBoundingClientRect();
|
|
5982
7347
|
return { x, y };
|
|
5983
7348
|
}
|
|
7349
|
+
/**
|
|
7350
|
+
* 获取元素的相对位置坐标(相对于其他dom元素)
|
|
7351
|
+
* @param element
|
|
7352
|
+
* @returns
|
|
7353
|
+
*/
|
|
5984
7354
|
function getElementRelativePosition(element, base) {
|
|
5985
7355
|
const posElement = getElementAbsolutePosition(element);
|
|
5986
7356
|
const posBase = getElementAbsolutePosition(base);
|
|
@@ -6012,12 +7382,23 @@
|
|
|
6012
7382
|
const getScaleY = (element) => {
|
|
6013
7383
|
return element.getBoundingClientRect().height / element.offsetHeight;
|
|
6014
7384
|
};
|
|
7385
|
+
/**
|
|
7386
|
+
* 获取目标元素的缩放因数
|
|
7387
|
+
* @param element 目标 dom 元素
|
|
7388
|
+
* @returns
|
|
7389
|
+
*/
|
|
6015
7390
|
const getScale = (element) => {
|
|
6016
7391
|
if (element.offsetWidth > 0) {
|
|
6017
7392
|
return getScaleX(element);
|
|
6018
7393
|
}
|
|
6019
7394
|
return getScaleY(element);
|
|
6020
7395
|
};
|
|
7396
|
+
/**
|
|
7397
|
+
* 判断是否是元素的父元素
|
|
7398
|
+
* @param element 从其父元素开始查找的 DOM 元素
|
|
7399
|
+
* @param target 要查找的目标父元素
|
|
7400
|
+
* @returns boolean, true 代表查找到,false 表示未找到
|
|
7401
|
+
*/
|
|
6021
7402
|
function hasParentElement(element, target) {
|
|
6022
7403
|
let parent = element.parentNode;
|
|
6023
7404
|
while (parent !== null) {
|
|
@@ -6028,6 +7409,11 @@
|
|
|
6028
7409
|
}
|
|
6029
7410
|
return false;
|
|
6030
7411
|
}
|
|
7412
|
+
/**
|
|
7413
|
+
* 将style字符串转换成对象形式
|
|
7414
|
+
* @param style字符串 例如:"color:red;line-height:20px;"
|
|
7415
|
+
* @returns style对象,如 { color: 'red', 'line-height': '20px' }
|
|
7416
|
+
*/
|
|
6031
7417
|
const styleStringToObject = (styleStr = '') => {
|
|
6032
7418
|
const res = {};
|
|
6033
7419
|
styleStr.split(';').forEach(item => {
|
|
@@ -6044,9 +7430,19 @@
|
|
|
6044
7430
|
});
|
|
6045
7431
|
return res;
|
|
6046
7432
|
};
|
|
7433
|
+
/**
|
|
7434
|
+
* 将小驼峰转换成中划线连接的字符串
|
|
7435
|
+
* @param str 如:'lineHeight' => 'line-height'
|
|
7436
|
+
* @returns
|
|
7437
|
+
*/
|
|
6047
7438
|
const lowerCamelCaseToMiddle = (str) => {
|
|
6048
7439
|
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
6049
7440
|
};
|
|
7441
|
+
/**
|
|
7442
|
+
* 将中划线连接的字符串转换成驼峰字符串
|
|
7443
|
+
* @param str 如:'line-height' => 'lineHeight'
|
|
7444
|
+
* @returns
|
|
7445
|
+
*/
|
|
6050
7446
|
function toCamelCase(str) {
|
|
6051
7447
|
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
6052
7448
|
}
|
|
@@ -6055,6 +7451,7 @@
|
|
|
6055
7451
|
return obj instanceof Element;
|
|
6056
7452
|
}
|
|
6057
7453
|
catch (_a) {
|
|
7454
|
+
// 跨端 plan B
|
|
6058
7455
|
const htmlElementKeys = [
|
|
6059
7456
|
'children',
|
|
6060
7457
|
'innerHTML',
|
|
@@ -6122,6 +7519,29 @@
|
|
|
6122
7519
|
return distance / factor;
|
|
6123
7520
|
}
|
|
6124
7521
|
|
|
7522
|
+
/**
|
|
7523
|
+
* The MIT License (MIT)
|
|
7524
|
+
*
|
|
7525
|
+
* Copyright (c) 2019 Morgan Herlocker
|
|
7526
|
+
*
|
|
7527
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7528
|
+
* this software and associated documentation files (the "Software"), to deal in
|
|
7529
|
+
* the Software without restriction, including without limitation the rights to
|
|
7530
|
+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
7531
|
+
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
7532
|
+
* subject to the following conditions:
|
|
7533
|
+
*
|
|
7534
|
+
* The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.
|
|
7535
|
+
*
|
|
7536
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
7537
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
7538
|
+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
7539
|
+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
7540
|
+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
7541
|
+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
7542
|
+
*
|
|
7543
|
+
*/
|
|
7544
|
+
// https://github.com/Turfjs/turf
|
|
6125
7545
|
function getGeom(geojson) {
|
|
6126
7546
|
if (geojson.type === 'Feature') {
|
|
6127
7547
|
return geojson.geometry;
|
|
@@ -6137,6 +7557,7 @@
|
|
|
6137
7557
|
}
|
|
6138
7558
|
const geom = getGeom(polygon);
|
|
6139
7559
|
const type = geom.type;
|
|
7560
|
+
// BBox [west, south, east, north]
|
|
6140
7561
|
const bbox = polygon.bbox;
|
|
6141
7562
|
let polys = geom.coordinates;
|
|
6142
7563
|
if (bbox && pointInRect(point, { x1: bbox[0], x2: bbox[1], y1: bbox[1], y2: bbox[3] }, true) === true) {
|
|
@@ -6157,11 +7578,13 @@
|
|
|
6157
7578
|
}
|
|
6158
7579
|
return result;
|
|
6159
7580
|
}
|
|
7581
|
+
// https://github.com/Turfjs/turf
|
|
6160
7582
|
function destination(point, distance, bearing, options = {}) {
|
|
6161
7583
|
const longitude1 = degreeToRadian(point[0]);
|
|
6162
7584
|
const latitude1 = degreeToRadian(point[1]);
|
|
6163
7585
|
const bearingRad = degreeToRadian(bearing);
|
|
6164
7586
|
const radians = lengthToRadians(distance, options.units);
|
|
7587
|
+
// Main
|
|
6165
7588
|
const latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(radians) + Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearingRad));
|
|
6166
7589
|
const longitude2 = longitude1 +
|
|
6167
7590
|
Math.atan2(Math.sin(bearingRad) * Math.sin(radians) * Math.cos(latitude1), Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2));
|
|
@@ -6170,17 +7593,41 @@
|
|
|
6170
7593
|
return { x: lng, y: lat };
|
|
6171
7594
|
}
|
|
6172
7595
|
|
|
7596
|
+
/* Adapted from venn.js by Ben Frederickson
|
|
7597
|
+
* https://github.com/benfred/venn.js
|
|
7598
|
+
* Licensed under the MIT
|
|
7599
|
+
|
|
7600
|
+
* url: https://github.com/benfred/venn.js/blob/master/src/circleintersection.js
|
|
7601
|
+
* License: https://github.com/benfred/venn.js/blob/master/LICENSE
|
|
7602
|
+
* @license
|
|
7603
|
+
*/
|
|
6173
7604
|
const SMALL = 1e-10;
|
|
6174
7605
|
|
|
7606
|
+
/* Adapted from venn.js by Ben Frederickson
|
|
7607
|
+
* https://github.com/benfred/venn.js
|
|
7608
|
+
* Licensed under the MIT
|
|
7609
|
+
|
|
7610
|
+
* url: https://github.com/benfred/venn.js/blob/master/src/circleintersection.js
|
|
7611
|
+
* License: https://github.com/benfred/venn.js/blob/master/LICENSE
|
|
7612
|
+
* @license
|
|
7613
|
+
*/
|
|
7614
|
+
/** Returns the intersection area of a bunch of circles (where each circle
|
|
7615
|
+
is an object having an x,y and radius property) */
|
|
6175
7616
|
function intersectionArea(circles, stats) {
|
|
7617
|
+
// get all the intersection points of the circles
|
|
6176
7618
|
const intersectionPoints = getIntersectionPoints(circles);
|
|
7619
|
+
// filter out points that aren't included in all the circles
|
|
6177
7620
|
const innerPoints = intersectionPoints.filter(function (p) {
|
|
6178
7621
|
return containedInCircles(p, circles);
|
|
6179
7622
|
});
|
|
6180
7623
|
let arcArea = 0;
|
|
6181
7624
|
let polygonArea = 0;
|
|
6182
7625
|
const arcs = [];
|
|
7626
|
+
// if we have intersection points that are within all the circles,
|
|
7627
|
+
// then figure out the area contained by them
|
|
6183
7628
|
if (innerPoints.length > 1) {
|
|
7629
|
+
// sort the points by angle from the center of the polygon, which lets
|
|
7630
|
+
// us just iterate over points to get the edges
|
|
6184
7631
|
const center = getCenter(innerPoints);
|
|
6185
7632
|
for (let i = 0; i < innerPoints.length; ++i) {
|
|
6186
7633
|
const p = innerPoints[i];
|
|
@@ -6189,14 +7636,20 @@
|
|
|
6189
7636
|
innerPoints.sort(function (a, b) {
|
|
6190
7637
|
return b.angle - a.angle;
|
|
6191
7638
|
});
|
|
7639
|
+
// iterate over all points, get arc between the points
|
|
7640
|
+
// and update the areas
|
|
6192
7641
|
let p2 = innerPoints[innerPoints.length - 1];
|
|
6193
7642
|
for (let i = 0; i < innerPoints.length; ++i) {
|
|
6194
7643
|
const p1 = innerPoints[i];
|
|
7644
|
+
// polygon area updates easily ...
|
|
6195
7645
|
polygonArea += (p2.x + p1.x) * (p1.y - p2.y);
|
|
7646
|
+
// updating the arc area is a little more involved
|
|
6196
7647
|
const midPoint = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
|
|
6197
7648
|
let arc = null;
|
|
6198
7649
|
for (let j = 0; j < p1.parentIndex.length; ++j) {
|
|
6199
7650
|
if (p2.parentIndex.indexOf(p1.parentIndex[j]) > -1) {
|
|
7651
|
+
// figure out the angle halfway between the two points
|
|
7652
|
+
// on the current circle
|
|
6200
7653
|
const circle = circles[p1.parentIndex[j]];
|
|
6201
7654
|
const a1 = Math.atan2(p1.x - circle.x, p1.y - circle.y);
|
|
6202
7655
|
const a2 = Math.atan2(p2.x - circle.x, p2.y - circle.y);
|
|
@@ -6204,14 +7657,19 @@
|
|
|
6204
7657
|
if (angleDiff < 0) {
|
|
6205
7658
|
angleDiff += 2 * Math.PI;
|
|
6206
7659
|
}
|
|
7660
|
+
// and use that angle to figure out the width of the
|
|
7661
|
+
// arc
|
|
6207
7662
|
const a = a2 - angleDiff / 2;
|
|
6208
7663
|
let width = PointService.distancePP(midPoint, {
|
|
6209
7664
|
x: circle.x + circle.radius * Math.sin(a),
|
|
6210
7665
|
y: circle.y + circle.radius * Math.cos(a)
|
|
6211
7666
|
});
|
|
7667
|
+
// clamp the width to the largest is can actually be
|
|
7668
|
+
// (sometimes slightly overflows because of FP errors)
|
|
6212
7669
|
if (width > circle.radius * 2) {
|
|
6213
7670
|
width = circle.radius * 2;
|
|
6214
7671
|
}
|
|
7672
|
+
// pick the circle whose arc has the smallest width
|
|
6215
7673
|
if (arc === null || arc.width > width) {
|
|
6216
7674
|
arc = { circle: circle, width: width, p1: p1, p2: p2 };
|
|
6217
7675
|
}
|
|
@@ -6225,12 +7683,16 @@
|
|
|
6225
7683
|
}
|
|
6226
7684
|
}
|
|
6227
7685
|
else {
|
|
7686
|
+
// no intersection points, is either disjoint - or is completely
|
|
7687
|
+
// overlapped. figure out which by examining the smallest circle
|
|
6228
7688
|
let smallest = circles[0];
|
|
6229
7689
|
for (let i = 1; i < circles.length; ++i) {
|
|
6230
7690
|
if (circles[i].radius < smallest.radius) {
|
|
6231
7691
|
smallest = circles[i];
|
|
6232
7692
|
}
|
|
6233
7693
|
}
|
|
7694
|
+
// make sure the smallest circle is completely contained in all
|
|
7695
|
+
// the other circles
|
|
6234
7696
|
let disjoint = false;
|
|
6235
7697
|
for (let i = 0; i < circles.length; ++i) {
|
|
6236
7698
|
if (PointService.distancePP(circles[i], smallest) > Math.abs(smallest.radius - circles[i].radius)) {
|
|
@@ -6262,6 +7724,7 @@
|
|
|
6262
7724
|
}
|
|
6263
7725
|
return arcArea + polygonArea;
|
|
6264
7726
|
}
|
|
7727
|
+
/** returns whether a point is contained by all of a list of circles */
|
|
6265
7728
|
function containedInCircles(point, circles) {
|
|
6266
7729
|
for (let i = 0; i < circles.length; ++i) {
|
|
6267
7730
|
if (PointService.distancePP(point, circles[i]) > circles[i].radius + SMALL) {
|
|
@@ -6270,6 +7733,7 @@
|
|
|
6270
7733
|
}
|
|
6271
7734
|
return true;
|
|
6272
7735
|
}
|
|
7736
|
+
/** Gets all intersection points between a bunch of circles */
|
|
6273
7737
|
function getIntersectionPoints(circles) {
|
|
6274
7738
|
const ret = [];
|
|
6275
7739
|
for (let i = 0; i < circles.length; ++i) {
|
|
@@ -6284,13 +7748,19 @@
|
|
|
6284
7748
|
}
|
|
6285
7749
|
return ret;
|
|
6286
7750
|
}
|
|
7751
|
+
/** Circular segment area calculation. See http://mathworld.wolfram.com/CircularSegment.html */
|
|
6287
7752
|
function circleArea(r, width) {
|
|
6288
7753
|
return r * r * Math.acos(1 - width / r) - (r - width) * Math.sqrt(width * (2 * r - width));
|
|
6289
7754
|
}
|
|
7755
|
+
/** Returns the overlap area of two circles of radius r1 and r2 - that
|
|
7756
|
+
have their centers separated by distance d. Simpler faster
|
|
7757
|
+
circle intersection for only two circles */
|
|
6290
7758
|
function circleOverlap(r1, r2, d) {
|
|
7759
|
+
// no overlap
|
|
6291
7760
|
if (d >= r1 + r2) {
|
|
6292
7761
|
return 0;
|
|
6293
7762
|
}
|
|
7763
|
+
// completely overlapped
|
|
6294
7764
|
if (d <= Math.abs(r1 - r2)) {
|
|
6295
7765
|
return Math.PI * Math.min(r1, r2) * Math.min(r1, r2);
|
|
6296
7766
|
}
|
|
@@ -6298,10 +7768,15 @@
|
|
|
6298
7768
|
const w2 = r2 - (d * d - r1 * r1 + r2 * r2) / (2 * d);
|
|
6299
7769
|
return circleArea(r1, w1) + circleArea(r2, w2);
|
|
6300
7770
|
}
|
|
7771
|
+
/** Given two circles (containing a x/y/radius attributes),
|
|
7772
|
+
returns the intersecting points if possible.
|
|
7773
|
+
note: doesn't handle cases where there are infinitely many
|
|
7774
|
+
intersection points (circles are equivalent):, or only one intersection point*/
|
|
6301
7775
|
function circleCircleIntersection(p1, p2) {
|
|
6302
7776
|
const d = PointService.distancePP(p1, p2);
|
|
6303
7777
|
const r1 = p1.radius;
|
|
6304
7778
|
const r2 = p2.radius;
|
|
7779
|
+
// if to far away, or self contained - can't be done
|
|
6305
7780
|
if (d >= r1 + r2 || d <= Math.abs(r1 - r2)) {
|
|
6306
7781
|
return [];
|
|
6307
7782
|
}
|
|
@@ -6316,6 +7791,7 @@
|
|
|
6316
7791
|
{ x: x0 - rx, y: y0 + ry }
|
|
6317
7792
|
];
|
|
6318
7793
|
}
|
|
7794
|
+
/** Returns the center of a bunch of points */
|
|
6319
7795
|
function getCenter(points) {
|
|
6320
7796
|
const center = { x: 0, y: 0 };
|
|
6321
7797
|
for (let i = 0; i < points.length; ++i) {
|
|
@@ -6327,6 +7803,14 @@
|
|
|
6327
7803
|
return center;
|
|
6328
7804
|
}
|
|
6329
7805
|
|
|
7806
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
7807
|
+
* https://github.com/d3/d3-time-format
|
|
7808
|
+
* Licensed under the ISC
|
|
7809
|
+
|
|
7810
|
+
* url: https://github.com/d3/d3-time-format/blob/d6feb945baa4b7a45898a9ca926ebca044e20657/src/locale.js
|
|
7811
|
+
* License: https://github.com/d3/d3-time-format/blob/main/LICENSE
|
|
7812
|
+
* @license
|
|
7813
|
+
*/
|
|
6330
7814
|
class TimeUtil {
|
|
6331
7815
|
static getInstance() {
|
|
6332
7816
|
if (!TimeUtil.instance) {
|
|
@@ -6339,8 +7823,9 @@
|
|
|
6339
7823
|
this.locale_periods = ['AM', 'PM'];
|
|
6340
7824
|
this.locale_weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
6341
7825
|
this.locale_shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
6342
|
-
this.numberRe = /^\s*\d+/;
|
|
7826
|
+
this.numberRe = /^\s*\d+/; // note: ignores next directive
|
|
6343
7827
|
this.pads = { '-': '', _: ' ', '0': '0' };
|
|
7828
|
+
// const percentRe = /^%/;
|
|
6344
7829
|
this.requoteRe = /[\\^$*+?|[\]().{}]/g;
|
|
6345
7830
|
this.locale_months = [
|
|
6346
7831
|
'January',
|
|
@@ -6617,18 +8102,22 @@
|
|
|
6617
8102
|
if (i !== string.length) {
|
|
6618
8103
|
return null;
|
|
6619
8104
|
}
|
|
8105
|
+
// If a UNIX timestamp is specified, return it.
|
|
6620
8106
|
if ('Q' in d) {
|
|
6621
8107
|
return new Date(d.Q);
|
|
6622
8108
|
}
|
|
6623
8109
|
if ('s' in d) {
|
|
6624
8110
|
return new Date(d.s * 1000 + ('L' in d ? d.L : 0));
|
|
6625
8111
|
}
|
|
8112
|
+
// If this is utcParse, never use the local timezone.
|
|
6626
8113
|
if (Z && !('Z' in d)) {
|
|
6627
8114
|
d.Z = 0;
|
|
6628
8115
|
}
|
|
8116
|
+
// The am-pm flag is 0 for AM, and 1 for PM.
|
|
6629
8117
|
if ('p' in d) {
|
|
6630
8118
|
d.H = (d.H % 12) + d.p * 12;
|
|
6631
8119
|
}
|
|
8120
|
+
// If the month was not specified, inherit from the quarter.
|
|
6632
8121
|
if (d.m === undefined) {
|
|
6633
8122
|
d.m = 'q' in d ? d.q : 0;
|
|
6634
8123
|
}
|
|
@@ -6637,6 +8126,7 @@
|
|
|
6637
8126
|
d.M += d.Z % 100;
|
|
6638
8127
|
return that.utcDate(d);
|
|
6639
8128
|
}
|
|
8129
|
+
// Otherwise, all fields are in local time.
|
|
6640
8130
|
return that.localDate(d);
|
|
6641
8131
|
};
|
|
6642
8132
|
}
|
|
@@ -6678,6 +8168,13 @@
|
|
|
6678
8168
|
}
|
|
6679
8169
|
}
|
|
6680
8170
|
|
|
8171
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8172
|
+
* https://github.com/d3/d3-format
|
|
8173
|
+
* Licensed under the ISC
|
|
8174
|
+
|
|
8175
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8176
|
+
* @license
|
|
8177
|
+
*/
|
|
6681
8178
|
function formatDecimal(x) {
|
|
6682
8179
|
return Math.abs((x = Math.round(x))) >= 1e21 ? x.toLocaleString('en').replace(/,/g, '') : x.toString(10);
|
|
6683
8180
|
}
|
|
@@ -6685,12 +8182,19 @@
|
|
|
6685
8182
|
const _x = p ? x.toExponential(p - 1) : x.toExponential();
|
|
6686
8183
|
const i = _x.indexOf('e');
|
|
6687
8184
|
if (i < 0) {
|
|
6688
|
-
return null;
|
|
8185
|
+
return null; // NaN, ±Infinity
|
|
6689
8186
|
}
|
|
6690
8187
|
const coefficient = _x.slice(0, i);
|
|
6691
8188
|
return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +_x.slice(i + 1)];
|
|
6692
8189
|
}
|
|
6693
8190
|
|
|
8191
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8192
|
+
* https://github.com/d3/d3-format
|
|
8193
|
+
* Licensed under the ISC
|
|
8194
|
+
|
|
8195
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8196
|
+
* @license
|
|
8197
|
+
*/
|
|
6694
8198
|
function formatGroup(grouping, thousands) {
|
|
6695
8199
|
return function (value, width) {
|
|
6696
8200
|
let i = value.length;
|
|
@@ -6712,6 +8216,13 @@
|
|
|
6712
8216
|
};
|
|
6713
8217
|
}
|
|
6714
8218
|
|
|
8219
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8220
|
+
* https://github.com/d3/d3-format
|
|
8221
|
+
* Licensed under the ISC
|
|
8222
|
+
|
|
8223
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8224
|
+
* @license
|
|
8225
|
+
*/
|
|
6715
8226
|
let prefixExponent;
|
|
6716
8227
|
function formatPrefixAuto(x, p) {
|
|
6717
8228
|
const d = formatDecimalParts(x, p);
|
|
@@ -6728,9 +8239,16 @@
|
|
|
6728
8239
|
? coefficient + new Array(i - n + 1).join('0')
|
|
6729
8240
|
: i > 0
|
|
6730
8241
|
? coefficient.slice(0, i) + '.' + coefficient.slice(i)
|
|
6731
|
-
: '0.' + new Array(1 - i).join('0') + formatDecimalParts(x, Math.max(0, p + i - 1))[0];
|
|
8242
|
+
: '0.' + new Array(1 - i).join('0') + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y!
|
|
6732
8243
|
}
|
|
6733
8244
|
|
|
8245
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8246
|
+
* https://github.com/d3/d3-format
|
|
8247
|
+
* Licensed under the ISC
|
|
8248
|
+
|
|
8249
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8250
|
+
* @license
|
|
8251
|
+
*/
|
|
6734
8252
|
function formatRounded(x, p) {
|
|
6735
8253
|
const d = formatDecimalParts(x, p);
|
|
6736
8254
|
if (!d) {
|
|
@@ -6745,6 +8263,13 @@
|
|
|
6745
8263
|
: coefficient + new Array(exponent - coefficient.length + 2).join('0');
|
|
6746
8264
|
}
|
|
6747
8265
|
|
|
8266
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8267
|
+
* https://github.com/d3/d3-format
|
|
8268
|
+
* Licensed under the ISC
|
|
8269
|
+
|
|
8270
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8271
|
+
* @license
|
|
8272
|
+
*/
|
|
6748
8273
|
function formatTrim(s) {
|
|
6749
8274
|
const n = s.length;
|
|
6750
8275
|
let i0 = -1;
|
|
@@ -6820,6 +8345,13 @@
|
|
|
6820
8345
|
});
|
|
6821
8346
|
}
|
|
6822
8347
|
|
|
8348
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8349
|
+
* https://github.com/d3/d3-format
|
|
8350
|
+
* Licensed under the ISC
|
|
8351
|
+
|
|
8352
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8353
|
+
* @license
|
|
8354
|
+
*/
|
|
6823
8355
|
const prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
|
6824
8356
|
class NumberUtil {
|
|
6825
8357
|
constructor() {
|
|
@@ -6868,19 +8400,33 @@
|
|
|
6868
8400
|
let precision = specifierIns.precision;
|
|
6869
8401
|
let trim = specifierIns.trim;
|
|
6870
8402
|
let type = specifierIns.type;
|
|
8403
|
+
// The "n" type is an alias for ",g".
|
|
6871
8404
|
if (type === 'n') {
|
|
6872
8405
|
(comma = true), (type = 'g');
|
|
6873
8406
|
}
|
|
8407
|
+
// The "" type, and any invalid type, is an alias for ".12~g".
|
|
8408
|
+
// @ts-ignore
|
|
6874
8409
|
else if (!formatTypes[type]) {
|
|
6875
8410
|
precision === undefined && (precision = 12), (trim = true), (type = 'g');
|
|
6876
8411
|
}
|
|
8412
|
+
// If zero fill is specified, padding goes after sign and before digits.
|
|
8413
|
+
// @ts-ignore
|
|
6877
8414
|
if (zero || (fill === '0' && align === '=')) {
|
|
6878
8415
|
(zero = true), (fill = '0'), (align = '=');
|
|
6879
8416
|
}
|
|
8417
|
+
// Compute the prefix and suffix.
|
|
8418
|
+
// For SI-prefix, the suffix is lazily computed.
|
|
6880
8419
|
const prefix = symbol === '$' ? this.currencyPrefix : symbol === '#' && /[boxX]/.test(type) ? '0' + type.toLowerCase() : '';
|
|
6881
8420
|
const suffix = symbol === '$' ? this.currencySuffix : /[%p]/.test(type) ? this.percent : '';
|
|
8421
|
+
// What format function should we use?
|
|
8422
|
+
// Is this an integer type?
|
|
8423
|
+
// Can this type generate exponential notation?
|
|
6882
8424
|
const formatType = formatTypes[type];
|
|
6883
8425
|
const maybeSuffix = /[defgprstz%]/.test(type);
|
|
8426
|
+
// Set the default precision if not specified,
|
|
8427
|
+
// or clamp the specified precision to the supported range.
|
|
8428
|
+
// For significant precision, it must be in [1, 21].
|
|
8429
|
+
// For fixed precision, it must be in [0, 20].
|
|
6884
8430
|
precision =
|
|
6885
8431
|
precision === undefined
|
|
6886
8432
|
? 6
|
|
@@ -6901,20 +8447,27 @@
|
|
|
6901
8447
|
}
|
|
6902
8448
|
else {
|
|
6903
8449
|
_value = +_value;
|
|
8450
|
+
// Determine the sign. -0 is not less than 0, but 1 / -0 is!
|
|
6904
8451
|
let valueNegative = _value < 0 || 1 / _value < 0;
|
|
8452
|
+
// Perform the initial formatting.
|
|
6905
8453
|
_value = isNaN(_value) ? nan : formatType(Math.abs(_value), precision);
|
|
8454
|
+
// Trim insignificant zeros.
|
|
6906
8455
|
if (trim) {
|
|
6907
8456
|
_value = formatTrim(_value);
|
|
6908
8457
|
}
|
|
8458
|
+
// If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
|
|
6909
8459
|
if (valueNegative && +_value === 0 && sign !== '+') {
|
|
6910
8460
|
valueNegative = false;
|
|
6911
8461
|
}
|
|
8462
|
+
// Compute the prefix and suffix.
|
|
6912
8463
|
valuePrefix =
|
|
6913
8464
|
(valueNegative ? (sign === '(' ? sign : minus) : sign === '-' || sign === '(' ? '' : sign) + valuePrefix;
|
|
6914
8465
|
valueSuffix =
|
|
6915
8466
|
(type === 's' ? prefixes[8 + prefixExponent / 3] : '') +
|
|
6916
8467
|
valueSuffix +
|
|
6917
8468
|
(valueNegative && sign === '(' ? ')' : '');
|
|
8469
|
+
// Break the formatted value into the integer “value” part that can be
|
|
8470
|
+
// grouped, and fractional or exponential “suffix” part that is not.
|
|
6918
8471
|
if (maybeSuffix) {
|
|
6919
8472
|
(i = -1), (n = _value.length);
|
|
6920
8473
|
while (++i < n) {
|
|
@@ -6926,15 +8479,19 @@
|
|
|
6926
8479
|
}
|
|
6927
8480
|
}
|
|
6928
8481
|
}
|
|
8482
|
+
// If the fill character is not "0", grouping is applied before padding.
|
|
6929
8483
|
if (comma && !zero) {
|
|
6930
8484
|
_value = group(_value, Infinity);
|
|
6931
8485
|
}
|
|
8486
|
+
// Compute the padding.
|
|
6932
8487
|
let length = valuePrefix.length + _value.length + valueSuffix.length;
|
|
6933
8488
|
let padding = length < width ? new Array(width - length + 1).join(fill) : '';
|
|
8489
|
+
// If the fill character is "0", grouping is applied after padding.
|
|
6934
8490
|
if (comma && zero) {
|
|
6935
8491
|
_value = group(padding + _value, padding.length ? width - valueSuffix.length : Infinity);
|
|
6936
8492
|
padding = '';
|
|
6937
8493
|
}
|
|
8494
|
+
// Reconstruct the final output based on the desired alignment.
|
|
6938
8495
|
switch (align) {
|
|
6939
8496
|
case '<':
|
|
6940
8497
|
_value = valuePrefix + _value + valueSuffix + padding;
|
|
@@ -6988,6 +8545,7 @@
|
|
|
6988
8545
|
X: (x) => Math.round(x).toString(16).toUpperCase(),
|
|
6989
8546
|
x: (x) => Math.round(x).toString(16),
|
|
6990
8547
|
t: (x, p) => {
|
|
8548
|
+
// 判断是否为整数
|
|
6991
8549
|
if (Number.isInteger(x)) {
|
|
6992
8550
|
return x.toFixed(2);
|
|
6993
8551
|
}
|
|
@@ -7005,6 +8563,16 @@
|
|
|
7005
8563
|
};
|
|
7006
8564
|
}
|
|
7007
8565
|
|
|
8566
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8567
|
+
* https://github.com/benfred/fmin
|
|
8568
|
+
* Licensed under the BSD-3-Clause
|
|
8569
|
+
|
|
8570
|
+
* url: https://github.com/benfred/fmin/blob/master/src/blas1.js
|
|
8571
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8572
|
+
* @license
|
|
8573
|
+
*/
|
|
8574
|
+
// need some basic operations on vectors, rather than adding a dependency,
|
|
8575
|
+
// just define here
|
|
7008
8576
|
function zeros(x) {
|
|
7009
8577
|
const r = new Array(x);
|
|
7010
8578
|
for (let i = 0; i < x; ++i) {
|
|
@@ -7036,6 +8604,15 @@
|
|
|
7036
8604
|
}
|
|
7037
8605
|
}
|
|
7038
8606
|
|
|
8607
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8608
|
+
* https://github.com/benfred/fmin
|
|
8609
|
+
* Licensed under the BSD-3-Clause
|
|
8610
|
+
|
|
8611
|
+
* url: https://github.com/benfred/fmin/blob/master/src/nelderMead.js
|
|
8612
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8613
|
+
* @license
|
|
8614
|
+
*/
|
|
8615
|
+
/** minimizes a function using the downhill simplex method */
|
|
7039
8616
|
function nelderMead(f, x0, parameters) {
|
|
7040
8617
|
parameters = parameters || {};
|
|
7041
8618
|
const maxIterations = parameters.maxIterations || x0.length * 200;
|
|
@@ -7048,6 +8625,7 @@
|
|
|
7048
8625
|
const psi = parameters.psi !== undefined ? parameters.psi : -0.5;
|
|
7049
8626
|
const sigma = parameters.sigma !== undefined ? parameters.sigma : 0.5;
|
|
7050
8627
|
let maxDiff;
|
|
8628
|
+
// initialize simplex.
|
|
7051
8629
|
const N = x0.length;
|
|
7052
8630
|
const simplex = new Array(N + 1);
|
|
7053
8631
|
simplex[0] = x0;
|
|
@@ -7076,6 +8654,8 @@
|
|
|
7076
8654
|
for (let iteration = 0; iteration < maxIterations; ++iteration) {
|
|
7077
8655
|
simplex.sort(sortOrder);
|
|
7078
8656
|
if (parameters.history) {
|
|
8657
|
+
// copy the simplex (since later iterations will mutate) and
|
|
8658
|
+
// sort it to have a consistent order between iterations
|
|
7079
8659
|
const sortedSimplex = simplex.map(function (x) {
|
|
7080
8660
|
const state = x.slice();
|
|
7081
8661
|
state.fx = x.fx;
|
|
@@ -7094,6 +8674,7 @@
|
|
|
7094
8674
|
if (Math.abs(simplex[0].fx - simplex[N].fx) < minErrorDelta && maxDiff < minTolerance) {
|
|
7095
8675
|
break;
|
|
7096
8676
|
}
|
|
8677
|
+
// compute the centroid of all but the worst point in the simplex
|
|
7097
8678
|
for (let i = 0; i < N; ++i) {
|
|
7098
8679
|
centroid[i] = 0;
|
|
7099
8680
|
for (let j = 0; j < N; ++j) {
|
|
@@ -7101,9 +8682,12 @@
|
|
|
7101
8682
|
}
|
|
7102
8683
|
centroid[i] /= N;
|
|
7103
8684
|
}
|
|
8685
|
+
// reflect the worst point past the centroid and compute loss at reflected
|
|
8686
|
+
// point
|
|
7104
8687
|
const worst = simplex[N];
|
|
7105
8688
|
weightedSum(reflected, 1 + rho, centroid, -rho, worst);
|
|
7106
8689
|
reflected.fx = f(reflected);
|
|
8690
|
+
// if the reflected point is the best seen, then possibly expand
|
|
7107
8691
|
if (reflected.fx < simplex[0].fx) {
|
|
7108
8692
|
weightedSum(expanded, 1 + chi, centroid, -chi, worst);
|
|
7109
8693
|
expanded.fx = f(expanded);
|
|
@@ -7114,9 +8698,12 @@
|
|
|
7114
8698
|
updateSimplex(reflected);
|
|
7115
8699
|
}
|
|
7116
8700
|
}
|
|
8701
|
+
// if the reflected point is worse than the second worst, we need to
|
|
8702
|
+
// contract
|
|
7117
8703
|
else if (reflected.fx >= simplex[N - 1].fx) {
|
|
7118
8704
|
let shouldReduce = false;
|
|
7119
8705
|
if (reflected.fx > worst.fx) {
|
|
8706
|
+
// do an inside contraction
|
|
7120
8707
|
weightedSum(contracted, 1 + psi, centroid, -psi, worst);
|
|
7121
8708
|
contracted.fx = f(contracted);
|
|
7122
8709
|
if (contracted.fx < worst.fx) {
|
|
@@ -7127,6 +8714,7 @@
|
|
|
7127
8714
|
}
|
|
7128
8715
|
}
|
|
7129
8716
|
else {
|
|
8717
|
+
// do an outside contraction
|
|
7130
8718
|
weightedSum(contracted, 1 - psi * rho, centroid, psi * rho, worst);
|
|
7131
8719
|
contracted.fx = f(contracted);
|
|
7132
8720
|
if (contracted.fx < reflected.fx) {
|
|
@@ -7137,9 +8725,11 @@
|
|
|
7137
8725
|
}
|
|
7138
8726
|
}
|
|
7139
8727
|
if (shouldReduce) {
|
|
8728
|
+
// if we don't contract here, we're done
|
|
7140
8729
|
if (sigma >= 1) {
|
|
7141
8730
|
break;
|
|
7142
8731
|
}
|
|
8732
|
+
// do a reduction
|
|
7143
8733
|
for (let i = 1; i < simplex.length; ++i) {
|
|
7144
8734
|
weightedSum(simplex[i], 1 - sigma, simplex[0], sigma, simplex[i]);
|
|
7145
8735
|
simplex[i].fx = f(simplex[i]);
|
|
@@ -7154,6 +8744,21 @@
|
|
|
7154
8744
|
return { fx: simplex[0].fx, x: simplex[0] };
|
|
7155
8745
|
}
|
|
7156
8746
|
|
|
8747
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8748
|
+
* https://github.com/benfred/fmin
|
|
8749
|
+
* Licensed under the BSD-3-Clause
|
|
8750
|
+
|
|
8751
|
+
* url: https://github.com/benfred/fmin/blob/master/src/linesearch.js
|
|
8752
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8753
|
+
* @license
|
|
8754
|
+
*/
|
|
8755
|
+
/// searches along line 'pk' for a point that satifies the wolfe conditions
|
|
8756
|
+
/// See 'Numerical Optimization' by Nocedal and Wright p59-60
|
|
8757
|
+
/// f : objective function
|
|
8758
|
+
/// pk : search direction
|
|
8759
|
+
/// current: object containing current gradient/loss
|
|
8760
|
+
/// next: output: contains next gradient/loss
|
|
8761
|
+
/// returns a: step size taken
|
|
7157
8762
|
function wolfeLineSearch(f, pk, current, next, a, c1, c2) {
|
|
7158
8763
|
const phi0 = current.fx;
|
|
7159
8764
|
const phiPrime0 = dotProduct(current.fxprime, pk);
|
|
@@ -7206,7 +8811,17 @@
|
|
|
7206
8811
|
return a;
|
|
7207
8812
|
}
|
|
7208
8813
|
|
|
8814
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8815
|
+
* https://github.com/benfred/fmin
|
|
8816
|
+
* Licensed under the BSD-3-Clause
|
|
8817
|
+
|
|
8818
|
+
* url: https://github.com/benfred/fmin/blob/master/src/conjugateGradient.js
|
|
8819
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8820
|
+
* @license
|
|
8821
|
+
*/
|
|
7209
8822
|
function conjugateGradient(f, initial, params) {
|
|
8823
|
+
// allocate all memory up front here, keep out of the loop for perfomance
|
|
8824
|
+
// reasons
|
|
7210
8825
|
let current = { x: initial.slice(), fx: 0, fxprime: initial.slice() };
|
|
7211
8826
|
let next = { x: initial.slice(), fx: 0, fxprime: initial.slice() };
|
|
7212
8827
|
const yk = initial.slice();
|
|
@@ -7219,13 +8834,17 @@
|
|
|
7219
8834
|
scale(pk, current.fxprime, -1);
|
|
7220
8835
|
for (let i = 0; i < maxIterations; ++i) {
|
|
7221
8836
|
a = wolfeLineSearch(f, pk, current, next, a);
|
|
8837
|
+
// todo: history in wrong spot?
|
|
7222
8838
|
if (params.history) {
|
|
7223
8839
|
params.history.push({ x: current.x.slice(), fx: current.fx, fxprime: current.fxprime.slice(), alpha: a });
|
|
7224
8840
|
}
|
|
7225
8841
|
if (!a) {
|
|
8842
|
+
// faiiled to find point that satifies wolfe conditions.
|
|
8843
|
+
// reset direction for next iteration
|
|
7226
8844
|
scale(pk, current.fxprime, -1);
|
|
7227
8845
|
}
|
|
7228
8846
|
else {
|
|
8847
|
+
// update direction using Polak–Ribiere CG method
|
|
7229
8848
|
weightedSum(yk, 1, next.fxprime, -1, current.fxprime);
|
|
7230
8849
|
const delta_k = dotProduct(current.fxprime, current.fxprime);
|
|
7231
8850
|
const beta_k = Math.max(0, dotProduct(yk, next.fxprime) / delta_k);
|
|
@@ -7288,6 +8907,7 @@
|
|
|
7288
8907
|
exports.binaryFuzzySearch = binaryFuzzySearch;
|
|
7289
8908
|
exports.binaryFuzzySearchInNumberRange = binaryFuzzySearchInNumberRange;
|
|
7290
8909
|
exports.bisect = bisect;
|
|
8910
|
+
exports.bootstrapEstimate = bootstrapEstimate;
|
|
7291
8911
|
exports.calculateAnchorOfArc = calculateAnchorOfArc;
|
|
7292
8912
|
exports.calculateAnchorOfBounds = calculateAnchorOfBounds;
|
|
7293
8913
|
exports.calculateMaxRadius = calculateMaxRadius;
|
|
@@ -7308,6 +8928,7 @@
|
|
|
7308
8928
|
exports.conjugateGradient = conjugateGradient;
|
|
7309
8929
|
exports.constant = constant;
|
|
7310
8930
|
exports.containedInCircles = containedInCircles;
|
|
8931
|
+
exports.correlation = correlation;
|
|
7311
8932
|
exports.cos = cos;
|
|
7312
8933
|
exports.crossProduct = crossProduct;
|
|
7313
8934
|
exports.crossProductPoint = crossProductPoint;
|
|
@@ -7426,6 +9047,7 @@
|
|
|
7426
9047
|
exports.lowerFirst = lowerFirst;
|
|
7427
9048
|
exports.max = max;
|
|
7428
9049
|
exports.maxInArray = maxInArray;
|
|
9050
|
+
exports.mean = mean;
|
|
7429
9051
|
exports.median = median;
|
|
7430
9052
|
exports.memoize = memoize;
|
|
7431
9053
|
exports.merge = merge;
|
|
@@ -7452,6 +9074,7 @@
|
|
|
7452
9074
|
exports.monthSetterName = monthSetterName;
|
|
7453
9075
|
exports.nelderMead = nelderMead;
|
|
7454
9076
|
exports.norm2 = norm2;
|
|
9077
|
+
exports.normalQuantile = normalQuantile;
|
|
7455
9078
|
exports.normalTransform = normalTransform;
|
|
7456
9079
|
exports.normalizeAngle = normalizeAngle;
|
|
7457
9080
|
exports.normalizePadding = normalizePadding;
|
|
@@ -7482,6 +9105,7 @@
|
|
|
7482
9105
|
exports.randomLCG = randomLCG;
|
|
7483
9106
|
exports.range = range;
|
|
7484
9107
|
exports.rectInsideAnotherRect = rectInsideAnotherRect;
|
|
9108
|
+
exports.regressionBootstrap = regressionBootstrap;
|
|
7485
9109
|
exports.regressionLinear = regressionLinear;
|
|
7486
9110
|
exports.regressionLogistic = regressionLogistic;
|
|
7487
9111
|
exports.regressionLowess = regressionLowess;
|