├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── karma.conf.ts ├── package-lock.json ├── package.json ├── src ├── double.ts ├── extensions │ ├── arrayExtensions.ts │ ├── enumExtensions.ts │ ├── logicExtensions.ts │ ├── regExpExtensions.ts │ └── stringExtensions.ts ├── index.ts ├── jsonComparer.ts ├── numericSequence │ ├── numericSequence.ts │ └── numericSequenceRange.ts ├── pixelConverter.ts ├── prototype.ts ├── textSizeDefaults.ts └── valueType.ts ├── test ├── doubleTest.ts ├── extensions │ ├── arrayExtensionsTests.ts │ ├── enumExtensionsTest.ts │ └── regExpExtensionsTest.ts ├── jsonComparerTest.ts ├── numericSequence │ ├── numericSequenceRangeTest.ts │ └── numericSequenceTest.ts ├── pixelConverterTest.ts ├── prototypeTests.ts ├── textSizeDefaultsTest.ts └── valueTypeTests.ts ├── tsconfig.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | test 5 | .eslintrc.js 6 | karma.conf.ts 7 | webpack.config.js 8 | test.webpack.config.js 9 | lib -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | "browser": true, 4 | "es6": true, 5 | "es2017": true 6 | }, 7 | root: true, 8 | parser: "@typescript-eslint/parser", 9 | parserOptions: { 10 | project: "tsconfig.json", 11 | tsconfigRootDir: ".", 12 | }, 13 | plugins: [ 14 | "powerbi-visuals" 15 | ], 16 | extends: [ 17 | "plugin:powerbi-visuals/recommended" 18 | ], 19 | rules: {} 20 | }; -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [18.x, 20.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm audit 28 | continue-on-error: true 29 | - run: npm outdated 30 | continue-on-error: true 31 | - run: npm ci 32 | - run: npm run lint 33 | - run: npm test 34 | env: 35 | CI: true 36 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [main, dev] 6 | pull_request: 7 | branches: [main, dev] 8 | schedule: 9 | - cron: '0 0 * * 3' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 60 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: ['typescript'] 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 2 31 | 32 | - name: Use Node.js 18 33 | uses: actions/setup-node@v2 34 | with: 35 | node-version: 18.x 36 | 37 | - name: Install Dependencies 38 | run: npm ci 39 | 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v3 42 | with: 43 | languages: ${{ matrix.language }} 44 | 45 | - name: Autobuild 46 | uses: github/codeql-action/autobuild@v3 47 | 48 | - name: Perform CodeQL Analysis 49 | uses: github/codeql-action/analyze@v3 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | environment: automated-release 10 | runs-on: ubuntu-latest 11 | env: 12 | GH_TOKEN: ${{secrets.GH_TOKEN}} 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Add config details 16 | run: | 17 | git config --global user.name ${{secrets.NAME}} 18 | git config --global user.email ${{secrets.EMAIL}} 19 | - name: Move release to draft 20 | run: gh release edit $TAG_NAME --draft=true 21 | env: 22 | TAG_NAME: ${{ github.event.release.tag_name }} 23 | - name: Run npm install, build and test 24 | run: | 25 | npm i 26 | npm run build 27 | npm run test 28 | npm run lint 29 | - run: zip -r release.zip lib CHANGELOG.md LICENSE package.json README.md 30 | - name: Upload production artifacts 31 | run: | 32 | gh release upload $TAG_NAME "release.zip#release" 33 | gh release edit $TAG_NAME --draft=false 34 | env: 35 | TAG_NAME: ${{ github.event.release.tag_name }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | typings 4 | /coverage 5 | /lib 6 | *.js.map 7 | .tmp -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 6.0.3 2 | * powerbi-visuals-api update to 5.9.0 3 | 4 | ## 6.0.2 5 | * Packages update 6 | 7 | ## 6.0.1 8 | * Packages update 9 | * Removed coveralls 10 | 11 | ## 6.0.0 12 | * Migrated to playwright 13 | * Packages update 14 | * Vulnerabilities patched 15 | 16 | ## 2.4.0 - 3.0.0 17 | * Migrated to ESlint 18 | * Vulnerabilities patched 19 | 20 | ## 2.3.1 21 | * Packages update 22 | * Vulnerabilities patched 23 | 24 | ## 2.3.0 25 | * Packages update, npm audit fixes 26 | * Github actions added 27 | * Outdated packages removed 28 | * Obsolete docs removed 29 | 30 | ## 2.2.1 31 | * Packages update 32 | 33 | ## 2.2.0 34 | * Update packages to fix vulnerabilities 35 | * Update powerbi-visuals-api to 2.6.0 36 | 37 | ## 2.1.0 38 | * Update packages to fix vulnerabilities 39 | 40 | ## 2.0.0 41 | * Moved to webpack 4, es6 modules 42 | 43 | ## 1.1.0 44 | * Removed `lodash` 45 | * Updated dependencies 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | If you would like to contribute to the Power BI visuals TypeUtils there are many ways you can help. 3 | 4 | ## Reporting issues 5 | We use [GitHub issues](https://github.com/Microsoft/powerbi-visuals-utils-typeutils/issues) as an issue tracker for the repository. Firstly, please search in open issues and try to make sure your problem doesn't exist. If there is an issue, add your comments to this issue. 6 | If there are no issues yet, please open a new one. 7 | 8 | ## Contributing Code 9 | If you would like to contribute an improvement or a fix please take a look at our [Development Workflow](./docs/dev/development-workflow.md) 10 | 11 | ## Sending a Pull Request 12 | Before submitting a pull request please make sure the following is done: 13 | 14 | 1. Fork [the repository](https://github.com/Microsoft/powerbi-visuals-utils-typeutils) 15 | 2. Create a branch from the ```main``` 16 | 3. Ensure that the code style checks are passed ([How to lint the source code](./docs/dev/development-workflow.md#how-to-lint-the-source-code)) 17 | 4. Ensure that the unit tests are passed ([How to run unit tests locally](./docs/dev/development-workflow.md#how-to-run-unit-tests-locally)) 18 | 5. Complete the [CLA](#contributor-license-agreement-cla) 19 | 20 | ### Code of Conduct 21 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 22 | 23 | ### Contributor License Agreement (CLA) 24 | You will need to complete a Contributor License Agreement (CLA). Briefly, this agreement testifies that you are granting us permission to use the submitted change according to the terms of the project's license, and that the work being submitted is under appropriate copyright. 25 | 26 | Please submit a Contributor License Agreement (CLA) before submitting a pull request. You may visit [https://cla.microsoft.com](https://cla.microsoft.com) to sign digitally. Alternatively, download the agreement ([Microsoft Contribution License Agreement.docx](https://www.codeplex.com/Download?ProjectName=typescript&DownloadId=822190)), sign, scan, and email it back to . Be sure to include your github user name along with the agreement. Once we have received the signed CLA, we'll review the request. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Power BI Visualizations 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | All rights reserved. 6 | 7 | MIT License 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft Power BI visuals TypeUtils 2 | ![Build](https://github.com/microsoft/powerbi-visuals-utils-typeutils/workflows/Build/badge.svg) [![npm version](https://img.shields.io/npm/v/powerbi-visuals-utils-typeutils.svg)](https://www.npmjs.com/package/powerbi-visuals-utils-typeutils) [![npm](https://img.shields.io/npm/dm/powerbi-visuals-utils-typeutils.svg)](https://www.npmjs.com/package/powerbi-visuals-utils-typeutils) 3 | 4 | > TypeUtils is a set of functions and classes in order to extend the basic types for Power BI custom visuals 5 | 6 | ## Usage 7 | Learn how to install and use the TypeUtils in your custom visuals: 8 | * [Usage Guide](https://docs.microsoft.com/en-us/power-bi/developer/visuals/utils-type) 9 | 10 | ## Contributing 11 | * Read our [contribution guideline](./CONTRIBUTING.md) to find out how to contribute bugs fixes and improvements 12 | * [Issue Tracker](https://github.com/Microsoft/powerbi-visuals-utils-typeutils/issues) 13 | 14 | ## License 15 | See the [LICENSE](./LICENSE) file for license rights and limitations (MIT). 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /karma.conf.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | "use strict"; 28 | 29 | const webpackConfig = require("./webpack.config.js"); 30 | const tsconfig = require("./tsconfig.json"); 31 | 32 | const testRecursivePath = "test/**/*.ts"; 33 | const srcOriginalRecursivePath = "src/**/*.ts"; 34 | const srcRecursivePath = "lib/**/*.js"; 35 | const coverageFolder = "coverage"; 36 | 37 | process.env.CHROME_BIN = require("playwright-chromium").chromium.executablePath(); 38 | module.exports = (config) => { 39 | config.set({ 40 | browsers: ["ChromeHeadless"], 41 | colors: true, 42 | frameworks: ["jasmine"], 43 | reporters: [ 44 | "progress", 45 | "coverage-istanbul" 46 | ], 47 | coverageIstanbulReporter: { 48 | reports: ["html", "lcovonly", "text-summary"], 49 | combineBrowserReports: true, 50 | fixWebpackSourcePaths: true 51 | }, 52 | singleRun: true, 53 | plugins: [ 54 | "karma-coverage", 55 | "karma-typescript", 56 | "karma-webpack", 57 | "karma-jasmine", 58 | "karma-sourcemap-loader", 59 | "karma-chrome-launcher", 60 | "karma-coverage-istanbul-reporter" 61 | ], 62 | files: [ 63 | srcRecursivePath, 64 | testRecursivePath, 65 | { 66 | pattern: srcOriginalRecursivePath, 67 | included: false, 68 | served: true 69 | } 70 | ], 71 | preprocessors: { 72 | [testRecursivePath]: ["webpack"], 73 | [srcRecursivePath]: ["webpack", "coverage"] 74 | }, 75 | typescriptPreprocessor: { 76 | options: tsconfig.compilerOptions 77 | }, 78 | coverageReporter: { 79 | dir: coverageFolder, 80 | reporters: [ 81 | { type: "html" }, 82 | { type: "lcov" } 83 | ] 84 | }, 85 | remapIstanbulReporter: { 86 | reports: { 87 | lcovonly: coverageFolder + "/lcov.info", 88 | html: coverageFolder, 89 | "text-summary": null 90 | } 91 | }, 92 | mime: { 93 | "text/x-typescript": ["ts", "tsx"] 94 | }, 95 | webpack: webpackConfig, 96 | webpackMiddleware: { 97 | stats: "errors-only" 98 | } 99 | }); 100 | }; 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "powerbi-visuals-utils-typeutils", 3 | "version": "6.0.3", 4 | "description": "TypeUtils", 5 | "main": "lib/index.js", 6 | "module": "lib/index.js", 7 | "jsnext:main": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "sideEffects": false, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Microsoft/powerbi-visuals-utils-typeutils.git" 13 | }, 14 | "keywords": [ 15 | "powerbi-visuals-typeutils" 16 | ], 17 | "author": "Microsoft", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/Microsoft/powerbi-visuals-utils-typeutils/issues" 21 | }, 22 | "homepage": "https://github.com/Microsoft/powerbi-visuals-utils-typeutils#readme", 23 | "files": [ 24 | "lib" 25 | ], 26 | "scripts": { 27 | "build:map": "tsc --sourceMap", 28 | "build": "tsc", 29 | "test": "karma start", 30 | "lint": "npx eslint . --ext .js,.jsx,.ts,.tsx" 31 | }, 32 | "devDependencies": { 33 | "@types/jasmine": "^5.1.4", 34 | "@types/karma": "^6.3.8", 35 | "@types/webpack": "^5.28.5", 36 | "@typescript-eslint/eslint-plugin": "^5.57.0", 37 | "@typescript-eslint/parser": "^5.57.0", 38 | "coverage-istanbul-loader": "^3.0.5", 39 | "eslint": "^8.37.0", 40 | "eslint-plugin-powerbi-visuals": "^0.8.1", 41 | "jasmine": "^5.1.0", 42 | "karma": "^6.4.1", 43 | "karma-chrome-launcher": "^3.2.0", 44 | "karma-coverage": "^2.2.1", 45 | "karma-coverage-istanbul-reporter": "^3.0.3", 46 | "karma-jasmine": "^5.1.0", 47 | "karma-sourcemap-loader": "0.4.0", 48 | "karma-typescript": "^5.5.4", 49 | "karma-typescript-preprocessor": "0.4.0", 50 | "karma-webpack": "^5.0.0", 51 | "playwright-chromium": "^1.40.1", 52 | "powerbi-visuals-api": "^5.9.0", 53 | "ts-loader": "^9.5.1", 54 | "ts-node": "^10.9.2", 55 | "typescript": "^4.9.5", 56 | "webpack": "^5.89.0" 57 | }, 58 | "optionalDependencies": { 59 | "fsevents": "*" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/double.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | /** 27 | * Module Double contains a set of constants and precision based utility methods 28 | * for dealing with doubles and their decimal garbage in the javascript. 29 | */ 30 | // Constants. 31 | export const MIN_VALUE = -Number.MAX_VALUE; 32 | export const MAX_VALUE = Number.MAX_VALUE; 33 | export const MIN_EXP = -308; 34 | export const MAX_EXP = 308; 35 | export const EPSILON = 1E-323; 36 | export const DEFAULT_PRECISION = 0.0001; 37 | export const DEFAULT_PRECISION_IN_DECIMAL_DIGITS = 12; 38 | export const LOG_E_10 = Math.log(10); 39 | export const POSITIVE_POWERS = [ 40 | 1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18, 1E19, 1E20, 1E21, 1E22, 1E23, 1E24, 1E25, 1E26, 1E27, 1E28, 1E29, 1E30, 1E31, 1E32, 1E33, 1E34, 1E35, 1E36, 1E37, 1E38, 1E39, 1E40, 1E41, 1E42, 1E43, 1E44, 1E45, 1E46, 1E47, 1E48, 1E49, 1E50, 1E51, 1E52, 1E53, 1E54, 1E55, 1E56, 1E57, 1E58, 1E59, 1E60, 1E61, 1E62, 1E63, 1E64, 1E65, 1E66, 1E67, 1E68, 1E69, 1E70, 1E71, 1E72, 1E73, 1E74, 1E75, 1E76, 1E77, 1E78, 1E79, 1E80, 1E81, 1E82, 1E83, 1E84, 1E85, 1E86, 1E87, 1E88, 1E89, 1E90, 1E91, 1E92, 1E93, 1E94, 1E95, 1E96, 1E97, 1E98, 1E99, 41 | 1E100, 1E101, 1E102, 1E103, 1E104, 1E105, 1E106, 1E107, 1E108, 1E109, 1E110, 1E111, 1E112, 1E113, 1E114, 1E115, 1E116, 1E117, 1E118, 1E119, 1E120, 1E121, 1E122, 1E123, 1E124, 1E125, 1E126, 1E127, 1E128, 1E129, 1E130, 1E131, 1E132, 1E133, 1E134, 1E135, 1E136, 1E137, 1E138, 1E139, 1E140, 1E141, 1E142, 1E143, 1E144, 1E145, 1E146, 1E147, 1E148, 1E149, 1E150, 1E151, 1E152, 1E153, 1E154, 1E155, 1E156, 1E157, 1E158, 1E159, 1E160, 1E161, 1E162, 1E163, 1E164, 1E165, 1E166, 1E167, 1E168, 1E169, 1E170, 1E171, 1E172, 1E173, 1E174, 1E175, 1E176, 1E177, 1E178, 1E179, 1E180, 1E181, 1E182, 1E183, 1E184, 1E185, 1E186, 1E187, 1E188, 1E189, 1E190, 1E191, 1E192, 1E193, 1E194, 1E195, 1E196, 1E197, 1E198, 1E199, 42 | 1E200, 1E201, 1E202, 1E203, 1E204, 1E205, 1E206, 1E207, 1E208, 1E209, 1E210, 1E211, 1E212, 1E213, 1E214, 1E215, 1E216, 1E217, 1E218, 1E219, 1E220, 1E221, 1E222, 1E223, 1E224, 1E225, 1E226, 1E227, 1E228, 1E229, 1E230, 1E231, 1E232, 1E233, 1E234, 1E235, 1E236, 1E237, 1E238, 1E239, 1E240, 1E241, 1E242, 1E243, 1E244, 1E245, 1E246, 1E247, 1E248, 1E249, 1E250, 1E251, 1E252, 1E253, 1E254, 1E255, 1E256, 1E257, 1E258, 1E259, 1E260, 1E261, 1E262, 1E263, 1E264, 1E265, 1E266, 1E267, 1E268, 1E269, 1E270, 1E271, 1E272, 1E273, 1E274, 1E275, 1E276, 1E277, 1E278, 1E279, 1E280, 1E281, 1E282, 1E283, 1E284, 1E285, 1E286, 1E287, 1E288, 1E289, 1E290, 1E291, 1E292, 1E293, 1E294, 1E295, 1E296, 1E297, 1E298, 1E299, 43 | 1E300, 1E301, 1E302, 1E303, 1E304, 1E305, 1E306, 1E307, 1E308]; 44 | export const NEGATIVE_POWERS = [ 45 | 1E0, 1E-1, 1E-2, 1E-3, 1E-4, 1E-5, 1E-6, 1E-7, 1E-8, 1E-9, 1E-10, 1E-11, 1E-12, 1E-13, 1E-14, 1E-15, 1E-16, 1E-17, 1E-18, 1E-19, 1E-20, 1E-21, 1E-22, 1E-23, 1E-24, 1E-25, 1E-26, 1E-27, 1E-28, 1E-29, 1E-30, 1E-31, 1E-32, 1E-33, 1E-34, 1E-35, 1E-36, 1E-37, 1E-38, 1E-39, 1E-40, 1E-41, 1E-42, 1E-43, 1E-44, 1E-45, 1E-46, 1E-47, 1E-48, 1E-49, 1E-50, 1E-51, 1E-52, 1E-53, 1E-54, 1E-55, 1E-56, 1E-57, 1E-58, 1E-59, 1E-60, 1E-61, 1E-62, 1E-63, 1E-64, 1E-65, 1E-66, 1E-67, 1E-68, 1E-69, 1E-70, 1E-71, 1E-72, 1E-73, 1E-74, 1E-75, 1E-76, 1E-77, 1E-78, 1E-79, 1E-80, 1E-81, 1E-82, 1E-83, 1E-84, 1E-85, 1E-86, 1E-87, 1E-88, 1E-89, 1E-90, 1E-91, 1E-92, 1E-93, 1E-94, 1E-95, 1E-96, 1E-97, 1E-98, 1E-99, 46 | 1E-100, 1E-101, 1E-102, 1E-103, 1E-104, 1E-105, 1E-106, 1E-107, 1E-108, 1E-109, 1E-110, 1E-111, 1E-112, 1E-113, 1E-114, 1E-115, 1E-116, 1E-117, 1E-118, 1E-119, 1E-120, 1E-121, 1E-122, 1E-123, 1E-124, 1E-125, 1E-126, 1E-127, 1E-128, 1E-129, 1E-130, 1E-131, 1E-132, 1E-133, 1E-134, 1E-135, 1E-136, 1E-137, 1E-138, 1E-139, 1E-140, 1E-141, 1E-142, 1E-143, 1E-144, 1E-145, 1E-146, 1E-147, 1E-148, 1E-149, 1E-150, 1E-151, 1E-152, 1E-153, 1E-154, 1E-155, 1E-156, 1E-157, 1E-158, 1E-159, 1E-160, 1E-161, 1E-162, 1E-163, 1E-164, 1E-165, 1E-166, 1E-167, 1E-168, 1E-169, 1E-170, 1E-171, 1E-172, 1E-173, 1E-174, 1E-175, 1E-176, 1E-177, 1E-178, 1E-179, 1E-180, 1E-181, 1E-182, 1E-183, 1E-184, 1E-185, 1E-186, 1E-187, 1E-188, 1E-189, 1E-190, 1E-191, 1E-192, 1E-193, 1E-194, 1E-195, 1E-196, 1E-197, 1E-198, 1E-199, 47 | 1E-200, 1E-201, 1E-202, 1E-203, 1E-204, 1E-205, 1E-206, 1E-207, 1E-208, 1E-209, 1E-210, 1E-211, 1E-212, 1E-213, 1E-214, 1E-215, 1E-216, 1E-217, 1E-218, 1E-219, 1E-220, 1E-221, 1E-222, 1E-223, 1E-224, 1E-225, 1E-226, 1E-227, 1E-228, 1E-229, 1E-230, 1E-231, 1E-232, 1E-233, 1E-234, 1E-235, 1E-236, 1E-237, 1E-238, 1E-239, 1E-240, 1E-241, 1E-242, 1E-243, 1E-244, 1E-245, 1E-246, 1E-247, 1E-248, 1E-249, 1E-250, 1E-251, 1E-252, 1E-253, 1E-254, 1E-255, 1E-256, 1E-257, 1E-258, 1E-259, 1E-260, 1E-261, 1E-262, 1E-263, 1E-264, 1E-265, 1E-266, 1E-267, 1E-268, 1E-269, 1E-270, 1E-271, 1E-272, 1E-273, 1E-274, 1E-275, 1E-276, 1E-277, 1E-278, 1E-279, 1E-280, 1E-281, 1E-282, 1E-283, 1E-284, 1E-285, 1E-286, 1E-287, 1E-288, 1E-289, 1E-290, 1E-291, 1E-292, 1E-293, 1E-294, 1E-295, 1E-296, 1E-297, 1E-298, 1E-299, 48 | 1E-300, 1E-301, 1E-302, 1E-303, 1E-304, 1E-305, 1E-306, 1E-307, 1E-308, 1E-309, 1E-310, 1E-311, 1E-312, 1E-313, 1E-314, 1E-315, 1E-316, 1E-317, 1E-318, 1E-319, 1E-320, 1E-321, 1E-322, 1E-323, 1E-324]; 49 | 50 | /** 51 | * Returns powers of 10. 52 | * Unlike the Math.pow this function produces no decimal garbage. 53 | * @param exp Exponent. 54 | */ 55 | export function pow10(exp: number): number { 56 | // Positive & zero 57 | if (exp >= 0) { 58 | if (exp < POSITIVE_POWERS.length) { 59 | return POSITIVE_POWERS[exp]; 60 | } else { 61 | return Infinity; 62 | } 63 | } 64 | // Negative 65 | exp = -exp; 66 | if (exp > 0 && exp < NEGATIVE_POWERS.length) { // if exp==int.MIN_VALUE then changing the sign will overflow and keep the number negative - we need to check for exp > 0 to filter out this corner case 67 | return NEGATIVE_POWERS[exp]; 68 | } else { 69 | return 0; 70 | } 71 | } 72 | 73 | /** 74 | * Returns the 10 base logarithm of the number. 75 | * Unlike Math.log function this produces integer results with no decimal garbage. 76 | * @param val Positive value or zero. 77 | */ 78 | // eslint-disable-next-line max-lines-per-function 79 | export function log10(val: number): number { 80 | // Fast Log10() algorithm 81 | if (val > 1 && val < 1E16) { 82 | if (val < 1E8) { 83 | if (val < 1E4) { 84 | if (val < 1E2) { 85 | if (val < 1E1) { 86 | return 0; 87 | } else { 88 | return 1; 89 | } 90 | } else { 91 | if (val < 1E3) { 92 | return 2; 93 | } else { 94 | return 3; 95 | } 96 | } 97 | } else { 98 | if (val < 1E6) { 99 | if (val < 1E5) { 100 | return 4; 101 | } else { 102 | return 5; 103 | } 104 | } else { 105 | if (val < 1E7) { 106 | return 6; 107 | } else { 108 | return 7; 109 | } 110 | } 111 | } 112 | } else { 113 | if (val < 1E12) { 114 | if (val < 1E10) { 115 | if (val < 1E9) { 116 | return 8; 117 | } else { 118 | return 9; 119 | } 120 | } else { 121 | if (val < 1E11) { 122 | return 10; 123 | } else { 124 | return 11; 125 | } 126 | } 127 | } else { 128 | if (val < 1E14) { 129 | if (val < 1E13) { 130 | return 12; 131 | } else { 132 | return 13; 133 | } 134 | } else { 135 | if (val < 1E15) { 136 | return 14; 137 | } else { 138 | return 15; 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | if (val > 1E-16 && val < 1) { 146 | if (val < 1E-8) { 147 | if (val < 1E-12) { 148 | if (val < 1E-14) { 149 | if (val < 1E-15) { 150 | return -16; 151 | } else { 152 | return -15; 153 | } 154 | } else { 155 | if (val < 1E-13) { 156 | return -14; 157 | } else { 158 | return -13; 159 | } 160 | } 161 | } else { 162 | if (val < 1E-10) { 163 | if (val < 1E-11) { 164 | return -12; 165 | } else { 166 | return -11; 167 | } 168 | } else { 169 | if (val < 1E-9) { 170 | return -10; 171 | } else { 172 | return -9; 173 | } 174 | } 175 | } 176 | } else { 177 | if (val < 1E-4) { 178 | if (val < 1E-6) { 179 | if (val < 1E-7) { 180 | return -8; 181 | } else { 182 | return -7; 183 | } 184 | } else { 185 | if (val < 1E-5) { 186 | return -6; 187 | } else { 188 | return -5; 189 | } 190 | } 191 | } else { 192 | if (val < 1E-2) { 193 | if (val < 1E-3) { 194 | return -4; 195 | } else { 196 | return -3; 197 | } 198 | } else { 199 | if (val < 1E-1) { 200 | return -2; 201 | } else { 202 | return -1; 203 | } 204 | } 205 | } 206 | } 207 | } 208 | // JS Math provides only natural log function so we need to calc the 10 base logarithm: 209 | // logb(x) = logk(x)/logk(b); 210 | const log10 = Math.log(val) / LOG_E_10; 211 | return floorWithPrecision(log10); 212 | } 213 | 214 | /** 215 | * Returns a power of 10 representing precision of the number based on the number of meaningful decimal digits. 216 | * For example the precision of 56,263.3767 with the 6 meaningful decimal digit is 0.1. 217 | * @param x Value. 218 | * @param decimalDigits How many decimal digits are meaningfull. 219 | */ 220 | export function getPrecision(x: number, decimalDigits?: number): number { 221 | if (decimalDigits === undefined) { 222 | decimalDigits = DEFAULT_PRECISION_IN_DECIMAL_DIGITS; 223 | } 224 | 225 | if (!x || !isFinite(x)) { 226 | return undefined; 227 | } 228 | 229 | const exp = log10(Math.abs(x)); 230 | 231 | if (exp < MIN_EXP) { 232 | return 0; 233 | } 234 | const precisionExp = Math.max(exp - decimalDigits, -NEGATIVE_POWERS.length + 1); 235 | return pow10(precisionExp); 236 | } 237 | 238 | /** 239 | * Checks if a delta between 2 numbers is less than provided precision. 240 | * @param x One value. 241 | * @param y Another value. 242 | * @param precision Precision value. 243 | */ 244 | export function equalWithPrecision(x: number, y: number, precision?: number): boolean { 245 | precision = detectPrecision(precision, x, y); 246 | 247 | return x === y || Math.abs(x - y) < precision; 248 | } 249 | 250 | /** 251 | * Checks if a first value is less than another taking 252 | * into account the loose precision based equality. 253 | * @param x One value. 254 | * @param y Another value. 255 | * @param precision Precision value. 256 | */ 257 | export function lessWithPrecision(x: number, y: number, precision?: number): boolean { 258 | precision = detectPrecision(precision, x, y); 259 | 260 | return x < y && Math.abs(x - y) > precision; 261 | } 262 | 263 | /** 264 | * Checks if a first value is less or equal than another taking 265 | * into account the loose precision based equality. 266 | * @param x One value. 267 | * @param y Another value. 268 | * @param precision Precision value. 269 | */ 270 | export function lessOrEqualWithPrecision(x: number, y: number, precision?: number): boolean { 271 | precision = detectPrecision(precision, x, y); 272 | 273 | return x < y || Math.abs(x - y) < precision; 274 | } 275 | 276 | /** 277 | * Checks if a first value is greater than another taking 278 | * into account the loose precision based equality. 279 | * @param x One value. 280 | * @param y Another value. 281 | * @param precision Precision value. 282 | */ 283 | export function greaterWithPrecision(x: number, y: number, precision?: number): boolean { 284 | precision = detectPrecision(precision, x, y); 285 | 286 | return x > y && Math.abs(x - y) > precision; 287 | } 288 | 289 | /** 290 | * Checks if a first value is greater or equal to another taking 291 | * into account the loose precision based equality. 292 | * @param x One value. 293 | * @param y Another value. 294 | * @param precision Precision value. 295 | */ 296 | export function greaterOrEqualWithPrecision(x: number, y: number, precision?: number): boolean { 297 | precision = detectPrecision(precision, x, y); 298 | 299 | return x > y || Math.abs(x - y) < precision; 300 | } 301 | 302 | /** 303 | * Floors the number unless it's withing the precision distance from the higher int. 304 | * @param x One value. 305 | * @param precision Precision value. 306 | */ 307 | export function floorWithPrecision(x: number, precision?: number): number { 308 | precision = precision != null ? precision : DEFAULT_PRECISION; 309 | 310 | const roundX = Math.round(x); 311 | if (Math.abs(x - roundX) < precision) { 312 | return roundX; 313 | } else { 314 | return Math.floor(x); 315 | } 316 | } 317 | 318 | /** 319 | * Ceils the number unless it's withing the precision distance from the lower int. 320 | * @param x One value. 321 | * @param precision Precision value. 322 | */ 323 | export function ceilWithPrecision(x: number, precision?: number): number { 324 | precision = detectPrecision(precision, DEFAULT_PRECISION); 325 | 326 | const roundX = Math.round(x); 327 | if (Math.abs(x - roundX) < precision) { 328 | return roundX; 329 | } else { 330 | return Math.ceil(x); 331 | } 332 | } 333 | 334 | /** 335 | * Floors the number to the provided precision. 336 | * For example 234,578 floored to 1,000 precision is 234,000. 337 | * @param x One value. 338 | * @param precision Precision value. 339 | */ 340 | export function floorToPrecision(x: number, precision?: number): number { 341 | precision = detectPrecision(precision, DEFAULT_PRECISION); 342 | 343 | if (precision === 0 || x === 0) { 344 | return x; 345 | } 346 | // Precision must be a Power of 10 347 | return Math.floor(x / precision) * precision; 348 | } 349 | 350 | /** 351 | * Ceils the number to the provided precision. 352 | * For example 234,578 floored to 1,000 precision is 235,000. 353 | * @param x One value. 354 | * @param precision Precision value. 355 | */ 356 | export function ceilToPrecision(x: number, precision?: number): number { 357 | precision = detectPrecision(precision, DEFAULT_PRECISION); 358 | 359 | if (precision === 0 || x === 0) { 360 | return x; 361 | } 362 | // Precision must be a Power of 10 363 | return Math.ceil(x / precision) * precision; 364 | } 365 | 366 | /** 367 | * Rounds the number to the provided precision. 368 | * For example 234,578 floored to 1,000 precision is 235,000. 369 | * @param x One value. 370 | * @param precision Precision value. 371 | */ 372 | export function roundToPrecision(x: number, precision?: number): number { 373 | precision = detectPrecision(precision, DEFAULT_PRECISION); 374 | 375 | if (precision === 0 || x === 0) { 376 | return x; 377 | } 378 | 379 | // Precision must be a Power of 10 380 | let result = Math.round(x / precision) * precision; 381 | const decimalDigits = Math.round(log10(Math.abs(x)) - log10(precision)) + 1; 382 | if (decimalDigits > 0 && decimalDigits < 16) { 383 | result = parseFloat(result.toPrecision(decimalDigits)); 384 | } 385 | 386 | return result; 387 | } 388 | 389 | /** 390 | * Returns the value making sure that it's restricted to the provided range. 391 | * @param x One value. 392 | * @param min Range min boundary. 393 | * @param max Range max boundary. 394 | */ 395 | export function ensureInRange(x: number, min: number, max: number): number { 396 | if (x === undefined || x === null) { 397 | return x; 398 | } 399 | if (x < min) { 400 | return min; 401 | } 402 | if (x > max) { 403 | return max; 404 | } 405 | return x; 406 | } 407 | 408 | /** 409 | * Rounds the value - this method is actually faster than Math.round - used in the graphics utils. 410 | * @param x Value to round. 411 | */ 412 | export function round(x: number): number { 413 | return (0.5 + x) << 0; 414 | } 415 | 416 | /** 417 | * Projects the value from the source range into the target range. 418 | * @param value Value to project. 419 | * @param fromMin Minimum of the source range. 420 | * @param toMin Minimum of the target range. 421 | * @param toMax Maximum of the target range. 422 | */ 423 | export function project(value: number, fromMin: number, fromSize: number, toMin: number, toSize: number): number { 424 | if (fromSize === 0 || toSize === 0) { 425 | if (fromMin <= value && value <= fromMin + fromSize) { 426 | return toMin; 427 | } else { 428 | return NaN; 429 | } 430 | } 431 | const relativeX = (value - fromMin) / fromSize; 432 | const projectedX = toMin + relativeX * toSize; 433 | return projectedX; 434 | } 435 | 436 | /** 437 | * Removes decimal noise. 438 | * @param value Value to be processed. 439 | */ 440 | export function removeDecimalNoise(value: number): number { 441 | return roundToPrecision(value, getPrecision(value)); 442 | } 443 | 444 | /** 445 | * Checks whether the number is integer. 446 | * @param value Value to be checked. 447 | */ 448 | export function isInteger(value: number): boolean { 449 | return value !== null && value % 1 === 0; 450 | } 451 | 452 | /** 453 | * Dividing by increment will give us count of increments 454 | * Round out the rough edges into even integer 455 | * Multiply back by increment to get rounded value 456 | * e.g. Rounder.toIncrement(0.647291, 0.05) => 0.65 457 | * @param value - value to round to nearest increment 458 | * @param increment - smallest increment to round toward 459 | */ 460 | export function toIncrement(value: number, increment: number): number { 461 | return Math.round(value / increment) * increment; 462 | } 463 | 464 | /** 465 | * Overrides the given precision with defaults if necessary. Exported only for tests 466 | * 467 | * precision defined returns precision 468 | * x defined with y undefined returns twelve digits of precision based on x 469 | * x defined but zero with y defined; returns twelve digits of precision based on y 470 | * x and y defined retursn twelve digits of precision based on the minimum of the two 471 | * if no applicable precision is found based on those (such as x and y being zero), the default precision is used 472 | */ 473 | export function detectPrecision(precision: number, x: number, y?: number): number { 474 | if (precision !== undefined) { 475 | return precision; 476 | } 477 | let calculatedPrecision; 478 | if (!y) { 479 | calculatedPrecision = getPrecision(x); 480 | } 481 | else if (!x) { 482 | calculatedPrecision = getPrecision(y); 483 | } 484 | else { 485 | calculatedPrecision = getPrecision(Math.min(Math.abs(x), Math.abs(y))); 486 | } 487 | return calculatedPrecision || DEFAULT_PRECISION; 488 | } 489 | -------------------------------------------------------------------------------- /src/extensions/arrayExtensions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | export interface ArrayIdItems extends Array { 27 | withId(id: number): T; 28 | } 29 | 30 | export interface ArrayNamedItems extends Array { 31 | withName(name: string): T; 32 | } 33 | 34 | /** 35 | * Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. 36 | */ 37 | export interface IComparer { 38 | /** 39 | * Returns a signed number that indicates the relative values of x and y, as shown in the following table. 40 | * 41 | * Value | Meaning 42 | * ------------------|-------------------- 43 | * Less than zero | a is less than b 44 | * Zero | a equals b 45 | * Greater than zero | a is greater than b 46 | */ 47 | (a: T, b: T): number; 48 | } 49 | 50 | /** 51 | * Returns items that exist in target and other. 52 | */ 53 | export function intersect(target: T[], other: T[]): T[] { 54 | const result: T[] = []; 55 | for (let i = target.length - 1; i >= 0; --i) { 56 | if (other.indexOf(target[i]) !== -1) { 57 | result.push(target[i]); 58 | } 59 | } 60 | return result; 61 | } 62 | 63 | /** 64 | * Return elements exists in target but not exists in other. 65 | */ 66 | export function diff(target: T[], other: T[]): T[] { 67 | const result: T[] = []; 68 | for (let i = target.length - 1; i >= 0; --i) { 69 | const value: T = target[i]; 70 | if (other.indexOf(value) === -1) { 71 | result.push(value); 72 | } 73 | } 74 | return result; 75 | } 76 | 77 | /** 78 | * Return an array with only the distinct items in the source. 79 | */ 80 | export function distinct(source: T[]): T[] { 81 | const result: T[] = []; 82 | for (let i = 0, len = source.length; i < len; i++) { 83 | const value: T = source[i]; 84 | if (result.indexOf(value) === -1) { 85 | result.push(value); 86 | } 87 | } 88 | return result; 89 | } 90 | 91 | /** 92 | * Pushes content of source onto target, 93 | * for parts of course that do not already exist in target. 94 | */ 95 | export function union(target: T[], source: T[]): void { 96 | for (let i = 0, len = source.length; i < len; ++i) { 97 | unionSingle(target, source[i]); 98 | } 99 | } 100 | 101 | /** 102 | * Pushes value onto target, if value does not already exist in target. 103 | */ 104 | export function unionSingle(target: T[], value: T): void { 105 | if (target.indexOf(value) < 0) { 106 | target.push(value); 107 | } 108 | } 109 | 110 | /** 111 | * Returns an array with a range of items from source, 112 | * including the startIndex & endIndex. 113 | */ 114 | export function range(source: T[], startIndex: number, endIndex: number): T[] { 115 | 116 | 117 | const result: T[] = []; 118 | for (let i = startIndex; i <= endIndex; ++i) { 119 | result.push(source[i]); 120 | } 121 | return result; 122 | } 123 | 124 | /** 125 | * Returns an array that includes items from source, up to the specified count. 126 | */ 127 | export function take(source: T[], count: number): T[] { 128 | 129 | 130 | const result: T[] = []; 131 | for (let i = 0; i < count; ++i) { 132 | result.push(source[i]); 133 | } 134 | return result; 135 | } 136 | 137 | export function copy(source: T[]): T[] { 138 | 139 | 140 | return take(source, source.length); 141 | } 142 | 143 | /** 144 | * Returns a value indicating whether the arrays have the same values in the same sequence. 145 | */ 146 | export function sequenceEqual(left: T[], right: U[], comparison: (x: T, y: U) => boolean): boolean { 147 | // Normalize falsy to null 148 | if (!left) { left = null; } 149 | if (!right) { right = null; } 150 | 151 | // T can be same as U, and it is possible for left and right to be the same array object... 152 | if (left === right) { 153 | return true; 154 | } 155 | 156 | if (!!left !== !!right) { 157 | return false; 158 | } 159 | 160 | const len = left.length; 161 | if (len !== right.length) { 162 | return false; 163 | } 164 | 165 | let i = 0; 166 | while (i < len && comparison(left[i], right[i])) { 167 | ++i; 168 | } 169 | 170 | return i === len; 171 | } 172 | 173 | /** 174 | * Returns null if the specified array is empty. 175 | * Otherwise returns the specified array. 176 | */ 177 | export function emptyToNull(array: T[]): T[] { 178 | if (array && array.length === 0) { 179 | return null; 180 | } 181 | return array; 182 | } 183 | 184 | export function indexOf(array: T[], predicate: (T) => boolean): number { 185 | 186 | for (let i = 0, len = array.length; i < len; ++i) { 187 | if (predicate(array[i])) { 188 | return i; 189 | } 190 | } 191 | return -1; 192 | } 193 | 194 | /** 195 | * Returns a copy of the array rotated by the specified offset. 196 | */ 197 | export function rotate(array: T[], offset: number): T[] { 198 | if (offset === 0) 199 | return array.slice(); 200 | 201 | const rotated = array.slice(offset); 202 | Array.prototype.push.apply(rotated, array.slice(0, offset)); 203 | 204 | return rotated; 205 | } 206 | 207 | export function createWithId(): ArrayIdItems { 208 | return extendWithId([]); 209 | } 210 | 211 | export function extendWithId(array: { id: number }[]): ArrayIdItems { 212 | const extended: ArrayIdItems = array; 213 | extended.withId = withId; 214 | 215 | return extended; 216 | } 217 | 218 | /** 219 | * Finds and returns the first item with a matching ID. 220 | */ 221 | export function findWithId(array: T[], id: number): T { 222 | for (let i = 0, len = array.length; i < len; i++) { 223 | const item = array[i]; 224 | if ((item).id === id) 225 | return item; 226 | } 227 | } 228 | 229 | function withId(id: number): T { 230 | return findWithId(this, id); 231 | } 232 | 233 | export function createWithName(): ArrayNamedItems { 234 | return extendWithName([]); 235 | } 236 | 237 | export function extendWithName(array: { name: string }[]): ArrayNamedItems { 238 | 239 | 240 | const extended: ArrayNamedItems = array; 241 | extended.withName = withName; 242 | 243 | return extended; 244 | } 245 | 246 | export function findItemWithName(array: T[], name: string): T { 247 | const index = indexWithName(array, name); 248 | if (index >= 0) 249 | return array[index]; 250 | } 251 | 252 | export function indexWithName(array: T[], name: string): number { 253 | for (let i = 0, len = array.length; i < len; i++) { 254 | const item = array[i]; 255 | if ((item).name === name) 256 | return i; 257 | } 258 | 259 | return -1; 260 | } 261 | 262 | /** 263 | * Inserts a number in sorted order into a list of numbers already in sorted order. 264 | * @returns True if the item was added, false if it already existed. 265 | */ 266 | export function insertSorted(list: number[], value: number): boolean { 267 | 268 | 269 | const len = list.length; 270 | 271 | // NOTE: iterate backwards because incoming values tend to be sorted already. 272 | for (let i = len - 1; i >= 0; i--) { 273 | const diff = list[i] - value; 274 | 275 | if (diff === 0) 276 | return false; 277 | 278 | if (diff > 0) 279 | continue; 280 | 281 | // diff < 0 282 | list.splice(i + 1, 0, value); 283 | return true; 284 | } 285 | 286 | list.unshift(value); 287 | return true; 288 | } 289 | 290 | /** 291 | * Removes the first occurrence of a value from a list if it exists. 292 | * @returns True if the value was removed, false if it did not exist in the list. 293 | */ 294 | export function removeFirst(list: T[], value: T): boolean { 295 | const index = list.indexOf(value); 296 | if (index < 0) 297 | return false; 298 | 299 | list.splice(index, 1); 300 | 301 | return true; 302 | } 303 | 304 | /** 305 | * Finds and returns the first item with a matching name. 306 | */ 307 | function withName(name: string): T { 308 | return findItemWithName(this, name); 309 | } 310 | 311 | /** 312 | * Deletes all items from the array. 313 | */ 314 | export function clear(array: any[]): void { 315 | if (!array) 316 | return; 317 | 318 | while (array.length > 0) 319 | array.pop(); 320 | } 321 | 322 | export function isUndefinedOrEmpty(array: any[]): boolean { 323 | if (!array || array.length === 0) { 324 | return true; 325 | } 326 | return false; 327 | } 328 | 329 | export function swap(array: T[], firstIndex: number, secondIndex: number): void { 330 | const temp = array[firstIndex]; 331 | array[firstIndex] = array[secondIndex]; 332 | array[secondIndex] = temp; 333 | } 334 | 335 | export function isInArray(array: T[], lookupItem: T, compareCallback: (item1: T, item2: T) => boolean): boolean { 336 | return array.some(item => compareCallback(item, lookupItem)); 337 | } 338 | 339 | /** Checks if the given object is an Array, and looking all the way up the prototype chain. */ 340 | export function isArrayOrInheritedArray(obj): obj is Array { 341 | 342 | let nextPrototype = obj; 343 | while (nextPrototype != null) { 344 | if (Array.isArray(nextPrototype)) 345 | return true; 346 | 347 | nextPrototype = Object.getPrototypeOf(nextPrototype); 348 | } 349 | 350 | return false; 351 | } 352 | 353 | /** 354 | * Returns true if the specified values array is sorted in an order as determined by the specified compareFunction. 355 | */ 356 | export function isSorted(values: T[], compareFunction: IComparer): boolean { 357 | 358 | const ilen = values.length; 359 | if (ilen >= 2) { 360 | for (let i = 1; i < ilen; i++) { 361 | if (compareFunction(values[i - 1], values[i]) > 0) { 362 | return false; 363 | } 364 | } 365 | } 366 | 367 | return true; 368 | } 369 | 370 | /** 371 | * Returns true if the specified number values array is sorted in ascending order 372 | * (or descending order if the specified descendingOrder is truthy). 373 | */ 374 | export function isSortedNumeric(values: number[], descendingOrder?: boolean): boolean { 375 | 376 | const compareFunction: IComparer = descendingOrder ? 377 | (a, b) => b - a : 378 | (a, b) => a - b; 379 | 380 | return isSorted(values, compareFunction); 381 | } 382 | 383 | /** 384 | * Ensures that the given T || T[] is in array form, either returning the array or 385 | * converting single items into an array of length one. 386 | */ 387 | export function ensureArray(value: T | T[]): T[] { 388 | if (Array.isArray(value)) { 389 | return value; 390 | } 391 | return [value]; 392 | } 393 | -------------------------------------------------------------------------------- /src/extensions/enumExtensions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | // NOTE: this file includes standalone utilities that should have no dependencies on external libraries, including jQuery. 27 | 28 | import { isInteger } from "../double"; 29 | 30 | /** 31 | * Extensions for Enumerations. 32 | */ 33 | /** 34 | * Gets a value indicating whether the value has the bit flags set. 35 | */ 36 | export function hasFlag(value: number, flag: number): boolean { 37 | return (value & flag) === flag; 38 | } 39 | 40 | /** 41 | * Sets a value of a flag without modifying any other flags. 42 | */ 43 | export function setFlag(value: number, flag: number): number { 44 | return value |= flag; 45 | } 46 | 47 | /** 48 | * Resets a value of a flag without modifying any other flags. 49 | */ 50 | export function resetFlag(value: number, flag: number): number { 51 | return value &= ~flag; 52 | } 53 | 54 | /** 55 | * According to the TypeScript Handbook, this is safe to do. 56 | */ 57 | export function toString(enumType: any, value: number): string { 58 | return enumType[value]; 59 | } 60 | 61 | /** 62 | * Returns the number of 1's in the specified value that is a set of binary bit flags. 63 | */ 64 | export function getBitCount(value: number): number { 65 | if (!isInteger(value)) 66 | return 0; 67 | 68 | let bitCount = 0; 69 | let shiftingValue = value; 70 | while (shiftingValue !== 0) { 71 | if ((shiftingValue & 1) === 1) { 72 | bitCount++; 73 | } 74 | 75 | shiftingValue = shiftingValue >>> 1; 76 | } 77 | return bitCount; 78 | } 79 | -------------------------------------------------------------------------------- /src/extensions/logicExtensions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | // NOTE: this file includes standalone utilities that should have no dependencies on external libraries, including jQuery. 27 | export function XOR(a: boolean, b: boolean): boolean { 28 | return (a || b) && !(a && b); 29 | } 30 | -------------------------------------------------------------------------------- /src/extensions/regExpExtensions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | // NOTE: this file includes standalone utilities that should have no dependencies on external libraries, including jQuery. 27 | 28 | /** 29 | * Runs exec on regex starting from 0 index 30 | * This is the expected behavior but RegExp actually remember 31 | * the last index they stopped at (found match at) and will 32 | * return unexpected results when run in sequence. 33 | * @param regex - regular expression object 34 | * @param value - string to search wiht regex 35 | * @param start - index within value to start regex 36 | */ 37 | export function run(regex: RegExp, value: string, start?: number): RegExpExecArray { 38 | regex.lastIndex = start || 0; 39 | return regex.exec(value); 40 | } 41 | -------------------------------------------------------------------------------- /src/extensions/stringExtensions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | /** 27 | * Extensions to String class. 28 | */ 29 | /** 30 | * Checks if a string ends with a sub-string. 31 | */ 32 | export function endsWith(str: string, suffix: string): boolean { 33 | return str.indexOf(suffix, str.length - suffix.length) !== -1; 34 | } 35 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as arrayExtensions from "./extensions/arrayExtensions"; 2 | import * as enumExtensions from "./extensions/enumExtensions"; 3 | import * as logicExtensions from "./extensions/logicExtensions"; 4 | import * as regExpExtensions from "./extensions/regExpExtensions"; 5 | import * as stringExtensions from "./extensions/stringExtensions"; 6 | import * as numericSequence from "./numericSequence/numericSequence"; 7 | import * as numericSequenceRange from "./numericSequence/numericSequenceRange"; 8 | import * as double from "./double"; 9 | import * as jsonComparer from "./jsonComparer"; 10 | import * as pixelConverter from "./pixelConverter"; 11 | import * as prototype from "./prototype"; 12 | import * as textSizeDefaults from "./textSizeDefaults"; 13 | import * as valueType from "./valueType"; 14 | 15 | export { 16 | arrayExtensions, enumExtensions, logicExtensions, regExpExtensions, 17 | stringExtensions, numericSequence, numericSequenceRange, double, 18 | jsonComparer, pixelConverter, prototype, textSizeDefaults, valueType 19 | }; -------------------------------------------------------------------------------- /src/jsonComparer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | // NOTE: this file includes standalone utilities that should have no dependencies on external libraries, including jQuery. 27 | 28 | /** 29 | * Performs JSON-style comparison of two objects. 30 | */ 31 | export function equals(x: T, y: T): boolean { 32 | if (x === y) 33 | return true; 34 | 35 | return JSON.stringify(x) === JSON.stringify(y); 36 | } 37 | -------------------------------------------------------------------------------- /src/numericSequence/numericSequence.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | import * as Double from "../double"; 28 | import { NumericSequenceRange } from "./numericSequenceRange"; 29 | 30 | export class NumericSequence { 31 | private static MIN_COUNT: number = 1; 32 | private static MAX_COUNT: number = 1000; 33 | 34 | private maxAllowedMargin: number; 35 | private canExtendMin: boolean; 36 | private canExtendMax: boolean; 37 | 38 | public interval: number; 39 | public intervalOffset: number; 40 | public min: number; 41 | public max: number; 42 | public precision: number; 43 | public sequence: number[]; 44 | 45 | // eslint-disable-next-line max-lines-per-function 46 | public static calculate(range: NumericSequenceRange, expectedCount: number, maxAllowedMargin?: number, minPower?: number, useZeroRefPoint?: boolean, steps?: number[]): NumericSequence { 47 | const result = new NumericSequence(); 48 | 49 | if (expectedCount === undefined) 50 | expectedCount = 10; 51 | else 52 | expectedCount = Double.ensureInRange(expectedCount, NumericSequence.MIN_COUNT, NumericSequence.MAX_COUNT); 53 | if (minPower === undefined) 54 | minPower = Double.MIN_EXP; 55 | if (useZeroRefPoint === undefined) 56 | useZeroRefPoint = false; 57 | if (maxAllowedMargin === undefined) 58 | maxAllowedMargin = 1; 59 | if (steps === undefined) 60 | steps = [1, 2, 5]; 61 | 62 | // Handle single stop case 63 | if (range.forcedSingleStop) { 64 | result.interval = range.getSize(); 65 | result.intervalOffset = result.interval - (range.forcedSingleStop - range.min); 66 | result.min = range.min; 67 | result.max = range.max; 68 | result.sequence = [range.forcedSingleStop]; 69 | return result; 70 | } 71 | 72 | let interval = 0; 73 | let min = 0; 74 | let max = 9; 75 | const canExtendMin = maxAllowedMargin > 0 && !range.hasFixedMin; 76 | const canExtendMax = maxAllowedMargin > 0 && !range.hasFixedMax; 77 | 78 | const size = range.getSize(); 79 | let exp = Double.log10(size); 80 | 81 | // Account for Exp of steps 82 | const stepExp = Double.log10(steps[0]); 83 | exp = exp - stepExp; 84 | 85 | // Account for MaxCount 86 | const expectedCountExp = Double.log10(expectedCount); 87 | exp = exp - expectedCountExp; 88 | 89 | // Account for MinPower 90 | exp = Math.max(exp, minPower - stepExp + 1); 91 | let count = undefined; 92 | // Create array of "good looking" numbers 93 | if (interval !== 0) { 94 | // If explicit interval is defined - use it instead of the steps array. 95 | const power = Double.pow10(exp); 96 | const roundMin = Double.floorToPrecision(range.min, power); 97 | const roundMax = Double.ceilToPrecision(range.max, power); 98 | const roundRange = NumericSequenceRange.calculateFixedRange(roundMin, roundMax); 99 | 100 | roundRange.shrinkByStep(range, interval); 101 | min = roundRange.min; 102 | max = roundRange.max; 103 | count = Math.floor(roundRange.getSize() / interval); 104 | } 105 | else { 106 | // No interval defined -> find optimal interval 107 | let dexp; 108 | for (dexp = 0; dexp < 3; dexp++) { 109 | const e = exp + dexp; 110 | const power = Double.pow10(e); 111 | 112 | const roundMin = Double.floorToPrecision(range.min, power); 113 | const roundMax = Double.ceilToPrecision(range.max, power); 114 | 115 | // Go throught the steps array looking for the smallest step that produces the right interval count. 116 | const stepsCount = steps.length; 117 | const stepPower = Double.pow10(e - 1); 118 | for (let i = 0; i < stepsCount; i++) { 119 | const step = steps[i] * stepPower; 120 | const roundRange = NumericSequenceRange.calculateFixedRange(roundMin, roundMax, useZeroRefPoint); 121 | roundRange.shrinkByStep(range, step); 122 | 123 | // If the range is based on Data we might need to extend it to provide nice data margins. 124 | if (canExtendMin && range.min === roundRange.min && maxAllowedMargin >= 1) 125 | roundRange.min -= step; 126 | if (canExtendMax && range.max === roundRange.max && maxAllowedMargin >= 1) 127 | roundRange.max += step; 128 | 129 | // Count the intervals 130 | count = Double.ceilWithPrecision(roundRange.getSize() / step, Double.DEFAULT_PRECISION); 131 | 132 | if (count <= expectedCount || (dexp === 2 && i === stepsCount - 1) || (expectedCount === 1 && count === 2 && (step > range.getSize() || (range.min < 0 && range.max > 0 && step * 2 >= range.getSize())))) { 133 | interval = step; 134 | min = roundRange.min; 135 | max = roundRange.max; 136 | break; 137 | } 138 | } 139 | 140 | // Increase the scale power until the interval is found 141 | if (interval !== 0) 142 | break; 143 | } 144 | } 145 | 146 | // Avoid extreme count cases (>1000 ticks) 147 | if (count > expectedCount * 32 || count > NumericSequence.MAX_COUNT) { 148 | count = Math.min(expectedCount * 32, NumericSequence.MAX_COUNT); 149 | interval = (max - min) / count; 150 | } 151 | 152 | result.min = min; 153 | result.max = max; 154 | result.interval = interval; 155 | result.intervalOffset = min - range.min; 156 | result.maxAllowedMargin = maxAllowedMargin; 157 | result.canExtendMin = canExtendMin; 158 | result.canExtendMax = canExtendMax; 159 | 160 | // Fill in the Sequence 161 | const precision = Double.getPrecision(interval, 0); 162 | result.precision = precision; 163 | 164 | const sequence = []; 165 | 166 | let x = Double.roundToPrecision(min, precision); 167 | sequence.push(x); 168 | for (let i = 0; i < count; i++) { 169 | x = Double.roundToPrecision(x + interval, precision); 170 | sequence.push(x); 171 | } 172 | 173 | result.sequence = sequence; 174 | 175 | result.trimMinMax(range.min, range.max); 176 | 177 | return result; 178 | } 179 | 180 | /** 181 | * Calculates the sequence of int numbers which are mapped to the multiples of the units grid. 182 | * @min - The minimum of the range. 183 | * @max - The maximum of the range. 184 | * @maxCount - The max count of intervals. 185 | * @steps - array of intervals. 186 | */ 187 | public static calculateUnits(min: number, max: number, maxCount: number, steps: number[]): NumericSequence { 188 | // Initialization actions 189 | maxCount = Double.ensureInRange(maxCount, NumericSequence.MIN_COUNT, NumericSequence.MAX_COUNT); 190 | if (min === max) { 191 | max = min + 1; 192 | } 193 | let stepCount = 0; 194 | let step = 0; 195 | 196 | // Calculate step 197 | for (let i = 0; i < steps.length; i++) { 198 | step = steps[i]; 199 | const maxStepCount = Double.ceilWithPrecision(max / step); 200 | const minStepCount = Double.floorWithPrecision(min / step); 201 | stepCount = maxStepCount - minStepCount; 202 | 203 | if (stepCount <= maxCount) { 204 | break; 205 | } 206 | } 207 | 208 | // Calculate the offset 209 | let offset = -min; 210 | offset = offset % step; 211 | 212 | // Create sequence 213 | const result = new NumericSequence(); 214 | result.sequence = []; 215 | for (let x = min + offset; ; x += step) { 216 | result.sequence.push(x); 217 | if (x >= max) 218 | break; 219 | } 220 | result.interval = step; 221 | result.intervalOffset = offset; 222 | result.min = result.sequence[0]; 223 | result.max = result.sequence[result.sequence.length - 1]; 224 | return result; 225 | } 226 | 227 | public trimMinMax(min: number, max: number): void { 228 | const minMargin = (min - this.min) / this.interval; 229 | const maxMargin = (this.max - max) / this.interval; 230 | const marginPrecision = 0.001; 231 | 232 | if (!this.canExtendMin || (minMargin > this.maxAllowedMargin && minMargin > marginPrecision)) { 233 | this.min = min; 234 | } 235 | 236 | if (!this.canExtendMax || (maxMargin > this.maxAllowedMargin && maxMargin > marginPrecision)) { 237 | this.max = max; 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/numericSequence/numericSequenceRange.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | import * as Double from "../double"; 28 | 29 | export class NumericSequenceRange { 30 | private static DEFAULT_MAX: number = 10; 31 | private static MIN_SUPPORTED_DOUBLE = -1E307; 32 | private static MAX_SUPPORTED_DOUBLE = 1E307; 33 | 34 | public min: number; 35 | public max: number; 36 | public includeZero: boolean; 37 | public forcedSingleStop: number; 38 | public hasDataRange: boolean; 39 | public hasFixedMin: boolean; 40 | public hasFixedMax: boolean; 41 | 42 | private _ensureIncludeZero(): void { 43 | if (this.includeZero) { 44 | // fixed min and max has higher priority than includeZero 45 | if (this.min > 0 && !this.hasFixedMin) { 46 | this.min = 0; 47 | } 48 | if (this.max < 0 && !this.hasFixedMax) { 49 | this.max = 0; 50 | } 51 | } 52 | } 53 | 54 | private _ensureNotEmpty(): void { 55 | if (this.min === this.max) { 56 | if (!this.min) { 57 | this.min = 0; 58 | this.max = NumericSequenceRange.DEFAULT_MAX; 59 | this.hasFixedMin = true; 60 | this.hasFixedMax = true; 61 | } else { 62 | // We are dealing with a single data value (includeZero is not set) 63 | // In order to fix the range we need to extend it in both directions by half of the interval. 64 | // Interval is calculated based on the number: 65 | // 1. Integers below 10,000 are extended by 0.5: so the [2006-2006] empty range is extended to [2005.5-2006.5] range and the ForsedSingleStop=2006 66 | // 2. Other numbers are extended by half of their power: [700,001-700,001] => [650,001-750,001] and the ForsedSingleStop=null as we want the intervals to be calculated to cover the range. 67 | const value = this.min; 68 | const exp = Double.log10(Math.abs(value)); 69 | let step: number; 70 | if (exp >= 0 && exp < 4) { 71 | step = 0.5; 72 | this.forcedSingleStop = value; 73 | } else { 74 | step = Double.pow10(exp) / 2; 75 | this.forcedSingleStop = null; 76 | } 77 | this.min = value - step; 78 | this.max = value + step; 79 | } 80 | } 81 | } 82 | 83 | private _ensureDirection() { 84 | if (this.min > this.max) { 85 | const temp = this.min; 86 | this.min = this.max; 87 | this.max = temp; 88 | } 89 | } 90 | 91 | public getSize(): number { 92 | return this.max - this.min; 93 | } 94 | 95 | public shrinkByStep(range: NumericSequenceRange, step: number) { 96 | let oldCount = this.min / step; 97 | let newCount = range.min / step; 98 | let deltaCount = Math.floor(newCount - oldCount); 99 | this.min += deltaCount * step; 100 | 101 | oldCount = this.max / step; 102 | newCount = range.max / step; 103 | deltaCount = Math.ceil(newCount - oldCount); 104 | this.max += deltaCount * step; 105 | } 106 | 107 | public static calculate(dataMin: number, dataMax: number, fixedMin?: number, fixedMax?: number, includeZero?: boolean): NumericSequenceRange { 108 | const result = new NumericSequenceRange(); 109 | result.includeZero = includeZero ? true : false; 110 | result.hasDataRange = hasValue(dataMin) && hasValue(dataMax); 111 | result.hasFixedMin = hasValue(fixedMin); 112 | result.hasFixedMax = hasValue(fixedMax); 113 | 114 | dataMin = Double.ensureInRange(dataMin, NumericSequenceRange.MIN_SUPPORTED_DOUBLE, NumericSequenceRange.MAX_SUPPORTED_DOUBLE); 115 | dataMax = Double.ensureInRange(dataMax, NumericSequenceRange.MIN_SUPPORTED_DOUBLE, NumericSequenceRange.MAX_SUPPORTED_DOUBLE); 116 | 117 | // Calculate the range using the min, max, dataRange 118 | if (result.hasFixedMin && result.hasFixedMax) { 119 | result.min = fixedMin; 120 | result.max = fixedMax; 121 | } else if (result.hasFixedMin) { 122 | result.min = fixedMin; 123 | result.max = dataMax > fixedMin ? dataMax : fixedMin; 124 | } else if (result.hasFixedMax) { 125 | result.min = dataMin < fixedMax ? dataMin : fixedMax; 126 | result.max = fixedMax; 127 | } else if (result.hasDataRange) { 128 | result.min = dataMin; 129 | result.max = dataMax; 130 | } else { 131 | result.min = 0; 132 | result.max = 0; 133 | } 134 | 135 | result._ensureIncludeZero(); 136 | result._ensureNotEmpty(); 137 | result._ensureDirection(); 138 | 139 | if (result.min === 0) { 140 | result.hasFixedMin = true; // If the range starts from zero we should prevent extending the intervals into the negative range 141 | } else if (result.max === 0) { 142 | result.hasFixedMax = true; // If the range ends at zero we should prevent extending the intervals into the positive range 143 | } 144 | 145 | return result; 146 | } 147 | 148 | public static calculateDataRange(dataMin: number, dataMax: number, includeZero?: boolean): NumericSequenceRange { 149 | if (!hasValue(dataMin) || !hasValue(dataMax)) { 150 | return NumericSequenceRange.calculateFixedRange(0, NumericSequenceRange.DEFAULT_MAX); 151 | } else { 152 | return NumericSequenceRange.calculate(dataMin, dataMax, null, null, includeZero); 153 | } 154 | } 155 | 156 | public static calculateFixedRange(fixedMin: number, fixedMax: number, includeZero?: boolean): NumericSequenceRange { 157 | const result = new NumericSequenceRange(); 158 | result.hasDataRange = false; 159 | result.includeZero = includeZero; 160 | result.min = fixedMin; 161 | result.max = fixedMax; 162 | result._ensureIncludeZero(); 163 | result._ensureNotEmpty(); 164 | result._ensureDirection(); 165 | result.hasFixedMin = true; 166 | result.hasFixedMax = true; 167 | 168 | return result; 169 | } 170 | } 171 | 172 | /** Note: Exported for testability */ 173 | export function hasValue(value: any): boolean { 174 | return value !== undefined && value !== null; 175 | } -------------------------------------------------------------------------------- /src/pixelConverter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | const PxPtRatio: number = 4 / 3; 27 | const PixelString: string = "px"; 28 | 29 | /** 30 | * Appends 'px' to the end of number value for use as pixel string in styles 31 | */ 32 | export function toString(px: number): string { 33 | return px + PixelString; 34 | } 35 | 36 | /** 37 | * Converts point value (pt) to pixels 38 | * Returns a string for font-size property 39 | * e.g. fromPoint(8) => '24px' 40 | */ 41 | export function fromPoint(pt: number): string { 42 | return toString(fromPointToPixel(pt)); 43 | } 44 | 45 | /** 46 | * Converts point value (pt) to pixels 47 | * Returns a number for font-size property 48 | * e.g. fromPoint(8) => 24px 49 | */ 50 | export function fromPointToPixel(pt: number): number { 51 | return (PxPtRatio * pt); 52 | } 53 | 54 | /** 55 | * Converts pixel value (px) to pt 56 | * e.g. toPoint(24) => 8 57 | */ 58 | export function toPoint(px: number): number { 59 | return px / PxPtRatio; 60 | } 61 | -------------------------------------------------------------------------------- /src/prototype.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | /** 27 | * Returns a new object with the provided obj as its prototype. 28 | */ 29 | export function inherit(obj: T, extension?: (inherited: T) => void): T { 30 | // eslint-disable-next-line @typescript-eslint/no-empty-function 31 | function wrapCtor(): void { } 32 | wrapCtor.prototype = obj; 33 | 34 | const inherited = new wrapCtor(); 35 | 36 | if (extension) 37 | extension(inherited); 38 | 39 | return inherited; 40 | } 41 | 42 | /** 43 | * Returns a new object with the provided obj as its prototype 44 | * if, and only if, the prototype has not been previously set 45 | */ 46 | export function inheritSingle(obj: T): T { 47 | const proto = Object.getPrototypeOf(obj); 48 | if (proto === Object.prototype || proto === Array.prototype) 49 | obj = inherit(obj); 50 | 51 | return obj; 52 | } 53 | 54 | /** 55 | * Uses the provided callback function to selectively replace contents in the provided array. 56 | * @return A new array with those values overriden 57 | * or undefined if no overrides are necessary. 58 | */ 59 | export function overrideArray(prototype: TArray, override: (T) => T): TArray { 60 | if (!prototype) 61 | return; 62 | 63 | let overwritten: TArray; 64 | 65 | for (let i = 0, len = (prototype).length; i < len; i++) { 66 | const value = override(prototype[i]); 67 | if (value) { 68 | if (!overwritten) 69 | overwritten = inherit(prototype); 70 | 71 | overwritten[i] = value; 72 | } 73 | } 74 | 75 | return overwritten; 76 | } 77 | -------------------------------------------------------------------------------- /src/textSizeDefaults.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | // NOTE: this file includes standalone utilities that should have no dependencies on external libraries, including jQuery. 27 | 28 | /** 29 | * Values are in terms of 'pt' 30 | * Convert to pixels using PixelConverter.fromPoint 31 | */ 32 | /** 33 | * Stored in terms of 'pt' 34 | * Convert to pixels using PixelConverter.fromPoint 35 | */ 36 | export const TextSizeMin: number = 8; 37 | 38 | /** 39 | * Stored in terms of 'pt' 40 | * Convert to pixels using PixelConverter.fromPoint 41 | */ 42 | export const TextSizeMax: number = 40; 43 | 44 | const TextSizeRange: number = TextSizeMax - TextSizeMin; 45 | 46 | /** 47 | * Returns the percentage of this value relative to the TextSizeMax 48 | * @param textSize - should be given in terms of 'pt' 49 | */ 50 | export function getScale(textSize: number) { 51 | return (textSize - TextSizeMin) / TextSizeRange; 52 | } 53 | -------------------------------------------------------------------------------- /src/valueType.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | import powerbi from "powerbi-visuals-api"; 28 | import ValueTypeDescriptor = powerbi.ValueTypeDescriptor; 29 | import IEnumType = powerbi.IEnumType; 30 | import ScriptTypeDescriptor = powerbi.ScriptTypeDescriptor; 31 | import TemporalTypeDescriptor = powerbi.TemporalTypeDescriptor; 32 | import GeographyTypeDescriptor = powerbi.GeographyTypeDescriptor; 33 | import MiscellaneousTypeDescriptor = powerbi.MiscellaneousTypeDescriptor; 34 | import FormattingTypeDescriptor = powerbi.FormattingTypeDescriptor; 35 | // powerbi.extensibility.utils.type 36 | import * as EnumExtensions from "./extensions/enumExtensions"; 37 | import { equals } from "./jsonComparer"; 38 | 39 | export interface IValueTypeDescriptor extends ValueTypeDescriptor { 40 | extendedType?: ExtendedType; 41 | } 42 | 43 | /** Describes a data value type, including a primitive type and extended type if any (derived from data category). */ 44 | export class ValueType implements IValueTypeDescriptor { 45 | private static typeCache: { [id: string]: ValueType } = {}; 46 | 47 | private underlyingType: ExtendedType; 48 | private category: string; 49 | 50 | private temporalType: TemporalType; 51 | private geographyType: GeographyType; 52 | private miscType: MiscellaneousType; 53 | private formattingType: FormattingType; 54 | private enumType: IEnumType; 55 | private scriptingType: ScriptType; 56 | private variationTypes: ValueType[]; 57 | 58 | /** Do not call the ValueType constructor directly. Use the ValueType.fromXXX methods. */ 59 | constructor(underlyingType: ExtendedType, category?: string, enumType?: IEnumType, variantTypes?: ValueType[]) { 60 | this.underlyingType = underlyingType; 61 | this.category = category; 62 | 63 | if (EnumExtensions.hasFlag(underlyingType, ExtendedType.Temporal)) { 64 | this.temporalType = new TemporalType(underlyingType); 65 | } 66 | if (EnumExtensions.hasFlag(underlyingType, ExtendedType.Geography)) { 67 | this.geographyType = new GeographyType(underlyingType); 68 | } 69 | if (EnumExtensions.hasFlag(underlyingType, ExtendedType.Miscellaneous)) { 70 | this.miscType = new MiscellaneousType(underlyingType); 71 | } 72 | if (EnumExtensions.hasFlag(underlyingType, ExtendedType.Formatting)) { 73 | this.formattingType = new FormattingType(underlyingType); 74 | } 75 | if (EnumExtensions.hasFlag(underlyingType, ExtendedType.Enumeration)) { 76 | this.enumType = enumType; 77 | } 78 | if (EnumExtensions.hasFlag(underlyingType, ExtendedType.Scripting)) { 79 | this.scriptingType = new ScriptType(underlyingType); 80 | } 81 | if (EnumExtensions.hasFlag(underlyingType, ExtendedType.Variant)) { 82 | this.variationTypes = variantTypes; 83 | } 84 | } 85 | 86 | /** Creates or retrieves a ValueType object based on the specified ValueTypeDescriptor. */ 87 | public static fromDescriptor(descriptor: IValueTypeDescriptor): ValueType { 88 | descriptor = descriptor || {}; 89 | 90 | // Simplified primitive types 91 | if (descriptor.text) return ValueType.fromExtendedType(ExtendedType.Text); 92 | if (descriptor.integer) return ValueType.fromExtendedType(ExtendedType.Integer); 93 | if (descriptor.numeric) return ValueType.fromExtendedType(ExtendedType.Double); 94 | if (descriptor.bool) return ValueType.fromExtendedType(ExtendedType.Boolean); 95 | if (descriptor.dateTime) return ValueType.fromExtendedType(ExtendedType.DateTime); 96 | if (descriptor.duration) return ValueType.fromExtendedType(ExtendedType.Duration); 97 | if (descriptor.binary) return ValueType.fromExtendedType(ExtendedType.Binary); 98 | if (descriptor.none) return ValueType.fromExtendedType(ExtendedType.None); 99 | 100 | // Extended types 101 | if (descriptor.scripting) { 102 | if (descriptor.scripting.source) return ValueType.fromExtendedType(ExtendedType.ScriptSource); 103 | } 104 | if (descriptor.enumeration) return ValueType.fromEnum(descriptor.enumeration); 105 | if (descriptor.temporal) { 106 | if (descriptor.temporal.year) return ValueType.fromExtendedType(ExtendedType.Years_Integer); 107 | if ((descriptor.temporal).quarter) return ValueType.fromExtendedType(ExtendedType.Quarters_Integer); 108 | if (descriptor.temporal.month) return ValueType.fromExtendedType(ExtendedType.Months_Integer); 109 | if ((descriptor.temporal).day) return ValueType.fromExtendedType(ExtendedType.DayOfMonth_Integer); 110 | if ((descriptor.temporal).paddedDateTableDate) return ValueType.fromExtendedType(ExtendedType.PaddedDateTableDates); 111 | } 112 | if (descriptor.geography) { 113 | if (descriptor.geography.address) return ValueType.fromExtendedType(ExtendedType.Address); 114 | if (descriptor.geography.city) return ValueType.fromExtendedType(ExtendedType.City); 115 | if (descriptor.geography.continent) return ValueType.fromExtendedType(ExtendedType.Continent); 116 | if (descriptor.geography.country) return ValueType.fromExtendedType(ExtendedType.Country); 117 | if (descriptor.geography.county) return ValueType.fromExtendedType(ExtendedType.County); 118 | if (descriptor.geography.region) return ValueType.fromExtendedType(ExtendedType.Region); 119 | if (descriptor.geography.postalCode) return ValueType.fromExtendedType(ExtendedType.PostalCode_Text); 120 | if (descriptor.geography.stateOrProvince) return ValueType.fromExtendedType(ExtendedType.StateOrProvince); 121 | if (descriptor.geography.place) return ValueType.fromExtendedType(ExtendedType.Place); 122 | if (descriptor.geography.latitude) return ValueType.fromExtendedType(ExtendedType.Latitude_Double); 123 | if (descriptor.geography.longitude) return ValueType.fromExtendedType(ExtendedType.Longitude_Double); 124 | } 125 | if (descriptor.misc) { 126 | if (descriptor.misc.image) return ValueType.fromExtendedType(ExtendedType.Image); 127 | if (descriptor.misc.imageUrl) return ValueType.fromExtendedType(ExtendedType.ImageUrl); 128 | if (descriptor.misc.webUrl) return ValueType.fromExtendedType(ExtendedType.WebUrl); 129 | if (descriptor.misc.barcode) return ValueType.fromExtendedType(ExtendedType.Barcode_Text); 130 | } 131 | if (descriptor.formatting) { 132 | if (descriptor.formatting.color) return ValueType.fromExtendedType(ExtendedType.Color); 133 | if (descriptor.formatting.formatString) return ValueType.fromExtendedType(ExtendedType.FormatString); 134 | if (descriptor.formatting.alignment) return ValueType.fromExtendedType(ExtendedType.Alignment); 135 | if (descriptor.formatting.labelDisplayUnits) return ValueType.fromExtendedType(ExtendedType.LabelDisplayUnits); 136 | if (descriptor.formatting.fontSize) return ValueType.fromExtendedType(ExtendedType.FontSize); 137 | if (descriptor.formatting.labelDensity) return ValueType.fromExtendedType(ExtendedType.LabelDensity); 138 | } 139 | if (descriptor.extendedType) { 140 | return ValueType.fromExtendedType(descriptor.extendedType); 141 | } 142 | if (descriptor.operations) { 143 | if (descriptor.operations.searchEnabled) return ValueType.fromExtendedType(ExtendedType.SearchEnabled); 144 | } 145 | if ((descriptor).variant) { 146 | const variantTypes = (descriptor).variant.map((variantType) => ValueType.fromDescriptor(variantType)); 147 | return ValueType.fromVariant(variantTypes); 148 | } 149 | 150 | return ValueType.fromExtendedType(ExtendedType.Null); 151 | } 152 | 153 | /** Advanced: Generally use fromDescriptor instead. Creates or retrieves a ValueType object for the specified ExtendedType. */ 154 | public static fromExtendedType(extendedType: ExtendedType): ValueType { 155 | extendedType = extendedType || ExtendedType.Null; 156 | 157 | const primitiveType = getPrimitiveType(extendedType), 158 | category = getCategoryFromExtendedType(extendedType); 159 | 160 | return ValueType.fromPrimitiveTypeAndCategory(primitiveType, category); 161 | } 162 | 163 | /** Creates or retrieves a ValueType object for the specified PrimitiveType and data category. */ 164 | public static fromPrimitiveTypeAndCategory(primitiveType: PrimitiveType, category?: string): ValueType { 165 | primitiveType = primitiveType || PrimitiveType.Null; 166 | category = category || null; 167 | 168 | let id = primitiveType.toString(); 169 | if (category) 170 | id += "|" + category; 171 | 172 | return ValueType.typeCache[id] || (ValueType.typeCache[id] = new ValueType(toExtendedType(primitiveType, category), category)); 173 | } 174 | 175 | /** Creates a ValueType to describe the given IEnumType. */ 176 | public static fromEnum(enumType: IEnumType): ValueType { 177 | return new ValueType(ExtendedType.Enumeration, null, enumType); 178 | } 179 | 180 | /** Creates a ValueType to describe the given Variant type. */ 181 | public static fromVariant(variantTypes: ValueType[]): ValueType { 182 | return new ValueType(ExtendedType.Variant, /* category */null, /* enumType */null, variantTypes); 183 | } 184 | 185 | /** Determines if the specified type is compatible from at least one of the otherTypes. */ 186 | public static isCompatibleTo(typeDescriptor: IValueTypeDescriptor, otherTypes: IValueTypeDescriptor[]): boolean { 187 | const valueType = ValueType.fromDescriptor(typeDescriptor); 188 | for (const otherType of otherTypes) { 189 | const otherValueType = ValueType.fromDescriptor(otherType); 190 | 191 | if (otherValueType.isCompatibleFrom(valueType)) 192 | return true; 193 | } 194 | 195 | return false; 196 | } 197 | 198 | /** Determines if the instance ValueType is convertable from the 'other' ValueType. */ 199 | public isCompatibleFrom(other: ValueType): boolean { 200 | const otherPrimitiveType = other.primitiveType; 201 | if (this === other || 202 | this.primitiveType === otherPrimitiveType || 203 | otherPrimitiveType === PrimitiveType.Null || 204 | // Return true if both types are numbers 205 | (this.numeric && other.numeric) 206 | ) 207 | return true; 208 | return false; 209 | } 210 | 211 | /** 212 | * Determines if the instance ValueType is equal to the 'other' ValueType 213 | * @param {ValueType} other the other ValueType to check equality against 214 | * @returns True if the instance ValueType is equal to the 'other' ValueType 215 | */ 216 | public equals(other: ValueType): boolean { 217 | return equals(this, other); 218 | } 219 | 220 | /** Gets the exact primitive type of this ValueType. */ 221 | public get primitiveType(): PrimitiveType { 222 | return getPrimitiveType(this.underlyingType); 223 | } 224 | 225 | /** Gets the exact extended type of this ValueType. */ 226 | public get extendedType(): ExtendedType { 227 | return this.underlyingType; 228 | } 229 | 230 | /** Gets the data category string (if any) for this ValueType. */ 231 | public get categoryString(): string { 232 | return this.category; 233 | } 234 | 235 | // Simplified primitive types 236 | 237 | /** Indicates whether the type represents text values. */ 238 | public get text(): boolean { 239 | return this.primitiveType === PrimitiveType.Text; 240 | } 241 | 242 | /** Indicates whether the type represents any numeric value. */ 243 | public get numeric(): boolean { 244 | return EnumExtensions.hasFlag(this.underlyingType, ExtendedType.Numeric); 245 | } 246 | 247 | /** Indicates whether the type represents integer numeric values. */ 248 | public get integer(): boolean { 249 | return this.primitiveType === PrimitiveType.Integer; 250 | } 251 | 252 | /** Indicates whether the type represents Boolean values. */ 253 | public get bool(): boolean { 254 | return this.primitiveType === PrimitiveType.Boolean; 255 | } 256 | 257 | /** Indicates whether the type represents any date/time values. */ 258 | public get dateTime(): boolean { 259 | return this.primitiveType === PrimitiveType.DateTime || 260 | this.primitiveType === PrimitiveType.Date || 261 | this.primitiveType === PrimitiveType.Time; 262 | } 263 | 264 | /** Indicates whether the type represents duration values. */ 265 | public get duration(): boolean { 266 | return this.primitiveType === PrimitiveType.Duration; 267 | } 268 | 269 | /** Indicates whether the type represents binary values. */ 270 | public get binary(): boolean { 271 | return this.primitiveType === PrimitiveType.Binary; 272 | } 273 | 274 | /** Indicates whether the type represents none values. */ 275 | public get none(): boolean { 276 | return this.primitiveType === PrimitiveType.None; 277 | } 278 | 279 | // Extended types 280 | 281 | /** Returns an object describing temporal values represented by the type, if it represents a temporal type. */ 282 | public get temporal(): TemporalType { 283 | return this.temporalType; 284 | } 285 | 286 | /** Returns an object describing geographic values represented by the type, if it represents a geographic type. */ 287 | public get geography(): GeographyType { 288 | return this.geographyType; 289 | } 290 | 291 | /** Returns an object describing the specific values represented by the type, if it represents a miscellaneous extended type. */ 292 | public get misc(): MiscellaneousType { 293 | return this.miscType; 294 | } 295 | 296 | /** Returns an object describing the formatting values represented by the type, if it represents a formatting type. */ 297 | public get formatting(): FormattingType { 298 | return this.formattingType; 299 | } 300 | 301 | /** Returns an object describing the enum values represented by the type, if it represents an enumeration type. */ 302 | public get enumeration(): IEnumType { 303 | return this.enumType; 304 | } 305 | 306 | public get scripting(): ScriptType { 307 | return this.scriptingType; 308 | } 309 | 310 | /** Returns an array describing the variant values represented by the type, if it represents an Variant type. */ 311 | public get variant(): ValueType[] { 312 | return this.variationTypes; 313 | } 314 | } 315 | 316 | export class ScriptType implements ScriptTypeDescriptor { 317 | private underlyingType: ExtendedType; 318 | 319 | constructor(underlyingType: ExtendedType) { 320 | this.underlyingType = underlyingType; 321 | } 322 | 323 | public get source(): boolean { 324 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.ScriptSource); 325 | } 326 | } 327 | 328 | export class TemporalType implements TemporalTypeDescriptor { 329 | private underlyingType: ExtendedType; 330 | 331 | constructor(underlyingType: ExtendedType) { 332 | this.underlyingType = underlyingType; 333 | } 334 | 335 | public get year(): boolean { 336 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Years); 337 | } 338 | public get quarter(): boolean { 339 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Quarters); 340 | } 341 | public get month(): boolean { 342 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Months); 343 | } 344 | public get day(): boolean { 345 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.DayOfMonth); 346 | } 347 | public get paddedDateTableDate(): boolean { 348 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.PaddedDateTableDates); 349 | } 350 | } 351 | 352 | export class GeographyType implements GeographyTypeDescriptor { 353 | private underlyingType: ExtendedType; 354 | 355 | constructor(underlyingType: ExtendedType) { 356 | this.underlyingType = underlyingType; 357 | } 358 | 359 | public get address(): boolean { 360 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Address); 361 | } 362 | public get city(): boolean { 363 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.City); 364 | } 365 | public get continent(): boolean { 366 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Continent); 367 | } 368 | public get country(): boolean { 369 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Country); 370 | } 371 | public get county(): boolean { 372 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.County); 373 | } 374 | public get region(): boolean { 375 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Region); 376 | } 377 | public get postalCode(): boolean { 378 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.PostalCode); 379 | } 380 | public get stateOrProvince(): boolean { 381 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.StateOrProvince); 382 | } 383 | public get place(): boolean { 384 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Place); 385 | } 386 | public get latitude(): boolean { 387 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Latitude); 388 | } 389 | public get longitude(): boolean { 390 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Longitude); 391 | } 392 | } 393 | 394 | export class MiscellaneousType implements MiscellaneousTypeDescriptor { 395 | private underlyingType: ExtendedType; 396 | 397 | constructor(underlyingType: ExtendedType) { 398 | this.underlyingType = underlyingType; 399 | } 400 | 401 | public get image(): boolean { 402 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Image); 403 | } 404 | public get imageUrl(): boolean { 405 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.ImageUrl); 406 | } 407 | public get webUrl(): boolean { 408 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.WebUrl); 409 | } 410 | public get barcode(): boolean { 411 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Barcode); 412 | } 413 | } 414 | 415 | export class FormattingType implements FormattingTypeDescriptor { 416 | private underlyingType: ExtendedType; 417 | 418 | constructor(underlyingType: ExtendedType) { 419 | this.underlyingType = underlyingType; 420 | } 421 | 422 | public get color(): boolean { 423 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Color); 424 | } 425 | 426 | public get formatString(): boolean { 427 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.FormatString); 428 | } 429 | 430 | public get alignment(): boolean { 431 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.Alignment); 432 | } 433 | 434 | public get labelDisplayUnits(): boolean { 435 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.LabelDisplayUnits); 436 | } 437 | 438 | public get fontSize(): boolean { 439 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.FontSize); 440 | } 441 | 442 | public get labelDensity(): boolean { 443 | return matchesExtendedTypeWithAnyPrimitive(this.underlyingType, ExtendedType.LabelDensity); 444 | } 445 | } 446 | 447 | /** Defines primitive value types. Must be consistent with types defined by server conceptual schema. */ 448 | export enum PrimitiveType { 449 | Null = 0, 450 | Text = 1, 451 | Decimal = 2, 452 | Double = 3, 453 | Integer = 4, 454 | Boolean = 5, 455 | Date = 6, 456 | DateTime = 7, 457 | DateTimeZone = 8, 458 | Time = 9, 459 | Duration = 10, 460 | Binary = 11, 461 | None = 12, 462 | Variant = 13, 463 | } 464 | 465 | enum PrimitiveTypeStrings { 466 | Null = PrimitiveType.Null, 467 | Text = PrimitiveType.Text, 468 | Decimal = PrimitiveType.Decimal, 469 | Double = PrimitiveType.Double, 470 | Integer = PrimitiveType.Integer, 471 | Boolean = PrimitiveType.Boolean, 472 | Date = PrimitiveType.Date, 473 | DateTime = PrimitiveType.DateTime, 474 | DateTimeZone = PrimitiveType.DateTimeZone, 475 | Time = PrimitiveType.Time, 476 | Duration = PrimitiveType.Duration, 477 | Binary = PrimitiveType.Binary, 478 | None = PrimitiveType.None, 479 | Variant = PrimitiveType.Variant, 480 | } 481 | 482 | /** Defines extended value types, which include primitive types and known data categories constrained to expected primitive types. */ 483 | export enum ExtendedType { 484 | // Flags (1 << 8-15 range [0xFF00]) 485 | // Important: Enum members must be declared before they are used in TypeScript. 486 | Numeric = 1 << 8, 487 | Temporal = 1 << 9, 488 | Geography = 1 << 10, 489 | Miscellaneous = 1 << 11, 490 | Formatting = 1 << 12, 491 | Scripting = 1 << 13, 492 | 493 | // Primitive types (0-255 range [0xFF] | flags) 494 | // The member names and base values must match those in PrimitiveType. 495 | Null = 0, 496 | Text = 1, 497 | Decimal = Numeric | 2, 498 | Double = Numeric | 3, 499 | Integer = Numeric | 4, 500 | Boolean = 5, 501 | Date = Temporal | 6, 502 | DateTime = Temporal | 7, 503 | DateTimeZone = Temporal | 8, 504 | Time = Temporal | 9, 505 | Duration = 10, 506 | Binary = 11, 507 | None = 12, 508 | Variant = 13, 509 | 510 | // Extended types (0-32767 << 16 range [0xFFFF0000] | corresponding primitive type | flags) 511 | // Temporal 512 | Years = Temporal | (1 << 16), 513 | Years_Text = Years | Text, 514 | Years_Integer = Years | Integer, 515 | Years_Date = Years | Date, 516 | Years_DateTime = Years | DateTime, 517 | Months = Temporal | (2 << 16), 518 | Months_Text = Months | Text, 519 | Months_Integer = Months | Integer, 520 | Months_Date = Months | Date, 521 | Months_DateTime = Months | DateTime, 522 | PaddedDateTableDates = Temporal | DateTime | (3 << 16), 523 | Quarters = Temporal | (4 << 16), 524 | Quarters_Text = Quarters | Text, 525 | Quarters_Integer = Quarters | Integer, 526 | Quarters_Date = Quarters | Date, 527 | Quarters_DateTime = Quarters | DateTime, 528 | DayOfMonth = Temporal | (5 << 16), 529 | DayOfMonth_Text = DayOfMonth | Text, 530 | DayOfMonth_Integer = DayOfMonth | Integer, 531 | DayOfMonth_Date = DayOfMonth | Date, 532 | DayOfMonth_DateTime = DayOfMonth | DateTime, 533 | 534 | // Geography 535 | Address = Text | Geography | (100 << 16), 536 | City = Text | Geography | (101 << 16), 537 | Continent = Text | Geography | (102 << 16), 538 | Country = Text | Geography | (103 << 16), 539 | County = Text | Geography | (104 << 16), 540 | Region = Text | Geography | (105 << 16), 541 | PostalCode = Geography | (106 << 16), 542 | PostalCode_Text = PostalCode | Text, 543 | PostalCode_Integer = PostalCode | Integer, 544 | StateOrProvince = Text | Geography | (107 << 16), 545 | Place = Text | Geography | (108 << 16), 546 | Latitude = Geography | (109 << 16), 547 | Latitude_Decimal = Latitude | Decimal, 548 | Latitude_Double = Latitude | Double, 549 | Longitude = Geography | (110 << 16), 550 | Longitude_Decimal = Longitude | Decimal, 551 | Longitude_Double = Longitude | Double, 552 | // Miscellaneous 553 | Image = Binary | Miscellaneous | (200 << 16), 554 | ImageUrl = Text | Miscellaneous | (201 << 16), 555 | WebUrl = Text | Miscellaneous | (202 << 16), 556 | Barcode = Miscellaneous | (203 << 16), 557 | Barcode_Text = Barcode | Text, 558 | Barcode_Integer = Barcode | Integer, 559 | 560 | // Formatting 561 | Color = Text | Formatting | (300 << 16), 562 | FormatString = Text | Formatting | (301 << 16), 563 | Alignment = Text | Formatting | (306 << 16), 564 | LabelDisplayUnits = Text | Formatting | (307 << 16), 565 | FontSize = Double | Formatting | (308 << 16), 566 | LabelDensity = Double | Formatting | (309 << 16), 567 | // Enumeration 568 | Enumeration = Text | 400 << 16, 569 | // Scripting 570 | ScriptSource = Text | Scripting | (500 << 16), 571 | // NOTE: To avoid confusion, underscores should be used only to delimit primitive type variants of an extended type 572 | // (e.g. Year_Integer or Latitude_Double above) 573 | 574 | // Operations 575 | SearchEnabled = Boolean | (1 << 16), 576 | } 577 | 578 | enum ExtendedTypeStrings { 579 | Numeric = ExtendedType.Numeric, 580 | Temporal = ExtendedType.Temporal, 581 | Geography = ExtendedType.Geography, 582 | Miscellaneous = ExtendedType.Miscellaneous, 583 | Formatting = ExtendedType.Formatting, 584 | Scripting = ExtendedType.Scripting, 585 | Null = ExtendedType.Null, 586 | Text = ExtendedType.Text, 587 | Decimal = ExtendedType.Decimal, 588 | Double = ExtendedType.Double, 589 | Integer = ExtendedType.Integer, 590 | Boolean = ExtendedType.Boolean, 591 | Date = ExtendedType.Date, 592 | DateTime = ExtendedType.DateTime, 593 | DateTimeZone = ExtendedType.DateTimeZone, 594 | Time = ExtendedType.Time, 595 | Duration = ExtendedType.Duration, 596 | Binary = ExtendedType.Binary, 597 | None = ExtendedType.None, 598 | Variant = ExtendedType.Variant, 599 | Years = ExtendedType.Years, 600 | Years_Text = ExtendedType.Years_Text, 601 | Years_Integer = ExtendedType.Years_Integer, 602 | Years_Date = ExtendedType.Years_Date, 603 | Years_DateTime = ExtendedType.Years_DateTime, 604 | Months = ExtendedType.Months, 605 | Months_Text = ExtendedType.Months_Text, 606 | Months_Integer = ExtendedType.Months_Integer, 607 | Months_Date = ExtendedType.Months_Date, 608 | Months_DateTime = ExtendedType.Months_DateTime, 609 | PaddedDateTableDates = ExtendedType.PaddedDateTableDates, 610 | Quarters = ExtendedType.Quarters, 611 | Quarters_Text = ExtendedType.Quarters_Text, 612 | Quarters_Integer = ExtendedType.Quarters_Integer, 613 | Quarters_Date = ExtendedType.Quarters_Date, 614 | Quarters_DateTime = ExtendedType.Quarters_DateTime, 615 | DayOfMonth = ExtendedType.DayOfMonth, 616 | DayOfMonth_Text = ExtendedType.DayOfMonth_Text, 617 | DayOfMonth_Integer = ExtendedType.DayOfMonth_Integer, 618 | DayOfMonth_Date = ExtendedType.DayOfMonth_Date, 619 | DayOfMonth_DateTime = ExtendedType.DayOfMonth_DateTime, 620 | Address = ExtendedType.Address, 621 | City = ExtendedType.City, 622 | Continent = ExtendedType.Continent, 623 | Country = ExtendedType.Country, 624 | County = ExtendedType.County, 625 | Region = ExtendedType.Region, 626 | PostalCode = ExtendedType.PostalCode, 627 | PostalCode_Text = ExtendedType.PostalCode_Text, 628 | PostalCode_Integer = ExtendedType.PostalCode_Integer, 629 | StateOrProvince = ExtendedType.StateOrProvince, 630 | Place = ExtendedType.Place, 631 | Latitude = ExtendedType.Latitude, 632 | Latitude_Decimal = ExtendedType.Latitude_Decimal, 633 | Latitude_Double = ExtendedType.Latitude_Double, 634 | Longitude = ExtendedType.Longitude, 635 | Longitude_Decimal = ExtendedType.Longitude_Decimal, 636 | Longitude_Double = ExtendedType.Longitude_Double, 637 | Image = ExtendedType.Image, 638 | ImageUrl = ExtendedType.ImageUrl, 639 | WebUrl = ExtendedType.WebUrl, 640 | Barcode = ExtendedType.Barcode, 641 | Barcode_Text = ExtendedType.Barcode_Text, 642 | Barcode_Integer = ExtendedType.Barcode_Integer, 643 | Color = ExtendedType.Color, 644 | FormatString = ExtendedType.FormatString, 645 | Alignment = ExtendedType.Alignment, 646 | LabelDisplayUnits = ExtendedType.LabelDisplayUnits, 647 | FontSize = ExtendedType.FontSize, 648 | LabelDensity = ExtendedType.LabelDensity, 649 | Enumeration = ExtendedType.Enumeration, 650 | ScriptSource = ExtendedType.ScriptSource, 651 | SearchEnabled = ExtendedType.SearchEnabled, 652 | } 653 | 654 | const PrimitiveTypeMask = 0xFF; 655 | const PrimitiveTypeWithFlagsMask = 0xFFFF; 656 | const PrimitiveTypeFlagsExcludedMask = 0xFFFF0000; 657 | 658 | function getPrimitiveType(extendedType: ExtendedType): PrimitiveType { 659 | return extendedType & PrimitiveTypeMask; 660 | } 661 | 662 | function isPrimitiveType(extendedType: ExtendedType): boolean { 663 | return (extendedType & PrimitiveTypeWithFlagsMask) === extendedType; 664 | } 665 | 666 | function getCategoryFromExtendedType(extendedType: ExtendedType): string { 667 | if (isPrimitiveType(extendedType)) 668 | return null; 669 | 670 | let category = ExtendedTypeStrings[extendedType]; 671 | if (category) { 672 | // Check for ExtendedType declaration without a primitive type. 673 | // If exists, use it as category (e.g. Longitude rather than Longitude_Double) 674 | // Otherwise use the ExtendedType declaration with a primitive type (e.g. Address) 675 | const delimIdx = category.lastIndexOf("_"); 676 | if (delimIdx > 0) { 677 | const baseCategory: string = category.slice(0, delimIdx); 678 | if (ExtendedTypeStrings[baseCategory]) { 679 | category = baseCategory; 680 | } 681 | } 682 | } 683 | return category || null; 684 | } 685 | 686 | function toExtendedType(primitiveType: PrimitiveType, category?: string): ExtendedType { 687 | const primitiveString = PrimitiveTypeStrings[primitiveType]; 688 | let t = ExtendedTypeStrings[primitiveString]; 689 | if (t == null) { 690 | t = ExtendedType.Null; 691 | } 692 | 693 | if (primitiveType && category) { 694 | let categoryType: ExtendedType = ExtendedTypeStrings[category]; 695 | if (categoryType) { 696 | const categoryPrimitiveType = getPrimitiveType(categoryType); 697 | if (categoryPrimitiveType === PrimitiveType.Null) { 698 | // Category supports multiple primitive types, check if requested primitive type is supported 699 | // (note: important to use t here rather than primitiveType as it may include primitive type flags) 700 | categoryType = t | categoryType; 701 | if (ExtendedTypeStrings[categoryType]) { 702 | t = categoryType; 703 | } 704 | } 705 | else if (categoryPrimitiveType === primitiveType) { 706 | // Primitive type matches the single supported type for the category 707 | t = categoryType; 708 | } 709 | } 710 | } 711 | 712 | return t; 713 | } 714 | 715 | function matchesExtendedTypeWithAnyPrimitive(a: ExtendedType, b: ExtendedType): boolean { 716 | return (a & PrimitiveTypeFlagsExcludedMask) === (b & PrimitiveTypeFlagsExcludedMask); 717 | } 718 | -------------------------------------------------------------------------------- /test/doubleTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import * as Double from "../src/double"; 27 | 28 | describe("Double", () => { 29 | 30 | it("Double Constants", function () { 31 | expect(Double.MIN_VALUE).toBe(-1.7976931348623157e+308); 32 | expect(Double.MAX_VALUE).toBe(1.7976931348623157e+308); 33 | expect(Double.MIN_EXP).toBe(-308); 34 | expect(Double.MAX_EXP).toBe(308); 35 | expect(Double.EPSILON).toBe(1E-323); 36 | expect(Double.DEFAULT_PRECISION).toBe(0.0001); 37 | expect(Double.DEFAULT_PRECISION_IN_DECIMAL_DIGITS).toBe(12); 38 | expect(Double.LOG_E_10).toBe(Math.log(10)); 39 | }); 40 | 41 | it("Double pow10()", function () { 42 | const positivePowers = [1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18, 1E19, 1E20, 1E21, 1E22, 1E23, 1E24, 1E25, 1E26, 1E27, 1E28, 1E29, 1E30, 1E31, 1E32]; 43 | const negativePowers = [1E0, 1E-1, 1E-2, 1E-3, 1E-4, 1E-5, 1E-6, 1E-7, 1E-8, 1E-9, 1E-10, 1E-11, 1E-12, 1E-13, 1E-14, 1E-15, 1E-16, 1E-17, 1E-18, 1E-19, 1E-20, 1E-21, 1E-22, 1E-23, 1E-24, 1E-25, 1E-26, 1E-27, 1E-28, 1E-29, 1E-30, 1E-31, 1E-32]; 44 | for (let i = 0; i < 33; i++) { 45 | expect(Double.pow10(-i)).toBe(negativePowers[i]); 46 | expect(Double.pow10(i)).toBe(positivePowers[i]); 47 | } 48 | 49 | expect(Double.pow10(-308)).toBe(1E-308); 50 | expect(Double.pow10(308)).toBe(1E308); 51 | expect(Double.pow10(3.3)).not.toBeDefined(); 52 | }); 53 | 54 | it("Double log10()", function () { 55 | const positivePowers = [1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18, 1E19, 1E20, 1E21, 1E22, 1E23, 1E24, 1E25, 1E26, 1E27, 1E28, 1E29, 1E30, 1E31, 1E32]; 56 | const negativePowers = [1E0, 1E-1, 1E-2, 1E-3, 1E-4, 1E-5, 1E-6, 1E-7, 1E-8, 1E-9, 1E-10, 1E-11, 1E-12, 1E-13, 1E-14, 1E-15, 1E-16, 1E-17, 1E-18, 1E-19, 1E-20, 1E-21, 1E-22, 1E-23, 1E-24, 1E-25, 1E-26, 1E-27, 1E-28, 1E-29, 1E-30, 1E-31, 1E-32]; 57 | for (let i = 0; i < 33; i++) { 58 | expect(Double.log10(negativePowers[i])).toBe(-i); 59 | expect(Double.log10(positivePowers[i])).toBe(i); 60 | } 61 | 62 | expect(Double.log10(1E-308)).toBe(-308); 63 | expect(Double.log10(1E308)).toBe(308); 64 | expect(Double.log10(756)).toBe(2); 65 | expect(Double.log10(0.0756)).toBe(-2); 66 | expect(Double.log10(0)).toBe(-Infinity); 67 | }); 68 | 69 | it("Double getPrecision()", function () { 70 | expect(Double.getPrecision(562344, 0)).toBe(100000); 71 | expect(Double.getPrecision(562344, 3)).toBe(100); 72 | expect(Double.getPrecision(562344, 6)).toBe(0.1); 73 | expect(Double.getPrecision(562344)).toBe(1E-7); // Default precision is 12 digits 74 | 75 | expect(Double.getPrecision(Double.MAX_VALUE)).toBe(1E296); 76 | expect(Double.getPrecision(Double.MIN_VALUE)).toBe(1E296); 77 | expect(Double.getPrecision(Double.EPSILON)).toBe(0); 78 | expect(Double.getPrecision(0)).toBe(undefined); 79 | expect(Double.getPrecision(undefined)).toBe(undefined); 80 | expect(Double.getPrecision(null)).toBe(undefined); 81 | expect(Double.getPrecision(null)).toBe(undefined); 82 | expect(Double.getPrecision(Infinity)).toBe(undefined); 83 | }); 84 | 85 | it("Double equalWithPrecision()", function () { 86 | expect(Double.equalWithPrecision(1, 1.005, 0.01)).toBe(true); 87 | expect(Double.equalWithPrecision(1, 0.995, 0.01)).toBe(true); 88 | expect(Double.equalWithPrecision(1, 1.005, 0.001)).toBe(false); 89 | expect(Double.equalWithPrecision(1, 0.995, 0.001)).toBe(false); 90 | expect(Double.equalWithPrecision(1, 0.995, 0)).toBe(false); 91 | }); 92 | 93 | it("Double lessWithPrecision()", function () { 94 | expect(Double.lessWithPrecision(1, 1, 0.01)).toBe(false); 95 | expect(Double.lessWithPrecision(0.995, 1, 0)).toBe(true); 96 | expect(Double.lessWithPrecision(1.005, 1, 0.01)).toBe(false); 97 | expect(Double.lessWithPrecision(0.995, 1, 0.01)).toBe(false); 98 | expect(Double.lessWithPrecision(0.995, 1, 0.001)).toBe(true); 99 | }); 100 | 101 | it("Double lessOrEqualWithPrecision()", function () { 102 | expect(Double.lessOrEqualWithPrecision(1, 1, 0.01)).toBe(true); 103 | expect(Double.lessOrEqualWithPrecision(1.005, 1, 0.01)).toBe(true); 104 | expect(Double.lessOrEqualWithPrecision(0.995, 1, 0.01)).toBe(true); 105 | expect(Double.lessOrEqualWithPrecision(1.005, 1, 0.001)).toBe(false); 106 | expect(Double.lessOrEqualWithPrecision(0.995, 1, 0.001)).toBe(true); 107 | }); 108 | 109 | it("Double greaterWithPrecision()", function () { 110 | expect(Double.greaterWithPrecision(1, 1, 0.01)).toBe(false); 111 | expect(Double.greaterWithPrecision(1, 1.005, 0.01)).toBe(false); 112 | expect(Double.greaterWithPrecision(1, 0.995, 0.01)).toBe(false); 113 | expect(Double.greaterWithPrecision(1, 0.995, 0.001)).toBe(true); 114 | expect(Double.greaterWithPrecision(1, 0.995, 0)).toBe(true); 115 | }); 116 | 117 | it("Double greaterOrEqualWithPrecision()", function () { 118 | expect(Double.greaterOrEqualWithPrecision(1, 1, 0.01)).toBe(true); 119 | expect(Double.greaterOrEqualWithPrecision(1, 1.005, 0.01)).toBe(true); 120 | expect(Double.greaterOrEqualWithPrecision(1, 0.995, 0.01)).toBe(true); 121 | expect(Double.greaterOrEqualWithPrecision(1, 1.005, 0.001)).toBe(false); 122 | expect(Double.greaterOrEqualWithPrecision(1, 0.995, 0.001)).toBe(true); 123 | expect(Double.greaterOrEqualWithPrecision(1, 0.995, 0)).toBe(true); 124 | }); 125 | 126 | it("Double floorWithPrecision()", function () { 127 | expect(Double.floorWithPrecision(-5.06, 0.001)).toBe(-6); 128 | expect(Double.floorWithPrecision(5.96, 0.001)).toBe(5); 129 | 130 | expect(Double.floorWithPrecision(-5.06, 0.1)).toBe(-5); 131 | expect(Double.floorWithPrecision(5.96, 0.1)).toBe(6); 132 | }); 133 | 134 | it("Double floorToPrecision()", function () { 135 | const espilon = 0.0000000001; 136 | expect(Double.floorToPrecision(-5.06, 0.001)).toBeCloseTo(-5.06, espilon); 137 | expect(Double.floorToPrecision(5.96, 0.001)).toBeCloseTo(5.96, espilon); 138 | 139 | expect(Double.floorToPrecision(-5.06, 0.1)).toBeCloseTo(-5.1, espilon); 140 | expect(Double.floorToPrecision(5.96, 0.1)).toBeCloseTo(5.9, espilon); 141 | 142 | expect(Double.floorToPrecision(-0.2, 0)).toBeCloseTo(-0.2, espilon); 143 | expect(Double.floorToPrecision(Infinity, 0)).toBe(Infinity); 144 | }); 145 | 146 | it("Double ceilWithPrecision()", function () { 147 | expect(Double.ceilWithPrecision(-5.96, 0.001)).toBe(-5); 148 | expect(Double.ceilWithPrecision(5.06, 0.001)).toBe(6); 149 | 150 | expect(Double.ceilWithPrecision(-5.96, 0.1)).toBe(-6); 151 | expect(Double.ceilWithPrecision(5.06, 0.1)).toBe(5); 152 | expect(Double.ceilWithPrecision(Infinity, 0)).toBe(Infinity); 153 | }); 154 | 155 | it("Double ceilToPrecision()", function () { 156 | expect(Double.ceilToPrecision(-506, 1)).toBe(-506); 157 | expect(Double.ceilToPrecision(596, 1)).toBe(596); 158 | 159 | expect(Double.ceilToPrecision(-506, 10)).toBe(-500); 160 | expect(Double.ceilToPrecision(596, 10)).toBe(600); 161 | 162 | expect(Double.ceilToPrecision(-0.2, 0)).toBe(-0.2); 163 | expect(Double.ceilToPrecision(Infinity, 0)).toBe(Infinity); 164 | }); 165 | 166 | it("Double roundToPrecision()", function () { 167 | expect(Double.roundToPrecision(-506, 1)).toBe(-506); 168 | expect(Double.roundToPrecision(596, 1)).toBe(596); 169 | 170 | expect(Double.roundToPrecision(-506, 10)).toBe(-510); 171 | expect(Double.roundToPrecision(596, 10)).toBe(600); 172 | 173 | expect(Double.roundToPrecision(-0.20003, 0.1)).toBe(-0.2); 174 | expect(Double.roundToPrecision(-0.2, 0)).toBe(-0.2); 175 | expect(Double.roundToPrecision(Infinity, 0)).toBe(Infinity); 176 | 177 | expect(Double.roundToPrecision(1.40000000001E207, 1E206)).toBe(1.4E207); 178 | expect(Double.roundToPrecision(1.40000000001E-207, 1E-208)).toBe(1.4E-207); 179 | expect(Double.roundToPrecision(1.41E-207, 1E-208)).toBe(1.4E-207); 180 | expect(Double.roundToPrecision(1.41E-207, 1E-209)).toBe(1.41E-207); 181 | }); 182 | 183 | it("Double removeDecimalNoise()", () => { 184 | let roundedNumber = 21493 * 0.001; // 21.493000000000002 185 | expect(Double.removeDecimalNoise(roundedNumber).toString()).toBe("21.493"); 186 | }); 187 | 188 | it("Double ensureInRange()", function () { 189 | expect(Double.ensureInRange(-27.2, -100, -10)).toBe(-27.2); 190 | expect(Double.ensureInRange(-27.2, -100, -50)).toBe(-50); 191 | expect(Double.ensureInRange(-27.2, -10, -5)).toBe(-10); 192 | expect(Double.ensureInRange(undefined, -77, 55)).toBe(undefined); 193 | }); 194 | 195 | it("Double round()", function () { 196 | expect(Double.round(27.2)).toBe(27); 197 | expect(Double.round(27.45)).toBe(27); 198 | expect(Double.round(27.5)).toBe(28); 199 | expect(Double.round(27.51)).toBe(28); 200 | 201 | }); 202 | 203 | it("Double isInteger()", () => { 204 | expect(Double.isInteger(undefined)).toBe(false); 205 | expect(Double.isInteger(null)).toBe(false); 206 | expect(Double.isInteger(3)).toBe(true); 207 | expect(Double.isInteger(-3)).toBe(true); 208 | expect(Double.isInteger(0)).toBe(true); 209 | expect(Double.isInteger(3.5)).toBe(false); 210 | }); 211 | 212 | it("Double toIncrement()", () => { 213 | expect(Double.toIncrement(0.6383723, 0.05)).toBe(0.65); 214 | expect(Double.toIncrement(73, 5)).toBe(75); 215 | expect(Double.toIncrement(58472623, 500)).toBe(58472500); 216 | }); 217 | 218 | it("Double detectPrecision()", () => { 219 | expect(Double.detectPrecision(0.00001, 0.1, 0.2)).toBe(0.00001); 220 | expect(Double.detectPrecision(undefined, 20)).toBe(1E-11); 221 | expect(Double.detectPrecision(undefined, 20, 20000)).toBe(1E-11); 222 | expect(Double.detectPrecision(undefined, 0, 20)).toBe(1E-11); 223 | expect(Double.detectPrecision(undefined, 0, 0)).toBe(0.0001); 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /test/extensions/arrayExtensionsTests.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import * as ArrayExtensions from "../../src/extensions/arrayExtensions"; 27 | import * as Prototype from "../../src/prototype"; 28 | 29 | interface TestIdType { id: number; } 30 | interface TestNamedType { name: string; } 31 | 32 | describe("ArrayExtensions", () => { 33 | 34 | it("ArrayIdItems_Extend", () => { 35 | const item0: TestIdType = { id: 123 }; 36 | const item1: TestIdType = { id: 456 }; 37 | const item2: TestIdType = { id: 789 }; 38 | 39 | const array: { id: number }[] = [ 40 | item0, 41 | item1, 42 | item2]; 43 | 44 | let extendedArray = ArrayExtensions.extendWithId(array); 45 | expect(extendedArray).toBe(array); 46 | 47 | expect(extendedArray.withId(123)).toBe(item0); 48 | expect(extendedArray.withId(456)).toBe(item1); 49 | expect(extendedArray.withId(789)).toBe(item2); 50 | 51 | expect(extendedArray.withId(0)).not.toBeDefined(); 52 | }); 53 | 54 | it("ArrayNamedItems_Extend", () => { 55 | const item0: TestNamedType = { name: "abc" }; 56 | const item1: TestNamedType = { name: "def" }; 57 | const item2: TestNamedType = { name: "ghi" }; 58 | 59 | const array: TestNamedType[] = [ 60 | item0, 61 | item1, 62 | item2]; 63 | 64 | let extendedArray = ArrayExtensions.extendWithName(array); 65 | expect(extendedArray).toBe(array); 66 | 67 | expect(extendedArray.withName("abc")).toBe(item0); 68 | expect(extendedArray.withName("def")).toBe(item1); 69 | expect(extendedArray.withName("ghi")).toBe(item2); 70 | 71 | expect(extendedArray.withName("xyz")).not.toBeDefined(); 72 | }); 73 | 74 | it("Clear an array with 4 items", () => { 75 | const array = ["a", "b", "c", "4"]; 76 | 77 | ArrayExtensions.clear(array); 78 | 79 | expect(array.length).toEqual(0); 80 | }); 81 | 82 | it("Intersection of 2 arrays", () => { 83 | const array: number[] = [1, 2, 3]; 84 | 85 | let result = ArrayExtensions.intersect(array, [2, 4, 5]); 86 | 87 | expect(result.length).toEqual(1); 88 | expect(result).toEqual([2]); 89 | }); 90 | 91 | it("Diff of 2 arrays", () => { 92 | const emptyArray: any = []; 93 | const array: number[] = [1, 2, 3]; 94 | const array2: number[] = [1, 2, 3, 4]; 95 | expect(ArrayExtensions.diff(array2, array)).toEqual([4]); 96 | expect(ArrayExtensions.diff(emptyArray, emptyArray)).toEqual([]); 97 | }); 98 | 99 | it("Union of an array and an element", () => { 100 | const array: number[] = [1, 2]; 101 | 102 | ArrayExtensions.unionSingle(array, 2); 103 | expect(array).toEqual([1, 2]); 104 | 105 | ArrayExtensions.unionSingle(array, 3); 106 | expect(array).toEqual([1, 2, 3]); 107 | }); 108 | 109 | it("Union of 2 arrays", () => { 110 | const array: number[] = [1, 2]; 111 | 112 | ArrayExtensions.union(array, [3, 1]); 113 | expect(array).toEqual([1, 2, 3]); 114 | }); 115 | 116 | it("ArrayExtensionsRange", () => { 117 | const array: number[] = [1, 2, 3, 4]; 118 | 119 | let result = ArrayExtensions.range(array, 2, 3); 120 | expect(result).toEqual([3, 4]); 121 | }); 122 | 123 | it("ArrayExtensionsTake", () => { 124 | const array: number[] = [1, 2, 3]; 125 | 126 | let result = ArrayExtensions.take(array, 2); 127 | expect(result).toEqual([1, 2]); 128 | }); 129 | 130 | it("Copy arrays", () => { 131 | const array: number[] = [1, 2, 3]; 132 | let result = ArrayExtensions.copy(array); 133 | result.push(4); 134 | expect(result === array).toBeFalsy(); 135 | expect(result).toEqual([1, 2, 3, 4]); 136 | }); 137 | 138 | it("ArrayExtensionsEmptyToNull", () => { 139 | 140 | expect(ArrayExtensions.emptyToNull([])).toBeNull(); 141 | expect(ArrayExtensions.emptyToNull([1, 2, 3])).toEqual([1, 2, 3]); 142 | }); 143 | 144 | it("ArrayExtensionsSequenceEqual T === U", () => { 145 | let equalsNegativesComparison: (x: number, y: number) => boolean = (x, y) => (x === -y); 146 | 147 | expect(ArrayExtensions.sequenceEqual([1, 2], [-1, -2], equalsNegativesComparison)).toBeTruthy(); 148 | expect(ArrayExtensions.sequenceEqual([1, 2], [-2, -1], equalsNegativesComparison)).toBeFalsy(); 149 | expect(ArrayExtensions.sequenceEqual([], [], equalsNegativesComparison)).toBeTruthy(); 150 | expect(ArrayExtensions.sequenceEqual(null, null, equalsNegativesComparison)).toBeTruthy(); 151 | expect(ArrayExtensions.sequenceEqual([1], [1], equalsNegativesComparison)).toBeFalsy(); 152 | expect(ArrayExtensions.sequenceEqual([1], null, equalsNegativesComparison)).toBeFalsy(); 153 | expect(ArrayExtensions.sequenceEqual(null, [1], equalsNegativesComparison)).toBeFalsy(); 154 | expect(ArrayExtensions.sequenceEqual(null, undefined, equalsNegativesComparison)).toBeTruthy(); 155 | expect(ArrayExtensions.sequenceEqual([1, 2], [1], equalsNegativesComparison)).toBeFalsy(); 156 | }); 157 | 158 | it("ArrayExtensionsSequenceEqual, T !== U", () => { 159 | let numberAndBooleanComparison: (x: number, y: boolean) => boolean = (x, y) => ((x !== 0) === y); 160 | 161 | expect(ArrayExtensions.sequenceEqual([1, 2, 0], [true, true, false], numberAndBooleanComparison)).toBeTruthy(); 162 | expect(ArrayExtensions.sequenceEqual([1, 2, 3], [true, true, false], numberAndBooleanComparison)).toBeFalsy(); 163 | }); 164 | 165 | it("ArrayExtensionsDistinct", () => { 166 | const array: number[] = [1, 1, 2, 3, 3, 4]; 167 | 168 | let result = ArrayExtensions.distinct(array); 169 | 170 | expect(result.length).toEqual(4); 171 | expect(result).toEqual([1, 2, 3, 4]); 172 | }); 173 | 174 | it("ArrayExtensions swap", () => { 175 | const array: number[] = [1, 2, 3]; 176 | ArrayExtensions.swap(array, 1, 2); 177 | expect(array).toEqual([1, 3, 2]); 178 | }); 179 | 180 | it("ArrayExtensions ensureArray", () => { 181 | expect(ArrayExtensions.ensureArray([1, 2, 3])).toEqual([1, 2, 3]); 182 | expect(ArrayExtensions.ensureArray([1])).toEqual([1]); 183 | expect(ArrayExtensions.ensureArray(1)).toEqual([1]); 184 | }); 185 | 186 | it("Get index by predicate", () => { 187 | expect(ArrayExtensions.indexOf([1, 2, 3], (num: number) => num === 2)).toEqual(1); 188 | expect(ArrayExtensions.indexOf([1, 2, 3], (num: number) => num === 4)).toEqual(-1); 189 | 190 | }); 191 | 192 | describe("insertSorted", () => { 193 | it("unsorted calls", () => { 194 | let list: number[] = []; 195 | expect(ArrayExtensions.insertSorted(list, 1)).toBe(true); 196 | expect(ArrayExtensions.insertSorted(list, 3)).toBe(true); 197 | expect(ArrayExtensions.insertSorted(list, 2)).toBe(true); 198 | expect(ArrayExtensions.insertSorted(list, 0)).toBe(true); 199 | expect(list).toEqual([0, 1, 2, 3]); 200 | }); 201 | 202 | it("duplicates", () => { 203 | let list: number[] = []; 204 | expect(ArrayExtensions.insertSorted(list, 1)).toBe(true); 205 | expect(ArrayExtensions.insertSorted(list, 1)).toBe(false); 206 | expect(list).toEqual([1]); 207 | }); 208 | }); 209 | 210 | describe("removeFirst", () => { 211 | it("found", () => { 212 | let list = [1, 2, 3]; 213 | expect(ArrayExtensions.removeFirst(list, 2)).toBe(true); 214 | expect(list).toEqual([1, 3]); 215 | }); 216 | 217 | it("not found", () => { 218 | let list = [1, 3]; 219 | expect(ArrayExtensions.removeFirst(list, 2)).toBe(false); 220 | expect(list).toEqual([1, 3]); 221 | }); 222 | 223 | it("empty list", () => { 224 | expect(ArrayExtensions.removeFirst([], 2)).toBe(false); 225 | }); 226 | }); 227 | 228 | describe("isArrayOrInheritedArray", () => { 229 | it("array", () => { 230 | let emptyArray = []; 231 | let nonEmptyArray = [1, 2, 3, 4, 5]; 232 | 233 | expect(ArrayExtensions.isArrayOrInheritedArray(emptyArray)).toBe(true); 234 | expect(ArrayExtensions.isArrayOrInheritedArray(nonEmptyArray)).toBe(true); 235 | }); 236 | 237 | it("inherited array", () => { 238 | let emptyInheritedArray = Prototype.inherit([]); 239 | let nonEmptyInheritedArray = Prototype.inherit(["a", "b", "c", "d"]); 240 | let modifiedInheritedArray = Prototype.inherit(["a", "b", "c", "d"]); 241 | modifiedInheritedArray.push("e"); 242 | 243 | expect(ArrayExtensions.isArrayOrInheritedArray(emptyInheritedArray)).toBe(true); 244 | expect(ArrayExtensions.isArrayOrInheritedArray(nonEmptyInheritedArray)).toBe(true); 245 | expect(ArrayExtensions.isArrayOrInheritedArray(modifiedInheritedArray)).toBe(true); 246 | }); 247 | 248 | it("not array", () => { 249 | let emptyObj = {}; 250 | let objLookingLikeArray = { 251 | 0: "a", 252 | 1: "b", 253 | length: 2, 254 | }; 255 | 256 | expect(ArrayExtensions.isArrayOrInheritedArray(emptyObj)).toBe(false); 257 | expect(ArrayExtensions.isArrayOrInheritedArray(objLookingLikeArray)).toBe(false); 258 | }); 259 | }); 260 | 261 | describe("isSorted", function () { 262 | function StringLengthComparer(a: string, b: string): number { 263 | return a.length - b.length; 264 | } 265 | 266 | it("isSorted - array with length 0", () => { 267 | let values: string[] = []; 268 | 269 | expect(ArrayExtensions.isSorted(values, StringLengthComparer)).toBe(true, "an empty array is always sorted"); 270 | }); 271 | 272 | it("isSorted - array with length 1", () => { 273 | let values: string[] = ["first"]; 274 | 275 | expect(ArrayExtensions.isSorted(values, StringLengthComparer)).toBe(true, "an array of single element is always sorted"); 276 | }); 277 | 278 | it("isSorted - array sorted", () => { 279 | let values: string[] = ["long", "longer", "longest"]; 280 | 281 | expect(ArrayExtensions.isSorted(values, StringLengthComparer)).toBe(true); 282 | }); 283 | 284 | it("isSorted - array sorted in reverse order", () => { 285 | let values: string[] = ["longest", "longer", "long"]; 286 | 287 | expect(ArrayExtensions.isSorted(values, StringLengthComparer)).toBe(false); 288 | }); 289 | 290 | it("isSorted - array not sorted", () => { 291 | let values: string[] = ["longest", "long", "longer"]; 292 | 293 | expect(ArrayExtensions.isSorted(values, StringLengthComparer)).toBe(false); 294 | }); 295 | 296 | it("isSortedNumeric - array with length 0", () => { 297 | let values: number[] = []; 298 | expect(ArrayExtensions.isSortedNumeric(values)).toBe(true, "an empty array is in default (ascending) order"); 299 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ false)).toBe(true, "an empty array is in ascending order"); 300 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ true)).toBe(true, "an empty array is also in descending order"); 301 | }); 302 | 303 | it("isSortedNumeric - array with length 1", () => { 304 | let values: number[] = [111]; 305 | expect(ArrayExtensions.isSortedNumeric(values)).toBe(true, "an array of single element is in default (ascending) order"); 306 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ false)).toBe(true, "an array of single element is in ascending order"); 307 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ true)).toBe(true, "an array of single element is also in descending order"); 308 | }); 309 | 310 | it("isSortedNumeric - array in ascending order", () => { 311 | let values: number[] = [1, 3, 3, 7]; 312 | expect(ArrayExtensions.isSortedNumeric(values)).toBe(true, "checking if in default (ascending) order"); 313 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ false)).toBe(true, "checking if in ascending order"); 314 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ true)).toBe(false, "checking if in descending order"); 315 | }); 316 | 317 | it("isSortedNumeric - array in descending order", () => { 318 | let values: number[] = [1, -3, -3, -7]; 319 | expect(ArrayExtensions.isSortedNumeric(values)).toBe(false, "checking if in default (ascending) order"); 320 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ false)).toBe(false, "checking if in ascending order"); 321 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ true)).toBe(true, "checking if in decending order"); 322 | }); 323 | 324 | it("isSortedNumeric - array not in any order", () => { 325 | let values: number[] = [3, 0, 6, 2, 4, 7, 0, 0]; 326 | expect(ArrayExtensions.isSortedNumeric(values)).toBe(false, "checking if in default (ascending) order"); 327 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ false)).toBe(false, "checking if in ascending order"); 328 | expect(ArrayExtensions.isSortedNumeric(values, /* descendingOrder */ true)).toBe(false, "checking if in decending order"); 329 | }); 330 | }); 331 | }); 332 | -------------------------------------------------------------------------------- /test/extensions/enumExtensionsTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import * as EnumExtensions from "../../src/extensions/enumExtensions"; 27 | 28 | describe("EnumExtensions", () => { 29 | it("getBitCount", () => { 30 | const enum TestEnumType { 31 | None = 0, 32 | A = 1, 33 | B = 1 << 1, 34 | C = 1 << 2, 35 | D = 1 << 3, 36 | E = 1 << 4, 37 | Negative1 = 1 << 31, 38 | } 39 | 40 | expect(EnumExtensions.getBitCount(TestEnumType.None)).toBe(0, "TestEnumType.None"); 41 | expect(EnumExtensions.getBitCount(TestEnumType.A)).toBe(1, "TestEnumType.A"); 42 | expect(EnumExtensions.getBitCount(TestEnumType.B)).toBe(1, "TestEnumType.B"); 43 | expect(EnumExtensions.getBitCount(TestEnumType.C)).toBe(1, "TestEnumType.C"); 44 | expect(EnumExtensions.getBitCount(TestEnumType.D)).toBe(1, "TestEnumType.D"); 45 | expect(EnumExtensions.getBitCount(TestEnumType.E)).toBe(1, "TestEnumType.E"); 46 | expect(EnumExtensions.getBitCount(TestEnumType.Negative1)).toBe(1, "TestEnumType.Negative1"); 47 | expect(EnumExtensions.getBitCount(TestEnumType.A | TestEnumType.B)).toBe(2, "TestEnumType.A | TestEnumType.B"); 48 | expect(EnumExtensions.getBitCount(TestEnumType.B | TestEnumType.E)).toBe(2, "TestEnumType.B | TestEnumType.E"); 49 | expect(EnumExtensions.getBitCount(TestEnumType.A | TestEnumType.Negative1)).toBe(2, "TestEnumType.A | TestEnumType.Negative1"); 50 | expect(EnumExtensions.getBitCount(TestEnumType.B | TestEnumType.C | TestEnumType.E)).toBe(3, "TestEnumType.B | TestEnumType.C | TestEnumType.E"); 51 | expect(EnumExtensions.getBitCount(TestEnumType.A | TestEnumType.C | TestEnumType.D | TestEnumType.E)).toBe(4, "TestEnumType.A | TestEnumType.C | TestEnumType.D | TestEnumType.E"); 52 | expect(EnumExtensions.getBitCount(TestEnumType.A | TestEnumType.B | TestEnumType.C | TestEnumType.D | TestEnumType.E)).toBe(5, "TestEnumType.A | TestEnumType.B | TestEnumType.C | TestEnumType.D | TestEnumType.E"); 53 | expect(EnumExtensions.getBitCount(TestEnumType.A | TestEnumType.C | TestEnumType.Negative1)).toBe(3, "TestEnumType.A | TestEnumType.C | TestEnumType.Negative1"); 54 | }); 55 | 56 | it("getBitCount - invalid values", () => { 57 | expect(EnumExtensions.getBitCount(undefined)).toBe(0, "undefined"); 58 | expect(EnumExtensions.getBitCount(null)).toBe(0, "null"); 59 | expect(EnumExtensions.getBitCount({})).toBe(0, "object"); 60 | expect(EnumExtensions.getBitCount(3.14)).toBe(0, "floating point number 3.14"); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/extensions/regExpExtensionsTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import * as RegExpExtensions from "../../src/extensions/regExpExtensions"; 27 | describe("Regexr", () => { 28 | 29 | describe("run", () => { 30 | let T = "@"; 31 | let regex = new RegExp(T, "g"); 32 | // let targets = '@--------@----@--@'; 33 | let targets = `${T}--------${T}----${T}--${T}`; 34 | 35 | function verifyMatch(match: RegExpExecArray, index: number): void { 36 | expect(match[0]).toBe(T); 37 | expect(match.index).toBe(index); 38 | } 39 | 40 | it("finds match", () => { 41 | verifyMatch(RegExpExtensions.run(regex, targets), 0); 42 | }); 43 | 44 | it("always starts at index 0", () => { 45 | verifyMatch(RegExpExtensions.run(regex, targets), 0); 46 | verifyMatch(RegExpExtensions.run(regex, targets), 0); 47 | }); 48 | 49 | describe("with start", () => { 50 | it("finds match", () => { 51 | let match = RegExpExtensions.run(regex, targets, 10); 52 | verifyMatch(match, 14); 53 | }); 54 | 55 | it("starts at specified index", () => { 56 | let match = RegExpExtensions.run(regex, targets, 2); 57 | verifyMatch(match, 9); 58 | 59 | match = RegExpExtensions.run(regex, targets, 10); 60 | verifyMatch(match, 14); 61 | 62 | match = RegExpExtensions.run(regex, targets, 15); 63 | verifyMatch(match, 17); 64 | 65 | match = RegExpExtensions.run(regex, targets, 18); 66 | expect(match).toBe(null); 67 | }); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/jsonComparerTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import * as JsonComparer from "../src/jsonComparer"; 27 | 28 | describe("JsonComparer", () => { 29 | 30 | it("Undefined_Undefined_ShouldBeEqual", () => { 31 | let x; 32 | let y; 33 | 34 | expect(JsonComparer.equals(x, y)).toBe(true); 35 | }); 36 | 37 | it("Null_Null_ShouldBeEqual", () => { 38 | let x = null; 39 | let y = null; 40 | 41 | expect(JsonComparer.equals(x, y)).toBe(true); 42 | }); 43 | 44 | it("Null_Undefined_ShouldBeEqual", () => { 45 | let x = null; 46 | let y; 47 | 48 | expect(JsonComparer.equals(x, y)).toBe(false); 49 | }); 50 | 51 | it("String_String_ShouldBeEqual", () => { 52 | const x = "abc"; 53 | const y = "abc"; 54 | 55 | expect(JsonComparer.equals(x, y)).toBe(true); 56 | }); 57 | 58 | it("String_DifferentString_ShouldBeEqual", () => { 59 | const x = ""; 60 | const y = " "; 61 | 62 | expect(JsonComparer.equals(x, y)).toBe(false); 63 | }); 64 | 65 | it("Number_Number_ShouldBeEqual", () => { 66 | const x = 123; 67 | const y = 123; 68 | 69 | expect(JsonComparer.equals(x, y)).toBe(true); 70 | }); 71 | 72 | it("Number_DifferentNumber_ShouldNotBeEqual", () => { 73 | const x = 123; 74 | const y = 321; 75 | 76 | expect(JsonComparer.equals(x, y)).toBe(false); 77 | }); 78 | 79 | it("Object_String_ShouldNotBeEqual", () => { 80 | const x = {}; 81 | const y = ""; 82 | 83 | expect(JsonComparer.equals(x, y)).toBe(false); 84 | }); 85 | 86 | it("Object_Number_ShouldNotBeEqual", () => { 87 | const x = {}; 88 | const y = 321; 89 | 90 | expect(JsonComparer.equals(x, y)).toBe(false); 91 | }); 92 | 93 | it("ObjectEmpty_ObjectEmpty_ShouldBeEqual", () => { 94 | const x = {}; 95 | const y = {}; 96 | 97 | expect(JsonComparer.equals(x, y)).toBe(true); 98 | }); 99 | 100 | it("Object_Object_ShouldBeEqual", () => { 101 | const x = { prop: { nested: "abc" } }; 102 | const y = { prop: { nested: "abc" } }; 103 | 104 | expect(JsonComparer.equals(x, y)).toBe(true); 105 | }); 106 | 107 | it("Object_Object_ShouldNotBeEqual", () => { 108 | const x = { prop: { nested: "abc" } }; 109 | const y = { prop: { nested: "def" } }; 110 | 111 | expect(JsonComparer.equals(x, y)).toBe(false); 112 | }); 113 | }); -------------------------------------------------------------------------------- /test/numericSequence/numericSequenceRangeTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import { NumericSequenceRange} from "../../src/numericSequence/numericSequenceRange"; 27 | describe("NumericSequenceRange", () => { 28 | it("The class is defined", () => { 29 | expect(NumericSequenceRange).toBeDefined(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/numericSequence/numericSequenceTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import { NumericSequence } from "../../src/numericSequence/numericSequence"; 27 | 28 | describe("NumericSequence", () => { 29 | it("The class is defined", () => { 30 | expect(NumericSequence).toBeDefined(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/pixelConverterTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import * as PixelConverter from "../src/pixelConverter"; 27 | describe("PixelConverter", () => { 28 | 29 | it("toString", () => { 30 | expect(PixelConverter.toString(34)).toBe("34px"); 31 | }); 32 | 33 | it("fromPoint", () => { 34 | expect(PixelConverter.fromPoint(10.5)).toBe("14px"); 35 | }); 36 | 37 | it("toPoint", () => { 38 | expect(PixelConverter.toPoint(24)).toBe(18); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/prototypeTests.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import * as Prototype from "../src/prototype"; 27 | 28 | describe("Prototype", () => { 29 | it("inherit: base", () => { 30 | const base = { prop: "abc" }; 31 | let inherited = Prototype.inherit(base); 32 | 33 | expect(Object.getPrototypeOf(inherited)).toBe(base); 34 | expect(inherited.prop).toBe("abc"); 35 | }); 36 | 37 | it("inherit: including override func", () => { 38 | const base = { prop: "abc", prop2: "def" }; 39 | let inherited = Prototype.inherit(base, arg => arg.prop2 = "ghi"); 40 | 41 | expect(Object.getPrototypeOf(inherited)).toBe(base); 42 | expect(inherited.prop).toBe("abc"); 43 | expect(inherited.prop2).toBe("ghi"); 44 | }); 45 | 46 | it("inheritSingle: base", () => { 47 | const base = { prop: "abc" }; 48 | 49 | let inherited = Prototype.inheritSingle(base); 50 | let proto = Object.getPrototypeOf(inherited); 51 | 52 | expect(proto).toBe(base); 53 | expect(inherited.prop).toBe("abc"); 54 | }); 55 | 56 | it("inheritSingle: existing object prototype", () => { 57 | const existingProto = { prop: "abc" }; 58 | let base = Prototype.inherit(existingProto); 59 | 60 | expect(Object.getPrototypeOf(base)).toBe(existingProto); 61 | 62 | let inherited = Prototype.inheritSingle(base); 63 | let proto = Object.getPrototypeOf(inherited); 64 | expect(proto).toBe(existingProto); 65 | expect(Object.getPrototypeOf(proto)).toBe(Object.prototype); 66 | }); 67 | 68 | it("inheritSingle: existing array prototype", () => { 69 | const existingProto = ["a", "b", "c"]; 70 | let base = Prototype.inherit(existingProto); 71 | 72 | expect(Object.getPrototypeOf(base)).toBe(existingProto); 73 | 74 | let inherited = Prototype.inheritSingle(base); 75 | let proto = Object.getPrototypeOf(inherited); 76 | expect(proto).toBe(existingProto); 77 | expect(Object.getPrototypeOf(proto)).toBe(Array.prototype); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/textSizeDefaultsTest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import { getScale } from "../src/textSizeDefaults"; 27 | 28 | describe("TextSizeDefaults", () => { 29 | 30 | it("getScale", () => { 31 | expect(getScale(24)).toBeCloseTo(0.50, 1); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/valueTypeTests.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Power BI Visualizations 3 | * 4 | * Copyright (c) Microsoft Corporation 5 | * All rights reserved. 6 | * MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the ""Software""), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | import { ValueType, ExtendedType } from "../src/valueType"; 27 | 28 | describe("isCompatibleFrom", () => { 29 | it("Both numbers is true", () => { 30 | let number1 = ValueType.fromDescriptor({ numeric: true }); 31 | let number2 = ValueType.fromDescriptor({ numeric: true }); 32 | 33 | expect(number1.isCompatibleFrom(number2)).toBeTruthy(); 34 | }); 35 | 36 | it("Both numbers but different extended type is true.", () => { 37 | let number1 = ValueType.fromExtendedType(ExtendedType.Decimal); 38 | let number2 = ValueType.fromExtendedType(ExtendedType.Double); 39 | 40 | expect(number1.isCompatibleFrom(number2)).toBeTruthy(); 41 | }); 42 | 43 | it("One is number other is not", () => { 44 | let number1 = ValueType.fromExtendedType(ExtendedType.Decimal); 45 | let number2 = ValueType.fromExtendedType(ExtendedType.Boolean); 46 | 47 | expect(number1.isCompatibleFrom(number2)).toBeFalsy(); 48 | }); 49 | }); 50 | 51 | describe("Variant ValueType", () => { 52 | it("Create variant ValueType and check the variationTypes", () => { 53 | let variantType = ValueType.fromDescriptor({ variant: [{ numeric: true }, { dateTime: true }] } as any); 54 | 55 | expect(variantType.extendedType).toBe(ExtendedType.Variant); 56 | expect(variantType.variant).toBeDefined(); 57 | expect(variantType.variant).toEqual([ValueType.fromDescriptor({ numeric: true }), ValueType.fromDescriptor({ dateTime: true })]); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "declaration": true, 7 | "sourceMap": true, 8 | "module": "ES2015", 9 | "moduleResolution": "node", 10 | "target": "ES2015", 11 | "lib": [ 12 | "es2015", 13 | "dom" 14 | ], 15 | "outDir": "./lib" 16 | }, 17 | "files": [ 18 | "src/index.ts" 19 | ], 20 | "include": [ 21 | "src/index.ts" 22 | ] 23 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | devtool: 'source-map', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tsx?$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/ 12 | }, 13 | { 14 | test: /\.tsx?$/i, 15 | enforce: 'post', 16 | include: /(src)/, 17 | exclude: /(node_modules|resources\/js\/vendor)/, 18 | loader: 'coverage-istanbul-loader', 19 | options: { esModules: true } 20 | }, 21 | { 22 | test: /\.json$/, 23 | loader: 'json-loader' 24 | } 25 | ] 26 | }, 27 | externals: { 28 | "powerbi-visuals-api": '{}' 29 | }, 30 | resolve: { 31 | extensions: ['.tsx', '.ts', '.js', '.css'] 32 | }, 33 | output: { 34 | path: path.resolve(__dirname, ".tmp") 35 | }, 36 | plugins: [ 37 | ] 38 | }; 39 | --------------------------------------------------------------------------------