├── .eslintignore
├── .eslintrc.js
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── symbol_request.md
└── workflows
│ ├── checks.yaml
│ └── npm-publish.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── dist
├── astrochart.js
└── project
│ └── src
│ ├── animation
│ ├── animator.d.ts
│ └── timer.d.ts
│ ├── aspect.d.ts
│ ├── chart.d.ts
│ ├── index.d.ts
│ ├── radix.d.ts
│ ├── settings.d.ts
│ ├── svg.d.ts
│ ├── transit.d.ts
│ ├── utils.d.ts
│ └── zodiac.d.ts
├── doc
└── images
│ └── transits.png
├── jest.config.js
├── package-lock.json
├── package.json
├── project
├── __tests__
│ ├── __snapshots__
│ │ └── radix.test.ts.snap
│ └── radix.test.ts
├── examples
│ ├── 2ChartsOnPage
│ │ └── 2radix.html
│ ├── debug
│ │ └── calibration.html
│ ├── radix
│ │ ├── radix.html
│ │ ├── radix_2016_11_15.html
│ │ ├── radix_collision.html
│ │ └── radix_custom_settings.html
│ └── transit
│ │ ├── animate.html
│ │ ├── stroke_only.html
│ │ └── transit.html
└── src
│ ├── animation
│ ├── animator.ts
│ └── timer.ts
│ ├── aspect.test.ts
│ ├── aspect.ts
│ ├── chart.test.ts
│ ├── chart.ts
│ ├── index.ts
│ ├── radix.test.ts
│ ├── radix.ts
│ ├── settings.ts
│ ├── svg.test.ts
│ ├── svg.ts
│ ├── transit.ts
│ ├── utils.test.ts
│ ├── utils.ts
│ ├── zodiac.test.ts
│ └── zodiac.ts
├── tsconfig.json
└── webpack.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | *.test.ts
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'commonjs': true,
5 | 'es6': true
6 | },
7 | 'extends': [
8 | 'eslint:recommended',
9 | 'plugin:@typescript-eslint/eslint-recommended'
10 | ],
11 | 'globals': {
12 | 'Atomics': 'readonly',
13 | 'SharedArrayBuffer': 'readonly'
14 | },
15 | 'parser': '@typescript-eslint/parser',
16 | 'parserOptions': {
17 | 'sourceType': 'module',
18 | 'ecmaVersion': 2018
19 | },
20 | 'plugins': [
21 | '@typescript-eslint'
22 | ],
23 | 'rules': {
24 | 'indent': [
25 | 'error',
26 | 2,
27 | { 'SwitchCase': 1 }
28 | ],
29 | 'linebreak-style': [
30 | 'error',
31 | 'unix'
32 | ],
33 | 'quotes': [
34 | 'error',
35 | 'single',
36 | { 'allowTemplateLiterals': true }
37 | ],
38 | 'semi': [
39 | 'error',
40 | 'never'
41 | ],
42 | 'no-var': ['error'],
43 | '@typescript-eslint/member-delimiter-style': [
44 | 'error',
45 | {
46 | 'multiline': {
47 | 'delimiter': 'none',
48 | 'requireLast': true
49 | },
50 | 'singleline': {
51 | 'delimiter': 'semi',
52 | 'requireLast': false
53 | }
54 | }
55 | ],
56 | '@typescript-eslint/explicit-function-return-type': 'off',
57 | '@typescript-eslint/no-unused-vars': ['warn', { 'vars': 'all', 'args': 'after-used', 'ignoreRestSiblings': false }],
58 | 'no-unused-vars': 'off',
59 | '@typescript-eslint/restrict-plus-operands': 'off',
60 | //'@typescript-eslint/no-unused-vars': 'off',
61 | '@typescript-eslint/prefer-optional-chain': 'off',
62 | '@typescript-eslint/naming-convention': 'off',
63 | '@typescript-eslint/strict-boolean-expressions': 'off',
64 | '@typescript-eslint/prefer-nullish-coalescing': 'off',
65 | 'no-prototype-builtins': 'off'
66 | },
67 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: afucher
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Additional info (please complete the following information):**
27 | - Browser [e.g. chrome, safari]
28 |
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/symbol_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Symbol request
3 | about: Suggest a new symbol to be supported
4 | title: ''
5 | labels: symbol request
6 | assignees: ''
7 |
8 | ---
9 |
10 | **What is the symbol that you need?**
11 | The name of the symbol/point, and if it is not a common planet/point, please describe why you need and the use of that.
12 |
13 | **Can you contribute to this request?**
14 | [ ] Yes, I have the svg and will add it to this issue.
15 | [ ] Yes, I will open a PR with the implementation.
16 |
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the symbol request here.
20 |
--------------------------------------------------------------------------------
/.github/workflows/checks.yaml:
--------------------------------------------------------------------------------
1 | name: checks
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | lint:
10 | runs-on: ubuntu-22.04
11 | steps:
12 | - uses: actions/checkout@v3
13 | - uses: actions/setup-node@v3
14 | with:
15 | node-version: 16
16 | - run: npm ci
17 | - run: npm run lint
18 |
19 | test:
20 | runs-on: ubuntu-22.04
21 | steps:
22 | - uses: actions/checkout@v3
23 | - uses: actions/setup-node@v3
24 | with:
25 | node-version: 16
26 | - run: npm ci
27 | - run: npm run test:coverage
28 |
29 | build: # sanity check that build does not throw errors
30 | runs-on: ubuntu-22.04
31 | steps:
32 | - uses: actions/checkout@v3
33 | - uses: actions/setup-node@v3
34 | with:
35 | node-version: 16
36 | - run: npm ci
37 | - run: npm run build
38 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [published]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 16
18 | - run: npm ci
19 | - run: npm test
20 |
21 | publish-npm:
22 | needs: build
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v3
26 | - uses: actions/setup-node@v3
27 | with:
28 | node-version: 16
29 | registry-url: https://registry.npmjs.org/
30 | - run: npm ci
31 | - run: npm publish
32 | env:
33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.settings/*
2 | /node_modules/*
3 | .project
4 | .DS_Store
5 | build/
6 | coverage/
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at arthurfucher@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2018 Matheus Alves
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 |
7 | of this software and associated documentation files (the "Software"), to deal
8 |
9 | in the Software without restriction, including without limitation the rights
10 |
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 |
13 | copies of the Software, and to permit persons to whom the Software is
14 |
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 |
19 | copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 |
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 |
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 |
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 |
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 |
31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 |
33 | SOFTWARE.
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AstroChart
2 |
3 | 
4 |
5 | A free and open-source TypeScript library for generating SVG charts to display planets in astrology. It does not calculate any positions of the planets in Universe.
6 |
7 | - Pure TypeScript implementation without dependencies.
8 | - SVG graphics.
9 | - Tested code.
10 |
11 | ## Example:
12 |
13 |
14 |
15 | ## Documentation
16 | A documentation is in progress, please checkout [website](https://astrodraw.github.io/).
17 |
18 | ## Contribution
19 | Contribution is always welcome. You can contribute in different ways:
20 | - Start or participate in the [discussions](https://github.com/AstroDraw/AstroChart/discussions)
21 | - Check opened issues, or improve our documentation
22 | - Open [an issue](https://github.com/AstroDraw/AstroChart/issues) to report a bug or give some enchancement idea
23 | - Open a PR with bug fixes or new features. To avoid rework, if is not small, is always good to open an issue to discuss before.
24 |
25 | ## Support
26 | Do you want to support the development of AstroChart? Here is some ways:
27 |
28 | Is your project using? Please [comment here](https://github.com/AstroDraw/AstroChart/discussions/48) so we can share nice projects that are using.
29 |
30 | A nice way to support is sharing this project with other people.
31 |
32 | Also, if you are a company consider sponsoring the project or [buying me a coffee](https://ko-fi.com/afucher)
33 |
--------------------------------------------------------------------------------
/dist/project/src/animation/animator.d.ts:
--------------------------------------------------------------------------------
1 | import type { AstroData } from '../radix';
2 | import type { Settings } from '../settings';
3 | import type Transit from '../transit';
4 | import Timer from './timer';
5 | /**
6 | * Transit chart animator
7 | *
8 | * Animates the object on a circle.
9 | *
10 | * @class
11 | * @public
12 | * @constructor
13 | * @param {Object} from, {"Sun":[12], "Moon":[60]}
14 | * @param {Object} to, {"Sun":[30], "Moon":[180]}
15 | * @param {Object} settings, {cx:100, cy:100, radius:200, prefix:"astro-chart-"}
16 | */
17 | declare class Animator {
18 | transit: Transit;
19 | isReverse: boolean;
20 | rotation: number;
21 | settings: Settings;
22 | actualPlanetPos: any;
23 | timer: Timer;
24 | timeSinceLoopStart: number;
25 | context: this;
26 | cuspsElement: any;
27 | data: AstroData;
28 | duration: number;
29 | callback: () => void;
30 | constructor(transit: Transit, settings: Settings);
31 | /**
32 | * Animate objects
33 |
34 | * @param {Object} data, targetPositions
35 | * @param {Integer} duration - seconds
36 | * @param {boolean} isReverse
37 | * @param {Function} callbck - start et the end of animation
38 | */
39 | animate(data: any, duration: number, isReverse: boolean, callback: () => void): void;
40 | update(deltaTime?: number): void;
41 | updateCusps(expectedNumberOfLoops: number): void;
42 | updatePlanets(expectedNumberOfLoops: number): void;
43 | }
44 | export default Animator;
45 |
--------------------------------------------------------------------------------
/dist/project/src/animation/timer.d.ts:
--------------------------------------------------------------------------------
1 | declare class Timer {
2 | debug: boolean;
3 | callback: (delta: number) => void;
4 | boundTick_: FrameRequestCallback;
5 | lastGameLoopFrame: number;
6 | requestID_: number | undefined;
7 | constructor(callback: (delta: number) => void, debug: boolean);
8 | start(): void;
9 | stop(): void;
10 | isRunning(): boolean;
11 | tick(): void;
12 | }
13 | export default Timer;
14 |
--------------------------------------------------------------------------------
/dist/project/src/aspect.d.ts:
--------------------------------------------------------------------------------
1 | import type { Points } from './radix';
2 | import type { AspectData, Settings } from './settings';
3 | export interface FormedAspect {
4 | point: {
5 | name: string;
6 | position: number;
7 | };
8 | toPoint: {
9 | name: string;
10 | position: number;
11 | };
12 | aspect: {
13 | name: string;
14 | degree: number;
15 | color: string;
16 | orbit: number;
17 | };
18 | precision: string;
19 | }
20 | /**
21 | * Aspects calculator
22 | *
23 | * @class
24 | * @public
25 | * @constructor
26 | * @param {AspectPoints} points; {"Sun":[0], "Moon":[90], "Neptune":[120], "As":[30]}
27 | * @param {Object | null } settings
28 | */
29 | declare class AspectCalculator {
30 | settings: Partial;
31 | toPoints: Points;
32 | context: this;
33 | constructor(toPoints: Points, settings?: Partial);
34 | /**
35 | * Getter for this.toPoints
36 | * @see constructor
37 | *
38 | * @return {Object}
39 | */
40 | getToPoints(): Points;
41 | /**
42 | * Radix aspects
43 | *
44 | * In radix calculation is the param "points" the same as param "toPoints" in constructor
45 | * , but without special points such as: As,Ds, Mc, Ic, ...
46 | *
47 | * @param {Object} points; {"Sun":[0], "Moon":[90]}
48 | *
49 | * @return {Array} [{"aspect":{"name":"conjunction", "degree":120}"", "point":{"name":"Sun", "position":123}, "toPoint":{"name":"Moon", "position":345}, "precision":0.5}]]
50 | */
51 | radix(points: Points): FormedAspect[];
52 | /**
53 | * Transit aspects
54 | *
55 | * @param {Object} points - transiting points; {"Sun":[0, 1], "Uranus":[90, -1], "NAME":[ANGLE, SPEED]};
56 | * @return {Array} [{"aspect":{"name":"conjunction", "degree":120}"", "point":{"name":"Sun", "position":123}, "toPoint":{"name":"Moon", "position":345}, "precision":0.5}]]
57 | */
58 | transit(points: Points): FormedAspect[];
59 | hasAspect(point: number, toPoint: number, aspect: AspectData): boolean;
60 | calcPrecision(point: number, toPoint: number, aspect: number): number;
61 | isTransitPointApproachingToAspect(aspect: number, toPoint: number, point: number): boolean;
62 | compareAspectsByPrecision(a: FormedAspect, b: FormedAspect): number;
63 | }
64 | export default AspectCalculator;
65 |
--------------------------------------------------------------------------------
/dist/project/src/chart.d.ts:
--------------------------------------------------------------------------------
1 | import type { Settings } from './settings';
2 | import Radix from './radix';
3 | import type { AstroData } from './radix';
4 | import SVG from './svg';
5 | /**
6 | * Displays astrology charts.
7 | *
8 | * @class
9 | * @public
10 | * @constructor
11 | * @param {String} elementId - root DOMElement
12 | * @param {int} width
13 | * @param {int} height
14 | * @param {Object} settings
15 | */
16 | declare class Chart {
17 | paper: SVG;
18 | cx: number;
19 | cy: number;
20 | radius: number;
21 | settings: Settings;
22 | constructor(elementId: string, width: number, height: number, settings?: Partial);
23 | /**
24 | * Display radix horoscope
25 | *
26 | * @param {Object} data
27 | * @example
28 | * {
29 | * "points":{"Moon":[0], "Sun":[30], ... },
30 | * "cusps":[300, 340, 30, 60, 75, 90, 116, 172, 210, 236, 250, 274]
31 | * }
32 | *
33 | * @return {Radix} radix
34 | */
35 | radix(data: AstroData): Radix;
36 | /**
37 | * Scale chart
38 | *
39 | * @param {int} factor
40 | */
41 | scale(factor: number): void;
42 | /**
43 | * Draw the symbol on the axis.
44 | * For debug only.
45 | *
46 | */
47 | calibrate(): Chart;
48 | }
49 | export default Chart;
50 |
--------------------------------------------------------------------------------
/dist/project/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import Chart from './chart';
2 | import AspectCalculator from './aspect';
3 | import { Settings } from './settings';
4 | export { Chart, AspectCalculator, Settings };
5 | export default Chart;
6 |
--------------------------------------------------------------------------------
/dist/project/src/radix.d.ts:
--------------------------------------------------------------------------------
1 | import type { FormedAspect } from './aspect';
2 | import Transit from './transit';
3 | import type SVG from './svg';
4 | import type { Settings } from './settings';
5 | export type Points = Record;
6 | export interface LocatedPoint {
7 | name: string;
8 | x: number;
9 | y: number;
10 | r: number;
11 | angle: number;
12 | pointer?: number;
13 | index?: number;
14 | }
15 | export interface AstroData {
16 | planets: Points;
17 | cusps: number[];
18 | }
19 | /**
20 | * Radix charts.
21 | *
22 | * @class
23 | * @public
24 | * @constructor
25 | * @param {this.settings.SVG} paper
26 | * @param {int} cx
27 | * @param {int} cy
28 | * @param {int} radius
29 | * @param {Object} data
30 | */
31 | declare class Radix {
32 | settings: Settings;
33 | data: AstroData;
34 | paper: SVG;
35 | cx: number;
36 | cy: number;
37 | radius: number;
38 | locatedPoints: LocatedPoint[];
39 | rulerRadius: number;
40 | pointRadius: number;
41 | toPoints: Points;
42 | shift: number;
43 | universe: Element;
44 | context: this;
45 | constructor(paper: SVG, cx: number, cy: number, radius: number, data: AstroData, settings: Settings);
46 | /**
47 | * Draw background
48 | */
49 | drawBg(): void;
50 | /**
51 | * Draw universe.
52 | */
53 | drawUniverse(): void;
54 | /**
55 | * Draw points
56 | */
57 | drawPoints(): void;
58 | drawAxis(): void;
59 | /**
60 | * Draw cusps
61 | */
62 | drawCusps(): void;
63 | /**
64 | * Draw aspects
65 | * @param{Array | null} customAspects - posible custom aspects to draw;
66 | */
67 | aspects(customAspects?: FormedAspect[] | null): Radix;
68 | /**
69 | * Add points of interest for aspects calculation
70 | * @param {Obect} points, {"As":[0],"Ic":[90],"Ds":[180],"Mc":[270]}
71 | * @see (this.settings.AspectCalculator( toPoints) )
72 | */
73 | addPointsOfInterest(points: Points): Radix;
74 | drawRuler(): void;
75 | /**
76 | * Draw circles
77 | */
78 | drawCircles(): void;
79 | /**
80 | * Display transit horoscope
81 | *
82 | * @param {Object} data
83 | * @example
84 | * {
85 | * "planets":{"Moon":[0], "Sun":[30], ... },
86 | * "cusps":[300, 340, 30, 60, 75, 90, 116, 172, 210, 236, 250, 274], *
87 | * }
88 | *
89 | * @return {Transit} transit
90 | */
91 | transit(data: AstroData): Transit;
92 | }
93 | export default Radix;
94 |
--------------------------------------------------------------------------------
/dist/project/src/settings.d.ts:
--------------------------------------------------------------------------------
1 | import type SVG from './svg';
2 | export interface AspectData {
3 | degree: number;
4 | orbit: number;
5 | color: string;
6 | }
7 | export type Aspect = Record;
8 | export interface Dignity {
9 | name: string;
10 | position: number;
11 | orbit: number;
12 | }
13 | export interface Settings {
14 | SYMBOL_SCALE: number;
15 | COLOR_BACKGROUND: string;
16 | POINTS_COLOR: string;
17 | POINTS_TEXT_SIZE: number;
18 | POINTS_STROKE: number;
19 | SIGNS_COLOR: string;
20 | SIGNS_STROKE: number;
21 | MARGIN: number;
22 | PADDING: number;
23 | ID_CHART: string;
24 | ID_RADIX: string;
25 | ID_TRANSIT: string;
26 | ID_ASPECTS: string;
27 | ID_POINTS: string;
28 | ID_SIGNS: string;
29 | ID_CIRCLES: string;
30 | ID_AXIS: string;
31 | ID_CUSPS: string;
32 | ID_RULER: string;
33 | ID_BG: string;
34 | CIRCLE_COLOR: string;
35 | CIRCLE_STRONG: number;
36 | LINE_COLOR: string;
37 | INDOOR_CIRCLE_RADIUS_RATIO: number;
38 | INNER_CIRCLE_RADIUS_RATIO: number;
39 | RULER_RADIUS: number;
40 | SYMBOL_SUN: string;
41 | SYMBOL_MOON: string;
42 | SYMBOL_MERCURY: string;
43 | SYMBOL_VENUS: string;
44 | SYMBOL_MARS: string;
45 | SYMBOL_JUPITER: string;
46 | SYMBOL_SATURN: string;
47 | SYMBOL_URANUS: string;
48 | SYMBOL_NEPTUNE: string;
49 | SYMBOL_PLUTO: string;
50 | SYMBOL_CHIRON: string;
51 | SYMBOL_LILITH: string;
52 | SYMBOL_NNODE: string;
53 | SYMBOL_SNODE: string;
54 | SYMBOL_FORTUNE: string;
55 | SYMBOL_AS: string;
56 | SYMBOL_DS: string;
57 | SYMBOL_MC: string;
58 | SYMBOL_IC: string;
59 | SYMBOL_AXIS_FONT_COLOR: string;
60 | SYMBOL_AXIS_STROKE: number;
61 | SYMBOL_CUSP_1: string;
62 | SYMBOL_CUSP_2: string;
63 | SYMBOL_CUSP_3: string;
64 | SYMBOL_CUSP_4: string;
65 | SYMBOL_CUSP_5: string;
66 | SYMBOL_CUSP_6: string;
67 | SYMBOL_CUSP_7: string;
68 | SYMBOL_CUSP_8: string;
69 | SYMBOL_CUSP_9: string;
70 | SYMBOL_CUSP_10: string;
71 | SYMBOL_CUSP_11: string;
72 | SYMBOL_CUSP_12: string;
73 | CUSPS_STROKE: number;
74 | CUSPS_FONT_COLOR: string;
75 | SYMBOL_ARIES: string;
76 | SYMBOL_TAURUS: string;
77 | SYMBOL_GEMINI: string;
78 | SYMBOL_CANCER: string;
79 | SYMBOL_LEO: string;
80 | SYMBOL_VIRGO: string;
81 | SYMBOL_LIBRA: string;
82 | SYMBOL_SCORPIO: string;
83 | SYMBOL_SAGITTARIUS: string;
84 | SYMBOL_CAPRICORN: string;
85 | SYMBOL_AQUARIUS: string;
86 | SYMBOL_PISCES: string;
87 | SYMBOL_SIGNS: string[];
88 | COLOR_ARIES: string;
89 | COLOR_TAURUS: string;
90 | COLOR_GEMINI: string;
91 | COLOR_CANCER: string;
92 | COLOR_LEO: string;
93 | COLOR_VIRGO: string;
94 | COLOR_LIBRA: string;
95 | COLOR_SCORPIO: string;
96 | COLOR_SAGITTARIUS: string;
97 | COLOR_CAPRICORN: string;
98 | COLOR_AQUARIUS: string;
99 | COLOR_PISCES: string;
100 | COLORS_SIGNS: string[];
101 | CUSTOM_SYMBOL_FN: null | ((name: string, x: number, y: number, context: SVG) => Element);
102 | SHIFT_IN_DEGREES: number;
103 | STROKE_ONLY: boolean;
104 | ADD_CLICK_AREA: boolean;
105 | COLLISION_RADIUS: number;
106 | ASPECTS: Aspect;
107 | SHOW_DIGNITIES_TEXT: boolean;
108 | DIGNITIES_RULERSHIP: string;
109 | DIGNITIES_DETRIMENT: string;
110 | DIGNITIES_EXALTATION: string;
111 | DIGNITIES_EXACT_EXALTATION: string;
112 | DIGNITIES_FALL: string;
113 | DIGNITIES_EXACT_EXALTATION_DEFAULT: Dignity[];
114 | ANIMATION_CUSPS_ROTATION_SPEED: number;
115 | DEBUG: boolean;
116 | }
117 | declare const default_settings: Settings;
118 | export default default_settings;
119 |
--------------------------------------------------------------------------------
/dist/project/src/svg.d.ts:
--------------------------------------------------------------------------------
1 | import type { Settings } from './settings';
2 | /**
3 | * SVG tools.
4 | *
5 | * @class
6 | * @public
7 | * @constructor
8 | * @param {String} elementId - root DOM Element
9 | * @param {int} width
10 | * @param {int} height
11 | */
12 | declare class SVG {
13 | settings: Settings;
14 | _paperElementId: string;
15 | DOMElement: SVGSVGElement;
16 | root: Element;
17 | width: number;
18 | height: number;
19 | context: this;
20 | constructor(elementId: string, width: number, height: number, settings: Settings);
21 | _getSymbol(name: string, x: number, y: number): Element;
22 | /**
23 | * Get a required symbol.
24 | *
25 | * @param {String} name
26 | * @param {int} x
27 | * @param {int} y
28 | *
29 | * @return {SVGElement g}
30 | */
31 | getSymbol(name: string, x: number, y: number): Element;
32 | /**
33 | * Create transparent rectangle.
34 | *
35 | * Used to improve area click, @see this.settings.ADD_CLICK_AREA
36 | *
37 | * @param {Number} x
38 | * @param {Number} y
39 | *
40 | * @return {Element} rect
41 | */
42 | createRectForClick(x: number, y: number): Element;
43 | /**
44 | * Get ID for sign wrapper.
45 | *
46 | * @param {String} sign
47 | *
48 | * @return {String id}
49 | */
50 | getSignWrapperId(sign: string): string;
51 | /**
52 | * Get ID for house wrapper.
53 | *
54 | * @param {String} house
55 | *
56 | * @return {String id}
57 | */
58 | getHouseIdWrapper(house: string): string;
59 | sun(x: number, y: number): Element;
60 | moon(x: number, y: number): Element;
61 | mercury(x: number, y: number): Element;
62 | venus(x: number, y: number): Element;
63 | mars(x: number, y: number): Element;
64 | jupiter(x: number, y: number): Element;
65 | saturn(x: number, y: number): Element;
66 | uranus(x: number, y: number): Element;
67 | neptune(x: number, y: number): Element;
68 | pluto(x: number, y: number): Element;
69 | chiron(x: number, y: number): Element;
70 | lilith(x: number, y: number): Element;
71 | nnode(x: number, y: number): Element;
72 | snode(x: number, y: number): Element;
73 | fortune(x: number, y: number): Element;
74 | aries(x: number, y: number): Element;
75 | taurus(x: number, y: number): Element;
76 | gemini(x: number, y: number): Element;
77 | cancer(x: number, y: number): Element;
78 | leo(x: number, y: number): Element;
79 | virgo(x: number, y: number): Element;
80 | libra(x: number, y: number): Element;
81 | scorpio(x: number, y: number): Element;
82 | sagittarius(x: number, y: number): Element;
83 | capricorn(x: number, y: number): Element;
84 | aquarius(x: number, y: number): Element;
85 | pisces(x: number, y: number): Element;
86 | /**
87 | * Draw As symbol
88 | */
89 | ascendant(x: number, y: number): Element;
90 | /**
91 | * Draw Ds symbol
92 | */
93 | descendant(x: number, y: number): Element;
94 | /**
95 | * Draw MC symbol
96 | */
97 | mediumCoeli(x: number, y: number): Element;
98 | /**
99 | * Draw IC symbol
100 | */
101 | immumCoeli(x: number, y: number): Element;
102 | number1(x: number, y: number): Element;
103 | number2(x: number, y: number): Element;
104 | number3(x: number, y: number): Element;
105 | number4(x: number, y: number): Element;
106 | number5(x: number, y: number): Element;
107 | number6(x: number, y: number): Element;
108 | number7(x: number, y: number): Element;
109 | number8(x: number, y: number): Element;
110 | number9(x: number, y: number): Element;
111 | number10(x: number, y: number): Element;
112 | number11(x: number, y: number): Element;
113 | number12(x: number, y: number): Element;
114 | /**
115 | * Draw circular sector
116 | *
117 | * @param {int} x - circle x center position
118 | * @param {int} y - circle y center position
119 | * @param {int} radius - circle radius in px
120 | * @param {int} a1 - angleFrom in degree
121 | * @param {int} a2 - angleTo in degree
122 | * @param {int} thickness - from outside to center in px
123 | *
124 | * @return {SVGElement} segment
125 | *
126 | * @see SVG Path arc: https://www.w3.org/TR/SVG/paths.html#PathData
127 | */
128 | segment(x: number, y: number, radius: number, a1: number, a2: number, thickness: number, lFlag?: number, sFlag?: number): Element;
129 | /**
130 | * Draw line in circle
131 | *
132 | * @param {int} x1
133 | * @param {int} y2
134 | * @param {int} x2
135 | * @param {int} y2
136 | * @param {String} color - HTML rgb
137 | *
138 | * @return {SVGElement} line
139 | */
140 | line(x1: number, y1: number, x2: number, y2: number): Element;
141 | /**
142 | * Draw a circle
143 | *
144 | * @param {int} cx
145 | * @param {int} cy
146 | * @param {int} radius
147 | *
148 | * @return {SVGElement} circle
149 | */
150 | circle(cx: number, cy: number, radius: number): Element;
151 | /**
152 | * Draw a text
153 | *
154 | * @param {String} text
155 | * @param {int} x
156 | * @param {int} y
157 | * @param {String} size - etc. "13px"
158 | * @param {String} color - HTML rgb
159 | *
160 | * @return {SVGElement} text
161 | */
162 | text(txt: string, x: number, y: number, size: string, color: string): Element;
163 | }
164 | export default SVG;
165 |
--------------------------------------------------------------------------------
/dist/project/src/transit.d.ts:
--------------------------------------------------------------------------------
1 | import type { FormedAspect } from './aspect';
2 | import type { AstroData, LocatedPoint, Points } from './radix';
3 | import type Radix from './radix';
4 | import type SVG from './svg';
5 | import type { Settings } from './settings';
6 | /**
7 | * Transit charts.
8 | *
9 | * @class
10 | * @public
11 | * @constructor
12 | * @param {this.settings.Radix} radix
13 | * @param {Object} data
14 | */
15 | declare class Transit {
16 | data: AstroData;
17 | paper: SVG;
18 | cx: number;
19 | cy: number;
20 | toPoints: Points;
21 | radius: number;
22 | settings: Settings;
23 | rulerRadius: number;
24 | pointRadius: number;
25 | shift: number;
26 | universe: Element;
27 | context: this;
28 | locatedPoints: LocatedPoint[];
29 | constructor(radix: Radix, data: AstroData, settings: Settings);
30 | /**
31 | * Draw background
32 | */
33 | drawBg(): void;
34 | /**
35 | * Draw planets
36 | *
37 | * @param{undefined | Object} planetsData, posible data planets to draw
38 | */
39 | drawPoints(planetsData?: Points): void;
40 | /**
41 | * Draw circles
42 | */
43 | drawCircles(): void;
44 | /**
45 | * Draw cusps
46 | * @param{undefined | Object} cuspsData, posible data cusps to draw
47 | */
48 | drawCusps(cuspsData?: number[]): void;
49 | drawRuler(): void;
50 | /**
51 | * Draw aspects
52 | * @param{Array | null} customAspects - posible custom aspects to draw;
53 | */
54 | aspects(customAspects: FormedAspect[]): Transit;
55 | /**
56 | * Moves points to another position.
57 | *
58 | * @param {Object} data - planets target positions.
59 | * @param {Integer} duration - in seconds
60 | * @param {boolean} isReverse
61 | * @param {Function | undefined} callbck - the function executed at the end of animation
62 | */
63 | animate(data: AstroData, duration: number, isReverse: boolean, callback: () => void): Transit;
64 | }
65 | export default Transit;
66 |
--------------------------------------------------------------------------------
/dist/project/src/utils.d.ts:
--------------------------------------------------------------------------------
1 | import type { AstroData, LocatedPoint } from './radix';
2 | import type { Settings } from './settings';
3 | /**
4 | * Calculate position of the point on the circle.
5 | *
6 | * @param {int} cx - center x
7 | * @param {int} cy - center y
8 | * @param {int} radius
9 | * @param {double} angle - degree
10 | *
11 | * @return {{x: number, y: number}} Obj - {x:10, y:20}
12 | */
13 | export declare const getPointPosition: (cx: number, cy: number, radius: number, angle: number, astrology: {
14 | SHIFT_IN_DEGREES: number;
15 | }) => {
16 | x: number;
17 | y: number;
18 | };
19 | export declare const degreeToRadians: (degrees: number) => number;
20 | export declare const radiansToDegree: (radians: number) => number;
21 | interface TextLocation {
22 | text: string;
23 | x: number;
24 | y: number;
25 | }
26 | /**
27 | * Calculates positions of the point description
28 | *
29 | * @param {Object} point
30 | * @param {Array} texts
31 | *
32 | * @return {Array} [{text:"abc", x:123, y:456}, {text:"cvb", x:456, y:852}, ...]
33 | */
34 | export declare const getDescriptionPosition: (point: {
35 | x: number;
36 | y: number;
37 | }, texts: string[], astrology: {
38 | COLLISION_RADIUS: number;
39 | SYMBOL_SCALE: number;
40 | }) => TextLocation[];
41 | /**
42 | * Checks a source data
43 | * @private
44 | *
45 | * @param {Object} data
46 | * @return {{hasError: boolean, messages: string[]}} status
47 | */
48 | export declare const validate: (data: AstroData) => {
49 | hasError: boolean;
50 | messages: string[];
51 | };
52 | /**
53 | * Get empty DOMElement with ID
54 | *
55 | * @param{String} elementID
56 | * @param{DOMElement} parent
57 | * @return {DOMElement}
58 | */
59 | export declare const getEmptyWrapper: (parent: Element, elementID: string, _paperElementId: string) => Element;
60 | /**
61 | * Remove childs
62 | *
63 | * @param{DOMElement} parent
64 | */
65 | export declare const removeChilds: (parent: HTMLElement) => void;
66 | /**
67 | * Check circle collision between two objects
68 | *
69 | * @param {Object} circle1, {x:123, y:123, r:50}
70 | * @param {Object} circle2, {x:456, y:456, r:60}
71 | * @return {boolean}
72 | */
73 | export declare const isCollision: (circle1: {
74 | x: number;
75 | y: number;
76 | r: number;
77 | }, circle2: {
78 | x: number;
79 | y: number;
80 | r: number;
81 | }) => boolean;
82 | /**
83 | * Places a new point in the located list
84 | *
85 | * @param {Array} locatedPoints, [{name:"Mars", x:123, y:123, r:50, ephemeris:45.96}, {name:"Sun", x:1234, y:1234, r:50, ephemeris:100.96}]
86 | * @param {Object} point, {name:"Venus", x:78, y:56, r:50, angle:15.96}
87 | * @param {Object} universe - current universe
88 | * @return {Array} locatedPoints
89 | */
90 | export declare const assemble: (locatedPoints: LocatedPoint[], point: LocatedPoint, universe: {
91 | cx: number;
92 | cy: number;
93 | r: number;
94 | }, astrology: Settings) => LocatedPoint[];
95 | /**
96 | * Sets the positions of two points that are in collision.
97 | *
98 | * @param {Object} p1, {..., pointer:123, angle:456}
99 | * @param {Object} p2, {..., pointer:23, angle:56}
100 | */
101 | export declare const placePointsInCollision: (p1: LocatedPoint, p2: LocatedPoint) => void;
102 | /**
103 | * Check collision between angle and object
104 | *
105 | * @param {double} angle
106 | * @param {Array} points, [{x:456, y:456, r:60, angle:123}, ...]
107 | * @return {boolean}
108 | */
109 | export declare const isInCollision: (angle: number, points: string | any[], astrology: Settings) => boolean;
110 | interface InitialEndPosition {
111 | startX: number;
112 | startY: number;
113 | endX: number;
114 | endY: number;
115 | }
116 | /**
117 | * Calculates positions of the dashed line passing through the obstacle.
118 | * *
119 | * @param {double} centerX
120 | * @param {double} centerY
121 | * @param {double} angle - line angle
122 | * @param {double} lineStartRadius
123 | * @param {double} lineEndRadius
124 | * @param {double} obstacleRadius
125 | * @param {Array} obstacles, [{x:456, y:456, r:60, angle:123}, ...]
126 | *
127 | * @return {Array} [{startX:1, startY:1, endX:4, endY:4}, {startX:6, startY:6, endX:8, endY:8}]
128 | */
129 | export declare const getDashedLinesPositions: (centerX: number, centerY: number, angle: number, lineStartRadius: number, lineEndRadius: number, obstacleRadius: number, obstacles: LocatedPoint[], astrology: Settings) => InitialEndPosition[];
130 | /**
131 | * Calculate ruler positions.
132 | *
133 | * @param {Double} centerX
134 | * @param {Double} centerY
135 | * @param {Double} startRadius
136 | * @param {Double} endRadius
137 | * @param {Boolean} startAngle
138 | *
139 | * @return {Array} [ {startX:1,startY:2, endX:3, endX:4 }, ...]
140 | */
141 | export declare const getRulerPositions: (centerX: number, centerY: number, startRadius: number, endRadius: number, startAngle: number, astrology: {
142 | SHIFT_IN_DEGREES: number;
143 | }) => InitialEndPosition[];
144 | /**
145 | * Compare two points
146 | *
147 | * @param {Object} pointA, {name:"Venus", x:78, y:56, r:50, angle:15.96}
148 | * @param {Object} pointB, {name:"Mercury", x:78, y:56, r:50, angle:20.26}
149 | * @return
150 | */
151 | export declare const comparePoints: (pointA: {
152 | angle: number;
153 | }, pointB: {
154 | angle: number;
155 | }) => number;
156 | export {};
157 |
--------------------------------------------------------------------------------
/dist/project/src/zodiac.d.ts:
--------------------------------------------------------------------------------
1 | import type { Dignity, Settings } from './settings';
2 | /**
3 | * Zodiac
4 | *
5 | * Gives the position of points in the zodiac.
6 | * Position of point in the zodiac.
7 | * Position of point in houses.
8 | * Dignities of planets.
9 | *
10 | * @class
11 | * @public
12 | * @constructor
13 | * @param {Array} cusps - cusprs in zodiac; [296, 350, 30, 56, 75, 94, 116, 170, 210, 236, 255, 274]
14 | * @param {Object | null } settings
15 | */
16 | declare class Zodiac {
17 | cusps: number[];
18 | settings: Settings;
19 | constructor(cusps: number[], settings?: Settings);
20 | /**
21 | * Get astrological sign
22 | * 1 - Arise, ... , 12 - Pisces
23 | *
24 | * @param {double} point - angle of point in circle
25 | * @return { \[1-9] | 1[0-2]\ }
26 | */
27 | getSign(point: number): number;
28 | /**
29 | * Is retrograde
30 | *
31 | * @param {double} speed
32 | * @return {boolean}
33 | */
34 | isRetrograde(speed: number): boolean;
35 | /**
36 | * Get house number
37 | * 1 - 12
38 | *
39 | * @param {double} point - angle of point in circle
40 | * @return { \[1-9] | 1[0-2]\ }
41 | */
42 | getHouseNumber(point: number): number;
43 | /**
44 | * Calculate dignities of planet
45 | *
46 | * r - Rulership
47 | * d - Detriment
48 | * e - Exaltation
49 | * E - Exalatation - Exact exaltation
50 | * f - Fall
51 | *
52 | * @param {Object} planet, { name:"Sun", position:60.2 }
53 | * @param {Array | null } exactExaltation - list of named angles, [{ name:"Sun", position:278, orbit:2 }, { name:"Moon", position:3, , orbit:2 }]
54 | * @return {Array}
55 | */
56 | getDignities(planet: {
57 | name: string;
58 | position: number;
59 | }, exactExaltation?: Dignity[]): string[];
60 | toDMS(d: number): string;
61 | hasConjunction(planetPosition: number, pointPosition: number, orbit: number): boolean;
62 | }
63 | export default Zodiac;
64 |
--------------------------------------------------------------------------------
/doc/images/transits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AstroDraw/AstroChart/f1fdbbeda752b3817ec9f851313830cbb09c5601/doc/images/transits.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | collectCoverageFrom: ['./project/src/**/*.{ts,js}'],
5 | testEnvironment: 'jsdom'
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@astrodraw/astrochart",
3 | "version": "3.0.2",
4 | "description": "A free and open-source JavaScript library for generating SVG charts to display planets in astrology.",
5 | "keywords": [
6 | "astrology",
7 | "chart",
8 | "horoscope",
9 | "zodiac",
10 | "ephemetis"
11 | ],
12 | "license": "MIT",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/AstroDraw/AstroChart.git"
16 | },
17 | "main": "./dist/astrochart.js",
18 | "types": "./dist/project/src/index.d.ts",
19 | "scripts": {
20 | "test": "jest",
21 | "test:w": "jest --watch",
22 | "test:coverage": "jest --coverage",
23 | "build": "webpack",
24 | "build:w": "webpack --watch",
25 | "lint": "eslint --ignore-path .eslintignore --ext .ts ."
26 | },
27 | "devDependencies": {
28 | "@types/jest": "^27.4.1",
29 | "@typescript-eslint/eslint-plugin": "^5.53.0",
30 | "@typescript-eslint/parser": "^5.53.0",
31 | "eslint": "^8.34.0",
32 | "eslint-config-standard-with-typescript": "^34.0.0",
33 | "eslint-plugin-import": "^2.27.5",
34 | "eslint-plugin-n": "^15.6.1",
35 | "eslint-plugin-promise": "^6.1.1",
36 | "jest": "^27.5.1",
37 | "terser-webpack-plugin": "^5.3.1",
38 | "ts-jest": "^27.1.4",
39 | "ts-loader": "^9.2.8",
40 | "typescript": "^4.9.5",
41 | "webpack": "^5.72.0",
42 | "webpack-cli": "^4.9.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/project/__tests__/radix.test.ts:
--------------------------------------------------------------------------------
1 | import Chart from "../src";
2 |
3 | test(`snapshot radix with default settings`, () => {
4 | const data = {
5 | "planets": { "Fortune": [45.930008627285154], "SNode": [263.2584780960899], "Jupiter": [173.07043720306802], "Mars": [217.97167231451178], "Lilith": [196.19480722950317], "Saturn": [252.92341772675047], "Chiron": [348.1157239728284], "Uranus": [16.7900184974611], "Sun": [297.68062428797253], "Mercury": [289.10132025725494], "Neptune": [338.01899718442604], "Pluto": [285.6473452237151, -0.123] },
6 | "cusps": [348.20510089894015, 38.108507808919654, 65.20783751818992, 84.96083001338991, 103.77897207128007, 127.1084408347092, 168.20510089894015, 218.10850780891965, 245.20783751818993, 264.9608300133899, 283.77897207128007, 307.1084408347092]
7 | };
8 |
9 |
10 | document.body.innerHTML =
11 | '' +
12 | '
';
13 | const chart = new Chart('test-element', 800, 800);
14 | chart.radix(data);
15 |
16 |
17 | const tree = document.getElementById('test-element')
18 | expect(tree).toMatchSnapshot();
19 |
20 | })
21 |
22 | test(`snapshot radix with scale`, () => {
23 | const data = {
24 | "planets": { "Fortune": [45.930008627285154], "SNode": [263.2584780960899], "Jupiter": [173.07043720306802], "Mars": [217.97167231451178], "Lilith": [196.19480722950317], "Saturn": [252.92341772675047], "Chiron": [348.1157239728284], "Uranus": [16.7900184974611], "Sun": [297.68062428797253], "Mercury": [289.10132025725494], "Neptune": [338.01899718442604], "Pluto": [285.6473452237151, -0.123] },
25 | "cusps": [348.20510089894015, 38.108507808919654, 65.20783751818992, 84.96083001338991, 103.77897207128007, 127.1084408347092, 168.20510089894015, 218.10850780891965, 245.20783751818993, 264.9608300133899, 283.77897207128007, 307.1084408347092]
26 | };
27 |
28 |
29 | document.body.innerHTML =
30 | '' +
31 | '
';
32 | const chart = new Chart('test-element', 800, 800, { SYMBOL_SCALE: 0.8 });
33 | chart.radix(data);
34 |
35 |
36 | const tree = document.getElementById('test-element')
37 | expect(tree).toMatchSnapshot();
38 |
39 | })
40 |
--------------------------------------------------------------------------------
/project/examples/2ChartsOnPage/2radix.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 2 Radix
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/project/examples/debug/calibration.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Radix
6 |
11 |
12 |
13 |
14 |
15 |
16 |
26 |
27 |
--------------------------------------------------------------------------------
/project/examples/radix/radix.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Radix
6 |
11 |
12 |
13 |
14 |
15 |
16 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/project/examples/radix/radix_2016_11_15.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Radix
6 |
11 |
12 |
13 |
14 | Day:15 Month:11 Year:2016 Time:9:56 [+01:00]
15 | Znojmo, The Czech Republic
16 | Latitude: 48° 51' 20 North
17 | Longitude: 16° 02' 56 East
18 | Placidus house system
19 | Orbit: 2.5
20 |
21 |
22 |
23 |
24 |
36 |
37 |
--------------------------------------------------------------------------------
/project/examples/radix/radix_collision.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Radix collision
6 |
11 |
12 |
13 |
14 |
15 |
16 |
31 |
32 |
--------------------------------------------------------------------------------
/project/examples/radix/radix_custom_settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Radix
6 |
11 |
12 |
13 |
14 |
15 |
16 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/project/examples/transit/animate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Animated transit
6 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
49 |
50 |
--------------------------------------------------------------------------------
/project/examples/transit/stroke_only.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Transit
6 |
15 |
16 |
17 | Stroke only
18 |
19 |
20 |
21 |
38 |
39 |
--------------------------------------------------------------------------------
/project/examples/transit/transit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Transit
6 |
15 |
16 |
17 |
18 | Radix: 18. 1. 2016 9:45 (Europe/Vienna, lo:16.0542676, la:48.8559107)
19 | Transit: 12. 12. 2016 14:15
20 | Orbit: 2.5°
21 |
22 |
23 |
24 |
25 |
49 |
50 |
--------------------------------------------------------------------------------
/project/src/animation/animator.ts:
--------------------------------------------------------------------------------
1 | import type { AstroData } from '../radix'
2 | import type { Settings } from '../settings'
3 | import type Transit from '../transit'
4 | import { radiansToDegree } from '../utils'
5 | import Timer from './timer'
6 | /**
7 | * Transit chart animator
8 | *
9 | * Animates the object on a circle.
10 | *
11 | * @class
12 | * @public
13 | * @constructor
14 | * @param {Object} from, {"Sun":[12], "Moon":[60]}
15 | * @param {Object} to, {"Sun":[30], "Moon":[180]}
16 | * @param {Object} settings, {cx:100, cy:100, radius:200, prefix:"astro-chart-"}
17 | */
18 | class Animator {
19 | transit: Transit
20 | isReverse: boolean
21 | rotation: number
22 | settings: Settings
23 | actualPlanetPos: any
24 | timer: Timer
25 | timeSinceLoopStart: number
26 | context: this
27 | cuspsElement: any
28 | data: AstroData
29 | duration: number
30 | callback: () => void
31 | constructor (transit: Transit, settings: Settings) {
32 | this.transit = transit
33 | this.isReverse = false
34 | this.rotation = 0
35 | this.settings = settings
36 | // Copy data
37 | this.actualPlanetPos = {}
38 | for (const planet in this.transit.data.planets) {
39 | if (this.transit.data.planets.hasOwnProperty(planet)) {
40 | this.actualPlanetPos[planet] = this.transit.data.planets[planet]
41 | }
42 | }
43 |
44 | this.timer = new Timer(this.update.bind(this), this.settings.DEBUG)
45 |
46 | // time, passed since the start of the loop
47 | this.timeSinceLoopStart = 0
48 |
49 | this.context = this
50 | this.cuspsElement = null
51 | }
52 |
53 | /**
54 | * Animate objects
55 |
56 | * @param {Object} data, targetPositions
57 | * @param {Integer} duration - seconds
58 | * @param {boolean} isReverse
59 | * @param {Function} callbck - start et the end of animation
60 | */
61 | animate (data: any, duration: number, isReverse: boolean, callback: () => void): void {
62 | this.data = data
63 | this.duration = duration * 1000
64 | this.isReverse = isReverse || false
65 | this.callback = callback
66 |
67 | this.rotation = 0
68 | this.cuspsElement = document.getElementById(this.transit.paper._paperElementId + '-' + this.settings.ID_TRANSIT + '-' + this.settings.ID_CUSPS)
69 |
70 | this.timer.start()
71 | }
72 |
73 | update (deltaTime?: number): void {
74 | deltaTime = deltaTime ?? 1
75 | this.timeSinceLoopStart += deltaTime
76 | if (this.timeSinceLoopStart >= this.duration) {
77 | this.timer.stop()
78 |
79 | if (typeof this.callback === 'function') {
80 | this.callback()
81 | }
82 |
83 | return
84 | }
85 |
86 | const expectedNumberOfLoops = (this.duration - this.timeSinceLoopStart) < deltaTime
87 | ? 1
88 | : Math.round((this.duration - this.timeSinceLoopStart) / deltaTime)
89 |
90 | this.updatePlanets(expectedNumberOfLoops)
91 | this.updateCusps(expectedNumberOfLoops)
92 | }
93 |
94 | /*
95 | * @private
96 | */
97 | updateCusps (expectedNumberOfLoops: number): void {
98 | const deg360 = radiansToDegree(2 * Math.PI)
99 | let targetCuspAngle = this.transit.data.cusps[0] - this.data.cusps[0]
100 |
101 | if (targetCuspAngle < 0) {
102 | targetCuspAngle += deg360
103 | }
104 |
105 | // speed
106 | if (this.settings.ANIMATION_CUSPS_ROTATION_SPEED > 0) {
107 | targetCuspAngle += (this.isReverse)
108 | ? -1 * ((this.settings.ANIMATION_CUSPS_ROTATION_SPEED * deg360) + deg360)
109 | : this.settings.ANIMATION_CUSPS_ROTATION_SPEED * deg360
110 | }
111 |
112 | let difference = (this.isReverse)
113 | ? this.rotation - targetCuspAngle
114 | : targetCuspAngle - this.rotation
115 |
116 | // zero crossing
117 | if (difference < 0) {
118 | difference += deg360
119 | }
120 |
121 | let increment = difference / expectedNumberOfLoops
122 |
123 | if (this.isReverse) {
124 | increment *= -1
125 | }
126 | this.rotation += increment
127 |
128 | this.cuspsElement.setAttribute('transform', 'rotate(' + this.rotation + ' ' + this.transit.cx + ' ' + this.transit.cy + ')')
129 |
130 | if (expectedNumberOfLoops === 1) {
131 | this.cuspsElement.removeAttribute('transform')
132 | }
133 | }
134 |
135 | /*
136 | * @private
137 | */
138 | updatePlanets (expectedNumberOfLoops: number): void {
139 | for (const planet in this.data.planets) {
140 | if (this.data.planets.hasOwnProperty(planet)) {
141 | const actualPlanetAngle: number = this.actualPlanetPos[planet][0]
142 | const targetPlanetAngle: number = this.data.planets[planet][0]
143 | const isRetrograde = this.actualPlanetPos[planet][1] != null && this.actualPlanetPos[planet][1] < 0
144 |
145 | let difference
146 | if (this.isReverse && isRetrograde) {
147 | difference = targetPlanetAngle - actualPlanetAngle
148 | } else if (this.isReverse || isRetrograde) {
149 | difference = actualPlanetAngle - targetPlanetAngle
150 | } else {
151 | difference = targetPlanetAngle - actualPlanetAngle
152 | }
153 |
154 | // zero crossing
155 | if (difference < 0) {
156 | difference += radiansToDegree(2 * Math.PI)
157 | }
158 |
159 | let increment = difference / expectedNumberOfLoops
160 |
161 | if (this.isReverse) {
162 | increment *= -1
163 | }
164 |
165 | if (isRetrograde) {
166 | increment *= -1
167 | }
168 |
169 | let newPos = actualPlanetAngle + increment
170 | if (newPos < 0) {
171 | newPos += radiansToDegree(2 * Math.PI)
172 | }
173 |
174 | this.actualPlanetPos[planet][0] = newPos
175 | }
176 | }
177 |
178 | this.transit.drawPoints(this.actualPlanetPos)
179 | }
180 | }
181 |
182 | export default Animator
183 |
--------------------------------------------------------------------------------
/project/src/animation/timer.ts:
--------------------------------------------------------------------------------
1 | class Timer {
2 | debug: boolean
3 | callback: (delta: number) => void
4 | boundTick_: FrameRequestCallback
5 | lastGameLoopFrame: number
6 | requestID_: number | undefined
7 | constructor(callback: (delta: number) => void, debug: boolean) {
8 | if (typeof callback !== 'function') {
9 | throw new Error('param \'callback\' has to be a function.')
10 | }
11 | this.debug = debug
12 |
13 | this.callback = callback
14 | this.boundTick_ = this.tick.bind(this)
15 | }
16 |
17 | start(): void {
18 | if (!this.requestID_) {
19 | this.lastGameLoopFrame = new Date().getTime()
20 | this.tick()
21 | if (this.debug) console.log('[astrology.Timer] start')
22 | }
23 | }
24 |
25 | stop(): void {
26 | if (this.requestID_) {
27 | window.cancelAnimationFrame(this.requestID_)
28 | this.requestID_ = undefined
29 | if (this.debug) console.log('[astrology.Timer] stop')
30 | }
31 | }
32 |
33 | isRunning(): boolean {
34 | return !!this.requestID_
35 | }
36 |
37 | tick(): void {
38 | const now = new Date().getTime()
39 | this.requestID_ = window.requestAnimationFrame(this.boundTick_)
40 | this.callback(now - this.lastGameLoopFrame)
41 | this.lastGameLoopFrame = now
42 | }
43 | }
44 |
45 | export default Timer
46 |
--------------------------------------------------------------------------------
/project/src/aspect.test.ts:
--------------------------------------------------------------------------------
1 | import AspectCalculator from "./aspect";
2 | import default_settings from "./settings";
3 |
4 | describe('radix', () => {
5 | test('1', () => {
6 | var toPoints = {
7 | Sun: [0],
8 | Moon: [91],
9 | Neptune: [122],
10 | As: [330],
11 | Ic: [90],
12 | };
13 |
14 | var points = {
15 | Sun: [0],
16 | Moon: [91],
17 | Neptune: [122],
18 | };
19 |
20 | const settings = {
21 | ...default_settings,
22 | ASPECTS: {
23 | conjunction: { degree: 0, orbit: 10, color: "transparent" },
24 | square: { degree: 90, orbit: 8, color: "#FF4500" },
25 | trine: { degree: 120, orbit: 8, color: "#27AE60" },
26 | opposition: { degree: 180, orbit: 10, color: "#27AE60" },
27 | },
28 | };
29 |
30 | const calculator = new AspectCalculator(toPoints, settings);
31 | const aspects = calculator.radix(points);
32 |
33 | expect(aspects).toStrictEqual([
34 | {
35 | aspect: {
36 | color: "#FF4500",
37 | degree: 90,
38 | name: "square",
39 | orbit: 8,
40 | },
41 | point: {
42 | name: "Sun",
43 | position: 0,
44 | },
45 | precision: "0.0000",
46 | toPoint: {
47 | name: "Ic",
48 | position: 90,
49 | },
50 | },
51 | {
52 | aspect: {
53 | color: "#FF4500",
54 | degree: 90,
55 | name: "square",
56 | orbit: 8,
57 | },
58 | point: {
59 | name: "Sun",
60 | position: 0,
61 | },
62 | precision: "1.0000",
63 | toPoint: {
64 | name: "Moon",
65 | position: 91,
66 | },
67 | },
68 | {
69 | aspect: {
70 | color: "#FF4500",
71 | degree: 90,
72 | name: "square",
73 | orbit: 8,
74 | },
75 | point: {
76 | name: "Moon",
77 | position: 91,
78 | },
79 | precision: "1.0000",
80 | toPoint: {
81 | name: "Sun",
82 | position: 0,
83 | },
84 | },
85 | {
86 | aspect: {
87 | color: "#27AE60",
88 | degree: 120,
89 | name: "trine",
90 | orbit: 8,
91 | },
92 | point: {
93 | name: "Moon",
94 | position: 91,
95 | },
96 | precision: "1.0000",
97 | toPoint: {
98 | name: "As",
99 | position: 330,
100 | },
101 | },
102 | {
103 | aspect: {
104 | color: "transparent",
105 | degree: 0,
106 | name: "conjunction",
107 | orbit: 10,
108 | },
109 | point: {
110 | name: "Moon",
111 | position: 91,
112 | },
113 | precision: "1.0000",
114 | toPoint: {
115 | name: "Ic",
116 | position: 90,
117 | },
118 | },
119 | {
120 | aspect: {
121 | color: "#27AE60",
122 | degree: 120,
123 | name: "trine",
124 | orbit: 8,
125 | },
126 | point: {
127 | name: "Sun",
128 | position: 0,
129 | },
130 | precision: "2.0000",
131 | toPoint: {
132 | name: "Neptune",
133 | position: 122,
134 | },
135 | },
136 | {
137 | aspect: {
138 | color: "#27AE60",
139 | degree: 120,
140 | name: "trine",
141 | orbit: 8,
142 | },
143 | point: {
144 | name: "Neptune",
145 | position: 122,
146 | },
147 | precision: "2.0000",
148 | toPoint: {
149 | name: "Sun",
150 | position: 0,
151 | },
152 | },
153 | ]);
154 | });
155 | });
156 |
157 | describe('transit', () => {
158 | test('1', () => {
159 | var toPoints = {
160 | "Sun":[0],
161 | };
162 |
163 | var transit = {
164 | "Sun":[1],
165 | }
166 |
167 | var settings = {...default_settings, ASPECTS: {
168 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
169 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
170 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
171 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
172 | }};
173 |
174 | var calculator = new AspectCalculator( toPoints, settings);
175 |
176 | expect(calculator.transit( transit )).toStrictEqual([
177 | {
178 | "aspect": {
179 | "color": "transparent",
180 | "degree": 0,
181 | "name": "conjunction",
182 | "orbit": 10
183 | },
184 | "point": {
185 | "name": "Sun",
186 | "position": 1
187 | },
188 | "precision": "1.0000",
189 | "toPoint": {
190 | "name": "Sun",
191 | "position": 0
192 | }
193 | }
194 | ]);
195 | })
196 |
197 | test('2', () => {
198 | var toPoints = {
199 | "Sun":[0],
200 | };
201 |
202 | var transit = {
203 | "Sun":[359],
204 | }
205 |
206 | const settings = {...default_settings, ASPECTS: {
207 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
208 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
209 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
210 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
211 | }}
212 |
213 | var calculator = new AspectCalculator( toPoints, settings);
214 |
215 | expect(calculator.transit( transit )).toStrictEqual([
216 | {
217 | "aspect": {
218 | "color": "transparent",
219 | "degree": 0,
220 | "name": "conjunction",
221 | "orbit": 10
222 | },
223 | "point": {
224 | "name": "Sun",
225 | "position": 359
226 | },
227 | "precision": "-1.0000",
228 | "toPoint": {
229 | "name": "Sun",
230 | "position": 0
231 | }
232 | }
233 | ]);
234 | })
235 |
236 | test('3', () => {
237 | var toPoints = {
238 | "Sun":[0],
239 | };
240 |
241 | var transit = {
242 | "Sun":[91],
243 | }
244 |
245 | var settings = {...default_settings, ASPECTS: {
246 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
247 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
248 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
249 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
250 | }}
251 |
252 | var calculator = new AspectCalculator( toPoints, settings);
253 |
254 | expect(calculator.transit( transit )).toStrictEqual(
255 | [
256 | {
257 | "aspect": {
258 | "color": "#FF4500",
259 | "degree": 90,
260 | "name": "square",
261 | "orbit": 8
262 | },
263 | "point": {
264 | "name": "Sun",
265 | "position": 91
266 | },
267 | "precision": "1.0000",
268 | "toPoint": {
269 | "name": "Sun",
270 | "position": 0
271 | }
272 | }
273 | ]);
274 | })
275 |
276 | test('4', () => {
277 | var toPoints = {
278 | "Sun":[0],
279 | };
280 |
281 | var transit = {
282 | "Sun":[89],
283 | }
284 |
285 | var settings = {...default_settings, ASPECTS: {
286 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
287 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
288 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
289 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
290 | }};
291 |
292 | var calculator = new AspectCalculator( toPoints, settings);
293 |
294 | expect(calculator.transit( transit )).toStrictEqual([
295 | {
296 | "aspect": {
297 | "color": "#FF4500",
298 | "degree": 90,
299 | "name": "square",
300 | "orbit": 8
301 | },
302 | "point": {
303 | "name": "Sun",
304 | "position": 89
305 | },
306 | "precision": "-1.0000",
307 | "toPoint": {
308 | "name": "Sun",
309 | "position": 0
310 | }
311 | }
312 | ]);
313 | })
314 |
315 | test('5', () => {
316 | var toPoints = {
317 | "Sun":[0],
318 | };
319 |
320 | var transit = {
321 | "Sun":[181],
322 | }
323 |
324 | var settings = {...default_settings, ASPECTS: {
325 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
326 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
327 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
328 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
329 | }};
330 |
331 | var calculator = new AspectCalculator( toPoints, settings);
332 |
333 | expect(calculator.transit( transit )).toStrictEqual([
334 | {
335 | "aspect": {
336 | "color": "#27AE60",
337 | "degree": 180,
338 | "name": "opposition",
339 | "orbit": 10
340 | },
341 | "point": {
342 | "name": "Sun",
343 | "position": 181
344 | },
345 | "precision": "1.0000",
346 | "toPoint": {
347 | "name": "Sun",
348 | "position": 0
349 | }
350 | }
351 | ]);
352 | })
353 |
354 | test('6', () => {
355 | var toPoints = {
356 | "Sun":[0],
357 | };
358 |
359 | var transit = {
360 | "Sun":[179],
361 | }
362 |
363 | var settings = {...default_settings, ASPECTS: {
364 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
365 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
366 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
367 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
368 | }};
369 |
370 | var calculator = new AspectCalculator( toPoints, settings);
371 |
372 | expect(calculator.transit( transit )).toStrictEqual([
373 | {
374 | "aspect": {
375 | "color": "#27AE60",
376 | "degree": 180,
377 | "name": "opposition",
378 | "orbit": 10
379 | },
380 | "point": {
381 | "name": "Sun",
382 | "position": 179
383 | },
384 | "precision": "-1.0000",
385 | "toPoint": {
386 | "name": "Sun",
387 | "position": 0
388 | }
389 | }
390 | ]);
391 | })
392 |
393 | test('7', () => {
394 | var toPoints = {
395 | "Sun":[0],
396 | };
397 |
398 | var transit = {
399 | "Sun":[271],
400 | }
401 |
402 | var settings = {...default_settings, ASPECTS: {
403 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
404 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
405 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
406 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
407 | }};
408 |
409 | var calculator = new AspectCalculator( toPoints, settings);
410 |
411 | expect(calculator.transit( transit )).toStrictEqual([
412 | {
413 | "aspect": {
414 | "color": "#FF4500",
415 | "degree": 90,
416 | "name": "square",
417 | "orbit": 8
418 | },
419 | "point": {
420 | "name": "Sun",
421 | "position": 271
422 | },
423 | "precision": "1.0000",
424 | "toPoint": {
425 | "name": "Sun",
426 | "position": 0
427 | }
428 | }
429 | ]);
430 | })
431 |
432 | test('8', () => {
433 | var toPoints = {
434 | "Sun":[0],
435 | };
436 |
437 | var transit = {
438 | "Sun":[269],
439 | }
440 |
441 | var settings = {...default_settings, ASPECTS: {
442 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
443 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
444 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
445 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
446 | }};
447 |
448 | var calculator = new AspectCalculator( toPoints, settings);
449 |
450 | expect(calculator.transit( transit )).toStrictEqual([
451 | {
452 | "aspect": {
453 | "color": "#FF4500",
454 | "degree": 90,
455 | "name": "square",
456 | "orbit": 8
457 | },
458 | "point": {
459 | "name": "Sun",
460 | "position": 269
461 | },
462 | "precision": "-1.0000",
463 | "toPoint": {
464 | "name": "Sun",
465 | "position": 0
466 | }
467 | }
468 | ]);
469 | })
470 |
471 | test('9', () => {
472 | var toPoints = {
473 | "Sun":[359],
474 | };
475 |
476 | var transit = {
477 | "Sun":[90],
478 | };
479 |
480 | var settings = {...default_settings, ASPECTS: {
481 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
482 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
483 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
484 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
485 | }};
486 |
487 | var calculator = new AspectCalculator( toPoints, settings);
488 |
489 | expect(calculator.transit( transit )).toStrictEqual([{
490 | "aspect": {
491 | "color": "#FF4500",
492 | "degree": 90,
493 | "name": "square",
494 | "orbit": 8
495 | },
496 | "point": {
497 | "name": "Sun",
498 | "position": 90
499 | },
500 | "precision": "1.0000",
501 | "toPoint": {
502 | "name": "Sun",
503 | "position": 359
504 | }
505 | }]);
506 | })
507 |
508 | test('10', () => {
509 | var toPoints = {
510 | "Sun":[359],
511 | };
512 |
513 | var transit = {
514 | "Sun":[88],
515 | };
516 |
517 | var settings = {...default_settings, ASPECTS: {
518 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
519 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
520 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
521 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
522 | }};
523 |
524 | var calculator = new AspectCalculator( toPoints, settings);
525 |
526 | expect(calculator.transit( transit )).toStrictEqual([{
527 | "aspect": {
528 | "color": "#FF4500",
529 | "degree": 90,
530 | "name": "square",
531 | "orbit": 8
532 | },
533 | "point": {
534 | "name": "Sun",
535 | "position": 88
536 | },
537 | "precision": "-1.0000",
538 | "toPoint": {
539 | "name": "Sun",
540 | "position": 359
541 | }
542 | }]);
543 | })
544 |
545 | test('11 - speed', () => {
546 | var toPoints = {
547 | "Sun":[0],
548 | };
549 |
550 | var transit = {
551 | "Sun":[1, 1],
552 | }
553 |
554 | var settings = {...default_settings, ASPECTS: {
555 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
556 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
557 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
558 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
559 | }};
560 |
561 | var calculator = new AspectCalculator( toPoints, settings);
562 |
563 | expect(calculator.transit( transit )).toStrictEqual([ {
564 | "aspect": {
565 | "color": "transparent",
566 | "degree": 0,
567 | "name": "conjunction",
568 | "orbit": 10
569 | },
570 | "point": {
571 | "name": "Sun",
572 | "position": 1
573 | },
574 | "precision": "1.0000",
575 | "toPoint": {
576 | "name": "Sun",
577 | "position": 0
578 | }
579 | }]);
580 | })
581 |
582 | test('12 - speed', () => {
583 | var toPoints = {
584 | "Sun":[0],
585 | };
586 |
587 | var transit = {
588 | "Sun":[1, -1],
589 | }
590 |
591 | var settings = {...default_settings, ASPECTS: {
592 | "conjunction":{"degree":0, "orbit":10, "color":"transparent"},
593 | "square":{"degree":90, "orbit":8, "color":"#FF4500"},
594 | "trine":{"degree":120, "orbit":8, "color":"#27AE60"},
595 | "opposition":{"degree":180, "orbit":10, "color":"#27AE60"}
596 | }};
597 |
598 | var calculator = new AspectCalculator( toPoints, settings);
599 |
600 | expect(calculator.transit( transit )).toStrictEqual([{
601 | "aspect": {
602 | "color": "transparent",
603 | "degree": 0,
604 | "name": "conjunction",
605 | "orbit": 10
606 | },
607 | "point": {
608 | "name": "Sun",
609 | "position": 1
610 | },
611 | "precision": "-1.0000",
612 | "toPoint": {
613 | "name": "Sun",
614 | "position": 0
615 | }
616 | }]);
617 | })
618 |
619 | })
620 |
--------------------------------------------------------------------------------
/project/src/aspect.ts:
--------------------------------------------------------------------------------
1 | import type { Points } from './radix'
2 | import type { AspectData, Settings } from './settings'
3 | import { radiansToDegree } from './utils'
4 |
5 | export interface FormedAspect {
6 | point: {
7 | name: string
8 | position: number
9 | }
10 | toPoint: {
11 | name: string
12 | position: number
13 | }
14 | aspect: {
15 | name: string
16 | degree: number
17 | color: string
18 | orbit: number
19 | }
20 | precision: string
21 | }
22 |
23 | const DEFAULT_ASPECTS = {
24 | conjunction: { degree: 0, orbit: 10, color: 'transparent' },
25 | square: { degree: 90, orbit: 8, color: '#FF4500' },
26 | trine: { degree: 120, orbit: 8, color: '#27AE60' },
27 | opposition: { degree: 180, orbit: 10, color: '#27AE60' }
28 | }
29 | /**
30 | * Aspects calculator
31 | *
32 | * @class
33 | * @public
34 | * @constructor
35 | * @param {AspectPoints} points; {"Sun":[0], "Moon":[90], "Neptune":[120], "As":[30]}
36 | * @param {Object | null } settings
37 | */
38 | class AspectCalculator {
39 | settings: Partial
40 | toPoints: Points
41 | context: this
42 | constructor (toPoints: Points, settings?: Partial) {
43 | if (toPoints == null) {
44 | throw new Error('Param \'toPoint\' must not be empty.')
45 | }
46 |
47 | this.settings = settings ?? {}
48 | this.settings.ASPECTS = settings?.ASPECTS ?? DEFAULT_ASPECTS
49 |
50 | this.toPoints = toPoints
51 |
52 | this.context = this
53 | }
54 |
55 | /**
56 | * Getter for this.toPoints
57 | * @see constructor
58 | *
59 | * @return {Object}
60 | */
61 | getToPoints (): Points {
62 | return this.toPoints
63 | }
64 |
65 | /**
66 | * Radix aspects
67 | *
68 | * In radix calculation is the param "points" the same as param "toPoints" in constructor
69 | * , but without special points such as: As,Ds, Mc, Ic, ...
70 | *
71 | * @param {Object} points; {"Sun":[0], "Moon":[90]}
72 | *
73 | * @return {Array} [{"aspect":{"name":"conjunction", "degree":120}"", "point":{"name":"Sun", "position":123}, "toPoint":{"name":"Moon", "position":345}, "precision":0.5}]]
74 | */
75 | radix (points: Points): FormedAspect[] {
76 | if (points == null) {
77 | return []
78 | }
79 |
80 | const aspects: FormedAspect[] = []
81 |
82 | for (const point in points) {
83 | if (points.hasOwnProperty(point)) {
84 | for (const toPoint in this.toPoints) {
85 | if (this.toPoints.hasOwnProperty(toPoint)) {
86 | if (point !== toPoint) {
87 | for (const aspect in this.settings.ASPECTS) {
88 | if (this.hasAspect(points[point][0], this.toPoints[toPoint][0], this.settings.ASPECTS[aspect])) {
89 | aspects.push(
90 | {
91 | aspect: { name: aspect, degree: this.settings.ASPECTS[aspect].degree, orbit: this.settings.ASPECTS[aspect].orbit, color: this.settings.ASPECTS[aspect].color },
92 | point: { name: point, position: points[point][0] },
93 | toPoint: { name: toPoint, position: this.toPoints[toPoint][0] },
94 | precision: this.calcPrecision(points[point][0], this.toPoints[toPoint][0], this.settings.ASPECTS[aspect].degree).toFixed(4)
95 | }
96 | )
97 | }
98 | }
99 | }
100 | }
101 | }
102 | }
103 | }
104 |
105 | return aspects.sort(this.compareAspectsByPrecision)
106 | }
107 |
108 | /**
109 | * Transit aspects
110 | *
111 | * @param {Object} points - transiting points; {"Sun":[0, 1], "Uranus":[90, -1], "NAME":[ANGLE, SPEED]};
112 | * @return {Array} [{"aspect":{"name":"conjunction", "degree":120}"", "point":{"name":"Sun", "position":123}, "toPoint":{"name":"Moon", "position":345}, "precision":0.5}]]
113 | */
114 | transit (points: Points): FormedAspect[] {
115 | if (points == null) {
116 | return []
117 | }
118 |
119 | const aspects = []
120 |
121 | for (const point in points) {
122 | if (points.hasOwnProperty(point)) {
123 | for (const toPoint in this.toPoints) {
124 | if (this.toPoints.hasOwnProperty(toPoint)) {
125 | for (const aspect in this.settings.ASPECTS) {
126 | if (this.hasAspect(points[point][0], this.toPoints[toPoint][0], this.settings.ASPECTS[aspect])) {
127 | let precision = this.calcPrecision(points[point][0], this.toPoints[toPoint][0], this.settings.ASPECTS[aspect].degree)
128 |
129 | // -1 : is approaching to aspect
130 | // +1 : is moving away
131 | if (this.isTransitPointApproachingToAspect(this.settings.ASPECTS[aspect].degree, this.toPoints[toPoint][0], points[point][0])) {
132 | precision *= -1
133 | }
134 |
135 | // if transit has speed value && transit is retrograde
136 | if (points[point][1] && points[point][1] < 0) {
137 | precision *= -1
138 | }
139 |
140 | aspects.push(
141 | {
142 | aspect: { name: aspect, degree: this.settings.ASPECTS[aspect].degree, orbit: this.settings.ASPECTS[aspect].orbit, color: this.settings.ASPECTS[aspect].color },
143 | point: { name: point, position: points[point][0] },
144 | toPoint: { name: toPoint, position: this.toPoints[toPoint][0] },
145 | precision: precision.toFixed(4)
146 | }
147 | )
148 | }
149 | }
150 | }
151 | }
152 | }
153 | }
154 |
155 | return aspects.sort(this.compareAspectsByPrecision)
156 | }
157 |
158 | /*
159 | * @private
160 | * @param {double} point
161 | * @param {double} toPoint
162 | * @param {Array} aspects; [DEGREE, ORBIT]
163 | */
164 | hasAspect (point: number, toPoint: number, aspect: AspectData): boolean {
165 | let result = false
166 |
167 | let gap = Math.abs(point - toPoint)
168 |
169 | if (gap > radiansToDegree(Math.PI)) {
170 | gap = radiansToDegree(2 * Math.PI) - gap
171 | }
172 |
173 | const orbitMin = aspect.degree - (aspect.orbit / 2)
174 | const orbitMax = aspect.degree + (aspect.orbit / 2)
175 |
176 | if (orbitMin <= gap && gap <= orbitMax) {
177 | result = true
178 | }
179 |
180 | return result
181 | }
182 |
183 | /*
184 | * @private
185 | * @param {Object} pointAngle
186 | * @param {Object} toPointAngle
187 | * @param {double} aspectDegree;
188 | */
189 | calcPrecision (point: number, toPoint: number, aspect: number): number {
190 | let gap = Math.abs(point - toPoint)
191 |
192 | if (gap > radiansToDegree(Math.PI)) {
193 | gap = radiansToDegree(2 * Math.PI) - gap
194 | }
195 | return Math.abs(gap - aspect)
196 | }
197 |
198 | /*
199 | * Calculate direction of aspect
200 | * whether the transiting planet is approaching or is falling
201 | * @private
202 | *
203 | * //TODO
204 | * This method is tested, and for tests gives the right results.
205 | * But the code is totally unclear. It needs to be rewritten.
206 | * @param {double} aspect - aspect degree; for example 90.
207 | * @param {double} toPoint - angle of standing point
208 | * @param {double} point - angle of transiting planet
209 | * @return {boolean}
210 | */
211 | isTransitPointApproachingToAspect (aspect: number, toPoint: number, point: number): boolean {
212 | if ((point - toPoint) > 0) {
213 | if ((point - toPoint) > radiansToDegree(Math.PI)) {
214 | point = (point + aspect) % radiansToDegree(2 * Math.PI)
215 | } else {
216 | toPoint = (toPoint + aspect) % radiansToDegree(2 * Math.PI)
217 | }
218 | } else {
219 | if ((toPoint - point) > radiansToDegree(Math.PI)) {
220 | toPoint = (toPoint + aspect) % radiansToDegree(2 * Math.PI)
221 | } else {
222 | point = (point + aspect) % radiansToDegree(2 * Math.PI)
223 | }
224 | }
225 |
226 | let _point = point
227 | let _toPoint = toPoint
228 |
229 | const difference = _point - _toPoint
230 |
231 | if (Math.abs(difference) > radiansToDegree(Math.PI)) {
232 | _point = toPoint
233 | _toPoint = point
234 | }
235 |
236 | return (_point - _toPoint < 0)
237 | }
238 |
239 | /*
240 | * Aspects comparator
241 | * by precision
242 | * @private
243 | * @param {Object} a
244 | * @param {Object} b
245 | */
246 | compareAspectsByPrecision (a: FormedAspect, b: FormedAspect): number {
247 | return parseFloat(a.precision) - parseFloat(b.precision)
248 | }
249 | }
250 |
251 | export default AspectCalculator
252 |
--------------------------------------------------------------------------------
/project/src/chart.test.ts:
--------------------------------------------------------------------------------
1 | import Chart from "./chart"
2 |
3 | describe.only('constructor', () => {
4 | test('should throw error when empty houses', () => {
5 | const chart = new Chart('paper', 1, 1)
6 |
7 | expect(() => {
8 | chart.radix({planets: {}, cusps: []})
9 | }).toThrowError(`Count of 'cusps' values has to be 12.`)
10 | })
11 |
12 | test('should throw error when houses are less than 12', () => {
13 | const chart = new Chart('paper', 1, 1)
14 |
15 | expect(() => {
16 | chart.radix({planets: {}, cusps: [1, 2, 3]})
17 | }).toThrowError(`Count of 'cusps' values has to be 12.`)
18 | })
19 |
20 | })
--------------------------------------------------------------------------------
/project/src/chart.ts:
--------------------------------------------------------------------------------
1 | import default_settings from './settings'
2 | import type { Settings } from './settings'
3 | import Radix from './radix'
4 | import type { AstroData } from './radix'
5 | import SVG from './svg'
6 | import { getPointPosition } from './utils'
7 | /**
8 | * Displays astrology charts.
9 | *
10 | * @class
11 | * @public
12 | * @constructor
13 | * @param {String} elementId - root DOMElement
14 | * @param {int} width
15 | * @param {int} height
16 | * @param {Object} settings
17 | */
18 |
19 | class Chart {
20 | paper: SVG
21 | cx: number
22 | cy: number
23 | radius: number
24 | settings: Settings
25 | constructor (elementId: string, width: number, height: number, settings?: Partial) {
26 | const chartSettings = default_settings
27 | if (settings != null) {
28 | Object.assign(chartSettings, settings)
29 | if (!('COLORS_SIGNS' in settings)) chartSettings.COLORS_SIGNS = [default_settings.COLOR_ARIES, default_settings.COLOR_TAURUS, default_settings.COLOR_GEMINI, default_settings.COLOR_CANCER, default_settings.COLOR_LEO, default_settings.COLOR_VIRGO, default_settings.COLOR_LIBRA, default_settings.COLOR_SCORPIO, default_settings.COLOR_SAGITTARIUS, default_settings.COLOR_CAPRICORN, default_settings.COLOR_AQUARIUS, default_settings.COLOR_PISCES]
30 | }
31 |
32 | if ((elementId !== '') && (document.getElementById(elementId) == null)) {
33 | const paper = document.createElement('div')
34 | paper.setAttribute('id', elementId)
35 | document.body.appendChild(paper)
36 | }
37 |
38 | this.paper = new SVG(elementId, width, height, chartSettings)
39 | this.cx = this.paper.width / 2
40 | this.cy = this.paper.height / 2
41 | this.radius = this.paper.height / 2 - chartSettings.MARGIN
42 | this.settings = chartSettings
43 | }
44 |
45 | /**
46 | * Display radix horoscope
47 | *
48 | * @param {Object} data
49 | * @example
50 | * {
51 | * "points":{"Moon":[0], "Sun":[30], ... },
52 | * "cusps":[300, 340, 30, 60, 75, 90, 116, 172, 210, 236, 250, 274]
53 | * }
54 | *
55 | * @return {Radix} radix
56 | */
57 | radix (data: AstroData): Radix {
58 | const radix = new Radix(this.paper, this.cx, this.cy, this.radius, data, this.settings)
59 |
60 | radix.drawBg()
61 | radix.drawUniverse()
62 | radix.drawRuler()
63 | radix.drawPoints()
64 | radix.drawCusps()
65 | radix.drawAxis()
66 | radix.drawCircles()
67 |
68 | return radix
69 | }
70 |
71 | /**
72 | * Scale chart
73 | *
74 | * @param {int} factor
75 | */
76 | scale (factor: number): void {
77 | this.paper.root.setAttribute('transform', 'translate(' + (-this.cx * (factor - 1)) + ',' + (-this.cy * (factor - 1)) + ') scale(' + factor + ')')
78 | }
79 |
80 | /**
81 | * Draw the symbol on the axis.
82 | * For debug only.
83 | *
84 | */
85 | calibrate (): Chart {
86 | let positions
87 | let circle
88 | let line
89 | const startRadius = 60
90 |
91 | const planets = ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Chiron', 'Lilith', 'NNode']
92 |
93 | for (let planet = 0; planet < planets.length; planet++) {
94 | positions = getPointPosition(this.cx, this.cy, this.radius * 2, planet * 30, this.settings)
95 |
96 | line = this.paper.line(this.cx, this.cy, positions.x, positions.y)
97 | line.setAttribute('stroke', this.settings.LINE_COLOR)
98 | this.paper.root.appendChild(line)
99 |
100 | circle = this.paper.circle(this.cx, this.cy, startRadius + startRadius * planet)
101 | circle.setAttribute('stroke', this.settings.LINE_COLOR)
102 | circle.setAttribute('stroke-width', '1')
103 | this.paper.root.appendChild(circle)
104 | }
105 |
106 | for (let n = 0, ln = planets.length; n < ln; n++) {
107 | const radius = startRadius + startRadius * n
108 |
109 | for (let i = 0; i < 12; i++) {
110 | positions = getPointPosition(this.cx, this.cy, radius, i * 30, this.settings)
111 |
112 | circle = this.paper.circle(positions.x, positions.y, this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE)
113 | circle.setAttribute('stroke', 'red')
114 | circle.setAttribute('stroke-width', '1')
115 | this.paper.root.appendChild(circle)
116 |
117 | this.paper.root.appendChild(this.paper.getSymbol(planets[n], positions.x, positions.y))
118 | }
119 | }
120 |
121 | return this
122 | }
123 | }
124 |
125 | export default Chart
126 |
--------------------------------------------------------------------------------
/project/src/index.ts:
--------------------------------------------------------------------------------
1 | import Chart from './chart'
2 | import AspectCalculator from './aspect'
3 | import { Settings } from './settings'
4 |
5 | export { Chart, AspectCalculator, Settings }
6 | export default Chart
--------------------------------------------------------------------------------
/project/src/radix.test.ts:
--------------------------------------------------------------------------------
1 | import Radix from './radix';
2 | import SVG from './svg';
3 | import default_settings from './settings';
4 |
5 | describe('Radix', () => {
6 | const data = {
7 | planets: {
8 | Sun: [0],
9 | Moon: [90],
10 | },
11 | cusps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330]
12 | };
13 |
14 | it('should draw aspect lines with default colors', () => {
15 | document.body.innerHTML = '
';
16 | const settings = default_settings;
17 | const paper = new SVG('chart', 500, 500, settings);
18 | const radix = new Radix(paper, 500, 500, 500, data, settings);
19 | radix.aspects();
20 |
21 | const aspectLines = document.querySelectorAll('#chart-astrology-aspects > line');
22 | expect(aspectLines[0].getAttribute('stroke')).toBe(default_settings.ASPECTS.square.color);
23 | });
24 |
25 | it('should draw aspect lines with custom colors via settings', () => {
26 | document.body.innerHTML = '
';
27 |
28 | const settings = {
29 | ...default_settings,
30 | ASPECTS: {
31 | square: { degree: 90, orbit: 10, color: 'purple' },
32 | },
33 | };
34 |
35 | const paper = new SVG('chart', 500, 500, settings);
36 | const radix = new Radix(paper, 500, 500, 500, data, settings);
37 | radix.aspects();
38 |
39 | const aspectLines = document.querySelectorAll('#chart-astrology-aspects > line');
40 | expect(aspectLines[0].getAttribute('stroke')).toBe('purple');
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/project/src/radix.ts:
--------------------------------------------------------------------------------
1 | import Zodiac from './zodiac'
2 | import AspectCalculator from './aspect'
3 | import type { FormedAspect } from './aspect'
4 | import Transit from './transit'
5 | import {
6 | validate
7 | , radiansToDegree
8 | , getEmptyWrapper
9 | , getPointPosition
10 | , getRulerPositions
11 | , getDescriptionPosition
12 | , getDashedLinesPositions
13 | , assemble
14 | } from './utils'
15 | import type SVG from './svg'
16 | import type { Settings } from './settings'
17 |
18 | export type Points = Record
19 | export interface LocatedPoint { name: string; x: number; y: number; r: number; angle: number; pointer?: number; index?: number }
20 | export interface AstroData {
21 | planets: Points
22 | cusps: number[]
23 | }
24 |
25 | /**
26 | * Radix charts.
27 | *
28 | * @class
29 | * @public
30 | * @constructor
31 | * @param {this.settings.SVG} paper
32 | * @param {int} cx
33 | * @param {int} cy
34 | * @param {int} radius
35 | * @param {Object} data
36 | */
37 | class Radix {
38 | settings: Settings
39 | data: AstroData
40 | paper: SVG
41 | cx: number
42 | cy: number
43 | radius: number
44 | locatedPoints: LocatedPoint[]
45 | rulerRadius: number
46 | pointRadius: number
47 | toPoints: Points
48 | shift: number
49 | universe: Element
50 | context: this
51 | constructor(paper: SVG, cx: number, cy: number, radius: number, data: AstroData, settings: Settings) {
52 | this.settings = settings
53 | // Validate data
54 | const status = validate(data)
55 | if (status.hasError) {
56 | throw new Error(status.messages.join(' | '))
57 | }
58 |
59 | this.data = data
60 | this.paper = paper
61 | this.cx = cx
62 | this.cy = cy
63 | this.radius = radius
64 |
65 | // after calling this.drawPoints() it contains current position of point
66 | this.locatedPoints = []
67 | this.rulerRadius = ((this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) / this.settings.RULER_RADIUS)
68 | this.pointRadius = this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + 2 * this.rulerRadius + (this.settings.PADDING * this.settings.SYMBOL_SCALE))
69 |
70 | // @see aspects()
71 | // @see setPointsOfInterest()
72 | this.toPoints = JSON.parse(JSON.stringify(this.data.planets)) // Clone object
73 |
74 | this.shift = 0
75 | if (this.data.cusps && this.data.cusps[0]) {
76 | const deg360 = radiansToDegree(2 * Math.PI)
77 | this.shift = deg360 - this.data.cusps[0]
78 | }
79 |
80 | // preparing wrapper for aspects. It is the lowest layer
81 | const divisionForAspects = document.createElementNS(this.paper.root.namespaceURI, 'g')
82 | divisionForAspects.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_ASPECTS)
83 | this.paper.root.appendChild(divisionForAspects)
84 |
85 | this.universe = document.createElementNS(this.paper.root.namespaceURI, 'g')
86 | this.universe.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_RADIX)
87 | this.paper.root.appendChild(this.universe)
88 |
89 | this.context = this
90 | }
91 |
92 | /**
93 | * Draw background
94 | */
95 | drawBg(): void {
96 | const universe = this.universe
97 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_BG, this.paper.root.id)
98 |
99 | const LARGE_ARC_FLAG = 1
100 | const start = 0 // degree
101 | const end = 359.99 // degree
102 | const hemisphere = this.paper.segment(this.cx, this.cy, this.radius - this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO, start, end, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, LARGE_ARC_FLAG)
103 | hemisphere.setAttribute('fill', this.settings.STROKE_ONLY ? 'none' : this.settings.COLOR_BACKGROUND)
104 | wrapper.appendChild(hemisphere)
105 | }
106 |
107 | /**
108 | * Draw universe.
109 | */
110 | drawUniverse(): void {
111 | const universe = this.universe
112 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_SIGNS, this.paper.root.id)
113 |
114 | // colors
115 | for (let i = 0, step = 30, start = this.shift, len = this.settings.COLORS_SIGNS.length; i < len; i++) {
116 | const segment = this.paper.segment(this.cx, this.cy, this.radius, start, start + step, this.radius - this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO)
117 | segment.setAttribute('fill', this.settings.STROKE_ONLY ? 'none' : this.settings.COLORS_SIGNS[i])
118 | segment.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_SIGNS + '-' + i)
119 | segment.setAttribute('stroke', this.settings.STROKE_ONLY ? this.settings.CIRCLE_COLOR : 'none')
120 | segment.setAttribute('stroke-width', this.settings.STROKE_ONLY ? '1' : '0')
121 | wrapper.appendChild(segment)
122 |
123 | start += step
124 | }
125 |
126 | // signs
127 | for (let i = 0, step = 30, start = 15 + this.shift, len = this.settings.SYMBOL_SIGNS.length; i < len; i++) {
128 | const position = getPointPosition(this.cx, this.cy, this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) / 2, start, this.settings)
129 | wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_SIGNS[i], position.x, position.y))
130 | start += step
131 | }
132 | }
133 |
134 | /**
135 | * Draw points
136 | */
137 | drawPoints(): void {
138 | if (this.data.planets == null) {
139 | return
140 | }
141 |
142 | const universe = this.universe
143 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_POINTS, this.paper.root.id)
144 |
145 | const gap = this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO)
146 | const step = (gap - 2 * (this.settings.PADDING * this.settings.SYMBOL_SCALE)) / Object.keys(this.data.planets).length
147 |
148 | const pointerRadius = this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.rulerRadius)
149 | let startPosition
150 | let endPosition
151 |
152 | for (const planet in this.data.planets) {
153 | if (this.data.planets.hasOwnProperty(planet)) {
154 | const position = getPointPosition(this.cx, this.cy, this.pointRadius, this.data.planets[planet][0] + this.shift, this.settings)
155 | const point = { name: planet, x: position.x, y: position.y, r: (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE), angle: this.data.planets[planet][0] + this.shift, pointer: this.data.planets[planet][0] + this.shift }
156 | this.locatedPoints = assemble(this.locatedPoints, point, { cx: this.cx, cy: this.cy, r: this.pointRadius }, this.settings)
157 | }
158 | }
159 |
160 | if (this.settings.DEBUG) console.log('Radix count of points: ' + this.locatedPoints.length)
161 | if (this.settings.DEBUG) console.log('Radix located points:\n' + JSON.stringify(this.locatedPoints))
162 |
163 | this.locatedPoints.forEach(function (point: LocatedPoint) {
164 | // draw pointer
165 | startPosition = getPointPosition(this.cx, this.cy, pointerRadius, this.data.planets[point.name][0] + this.shift, this.settings)
166 | endPosition = getPointPosition(this.cx, this.cy, pointerRadius - this.rulerRadius / 2, this.data.planets[point.name][0] + this.shift, this.settings)
167 | const pointer = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y)
168 | pointer.setAttribute('stroke', this.settings.CIRCLE_COLOR)
169 | pointer.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE))
170 | wrapper.appendChild(pointer)
171 |
172 | // draw pointer line
173 | if (!this.settings.STROKE_ONLY && (this.data.planets[point.name][0] + this.shift) !== point.angle) {
174 | startPosition = endPosition
175 | endPosition = getPointPosition(this.cx, this.cy, this.pointRadius + (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE), point.angle, this.settings)
176 | const line = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y)
177 | line.setAttribute('stroke', this.settings.LINE_COLOR)
178 | line.setAttribute('stroke-width', 0.5 * (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE))
179 | wrapper.appendChild(line)
180 | }
181 |
182 | // draw symbol
183 | const symbol = this.paper.getSymbol(point.name, point.x, point.y)
184 | symbol.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_POINTS + '-' + point.name)
185 | wrapper.appendChild(symbol)
186 |
187 | // draw point descriptions
188 | let textsToShow = [(Math.floor(this.data.planets[point.name][0]) % 30).toString()]
189 |
190 | const zodiac = new Zodiac(this.data.cusps, this.settings)
191 |
192 | if (this.data.planets[point.name][1] && zodiac.isRetrograde(this.data.planets[point.name][1])) {
193 | textsToShow.push('R')
194 | } else {
195 | textsToShow.push('')
196 | }
197 |
198 | if (this.settings.SHOW_DIGNITIES_TEXT)
199 | textsToShow = textsToShow.concat(zodiac.getDignities({ name: point.name, position: this.data.planets[point.name][0] }, this.settings.DIGNITIES_EXACT_EXALTATION_DEFAULT).join(','))
200 |
201 | const pointDescriptions = getDescriptionPosition(point, textsToShow, this.settings)
202 | pointDescriptions.forEach(function (dsc) {
203 | wrapper.appendChild(this.paper.text(dsc.text, dsc.x, dsc.y, this.settings.POINTS_TEXT_SIZE, this.settings.SIGNS_COLOR))
204 | }, this)
205 | }, this)
206 | }
207 |
208 | drawAxis(): void {
209 | if (this.data.cusps == null) {
210 | return
211 | }
212 |
213 | const universe = this.universe
214 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_AXIS, this.paper.root.id)
215 |
216 | const axisRadius = this.radius + ((this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) / 4)
217 |
218 | const AS = 0
219 | const IC = 3
220 | const DC = 6
221 | const MC = 9
222 | let overlapLine
223 | let startPosition
224 | let endPosition
225 |
226 | [AS, IC, DC, MC].forEach(function (i) {
227 | let textPosition
228 | // overlap
229 | startPosition = getPointPosition(this.cx, this.cy, this.radius, this.data.cusps[i] + this.shift, this.settings)
230 | endPosition = getPointPosition(this.cx, this.cy, axisRadius, this.data.cusps[i] + this.shift, this.settings)
231 | overlapLine = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y)
232 | overlapLine.setAttribute('stroke', this.settings.LINE_COLOR)
233 | overlapLine.setAttribute('stroke-width', (this.settings.SYMBOL_AXIS_STROKE * this.settings.SYMBOL_SCALE))
234 | wrapper.appendChild(overlapLine)
235 |
236 | // As
237 | if (i === AS) {
238 | // Text
239 | textPosition = getPointPosition(this.cx, this.cy, axisRadius + (20 * this.settings.SYMBOL_SCALE), this.data.cusps[i] + this.shift, this.settings)
240 | wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_AS, textPosition.x, textPosition.y))
241 | }
242 |
243 | // Ds
244 | if (i === DC) {
245 | // Text
246 | textPosition = getPointPosition(this.cx, this.cy, axisRadius + (2 * this.settings.SYMBOL_SCALE), this.data.cusps[i] + this.shift, this.settings)
247 | wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_DS, textPosition.x, textPosition.y))
248 | }
249 |
250 | // Ic
251 | if (i === IC) {
252 | // Text
253 | textPosition = getPointPosition(this.cx, this.cy, axisRadius + (10 * this.settings.SYMBOL_SCALE), this.data.cusps[i] - 2 + this.shift, this.settings)
254 | wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_IC, textPosition.x, textPosition.y))
255 | }
256 |
257 | // Mc
258 | if (i === MC) {
259 | // Text
260 | textPosition = getPointPosition(this.cx, this.cy, axisRadius + (10 * this.settings.SYMBOL_SCALE), this.data.cusps[i] + 2 + this.shift, this.settings)
261 | wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_MC, textPosition.x, textPosition.y))
262 | }
263 | }, this)
264 | }
265 |
266 | /**
267 | * Draw cusps
268 | */
269 | drawCusps(): void {
270 | if (this.data.cusps == null) {
271 | return
272 | }
273 |
274 | let lines
275 | const universe = this.universe
276 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_CUSPS, this.paper.root.id)
277 |
278 | const numbersRadius = this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO + (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE)
279 |
280 | const AS = 0
281 | const IC = 3
282 | const DC = 6
283 | const MC = 9
284 | const mainAxis = [AS, IC, DC, MC]
285 |
286 | // Cusps
287 | for (let i = 0, ln = this.data.cusps.length; i < ln; i++) {
288 | // Draws a dashed line when an point is in the way
289 | lines = getDashedLinesPositions(
290 | this.cx,
291 | this.cy,
292 | this.data.cusps[i] + this.shift,
293 | this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO,
294 | this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.rulerRadius),
295 | this.pointRadius,
296 | this.locatedPoints,
297 | this.settings
298 | )
299 |
300 | lines.forEach(function (line) {
301 | const newLine = this.paper.line(line.startX, line.startY, line.endX, line.endY)
302 | newLine.setAttribute('stroke', this.settings.LINE_COLOR)
303 |
304 | if (mainAxis.includes(i)) {
305 | newLine.setAttribute('stroke-width', (this.settings.SYMBOL_AXIS_STROKE * this.settings.SYMBOL_SCALE))
306 | } else {
307 | newLine.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE))
308 | }
309 |
310 | wrapper.appendChild(newLine)
311 | }, this)
312 |
313 | // Cup number
314 | const deg360 = radiansToDegree(2 * Math.PI)
315 | const startOfCusp = this.data.cusps[i]
316 | const endOfCusp = this.data.cusps[(i + 1) % 12]
317 | const gap = endOfCusp - startOfCusp > 0 ? endOfCusp - startOfCusp : endOfCusp - startOfCusp + deg360
318 | const textPosition = getPointPosition(this.cx, this.cy, numbersRadius, ((startOfCusp + gap / 2) % deg360) + this.shift, this.settings)
319 | wrapper.appendChild(this.paper.getSymbol((i + 1).toString(), textPosition.x, textPosition.y))
320 | }
321 | }
322 |
323 | /**
324 | * Draw aspects
325 | * @param{Array | null} customAspects - posible custom aspects to draw;
326 | */
327 | aspects(customAspects?: FormedAspect[] | null): Radix {
328 | const aspectsList = customAspects != null && Array.isArray(customAspects)
329 | ? customAspects
330 | : new AspectCalculator(this.toPoints, this.settings).radix(this.data.planets)
331 |
332 | const universe = this.universe
333 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_ASPECTS, this.paper.root.id)
334 |
335 | const duplicateCheck: string[] = []
336 |
337 | for (let i = 0, ln = aspectsList.length; i < ln; i++) {
338 | const key = aspectsList[i].aspect.name + '-' + aspectsList[i].point.name + '-' + aspectsList[i].toPoint.name
339 | const opositeKey = aspectsList[i].aspect.name + '-' + aspectsList[i].toPoint.name + '-' + aspectsList[i].point.name
340 | if (!duplicateCheck.includes(opositeKey)) {
341 | duplicateCheck.push(key)
342 |
343 | const startPoint = getPointPosition(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, aspectsList[i].toPoint.position + this.shift, this.settings)
344 | const endPoint = getPointPosition(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, aspectsList[i].point.position + this.shift, this.settings)
345 |
346 | const line = this.paper.line(startPoint.x, startPoint.y, endPoint.x, endPoint.y)
347 | line.setAttribute('stroke', this.settings.STROKE_ONLY ? this.settings.LINE_COLOR : aspectsList[i].aspect.color)
348 | line.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE).toString())
349 |
350 | line.setAttribute('data-name', aspectsList[i].aspect.name)
351 | line.setAttribute('data-degree', aspectsList[i].aspect.degree.toString())
352 | line.setAttribute('data-point', aspectsList[i].point.name)
353 | line.setAttribute('data-toPoint', aspectsList[i].toPoint.name)
354 | line.setAttribute('data-precision', aspectsList[i].precision.toString())
355 |
356 | wrapper.appendChild(line)
357 | }
358 | }
359 |
360 | return this.context
361 | }
362 |
363 | /**
364 | * Add points of interest for aspects calculation
365 | * @param {Obect} points, {"As":[0],"Ic":[90],"Ds":[180],"Mc":[270]}
366 | * @see (this.settings.AspectCalculator( toPoints) )
367 | */
368 | addPointsOfInterest(points: Points): Radix {
369 | for (const point in points) {
370 | if (points.hasOwnProperty(point)) {
371 | this.toPoints[point] = points[point]
372 | }
373 | }
374 |
375 | return this.context
376 | }
377 |
378 | drawRuler(): void {
379 | const universe = this.universe
380 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_RULER, this.paper.root.id)
381 |
382 | const startRadius = (this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.rulerRadius))
383 | const rays = getRulerPositions(this.cx, this.cy, startRadius, startRadius + this.rulerRadius, this.shift, this.settings)
384 |
385 | rays.forEach(function (ray) {
386 | const line = this.paper.line(ray.startX, ray.startY, ray.endX, ray.endY)
387 | line.setAttribute('stroke', this.settings.CIRCLE_COLOR)
388 | line.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE))
389 | wrapper.appendChild(line)
390 | }, this)
391 |
392 | const circle = this.paper.circle(this.cx, this.cy, startRadius)
393 | circle.setAttribute('stroke', this.settings.CIRCLE_COLOR)
394 | circle.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE).toString())
395 | wrapper.appendChild(circle)
396 | }
397 |
398 | /**
399 | * Draw circles
400 | */
401 | drawCircles(): void {
402 | const universe = this.universe
403 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_CIRCLES, this.paper.root.id)
404 |
405 | // indoor circle
406 | let circle = this.paper.circle(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO)
407 | circle.setAttribute('stroke', this.settings.CIRCLE_COLOR)
408 | circle.setAttribute('stroke-width', (this.settings.CIRCLE_STRONG * this.settings.SYMBOL_SCALE).toString())
409 | wrapper.appendChild(circle)
410 |
411 | // outdoor circle
412 | circle = this.paper.circle(this.cx, this.cy, this.radius)
413 | circle.setAttribute('stroke', this.settings.CIRCLE_COLOR)
414 | circle.setAttribute('stroke-width', (this.settings.CIRCLE_STRONG * this.settings.SYMBOL_SCALE).toString())
415 | wrapper.appendChild(circle)
416 |
417 | // inner circle
418 | circle = this.paper.circle(this.cx, this.cy, this.radius - this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO)
419 | circle.setAttribute('stroke', this.settings.CIRCLE_COLOR)
420 | circle.setAttribute('stroke-width', (this.settings.CIRCLE_STRONG * this.settings.SYMBOL_SCALE).toString())
421 | wrapper.appendChild(circle)
422 | }
423 |
424 | /**
425 | * Display transit horoscope
426 | *
427 | * @param {Object} data
428 | * @example
429 | * {
430 | * "planets":{"Moon":[0], "Sun":[30], ... },
431 | * "cusps":[300, 340, 30, 60, 75, 90, 116, 172, 210, 236, 250, 274], *
432 | * }
433 | *
434 | * @return {Transit} transit
435 | */
436 | transit(data: AstroData): Transit {
437 | // remove axis (As, Ds, Mc, Ic) from radix
438 | getEmptyWrapper(this.universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_AXIS, this.paper.root.id)
439 | const transit = new Transit(this.context, data, this.settings)
440 | transit.drawBg()
441 | transit.drawPoints()
442 | transit.drawCusps()
443 | transit.drawRuler()
444 | transit.drawCircles()
445 | return transit
446 | }
447 | }
448 |
449 | export default Radix
450 |
--------------------------------------------------------------------------------
/project/src/settings.ts:
--------------------------------------------------------------------------------
1 | import type SVG from './svg'
2 |
3 | export interface AspectData { degree: number; orbit: number; color: string }
4 | export type Aspect = Record
5 | export interface Dignity {
6 | name: string
7 | position: number
8 | orbit: number
9 | }
10 |
11 | export interface Settings {
12 | SYMBOL_SCALE: number
13 | COLOR_BACKGROUND: string
14 | POINTS_COLOR: string
15 | POINTS_TEXT_SIZE: number
16 | POINTS_STROKE: number
17 | SIGNS_COLOR: string
18 | SIGNS_STROKE: number
19 | MARGIN: number
20 | PADDING: number
21 | ID_CHART: string
22 | ID_RADIX: string
23 | ID_TRANSIT: string
24 | ID_ASPECTS: string
25 | ID_POINTS: string
26 | ID_SIGNS: string
27 | ID_CIRCLES: string
28 | ID_AXIS: string
29 | ID_CUSPS: string
30 | ID_RULER: string
31 | ID_BG: string
32 | CIRCLE_COLOR: string
33 | CIRCLE_STRONG: number
34 | LINE_COLOR: string
35 | INDOOR_CIRCLE_RADIUS_RATIO: number
36 | INNER_CIRCLE_RADIUS_RATIO: number
37 | RULER_RADIUS: number
38 | SYMBOL_SUN: string
39 | SYMBOL_MOON: string
40 | SYMBOL_MERCURY: string
41 | SYMBOL_VENUS: string
42 | SYMBOL_MARS: string
43 | SYMBOL_JUPITER: string
44 | SYMBOL_SATURN: string
45 | SYMBOL_URANUS: string
46 | SYMBOL_NEPTUNE: string
47 | SYMBOL_PLUTO: string
48 | SYMBOL_CHIRON: string
49 | SYMBOL_LILITH: string
50 | SYMBOL_NNODE: string
51 | SYMBOL_SNODE: string
52 | SYMBOL_FORTUNE: string
53 | SYMBOL_AS: string
54 | SYMBOL_DS: string
55 | SYMBOL_MC: string
56 | SYMBOL_IC: string
57 | SYMBOL_AXIS_FONT_COLOR: string
58 | SYMBOL_AXIS_STROKE: number
59 | SYMBOL_CUSP_1: string
60 | SYMBOL_CUSP_2: string
61 | SYMBOL_CUSP_3: string
62 | SYMBOL_CUSP_4: string
63 | SYMBOL_CUSP_5: string
64 | SYMBOL_CUSP_6: string
65 | SYMBOL_CUSP_7: string
66 | SYMBOL_CUSP_8: string
67 | SYMBOL_CUSP_9: string
68 | SYMBOL_CUSP_10: string
69 | SYMBOL_CUSP_11: string
70 | SYMBOL_CUSP_12: string
71 | CUSPS_STROKE: number
72 | CUSPS_FONT_COLOR: string
73 | SYMBOL_ARIES: string
74 | SYMBOL_TAURUS: string
75 | SYMBOL_GEMINI: string
76 | SYMBOL_CANCER: string
77 | SYMBOL_LEO: string
78 | SYMBOL_VIRGO: string
79 | SYMBOL_LIBRA: string
80 | SYMBOL_SCORPIO: string
81 | SYMBOL_SAGITTARIUS: string
82 | SYMBOL_CAPRICORN: string
83 | SYMBOL_AQUARIUS: string
84 | SYMBOL_PISCES: string
85 | SYMBOL_SIGNS: string[]
86 | COLOR_ARIES: string
87 | COLOR_TAURUS: string
88 | COLOR_GEMINI: string
89 | COLOR_CANCER: string
90 | COLOR_LEO: string
91 | COLOR_VIRGO: string
92 | COLOR_LIBRA: string
93 | COLOR_SCORPIO: string
94 | COLOR_SAGITTARIUS: string
95 | COLOR_CAPRICORN: string
96 | COLOR_AQUARIUS: string
97 | COLOR_PISCES: string
98 | COLORS_SIGNS: string[]
99 | CUSTOM_SYMBOL_FN: null | ((name: string, x: number, y: number, context: SVG) => Element)
100 | SHIFT_IN_DEGREES: number
101 | STROKE_ONLY: boolean
102 | ADD_CLICK_AREA: boolean
103 | COLLISION_RADIUS: number
104 | ASPECTS: Aspect
105 | SHOW_DIGNITIES_TEXT: boolean
106 | DIGNITIES_RULERSHIP: string
107 | DIGNITIES_DETRIMENT: string
108 | DIGNITIES_EXALTATION: string
109 | DIGNITIES_EXACT_EXALTATION: string
110 | DIGNITIES_FALL: string
111 | DIGNITIES_EXACT_EXALTATION_DEFAULT: Dignity[]
112 | ANIMATION_CUSPS_ROTATION_SPEED: number
113 | DEBUG: boolean
114 | }
115 |
116 | const settings: Settings = {
117 |
118 | // Scale of symbols
119 | SYMBOL_SCALE: 1,
120 |
121 | // BG color
122 | COLOR_BACKGROUND: '#fff',
123 |
124 | // Color of planet's symbols
125 | POINTS_COLOR: '#000',
126 |
127 | // Size of description text next to the planet: angle, retrograde, dignities, ...
128 | POINTS_TEXT_SIZE: 8,
129 |
130 | // Points strength of lines
131 | POINTS_STROKE: 1.8,
132 |
133 | // Font color of signs symbols
134 | SIGNS_COLOR: '#000',
135 |
136 | // Signs strength of lines
137 | SIGNS_STROKE: 1.5,
138 |
139 | // Chart margin
140 | MARGIN: 50, // px
141 |
142 | // Chart Padding
143 | PADDING: 18, // px
144 |
145 | // Module wrapper element ID
146 | ID_CHART: 'astrology',
147 |
148 | // Radix chart element ID
149 | ID_RADIX: 'radix',
150 |
151 | // Transit chart element ID
152 | ID_TRANSIT: 'transit',
153 |
154 | // Aspects wrapper element ID
155 | ID_ASPECTS: 'aspects',
156 |
157 | // Aspects wrapper element ID
158 | ID_POINTS: 'planets',
159 |
160 | // Signs wrapper element ID
161 | ID_SIGNS: 'signs',
162 |
163 | // Circles wrapper element ID
164 | ID_CIRCLES: 'circles',
165 |
166 | // Axis wrapper element ID
167 | ID_AXIS: 'axis',
168 |
169 | // Cusps wrapper element ID
170 | ID_CUSPS: 'cusps',
171 |
172 | // Cusps wrapper element ID
173 | ID_RULER: 'ruler',
174 |
175 | // Background wrapper element ID
176 | ID_BG: 'bg',
177 |
178 | // Color of circles in charts
179 | CIRCLE_COLOR: '#333',
180 |
181 | // Circles strength of lines
182 | CIRCLE_STRONG: 2,
183 |
184 | // Color of lines in charts
185 | LINE_COLOR: '#333',
186 |
187 | // radius / INDOOR_CIRCLE_RADIUS_RATIO
188 | INDOOR_CIRCLE_RADIUS_RATIO: 2,
189 |
190 | // radius - radius/INNER_CIRCLE_RADIUS_RATIO
191 | INNER_CIRCLE_RADIUS_RATIO: 8,
192 |
193 | // ( radius / INNER_CIRCLE_RADIUS_RATIO ) / RULER_RADIUS
194 | RULER_RADIUS: 4,
195 |
196 | // Points
197 | SYMBOL_SUN: 'Sun',
198 | SYMBOL_MOON: 'Moon',
199 | SYMBOL_MERCURY: 'Mercury',
200 | SYMBOL_VENUS: 'Venus',
201 | SYMBOL_MARS: 'Mars',
202 | SYMBOL_JUPITER: 'Jupiter',
203 | SYMBOL_SATURN: 'Saturn',
204 | SYMBOL_URANUS: 'Uranus',
205 | SYMBOL_NEPTUNE: 'Neptune',
206 | SYMBOL_PLUTO: 'Pluto',
207 | SYMBOL_CHIRON: 'Chiron',
208 | SYMBOL_LILITH: 'Lilith',
209 | SYMBOL_NNODE: 'NNode',
210 | SYMBOL_SNODE: 'SNode',
211 | SYMBOL_FORTUNE: 'Fortune',
212 |
213 | // Axis
214 | SYMBOL_AS: 'As',
215 | SYMBOL_DS: 'Ds',
216 | SYMBOL_MC: 'Mc',
217 | SYMBOL_IC: 'Ic',
218 |
219 | SYMBOL_AXIS_FONT_COLOR: '#333',
220 | SYMBOL_AXIS_STROKE: 1.6,
221 |
222 | // Cusps
223 | SYMBOL_CUSP_1: '1',
224 | SYMBOL_CUSP_2: '2',
225 | SYMBOL_CUSP_3: '3',
226 | SYMBOL_CUSP_4: '4',
227 | SYMBOL_CUSP_5: '5',
228 | SYMBOL_CUSP_6: '6',
229 | SYMBOL_CUSP_7: '7',
230 | SYMBOL_CUSP_8: '8',
231 | SYMBOL_CUSP_9: '9',
232 | SYMBOL_CUSP_10: '10',
233 | SYMBOL_CUSP_11: '11',
234 | SYMBOL_CUSP_12: '12',
235 |
236 | // Cusps strength of lines
237 | CUSPS_STROKE: 1,
238 | CUSPS_FONT_COLOR: '#000',
239 |
240 | // Signs
241 | SYMBOL_ARIES: 'Aries',
242 | SYMBOL_TAURUS: 'Taurus',
243 | SYMBOL_GEMINI: 'Gemini',
244 | SYMBOL_CANCER: 'Cancer',
245 | SYMBOL_LEO: 'Leo',
246 | SYMBOL_VIRGO: 'Virgo',
247 | SYMBOL_LIBRA: 'Libra',
248 | SYMBOL_SCORPIO: 'Scorpio',
249 | SYMBOL_SAGITTARIUS: 'Sagittarius',
250 | SYMBOL_CAPRICORN: 'Capricorn',
251 | SYMBOL_AQUARIUS: 'Aquarius',
252 | SYMBOL_PISCES: 'Pisces',
253 | SYMBOL_SIGNS: ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'],
254 |
255 | // http://www.rapidtables.com/web/color/html-color-codes.htm
256 | COLOR_ARIES: '#FF4500',
257 | COLOR_TAURUS: '#8B4513',
258 | COLOR_GEMINI: '#87CEEB',
259 | COLOR_CANCER: '#27AE60',
260 | COLOR_LEO: '#FF4500',
261 | COLOR_VIRGO: '#8B4513',
262 | COLOR_LIBRA: '#87CEEB',
263 | COLOR_SCORPIO: '#27AE60',
264 | COLOR_SAGITTARIUS: '#FF4500',
265 | COLOR_CAPRICORN: '#8B4513',
266 | COLOR_AQUARIUS: '#87CEEB',
267 | COLOR_PISCES: '#27AE60',
268 | COLORS_SIGNS: ['#FF4500', '#8B4513', '#87CEEB', '#27AE60', '#FF4500', '#8B4513', '#87CEEB', '#27AE60', '#FF4500', '#8B4513', '#87CEEB', '#27AE60'],
269 |
270 | CUSTOM_SYMBOL_FN: null,
271 |
272 | // 0 degree is on the West
273 | SHIFT_IN_DEGREES: 180,
274 |
275 | // No fill, only stroke
276 | STROKE_ONLY: false,
277 |
278 | ADD_CLICK_AREA: false,
279 |
280 | // Planets collision circle radius for SYMBOL_SCALE : 1
281 | // Scaling changes the collision radius
282 | COLLISION_RADIUS: 10, // px
283 |
284 | // Aspects
285 | ASPECTS: {
286 | conjunction: { degree: 0, orbit: 10, color: 'transparent' },
287 | square: { degree: 90, orbit: 8, color: '#FF4500' },
288 | trine: { degree: 120, orbit: 8, color: '#27AE60' },
289 | opposition: { degree: 180, orbit: 10, color: '#27AE60' }
290 | },
291 |
292 | // Dignities
293 | SHOW_DIGNITIES_TEXT: true,
294 | DIGNITIES_RULERSHIP: 'r',
295 | DIGNITIES_DETRIMENT: 'd',
296 | DIGNITIES_EXALTATION: 'e',
297 | DIGNITIES_EXACT_EXALTATION: 'E',
298 | DIGNITIES_FALL: 'f',
299 |
300 | // Source: Aleister Crowley
301 | DIGNITIES_EXACT_EXALTATION_DEFAULT: [
302 | { name: 'Sun', position: 19, orbit: 2 }, // 19 Arise
303 | { name: 'Moon', position: 33, orbit: 2 }, // 3 Taurus
304 | { name: 'Mercury', position: 155, orbit: 2 }, // 15 Virgo
305 | { name: 'Venus', position: 357, orbit: 2 }, // 27 Pisces
306 | { name: 'Mars', position: 298, orbit: 2 }, // 28 Capricorn
307 | { name: 'Jupiter', position: 105, orbit: 2 }, // 15 Cancer
308 | { name: 'Saturn', position: 201, orbit: 2 }, // 21 Libra
309 | { name: 'NNode', position: 63, orbit: 2 } // 3 Geminy
310 | ],
311 |
312 | // 0 - 4
313 | ANIMATION_CUSPS_ROTATION_SPEED: 2,
314 |
315 | DEBUG: false
316 | }
317 |
318 | const default_settings = settings
319 | export default default_settings
320 |
--------------------------------------------------------------------------------
/project/src/svg.test.ts:
--------------------------------------------------------------------------------
1 | import default_settings from "./settings";
2 | import SVG from "./svg";
3 |
4 | describe('getSymbol', () => {
5 | beforeAll(() => {
6 | document.body.innerHTML =
7 | '' +
8 | '
';
9 | });
10 | describe('ADD_CLICK_AREA', () => {
11 | test(`don't create rect for cusps if ADD_CLICK_AREA is false`, () => {
12 | for(var i = 1; i <= 12; i++){
13 | const svg = new SVG("test-element", 100, 100, {...default_settings, ADD_CLICK_AREA : false})
14 | const element = svg.getSymbol(i.toString(), 1, 1)
15 | expect(element.getElementsByTagName("rect")).toHaveLength(0)
16 | }
17 | })
18 |
19 | test(`create rect for cusps if ADD_CLICK_AREA is true`, () => {
20 | for(var i = 1; i <= 12; i++){
21 | const svg = new SVG("test-element", 100, 100, {...default_settings, ADD_CLICK_AREA : true})
22 | const element = svg.getSymbol(i.toString(), 1, 1)
23 | expect(element.getElementsByTagName("rect")).toHaveLength(1)
24 | }
25 | })
26 |
27 | test.each([
28 | [default_settings.SYMBOL_JUPITER, default_settings.SYMBOL_URANUS]
29 | ])(`create rect for %s if ADD_CLICK_AREA is true`, (planet) => {
30 | const svg = new SVG("test-element", 100, 100, {...default_settings, ADD_CLICK_AREA : true})
31 | const element = svg.getSymbol(planet, 1, 1)
32 | expect(element.getElementsByTagName("rect")).toHaveLength(1)
33 | })
34 |
35 | test.each([
36 | [default_settings.SYMBOL_JUPITER, default_settings.SYMBOL_URANUS]
37 | ])(`don't create rect for %s if ADD_CLICK_AREA is false`, (planet) => {
38 | const svg = new SVG("test-element", 100, 100, {...default_settings, ADD_CLICK_AREA : false})
39 | const element = svg.getSymbol(planet, 1, 1)
40 | expect(element.getElementsByTagName("rect")).toHaveLength(0)
41 | })
42 |
43 | test.each([default_settings.SYMBOL_SIGNS])('create rect for %s if ADD_CLICK_AREA is true', (sign) => {
44 | const svg = new SVG("test-element", 100, 100, {...default_settings, ADD_CLICK_AREA : true})
45 | var element = svg.getSymbol(sign, 1, 1);
46 | expect(element.getElementsByTagName("rect")).toHaveLength(1)
47 | });
48 |
49 | test.each([default_settings.SYMBOL_SIGNS])(`don't create rect for %s if ADD_CLICK_AREA is false`, (sign) => {
50 | const svg = new SVG("test-element", 100, 100, {...default_settings, ADD_CLICK_AREA : false})
51 | var element = svg.getSymbol(sign, 1, 1);
52 | expect(element.getElementsByTagName("rect")).toHaveLength(0)
53 | });
54 | })
55 |
56 | test('should call custom getSymbol function', () => {
57 | const mockFn = jest.fn()
58 | const svg = new SVG("test-element", 100, 100, {...default_settings, CUSTOM_SYMBOL_FN : mockFn})
59 | svg.getSymbol(default_settings.SYMBOL_URANUS, 1, 2);
60 | expect(mockFn).toHaveBeenCalledWith(default_settings.SYMBOL_URANUS, 1, 2, svg);
61 | })
62 | })
--------------------------------------------------------------------------------
/project/src/transit.ts:
--------------------------------------------------------------------------------
1 | import Zodiac from './zodiac'
2 | import AspectCalculator from './aspect'
3 | import type { FormedAspect } from './aspect'
4 | import Animator from './animation/animator'
5 | import { validate, getEmptyWrapper, getPointPosition, getRulerPositions, getDescriptionPosition, assemble, radiansToDegree } from './utils'
6 | import type { AstroData, LocatedPoint, Points } from './radix'
7 | import type Radix from './radix'
8 | import type SVG from './svg'
9 | import type { Settings } from './settings'
10 |
11 | /**
12 | * Transit charts.
13 | *
14 | * @class
15 | * @public
16 | * @constructor
17 | * @param {this.settings.Radix} radix
18 | * @param {Object} data
19 | */
20 | class Transit {
21 | data: AstroData
22 | paper: SVG
23 | cx: number
24 | cy: number
25 | toPoints: Points
26 | radius: number
27 | settings: Settings
28 | rulerRadius: number
29 | pointRadius: number
30 | shift: number
31 | universe: Element
32 | context: this
33 | locatedPoints: LocatedPoint[]
34 | constructor(radix: Radix, data: AstroData, settings: Settings) {
35 | // Validate data
36 | const status = validate(data)
37 | if (status.hasError) {
38 | throw new Error(status.messages.join(' | '))
39 | }
40 |
41 | this.data = data
42 | this.paper = radix.paper
43 | this.cx = radix.cx
44 | this.cy = radix.cy
45 | this.toPoints = radix.toPoints
46 | this.radius = radix.radius
47 | this.settings = settings
48 |
49 | this.rulerRadius = ((this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) / this.settings.RULER_RADIUS)
50 | this.pointRadius = this.radius + (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + (this.settings.PADDING * this.settings.SYMBOL_SCALE))
51 |
52 | this.shift = radix.shift
53 |
54 | this.universe = document.createElementNS(this.paper.root.namespaceURI, 'g')
55 | this.universe.setAttribute('id', this.paper._paperElementId + '-' + this.settings.ID_TRANSIT)
56 | this.paper.root.appendChild(this.universe)
57 |
58 | this.context = this
59 | }
60 |
61 | /**
62 | * Draw background
63 | */
64 | drawBg(): void {
65 | const universe = this.universe
66 |
67 | const wrapper = getEmptyWrapper(universe, this.paper._paperElementId + '-' + this.settings.ID_BG, this.paper._paperElementId)
68 |
69 | const LARGE_ARC_FLAG = 1
70 | const start = 0 // degree
71 | const end = 359.99 // degree
72 | const hemisphere = this.paper.segment(this.cx, this.cy, this.radius + this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO, start, end, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, LARGE_ARC_FLAG)
73 | hemisphere.setAttribute('fill', this.settings.STROKE_ONLY ? 'none' : this.settings.COLOR_BACKGROUND)
74 | wrapper.appendChild(hemisphere)
75 | }
76 |
77 | /**
78 | * Draw planets
79 | *
80 | * @param{undefined | Object} planetsData, posible data planets to draw
81 | */
82 | drawPoints(planetsData?: Points): void {
83 | const planets = (planetsData == null) ? this.data.planets : planetsData
84 | if (planets == null) {
85 | return
86 | }
87 |
88 | const universe = this.universe
89 | const wrapper = getEmptyWrapper(universe, this.paper._paperElementId + '-' + this.settings.ID_TRANSIT + '-' + this.settings.ID_POINTS, this.paper._paperElementId)
90 |
91 | const gap = this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO)
92 | const step = (gap - 2 * (this.settings.PADDING * this.settings.SYMBOL_SCALE)) / Object.keys(planets).length
93 |
94 | const pointerRadius = this.radius + (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO)
95 | let startPosition
96 | let endPosition
97 |
98 | this.locatedPoints = []
99 | for (const planet in planets) {
100 | if (planets.hasOwnProperty(planet)) {
101 | const position = getPointPosition(this.cx, this.cy, this.pointRadius, planets[planet][0] + this.shift, this.settings)
102 | const point = { name: planet, x: position.x, y: position.y, r: (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE), angle: planets[planet][0] + this.shift, pointer: planets[planet][0] + this.shift }
103 | this.locatedPoints = assemble(this.locatedPoints, point, { cx: this.cx, cy: this.cy, r: this.pointRadius }, this.settings)
104 | }
105 | }
106 |
107 | if (this.settings.DEBUG) console.log('Transit count of points: ' + this.locatedPoints.length)
108 | if (this.settings.DEBUG) console.log('Transit located points:\n' + JSON.stringify(this.locatedPoints))
109 |
110 | this.locatedPoints.forEach(function (point) {
111 | // draw pointer
112 | startPosition = getPointPosition(this.cx, this.cy, pointerRadius, planets[point.name][0] + this.shift, this.settings)
113 | endPosition = getPointPosition(this.cx, this.cy, pointerRadius + this.rulerRadius / 2, planets[point.name][0] + this.shift, this.settings)
114 | const pointer = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y)
115 | pointer.setAttribute('stroke', this.settings.CIRCLE_COLOR)
116 | pointer.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE))
117 | wrapper.appendChild(pointer)
118 |
119 | // draw pointer line
120 | if (!this.settings.STROKE_ONLY && (planets[point.name][0] + this.shift) !== point.angle) {
121 | startPosition = endPosition
122 | endPosition = getPointPosition(this.cx, this.cy, this.pointRadius - (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE), point.angle, this.settings)
123 | const line = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y)
124 | line.setAttribute('stroke', this.settings.LINE_COLOR)
125 | line.setAttribute('stroke-width', 0.5 * (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE))
126 | wrapper.appendChild(line)
127 | }
128 |
129 | // draw symbol
130 | const symbol = this.paper.getSymbol(point.name, point.x, point.y)
131 | symbol.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_TRANSIT + '-' + this.settings.ID_POINTS + '-' + point.name)
132 | wrapper.appendChild(symbol)
133 |
134 | // draw point descriptions
135 | let textsToShow = [(Math.floor(planets[point.name][0]) % 30).toString()]
136 |
137 | const zodiac = new Zodiac(this.data.cusps, this.settings)
138 | if (planets[point.name][1] && zodiac.isRetrograde(planets[point.name][1])) {
139 | textsToShow.push('R')
140 | } else {
141 | textsToShow.push('')
142 | }
143 | textsToShow = textsToShow.concat(zodiac.getDignities({ name: point.name, position: planets[point.name][0] }, this.settings.DIGNITIES_EXACT_EXALTATION_DEFAULT).join(','))
144 |
145 | const pointDescriptions = getDescriptionPosition(point, textsToShow, this.settings)
146 | pointDescriptions.forEach(function (dsc) {
147 | wrapper.appendChild(this.paper.text(dsc.text, dsc.x, dsc.y, this.settings.POINTS_TEXT_SIZE, this.settings.SIGNS_COLOR))
148 | }, this)
149 | }, this)
150 | }
151 |
152 | /**
153 | * Draw circles
154 | */
155 | drawCircles(): void {
156 | const universe = this.universe
157 | const wrapper = getEmptyWrapper(universe, this.paper._paperElementId + '-' + this.settings.ID_TRANSIT + '-' + this.settings.ID_CIRCLES, this.paper._paperElementId)
158 | const radius = this.radius + this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO
159 |
160 | const circle = this.paper.circle(this.cx, this.cy, radius)
161 | circle.setAttribute('stroke', this.settings.CIRCLE_COLOR)
162 | circle.setAttribute('stroke-width', (this.settings.CIRCLE_STRONG * this.settings.SYMBOL_SCALE).toString())
163 | wrapper.appendChild(circle)
164 | }
165 |
166 | /**
167 | * Draw cusps
168 | * @param{undefined | Object} cuspsData, posible data cusps to draw
169 | */
170 | drawCusps(cuspsData?: number[]): void {
171 | const cusps = (cuspsData == null) ? this.data.cusps : cuspsData
172 | if (cusps == null) {
173 | return
174 | }
175 |
176 | let bottomPosition
177 | const universe = this.universe
178 | const wrapper = getEmptyWrapper(universe, this.paper._paperElementId + '-' + this.settings.ID_TRANSIT + '-' + this.settings.ID_CUSPS, this.paper._paperElementId)
179 | const numbersRadius = this.radius + ((this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO - this.rulerRadius) / 2)
180 |
181 | const AS = 0
182 | const IC = 3
183 | const DC = 6
184 | const MC = 9
185 | const mainAxis = [AS, IC, DC, MC]
186 |
187 | // Cusps
188 | for (let i = 0, ln = cusps.length; i < ln; i++) {
189 | // Lines
190 | const startPosition = bottomPosition = getPointPosition(this.cx, this.cy, this.radius, cusps[i] + this.shift, this.settings)
191 | const endPosition = getPointPosition(this.cx, this.cy, this.radius + this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO - this.rulerRadius, cusps[i] + this.shift, this.settings)
192 | const line = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y)
193 | line.setAttribute('stroke', this.settings.LINE_COLOR)
194 | line.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE).toString())
195 |
196 | wrapper.appendChild(line)
197 |
198 | // Cup number
199 | const deg360 = radiansToDegree(2 * Math.PI)
200 | const startOfCusp = cusps[i]
201 | const endOfCusp = cusps[(i + 1) % 12]
202 | const gap = endOfCusp - startOfCusp > 0 ? endOfCusp - startOfCusp : endOfCusp - startOfCusp + deg360
203 | const textPosition = getPointPosition(this.cx, this.cy, numbersRadius, ((startOfCusp + gap / 2) % deg360) + this.shift, this.settings)
204 | wrapper.appendChild(this.paper.getSymbol((i + 1).toString(), textPosition.x, textPosition.y))
205 | }
206 | }
207 |
208 | drawRuler(): void {
209 | const universe = this.universe
210 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_TRANSIT + '-' + this.settings.ID_RULER, this.paper._paperElementId)
211 |
212 | const startRadius = (this.radius + (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO))
213 | const rays = getRulerPositions(this.cx, this.cy, startRadius, startRadius - this.rulerRadius, this.shift, this.settings)
214 |
215 | rays.forEach(function (ray) {
216 | const line = this.paper.line(ray.startX, ray.startY, ray.endX, ray.endY)
217 | line.setAttribute('stroke', this.settings.CIRCLE_COLOR)
218 | line.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE))
219 | wrapper.appendChild(line)
220 | }, this)
221 |
222 | const circle = this.paper.circle(this.cx, this.cy, startRadius - this.rulerRadius)
223 | circle.setAttribute('stroke', this.settings.CIRCLE_COLOR)
224 | circle.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE).toString())
225 | wrapper.appendChild(circle)
226 | }
227 |
228 | /**
229 | * Draw aspects
230 | * @param{Array | null} customAspects - posible custom aspects to draw;
231 | */
232 | aspects(customAspects: FormedAspect[]): Transit {
233 | const aspectsList = customAspects != null && Array.isArray(customAspects)
234 | ? customAspects
235 | : new AspectCalculator(this.toPoints, this.settings).transit(this.data.planets)
236 |
237 | const universe = this.universe
238 | const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_ASPECTS, this.paper._paperElementId)
239 |
240 | for (let i = 0, ln = aspectsList.length; i < ln; i++) {
241 | const startPoint = getPointPosition(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, aspectsList[i].toPoint.position + this.shift, this.settings)
242 | const endPoint = getPointPosition(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, aspectsList[i].point.position + this.shift, this.settings)
243 |
244 | const line = this.paper.line(startPoint.x, startPoint.y, endPoint.x, endPoint.y)
245 | line.setAttribute('stroke', this.settings.STROKE_ONLY ? this.settings.LINE_COLOR : aspectsList[i].aspect.color)
246 | line.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE).toString())
247 |
248 | line.setAttribute('data-name', aspectsList[i].aspect.name)
249 | line.setAttribute('data-degree', aspectsList[i].aspect.degree.toString())
250 | line.setAttribute('data-point', aspectsList[i].point.name)
251 | line.setAttribute('data-toPoint', aspectsList[i].toPoint.name)
252 | line.setAttribute('data-precision', aspectsList[i].precision.toString())
253 |
254 | wrapper.appendChild(line)
255 | }
256 |
257 | // this
258 | return this.context
259 | }
260 |
261 | /**
262 | * Moves points to another position.
263 | *
264 | * @param {Object} data - planets target positions.
265 | * @param {Integer} duration - in seconds
266 | * @param {boolean} isReverse
267 | * @param {Function | undefined} callbck - the function executed at the end of animation
268 | */
269 | animate(data: AstroData, duration: number, isReverse: boolean, callback: () => void): Transit {
270 | // Validate data
271 | const status = validate(data)
272 | if (status.hasError) {
273 | throw new Error(status.messages.join(' | '))
274 | }
275 |
276 | // remove aspects
277 | getEmptyWrapper(this.universe, this.paper._paperElementId + '-' + this.settings.ID_ASPECTS, this.paper._paperElementId)
278 |
279 | const animator = new Animator(this.context, this.settings)
280 | animator.animate(data, duration, isReverse, function () {
281 | // animation is finished
282 | this.data = data
283 | this.drawPoints()
284 | this.drawCusps()
285 | this.aspects()
286 |
287 | if (typeof callback === 'function') {
288 | callback()
289 | }
290 | }.bind(this))
291 |
292 | // this
293 | return this.context
294 | }
295 | }
296 |
297 | export default Transit
298 |
--------------------------------------------------------------------------------
/project/src/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { LocatedPoint, Points } from './radix';
2 | import default_settings, { Settings } from './settings';
3 | import { getPointPosition, isCollision, comparePoints, placePointsInCollision, assemble } from './utils'
4 |
5 | describe('getPointPosition', () => {
6 | test.each([
7 | [10, 10, 10, 0, {x:0, y:10}],
8 | [10, 10, 10, 90, {x:10, y:20}],
9 | [10, 10, 10, 180, {x:20, y:10}],
10 | [10, 10, 10, 270, {x:10, y:0}],
11 | [10, 10, 10, 360, {x:0, y:10}],
12 | [10, 10, 10, 450, {x:10, y:20}],
13 | ])('should return position for angle', (cx, cy, radius, angle, expected) => {
14 | const position = getPointPosition(cx, cy, radius, angle, default_settings)
15 | expect(position.x).toBeCloseTo(expected.x)
16 | expect(position.y).toBeCloseTo(expected.y)
17 | })
18 | });
19 |
20 | describe('isCollision', () => {
21 | test.each([
22 | [{x:10, y:10, r:5}, {x:10, y:10, r:5}, true],
23 | [{x:10, y:10, r:5}, {x:21, y:10, r:5}, false],
24 | [{x:10, y:10, r:5}, {x:10, y:20, r:5}, true],
25 | [{x:10, y:10, r:5}, {x:10, y:21, r:5}, false],
26 | [{x:10, y:10, r:5}, {x:20, y:10, r:10}, true]
27 | ])('should return if circles are in collision', (circle1, circle2, expected) => {
28 | expect(isCollision(circle1, circle2)).toBe(expected)
29 | })
30 | })
31 |
32 | describe('comparePoints', () => {
33 | test.each([
34 | [ 10, 20, -10],
35 | [ 20, 20, 0],
36 | [ 30, 20, 10],
37 | [ 0, 1, -1],
38 | [259.99, 0, 259.99],
39 | ])('should return if circles are in collision', (angle1, angle2, expected) => {
40 | expect(comparePoints({angle: angle1}, {angle: angle2})).toBe(expected)
41 | })
42 | })
43 |
44 | describe('placePointsInCollision', () => {
45 | test('should place correctly planets', () => {
46 | let p1 = {name:'Sun', pointer:1, angle:1, x:1, y:1, r:1};
47 | let p2 = {name:'Mercury', pointer:359, angle:1, x:1, y:1, r:1};
48 |
49 | placePointsInCollision(p1, p2);
50 |
51 | expect(p1).toStrictEqual({name:'Sun', pointer:1, angle:2, x:1, y:1, r:1});
52 | expect(p2).toStrictEqual({name:'Mercury', pointer:359, angle:0, x:1, y:1, r:1});
53 | })
54 |
55 | test('should place correctly planets when passing in other order', () => {
56 | let p1 = {name:'Mercury', pointer:359, angle:1, x:1, y:1, r:1};
57 | let p2 = {name:'Sun', pointer:1, angle:1, x:1, y:1, r:1};
58 |
59 | placePointsInCollision(p1, p2);
60 |
61 | expect(p1).toStrictEqual({name:'Mercury', pointer:359, angle:0, x:1, y:1, r:1});
62 | expect(p2).toStrictEqual({name:'Sun', pointer:1, angle:2, x:1, y:1, r:1});
63 | })
64 |
65 | test('should place correctly planets when in collision', () => {
66 | let p1 = {name:'Sun', pointer:10, angle:10, x:1, y:1, r:1};
67 | let p2 = {name:'Mercury', pointer:20, angle:10, x:1, y:1, r:1};
68 |
69 | placePointsInCollision(p1, p2);
70 |
71 | expect(p1).toStrictEqual({name:'Sun', pointer:10, angle:9, x:1, y:1, r:1});
72 | expect(p2).toStrictEqual({name:'Mercury', pointer:20, angle:11, x:1, y:1, r:1});
73 | })
74 | })
75 |
76 | describe('assemble', () => {
77 | test('assemble', () => {
78 | let locatedPoints: LocatedPoint[] = [];
79 | const universe = {cx:0, cy:0, r:100}
80 | const planetRadius = 10
81 | const settings: Settings = {...default_settings, COLLISION_RADIUS: planetRadius}
82 |
83 | let point: LocatedPoint = {name:"Point1", x:100, y:0, r:planetRadius, angle:180}
84 | locatedPoints = assemble(locatedPoints, point, universe, settings);
85 |
86 | point = {name:"Point2", x:100, y:0, r:planetRadius, angle:180}
87 | locatedPoints = assemble(locatedPoints, point, universe, settings);
88 |
89 | point = {name:"Point3", x:100, y:0, r:planetRadius, angle:180}
90 | locatedPoints = assemble(locatedPoints, point, universe, settings);
91 |
92 | point = {name:"Point4", x:100, y:0, r:planetRadius, angle:180}
93 | locatedPoints = assemble(locatedPoints, point, universe, settings);
94 |
95 | point = {name:"Point5", x:100, y:0, r:planetRadius, angle:180}
96 | locatedPoints = assemble(locatedPoints, point, universe, settings);
97 |
98 | point = {name:"Point6", x:100, y:0, r:planetRadius, angle:180}
99 | locatedPoints = assemble(locatedPoints, point, universe, settings);
100 |
101 | point = {name:"Point7", x:100, y:0, r:planetRadius, angle:180}
102 | locatedPoints = assemble(locatedPoints, point, universe, settings);
103 |
104 | point = {name:"Point8", x:100, y:0, r:planetRadius, angle:180}
105 | locatedPoints = assemble(locatedPoints, point, universe, settings);
106 |
107 | point = {name:"Point9", x:100, y:0, r:planetRadius, angle:180}
108 | locatedPoints = assemble(locatedPoints, point, universe, settings);
109 |
110 | point = {name:"Point10", x:100, y:0, r:planetRadius, angle:180}
111 | locatedPoints = assemble(locatedPoints, point, universe, settings);
112 |
113 | expect( locatedPoints ).toHaveLength( 10 );
114 | })
115 |
116 | test('assemble2', () => {
117 | const universe = {cx:0, cy:0, r:100};
118 | const collisionRadius = 10;
119 | let angle, planetPosition, sun, mercury, venus, result, locatedPoints;
120 |
121 | angle = 0;
122 | planetPosition = getPointPosition(universe.cx, universe.cy, universe.r, angle, default_settings);
123 | sun = {name:"Sun", x:planetPosition.x, y:planetPosition.y, r:collisionRadius, angle:angle};
124 | expect(assemble([], sun, universe, default_settings)).toStrictEqual([sun]);
125 |
126 | angle = 90;
127 | planetPosition = getPointPosition(universe.cx, universe.cy, universe.r, angle, default_settings);
128 | mercury = {name:"Mercury", x:planetPosition.x, y:planetPosition.y, r:collisionRadius, angle:angle};
129 |
130 | locatedPoints = assemble([sun], mercury, universe, default_settings);
131 | expect(locatedPoints[0].angle).toBe(0);
132 | expect(locatedPoints[1].angle).toBe(90);
133 |
134 | angle = 2;
135 | planetPosition = getPointPosition(universe.cx, universe.cy, universe.r, angle, default_settings);
136 | mercury = {name:"Mercury", x:planetPosition.x, y:planetPosition.y, r:collisionRadius, angle:angle};
137 |
138 | locatedPoints = assemble([sun], mercury, universe, default_settings);
139 | expect(locatedPoints[0].angle).toBe(7);
140 | expect(locatedPoints[1].angle).toBe(355);
141 | expect(locatedPoints).toHaveLength(2);
142 |
143 | angle = 1;
144 | planetPosition = getPointPosition(universe.cx, universe.cy, universe.r, angle, default_settings);
145 | venus = {name:"Venus", x:planetPosition.x, y:planetPosition.y, r:collisionRadius, angle:angle};
146 |
147 | locatedPoints = assemble([sun, mercury], venus, universe, default_settings);
148 |
149 | expect( locatedPoints[0].name ).toBe("Venus");
150 | expect( locatedPoints[0].angle ).toBe(1);
151 |
152 | expect( locatedPoints[1].name ).toBe("Mercury");
153 | expect( locatedPoints[1].angle ).toBe(13);
154 |
155 | expect( locatedPoints[2].name ).toBe("Sun");
156 | expect( locatedPoints[2].angle ).toBe(349);
157 |
158 | expect(locatedPoints).toHaveLength(3);
159 | })
160 |
161 | test('assemble should not change planet position when planet are in collision', () => {
162 | const shift = 344.07396032340205;
163 | const universe = {cx:300, cy:300, r:185.125};
164 | const collisionRadius = 10;
165 | let angle, planetPosition, sun, mercury, venus, result
166 | let locatedPoints: LocatedPoint[] = [];
167 | const planets: Points = {"Sun": [264.7071707108],"Mercury": [283.248841516],"Saturn": [283.8137792332],"Uranus": [274.8367919812],"Neptune": [281.4462451253]}
168 |
169 | for(var planet in planets){
170 | planetPosition = getPointPosition(universe.cx, universe.cy, universe.r, planets[planet][0]+shift, default_settings);
171 | const planetObject = {name:planet, x:planetPosition.x, y:planetPosition.y, r:collisionRadius, angle:planets[planet][0]+shift,pointer:planets[planet][0]+shift};
172 |
173 | locatedPoints = assemble(locatedPoints, planetObject, universe, default_settings);
174 | }
175 |
176 | var expectedPlanetOrder = ["Sun", "Uranus", "Neptune", "Mercury", "Saturn"];
177 |
178 | expect(locatedPoints.map(lp => lp.name)).toStrictEqual(expectedPlanetOrder);
179 | })
180 | })
--------------------------------------------------------------------------------
/project/src/utils.ts:
--------------------------------------------------------------------------------
1 | import type { AstroData, LocatedPoint } from './radix'
2 | import type { Settings } from './settings'
3 |
4 | /**
5 | * Calculate position of the point on the circle.
6 | *
7 | * @param {int} cx - center x
8 | * @param {int} cy - center y
9 | * @param {int} radius
10 | * @param {double} angle - degree
11 | *
12 | * @return {{x: number, y: number}} Obj - {x:10, y:20}
13 | */
14 | export const getPointPosition = (cx: number, cy: number, radius: number, angle: number, astrology: { SHIFT_IN_DEGREES: number }): { x: number; y: number } => {
15 | const angleInRadius = (astrology.SHIFT_IN_DEGREES - angle) * Math.PI / 180
16 | const xPos = cx + radius * Math.cos(angleInRadius)
17 | const yPos = cy + radius * Math.sin(angleInRadius)
18 | return { x: xPos, y: yPos }
19 | }
20 |
21 | export const degreeToRadians = (degrees: number): number => degrees * Math.PI / 180
22 |
23 | export const radiansToDegree = (radians: number): number => radians * 180 / Math.PI
24 |
25 | interface TextLocation { text: string; x: number; y: number }
26 |
27 | /**
28 | * Calculates positions of the point description
29 | *
30 | * @param {Object} point
31 | * @param {Array} texts
32 | *
33 | * @return {Array} [{text:"abc", x:123, y:456}, {text:"cvb", x:456, y:852}, ...]
34 | */
35 | export const getDescriptionPosition = function (point: { x: number; y: number }, texts: string[], astrology: { COLLISION_RADIUS: number; SYMBOL_SCALE: number }): TextLocation[] {
36 | const RATION = 1.4
37 | const result: TextLocation[] = []
38 | const posX = point.x + (astrology.COLLISION_RADIUS / RATION * astrology.SYMBOL_SCALE)
39 | const posY = point.y - (astrology.COLLISION_RADIUS * astrology.SYMBOL_SCALE)
40 |
41 | texts.forEach((text, idx) => {
42 | result.push({ text, x: posX, y: posY + (astrology.COLLISION_RADIUS / RATION * astrology.SYMBOL_SCALE * idx) })
43 | }, this)
44 |
45 | return result
46 | }
47 |
48 | /**
49 | * Checks a source data
50 | * @private
51 | *
52 | * @param {Object} data
53 | * @return {{hasError: boolean, messages: string[]}} status
54 | */
55 | export const validate = (data: AstroData): { hasError: boolean; messages: string[] } => {
56 | const status = { hasError: false, messages: [] as string[] }
57 |
58 | if (data == null) {
59 | status.messages.push('Data is not set.')
60 | status.hasError = true
61 | return status
62 | }
63 |
64 | if (data.planets == null) {
65 | status.messages.push('There is not property \'planets\'.')
66 | status.hasError = true
67 | }
68 |
69 | for (const property in data.planets) {
70 | if (data.planets.hasOwnProperty(property)) {
71 | if (!Array.isArray(data.planets[property])) {
72 | status.messages.push('The planets property \'' + property + '\' has to be Array.')
73 | status.hasError = true
74 | }
75 | }
76 | }
77 |
78 | if (data.cusps != null && !Array.isArray(data.cusps)) {
79 | status.messages.push('Property \'cusps\' has to be Array.')
80 | status.hasError = true
81 | }
82 |
83 | if (data.cusps != null && data.cusps.length !== 12) {
84 | status.messages.push('Count of \'cusps\' values has to be 12.')
85 | status.hasError = true
86 | }
87 |
88 | return status
89 | }
90 |
91 | /**
92 | * Get empty DOMElement with ID
93 | *
94 | * @param{String} elementID
95 | * @param{DOMElement} parent
96 | * @return {DOMElement}
97 | */
98 | export const getEmptyWrapper = (parent: Element, elementID: string, _paperElementId: string): Element => {
99 | const element = document.getElementById(elementID)
100 | if (element != null) {
101 | removeChilds(element)
102 | return element
103 | }
104 | const paper = document.getElementById(_paperElementId)
105 | if (paper == null) throw new Error('Paper element should exist')
106 |
107 | const wrapper = document.createElementNS(paper.namespaceURI, 'g')
108 | wrapper.setAttribute('id', elementID)
109 | parent.appendChild(wrapper)
110 |
111 | return wrapper
112 | }
113 |
114 | /**
115 | * Remove childs
116 | *
117 | * @param{DOMElement} parent
118 | */
119 | export const removeChilds = (parent: HTMLElement): void => {
120 | if (parent == null) {
121 | return
122 | }
123 |
124 | let last
125 | while ((last = parent.lastChild) != null) {
126 | parent.removeChild(last)
127 | }
128 | }
129 |
130 | /**
131 | * Check circle collision between two objects
132 | *
133 | * @param {Object} circle1, {x:123, y:123, r:50}
134 | * @param {Object} circle2, {x:456, y:456, r:60}
135 | * @return {boolean}
136 | */
137 | export const isCollision = (circle1: { x: number; y: number; r: number }, circle2: { x: number; y: number; r: number }): boolean => {
138 | // Calculate the vector between the circles’ center points
139 | const vx = circle1.x - circle2.x
140 | const vy = circle1.y - circle2.y
141 |
142 | const magnitude = Math.sqrt(vx * vx + vy * vy)
143 |
144 | // circle.radius has been set to astrology.COLLISION_RADIUS;
145 | const totalRadii = circle1.r + circle2.r
146 |
147 | return magnitude <= totalRadii
148 | }
149 |
150 | /**
151 | * Places a new point in the located list
152 | *
153 | * @param {Array} locatedPoints, [{name:"Mars", x:123, y:123, r:50, ephemeris:45.96}, {name:"Sun", x:1234, y:1234, r:50, ephemeris:100.96}]
154 | * @param {Object} point, {name:"Venus", x:78, y:56, r:50, angle:15.96}
155 | * @param {Object} universe - current universe
156 | * @return {Array} locatedPoints
157 | */
158 | export const assemble = (locatedPoints: LocatedPoint[], point: LocatedPoint, universe: { cx: number; cy: number; r: number }, astrology: Settings): LocatedPoint[] => {
159 | // first item
160 | if (locatedPoints.length === 0) {
161 | locatedPoints.push(point)
162 | return locatedPoints
163 | }
164 |
165 | if ((2 * Math.PI * universe.r) - (2 * (astrology.COLLISION_RADIUS * astrology.SYMBOL_SCALE) * (locatedPoints.length + 2)) <= 0) {
166 | if (astrology.DEBUG) console.log('Universe circumference: ' + (2 * Math.PI * universe.r) + ', Planets circumference: ' + (2 * (astrology.COLLISION_RADIUS * astrology.SYMBOL_SCALE) * (locatedPoints.length + 2)))
167 | throw new Error('Unresolved planet collision. Try change SYMBOL_SCALE or paper size.')
168 | }
169 |
170 | let hasCollision = false
171 | let locatedButInCollisionPoint
172 | locatedPoints.sort(comparePoints)
173 | for (let i = 0, ln = locatedPoints.length; i < ln; i++) {
174 | if (isCollision(locatedPoints[i], point)) {
175 | hasCollision = true
176 | locatedButInCollisionPoint = locatedPoints[i]
177 | locatedButInCollisionPoint.index = i
178 |
179 | if (astrology.DEBUG) console.log('Resolve collision: ' + locatedButInCollisionPoint.name + ' X ' + point.name)
180 |
181 | break
182 | }
183 | }
184 |
185 | if (hasCollision && locatedButInCollisionPoint != null && locatedButInCollisionPoint.index != null) {
186 | placePointsInCollision(locatedButInCollisionPoint, point)
187 |
188 | let newPointPosition = getPointPosition(universe.cx, universe.cy, universe.r, locatedButInCollisionPoint.angle, astrology)
189 | locatedButInCollisionPoint.x = newPointPosition.x
190 | locatedButInCollisionPoint.y = newPointPosition.y
191 |
192 | newPointPosition = getPointPosition(universe.cx, universe.cy, universe.r, point.angle, astrology)
193 | point.x = newPointPosition.x
194 | point.y = newPointPosition.y
195 |
196 | // remove locatedButInCollisionPoint from locatedPoints
197 | locatedPoints.splice(locatedButInCollisionPoint.index, 1)
198 |
199 | // call recursive
200 | locatedPoints = assemble(locatedPoints, locatedButInCollisionPoint, universe, astrology)
201 | locatedPoints = assemble(locatedPoints, point, universe, astrology)
202 | } else {
203 | locatedPoints.push(point)
204 | }
205 | locatedPoints.sort(comparePoints)
206 | return locatedPoints
207 | }
208 |
209 | /**
210 | * Sets the positions of two points that are in collision.
211 | *
212 | * @param {Object} p1, {..., pointer:123, angle:456}
213 | * @param {Object} p2, {..., pointer:23, angle:56}
214 | */
215 | export const placePointsInCollision = (p1: LocatedPoint, p2: LocatedPoint): void => {
216 | const step = 1
217 | let adjustedP1Pointer = p1.pointer === undefined ? p1.angle : p1.pointer
218 | let adjustedP2Pointer = p2.pointer === undefined ? p2.angle : p2.pointer
219 |
220 | // solving problems with zero crossing
221 | if (Math.abs(adjustedP1Pointer - adjustedP2Pointer) > 180) {
222 | adjustedP1Pointer = (adjustedP1Pointer + 180) % 360
223 | adjustedP2Pointer = (adjustedP2Pointer + 180) % 360
224 | }
225 |
226 | if (adjustedP1Pointer <= adjustedP2Pointer) {
227 | p1.angle = p1.angle - step
228 | p2.angle = p2.angle + step
229 | } else if (adjustedP1Pointer >= adjustedP2Pointer) {
230 | p1.angle = p1.angle + step
231 | p2.angle = p2.angle - step
232 | }
233 |
234 | p1.angle = (p1.angle + 360) % 360
235 | p2.angle = (p2.angle + 360) % 360
236 | }
237 |
238 | /**
239 | * Check collision between angle and object
240 | *
241 | * @param {double} angle
242 | * @param {Array} points, [{x:456, y:456, r:60, angle:123}, ...]
243 | * @return {boolean}
244 | */
245 | export const isInCollision = (angle: number, points: string | any[], astrology: Settings): boolean => {
246 | const deg360 = radiansToDegree(2 * Math.PI)
247 | const collisionRadius = (astrology.COLLISION_RADIUS * astrology.SYMBOL_SCALE) / 2
248 |
249 | let result = false
250 | for (let i = 0, ln = points.length; i < ln; i++) {
251 | if (Math.abs(points[i].angle - angle) <= collisionRadius ||
252 | (deg360 - Math.abs(points[i].angle - angle)) <= collisionRadius) {
253 | result = true
254 | break
255 | }
256 | }
257 |
258 | return result
259 | }
260 |
261 | interface InitialEndPosition {
262 | startX: number
263 | startY: number
264 | endX: number
265 | endY: number
266 | }
267 |
268 | /**
269 | * Calculates positions of the dashed line passing through the obstacle.
270 | * *
271 | * @param {double} centerX
272 | * @param {double} centerY
273 | * @param {double} angle - line angle
274 | * @param {double} lineStartRadius
275 | * @param {double} lineEndRadius
276 | * @param {double} obstacleRadius
277 | * @param {Array} obstacles, [{x:456, y:456, r:60, angle:123}, ...]
278 | *
279 | * @return {Array} [{startX:1, startY:1, endX:4, endY:4}, {startX:6, startY:6, endX:8, endY:8}]
280 | */
281 | export const getDashedLinesPositions = (centerX: number, centerY: number, angle: number, lineStartRadius: number, lineEndRadius: number, obstacleRadius: number, obstacles: LocatedPoint[], astrology: Settings): InitialEndPosition[] => {
282 | let startPos
283 | let endPos
284 | const result = []
285 |
286 | if (isInCollision(angle, obstacles, astrology)) {
287 | startPos = getPointPosition(centerX, centerY, lineStartRadius, angle, astrology)
288 | endPos = getPointPosition(centerX, centerY, obstacleRadius - (astrology.COLLISION_RADIUS * astrology.SYMBOL_SCALE), angle, astrology)
289 | result.push({ startX: startPos.x, startY: startPos.y, endX: endPos.x, endY: endPos.y })
290 |
291 | // the second part of the line when is space
292 | if ((obstacleRadius + 2 * (astrology.COLLISION_RADIUS * astrology.SYMBOL_SCALE)) < lineEndRadius) {
293 | startPos = getPointPosition(centerX, centerY, obstacleRadius + 2 * (astrology.COLLISION_RADIUS * astrology.SYMBOL_SCALE), angle, astrology)
294 | endPos = getPointPosition(centerX, centerY, lineEndRadius, angle, astrology)
295 | result.push({ startX: startPos.x, startY: startPos.y, endX: endPos.x, endY: endPos.y })
296 | }
297 | } else {
298 | startPos = getPointPosition(centerX, centerY, lineStartRadius, angle, astrology)
299 | endPos = getPointPosition(centerX, centerY, lineEndRadius, angle, astrology)
300 | result.push({ startX: startPos.x, startY: startPos.y, endX: endPos.x, endY: endPos.y })
301 | }
302 |
303 | return result
304 | }
305 |
306 | /**
307 | * Calculate ruler positions.
308 | *
309 | * @param {Double} centerX
310 | * @param {Double} centerY
311 | * @param {Double} startRadius
312 | * @param {Double} endRadius
313 | * @param {Boolean} startAngle
314 | *
315 | * @return {Array} [ {startX:1,startY:2, endX:3, endX:4 }, ...]
316 | */
317 | export const getRulerPositions = (centerX: number, centerY: number, startRadius: number, endRadius: number, startAngle: number, astrology: { SHIFT_IN_DEGREES: number }): InitialEndPosition[] => {
318 | const result = []
319 |
320 | const rayRadius = endRadius
321 | const halfRayRadius = (startRadius <= endRadius) ? rayRadius - (Math.abs(endRadius - startRadius) / 2) : rayRadius + (Math.abs(endRadius - startRadius) / 2)
322 |
323 | for (let i = 0, start = 0, step = 5; i < 72; i++) {
324 | const angle = start + startAngle
325 | const startPos = getPointPosition(centerX, centerY, startRadius, angle, astrology)
326 | const endPos = getPointPosition(centerX, centerY, (i % 2 === 0 ? rayRadius : halfRayRadius), angle, astrology)
327 | result.push({ startX: startPos.x, startY: startPos.y, endX: endPos.x, endY: endPos.y })
328 |
329 | start += step
330 | }
331 |
332 | return result
333 | }
334 |
335 | /**
336 | * Compare two points
337 | *
338 | * @param {Object} pointA, {name:"Venus", x:78, y:56, r:50, angle:15.96}
339 | * @param {Object} pointB, {name:"Mercury", x:78, y:56, r:50, angle:20.26}
340 | * @return
341 | */
342 | export const comparePoints = (pointA: { angle: number }, pointB: { angle: number }): number => {
343 | return pointA.angle - pointB.angle
344 | }
345 |
--------------------------------------------------------------------------------
/project/src/zodiac.test.ts:
--------------------------------------------------------------------------------
1 | import default_settings from "./settings";
2 | import Zodiac from "./zodiac";
3 |
4 | describe('getSign', () => {
5 | const cusps = [296, 350, 30, 56, 75, 94, 116, 170, 210, 236, 255, 274];
6 | var reporter = new Zodiac( cusps );
7 | test.each([
8 | [0, 1],
9 | [30, 2],
10 | [270, 10],
11 | [359, 12],
12 | [361, 1],
13 | ])('should return correct sign', (point, expected) => {
14 | expect( reporter.getSign( point ) ).toBe( expected );
15 | })
16 | })
17 |
18 | test.each([
19 | [-1, true],
20 | [1, false],
21 | ])('should return retrograde if speed is negative', (speed, expected) => {
22 | const cusps = [296, 350, 30, 56, 75, 94, 116, 170, 210, 236, 255, 274];
23 | const zodiac = new Zodiac( cusps );
24 |
25 | expect(zodiac.isRetrograde(speed)).toBe(expected);
26 | })
27 |
28 | test.each([
29 | [274, 12],
30 | [296, 12],
31 | [296.1, 1],
32 | [350, 2],
33 | [359, 2],
34 | [0, 2],
35 | [361, 2],
36 | [29.9, 2],
37 | [30, 3],
38 | ])('getHouseNumber [1] %i', (house, expected) => {
39 | const cusps = [296.1, 350, 30, 56, 75, 94, 116, 170, 210, 236, 255, 274];
40 | const zodiac = new Zodiac( cusps );
41 |
42 | expect(zodiac.getHouseNumber(house)).toBe(expected);
43 | })
44 |
45 | test.each([
46 | [248.58, 12],
47 | [265, 12],
48 | [265.7, 1],
49 | [307.65, 2],
50 | [353.4, 3],
51 | [0, 3],
52 | [361, 3],
53 | [26.87, 4],
54 | ])('getHouseNumber [2] %i', (house, expected) => {
55 | const cusps = [265.6850555442075,307.6441825689919,353.38796689506074,26.86890880306794,50.191811553503044,68.57049261566578,85.6850555442075,127.64418256899188,173.3879668950608,206.8689088030679,230.19181155350307,248.5704926156658];
56 | const zodiac = new Zodiac( cusps );
57 |
58 | expect(zodiac.getHouseNumber(house)).toBe(expected);
59 | })
60 |
61 | test.each([
62 | [266.1234, "266° 7' 24"],
63 | [0.1234, "0° 7' 24"],
64 | [360.1234, "360° 7' 24"],
65 | [266.3251184363515, "266° 19' 30"],
66 | ])('toDMS %i', (angle, expected) => {
67 | const cusps = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330];
68 | const zodiac = new Zodiac( cusps );
69 |
70 | expect(zodiac.toDMS(angle)).toBe(expected);
71 | })
72 |
73 | describe('dignities', () => {
74 | const cusps = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330];
75 | const reporter = new Zodiac( cusps );
76 | test('Sun', () => {
77 | expect( reporter.getDignities( {name:"Sun", position:120} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
78 | expect( reporter.getDignities( {name:"Sun", position:300} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
79 | expect( reporter.getDignities( {name:"Sun", position:150} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
80 | expect( reporter.getDignities( {name:"Sun", position:18} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
81 | expect( reporter.getDignities( {name:"Sun", position:0}, [{"name":"Sun", "position":0, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
82 | })
83 |
84 | test('Moon', () => {
85 | expect( reporter.getDignities( {name:"Moon", position:90} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
86 | expect( reporter.getDignities( {name:"Moon", position:270} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
87 | expect( reporter.getDignities( {name:"Moon", position:210} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
88 | expect( reporter.getDignities( {name:"Moon", position:30} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
89 | expect( reporter.getDignities( {name:"Moon", position:32}, [{"name":"Moon", "position":33, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
90 | })
91 |
92 | test('Mercury', () => {
93 | expect( reporter.getDignities( {name:"Mercury", position:60} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
94 | expect( reporter.getDignities( {name:"Mercury", position:240} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
95 | expect( reporter.getDignities( {name:"Mercury", position:330} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
96 | expect( reporter.getDignities( {name:"Mercury", position:150} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
97 | expect( reporter.getDignities( {name:"Mercury", position:156}, [{"name":"Mercury", "position":155, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
98 | })
99 |
100 | test('Venus', () => {
101 | expect( reporter.getDignities( {name:"Venus", position:30} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
102 | expect( reporter.getDignities( {name:"Venus", position:180} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
103 |
104 | expect( reporter.getDignities( {name:"Venus", position:0} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
105 | expect( reporter.getDignities( {name:"Venus", position:210} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
106 |
107 | expect( reporter.getDignities( {name:"Venus", position:150} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
108 |
109 | expect( reporter.getDignities( {name:"Venus", position:330} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
110 | expect( reporter.getDignities( {name:"Venus", position:357.987}, [{"name":"Venus", "position":357, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
111 | })
112 |
113 | test('Mars', () => {
114 |
115 | expect( reporter.getDignities( {name:"Mars", position:0} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
116 | expect( reporter.getDignities( {name:"Mars", position:210} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
117 |
118 | expect( reporter.getDignities( {name:"Mars", position:30} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
119 | expect( reporter.getDignities( {name:"Mars", position:180} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
120 |
121 | expect( reporter.getDignities( {name:"Mars", position:90} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
122 |
123 | expect( reporter.getDignities( {name:"Mars", position:270} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
124 | expect( reporter.getDignities( {name:"Mars", position:298}, [{"name":"Mars", "position":298, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
125 | })
126 |
127 | test('Jupiter', () => {
128 | expect( reporter.getDignities( {name:"Jupiter", position:240} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
129 | expect( reporter.getDignities( {name:"Jupiter", position:330} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
130 |
131 | expect( reporter.getDignities( {name:"Jupiter", position:60} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
132 | expect( reporter.getDignities( {name:"Jupiter", position:150} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
133 |
134 | expect( reporter.getDignities( {name:"Jupiter", position:270} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
135 |
136 | expect( reporter.getDignities( {name:"Jupiter", position:90} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
137 | expect( reporter.getDignities( {name:"Jupiter", position:105}, [{"name":"Jupiter", "position":105, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
138 | })
139 |
140 | test('Saturn', () => {
141 | expect( reporter.getDignities( {name:"Saturn", position:300} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
142 |
143 | expect( reporter.getDignities( {name:"Saturn", position:90} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
144 | expect( reporter.getDignities( {name:"Saturn", position:120} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
145 |
146 | expect( reporter.getDignities( {name:"Saturn", position:0} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
147 |
148 | expect( reporter.getDignities( {name:"Saturn", position:180} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
149 | expect( reporter.getDignities( {name:"Saturn", position:201}, [{"name":"Saturn", "position":201, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
150 | })
151 |
152 | test('Uranus', () => {
153 | expect( reporter.getDignities( {name:"Uranus", position:300} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
154 | expect( reporter.getDignities( {name:"Uranus", position:120} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
155 | expect( reporter.getDignities( {name:"Uranus", position:30} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
156 | expect( reporter.getDignities( {name:"Uranus", position:210} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
157 | expect( reporter.getDignities( {name:"Uranus", position:218}, [{"name":"Uranus", "position":218, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
158 | })
159 |
160 | test('Neptune', () => {
161 | expect( reporter.getDignities( {name:"Neptune", position:330} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
162 | expect( reporter.getDignities( {name:"Neptune", position:150} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
163 | expect( reporter.getDignities( {name:"Neptune", position:60} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
164 | expect( reporter.getDignities( {name:"Neptune", position:300} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
165 |
166 | expect( reporter.getDignities( {name:"Neptune", position:120} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
167 | expect( reporter.getDignities( {name:"Neptune", position:240} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
168 | expect( reporter.getDignities( {name:"Neptune", position:241}, [{"name":"Neptune", "position":241, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
169 | })
170 |
171 | test('Pluto', () => {
172 | expect( reporter.getDignities( {name:"Pluto", position:210} )).toStrictEqual([default_settings.DIGNITIES_RULERSHIP] );
173 | expect( reporter.getDignities( {name:"Pluto", position:30} )).toStrictEqual([default_settings.DIGNITIES_DETRIMENT] );
174 | expect( reporter.getDignities( {name:"Pluto", position:180} )).toStrictEqual([default_settings.DIGNITIES_FALL] );
175 | expect( reporter.getDignities( {name:"Pluto", position:0} )).toStrictEqual([default_settings.DIGNITIES_EXALTATION] );
176 | expect( reporter.getDignities( {name:"Pluto", position:18}, [{"name":"Pluto", "position":18, "orbit":2}] )).toStrictEqual([default_settings.DIGNITIES_EXALTATION, default_settings.DIGNITIES_EXACT_EXALTATION] );
177 | })
178 | })
179 |
--------------------------------------------------------------------------------
/project/src/zodiac.ts:
--------------------------------------------------------------------------------
1 | import default_settings from './settings'
2 | import type { Dignity, Settings } from './settings'
3 | import { radiansToDegree } from './utils'
4 | // Zodiac
5 | const SIGNS_ARIES = 1
6 | const SIGNS_TAURUS = 2
7 | const SIGNS_GEMINI = 3
8 | const SIGNS_CANCER = 4
9 | const SIGNS_LEO = 5
10 | const SIGNS_VIRGO = 6
11 | const SIGNS_LIBRA = 7
12 | const SIGNS_SCORPIO = 8
13 | const SIGNS_SAGITTARIUS = 9
14 | const SIGNS_CAPRICORN = 10
15 | const SIGNS_AQUARIUS = 11
16 | const SIGNS_PISCES = 12
17 |
18 | /**
19 | * Zodiac
20 | *
21 | * Gives the position of points in the zodiac.
22 | * Position of point in the zodiac.
23 | * Position of point in houses.
24 | * Dignities of planets.
25 | *
26 | * @class
27 | * @public
28 | * @constructor
29 | * @param {Array} cusps - cusprs in zodiac; [296, 350, 30, 56, 75, 94, 116, 170, 210, 236, 255, 274]
30 | * @param {Object | null } settings
31 | */
32 | class Zodiac {
33 | cusps: number[]
34 | settings: Settings
35 | constructor (cusps: number[], settings?: Settings) {
36 | if (cusps === null) {
37 | throw new Error('Param \'cusps\' must not be empty.')
38 | }
39 |
40 | if (!(Array.isArray(cusps) && cusps.length === 12)) {
41 | throw new Error('Param \'cusps\' is not 12 length Array.')
42 | }
43 |
44 | this.cusps = cusps
45 | this.settings = settings ?? default_settings
46 | }
47 |
48 | /**
49 | * Get astrological sign
50 | * 1 - Arise, ... , 12 - Pisces
51 | *
52 | * @param {double} point - angle of point in circle
53 | * @return { \[1-9] | 1[0-2]\ }
54 | */
55 | getSign (point: number): number {
56 | const angle = point % radiansToDegree(2 * Math.PI)
57 | return Math.floor((angle / 30) + 1)
58 | }
59 |
60 | /**
61 | * Is retrograde
62 | *
63 | * @param {double} speed
64 | * @return {boolean}
65 | */
66 | isRetrograde (speed: number): boolean {
67 | return speed < 0
68 | }
69 |
70 | /**
71 | * Get house number
72 | * 1 - 12
73 | *
74 | * @param {double} point - angle of point in circle
75 | * @return { \[1-9] | 1[0-2]\ }
76 | */
77 | getHouseNumber (point: number): number {
78 | const angle = point % radiansToDegree(2 * Math.PI)
79 |
80 | for (let i = 0, ln = this.cusps.length; i < ln; i++) {
81 | if (angle >= this.cusps[i] && angle < this.cusps[(i % (ln - 1)) + 1]) {
82 | return i + 1
83 | }
84 | }
85 |
86 | // cusp passes over zero
87 | for (let i = 0, ln = this.cusps.length; i < ln; i++) {
88 | if (this.cusps[i] > this.cusps[(i % (ln - 1)) + 1]) {
89 | return i + 1
90 | }
91 | }
92 |
93 | throw new Error('Oops, serious error in the method: \'astrology.Zodiac.getHouseNumber\'.')
94 | }
95 |
96 | /**
97 | * Calculate dignities of planet
98 | *
99 | * r - Rulership
100 | * d - Detriment
101 | * e - Exaltation
102 | * E - Exalatation - Exact exaltation
103 | * f - Fall
104 | *
105 | * @param {Object} planet, { name:"Sun", position:60.2 }
106 | * @param {Array | null } exactExaltation - list of named angles, [{ name:"Sun", position:278, orbit:2 }, { name:"Moon", position:3, , orbit:2 }]
107 | * @return {Array}
108 | */
109 | getDignities (planet: { name: string; position: number }, exactExaltation?: Dignity[]): string[] {
110 | if (!(planet && planet.name && planet.position != null)) {
111 | return []
112 | }
113 |
114 | const result = []
115 | const sign = this.getSign(planet.position)
116 |
117 | const position = planet.position % radiansToDegree(2 * Math.PI)
118 |
119 | switch (planet.name) {
120 | case this.settings.SYMBOL_SUN:
121 |
122 | if (sign === SIGNS_LEO) {
123 | result.push(this.settings.DIGNITIES_RULERSHIP)
124 | } else if (sign === SIGNS_AQUARIUS) {
125 | result.push(this.settings.DIGNITIES_DETRIMENT)
126 | }
127 |
128 | if (sign === SIGNS_ARIES) {
129 | result.push(this.settings.DIGNITIES_EXALTATION)
130 | } else if (sign === SIGNS_VIRGO) {
131 | result.push(this.settings.DIGNITIES_FALL)
132 | }
133 |
134 | break
135 |
136 | case this.settings.SYMBOL_MOON:
137 |
138 | if (sign === SIGNS_CANCER) {
139 | result.push(this.settings.DIGNITIES_RULERSHIP)
140 | } else if (sign === SIGNS_CAPRICORN) {
141 | result.push(this.settings.DIGNITIES_DETRIMENT)
142 | }
143 |
144 | if (sign === SIGNS_TAURUS) {
145 | result.push(this.settings.DIGNITIES_EXALTATION)
146 | } else if (sign === SIGNS_SCORPIO) {
147 | result.push(this.settings.DIGNITIES_FALL)
148 | }
149 |
150 | break
151 |
152 | case this.settings.SYMBOL_MERCURY:
153 |
154 | if (sign === SIGNS_GEMINI) {
155 | result.push(this.settings.DIGNITIES_RULERSHIP)
156 | } else if (sign === SIGNS_SAGITTARIUS) {
157 | result.push(this.settings.DIGNITIES_DETRIMENT)
158 | }
159 |
160 | if (sign === SIGNS_VIRGO) {
161 | result.push(this.settings.DIGNITIES_EXALTATION)
162 | } else if (sign === SIGNS_PISCES) {
163 | result.push(this.settings.DIGNITIES_FALL)
164 | }
165 |
166 | break
167 |
168 | case this.settings.SYMBOL_VENUS:
169 |
170 | if (sign === SIGNS_TAURUS || sign === SIGNS_LIBRA) {
171 | result.push(this.settings.DIGNITIES_RULERSHIP)
172 | } else if (sign === SIGNS_ARIES || sign === SIGNS_SCORPIO) {
173 | result.push(this.settings.DIGNITIES_DETRIMENT)
174 | }
175 |
176 | if (sign === SIGNS_PISCES) {
177 | result.push(this.settings.DIGNITIES_EXALTATION)
178 | } else if (sign === SIGNS_VIRGO) {
179 | result.push(this.settings.DIGNITIES_FALL)
180 | }
181 |
182 | break
183 |
184 | case this.settings.SYMBOL_MARS:
185 |
186 | if (sign === SIGNS_ARIES || sign === SIGNS_SCORPIO) {
187 | result.push(this.settings.DIGNITIES_RULERSHIP)
188 | } else if (sign === SIGNS_TAURUS || sign === SIGNS_LIBRA) {
189 | result.push(this.settings.DIGNITIES_DETRIMENT)
190 | }
191 |
192 | if (sign === SIGNS_CAPRICORN) {
193 | result.push(this.settings.DIGNITIES_EXALTATION)
194 | } else if (sign === SIGNS_CANCER) {
195 | result.push(this.settings.DIGNITIES_FALL)
196 | }
197 |
198 | break
199 |
200 | case this.settings.SYMBOL_JUPITER:
201 |
202 | if (sign === SIGNS_SAGITTARIUS || sign === SIGNS_PISCES) {
203 | result.push(this.settings.DIGNITIES_RULERSHIP)
204 | } else if (sign === SIGNS_GEMINI || sign === SIGNS_VIRGO) {
205 | result.push(this.settings.DIGNITIES_DETRIMENT)
206 | }
207 |
208 | if (sign === SIGNS_CANCER) {
209 | result.push(this.settings.DIGNITIES_EXALTATION)
210 | } else if (sign === SIGNS_CAPRICORN) {
211 | result.push(this.settings.DIGNITIES_FALL)
212 | }
213 |
214 | break
215 |
216 | case this.settings.SYMBOL_SATURN:
217 |
218 | if (sign === SIGNS_CAPRICORN || sign === SIGNS_AQUARIUS) {
219 | result.push(this.settings.DIGNITIES_RULERSHIP)
220 | } else if (sign === SIGNS_CANCER || sign === SIGNS_LEO) {
221 | result.push(this.settings.DIGNITIES_DETRIMENT)
222 | }
223 |
224 | if (sign === SIGNS_LIBRA) {
225 | result.push(this.settings.DIGNITIES_EXALTATION)
226 | } else if (sign === SIGNS_ARIES) {
227 | result.push(this.settings.DIGNITIES_FALL)
228 | }
229 |
230 | break
231 |
232 | case this.settings.SYMBOL_URANUS:
233 |
234 | if (sign === SIGNS_AQUARIUS) {
235 | result.push(this.settings.DIGNITIES_RULERSHIP)
236 | } else if (sign === SIGNS_LEO) {
237 | result.push(this.settings.DIGNITIES_DETRIMENT)
238 | }
239 |
240 | if (sign === SIGNS_SCORPIO) {
241 | result.push(this.settings.DIGNITIES_EXALTATION)
242 | } else if (sign === SIGNS_TAURUS) {
243 | result.push(this.settings.DIGNITIES_FALL)
244 | }
245 |
246 | break
247 |
248 | case this.settings.SYMBOL_NEPTUNE:
249 |
250 | if (sign === SIGNS_PISCES) {
251 | result.push(this.settings.DIGNITIES_RULERSHIP)
252 | } else if (sign === SIGNS_VIRGO) {
253 | result.push(this.settings.DIGNITIES_DETRIMENT)
254 | }
255 |
256 | if (sign === SIGNS_LEO || sign === SIGNS_SAGITTARIUS) {
257 | result.push(this.settings.DIGNITIES_EXALTATION)
258 | } else if (sign === SIGNS_AQUARIUS || sign === SIGNS_GEMINI) {
259 | result.push(this.settings.DIGNITIES_FALL)
260 | }
261 |
262 | break
263 |
264 | case this.settings.SYMBOL_PLUTO:
265 |
266 | if (sign === SIGNS_SCORPIO) {
267 | result.push(this.settings.DIGNITIES_RULERSHIP)
268 | } else if (sign === SIGNS_TAURUS) {
269 | result.push(this.settings.DIGNITIES_DETRIMENT)
270 | }
271 |
272 | if (sign === SIGNS_ARIES) {
273 | result.push(this.settings.DIGNITIES_EXALTATION)
274 | } else if (sign === SIGNS_LIBRA) {
275 | result.push(this.settings.DIGNITIES_FALL)
276 | }
277 | break
278 | default:
279 | break
280 | }
281 |
282 | if (exactExaltation != null && Array.isArray(exactExaltation)) {
283 | for (let i = 0, ln = exactExaltation.length; i < ln; i++) {
284 | if (planet.name === exactExaltation[i].name) {
285 | if (this.hasConjunction(planet.position, exactExaltation[i].position, exactExaltation[i].orbit)) {
286 | result.push(this.settings.DIGNITIES_EXACT_EXALTATION)
287 | }
288 | }
289 | }
290 | }
291 |
292 | return result
293 | }
294 |
295 | /*
296 | * To hours:minutes:seconds
297 | * @param {Double} d
298 | * @return {String}
299 | */
300 | toDMS (d: number): string {
301 | d += 0.5 / 3600.0 / 10000.0 // round to 1/1000 of a second
302 | const deg = parseInt(d.toString(), 10)
303 | d = (d - deg) * 60
304 | const min = parseInt(d.toString(), 10)
305 | const sec = parseInt(((d - min) * 60).toString(), 10)
306 | return deg + '° ' + min + '\' ' + sec
307 | }
308 |
309 | /*
310 | * Has conjunction with point
311 | *
312 | * @private
313 | *
314 | * @param {Double} planetPosition
315 | * @param {Double} poitPosition
316 | * @param {Integer} orbit
317 | * @return {boolean}
318 | */
319 | hasConjunction (planetPosition: number, pointPosition: number, orbit: number): boolean {
320 | let result = false
321 |
322 | const minOrbit = (pointPosition - orbit / 2) < 0
323 | ? radiansToDegree(2 * Math.PI) - (pointPosition - orbit / 2)
324 | : pointPosition - orbit / 2
325 |
326 | const maxOrbit = (pointPosition + orbit / 2) >= radiansToDegree(2 * Math.PI)
327 | ? (pointPosition + orbit / 2) - radiansToDegree(2 * Math.PI)
328 | : (pointPosition + orbit / 2)
329 |
330 | if (minOrbit > maxOrbit) { // crossing over zero
331 | if (minOrbit >= planetPosition && planetPosition <= minOrbit) {
332 | result = true
333 | }
334 | } else {
335 | if (minOrbit <= planetPosition && planetPosition <= maxOrbit) {
336 | result = true
337 | }
338 | }
339 |
340 | return result
341 | }
342 | }
343 |
344 | export default Zodiac
345 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "noImplicitAny": true,
5 | "module": "es6",
6 | "target": "es5",
7 | "jsx": "react",
8 | "allowJs": true,
9 | "moduleResolution": "node",
10 | "declaration": true,
11 | "strictNullChecks": true
12 | },
13 | "exclude": ["project/src/**/*.test.ts", "dist"]
14 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | entry: './project/src/index.ts',
5 | module: {
6 | rules: [
7 | {
8 | test: /\.tsx?$/,
9 | use: 'ts-loader',
10 | exclude: [/node_modules/, /.test.ts/],
11 | },
12 | ],
13 | },
14 | resolve: {
15 | extensions: ['.tsx', '.ts', '.js'],
16 | },
17 | mode: 'production',
18 | output: {
19 | clean: true,
20 | path: path.resolve(__dirname, 'dist'),
21 | filename: 'astrochart.js',
22 | library: {
23 | name: 'astrochart',
24 | type: 'umd'
25 | }
26 | },
27 | }
--------------------------------------------------------------------------------