@visactor/vutils 1.0.24 → 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/ascending.js +1 -2
- 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 +4 -2
- package/cjs/common/extent.js.map +1 -1
- package/cjs/common/field.d.ts +6 -0
- package/cjs/common/field.js +3 -0
- package/cjs/common/field.js.map +1 -1
- package/cjs/common/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 +33 -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/isNull.js +2 -1
- package/cjs/common/isNumber.d.ts +23 -0
- package/cjs/common/isNumber.js +23 -1
- package/cjs/common/isNumber.js.map +1 -1
- package/cjs/common/isNumeric.d.ts +25 -0
- package/cjs/common/isNumeric.js +25 -0
- package/cjs/common/isNumeric.js.map +1 -1
- package/cjs/common/isObject.d.ts +21 -0
- package/cjs/common/isObject.js +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.map +1 -1
- package/cjs/common/isShallowEqual.js +2 -1
- package/cjs/common/isShallowEqual.js.map +1 -1
- package/cjs/common/isString.d.ts +13 -0
- package/cjs/common/isString.js +14 -2
- package/cjs/common/isString.js.map +1 -1
- package/cjs/common/isType.d.ts +6 -0
- package/cjs/common/isType.js +7 -1
- package/cjs/common/isType.js.map +1 -1
- package/cjs/common/isValidNumber.js.map +1 -1
- package/cjs/common/isValidUrl.js.map +1 -1
- package/cjs/common/kde.d.ts +28 -0
- package/cjs/common/kde.js +29 -5
- package/cjs/common/kde.js.map +1 -1
- package/cjs/common/mean.d.ts +2 -0
- package/cjs/common/mean.js +25 -0
- package/cjs/common/mean.js.map +1 -0
- package/cjs/common/merge.js +20 -4
- package/cjs/common/merge.js.map +1 -1
- package/cjs/common/mixin.js +14 -3
- package/cjs/common/mixin.js.map +1 -1
- package/cjs/common/normalQuantile.d.ts +5 -0
- package/cjs/common/normalQuantile.js +21 -0
- package/cjs/common/normalQuantile.js.map +1 -0
- package/cjs/common/number.d.ts +24 -0
- package/cjs/common/number.js +24 -1
- package/cjs/common/number.js.map +1 -1
- package/cjs/common/pad.d.ts +21 -0
- package/cjs/common/pad.js +21 -1
- package/cjs/common/pad.js.map +1 -1
- package/cjs/common/random.d.ts +3 -0
- package/cjs/common/random.js +13 -0
- package/cjs/common/random.js.map +1 -1
- package/cjs/common/regression-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 +51 -23
- 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 +75 -8
- package/cjs/data-structure/bounds.js.map +1 -1
- package/cjs/data-structure/hashTable.d.ts +4 -0
- package/cjs/data-structure/hashTable.js +13 -3
- package/cjs/data-structure/hashTable.js.map +1 -1
- package/cjs/data-structure/index.js +7 -3
- package/cjs/data-structure/index.js.map +1 -1
- package/cjs/data-structure/matrix.d.ts +91 -0
- package/cjs/data-structure/matrix.js +53 -5
- package/cjs/data-structure/matrix.js.map +1 -1
- package/cjs/data-structure/point.d.ts +3 -0
- package/cjs/data-structure/point.js +6 -2
- 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 +1602 -109
- 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/ascending.js +1 -2
- 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 +8 -3
- package/es/common/extent.js.map +1 -1
- package/es/common/field.d.ts +6 -0
- package/es/common/field.js +3 -1
- package/es/common/field.js.map +1 -1
- package/es/common/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 +22 -0
- package/es/common/index.js.map +1 -1
- package/es/common/interpolate.js +16 -3
- package/es/common/interpolate.js.map +1 -1
- package/es/common/isBase64.d.ts +6 -0
- package/es/common/isBase64.js +6 -0
- package/es/common/isBase64.js.map +1 -1
- package/es/common/isBoolean.d.ts +13 -0
- package/es/common/isBoolean.js +13 -1
- package/es/common/isBoolean.js.map +1 -1
- package/es/common/isEmpty.js +11 -1
- package/es/common/isEmpty.js.map +1 -1
- package/es/common/isEqual.js +19 -7
- package/es/common/isEqual.js.map +1 -1
- package/es/common/isFunction.d.ts +25 -0
- package/es/common/isFunction.js +26 -0
- package/es/common/isFunction.js.map +1 -1
- package/es/common/isNull.js +2 -1
- package/es/common/isNumber.d.ts +23 -0
- package/es/common/isNumber.js +23 -1
- package/es/common/isNumber.js.map +1 -1
- package/es/common/isNumeric.d.ts +25 -0
- package/es/common/isNumeric.js +25 -0
- package/es/common/isNumeric.js.map +1 -1
- package/es/common/isObject.d.ts +21 -0
- package/es/common/isObject.js +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 +26 -0
- package/es/common/isPlainObject.js.map +1 -1
- package/es/common/isShallowEqual.js +2 -1
- package/es/common/isShallowEqual.js.map +1 -1
- package/es/common/isString.d.ts +13 -0
- package/es/common/isString.js +14 -2
- package/es/common/isString.js.map +1 -1
- package/es/common/isType.d.ts +6 -0
- package/es/common/isType.js +7 -1
- package/es/common/isType.js.map +1 -1
- package/es/common/isValidNumber.js.map +1 -1
- package/es/common/isValidUrl.js.map +1 -1
- package/es/common/kde.d.ts +28 -0
- package/es/common/kde.js +29 -7
- package/es/common/kde.js.map +1 -1
- package/es/common/mean.d.ts +2 -0
- package/es/common/mean.js +19 -0
- package/es/common/mean.js.map +1 -0
- package/es/common/merge.js +22 -5
- package/es/common/merge.js.map +1 -1
- package/es/common/mixin.js +14 -3
- package/es/common/mixin.js.map +1 -1
- package/es/common/normalQuantile.d.ts +5 -0
- package/es/common/normalQuantile.js +15 -0
- package/es/common/normalQuantile.js.map +1 -0
- package/es/common/number.d.ts +24 -0
- package/es/common/number.js +24 -2
- package/es/common/number.js.map +1 -1
- package/es/common/pad.d.ts +21 -0
- package/es/common/pad.js +21 -1
- package/es/common/pad.js.map +1 -1
- package/es/common/random.d.ts +3 -0
- package/es/common/random.js +13 -1
- package/es/common/random.js.map +1 -1
- package/es/common/regression-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 +50 -24
- 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 +75 -9
- package/es/data-structure/bounds.js.map +1 -1
- package/es/data-structure/hashTable.d.ts +4 -0
- package/es/data-structure/hashTable.js +13 -3
- package/es/data-structure/hashTable.js.map +1 -1
- package/es/data-structure/index.js +3 -1
- package/es/data-structure/index.js.map +1 -1
- package/es/data-structure/matrix.d.ts +91 -0
- package/es/data-structure/matrix.js +53 -6
- package/es/data-structure/matrix.js.map +1 -1
- package/es/data-structure/point.d.ts +3 -0
- package/es/data-structure/point.js +6 -2
- 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 +3 -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
2635
|
visitPoints(data, x, y, (dx, dy, index) => {
|
|
2099
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) {
|
|
@@ -2123,6 +2665,7 @@
|
|
|
2123
2665
|
}
|
|
2124
2666
|
}
|
|
2125
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.
|
|
2126
2669
|
let lo = left - 1;
|
|
2127
2670
|
let hi = left;
|
|
2128
2671
|
for (let count = 0; count < m; count++) {
|
|
@@ -2135,6 +2678,7 @@
|
|
|
2135
2678
|
}
|
|
2136
2679
|
const actualStart = lo + 1;
|
|
2137
2680
|
const end = hi;
|
|
2681
|
+
// Find max distance and compute weights in single pass
|
|
2138
2682
|
let maxDist = 0;
|
|
2139
2683
|
const windowSize = end - actualStart;
|
|
2140
2684
|
const distances = new Array(windowSize);
|
|
@@ -2145,6 +2689,7 @@
|
|
|
2145
2689
|
maxDist = dist;
|
|
2146
2690
|
}
|
|
2147
2691
|
}
|
|
2692
|
+
// Compute weights using pre-calculated distances
|
|
2148
2693
|
let sumw = 0;
|
|
2149
2694
|
const w = new Array(windowSize);
|
|
2150
2695
|
for (let i = 0; i < windowSize; i++) {
|
|
@@ -2157,6 +2702,7 @@
|
|
|
2157
2702
|
sumw += wi;
|
|
2158
2703
|
}
|
|
2159
2704
|
if (sumw === 0) {
|
|
2705
|
+
// Fall back to the nearest y, preserving input order for equal distances.
|
|
2160
2706
|
let nearestIdx = 0;
|
|
2161
2707
|
let nearestDistance = Math.abs(ptsX[0] - x0);
|
|
2162
2708
|
for (let i = 1; i < n; i++) {
|
|
@@ -2176,6 +2722,7 @@
|
|
|
2176
2722
|
}
|
|
2177
2723
|
return s / sumw;
|
|
2178
2724
|
}
|
|
2725
|
+
// weighted linear regression on local points
|
|
2179
2726
|
let sw = 0;
|
|
2180
2727
|
let sx = 0;
|
|
2181
2728
|
let sy = 0;
|
|
@@ -2228,16 +2775,21 @@
|
|
|
2228
2775
|
return out;
|
|
2229
2776
|
}
|
|
2230
2777
|
const step = (max - min) / (N - 1);
|
|
2778
|
+
// optionally add robust iterations
|
|
2231
2779
|
let robustWeights;
|
|
2780
|
+
// Disable robustness iterations for large datasets to improve performance
|
|
2781
|
+
// Users can override by setting iterations explicitly
|
|
2232
2782
|
const effectiveIterations = options.iterations != null ? iterations : n > 500 ? 0 : iterations;
|
|
2233
2783
|
if (effectiveIterations > 0) {
|
|
2234
2784
|
for (let iter = 0; iter < effectiveIterations; iter++) {
|
|
2785
|
+
// compute fits - pre-allocate arrays
|
|
2235
2786
|
const fits = new Array(n);
|
|
2236
2787
|
const res = new Array(n);
|
|
2237
2788
|
for (let i = 0; i < n; i++) {
|
|
2238
2789
|
fits[i] = predictSingle(ptsX[i], robustWeights);
|
|
2239
2790
|
res[i] = Math.abs(ptsY[i] - fits[i]);
|
|
2240
2791
|
}
|
|
2792
|
+
// median absolute deviation
|
|
2241
2793
|
const sortedRes = res.slice().sort((a, b) => a - b);
|
|
2242
2794
|
const med = sortedRes[Math.floor(n / 2)] || 0;
|
|
2243
2795
|
robustWeights = new Array(n);
|
|
@@ -2262,6 +2814,7 @@
|
|
|
2262
2814
|
if (n === 0) {
|
|
2263
2815
|
return out;
|
|
2264
2816
|
}
|
|
2817
|
+
// use data x-range (already sorted)
|
|
2265
2818
|
const min = ptsX[0];
|
|
2266
2819
|
const max = ptsX[n - 1];
|
|
2267
2820
|
if (min === undefined || max === undefined || min === Infinity || max === -Infinity) {
|
|
@@ -2271,18 +2824,20 @@
|
|
|
2271
2824
|
if (comps.n === 0) {
|
|
2272
2825
|
return out;
|
|
2273
2826
|
}
|
|
2274
|
-
const z =
|
|
2827
|
+
const z = confidenceCriticalValue(alpha);
|
|
2275
2828
|
if (comps.min === comps.max) {
|
|
2276
2829
|
const v = predictSingle(comps.min);
|
|
2277
2830
|
const errs = stdErrorsAt(comps.min, comps);
|
|
2831
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2832
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2278
2833
|
for (let i = 0; i < N; i++) {
|
|
2279
2834
|
out.push({
|
|
2280
2835
|
x: comps.min,
|
|
2281
2836
|
mean: v,
|
|
2282
|
-
lower: v -
|
|
2283
|
-
upper: v +
|
|
2284
|
-
predLower: v -
|
|
2285
|
-
predUpper: v +
|
|
2837
|
+
lower: v - meanHalfWidth,
|
|
2838
|
+
upper: v + meanHalfWidth,
|
|
2839
|
+
predLower: v - predHalfWidth,
|
|
2840
|
+
predUpper: v + predHalfWidth
|
|
2286
2841
|
});
|
|
2287
2842
|
}
|
|
2288
2843
|
return out;
|
|
@@ -2292,13 +2847,15 @@
|
|
|
2292
2847
|
const px = i === N - 1 ? max : min + step * i;
|
|
2293
2848
|
const yh = predictSingle(px);
|
|
2294
2849
|
const errs = stdErrorsAt(px, comps);
|
|
2850
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
2851
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2295
2852
|
out.push({
|
|
2296
2853
|
x: px,
|
|
2297
2854
|
mean: yh,
|
|
2298
|
-
lower: yh -
|
|
2299
|
-
upper: yh +
|
|
2300
|
-
predLower: yh -
|
|
2301
|
-
predUpper: yh +
|
|
2855
|
+
lower: yh - meanHalfWidth,
|
|
2856
|
+
upper: yh + meanHalfWidth,
|
|
2857
|
+
predLower: yh - predHalfWidth,
|
|
2858
|
+
predUpper: yh + predHalfWidth
|
|
2302
2859
|
});
|
|
2303
2860
|
}
|
|
2304
2861
|
return out;
|
|
@@ -2312,13 +2869,16 @@
|
|
|
2312
2869
|
}
|
|
2313
2870
|
|
|
2314
2871
|
function solveLinearSystem(A, b) {
|
|
2872
|
+
// Gaussian elimination with partial pivoting
|
|
2315
2873
|
const n = b.length;
|
|
2874
|
+
// clone
|
|
2316
2875
|
const M = new Array(n);
|
|
2317
2876
|
for (let i = 0; i < n; i++) {
|
|
2318
2877
|
M[i] = A[i].slice();
|
|
2319
2878
|
M[i].push(b[i]);
|
|
2320
2879
|
}
|
|
2321
2880
|
for (let k = 0; k < n; k++) {
|
|
2881
|
+
// find pivot
|
|
2322
2882
|
let maxRow = k;
|
|
2323
2883
|
let maxVal = Math.abs(M[k][k]);
|
|
2324
2884
|
for (let i = k + 1; i < n; i++) {
|
|
@@ -2333,14 +2893,18 @@
|
|
|
2333
2893
|
M[k] = M[maxRow];
|
|
2334
2894
|
M[maxRow] = tmp;
|
|
2335
2895
|
}
|
|
2896
|
+
// singular check
|
|
2336
2897
|
if (Math.abs(M[k][k]) < 1e-12) {
|
|
2898
|
+
// return least squares fallback zeros
|
|
2337
2899
|
const res = new Array(n).fill(0);
|
|
2338
2900
|
return res;
|
|
2339
2901
|
}
|
|
2902
|
+
// normalize row
|
|
2340
2903
|
for (let j = k + 1; j <= n; j++) {
|
|
2341
2904
|
M[k][j] = M[k][j] / M[k][k];
|
|
2342
2905
|
}
|
|
2343
2906
|
M[k][k] = 1;
|
|
2907
|
+
// eliminate
|
|
2344
2908
|
for (let i = 0; i < n; i++) {
|
|
2345
2909
|
if (i === k) {
|
|
2346
2910
|
continue;
|
|
@@ -2377,6 +2941,7 @@
|
|
|
2377
2941
|
xp *= dx;
|
|
2378
2942
|
}
|
|
2379
2943
|
});
|
|
2944
|
+
// build normal matrix
|
|
2380
2945
|
const A = new Array(m);
|
|
2381
2946
|
for (let i = 0; i < m; i++) {
|
|
2382
2947
|
A[i] = new Array(m).fill(0);
|
|
@@ -2407,6 +2972,7 @@
|
|
|
2407
2972
|
coef,
|
|
2408
2973
|
predict,
|
|
2409
2974
|
rSquared: rSquared(data, x, y, (() => {
|
|
2975
|
+
// compute mean y
|
|
2410
2976
|
let sum = 0;
|
|
2411
2977
|
let cnt = 0;
|
|
2412
2978
|
visitPoints(data, x, y, (_dx, dy) => {
|
|
@@ -2420,6 +2986,7 @@
|
|
|
2420
2986
|
if (N <= 0) {
|
|
2421
2987
|
return out;
|
|
2422
2988
|
}
|
|
2989
|
+
// compute range
|
|
2423
2990
|
let min = Infinity;
|
|
2424
2991
|
let max = -Infinity;
|
|
2425
2992
|
visitPoints(data, x, y, dx => {
|
|
@@ -2458,18 +3025,20 @@
|
|
|
2458
3025
|
if (comps.n === 0) {
|
|
2459
3026
|
return out;
|
|
2460
3027
|
}
|
|
2461
|
-
const z =
|
|
3028
|
+
const z = confidenceCriticalValue(alpha);
|
|
2462
3029
|
if (comps.min === comps.max) {
|
|
2463
3030
|
const v = predict(comps.min);
|
|
2464
3031
|
const errs = stdErrorsAt(comps.min, comps);
|
|
3032
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3033
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2465
3034
|
for (let i = 0; i < N; i++) {
|
|
2466
3035
|
out.push({
|
|
2467
3036
|
x: comps.min,
|
|
2468
3037
|
mean: v,
|
|
2469
|
-
lower: v -
|
|
2470
|
-
upper: v +
|
|
2471
|
-
predLower: v -
|
|
2472
|
-
predUpper: v +
|
|
3038
|
+
lower: v - meanHalfWidth,
|
|
3039
|
+
upper: v + meanHalfWidth,
|
|
3040
|
+
predLower: v - predHalfWidth,
|
|
3041
|
+
predUpper: v + predHalfWidth
|
|
2473
3042
|
});
|
|
2474
3043
|
}
|
|
2475
3044
|
return out;
|
|
@@ -2479,13 +3048,15 @@
|
|
|
2479
3048
|
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
2480
3049
|
const yh = predict(px);
|
|
2481
3050
|
const errs = stdErrorsAt(px, comps);
|
|
3051
|
+
const meanHalfWidth = confidenceHalfWidth(z, errs.seMean);
|
|
3052
|
+
const predHalfWidth = confidenceHalfWidth(z, errs.sePred);
|
|
2482
3053
|
out.push({
|
|
2483
3054
|
x: px,
|
|
2484
3055
|
mean: yh,
|
|
2485
|
-
lower: yh -
|
|
2486
|
-
upper: yh +
|
|
2487
|
-
predLower: yh -
|
|
2488
|
-
predUpper: yh +
|
|
3056
|
+
lower: yh - meanHalfWidth,
|
|
3057
|
+
upper: yh + meanHalfWidth,
|
|
3058
|
+
predLower: yh - predHalfWidth,
|
|
3059
|
+
predUpper: yh + predHalfWidth
|
|
2489
3060
|
});
|
|
2490
3061
|
}
|
|
2491
3062
|
return out;
|
|
@@ -2493,14 +3064,29 @@
|
|
|
2493
3064
|
};
|
|
2494
3065
|
}
|
|
2495
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) */
|
|
2496
3076
|
const gaussian = (u) => {
|
|
2497
3077
|
const invSqrt2Pi = 1 / Math.sqrt(2 * Math.PI);
|
|
2498
3078
|
return invSqrt2Pi * Math.exp(-0.5 * u * u);
|
|
2499
3079
|
};
|
|
3080
|
+
/** Epanechnikov kernel (compact support) */
|
|
2500
3081
|
const epanechnikov = (u) => {
|
|
2501
3082
|
const absu = Math.abs(u);
|
|
2502
3083
|
return absu <= 1 ? 0.75 * (1 - u * u) : 0;
|
|
2503
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
|
+
*/
|
|
2504
3090
|
function scott(n, std, dim = 1) {
|
|
2505
3091
|
if (n <= 0 || std === 0) {
|
|
2506
3092
|
return 0;
|
|
@@ -2514,6 +3100,9 @@
|
|
|
2514
3100
|
const factor = Math.pow(4 / (dim + 2), 1 / (dim + 4));
|
|
2515
3101
|
return factor * std * Math.pow(n, -1 / (dim + 4));
|
|
2516
3102
|
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Compute standard deviation for numeric array
|
|
3105
|
+
*/
|
|
2517
3106
|
function std(values) {
|
|
2518
3107
|
const n = values.length;
|
|
2519
3108
|
if (n === 0) {
|
|
@@ -2531,6 +3120,10 @@
|
|
|
2531
3120
|
}
|
|
2532
3121
|
return Math.sqrt(s / n);
|
|
2533
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
|
+
*/
|
|
2534
3127
|
function kde(data, options = {}) {
|
|
2535
3128
|
const n = data.length;
|
|
2536
3129
|
const kernel = options.kernel || gaussian;
|
|
@@ -2545,6 +3138,7 @@
|
|
|
2545
3138
|
h = scott(n, sd, 1);
|
|
2546
3139
|
}
|
|
2547
3140
|
}
|
|
3141
|
+
// if still zero (constant data), evaluator returns zeros
|
|
2548
3142
|
if (!h || h <= 0) {
|
|
2549
3143
|
const zerosEval = (x) => {
|
|
2550
3144
|
if (Array.isArray(x)) {
|
|
@@ -2561,6 +3155,7 @@
|
|
|
2561
3155
|
if (N <= 0) {
|
|
2562
3156
|
return out;
|
|
2563
3157
|
}
|
|
3158
|
+
// compute data min (use data range if available)
|
|
2564
3159
|
let min = Infinity;
|
|
2565
3160
|
let max = -Infinity;
|
|
2566
3161
|
for (let j = 0; j < n; j++) {
|
|
@@ -2617,6 +3212,7 @@
|
|
|
2617
3212
|
if (N <= 0) {
|
|
2618
3213
|
return out;
|
|
2619
3214
|
}
|
|
3215
|
+
// compute data range
|
|
2620
3216
|
let min = Infinity;
|
|
2621
3217
|
let max = -Infinity;
|
|
2622
3218
|
for (let i = 0; i < n; i++) {
|
|
@@ -2632,6 +3228,7 @@
|
|
|
2632
3228
|
return out;
|
|
2633
3229
|
}
|
|
2634
3230
|
if (min === max) {
|
|
3231
|
+
// single point repeated
|
|
2635
3232
|
for (let i = 0; i < N; i++) {
|
|
2636
3233
|
out.push({ x: min, y: evalPoint(min) });
|
|
2637
3234
|
}
|
|
@@ -2647,6 +3244,14 @@
|
|
|
2647
3244
|
};
|
|
2648
3245
|
}
|
|
2649
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
|
+
*/
|
|
2650
3255
|
function ecdf(data) {
|
|
2651
3256
|
const n = data.length;
|
|
2652
3257
|
const sorted = data.slice().sort((a, b) => a - b);
|
|
@@ -2654,8 +3259,9 @@
|
|
|
2654
3259
|
if (n === 0) {
|
|
2655
3260
|
return 0;
|
|
2656
3261
|
}
|
|
3262
|
+
// count of values <= x (upper bound index)
|
|
2657
3263
|
let lo = 0;
|
|
2658
|
-
let hi = n;
|
|
3264
|
+
let hi = n; // exclusive
|
|
2659
3265
|
while (lo < hi) {
|
|
2660
3266
|
const mid = (lo + hi) >>> 1;
|
|
2661
3267
|
if (sorted[mid] <= x) {
|
|
@@ -2707,8 +3313,192 @@
|
|
|
2707
3313
|
};
|
|
2708
3314
|
}
|
|
2709
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
|
+
|
|
2710
3499
|
class HashValue {
|
|
2711
3500
|
}
|
|
3501
|
+
// 基于 array 和 object的 HashTable
|
|
2712
3502
|
class HashTable {
|
|
2713
3503
|
constructor() {
|
|
2714
3504
|
this.items = {};
|
|
@@ -2744,6 +3534,10 @@
|
|
|
2744
3534
|
this.resetIndex();
|
|
2745
3535
|
}
|
|
2746
3536
|
}
|
|
3537
|
+
/**
|
|
3538
|
+
* 不包含当前index
|
|
3539
|
+
* @param index
|
|
3540
|
+
*/
|
|
2747
3541
|
delFrom(index) {
|
|
2748
3542
|
for (let i = index + 1; i < this.count(); i++) {
|
|
2749
3543
|
const key = this.itemList[i].key;
|
|
@@ -2787,6 +3581,7 @@
|
|
|
2787
3581
|
getKeyByIndex(index) {
|
|
2788
3582
|
return this.itemList[index].key;
|
|
2789
3583
|
}
|
|
3584
|
+
//遍历 扩展
|
|
2790
3585
|
foreach(callback) {
|
|
2791
3586
|
for (const key in this.items) {
|
|
2792
3587
|
const returnVal = callback(key, this.items[key].value);
|
|
@@ -2813,12 +3608,14 @@
|
|
|
2813
3608
|
}
|
|
2814
3609
|
return false;
|
|
2815
3610
|
}
|
|
3611
|
+
//获取index
|
|
2816
3612
|
indexOf(key) {
|
|
2817
3613
|
if (this.has(key)) {
|
|
2818
3614
|
return this.items[key].index;
|
|
2819
3615
|
}
|
|
2820
3616
|
return -1;
|
|
2821
3617
|
}
|
|
3618
|
+
//插入
|
|
2822
3619
|
insertAt(index, value, key) {
|
|
2823
3620
|
const hashV = new HashValue();
|
|
2824
3621
|
hashV.index = index;
|
|
@@ -2851,6 +3648,7 @@
|
|
|
2851
3648
|
return returnArr;
|
|
2852
3649
|
}
|
|
2853
3650
|
toImmutableMap() {
|
|
3651
|
+
// do nothing
|
|
2854
3652
|
}
|
|
2855
3653
|
}
|
|
2856
3654
|
|
|
@@ -2877,6 +3675,15 @@
|
|
|
2877
3675
|
function asin(x) {
|
|
2878
3676
|
return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x);
|
|
2879
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
|
+
*/
|
|
2880
3687
|
function pointAt(x1, y1, x2, y2, t) {
|
|
2881
3688
|
let x = x2;
|
|
2882
3689
|
let y = y2;
|
|
@@ -2892,6 +3699,7 @@
|
|
|
2892
3699
|
};
|
|
2893
3700
|
}
|
|
2894
3701
|
function lengthFromPointToLine(point, point1, point2) {
|
|
3702
|
+
// 面积
|
|
2895
3703
|
const dir1X = point2.x - point1.x;
|
|
2896
3704
|
const dir1Y = point2.y - point1.y;
|
|
2897
3705
|
const dir2X = point.x - point1.x;
|
|
@@ -2936,6 +3744,9 @@
|
|
|
2936
3744
|
|
|
2937
3745
|
class Point {
|
|
2938
3746
|
constructor(x = 0, y = 0, x1, y1) {
|
|
3747
|
+
/**
|
|
3748
|
+
* 面积图的下点
|
|
3749
|
+
*/
|
|
2939
3750
|
this.x = 0;
|
|
2940
3751
|
this.y = 0;
|
|
2941
3752
|
this.x = x;
|
|
@@ -2988,6 +3799,7 @@
|
|
|
2988
3799
|
}
|
|
2989
3800
|
}
|
|
2990
3801
|
class PointService {
|
|
3802
|
+
/* distance */
|
|
2991
3803
|
static distancePP(p1, p2) {
|
|
2992
3804
|
return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
|
|
2993
3805
|
}
|
|
@@ -2997,6 +3809,7 @@
|
|
|
2997
3809
|
static distancePN(point, x, y) {
|
|
2998
3810
|
return sqrt(pow(x - point.x, 2) + pow(y - point.y, 2));
|
|
2999
3811
|
}
|
|
3812
|
+
/* point at */
|
|
3000
3813
|
static pointAtPP(p1, p2, t) {
|
|
3001
3814
|
return new Point((p2.x - p1.x) * t + p1.x, (p2.y - p1.y) * t + p1.y);
|
|
3002
3815
|
}
|
|
@@ -3045,12 +3858,30 @@
|
|
|
3045
3858
|
return new ImageData(flipClampBuffer, width, height);
|
|
3046
3859
|
};
|
|
3047
3860
|
|
|
3861
|
+
/**
|
|
3862
|
+
* 处理角度相关的数据计算
|
|
3863
|
+
*/
|
|
3864
|
+
/**
|
|
3865
|
+
* degree -> radian
|
|
3866
|
+
* @param degree
|
|
3867
|
+
* @returns radian
|
|
3868
|
+
*/
|
|
3048
3869
|
function degreeToRadian(degree) {
|
|
3049
3870
|
return degree * (Math.PI / 180);
|
|
3050
3871
|
}
|
|
3872
|
+
/**
|
|
3873
|
+
* radian -> degree
|
|
3874
|
+
* @param radian
|
|
3875
|
+
* @returns
|
|
3876
|
+
*/
|
|
3051
3877
|
function radianToDegree(radian) {
|
|
3052
3878
|
return (radian * 180) / Math.PI;
|
|
3053
3879
|
}
|
|
3880
|
+
/**
|
|
3881
|
+
* 对弧度进行格式化
|
|
3882
|
+
* @param a 传入的弧度
|
|
3883
|
+
* @returns
|
|
3884
|
+
*/
|
|
3054
3885
|
const clampRadian = (angle = 0) => {
|
|
3055
3886
|
if (angle < 0) {
|
|
3056
3887
|
while (angle < -tau) {
|
|
@@ -3064,14 +3895,28 @@
|
|
|
3064
3895
|
}
|
|
3065
3896
|
return angle;
|
|
3066
3897
|
};
|
|
3898
|
+
// alias of clampRadian
|
|
3067
3899
|
const clampAngleByRadian = clampRadian;
|
|
3900
|
+
/**
|
|
3901
|
+
* 对角度进行格式化
|
|
3902
|
+
* @param a 角度
|
|
3903
|
+
* @returns
|
|
3904
|
+
*/
|
|
3068
3905
|
const clampDegree = (a = 0) => {
|
|
3069
3906
|
if (a > 360 || a < -360) {
|
|
3070
3907
|
return a % 360;
|
|
3071
3908
|
}
|
|
3072
3909
|
return a;
|
|
3073
3910
|
};
|
|
3911
|
+
// alias of clampDegree
|
|
3074
3912
|
const clampAngleByDegree = clampDegree;
|
|
3913
|
+
/**
|
|
3914
|
+
* 根据弧度及半径计算极坐标系下的坐标点
|
|
3915
|
+
* @param centerX 圆心坐标
|
|
3916
|
+
* @param radius 半径
|
|
3917
|
+
* @param angleInRadian 弧度
|
|
3918
|
+
* @returns 返回笛卡尔坐标点
|
|
3919
|
+
*/
|
|
3075
3920
|
function polarToCartesian(center, radius, angleInRadian) {
|
|
3076
3921
|
if (!radius) {
|
|
3077
3922
|
return { x: center.x, y: center.y };
|
|
@@ -3081,6 +3926,14 @@
|
|
|
3081
3926
|
y: center.y + radius * Math.sin(angleInRadian)
|
|
3082
3927
|
};
|
|
3083
3928
|
}
|
|
3929
|
+
/**
|
|
3930
|
+
* 根据笛卡尔坐标系坐标计算极坐标坐标点
|
|
3931
|
+
* @param point
|
|
3932
|
+
* @param center 极坐标系中心点坐标
|
|
3933
|
+
* @param startAngle 极坐标系起始角度
|
|
3934
|
+
* @param endAngle 极坐标系结束角度
|
|
3935
|
+
* @returns 极坐标系坐标点
|
|
3936
|
+
*/
|
|
3084
3937
|
function cartesianToPolar(point, center = { x: 0, y: 0 }, startAngle = 0, endAngle = 2 * Math.PI) {
|
|
3085
3938
|
const { x, y } = point;
|
|
3086
3939
|
const { x: centerX, y: centerY } = center;
|
|
@@ -3111,9 +3964,19 @@
|
|
|
3111
3964
|
angle: radian
|
|
3112
3965
|
};
|
|
3113
3966
|
}
|
|
3967
|
+
/**
|
|
3968
|
+
* 根据点的笛卡尔坐标获取该点与圆心的连线同正 x 轴方向的夹角
|
|
3969
|
+
* @param center 圆心坐标
|
|
3970
|
+
* @param point 待求的点坐标
|
|
3971
|
+
* @returns 返回夹角对应的弧度值
|
|
3972
|
+
*/
|
|
3114
3973
|
function getAngleByPoint(center, point) {
|
|
3115
3974
|
return Math.atan2(point.y - center.y, point.x - center.x);
|
|
3116
3975
|
}
|
|
3976
|
+
/**
|
|
3977
|
+
* 角度标准化处理
|
|
3978
|
+
* @param angle 弧度角
|
|
3979
|
+
*/
|
|
3117
3980
|
function normalizeAngle(angle) {
|
|
3118
3981
|
while (angle < 0) {
|
|
3119
3982
|
angle += Math.PI * 2;
|
|
@@ -3123,6 +3986,14 @@
|
|
|
3123
3986
|
}
|
|
3124
3987
|
return angle;
|
|
3125
3988
|
}
|
|
3989
|
+
/**
|
|
3990
|
+
* 指定开始角度和结束角度,计算这个范围内的边界角度,
|
|
3991
|
+
* 即起始角度以及经过的东、南、西、北四个方向的角度
|
|
3992
|
+
* 计算这个角度,可以用于计算一个弧度的bounding box 等
|
|
3993
|
+
* @param startAngle 起始角度的弧度值
|
|
3994
|
+
* @param endAngle 结束角度的弧度值
|
|
3995
|
+
* @returns 边界角度数组
|
|
3996
|
+
*/
|
|
3126
3997
|
function findBoundaryAngles(startAngle, endAngle) {
|
|
3127
3998
|
const deltaAngle = Math.abs(endAngle - startAngle);
|
|
3128
3999
|
if (deltaAngle >= 2 * Math.PI || 2 * Math.PI - deltaAngle < 1e-6) {
|
|
@@ -3141,6 +4012,14 @@
|
|
|
3141
4012
|
}
|
|
3142
4013
|
return steps;
|
|
3143
4014
|
}
|
|
4015
|
+
/**
|
|
4016
|
+
* 计算指定范围内,指定中心的情况下,不超出边界的最大可用半径
|
|
4017
|
+
* @param rect 矩形的大小
|
|
4018
|
+
* @param center 中心点
|
|
4019
|
+
* @param startAngle 起始角度的弧度值
|
|
4020
|
+
* @param endAngle 结束角度的弧度值
|
|
4021
|
+
* @returns 最大半径
|
|
4022
|
+
*/
|
|
3144
4023
|
function calculateMaxRadius(rect, center, startAngle, endAngle) {
|
|
3145
4024
|
const { x, y } = center;
|
|
3146
4025
|
const steps = findBoundaryAngles(startAngle, endAngle);
|
|
@@ -3192,10 +4071,18 @@
|
|
|
3192
4071
|
return 1;
|
|
3193
4072
|
}
|
|
3194
4073
|
|
|
4074
|
+
/* 用于判断2d相交 */
|
|
3195
4075
|
function sub(out, v1, v2) {
|
|
3196
4076
|
out[0] = v1[0] - v2[0];
|
|
3197
4077
|
out[1] = v1[1] - v2[1];
|
|
3198
4078
|
}
|
|
4079
|
+
/**
|
|
4080
|
+
* 判断直线是否相交,投影法
|
|
4081
|
+
* @param left1
|
|
4082
|
+
* @param right1
|
|
4083
|
+
* @param left2
|
|
4084
|
+
* @param right2
|
|
4085
|
+
*/
|
|
3199
4086
|
function isIntersect(left1, right1, left2, right2) {
|
|
3200
4087
|
let min1 = left1[0];
|
|
3201
4088
|
let max1 = right1[0];
|
|
@@ -3222,6 +4109,15 @@
|
|
|
3222
4109
|
}
|
|
3223
4110
|
return true;
|
|
3224
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
|
+
*/
|
|
3225
4121
|
function getIntersectPoint(left1, right1, left2, right2) {
|
|
3226
4122
|
if (!isIntersect(left1, right1, left2, right2)) {
|
|
3227
4123
|
return false;
|
|
@@ -3231,9 +4127,16 @@
|
|
|
3231
4127
|
const tempVec = [0, 0];
|
|
3232
4128
|
sub(dir1, right1, left1);
|
|
3233
4129
|
sub(dir2, right2, left2);
|
|
4130
|
+
// 判断共线
|
|
3234
4131
|
if (fuzzyEqualVec(dir1, dir2)) {
|
|
3235
4132
|
return true;
|
|
3236
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时,可以求出交点
|
|
3237
4140
|
sub(tempVec, left2, left1);
|
|
3238
4141
|
const t = crossProduct(tempVec, dir2) / crossProduct(dir1, dir2);
|
|
3239
4142
|
if (t >= 0 && t <= 1) {
|
|
@@ -3241,6 +4144,13 @@
|
|
|
3241
4144
|
}
|
|
3242
4145
|
return false;
|
|
3243
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
|
+
*/
|
|
3244
4154
|
function getRectIntersect(bbox1, bbox2, format) {
|
|
3245
4155
|
if (bbox1 === null) {
|
|
3246
4156
|
return bbox2;
|
|
@@ -3285,19 +4195,35 @@
|
|
|
3285
4195
|
}
|
|
3286
4196
|
return { x11, x12, y11, y12, x21, x22, y21, y22 };
|
|
3287
4197
|
};
|
|
4198
|
+
/**
|
|
4199
|
+
* 矩形是否在另一个矩形内部
|
|
4200
|
+
* 返回InnerBBox
|
|
4201
|
+
* @param bbox1
|
|
4202
|
+
* @param bbox2
|
|
4203
|
+
* @param format
|
|
4204
|
+
*/
|
|
3288
4205
|
function rectInsideAnotherRect(bbox1, bbox2, format) {
|
|
3289
4206
|
if (!bbox1 || !bbox2) {
|
|
3290
4207
|
return exports.InnerBBox.NONE;
|
|
3291
4208
|
}
|
|
3292
4209
|
const { x11, x12, y11, y12, x21, x22, y21, y22 } = formatTwoBBox(bbox1, bbox2, format);
|
|
4210
|
+
// bbox1在bbox2内部
|
|
3293
4211
|
if (x11 > x21 && x12 < x22 && y11 > y21 && y12 < y22) {
|
|
3294
4212
|
return exports.InnerBBox.BBOX1;
|
|
3295
4213
|
}
|
|
4214
|
+
// bbox2在bbox1内部
|
|
3296
4215
|
if (x21 > x11 && x22 < x12 && y21 > y11 && y22 < y12) {
|
|
3297
4216
|
return exports.InnerBBox.BBOX2;
|
|
3298
4217
|
}
|
|
3299
4218
|
return exports.InnerBBox.NONE;
|
|
3300
4219
|
}
|
|
4220
|
+
/**
|
|
4221
|
+
* 两个矩形是否相交
|
|
4222
|
+
* 如果有矩形为null,判断为相交
|
|
4223
|
+
* @param bbox1
|
|
4224
|
+
* @param bbox2
|
|
4225
|
+
* @param format
|
|
4226
|
+
*/
|
|
3301
4227
|
function isRectIntersect(bbox1, bbox2, format) {
|
|
3302
4228
|
if (bbox1 && bbox2) {
|
|
3303
4229
|
if (!format) {
|
|
@@ -3314,6 +4240,12 @@
|
|
|
3314
4240
|
}
|
|
3315
4241
|
return true;
|
|
3316
4242
|
}
|
|
4243
|
+
/**
|
|
4244
|
+
* 点在box内部
|
|
4245
|
+
* 如果bbox为null返回true
|
|
4246
|
+
* @param point
|
|
4247
|
+
* @param bbox
|
|
4248
|
+
*/
|
|
3317
4249
|
function pointInRect(point, bbox, format) {
|
|
3318
4250
|
if (!bbox) {
|
|
3319
4251
|
return true;
|
|
@@ -3333,9 +4265,20 @@
|
|
|
3333
4265
|
}
|
|
3334
4266
|
return point.x >= x11 && point.x <= x12 && point.y >= y11 && point.y <= y12;
|
|
3335
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
|
+
*/
|
|
3336
4274
|
function getProjectionRadius(checkAxis, axis) {
|
|
3337
4275
|
return Math.abs(axis[0] * checkAxis[0] + axis[1] * checkAxis[1]);
|
|
3338
4276
|
}
|
|
4277
|
+
/**
|
|
4278
|
+
* 逆时针旋转矩阵(从正 x 轴开始,旋转到正 y 轴的方向)
|
|
4279
|
+
* [ cos(θ) -sin(θ) ]
|
|
4280
|
+
* [ sin(θ) cos(θ) ]
|
|
4281
|
+
*/
|
|
3339
4282
|
function rotatePoint({ x, y }, rad, origin = { x: 0, y: 0 }) {
|
|
3340
4283
|
return {
|
|
3341
4284
|
x: (x - origin.x) * Math.cos(rad) - (y - origin.y) * Math.sin(rad) + origin.x,
|
|
@@ -3348,6 +4291,10 @@
|
|
|
3348
4291
|
y: (box.y1 + box.y2) / 2
|
|
3349
4292
|
};
|
|
3350
4293
|
}
|
|
4294
|
+
/**
|
|
4295
|
+
* 转化为顶点坐标数组
|
|
4296
|
+
* @param {Object} box
|
|
4297
|
+
*/
|
|
3351
4298
|
function toRect(box, isDeg) {
|
|
3352
4299
|
const deg = isDeg ? degreeToRadian(box.angle) : box.angle;
|
|
3353
4300
|
const cp = getCenterPoint(box);
|
|
@@ -3376,15 +4323,21 @@
|
|
|
3376
4323
|
const vector = (start, end) => {
|
|
3377
4324
|
return [end.x - start.x, end.y - start.y];
|
|
3378
4325
|
};
|
|
4326
|
+
// 两个矩形的中心点
|
|
3379
4327
|
const p1 = getCenterPoint(box1);
|
|
3380
4328
|
const p2 = getCenterPoint(box2);
|
|
4329
|
+
// 向量 p1p2
|
|
3381
4330
|
const vp1p2 = vector(p1, p2);
|
|
4331
|
+
//矩形1的两边向量
|
|
3382
4332
|
const AB = vector(rect1[0], rect1[1]);
|
|
3383
4333
|
const BC = vector(rect1[1], rect1[2]);
|
|
4334
|
+
//矩形2的两边向量
|
|
3384
4335
|
const A1B1 = vector(rect2[0], rect2[1]);
|
|
3385
4336
|
const B1C1 = vector(rect2[1], rect2[2]);
|
|
4337
|
+
// 矩形1 的两个弧度
|
|
3386
4338
|
const deg11 = isDeg ? degreeToRadian(box1.angle) : box1.angle;
|
|
3387
4339
|
let deg12 = isDeg ? degreeToRadian(90 - box1.angle) : box1.angle + halfPi;
|
|
4340
|
+
// 矩形2 的两个弧度
|
|
3388
4341
|
const deg21 = isDeg ? degreeToRadian(box2.angle) : box2.angle;
|
|
3389
4342
|
let deg22 = isDeg ? degreeToRadian(90 - box2.angle) : box2.angle + halfPi;
|
|
3390
4343
|
if (deg12 > pi2) {
|
|
@@ -3393,10 +4346,12 @@
|
|
|
3393
4346
|
if (deg22 > pi2) {
|
|
3394
4347
|
deg22 -= pi2;
|
|
3395
4348
|
}
|
|
4349
|
+
// 投影重叠
|
|
3396
4350
|
const isCover = (checkAxisRadius, deg, targetAxis1, targetAxis2) => {
|
|
3397
4351
|
const checkAxis = [Math.cos(deg), Math.sin(deg)];
|
|
3398
4352
|
const targetAxisRadius = (getProjectionRadius(checkAxis, targetAxis1) + getProjectionRadius(checkAxis, targetAxis2)) / 2;
|
|
3399
4353
|
const centerPointRadius = getProjectionRadius(checkAxis, vp1p2);
|
|
4354
|
+
// console.log('abc', `checkAxis:${checkAxis},三个投影:${checkAxisRadius}, ${targetAxisRadius}, ${centerPointRadius}`)
|
|
3400
4355
|
return checkAxisRadius + targetAxisRadius > centerPointRadius;
|
|
3401
4356
|
};
|
|
3402
4357
|
return (isCover((box1.x2 - box1.x1) / 2, deg11, A1B1, B1C1) &&
|
|
@@ -3411,6 +4366,7 @@
|
|
|
3411
4366
|
let y2;
|
|
3412
4367
|
function getAABBFromPoints(points) {
|
|
3413
4368
|
(x1 = Infinity), (y1 = Infinity), (x2 = -Infinity), (y2 = -Infinity);
|
|
4369
|
+
// todo 有优化空间
|
|
3414
4370
|
points.forEach(point => {
|
|
3415
4371
|
if (x1 > point.x) {
|
|
3416
4372
|
x1 = point.x;
|
|
@@ -3462,6 +4418,7 @@
|
|
|
3462
4418
|
y22 = temp;
|
|
3463
4419
|
}
|
|
3464
4420
|
}
|
|
4421
|
+
// 不相交直接跳过
|
|
3465
4422
|
if (x11 >= x22 || x12 <= x21 || y11 >= y22 || y12 <= y21) {
|
|
3466
4423
|
return [bounds1, bounds2];
|
|
3467
4424
|
}
|
|
@@ -3483,6 +4440,7 @@
|
|
|
3483
4440
|
const l = [];
|
|
3484
4441
|
list.forEach(b => {
|
|
3485
4442
|
let arr;
|
|
4443
|
+
// 相交
|
|
3486
4444
|
if ((arr = unionAABB(baseBound, b)).length > 1) {
|
|
3487
4445
|
l.push(b);
|
|
3488
4446
|
return;
|
|
@@ -3596,8 +4554,10 @@
|
|
|
3596
4554
|
};
|
|
3597
4555
|
}
|
|
3598
4556
|
estimate({ text, fontSize }) {
|
|
3599
|
-
|
|
3600
|
-
let
|
|
4557
|
+
// 假设只有英文和中文字符
|
|
4558
|
+
let eCharLen = 0; // 英文字符
|
|
4559
|
+
let cCharLen = 0; // 中文字符
|
|
4560
|
+
// 判断ascii码,如果是
|
|
3601
4561
|
for (let i = 0; i < text.length; i++) {
|
|
3602
4562
|
text.charCodeAt(i) < 128 ? eCharLen++ : cCharLen++;
|
|
3603
4563
|
}
|
|
@@ -3615,6 +4575,14 @@
|
|
|
3615
4575
|
}
|
|
3616
4576
|
|
|
3617
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
|
+
*/
|
|
3618
4586
|
function lineIntersectPolygon(a1x, a1y, a2x, a2y, points) {
|
|
3619
4587
|
for (let i = 0, p2 = points[points.length - 1]; i < points.length; i++) {
|
|
3620
4588
|
const p = points[i];
|
|
@@ -3625,6 +4593,8 @@
|
|
|
3625
4593
|
}
|
|
3626
4594
|
return false;
|
|
3627
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
|
|
3628
4598
|
function polygonContainPoint(points, x, y) {
|
|
3629
4599
|
let w = 0;
|
|
3630
4600
|
let p = points[0];
|
|
@@ -3636,30 +4606,44 @@
|
|
|
3636
4606
|
w += isPointInLine(p.x, p.y, p2.x, p2.y, x, y);
|
|
3637
4607
|
p = p2;
|
|
3638
4608
|
}
|
|
4609
|
+
// Close polygon
|
|
3639
4610
|
const p0 = points[0];
|
|
3640
4611
|
if (!isAroundEqual(p.x, p0.x) || !isAroundEqual(p.y, p0.y)) {
|
|
3641
4612
|
w += isPointInLine(p.x, p.y, p0.x, p0.y, x, y);
|
|
3642
4613
|
}
|
|
3643
4614
|
return w !== 0;
|
|
3644
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
|
|
3645
4618
|
function isPointInLine(x0, y0, x1, y1, x, y) {
|
|
3646
4619
|
if ((y > y0 && y > y1) || (y < y0 && y < y1)) {
|
|
3647
4620
|
return 0;
|
|
3648
4621
|
}
|
|
4622
|
+
// Ignore horizontal line
|
|
3649
4623
|
if (y1 === y0) {
|
|
3650
4624
|
return 0;
|
|
3651
4625
|
}
|
|
3652
4626
|
const t = (y - y0) / (y1 - y0);
|
|
3653
4627
|
let dir = y1 < y0 ? 1 : -1;
|
|
4628
|
+
// Avoid winding error when intersection point is the connect point of two line of polygon
|
|
3654
4629
|
if (t === 1 || t === 0) {
|
|
3655
4630
|
dir = y1 < y0 ? 0.5 : -0.5;
|
|
3656
4631
|
}
|
|
3657
4632
|
const x_ = t * (x1 - x0) + x0;
|
|
4633
|
+
// If (x, y) on the line, considered as "contain".
|
|
3658
4634
|
return x_ === x ? Infinity : x_ > x ? dir : 0;
|
|
3659
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
|
|
3660
4638
|
function isAroundEqual(a, b) {
|
|
3661
4639
|
return Math.abs(a - b) < EPSILON;
|
|
3662
4640
|
}
|
|
4641
|
+
/**
|
|
4642
|
+
* polygon图元之间是否相交
|
|
4643
|
+
* @param pointsA 图元A的points
|
|
4644
|
+
* @param pointsB 图元B的points
|
|
4645
|
+
* @returns 是否相交
|
|
4646
|
+
*/
|
|
3663
4647
|
function polygonIntersectPolygon(pointsA, pointsB) {
|
|
3664
4648
|
for (let i = 0; i < pointsB.length; i++) {
|
|
3665
4649
|
if (polygonContainPoint(pointsA, pointsB[i].x, pointsB[i].y)) {
|
|
@@ -3716,9 +4700,11 @@
|
|
|
3716
4700
|
let width = 0;
|
|
3717
4701
|
for (const character of string) {
|
|
3718
4702
|
const codePoint = character.codePointAt(0);
|
|
4703
|
+
// Ignore control characters
|
|
3719
4704
|
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
3720
4705
|
continue;
|
|
3721
4706
|
}
|
|
4707
|
+
// Ignore combining characters
|
|
3722
4708
|
if (codePoint >= 0x300 && codePoint <= 0x36f) {
|
|
3723
4709
|
continue;
|
|
3724
4710
|
}
|
|
@@ -3745,6 +4731,7 @@
|
|
|
3745
4731
|
};
|
|
3746
4732
|
const ansiRegex = ({ onlyFirst = false } = {}) => {
|
|
3747
4733
|
const pattern = [
|
|
4734
|
+
// eslint-disable-next-line max-len
|
|
3748
4735
|
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
3749
4736
|
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
|
|
3750
4737
|
].join('|');
|
|
@@ -4001,9 +4988,12 @@
|
|
|
4001
4988
|
return 'N';
|
|
4002
4989
|
};
|
|
4003
4990
|
const emojiRegex = () => {
|
|
4991
|
+
// https://mths.be/emoji
|
|
4992
|
+
// eslint-disable-next-line max-len
|
|
4004
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;
|
|
4005
4994
|
};
|
|
4006
4995
|
|
|
4996
|
+
// FIXME: from VRender
|
|
4007
4997
|
function getContextFont(text, defaultAttr = {}, fontSizeScale) {
|
|
4008
4998
|
if (!fontSizeScale) {
|
|
4009
4999
|
fontSizeScale = 1;
|
|
@@ -4020,16 +5010,25 @@
|
|
|
4020
5010
|
|
|
4021
5011
|
class TextMeasure {
|
|
4022
5012
|
constructor(option, textSpec) {
|
|
5013
|
+
/** 数字大小缓存 */
|
|
4023
5014
|
this._numberCharSize = null;
|
|
5015
|
+
/** 全角字符大小缓存 */
|
|
4024
5016
|
this._fullCharSize = null;
|
|
5017
|
+
/** 字母大小缓存 */
|
|
4025
5018
|
this._letterCharSize = null;
|
|
5019
|
+
/** 特殊字符缓存 */
|
|
4026
5020
|
this._specialCharSizeMap = {};
|
|
5021
|
+
/** 内置测量 canvas */
|
|
4027
5022
|
this._canvas = null;
|
|
4028
5023
|
this._context = null;
|
|
4029
5024
|
this._contextSaved = false;
|
|
5025
|
+
/** 是否不支持 canvas */
|
|
4030
5026
|
this._notSupportCanvas = false;
|
|
5027
|
+
/** 是否不支持 vrender */
|
|
4031
5028
|
this._notSupportVRender = false;
|
|
5029
|
+
/** 文字 spec */
|
|
4032
5030
|
this._userSpec = {};
|
|
5031
|
+
/** 特殊字符集 */
|
|
4033
5032
|
this.specialCharSet = '-/: .,@%\'"~';
|
|
4034
5033
|
this._option = option;
|
|
4035
5034
|
this._userSpec = textSpec !== null && textSpec !== void 0 ? textSpec : {};
|
|
@@ -4041,6 +5040,7 @@
|
|
|
4041
5040
|
? this.fullMeasure.bind(this)
|
|
4042
5041
|
: this.measureWithNaiveCanvas.bind(this);
|
|
4043
5042
|
}
|
|
5043
|
+
/** 初始化测量相关上下文 */
|
|
4044
5044
|
initContext() {
|
|
4045
5045
|
if (this._notSupportCanvas) {
|
|
4046
5046
|
return false;
|
|
@@ -4054,6 +5054,7 @@
|
|
|
4054
5054
|
typeof window.document !== 'undefined' &&
|
|
4055
5055
|
globalThis &&
|
|
4056
5056
|
isValid(globalThis.document)) {
|
|
5057
|
+
// 默认创建方法
|
|
4057
5058
|
this._canvas = globalThis.document.createElement('canvas');
|
|
4058
5059
|
}
|
|
4059
5060
|
}
|
|
@@ -4094,6 +5095,7 @@
|
|
|
4094
5095
|
lineHeight
|
|
4095
5096
|
};
|
|
4096
5097
|
}
|
|
5098
|
+
/** 计算文本宽高 */
|
|
4097
5099
|
measure(text, method) {
|
|
4098
5100
|
switch (method) {
|
|
4099
5101
|
case 'vrender':
|
|
@@ -4108,12 +5110,13 @@
|
|
|
4108
5110
|
return this.quickMeasure(text);
|
|
4109
5111
|
}
|
|
4110
5112
|
}
|
|
5113
|
+
/** 精确计算文本宽高 */
|
|
4111
5114
|
fullMeasure(text) {
|
|
4112
5115
|
if (isNil(text)) {
|
|
4113
5116
|
return { width: 0, height: 0 };
|
|
4114
5117
|
}
|
|
4115
5118
|
if (isNil(this._option.getTextBounds) || !this._notSupportVRender) {
|
|
4116
|
-
return this.measureWithNaiveCanvas(text);
|
|
5119
|
+
return this.measureWithNaiveCanvas(text); // 降级
|
|
4117
5120
|
}
|
|
4118
5121
|
const { fontFamily, fontSize, fontWeight, textAlign, textBaseline, ellipsis, limit, lineHeight } = this.textSpec;
|
|
4119
5122
|
let size;
|
|
@@ -4133,17 +5136,18 @@
|
|
|
4133
5136
|
}
|
|
4134
5137
|
catch (e) {
|
|
4135
5138
|
this._notSupportVRender = true;
|
|
4136
|
-
size = this.measureWithNaiveCanvas(text);
|
|
5139
|
+
size = this.measureWithNaiveCanvas(text); // 降级
|
|
4137
5140
|
}
|
|
4138
5141
|
return size;
|
|
4139
5142
|
}
|
|
5143
|
+
/** 通过原生 canvas 精确计算文本宽度,高度为估算 */
|
|
4140
5144
|
measureWithNaiveCanvas(text) {
|
|
4141
5145
|
return this._measureReduce(text, this._measureWithNaiveCanvas.bind(this));
|
|
4142
5146
|
}
|
|
4143
5147
|
_measureWithNaiveCanvas(text) {
|
|
4144
5148
|
var _a;
|
|
4145
5149
|
if (!this.initContext()) {
|
|
4146
|
-
return this._quickMeasureWithoutCanvas(text);
|
|
5150
|
+
return this._quickMeasureWithoutCanvas(text); // 降级
|
|
4147
5151
|
}
|
|
4148
5152
|
const metrics = this._context.measureText(text);
|
|
4149
5153
|
const { fontSize, lineHeight } = this.textSpec;
|
|
@@ -4154,6 +5158,7 @@
|
|
|
4154
5158
|
fontBoundingBoxDescent: metrics.fontBoundingBoxDescent
|
|
4155
5159
|
};
|
|
4156
5160
|
}
|
|
5161
|
+
/** 快速估算文本宽高 */
|
|
4157
5162
|
quickMeasure(text) {
|
|
4158
5163
|
return this._measureReduce(text, this._quickMeasure.bind(this));
|
|
4159
5164
|
}
|
|
@@ -4164,13 +5169,17 @@
|
|
|
4164
5169
|
};
|
|
4165
5170
|
for (let i = 0; i < text.length; i++) {
|
|
4166
5171
|
const char = text[i];
|
|
5172
|
+
// 先判断是否特殊字符
|
|
4167
5173
|
let size = this._measureSpecialChar(char);
|
|
5174
|
+
// 再判断是否数字
|
|
4168
5175
|
if (isNil(size) && TextMeasure.NUMBERS_CHAR_SET.includes(char)) {
|
|
4169
5176
|
size = this._measureNumberChar();
|
|
4170
5177
|
}
|
|
5178
|
+
// 再判断全角字符
|
|
4171
5179
|
if (isNil(size) && ['F', 'W'].includes(eastAsianCharacterInfo(char))) {
|
|
4172
5180
|
size = this._measureFullSizeChar();
|
|
4173
5181
|
}
|
|
5182
|
+
// 最后按半角字符处理
|
|
4174
5183
|
if (isNil(size)) {
|
|
4175
5184
|
size = this._measureLetterChar();
|
|
4176
5185
|
}
|
|
@@ -4181,6 +5190,7 @@
|
|
|
4181
5190
|
}
|
|
4182
5191
|
return totalSize;
|
|
4183
5192
|
}
|
|
5193
|
+
/** 不使用 canvas 来快速估算文本宽高 */
|
|
4184
5194
|
quickMeasureWithoutCanvas(text) {
|
|
4185
5195
|
return this._measureReduce(text, this._quickMeasureWithoutCanvas.bind(this));
|
|
4186
5196
|
}
|
|
@@ -4193,6 +5203,7 @@
|
|
|
4193
5203
|
const { fontSize, lineHeight } = this.textSpec;
|
|
4194
5204
|
for (let i = 0; i < text.length; i++) {
|
|
4195
5205
|
const char = text[i];
|
|
5206
|
+
// 判断全角字符
|
|
4196
5207
|
const size = ['F', 'W'].includes(eastAsianCharacterInfo(char)) ? 1 : 0.53;
|
|
4197
5208
|
totalSize.width += size * fontSize;
|
|
4198
5209
|
}
|
|
@@ -4216,11 +5227,12 @@
|
|
|
4216
5227
|
}
|
|
4217
5228
|
return {
|
|
4218
5229
|
width: textArr.reduce((maxWidth, cur) => Math.max(maxWidth, processor(cur).width), 0),
|
|
4219
|
-
height: textArr.length * (((_a = lineHeight) !== null && _a !== void 0 ? _a : fontSize) + 1) + 1
|
|
5230
|
+
height: textArr.length * (((_a = lineHeight) !== null && _a !== void 0 ? _a : fontSize) + 1) + 1 // 经验值
|
|
4220
5231
|
};
|
|
4221
5232
|
}
|
|
4222
5233
|
return processor(text.toString());
|
|
4223
5234
|
}
|
|
5235
|
+
/** 测量一个数字 */
|
|
4224
5236
|
_measureNumberChar() {
|
|
4225
5237
|
if (isNil(this._numberCharSize)) {
|
|
4226
5238
|
const numberBounds = this._standardMethod(TextMeasure.NUMBERS_CHAR_SET);
|
|
@@ -4233,12 +5245,14 @@
|
|
|
4233
5245
|
}
|
|
4234
5246
|
return this._numberCharSize;
|
|
4235
5247
|
}
|
|
5248
|
+
/** 测量一个全角字符 */
|
|
4236
5249
|
_measureFullSizeChar() {
|
|
4237
5250
|
if (isNil(this._fullCharSize)) {
|
|
4238
5251
|
this._fullCharSize = this._standardMethod(TextMeasure.FULL_SIZE_CHAR);
|
|
4239
5252
|
}
|
|
4240
5253
|
return this._fullCharSize;
|
|
4241
5254
|
}
|
|
5255
|
+
/** 测量一个英文字符 */
|
|
4242
5256
|
_measureLetterChar() {
|
|
4243
5257
|
if (isNil(this._letterCharSize)) {
|
|
4244
5258
|
const alphabetBounds = this._standardMethod(TextMeasure.ALPHABET_CHAR_SET);
|
|
@@ -4251,6 +5265,7 @@
|
|
|
4251
5265
|
}
|
|
4252
5266
|
return this._letterCharSize;
|
|
4253
5267
|
}
|
|
5268
|
+
/** 测量一个特殊字符 */
|
|
4254
5269
|
_measureSpecialChar(char) {
|
|
4255
5270
|
if (isValid(this._specialCharSizeMap[char])) {
|
|
4256
5271
|
return this._specialCharSizeMap[char];
|
|
@@ -4274,8 +5289,11 @@
|
|
|
4274
5289
|
}
|
|
4275
5290
|
}
|
|
4276
5291
|
}
|
|
5292
|
+
/** 英文字母集 */
|
|
4277
5293
|
TextMeasure.ALPHABET_CHAR_SET = 'abcdefghijklmnopqrstuvwxyz';
|
|
5294
|
+
/** 数字集 */
|
|
4278
5295
|
TextMeasure.NUMBERS_CHAR_SET = '0123456789';
|
|
5296
|
+
/** 全角字符 */
|
|
4279
5297
|
TextMeasure.FULL_SIZE_CHAR = '字';
|
|
4280
5298
|
|
|
4281
5299
|
const calculateAnchorOfBounds = (bounds, anchorType) => {
|
|
@@ -4331,26 +5349,28 @@
|
|
|
4331
5349
|
{ x: Math.cos(a.angle), y: Math.sin(a.angle) },
|
|
4332
5350
|
{ x: -Math.sin(a.angle), y: Math.cos(a.angle) },
|
|
4333
5351
|
{ x: Math.cos(b.angle), y: Math.sin(a.angle) },
|
|
4334
|
-
{ 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
|
|
4335
5353
|
];
|
|
5354
|
+
// calculate the projection range of a rectangle on a given axis
|
|
4336
5355
|
function getProjectionRange(obb, axisX, axisY) {
|
|
4337
5356
|
const corners = obb.getRotatedCorners();
|
|
4338
5357
|
const projections = corners.map(p => p.x * axisX + p.y * axisY);
|
|
4339
5358
|
return { min: Math.min(...projections), max: Math.max(...projections) };
|
|
4340
5359
|
}
|
|
5360
|
+
// Calculate distances for all axes
|
|
4341
5361
|
let maxDistance = 0;
|
|
4342
5362
|
for (const axis of axes) {
|
|
4343
5363
|
const rangeA = getProjectionRange(a, axis.x, axis.y);
|
|
4344
5364
|
const rangeB = getProjectionRange(b, axis.x, axis.y);
|
|
4345
5365
|
let distance;
|
|
4346
5366
|
if (rangeA.max < rangeB.min) {
|
|
4347
|
-
distance = rangeB.min - rangeA.max;
|
|
5367
|
+
distance = rangeB.min - rangeA.max; // B is to the right of A
|
|
4348
5368
|
}
|
|
4349
5369
|
else if (rangeB.max < rangeA.min) {
|
|
4350
|
-
distance = rangeA.min - rangeB.max;
|
|
5370
|
+
distance = rangeA.min - rangeB.max; // A is to the right of B
|
|
4351
5371
|
}
|
|
4352
5372
|
else {
|
|
4353
|
-
distance = 0;
|
|
5373
|
+
distance = 0; // Overlapping
|
|
4354
5374
|
}
|
|
4355
5375
|
maxDistance = Math.max(maxDistance, distance);
|
|
4356
5376
|
}
|
|
@@ -4359,6 +5379,7 @@
|
|
|
4359
5379
|
|
|
4360
5380
|
function transformBoundsWithMatrix(out, bounds, matrix) {
|
|
4361
5381
|
const { x1, y1, x2, y2 } = bounds;
|
|
5382
|
+
// 如果没有旋转和缩放,那就直接translate
|
|
4362
5383
|
if (matrix.onlyTranslate()) {
|
|
4363
5384
|
if (out !== bounds) {
|
|
4364
5385
|
out.setValue(bounds.x1, bounds.y1, bounds.x2, bounds.y2);
|
|
@@ -4373,6 +5394,16 @@
|
|
|
4373
5394
|
out.add(matrix.a * x1 + matrix.c * y2 + matrix.e, matrix.b * x1 + matrix.d * y2 + matrix.f);
|
|
4374
5395
|
return bounds;
|
|
4375
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
|
+
*/
|
|
4376
5407
|
function transformBounds(bounds, x, y, scaleX, scaleY, angle, rotateCenter) {
|
|
4377
5408
|
if (abs(scaleX) <= epsilon || abs(scaleY) <= epsilon) {
|
|
4378
5409
|
return;
|
|
@@ -4390,6 +5421,37 @@
|
|
|
4390
5421
|
}
|
|
4391
5422
|
bounds.translate(x, y);
|
|
4392
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
|
|
4393
5455
|
class Bounds {
|
|
4394
5456
|
constructor(bounds) {
|
|
4395
5457
|
if (bounds) {
|
|
@@ -4496,6 +5558,11 @@
|
|
|
4496
5558
|
const p = this.scalePoints(sx, sy, x, y);
|
|
4497
5559
|
return this.clear().add(p[0], p[1]).add(p[2], p[3]);
|
|
4498
5560
|
}
|
|
5561
|
+
/**
|
|
5562
|
+
* 并集
|
|
5563
|
+
* @param b
|
|
5564
|
+
* @returns
|
|
5565
|
+
*/
|
|
4499
5566
|
union(b) {
|
|
4500
5567
|
if (b.x1 < this.x1) {
|
|
4501
5568
|
this.x1 = b.x1;
|
|
@@ -4511,6 +5578,11 @@
|
|
|
4511
5578
|
}
|
|
4512
5579
|
return this;
|
|
4513
5580
|
}
|
|
5581
|
+
/**
|
|
5582
|
+
* 交集
|
|
5583
|
+
* @param b
|
|
5584
|
+
* @returns
|
|
5585
|
+
*/
|
|
4514
5586
|
intersect(b) {
|
|
4515
5587
|
if (b.x1 > this.x1) {
|
|
4516
5588
|
this.x1 = b.x1;
|
|
@@ -4526,15 +5598,36 @@
|
|
|
4526
5598
|
}
|
|
4527
5599
|
return this;
|
|
4528
5600
|
}
|
|
5601
|
+
/**
|
|
5602
|
+
* 是否包含b
|
|
5603
|
+
* @param b
|
|
5604
|
+
* @returns
|
|
5605
|
+
*/
|
|
4529
5606
|
encloses(b) {
|
|
4530
5607
|
return b && this.x1 <= b.x1 && this.x2 >= b.x2 && this.y1 <= b.y1 && this.y2 >= b.y2;
|
|
4531
5608
|
}
|
|
5609
|
+
/**
|
|
5610
|
+
* 是否共边
|
|
5611
|
+
* @param b
|
|
5612
|
+
* @returns
|
|
5613
|
+
*/
|
|
4532
5614
|
alignsWith(b) {
|
|
4533
5615
|
return b && (this.x1 === b.x1 || this.x2 === b.x2 || this.y1 === b.y1 || this.y2 === b.y2);
|
|
4534
5616
|
}
|
|
5617
|
+
/**
|
|
5618
|
+
* 是否相交
|
|
5619
|
+
* @param b
|
|
5620
|
+
* @returns
|
|
5621
|
+
*/
|
|
4535
5622
|
intersects(b) {
|
|
4536
5623
|
return b && !(this.x2 < b.x1 || this.x1 > b.x2 || this.y2 < b.y1 || this.y1 > b.y2);
|
|
4537
5624
|
}
|
|
5625
|
+
/**
|
|
5626
|
+
* 是否包含
|
|
5627
|
+
* @param x
|
|
5628
|
+
* @param y
|
|
5629
|
+
* @returns
|
|
5630
|
+
*/
|
|
4538
5631
|
contains(x = 0, y = 0) {
|
|
4539
5632
|
return !(x < this.x1 || x > this.x2 || y < this.y1 || y > this.y2);
|
|
4540
5633
|
}
|
|
@@ -4640,6 +5733,7 @@
|
|
|
4640
5733
|
this.f = f;
|
|
4641
5734
|
}
|
|
4642
5735
|
equalToMatrix(m2) {
|
|
5736
|
+
// ef -> ad -> bc
|
|
4643
5737
|
return !(this.e !== m2.e ||
|
|
4644
5738
|
this.f !== m2.f ||
|
|
4645
5739
|
this.a !== m2.a ||
|
|
@@ -4648,6 +5742,7 @@
|
|
|
4648
5742
|
this.c !== m2.c);
|
|
4649
5743
|
}
|
|
4650
5744
|
equalTo(a, b, c, d, e, f) {
|
|
5745
|
+
// ef -> ad -> bc
|
|
4651
5746
|
return !(this.e !== e || this.f !== f || this.a !== a || this.d !== d || this.b !== b || this.c !== c);
|
|
4652
5747
|
}
|
|
4653
5748
|
setValue(a, b, c, d, e, f) {
|
|
@@ -4668,6 +5763,9 @@
|
|
|
4668
5763
|
this.f = 0;
|
|
4669
5764
|
return this;
|
|
4670
5765
|
}
|
|
5766
|
+
/**
|
|
5767
|
+
* 获取当前矩阵的逆矩阵
|
|
5768
|
+
*/
|
|
4671
5769
|
getInverse() {
|
|
4672
5770
|
const a = this.a;
|
|
4673
5771
|
const b = this.b;
|
|
@@ -4699,6 +5797,17 @@
|
|
|
4699
5797
|
return this;
|
|
4700
5798
|
}
|
|
4701
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
|
+
*/
|
|
4702
5811
|
const cos = Math.cos(rad);
|
|
4703
5812
|
const sin = Math.sin(rad);
|
|
4704
5813
|
const rotateM13 = (1 - cos) * cx + sin * cy;
|
|
@@ -4741,6 +5850,15 @@
|
|
|
4741
5850
|
return this;
|
|
4742
5851
|
}
|
|
4743
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
|
+
*/
|
|
4744
5862
|
const { a, b, c, d, e, f } = this;
|
|
4745
5863
|
this.a = b;
|
|
4746
5864
|
this.b = a;
|
|
@@ -4750,6 +5868,10 @@
|
|
|
4750
5868
|
this.f = e;
|
|
4751
5869
|
return this;
|
|
4752
5870
|
}
|
|
5871
|
+
/**
|
|
5872
|
+
* 矩阵相乘
|
|
5873
|
+
* @param matrix
|
|
5874
|
+
*/
|
|
4753
5875
|
multiply(a2, b2, c2, d2, e2, f2) {
|
|
4754
5876
|
const a1 = this.a;
|
|
4755
5877
|
const b1 = this.b;
|
|
@@ -4771,6 +5893,11 @@
|
|
|
4771
5893
|
this.f = dy;
|
|
4772
5894
|
return this;
|
|
4773
5895
|
}
|
|
5896
|
+
/**
|
|
5897
|
+
* 插值计算
|
|
5898
|
+
* @param m2
|
|
5899
|
+
* @param t
|
|
5900
|
+
*/
|
|
4774
5901
|
interpolate(m2, t) {
|
|
4775
5902
|
const m = new Matrix();
|
|
4776
5903
|
m.a = this.a + (m2.a - this.a) * t;
|
|
@@ -4781,6 +5908,11 @@
|
|
|
4781
5908
|
m.f = this.f + (m2.f - this.f) * t;
|
|
4782
5909
|
return m;
|
|
4783
5910
|
}
|
|
5911
|
+
/**
|
|
5912
|
+
* 将point转到当前矩阵的坐标空间中
|
|
5913
|
+
* @param source
|
|
5914
|
+
* @param target
|
|
5915
|
+
*/
|
|
4784
5916
|
transformPoint(source, target) {
|
|
4785
5917
|
const { a, b, c, d, e, f } = this;
|
|
4786
5918
|
const dt = a * d - b * c;
|
|
@@ -4794,6 +5926,7 @@
|
|
|
4794
5926
|
target.x = x * nextA + y * nextC + nextE;
|
|
4795
5927
|
target.y = x * nextB + y * nextD + nextF;
|
|
4796
5928
|
}
|
|
5929
|
+
// 只有translate
|
|
4797
5930
|
onlyTranslate(scale = 1) {
|
|
4798
5931
|
return this.a === scale && this.b === 0 && this.c === 0 && this.d === scale;
|
|
4799
5932
|
}
|
|
@@ -4817,6 +5950,7 @@
|
|
|
4817
5950
|
skewX: 0,
|
|
4818
5951
|
skewY: 0
|
|
4819
5952
|
};
|
|
5953
|
+
// Apply the QR-like decomposition.
|
|
4820
5954
|
if (a !== 0 || b !== 0) {
|
|
4821
5955
|
const r = Math.sqrt(a * a + b * b);
|
|
4822
5956
|
result.rotateDeg = b > 0 ? Math.acos(a / r) : -Math.acos(a / r);
|
|
@@ -4838,6 +5972,17 @@
|
|
|
4838
5972
|
return result;
|
|
4839
5973
|
}
|
|
4840
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
|
+
*/
|
|
4841
5986
|
function normalTransform(out, origin, x, y, scaleX, scaleY, angle, rotateCenter) {
|
|
4842
5987
|
const oa = origin.a;
|
|
4843
5988
|
const ob = origin.b;
|
|
@@ -4871,18 +6016,20 @@
|
|
|
4871
6016
|
out.f = of + ob * rotateCenterX + od * rotateCenterY - b1 * offsetX - d1 * offsetY;
|
|
4872
6017
|
}
|
|
4873
6018
|
|
|
6019
|
+
// LRU清除
|
|
4874
6020
|
class LRU {
|
|
4875
6021
|
constructor() {
|
|
4876
6022
|
this.CLEAN_THRESHOLD = 1e3;
|
|
4877
|
-
this.L_TIME = 1000;
|
|
4878
|
-
this.R_COUNT = 1;
|
|
4879
|
-
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最大长度
|
|
4880
6026
|
}
|
|
4881
6027
|
clearCache(cache, params) {
|
|
4882
6028
|
const { CLEAN_THRESHOLD = this.CLEAN_THRESHOLD, L_TIME = this.L_TIME, R_COUNT = this.R_COUNT } = params;
|
|
4883
6029
|
if (cache.size < CLEAN_THRESHOLD) {
|
|
4884
6030
|
return 0;
|
|
4885
6031
|
}
|
|
6032
|
+
// 开始清理,清理最近1000ms内使用次数为0的缓存
|
|
4886
6033
|
let clearNum = 0;
|
|
4887
6034
|
const clear = (key) => {
|
|
4888
6035
|
clearNum++;
|
|
@@ -4892,7 +6039,7 @@
|
|
|
4892
6039
|
cache.forEach((item, key) => {
|
|
4893
6040
|
if (item.timestamp.length < R_COUNT) {
|
|
4894
6041
|
return clear(key);
|
|
4895
|
-
}
|
|
6042
|
+
} // 最近使用次数小于R_COUNT,清除
|
|
4896
6043
|
let useCount = 0;
|
|
4897
6044
|
while (now - item.timestamp[item.timestamp.length - 1 - useCount] < L_TIME) {
|
|
4898
6045
|
useCount++;
|
|
@@ -4902,7 +6049,8 @@
|
|
|
4902
6049
|
}
|
|
4903
6050
|
if (useCount < R_COUNT) {
|
|
4904
6051
|
return clear(key);
|
|
4905
|
-
}
|
|
6052
|
+
} // 最近L_TIME以内使用次数小于R_COUNT,清除
|
|
6053
|
+
// 清除L_TIME以外的key,避免数组过长
|
|
4906
6054
|
while (now - item.timestamp[0] > L_TIME) {
|
|
4907
6055
|
item.timestamp.shift();
|
|
4908
6056
|
}
|
|
@@ -4913,6 +6061,7 @@
|
|
|
4913
6061
|
addLimitedTimestamp(cacheItem, t, params) {
|
|
4914
6062
|
const { R_TIMESTAMP_MAX_SIZE = this.R_TIMESTAMP_MAX_SIZE } = params;
|
|
4915
6063
|
if (cacheItem.timestamp.length > R_TIMESTAMP_MAX_SIZE) {
|
|
6064
|
+
// cacheItem.timestamp[cacheItem.timestamp.length-1] =
|
|
4916
6065
|
cacheItem.timestamp.shift();
|
|
4917
6066
|
}
|
|
4918
6067
|
cacheItem.timestamp.push(t);
|
|
@@ -4984,6 +6133,7 @@
|
|
|
4984
6133
|
r /= 255;
|
|
4985
6134
|
g /= 255;
|
|
4986
6135
|
b /= 255;
|
|
6136
|
+
// Find greatest and smallest channel values
|
|
4987
6137
|
const cMin = Math.min(r, g, b);
|
|
4988
6138
|
const cMax = Math.max(r, g, b);
|
|
4989
6139
|
const delta = cMax - cMin;
|
|
@@ -4993,12 +6143,15 @@
|
|
|
4993
6143
|
if (delta === 0) {
|
|
4994
6144
|
h = 0;
|
|
4995
6145
|
}
|
|
6146
|
+
// Red is max
|
|
4996
6147
|
else if (cMax === r) {
|
|
4997
6148
|
h = ((g - b) / delta) % 6;
|
|
4998
6149
|
}
|
|
6150
|
+
// Green is max
|
|
4999
6151
|
else if (cMax === g) {
|
|
5000
6152
|
h = (b - r) / delta + 2;
|
|
5001
6153
|
}
|
|
6154
|
+
// Blue is max
|
|
5002
6155
|
else {
|
|
5003
6156
|
h = (r - g) / delta + 4;
|
|
5004
6157
|
}
|
|
@@ -5007,7 +6160,9 @@
|
|
|
5007
6160
|
h += 360;
|
|
5008
6161
|
}
|
|
5009
6162
|
l = (cMax + cMin) / 2;
|
|
6163
|
+
// Calculate saturation
|
|
5010
6164
|
s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
|
|
6165
|
+
// Multiply l and s by 100
|
|
5011
6166
|
s = +(s * 100).toFixed(1);
|
|
5012
6167
|
l = +(l * 100).toFixed(1);
|
|
5013
6168
|
return {
|
|
@@ -5017,6 +6172,34 @@
|
|
|
5017
6172
|
};
|
|
5018
6173
|
}
|
|
5019
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
|
+
*/
|
|
5020
6203
|
const REG_HEX = /^#([0-9a-f]{3,8})$/;
|
|
5021
6204
|
const DEFAULT_COLORS_OPACITY = {
|
|
5022
6205
|
transparent: 0xffffff00
|
|
@@ -5182,6 +6365,7 @@
|
|
|
5182
6365
|
else if (isArray(value)) {
|
|
5183
6366
|
return new RGB(value[0], value[1], value[2]);
|
|
5184
6367
|
}
|
|
6368
|
+
// default return
|
|
5185
6369
|
return new RGB(255, 255, 255);
|
|
5186
6370
|
}
|
|
5187
6371
|
function rgba(value) {
|
|
@@ -5191,6 +6375,7 @@
|
|
|
5191
6375
|
else if (isArray(value)) {
|
|
5192
6376
|
return new RGB(value[0], value[1], value[2], value[3]);
|
|
5193
6377
|
}
|
|
6378
|
+
// default return
|
|
5194
6379
|
return new RGB(255, 255, 255, 1);
|
|
5195
6380
|
}
|
|
5196
6381
|
function SRGBToLinear(c) {
|
|
@@ -5204,12 +6389,15 @@
|
|
|
5204
6389
|
if (forceHex || isHex) {
|
|
5205
6390
|
const hex = parseInt(isHex[1], 16);
|
|
5206
6391
|
const hexLength = isHex[1].length;
|
|
6392
|
+
// #fff
|
|
5207
6393
|
if (hexLength === 3) {
|
|
5208
6394
|
return new RGB(((hex >> 8) & 0xf) + (((hex >> 8) & 0xf) << 4), ((hex >> 4) & 0xf) + (((hex >> 4) & 0xf) << 4), (hex & 0xf) + ((hex & 0xf) << 4), 1);
|
|
5209
6395
|
}
|
|
6396
|
+
// #ffffff
|
|
5210
6397
|
if (hexLength === 6) {
|
|
5211
6398
|
return rgb(hex);
|
|
5212
6399
|
}
|
|
6400
|
+
// #ffffffaa
|
|
5213
6401
|
else if (hexLength === 8) {
|
|
5214
6402
|
return new RGB((hex >> 24) & 0xff, (hex >> 16) & 0xff, (hex >> 8) & 0xff, (hex & 0xff) / 0xff);
|
|
5215
6403
|
}
|
|
@@ -5250,21 +6438,26 @@
|
|
|
5250
6438
|
}
|
|
5251
6439
|
}
|
|
5252
6440
|
static parseColorString(value) {
|
|
6441
|
+
// case0: transparent
|
|
5253
6442
|
if (isValid(DEFAULT_COLORS_OPACITY[value])) {
|
|
5254
6443
|
return rgba(DEFAULT_COLORS_OPACITY[value]);
|
|
5255
6444
|
}
|
|
6445
|
+
// case1: 简写 ['blue','red']
|
|
5256
6446
|
if (isValid(DEFAULT_COLORS[value])) {
|
|
5257
6447
|
return rgb(DEFAULT_COLORS[value]);
|
|
5258
6448
|
}
|
|
5259
6449
|
const formatValue = `${value}`.trim().toLowerCase();
|
|
6450
|
+
// case2: 16进制
|
|
5260
6451
|
const hexRes = setHex(formatValue);
|
|
5261
6452
|
if (hexRes !== undefined) {
|
|
5262
6453
|
return hexRes;
|
|
5263
6454
|
}
|
|
6455
|
+
// case3 : rgb
|
|
5264
6456
|
if (/^(rgb|RGB|rgba|RGBA)/.test(formatValue)) {
|
|
5265
6457
|
const aColor = formatValue.replace(/(?:\(|\)|rgba|RGBA|rgb|RGB)*/g, '').split(',');
|
|
5266
6458
|
return new RGB(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10), parseFloat(aColor[3]));
|
|
5267
6459
|
}
|
|
6460
|
+
// case4: hsl
|
|
5268
6461
|
if (/^(hsl|HSL|hsla|HSLA)/.test(formatValue)) {
|
|
5269
6462
|
const aColor = formatValue.replace(/(?:\(|\)|hsla|HSLA|hsl|HSL)*/g, '').split(',');
|
|
5270
6463
|
const rgb = hslToRgb(parseInt(aColor[0], 10), parseInt(aColor[1], 10), parseInt(aColor[2], 10));
|
|
@@ -5274,10 +6467,12 @@
|
|
|
5274
6467
|
}
|
|
5275
6468
|
constructor(value) {
|
|
5276
6469
|
const color = Color.parseColorString(value);
|
|
6470
|
+
// case0: transparent
|
|
5277
6471
|
if (color) {
|
|
5278
6472
|
this.color = color;
|
|
5279
6473
|
}
|
|
5280
6474
|
else {
|
|
6475
|
+
// default: 白色
|
|
5281
6476
|
console.warn(`Warn: 传入${value}无法解析为Color`);
|
|
5282
6477
|
this.color = new RGB(255, 255, 255);
|
|
5283
6478
|
}
|
|
@@ -5321,6 +6516,7 @@
|
|
|
5321
6516
|
this.color.b = Math.max(0, Math.min(255, Math.floor(b * color.color.b)));
|
|
5322
6517
|
return this;
|
|
5323
6518
|
}
|
|
6519
|
+
// todo: 这里亮度计算都没考虑alpha通道
|
|
5324
6520
|
getHSVBrightness() {
|
|
5325
6521
|
return Math.max(this.color.r, this.color.g, this.color.b) / 255;
|
|
5326
6522
|
}
|
|
@@ -5348,11 +6544,14 @@
|
|
|
5348
6544
|
return res !== null && res !== void 0 ? res : this;
|
|
5349
6545
|
}
|
|
5350
6546
|
setColorName(name) {
|
|
6547
|
+
// color keywords
|
|
5351
6548
|
const hex = DEFAULT_COLORS[name.toLowerCase()];
|
|
5352
6549
|
if (typeof hex !== 'undefined') {
|
|
6550
|
+
// red
|
|
5353
6551
|
this.setHex(hex);
|
|
5354
6552
|
}
|
|
5355
6553
|
else {
|
|
6554
|
+
// unknown color
|
|
5356
6555
|
console.warn('THREE.Color: Unknown color ' + name);
|
|
5357
6556
|
}
|
|
5358
6557
|
return this;
|
|
@@ -5376,6 +6575,9 @@
|
|
|
5376
6575
|
getLuminance3() {
|
|
5377
6576
|
return (0.299 * this.color.r + 0.587 * this.color.g + 0.114 * this.color.b) / 255;
|
|
5378
6577
|
}
|
|
6578
|
+
/**
|
|
6579
|
+
* https://www.w3.org/TR/WCAG21/#use-of-color
|
|
6580
|
+
*/
|
|
5379
6581
|
getLuminanceWCAG() {
|
|
5380
6582
|
const RsRGB = this.color.r / 255;
|
|
5381
6583
|
const GsRGB = this.color.g / 255;
|
|
@@ -5461,6 +6663,7 @@
|
|
|
5461
6663
|
this.opacity = 1;
|
|
5462
6664
|
}
|
|
5463
6665
|
}
|
|
6666
|
+
// 转为16进制
|
|
5464
6667
|
formatHex() {
|
|
5465
6668
|
return `#${hex(this.r) + hex(this.g) + hex(this.b) + (this.opacity === 1 ? '' : hex(this.opacity * 255))}`;
|
|
5466
6669
|
}
|
|
@@ -5473,11 +6676,17 @@
|
|
|
5473
6676
|
const { h, s, l } = rgbToHsl(this.r, this.g, this.b);
|
|
5474
6677
|
return `${opacity === 1 ? 'hsl(' : 'hsla('}${h},${s}%,${l}%${opacity === 1 ? ')' : `,${opacity})`}`;
|
|
5475
6678
|
}
|
|
6679
|
+
// 转为16进制
|
|
5476
6680
|
toString() {
|
|
5477
6681
|
return this.formatHex();
|
|
5478
6682
|
}
|
|
5479
6683
|
}
|
|
5480
6684
|
|
|
6685
|
+
/**
|
|
6686
|
+
* 将 hex 格式颜色转换为 rgb 格式
|
|
6687
|
+
* @param str hex 格式的颜色值
|
|
6688
|
+
* @returns rgb 格式
|
|
6689
|
+
*/
|
|
5481
6690
|
function hexToRgb(str) {
|
|
5482
6691
|
let r = '';
|
|
5483
6692
|
let g = '';
|
|
@@ -5539,6 +6748,11 @@
|
|
|
5539
6748
|
rgbToHsl: rgbToHsl
|
|
5540
6749
|
});
|
|
5541
6750
|
|
|
6751
|
+
/**
|
|
6752
|
+
* 将 padding 转换为通用的格式([上,右,下,左])
|
|
6753
|
+
* @param padding
|
|
6754
|
+
* @return [ top, right, bottom, left ]
|
|
6755
|
+
*/
|
|
5542
6756
|
function normalizePadding(padding) {
|
|
5543
6757
|
if (isValidNumber(padding)) {
|
|
5544
6758
|
return [padding, padding, padding, padding];
|
|
@@ -5610,6 +6824,12 @@
|
|
|
5610
6824
|
function millisecondsSetterName(isUTC) {
|
|
5611
6825
|
return isUTC ? 'setUTCMilliseconds' : 'setMilliseconds';
|
|
5612
6826
|
}
|
|
6827
|
+
/**
|
|
6828
|
+
* 根据时间推断格式化字符串
|
|
6829
|
+
* @param value
|
|
6830
|
+
* @param isUTC
|
|
6831
|
+
* @returns
|
|
6832
|
+
*/
|
|
5613
6833
|
function getFormatFromValue(value, isUTC) {
|
|
5614
6834
|
const date = toDate(value);
|
|
5615
6835
|
const M = date[monthGetterName(isUTC)]() + 1;
|
|
@@ -5657,15 +6877,20 @@
|
|
|
5657
6877
|
const m = date[minutesGetterName(isUTC)]();
|
|
5658
6878
|
const s = date[secondsGetterName(isUTC)]();
|
|
5659
6879
|
const S = date[millisecondsGetterName(isUTC)]();
|
|
6880
|
+
// 月份,周等需要管理管理locale的时间格式化暂时不支持
|
|
5660
6881
|
return ((template || '')
|
|
5661
6882
|
.replace(/YYYY/g, pad(y + '', 4, '0', 'left'))
|
|
5662
6883
|
.replace(/yyyy/g, y + '')
|
|
5663
6884
|
.replace(/yy/g, (y % 100) + '')
|
|
5664
6885
|
.replace(/Q/g, q + '')
|
|
6886
|
+
// .replace(/{MMMM}/g, month[M - 1])
|
|
6887
|
+
// .replace(/{MMM}/g, monthAbbr[M - 1])
|
|
5665
6888
|
.replace(/MM/g, pad(M, 2, '0', 'left'))
|
|
5666
6889
|
.replace(/M/g, M + '')
|
|
5667
6890
|
.replace(/dd/g, pad(d, 2, '0', 'left'))
|
|
5668
6891
|
.replace(/d/g, d + '')
|
|
6892
|
+
// .replace(/{eeee}/g, dayOfWeek[e])
|
|
6893
|
+
// .replace(/{ee}/g, dayOfWeekAbbr[e])
|
|
5669
6894
|
.replace(/e/g, e + '')
|
|
5670
6895
|
.replace(/HH/g, pad(H, 2, '0', 'left'))
|
|
5671
6896
|
.replace(/H/g, H + '')
|
|
@@ -5967,9 +7192,14 @@
|
|
|
5967
7192
|
};
|
|
5968
7193
|
}
|
|
5969
7194
|
const style = getComputedStyle(el);
|
|
7195
|
+
// clientWidth/clientHeight: 默认整数,会向上取整,导致canvas > container
|
|
7196
|
+
// getBoundingClientRect:默认小数,但是在container上有css类似 transform: scale(0.5)时,获取结果不对
|
|
7197
|
+
// getComputedStyle:默认小数,获取最终结果,但是会包含padding;
|
|
5970
7198
|
if (/^(\d*\.?\d+)(px)$/.exec(style.width)) {
|
|
7199
|
+
// 当dom元素的 display: 'none' 的时候,获取到的宽高会变成原始的样式配置,而不是计算后的像素值
|
|
5971
7200
|
const computedWidth = parseFloat(style.width) - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight) || el.clientWidth - 1;
|
|
5972
7201
|
const computedHeight = parseFloat(style.height) - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom) || el.clientHeight - 1;
|
|
7202
|
+
// 理论上不用向下取整,目前没加。
|
|
5973
7203
|
return {
|
|
5974
7204
|
width: computedWidth <= 0 ? defaultWidth : computedWidth,
|
|
5975
7205
|
height: computedHeight <= 0 ? defaultHeight : computedHeight
|
|
@@ -5977,10 +7207,20 @@
|
|
|
5977
7207
|
}
|
|
5978
7208
|
return { width: defaultWidth, height: defaultHeight };
|
|
5979
7209
|
}
|
|
7210
|
+
/**
|
|
7211
|
+
* 获取元素的绝对位置坐标(相对于页面左上角)
|
|
7212
|
+
* @param element
|
|
7213
|
+
* @returns
|
|
7214
|
+
*/
|
|
5980
7215
|
function getElementAbsolutePosition(element) {
|
|
5981
7216
|
const { x, y } = element.getBoundingClientRect();
|
|
5982
7217
|
return { x, y };
|
|
5983
7218
|
}
|
|
7219
|
+
/**
|
|
7220
|
+
* 获取元素的相对位置坐标(相对于其他dom元素)
|
|
7221
|
+
* @param element
|
|
7222
|
+
* @returns
|
|
7223
|
+
*/
|
|
5984
7224
|
function getElementRelativePosition(element, base) {
|
|
5985
7225
|
const posElement = getElementAbsolutePosition(element);
|
|
5986
7226
|
const posBase = getElementAbsolutePosition(base);
|
|
@@ -6012,12 +7252,23 @@
|
|
|
6012
7252
|
const getScaleY = (element) => {
|
|
6013
7253
|
return element.getBoundingClientRect().height / element.offsetHeight;
|
|
6014
7254
|
};
|
|
7255
|
+
/**
|
|
7256
|
+
* 获取目标元素的缩放因数
|
|
7257
|
+
* @param element 目标 dom 元素
|
|
7258
|
+
* @returns
|
|
7259
|
+
*/
|
|
6015
7260
|
const getScale = (element) => {
|
|
6016
7261
|
if (element.offsetWidth > 0) {
|
|
6017
7262
|
return getScaleX(element);
|
|
6018
7263
|
}
|
|
6019
7264
|
return getScaleY(element);
|
|
6020
7265
|
};
|
|
7266
|
+
/**
|
|
7267
|
+
* 判断是否是元素的父元素
|
|
7268
|
+
* @param element 从其父元素开始查找的 DOM 元素
|
|
7269
|
+
* @param target 要查找的目标父元素
|
|
7270
|
+
* @returns boolean, true 代表查找到,false 表示未找到
|
|
7271
|
+
*/
|
|
6021
7272
|
function hasParentElement(element, target) {
|
|
6022
7273
|
let parent = element.parentNode;
|
|
6023
7274
|
while (parent !== null) {
|
|
@@ -6028,6 +7279,11 @@
|
|
|
6028
7279
|
}
|
|
6029
7280
|
return false;
|
|
6030
7281
|
}
|
|
7282
|
+
/**
|
|
7283
|
+
* 将style字符串转换成对象形式
|
|
7284
|
+
* @param style字符串 例如:"color:red;line-height:20px;"
|
|
7285
|
+
* @returns style对象,如 { color: 'red', 'line-height': '20px' }
|
|
7286
|
+
*/
|
|
6031
7287
|
const styleStringToObject = (styleStr = '') => {
|
|
6032
7288
|
const res = {};
|
|
6033
7289
|
styleStr.split(';').forEach(item => {
|
|
@@ -6044,9 +7300,19 @@
|
|
|
6044
7300
|
});
|
|
6045
7301
|
return res;
|
|
6046
7302
|
};
|
|
7303
|
+
/**
|
|
7304
|
+
* 将小驼峰转换成中划线连接的字符串
|
|
7305
|
+
* @param str 如:'lineHeight' => 'line-height'
|
|
7306
|
+
* @returns
|
|
7307
|
+
*/
|
|
6047
7308
|
const lowerCamelCaseToMiddle = (str) => {
|
|
6048
7309
|
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
6049
7310
|
};
|
|
7311
|
+
/**
|
|
7312
|
+
* 将中划线连接的字符串转换成驼峰字符串
|
|
7313
|
+
* @param str 如:'line-height' => 'lineHeight'
|
|
7314
|
+
* @returns
|
|
7315
|
+
*/
|
|
6050
7316
|
function toCamelCase(str) {
|
|
6051
7317
|
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
6052
7318
|
}
|
|
@@ -6055,6 +7321,7 @@
|
|
|
6055
7321
|
return obj instanceof Element;
|
|
6056
7322
|
}
|
|
6057
7323
|
catch (_a) {
|
|
7324
|
+
// 跨端 plan B
|
|
6058
7325
|
const htmlElementKeys = [
|
|
6059
7326
|
'children',
|
|
6060
7327
|
'innerHTML',
|
|
@@ -6122,6 +7389,29 @@
|
|
|
6122
7389
|
return distance / factor;
|
|
6123
7390
|
}
|
|
6124
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
|
|
6125
7415
|
function getGeom(geojson) {
|
|
6126
7416
|
if (geojson.type === 'Feature') {
|
|
6127
7417
|
return geojson.geometry;
|
|
@@ -6137,6 +7427,7 @@
|
|
|
6137
7427
|
}
|
|
6138
7428
|
const geom = getGeom(polygon);
|
|
6139
7429
|
const type = geom.type;
|
|
7430
|
+
// BBox [west, south, east, north]
|
|
6140
7431
|
const bbox = polygon.bbox;
|
|
6141
7432
|
let polys = geom.coordinates;
|
|
6142
7433
|
if (bbox && pointInRect(point, { x1: bbox[0], x2: bbox[1], y1: bbox[1], y2: bbox[3] }, true) === true) {
|
|
@@ -6157,11 +7448,13 @@
|
|
|
6157
7448
|
}
|
|
6158
7449
|
return result;
|
|
6159
7450
|
}
|
|
7451
|
+
// https://github.com/Turfjs/turf
|
|
6160
7452
|
function destination(point, distance, bearing, options = {}) {
|
|
6161
7453
|
const longitude1 = degreeToRadian(point[0]);
|
|
6162
7454
|
const latitude1 = degreeToRadian(point[1]);
|
|
6163
7455
|
const bearingRad = degreeToRadian(bearing);
|
|
6164
7456
|
const radians = lengthToRadians(distance, options.units);
|
|
7457
|
+
// Main
|
|
6165
7458
|
const latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(radians) + Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearingRad));
|
|
6166
7459
|
const longitude2 = longitude1 +
|
|
6167
7460
|
Math.atan2(Math.sin(bearingRad) * Math.sin(radians) * Math.cos(latitude1), Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2));
|
|
@@ -6170,17 +7463,41 @@
|
|
|
6170
7463
|
return { x: lng, y: lat };
|
|
6171
7464
|
}
|
|
6172
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
|
+
*/
|
|
6173
7474
|
const SMALL = 1e-10;
|
|
6174
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) */
|
|
6175
7486
|
function intersectionArea(circles, stats) {
|
|
7487
|
+
// get all the intersection points of the circles
|
|
6176
7488
|
const intersectionPoints = getIntersectionPoints(circles);
|
|
7489
|
+
// filter out points that aren't included in all the circles
|
|
6177
7490
|
const innerPoints = intersectionPoints.filter(function (p) {
|
|
6178
7491
|
return containedInCircles(p, circles);
|
|
6179
7492
|
});
|
|
6180
7493
|
let arcArea = 0;
|
|
6181
7494
|
let polygonArea = 0;
|
|
6182
7495
|
const arcs = [];
|
|
7496
|
+
// if we have intersection points that are within all the circles,
|
|
7497
|
+
// then figure out the area contained by them
|
|
6183
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
|
|
6184
7501
|
const center = getCenter(innerPoints);
|
|
6185
7502
|
for (let i = 0; i < innerPoints.length; ++i) {
|
|
6186
7503
|
const p = innerPoints[i];
|
|
@@ -6189,14 +7506,20 @@
|
|
|
6189
7506
|
innerPoints.sort(function (a, b) {
|
|
6190
7507
|
return b.angle - a.angle;
|
|
6191
7508
|
});
|
|
7509
|
+
// iterate over all points, get arc between the points
|
|
7510
|
+
// and update the areas
|
|
6192
7511
|
let p2 = innerPoints[innerPoints.length - 1];
|
|
6193
7512
|
for (let i = 0; i < innerPoints.length; ++i) {
|
|
6194
7513
|
const p1 = innerPoints[i];
|
|
7514
|
+
// polygon area updates easily ...
|
|
6195
7515
|
polygonArea += (p2.x + p1.x) * (p1.y - p2.y);
|
|
7516
|
+
// updating the arc area is a little more involved
|
|
6196
7517
|
const midPoint = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
|
|
6197
7518
|
let arc = null;
|
|
6198
7519
|
for (let j = 0; j < p1.parentIndex.length; ++j) {
|
|
6199
7520
|
if (p2.parentIndex.indexOf(p1.parentIndex[j]) > -1) {
|
|
7521
|
+
// figure out the angle halfway between the two points
|
|
7522
|
+
// on the current circle
|
|
6200
7523
|
const circle = circles[p1.parentIndex[j]];
|
|
6201
7524
|
const a1 = Math.atan2(p1.x - circle.x, p1.y - circle.y);
|
|
6202
7525
|
const a2 = Math.atan2(p2.x - circle.x, p2.y - circle.y);
|
|
@@ -6204,14 +7527,19 @@
|
|
|
6204
7527
|
if (angleDiff < 0) {
|
|
6205
7528
|
angleDiff += 2 * Math.PI;
|
|
6206
7529
|
}
|
|
7530
|
+
// and use that angle to figure out the width of the
|
|
7531
|
+
// arc
|
|
6207
7532
|
const a = a2 - angleDiff / 2;
|
|
6208
7533
|
let width = PointService.distancePP(midPoint, {
|
|
6209
7534
|
x: circle.x + circle.radius * Math.sin(a),
|
|
6210
7535
|
y: circle.y + circle.radius * Math.cos(a)
|
|
6211
7536
|
});
|
|
7537
|
+
// clamp the width to the largest is can actually be
|
|
7538
|
+
// (sometimes slightly overflows because of FP errors)
|
|
6212
7539
|
if (width > circle.radius * 2) {
|
|
6213
7540
|
width = circle.radius * 2;
|
|
6214
7541
|
}
|
|
7542
|
+
// pick the circle whose arc has the smallest width
|
|
6215
7543
|
if (arc === null || arc.width > width) {
|
|
6216
7544
|
arc = { circle: circle, width: width, p1: p1, p2: p2 };
|
|
6217
7545
|
}
|
|
@@ -6225,12 +7553,16 @@
|
|
|
6225
7553
|
}
|
|
6226
7554
|
}
|
|
6227
7555
|
else {
|
|
7556
|
+
// no intersection points, is either disjoint - or is completely
|
|
7557
|
+
// overlapped. figure out which by examining the smallest circle
|
|
6228
7558
|
let smallest = circles[0];
|
|
6229
7559
|
for (let i = 1; i < circles.length; ++i) {
|
|
6230
7560
|
if (circles[i].radius < smallest.radius) {
|
|
6231
7561
|
smallest = circles[i];
|
|
6232
7562
|
}
|
|
6233
7563
|
}
|
|
7564
|
+
// make sure the smallest circle is completely contained in all
|
|
7565
|
+
// the other circles
|
|
6234
7566
|
let disjoint = false;
|
|
6235
7567
|
for (let i = 0; i < circles.length; ++i) {
|
|
6236
7568
|
if (PointService.distancePP(circles[i], smallest) > Math.abs(smallest.radius - circles[i].radius)) {
|
|
@@ -6262,6 +7594,7 @@
|
|
|
6262
7594
|
}
|
|
6263
7595
|
return arcArea + polygonArea;
|
|
6264
7596
|
}
|
|
7597
|
+
/** returns whether a point is contained by all of a list of circles */
|
|
6265
7598
|
function containedInCircles(point, circles) {
|
|
6266
7599
|
for (let i = 0; i < circles.length; ++i) {
|
|
6267
7600
|
if (PointService.distancePP(point, circles[i]) > circles[i].radius + SMALL) {
|
|
@@ -6270,6 +7603,7 @@
|
|
|
6270
7603
|
}
|
|
6271
7604
|
return true;
|
|
6272
7605
|
}
|
|
7606
|
+
/** Gets all intersection points between a bunch of circles */
|
|
6273
7607
|
function getIntersectionPoints(circles) {
|
|
6274
7608
|
const ret = [];
|
|
6275
7609
|
for (let i = 0; i < circles.length; ++i) {
|
|
@@ -6284,13 +7618,19 @@
|
|
|
6284
7618
|
}
|
|
6285
7619
|
return ret;
|
|
6286
7620
|
}
|
|
7621
|
+
/** Circular segment area calculation. See http://mathworld.wolfram.com/CircularSegment.html */
|
|
6287
7622
|
function circleArea(r, width) {
|
|
6288
7623
|
return r * r * Math.acos(1 - width / r) - (r - width) * Math.sqrt(width * (2 * r - width));
|
|
6289
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 */
|
|
6290
7628
|
function circleOverlap(r1, r2, d) {
|
|
7629
|
+
// no overlap
|
|
6291
7630
|
if (d >= r1 + r2) {
|
|
6292
7631
|
return 0;
|
|
6293
7632
|
}
|
|
7633
|
+
// completely overlapped
|
|
6294
7634
|
if (d <= Math.abs(r1 - r2)) {
|
|
6295
7635
|
return Math.PI * Math.min(r1, r2) * Math.min(r1, r2);
|
|
6296
7636
|
}
|
|
@@ -6298,10 +7638,15 @@
|
|
|
6298
7638
|
const w2 = r2 - (d * d - r1 * r1 + r2 * r2) / (2 * d);
|
|
6299
7639
|
return circleArea(r1, w1) + circleArea(r2, w2);
|
|
6300
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*/
|
|
6301
7645
|
function circleCircleIntersection(p1, p2) {
|
|
6302
7646
|
const d = PointService.distancePP(p1, p2);
|
|
6303
7647
|
const r1 = p1.radius;
|
|
6304
7648
|
const r2 = p2.radius;
|
|
7649
|
+
// if to far away, or self contained - can't be done
|
|
6305
7650
|
if (d >= r1 + r2 || d <= Math.abs(r1 - r2)) {
|
|
6306
7651
|
return [];
|
|
6307
7652
|
}
|
|
@@ -6316,6 +7661,7 @@
|
|
|
6316
7661
|
{ x: x0 - rx, y: y0 + ry }
|
|
6317
7662
|
];
|
|
6318
7663
|
}
|
|
7664
|
+
/** Returns the center of a bunch of points */
|
|
6319
7665
|
function getCenter(points) {
|
|
6320
7666
|
const center = { x: 0, y: 0 };
|
|
6321
7667
|
for (let i = 0; i < points.length; ++i) {
|
|
@@ -6327,6 +7673,14 @@
|
|
|
6327
7673
|
return center;
|
|
6328
7674
|
}
|
|
6329
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
|
+
*/
|
|
6330
7684
|
class TimeUtil {
|
|
6331
7685
|
static getInstance() {
|
|
6332
7686
|
if (!TimeUtil.instance) {
|
|
@@ -6339,8 +7693,9 @@
|
|
|
6339
7693
|
this.locale_periods = ['AM', 'PM'];
|
|
6340
7694
|
this.locale_weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
6341
7695
|
this.locale_shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
6342
|
-
this.numberRe = /^\s*\d+/;
|
|
7696
|
+
this.numberRe = /^\s*\d+/; // note: ignores next directive
|
|
6343
7697
|
this.pads = { '-': '', _: ' ', '0': '0' };
|
|
7698
|
+
// const percentRe = /^%/;
|
|
6344
7699
|
this.requoteRe = /[\\^$*+?|[\]().{}]/g;
|
|
6345
7700
|
this.locale_months = [
|
|
6346
7701
|
'January',
|
|
@@ -6617,18 +7972,22 @@
|
|
|
6617
7972
|
if (i !== string.length) {
|
|
6618
7973
|
return null;
|
|
6619
7974
|
}
|
|
7975
|
+
// If a UNIX timestamp is specified, return it.
|
|
6620
7976
|
if ('Q' in d) {
|
|
6621
7977
|
return new Date(d.Q);
|
|
6622
7978
|
}
|
|
6623
7979
|
if ('s' in d) {
|
|
6624
7980
|
return new Date(d.s * 1000 + ('L' in d ? d.L : 0));
|
|
6625
7981
|
}
|
|
7982
|
+
// If this is utcParse, never use the local timezone.
|
|
6626
7983
|
if (Z && !('Z' in d)) {
|
|
6627
7984
|
d.Z = 0;
|
|
6628
7985
|
}
|
|
7986
|
+
// The am-pm flag is 0 for AM, and 1 for PM.
|
|
6629
7987
|
if ('p' in d) {
|
|
6630
7988
|
d.H = (d.H % 12) + d.p * 12;
|
|
6631
7989
|
}
|
|
7990
|
+
// If the month was not specified, inherit from the quarter.
|
|
6632
7991
|
if (d.m === undefined) {
|
|
6633
7992
|
d.m = 'q' in d ? d.q : 0;
|
|
6634
7993
|
}
|
|
@@ -6637,6 +7996,7 @@
|
|
|
6637
7996
|
d.M += d.Z % 100;
|
|
6638
7997
|
return that.utcDate(d);
|
|
6639
7998
|
}
|
|
7999
|
+
// Otherwise, all fields are in local time.
|
|
6640
8000
|
return that.localDate(d);
|
|
6641
8001
|
};
|
|
6642
8002
|
}
|
|
@@ -6678,6 +8038,13 @@
|
|
|
6678
8038
|
}
|
|
6679
8039
|
}
|
|
6680
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
|
+
*/
|
|
6681
8048
|
function formatDecimal(x) {
|
|
6682
8049
|
return Math.abs((x = Math.round(x))) >= 1e21 ? x.toLocaleString('en').replace(/,/g, '') : x.toString(10);
|
|
6683
8050
|
}
|
|
@@ -6685,12 +8052,19 @@
|
|
|
6685
8052
|
const _x = p ? x.toExponential(p - 1) : x.toExponential();
|
|
6686
8053
|
const i = _x.indexOf('e');
|
|
6687
8054
|
if (i < 0) {
|
|
6688
|
-
return null;
|
|
8055
|
+
return null; // NaN, ±Infinity
|
|
6689
8056
|
}
|
|
6690
8057
|
const coefficient = _x.slice(0, i);
|
|
6691
8058
|
return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +_x.slice(i + 1)];
|
|
6692
8059
|
}
|
|
6693
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
|
+
*/
|
|
6694
8068
|
function formatGroup(grouping, thousands) {
|
|
6695
8069
|
return function (value, width) {
|
|
6696
8070
|
let i = value.length;
|
|
@@ -6712,6 +8086,13 @@
|
|
|
6712
8086
|
};
|
|
6713
8087
|
}
|
|
6714
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
|
+
*/
|
|
6715
8096
|
let prefixExponent;
|
|
6716
8097
|
function formatPrefixAuto(x, p) {
|
|
6717
8098
|
const d = formatDecimalParts(x, p);
|
|
@@ -6728,9 +8109,16 @@
|
|
|
6728
8109
|
? coefficient + new Array(i - n + 1).join('0')
|
|
6729
8110
|
: i > 0
|
|
6730
8111
|
? coefficient.slice(0, i) + '.' + coefficient.slice(i)
|
|
6731
|
-
: '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!
|
|
6732
8113
|
}
|
|
6733
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
|
+
*/
|
|
6734
8122
|
function formatRounded(x, p) {
|
|
6735
8123
|
const d = formatDecimalParts(x, p);
|
|
6736
8124
|
if (!d) {
|
|
@@ -6745,6 +8133,13 @@
|
|
|
6745
8133
|
: coefficient + new Array(exponent - coefficient.length + 2).join('0');
|
|
6746
8134
|
}
|
|
6747
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
|
+
*/
|
|
6748
8143
|
function formatTrim(s) {
|
|
6749
8144
|
const n = s.length;
|
|
6750
8145
|
let i0 = -1;
|
|
@@ -6820,6 +8215,13 @@
|
|
|
6820
8215
|
});
|
|
6821
8216
|
}
|
|
6822
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
|
+
*/
|
|
6823
8225
|
const prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
|
6824
8226
|
class NumberUtil {
|
|
6825
8227
|
constructor() {
|
|
@@ -6868,19 +8270,33 @@
|
|
|
6868
8270
|
let precision = specifierIns.precision;
|
|
6869
8271
|
let trim = specifierIns.trim;
|
|
6870
8272
|
let type = specifierIns.type;
|
|
8273
|
+
// The "n" type is an alias for ",g".
|
|
6871
8274
|
if (type === 'n') {
|
|
6872
8275
|
(comma = true), (type = 'g');
|
|
6873
8276
|
}
|
|
8277
|
+
// The "" type, and any invalid type, is an alias for ".12~g".
|
|
8278
|
+
// @ts-ignore
|
|
6874
8279
|
else if (!formatTypes[type]) {
|
|
6875
8280
|
precision === undefined && (precision = 12), (trim = true), (type = 'g');
|
|
6876
8281
|
}
|
|
8282
|
+
// If zero fill is specified, padding goes after sign and before digits.
|
|
8283
|
+
// @ts-ignore
|
|
6877
8284
|
if (zero || (fill === '0' && align === '=')) {
|
|
6878
8285
|
(zero = true), (fill = '0'), (align = '=');
|
|
6879
8286
|
}
|
|
8287
|
+
// Compute the prefix and suffix.
|
|
8288
|
+
// For SI-prefix, the suffix is lazily computed.
|
|
6880
8289
|
const prefix = symbol === '$' ? this.currencyPrefix : symbol === '#' && /[boxX]/.test(type) ? '0' + type.toLowerCase() : '';
|
|
6881
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?
|
|
6882
8294
|
const formatType = formatTypes[type];
|
|
6883
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].
|
|
6884
8300
|
precision =
|
|
6885
8301
|
precision === undefined
|
|
6886
8302
|
? 6
|
|
@@ -6901,20 +8317,27 @@
|
|
|
6901
8317
|
}
|
|
6902
8318
|
else {
|
|
6903
8319
|
_value = +_value;
|
|
8320
|
+
// Determine the sign. -0 is not less than 0, but 1 / -0 is!
|
|
6904
8321
|
let valueNegative = _value < 0 || 1 / _value < 0;
|
|
8322
|
+
// Perform the initial formatting.
|
|
6905
8323
|
_value = isNaN(_value) ? nan : formatType(Math.abs(_value), precision);
|
|
8324
|
+
// Trim insignificant zeros.
|
|
6906
8325
|
if (trim) {
|
|
6907
8326
|
_value = formatTrim(_value);
|
|
6908
8327
|
}
|
|
8328
|
+
// If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
|
|
6909
8329
|
if (valueNegative && +_value === 0 && sign !== '+') {
|
|
6910
8330
|
valueNegative = false;
|
|
6911
8331
|
}
|
|
8332
|
+
// Compute the prefix and suffix.
|
|
6912
8333
|
valuePrefix =
|
|
6913
8334
|
(valueNegative ? (sign === '(' ? sign : minus) : sign === '-' || sign === '(' ? '' : sign) + valuePrefix;
|
|
6914
8335
|
valueSuffix =
|
|
6915
8336
|
(type === 's' ? prefixes[8 + prefixExponent / 3] : '') +
|
|
6916
8337
|
valueSuffix +
|
|
6917
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.
|
|
6918
8341
|
if (maybeSuffix) {
|
|
6919
8342
|
(i = -1), (n = _value.length);
|
|
6920
8343
|
while (++i < n) {
|
|
@@ -6926,15 +8349,19 @@
|
|
|
6926
8349
|
}
|
|
6927
8350
|
}
|
|
6928
8351
|
}
|
|
8352
|
+
// If the fill character is not "0", grouping is applied before padding.
|
|
6929
8353
|
if (comma && !zero) {
|
|
6930
8354
|
_value = group(_value, Infinity);
|
|
6931
8355
|
}
|
|
8356
|
+
// Compute the padding.
|
|
6932
8357
|
let length = valuePrefix.length + _value.length + valueSuffix.length;
|
|
6933
8358
|
let padding = length < width ? new Array(width - length + 1).join(fill) : '';
|
|
8359
|
+
// If the fill character is "0", grouping is applied after padding.
|
|
6934
8360
|
if (comma && zero) {
|
|
6935
8361
|
_value = group(padding + _value, padding.length ? width - valueSuffix.length : Infinity);
|
|
6936
8362
|
padding = '';
|
|
6937
8363
|
}
|
|
8364
|
+
// Reconstruct the final output based on the desired alignment.
|
|
6938
8365
|
switch (align) {
|
|
6939
8366
|
case '<':
|
|
6940
8367
|
_value = valuePrefix + _value + valueSuffix + padding;
|
|
@@ -6988,6 +8415,7 @@
|
|
|
6988
8415
|
X: (x) => Math.round(x).toString(16).toUpperCase(),
|
|
6989
8416
|
x: (x) => Math.round(x).toString(16),
|
|
6990
8417
|
t: (x, p) => {
|
|
8418
|
+
// 判断是否为整数
|
|
6991
8419
|
if (Number.isInteger(x)) {
|
|
6992
8420
|
return x.toFixed(2);
|
|
6993
8421
|
}
|
|
@@ -7005,6 +8433,16 @@
|
|
|
7005
8433
|
};
|
|
7006
8434
|
}
|
|
7007
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
|
|
7008
8446
|
function zeros(x) {
|
|
7009
8447
|
const r = new Array(x);
|
|
7010
8448
|
for (let i = 0; i < x; ++i) {
|
|
@@ -7036,6 +8474,15 @@
|
|
|
7036
8474
|
}
|
|
7037
8475
|
}
|
|
7038
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 */
|
|
7039
8486
|
function nelderMead(f, x0, parameters) {
|
|
7040
8487
|
parameters = parameters || {};
|
|
7041
8488
|
const maxIterations = parameters.maxIterations || x0.length * 200;
|
|
@@ -7048,6 +8495,7 @@
|
|
|
7048
8495
|
const psi = parameters.psi !== undefined ? parameters.psi : -0.5;
|
|
7049
8496
|
const sigma = parameters.sigma !== undefined ? parameters.sigma : 0.5;
|
|
7050
8497
|
let maxDiff;
|
|
8498
|
+
// initialize simplex.
|
|
7051
8499
|
const N = x0.length;
|
|
7052
8500
|
const simplex = new Array(N + 1);
|
|
7053
8501
|
simplex[0] = x0;
|
|
@@ -7076,6 +8524,8 @@
|
|
|
7076
8524
|
for (let iteration = 0; iteration < maxIterations; ++iteration) {
|
|
7077
8525
|
simplex.sort(sortOrder);
|
|
7078
8526
|
if (parameters.history) {
|
|
8527
|
+
// copy the simplex (since later iterations will mutate) and
|
|
8528
|
+
// sort it to have a consistent order between iterations
|
|
7079
8529
|
const sortedSimplex = simplex.map(function (x) {
|
|
7080
8530
|
const state = x.slice();
|
|
7081
8531
|
state.fx = x.fx;
|
|
@@ -7094,6 +8544,7 @@
|
|
|
7094
8544
|
if (Math.abs(simplex[0].fx - simplex[N].fx) < minErrorDelta && maxDiff < minTolerance) {
|
|
7095
8545
|
break;
|
|
7096
8546
|
}
|
|
8547
|
+
// compute the centroid of all but the worst point in the simplex
|
|
7097
8548
|
for (let i = 0; i < N; ++i) {
|
|
7098
8549
|
centroid[i] = 0;
|
|
7099
8550
|
for (let j = 0; j < N; ++j) {
|
|
@@ -7101,9 +8552,12 @@
|
|
|
7101
8552
|
}
|
|
7102
8553
|
centroid[i] /= N;
|
|
7103
8554
|
}
|
|
8555
|
+
// reflect the worst point past the centroid and compute loss at reflected
|
|
8556
|
+
// point
|
|
7104
8557
|
const worst = simplex[N];
|
|
7105
8558
|
weightedSum(reflected, 1 + rho, centroid, -rho, worst);
|
|
7106
8559
|
reflected.fx = f(reflected);
|
|
8560
|
+
// if the reflected point is the best seen, then possibly expand
|
|
7107
8561
|
if (reflected.fx < simplex[0].fx) {
|
|
7108
8562
|
weightedSum(expanded, 1 + chi, centroid, -chi, worst);
|
|
7109
8563
|
expanded.fx = f(expanded);
|
|
@@ -7114,9 +8568,12 @@
|
|
|
7114
8568
|
updateSimplex(reflected);
|
|
7115
8569
|
}
|
|
7116
8570
|
}
|
|
8571
|
+
// if the reflected point is worse than the second worst, we need to
|
|
8572
|
+
// contract
|
|
7117
8573
|
else if (reflected.fx >= simplex[N - 1].fx) {
|
|
7118
8574
|
let shouldReduce = false;
|
|
7119
8575
|
if (reflected.fx > worst.fx) {
|
|
8576
|
+
// do an inside contraction
|
|
7120
8577
|
weightedSum(contracted, 1 + psi, centroid, -psi, worst);
|
|
7121
8578
|
contracted.fx = f(contracted);
|
|
7122
8579
|
if (contracted.fx < worst.fx) {
|
|
@@ -7127,6 +8584,7 @@
|
|
|
7127
8584
|
}
|
|
7128
8585
|
}
|
|
7129
8586
|
else {
|
|
8587
|
+
// do an outside contraction
|
|
7130
8588
|
weightedSum(contracted, 1 - psi * rho, centroid, psi * rho, worst);
|
|
7131
8589
|
contracted.fx = f(contracted);
|
|
7132
8590
|
if (contracted.fx < reflected.fx) {
|
|
@@ -7137,9 +8595,11 @@
|
|
|
7137
8595
|
}
|
|
7138
8596
|
}
|
|
7139
8597
|
if (shouldReduce) {
|
|
8598
|
+
// if we don't contract here, we're done
|
|
7140
8599
|
if (sigma >= 1) {
|
|
7141
8600
|
break;
|
|
7142
8601
|
}
|
|
8602
|
+
// do a reduction
|
|
7143
8603
|
for (let i = 1; i < simplex.length; ++i) {
|
|
7144
8604
|
weightedSum(simplex[i], 1 - sigma, simplex[0], sigma, simplex[i]);
|
|
7145
8605
|
simplex[i].fx = f(simplex[i]);
|
|
@@ -7154,6 +8614,21 @@
|
|
|
7154
8614
|
return { fx: simplex[0].fx, x: simplex[0] };
|
|
7155
8615
|
}
|
|
7156
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
|
|
7157
8632
|
function wolfeLineSearch(f, pk, current, next, a, c1, c2) {
|
|
7158
8633
|
const phi0 = current.fx;
|
|
7159
8634
|
const phiPrime0 = dotProduct(current.fxprime, pk);
|
|
@@ -7206,7 +8681,17 @@
|
|
|
7206
8681
|
return a;
|
|
7207
8682
|
}
|
|
7208
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
|
+
*/
|
|
7209
8692
|
function conjugateGradient(f, initial, params) {
|
|
8693
|
+
// allocate all memory up front here, keep out of the loop for perfomance
|
|
8694
|
+
// reasons
|
|
7210
8695
|
let current = { x: initial.slice(), fx: 0, fxprime: initial.slice() };
|
|
7211
8696
|
let next = { x: initial.slice(), fx: 0, fxprime: initial.slice() };
|
|
7212
8697
|
const yk = initial.slice();
|
|
@@ -7219,13 +8704,17 @@
|
|
|
7219
8704
|
scale(pk, current.fxprime, -1);
|
|
7220
8705
|
for (let i = 0; i < maxIterations; ++i) {
|
|
7221
8706
|
a = wolfeLineSearch(f, pk, current, next, a);
|
|
8707
|
+
// todo: history in wrong spot?
|
|
7222
8708
|
if (params.history) {
|
|
7223
8709
|
params.history.push({ x: current.x.slice(), fx: current.fx, fxprime: current.fxprime.slice(), alpha: a });
|
|
7224
8710
|
}
|
|
7225
8711
|
if (!a) {
|
|
8712
|
+
// faiiled to find point that satifies wolfe conditions.
|
|
8713
|
+
// reset direction for next iteration
|
|
7226
8714
|
scale(pk, current.fxprime, -1);
|
|
7227
8715
|
}
|
|
7228
8716
|
else {
|
|
8717
|
+
// update direction using Polak–Ribiere CG method
|
|
7229
8718
|
weightedSum(yk, 1, next.fxprime, -1, current.fxprime);
|
|
7230
8719
|
const delta_k = dotProduct(current.fxprime, current.fxprime);
|
|
7231
8720
|
const beta_k = Math.max(0, dotProduct(yk, next.fxprime) / delta_k);
|
|
@@ -7288,6 +8777,7 @@
|
|
|
7288
8777
|
exports.binaryFuzzySearch = binaryFuzzySearch;
|
|
7289
8778
|
exports.binaryFuzzySearchInNumberRange = binaryFuzzySearchInNumberRange;
|
|
7290
8779
|
exports.bisect = bisect;
|
|
8780
|
+
exports.bootstrapEstimate = bootstrapEstimate;
|
|
7291
8781
|
exports.calculateAnchorOfArc = calculateAnchorOfArc;
|
|
7292
8782
|
exports.calculateAnchorOfBounds = calculateAnchorOfBounds;
|
|
7293
8783
|
exports.calculateMaxRadius = calculateMaxRadius;
|
|
@@ -7308,6 +8798,7 @@
|
|
|
7308
8798
|
exports.conjugateGradient = conjugateGradient;
|
|
7309
8799
|
exports.constant = constant;
|
|
7310
8800
|
exports.containedInCircles = containedInCircles;
|
|
8801
|
+
exports.correlation = correlation;
|
|
7311
8802
|
exports.cos = cos;
|
|
7312
8803
|
exports.crossProduct = crossProduct;
|
|
7313
8804
|
exports.crossProductPoint = crossProductPoint;
|
|
@@ -7426,6 +8917,7 @@
|
|
|
7426
8917
|
exports.lowerFirst = lowerFirst;
|
|
7427
8918
|
exports.max = max;
|
|
7428
8919
|
exports.maxInArray = maxInArray;
|
|
8920
|
+
exports.mean = mean;
|
|
7429
8921
|
exports.median = median;
|
|
7430
8922
|
exports.memoize = memoize;
|
|
7431
8923
|
exports.merge = merge;
|
|
@@ -7452,6 +8944,7 @@
|
|
|
7452
8944
|
exports.monthSetterName = monthSetterName;
|
|
7453
8945
|
exports.nelderMead = nelderMead;
|
|
7454
8946
|
exports.norm2 = norm2;
|
|
8947
|
+
exports.normalQuantile = normalQuantile;
|
|
7455
8948
|
exports.normalTransform = normalTransform;
|
|
7456
8949
|
exports.normalizeAngle = normalizeAngle;
|
|
7457
8950
|
exports.normalizePadding = normalizePadding;
|