@visactor/vutils 1.0.23 → 1.0.25
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/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/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 +2 -1
- 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/getType.js +1 -2
- 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 +4 -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 +26 -0
- package/cjs/common/isFunction.js.map +1 -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 +23 -2
- 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 +2 -1
- package/cjs/common/isPlainObject.js.map +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/isUndefined.js +1 -1
- package/cjs/common/isValid.js +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-linear.d.ts +4 -0
- package/cjs/common/regression-linear.js +19 -12
- 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 +9 -0
- package/cjs/common/regression-lowess.js +64 -26
- package/cjs/common/regression-lowess.js.map +1 -1
- package/cjs/common/regression-polynomial.js +26 -15
- package/cjs/common/regression-polynomial.js.map +1 -1
- 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 +1627 -116
- 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/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 +6 -2
- 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/getType.js +1 -2
- 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 +4 -0
- package/es/common/index.js +24 -1
- 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 +26 -0
- package/es/common/isFunction.js.map +1 -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 +23 -2
- 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 +28 -1
- package/es/common/isPlainObject.js.map +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/isUndefined.js +1 -1
- package/es/common/isValid.js +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-linear.d.ts +4 -0
- package/es/common/regression-linear.js +18 -14
- 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 +9 -0
- package/es/common/regression-lowess.js +63 -27
- package/es/common/regression-lowess.js.map +1 -1
- package/es/common/regression-polynomial.js +25 -16
- package/es/common/regression-polynomial.js.map +1 -1
- 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 +8 -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,26 @@
|
|
|
1716
2236
|
return [min, max];
|
|
1717
2237
|
};
|
|
1718
2238
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
const a1 = -39.6968302866538;
|
|
1724
|
-
const a2 = 220.946098424521;
|
|
1725
|
-
const a3 = -275.928510446969;
|
|
1726
|
-
const a4 = 138.357751867269;
|
|
1727
|
-
const a5 = -30.6647980661472;
|
|
1728
|
-
const a6 = 2.50662827745924;
|
|
1729
|
-
const b1 = -54.4760987982241;
|
|
1730
|
-
const b2 = 161.585836858041;
|
|
1731
|
-
const b3 = -155.698979859887;
|
|
1732
|
-
const b4 = 66.8013118877197;
|
|
1733
|
-
const b5 = -13.2806815528857;
|
|
1734
|
-
const c1 = -0.00778489400243029;
|
|
1735
|
-
const c2 = -0.322396458041136;
|
|
1736
|
-
const c3 = -2.40075827716184;
|
|
1737
|
-
const c4 = -2.54973253934373;
|
|
1738
|
-
const c5 = 4.37466414146497;
|
|
1739
|
-
const c6 = 2.93816398269878;
|
|
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);
|
|
2239
|
+
/** Compute a two-sided normal critical value without subtracting a small tail probability from one. */
|
|
2240
|
+
function confidenceCriticalValue(alpha) {
|
|
2241
|
+
if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
|
2242
|
+
return NaN;
|
|
1751
2243
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2244
|
+
return -normalQuantile(alpha / 2);
|
|
2245
|
+
}
|
|
2246
|
+
/** A zero standard error gives a degenerate interval even when the critical value is infinite. */
|
|
2247
|
+
function confidenceHalfWidth(z, standardError) {
|
|
2248
|
+
if (Number.isNaN(z)) {
|
|
2249
|
+
return NaN;
|
|
1757
2250
|
}
|
|
1758
|
-
|
|
1759
|
-
return -((((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1));
|
|
2251
|
+
return standardError === 0 ? 0 : z * standardError;
|
|
1760
2252
|
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Compute basic components used for linear regression confidence / prediction intervals.
|
|
2255
|
+
* - scans data to compute min/max of x, n, mean X, SSE and Sxx (sum (xi - X)^2).
|
|
2256
|
+
*/
|
|
1761
2257
|
function computeLinearCIComponents(data, x, y, predict) {
|
|
2258
|
+
// simple local scanner to avoid circular imports
|
|
1762
2259
|
let min = Infinity;
|
|
1763
2260
|
let max = -Infinity;
|
|
1764
2261
|
let n = 0;
|
|
@@ -1797,6 +2294,9 @@
|
|
|
1797
2294
|
}
|
|
1798
2295
|
return { min, max, n, X, SSE, Sxx };
|
|
1799
2296
|
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Compute standard errors for mean and prediction at px using components.
|
|
2299
|
+
*/
|
|
1800
2300
|
function stdErrorsAt(px, comps) {
|
|
1801
2301
|
const { n, X, Sxx, SSE } = comps;
|
|
1802
2302
|
const s2 = n > 2 ? SSE / (n - 2) : 0;
|
|
@@ -1805,6 +2305,10 @@
|
|
|
1805
2305
|
return { seMean, sePred };
|
|
1806
2306
|
}
|
|
1807
2307
|
|
|
2308
|
+
/**
|
|
2309
|
+
* Linear regression utilities (single clean implementation).
|
|
2310
|
+
* Exports: ordinaryLeastSquares, visitPoints, rSquared, regressionLinear
|
|
2311
|
+
*/
|
|
1808
2312
|
function ordinaryLeastSquares(uX, uY, uXY, uX2) {
|
|
1809
2313
|
const denom = uX2 - uX * uX;
|
|
1810
2314
|
if (Math.abs(denom) < Number.EPSILON) {
|
|
@@ -1843,6 +2347,7 @@
|
|
|
1843
2347
|
function regressionLinear(data, x = d => d.x, y = d => d.y, options) {
|
|
1844
2348
|
var _a;
|
|
1845
2349
|
const alpha = (_a = options === null || options === void 0 ? void 0 : options.alpha) !== null && _a !== void 0 ? _a : 0.05;
|
|
2350
|
+
// accumulate online means (sufficient statistics)
|
|
1846
2351
|
let n = 0;
|
|
1847
2352
|
let meanX = 0;
|
|
1848
2353
|
let meanY = 0;
|
|
@@ -1881,18 +2386,20 @@
|
|
|
1881
2386
|
if (comps.n === 0 || N <= 0) {
|
|
1882
2387
|
return out;
|
|
1883
2388
|
}
|
|
1884
|
-
const z =
|
|
2389
|
+
const z = confidenceCriticalValue(alpha);
|
|
1885
2390
|
if (comps.min === comps.max) {
|
|
1886
2391
|
const m = predict(comps.min);
|
|
1887
2392
|
const errs = stdErrorsAt(comps.min, comps);
|
|
2393
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2394
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
1888
2395
|
for (let i = 0; i < N; i++) {
|
|
1889
2396
|
out.push({
|
|
1890
2397
|
x: comps.min,
|
|
1891
2398
|
mean: m,
|
|
1892
|
-
lower: m -
|
|
1893
|
-
upper: m +
|
|
1894
|
-
predLower: m -
|
|
1895
|
-
predUpper: m +
|
|
2399
|
+
lower: m - meanHalfWidth,
|
|
2400
|
+
upper: m + meanHalfWidth,
|
|
2401
|
+
predLower: m - predHalfWidth,
|
|
2402
|
+
predUpper: m + predHalfWidth
|
|
1896
2403
|
});
|
|
1897
2404
|
}
|
|
1898
2405
|
return out;
|
|
@@ -1902,13 +2409,15 @@
|
|
|
1902
2409
|
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
1903
2410
|
const m = predict(px);
|
|
1904
2411
|
const errs = stdErrorsAt(px, comps);
|
|
2412
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2413
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
1905
2414
|
out.push({
|
|
1906
2415
|
x: px,
|
|
1907
2416
|
mean: m,
|
|
1908
|
-
lower: m -
|
|
1909
|
-
upper: m +
|
|
1910
|
-
predLower: m -
|
|
1911
|
-
predUpper: m +
|
|
2417
|
+
lower: m - meanHalfWidth,
|
|
2418
|
+
upper: m + meanHalfWidth,
|
|
2419
|
+
predLower: m - predHalfWidth,
|
|
2420
|
+
predUpper: m + predHalfWidth
|
|
1912
2421
|
});
|
|
1913
2422
|
}
|
|
1914
2423
|
return out;
|
|
@@ -1922,11 +2431,18 @@
|
|
|
1922
2431
|
};
|
|
1923
2432
|
}
|
|
1924
2433
|
|
|
2434
|
+
/**
|
|
2435
|
+
* Simple logistic regression (binary) using Newton-Raphson (IRLS).
|
|
2436
|
+
* Returns { coef, predict, evaluateGrid }
|
|
2437
|
+
* - coef: [intercept, beta]
|
|
2438
|
+
* - predict(x): probability p(y=1|x)
|
|
2439
|
+
*/
|
|
1925
2440
|
function regressionLogistic(data, x = d => d.x, y = d => d.y, options) {
|
|
1926
2441
|
var _a, _b, _c;
|
|
1927
2442
|
const maxIter = (_a = options === null || options === void 0 ? void 0 : options.maxIteration) !== null && _a !== void 0 ? _a : 25;
|
|
1928
2443
|
const tol = (_b = options === null || options === void 0 ? void 0 : options.tol) !== null && _b !== void 0 ? _b : 1e-6;
|
|
1929
2444
|
const alpha = (_c = options === null || options === void 0 ? void 0 : options.alpha) !== null && _c !== void 0 ? _c : 0.05;
|
|
2445
|
+
// build arrays
|
|
1930
2446
|
const xs = [];
|
|
1931
2447
|
const ys = [];
|
|
1932
2448
|
visitPoints(data, x, y, (dx, dy) => {
|
|
@@ -1942,6 +2458,7 @@
|
|
|
1942
2458
|
confidenceInterval: (N = 50) => []
|
|
1943
2459
|
};
|
|
1944
2460
|
}
|
|
2461
|
+
// initial coef via linear regression rough guess
|
|
1945
2462
|
let intercept = 0;
|
|
1946
2463
|
let beta = 0;
|
|
1947
2464
|
for (let iter = 0; iter < maxIter; iter++) {
|
|
@@ -1952,6 +2469,7 @@
|
|
|
1952
2469
|
const pi = 1 / (1 + Math.exp(-z));
|
|
1953
2470
|
p[i] = pi;
|
|
1954
2471
|
}
|
|
2472
|
+
// compute gradient and hessian
|
|
1955
2473
|
let g0 = 0;
|
|
1956
2474
|
let g1 = 0;
|
|
1957
2475
|
let h00 = 0;
|
|
@@ -1966,6 +2484,7 @@
|
|
|
1966
2484
|
h01 += wi * xs[i];
|
|
1967
2485
|
h11 += wi * xs[i] * xs[i];
|
|
1968
2486
|
}
|
|
2487
|
+
// solve 2x2 system H * delta = g
|
|
1969
2488
|
const det = h00 * h11 - h01 * h01;
|
|
1970
2489
|
if (Math.abs(det) < 1e-12) {
|
|
1971
2490
|
break;
|
|
@@ -2026,18 +2545,20 @@
|
|
|
2026
2545
|
if (comps.n === 0) {
|
|
2027
2546
|
return out;
|
|
2028
2547
|
}
|
|
2029
|
-
const z =
|
|
2548
|
+
const z = confidenceCriticalValue(alpha);
|
|
2030
2549
|
if (comps.min === comps.max) {
|
|
2031
2550
|
const v = predict(comps.min);
|
|
2032
2551
|
const errs = stdErrorsAt(comps.min, comps);
|
|
2552
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2553
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2033
2554
|
for (let i = 0; i < N; i++) {
|
|
2034
2555
|
out.push({
|
|
2035
2556
|
x: comps.min,
|
|
2036
2557
|
mean: v,
|
|
2037
|
-
lower: v -
|
|
2038
|
-
upper: v +
|
|
2039
|
-
predLower: v -
|
|
2040
|
-
predUpper: v +
|
|
2558
|
+
lower: v - meanHalfWidth,
|
|
2559
|
+
upper: v + meanHalfWidth,
|
|
2560
|
+
predLower: v - predHalfWidth,
|
|
2561
|
+
predUpper: v + predHalfWidth
|
|
2041
2562
|
});
|
|
2042
2563
|
}
|
|
2043
2564
|
return out;
|
|
@@ -2047,13 +2568,15 @@
|
|
|
2047
2568
|
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
2048
2569
|
const yh = predict(px);
|
|
2049
2570
|
const errs = stdErrorsAt(px, comps);
|
|
2571
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2572
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2050
2573
|
out.push({
|
|
2051
2574
|
x: px,
|
|
2052
2575
|
mean: yh,
|
|
2053
|
-
lower: yh -
|
|
2054
|
-
upper: yh +
|
|
2055
|
-
predLower: yh -
|
|
2056
|
-
predUpper: yh +
|
|
2576
|
+
lower: yh - meanHalfWidth,
|
|
2577
|
+
upper: yh + meanHalfWidth,
|
|
2578
|
+
predLower: yh - predHalfWidth,
|
|
2579
|
+
predUpper: yh + predHalfWidth
|
|
2057
2580
|
});
|
|
2058
2581
|
}
|
|
2059
2582
|
return out;
|
|
@@ -2074,12 +2597,16 @@
|
|
|
2074
2597
|
const t = 1 - uu * uu * uu;
|
|
2075
2598
|
return t * t * t;
|
|
2076
2599
|
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Stratified sampling to reduce data size while preserving distribution
|
|
2602
|
+
*/
|
|
2077
2603
|
function stratifiedSample(sortedData, maxSamples) {
|
|
2078
2604
|
const n = sortedData.length;
|
|
2079
2605
|
if (n <= maxSamples) {
|
|
2080
2606
|
return sortedData;
|
|
2081
2607
|
}
|
|
2082
2608
|
const sampled = [];
|
|
2609
|
+
// More aggressive sampling - use exact step size
|
|
2083
2610
|
const step = n / maxSamples;
|
|
2084
2611
|
for (let i = 0; i < maxSamples; i++) {
|
|
2085
2612
|
const idx = Math.min(Math.floor(i * step), n - 1);
|
|
@@ -2087,6 +2614,15 @@
|
|
|
2087
2614
|
}
|
|
2088
2615
|
return sampled;
|
|
2089
2616
|
}
|
|
2617
|
+
/**
|
|
2618
|
+
* Simple lowess implementation (univariate x)
|
|
2619
|
+
* options:
|
|
2620
|
+
* - span: fraction of points used in local regression (0,1]
|
|
2621
|
+
* - degree: 0 (constant) or 1 (linear)
|
|
2622
|
+
* - iterations: number of robustifying iterations
|
|
2623
|
+
* - alpha: confidence level for CI
|
|
2624
|
+
* - maxSamples: maximum number of points to use (default: 1000 for fast processing)
|
|
2625
|
+
*/
|
|
2090
2626
|
function regressionLowess(data, x = d => d.x, y = d => d.y, options = {}) {
|
|
2091
2627
|
var _a;
|
|
2092
2628
|
const span = options.span || 0.3;
|
|
@@ -2094,11 +2630,13 @@
|
|
|
2094
2630
|
const alpha = (_a = options.alpha) !== null && _a !== void 0 ? _a : 0.05;
|
|
2095
2631
|
const iterations = options.iterations == null ? 2 : options.iterations;
|
|
2096
2632
|
const maxSamples = options.maxSamples || 1000;
|
|
2633
|
+
// Collect and sort data by x
|
|
2097
2634
|
const rawPoints = [];
|
|
2098
|
-
visitPoints(data, x, y, (dx, dy) => {
|
|
2099
|
-
rawPoints.push({ x: dx, y: dy });
|
|
2635
|
+
visitPoints(data, x, y, (dx, dy, index) => {
|
|
2636
|
+
rawPoints.push({ x: dx, y: dy, index });
|
|
2100
2637
|
});
|
|
2101
2638
|
rawPoints.sort((a, b) => a.x - b.x);
|
|
2639
|
+
// Apply sampling if needed
|
|
2102
2640
|
const sampledPoints = stratifiedSample(rawPoints, maxSamples);
|
|
2103
2641
|
const n = sampledPoints.length;
|
|
2104
2642
|
const ptsX = new Array(n);
|
|
@@ -2107,10 +2645,14 @@
|
|
|
2107
2645
|
ptsX[i] = sampledPoints[i].x;
|
|
2108
2646
|
ptsY[i] = sampledPoints[i].y;
|
|
2109
2647
|
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Optimized predictSingle using binary search on pre-sorted data
|
|
2650
|
+
*/
|
|
2110
2651
|
function predictSingle(x0, robustWeights) {
|
|
2111
2652
|
if (n === 0) {
|
|
2112
2653
|
return 0;
|
|
2113
2654
|
}
|
|
2655
|
+
// Binary search to find insertion point
|
|
2114
2656
|
let left = 0;
|
|
2115
2657
|
let right = n;
|
|
2116
2658
|
while (left < right) {
|
|
@@ -2122,10 +2664,21 @@
|
|
|
2122
2664
|
right = mid;
|
|
2123
2665
|
}
|
|
2124
2666
|
}
|
|
2125
|
-
const m = Math.
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2667
|
+
const m = Math.min(n, Math.max(2, Math.floor(span * n)));
|
|
2668
|
+
// Expand by distance: equal numbers on either side need not be the nearest points.
|
|
2669
|
+
let lo = left - 1;
|
|
2670
|
+
let hi = left;
|
|
2671
|
+
for (let count = 0; count < m; count++) {
|
|
2672
|
+
if (lo >= 0 && (hi >= n || x0 - ptsX[lo] <= ptsX[hi] - x0)) {
|
|
2673
|
+
lo--;
|
|
2674
|
+
}
|
|
2675
|
+
else {
|
|
2676
|
+
hi++;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
const actualStart = lo + 1;
|
|
2680
|
+
const end = hi;
|
|
2681
|
+
// Find max distance and compute weights in single pass
|
|
2129
2682
|
let maxDist = 0;
|
|
2130
2683
|
const windowSize = end - actualStart;
|
|
2131
2684
|
const distances = new Array(windowSize);
|
|
@@ -2136,6 +2689,7 @@
|
|
|
2136
2689
|
maxDist = dist;
|
|
2137
2690
|
}
|
|
2138
2691
|
}
|
|
2692
|
+
// Compute weights using pre-calculated distances
|
|
2139
2693
|
let sumw = 0;
|
|
2140
2694
|
const w = new Array(windowSize);
|
|
2141
2695
|
for (let i = 0; i < windowSize; i++) {
|
|
@@ -2148,7 +2702,17 @@
|
|
|
2148
2702
|
sumw += wi;
|
|
2149
2703
|
}
|
|
2150
2704
|
if (sumw === 0) {
|
|
2151
|
-
|
|
2705
|
+
// Fall back to the nearest y, preserving input order for equal distances.
|
|
2706
|
+
let nearestIdx = 0;
|
|
2707
|
+
let nearestDistance = Math.abs(ptsX[0] - x0);
|
|
2708
|
+
for (let i = 1; i < n; i++) {
|
|
2709
|
+
const distance = Math.abs(ptsX[i] - x0);
|
|
2710
|
+
if (distance < nearestDistance ||
|
|
2711
|
+
(distance === nearestDistance && sampledPoints[i].index < sampledPoints[nearestIdx].index)) {
|
|
2712
|
+
nearestIdx = i;
|
|
2713
|
+
nearestDistance = distance;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2152
2716
|
return ptsY[nearestIdx];
|
|
2153
2717
|
}
|
|
2154
2718
|
if (degree === 0) {
|
|
@@ -2158,6 +2722,7 @@
|
|
|
2158
2722
|
}
|
|
2159
2723
|
return s / sumw;
|
|
2160
2724
|
}
|
|
2725
|
+
// weighted linear regression on local points
|
|
2161
2726
|
let sw = 0;
|
|
2162
2727
|
let sx = 0;
|
|
2163
2728
|
let sy = 0;
|
|
@@ -2210,16 +2775,21 @@
|
|
|
2210
2775
|
return out;
|
|
2211
2776
|
}
|
|
2212
2777
|
const step = (max - min) / (N - 1);
|
|
2778
|
+
// optionally add robust iterations
|
|
2213
2779
|
let robustWeights;
|
|
2780
|
+
// Disable robustness iterations for large datasets to improve performance
|
|
2781
|
+
// Users can override by setting iterations explicitly
|
|
2214
2782
|
const effectiveIterations = options.iterations != null ? iterations : n > 500 ? 0 : iterations;
|
|
2215
2783
|
if (effectiveIterations > 0) {
|
|
2216
2784
|
for (let iter = 0; iter < effectiveIterations; iter++) {
|
|
2785
|
+
// compute fits - pre-allocate arrays
|
|
2217
2786
|
const fits = new Array(n);
|
|
2218
2787
|
const res = new Array(n);
|
|
2219
2788
|
for (let i = 0; i < n; i++) {
|
|
2220
2789
|
fits[i] = predictSingle(ptsX[i], robustWeights);
|
|
2221
2790
|
res[i] = Math.abs(ptsY[i] - fits[i]);
|
|
2222
2791
|
}
|
|
2792
|
+
// median absolute deviation
|
|
2223
2793
|
const sortedRes = res.slice().sort((a, b) => a - b);
|
|
2224
2794
|
const med = sortedRes[Math.floor(n / 2)] || 0;
|
|
2225
2795
|
robustWeights = new Array(n);
|
|
@@ -2244,6 +2814,7 @@
|
|
|
2244
2814
|
if (n === 0) {
|
|
2245
2815
|
return out;
|
|
2246
2816
|
}
|
|
2817
|
+
// use data x-range (already sorted)
|
|
2247
2818
|
const min = ptsX[0];
|
|
2248
2819
|
const max = ptsX[n - 1];
|
|
2249
2820
|
if (min === undefined || max === undefined || min === Infinity || max === -Infinity) {
|
|
@@ -2253,18 +2824,20 @@
|
|
|
2253
2824
|
if (comps.n === 0) {
|
|
2254
2825
|
return out;
|
|
2255
2826
|
}
|
|
2256
|
-
const z =
|
|
2827
|
+
const z = confidenceCriticalValue(alpha);
|
|
2257
2828
|
if (comps.min === comps.max) {
|
|
2258
2829
|
const v = predictSingle(comps.min);
|
|
2259
2830
|
const errs = stdErrorsAt(comps.min, comps);
|
|
2831
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2832
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2260
2833
|
for (let i = 0; i < N; i++) {
|
|
2261
2834
|
out.push({
|
|
2262
2835
|
x: comps.min,
|
|
2263
2836
|
mean: v,
|
|
2264
|
-
lower: v -
|
|
2265
|
-
upper: v +
|
|
2266
|
-
predLower: v -
|
|
2267
|
-
predUpper: v +
|
|
2837
|
+
lower: v - meanHalfWidth,
|
|
2838
|
+
upper: v + meanHalfWidth,
|
|
2839
|
+
predLower: v - predHalfWidth,
|
|
2840
|
+
predUpper: v + predHalfWidth
|
|
2268
2841
|
});
|
|
2269
2842
|
}
|
|
2270
2843
|
return out;
|
|
@@ -2274,13 +2847,15 @@
|
|
|
2274
2847
|
const px = i === N - 1 ? max : min + step * i;
|
|
2275
2848
|
const yh = predictSingle(px);
|
|
2276
2849
|
const errs = stdErrorsAt(px, comps);
|
|
2850
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2851
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2277
2852
|
out.push({
|
|
2278
2853
|
x: px,
|
|
2279
2854
|
mean: yh,
|
|
2280
|
-
lower: yh -
|
|
2281
|
-
upper: yh +
|
|
2282
|
-
predLower: yh -
|
|
2283
|
-
predUpper: yh +
|
|
2855
|
+
lower: yh - meanHalfWidth,
|
|
2856
|
+
upper: yh + meanHalfWidth,
|
|
2857
|
+
predLower: yh - predHalfWidth,
|
|
2858
|
+
predUpper: yh + predHalfWidth
|
|
2284
2859
|
});
|
|
2285
2860
|
}
|
|
2286
2861
|
return out;
|
|
@@ -2294,13 +2869,16 @@
|
|
|
2294
2869
|
}
|
|
2295
2870
|
|
|
2296
2871
|
function solveLinearSystem(A, b) {
|
|
2872
|
+
// Gaussian elimination with partial pivoting
|
|
2297
2873
|
const n = b.length;
|
|
2874
|
+
// clone
|
|
2298
2875
|
const M = new Array(n);
|
|
2299
2876
|
for (let i = 0; i < n; i++) {
|
|
2300
2877
|
M[i] = A[i].slice();
|
|
2301
2878
|
M[i].push(b[i]);
|
|
2302
2879
|
}
|
|
2303
2880
|
for (let k = 0; k < n; k++) {
|
|
2881
|
+
// find pivot
|
|
2304
2882
|
let maxRow = k;
|
|
2305
2883
|
let maxVal = Math.abs(M[k][k]);
|
|
2306
2884
|
for (let i = k + 1; i < n; i++) {
|
|
@@ -2315,14 +2893,18 @@
|
|
|
2315
2893
|
M[k] = M[maxRow];
|
|
2316
2894
|
M[maxRow] = tmp;
|
|
2317
2895
|
}
|
|
2896
|
+
// singular check
|
|
2318
2897
|
if (Math.abs(M[k][k]) < 1e-12) {
|
|
2898
|
+
// return least squares fallback zeros
|
|
2319
2899
|
const res = new Array(n).fill(0);
|
|
2320
2900
|
return res;
|
|
2321
2901
|
}
|
|
2902
|
+
// normalize row
|
|
2322
2903
|
for (let j = k + 1; j <= n; j++) {
|
|
2323
2904
|
M[k][j] = M[k][j] / M[k][k];
|
|
2324
2905
|
}
|
|
2325
2906
|
M[k][k] = 1;
|
|
2907
|
+
// eliminate
|
|
2326
2908
|
for (let i = 0; i < n; i++) {
|
|
2327
2909
|
if (i === k) {
|
|
2328
2910
|
continue;
|
|
@@ -2359,6 +2941,7 @@
|
|
|
2359
2941
|
xp *= dx;
|
|
2360
2942
|
}
|
|
2361
2943
|
});
|
|
2944
|
+
// build normal matrix
|
|
2362
2945
|
const A = new Array(m);
|
|
2363
2946
|
for (let i = 0; i < m; i++) {
|
|
2364
2947
|
A[i] = new Array(m).fill(0);
|
|
@@ -2389,6 +2972,7 @@
|
|
|
2389
2972
|
coef,
|
|
2390
2973
|
predict,
|
|
2391
2974
|
rSquared: rSquared(data, x, y, (() => {
|
|
2975
|
+
// compute mean y
|
|
2392
2976
|
let sum = 0;
|
|
2393
2977
|
let cnt = 0;
|
|
2394
2978
|
visitPoints(data, x, y, (_dx, dy) => {
|
|
@@ -2402,6 +2986,7 @@
|
|
|
2402
2986
|
if (N <= 0) {
|
|
2403
2987
|
return out;
|
|
2404
2988
|
}
|
|
2989
|
+
// compute range
|
|
2405
2990
|
let min = Infinity;
|
|
2406
2991
|
let max = -Infinity;
|
|
2407
2992
|
visitPoints(data, x, y, dx => {
|
|
@@ -2440,18 +3025,20 @@
|
|
|
2440
3025
|
if (comps.n === 0) {
|
|
2441
3026
|
return out;
|
|
2442
3027
|
}
|
|
2443
|
-
const z =
|
|
3028
|
+
const z = confidenceCriticalValue(alpha);
|
|
2444
3029
|
if (comps.min === comps.max) {
|
|
2445
3030
|
const v = predict(comps.min);
|
|
2446
3031
|
const errs = stdErrorsAt(comps.min, comps);
|
|
3032
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3033
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2447
3034
|
for (let i = 0; i < N; i++) {
|
|
2448
3035
|
out.push({
|
|
2449
3036
|
x: comps.min,
|
|
2450
3037
|
mean: v,
|
|
2451
|
-
lower: v -
|
|
2452
|
-
upper: v +
|
|
2453
|
-
predLower: v -
|
|
2454
|
-
predUpper: v +
|
|
3038
|
+
lower: v - meanHalfWidth,
|
|
3039
|
+
upper: v + meanHalfWidth,
|
|
3040
|
+
predLower: v - predHalfWidth,
|
|
3041
|
+
predUpper: v + predHalfWidth
|
|
2455
3042
|
});
|
|
2456
3043
|
}
|
|
2457
3044
|
return out;
|
|
@@ -2461,13 +3048,15 @@
|
|
|
2461
3048
|
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
2462
3049
|
const yh = predict(px);
|
|
2463
3050
|
const errs = stdErrorsAt(px, comps);
|
|
3051
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3052
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2464
3053
|
out.push({
|
|
2465
3054
|
x: px,
|
|
2466
3055
|
mean: yh,
|
|
2467
|
-
lower: yh -
|
|
2468
|
-
upper: yh +
|
|
2469
|
-
predLower: yh -
|
|
2470
|
-
predUpper: yh +
|
|
3056
|
+
lower: yh - meanHalfWidth,
|
|
3057
|
+
upper: yh + meanHalfWidth,
|
|
3058
|
+
predLower: yh - predHalfWidth,
|
|
3059
|
+
predUpper: yh + predHalfWidth
|
|
2471
3060
|
});
|
|
2472
3061
|
}
|
|
2473
3062
|
return out;
|
|
@@ -2475,14 +3064,29 @@
|
|
|
2475
3064
|
};
|
|
2476
3065
|
}
|
|
2477
3066
|
|
|
3067
|
+
/**
|
|
3068
|
+
* Kernel Density Estimation (KDE) utilities
|
|
3069
|
+
*
|
|
3070
|
+
* Exports:
|
|
3071
|
+
* - Kernel functions: gaussian, epanechnikov
|
|
3072
|
+
* - bandwidth selectors: scott, silverman
|
|
3073
|
+
* - kde: main function to compute density estimates at provided points
|
|
3074
|
+
*/
|
|
3075
|
+
/** Gaussian kernel (standard normal) */
|
|
2478
3076
|
const gaussian = (u) => {
|
|
2479
3077
|
const invSqrt2Pi = 1 / Math.sqrt(2 * Math.PI);
|
|
2480
3078
|
return invSqrt2Pi * Math.exp(-0.5 * u * u);
|
|
2481
3079
|
};
|
|
3080
|
+
/** Epanechnikov kernel (compact support) */
|
|
2482
3081
|
const epanechnikov = (u) => {
|
|
2483
3082
|
const absu = Math.abs(u);
|
|
2484
3083
|
return absu <= 1 ? 0.75 * (1 - u * u) : 0;
|
|
2485
3084
|
};
|
|
3085
|
+
/**
|
|
3086
|
+
* Bandwidth selectors
|
|
3087
|
+
* - scott: h = n^{-1/(d+4)} * std
|
|
3088
|
+
* - silverman: h = ( (4/(d+2))^{1/(d+4)} ) * n^{-1/(d+4)} * std
|
|
3089
|
+
*/
|
|
2486
3090
|
function scott(n, std, dim = 1) {
|
|
2487
3091
|
if (n <= 0 || std === 0) {
|
|
2488
3092
|
return 0;
|
|
@@ -2496,6 +3100,9 @@
|
|
|
2496
3100
|
const factor = Math.pow(4 / (dim + 2), 1 / (dim + 4));
|
|
2497
3101
|
return factor * std * Math.pow(n, -1 / (dim + 4));
|
|
2498
3102
|
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Compute standard deviation for numeric array
|
|
3105
|
+
*/
|
|
2499
3106
|
function std(values) {
|
|
2500
3107
|
const n = values.length;
|
|
2501
3108
|
if (n === 0) {
|
|
@@ -2513,6 +3120,10 @@
|
|
|
2513
3120
|
}
|
|
2514
3121
|
return Math.sqrt(s / n);
|
|
2515
3122
|
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Factory: create a KDE evaluator for given data and options.
|
|
3125
|
+
* Usage: const model = kde(data, options); model.evaluate(x) or model.evaluate([x1,x2])
|
|
3126
|
+
*/
|
|
2516
3127
|
function kde(data, options = {}) {
|
|
2517
3128
|
const n = data.length;
|
|
2518
3129
|
const kernel = options.kernel || gaussian;
|
|
@@ -2527,6 +3138,7 @@
|
|
|
2527
3138
|
h = scott(n, sd, 1);
|
|
2528
3139
|
}
|
|
2529
3140
|
}
|
|
3141
|
+
// if still zero (constant data), evaluator returns zeros
|
|
2530
3142
|
if (!h || h <= 0) {
|
|
2531
3143
|
const zerosEval = (x) => {
|
|
2532
3144
|
if (Array.isArray(x)) {
|
|
@@ -2543,6 +3155,7 @@
|
|
|
2543
3155
|
if (N <= 0) {
|
|
2544
3156
|
return out;
|
|
2545
3157
|
}
|
|
3158
|
+
// compute data min (use data range if available)
|
|
2546
3159
|
let min = Infinity;
|
|
2547
3160
|
let max = -Infinity;
|
|
2548
3161
|
for (let j = 0; j < n; j++) {
|
|
@@ -2599,6 +3212,7 @@
|
|
|
2599
3212
|
if (N <= 0) {
|
|
2600
3213
|
return out;
|
|
2601
3214
|
}
|
|
3215
|
+
// compute data range
|
|
2602
3216
|
let min = Infinity;
|
|
2603
3217
|
let max = -Infinity;
|
|
2604
3218
|
for (let i = 0; i < n; i++) {
|
|
@@ -2614,6 +3228,7 @@
|
|
|
2614
3228
|
return out;
|
|
2615
3229
|
}
|
|
2616
3230
|
if (min === max) {
|
|
3231
|
+
// single point repeated
|
|
2617
3232
|
for (let i = 0; i < N; i++) {
|
|
2618
3233
|
out.push({ x: min, y: evalPoint(min) });
|
|
2619
3234
|
}
|
|
@@ -2629,6 +3244,14 @@
|
|
|
2629
3244
|
};
|
|
2630
3245
|
}
|
|
2631
3246
|
|
|
3247
|
+
/**
|
|
3248
|
+
* Empirical Cumulative Distribution Function (ECDF)
|
|
3249
|
+
* Returns a factory that builds an evaluator over provided samples.
|
|
3250
|
+
* The evaluator exposes:
|
|
3251
|
+
* - evaluate(x:number) => proportion of samples <= x
|
|
3252
|
+
* - evaluate(xs:number[]) => number[]
|
|
3253
|
+
* - evaluateGrid(N:number) => { points: number[], cdf: number[] } over sample range
|
|
3254
|
+
*/
|
|
2632
3255
|
function ecdf(data) {
|
|
2633
3256
|
const n = data.length;
|
|
2634
3257
|
const sorted = data.slice().sort((a, b) => a - b);
|
|
@@ -2636,8 +3259,9 @@
|
|
|
2636
3259
|
if (n === 0) {
|
|
2637
3260
|
return 0;
|
|
2638
3261
|
}
|
|
3262
|
+
// count of values <= x (upper bound index)
|
|
2639
3263
|
let lo = 0;
|
|
2640
|
-
let hi = n;
|
|
3264
|
+
let hi = n; // exclusive
|
|
2641
3265
|
while (lo < hi) {
|
|
2642
3266
|
const mid = (lo + hi) >>> 1;
|
|
2643
3267
|
if (sorted[mid] <= x) {
|
|
@@ -2689,8 +3313,192 @@
|
|
|
2689
3313
|
};
|
|
2690
3314
|
}
|
|
2691
3315
|
|
|
3316
|
+
function pearson(x, y) {
|
|
3317
|
+
let sx = 0;
|
|
3318
|
+
let sy = 0;
|
|
3319
|
+
for (let i = 0; i < x.length; i++) {
|
|
3320
|
+
sx = Math.max(sx, Math.abs(x[i]));
|
|
3321
|
+
sy = Math.max(sy, Math.abs(y[i]));
|
|
3322
|
+
}
|
|
3323
|
+
if (!sx || !sy) {
|
|
3324
|
+
return undefined;
|
|
3325
|
+
}
|
|
3326
|
+
let mx = 0;
|
|
3327
|
+
let my = 0;
|
|
3328
|
+
let cx = 0;
|
|
3329
|
+
let cy = 0;
|
|
3330
|
+
for (let i = 0; i < x.length; i++) {
|
|
3331
|
+
const vx = x[i] / sx - cx;
|
|
3332
|
+
const vy = y[i] / sy - cy;
|
|
3333
|
+
const tx = mx + vx;
|
|
3334
|
+
const ty = my + vy;
|
|
3335
|
+
cx = tx - mx - vx;
|
|
3336
|
+
cy = ty - my - vy;
|
|
3337
|
+
mx = tx;
|
|
3338
|
+
my = ty;
|
|
3339
|
+
}
|
|
3340
|
+
mx /= x.length;
|
|
3341
|
+
my /= x.length;
|
|
3342
|
+
let xx = 0;
|
|
3343
|
+
let yy = 0;
|
|
3344
|
+
let xy = 0;
|
|
3345
|
+
for (let i = 0; i < x.length; i++) {
|
|
3346
|
+
const dx = x[i] / sx - mx;
|
|
3347
|
+
const dy = y[i] / sy - my;
|
|
3348
|
+
xx += dx * dx;
|
|
3349
|
+
yy += dy * dy;
|
|
3350
|
+
xy += dx * dy;
|
|
3351
|
+
}
|
|
3352
|
+
if (xx === 0 || yy === 0) {
|
|
3353
|
+
return undefined;
|
|
3354
|
+
}
|
|
3355
|
+
const result = xy / Math.sqrt(xx * yy);
|
|
3356
|
+
// 仅修正浮点舍入越过理论端点的几个 ulp。
|
|
3357
|
+
if (Math.abs(result) > 1 && Math.abs(result) <= 1 + 8 * Number.EPSILON) {
|
|
3358
|
+
return Math.sign(result);
|
|
3359
|
+
}
|
|
3360
|
+
return result;
|
|
3361
|
+
}
|
|
3362
|
+
function ranks(values) {
|
|
3363
|
+
const order = values.map((value, index) => ({ value, index })).sort((a, b) => a.value - b.value);
|
|
3364
|
+
const result = new Array(values.length);
|
|
3365
|
+
for (let start = 0; start < order.length;) {
|
|
3366
|
+
let end = start + 1;
|
|
3367
|
+
while (end < order.length && order[end].value === order[start].value) {
|
|
3368
|
+
end++;
|
|
3369
|
+
}
|
|
3370
|
+
const rank = (start + end + 1) / 2;
|
|
3371
|
+
for (let i = start; i < end; i++) {
|
|
3372
|
+
result[order[i].index] = rank;
|
|
3373
|
+
}
|
|
3374
|
+
start = end;
|
|
3375
|
+
}
|
|
3376
|
+
return result;
|
|
3377
|
+
}
|
|
3378
|
+
function kendall(x, y) {
|
|
3379
|
+
const pairs = x.map((value, index) => ({ x: value, y: y[index] })).sort((a, b) => a.x - b.x || a.y - b.y);
|
|
3380
|
+
const sortedY = [...y].sort((a, b) => a - b);
|
|
3381
|
+
const yRanks = new Map();
|
|
3382
|
+
let tiesY = 0;
|
|
3383
|
+
for (let start = 0; start < sortedY.length;) {
|
|
3384
|
+
let end = start + 1;
|
|
3385
|
+
while (end < sortedY.length && sortedY[end] === sortedY[start]) {
|
|
3386
|
+
end++;
|
|
3387
|
+
}
|
|
3388
|
+
tiesY += ((end - start) * (end - start - 1)) / 2;
|
|
3389
|
+
yRanks.set(sortedY[start], yRanks.size + 1);
|
|
3390
|
+
start = end;
|
|
3391
|
+
}
|
|
3392
|
+
const tree = new Float64Array(yRanks.size + 1);
|
|
3393
|
+
let tiesX = 0;
|
|
3394
|
+
let tiesBoth = 0;
|
|
3395
|
+
let discordant = 0;
|
|
3396
|
+
for (let start = 0; start < pairs.length;) {
|
|
3397
|
+
let end = start + 1;
|
|
3398
|
+
while (end < pairs.length && pairs[end].x === pairs[start].x) {
|
|
3399
|
+
end++;
|
|
3400
|
+
}
|
|
3401
|
+
tiesX += ((end - start) * (end - start - 1)) / 2;
|
|
3402
|
+
for (let i = start; i < end;) {
|
|
3403
|
+
let j = i + 1;
|
|
3404
|
+
while (j < end && pairs[j].y === pairs[i].y) {
|
|
3405
|
+
j++;
|
|
3406
|
+
}
|
|
3407
|
+
tiesBoth += ((j - i) * (j - i - 1)) / 2;
|
|
3408
|
+
i = j;
|
|
3409
|
+
}
|
|
3410
|
+
// 同 x 组先查询再更新,避免将 x ties 算成逆序。
|
|
3411
|
+
for (let i = start; i < end; i++) {
|
|
3412
|
+
let lessOrEqual = 0;
|
|
3413
|
+
for (let rank = yRanks.get(pairs[i].y); rank > 0; rank -= rank & -rank) {
|
|
3414
|
+
lessOrEqual += tree[rank];
|
|
3415
|
+
}
|
|
3416
|
+
discordant += start - lessOrEqual;
|
|
3417
|
+
}
|
|
3418
|
+
for (let i = start; i < end; i++) {
|
|
3419
|
+
for (let rank = yRanks.get(pairs[i].y); rank < tree.length; rank += rank & -rank) {
|
|
3420
|
+
tree[rank]++;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
start = end;
|
|
3424
|
+
}
|
|
3425
|
+
const total = (x.length * (x.length - 1)) / 2;
|
|
3426
|
+
if (total === tiesX || total === tiesY) {
|
|
3427
|
+
return undefined;
|
|
3428
|
+
}
|
|
3429
|
+
return (total - tiesX - tiesY + tiesBoth - 2 * discordant) / Math.sqrt((total - tiesX) * (total - tiesY));
|
|
3430
|
+
}
|
|
3431
|
+
/** 等长有限样本的相关系数;常量/不足两条返回 undefined,不修改数组。 */
|
|
3432
|
+
function correlation(x, y, method = 'pearson') {
|
|
3433
|
+
if (x.length !== y.length ||
|
|
3434
|
+
!['pearson', 'spearman', 'kendall'].includes(method) ||
|
|
3435
|
+
x.some(value => !Number.isFinite(value)) ||
|
|
3436
|
+
y.some(value => !Number.isFinite(value))) {
|
|
3437
|
+
throw new RangeError('correlation requires equal finite arrays and a supported method');
|
|
3438
|
+
}
|
|
3439
|
+
if (x.length < 2) {
|
|
3440
|
+
return undefined;
|
|
3441
|
+
}
|
|
3442
|
+
return method === 'kendall' ? kendall(x, y) : method === 'spearman' ? pearson(ranks(x), ranks(y)) : pearson(x, y);
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
/** 有限数值的均值;空输入没有均值,不修改输入。 */
|
|
3446
|
+
function mean(values) {
|
|
3447
|
+
if (!values.length) {
|
|
3448
|
+
return undefined;
|
|
3449
|
+
}
|
|
3450
|
+
let scale = 0;
|
|
3451
|
+
for (const value of values) {
|
|
3452
|
+
if (!Number.isFinite(value)) {
|
|
3453
|
+
throw new RangeError('mean requires finite numbers');
|
|
3454
|
+
}
|
|
3455
|
+
scale = Math.max(scale, Math.abs(value));
|
|
3456
|
+
}
|
|
3457
|
+
if (scale === 0) {
|
|
3458
|
+
return 0;
|
|
3459
|
+
}
|
|
3460
|
+
let sum = 0;
|
|
3461
|
+
let correction = 0;
|
|
3462
|
+
for (const value of values) {
|
|
3463
|
+
const next = value / scale;
|
|
3464
|
+
const total = sum + next;
|
|
3465
|
+
// Neumaier compensation retains small contributions even when larger values cancel.
|
|
3466
|
+
correction += Math.abs(sum) >= Math.abs(next) ? sum - total + next : next - total + sum;
|
|
3467
|
+
sum = total;
|
|
3468
|
+
}
|
|
3469
|
+
return ((sum + correction) / values.length) * scale;
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
/** 有放回重采样;回调不得修改或持有复用的只读样本缓冲区。 */
|
|
3473
|
+
function bootstrapEstimate(values, estimator, options) {
|
|
3474
|
+
if (!values.length ||
|
|
3475
|
+
values.some(value => !Number.isFinite(value)) ||
|
|
3476
|
+
!Number.isInteger(options.resamples) ||
|
|
3477
|
+
options.resamples < 1 ||
|
|
3478
|
+
!Number.isInteger(options.seed) ||
|
|
3479
|
+
options.seed < 0 ||
|
|
3480
|
+
options.seed > 0xffffffff) {
|
|
3481
|
+
throw new RangeError('bootstrap requires finite observations, positive resamples and a uint32 seed');
|
|
3482
|
+
}
|
|
3483
|
+
const random = randomLCG(options.seed);
|
|
3484
|
+
const sample = new Array(values.length);
|
|
3485
|
+
const estimates = new Array(options.resamples);
|
|
3486
|
+
for (let b = 0; b < options.resamples; b++) {
|
|
3487
|
+
for (let i = 0; i < sample.length; i++) {
|
|
3488
|
+
sample[i] = values[Math.floor(random() * values.length)];
|
|
3489
|
+
}
|
|
3490
|
+
const estimate = estimator(sample);
|
|
3491
|
+
if (!Number.isFinite(estimate)) {
|
|
3492
|
+
throw new RangeError('bootstrap estimator must return a finite number');
|
|
3493
|
+
}
|
|
3494
|
+
estimates[b] = estimate;
|
|
3495
|
+
}
|
|
3496
|
+
return estimates;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
2692
3499
|
class HashValue {
|
|
2693
3500
|
}
|
|
3501
|
+
// 基于 array 和 object的 HashTable
|
|
2694
3502
|
class HashTable {
|
|
2695
3503
|
constructor() {
|
|
2696
3504
|
this.items = {};
|
|
@@ -2726,6 +3534,10 @@
|
|
|
2726
3534
|
this.resetIndex();
|
|
2727
3535
|
}
|
|
2728
3536
|
}
|
|
3537
|
+
/**
|
|
3538
|
+
* 不包含当前index
|
|
3539
|
+
* @param index
|
|
3540
|
+
*/
|
|
2729
3541
|
delFrom(index) {
|
|
2730
3542
|
for (let i = index + 1; i < this.count(); i++) {
|
|
2731
3543
|
const key = this.itemList[i].key;
|
|
@@ -2769,6 +3581,7 @@
|
|
|
2769
3581
|
getKeyByIndex(index) {
|
|
2770
3582
|
return this.itemList[index].key;
|
|
2771
3583
|
}
|
|
3584
|
+
//遍历 扩展
|
|
2772
3585
|
foreach(callback) {
|
|
2773
3586
|
for (const key in this.items) {
|
|
2774
3587
|
const returnVal = callback(key, this.items[key].value);
|
|
@@ -2795,12 +3608,14 @@
|
|
|
2795
3608
|
}
|
|
2796
3609
|
return false;
|
|
2797
3610
|
}
|
|
3611
|
+
//获取index
|
|
2798
3612
|
indexOf(key) {
|
|
2799
3613
|
if (this.has(key)) {
|
|
2800
3614
|
return this.items[key].index;
|
|
2801
3615
|
}
|
|
2802
3616
|
return -1;
|
|
2803
3617
|
}
|
|
3618
|
+
//插入
|
|
2804
3619
|
insertAt(index, value, key) {
|
|
2805
3620
|
const hashV = new HashValue();
|
|
2806
3621
|
hashV.index = index;
|
|
@@ -2833,6 +3648,7 @@
|
|
|
2833
3648
|
return returnArr;
|
|
2834
3649
|
}
|
|
2835
3650
|
toImmutableMap() {
|
|
3651
|
+
// do nothing
|
|
2836
3652
|
}
|
|
2837
3653
|
}
|
|
2838
3654
|
|
|
@@ -2859,6 +3675,15 @@
|
|
|
2859
3675
|
function asin(x) {
|
|
2860
3676
|
return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x);
|
|
2861
3677
|
}
|
|
3678
|
+
/**
|
|
3679
|
+
* 根据比例获直线上的点
|
|
3680
|
+
* @param {number} x1 起始点 x
|
|
3681
|
+
* @param {number} y1 起始点 y
|
|
3682
|
+
* @param {number} x2 结束点 x
|
|
3683
|
+
* @param {number} y2 结束点 y
|
|
3684
|
+
* @param {number} t 指定比例
|
|
3685
|
+
* @return {object} 包含 x, y 的点
|
|
3686
|
+
*/
|
|
2862
3687
|
function pointAt(x1, y1, x2, y2, t) {
|
|
2863
3688
|
let x = x2;
|
|
2864
3689
|
let y = y2;
|
|
@@ -2874,6 +3699,7 @@
|
|
|
2874
3699
|
};
|
|
2875
3700
|
}
|
|
2876
3701
|
function lengthFromPointToLine(point, point1, point2) {
|
|
3702
|
+
// 面积
|
|
2877
3703
|
const dir1X = point2.x - point1.x;
|
|
2878
3704
|
const dir1Y = point2.y - point1.y;
|
|
2879
3705
|
const dir2X = point.x - point1.x;
|
|
@@ -2918,6 +3744,9 @@
|
|
|
2918
3744
|
|
|
2919
3745
|
class Point {
|
|
2920
3746
|
constructor(x = 0, y = 0, x1, y1) {
|
|
3747
|
+
/**
|
|
3748
|
+
* 面积图的下点
|
|
3749
|
+
*/
|
|
2921
3750
|
this.x = 0;
|
|
2922
3751
|
this.y = 0;
|
|
2923
3752
|
this.x = x;
|
|
@@ -2970,6 +3799,7 @@
|
|
|
2970
3799
|
}
|
|
2971
3800
|
}
|
|
2972
3801
|
class PointService {
|
|
3802
|
+
/* distance */
|
|
2973
3803
|
static distancePP(p1, p2) {
|
|
2974
3804
|
return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
|
|
2975
3805
|
}
|
|
@@ -2979,6 +3809,7 @@
|
|
|
2979
3809
|
static distancePN(point, x, y) {
|
|
2980
3810
|
return sqrt(pow(x - point.x, 2) + pow(y - point.y, 2));
|
|
2981
3811
|
}
|
|
3812
|
+
/* point at */
|
|
2982
3813
|
static pointAtPP(p1, p2, t) {
|
|
2983
3814
|
return new Point((p2.x - p1.x) * t + p1.x, (p2.y - p1.y) * t + p1.y);
|
|
2984
3815
|
}
|
|
@@ -3027,12 +3858,30 @@
|
|
|
3027
3858
|
return new ImageData(flipClampBuffer, width, height);
|
|
3028
3859
|
};
|
|
3029
3860
|
|
|
3861
|
+
/**
|
|
3862
|
+
* 处理角度相关的数据计算
|
|
3863
|
+
*/
|
|
3864
|
+
/**
|
|
3865
|
+
* degree -> radian
|
|
3866
|
+
* @param degree
|
|
3867
|
+
* @returns radian
|
|
3868
|
+
*/
|
|
3030
3869
|
function degreeToRadian(degree) {
|
|
3031
3870
|
return degree * (Math.PI / 180);
|
|
3032
3871
|
}
|
|
3872
|
+
/**
|
|
3873
|
+
* radian -> degree
|
|
3874
|
+
* @param radian
|
|
3875
|
+
* @returns
|
|
3876
|
+
*/
|
|
3033
3877
|
function radianToDegree(radian) {
|
|
3034
3878
|
return (radian * 180) / Math.PI;
|
|
3035
3879
|
}
|
|
3880
|
+
/**
|
|
3881
|
+
* 对弧度进行格式化
|
|
3882
|
+
* @param a 传入的弧度
|
|
3883
|
+
* @returns
|
|
3884
|
+
*/
|
|
3036
3885
|
const clampRadian = (angle = 0) => {
|
|
3037
3886
|
if (angle < 0) {
|
|
3038
3887
|
while (angle < -tau) {
|
|
@@ -3046,14 +3895,28 @@
|
|
|
3046
3895
|
}
|
|
3047
3896
|
return angle;
|
|
3048
3897
|
};
|
|
3898
|
+
// alias of clampRadian
|
|
3049
3899
|
const clampAngleByRadian = clampRadian;
|
|
3900
|
+
/**
|
|
3901
|
+
* 对角度进行格式化
|
|
3902
|
+
* @param a 角度
|
|
3903
|
+
* @returns
|
|
3904
|
+
*/
|
|
3050
3905
|
const clampDegree = (a = 0) => {
|
|
3051
3906
|
if (a > 360 || a < -360) {
|
|
3052
3907
|
return a % 360;
|
|
3053
3908
|
}
|
|
3054
3909
|
return a;
|
|
3055
3910
|
};
|
|
3911
|
+
// alias of clampDegree
|
|
3056
3912
|
const clampAngleByDegree = clampDegree;
|
|
3913
|
+
/**
|
|
3914
|
+
* 根据弧度及半径计算极坐标系下的坐标点
|
|
3915
|
+
* @param centerX 圆心坐标
|
|
3916
|
+
* @param radius 半径
|
|
3917
|
+
* @param angleInRadian 弧度
|
|
3918
|
+
* @returns 返回笛卡尔坐标点
|
|
3919
|
+
*/
|
|
3057
3920
|
function polarToCartesian(center, radius, angleInRadian) {
|
|
3058
3921
|
if (!radius) {
|
|
3059
3922
|
return { x: center.x, y: center.y };
|
|
@@ -3063,6 +3926,14 @@
|
|
|
3063
3926
|
y: center.y + radius * Math.sin(angleInRadian)
|
|
3064
3927
|
};
|
|
3065
3928
|
}
|
|
3929
|
+
/**
|
|
3930
|
+
* 根据笛卡尔坐标系坐标计算极坐标坐标点
|
|
3931
|
+
* @param point
|
|
3932
|
+
* @param center 极坐标系中心点坐标
|
|
3933
|
+
* @param startAngle 极坐标系起始角度
|
|
3934
|
+
* @param endAngle 极坐标系结束角度
|
|
3935
|
+
* @returns 极坐标系坐标点
|
|
3936
|
+
*/
|
|
3066
3937
|
function cartesianToPolar(point, center = { x: 0, y: 0 }, startAngle = 0, endAngle = 2 * Math.PI) {
|
|
3067
3938
|
const { x, y } = point;
|
|
3068
3939
|
const { x: centerX, y: centerY } = center;
|
|
@@ -3093,9 +3964,19 @@
|
|
|
3093
3964
|
angle: radian
|
|
3094
3965
|
};
|
|
3095
3966
|
}
|
|
3967
|
+
/**
|
|
3968
|
+
* 根据点的笛卡尔坐标获取该点与圆心的连线同正 x 轴方向的夹角
|
|
3969
|
+
* @param center 圆心坐标
|
|
3970
|
+
* @param point 待求的点坐标
|
|
3971
|
+
* @returns 返回夹角对应的弧度值
|
|
3972
|
+
*/
|
|
3096
3973
|
function getAngleByPoint(center, point) {
|
|
3097
3974
|
return Math.atan2(point.y - center.y, point.x - center.x);
|
|
3098
3975
|
}
|
|
3976
|
+
/**
|
|
3977
|
+
* 角度标准化处理
|
|
3978
|
+
* @param angle 弧度角
|
|
3979
|
+
*/
|
|
3099
3980
|
function normalizeAngle(angle) {
|
|
3100
3981
|
while (angle < 0) {
|
|
3101
3982
|
angle += Math.PI * 2;
|
|
@@ -3105,6 +3986,14 @@
|
|
|
3105
3986
|
}
|
|
3106
3987
|
return angle;
|
|
3107
3988
|
}
|
|
3989
|
+
/**
|
|
3990
|
+
* 指定开始角度和结束角度,计算这个范围内的边界角度,
|
|
3991
|
+
* 即起始角度以及经过的东、南、西、北四个方向的角度
|
|
3992
|
+
* 计算这个角度,可以用于计算一个弧度的bounding box 等
|
|
3993
|
+
* @param startAngle 起始角度的弧度值
|
|
3994
|
+
* @param endAngle 结束角度的弧度值
|
|
3995
|
+
* @returns 边界角度数组
|
|
3996
|
+
*/
|
|
3108
3997
|
function findBoundaryAngles(startAngle, endAngle) {
|
|
3109
3998
|
const deltaAngle = Math.abs(endAngle - startAngle);
|
|
3110
3999
|
if (deltaAngle >= 2 * Math.PI || 2 * Math.PI - deltaAngle < 1e-6) {
|
|
@@ -3123,6 +4012,14 @@
|
|
|
3123
4012
|
}
|
|
3124
4013
|
return steps;
|
|
3125
4014
|
}
|
|
4015
|
+
/**
|
|
4016
|
+
* 计算指定范围内,指定中心的情况下,不超出边界的最大可用半径
|
|
4017
|
+
* @param rect 矩形的大小
|
|
4018
|
+
* @param center 中心点
|
|
4019
|
+
* @param startAngle 起始角度的弧度值
|
|
4020
|
+
* @param endAngle 结束角度的弧度值
|
|
4021
|
+
* @returns 最大半径
|
|
4022
|
+
*/
|
|
3126
4023
|
function calculateMaxRadius(rect, center, startAngle, endAngle) {
|
|
3127
4024
|
const { x, y } = center;
|
|
3128
4025
|
const steps = findBoundaryAngles(startAngle, endAngle);
|
|
@@ -3174,10 +4071,18 @@
|
|
|
3174
4071
|
return 1;
|
|
3175
4072
|
}
|
|
3176
4073
|
|
|
4074
|
+
/* 用于判断2d相交 */
|
|
3177
4075
|
function sub(out, v1, v2) {
|
|
3178
4076
|
out[0] = v1[0] - v2[0];
|
|
3179
4077
|
out[1] = v1[1] - v2[1];
|
|
3180
4078
|
}
|
|
4079
|
+
/**
|
|
4080
|
+
* 判断直线是否相交,投影法
|
|
4081
|
+
* @param left1
|
|
4082
|
+
* @param right1
|
|
4083
|
+
* @param left2
|
|
4084
|
+
* @param right2
|
|
4085
|
+
*/
|
|
3181
4086
|
function isIntersect(left1, right1, left2, right2) {
|
|
3182
4087
|
let min1 = left1[0];
|
|
3183
4088
|
let max1 = right1[0];
|
|
@@ -3204,6 +4109,15 @@
|
|
|
3204
4109
|
}
|
|
3205
4110
|
return true;
|
|
3206
4111
|
}
|
|
4112
|
+
/**
|
|
4113
|
+
* 获取直线交点
|
|
4114
|
+
* 不相交返回false,共线返回true,相交返回交点
|
|
4115
|
+
* https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
|
|
4116
|
+
* @param left1
|
|
4117
|
+
* @param right1
|
|
4118
|
+
* @param left2
|
|
4119
|
+
* @param right2
|
|
4120
|
+
*/
|
|
3207
4121
|
function getIntersectPoint(left1, right1, left2, right2) {
|
|
3208
4122
|
if (!isIntersect(left1, right1, left2, right2)) {
|
|
3209
4123
|
return false;
|
|
@@ -3213,9 +4127,16 @@
|
|
|
3213
4127
|
const tempVec = [0, 0];
|
|
3214
4128
|
sub(dir1, right1, left1);
|
|
3215
4129
|
sub(dir2, right2, left2);
|
|
4130
|
+
// 判断共线
|
|
3216
4131
|
if (fuzzyEqualVec(dir1, dir2)) {
|
|
3217
4132
|
return true;
|
|
3218
4133
|
}
|
|
4134
|
+
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
|
|
4135
|
+
// line1: left1 + dir1 * t
|
|
4136
|
+
// line2: left2 + dir2 * u
|
|
4137
|
+
// 当 left1 + dir1 * t = left2 + dir2 * u => (left1 + dir1 * t) x dir2 = (left2 + dir2 * u) x dir2
|
|
4138
|
+
// => dir1 x dir2 * t = (left2 - left1) x dir2 => t = (left2 - left1) x dir2 / (dir1 x dir2)
|
|
4139
|
+
// 直线不平行,dir1 x dir2 ≠ 0,当0 <= t <= 1时,可以求出交点
|
|
3219
4140
|
sub(tempVec, left2, left1);
|
|
3220
4141
|
const t = crossProduct(tempVec, dir2) / crossProduct(dir1, dir2);
|
|
3221
4142
|
if (t >= 0 && t <= 1) {
|
|
@@ -3223,6 +4144,13 @@
|
|
|
3223
4144
|
}
|
|
3224
4145
|
return false;
|
|
3225
4146
|
}
|
|
4147
|
+
/**
|
|
4148
|
+
* 获取两个rect的相交部分
|
|
4149
|
+
* 如果有bbox为null,返回null,如果不相交返回{x1: 0, y1: 0, x2: 0, y2: 0}
|
|
4150
|
+
* @param bbox1
|
|
4151
|
+
* @param bbox2
|
|
4152
|
+
* @param format
|
|
4153
|
+
*/
|
|
3226
4154
|
function getRectIntersect(bbox1, bbox2, format) {
|
|
3227
4155
|
if (bbox1 === null) {
|
|
3228
4156
|
return bbox2;
|
|
@@ -3267,19 +4195,35 @@
|
|
|
3267
4195
|
}
|
|
3268
4196
|
return { x11, x12, y11, y12, x21, x22, y21, y22 };
|
|
3269
4197
|
};
|
|
4198
|
+
/**
|
|
4199
|
+
* 矩形是否在另一个矩形内部
|
|
4200
|
+
* 返回InnerBBox
|
|
4201
|
+
* @param bbox1
|
|
4202
|
+
* @param bbox2
|
|
4203
|
+
* @param format
|
|
4204
|
+
*/
|
|
3270
4205
|
function rectInsideAnotherRect(bbox1, bbox2, format) {
|
|
3271
4206
|
if (!bbox1 || !bbox2) {
|
|
3272
4207
|
return exports.InnerBBox.NONE;
|
|
3273
4208
|
}
|
|
3274
4209
|
const { x11, x12, y11, y12, x21, x22, y21, y22 } = formatTwoBBox(bbox1, bbox2, format);
|
|
4210
|
+
// bbox1在bbox2内部
|
|
3275
4211
|
if (x11 > x21 && x12 < x22 && y11 > y21 && y12 < y22) {
|
|
3276
4212
|
return exports.InnerBBox.BBOX1;
|
|
3277
4213
|
}
|
|
4214
|
+
// bbox2在bbox1内部
|
|
3278
4215
|
if (x21 > x11 && x22 < x12 && y21 > y11 && y22 < y12) {
|
|
3279
4216
|
return exports.InnerBBox.BBOX2;
|
|
3280
4217
|
}
|
|
3281
4218
|
return exports.InnerBBox.NONE;
|
|
3282
4219
|
}
|
|
4220
|
+
/**
|
|
4221
|
+
* 两个矩形是否相交
|
|
4222
|
+
* 如果有矩形为null,判断为相交
|
|
4223
|
+
* @param bbox1
|
|
4224
|
+
* @param bbox2
|
|
4225
|
+
* @param format
|
|
4226
|
+
*/
|
|
3283
4227
|
function isRectIntersect(bbox1, bbox2, format) {
|
|
3284
4228
|
if (bbox1 && bbox2) {
|
|
3285
4229
|
if (!format) {
|
|
@@ -3296,6 +4240,12 @@
|
|
|
3296
4240
|
}
|
|
3297
4241
|
return true;
|
|
3298
4242
|
}
|
|
4243
|
+
/**
|
|
4244
|
+
* 点在box内部
|
|
4245
|
+
* 如果bbox为null返回true
|
|
4246
|
+
* @param point
|
|
4247
|
+
* @param bbox
|
|
4248
|
+
*/
|
|
3299
4249
|
function pointInRect(point, bbox, format) {
|
|
3300
4250
|
if (!bbox) {
|
|
3301
4251
|
return true;
|
|
@@ -3315,9 +4265,20 @@
|
|
|
3315
4265
|
}
|
|
3316
4266
|
return point.x >= x11 && point.x <= x12 && point.y >= y11 && point.y <= y12;
|
|
3317
4267
|
}
|
|
4268
|
+
// 参考https://github.com/francecil/leetcode/issues/1
|
|
4269
|
+
/**
|
|
4270
|
+
* 计算投影半径
|
|
4271
|
+
* @param {Array(Number)} checkAxis 检测轴 [cosθ,sinθ]
|
|
4272
|
+
* @param {Array} axis 目标轴 [x,y]
|
|
4273
|
+
*/
|
|
3318
4274
|
function getProjectionRadius(checkAxis, axis) {
|
|
3319
4275
|
return Math.abs(axis[0] * checkAxis[0] + axis[1] * checkAxis[1]);
|
|
3320
4276
|
}
|
|
4277
|
+
/**
|
|
4278
|
+
* 逆时针旋转矩阵(从正 x 轴开始,旋转到正 y 轴的方向)
|
|
4279
|
+
* [ cos(θ) -sin(θ) ]
|
|
4280
|
+
* [ sin(θ) cos(θ) ]
|
|
4281
|
+
*/
|
|
3321
4282
|
function rotatePoint({ x, y }, rad, origin = { x: 0, y: 0 }) {
|
|
3322
4283
|
return {
|
|
3323
4284
|
x: (x - origin.x) * Math.cos(rad) - (y - origin.y) * Math.sin(rad) + origin.x,
|
|
@@ -3330,6 +4291,10 @@
|
|
|
3330
4291
|
y: (box.y1 + box.y2) / 2
|
|
3331
4292
|
};
|
|
3332
4293
|
}
|
|
4294
|
+
/**
|
|
4295
|
+
* 转化为顶点坐标数组
|
|
4296
|
+
* @param {Object} box
|
|
4297
|
+
*/
|
|
3333
4298
|
function toRect(box, isDeg) {
|
|
3334
4299
|
const deg = isDeg ? degreeToRadian(box.angle) : box.angle;
|
|
3335
4300
|
const cp = getCenterPoint(box);
|
|
@@ -3358,15 +4323,21 @@
|
|
|
3358
4323
|
const vector = (start, end) => {
|
|
3359
4324
|
return [end.x - start.x, end.y - start.y];
|
|
3360
4325
|
};
|
|
4326
|
+
// 两个矩形的中心点
|
|
3361
4327
|
const p1 = getCenterPoint(box1);
|
|
3362
4328
|
const p2 = getCenterPoint(box2);
|
|
4329
|
+
// 向量 p1p2
|
|
3363
4330
|
const vp1p2 = vector(p1, p2);
|
|
4331
|
+
//矩形1的两边向量
|
|
3364
4332
|
const AB = vector(rect1[0], rect1[1]);
|
|
3365
4333
|
const BC = vector(rect1[1], rect1[2]);
|
|
4334
|
+
//矩形2的两边向量
|
|
3366
4335
|
const A1B1 = vector(rect2[0], rect2[1]);
|
|
3367
4336
|
const B1C1 = vector(rect2[1], rect2[2]);
|
|
4337
|
+
// 矩形1 的两个弧度
|
|
3368
4338
|
const deg11 = isDeg ? degreeToRadian(box1.angle) : box1.angle;
|
|
3369
4339
|
let deg12 = isDeg ? degreeToRadian(90 - box1.angle) : box1.angle + halfPi;
|
|
4340
|
+
// 矩形2 的两个弧度
|
|
3370
4341
|
const deg21 = isDeg ? degreeToRadian(box2.angle) : box2.angle;
|
|
3371
4342
|
let deg22 = isDeg ? degreeToRadian(90 - box2.angle) : box2.angle + halfPi;
|
|
3372
4343
|
if (deg12 > pi2) {
|
|
@@ -3375,10 +4346,12 @@
|
|
|
3375
4346
|
if (deg22 > pi2) {
|
|
3376
4347
|
deg22 -= pi2;
|
|
3377
4348
|
}
|
|
4349
|
+
// 投影重叠
|
|
3378
4350
|
const isCover = (checkAxisRadius, deg, targetAxis1, targetAxis2) => {
|
|
3379
4351
|
const checkAxis = [Math.cos(deg), Math.sin(deg)];
|
|
3380
4352
|
const targetAxisRadius = (getProjectionRadius(checkAxis, targetAxis1) + getProjectionRadius(checkAxis, targetAxis2)) / 2;
|
|
3381
4353
|
const centerPointRadius = getProjectionRadius(checkAxis, vp1p2);
|
|
4354
|
+
// console.log('abc', `checkAxis:${checkAxis},三个投影:${checkAxisRadius}, ${targetAxisRadius}, ${centerPointRadius}`)
|
|
3382
4355
|
return checkAxisRadius + targetAxisRadius > centerPointRadius;
|
|
3383
4356
|
};
|
|
3384
4357
|
return (isCover((box1.x2 - box1.x1) / 2, deg11, A1B1, B1C1) &&
|
|
@@ -3393,6 +4366,7 @@
|
|
|
3393
4366
|
let y2;
|
|
3394
4367
|
function getAABBFromPoints(points) {
|
|
3395
4368
|
(x1 = Infinity), (y1 = Infinity), (x2 = -Infinity), (y2 = -Infinity);
|
|
4369
|
+
// todo 有优化空间
|
|
3396
4370
|
points.forEach(point => {
|
|
3397
4371
|
if (x1 > point.x) {
|
|
3398
4372
|
x1 = point.x;
|
|
@@ -3444,6 +4418,7 @@
|
|
|
3444
4418
|
y22 = temp;
|
|
3445
4419
|
}
|
|
3446
4420
|
}
|
|
4421
|
+
// 不相交直接跳过
|
|
3447
4422
|
if (x11 >= x22 || x12 <= x21 || y11 >= y22 || y12 <= y21) {
|
|
3448
4423
|
return [bounds1, bounds2];
|
|
3449
4424
|
}
|
|
@@ -3465,6 +4440,7 @@
|
|
|
3465
4440
|
const l = [];
|
|
3466
4441
|
list.forEach(b => {
|
|
3467
4442
|
let arr;
|
|
4443
|
+
// 相交
|
|
3468
4444
|
if ((arr = unionAABB(baseBound, b)).length > 1) {
|
|
3469
4445
|
l.push(b);
|
|
3470
4446
|
return;
|
|
@@ -3578,8 +4554,10 @@
|
|
|
3578
4554
|
};
|
|
3579
4555
|
}
|
|
3580
4556
|
estimate({ text, fontSize }) {
|
|
3581
|
-
|
|
3582
|
-
let
|
|
4557
|
+
// 假设只有英文和中文字符
|
|
4558
|
+
let eCharLen = 0; // 英文字符
|
|
4559
|
+
let cCharLen = 0; // 中文字符
|
|
4560
|
+
// 判断ascii码,如果是
|
|
3583
4561
|
for (let i = 0; i < text.length; i++) {
|
|
3584
4562
|
text.charCodeAt(i) < 128 ? eCharLen++ : cCharLen++;
|
|
3585
4563
|
}
|
|
@@ -3597,6 +4575,14 @@
|
|
|
3597
4575
|
}
|
|
3598
4576
|
|
|
3599
4577
|
const EPSILON = 1e-8;
|
|
4578
|
+
// Adapted from https://github.com/apache/echarts/blob/master/src/util/graphic.ts by striezel
|
|
4579
|
+
// License: https://github.com/apache/echarts/blob/master/LICENSE
|
|
4580
|
+
/**
|
|
4581
|
+
* Return `true` if the given line (line `a`) and the given polygon
|
|
4582
|
+
* are intersect.
|
|
4583
|
+
* Note that we do not count colinear as intersect here because no
|
|
4584
|
+
* requirement for that. We could do that if required in future.
|
|
4585
|
+
*/
|
|
3600
4586
|
function lineIntersectPolygon(a1x, a1y, a2x, a2y, points) {
|
|
3601
4587
|
for (let i = 0, p2 = points[points.length - 1]; i < points.length; i++) {
|
|
3602
4588
|
const p = points[i];
|
|
@@ -3607,6 +4593,8 @@
|
|
|
3607
4593
|
}
|
|
3608
4594
|
return false;
|
|
3609
4595
|
}
|
|
4596
|
+
// Adapted from https://github.com/ecomfe/zrender/blob/master/src/contain/polygon.ts by pissang
|
|
4597
|
+
// License: https://github.com/ecomfe/zrender/blob/master/LICENSE
|
|
3610
4598
|
function polygonContainPoint(points, x, y) {
|
|
3611
4599
|
let w = 0;
|
|
3612
4600
|
let p = points[0];
|
|
@@ -3618,30 +4606,44 @@
|
|
|
3618
4606
|
w += isPointInLine(p.x, p.y, p2.x, p2.y, x, y);
|
|
3619
4607
|
p = p2;
|
|
3620
4608
|
}
|
|
4609
|
+
// Close polygon
|
|
3621
4610
|
const p0 = points[0];
|
|
3622
4611
|
if (!isAroundEqual(p.x, p0.x) || !isAroundEqual(p.y, p0.y)) {
|
|
3623
4612
|
w += isPointInLine(p.x, p.y, p0.x, p0.y, x, y);
|
|
3624
4613
|
}
|
|
3625
4614
|
return w !== 0;
|
|
3626
4615
|
}
|
|
4616
|
+
// Adapted from https://github.com/ecomfe/zrender/blob/master/src/contain/windingLine.ts by pissang
|
|
4617
|
+
// License: https://github.com/ecomfe/zrender/blob/master/LICENSE
|
|
3627
4618
|
function isPointInLine(x0, y0, x1, y1, x, y) {
|
|
3628
4619
|
if ((y > y0 && y > y1) || (y < y0 && y < y1)) {
|
|
3629
4620
|
return 0;
|
|
3630
4621
|
}
|
|
4622
|
+
// Ignore horizontal line
|
|
3631
4623
|
if (y1 === y0) {
|
|
3632
4624
|
return 0;
|
|
3633
4625
|
}
|
|
3634
4626
|
const t = (y - y0) / (y1 - y0);
|
|
3635
4627
|
let dir = y1 < y0 ? 1 : -1;
|
|
4628
|
+
// Avoid winding error when intersection point is the connect point of two line of polygon
|
|
3636
4629
|
if (t === 1 || t === 0) {
|
|
3637
4630
|
dir = y1 < y0 ? 0.5 : -0.5;
|
|
3638
4631
|
}
|
|
3639
4632
|
const x_ = t * (x1 - x0) + x0;
|
|
4633
|
+
// If (x, y) on the line, considered as "contain".
|
|
3640
4634
|
return x_ === x ? Infinity : x_ > x ? dir : 0;
|
|
3641
4635
|
}
|
|
4636
|
+
// Adapted from https://github.com/ecomfe/zrender/blob/master/src/contain/polygon.ts by pissang
|
|
4637
|
+
// License: https://github.com/ecomfe/zrender/blob/master/LICENSE
|
|
3642
4638
|
function isAroundEqual(a, b) {
|
|
3643
4639
|
return Math.abs(a - b) < EPSILON;
|
|
3644
4640
|
}
|
|
4641
|
+
/**
|
|
4642
|
+
* polygon图元之间是否相交
|
|
4643
|
+
* @param pointsA 图元A的points
|
|
4644
|
+
* @param pointsB 图元B的points
|
|
4645
|
+
* @returns 是否相交
|
|
4646
|
+
*/
|
|
3645
4647
|
function polygonIntersectPolygon(pointsA, pointsB) {
|
|
3646
4648
|
for (let i = 0; i < pointsB.length; i++) {
|
|
3647
4649
|
if (polygonContainPoint(pointsA, pointsB[i].x, pointsB[i].y)) {
|
|
@@ -3698,9 +4700,11 @@
|
|
|
3698
4700
|
let width = 0;
|
|
3699
4701
|
for (const character of string) {
|
|
3700
4702
|
const codePoint = character.codePointAt(0);
|
|
4703
|
+
// Ignore control characters
|
|
3701
4704
|
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
3702
4705
|
continue;
|
|
3703
4706
|
}
|
|
4707
|
+
// Ignore combining characters
|
|
3704
4708
|
if (codePoint >= 0x300 && codePoint <= 0x36f) {
|
|
3705
4709
|
continue;
|
|
3706
4710
|
}
|
|
@@ -3727,6 +4731,7 @@
|
|
|
3727
4731
|
};
|
|
3728
4732
|
const ansiRegex = ({ onlyFirst = false } = {}) => {
|
|
3729
4733
|
const pattern = [
|
|
4734
|
+
// eslint-disable-next-line max-len
|
|
3730
4735
|
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
3731
4736
|
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
|
|
3732
4737
|
].join('|');
|
|
@@ -3983,9 +4988,12 @@
|
|
|
3983
4988
|
return 'N';
|
|
3984
4989
|
};
|
|
3985
4990
|
const emojiRegex = () => {
|
|
4991
|
+
// https://mths.be/emoji
|
|
4992
|
+
// eslint-disable-next-line max-len
|
|
3986
4993
|
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;
|
|
3987
4994
|
};
|
|
3988
4995
|
|
|
4996
|
+
// FIXME: from VRender
|
|
3989
4997
|
function getContextFont(text, defaultAttr = {}, fontSizeScale) {
|
|
3990
4998
|
if (!fontSizeScale) {
|
|
3991
4999
|
fontSizeScale = 1;
|
|
@@ -4002,16 +5010,25 @@
|
|
|
4002
5010
|
|
|
4003
5011
|
class TextMeasure {
|
|
4004
5012
|
constructor(option, textSpec) {
|
|
5013
|
+
/** 数字大小缓存 */
|
|
4005
5014
|
this._numberCharSize = null;
|
|
5015
|
+
/** 全角字符大小缓存 */
|
|
4006
5016
|
this._fullCharSize = null;
|
|
5017
|
+
/** 字母大小缓存 */
|
|
4007
5018
|
this._letterCharSize = null;
|
|
5019
|
+
/** 特殊字符缓存 */
|
|
4008
5020
|
this._specialCharSizeMap = {};
|
|
5021
|
+
/** 内置测量 canvas */
|
|
4009
5022
|
this._canvas = null;
|
|
4010
5023
|
this._context = null;
|
|
4011
5024
|
this._contextSaved = false;
|
|
5025
|
+
/** 是否不支持 canvas */
|
|
4012
5026
|
this._notSupportCanvas = false;
|
|
5027
|
+
/** 是否不支持 vrender */
|
|
4013
5028
|
this._notSupportVRender = false;
|
|
5029
|
+
/** 文字 spec */
|
|
4014
5030
|
this._userSpec = {};
|
|
5031
|
+
/** 特殊字符集 */
|
|
4015
5032
|
this.specialCharSet = '-/: .,@%\'"~';
|
|
4016
5033
|
this._option = option;
|
|
4017
5034
|
this._userSpec = textSpec !== null && textSpec !== void 0 ? textSpec : {};
|
|
@@ -4023,6 +5040,7 @@
|
|
|
4023
5040
|
? this.fullMeasure.bind(this)
|
|
4024
5041
|
: this.measureWithNaiveCanvas.bind(this);
|
|
4025
5042
|
}
|
|
5043
|
+
/** 初始化测量相关上下文 */
|
|
4026
5044
|
initContext() {
|
|
4027
5045
|
if (this._notSupportCanvas) {
|
|
4028
5046
|
return false;
|
|
@@ -4036,6 +5054,7 @@
|
|
|
4036
5054
|
typeof window.document !== 'undefined' &&
|
|
4037
5055
|
globalThis &&
|
|
4038
5056
|
isValid(globalThis.document)) {
|
|
5057
|
+
// 默认创建方法
|
|
4039
5058
|
this._canvas = globalThis.document.createElement('canvas');
|
|
4040
5059
|
}
|
|
4041
5060
|
}
|
|
@@ -4076,6 +5095,7 @@
|
|
|
4076
5095
|
lineHeight
|
|
4077
5096
|
};
|
|
4078
5097
|
}
|
|
5098
|
+
/** 计算文本宽高 */
|
|
4079
5099
|
measure(text, method) {
|
|
4080
5100
|
switch (method) {
|
|
4081
5101
|
case 'vrender':
|
|
@@ -4090,12 +5110,13 @@
|
|
|
4090
5110
|
return this.quickMeasure(text);
|
|
4091
5111
|
}
|
|
4092
5112
|
}
|
|
5113
|
+
/** 精确计算文本宽高 */
|
|
4093
5114
|
fullMeasure(text) {
|
|
4094
5115
|
if (isNil(text)) {
|
|
4095
5116
|
return { width: 0, height: 0 };
|
|
4096
5117
|
}
|
|
4097
5118
|
if (isNil(this._option.getTextBounds) || !this._notSupportVRender) {
|
|
4098
|
-
return this.measureWithNaiveCanvas(text);
|
|
5119
|
+
return this.measureWithNaiveCanvas(text); // 降级
|
|
4099
5120
|
}
|
|
4100
5121
|
const { fontFamily, fontSize, fontWeight, textAlign, textBaseline, ellipsis, limit, lineHeight } = this.textSpec;
|
|
4101
5122
|
let size;
|
|
@@ -4115,17 +5136,18 @@
|
|
|
4115
5136
|
}
|
|
4116
5137
|
catch (e) {
|
|
4117
5138
|
this._notSupportVRender = true;
|
|
4118
|
-
size = this.measureWithNaiveCanvas(text);
|
|
5139
|
+
size = this.measureWithNaiveCanvas(text); // 降级
|
|
4119
5140
|
}
|
|
4120
5141
|
return size;
|
|
4121
5142
|
}
|
|
5143
|
+
/** 通过原生 canvas 精确计算文本宽度,高度为估算 */
|
|
4122
5144
|
measureWithNaiveCanvas(text) {
|
|
4123
5145
|
return this._measureReduce(text, this._measureWithNaiveCanvas.bind(this));
|
|
4124
5146
|
}
|
|
4125
5147
|
_measureWithNaiveCanvas(text) {
|
|
4126
5148
|
var _a;
|
|
4127
5149
|
if (!this.initContext()) {
|
|
4128
|
-
return this._quickMeasureWithoutCanvas(text);
|
|
5150
|
+
return this._quickMeasureWithoutCanvas(text); // 降级
|
|
4129
5151
|
}
|
|
4130
5152
|
const metrics = this._context.measureText(text);
|
|
4131
5153
|
const { fontSize, lineHeight } = this.textSpec;
|
|
@@ -4136,6 +5158,7 @@
|
|
|
4136
5158
|
fontBoundingBoxDescent: metrics.fontBoundingBoxDescent
|
|
4137
5159
|
};
|
|
4138
5160
|
}
|
|
5161
|
+
/** 快速估算文本宽高 */
|
|
4139
5162
|
quickMeasure(text) {
|
|
4140
5163
|
return this._measureReduce(text, this._quickMeasure.bind(this));
|
|
4141
5164
|
}
|
|
@@ -4146,13 +5169,17 @@
|
|
|
4146
5169
|
};
|
|
4147
5170
|
for (let i = 0; i < text.length; i++) {
|
|
4148
5171
|
const char = text[i];
|
|
5172
|
+
// 先判断是否特殊字符
|
|
4149
5173
|
let size = this._measureSpecialChar(char);
|
|
5174
|
+
// 再判断是否数字
|
|
4150
5175
|
if (isNil(size) && TextMeasure.NUMBERS_CHAR_SET.includes(char)) {
|
|
4151
5176
|
size = this._measureNumberChar();
|
|
4152
5177
|
}
|
|
5178
|
+
// 再判断全角字符
|
|
4153
5179
|
if (isNil(size) && ['F', 'W'].includes(eastAsianCharacterInfo(char))) {
|
|
4154
5180
|
size = this._measureFullSizeChar();
|
|
4155
5181
|
}
|
|
5182
|
+
// 最后按半角字符处理
|
|
4156
5183
|
if (isNil(size)) {
|
|
4157
5184
|
size = this._measureLetterChar();
|
|
4158
5185
|
}
|
|
@@ -4163,6 +5190,7 @@
|
|
|
4163
5190
|
}
|
|
4164
5191
|
return totalSize;
|
|
4165
5192
|
}
|
|
5193
|
+
/** 不使用 canvas 来快速估算文本宽高 */
|
|
4166
5194
|
quickMeasureWithoutCanvas(text) {
|
|
4167
5195
|
return this._measureReduce(text, this._quickMeasureWithoutCanvas.bind(this));
|
|
4168
5196
|
}
|
|
@@ -4175,6 +5203,7 @@
|
|
|
4175
5203
|
const { fontSize, lineHeight } = this.textSpec;
|
|
4176
5204
|
for (let i = 0; i < text.length; i++) {
|
|
4177
5205
|
const char = text[i];
|
|
5206
|
+
// 判断全角字符
|
|
4178
5207
|
const size = ['F', 'W'].includes(eastAsianCharacterInfo(char)) ? 1 : 0.53;
|
|
4179
5208
|
totalSize.width += size * fontSize;
|
|
4180
5209
|
}
|
|
@@ -4198,11 +5227,12 @@
|
|
|
4198
5227
|
}
|
|
4199
5228
|
return {
|
|
4200
5229
|
width: textArr.reduce((maxWidth, cur) => Math.max(maxWidth, processor(cur).width), 0),
|
|
4201
|
-
height: textArr.length * (((_a = lineHeight) !== null && _a !== void 0 ? _a : fontSize) + 1) + 1
|
|
5230
|
+
height: textArr.length * (((_a = lineHeight) !== null && _a !== void 0 ? _a : fontSize) + 1) + 1 // 经验值
|
|
4202
5231
|
};
|
|
4203
5232
|
}
|
|
4204
5233
|
return processor(text.toString());
|
|
4205
5234
|
}
|
|
5235
|
+
/** 测量一个数字 */
|
|
4206
5236
|
_measureNumberChar() {
|
|
4207
5237
|
if (isNil(this._numberCharSize)) {
|
|
4208
5238
|
const numberBounds = this._standardMethod(TextMeasure.NUMBERS_CHAR_SET);
|
|
@@ -4215,12 +5245,14 @@
|
|
|
4215
5245
|
}
|
|
4216
5246
|
return this._numberCharSize;
|
|
4217
5247
|
}
|
|
5248
|
+
/** 测量一个全角字符 */
|
|
4218
5249
|
_measureFullSizeChar() {
|
|
4219
5250
|
if (isNil(this._fullCharSize)) {
|
|
4220
5251
|
this._fullCharSize = this._standardMethod(TextMeasure.FULL_SIZE_CHAR);
|
|
4221
5252
|
}
|
|
4222
5253
|
return this._fullCharSize;
|
|
4223
5254
|
}
|
|
5255
|
+
/** 测量一个英文字符 */
|
|
4224
5256
|
_measureLetterChar() {
|
|
4225
5257
|
if (isNil(this._letterCharSize)) {
|
|
4226
5258
|
const alphabetBounds = this._standardMethod(TextMeasure.ALPHABET_CHAR_SET);
|
|
@@ -4233,6 +5265,7 @@
|
|
|
4233
5265
|
}
|
|
4234
5266
|
return this._letterCharSize;
|
|
4235
5267
|
}
|
|
5268
|
+
/** 测量一个特殊字符 */
|
|
4236
5269
|
_measureSpecialChar(char) {
|
|
4237
5270
|
if (isValid(this._specialCharSizeMap[char])) {
|
|
4238
5271
|
return this._specialCharSizeMap[char];
|
|
@@ -4256,8 +5289,11 @@
|
|
|
4256
5289
|
}
|
|
4257
5290
|
}
|
|
4258
5291
|
}
|
|
5292
|
+
/** 英文字母集 */
|
|
4259
5293
|
TextMeasure.ALPHABET_CHAR_SET = 'abcdefghijklmnopqrstuvwxyz';
|
|
5294
|
+
/** 数字集 */
|
|
4260
5295
|
TextMeasure.NUMBERS_CHAR_SET = '0123456789';
|
|
5296
|
+
/** 全角字符 */
|
|
4261
5297
|
TextMeasure.FULL_SIZE_CHAR = '字';
|
|
4262
5298
|
|
|
4263
5299
|
const calculateAnchorOfBounds = (bounds, anchorType) => {
|
|
@@ -4313,26 +5349,28 @@
|
|
|
4313
5349
|
{ x: Math.cos(a.angle), y: Math.sin(a.angle) },
|
|
4314
5350
|
{ x: -Math.sin(a.angle), y: Math.cos(a.angle) },
|
|
4315
5351
|
{ x: Math.cos(b.angle), y: Math.sin(a.angle) },
|
|
4316
|
-
{ x: -Math.sin(b.angle), y: Math.cos(a.angle) }
|
|
5352
|
+
{ x: -Math.sin(b.angle), y: Math.cos(a.angle) } // Rect B's second axis
|
|
4317
5353
|
];
|
|
5354
|
+
// calculate the projection range of a rectangle on a given axis
|
|
4318
5355
|
function getProjectionRange(obb, axisX, axisY) {
|
|
4319
5356
|
const corners = obb.getRotatedCorners();
|
|
4320
5357
|
const projections = corners.map(p => p.x * axisX + p.y * axisY);
|
|
4321
5358
|
return { min: Math.min(...projections), max: Math.max(...projections) };
|
|
4322
5359
|
}
|
|
5360
|
+
// Calculate distances for all axes
|
|
4323
5361
|
let maxDistance = 0;
|
|
4324
5362
|
for (const axis of axes) {
|
|
4325
5363
|
const rangeA = getProjectionRange(a, axis.x, axis.y);
|
|
4326
5364
|
const rangeB = getProjectionRange(b, axis.x, axis.y);
|
|
4327
5365
|
let distance;
|
|
4328
5366
|
if (rangeA.max < rangeB.min) {
|
|
4329
|
-
distance = rangeB.min - rangeA.max;
|
|
5367
|
+
distance = rangeB.min - rangeA.max; // B is to the right of A
|
|
4330
5368
|
}
|
|
4331
5369
|
else if (rangeB.max < rangeA.min) {
|
|
4332
|
-
distance = rangeA.min - rangeB.max;
|
|
5370
|
+
distance = rangeA.min - rangeB.max; // A is to the right of B
|
|
4333
5371
|
}
|
|
4334
5372
|
else {
|
|
4335
|
-
distance = 0;
|
|
5373
|
+
distance = 0; // Overlapping
|
|
4336
5374
|
}
|
|
4337
5375
|
maxDistance = Math.max(maxDistance, distance);
|
|
4338
5376
|
}
|
|
@@ -4341,6 +5379,7 @@
|
|
|
4341
5379
|
|
|
4342
5380
|
function transformBoundsWithMatrix(out, bounds, matrix) {
|
|
4343
5381
|
const { x1, y1, x2, y2 } = bounds;
|
|
5382
|
+
// 如果没有旋转和缩放,那就直接translate
|
|
4344
5383
|
if (matrix.onlyTranslate()) {
|
|
4345
5384
|
if (out !== bounds) {
|
|
4346
5385
|
out.setValue(bounds.x1, bounds.y1, bounds.x2, bounds.y2);
|
|
@@ -4355,6 +5394,16 @@
|
|
|
4355
5394
|
out.add(matrix.a * x1 + matrix.c * y2 + matrix.e, matrix.b * x1 + matrix.d * y2 + matrix.f);
|
|
4356
5395
|
return bounds;
|
|
4357
5396
|
}
|
|
5397
|
+
/**
|
|
5398
|
+
* 依据graphic的属性对bounds进行变换
|
|
5399
|
+
* @param bounds
|
|
5400
|
+
* @param x
|
|
5401
|
+
* @param y
|
|
5402
|
+
* @param scaleX
|
|
5403
|
+
* @param scaleY
|
|
5404
|
+
* @param angle
|
|
5405
|
+
* @param rotateCenter
|
|
5406
|
+
*/
|
|
4358
5407
|
function transformBounds(bounds, x, y, scaleX, scaleY, angle, rotateCenter) {
|
|
4359
5408
|
if (abs(scaleX) <= epsilon || abs(scaleY) <= epsilon) {
|
|
4360
5409
|
return;
|
|
@@ -4372,6 +5421,37 @@
|
|
|
4372
5421
|
}
|
|
4373
5422
|
bounds.translate(x, y);
|
|
4374
5423
|
}
|
|
5424
|
+
/**
|
|
5425
|
+
Copyright (c) 2015-2021, University of Washington Interactive Data Lab
|
|
5426
|
+
All rights reserved.
|
|
5427
|
+
|
|
5428
|
+
Redistribution and use in source and binary forms, with or without
|
|
5429
|
+
modification, are permitted provided that the following conditions are met:
|
|
5430
|
+
|
|
5431
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
5432
|
+
list of conditions and the following disclaimer.
|
|
5433
|
+
|
|
5434
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
5435
|
+
this list of conditions and the following disclaimer in the documentation
|
|
5436
|
+
and/or other materials provided with the distribution.
|
|
5437
|
+
|
|
5438
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
5439
|
+
may be used to endorse or promote products derived from this software
|
|
5440
|
+
without specific prior written permission.
|
|
5441
|
+
|
|
5442
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
5443
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
5444
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
5445
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
5446
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
5447
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
5448
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
5449
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
5450
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
5451
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
5452
|
+
*/
|
|
5453
|
+
// 基于Vega的Bounds重构
|
|
5454
|
+
// https://github.com/vega/vega/blob/825bfaba6ccfe3306183df22b8c955a07bb30714/packages/vega-scenegraph/src/Bounds.js
|
|
4375
5455
|
class Bounds {
|
|
4376
5456
|
constructor(bounds) {
|
|
4377
5457
|
if (bounds) {
|
|
@@ -4478,6 +5558,11 @@
|
|
|
4478
5558
|
const p = this.scalePoints(sx, sy, x, y);
|
|
4479
5559
|
return this.clear().add(p[0], p[1]).add(p[2], p[3]);
|
|
4480
5560
|
}
|
|
5561
|
+
/**
|
|
5562
|
+
* 并集
|
|
5563
|
+
* @param b
|
|
5564
|
+
* @returns
|
|
5565
|
+
*/
|
|
4481
5566
|
union(b) {
|
|
4482
5567
|
if (b.x1 < this.x1) {
|
|
4483
5568
|
this.x1 = b.x1;
|
|
@@ -4493,6 +5578,11 @@
|
|
|
4493
5578
|
}
|
|
4494
5579
|
return this;
|
|
4495
5580
|
}
|
|
5581
|
+
/**
|
|
5582
|
+
* 交集
|
|
5583
|
+
* @param b
|
|
5584
|
+
* @returns
|
|
5585
|
+
*/
|
|
4496
5586
|
intersect(b) {
|
|
4497
5587
|
if (b.x1 > this.x1) {
|
|
4498
5588
|
this.x1 = b.x1;
|
|
@@ -4508,15 +5598,36 @@
|
|
|
4508
5598
|
}
|
|
4509
5599
|
return this;
|
|
4510
5600
|
}
|
|
5601
|
+
/**
|
|
5602
|
+
* 是否包含b
|
|
5603
|
+
* @param b
|
|
5604
|
+
* @returns
|
|
5605
|
+
*/
|
|
4511
5606
|
encloses(b) {
|
|
4512
5607
|
return b && this.x1 <= b.x1 && this.x2 >= b.x2 && this.y1 <= b.y1 && this.y2 >= b.y2;
|
|
4513
5608
|
}
|
|
5609
|
+
/**
|
|
5610
|
+
* 是否共边
|
|
5611
|
+
* @param b
|
|
5612
|
+
* @returns
|
|
5613
|
+
*/
|
|
4514
5614
|
alignsWith(b) {
|
|
4515
5615
|
return b && (this.x1 === b.x1 || this.x2 === b.x2 || this.y1 === b.y1 || this.y2 === b.y2);
|
|
4516
5616
|
}
|
|
5617
|
+
/**
|
|
5618
|
+
* 是否相交
|
|
5619
|
+
* @param b
|
|
5620
|
+
* @returns
|
|
5621
|
+
*/
|
|
4517
5622
|
intersects(b) {
|
|
4518
5623
|
return b && !(this.x2 < b.x1 || this.x1 > b.x2 || this.y2 < b.y1 || this.y1 > b.y2);
|
|
4519
5624
|
}
|
|
5625
|
+
/**
|
|
5626
|
+
* 是否包含
|
|
5627
|
+
* @param x
|
|
5628
|
+
* @param y
|
|
5629
|
+
* @returns
|
|
5630
|
+
*/
|
|
4520
5631
|
contains(x = 0, y = 0) {
|
|
4521
5632
|
return !(x < this.x1 || x > this.x2 || y < this.y1 || y > this.y2);
|
|
4522
5633
|
}
|
|
@@ -4622,6 +5733,7 @@
|
|
|
4622
5733
|
this.f = f;
|
|
4623
5734
|
}
|
|
4624
5735
|
equalToMatrix(m2) {
|
|
5736
|
+
// ef -> ad -> bc
|
|
4625
5737
|
return !(this.e !== m2.e ||
|
|
4626
5738
|
this.f !== m2.f ||
|
|
4627
5739
|
this.a !== m2.a ||
|
|
@@ -4630,6 +5742,7 @@
|
|
|
4630
5742
|
this.c !== m2.c);
|
|
4631
5743
|
}
|
|
4632
5744
|
equalTo(a, b, c, d, e, f) {
|
|
5745
|
+
// ef -> ad -> bc
|
|
4633
5746
|
return !(this.e !== e || this.f !== f || this.a !== a || this.d !== d || this.b !== b || this.c !== c);
|
|
4634
5747
|
}
|
|
4635
5748
|
setValue(a, b, c, d, e, f) {
|
|
@@ -4650,6 +5763,9 @@
|
|
|
4650
5763
|
this.f = 0;
|
|
4651
5764
|
return this;
|
|
4652
5765
|
}
|
|
5766
|
+
/**
|
|
5767
|
+
* 获取当前矩阵的逆矩阵
|
|
5768
|
+
*/
|
|
4653
5769
|
getInverse() {
|
|
4654
5770
|
const a = this.a;
|
|
4655
5771
|
const b = this.b;
|
|
@@ -4681,6 +5797,17 @@
|
|
|
4681
5797
|
return this;
|
|
4682
5798
|
}
|
|
4683
5799
|
rotateByCenter(rad, cx, cy) {
|
|
5800
|
+
/** rotate matrix
|
|
5801
|
+
* | cos -sin (1-cos)*cx+sin*cy |
|
|
5802
|
+
* | sin cos (1-cos)*cy-sin*cx |
|
|
5803
|
+
* | 0 0 1 |
|
|
5804
|
+
* after multiply
|
|
5805
|
+
* m13 = (1-cos)*cx+sin*cy;
|
|
5806
|
+
* m23 = (1-cos)*cy-sin*cx;
|
|
5807
|
+
* | cos*a-sin*b cos*c-sin*d cos*e-sin*f+m13 |
|
|
5808
|
+
* | sin*a+cos*b sin*c+cos*d sin*e+cos*f+m23 |
|
|
5809
|
+
* | 0 0 1 |
|
|
5810
|
+
*/
|
|
4684
5811
|
const cos = Math.cos(rad);
|
|
4685
5812
|
const sin = Math.sin(rad);
|
|
4686
5813
|
const rotateM13 = (1 - cos) * cx + sin * cy;
|
|
@@ -4723,6 +5850,15 @@
|
|
|
4723
5850
|
return this;
|
|
4724
5851
|
}
|
|
4725
5852
|
transpose() {
|
|
5853
|
+
/** transpose matrix
|
|
5854
|
+
* | 0 1 0 |
|
|
5855
|
+
* | 1 0 0 |
|
|
5856
|
+
* | 0 0 1 |
|
|
5857
|
+
* after multiply
|
|
5858
|
+
* | b d f |
|
|
5859
|
+
* | a c e |
|
|
5860
|
+
* | 0 0 1 |
|
|
5861
|
+
*/
|
|
4726
5862
|
const { a, b, c, d, e, f } = this;
|
|
4727
5863
|
this.a = b;
|
|
4728
5864
|
this.b = a;
|
|
@@ -4732,6 +5868,10 @@
|
|
|
4732
5868
|
this.f = e;
|
|
4733
5869
|
return this;
|
|
4734
5870
|
}
|
|
5871
|
+
/**
|
|
5872
|
+
* 矩阵相乘
|
|
5873
|
+
* @param matrix
|
|
5874
|
+
*/
|
|
4735
5875
|
multiply(a2, b2, c2, d2, e2, f2) {
|
|
4736
5876
|
const a1 = this.a;
|
|
4737
5877
|
const b1 = this.b;
|
|
@@ -4753,6 +5893,11 @@
|
|
|
4753
5893
|
this.f = dy;
|
|
4754
5894
|
return this;
|
|
4755
5895
|
}
|
|
5896
|
+
/**
|
|
5897
|
+
* 插值计算
|
|
5898
|
+
* @param m2
|
|
5899
|
+
* @param t
|
|
5900
|
+
*/
|
|
4756
5901
|
interpolate(m2, t) {
|
|
4757
5902
|
const m = new Matrix();
|
|
4758
5903
|
m.a = this.a + (m2.a - this.a) * t;
|
|
@@ -4763,6 +5908,11 @@
|
|
|
4763
5908
|
m.f = this.f + (m2.f - this.f) * t;
|
|
4764
5909
|
return m;
|
|
4765
5910
|
}
|
|
5911
|
+
/**
|
|
5912
|
+
* 将point转到当前矩阵的坐标空间中
|
|
5913
|
+
* @param source
|
|
5914
|
+
* @param target
|
|
5915
|
+
*/
|
|
4766
5916
|
transformPoint(source, target) {
|
|
4767
5917
|
const { a, b, c, d, e, f } = this;
|
|
4768
5918
|
const dt = a * d - b * c;
|
|
@@ -4776,6 +5926,7 @@
|
|
|
4776
5926
|
target.x = x * nextA + y * nextC + nextE;
|
|
4777
5927
|
target.y = x * nextB + y * nextD + nextF;
|
|
4778
5928
|
}
|
|
5929
|
+
// 只有translate
|
|
4779
5930
|
onlyTranslate(scale = 1) {
|
|
4780
5931
|
return this.a === scale && this.b === 0 && this.c === 0 && this.d === scale;
|
|
4781
5932
|
}
|
|
@@ -4799,6 +5950,7 @@
|
|
|
4799
5950
|
skewX: 0,
|
|
4800
5951
|
skewY: 0
|
|
4801
5952
|
};
|
|
5953
|
+
// Apply the QR-like decomposition.
|
|
4802
5954
|
if (a !== 0 || b !== 0) {
|
|
4803
5955
|
const r = Math.sqrt(a * a + b * b);
|
|
4804
5956
|
result.rotateDeg = b > 0 ? Math.acos(a / r) : -Math.acos(a / r);
|
|
@@ -4820,6 +5972,17 @@
|
|
|
4820
5972
|
return result;
|
|
4821
5973
|
}
|
|
4822
5974
|
}
|
|
5975
|
+
/**
|
|
5976
|
+
* 对matrix进行通常的变换(基于rotateCenter进行旋转的同时进行translate和scale)
|
|
5977
|
+
* @param out
|
|
5978
|
+
* @param origin
|
|
5979
|
+
* @param x
|
|
5980
|
+
* @param y
|
|
5981
|
+
* @param scaleX
|
|
5982
|
+
* @param scaleY
|
|
5983
|
+
* @param angle
|
|
5984
|
+
* @param rotateCenter
|
|
5985
|
+
*/
|
|
4823
5986
|
function normalTransform(out, origin, x, y, scaleX, scaleY, angle, rotateCenter) {
|
|
4824
5987
|
const oa = origin.a;
|
|
4825
5988
|
const ob = origin.b;
|
|
@@ -4853,18 +6016,20 @@
|
|
|
4853
6016
|
out.f = of + ob * rotateCenterX + od * rotateCenterY - b1 * offsetX - d1 * offsetY;
|
|
4854
6017
|
}
|
|
4855
6018
|
|
|
6019
|
+
// LRU清除
|
|
4856
6020
|
class LRU {
|
|
4857
6021
|
constructor() {
|
|
4858
6022
|
this.CLEAN_THRESHOLD = 1e3;
|
|
4859
|
-
this.L_TIME = 1000;
|
|
4860
|
-
this.R_COUNT = 1;
|
|
4861
|
-
this.R_TIMESTAMP_MAX_SIZE = 20;
|
|
6023
|
+
this.L_TIME = 1000; // 1000ms以内
|
|
6024
|
+
this.R_COUNT = 1; // 使用次数为0
|
|
6025
|
+
this.R_TIMESTAMP_MAX_SIZE = 20; // timestamp最大长度
|
|
4862
6026
|
}
|
|
4863
6027
|
clearCache(cache, params) {
|
|
4864
6028
|
const { CLEAN_THRESHOLD = this.CLEAN_THRESHOLD, L_TIME = this.L_TIME, R_COUNT = this.R_COUNT } = params;
|
|
4865
6029
|
if (cache.size < CLEAN_THRESHOLD) {
|
|
4866
6030
|
return 0;
|
|
4867
6031
|
}
|
|
6032
|
+
// 开始清理,清理最近1000ms内使用次数为0的缓存
|
|
4868
6033
|
let clearNum = 0;
|
|
4869
6034
|
const clear = (key) => {
|
|
4870
6035
|
clearNum++;
|
|
@@ -4874,7 +6039,7 @@
|
|
|
4874
6039
|
cache.forEach((item, key) => {
|
|
4875
6040
|
if (item.timestamp.length < R_COUNT) {
|
|
4876
6041
|
return clear(key);
|
|
4877
|
-
}
|
|
6042
|
+
} // 最近使用次数小于R_COUNT,清除
|
|
4878
6043
|
let useCount = 0;
|
|
4879
6044
|
while (now - item.timestamp[item.timestamp.length - 1 - useCount] < L_TIME) {
|
|
4880
6045
|
useCount++;
|
|
@@ -4884,7 +6049,8 @@
|
|
|
4884
6049
|
}
|
|
4885
6050
|
if (useCount < R_COUNT) {
|
|
4886
6051
|
return clear(key);
|
|
4887
|
-
}
|
|
6052
|
+
} // 最近L_TIME以内使用次数小于R_COUNT,清除
|
|
6053
|
+
// 清除L_TIME以外的key,避免数组过长
|
|
4888
6054
|
while (now - item.timestamp[0] > L_TIME) {
|
|
4889
6055
|
item.timestamp.shift();
|
|
4890
6056
|
}
|
|
@@ -4895,6 +6061,7 @@
|
|
|
4895
6061
|
addLimitedTimestamp(cacheItem, t, params) {
|
|
4896
6062
|
const { R_TIMESTAMP_MAX_SIZE = this.R_TIMESTAMP_MAX_SIZE } = params;
|
|
4897
6063
|
if (cacheItem.timestamp.length > R_TIMESTAMP_MAX_SIZE) {
|
|
6064
|
+
// cacheItem.timestamp[cacheItem.timestamp.length-1] =
|
|
4898
6065
|
cacheItem.timestamp.shift();
|
|
4899
6066
|
}
|
|
4900
6067
|
cacheItem.timestamp.push(t);
|
|
@@ -4966,6 +6133,7 @@
|
|
|
4966
6133
|
r /= 255;
|
|
4967
6134
|
g /= 255;
|
|
4968
6135
|
b /= 255;
|
|
6136
|
+
// Find greatest and smallest channel values
|
|
4969
6137
|
const cMin = Math.min(r, g, b);
|
|
4970
6138
|
const cMax = Math.max(r, g, b);
|
|
4971
6139
|
const delta = cMax - cMin;
|
|
@@ -4975,12 +6143,15 @@
|
|
|
4975
6143
|
if (delta === 0) {
|
|
4976
6144
|
h = 0;
|
|
4977
6145
|
}
|
|
6146
|
+
// Red is max
|
|
4978
6147
|
else if (cMax === r) {
|
|
4979
6148
|
h = ((g - b) / delta) % 6;
|
|
4980
6149
|
}
|
|
6150
|
+
// Green is max
|
|
4981
6151
|
else if (cMax === g) {
|
|
4982
6152
|
h = (b - r) / delta + 2;
|
|
4983
6153
|
}
|
|
6154
|
+
// Blue is max
|
|
4984
6155
|
else {
|
|
4985
6156
|
h = (r - g) / delta + 4;
|
|
4986
6157
|
}
|
|
@@ -4989,7 +6160,9 @@
|
|
|
4989
6160
|
h += 360;
|
|
4990
6161
|
}
|
|
4991
6162
|
l = (cMax + cMin) / 2;
|
|
6163
|
+
// Calculate saturation
|
|
4992
6164
|
s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
|
|
6165
|
+
// Multiply l and s by 100
|
|
4993
6166
|
s = +(s * 100).toFixed(1);
|
|
4994
6167
|
l = +(l * 100).toFixed(1);
|
|
4995
6168
|
return {
|
|
@@ -4999,6 +6172,34 @@
|
|
|
4999
6172
|
};
|
|
5000
6173
|
}
|
|
5001
6174
|
|
|
6175
|
+
/*
|
|
6176
|
+
Copyright (c) 2013, Jason Davies.
|
|
6177
|
+
All rights reserved.
|
|
6178
|
+
|
|
6179
|
+
Redistribution and use in source and binary forms, with or without
|
|
6180
|
+
modification, are permitted provided that the following conditions are met:
|
|
6181
|
+
|
|
6182
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
6183
|
+
list of conditions and the following disclaimer.
|
|
6184
|
+
|
|
6185
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
6186
|
+
this list of conditions and the following disclaimer in the documentation
|
|
6187
|
+
and/or other materials provided with the distribution.
|
|
6188
|
+
|
|
6189
|
+
* The name Jason Davies may not be used to endorse or promote products
|
|
6190
|
+
derived from this software without specific prior written permission.
|
|
6191
|
+
|
|
6192
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
6193
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
6194
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
6195
|
+
DISCLAIMED. IN NO EVENT SHALL JASON DAVIES BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
6196
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
6197
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
6198
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
6199
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
6200
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
6201
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
6202
|
+
*/
|
|
5002
6203
|
const REG_HEX = /^#([0-9a-f]{3,8})$/;
|
|
5003
6204
|
const DEFAULT_COLORS_OPACITY = {
|
|
5004
6205
|
transparent: 0xffffff00
|
|
@@ -5164,6 +6365,7 @@
|
|
|
5164
6365
|
else if (isArray(value)) {
|
|
5165
6366
|
return new RGB(value[0], value[1], value[2]);
|
|
5166
6367
|
}
|
|
6368
|
+
// default return
|
|
5167
6369
|
return new RGB(255, 255, 255);
|
|
5168
6370
|
}
|
|
5169
6371
|
function rgba(value) {
|
|
@@ -5173,6 +6375,7 @@
|
|
|
5173
6375
|
else if (isArray(value)) {
|
|
5174
6376
|
return new RGB(value[0], value[1], value[2], value[3]);
|
|
5175
6377
|
}
|
|
6378
|
+
// default return
|
|
5176
6379
|
return new RGB(255, 255, 255, 1);
|
|
5177
6380
|
}
|
|
5178
6381
|
function SRGBToLinear(c) {
|
|
@@ -5186,12 +6389,15 @@
|
|
|
5186
6389
|
if (forceHex || isHex) {
|
|
5187
6390
|
const hex = parseInt(isHex[1], 16);
|
|
5188
6391
|
const hexLength = isHex[1].length;
|
|
6392
|
+
// #fff
|
|
5189
6393
|
if (hexLength === 3) {
|
|
5190
6394
|
return new RGB(((hex >> 8) & 0xf) + (((hex >> 8) & 0xf) << 4), ((hex >> 4) & 0xf) + (((hex >> 4) & 0xf) << 4), (hex & 0xf) + ((hex & 0xf) << 4), 1);
|
|
5191
6395
|
}
|
|
6396
|
+
// #ffffff
|
|
5192
6397
|
if (hexLength === 6) {
|
|
5193
6398
|
return rgb(hex);
|
|
5194
6399
|
}
|
|
6400
|
+
// #ffffffaa
|
|
5195
6401
|
else if (hexLength === 8) {
|
|
5196
6402
|
return new RGB((hex >> 24) & 0xff, (hex >> 16) & 0xff, (hex >> 8) & 0xff, (hex & 0xff) / 0xff);
|
|
5197
6403
|
}
|
|
@@ -5232,21 +6438,26 @@
|
|
|
5232
6438
|
}
|
|
5233
6439
|
}
|
|
5234
6440
|
static parseColorString(value) {
|
|
6441
|
+
// case0: transparent
|
|
5235
6442
|
if (isValid(DEFAULT_COLORS_OPACITY[value])) {
|
|
5236
6443
|
return rgba(DEFAULT_COLORS_OPACITY[value]);
|
|
5237
6444
|
}
|
|
6445
|
+
// case1: 简写 ['blue','red']
|
|
5238
6446
|
if (isValid(DEFAULT_COLORS[value])) {
|
|
5239
6447
|
return rgb(DEFAULT_COLORS[value]);
|
|
5240
6448
|
}
|
|
5241
6449
|
const formatValue = `${value}`.trim().toLowerCase();
|
|
6450
|
+
// case2: 16进制
|
|
5242
6451
|
const hexRes = setHex(formatValue);
|
|
5243
6452
|
if (hexRes !== undefined) {
|
|
5244
6453
|
return hexRes;
|
|
5245
6454
|
}
|
|
6455
|
+
// case3 : rgb
|
|
5246
6456
|
if (/^(rgb|RGB|rgba|RGBA)/.test(formatValue)) {
|
|
5247
6457
|
const aColor = formatValue.replace(/(?:\(|\)|rgba|RGBA|rgb|RGB)*/g, '').split(',');
|
|
5248
6458
|
return new RGB(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10), parseFloat(aColor[3]));
|
|
5249
6459
|
}
|
|
6460
|
+
// case4: hsl
|
|
5250
6461
|
if (/^(hsl|HSL|hsla|HSLA)/.test(formatValue)) {
|
|
5251
6462
|
const aColor = formatValue.replace(/(?:\(|\)|hsla|HSLA|hsl|HSL)*/g, '').split(',');
|
|
5252
6463
|
const rgb = hslToRgb(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10));
|
|
@@ -5256,10 +6467,12 @@
|
|
|
5256
6467
|
}
|
|
5257
6468
|
constructor(value) {
|
|
5258
6469
|
const color = Color.parseColorString(value);
|
|
6470
|
+
// case0: transparent
|
|
5259
6471
|
if (color) {
|
|
5260
6472
|
this.color = color;
|
|
5261
6473
|
}
|
|
5262
6474
|
else {
|
|
6475
|
+
// default: 白色
|
|
5263
6476
|
console.warn(`Warn: 传入${value}无法解析为Color`);
|
|
5264
6477
|
this.color = new RGB(255, 255, 255);
|
|
5265
6478
|
}
|
|
@@ -5303,6 +6516,7 @@
|
|
|
5303
6516
|
this.color.b = Math.max(0, Math.min(255, Math.floor(b * color.color.b)));
|
|
5304
6517
|
return this;
|
|
5305
6518
|
}
|
|
6519
|
+
// todo: 这里亮度计算都没考虑alpha通道
|
|
5306
6520
|
getHSVBrightness() {
|
|
5307
6521
|
return Math.max(this.color.r, this.color.g, this.color.b) / 255;
|
|
5308
6522
|
}
|
|
@@ -5330,11 +6544,14 @@
|
|
|
5330
6544
|
return res !== null && res !== void 0 ? res : this;
|
|
5331
6545
|
}
|
|
5332
6546
|
setColorName(name) {
|
|
6547
|
+
// color keywords
|
|
5333
6548
|
const hex = DEFAULT_COLORS[name.toLowerCase()];
|
|
5334
6549
|
if (typeof hex !== 'undefined') {
|
|
6550
|
+
// red
|
|
5335
6551
|
this.setHex(hex);
|
|
5336
6552
|
}
|
|
5337
6553
|
else {
|
|
6554
|
+
// unknown color
|
|
5338
6555
|
console.warn('THREE.Color: Unknown color ' + name);
|
|
5339
6556
|
}
|
|
5340
6557
|
return this;
|
|
@@ -5358,6 +6575,9 @@
|
|
|
5358
6575
|
getLuminance3() {
|
|
5359
6576
|
return (0.299 * this.color.r + 0.587 * this.color.g + 0.114 * this.color.b) / 255;
|
|
5360
6577
|
}
|
|
6578
|
+
/**
|
|
6579
|
+
* https://www.w3.org/TR/WCAG21/#use-of-color
|
|
6580
|
+
*/
|
|
5361
6581
|
getLuminanceWCAG() {
|
|
5362
6582
|
const RsRGB = this.color.r / 255;
|
|
5363
6583
|
const GsRGB = this.color.g / 255;
|
|
@@ -5443,6 +6663,7 @@
|
|
|
5443
6663
|
this.opacity = 1;
|
|
5444
6664
|
}
|
|
5445
6665
|
}
|
|
6666
|
+
// 转为16进制
|
|
5446
6667
|
formatHex() {
|
|
5447
6668
|
return `#${hex(this.r) + hex(this.g) + hex(this.b) + (this.opacity === 1 ? '' : hex(this.opacity * 255))}`;
|
|
5448
6669
|
}
|
|
@@ -5455,11 +6676,17 @@
|
|
|
5455
6676
|
const { h, s, l } = rgbToHsl(this.r, this.g, this.b);
|
|
5456
6677
|
return `${opacity === 1 ? 'hsl(' : 'hsla('}${h},${s}%,${l}%${opacity === 1 ? ')' : `,${opacity})`}`;
|
|
5457
6678
|
}
|
|
6679
|
+
// 转为16进制
|
|
5458
6680
|
toString() {
|
|
5459
6681
|
return this.formatHex();
|
|
5460
6682
|
}
|
|
5461
6683
|
}
|
|
5462
6684
|
|
|
6685
|
+
/**
|
|
6686
|
+
* 将 hex 格式颜色转换为 rgb 格式
|
|
6687
|
+
* @param str hex 格式的颜色值
|
|
6688
|
+
* @returns rgb 格式
|
|
6689
|
+
*/
|
|
5463
6690
|
function hexToRgb(str) {
|
|
5464
6691
|
let r = '';
|
|
5465
6692
|
let g = '';
|
|
@@ -5521,6 +6748,11 @@
|
|
|
5521
6748
|
rgbToHsl: rgbToHsl
|
|
5522
6749
|
});
|
|
5523
6750
|
|
|
6751
|
+
/**
|
|
6752
|
+
* 将 padding 转换为通用的格式([上,右,下,左])
|
|
6753
|
+
* @param padding
|
|
6754
|
+
* @return [ top, right, bottom, left ]
|
|
6755
|
+
*/
|
|
5524
6756
|
function normalizePadding(padding) {
|
|
5525
6757
|
if (isValidNumber(padding)) {
|
|
5526
6758
|
return [padding, padding, padding, padding];
|
|
@@ -5592,6 +6824,12 @@
|
|
|
5592
6824
|
function millisecondsSetterName(isUTC) {
|
|
5593
6825
|
return isUTC ? 'setUTCMilliseconds' : 'setMilliseconds';
|
|
5594
6826
|
}
|
|
6827
|
+
/**
|
|
6828
|
+
* 根据时间推断格式化字符串
|
|
6829
|
+
* @param value
|
|
6830
|
+
* @param isUTC
|
|
6831
|
+
* @returns
|
|
6832
|
+
*/
|
|
5595
6833
|
function getFormatFromValue(value, isUTC) {
|
|
5596
6834
|
const date = toDate(value);
|
|
5597
6835
|
const M = date[monthGetterName(isUTC)]() + 1;
|
|
@@ -5639,15 +6877,20 @@
|
|
|
5639
6877
|
const m = date[minutesGetterName(isUTC)]();
|
|
5640
6878
|
const s = date[secondsGetterName(isUTC)]();
|
|
5641
6879
|
const S = date[millisecondsGetterName(isUTC)]();
|
|
6880
|
+
// 月份,周等需要管理管理locale的时间格式化暂时不支持
|
|
5642
6881
|
return ((template || '')
|
|
5643
6882
|
.replace(/YYYY/g, pad(y + '', 4, '0', 'left'))
|
|
5644
6883
|
.replace(/yyyy/g, y + '')
|
|
5645
6884
|
.replace(/yy/g, (y % 100) + '')
|
|
5646
6885
|
.replace(/Q/g, q + '')
|
|
6886
|
+
// .replace(/{MMMM}/g, month[M - 1])
|
|
6887
|
+
// .replace(/{MMM}/g, monthAbbr[M - 1])
|
|
5647
6888
|
.replace(/MM/g, pad(M, 2, '0', 'left'))
|
|
5648
6889
|
.replace(/M/g, M + '')
|
|
5649
6890
|
.replace(/dd/g, pad(d, 2, '0', 'left'))
|
|
5650
6891
|
.replace(/d/g, d + '')
|
|
6892
|
+
// .replace(/{eeee}/g, dayOfWeek[e])
|
|
6893
|
+
// .replace(/{ee}/g, dayOfWeekAbbr[e])
|
|
5651
6894
|
.replace(/e/g, e + '')
|
|
5652
6895
|
.replace(/HH/g, pad(H, 2, '0', 'left'))
|
|
5653
6896
|
.replace(/H/g, H + '')
|
|
@@ -5949,9 +7192,14 @@
|
|
|
5949
7192
|
};
|
|
5950
7193
|
}
|
|
5951
7194
|
const style = getComputedStyle(el);
|
|
7195
|
+
// clientWidth/clientHeight: 默认整数,会向上取整,导致canvas > container
|
|
7196
|
+
// getBoundingClientRect:默认小数,但是在container上有css类似 transform: scale(0.5)时,获取结果不对
|
|
7197
|
+
// getComputedStyle:默认小数,获取最终结果,但是会包含padding;
|
|
5952
7198
|
if (/^(\d*\.?\d+)(px)$/.exec(style.width)) {
|
|
7199
|
+
// 当dom元素的 display: 'none' 的时候,获取到的宽高会变成原始的样式配置,而不是计算后的像素值
|
|
5953
7200
|
const computedWidth = parseFloat(style.width) - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight) || el.clientWidth - 1;
|
|
5954
7201
|
const computedHeight = parseFloat(style.height) - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom) || el.clientHeight - 1;
|
|
7202
|
+
// 理论上不用向下取整,目前没加。
|
|
5955
7203
|
return {
|
|
5956
7204
|
width: computedWidth <= 0 ? defaultWidth : computedWidth,
|
|
5957
7205
|
height: computedHeight <= 0 ? defaultHeight : computedHeight
|
|
@@ -5959,10 +7207,20 @@
|
|
|
5959
7207
|
}
|
|
5960
7208
|
return { width: defaultWidth, height: defaultHeight };
|
|
5961
7209
|
}
|
|
7210
|
+
/**
|
|
7211
|
+
* 获取元素的绝对位置坐标(相对于页面左上角)
|
|
7212
|
+
* @param element
|
|
7213
|
+
* @returns
|
|
7214
|
+
*/
|
|
5962
7215
|
function getElementAbsolutePosition(element) {
|
|
5963
7216
|
const { x, y } = element.getBoundingClientRect();
|
|
5964
7217
|
return { x, y };
|
|
5965
7218
|
}
|
|
7219
|
+
/**
|
|
7220
|
+
* 获取元素的相对位置坐标(相对于其他dom元素)
|
|
7221
|
+
* @param element
|
|
7222
|
+
* @returns
|
|
7223
|
+
*/
|
|
5966
7224
|
function getElementRelativePosition(element, base) {
|
|
5967
7225
|
const posElement = getElementAbsolutePosition(element);
|
|
5968
7226
|
const posBase = getElementAbsolutePosition(base);
|
|
@@ -5994,12 +7252,23 @@
|
|
|
5994
7252
|
const getScaleY = (element) => {
|
|
5995
7253
|
return element.getBoundingClientRect().height / element.offsetHeight;
|
|
5996
7254
|
};
|
|
7255
|
+
/**
|
|
7256
|
+
* 获取目标元素的缩放因数
|
|
7257
|
+
* @param element 目标 dom 元素
|
|
7258
|
+
* @returns
|
|
7259
|
+
*/
|
|
5997
7260
|
const getScale = (element) => {
|
|
5998
7261
|
if (element.offsetWidth > 0) {
|
|
5999
7262
|
return getScaleX(element);
|
|
6000
7263
|
}
|
|
6001
7264
|
return getScaleY(element);
|
|
6002
7265
|
};
|
|
7266
|
+
/**
|
|
7267
|
+
* 判断是否是元素的父元素
|
|
7268
|
+
* @param element 从其父元素开始查找的 DOM 元素
|
|
7269
|
+
* @param target 要查找的目标父元素
|
|
7270
|
+
* @returns boolean, true 代表查找到,false 表示未找到
|
|
7271
|
+
*/
|
|
6003
7272
|
function hasParentElement(element, target) {
|
|
6004
7273
|
let parent = element.parentNode;
|
|
6005
7274
|
while (parent !== null) {
|
|
@@ -6010,6 +7279,11 @@
|
|
|
6010
7279
|
}
|
|
6011
7280
|
return false;
|
|
6012
7281
|
}
|
|
7282
|
+
/**
|
|
7283
|
+
* 将style字符串转换成对象形式
|
|
7284
|
+
* @param style字符串 例如:"color:red;line-height:20px;"
|
|
7285
|
+
* @returns style对象,如 { color: 'red', 'line-height': '20px' }
|
|
7286
|
+
*/
|
|
6013
7287
|
const styleStringToObject = (styleStr = '') => {
|
|
6014
7288
|
const res = {};
|
|
6015
7289
|
styleStr.split(';').forEach(item => {
|
|
@@ -6026,9 +7300,19 @@
|
|
|
6026
7300
|
});
|
|
6027
7301
|
return res;
|
|
6028
7302
|
};
|
|
7303
|
+
/**
|
|
7304
|
+
* 将小驼峰转换成中划线连接的字符串
|
|
7305
|
+
* @param str 如:'lineHeight' => 'line-height'
|
|
7306
|
+
* @returns
|
|
7307
|
+
*/
|
|
6029
7308
|
const lowerCamelCaseToMiddle = (str) => {
|
|
6030
7309
|
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
6031
7310
|
};
|
|
7311
|
+
/**
|
|
7312
|
+
* 将中划线连接的字符串转换成驼峰字符串
|
|
7313
|
+
* @param str 如:'line-height' => 'lineHeight'
|
|
7314
|
+
* @returns
|
|
7315
|
+
*/
|
|
6032
7316
|
function toCamelCase(str) {
|
|
6033
7317
|
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
6034
7318
|
}
|
|
@@ -6037,6 +7321,7 @@
|
|
|
6037
7321
|
return obj instanceof Element;
|
|
6038
7322
|
}
|
|
6039
7323
|
catch (_a) {
|
|
7324
|
+
// 跨端 plan B
|
|
6040
7325
|
const htmlElementKeys = [
|
|
6041
7326
|
'children',
|
|
6042
7327
|
'innerHTML',
|
|
@@ -6104,6 +7389,29 @@
|
|
|
6104
7389
|
return distance / factor;
|
|
6105
7390
|
}
|
|
6106
7391
|
|
|
7392
|
+
/**
|
|
7393
|
+
* The MIT License (MIT)
|
|
7394
|
+
*
|
|
7395
|
+
* Copyright (c) 2019 Morgan Herlocker
|
|
7396
|
+
*
|
|
7397
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7398
|
+
* this software and associated documentation files (the "Software"), to deal in
|
|
7399
|
+
* the Software without restriction, including without limitation the rights to
|
|
7400
|
+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
7401
|
+
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
7402
|
+
* subject to the following conditions:
|
|
7403
|
+
*
|
|
7404
|
+
* The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.
|
|
7405
|
+
*
|
|
7406
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
7407
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
7408
|
+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
7409
|
+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
7410
|
+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
7411
|
+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
7412
|
+
*
|
|
7413
|
+
*/
|
|
7414
|
+
// https://github.com/Turfjs/turf
|
|
6107
7415
|
function getGeom(geojson) {
|
|
6108
7416
|
if (geojson.type === 'Feature') {
|
|
6109
7417
|
return geojson.geometry;
|
|
@@ -6119,6 +7427,7 @@
|
|
|
6119
7427
|
}
|
|
6120
7428
|
const geom = getGeom(polygon);
|
|
6121
7429
|
const type = geom.type;
|
|
7430
|
+
// BBox [west, south, east, north]
|
|
6122
7431
|
const bbox = polygon.bbox;
|
|
6123
7432
|
let polys = geom.coordinates;
|
|
6124
7433
|
if (bbox && pointInRect(point, { x1: bbox[0], x2: bbox[1], y1: bbox[1], y2: bbox[3] }, true) === true) {
|
|
@@ -6139,11 +7448,13 @@
|
|
|
6139
7448
|
}
|
|
6140
7449
|
return result;
|
|
6141
7450
|
}
|
|
7451
|
+
// https://github.com/Turfjs/turf
|
|
6142
7452
|
function destination(point, distance, bearing, options = {}) {
|
|
6143
7453
|
const longitude1 = degreeToRadian(point[0]);
|
|
6144
7454
|
const latitude1 = degreeToRadian(point[1]);
|
|
6145
7455
|
const bearingRad = degreeToRadian(bearing);
|
|
6146
7456
|
const radians = lengthToRadians(distance, options.units);
|
|
7457
|
+
// Main
|
|
6147
7458
|
const latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(radians) + Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearingRad));
|
|
6148
7459
|
const longitude2 = longitude1 +
|
|
6149
7460
|
Math.atan2(Math.sin(bearingRad) * Math.sin(radians) * Math.cos(latitude1), Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2));
|
|
@@ -6152,17 +7463,41 @@
|
|
|
6152
7463
|
return { x: lng, y: lat };
|
|
6153
7464
|
}
|
|
6154
7465
|
|
|
7466
|
+
/* Adapted from venn.js by Ben Frederickson
|
|
7467
|
+
* https://github.com/benfred/venn.js
|
|
7468
|
+
* Licensed under the MIT
|
|
7469
|
+
|
|
7470
|
+
* url: https://github.com/benfred/venn.js/blob/master/src/circleintersection.js
|
|
7471
|
+
* License: https://github.com/benfred/venn.js/blob/master/LICENSE
|
|
7472
|
+
* @license
|
|
7473
|
+
*/
|
|
6155
7474
|
const SMALL = 1e-10;
|
|
6156
7475
|
|
|
7476
|
+
/* Adapted from venn.js by Ben Frederickson
|
|
7477
|
+
* https://github.com/benfred/venn.js
|
|
7478
|
+
* Licensed under the MIT
|
|
7479
|
+
|
|
7480
|
+
* url: https://github.com/benfred/venn.js/blob/master/src/circleintersection.js
|
|
7481
|
+
* License: https://github.com/benfred/venn.js/blob/master/LICENSE
|
|
7482
|
+
* @license
|
|
7483
|
+
*/
|
|
7484
|
+
/** Returns the intersection area of a bunch of circles (where each circle
|
|
7485
|
+
is an object having an x,y and radius property) */
|
|
6157
7486
|
function intersectionArea(circles, stats) {
|
|
7487
|
+
// get all the intersection points of the circles
|
|
6158
7488
|
const intersectionPoints = getIntersectionPoints(circles);
|
|
7489
|
+
// filter out points that aren't included in all the circles
|
|
6159
7490
|
const innerPoints = intersectionPoints.filter(function (p) {
|
|
6160
7491
|
return containedInCircles(p, circles);
|
|
6161
7492
|
});
|
|
6162
7493
|
let arcArea = 0;
|
|
6163
7494
|
let polygonArea = 0;
|
|
6164
7495
|
const arcs = [];
|
|
7496
|
+
// if we have intersection points that are within all the circles,
|
|
7497
|
+
// then figure out the area contained by them
|
|
6165
7498
|
if (innerPoints.length > 1) {
|
|
7499
|
+
// sort the points by angle from the center of the polygon, which lets
|
|
7500
|
+
// us just iterate over points to get the edges
|
|
6166
7501
|
const center = getCenter(innerPoints);
|
|
6167
7502
|
for (let i = 0; i < innerPoints.length; ++i) {
|
|
6168
7503
|
const p = innerPoints[i];
|
|
@@ -6171,14 +7506,20 @@
|
|
|
6171
7506
|
innerPoints.sort(function (a, b) {
|
|
6172
7507
|
return b.angle - a.angle;
|
|
6173
7508
|
});
|
|
7509
|
+
// iterate over all points, get arc between the points
|
|
7510
|
+
// and update the areas
|
|
6174
7511
|
let p2 = innerPoints[innerPoints.length - 1];
|
|
6175
7512
|
for (let i = 0; i < innerPoints.length; ++i) {
|
|
6176
7513
|
const p1 = innerPoints[i];
|
|
7514
|
+
// polygon area updates easily ...
|
|
6177
7515
|
polygonArea += (p2.x + p1.x) * (p1.y - p2.y);
|
|
7516
|
+
// updating the arc area is a little more involved
|
|
6178
7517
|
const midPoint = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
|
|
6179
7518
|
let arc = null;
|
|
6180
7519
|
for (let j = 0; j < p1.parentIndex.length; ++j) {
|
|
6181
7520
|
if (p2.parentIndex.indexOf(p1.parentIndex[j]) > -1) {
|
|
7521
|
+
// figure out the angle halfway between the two points
|
|
7522
|
+
// on the current circle
|
|
6182
7523
|
const circle = circles[p1.parentIndex[j]];
|
|
6183
7524
|
const a1 = Math.atan2(p1.x - circle.x, p1.y - circle.y);
|
|
6184
7525
|
const a2 = Math.atan2(p2.x - circle.x, p2.y - circle.y);
|
|
@@ -6186,14 +7527,19 @@
|
|
|
6186
7527
|
if (angleDiff < 0) {
|
|
6187
7528
|
angleDiff += 2 * Math.PI;
|
|
6188
7529
|
}
|
|
7530
|
+
// and use that angle to figure out the width of the
|
|
7531
|
+
// arc
|
|
6189
7532
|
const a = a2 - angleDiff / 2;
|
|
6190
7533
|
let width = PointService.distancePP(midPoint, {
|
|
6191
7534
|
x: circle.x + circle.radius * Math.sin(a),
|
|
6192
7535
|
y: circle.y + circle.radius * Math.cos(a)
|
|
6193
7536
|
});
|
|
7537
|
+
// clamp the width to the largest is can actually be
|
|
7538
|
+
// (sometimes slightly overflows because of FP errors)
|
|
6194
7539
|
if (width > circle.radius * 2) {
|
|
6195
7540
|
width = circle.radius * 2;
|
|
6196
7541
|
}
|
|
7542
|
+
// pick the circle whose arc has the smallest width
|
|
6197
7543
|
if (arc === null || arc.width > width) {
|
|
6198
7544
|
arc = { circle: circle, width: width, p1: p1, p2: p2 };
|
|
6199
7545
|
}
|
|
@@ -6207,12 +7553,16 @@
|
|
|
6207
7553
|
}
|
|
6208
7554
|
}
|
|
6209
7555
|
else {
|
|
7556
|
+
// no intersection points, is either disjoint - or is completely
|
|
7557
|
+
// overlapped. figure out which by examining the smallest circle
|
|
6210
7558
|
let smallest = circles[0];
|
|
6211
7559
|
for (let i = 1; i < circles.length; ++i) {
|
|
6212
7560
|
if (circles[i].radius < smallest.radius) {
|
|
6213
7561
|
smallest = circles[i];
|
|
6214
7562
|
}
|
|
6215
7563
|
}
|
|
7564
|
+
// make sure the smallest circle is completely contained in all
|
|
7565
|
+
// the other circles
|
|
6216
7566
|
let disjoint = false;
|
|
6217
7567
|
for (let i = 0; i < circles.length; ++i) {
|
|
6218
7568
|
if (PointService.distancePP(circles[i], smallest) > Math.abs(smallest.radius - circles[i].radius)) {
|
|
@@ -6244,6 +7594,7 @@
|
|
|
6244
7594
|
}
|
|
6245
7595
|
return arcArea + polygonArea;
|
|
6246
7596
|
}
|
|
7597
|
+
/** returns whether a point is contained by all of a list of circles */
|
|
6247
7598
|
function containedInCircles(point, circles) {
|
|
6248
7599
|
for (let i = 0; i < circles.length; ++i) {
|
|
6249
7600
|
if (PointService.distancePP(point, circles[i]) > circles[i].radius + SMALL) {
|
|
@@ -6252,6 +7603,7 @@
|
|
|
6252
7603
|
}
|
|
6253
7604
|
return true;
|
|
6254
7605
|
}
|
|
7606
|
+
/** Gets all intersection points between a bunch of circles */
|
|
6255
7607
|
function getIntersectionPoints(circles) {
|
|
6256
7608
|
const ret = [];
|
|
6257
7609
|
for (let i = 0; i < circles.length; ++i) {
|
|
@@ -6266,13 +7618,19 @@
|
|
|
6266
7618
|
}
|
|
6267
7619
|
return ret;
|
|
6268
7620
|
}
|
|
7621
|
+
/** Circular segment area calculation. See http://mathworld.wolfram.com/CircularSegment.html */
|
|
6269
7622
|
function circleArea(r, width) {
|
|
6270
7623
|
return r * r * Math.acos(1 - width / r) - (r - width) * Math.sqrt(width * (2 * r - width));
|
|
6271
7624
|
}
|
|
7625
|
+
/** Returns the overlap area of two circles of radius r1 and r2 - that
|
|
7626
|
+
have their centers separated by distance d. Simpler faster
|
|
7627
|
+
circle intersection for only two circles */
|
|
6272
7628
|
function circleOverlap(r1, r2, d) {
|
|
7629
|
+
// no overlap
|
|
6273
7630
|
if (d >= r1 + r2) {
|
|
6274
7631
|
return 0;
|
|
6275
7632
|
}
|
|
7633
|
+
// completely overlapped
|
|
6276
7634
|
if (d <= Math.abs(r1 - r2)) {
|
|
6277
7635
|
return Math.PI * Math.min(r1, r2) * Math.min(r1, r2);
|
|
6278
7636
|
}
|
|
@@ -6280,10 +7638,15 @@
|
|
|
6280
7638
|
const w2 = r2 - (d * d - r1 * r1 + r2 * r2) / (2 * d);
|
|
6281
7639
|
return circleArea(r1, w1) + circleArea(r2, w2);
|
|
6282
7640
|
}
|
|
7641
|
+
/** Given two circles (containing a x/y/radius attributes),
|
|
7642
|
+
returns the intersecting points if possible.
|
|
7643
|
+
note: doesn't handle cases where there are infinitely many
|
|
7644
|
+
intersection points (circles are equivalent):, or only one intersection point*/
|
|
6283
7645
|
function circleCircleIntersection(p1, p2) {
|
|
6284
7646
|
const d = PointService.distancePP(p1, p2);
|
|
6285
7647
|
const r1 = p1.radius;
|
|
6286
7648
|
const r2 = p2.radius;
|
|
7649
|
+
// if to far away, or self contained - can't be done
|
|
6287
7650
|
if (d >= r1 + r2 || d <= Math.abs(r1 - r2)) {
|
|
6288
7651
|
return [];
|
|
6289
7652
|
}
|
|
@@ -6298,6 +7661,7 @@
|
|
|
6298
7661
|
{ x: x0 - rx, y: y0 + ry }
|
|
6299
7662
|
];
|
|
6300
7663
|
}
|
|
7664
|
+
/** Returns the center of a bunch of points */
|
|
6301
7665
|
function getCenter(points) {
|
|
6302
7666
|
const center = { x: 0, y: 0 };
|
|
6303
7667
|
for (let i = 0; i < points.length; ++i) {
|
|
@@ -6309,6 +7673,14 @@
|
|
|
6309
7673
|
return center;
|
|
6310
7674
|
}
|
|
6311
7675
|
|
|
7676
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
7677
|
+
* https://github.com/d3/d3-time-format
|
|
7678
|
+
* Licensed under the ISC
|
|
7679
|
+
|
|
7680
|
+
* url: https://github.com/d3/d3-time-format/blob/d6feb945baa4b7a45898a9ca926ebca044e20657/src/locale.js
|
|
7681
|
+
* License: https://github.com/d3/d3-time-format/blob/main/LICENSE
|
|
7682
|
+
* @license
|
|
7683
|
+
*/
|
|
6312
7684
|
class TimeUtil {
|
|
6313
7685
|
static getInstance() {
|
|
6314
7686
|
if (!TimeUtil.instance) {
|
|
@@ -6321,8 +7693,9 @@
|
|
|
6321
7693
|
this.locale_periods = ['AM', 'PM'];
|
|
6322
7694
|
this.locale_weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
6323
7695
|
this.locale_shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
6324
|
-
this.numberRe = /^\s*\d+/;
|
|
7696
|
+
this.numberRe = /^\s*\d+/; // note: ignores next directive
|
|
6325
7697
|
this.pads = { '-': '', _: ' ', '0': '0' };
|
|
7698
|
+
// const percentRe = /^%/;
|
|
6326
7699
|
this.requoteRe = /[\\^$*+?|[\]().{}]/g;
|
|
6327
7700
|
this.locale_months = [
|
|
6328
7701
|
'January',
|
|
@@ -6599,18 +7972,22 @@
|
|
|
6599
7972
|
if (i !== string.length) {
|
|
6600
7973
|
return null;
|
|
6601
7974
|
}
|
|
7975
|
+
// If a UNIX timestamp is specified, return it.
|
|
6602
7976
|
if ('Q' in d) {
|
|
6603
7977
|
return new Date(d.Q);
|
|
6604
7978
|
}
|
|
6605
7979
|
if ('s' in d) {
|
|
6606
7980
|
return new Date(d.s * 1000 + ('L' in d ? d.L : 0));
|
|
6607
7981
|
}
|
|
7982
|
+
// If this is utcParse, never use the local timezone.
|
|
6608
7983
|
if (Z && !('Z' in d)) {
|
|
6609
7984
|
d.Z = 0;
|
|
6610
7985
|
}
|
|
7986
|
+
// The am-pm flag is 0 for AM, and 1 for PM.
|
|
6611
7987
|
if ('p' in d) {
|
|
6612
7988
|
d.H = (d.H % 12) + d.p * 12;
|
|
6613
7989
|
}
|
|
7990
|
+
// If the month was not specified, inherit from the quarter.
|
|
6614
7991
|
if (d.m === undefined) {
|
|
6615
7992
|
d.m = 'q' in d ? d.q : 0;
|
|
6616
7993
|
}
|
|
@@ -6619,6 +7996,7 @@
|
|
|
6619
7996
|
d.M += d.Z % 100;
|
|
6620
7997
|
return that.utcDate(d);
|
|
6621
7998
|
}
|
|
7999
|
+
// Otherwise, all fields are in local time.
|
|
6622
8000
|
return that.localDate(d);
|
|
6623
8001
|
};
|
|
6624
8002
|
}
|
|
@@ -6660,6 +8038,13 @@
|
|
|
6660
8038
|
}
|
|
6661
8039
|
}
|
|
6662
8040
|
|
|
8041
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8042
|
+
* https://github.com/d3/d3-format
|
|
8043
|
+
* Licensed under the ISC
|
|
8044
|
+
|
|
8045
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8046
|
+
* @license
|
|
8047
|
+
*/
|
|
6663
8048
|
function formatDecimal(x) {
|
|
6664
8049
|
return Math.abs((x = Math.round(x))) >= 1e21 ? x.toLocaleString('en').replace(/,/g, '') : x.toString(10);
|
|
6665
8050
|
}
|
|
@@ -6667,12 +8052,19 @@
|
|
|
6667
8052
|
const _x = p ? x.toExponential(p - 1) : x.toExponential();
|
|
6668
8053
|
const i = _x.indexOf('e');
|
|
6669
8054
|
if (i < 0) {
|
|
6670
|
-
return null;
|
|
8055
|
+
return null; // NaN, ±Infinity
|
|
6671
8056
|
}
|
|
6672
8057
|
const coefficient = _x.slice(0, i);
|
|
6673
8058
|
return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +_x.slice(i + 1)];
|
|
6674
8059
|
}
|
|
6675
8060
|
|
|
8061
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8062
|
+
* https://github.com/d3/d3-format
|
|
8063
|
+
* Licensed under the ISC
|
|
8064
|
+
|
|
8065
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8066
|
+
* @license
|
|
8067
|
+
*/
|
|
6676
8068
|
function formatGroup(grouping, thousands) {
|
|
6677
8069
|
return function (value, width) {
|
|
6678
8070
|
let i = value.length;
|
|
@@ -6694,6 +8086,13 @@
|
|
|
6694
8086
|
};
|
|
6695
8087
|
}
|
|
6696
8088
|
|
|
8089
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8090
|
+
* https://github.com/d3/d3-format
|
|
8091
|
+
* Licensed under the ISC
|
|
8092
|
+
|
|
8093
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8094
|
+
* @license
|
|
8095
|
+
*/
|
|
6697
8096
|
let prefixExponent;
|
|
6698
8097
|
function formatPrefixAuto(x, p) {
|
|
6699
8098
|
const d = formatDecimalParts(x, p);
|
|
@@ -6710,9 +8109,16 @@
|
|
|
6710
8109
|
? coefficient + new Array(i - n + 1).join('0')
|
|
6711
8110
|
: i > 0
|
|
6712
8111
|
? coefficient.slice(0, i) + '.' + coefficient.slice(i)
|
|
6713
|
-
: '0.' + new Array(1 - i).join('0') + formatDecimalParts(x, Math.max(0, p + i - 1))[0];
|
|
8112
|
+
: '0.' + new Array(1 - i).join('0') + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y!
|
|
6714
8113
|
}
|
|
6715
8114
|
|
|
8115
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8116
|
+
* https://github.com/d3/d3-format
|
|
8117
|
+
* Licensed under the ISC
|
|
8118
|
+
|
|
8119
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8120
|
+
* @license
|
|
8121
|
+
*/
|
|
6716
8122
|
function formatRounded(x, p) {
|
|
6717
8123
|
const d = formatDecimalParts(x, p);
|
|
6718
8124
|
if (!d) {
|
|
@@ -6727,6 +8133,13 @@
|
|
|
6727
8133
|
: coefficient + new Array(exponent - coefficient.length + 2).join('0');
|
|
6728
8134
|
}
|
|
6729
8135
|
|
|
8136
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8137
|
+
* https://github.com/d3/d3-format
|
|
8138
|
+
* Licensed under the ISC
|
|
8139
|
+
|
|
8140
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8141
|
+
* @license
|
|
8142
|
+
*/
|
|
6730
8143
|
function formatTrim(s) {
|
|
6731
8144
|
const n = s.length;
|
|
6732
8145
|
let i0 = -1;
|
|
@@ -6802,6 +8215,13 @@
|
|
|
6802
8215
|
});
|
|
6803
8216
|
}
|
|
6804
8217
|
|
|
8218
|
+
/* Adapted from d3-time-format by Mike Bostock
|
|
8219
|
+
* https://github.com/d3/d3-format
|
|
8220
|
+
* Licensed under the ISC
|
|
8221
|
+
|
|
8222
|
+
* License: https://github.com/d3/d3-format/blob/main/LICENSE
|
|
8223
|
+
* @license
|
|
8224
|
+
*/
|
|
6805
8225
|
const prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
|
6806
8226
|
class NumberUtil {
|
|
6807
8227
|
constructor() {
|
|
@@ -6850,19 +8270,33 @@
|
|
|
6850
8270
|
let precision = specifierIns.precision;
|
|
6851
8271
|
let trim = specifierIns.trim;
|
|
6852
8272
|
let type = specifierIns.type;
|
|
8273
|
+
// The "n" type is an alias for ",g".
|
|
6853
8274
|
if (type === 'n') {
|
|
6854
8275
|
(comma = true), (type = 'g');
|
|
6855
8276
|
}
|
|
8277
|
+
// The "" type, and any invalid type, is an alias for ".12~g".
|
|
8278
|
+
// @ts-ignore
|
|
6856
8279
|
else if (!formatTypes[type]) {
|
|
6857
8280
|
precision === undefined && (precision = 12), (trim = true), (type = 'g');
|
|
6858
8281
|
}
|
|
8282
|
+
// If zero fill is specified, padding goes after sign and before digits.
|
|
8283
|
+
// @ts-ignore
|
|
6859
8284
|
if (zero || (fill === '0' && align === '=')) {
|
|
6860
8285
|
(zero = true), (fill = '0'), (align = '=');
|
|
6861
8286
|
}
|
|
8287
|
+
// Compute the prefix and suffix.
|
|
8288
|
+
// For SI-prefix, the suffix is lazily computed.
|
|
6862
8289
|
const prefix = symbol === '$' ? this.currencyPrefix : symbol === '#' && /[boxX]/.test(type) ? '0' + type.toLowerCase() : '';
|
|
6863
8290
|
const suffix = symbol === '$' ? this.currencySuffix : /[%p]/.test(type) ? this.percent : '';
|
|
8291
|
+
// What format function should we use?
|
|
8292
|
+
// Is this an integer type?
|
|
8293
|
+
// Can this type generate exponential notation?
|
|
6864
8294
|
const formatType = formatTypes[type];
|
|
6865
8295
|
const maybeSuffix = /[defgprstz%]/.test(type);
|
|
8296
|
+
// Set the default precision if not specified,
|
|
8297
|
+
// or clamp the specified precision to the supported range.
|
|
8298
|
+
// For significant precision, it must be in [1, 21].
|
|
8299
|
+
// For fixed precision, it must be in [0, 20].
|
|
6866
8300
|
precision =
|
|
6867
8301
|
precision === undefined
|
|
6868
8302
|
? 6
|
|
@@ -6883,20 +8317,27 @@
|
|
|
6883
8317
|
}
|
|
6884
8318
|
else {
|
|
6885
8319
|
_value = +_value;
|
|
8320
|
+
// Determine the sign. -0 is not less than 0, but 1 / -0 is!
|
|
6886
8321
|
let valueNegative = _value < 0 || 1 / _value < 0;
|
|
8322
|
+
// Perform the initial formatting.
|
|
6887
8323
|
_value = isNaN(_value) ? nan : formatType(Math.abs(_value), precision);
|
|
8324
|
+
// Trim insignificant zeros.
|
|
6888
8325
|
if (trim) {
|
|
6889
8326
|
_value = formatTrim(_value);
|
|
6890
8327
|
}
|
|
8328
|
+
// If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
|
|
6891
8329
|
if (valueNegative && +_value === 0 && sign !== '+') {
|
|
6892
8330
|
valueNegative = false;
|
|
6893
8331
|
}
|
|
8332
|
+
// Compute the prefix and suffix.
|
|
6894
8333
|
valuePrefix =
|
|
6895
8334
|
(valueNegative ? (sign === '(' ? sign : minus) : sign === '-' || sign === '(' ? '' : sign) + valuePrefix;
|
|
6896
8335
|
valueSuffix =
|
|
6897
8336
|
(type === 's' ? prefixes[8 + prefixExponent / 3] : '') +
|
|
6898
8337
|
valueSuffix +
|
|
6899
8338
|
(valueNegative && sign === '(' ? ')' : '');
|
|
8339
|
+
// Break the formatted value into the integer “value” part that can be
|
|
8340
|
+
// grouped, and fractional or exponential “suffix” part that is not.
|
|
6900
8341
|
if (maybeSuffix) {
|
|
6901
8342
|
(i = -1), (n = _value.length);
|
|
6902
8343
|
while (++i < n) {
|
|
@@ -6908,15 +8349,19 @@
|
|
|
6908
8349
|
}
|
|
6909
8350
|
}
|
|
6910
8351
|
}
|
|
8352
|
+
// If the fill character is not "0", grouping is applied before padding.
|
|
6911
8353
|
if (comma && !zero) {
|
|
6912
8354
|
_value = group(_value, Infinity);
|
|
6913
8355
|
}
|
|
8356
|
+
// Compute the padding.
|
|
6914
8357
|
let length = valuePrefix.length + _value.length + valueSuffix.length;
|
|
6915
8358
|
let padding = length < width ? new Array(width - length + 1).join(fill) : '';
|
|
8359
|
+
// If the fill character is "0", grouping is applied after padding.
|
|
6916
8360
|
if (comma && zero) {
|
|
6917
8361
|
_value = group(padding + _value, padding.length ? width - valueSuffix.length : Infinity);
|
|
6918
8362
|
padding = '';
|
|
6919
8363
|
}
|
|
8364
|
+
// Reconstruct the final output based on the desired alignment.
|
|
6920
8365
|
switch (align) {
|
|
6921
8366
|
case '<':
|
|
6922
8367
|
_value = valuePrefix + _value + valueSuffix + padding;
|
|
@@ -6970,6 +8415,7 @@
|
|
|
6970
8415
|
X: (x) => Math.round(x).toString(16).toUpperCase(),
|
|
6971
8416
|
x: (x) => Math.round(x).toString(16),
|
|
6972
8417
|
t: (x, p) => {
|
|
8418
|
+
// 判断是否为整数
|
|
6973
8419
|
if (Number.isInteger(x)) {
|
|
6974
8420
|
return x.toFixed(2);
|
|
6975
8421
|
}
|
|
@@ -6987,6 +8433,16 @@
|
|
|
6987
8433
|
};
|
|
6988
8434
|
}
|
|
6989
8435
|
|
|
8436
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8437
|
+
* https://github.com/benfred/fmin
|
|
8438
|
+
* Licensed under the BSD-3-Clause
|
|
8439
|
+
|
|
8440
|
+
* url: https://github.com/benfred/fmin/blob/master/src/blas1.js
|
|
8441
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8442
|
+
* @license
|
|
8443
|
+
*/
|
|
8444
|
+
// need some basic operations on vectors, rather than adding a dependency,
|
|
8445
|
+
// just define here
|
|
6990
8446
|
function zeros(x) {
|
|
6991
8447
|
const r = new Array(x);
|
|
6992
8448
|
for (let i = 0; i < x; ++i) {
|
|
@@ -7018,6 +8474,15 @@
|
|
|
7018
8474
|
}
|
|
7019
8475
|
}
|
|
7020
8476
|
|
|
8477
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8478
|
+
* https://github.com/benfred/fmin
|
|
8479
|
+
* Licensed under the BSD-3-Clause
|
|
8480
|
+
|
|
8481
|
+
* url: https://github.com/benfred/fmin/blob/master/src/nelderMead.js
|
|
8482
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8483
|
+
* @license
|
|
8484
|
+
*/
|
|
8485
|
+
/** minimizes a function using the downhill simplex method */
|
|
7021
8486
|
function nelderMead(f, x0, parameters) {
|
|
7022
8487
|
parameters = parameters || {};
|
|
7023
8488
|
const maxIterations = parameters.maxIterations || x0.length * 200;
|
|
@@ -7030,6 +8495,7 @@
|
|
|
7030
8495
|
const psi = parameters.psi !== undefined ? parameters.psi : -0.5;
|
|
7031
8496
|
const sigma = parameters.sigma !== undefined ? parameters.sigma : 0.5;
|
|
7032
8497
|
let maxDiff;
|
|
8498
|
+
// initialize simplex.
|
|
7033
8499
|
const N = x0.length;
|
|
7034
8500
|
const simplex = new Array(N + 1);
|
|
7035
8501
|
simplex[0] = x0;
|
|
@@ -7058,6 +8524,8 @@
|
|
|
7058
8524
|
for (let iteration = 0; iteration < maxIterations; ++iteration) {
|
|
7059
8525
|
simplex.sort(sortOrder);
|
|
7060
8526
|
if (parameters.history) {
|
|
8527
|
+
// copy the simplex (since later iterations will mutate) and
|
|
8528
|
+
// sort it to have a consistent order between iterations
|
|
7061
8529
|
const sortedSimplex = simplex.map(function (x) {
|
|
7062
8530
|
const state = x.slice();
|
|
7063
8531
|
state.fx = x.fx;
|
|
@@ -7076,6 +8544,7 @@
|
|
|
7076
8544
|
if (Math.abs(simplex[0].fx - simplex[N].fx) < minErrorDelta && maxDiff < minTolerance) {
|
|
7077
8545
|
break;
|
|
7078
8546
|
}
|
|
8547
|
+
// compute the centroid of all but the worst point in the simplex
|
|
7079
8548
|
for (let i = 0; i < N; ++i) {
|
|
7080
8549
|
centroid[i] = 0;
|
|
7081
8550
|
for (let j = 0; j < N; ++j) {
|
|
@@ -7083,9 +8552,12 @@
|
|
|
7083
8552
|
}
|
|
7084
8553
|
centroid[i] /= N;
|
|
7085
8554
|
}
|
|
8555
|
+
// reflect the worst point past the centroid and compute loss at reflected
|
|
8556
|
+
// point
|
|
7086
8557
|
const worst = simplex[N];
|
|
7087
8558
|
weightedSum(reflected, 1 + rho, centroid, -rho, worst);
|
|
7088
8559
|
reflected.fx = f(reflected);
|
|
8560
|
+
// if the reflected point is the best seen, then possibly expand
|
|
7089
8561
|
if (reflected.fx < simplex[0].fx) {
|
|
7090
8562
|
weightedSum(expanded, 1 + chi, centroid, -chi, worst);
|
|
7091
8563
|
expanded.fx = f(expanded);
|
|
@@ -7096,9 +8568,12 @@
|
|
|
7096
8568
|
updateSimplex(reflected);
|
|
7097
8569
|
}
|
|
7098
8570
|
}
|
|
8571
|
+
// if the reflected point is worse than the second worst, we need to
|
|
8572
|
+
// contract
|
|
7099
8573
|
else if (reflected.fx >= simplex[N - 1].fx) {
|
|
7100
8574
|
let shouldReduce = false;
|
|
7101
8575
|
if (reflected.fx > worst.fx) {
|
|
8576
|
+
// do an inside contraction
|
|
7102
8577
|
weightedSum(contracted, 1 + psi, centroid, -psi, worst);
|
|
7103
8578
|
contracted.fx = f(contracted);
|
|
7104
8579
|
if (contracted.fx < worst.fx) {
|
|
@@ -7109,6 +8584,7 @@
|
|
|
7109
8584
|
}
|
|
7110
8585
|
}
|
|
7111
8586
|
else {
|
|
8587
|
+
// do an outside contraction
|
|
7112
8588
|
weightedSum(contracted, 1 - psi * rho, centroid, psi * rho, worst);
|
|
7113
8589
|
contracted.fx = f(contracted);
|
|
7114
8590
|
if (contracted.fx < reflected.fx) {
|
|
@@ -7119,9 +8595,11 @@
|
|
|
7119
8595
|
}
|
|
7120
8596
|
}
|
|
7121
8597
|
if (shouldReduce) {
|
|
8598
|
+
// if we don't contract here, we're done
|
|
7122
8599
|
if (sigma >= 1) {
|
|
7123
8600
|
break;
|
|
7124
8601
|
}
|
|
8602
|
+
// do a reduction
|
|
7125
8603
|
for (let i = 1; i < simplex.length; ++i) {
|
|
7126
8604
|
weightedSum(simplex[i], 1 - sigma, simplex[0], sigma, simplex[i]);
|
|
7127
8605
|
simplex[i].fx = f(simplex[i]);
|
|
@@ -7136,6 +8614,21 @@
|
|
|
7136
8614
|
return { fx: simplex[0].fx, x: simplex[0] };
|
|
7137
8615
|
}
|
|
7138
8616
|
|
|
8617
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8618
|
+
* https://github.com/benfred/fmin
|
|
8619
|
+
* Licensed under the BSD-3-Clause
|
|
8620
|
+
|
|
8621
|
+
* url: https://github.com/benfred/fmin/blob/master/src/linesearch.js
|
|
8622
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8623
|
+
* @license
|
|
8624
|
+
*/
|
|
8625
|
+
/// searches along line 'pk' for a point that satifies the wolfe conditions
|
|
8626
|
+
/// See 'Numerical Optimization' by Nocedal and Wright p59-60
|
|
8627
|
+
/// f : objective function
|
|
8628
|
+
/// pk : search direction
|
|
8629
|
+
/// current: object containing current gradient/loss
|
|
8630
|
+
/// next: output: contains next gradient/loss
|
|
8631
|
+
/// returns a: step size taken
|
|
7139
8632
|
function wolfeLineSearch(f, pk, current, next, a, c1, c2) {
|
|
7140
8633
|
const phi0 = current.fx;
|
|
7141
8634
|
const phiPrime0 = dotProduct(current.fxprime, pk);
|
|
@@ -7188,7 +8681,17 @@
|
|
|
7188
8681
|
return a;
|
|
7189
8682
|
}
|
|
7190
8683
|
|
|
8684
|
+
/* Adapted from fmin by Ben Frederickson
|
|
8685
|
+
* https://github.com/benfred/fmin
|
|
8686
|
+
* Licensed under the BSD-3-Clause
|
|
8687
|
+
|
|
8688
|
+
* url: https://github.com/benfred/fmin/blob/master/src/conjugateGradient.js
|
|
8689
|
+
* License: https://github.com/benfred/fmin/blob/master/LICENSE
|
|
8690
|
+
* @license
|
|
8691
|
+
*/
|
|
7191
8692
|
function conjugateGradient(f, initial, params) {
|
|
8693
|
+
// allocate all memory up front here, keep out of the loop for perfomance
|
|
8694
|
+
// reasons
|
|
7192
8695
|
let current = { x: initial.slice(), fx: 0, fxprime: initial.slice() };
|
|
7193
8696
|
let next = { x: initial.slice(), fx: 0, fxprime: initial.slice() };
|
|
7194
8697
|
const yk = initial.slice();
|
|
@@ -7201,13 +8704,17 @@
|
|
|
7201
8704
|
scale(pk, current.fxprime, -1);
|
|
7202
8705
|
for (let i = 0; i < maxIterations; ++i) {
|
|
7203
8706
|
a = wolfeLineSearch(f, pk, current, next, a);
|
|
8707
|
+
// todo: history in wrong spot?
|
|
7204
8708
|
if (params.history) {
|
|
7205
8709
|
params.history.push({ x: current.x.slice(), fx: current.fx, fxprime: current.fxprime.slice(), alpha: a });
|
|
7206
8710
|
}
|
|
7207
8711
|
if (!a) {
|
|
8712
|
+
// faiiled to find point that satifies wolfe conditions.
|
|
8713
|
+
// reset direction for next iteration
|
|
7208
8714
|
scale(pk, current.fxprime, -1);
|
|
7209
8715
|
}
|
|
7210
8716
|
else {
|
|
8717
|
+
// update direction using Polak–Ribiere CG method
|
|
7211
8718
|
weightedSum(yk, 1, next.fxprime, -1, current.fxprime);
|
|
7212
8719
|
const delta_k = dotProduct(current.fxprime, current.fxprime);
|
|
7213
8720
|
const beta_k = Math.max(0, dotProduct(yk, next.fxprime) / delta_k);
|
|
@@ -7270,6 +8777,7 @@
|
|
|
7270
8777
|
exports.binaryFuzzySearch = binaryFuzzySearch;
|
|
7271
8778
|
exports.binaryFuzzySearchInNumberRange = binaryFuzzySearchInNumberRange;
|
|
7272
8779
|
exports.bisect = bisect;
|
|
8780
|
+
exports.bootstrapEstimate = bootstrapEstimate;
|
|
7273
8781
|
exports.calculateAnchorOfArc = calculateAnchorOfArc;
|
|
7274
8782
|
exports.calculateAnchorOfBounds = calculateAnchorOfBounds;
|
|
7275
8783
|
exports.calculateMaxRadius = calculateMaxRadius;
|
|
@@ -7290,6 +8798,7 @@
|
|
|
7290
8798
|
exports.conjugateGradient = conjugateGradient;
|
|
7291
8799
|
exports.constant = constant;
|
|
7292
8800
|
exports.containedInCircles = containedInCircles;
|
|
8801
|
+
exports.correlation = correlation;
|
|
7293
8802
|
exports.cos = cos;
|
|
7294
8803
|
exports.crossProduct = crossProduct;
|
|
7295
8804
|
exports.crossProductPoint = crossProductPoint;
|
|
@@ -7408,6 +8917,7 @@
|
|
|
7408
8917
|
exports.lowerFirst = lowerFirst;
|
|
7409
8918
|
exports.max = max;
|
|
7410
8919
|
exports.maxInArray = maxInArray;
|
|
8920
|
+
exports.mean = mean;
|
|
7411
8921
|
exports.median = median;
|
|
7412
8922
|
exports.memoize = memoize;
|
|
7413
8923
|
exports.merge = merge;
|
|
@@ -7434,6 +8944,7 @@
|
|
|
7434
8944
|
exports.monthSetterName = monthSetterName;
|
|
7435
8945
|
exports.nelderMead = nelderMead;
|
|
7436
8946
|
exports.norm2 = norm2;
|
|
8947
|
+
exports.normalQuantile = normalQuantile;
|
|
7437
8948
|
exports.normalTransform = normalTransform;
|
|
7438
8949
|
exports.normalizeAngle = normalizeAngle;
|
|
7439
8950
|
exports.normalizePadding = normalizePadding;
|