mirror of
https://github.com/minagawah/mikaboshi.git
synced 2026-03-27 08:28:29 +07:00
feat: Added more Feng-Shui calculations
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mikaboshi"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
@@ -13,3 +13,6 @@ sowngwala = { git = "https://github.com/minagawah/sowngwala", version = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
approx_eq = "0.1.8"
|
||||
js-sys = "0.3.52"
|
||||
wasm-bindgen = { version = "0.2.70", features = ["serde-serialize"] }
|
||||
wasm-bindgen-test = "0.3.13"
|
||||
|
||||
239
README.md
239
README.md
@@ -1,121 +1,187 @@
|
||||
# mikaboshi
|
||||
|
||||
## About
|
||||

|
||||

|
||||
|
||||
`mikaboshi` provides basic calculations for Chinese astrology, known as _Bazi_.
|
||||
The name `mikaboshi` derives from a Marvel character,
|
||||
[Amatsu-mikaboshi](https://marvel.fandom.com/wiki/Amatsu-Mikaboshi_(Earth-616)),
|
||||
or one of goddesses in Shinto who equates to Lucifer in the west.
|
||||
It literally means "the shinning star in the sky".
|
||||
## 1. About
|
||||
|
||||
__"mikaboshi"__ is a Rust library for 風水 (风水) (_Feng-Shui_) calculations.
|
||||
As such, it provides commonly used calculation logic
|
||||
for basic Chinese astrological concepts such as:
|
||||
|
||||
- [八卦 (Ba-Gua)](./docs/bagua.md)
|
||||
- [干支 (Gan-Zhi)](./docs/ganzhi.md)
|
||||
- [九星 (Jiu-Xing)](./docs/jiuxing.md)
|
||||
- [二十四节气 (Er-Shi-Si Jie-Qi)](./docs/solar_terms.md)
|
||||
- [二十四山向 (Er-Shi-Si Shan-Xiang)](./docs/compass.md)
|
||||
- [生死衰旺 (Sheng-Si Shuai-Wang)](./docs/shengsi.md)
|
||||
|
||||
As you may have noticed that sample codes in
|
||||
[samples](./docs/examples/index.md)
|
||||
are that of a WASM (WebAssembly) app,
|
||||
it does not always have to be a Rust app when using the library.
|
||||
It is amazing how you could easily link
|
||||
your codes written in Rust to JS apps
|
||||
when provided as a WASM app.
|
||||
|
||||
The name __"mikaboshi"__ derives from a _Marvel_ character
|
||||
_["Amatsu-Mikaboshi"](https://marvel.fandom.com/wiki/Amatsu-Mikaboshi_(Earth-616))_
|
||||
or one of the goddesses in _Shinto_. _"Amatsu-Mikaboshi"_ means
|
||||
_"The Shinning Star in the Sky"_ when literally translated,
|
||||
which reminds us of _Lucifer_ in the west.
|
||||
|
||||
This library depends on
|
||||
_[sowngwala](https://github.com/minagawah/sowngwala/)_
|
||||
for calculating sun's position.
|
||||
It also use some structs from
|
||||
_[sowngwala](https://github.com/minagawah/sowngwala/)_,
|
||||
namely, _`Date`_, _`DateTime`_, _`Time`_, and _`Month`_
|
||||
with which you need to specify as arguments for some functions.
|
||||
Also, they are re-exported for public use.
|
||||
|
||||
__What Makes The Program Tricky?__
|
||||
|
||||
So, the library expects you to have
|
||||
a Feng-Shui board with 9 boxes drawn on a device screen.
|
||||
1 empty box in the middle surrounded by 8 boxes.
|
||||
360 divided by 8, makes it 45 degrees for each.
|
||||
See how it goes when pointing "N" (north):
|
||||
|
||||
(When pointing "N")
|
||||
1st row ‐‐> "NW", "N", and "NE"
|
||||
2nd row ‐‐> "W", (middle), and "E"
|
||||
3rd row ‐‐> "SW", "S", and "SE"
|
||||
|
||||

|
||||
|
||||
However, it gets tricky when device rotates.
|
||||
Say, the device rotates for 45 degrees clockwise.
|
||||
It now points to "NE" (north-east):
|
||||
|
||||
(When pointing "NE")
|
||||
1st row ‐‐> "N", "NE", and "E"
|
||||
2nd row ‐‐> "NW", (middle), and "SE"
|
||||
3rd row ‐‐> "W", "SW", and "S"
|
||||
|
||||
As you can imagine, when the above is expressed in a Rust program,
|
||||
we need `Vec` and `HashMap`, and that is why we have
|
||||
many `Vec` and `HashMap` as to map variations
|
||||
that manifest per compass direction
|
||||
(usually 8, but sometimes 9 when center is concerned).
|
||||
This is so, not only for compass directions,
|
||||
but for Feng-Shui mappings as well.
|
||||
Whenever we calculate positions for Feng-Shui elements,
|
||||
the positions are provided in `Vec` or `HashMap`
|
||||
so that they will have 8 or 9 patterns.
|
||||
|
||||
|
||||
## Important
|
||||
## 2. Examples
|
||||
|
||||
`mikaboshi` depends on
|
||||
[sowngwala](https://github.com/minagawah/sowngwala/)
|
||||
for many structs and functions.
|
||||
When you work with `mikaboshi`, you need some structs from `sowngwala`.
|
||||
For some functions in `mikaboshi` expects `DateTime` of `sowngwala` as arguments.
|
||||
As such, `mikaboshi` is re-exporting date & time related structs from `sowngwala`:
|
||||
[A few examples](./docs/examples/index.md) which may or may not help...
|
||||
|
||||
```rust
|
||||
pub use sowngwala::time::{
|
||||
Month,
|
||||
Date,
|
||||
Time,
|
||||
DateTime,
|
||||
};
|
||||
|
||||
## 3. Documentation
|
||||
|
||||
You may:
|
||||
|
||||
```bash
|
||||
cargo doc
|
||||
```
|
||||
|
||||
Refer to the source codes of `sowngwala` for specifications:
|
||||
https://github.com/minagawah/sowngwala/blob/main/src/time.rs
|
||||
however, you will probably get more from documentations bellow:
|
||||
|
||||
|
||||
## Calculation
|
||||
### [八卦 (Bagua)](./docs/bagua.md)
|
||||
|
||||
- Longitude of the sun is that of 0:00 midnight for the given day
|
||||
- Day changes at 0:00 midnight
|
||||
- [Bagua](./docs/bagua.md#baguabagua)
|
||||
- [BaguaRawData](./docs/bagua.md#baguabaguarawdata)
|
||||
- [BAGUA](./docs/bagua.md#baguabagua)
|
||||
- [BAGUA_START_NORTH_INDEXES](./docs/bagua.md#baguabagua_start_north_indexes)
|
||||
- [BAGUA_START_NORTH](./docs/bagua.md#baguabagua_start_north)
|
||||
- [get_bagua_start_north](./docs/bagua.md#baguaget_bagua_start_north)
|
||||
|
||||
## Usage
|
||||
### [二十四山向 (Er-Shi-Si Shan-Xiang)](./docs/compass.md)
|
||||
|
||||
### `ut_from_local()`
|
||||
- [Direction](./docs/compass.md#compassdirection)
|
||||
- [TwentyFourType](./docs/compass.md#compasstwentyfourtype)
|
||||
- [DIRECTIONS](./docs/compass.md#compassdirections)
|
||||
- [OPPOSITE_DIRECTION](./docs/compass.md#compassopposite_direction)
|
||||
- [DIRECTION_POSITIONS_IN_CHART](./docs/compass.md#compassdirection_positions_in_chart)
|
||||
- [TWENTYFOUR_DIRECTIONS_TO_INDEX](./docs/compass.md#compasstwentyfour_directions_to_index)
|
||||
- [TWENTYFOUR_INDEX_TO_DIRECTIONS](./docs/compass.md#compasstwentyfour_index_to_directions)
|
||||
- [TWENTYFOUR_ORDER_START_NORTH](./docs/compass.md#compasstwentyfour_order_start_north)
|
||||
- [TWENTYFOUR_SECTORS](./docs/compass.md#compasstwentyfour_sectors)
|
||||
- [get_direction_positions_in_chart](./docs/compass.md#compassget_direction_positions_in_chart)
|
||||
- [get_opposite_direction](./docs/compass.md#compassget_opposite_direction)
|
||||
- [get_twentyfour_data_from_direction](./docs/compass.md#compassget_twentyfour_data_from_direction)
|
||||
- [get_twentyfour_data_from_index](./docs/compass.md#compassget_twentyfour_data_from_index)
|
||||
- [get_twentyfour_direction_from_degrees](./docs/compass.md#compassget_twentyfour_direction_from_degrees)
|
||||
- [get_twentyfour_direction_from_direction](./docs/compass.md#compassget_twentyfour_direction_from_direction)
|
||||
- [get_twentyfour_direction_from_index](./docs/compass.md#compassget_twentyfour_direction_from_index)
|
||||
- [get_twentyfour_index_from_direction](./docs/compass.md#compassget_twentyfour_index_from_direction)
|
||||
|
||||
You may convert your local time into _UT_:
|
||||
### [干支 (Gan-Zhi)](./docs/ganzhi.md)
|
||||
|
||||
```rust
|
||||
use mikaboshi::time::{
|
||||
Month,
|
||||
DateTime,
|
||||
ut_from_local,
|
||||
};
|
||||
- [Stem](./docs/ganzhi.md#ganzhistem)
|
||||
- [Branch](./docs/ganzhi.md#ganzhibranch)
|
||||
- [StemRawData](./docs/ganzhi.md#ganzhistemrawdata)
|
||||
- [BranchRawData](./docs/ganzhi.md#ganzhibranchrawdata)
|
||||
- [GanZhi](./docs/ganzhi.md#ganzhiganzhi)
|
||||
- [Bazi](./docs/ganzhi.md#ganzhibazi)
|
||||
- [STEMS](./docs/ganzhi.md#ganzhistems)
|
||||
- [BRANCHES](./docs/ganzhi.md#ganzhibranches)
|
||||
- [GANZHI_SEXAGESIMAL](./docs/ganzhi.md#ganzhiganzhi_sexagesimal)
|
||||
- [HOUR_STEM_TABLE](./docs/ganzhi.md#ganzhihour_stem_table)
|
||||
- [Bazi::from_local](./docs/ganzhi.md#ganzhibazifrom_local)
|
||||
|
||||
let zone: i8 = 9;
|
||||
### [九星 (Jiu-Xing)](./docs/jiuxing.md)
|
||||
|
||||
let local = DateTime {
|
||||
year: 2021,
|
||||
month: Month::Jul,
|
||||
day: 7.0,
|
||||
hour: 0,
|
||||
min: 0,
|
||||
sec: 0.0,
|
||||
};
|
||||
- [JiuXing](./docs/jiuxing.md#jiuxingjiuxing)
|
||||
- [JiuXingRawData](./docs/jiuxing.md#jiuxingjiuxingrawdata)
|
||||
- [XiaGuaTu](./docs/jiuxing.md#jiuxingxiaguatu)
|
||||
- [DIRECTION_TO_JIU_XING](./docs/jiuxing.md#jiuxingdirection_to_jiu_xing)
|
||||
- [JIU_XING](./docs/jiuxing.md#jiuxingjiu_xing)
|
||||
- [JIU_XING_DI_PAN_POSITIONS](./docs/jiuxing.md#jiuxingjiu_xing_di_pan_positions)
|
||||
- [get_jiuxing_dipan_positions_from_direction](./docs/jiuxing.md#jiuxingget_jiuxing_dipan_positions_from_direction)
|
||||
- [get_jiuxing_from_index](./docs/jiuxing.md#jiuxingget_jiuxing_from_index)
|
||||
- [normalize_jiuxing](./docs/jiuxing.md#jiuxingnormalize_jiuxing)
|
||||
- [fly_flying_stars](./docs/jiuxing.md#jiuxingfly_flying_stars)
|
||||
- [get_xiaguatu_from_unpan_index](./docs/jiuxing.md#jiuxingget_xiaguatu_from_unpan_index)
|
||||
|
||||
let ut: DateTime = ut_from_local(&local, zone);
|
||||
println!("ut: {:?}", ut);
|
||||
### [生死衰旺 (Sheng-Si Shuai-Wang)](./docs/shengsi.md)
|
||||
|
||||
// {
|
||||
// year: 2021,
|
||||
// month: Jul,
|
||||
// day: 6.0,
|
||||
// hour: 14,
|
||||
// min: 57,
|
||||
// sec: 17.13778432735566
|
||||
// }
|
||||
```
|
||||
- [ShengSi](./docs/shengsi.md#shengsishengsi)
|
||||
- [ShengSiYearlyAlloc](./docs/shengsi.md#shengsishengsiyearalloc)
|
||||
- [SHENG_SI](./docs/shengsi.md#shengsisheng_si)
|
||||
- [SHENG_SI_ALLOC](./docs/shengsi.md#shengsisheng_si_alloc)
|
||||
- [get_shengsi_mapping](./docs/shengsi.md#shengsiget_shengsi_mapping)
|
||||
|
||||
### `Bazi::from_local()`
|
||||
### [二十四节气 (Er-Shi-Si Jie-Qi) and 立春 (Li-Chun)](./docs/solar_terms.md)
|
||||
|
||||
You can easily calculate for _Bazi_:
|
||||
- [get_last_term](./docs/solar_terms.md#solar_termsget_last_term)
|
||||
- [get_lichun](./docs/solar_terms.md#solar_termsget_lichun)
|
||||
|
||||
```rust
|
||||
use mikaboshi::time::{ Month, DateTime };
|
||||
use mikaboshi::ganzhi::{ Bazi, GanZhi };
|
||||
### [Planets](./docs/planet.md)
|
||||
|
||||
let zone: i8 = 9;
|
||||
- [Planet](./docs/planet.md#planet)
|
||||
- [PlanetRawData](./docs/planet.md#planetrawdata)
|
||||
- [PLANETS](./docs/planet.md#planets)
|
||||
|
||||
let lt = DateTime {
|
||||
year: 2021,
|
||||
month: Month::Jul,
|
||||
day: 7.0,
|
||||
hour: 0,
|
||||
min: 0,
|
||||
sec: 0.0,
|
||||
};
|
||||
let bazi: Bazi = Bazi::from_local(<, zone);
|
||||
### [Time](./docs/time.md)
|
||||
|
||||
let year: GanZhi = bazi.year;
|
||||
let month: GanZhi = bazi.month;
|
||||
let day: GanZhi = bazi.day;
|
||||
let hour: GanZhi = bazi.hour;
|
||||
- [Date](./docs/time.md#timedate)
|
||||
- [DateTime](./docs/time.md#timedatetime)
|
||||
- [Time](./docs/time.md#timetime)
|
||||
- [ut_from_local](./docs/time.md#timeut_from_local)
|
||||
|
||||
println!("年: {} ({})", year.alphabet(), year.alphabet_ja());
|
||||
println!("月: {} ({})", month.alphabet(), month.alphabet_ja());
|
||||
println!("日: {} ({})", day.alphabet(), day.alphabet_ja());
|
||||
println!("時: {} ({})", hour.alphabet(), hour.alphabet_ja());
|
||||
|
||||
// 年: 辛丑 (かのと・うし)
|
||||
// 月: 甲午 (きのえ・うま)
|
||||
// 日: 乙卯 (きのと・う)
|
||||
// 時: 癸未 (みずのと・ひつじ)
|
||||
```
|
||||
|
||||
## Test
|
||||
## 4. Test
|
||||
|
||||
```
|
||||
RUST_BACKTRACE=1 cargo test -vv -- --nocapture
|
||||
```
|
||||
|
||||
## Dislaimer
|
||||
## 5. Dislaimer
|
||||
|
||||
There is absolutely no gurantee about the accuracy of the service,
|
||||
information, or calculated results provided by the program,
|
||||
@@ -127,6 +193,7 @@ for which the author of the program shall not be liable.
|
||||
It shall be your own responsibility to ensure the service,
|
||||
information, or calculated results meet your specific requirements.
|
||||
|
||||
## License
|
||||
|
||||
## 6. License
|
||||
|
||||
MIT license ([LICENSE](LICENSE))
|
||||
|
||||
127
docs/bagua.md
Normal file
127
docs/bagua.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 八卦 (Bagua)
|
||||
|
||||
Source: [src/bagua.rs](../src/bagua.rs)
|
||||
|
||||

|
||||
|
||||
Although 八卦 (Ba-Gua) is a concept in 易経 (I-Ching),
|
||||
when used in Feng-Shui, it is often associated with 九星 (Jiu-Xing).
|
||||
While everthing in this world is (said to be) divided into either 陰 (Yin)
|
||||
or 陽 (Yang), each could be further divided into lesser Yin and Yang.
|
||||
For Yang, some may be abundant in Yang. Or, some may slightly lean toward Yin.
|
||||
This goes for Yin as well. Here, the initial Yin and Yang,
|
||||
now divided into 4, or becomes 4 patterns. Then, for each,
|
||||
there are further divisions, and for this time, it now makes it 8 patterns.
|
||||
Ancient Chinese had a specific term for these 8 patterns,
|
||||
and it is called, 八卦 (Ba-Gua), or "8 Gua".
|
||||
|
||||
[0] 坎 (Kan)
|
||||
[1] 坤 (Kun)
|
||||
[2] 震 (Zhen)
|
||||
[3] 巽 (Xun)
|
||||
[4] 乾 (Qian)
|
||||
[5] 兌 (Dui)
|
||||
[6] 艮 (Gen)
|
||||
[7] 離 (Li)
|
||||
|
||||
|
||||
## bagua::Bagua
|
||||
|
||||
A struct representing 卦 (Gua) and stores its attributes.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Bagua {
|
||||
pub num: u8,
|
||||
pub name: Language,
|
||||
pub direction: String,
|
||||
pub element: u8,
|
||||
}
|
||||
```
|
||||
|
||||
## bagua::BaguaRawData
|
||||
|
||||
A temporary struct for loading JSON data when defining a static const `BAGUA`.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BaguaRawData {
|
||||
pub num: u8,
|
||||
pub name: LanguageData,
|
||||
pub direction: String,
|
||||
pub element: u8,
|
||||
}
|
||||
```
|
||||
|
||||
## bagua::BAGUA
|
||||
|
||||
`Vec<Bagua>`
|
||||
|
||||
A static vector with 9 items, each represents 卦 (Gua) of 八卦 (Ba-Gua),
|
||||
or what known as "Eight Trigrams". Each stores associated attributes
|
||||
for the 卦 (Gua), and are in the order of Ba-Gua Numbers. For instance,
|
||||
坎 (Kan) is the first in Ba-Gua, so it comes first in the vector.
|
||||
However, be careful when you refer to each 卦 (Gua) as the program
|
||||
refers 卦 (Gua) not by Ba-Gua Numbers, but by vector indexes.
|
||||
For 坎 (Kan), for instance, while it has the Ba-Gua Number of 1,
|
||||
it is referred as 0 because that is the index is for 坎 (Kan).
|
||||
|
||||
[0] 坎 (1 = Kan)
|
||||
[1] 坤 (2 = Kun)
|
||||
[2] 震 (3 = Zhen)
|
||||
[3] 巽 (4 = Xun)
|
||||
[4] 中 (5 = Zhong)
|
||||
[5] 乾 (6 = Qian)
|
||||
[6] 兌 (7 = Dui)
|
||||
[7] 艮 (8 = Gen)
|
||||
[8] 離 (9 = Li)
|
||||
|
||||
For attributes details stored in the vector is found in JSON file:
|
||||
[json/bagua.json](../json/bagua.json)
|
||||
|
||||
## bagua::BAGUA_START_NORTH_INDEXES
|
||||
|
||||
`Vec<u8>`
|
||||
|
||||
Another static vector for 八卦 (Ba-Gua) (and is `Vec<u8>`).
|
||||
However, this time, it has only 8 items because this is used
|
||||
for compass rotation. When using a compass, only 8 directions matter,
|
||||
and the middle does not mean anything. When 八卦 (Ba-Gua) are
|
||||
placed in 洛書 (Lo-Shu) order, 中 (Zhong) comes in the middle.
|
||||
So, 中 (Zhong) is missing for this vector. For 八卦 (Ba-Gua),
|
||||
it begins with 坎 (Kan) which resides in the north
|
||||
when they are placed in Lo-Shu order. Moving clockwise,
|
||||
what you see next in the north-east, is 艮 (Gen).
|
||||
Then, comes 震 (3 = Zhen) which is in the east, and, so on.
|
||||
|
||||
[0] 坎 (1 = Kan)
|
||||
[1] 艮 (8 = Gen)
|
||||
[2] 震 (3 = Zhen)
|
||||
[3] 巽 (4 = Xun)
|
||||
[4] 離 (9 = Li)
|
||||
[5] 坤 (2 = Kun)
|
||||
[6] 兌 (7 = Dui)
|
||||
[7] 乾 (6 = Qian)
|
||||
|
||||
## bagua::BAGUA_START_NORTH
|
||||
|
||||
`Vec<Bagua>`
|
||||
|
||||
While the order is the same as `BAGUA_START_NORTH_INDEXES`,
|
||||
for this time, it is `Vec<Bagua>` instead of `Vec<u8>`.
|
||||
|
||||
## bagua::get_bagua_start_north
|
||||
|
||||
A getter for `BAGUA_START_NORTH`.
|
||||
|
||||
Example:
|
||||
```rust
|
||||
use mikaboshi::bagua::{get_bagua_start_north, Bagua};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(index: usize) -> JsValue {
|
||||
let bagua: Option<&Bagua> = get_bagua_start_north(index);
|
||||
JsValue::from_serde(&bagua).unwrap()
|
||||
}
|
||||
```
|
||||
286
docs/compass.md
Normal file
286
docs/compass.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# 二十四山向 (Er-Shi-Si Shan-Xiang)
|
||||
|
||||
Source: [src/compass.rs](../src/compass.rs)
|
||||
|
||||

|
||||
|
||||
A module for compass directions. When dividing 360 degrees into 8,
|
||||
we get 45 degrees. Ancient Chinese further divided them each into 3
|
||||
(called "sectors"), each having 15 degrees. Meaning, there are
|
||||
24 sectors as a total. This is called, 二十四山向 (Er-Shi-Si Shan-Xiang).
|
||||
Not only for 8 directions, but these 24 directions (sectors)
|
||||
are used in Feng-Shui, and this is the module for these directions.
|
||||
|
||||
Reference:
|
||||
- [二十四山 - Wiki](https://ja.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%9B%9B%E5%B1%B1)
|
||||
|
||||
|
||||
## compass::Direction
|
||||
|
||||
A struct representing compass direction.
|
||||
For each direction, there are 3 sectors.
|
||||
|
||||
Example:
|
||||
```rust
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Direction {
|
||||
pub direction: String,
|
||||
pub sector: usize,
|
||||
}
|
||||
```
|
||||
|
||||
## compass::TwentyFourType
|
||||
|
||||
二十四山向 (Er-Shi-Si Shan-Xiang) can be either 卦 (Gua), 干 (Gan), or 支 (Zhi).
|
||||
|
||||
```rust
|
||||
pub enum TwentyFourType<'a> {
|
||||
Bagua(&'a Bagua),
|
||||
Stem(&'a Stem),
|
||||
Branch(&'a Branch),
|
||||
}
|
||||
```
|
||||
|
||||
## compass::DIRECTIONS
|
||||
|
||||
`[&str; 8]`
|
||||
|
||||
An array for 8 directions.
|
||||
|
||||
```rust
|
||||
pub const DIRECTIONS: [&str; 8] =
|
||||
["n", "ne", "e", "se", "s", "sw", "w", "nw"];
|
||||
```
|
||||
|
||||
## compass::OPPOSITE_DIRECTION
|
||||
|
||||
`HashMap<&str, &str>`
|
||||
|
||||
A hash map for the opposite direction.
|
||||
|
||||
## compass::DIRECTION_POSITIONS_IN_CHART
|
||||
|
||||
`HashMap<&str, [&str; 9]>`
|
||||
|
||||
A hash map with 9 items.
|
||||
Say, we have 9 boxes displayed on a device screen.
|
||||
Except for the box in the middle, we have 8 boxes
|
||||
around the middle to represent 8 compass directions.
|
||||
When facing "n" (north), for the first row,
|
||||
we have "nw", "n", and "ne". For the second row,
|
||||
we have "w", "", and "e" (where "" being the middle box).
|
||||
For the last, we have "sw", "s", and "se".
|
||||
|
||||
[0] nw [1] n [2] ne
|
||||
[3] w [4] [5] e
|
||||
[6] sw [7] s [8] se
|
||||
|
||||
Now, consider when the device rotates.
|
||||
Depending on which direction the device is facing,
|
||||
we have different labels. For all 8 directions,
|
||||
this HashMap provides a map for the positions.
|
||||
|
||||
## compass::TWENTYFOUR_DIRECTIONS_TO_INDEX
|
||||
|
||||
`HashMap<String, usize>`
|
||||
|
||||
A HashMap mapping direction (combination of "direction" and "sector")
|
||||
to the corresponding index.
|
||||
|
||||
n2: 0
|
||||
n3: 1
|
||||
ne1: 2
|
||||
ne2: 3
|
||||
...
|
||||
...
|
||||
|
||||
## compass::TWENTYFOUR_INDEX_TO_DIRECTIONS
|
||||
|
||||
`Vec<Direction>`
|
||||
|
||||
An array with 24 items, for each represents
|
||||
each in 二十四山向 (Er-Shi-Si Shan-Xiang).
|
||||
Note, the array begins with "N2"
|
||||
(and "N1" is stored at the very last, or [23]).
|
||||
|
||||
0: Direction { direction: "n", sector: 2 }
|
||||
1: Direction { direction: "n", sector: 3 }
|
||||
2: Direction { direction: "ne", sector: 1 }
|
||||
3: Direction { direction: "ne", sector: 2 }
|
||||
...
|
||||
...
|
||||
|
||||
## compass::TWENTYFOUR_ORDER_START_NORTH
|
||||
|
||||
`[(usize, usize); 24]`
|
||||
|
||||
An array with 24 items, each being a tuple. For each tuple,
|
||||
the first represents the type of "二十四山向" (Er-Shi-Si Shan-Xiang),
|
||||
and the second is the index of the type.
|
||||
The type being: [0] BAGUA, [1] STEM, or [2] BRANCH.
|
||||
|
||||
(2, 0) --> [0] 子
|
||||
(1, 9) --> [9] 癸
|
||||
(2, 1) --> [1] 丑
|
||||
(0, 7) --> [7] 艮
|
||||
(2, 2) --> [2] 寅
|
||||
(1, 0) --> [0] 甲
|
||||
...
|
||||
...
|
||||
|
||||
## compass::TWENTYFOUR_SECTORS
|
||||
|
||||
`[u8; 24]`
|
||||
|
||||
An array with 24 items. Imagine having a circlar disc displayed
|
||||
on a device screen. When dividing 360 by 8 directions, we get
|
||||
45 degrees for each. When each direction is further divided
|
||||
into 3, then each is called a "sector", and it has 15 degrees
|
||||
for each "sector". Sectors are placed in clockwise order
|
||||
(left to right) for each direction, so that you see
|
||||
the sector 1 being placed on your very left. Then, you see
|
||||
the sector 2 in the middle, and the sector 3 on your right.
|
||||
Imagine the device pointing north. On the circular disc,
|
||||
what you see at the very top is the sector 2 of "N" (north),
|
||||
denoted as "N2". On your left, you see "N1".
|
||||
On your right, "N3".
|
||||
|
||||
When we want to express all the 24 sectors, we want
|
||||
an array with 24 items. For the first item in the array [0],
|
||||
it is convenient to have "N2". Then, for the second item
|
||||
in the array [1], we want "N3". For [2], we want "NE1".
|
||||
For [3], we want "NE2". And, so on. As you can imagine,
|
||||
"N1" comes to the very last in the array, or [23].
|
||||
|
||||
2 --> n
|
||||
3 --> n
|
||||
1 --> ne
|
||||
2 --> ne
|
||||
3 --> ne
|
||||
1 --> e
|
||||
...
|
||||
...
|
||||
|
||||
## compass::get_direction_positions_in_chart
|
||||
|
||||
An getter for `DIRECTION_POSITIONS_IN_CHART`.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::compass::get_direction_positions_in_chart;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(direction: &str) -> JsValue {
|
||||
JsValue::from(
|
||||
(match get_direction_positions_in_chart(direction) {
|
||||
Some(positions) => positions.to_vec(),
|
||||
_ => Vec::new(),
|
||||
})
|
||||
.into_iter()
|
||||
.map(JsValue::from)
|
||||
.collect::<js_sys::Array>(),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## compass::get_opposite_direction
|
||||
|
||||
A getter for `OPPOSITE_DIRECTION`.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::compass::get_opposite_direction;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(direction: &str) -> JsValue {
|
||||
JsValue::from(get_opposite_direction(direction))
|
||||
}
|
||||
```
|
||||
|
||||
## compass::get_twentyfour_data_from_direction
|
||||
|
||||
From the given direction and sector, returns `TwentyFourType`.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::compass::{get_twentyfour_data_from_direction, TwentyFourType};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(direction: &str, sector: usize) -> JsValue {
|
||||
let t_type: TwentyFourType = get_twentyfour_data_from_direction(direction, sector);
|
||||
match t_type {
|
||||
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
|
||||
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
|
||||
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## compass::get_twentyfour_data_from_index
|
||||
|
||||
From index, simply returns the corresponding `TwentyFourType`.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::compass::{get_twentyfour_data_from_index, TwentyFourType};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(index: usize) -> JsValue {
|
||||
let t_type: TwentyFourType = get_twentyfour_data_from_index(index);
|
||||
match t_type {
|
||||
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
|
||||
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
|
||||
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## compass::get_twentyfour_direction_from_degrees
|
||||
|
||||
From the given degrees, returns the corresponding `Direction`.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::compass::{get_twentyfour_direction_from_degrees, Direction};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(degrees: f32) -> JsValue {
|
||||
let dir: Direction = get_twentyfour_direction_from_degrees(degrees);
|
||||
JsValue::from_serde(&dir).unwrap()
|
||||
}
|
||||
```
|
||||
|
||||
## compass::get_twentyfour_direction_from_direction
|
||||
|
||||
From the given direction and sector, returns `Direction`.
|
||||
|
||||
## compass::get_twentyfour_direction_from_index
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::compass::{get_twentyfour_direction_from_index, Direction};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(index: usize) -> JsValue {
|
||||
let dir: &Direction = get_twentyfour_direction_from_index(index);
|
||||
JsValue::from_serde(dir).unwrap()
|
||||
}
|
||||
```
|
||||
|
||||
## compass::get_twentyfour_index_from_direction
|
||||
|
||||
From the given direction and sector, finds the corresponding index
|
||||
in `TWENTYFOUR_DIRECTIONS_TO_INDEX`
|
||||
|
||||
32
docs/examples/Cargo.toml
Normal file
32
docs/examples/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = ""
|
||||
version = "0.1.0"
|
||||
authors = [""]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "^0.1.6"
|
||||
js-sys = "0.3.52"
|
||||
mikaboshi = { git = "https://github.com/minagawah/mikaboshi", version = "0.5.0" }
|
||||
serde = { version = "1.0.127", features = ["derive"] }
|
||||
wasm-bindgen = { version = "0.2.70", features = ["serde-serialize"] }
|
||||
|
||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||
# compared to the default allocator's ~10K. It is slower than the default
|
||||
# allocator, however.
|
||||
#
|
||||
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||
wee_alloc = { version = "0.4.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.13"
|
||||
|
||||
[profile.release]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
613
docs/examples/FengShui.js
Normal file
613
docs/examples/FengShui.js
Normal file
@@ -0,0 +1,613 @@
|
||||
/**
|
||||
* A context provider to load a WASM app ("voi-feng-shui" in our case).
|
||||
* Once `FengShuiContext` is set in the parent component, you can use
|
||||
* `useFengShui` in child components to access the given properties.
|
||||
* To start with, it requies data in `profile`, however, `profile`
|
||||
* is managed in `ProfilesProvider`. For two providers cannot
|
||||
* communicate to each other, we have `useFengShuiSync`
|
||||
* to serve the purpose. For any components handling `profile`,
|
||||
* we run `useFengShuiSync` in there, and it will automatically
|
||||
* @namespace FengShui
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useContext,
|
||||
createContext,
|
||||
useEffect,
|
||||
useState,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import moment from 'moment';
|
||||
import init, {
|
||||
// 八卦 (Ba-Gua)
|
||||
get_bagua_start_north as wasm_get_bagua_start_north,
|
||||
|
||||
// 二十四山向 (Er-Shi Si-Shan Xiang)
|
||||
get_twentyfour_direction_from_index as wasm_get_twentyfour_direction_from_index,
|
||||
get_twentyfour_direction_from_degrees as wasm_get_twentyfour_direction_from_degrees,
|
||||
get_twentyfour_data_from_index as wasm_get_twentyfour_data_from_index,
|
||||
get_twentyfour_data_from_direction as wasm_get_twentyfour_data_from_direction,
|
||||
|
||||
// 干支 (Gan-Zhi)
|
||||
get_bazi as wasm_get_bazi,
|
||||
get_lichun as wasm_get_lichun,
|
||||
|
||||
// 九星 (Jiu-Xings)
|
||||
get_jiuxing_from_index as wasm_get_jiuxing_from_index,
|
||||
get_unpan_xing_index as wasm_get_unpan_xing_index,
|
||||
get_xiaguatu_from_unpan_index as wasm_get_xiaguatu_from_unpan_index,
|
||||
get_jiuxing_dipan_positions_from_direction as wasm_get_jiuxing_dipan_positions_from_direction,
|
||||
|
||||
// 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
get_shengsi_mapping as wasm_get_shengsi_mapping,
|
||||
} from 'voi-feng-shui';
|
||||
|
||||
import { DATETIME_FORMAT, BAZI_TYPE_KEYS, GANZHI_TYPE_KEYS } from '@/constants';
|
||||
import { int, noop, get_utc_offset_in_hours } from '@/lib/utils';
|
||||
|
||||
import {
|
||||
useProfiles,
|
||||
get_first_valid_profile_from_profiles,
|
||||
} from '@/contexts/Profiles';
|
||||
|
||||
const WASM_PATH =
|
||||
NODE_ENV === 'production'
|
||||
? 'wasm/voi-feng-shui/voi-feng-shui_bg.wasm'
|
||||
: void 0;
|
||||
|
||||
// console.log('[useFengShui] WASM_PATH: ', WASM_PATH);
|
||||
|
||||
const DATETIME_KEYS = Object.keys(DATETIME_FORMAT);
|
||||
const DATE_KEYS = DATETIME_KEYS.filter(
|
||||
key =>
|
||||
key.indexOf('year') > -1 ||
|
||||
key.indexOf('month') > -1 ||
|
||||
key.indexOf('day') > -1
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const has_valid_profile = (p = {}) =>
|
||||
p.localtime && p.direction && p.sector;
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// FengShuiContext
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef FengShui.FengShuiContext
|
||||
* @property {boolean} ready
|
||||
* @property {Object} profile
|
||||
* @property {Object} bazi
|
||||
* @property {Object} lichun
|
||||
* @property {Object} unpan_xing
|
||||
* @property {Object} shan_xing
|
||||
* @property {Object} xiang_xing
|
||||
* @property {Function} update
|
||||
* @property {FengShui.FengShuiContext.get_bagua_start_north}
|
||||
* @property {FengShui.FengShuiContext.get_direction_positions_in_chart} - NOT IN USE
|
||||
* @property {FengShui.FengShuiContext.get_opposite_direction} - NOT IN USE
|
||||
* @property {FengShui.FengShuiContext.get_twentyfour_direction_from_index}
|
||||
* @property {FengShui.FengShuiContext.get_twentyfour_direction_from_degrees}
|
||||
* @property {FengShui.FengShuiContext.get_twentyfour_data_from_index}
|
||||
* @property {FengShui.FengShuiContext.get_twentyfour_data_from_direction}
|
||||
* @property {FengShui.FengShuiContext.get_bazi} get_bazi
|
||||
* @property {FengShui.FengShuiContext.get_lichun} get_lichun
|
||||
* @property {FengShui.FengShuiContext.get_unpan_xing_index}
|
||||
* @property {FengShui.FengShuiContext.get_jiuxing_from_index}
|
||||
* @property {FengShui.FengShuiContext.get_xiaguatu_from_unpan_index}
|
||||
* @property {FengShui.FengShuiContext.get_jiuxing_dipan_positions_from_direction}
|
||||
* @property {FengShui.FengShuiContext.get_shengsi_mapping}
|
||||
*/
|
||||
const FengShuiContext = createContext({
|
||||
ready: false,
|
||||
profile: null, // localtime, direction, sector
|
||||
bazi: null,
|
||||
lichun: null,
|
||||
unpan_xing: null, // 運盤星
|
||||
shan_xing: null, // 山星
|
||||
xiang_xing: null, // 向星
|
||||
update: noop,
|
||||
|
||||
// 八卦 (Ba-Gua)
|
||||
get_bagua_start_north: noop,
|
||||
|
||||
// 二十四山向 (Er-Shi Si-Shan Xiang)
|
||||
get_twentyfour_direction_from_index: noop,
|
||||
get_twentyfour_direction_from_degrees: noop,
|
||||
get_twentyfour_data_from_index: noop,
|
||||
get_twentyfour_data_from_direction: noop,
|
||||
|
||||
// 干支 (Gan-Zhi)
|
||||
get_bazi: noop,
|
||||
get_lichun: noop,
|
||||
|
||||
// 九星 (Jiu-Xings)
|
||||
unpan_xing_index: noop,
|
||||
get_xiaguatu_from_unpan_index: noop,
|
||||
get_jiuxing_dipan_positions_from_direction: noop,
|
||||
|
||||
// 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
get_shengsi_mapping: noop,
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// FengShuiProvider
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef FengShui.FengShuiProvider
|
||||
* @function
|
||||
*/
|
||||
export const FengShuiProvider = props => {
|
||||
const [ready, setReady] = useState(false);
|
||||
const [profile, setProfile] = useState(null);
|
||||
const [bazi, setBazi] = useState(null);
|
||||
const [lichun, setLiChun] = useState(null);
|
||||
const [unpan_xing, setUnPanXing] = useState(null);
|
||||
const [shan_xing, setShanXing] = useState(null);
|
||||
const [xiang_xing, setXiangXing] = useState(null);
|
||||
|
||||
// ================================================================
|
||||
// 八卦 (Bagua)
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* A simple accessor for values stored in `BAGUA_START_NORTH`.
|
||||
* @typedef FengShui.FengShuiContext.get_bagua_start_north
|
||||
* @function
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {FengShui.FengShuiContext.get_bagua_start_north}
|
||||
*/
|
||||
const get_bagua_start_north = useCallback(
|
||||
dir => {
|
||||
return ready && wasm_get_bagua_start_north(dir);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// 二十四山向 (Er-Shi Si-Shan Xiang)
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* From the index, returns `Direction`.
|
||||
* @typedef FengShui.FengShuiContext.get_twentyfour_direction_from_index
|
||||
* @function
|
||||
* @param {number} [index]
|
||||
* @returns {Object} - { direction, sector }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {FengShui.FengShuiContext.get_twentyfour_direction_from_index}
|
||||
*/
|
||||
const get_twentyfour_direction_from_index = useCallback(
|
||||
index => {
|
||||
return ready && wasm_get_twentyfour_direction_from_index(index);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
/**
|
||||
* From `degrees`, returns `Direction` (for 二十四山向; Er-Shi Si-Shan Xiang).
|
||||
* @typedef FengShui.FengShuiContext.get_twentyfour_direction_from_degrees
|
||||
* @function
|
||||
* @param {number} [degrees=0]
|
||||
* @returns {Object} - { direction, sector }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {FengShui.FengShuiContext.get_twentyfour_direction_from_degrees}
|
||||
*/
|
||||
const get_twentyfour_direction_from_degrees = useCallback(
|
||||
degrees => {
|
||||
return ready && wasm_get_twentyfour_direction_from_degrees(degrees);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
/**
|
||||
* From index, returns `TwentyFourType`.
|
||||
* @typedef FengShui.FengShuiContext.get_twentyfour_data_from_index
|
||||
* @function
|
||||
* @param {number} [index]
|
||||
* @returns {Bagua|Stem|Branch} - TwentyFourType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {FengShui.FengShuiContext.get_twentyfour_data_from_index}
|
||||
*/
|
||||
const get_twentyfour_data_from_index = useCallback(
|
||||
index => {
|
||||
return ready && wasm_get_twentyfour_data_from_index(index);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
/**
|
||||
* From `direction`, returns `TwentyFourType`.
|
||||
* @typedef FengShui.FengShuiContext.get_twentyfour_data_from_direction
|
||||
* @function
|
||||
* @param {number} [index]
|
||||
* @returns {Bagua|Stem|Branch} - TwentyFourType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {FengShui.FengShuiContext.get_twentyfour_data_from_direction}
|
||||
*/
|
||||
const get_twentyfour_data_from_direction = useCallback(
|
||||
(dir, sec) => {
|
||||
return ready && wasm_get_twentyfour_data_from_direction(dir, sec);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// 干支 (Gan-Zhi)
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* From `t` (localtime), calculates for 八字 (Bazi).
|
||||
* Internally mapped to: get_bazi
|
||||
* @typedef FengShui.FengShuiContext.get_bazi
|
||||
* @function
|
||||
* @param {Object} [t] - localtime (as a momemt object)
|
||||
* @returns {Object} - Bazi
|
||||
*/
|
||||
|
||||
/** @type {FengShui.FengShuiContext.get_bazi} */
|
||||
const get_bazi = useCallback(
|
||||
t => {
|
||||
return (
|
||||
ready &&
|
||||
normalize_bazi_data(wasm_get_bazi(datetime_params_from_localtime(t)))
|
||||
);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
/**
|
||||
* From `year`, calculates for 立春 (Li-Chun).
|
||||
* Internally mapped to: get_lichun
|
||||
* @typedef FengShui.FengShuiContext.get_lichun
|
||||
* @function
|
||||
* @param {number} [y] - year
|
||||
* @returns {string} - Li-Chun
|
||||
*/
|
||||
|
||||
/** @type {FengShui.FengShuiContext.get_lichun} */
|
||||
const get_lichun = useCallback(
|
||||
y => {
|
||||
return ready && y && wasm_get_lichun(~~y);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// 九星 (Jiu-Xing)
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* A simple accessor for values stored in `JIU_XING`.
|
||||
* @typedef FengShui.FengShuiContext.get_jiuxing_from_index
|
||||
* @function
|
||||
* @param {Object} [index] - Jiu-Xing index
|
||||
* @returns {Object} - JiuXing
|
||||
*/
|
||||
|
||||
/** @type {FengShui.FengShuiContext.get_jiuxing_from_index} */
|
||||
const get_jiuxing_from_index = useCallback(
|
||||
index => {
|
||||
return ready && wasm_get_jiuxing_from_index(index);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
/**
|
||||
* From: (1) `current` (current localtime), and
|
||||
* (2) `lichun` (立春 (Li-Chun) for the year),
|
||||
* returns 運盤星 (Un-Pan Xing) index.
|
||||
* @typedef FengShui.FengShuiContext.get_unpan_xing_index
|
||||
* @function
|
||||
* @param {Object} [current] - Current localtime
|
||||
* @param {Object} [lichun] - Li-Chun (for the year)
|
||||
* @returns {number} - Un-Pan Xing index
|
||||
*/
|
||||
|
||||
/** @type {FengShui.FengShuiContext.get_unpan_xing_index} */
|
||||
const get_unpan_xing_index = useCallback(
|
||||
params => {
|
||||
const { current, lichun } = params;
|
||||
return (
|
||||
ready &&
|
||||
wasm_get_unpan_xing_index(
|
||||
date_params_from_localtime(current),
|
||||
date_params_from_localtime(lichun)
|
||||
)
|
||||
);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
/**
|
||||
* Takes 運盤星 (Un-Pan Xing) information and 向星 (Xiang-Xing)
|
||||
* information, and returns 下卦図 (Xia-Gua-Tu).
|
||||
* For 運盤星 (Un-Pan Xing), we need 2 kinds: 運盤 (Un-Pan)
|
||||
* which is currently in the center, and 洛書 (Lo-Shu) order
|
||||
* in its re-arranged form. For 向星 (Xiang-Xing),
|
||||
* we want `direction` and `sector`.
|
||||
* @typedef FengShui.FengShuiContext.get_xiaguatu_from_unpan_index
|
||||
* @function
|
||||
* @param {Object} arg
|
||||
* @param {number} arg.unpan_xing_center - 運盤星 (Un-Pan Xing) index
|
||||
* @param {Array} arg.unpan_xing_order - Jiu-Xing Order
|
||||
* @param {string} arg.xiang_xing_direction - Direction for 向星 (Xiang-Xing)
|
||||
* @param {number} arg.xiang_xing_sector - Sector for both 山星 (Shan-Xing) and 向星 (Xiang-Xing)
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
/** @type {FengShui.FengShuiContext.get_xiaguatu_from_unpan_index} */
|
||||
const get_xiaguatu_from_unpan_index = useCallback(
|
||||
params => {
|
||||
return (
|
||||
ready &&
|
||||
wasm_get_xiaguatu_from_unpan_index({
|
||||
...params,
|
||||
unpan_xing_order: params.unpan_xing_order || [
|
||||
5, 0, 7, 6, 4, 2, 1, 8, 3,
|
||||
],
|
||||
})
|
||||
);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
/**
|
||||
* A simple accessor for values stored in `JIU_XING_DI_PAN_POSITIONS`.
|
||||
* @type {FengShui.FengShuiContext.get_jiuxing_dipan_positions_from_direction}
|
||||
*/
|
||||
const get_jiuxing_dipan_positions_from_direction = useCallback(
|
||||
dir => {
|
||||
return ready && wasm_get_jiuxing_dipan_positions_from_direction(dir);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* When provided with 運盤 (Un-Pan) index and the current chart,
|
||||
* returns the corresponding 生死衰旺 (Sheng-Si Shuai-Wang).
|
||||
* @typedef FengShui.FengShuiContext.get_shengsi_mapping
|
||||
* @function
|
||||
* @param {Object} arg
|
||||
* @param {number} arg.unpan_id - 運盤星 (Un-Pan Xing) index
|
||||
* @param {Array} arg.unpan_xing_chart - 運盤星 (Un-Pan Xing) index
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
/** @type {FengShui.FengShuiContext.get_shengsi_mapping} */
|
||||
const get_shengsi_mapping = useCallback(
|
||||
params => {
|
||||
return (
|
||||
ready &&
|
||||
wasm_get_shengsi_mapping({
|
||||
...params,
|
||||
unpan_xing_chart: params.unpan_xing_chart || [
|
||||
5, 0, 7, 6, 4, 2, 1, 8, 3,
|
||||
],
|
||||
})
|
||||
);
|
||||
},
|
||||
[ready]
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// MAIN FOR THE CONTEXT PROVIDER
|
||||
// ================================================================
|
||||
|
||||
/** @private */
|
||||
const _set = useCallback(
|
||||
(prof = {}) => {
|
||||
const { localtime: current, direction, sector } = prof;
|
||||
|
||||
if (ready && current && direction) {
|
||||
const lichun_0 = get_lichun(current.year());
|
||||
const lichun = moment(lichun_0);
|
||||
const center = get_unpan_xing_index({ current, lichun });
|
||||
|
||||
const xgtu = get_xiaguatu_from_unpan_index({
|
||||
unpan_xing_center: center,
|
||||
xiang_xing_direction: direction,
|
||||
xiang_xing_sector: sector,
|
||||
});
|
||||
|
||||
setBazi(
|
||||
normalize_bazi_data(
|
||||
wasm_get_bazi(datetime_params_from_localtime(current))
|
||||
)
|
||||
);
|
||||
setLiChun(lichun);
|
||||
setUnPanXing(xgtu.unpan_xing);
|
||||
setShanXing(xgtu.shan_xing);
|
||||
setXiangXing(xgtu.xiang_xing);
|
||||
}
|
||||
},
|
||||
[ready, profile?.locatltime, profile?.direction]
|
||||
);
|
||||
|
||||
/**
|
||||
* @typedef FengShui.FengShuiContext.update
|
||||
* @function
|
||||
*/
|
||||
|
||||
/** @type {FengShui.FengShuiContext.update} */
|
||||
const update = useCallback(
|
||||
prof => {
|
||||
if (ready) {
|
||||
if (!has_valid_profile(prof)) {
|
||||
throw new Error(
|
||||
'[FengShuiContext] Need a valid profile for the argument'
|
||||
);
|
||||
}
|
||||
setProfile(prof);
|
||||
_set(prof);
|
||||
}
|
||||
},
|
||||
[ready, _set]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready !== true) {
|
||||
init(WASM_PATH)
|
||||
.then(() => {
|
||||
setReady(true);
|
||||
})
|
||||
.catch(err => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}, [props?.localtime, props?.direction]);
|
||||
|
||||
return (
|
||||
<FengShuiContext.Provider
|
||||
value={{
|
||||
ready,
|
||||
profile,
|
||||
bazi,
|
||||
lichun,
|
||||
unpan_xing,
|
||||
shan_xing,
|
||||
xiang_xing,
|
||||
update,
|
||||
get_bagua_start_north,
|
||||
get_twentyfour_direction_from_index,
|
||||
get_twentyfour_direction_from_degrees,
|
||||
get_twentyfour_data_from_index,
|
||||
get_twentyfour_data_from_direction,
|
||||
get_bazi,
|
||||
get_lichun,
|
||||
get_jiuxing_from_index,
|
||||
get_unpan_xing_index,
|
||||
get_xiaguatu_from_unpan_index,
|
||||
get_jiuxing_dipan_positions_from_direction,
|
||||
get_shengsi_mapping,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// useFengShui
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef FengShui.useFengShui
|
||||
* @function
|
||||
*/
|
||||
export const useFengShui = () => useContext(FengShuiContext);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// useFengShuiSync (hook)
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Syncing the latest primary profile to FengShuiContext.profile
|
||||
* @typedef FengShui.useFengShuiSync
|
||||
* @function
|
||||
*/
|
||||
export const useFengShuiSync = () => {
|
||||
const { profiles } = useProfiles();
|
||||
const { ready, update } = useFengShui();
|
||||
const [hasProfile, setHasProfile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const p = get_first_valid_profile_from_profiles(profiles);
|
||||
if (p) {
|
||||
const t = p.localtime.format('YYYY-MM-DD');
|
||||
const dir = `${p.direction}${p.sector}`;
|
||||
// console.log(`[FengShuiContext] (useFengShuiSync) localtime: ${t}`);
|
||||
// console.log(`[FengShuiContext] (useFengShuiSync) dir: ${dir}`);
|
||||
|
||||
setHasProfile(true);
|
||||
update(p);
|
||||
} else {
|
||||
// console.log('[FengShuiContext] No valid profiles...');
|
||||
setHasProfile(false);
|
||||
}
|
||||
}, [ready, profiles]);
|
||||
|
||||
return hasProfile;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// All the others
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} t - Moment object in localtime + explicit UTC offset
|
||||
*/
|
||||
function date_params_from_localtime(t) {
|
||||
if (!t) throw new Error("No 'localtime' specified for FengShui");
|
||||
|
||||
return {
|
||||
...DATE_KEYS.reduce((acc, key) => {
|
||||
acc[key] = int(t.format(DATETIME_FORMAT[key]));
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} t - Moment object in localtime + explicit UTC offset
|
||||
*/
|
||||
function datetime_params_from_localtime(t) {
|
||||
if (!t) throw new Error("No 'localtime' specified for FengShui");
|
||||
|
||||
return {
|
||||
...DATETIME_KEYS.reduce((acc, key) => {
|
||||
acc[key] = int(t.format(DATETIME_FORMAT[key]));
|
||||
return acc;
|
||||
}, {}),
|
||||
zone: get_utc_offset_in_hours(t),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function normalize_bazi_data(orig) {
|
||||
const hash = { __orig: { ...orig } };
|
||||
|
||||
// 'year', 'month', 'hour', 'day'
|
||||
BAZI_TYPE_KEYS.forEach(bazi_key => {
|
||||
hash[bazi_key] = {
|
||||
__obj: orig[bazi_key][bazi_key],
|
||||
};
|
||||
|
||||
// 'stem', 'branch'
|
||||
GANZHI_TYPE_KEYS.forEach(ganzhi_key => {
|
||||
hash[bazi_key][ganzhi_key] = {
|
||||
kanji: orig[bazi_key][ganzhi_key].name.zh_cn.alphabet,
|
||||
yomi: orig[bazi_key][ganzhi_key].name.ja.alphabet,
|
||||
};
|
||||
});
|
||||
|
||||
hash[
|
||||
bazi_key
|
||||
].kanji = `${hash[bazi_key].stem.kanji}${hash[bazi_key].branch.kanji}`;
|
||||
hash[
|
||||
bazi_key
|
||||
].yomi = `${hash[bazi_key].stem.yomi}${hash[bazi_key].branch.yomi}`;
|
||||
});
|
||||
|
||||
return hash;
|
||||
}
|
||||
134
docs/examples/bagua.jsx
Normal file
134
docs/examples/bagua.jsx
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* SVG Pie Drawing
|
||||
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
|
||||
*/
|
||||
import React, { useRef, useMemo, useEffect, useCallback } from 'react';
|
||||
import { compose, tap } from 'ramda';
|
||||
import tw, { css } from 'twin.macro';
|
||||
import Snap from 'snapsvg-cjs';
|
||||
|
||||
import { RADIAN_45, RADIAN_90, Z_INDEX_FENGSHUI_BAGUA } from '@/constants';
|
||||
import { int, rad_to_deg, normalize_angle, get_position } from '@/lib/utils';
|
||||
|
||||
import { useWorld } from '@/contexts/World';
|
||||
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
|
||||
|
||||
const FILL_COLOR = '#ef4444';
|
||||
const STROKE_COLOR = '#ffffff';
|
||||
const OUTER_RATIO = 0.8;
|
||||
|
||||
const wrapperStyle = css`
|
||||
${tw`absolute w-full flex flex-col justify-center items-center`}
|
||||
z-index: ${Z_INDEX_FENGSHUI_BAGUA};
|
||||
`;
|
||||
|
||||
export const FengShuiBagua = () => {
|
||||
const { worldInfo: world } = useWorld();
|
||||
const deviceOrientation = useDeviceOrientation();
|
||||
|
||||
const snap = useRef(null);
|
||||
|
||||
const chart_w = world?.chart?.width;
|
||||
const chart_h = world?.chart?.height;
|
||||
const alpha = deviceOrientation?.alpha;
|
||||
|
||||
const draw = useCallback(
|
||||
({ index }) => {
|
||||
const s = snap.current && Snap(snap.current);
|
||||
|
||||
if (s) {
|
||||
const beg = get_position(index * RADIAN_45, OUTER_RATIO);
|
||||
const end = get_position(index * RADIAN_45 + RADIAN_45, OUTER_RATIO);
|
||||
const large = end.deg - beg.deg > 180 ? 1 : 0;
|
||||
|
||||
// Remember, the whole wrapper is rotated later
|
||||
// by 112.5 (= 90 + 45/2), degrees so that
|
||||
// the first slice which originally sits
|
||||
// at the 3 o'clock position will come
|
||||
// to the 12 o'clock position.
|
||||
//
|
||||
// Also, we apply `viewbox="-1 -1 2 2"` for the SVG,
|
||||
// meaning, the origin of SVG is situated
|
||||
// at the very center of the wrapper.
|
||||
// That is to say, we have `x = 0`.
|
||||
// When we have `x = 1`, it means `x` is reaching
|
||||
// all the way at the right edge of the wrapper.
|
||||
//
|
||||
// For each slice, we use SVG's arc function
|
||||
// which is defined as:
|
||||
//
|
||||
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
||||
//
|
||||
// At first, we will first move (`M`) to where
|
||||
// it is close to the right edge of the screen.
|
||||
// If `OUTER_RATIO = 0.8`, it will probably
|
||||
// be like `M0.8,0`. Next, for the arc, we will
|
||||
// probably have `A0.8,0.8 0 0,1 0.56,0.56`.
|
||||
// So, it is an arc having `0.8` for the radius,
|
||||
// and it will draw the arc for 45 degrees.
|
||||
// Lastly, we have `L0,0` to go back to the origin,
|
||||
// otherwise, it won't fill (with given color)
|
||||
// the region surrounded by the path.
|
||||
|
||||
const path = [
|
||||
`M${beg.x},${beg.y}`,
|
||||
`A${OUTER_RATIO},${OUTER_RATIO} 0 ${large},1 ${end.x},${end.y}`,
|
||||
`L0,0`,
|
||||
].join(' ');
|
||||
|
||||
const fill =
|
||||
index === 0
|
||||
? {
|
||||
fill: FILL_COLOR,
|
||||
}
|
||||
: {};
|
||||
|
||||
s.path(path).attr({
|
||||
...fill,
|
||||
stroke: STROKE_COLOR,
|
||||
strokeWidth: world?.stroke_size,
|
||||
});
|
||||
}
|
||||
},
|
||||
[chart_w]
|
||||
);
|
||||
|
||||
const baguaStyle = useMemo(() => {
|
||||
const rotation = normalize_angle(
|
||||
alpha - rad_to_deg(Math.PI / 2 + RADIAN_45 / 2)
|
||||
);
|
||||
return css`
|
||||
width: ${chart_w}px;
|
||||
height: ${chart_h}px;
|
||||
transform: rotate(${rotation}deg);
|
||||
`;
|
||||
}, [chart_w, alpha]);
|
||||
|
||||
useEffect(() => {
|
||||
const s = snap.current && Snap(snap.current);
|
||||
if (s) {
|
||||
[...new Array(8)].map((_, i) => {
|
||||
draw({ index: i });
|
||||
});
|
||||
}
|
||||
}, [chart_w, draw]);
|
||||
|
||||
return (
|
||||
<div id="bagua-wrapper" css={wrapperStyle}>
|
||||
<svg id="bagua" ref={snap} viewBox="-1 -1 2 2" css={baguaStyle}></svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// const pos = [];
|
||||
// pos[0] = get_position(index * RADIAN_45, OUTER_RATIO);
|
||||
// pos[1] = get_position(index * RADIAN_45 + RADIAN_45, OUTER_RATIO);
|
||||
// pos[2] = get_position(index * RADIAN_45 + RADIAN_45, INNER_RATIO);
|
||||
// pos[3] = get_position(index * RADIAN_45, INNER_RATIO);
|
||||
//
|
||||
// const path = [
|
||||
// `M${pos[0].x},${pos[0].y}`,
|
||||
// `L${pos[1].x},${pos[1].y}`,
|
||||
// `L${pos[2].x},${pos[2].y}`,
|
||||
// `L${pos[3].x},${pos[3].y}`,
|
||||
// ].join(' ');
|
||||
115
docs/examples/bagua_info.jsx
Normal file
115
docs/examples/bagua_info.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* SVG Pie Drawing
|
||||
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
|
||||
*/
|
||||
import React, {
|
||||
useRef,
|
||||
useState,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { compose, tap } from 'ramda';
|
||||
import tw, { css } from 'twin.macro';
|
||||
|
||||
import { RADIAN_45, RADIAN_90, Z_INDEX_FENGSHUI_BAGUA_INFO } from '@/constants';
|
||||
import {
|
||||
fixed,
|
||||
deg_to_rad,
|
||||
get_position_clock,
|
||||
gen_code_12,
|
||||
} from '@/lib/utils';
|
||||
|
||||
import { useWorld } from '@/contexts/World';
|
||||
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
|
||||
import { useFengShui } from '@/contexts/FengShui';
|
||||
import { BaguaIcon } from '@/components/icons/bagua';
|
||||
|
||||
const RADIUS_RATIO = 0.66;
|
||||
const ICON_RATIO = 0.094;
|
||||
const RADIAN_45_HALF = RADIAN_45 / 2;
|
||||
|
||||
const fix2 = fixed(2);
|
||||
const fix3 = fixed(3);
|
||||
|
||||
export const FengShuiBaguaInfo = () => {
|
||||
const { worldInfo: world } = useWorld();
|
||||
const deviceOrientation = useDeviceOrientation();
|
||||
const { ready, get_bagua_start_north } = useFengShui();
|
||||
|
||||
const [boxes, setBoxes] = useState([]);
|
||||
|
||||
const chart_w = world?.chart?.width;
|
||||
const chart_h = world?.chart?.height;
|
||||
const alpha = deviceOrientation?.alpha || 0;
|
||||
|
||||
const wrapperStyle = useMemo(
|
||||
() => css`
|
||||
${tw`absolute text-white`}
|
||||
width: ${chart_w}px;
|
||||
height: ${chart_h}px;
|
||||
`,
|
||||
[chart_w]
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
index => {
|
||||
const chart_w_half = chart_w / 2;
|
||||
const chart_h_half = chart_h / 2;
|
||||
const icon_size = fix3(chart_w_half * ICON_RATIO);
|
||||
const angle = index * RADIAN_45 + deg_to_rad(alpha);
|
||||
const radius = chart_w_half * RADIUS_RATIO;
|
||||
const pos = get_position_clock(angle, radius, {
|
||||
x: chart_w_half,
|
||||
y: chart_h_half,
|
||||
});
|
||||
const z_index = Z_INDEX_FENGSHUI_BAGUA_INFO + index * 1;
|
||||
const style = css`
|
||||
${tw`absolute flex flex-col justify-center items-center`}
|
||||
top: ${fix2(pos.y)}px;
|
||||
left: ${fix2(pos.x)}px;
|
||||
z-index: ${z_index};
|
||||
font-size: ${world?.text_lg}px;
|
||||
line-height: 1em;
|
||||
transform: translate(-50%, -50%);
|
||||
`;
|
||||
return { icon_size, style };
|
||||
},
|
||||
[chart_w, alpha]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready) {
|
||||
setBoxes(
|
||||
[...new Array(8)].map((_, index) => {
|
||||
const bagua = get_bagua_start_north(index);
|
||||
const name = bagua?.name;
|
||||
return name
|
||||
? {
|
||||
key: gen_code_12(),
|
||||
en: name.en,
|
||||
ch: name.zh_cn?.alphabet,
|
||||
}
|
||||
: {};
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [ready]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && chart_w > 0) {
|
||||
setBoxes(prev => prev.map((p, i) => ({ ...p, ...update(i) })));
|
||||
}
|
||||
}, [ready, chart_w, alpha, update]);
|
||||
|
||||
return (
|
||||
<div id="bagua-info-wrapper" css={wrapperStyle}>
|
||||
{boxes.map((box, i) => (
|
||||
<div id={`bagua-info-box-${i}`} key={box.key} css={box.style}>
|
||||
<BaguaIcon name={box.en} styles={{ size: box.icon_size }} />
|
||||
<div tw="mt-1">{box.ch}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
50
docs/examples/chart.jsx
Normal file
50
docs/examples/chart.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import tw, { css } from 'twin.macro';
|
||||
|
||||
import { gen_code_12 } from '@/lib/utils';
|
||||
import { useErrors } from '@/contexts/Errors';
|
||||
import { useWorld } from '@/contexts/World';
|
||||
import { useFengShuiSync } from '@/contexts/FengShui';
|
||||
|
||||
import { FengShuiTwentyFour } from '@/components/fengshui/twentyfour';
|
||||
import { FengShuiTwentyFourInfo } from '@/components/fengshui/twentyfour_info';
|
||||
import { FengShuiCircle } from '@/components/fengshui/circle';
|
||||
import { FengShuiNorth } from '@/components/fengshui/north';
|
||||
import { FengShuiJiuXing } from '@/components/fengshui/jiuxing';
|
||||
|
||||
const wrapperStyleBase = tw`flex-none relative overflow-hidden w-full flex flex-col justify-center items-center`;
|
||||
|
||||
export const ChartChart = () => {
|
||||
const { errors } = useErrors();
|
||||
const { worldInfo: world } = useWorld();
|
||||
|
||||
const chart_w = world?.chart?.width;
|
||||
const chart_h = world?.chart?.height;
|
||||
|
||||
const wrapperStyle = useMemo(
|
||||
() => css`
|
||||
width: ${chart_w || 0}px;
|
||||
height: ${chart_h || 0}px;
|
||||
`,
|
||||
[chart_w]
|
||||
);
|
||||
|
||||
// Sync the latest primary profile to FengShuiContext.profile
|
||||
useFengShuiSync();
|
||||
|
||||
return errors.length ? (
|
||||
<div id="chart-main-wrapper" tw="text-white text-xs">
|
||||
{errors.map(err => (
|
||||
<div key={err.key}>{err.error}</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div id="chart-main-wrapper" css={[wrapperStyleBase, wrapperStyle]}>
|
||||
<FengShuiTwentyFour key={gen_code_12()} />
|
||||
<FengShuiTwentyFourInfo key={gen_code_12()} />
|
||||
<FengShuiCircle key={gen_code_12()} ratio={0.33} />
|
||||
<FengShuiNorth key={gen_code_12()} />
|
||||
<FengShuiJiuXing key={gen_code_12()} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
298
docs/examples/index.md
Normal file
298
docs/examples/index.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# Examples
|
||||
|
||||
[1. Example Rust + React Codes](#1-example-rust-react-codes)
|
||||
[2. Setups for WASM Apps in Webpack](#2-setups-for-wasm-apps-in-webpack)
|
||||
[3. Using WASM Apps from React](#3-using-wasm-apps-from-react)
|
||||
[4. Feng-Shui Examples](#4-feng-shui-examples)
|
||||
[4-1. `src/contexts/FengShui.js`](#4-1-srccontextsfengshuijs)
|
||||
[4-2. `src/components/fengshui/jiuxing.jsx`](#4-2-srccomponentsfengshuijiuxingjsx)
|
||||
|
||||
## 1. Example Rust + React Codes
|
||||
|
||||
I picked up some files from one of the real-world projects of mine.
|
||||
In this project, I have Rust codes in `src_for_wasm` and React codes in `src`.
|
||||
|
||||
Rust
|
||||
- [src_for_wasm/Cargo.toml](./Cargo.toml)
|
||||
- [src_for_wasm/lib.rs](./lib.rs)
|
||||
Acts like a proxy to all the public functions provided by _mikaboshi_.
|
||||
|
||||
React
|
||||
- [src/contexts/FengShui.js](FengShui.js)
|
||||
A context provider set to the React app top, and delegates functions provided by `src_for_wasm/lib.rs`.
|
||||
- [src/components/chart/chart.jsx](chart.jsx)
|
||||
Provides a layout for Feng-Shui chart which contains a several child components such as `src/components/fengshui/jiuxing.jsx`, `src/components/twentyfour.jsx`, and so forth.
|
||||
- [src/components/fengshui/bagua_info.jsx](bagua_info.jsx)
|
||||
- [src/components/fengshui/bagua.jsx](bagua.jsx)
|
||||
- [src/components/fengshui/jiuxing.jsx](jiuxing.jsx)
|
||||
- [src/components/fengshui/north.jsx](north.jsx)
|
||||
- [src/components/fengshui/twentyfour_info.jsx](twentyfour_info.jsx)
|
||||
- [src/components/fengshui/twentyfour.jsx](twentyfour.jsx)
|
||||
- [src/lib/utils.js](utils.js)
|
||||
|
||||
|
||||
## 2. Setups for WASM Apps in Webpack
|
||||
|
||||
For how you can serve a WASM app, you may want to check out
|
||||
one of my other projects,
|
||||
_[perlin-experiment](https://github.com/minagawah/perlin-experiment)_.
|
||||
Setups are about the same.
|
||||
|
||||
|
||||
## 3. Using WASM Apps from React
|
||||
|
||||
When using a WASM app from React, you need to first
|
||||
asynchronously wait for the module to be ready.
|
||||
|
||||
```js
|
||||
import React, { useContext, createContext, useEffect, useState } from 'react';
|
||||
import init, { order_pizza } from 'wasm-pizza';
|
||||
|
||||
const WASM_PATH =
|
||||
NODE_ENV === 'production'
|
||||
? 'wasm/wasm-pizza/wasm-pizza_bg.wasm'
|
||||
: void 0;
|
||||
|
||||
const PizzaContext = createContext({
|
||||
ready: false,
|
||||
orderPizza: () => {},
|
||||
});
|
||||
|
||||
export const PizzaProvider = () => {
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready !== true) {
|
||||
init(WASM_PATH)
|
||||
.then(() => {
|
||||
setReady(true);
|
||||
})
|
||||
.catch(err => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const orderPizza = params => {
|
||||
return order_pizza(params);
|
||||
};
|
||||
|
||||
return (
|
||||
<PizzaContext.Provider
|
||||
value={{
|
||||
ready,
|
||||
orderPizza,
|
||||
}}
|
||||
/>
|
||||
};
|
||||
|
||||
export const usePizza = () => useContext(PizzaContext);
|
||||
```
|
||||
|
||||
Notice that we import `init` from `wasm-pizza` which is a compiled WASM app
|
||||
provided as a NPM module. As mentioned in the previous section,
|
||||
[take a look at one of my projects](https://github.com/minagawah/perlin-experiment)
|
||||
for it explains how.
|
||||
|
||||
Now, you may use the provider:
|
||||
|
||||
`src/App.jsx`
|
||||
|
||||
```js
|
||||
ReactDOM.render(
|
||||
<PizzaProvider>
|
||||
<App />
|
||||
</PizzaProvider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
From one of your components, you call the method:
|
||||
|
||||
```js
|
||||
import { usePizza } from '@/contexts/Pizza';
|
||||
|
||||
export const Order = () => {
|
||||
const { ready, orderPizza } = usePizza();
|
||||
const [pizza, setPizza] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setPizza(
|
||||
orderPizza()
|
||||
);
|
||||
}, [ready])
|
||||
|
||||
return <div>{pizza}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## 4. Feng-Shui Examples
|
||||
|
||||
For example files provided at the beginning,
|
||||
I will explain key features found in the codes.
|
||||
Although it is the usage from React,
|
||||
I believe you will at least get ideas
|
||||
when using _mikaboshi_ for your projects.
|
||||
|
||||
|
||||
### 4-1. `src/contexts/FengShui.js`
|
||||
|
||||
Source: [src/contexts/FengShui.js](FengShui.js)
|
||||
|
||||
`src/contexts/FengShui.js` is a React context provider,
|
||||
and it provides `FengShuiContext`.
|
||||
For resources provided by `FengShuiContext`
|
||||
will be accessible for any child components
|
||||
when using `useFengShui()`.
|
||||
|
||||
```js
|
||||
const FengShuiContext = createContext({
|
||||
ready: false,
|
||||
profile: null, // localtime, direction, sector
|
||||
bazi: null,
|
||||
lichun: null,
|
||||
unpan_xing: null, // 運盤星
|
||||
shan_xing: null, // 山星
|
||||
xiang_xing: null, // 向星
|
||||
update: noop,
|
||||
...
|
||||
...
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
`src/contexts/FengShui.js` loads a WASM app (`src_for_wasm/lib/rs`).
|
||||
Since it loads the WASM app asynchronously,
|
||||
components must wait for `ready` to become `true`
|
||||
for all the features to become available.
|
||||
|
||||
For `profile` is not actually peculiar to `src/contexts/FengShui.js`,
|
||||
but is something managed in another provider `src/contexts/Profiles.js`.
|
||||
Since there is no way for 2 providers to communicate,
|
||||
we run `useFengShuiSync()` somewhere in one of the components
|
||||
to sync the contents of `profile`.
|
||||
|
||||
`_set()` runs when the content of `profile` changes:
|
||||
|
||||
```js
|
||||
const _set = useCallback(
|
||||
(prof = {}) => {
|
||||
const { localtime: current, direction, sector } = prof;
|
||||
|
||||
if (ready && current && direction) {
|
||||
const lichun_0 = get_lichun(current.year());
|
||||
const lichun = moment(lichun_0);
|
||||
const center = get_unpan_xing_index({ current, lichun });
|
||||
|
||||
const xgtu = get_xiaguatu_from_unpan_index({
|
||||
unpan_xing_center: center,
|
||||
xiang_xing_direction: direction,
|
||||
xiang_xing_sector: sector,
|
||||
});
|
||||
|
||||
setBazi(
|
||||
normalize_bazi_data(
|
||||
wasm_get_bazi(datetime_params_from_localtime(current))
|
||||
)
|
||||
);
|
||||
setLiChun(lichun);
|
||||
setUnPanXing(xgtu.unpan_xing);
|
||||
setShanXing(xgtu.shan_xing);
|
||||
setXiangXing(xgtu.xiang_xing);
|
||||
}
|
||||
},
|
||||
[ready, profile?.locatltime, profile?.direction]
|
||||
);
|
||||
```
|
||||
|
||||
In the above, using _mikaboshi_'s `get_xiaguatu_from_unpan_index()`
|
||||
to obtain 下卦図 (Xia-Gua-Tu). For 3 arguments required,
|
||||
we already have `xiang_xing_direction` and `xiang_xing_sector`
|
||||
in `profile`, but for `unpan_xing_center` needs a preparation.
|
||||
|
||||
For `unpan_xing_center`, we run `get_unpan_xing_index()`.
|
||||
And, again, it requires another preparation for `lichun`,
|
||||
and we run `get_lichun()` for `lichun`.
|
||||
|
||||
|
||||
### 4-2. `src/components/fengshui/jiuxing.jsx`
|
||||
|
||||
Source: [src/components/fengshui/jiuxing.jsx](jiuxing.jsx)
|
||||
|
||||
`jiuxing.jsx` is one of the child components wrapped in `FengShuiContext` provider.
|
||||
Here is how it starts:
|
||||
|
||||
```js
|
||||
export const FengShuiJiuXing = () => {
|
||||
const { worldInfo: world } = useWorld();
|
||||
const deviceOrientation = useDeviceOrientation();
|
||||
const {
|
||||
ready,
|
||||
unpan_xing,
|
||||
shan_xing,
|
||||
xiang_xing,
|
||||
get_xiaguatu_from_unpan_index,
|
||||
get_jiuxing_dipan_positions_from_direction,
|
||||
get_twentyfour_direction_from_degrees,
|
||||
get_shengsi_mapping,
|
||||
} = useFengShui();
|
||||
```
|
||||
|
||||
As mentioned, using `useFengShui()`, will get you an access
|
||||
for resources provided by `FengShuiContext`.
|
||||
|
||||
Let's continue.
|
||||
|
||||
```js
|
||||
const { direction: curr_dir } = get_twentyfour_direction_from_degrees(
|
||||
360 - alpha
|
||||
);
|
||||
```
|
||||
|
||||
Now, we are using `get_twentyfour_direction_from_degrees()`.
|
||||
|
||||
For a given angle (in degrees),
|
||||
`get_twentyfour_direction_from_degrees()`
|
||||
returns `direction` and `sector`.
|
||||
|
||||
`direction` is a compass direction represented
|
||||
in a lower case string (e.g. `n`, `ne`, `e`, `se`, etc.).
|
||||
|
||||
`sector` is special concept unique to
|
||||
[二十四山向 (Er-Shi-Si Shan-Xiang)](../compass.md).
|
||||
For each compass direction is further divided into 3 sectors,
|
||||
represented in a number `1`, `2`, or `3`.
|
||||
|
||||
Yet, for the above example, this time,
|
||||
we are getting `direction` only,
|
||||
and naming it: `curr_dir`
|
||||
|
||||
```js
|
||||
const u_id = unpan_xing.center;
|
||||
|
||||
// When calculating for 下卦図 (Xia-Gua-Tu), not only
|
||||
// the current 運盤星 (Un-Pan Xing), but we also want
|
||||
// all 九星 (Jiu-Xing) in the 洛書 (Lo-Shu) order.
|
||||
// Although we have `JIU_XING_DI_PAN_POSITIONS`
|
||||
// which defines 洛書 (Lo-Shu) order, we want it
|
||||
// in re-arranged order for the current device rotation,
|
||||
// and that is what we pass for the second argument
|
||||
// of `get_xiaguatu_from_unpan_index()`.
|
||||
const u_order = get_jiuxing_dipan_positions_from_direction(curr_dir);
|
||||
|
||||
// Now, calculate for 下卦図 (Xia-Gua-Tu).
|
||||
const xiagua = get_xiaguatu_from_unpan_index({
|
||||
unpan_xing_center: u_id,
|
||||
unpan_xing_order: u_order,
|
||||
xiang_xing_direction: xiang_xing.direction,
|
||||
xiang_xing_sector: xiang_xing.sector,
|
||||
});
|
||||
```
|
||||
|
||||
As mentioned before, `unpan_xing` (運盤星; Un-Pan Xing)
|
||||
is managed in `src/contexts/FengShui.js`,
|
||||
and we simply want to refer to it.
|
||||
The same goes for `shan_xing` (山星; Shan-Xing),
|
||||
and `xiang_xing` (向星; Xiang-Xing),
|
||||
and we expect that we already have the values
|
||||
stored in `src/contexts/FengShui.js`.
|
||||
258
docs/examples/jiuxing.jsx
Normal file
258
docs/examples/jiuxing.jsx
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* This component renders 下卦図 (Xia-Gua-Tu)
|
||||
* (also known as 飞星図; Fei-Xing-Tu; "Flying Star Chart").
|
||||
* Like any other Feng-Shui charts, it consists of 9 boxes.
|
||||
* Each box holds 3 different kinds of 九星 (Jiu-Xing),
|
||||
* namely,
|
||||
* 運盤星 (Un-Pan Xing) at the BOTTOM CENTER,
|
||||
* 山星 (Shan-Xing) at the TOP LEFT, and
|
||||
* 向星 (Xiang-Xing) at the TOP RIGHT.
|
||||
* It also displays (in each box) the star's
|
||||
* 生死衰旺 (Sheng-Si Shuai-Wang) status.
|
||||
*/
|
||||
import React, {
|
||||
useRef,
|
||||
useState,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { compose, tap } from 'ramda';
|
||||
import tw, { css } from 'twin.macro';
|
||||
|
||||
import { Z_INDEX_FENGSHUI_JIU_XING_BOXES } from '@/constants';
|
||||
import { fixed, gen_code_12 } from '@/lib/utils';
|
||||
|
||||
import { useWorld } from '@/contexts/World';
|
||||
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
|
||||
import { useFengShui } from '@/contexts/FengShui';
|
||||
|
||||
const BOX_RATIO = 0.55;
|
||||
|
||||
const SHENG_SI_COLOR = {
|
||||
si: tw`bg-tomato-dark text-gray-100`,
|
||||
shuai: tw`bg-abura-dark text-gray-100`,
|
||||
};
|
||||
|
||||
const fix2 = fixed(2);
|
||||
|
||||
// This is the inner most wrapper which holds 9 boxes.
|
||||
// As you can see, we have 3 rows and 3 columns.
|
||||
const wrapperStyle = css`
|
||||
width: calc(100% - 2px);
|
||||
height: calc(100% - 2px);
|
||||
display: grid;
|
||||
grid-gap: 1px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
`;
|
||||
|
||||
// This is the wrapper for each box.
|
||||
// As you can see, it has 2 rows and 2 columns.
|
||||
// However, the bottom row will fill up
|
||||
// using all 2 columns, and 運盤星 (Un-Pan Xing)
|
||||
// is placed in the middle.
|
||||
const boxStyle = css`
|
||||
${tw`text-gray-900 text-sm`}
|
||||
display: grid;
|
||||
grid-gap: 1px;
|
||||
grid-template-columns: repeat(2, 0.5fr);
|
||||
grid-template-rows: 0.333fr 0.666fr;
|
||||
`;
|
||||
|
||||
const xingStyle = tw`
|
||||
flex flex-col justify-center items-center text-sm font-medium
|
||||
`;
|
||||
|
||||
export const FengShuiJiuXing = () => {
|
||||
const { worldInfo: world } = useWorld();
|
||||
const deviceOrientation = useDeviceOrientation();
|
||||
const {
|
||||
ready,
|
||||
unpan_xing,
|
||||
shan_xing,
|
||||
xiang_xing,
|
||||
get_xiaguatu_from_unpan_index,
|
||||
get_jiuxing_dipan_positions_from_direction,
|
||||
get_twentyfour_direction_from_degrees,
|
||||
get_shengsi_mapping,
|
||||
} = useFengShui();
|
||||
|
||||
const [flyingStarChart, setFlyingStarChart] = useState([]);
|
||||
|
||||
const chart_w = world?.chart?.width;
|
||||
const chart_h = world?.chart?.height;
|
||||
const body_h = world?.body?.height;
|
||||
|
||||
// Passing current degrees for the device, and obtaining
|
||||
// the compass direction: "n", "ne", "e", "se", etc.
|
||||
const alpha = deviceOrientation?.alpha || 0;
|
||||
|
||||
// `alpha` is a value that you get from Web API,
|
||||
// but what you get for `alpha` is counterintuitive...
|
||||
// When you have 10 degrees, it means that your device
|
||||
// is not pointing toward the compass NE, but NW.
|
||||
// When the device is pointing NW, you will see
|
||||
// the red arrow (which usually points the magnetic N)
|
||||
// will come to the top right. There, getting 10 degrees
|
||||
// means that your device is rotating counter-clockwise,
|
||||
// but we want the opposite. When it gives us 10 degrees,
|
||||
// we want it to mean it is rotating clockwise,
|
||||
// and the device to be pointing NE (instead of NW).
|
||||
// That is why we are passing the complementary angle
|
||||
// as a function argument (by subtracting the degree
|
||||
// from 360).
|
||||
const { direction: curr_dir } = get_twentyfour_direction_from_degrees(
|
||||
360 - alpha
|
||||
);
|
||||
|
||||
// This is the outer most wrapper, and is positioned `absolute`.
|
||||
const wrapperWrapperWrapperStyle = useMemo(
|
||||
() => css`
|
||||
${tw`absolute w-full flex flex-col justify-center items-center`}
|
||||
height: ${fix2(body_h)}px;
|
||||
z-index: ${Z_INDEX_FENGSHUI_JIU_XING_BOXES};
|
||||
`,
|
||||
[body_h]
|
||||
);
|
||||
|
||||
// The 2nd wrapper which basically defines width and height,
|
||||
// and place the inner wrapper vertically and horizontally
|
||||
// in the center.
|
||||
const wrapperWrapperStyle = useMemo(() => {
|
||||
const width = chart_w * BOX_RATIO;
|
||||
return css`
|
||||
${tw`flex flex-col justify-center items-center bg-gray-900`}
|
||||
width: ${fix2(width)}px;
|
||||
height: ${fix2(width)}px;
|
||||
`;
|
||||
}, [chart_w]);
|
||||
|
||||
// As long as you have information for the current
|
||||
// 運盤星 (Un-Pan Xing) and 向星 (Xiang-Xing),
|
||||
// you can have `get_xiaguatu_from_unpan_index()`
|
||||
// calculates 下卦図 (Xia-Gua-Tu) for you.
|
||||
// Once you obtain 下卦図 (Xia-Gua-Tu)
|
||||
// (expressed as `xiagua` in the program),
|
||||
// you will get charts for all 3 stars.
|
||||
// For 生死衰旺 (Sheng-Si Shuai-Wang) is
|
||||
// calculated from 運盤星 (Un-Pan Xing) only.
|
||||
useEffect(() => {
|
||||
if (ready && unpan_xing && shan_xing && xiang_xing) {
|
||||
// console.log('[fengshui/jiuxing] curr_dir: ', curr_dir);
|
||||
|
||||
// The current 運盤星 (Un-Pan Xing) holds the key to everything!
|
||||
const u_id = unpan_xing.center;
|
||||
|
||||
// When calculating for 下卦図 (Xia-Gua-Tu), not only
|
||||
// the current 運盤星 (Un-Pan Xing), but we also want
|
||||
// all 九星 (Jiu-Xing) in the 洛書 (Lo-Shu) order.
|
||||
// Although we have `JIU_XING_DI_PAN_POSITIONS`
|
||||
// which defines 洛書 (Lo-Shu) order, we want it
|
||||
// in re-arranged order for the current device rotation,
|
||||
// and that is what we pass for the second argument
|
||||
// of `get_xiaguatu_from_unpan_index()`.
|
||||
const u_order = get_jiuxing_dipan_positions_from_direction(curr_dir);
|
||||
|
||||
// Now, calculate for 下卦図 (Xia-Gua-Tu).
|
||||
const xiagua = get_xiaguatu_from_unpan_index({
|
||||
unpan_xing_center: u_id,
|
||||
unpan_xing_order: u_order,
|
||||
xiang_xing_direction: xiang_xing.direction,
|
||||
xiang_xing_sector: xiang_xing.sector,
|
||||
});
|
||||
|
||||
const u_chart = xiagua?.unpan_xing?.chart || [];
|
||||
const s_chart = xiagua?.shan_xing?.chart || [];
|
||||
const x_chart = xiagua?.xiang_xing?.chart || [];
|
||||
|
||||
if (u_chart.length > 0 && s_chart.length > 0 && x_chart.length > 0) {
|
||||
// 生死衰旺 (Sheng-Si Shuai-Wang) for the current 運盤星 (Un-Pan Xing).
|
||||
const shengsi = get_shengsi_mapping({
|
||||
unpan_id: u_id,
|
||||
unpan_xing_chart: u_chart,
|
||||
});
|
||||
|
||||
let arr = [];
|
||||
|
||||
// Iterating for 9 boxes, and for each box, we have 3 stars.
|
||||
// For each star, we are simply adding 1 to the star's "index"
|
||||
// to get the star's "number" which is to be displayed.
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const kanji = shengsi[i]?.kanji;
|
||||
|
||||
arr.push({
|
||||
key: gen_code_12(),
|
||||
style: SHENG_SI_COLOR[kanji] || tw`bg-cream`,
|
||||
unpan_xing: { num: u_chart[i] + 1, kanji },
|
||||
shan_xing: { num: s_chart[i] + 1 },
|
||||
xiang_xing: { num: x_chart[i] + 1 },
|
||||
});
|
||||
}
|
||||
|
||||
if (arr.length > 0) {
|
||||
setFlyingStarChart(arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
ready,
|
||||
unpan_xing?.center,
|
||||
shan_xing?.center,
|
||||
xiang_xing?.center,
|
||||
curr_dir,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div id="jiuxing-wrapper-wrapper-wrapper" css={wrapperWrapperWrapperStyle}>
|
||||
<div id="jiuxing-wrapper-wrapper" css={wrapperWrapperStyle}>
|
||||
<div id="jiuxing-wrapper" css={wrapperStyle}>
|
||||
{flyingStarChart.map((box, i) => (
|
||||
<div key={box.key} id={`box-${i}`} css={[boxStyle, box.style]}>
|
||||
<div
|
||||
id={`shan-xing-${i}`}
|
||||
css={[
|
||||
xingStyle,
|
||||
css`
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1 / 2;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
{box.shan_xing.num}
|
||||
</div>
|
||||
|
||||
<div
|
||||
id={`xiang-xing-${i}`}
|
||||
css={[
|
||||
xingStyle,
|
||||
css`
|
||||
grid-column: 2 / -1;
|
||||
grid-row: 1 / 2;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
{box.xiang_xing.num}
|
||||
</div>
|
||||
|
||||
<div
|
||||
id={`un-pan-${i}`}
|
||||
css={[
|
||||
xingStyle,
|
||||
css`
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2 / -1;
|
||||
justify-content: flex-start;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
<div>{box.unpan_xing.num}</div>
|
||||
<div tw="text-xs font-bold">{box.unpan_xing.kanji}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
201
docs/examples/lib.rs
Normal file
201
docs/examples/lib.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
use mikaboshi::bagua::{get_bagua_start_north as _bagua_start_north, Bagua};
|
||||
use mikaboshi::compass::{
|
||||
get_direction_positions_in_chart as _direction_positions_in_chart,
|
||||
get_opposite_direction as _opposite_direction,
|
||||
get_twentyfour_data_from_direction as _twentyfour_data_from_direction,
|
||||
get_twentyfour_data_from_index as _twentyfour_data_from_index,
|
||||
get_twentyfour_direction_from_degrees as _twentyfour_direction_from_degrees,
|
||||
get_twentyfour_direction_from_index as _twentyfour_direction_from_index, Direction,
|
||||
TwentyFourType,
|
||||
};
|
||||
use mikaboshi::ganzhi::Bazi;
|
||||
use mikaboshi::jiuxing::{
|
||||
get_jiuxing_dipan_positions_from_direction as _jiuxing_dipan_positions_from_direction,
|
||||
get_jiuxing_from_index as _jiuxing_from_index,
|
||||
get_xiaguatu_from_unpan_index as _xiaguatu_from_unpan_index,
|
||||
unpan_xing_index as _unpan_xing_index, JiuXing, XiaGuaTu,
|
||||
};
|
||||
use mikaboshi::shengsi::{get_shengsi_mapping as _get_shengsi_mapping, ShengSi};
|
||||
use mikaboshi::solar_terms::get_lichun as _get_lichun;
|
||||
use mikaboshi::time::{Date, DateTime};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{From, TryInto};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// use log::info;
|
||||
// use log::Level;
|
||||
|
||||
pub mod structs;
|
||||
|
||||
use crate::structs::{DateParams, DateTimeParams, ShengSiParams, XiaGuaTuParams};
|
||||
|
||||
/// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global allocator.
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 八卦 (Bagua)
|
||||
// ================================================================
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_bagua_start_north(index: usize) -> JsValue {
|
||||
let bagua: Option<&Bagua> = _bagua_start_north(index);
|
||||
JsValue::from_serde(&bagua).unwrap()
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 二十四山向 (Er-Shi Si-Shan Xiang)
|
||||
// ================================================================
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_twentyfour_direction_from_index(index: usize) -> JsValue {
|
||||
let dir: &Direction = _twentyfour_direction_from_index(index);
|
||||
JsValue::from_serde(dir).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_twentyfour_data_from_index(index: usize) -> JsValue {
|
||||
let t_type: TwentyFourType = _twentyfour_data_from_index(index);
|
||||
match t_type {
|
||||
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
|
||||
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
|
||||
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_twentyfour_direction_from_degrees(degrees: f32) -> JsValue {
|
||||
let dir: Direction = _twentyfour_direction_from_degrees(degrees);
|
||||
// log(&format!("[wasm] degrees: {}", degrees));
|
||||
// log(&format!("[wasm] dir: {:?}", dir));
|
||||
JsValue::from_serde(&dir).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_twentyfour_data_from_direction(direction: &str, sector: usize) -> JsValue {
|
||||
let t_type: TwentyFourType = _twentyfour_data_from_direction(direction, sector);
|
||||
match t_type {
|
||||
TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
|
||||
TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
|
||||
TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 干支 (Gan-Zhi)
|
||||
// ================================================================
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_bazi(params: &JsValue) -> JsValue {
|
||||
let params: DateTimeParams = params.into_serde().unwrap();
|
||||
let localtime = DateTime::from(¶ms);
|
||||
let zone = params.zone;
|
||||
JsValue::from_serde(&Bazi::from_local(&localtime, zone)).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_lichun(year: i16) -> JsValue {
|
||||
// log(&format!("{:?}", year));
|
||||
|
||||
let lichun = _get_lichun(year);
|
||||
JsValue::from_str(&format!(
|
||||
"{:04}-{:02}-{:02}",
|
||||
lichun.year as u16, lichun.month as u8, lichun.day as u8
|
||||
))
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 九星 (Jiu-Xing)
|
||||
// ================================================================
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_jiuxing_from_index(index: usize) -> JsValue {
|
||||
let dir: &JiuXing = _jiuxing_from_index(index);
|
||||
JsValue::from_serde(dir).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_unpan_xing_index(current: &JsValue, lichun: &JsValue) -> JsValue {
|
||||
let params_1: DateParams = current.into_serde().unwrap();
|
||||
let params_2: DateParams = lichun.into_serde().unwrap();
|
||||
// log(&format!("params_1: {:?}", params_1));
|
||||
// log(&format!("params_2: {:?}", params_2));
|
||||
|
||||
let current = Date::from(¶ms_1);
|
||||
let lichun = Date::from(¶ms_2);
|
||||
|
||||
let index: usize = _unpan_xing_index(¤t, &lichun);
|
||||
JsValue::from_f64(index as f64)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_xiaguatu_from_unpan_index(params: &JsValue) -> JsValue {
|
||||
let params: XiaGuaTuParams = params.into_serde().unwrap();
|
||||
// log("[wasm] get_xiaguatu_from_unpan_index()");
|
||||
// log(&format!("[wasm] params: {:?}", params));
|
||||
|
||||
let unpan_xing_order: [usize; 9] =
|
||||
params
|
||||
.unpan_xing_order
|
||||
.try_into()
|
||||
.unwrap_or_else(|v: Vec<usize>| {
|
||||
panic!("Expected a Vec of length 9 but it was {}", v.len())
|
||||
});
|
||||
|
||||
let xia_gua_tu: HashMap<&str, XiaGuaTu> = _xiaguatu_from_unpan_index(
|
||||
params.unpan_xing_center,
|
||||
&unpan_xing_order,
|
||||
params.xiang_xing_direction.as_str(),
|
||||
params.xiang_xing_sector,
|
||||
);
|
||||
|
||||
JsValue::from_serde(&xia_gua_tu).unwrap()
|
||||
}
|
||||
|
||||
// A simple accessor for getting values in JIU_XING_DI_PAN_POSITIONS.
|
||||
#[wasm_bindgen]
|
||||
pub fn get_jiuxing_dipan_positions_from_direction(direction: &str) -> JsValue {
|
||||
JsValue::from(
|
||||
(match _jiuxing_dipan_positions_from_direction(direction) {
|
||||
Some(positions) => positions.to_vec(),
|
||||
_ => Vec::new(),
|
||||
})
|
||||
.into_iter()
|
||||
.map(|index| JsValue::from(index as u32))
|
||||
.collect::<js_sys::Array>(),
|
||||
)
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
// ================================================================
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_shengsi_mapping(params: &JsValue) -> JsValue {
|
||||
let params: ShengSiParams = params.into_serde().unwrap();
|
||||
let unpan_id: usize = params.unpan_id;
|
||||
|
||||
let chart: [usize; 9] = params
|
||||
.unpan_xing_chart
|
||||
.try_into()
|
||||
.unwrap_or_else(|v: Vec<usize>| {
|
||||
panic!("Expected a Vec of length 9 but it was {}", v.len())
|
||||
});
|
||||
|
||||
let mapping: Vec<Option<&ShengSi>> = _get_shengsi_mapping(unpan_id, &chart);
|
||||
// log(&format!("[wasm] mapping: {:?}", mapping));
|
||||
|
||||
JsValue::from_serde(&mapping).unwrap()
|
||||
}
|
||||
94
docs/examples/north.jsx
Normal file
94
docs/examples/north.jsx
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* SVG Pie Drawing
|
||||
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
|
||||
*/
|
||||
import React, { useRef, useMemo, useEffect, useCallback } from 'react';
|
||||
import { compose, tap } from 'ramda';
|
||||
import tw, { css } from 'twin.macro';
|
||||
import Snap from 'snapsvg-cjs';
|
||||
|
||||
import { RADIAN_45, RADIAN_90, Z_INDEX_FENGSHUI_NORTH } from '@/constants';
|
||||
import { int, rad_to_deg, normalize_angle, get_position } from '@/lib/utils';
|
||||
|
||||
import { useWorld } from '@/contexts/World';
|
||||
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
|
||||
|
||||
const FILL_COLOR = '#ef4444';
|
||||
const OUTER_RATIO = 0.8;
|
||||
const INNER_RATIO = 0.7;
|
||||
const ARROW_SIZE = (OUTER_RATIO - INNER_RATIO) * 0.5;
|
||||
|
||||
const wrapperStyle = css`
|
||||
${tw`absolute w-full flex flex-col justify-center items-center`}
|
||||
z-index: ${Z_INDEX_FENGSHUI_NORTH};
|
||||
`;
|
||||
|
||||
export const FengShuiNorth = () => {
|
||||
const { worldInfo: world } = useWorld();
|
||||
const deviceOrientation = useDeviceOrientation();
|
||||
|
||||
const snap = useRef(null);
|
||||
|
||||
const chart_w = world?.chart?.width;
|
||||
const chart_h = world?.chart?.height;
|
||||
const alpha = deviceOrientation?.alpha;
|
||||
|
||||
const northStyle = useMemo(() => {
|
||||
const rotation = normalize_angle(alpha - rad_to_deg(Math.PI / 2));
|
||||
return css`
|
||||
width: ${chart_w}px;
|
||||
height: ${chart_h}px;
|
||||
transform: rotate(${rotation}deg);
|
||||
`;
|
||||
}, [chart_w, alpha]);
|
||||
|
||||
// A: rx,ry x-axis-rotation large-flag,sweep-flag end-x,end-y
|
||||
const draw = useCallback(
|
||||
({ index }) => {
|
||||
const s = snap.current && Snap(snap.current);
|
||||
|
||||
if (s) {
|
||||
}
|
||||
},
|
||||
[chart_w, chart_h]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const s = snap.current && Snap(snap.current);
|
||||
if (s) {
|
||||
const g = s.path('');
|
||||
g.remove();
|
||||
|
||||
const pos = [];
|
||||
pos[0] = {
|
||||
x: INNER_RATIO,
|
||||
y: -ARROW_SIZE,
|
||||
};
|
||||
pos[1] = {
|
||||
x: OUTER_RATIO,
|
||||
y: 0,
|
||||
};
|
||||
pos[2] = {
|
||||
x: INNER_RATIO,
|
||||
y: ARROW_SIZE,
|
||||
};
|
||||
|
||||
const path = [
|
||||
`M${pos[0].x},${pos[0].y}`,
|
||||
`L${pos[1].x},${pos[1].y}`,
|
||||
`L${pos[2].x},${pos[2].y}`,
|
||||
`L${pos[0].x},${pos[0].y}`,
|
||||
].join(' ');
|
||||
|
||||
s.path(path).attr({
|
||||
fill: FILL_COLOR,
|
||||
});
|
||||
}
|
||||
}, [chart_w, chart_h]);
|
||||
|
||||
return (
|
||||
<div id="north-wrapper" css={wrapperStyle}>
|
||||
<svg id="north" ref={snap} viewBox="-1 -1 2 2" css={northStyle}></svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
147
docs/examples/twentyfour.jsx
Normal file
147
docs/examples/twentyfour.jsx
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* SVG Pie Drawing
|
||||
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
|
||||
*/
|
||||
import React, { useRef, useMemo, useEffect, useCallback } from 'react';
|
||||
import { compose, tap } from 'ramda';
|
||||
import tw, { css } from 'twin.macro';
|
||||
import Snap from 'snapsvg-cjs';
|
||||
|
||||
import { RADIAN_15, Z_INDEX_FENGSHUI_ER_SHI_SI } from '@/constants';
|
||||
|
||||
import {
|
||||
rad_to_deg,
|
||||
deg_to_rad,
|
||||
normalize_angle,
|
||||
get_position,
|
||||
} from '@/lib/utils';
|
||||
|
||||
import { TW_CUSTOM_COLORS } from '@/styles/shared';
|
||||
|
||||
import { useWorld } from '@/contexts/World';
|
||||
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
|
||||
import { useFengShui } from '@/contexts/FengShui';
|
||||
|
||||
const FILL_COLOR = '#303030';
|
||||
const FILL_COLOR_DARK = '#000000';
|
||||
const STROKE_COLOR = '#ffffff';
|
||||
|
||||
const wrapperStyle = css`
|
||||
${tw`absolute w-full flex flex-col justify-center items-center`}
|
||||
z-index: ${Z_INDEX_FENGSHUI_ER_SHI_SI};
|
||||
`;
|
||||
|
||||
export const FengShuiTwentyFour = () => {
|
||||
const { worldInfo: world } = useWorld();
|
||||
const deviceOrientation = useDeviceOrientation();
|
||||
const {
|
||||
ready,
|
||||
xiang_xing,
|
||||
get_twentyfour_direction_from_index,
|
||||
get_twentyfour_data_from_index,
|
||||
} = useFengShui();
|
||||
|
||||
const snap = useRef(null);
|
||||
|
||||
const chart_w = world?.chart?.width;
|
||||
const chart_h = world?.chart?.height;
|
||||
const alpha = deviceOrientation?.alpha || 0;
|
||||
|
||||
const svgStyle = useMemo(() => {
|
||||
const rotation = normalize_angle(
|
||||
alpha - rad_to_deg(Math.PI / 2 + RADIAN_15 / 2)
|
||||
);
|
||||
|
||||
return css`
|
||||
transform: rotate(${rotation}deg);
|
||||
width: ${chart_w}px;
|
||||
height: ${chart_h}px;
|
||||
`;
|
||||
}, [chart_w, alpha]);
|
||||
|
||||
const draw = useCallback(
|
||||
({ index }) => {
|
||||
const s = snap.current && Snap(snap.current);
|
||||
|
||||
if (s && ready) {
|
||||
const beg = get_position(index * RADIAN_15);
|
||||
const end = get_position(index * RADIAN_15 + RADIAN_15);
|
||||
const large = end.deg - beg.deg > 180 ? 1 : 0;
|
||||
|
||||
// Remember, the whole wrapper is rotated later
|
||||
// by 262.5 (= 90 + 15/2), degrees so that
|
||||
// the first slice which originally sits
|
||||
// at the 3 o'clock position will come
|
||||
// to the 12 o'clock position.
|
||||
//
|
||||
// Also, we apply `viewbox="-1 -1 2 2"` for the SVG,
|
||||
// meaning, the origin of SVG is situated
|
||||
// at the very center of the wrapper.
|
||||
// That is to say, we have `x = 0`.
|
||||
// When we have `x = 1`, it means `x` is reaching
|
||||
// all the way at the right edge of the wrapper.
|
||||
//
|
||||
// For each slice, we use SVG's arc function
|
||||
// which is defined as:
|
||||
//
|
||||
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
||||
//
|
||||
// A1,1 0 0,1 0.965,0.258
|
||||
//
|
||||
// At first, we will first move (`M`) to where
|
||||
// it is close to the right edge of the screen.
|
||||
// Since we have no ratio specified, and the SVG
|
||||
// will fill up the whole space for the wrapper,
|
||||
// we will have it being `M1,0`.
|
||||
// Next, for the arc, we will probably have
|
||||
// `A1,1 0 0,1 0.965,0.258`.
|
||||
// So, it is an arc having `1` for the radius,
|
||||
// and it will draw the arc for 15 degrees.
|
||||
// Lastly, we have `L0,0` to go back to the origin,
|
||||
// otherwise, it won't fill (with given color)
|
||||
// the region surrounded by the path.
|
||||
|
||||
const path = [
|
||||
`M${beg.x},${beg.y}`,
|
||||
`A1,1 0 ${large},1 ${end.x},${end.y}`,
|
||||
`L0,0`,
|
||||
].join(' ');
|
||||
|
||||
const er_shi_si = get_twentyfour_data_from_index(index);
|
||||
const { direction, sector } =
|
||||
get_twentyfour_direction_from_index(index);
|
||||
|
||||
let fill = er_shi_si[0] === 2 ? FILL_COLOR : FILL_COLOR_DARK;
|
||||
if (
|
||||
xiang_xing &&
|
||||
direction === xiang_xing.direction &&
|
||||
sector === xiang_xing.sector
|
||||
) {
|
||||
fill = TW_CUSTOM_COLORS['abura-lightest'];
|
||||
}
|
||||
|
||||
s.path(path).attr({
|
||||
fill,
|
||||
stroke: STROKE_COLOR,
|
||||
strokeWidth: world?.stroke_size,
|
||||
});
|
||||
}
|
||||
},
|
||||
[ready, chart_w, xiang_xing]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const s = snap.current && Snap(snap.current);
|
||||
if (s && ready) {
|
||||
[...new Array(24)].map((_, i) => {
|
||||
draw({ index: i });
|
||||
});
|
||||
}
|
||||
}, [ready, chart_w, xiang_xing, draw]);
|
||||
|
||||
return (
|
||||
<div id="er-shi-si-wrapper" css={wrapperStyle}>
|
||||
<svg id="er-shi-si" ref={snap} viewBox="-1 -1 2 2" css={svgStyle}></svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
147
docs/examples/twentyfour_info.jsx
Normal file
147
docs/examples/twentyfour_info.jsx
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* SVG Pie Drawing
|
||||
* https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
|
||||
*/
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { compose, tap } from 'ramda';
|
||||
import tw, { css } from 'twin.macro';
|
||||
|
||||
import { RADIAN_15, Z_INDEX_FENGSHUI_ER_SHI_SI_INFO } from '@/constants';
|
||||
|
||||
import {
|
||||
int,
|
||||
fixed,
|
||||
deg_to_rad,
|
||||
get_position_clock,
|
||||
gen_code_12,
|
||||
} from '@/lib/utils';
|
||||
|
||||
import { useWorld } from '@/contexts/World';
|
||||
import { useDeviceOrientation } from '@/contexts/DeviceOrientation';
|
||||
import { useFengShui } from '@/contexts/FengShui';
|
||||
|
||||
const RATIO = 0.9;
|
||||
const RADIAN_15_HALF = RADIAN_15 / 2;
|
||||
|
||||
const fix2 = fixed(2);
|
||||
|
||||
const boxBaseStyle = css`
|
||||
${tw`absolute flex flex-col justify-center items-center`}
|
||||
line-height: 1em;
|
||||
transform: translate(-50%, -50%);
|
||||
`;
|
||||
|
||||
const sectorStyle = css`
|
||||
font-size: 0.94em;
|
||||
`;
|
||||
|
||||
export const FengShuiTwentyFourInfo = () => {
|
||||
const { worldInfo: world } = useWorld();
|
||||
const deviceOrientation = useDeviceOrientation();
|
||||
const {
|
||||
ready,
|
||||
xiang_xing,
|
||||
get_twentyfour_direction_from_index,
|
||||
get_twentyfour_direction_from_degrees,
|
||||
get_twentyfour_data_from_index,
|
||||
} = useFengShui();
|
||||
|
||||
const [boxes, setBoxes] = useState([]);
|
||||
|
||||
const chart_w = world?.chart?.width;
|
||||
const chart_h = world?.chart?.height;
|
||||
const alpha = deviceOrientation?.alpha || 0;
|
||||
|
||||
const wrapperStyle = useMemo(
|
||||
() => css`
|
||||
${tw`absolute text-white`}
|
||||
width: ${chart_w}px;
|
||||
height: ${chart_h}px;
|
||||
`,
|
||||
[chart_w]
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
index => {
|
||||
if (!ready) return;
|
||||
|
||||
const z_index = Z_INDEX_FENGSHUI_ER_SHI_SI_INFO + index * 1;
|
||||
const chart_w_half = chart_w / 2;
|
||||
const chart_h_half = chart_h / 2;
|
||||
const angle = index * RADIAN_15 + deg_to_rad(alpha);
|
||||
const radius = chart_w_half * RATIO;
|
||||
|
||||
const pos = get_position_clock(angle, radius, {
|
||||
x: chart_w_half,
|
||||
y: chart_h_half,
|
||||
});
|
||||
|
||||
const { direction, sector } = get_twentyfour_direction_from_index(index);
|
||||
|
||||
const color =
|
||||
xiang_xing &&
|
||||
direction === xiang_xing.direction &&
|
||||
sector === xiang_xing.sector
|
||||
? 'color: #000000'
|
||||
: '';
|
||||
|
||||
return {
|
||||
style: css`
|
||||
z-index: ${z_index};
|
||||
top: ${fix2(pos.y)}px;
|
||||
left: ${fix2(pos.x)}px;
|
||||
font-size: ${world?.text_base}px;
|
||||
${color}
|
||||
`,
|
||||
};
|
||||
},
|
||||
[ready, chart_w, alpha, xiang_xing]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready) {
|
||||
const arr = [...new Array(24)].map((_, i) => {
|
||||
const er_shi_si = get_twentyfour_data_from_index(i);
|
||||
const name = er_shi_si?.name;
|
||||
const degrees = i * 15;
|
||||
|
||||
const { direction, sector } =
|
||||
get_twentyfour_direction_from_degrees(degrees);
|
||||
|
||||
const text = name?.zh_cn?.alphabet || '';
|
||||
const text2 = `${direction.toUpperCase()}${sector}`;
|
||||
// console.log(`[fengshui/twentyfour_info] [${i}] (${degrees} degrees)`);
|
||||
// console.log(`[fengshui/twentyfour_info] --> ${text2}: ${text}`);
|
||||
|
||||
return {
|
||||
key: gen_code_12(),
|
||||
text,
|
||||
text2,
|
||||
};
|
||||
});
|
||||
|
||||
setBoxes(arr);
|
||||
}
|
||||
}, [ready]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready) {
|
||||
setBoxes(prev => prev.map((p, i) => ({ ...p, ...update(i) })));
|
||||
}
|
||||
}, [ready, chart_w, alpha, update]);
|
||||
|
||||
return (
|
||||
<div id="er-shi-si-info-wrapper" css={wrapperStyle}>
|
||||
{boxes.map((box, i) => (
|
||||
<div
|
||||
id={`er-shi-si-info-box-${i}`}
|
||||
key={box.key}
|
||||
css={[boxBaseStyle, box.style]}
|
||||
>
|
||||
<div>{box.text}</div>
|
||||
<div css={sectorStyle}>{box.text2}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
139
docs/examples/utils.js
Normal file
139
docs/examples/utils.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import { compose } from 'ramda';
|
||||
import { PIE_UNIT_HALF } from '@/constants';
|
||||
|
||||
export const int = Math.trunc;
|
||||
|
||||
export const noop = () => {};
|
||||
|
||||
export const fixed =
|
||||
(decimals = 3) =>
|
||||
n => {
|
||||
const place = Math.pow(10, decimals);
|
||||
return int(n * place) / place;
|
||||
};
|
||||
|
||||
// export const to_fixed = (n, decimals = 3) => fixed(decimals)(n);
|
||||
|
||||
export const capitalize = s => s[0].toUpperCase() + s.slice(1);
|
||||
|
||||
export const gen_code_4 = () =>
|
||||
Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
|
||||
export const gen_code_12 = () =>
|
||||
`${gen_code_4()}${gen_code_4()}${gen_code_4()}`;
|
||||
|
||||
export const rad_to_deg = rad => rad * (180 / Math.PI);
|
||||
export const deg_to_rad = deg => deg * (Math.PI / 180);
|
||||
|
||||
export const normalize_degree = deg => ((deg % 360) + 360) % 360;
|
||||
export const normalize_angle = normalize_degree;
|
||||
export const normalize_radian = compose(
|
||||
deg_to_rad,
|
||||
normalize_degree,
|
||||
rad_to_deg
|
||||
);
|
||||
|
||||
export const get_position = (rad, radius = 1, center = { x: 0, y: 0 }) => ({
|
||||
deg: normalize_degree(rad_to_deg(rad)),
|
||||
x: center.x + Math.cos(rad) * radius,
|
||||
y: center.y + Math.sin(rad) * radius,
|
||||
});
|
||||
|
||||
// Compared to `get_position`, this function uses
|
||||
// `Math.sin()` for `x`, and `Math.cos()` for `y`.
|
||||
export const get_position_clock = (
|
||||
rad,
|
||||
radius = 1,
|
||||
center = { x: 0, y: 0 }
|
||||
) => ({
|
||||
deg: normalize_degree(rad_to_deg(rad)),
|
||||
x: center.x + Math.sin(rad) * radius,
|
||||
y: center.y - Math.cos(rad) * radius,
|
||||
});
|
||||
|
||||
export const euler_from_quaternion = (q = []) => {
|
||||
/*
|
||||
* alpha
|
||||
* - Yew
|
||||
* - Rotation around Z-axis (a ray coming out of your phone)
|
||||
* - Screen-face rotation (like a car handle when driving)
|
||||
*/
|
||||
const alpha = normalize_degree(
|
||||
rad_to_deg(
|
||||
Math.atan2(
|
||||
2 * q[0] * q[1] + 2 * q[2] * q[3],
|
||||
1 - 2 * q[1] * q[1] - 2 * q[2] * q[2]
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* gamma
|
||||
* - Roll
|
||||
* - Rotation around Y-axis (phone's vertical axis)
|
||||
* - Horizontal tilt (left-right tilt)
|
||||
*/
|
||||
const gamma = normalize_degree(
|
||||
rad_to_deg(
|
||||
Math.atan2(
|
||||
2 * (q[3] * q[0] + q[1] * q[2]),
|
||||
1 - 2 * (q[0] * q[0] + q[1] * q[1])
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return [alpha, gamma];
|
||||
};
|
||||
|
||||
export const get_utc_offset_in_hours = dt =>
|
||||
int(Math.floor(dt.utcOffset() / 60));
|
||||
|
||||
export const str_to_hex = str => parseInt(str.slice(1), 16);
|
||||
|
||||
export const pad =
|
||||
(digits = 2) =>
|
||||
(n = 0) =>
|
||||
n.toString().padStart(digits, '0');
|
||||
|
||||
export const is_leap_year = year => {
|
||||
if (year % 4 == 0) {
|
||||
if (year % 100 == 0) {
|
||||
return year % 400 == 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const is_iOS =
|
||||
navigator.userAgent.match(/(iPod|iPhone|iPad)/) &&
|
||||
navigator.userAgent.match(/AppleWebKit/);
|
||||
|
||||
let time = Date.now();
|
||||
|
||||
export const debounce = (f, delay) => {
|
||||
let timeout = null;
|
||||
let args = null;
|
||||
|
||||
const g = () => {
|
||||
f.apply(null, args);
|
||||
time = Date.now();
|
||||
};
|
||||
|
||||
return function () {
|
||||
args = arguments;
|
||||
|
||||
if (Date.now() >= time + delay) {
|
||||
// Execute if the time has passed.
|
||||
g();
|
||||
} else {
|
||||
// Cancel the previous ones, and execute only the last one.
|
||||
!!timeout && clearTimeout(timeout);
|
||||
timeout = setTimeout(g, delay);
|
||||
}
|
||||
};
|
||||
};
|
||||
264
docs/ganzhi.md
Normal file
264
docs/ganzhi.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# 干支 (Gan-Zhi)
|
||||
|
||||
Source: [src/ganzhi.rs](../src/ganzhi.rs)
|
||||
|
||||
Based on 5 elements in nature with its 陰 (Yin) and 陽 (Yang) for each,
|
||||
ancient Chinese described the plant growth using 10 conventional symbols
|
||||
known as "10 Gan" (十干). Also, they tracked the motion of Jupiter
|
||||
(which has 12 year cycle) and so they did divided the night sky into 12 regions,
|
||||
and this is known as "12 Zhi" (十二支). When they record time and space,
|
||||
they used the combinations of 10 Gan (干) and 12 Zhi (支)
|
||||
which makes 60 patterns, and this is called 干支 (Gan-Zhi).
|
||||
|
||||
10 Gan (干):
|
||||
|
||||
[0] 甲 (Jia)
|
||||
[1] 乙 (Yi)
|
||||
[2] 丙 (Bing)
|
||||
[3] 丁 (Ding)
|
||||
[4] 戊 (Wu)
|
||||
[5] 己 (Ji)
|
||||
[6] 庚 (Geng)
|
||||
[7] 辛 (Xin)
|
||||
[8] 壬 (Ren)
|
||||
[9] 癸 (Gui)
|
||||
|
||||
12 Zhi (支):
|
||||
|
||||
[0] 子 (Zi)
|
||||
[1] 丑 (Chou)
|
||||
[2] 寅 (Yin)
|
||||
[3] 卯 (Mao)
|
||||
[4] 辰 (Chen)
|
||||
[5] 巳 (Si)
|
||||
[6] 午 (Wu)
|
||||
[7] 未 (Wei)
|
||||
[8] 申 (Shen)
|
||||
[9] 酉 (You)
|
||||
[10] 戌 (Xu)
|
||||
[11] 亥 (Hai)
|
||||
|
||||
Reference:
|
||||
- [Sexagenary cycle - Wiki](https://en.wikipedia.org/wiki/Sexagenary_cycle)
|
||||
|
||||
|
||||
## ganzhi::Stem
|
||||
|
||||
A struct representing 干 (Gan) or "Stem" and stores its attributes.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Stem {
|
||||
pub num: u8,
|
||||
pub name: Language,
|
||||
}
|
||||
```
|
||||
|
||||
## ganzhi::Branch
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Branch {
|
||||
pub num: u8,
|
||||
pub name: Language,
|
||||
}
|
||||
```
|
||||
|
||||
A struct representing 支 (Zhi) or "Branch" and stores its attributes.
|
||||
|
||||
## ganzhi::StemRawData
|
||||
|
||||
A temporary struct for loading JSON data when defining a static const `STEMS`.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StemRawData {
|
||||
pub num: u8,
|
||||
pub name: LanguageData,
|
||||
}
|
||||
```
|
||||
|
||||
## ganzhi::BranchRawData
|
||||
|
||||
A temporary struct for loading JSON data when defining a static const `BRANCHES`.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BranchRawData {
|
||||
pub num: u8,
|
||||
pub name: LanguageData,
|
||||
}
|
||||
```
|
||||
|
||||
## ganzhi::GanZhi
|
||||
|
||||
A struct for holding `Stem` and `Branch`, or denoted as 干支 (Gan-Zhi).
|
||||
|
||||
```
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GanZhi<'a> {
|
||||
pub stem: &'a Stem,
|
||||
pub branch: &'a Branch,
|
||||
}
|
||||
```
|
||||
|
||||
## ganzhi::Bazi
|
||||
|
||||
A struct representing 八字 (Bazi) and stores `GanZhi` as its attributes.
|
||||
It is referred as _"The Four Pillars of Destiny"_ in English
|
||||
mainly because the structure of 八字 (Bazi) necessary
|
||||
for divinations in 四柱命理学 (_"The Four Pillars of Destiny"_).
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Bazi<'a> {
|
||||
pub year: GanZhi<'a>,
|
||||
pub month: GanZhi<'a>,
|
||||
pub day: GanZhi<'a>,
|
||||
pub hour: GanZhi<'a>,
|
||||
}
|
||||
```
|
||||
|
||||
## ganzhi::STEMS
|
||||
|
||||
`Vec<Stem>`
|
||||
|
||||
A static vector with 10 items, each represents 干 (Gan).
|
||||
Each stores associated attributes for the 干 (Gan).
|
||||
|
||||
[0] 甲 (Jia)
|
||||
[1] 乙 (Yi)
|
||||
[2] 丙 (Bing)
|
||||
[3] 丁 (Ding)
|
||||
[4] 戊 (Wu)
|
||||
[5] 己 (Ji)
|
||||
[6] 庚 (Geng)
|
||||
[7] 辛 (Xin)
|
||||
[8] 壬 (Ren)
|
||||
[9] 癸 (Gui)
|
||||
|
||||
For attributes details stored in the vector is found in JSON file:
|
||||
[json/ganzhi_stems.json](../json/ganzhi_stems.json)
|
||||
|
||||
## ganzhi::BRANCHES
|
||||
|
||||
`Vec<Branch>`
|
||||
|
||||
A static vector with 10 items, each represents 支 (Zhi).
|
||||
Each stores associated attributes for the 支 (Zhi).
|
||||
|
||||
[0] 子 (Zi)
|
||||
[1] 丑 (Chou)
|
||||
[2] 寅 (Yin)
|
||||
[3] 卯 (Mao)
|
||||
[4] 辰 (Chen)
|
||||
[5] 巳 (Si)
|
||||
[6] 午 (Wu)
|
||||
[7] 未 (Wei)
|
||||
[8] 申 (Shen)
|
||||
[9] 酉 (You)
|
||||
[10] 戌 (Xu)
|
||||
[11] 亥 (Hai)
|
||||
|
||||
For attributes details stored in the vector is found in JSON file:
|
||||
`src/json/ganzhi_branches.json`
|
||||
|
||||
## ganzhi::GANZHI_SEXAGESIMAL
|
||||
|
||||
`Vec<(usize, usize)>`
|
||||
|
||||
A static vector with 60 items. `Vec<usize, usize>` where the first
|
||||
`usize` being the `STEMS` index, and the second for the `BRANCHES`.
|
||||
It is simply the combination of 10 stems and 12 branches
|
||||
which eventually adds up to 60 patterns.
|
||||
|
||||
## ganzhi::HOUR_STEM_TABLE
|
||||
|
||||
`[[usize; 5]; 12]`
|
||||
|
||||
This is a table used when finding "Hour Stem".
|
||||
Columns represents "Day Stem" groups, and there are 5 groups.
|
||||
For insntace, if you have 甲 for "Day Stem",
|
||||
you are looking into the first column (group).
|
||||
Rows represents "Hour Branches" for which there are 12.
|
||||
For instance, if you have 子 for "Hour Branch",
|
||||
you are looking into the first row.
|
||||
So, when you have 甲 for "Day Stem",
|
||||
and 子 for "Hour Branch", "Hour Stem" is located
|
||||
in the first column in the first row, which is 甲.
|
||||
|
||||
甲乙丙丁戊
|
||||
己庚辛壬癸
|
||||
‐‐‐‐‐‐‐‐‐‐‐‐‐
|
||||
子: 甲丙戊庚壬
|
||||
丑: 乙丁己辛癸
|
||||
寅: 丙戊庚壬甲
|
||||
卯: 丁己辛癸乙
|
||||
辰: 戊庚壬甲丙
|
||||
巳: 己辛癸乙丁
|
||||
午: 庚壬甲丙戊
|
||||
未: 辛癸乙丁己
|
||||
申: 壬甲丙戊庚
|
||||
酉: 癸乙丁己辛
|
||||
戌: 甲丙戊庚壬
|
||||
亥: 乙丁己辛癸
|
||||
|
||||
## ganzhi::Bazi::from_local
|
||||
|
||||
Returns `Bazi` from localtime (`DateTime`) and zone (`i8`).
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::time::{ Month, DateTime };
|
||||
use mikaboshi::ganzhi::{ Bazi, GanZhi };
|
||||
|
||||
let zone: i8 = 9;
|
||||
|
||||
let lt = DateTime {
|
||||
year: 2021,
|
||||
month: Month::Jul,
|
||||
day: 7.0,
|
||||
hour: 0,
|
||||
min: 0,
|
||||
sec: 0.0,
|
||||
};
|
||||
let bazi: Bazi = Bazi::from_local(<, zone);
|
||||
|
||||
let year: GanZhi = bazi.year;
|
||||
let month: GanZhi = bazi.month;
|
||||
let day: GanZhi = bazi.day;
|
||||
let hour: GanZhi = bazi.hour;
|
||||
|
||||
println!("年: {} ({})", year.alphabet(), year.alphabet_ja());
|
||||
println!("月: {} ({})", month.alphabet(), month.alphabet_ja());
|
||||
println!("日: {} ({})", day.alphabet(), day.alphabet_ja());
|
||||
println!("時: {} ({})", hour.alphabet(), hour.alphabet_ja());
|
||||
|
||||
// 年: 辛丑 (かのと・うし)
|
||||
// 月: 甲午 (きのえ・うま)
|
||||
// 日: 乙卯 (きのと・う)
|
||||
// 時: 癸未 (みずのと・ひつじ)
|
||||
```
|
||||
|
||||
Example using `js_sys`:
|
||||
|
||||
```rust
|
||||
use mikaboshi::ganzhi::Bazi;
|
||||
use mikaboshi::time::{DateTime, Month};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_bazi(params: &JsValue) -> JsValue {
|
||||
let localtime = DateTime {
|
||||
year: 1985,
|
||||
month: Month::Nov,
|
||||
day: 5.0,
|
||||
hour: 1,
|
||||
min: 35,
|
||||
sec: 0.0,
|
||||
};
|
||||
let zone: i8 = 9;
|
||||
JsValue::from_serde(&Bazi::from_local(&localtime, zone)).unwrap()
|
||||
}
|
||||
```
|
||||
301
docs/jiuxing.md
Normal file
301
docs/jiuxing.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# 九星 (Jiu-Xing)
|
||||
|
||||
Source: [src/jiuxing.rs](../src/jiuxing.rs)
|
||||
|
||||
At the beginning, the law governing the universe was simple.
|
||||
Yet, as man acquired the faculty of thought, was it no longer so.
|
||||
It was not the universe which changed, but was about how man began
|
||||
to see the universe differently. Thought, after all, is nothing
|
||||
but reflections of the outer world. In another word, the outer
|
||||
world we perceive could only be understood via patterns
|
||||
that are innate to man's thought. Just like "Malkuth" in _Kabbalha_
|
||||
is about both the earthly kingdom and the man himself,
|
||||
as ancient Chinese attempted describing patterns in the universe,
|
||||
they introduced another artificial element "metal"
|
||||
(or "earth" when it is deployed in actual reality).
|
||||
For the ancient Chinese, the former is called
|
||||
先天八卦 ("the Primordial Heaven"), and the latter,
|
||||
後天八卦 ("the Manifested Heaven").
|
||||
To study the patterns peculiar to each universe, a conventional
|
||||
board with 8 directions and 1 in the center has been in use,
|
||||
where "8 Gua" (八卦) are assigned for slots on the board.
|
||||
However, for many 風水 (Feng-Shui) systems, we are normally
|
||||
dealing with the latter, or 後天八卦 ("the Manifested Heaven").
|
||||
|
||||
For 後天八卦 ("the Manifested Heaven") has a specific name
|
||||
in 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui), and is called
|
||||
地盤 (Di-Pan). However, there are 3 more boards in
|
||||
玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui)
|
||||
in addition to 地盤 (Di-Pan), namely:
|
||||
|
||||
(1) 運盤 (Un-Pan) (or 天盤 (Tien-Pan))
|
||||
(2) 山星 (Shan-Xing)
|
||||
(3) 向星 (Xiang-Xing)
|
||||
|
||||
In practice, for all the above 3 boards, 九星 (Jiu-Xing)
|
||||
or "the Nine Stars" are assigned. While "8 Gua" (八卦)
|
||||
has fixed positions, 九星 (Jiu-Xing) changes
|
||||
over time for spatial constraints given.
|
||||
When their positions change, the movement is called
|
||||
飞泊 (Fei-Po) or "flying" because of how it appears
|
||||
to our eyes when they move.
|
||||
|
||||
For the first board 運盤 (Un-Pan), positions of 九星 (Jiu-Xing)
|
||||
are determined by building's construction year,
|
||||
and calculated based on 三元九運 (Sang-Yuan Jiu-Yun)
|
||||
or "9 Yearly Cycles". We could say that 運盤 (Un-Pan)
|
||||
essentially describes of the temporal aspect of the building
|
||||
For 山星 (Shan-Xing) and 向星 (Xiang-Xing) are determined
|
||||
by spatial aspects of the building, though, temporal aspects
|
||||
are also associated indirectly in calculations.
|
||||
|
||||
When 運盤 (Un-Pan), 山星 (Shan-Xing), and 向星 (Xiang-Xing)
|
||||
are added to 地盤 (Di-Pan) at the bottom, it is called
|
||||
下卦図 (Xia-Gua-Tu), or simply referred as
|
||||
飞星図 (Fei-Xing-Tu; "the Flying Star Chart").
|
||||
|
||||
Jiu-Xing (九星):
|
||||
|
||||
[0] 一白水星 (1 White)
|
||||
[1] 二黒土星 (2 Black)
|
||||
[2] 三碧木星 (3 Jade)
|
||||
[3] 四緑木星 (4 Green)
|
||||
[4] 五黄土星 (5 Yellow)
|
||||
[5] 六白金星 (6 White)
|
||||
[6] 七赤金星 (7 Red)
|
||||
[7] 八白土星 (8 White)
|
||||
[8] 九紫火星 (9 Purple)
|
||||
|
||||
|
||||
Reference:
|
||||
- [Flying Star Feng Shui - Wiki](https://en.wikipedia.org/wiki/Flying_Star_Feng_Shui)
|
||||
|
||||
|
||||
## jiuxing::JiuXing
|
||||
|
||||
A struct representing 九星 (Jiu-Xing).
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JiuXing {
|
||||
pub num: u8,
|
||||
pub direction: String,
|
||||
pub name: Language,
|
||||
pub color: String,
|
||||
pub element: WuXing,
|
||||
pub planet: Planet,
|
||||
}
|
||||
```
|
||||
|
||||
## jiuxing::JiuXingRawData
|
||||
|
||||
A temporary struct for loading JSON data when defining a static const `JIU_XING`.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JiuXingRawData {
|
||||
pub num: u8,
|
||||
pub direction: String,
|
||||
pub name: LanguageData,
|
||||
pub color: String,
|
||||
pub element: u8,
|
||||
pub planet: u8,
|
||||
}
|
||||
```
|
||||
|
||||
## jiuxing::XiaGuaTuKind
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
|
||||
pub enum XiaGuaTuKind {
|
||||
UnPanXing, // 運盤
|
||||
ShanXing, // 山星
|
||||
XiangXing, // 向星
|
||||
}
|
||||
```
|
||||
|
||||
## jiuxing::XiaGuaTu
|
||||
|
||||
A struct representing 下卦図 (Xia-Gua-Tu).
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct XiaGuaTu<'a> {
|
||||
pub kind: XiaGuaTuKind,
|
||||
pub center: Option<usize>,
|
||||
pub direction: Option<&'a str>,
|
||||
pub sector: Option<usize>,
|
||||
pub chart: Option<[usize; 9]>,
|
||||
}
|
||||
```
|
||||
|
||||
## jiuxing::DIRECTION_TO_JIU_XING
|
||||
|
||||
`HashMap<&str, usize>`
|
||||
|
||||
## jiuxing::JIU_XING
|
||||
|
||||
`[JiuXing; 9]`
|
||||
|
||||
A static vector with 9 items, each represents 九星 (Jiu-Xing).
|
||||
|
||||
[0] 一白水星 (1 White)
|
||||
[1] 二黒土星 (2 Black)
|
||||
[2] 三碧木星 (3 Jade)
|
||||
[3] 四緑木星 (4 Green)
|
||||
[4] 五黄土星 (5 Yellow)
|
||||
[5] 六白金星 (6 White)
|
||||
[6] 七赤金星 (7 Red)
|
||||
[7] 八白土星 (8 White)
|
||||
[8] 九紫火星 (9 Purple)
|
||||
|
||||
For attributes details stored in the vector is found in JSON file:
|
||||
[json/jiuxing.json](../json/jiuxing.json)
|
||||
|
||||
## jiuxing::JIU_XING_DI_PAN_POSITIONS
|
||||
|
||||
`HashMap<&str, [usize; 9]>`
|
||||
|
||||
Although 洛書 (Lo-Shu) order is fixed, when 地盤 (Di-Pan)
|
||||
is drawn on a device screen, the mapping for
|
||||
九星 (Jiu-Xing) changes as the device rotates.
|
||||
For example, 一白水星 (1 White) usually comes to the top
|
||||
of the board when a device is pointing north. However,
|
||||
when pointing north east, 一白水星 (1 White) moves
|
||||
to the top left (which is north west).
|
||||
For 8 compass directions, this constant provides
|
||||
a mapping for the 洛書 (Lo-Shu) order.
|
||||
For "n", 一白水星 (1 White) is the 2nd in the array.
|
||||
For "ne", 一白水星 (1 White) is the 1st in the array.
|
||||
|
||||
It would look like this:
|
||||
|
||||
[5] 六白 [0] 一白 [7] 八白
|
||||
[6] 七赤 [4] 五黄 [2] 三碧
|
||||
[1] 二黒 [8] 九紫 [3] 四緑
|
||||
n: [5, 0, 7, 6, 4, 2, 1, 8, 3]
|
||||
|
||||
[0] 一白 [7] 八白 [2] 三碧
|
||||
[5] 六白 [4] 五黄 [3] 四緑
|
||||
[6] 七赤 [1] 二黒 [8] 九紫
|
||||
ne: [0, 7, 2, 5, 4, 3, 6, 1, 8]
|
||||
|
||||
[7] 八白 [2] 三碧 [3] 四緑
|
||||
[0] 一白 [4] 五黄 [8] 九紫
|
||||
[5] 六白 [6] 七赤 [1] 二黒
|
||||
e: [7, 2, 3, 0, 4, 8, 5, 6, 1]
|
||||
|
||||
[2] 三碧 [3] 四緑 [8] 九紫
|
||||
[7] 八白 [4] 五黄 [1] 二黒
|
||||
[0] 一白 [5] 六白 [6] 七赤
|
||||
se: [2, 3, 8, 7, 4, 1, 0, 5, 6]
|
||||
|
||||
[3] 四緑 [8] 九紫 [1] 二黒
|
||||
[2] 三碧 [4] 五黄 [6] 七赤
|
||||
[7] 八白 [0] 一白 [5] 六白
|
||||
s: [3, 8, 1, 2, 4, 6, 7, 0, 5]
|
||||
|
||||
[8] 九紫 [1] 二黒 [6] 七赤
|
||||
[3] 四緑 [4] 五黄 [5] 六白
|
||||
[2] 三碧 [7] 八白 [0] 一白
|
||||
sw: [8, 1, 6, 3, 4, 5, 2, 7, 0]
|
||||
|
||||
[1] 二黒 [6] 七赤 [5] 六白
|
||||
[8] 九紫 [4] 五黄 [0] 一白
|
||||
[3] 四緑 [2] 三碧 [7] 八白
|
||||
w: [1, 6, 5, 8, 4, 0, 3, 2, 7]
|
||||
|
||||
[6] 七赤 [5] 六白 [0] 一白
|
||||
[1] 二黒 [4] 五黄 [7] 八白
|
||||
[8] 九紫 [3] 四緑 [2] 三碧
|
||||
nw: [6, 5, 0, 1, 4, 7, 8, 3, 2]
|
||||
|
||||
## jiuxing::get_jiuxing_dipan_positions_from_direction
|
||||
|
||||
A getter for `JIU_XING_DI_PAN_POSITIONS`.
|
||||
|
||||
## jiuxing::get_jiuxing_from_index
|
||||
|
||||
A getter for `JIU_XING`.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::jiuxing::{get_jiuxing_from_index, JiuXing};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(index: usize) -> JsValue {
|
||||
let dir: &JiuXing = get_jiuxing_from_index(index);
|
||||
JsValue::from_serde(dir).unwrap()
|
||||
}
|
||||
```
|
||||
|
||||
## jiuxing::normalize_jiuxing
|
||||
|
||||
Given incorrect value for Jiu-Xing index, applies a modulo
|
||||
to normalize it to fit within the range of 0 to 8.
|
||||
|
||||
Example:
|
||||
0 --> 0 ... Stays the same. "0" being "一白水星 (1 White)".
|
||||
8 --> 8 ... Stays the same. "8" being "九紫火星 (9 Purple)".
|
||||
9 --> 0 ... "9" is too much for the range, and becoming "0" which is "一白水星".
|
||||
10 --> 1 ... "10" is too much, and becoming "1" which is "二黒土星 (2 Black)".
|
||||
-1 --> 8 ... Making it positive. "8" being "九紫火星 (9 Purple)".
|
||||
-2 --> 7 ... Making it positive. "8" being "八白土星 (8 White)".
|
||||
|
||||
|
||||
## jiuxing::fly_flying_stars
|
||||
|
||||
This is a function for 飞泊 (Fei-Po) or "flying".
|
||||
The idea is quite simple. Given the order (which is
|
||||
the second argument `order` in array) of
|
||||
九星 (Jiu-Xing) indexes, increments or decrements
|
||||
each in the array, and simply return the array.
|
||||
Depending on whichever currently resides in the center of
|
||||
the board (which is the first argument `center`),
|
||||
the value to increment or decrement changes.
|
||||
For `order` is fundamentally that of the Lo-Shu order
|
||||
(which is defined in `JIU_XING_DI_PAN_POSITIONS`),
|
||||
however, the layout is always different since
|
||||
the position changes depending on which direction
|
||||
the device is pointing as the device rotates.
|
||||
|
||||
|
||||
## jiuxing::get_xiaguatu_from_unpan_index
|
||||
|
||||
Calculates for 下卦図 (Xia-Gua-Tu). 1st and 2nd
|
||||
arguments (`unpan_xing_center` and `unpan_xing_order`)
|
||||
are required for all. For calculating a chart
|
||||
for 運盤星 (Un-Pan Xing), that is all we need.
|
||||
However, to calculate charts for 山星 (Shan-Xing)
|
||||
and 向星 (Xiang-Xing), requires 3rd and 4th arguments
|
||||
(`xiang_xing_direction` and `xiang_xing_sector`.
|
||||
|
||||
Example:
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use mikaboshi::jiuxing::{get_xiaguatu_from_unpan_index, XiaGuaTu};
|
||||
use mikaboshi::test_mods::XiaGuaTuParams;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(params: &JsValue) -> JsValue {
|
||||
let params: XiaGuaTuParams = params.into_serde().unwrap();
|
||||
let unpan_xing_order: [usize; 9] =
|
||||
params
|
||||
.unpan_xing_order
|
||||
.try_into()
|
||||
.unwrap_or_else(|v: Vec<usize>| {
|
||||
panic!("Expected a Vec of length 9 but it was {}", v.len())
|
||||
});
|
||||
let xia_gua_tu: HashMap<&str, XiaGuaTu> = get_xiaguatu_from_unpan_index(
|
||||
params.unpan_xing_center,
|
||||
&unpan_xing_order,
|
||||
params.xiang_xing_direction.as_str(),
|
||||
params.xiang_xing_sector,
|
||||
);
|
||||
JsValue::from_serde(&xia_gua_tu).unwrap()
|
||||
}
|
||||
```
|
||||
68
docs/planet.md
Normal file
68
docs/planet.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Planets
|
||||
|
||||
Source: [src/planet.rs](../src/planet.rs)
|
||||
|
||||
Information about planets in our solar system.
|
||||
Notice the planets in `PLANETS` are stored in a special order
|
||||
known as _the Ptolemaic Order_. In many ancient traditions,
|
||||
when a man is deceased, he will depart the Earth,
|
||||
and head toward the Moon. Leaving the Moon behind,
|
||||
the Mercury, the Venus, and the Sun. He will continue
|
||||
his journey after the Sun, this time, to _the outer planets_,
|
||||
that are the Mars, the Jupiter, and the Saturn.
|
||||
|
||||
After all, this library provides methodologies
|
||||
_NOT_ for _"astronomy"_, but for _"astrology"_, hence,
|
||||
follows the tradition which was common to the ancients.
|
||||
|
||||
Also noteworthy that, according to Rudolf Steiner,
|
||||
"Mercury" was formerly known as "Venus" in ancient times.
|
||||
Yet, it is only so when we are talking about the order
|
||||
of the _physical_ planets, not in its _symbolical_ sense.
|
||||
For instance, when ancients mentioned of "Mercury",
|
||||
it was simply about "Mercury" and not "Venus".
|
||||
|
||||
## planet::Planet
|
||||
|
||||
A struct representing a planet and stores its attributes.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Planet {
|
||||
pub name: Language,
|
||||
}
|
||||
```
|
||||
|
||||
## planet::PlanetRawData
|
||||
|
||||
A temporary struct for loading JSON data when defining a static const `PLANETS`.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlanetRawData {
|
||||
pub name: LanguageData,
|
||||
}
|
||||
```
|
||||
|
||||
## planet::PLANETS
|
||||
|
||||
`Vec<Planet>`
|
||||
|
||||
A static vector with 11 items, each represents a planet
|
||||
in our solar system. Planets are in Ptolemaic order.
|
||||
|
||||
[0] Earth
|
||||
[1] Moon
|
||||
[2] Mercury
|
||||
[3] Venus
|
||||
[4] Sun
|
||||
[5] Mars
|
||||
[6] Jupiter
|
||||
[7] Saturn
|
||||
[8] Uranus
|
||||
[9] Neptune
|
||||
[10] Pluto
|
||||
|
||||
For attributes details stored in the vector is found in JSON file:
|
||||
[json/planets.json](../json/planets.json)
|
||||
|
||||
BIN
docs/sample_bagua.png
Normal file
BIN
docs/sample_bagua.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/sample_shengsi.png
Normal file
BIN
docs/sample_shengsi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/sample_twentyfour.png
Normal file
BIN
docs/sample_twentyfour.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
104
docs/shengsi.md
Normal file
104
docs/shengsi.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
|
||||
Source: [src/shengsi.rs](../src/shengsi.rs)
|
||||
|
||||

|
||||
|
||||
生死衰旺 (Sheng-Si Shuai-Wang) is just a combination
|
||||
of 4 Chinese characters, each being:
|
||||
|
||||
(1) Growing --> 生 (Sheng)
|
||||
(2) Deadly --> 死 (Si)
|
||||
(3) Perishing --> 衰 (Shuai)
|
||||
(4) Prosperous --> 旺 (Wang)
|
||||
|
||||
They are often used in 四柱命理学 (The Four Pillars of Destiny),
|
||||
but used in Feng-Shui as well. It simply suggests
|
||||
that there are 4 states to the energy occupying the space.
|
||||
In 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui),
|
||||
it describes the state for the target year
|
||||
in 三元九運 (Sang-Yuan Jiu-Yun),
|
||||
especially, for its 向星 (Xiang-Xing).
|
||||
|
||||
|
||||
## shengsi::ShengSi
|
||||
|
||||
A struct representing 生死衰旺 (Sheng-Si Shuai-Wang).
|
||||
`key` would be: "sheng", "si", "shuai", or "wang".
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ShengSi<'a> {
|
||||
pub key: &'a str,
|
||||
pub kanji: &'a str,
|
||||
pub meaning: &'a str,
|
||||
}
|
||||
```
|
||||
|
||||
## shengsi::ShengSiYearlyAlloc
|
||||
|
||||
A struct holding allocations of 生死衰旺 (Sheng-Si Shuai-Wang) for the given year.
|
||||
For `usize` (in `Vec<usize>`) is 九星 (Jiu-Xing) index.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShengSiYearlyAlloc {
|
||||
pub wang: Vec<usize>,
|
||||
pub sheng: Vec<usize>,
|
||||
pub shuai: Vec<usize>,
|
||||
pub si: Vec<usize>,
|
||||
}
|
||||
```
|
||||
|
||||
## shengsi::SHENG_SI
|
||||
|
||||
`HashMap<&str, ShengSi>`
|
||||
|
||||
A HashMap for 生死衰旺 (Sheng-Si Shuai-Wang) by key
|
||||
(for each holds `ShengSi`).
|
||||
|
||||
## shengsi::SHENG_SI_ALLOC
|
||||
|
||||
`Vec<ShengSiYearlyAlloc>`
|
||||
|
||||
For every year, some 九星 (Jiu-Xing) maybe in 旺 (Wang = Prospering)
|
||||
phase, but some maybe in 死 (Si = Dying). 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
for 九星 (Jiu-Xing) is no random, but has certain patterns,
|
||||
and is repeated every 9 years. This cycle is called
|
||||
三元九運 (Sang-Yuan Jiu-Yun), and given the 運盤星 (Un-Pan Xing) index
|
||||
for the specific year, you can tell of 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
for all the other 九星 (Jiu-Xing). Here, it is constructing
|
||||
the patterns for 9 years, and making them into a static vector
|
||||
for which each index being the 運盤星 (Un-Pan Xing) index.
|
||||
If you know the 運盤星 (Un-Pan Xing) index for the year,
|
||||
this static vector will tell you 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
for all 九星 (Jiu-Xing).
|
||||
|
||||
## shengsi::get_shengsi_mapping
|
||||
|
||||
Given 運盤 (Un-Pan) index and given a layout for the current
|
||||
運盤 (Un-Pan) positions (`&[usize; 9]`), returns the corresponding
|
||||
生死衰旺 (Sheng-Si Shuai-Wang) situation.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use std::convert::TryInto;
|
||||
use mikaboshi::shengsi::{get_shengsi_mapping, ShengSi};
|
||||
use mikaboshi::test_mods::ShengSiParams;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(params: &JsValue) -> JsValue {
|
||||
let params: ShengSiParams = params.into_serde().unwrap();
|
||||
let unpan_id: usize = params.unpan_id;
|
||||
let chart: [usize; 9] = params
|
||||
.unpan_xing_chart
|
||||
.try_into()
|
||||
.unwrap_or_else(|v: Vec<usize>| {
|
||||
panic!("Expected a Vec of length 9 but it was {}", v.len())
|
||||
});
|
||||
let mapping: Vec<Option<&ShengSi>> = get_shengsi_mapping(unpan_id, &chart);
|
||||
JsValue::from_serde(&mapping).unwrap()
|
||||
}
|
||||
```
|
||||
56
docs/solar_terms.md
Normal file
56
docs/solar_terms.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 二十四节气 (Er-Shi-Si Jie-Qi) and 立春 (Li-Chun)
|
||||
|
||||
Source: [src/solar_terms.rs](../src/solar_terms.rs)
|
||||
|
||||
A module for 二十四节气 (Er-Shi-Si Jie-Qi).
|
||||
Or, for calculating 立春 (Li-Chun).
|
||||
|
||||
Reference:
|
||||
- [Solar term - Wiki](https://en.wikipedia.org/wiki/Solar_term)
|
||||
|
||||
|
||||
## solar_terms::SolarTerm
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
pub struct SolarTerm {
|
||||
pub id: u8,
|
||||
pub name: Language,
|
||||
pub angle: u16,
|
||||
}
|
||||
```
|
||||
|
||||
## solar_terms::SolarTermRawData
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SolarTermRawData {
|
||||
pub id: u8,
|
||||
pub name: LanguageData,
|
||||
pub angle: u16,
|
||||
}
|
||||
```
|
||||
|
||||
## solar_terms::SOLAR_TERMS
|
||||
|
||||
`Vec<SolarTerm>`
|
||||
|
||||
## solar_terms::get_last_term
|
||||
|
||||
## solar_terms::get_lichun
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use mikaboshi::solar_terms::get_lichun;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xx(year: i16) -> JsValue {
|
||||
let lichun = get_lichun(year);
|
||||
JsValue::from_str(&format!(
|
||||
"{:04}-{:02}-{:02}",
|
||||
lichun.year as u16, lichun.month as u8, lichun.day as u8
|
||||
))
|
||||
}
|
||||
```
|
||||
44
docs/time.md
Normal file
44
docs/time.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Time
|
||||
|
||||
Source: [src/time.rs](../src/time.rs)
|
||||
|
||||
All the time related. Currently, has only 1 function.
|
||||
|
||||
## time::Date
|
||||
## time::DateTime
|
||||
## time::Time
|
||||
|
||||
## time::ut_from_local
|
||||
|
||||
You may convert your local time into _UT_:
|
||||
|
||||
```rust
|
||||
use mikaboshi::time::{
|
||||
Month,
|
||||
DateTime,
|
||||
ut_from_local,
|
||||
};
|
||||
|
||||
let zone: i8 = 9;
|
||||
|
||||
let local = DateTime {
|
||||
year: 2021,
|
||||
month: Month::Jul,
|
||||
day: 7.0,
|
||||
hour: 0,
|
||||
min: 0,
|
||||
sec: 0.0,
|
||||
};
|
||||
|
||||
let ut: DateTime = ut_from_local(&local, zone);
|
||||
println!("ut: {:?}", ut);
|
||||
|
||||
// {
|
||||
// year: 2021,
|
||||
// month: Jul,
|
||||
// day: 6.0,
|
||||
// hour: 14,
|
||||
// min: 57,
|
||||
// sec: 17.13778432735566
|
||||
// }
|
||||
```
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": {
|
||||
"en": "kan",
|
||||
"zh_cn": ["坎", "kǎn"],
|
||||
"zh_cn": ["坎", "kǎn"],
|
||||
"zh_tw": ["坎", "kǎn"],
|
||||
"ja": ["かん", "kan"],
|
||||
"vi": []
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"no": 1,
|
||||
"num": 1,
|
||||
"name": {
|
||||
"en": "zi",
|
||||
"ja": ["ね", "ne"],
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 2,
|
||||
"num": 2,
|
||||
"name": {
|
||||
"en": "chou",
|
||||
"ja": ["うし", "ushi"],
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 3,
|
||||
"num": 3,
|
||||
"name": {
|
||||
"en": "",
|
||||
"ja": ["とら", "tora"],
|
||||
@@ -30,7 +30,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 4,
|
||||
"num": 4,
|
||||
"name": {
|
||||
"en": "mao",
|
||||
"ja": ["う", "u"],
|
||||
@@ -40,7 +40,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 5,
|
||||
"num": 5,
|
||||
"name": {
|
||||
"en": "",
|
||||
"ja": ["たつ", "tatsu"],
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 6,
|
||||
"num": 6,
|
||||
"name": {
|
||||
"en": "si",
|
||||
"ja": ["み", "mi"],
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 7,
|
||||
"num": 7,
|
||||
"name": {
|
||||
"en": "wu",
|
||||
"ja": ["うま", "uma"],
|
||||
@@ -70,7 +70,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 8,
|
||||
"num": 8,
|
||||
"name": {
|
||||
"en": "wei",
|
||||
"ja": ["ひつじ", "hitsuji"],
|
||||
@@ -80,7 +80,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 9,
|
||||
"num": 9,
|
||||
"name": {
|
||||
"en": "shen",
|
||||
"ja": ["さる", "saru"],
|
||||
@@ -90,7 +90,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 10,
|
||||
"num": 10,
|
||||
"name": {
|
||||
"en": "you",
|
||||
"ja": ["とり", "tori"],
|
||||
@@ -100,7 +100,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 11,
|
||||
"num": 11,
|
||||
"name": {
|
||||
"en": "xu",
|
||||
"ja": ["いぬ", "inu"],
|
||||
@@ -110,7 +110,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 12,
|
||||
"num": 12,
|
||||
"name": {
|
||||
"en": "hai",
|
||||
"ja": ["い", "i"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"no": 1,
|
||||
"num": 1,
|
||||
"name": {
|
||||
"en": "jia",
|
||||
"ja": ["きのえ", "kinoe"],
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 2,
|
||||
"num": 2,
|
||||
"name": {
|
||||
"en": "yi",
|
||||
"ja": ["きのと", "kinoto"],
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 3,
|
||||
"num": 3,
|
||||
"name": {
|
||||
"en": "bing",
|
||||
"ja": ["ひのえ", "hinoe"],
|
||||
@@ -30,7 +30,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 4,
|
||||
"num": 4,
|
||||
"name": {
|
||||
"en": "ding",
|
||||
"ja": ["ひのと", "hinoto"],
|
||||
@@ -40,7 +40,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 5,
|
||||
"num": 5,
|
||||
"name": {
|
||||
"en": "wu",
|
||||
"ja": ["つちのえ", "tsuchinoe"],
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 6,
|
||||
"num": 6,
|
||||
"name": {
|
||||
"en": "ji",
|
||||
"ja": ["つちのと", "tsuchinoe"],
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 7,
|
||||
"num": 7,
|
||||
"name": {
|
||||
"en": "geng",
|
||||
"ja": ["かのえ", "kanoe"],
|
||||
@@ -70,7 +70,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 8,
|
||||
"num": 8,
|
||||
"name": {
|
||||
"en": "xin",
|
||||
"ja": ["かのと", "kanoto"],
|
||||
@@ -80,7 +80,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 9,
|
||||
"num": 9,
|
||||
"name": {
|
||||
"en": "ren",
|
||||
"ja": ["みずのえ", "mizunoe"],
|
||||
@@ -90,7 +90,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"no": 10,
|
||||
"num": 10,
|
||||
"name": {
|
||||
"en": "gui",
|
||||
"ja": ["みずのと", "mizunoto"],
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
},
|
||||
{
|
||||
"num": 5,
|
||||
"direction": null,
|
||||
"direction": "",
|
||||
"name": {
|
||||
"en": "5 Green",
|
||||
"zh_cn": ["五黄土星", "wǔ huáng tǔ xīng"],
|
||||
|
||||
132
json/planet.json
132
json/planet.json
@@ -1,79 +1,101 @@
|
||||
[
|
||||
{
|
||||
"en": "earth",
|
||||
"zh_cn": ["地球", "dì qiú"],
|
||||
"zh_tw": ["地球", "dì qiú"],
|
||||
"ja": ["ちきゅう", "chikyu"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "earth",
|
||||
"zh_cn": ["地球", "dì qiú"],
|
||||
"zh_tw": ["地球", "dì qiú"],
|
||||
"ja": ["ちきゅう", "chikyu"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "moon",
|
||||
"zh_cn": ["月", ""],
|
||||
"zh_tw": ["月", ""],
|
||||
"ja": ["つき", "tsuki"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "moon",
|
||||
"zh_cn": ["月", ""],
|
||||
"zh_tw": ["月", ""],
|
||||
"ja": ["つき", "tsuki"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "mercury"
|
||||
"zh_cn": ["水星", "shuǐ xīng"],
|
||||
"zh_tw": ["水星", "shuǐ xīng"],
|
||||
"ja": ["すいせい", "suisei"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "mercury",
|
||||
"zh_cn": ["水星", "shuǐ xīng"],
|
||||
"zh_tw": ["水星", "shuǐ xīng"],
|
||||
"ja": ["すいせい", "suisei"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "venus",
|
||||
"zh_cn": ["金星", "jīn xīng"],
|
||||
"zh_tw": ["金星", "jīn xīng"],
|
||||
"ja": ["きんせい", "kinsei"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "venus",
|
||||
"zh_cn": ["金星", "jīn xīng"],
|
||||
"zh_tw": ["金星", "jīn xīng"],
|
||||
"ja": ["きんせい", "kinsei"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "sun",
|
||||
"zh_cn": ["太陽", ""],
|
||||
"zh_tw": ["太陽", ""],
|
||||
"ja": ["たいよう", "taiyo"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "sun",
|
||||
"zh_cn": ["太陽", ""],
|
||||
"zh_tw": ["太陽", ""],
|
||||
"ja": ["たいよう", "taiyo"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "mars",
|
||||
"zh_cn": ["火星", "huǒ xīng"],
|
||||
"zh_tw": ["火星", "huǒ xīng"],
|
||||
"ja": ["かせい", "kasei"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "mars",
|
||||
"zh_cn": ["火星", "huǒ xīng"],
|
||||
"zh_tw": ["火星", "huǒ xīng"],
|
||||
"ja": ["かせい", "kasei"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "jupiter",
|
||||
"zh_cn": ["木星", "mù xīng"],
|
||||
"zh_tw": ["木星", "mù xīng"],
|
||||
"ja": ["もくせい", "mokusei"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "jupiter",
|
||||
"zh_cn": ["木星", "mù xīng"],
|
||||
"zh_tw": ["木星", "mù xīng"],
|
||||
"ja": ["もくせい", "mokusei"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "saturn",
|
||||
"zh_cn": ["土星", "tǔ xīng"],
|
||||
"zh_tw": ["土星", "tǔ xīng"],
|
||||
"ja": ["どせい", "dosei"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "saturn",
|
||||
"zh_cn": ["土星", "tǔ xīng"],
|
||||
"zh_tw": ["土星", "tǔ xīng"],
|
||||
"ja": ["どせい", "dosei"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "uranus",
|
||||
"zh_cn": ["天王星", "tiānwáng xīng"],
|
||||
"zh_tw": ["天王星", "tiānwáng xīng"],
|
||||
"ja": ["てんのうせい", ""],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "uranus",
|
||||
"zh_cn": ["天王星", "tiānwáng xīng"],
|
||||
"zh_tw": ["天王星", "tiānwáng xīng"],
|
||||
"ja": ["てんのうせい", ""],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "neptune",
|
||||
"zh_cn": ["海王星", "hǎiwáng xīng"],
|
||||
"zh_tw": ["海王星", "hǎiwáng xīng"],
|
||||
"ja": ["かいおうせい", "kaiosei"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "neptune",
|
||||
"zh_cn": ["海王星", "hǎiwáng xīng"],
|
||||
"zh_tw": ["海王星", "hǎiwáng xīng"],
|
||||
"ja": ["かいおうせい", "kaiosei"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "pluto",
|
||||
"zh_cn": ["冥王星", "míngwáng xīng"],
|
||||
"zh_tw": ["冥王星", "míngwáng xīng"],
|
||||
"ja": ["めいおうせい", "meiosei"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "pluto",
|
||||
"zh_cn": ["冥王星", "míngwáng xīng"],
|
||||
"zh_tw": ["冥王星", "míngwáng xīng"],
|
||||
"ja": ["めいおうせい", "meiosei"],
|
||||
"vi": []
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
[
|
||||
{
|
||||
"en": "wood",
|
||||
"zh_cn": ["木", "mù"],
|
||||
"zh_tw": ["木", "mù"],
|
||||
"ja": ["もく", "moku", "ki"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "wood",
|
||||
"zh_cn": ["木", "mù"],
|
||||
"zh_tw": ["木", "mù"],
|
||||
"ja": ["もく", "moku", "ki"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "fire",
|
||||
"zh_cn": ["火", "huǒ"],
|
||||
"zh_tw": ["火", "huǒ"],
|
||||
"ja": ["か", "ka", "hi"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "fire",
|
||||
"zh_cn": ["火", "huǒ"],
|
||||
"zh_tw": ["火", "huǒ"],
|
||||
"ja": ["か", "ka", "hi"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "earth",
|
||||
"zh_cn": ["土", "tǔ"],
|
||||
"zh_tw": ["土", "tǔ"],
|
||||
"ja": ["ど", "do"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "earth",
|
||||
"zh_cn": ["土", "tǔ"],
|
||||
"zh_tw": ["土", "tǔ"],
|
||||
"ja": ["ど", "do"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "metal",
|
||||
"zh_cn": ["金", "jīn"],
|
||||
"zh_tw": ["金", "jīn"],
|
||||
"ja": ["ごん", "gon"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "metal",
|
||||
"zh_cn": ["金", "jīn"],
|
||||
"zh_tw": ["金", "jīn"],
|
||||
"ja": ["ごん", "gon"],
|
||||
"vi": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"en": "water",
|
||||
"zh_cn": ["水", "shuǐ"],
|
||||
"zh_tw": ["水", "shuǐ"],
|
||||
"ja": ["すい", "sui"],
|
||||
"vi": []
|
||||
"name": {
|
||||
"en": "water",
|
||||
"zh_cn": ["水", "shuǐ"],
|
||||
"zh_tw": ["水", "shuǐ"],
|
||||
"ja": ["すい", "sui"],
|
||||
"vi": []
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
screenshot2.png
Normal file
BIN
screenshot2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
139
src/bagua.rs
139
src/bagua.rs
@@ -1,7 +1,28 @@
|
||||
//! Although 八卦 (Ba-Gua) is a concept in 易経 (I-Ching),
|
||||
//! when used in Feng-Shui, it is often associated with 九星 (Jiu-Xing).
|
||||
//! While everthing in this world is (said to be) divided into either 陰 (Yin)
|
||||
//! or 陽 (Yang), each could be further divided into lesser Yin and Yang.
|
||||
//! For Yang, some may be abundant in Yang. Or, some may slightly lean toward Yin.
|
||||
//! This goes for Yin as well. Here, the initial Yin and Yang,
|
||||
//! now divided into 4, or becomes 4 patterns. Then, for each,
|
||||
//! there are further divisions, and for this time, it now makes it 8 patterns.
|
||||
//! Ancient Chinese had a specific term for these 8 patterns,
|
||||
//! and it is called, 八卦 (Ba-Gua), or "8 Gua".
|
||||
//!
|
||||
//! [0] 坎 (Kan)
|
||||
//! [1] 坤 (Kun)
|
||||
//! [2] 震 (Zhen)
|
||||
//! [3] 巽 (Xun)
|
||||
//! [4] 乾 (Qian)
|
||||
//! [5] 兌 (Dui)
|
||||
//! [6] 艮 (Gen)
|
||||
//! [7] 離 (Li)
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::language::{Language, LanguageTrait};
|
||||
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
|
||||
use crate::utils::{get_json, make_sort};
|
||||
|
||||
/// A struct representing 卦 (Gua) and stores its attributes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Bagua {
|
||||
pub num: u8,
|
||||
@@ -10,8 +31,124 @@ pub struct Bagua {
|
||||
pub element: u8,
|
||||
}
|
||||
|
||||
/// A temporary struct for loading JSON data when defining a static const `BAGUA`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BaguaRawData {
|
||||
pub num: u8,
|
||||
pub name: LanguageData,
|
||||
pub direction: String,
|
||||
pub element: u8,
|
||||
}
|
||||
|
||||
impl LanguageTrait for Bagua {
|
||||
fn name(&self) -> Box<Language> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl NameDataTrait for BaguaRawData {
|
||||
fn name(&self) -> Box<LanguageData> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A static vector with 9 items, each represents 卦 (Gua) of 八卦 (Ba-Gua),
|
||||
/// or what known as "Eight Trigrams". Each stores associated attributes
|
||||
/// for the 卦 (Gua), and are in the order of Ba-Gua Numbers. For instance,
|
||||
/// 坎 (Kan) is the first in Ba-Gua, so it comes first in the vector.
|
||||
/// However, be careful when you refer to each 卦 (Gua) as the program
|
||||
/// refers 卦 (Gua) not by Ba-Gua Numbers, but by vector indexes.
|
||||
/// For 坎 (Kan), for instance, while it has the Ba-Gua Number of 1,
|
||||
/// it is referred as 0 because that is the index is for 坎 (Kan).
|
||||
///
|
||||
/// [0] 坎 (1 = Kan)
|
||||
/// [1] 坤 (2 = Kun)
|
||||
/// [2] 震 (3 = Zhen)
|
||||
/// [3] 巽 (4 = Xun)
|
||||
/// [4] 中 (5 = Zhong)
|
||||
/// [5] 乾 (6 = Qian)
|
||||
/// [6] 兌 (7 = Dui)
|
||||
/// [7] 艮 (8 = Gen)
|
||||
/// [8] 離 (9 = Li)
|
||||
///
|
||||
/// For attributes details stored in the vector is found in JSON file:
|
||||
/// `src/json/bagua.json`
|
||||
pub static ref BAGUA: Vec<Bagua> = {
|
||||
let json = &include_str!("../json/bagua.json");
|
||||
let data: Vec<BaguaRawData> = get_json::<BaguaRawData>(json);
|
||||
data.iter().map(|item| {
|
||||
let item = item.clone();
|
||||
Bagua {
|
||||
num: item.num,
|
||||
name: item.language_from_data(),
|
||||
direction: item.direction,
|
||||
element: item.element,
|
||||
}
|
||||
}).collect()
|
||||
};
|
||||
|
||||
/// Another static vector for 八卦 (Ba-Gua) (and is `Vec<u8>`).
|
||||
/// However, this time, it has only 8 items because this is used
|
||||
/// for compass rotation. When using a compass, only 8 directions matter,
|
||||
/// and the middle does not mean anything. When 八卦 (Ba-Gua) are
|
||||
/// placed in 洛書 (Lo-Shu) order, 中 (Zhong) comes in the middle.
|
||||
/// So, 中 (Zhong) is missing for this vector. For 八卦 (Ba-Gua),
|
||||
/// it begins with 坎 (Kan) which resides in the north
|
||||
/// when they are placed in Lo-Shu order. Moving clockwise,
|
||||
/// what you see next in the north-east, is 艮 (Gen).
|
||||
/// Then, comes 震 (3 = Zhen) which is in the east, and, so on.
|
||||
/// Note that it is `Vec<u8>`, and not `Vec<Bagua>`.
|
||||
///
|
||||
/// [0] 坎 (1 = Kan)
|
||||
/// [1] 艮 (8 = Gen)
|
||||
/// [2] 震 (3 = Zhen)
|
||||
/// [3] 巽 (4 = Xun)
|
||||
/// [4] 離 (9 = Li)
|
||||
/// [5] 坤 (2 = Kun)
|
||||
/// [6] 兌 (7 = Dui)
|
||||
/// [7] 乾 (6 = Qian)
|
||||
pub static ref BAGUA_START_NORTH_INDEXES: Vec<u8> = vec![0, 7, 2, 3, 8, 1, 6, 5];
|
||||
|
||||
/// The order is the same as `BAGUA_START_NORTH_INDEXES`,
|
||||
/// however, it is not `Vec<u8>` but `Vec<Bagua>` for this time.
|
||||
pub static ref BAGUA_START_NORTH: Vec<Bagua> = make_sort(
|
||||
BAGUA_START_NORTH_INDEXES.to_vec()
|
||||
)(
|
||||
BAGUA.to_vec()
|
||||
);
|
||||
}
|
||||
|
||||
/// A getter for `BAGUA_START_NORTH`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::bagua::{get_bagua_start_north, Bagua};
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(index: usize) -> JsValue {
|
||||
/// let bagua: Option<&Bagua> = get_bagua_start_north(index);
|
||||
/// JsValue::from_serde(&bagua).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_bagua_start_north(index: usize) -> Option<&'static Bagua> {
|
||||
match BAGUA_START_NORTH.get(index) {
|
||||
Some(bagua) => Some(bagua),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_constant_bagua() {
|
||||
assert_eq!(BAGUA[0].num, 1);
|
||||
}
|
||||
|
||||
// TODO: BAGUA_START_NORTH_INDEXES
|
||||
// TODO: BAGUA_START_NORTH
|
||||
// TODO: get_get_bagua_start_north
|
||||
}
|
||||
|
||||
486
src/compass.rs
Normal file
486
src/compass.rs
Normal file
@@ -0,0 +1,486 @@
|
||||
//! A module for compass directions. When dividing 360 degrees into 8,
|
||||
//! we get 45 degrees. Ancient Chinese further divided them each into 3
|
||||
//! (called "sectors"), each having 15 degrees. Meaning, there are
|
||||
//! 24 sectors as a total. This is called, 二十四山向 (Er-Shi-Si Shan-Xiang).
|
||||
//! Not only for 8 directions, but these 24 directions (sectors)
|
||||
//! are used in Feng-Shui, and this is the module for these directions.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::bagua::{Bagua, BAGUA};
|
||||
use crate::ganzhi::{Branch, Stem, BRANCHES, STEMS};
|
||||
|
||||
/// 二十四山向 (Er-Shi-Si Shan-Xiang) can be
|
||||
/// either 卦 (Gua), 干 (Gan), or 支 (Zhi).
|
||||
pub enum TwentyFourType<'a> {
|
||||
Bagua(&'a Bagua),
|
||||
Stem(&'a Stem),
|
||||
Branch(&'a Branch),
|
||||
}
|
||||
|
||||
/// A struct representing compass direction.
|
||||
/// For each direction, there are 3 sectors.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Direction {
|
||||
pub direction: String,
|
||||
pub sector: usize,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn new(direction: &str, sector: usize) -> Direction {
|
||||
Direction {
|
||||
direction: direction.to_string(),
|
||||
sector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An array for 8 directions.
|
||||
pub const DIRECTIONS: [&str; 8] = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
|
||||
|
||||
lazy_static! {
|
||||
/// A hash map with 9 items.
|
||||
/// Say, we have 9 boxes displayed on a device screen.
|
||||
/// Except for the box in the middle, we have 8 boxes
|
||||
/// around the middle to represent 8 compass directions.
|
||||
/// When facing "n" (north), for the first row,
|
||||
/// we have "nw", "n", and "ne". For the second row,
|
||||
/// we have "w", "", and "e" (where "" being the middle box).
|
||||
/// For the last, we have "sw", "s", and "se".
|
||||
///
|
||||
/// [0] nw [1] n [2] ne
|
||||
/// [3] w [4] [5] e
|
||||
/// [6] sw [7] s [8] se
|
||||
///
|
||||
/// Now, consider when the device rotates.
|
||||
/// Depending on which direction the device is facing,
|
||||
/// we have different labels. For all 8 directions,
|
||||
/// this HashMap provides a map for the positions.
|
||||
pub static ref DIRECTION_POSITIONS_IN_CHART: HashMap<&'static str, [&'static str; 9]> = [
|
||||
("n", ["nw", "n", "ne", "w", "", "e", "sw", "s", "se"]),
|
||||
("ne", ["n", "ne", "e", "nw", "", "se", "w", "sw", "s"]),
|
||||
("e", ["ne", "e", "se", "n", "", "s", "nw", "w", "sw"]),
|
||||
("se", ["e", "se", "s", "ne", "", "sw", "n", "nw", "w"]),
|
||||
("s", ["se", "s", "sw", "e", "", "w", "ne", "n", "nw"]),
|
||||
("sw", ["s", "sw", "w", "se", "", "nw", "e", "ne", "n"]),
|
||||
("w", ["sw", "w", "nw", "s", "", "n", "se", "e", "ne"]),
|
||||
("nw", ["w", "nw", "n", "sw", "", "ne", "s", "se", "e"]),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
/// An getter for `DIRECTION_POSITIONS_IN_CHART`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::compass::get_direction_positions_in_chart;
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(direction: &str) -> JsValue {
|
||||
/// JsValue::from(
|
||||
/// (match get_direction_positions_in_chart(direction) {
|
||||
/// Some(positions) => positions.to_vec(),
|
||||
/// _ => Vec::new(),
|
||||
/// })
|
||||
/// .into_iter()
|
||||
/// .map(JsValue::from)
|
||||
/// .collect::<js_sys::Array>(),
|
||||
/// )
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_direction_positions_in_chart(direction: &str) -> Option<&[&str; 9]> {
|
||||
DIRECTION_POSITIONS_IN_CHART.get(direction)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A hash map for the opposite direction.
|
||||
pub static ref OPPOSITE_DIRECTION: HashMap<&'static str, &'static str> = [
|
||||
("n", "s"),
|
||||
("ne", "sw"),
|
||||
("e", "w"),
|
||||
("se", "nw"),
|
||||
("s", "n"),
|
||||
("sw", "ne"),
|
||||
("w", "e"),
|
||||
("nw", "se"),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// A getter for `OPPOSITE_DIRECTION`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::compass::get_opposite_direction;
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(direction: &str) -> JsValue {
|
||||
/// JsValue::from(get_opposite_direction(direction))
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_opposite_direction(dir: &str) -> &str {
|
||||
if !OPPOSITE_DIRECTION.contains_key(dir) {
|
||||
panic!("Invalid direction: {}", dir);
|
||||
}
|
||||
OPPOSITE_DIRECTION[dir]
|
||||
}
|
||||
|
||||
/// An array with 24 items. Imagine having a circlar disc displayed
|
||||
/// on a device screen. When dividing 360 by 8 directions, we get
|
||||
/// 45 degrees for each. When each direction is further divided
|
||||
/// into 3, then each is called a "sector", and it has 15 degrees
|
||||
/// for each "sector". Sectors are placed in clockwise order
|
||||
/// (left to right) for each direction, so that you see
|
||||
/// the sector 1 being placed on your very left. Then, you see
|
||||
/// the sector 2 in the middle, and the sector 3 on your right.
|
||||
/// Imagine the device pointing north. On the circular disc,
|
||||
/// what you see at the very top is the sector 2 of "N" (north),
|
||||
/// denoted as "N2". On your left, you see "N1".
|
||||
/// On your right, "N3".
|
||||
///
|
||||
/// When we want to express all the 24 sectors, we want
|
||||
/// an array with 24 items. For the first item in the array [0],
|
||||
/// it is convenient to have "N2". Then, for the second item
|
||||
/// in the array [1], we want "N3". For [2], we want "NE1".
|
||||
/// For [3], we want "NE2". And, so on. As you can imagine,
|
||||
/// "N1" comes to the very last in the array, or [23].
|
||||
pub const TWENTYFOUR_SECTORS: [u8; 24] = [
|
||||
2, // 0: n
|
||||
3, // 1: n
|
||||
1, // 2: ne
|
||||
2, // 3: ne
|
||||
3, // 4: ne
|
||||
1, // 5: e
|
||||
2, // 6: e
|
||||
3, // 7: e
|
||||
1, // 8: se
|
||||
2, // 9: se
|
||||
3, // 10: se
|
||||
1, // 11: s
|
||||
2, // 12: s
|
||||
3, // 13: s
|
||||
1, // 14: sw
|
||||
2, // 15: sw
|
||||
3, // 16: sw
|
||||
1, // 17: w
|
||||
2, // 18: w
|
||||
3, // 19: w
|
||||
1, // 20: nw
|
||||
2, // 21: nw
|
||||
3, // 22: nw
|
||||
1, // 23: n
|
||||
];
|
||||
|
||||
lazy_static! {
|
||||
/// An array with 24 items, for each represents
|
||||
/// each in 二十四山向 (Er-Shi-Si Shan-Xiang).
|
||||
/// Note, the array begins with "N2"
|
||||
/// (and "N1" is stored at the very last, or [23]).
|
||||
/// Ex.
|
||||
/// 0: Direction { direction: "n", sector: 2 }
|
||||
/// 1: Direction { direction: "n", sector: 3 }
|
||||
/// 2: Direction { direction: "ne", sector: 1 }
|
||||
/// 3: Direction { direction: "ne", sector: 2 }
|
||||
pub static ref TWENTYFOUR_INDEX_TO_DIRECTIONS: Vec<Direction> = {
|
||||
let mut vec: Vec<Direction> = DIRECTIONS
|
||||
.iter()
|
||||
.fold(Vec::new(), |mut acc: Vec<Direction>, &direction: &&str| {
|
||||
acc.append(
|
||||
&mut (1..4).map(|sector: usize| {
|
||||
Direction {
|
||||
direction: direction.to_string(),
|
||||
sector,
|
||||
}
|
||||
}).collect()
|
||||
);
|
||||
acc
|
||||
});
|
||||
vec.rotate_left(1);
|
||||
vec
|
||||
};
|
||||
}
|
||||
|
||||
/// A getter for `TWENTYFOUR_INDEX_TO_DIRECTIONS`
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::compass::{get_twentyfour_direction_from_index, Direction};
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(index: usize) -> JsValue {
|
||||
/// let dir: &Direction = get_twentyfour_direction_from_index(index);
|
||||
/// JsValue::from_serde(dir).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_twentyfour_direction_from_index(index: usize) -> &'static Direction {
|
||||
&TWENTYFOUR_INDEX_TO_DIRECTIONS[index]
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A HashMap mapping direction (combination of "direction" and "sector")
|
||||
/// to the corresponding index.
|
||||
///
|
||||
/// n2: 0
|
||||
/// n3: 1
|
||||
/// ne1: 2
|
||||
/// ne2: 3
|
||||
/// ...
|
||||
/// ...
|
||||
pub static ref TWENTYFOUR_DIRECTIONS_TO_INDEX: HashMap<String, usize> = TWENTYFOUR_INDEX_TO_DIRECTIONS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, dir)| {
|
||||
let key = format!("{}{}", dir.direction, dir.sector);
|
||||
(key, i)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// An array with 24 items, each being a tuple. For each tuple,
|
||||
/// the first represents the type of 二十四山向 (Er-Shi-Si Shan-Xiang),
|
||||
/// and the second is the index of the type.
|
||||
/// The type being: [0] BAGUA, [1] STEM, or [2] BRANCH.
|
||||
pub const TWENTYFOUR_ORDER_START_NORTH: [(usize, usize); 24] = [
|
||||
(2, 0), // 0: [0] 子
|
||||
(1, 9), // 1: [9] 癸
|
||||
(2, 1), // 2: [1] 丑
|
||||
(0, 7), // 3: [7] 艮
|
||||
(2, 2), // 4: [2] 寅
|
||||
(1, 0), // 5: [0] 甲
|
||||
(2, 3), // 6: [3] 卯
|
||||
(1, 1), // 7: [1] 乙
|
||||
(2, 4), // 8: [4] 辰
|
||||
(0, 3), // 9: [3] 巽
|
||||
(2, 5), // 10: [5] 巳
|
||||
(1, 2), // 11: [2] 丙
|
||||
(2, 6), // 12: [6] 午
|
||||
(1, 3), // 13: [3] 丁
|
||||
(2, 7), // 14: [7] 未
|
||||
(0, 1), // 15: [1] 坤
|
||||
(2, 8), // 16: [8] 申
|
||||
(1, 6), // 17: [6] 庚
|
||||
(2, 9), // 18: [9] 酉
|
||||
(1, 7), // 19: [7] 辛
|
||||
(2, 10), // 20: [10] 戌
|
||||
(0, 5), // 21: [5] 乾
|
||||
(2, 11), // 22: [11] 亥
|
||||
(1, 8), // 23: [8] 壬
|
||||
];
|
||||
|
||||
/// From index, simply returns the corresponding `TwentyFourType`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::compass::{get_twentyfour_data_from_index, TwentyFourType};
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(index: usize) -> JsValue {
|
||||
/// let t_type: TwentyFourType = get_twentyfour_data_from_index(index);
|
||||
/// match t_type {
|
||||
/// TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
|
||||
/// TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
|
||||
/// TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_twentyfour_data_from_index(index: usize) -> TwentyFourType<'static> {
|
||||
let (t_type, t_index) = TWENTYFOUR_ORDER_START_NORTH[index];
|
||||
match t_type {
|
||||
0 => TwentyFourType::Bagua(&BAGUA[t_index]),
|
||||
1 => TwentyFourType::Stem(&STEMS[t_index]),
|
||||
2 => TwentyFourType::Branch(&BRANCHES[t_index]),
|
||||
_ => panic!("Unknown type: {}", t_type),
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// From **DEGREES**
|
||||
// ===========================================================
|
||||
|
||||
/// From the given degrees, returns the corresponding `Direction`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::compass::{get_twentyfour_direction_from_degrees, Direction};
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(degrees: f32) -> JsValue {
|
||||
/// let dir: Direction = get_twentyfour_direction_from_degrees(degrees);
|
||||
/// JsValue::from_serde(&dir).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_twentyfour_direction_from_degrees(d: f32) -> Direction {
|
||||
if !(7.5..352.5).contains(&d) {
|
||||
// d >= 352.5 || d < 7.5
|
||||
Direction::new("n", 2)
|
||||
} else if d < 22.5 {
|
||||
Direction::new("n", 3)
|
||||
} else if d < 37.5 {
|
||||
Direction::new("ne", 1)
|
||||
} else if d < 52.5 {
|
||||
Direction::new("ne", 2)
|
||||
} else if d < 67.5 {
|
||||
Direction::new("ne", 3)
|
||||
} else if d < 82.5 {
|
||||
Direction::new("e", 1)
|
||||
} else if d < 97.5 {
|
||||
Direction::new("e", 2)
|
||||
} else if d < 112.5 {
|
||||
Direction::new("e", 3)
|
||||
} else if d < 127.5 {
|
||||
Direction::new("se", 1)
|
||||
} else if d < 142.5 {
|
||||
Direction::new("se", 2)
|
||||
} else if d < 157.5 {
|
||||
Direction::new("se", 3)
|
||||
} else if d < 172.5 {
|
||||
Direction::new("s", 1)
|
||||
} else if d < 187.5 {
|
||||
Direction::new("s", 2)
|
||||
} else if d < 202.5 {
|
||||
Direction::new("s", 3)
|
||||
} else if d < 217.5 {
|
||||
Direction::new("sw", 1)
|
||||
} else if d < 232.5 {
|
||||
Direction::new("sw", 2)
|
||||
} else if d < 247.5 {
|
||||
Direction::new("sw", 3)
|
||||
} else if d < 262.5 {
|
||||
Direction::new("w", 1)
|
||||
} else if d < 277.5 {
|
||||
Direction::new("w", 2)
|
||||
} else if d < 292.5 {
|
||||
Direction::new("w", 3)
|
||||
} else if d < 307.5 {
|
||||
Direction::new("nw", 1)
|
||||
} else if d < 322.5 {
|
||||
Direction::new("nw", 2)
|
||||
} else if d < 337.5 {
|
||||
Direction::new("nw", 3)
|
||||
} else {
|
||||
// d < 352.5
|
||||
Direction::new("n", 1)
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// From **DIRECTION**
|
||||
// ===========================================================
|
||||
|
||||
/// From the given direction and sector, finds the corresponding index
|
||||
/// in `TWENTYFOUR_DIRECTIONS_TO_INDEX`
|
||||
pub fn get_twentyfour_index_from_direction(direction: &str, sector: usize) -> usize {
|
||||
*TWENTYFOUR_DIRECTIONS_TO_INDEX
|
||||
.get(format!("{}{}", direction, sector).as_str())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// From the given direction and sector, returns `TwentyFourType`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::compass::{get_twentyfour_data_from_direction, TwentyFourType};
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(direction: &str, sector: usize) -> JsValue {
|
||||
/// let t_type: TwentyFourType = get_twentyfour_data_from_direction(direction, sector);
|
||||
/// match t_type {
|
||||
/// TwentyFourType::Bagua(bagua) => JsValue::from_serde(bagua).unwrap(),
|
||||
/// TwentyFourType::Stem(stem) => JsValue::from_serde(stem).unwrap(),
|
||||
/// TwentyFourType::Branch(branch) => JsValue::from_serde(branch).unwrap(),
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_twentyfour_data_from_direction(
|
||||
direction: &str,
|
||||
sector: usize,
|
||||
) -> TwentyFourType<'static> {
|
||||
get_twentyfour_data_from_index(get_twentyfour_index_from_direction(direction, sector))
|
||||
}
|
||||
|
||||
/// From the given direction and sector, returns `Direction`.
|
||||
pub fn get_twentyfour_direction_from_direction(direction: &str, sector: usize) -> &Direction {
|
||||
&TWENTYFOUR_INDEX_TO_DIRECTIONS[get_twentyfour_index_from_direction(direction, sector)]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO: DIRECTION
|
||||
// TODO: DIRECTION_POSITIONS_IN_CHART
|
||||
|
||||
#[test]
|
||||
fn test_get_direction_positions_in_chart() {
|
||||
let exp = ["nw", "n", "ne", "w", "", "e", "sw", "s", "se"];
|
||||
assert_eq!(get_direction_positions_in_chart("n").unwrap(), &exp);
|
||||
}
|
||||
|
||||
// TODO: OPPOSITE_DIRECTION
|
||||
// TODO: get_opposite_direction
|
||||
// TODO: TWENTYFOUR_SECTORS
|
||||
|
||||
#[test]
|
||||
fn test_constant_twentyfour_index_to_directions() {
|
||||
assert_eq!(
|
||||
TWENTYFOUR_INDEX_TO_DIRECTIONS[0],
|
||||
Direction {
|
||||
direction: String::from("n"),
|
||||
sector: 2,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_twentyfour_direction_from_index() {
|
||||
let exp = Direction {
|
||||
direction: String::from("n"),
|
||||
sector: 2,
|
||||
};
|
||||
assert_eq!(get_twentyfour_direction_from_index(0), &exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constant_twentyfour_directions_to_index() {
|
||||
assert_eq!(*TWENTYFOUR_DIRECTIONS_TO_INDEX.get("n2").unwrap(), 0_usize);
|
||||
}
|
||||
|
||||
// Only for test
|
||||
impl TwentyFourType<'static> {
|
||||
fn is_branch(&self) -> bool {
|
||||
match self {
|
||||
TwentyFourType::Branch(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: TWENTYFOUR_ORDER_START_NORTH
|
||||
|
||||
#[test]
|
||||
fn test_get_twentyfour_data_from_index() {
|
||||
assert!(get_twentyfour_data_from_index(0).is_branch());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_twentyfour_direction_from_degrees() {
|
||||
assert_eq!(
|
||||
get_twentyfour_direction_from_degrees(0_f32),
|
||||
Direction {
|
||||
direction: String::from("n"),
|
||||
sector: 2,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: get_twentyfour_index_from_direction
|
||||
// TODO: get_twentyfour_data_from_direction
|
||||
// TODO: get_twentyfour_direction_from_direction
|
||||
}
|
||||
128
src/constants.rs
128
src/constants.rs
@@ -1,128 +0,0 @@
|
||||
use serde::{Deserialize};
|
||||
|
||||
use crate::ganzhi::{Stem, Branch, StemData, BranchData};
|
||||
use crate::language::NameDataTrait;
|
||||
use crate::solar_terms::{SolarTerm, SolarTermData};
|
||||
use crate::wuxing::{WuXing, WuXingData};
|
||||
|
||||
fn get_json<'a, T: Deserialize<'a>>(json: &'a str) -> Vec<T> {
|
||||
match serde_json::from_str(json) {
|
||||
Ok(json) => json,
|
||||
Err(err) => panic!("Error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SOLAR_TERMS: Vec<SolarTerm> = {
|
||||
let json = &include_str!("../json/solar_terms.json");
|
||||
let data: Vec<SolarTermData> = get_json::<SolarTermData>(json);
|
||||
data.iter().map(|item| {
|
||||
let item = item.clone();
|
||||
SolarTerm {
|
||||
id: item.id,
|
||||
name: item.language_from_data(),
|
||||
angle: item.angle,
|
||||
}
|
||||
}).collect()
|
||||
};
|
||||
|
||||
// Combination of Stems (10) and Branches (12) which makes 60 patterns.
|
||||
pub static ref GANZHI_SEXAGESIMAL: Vec<(usize, usize)> = {
|
||||
let mut v = vec![];
|
||||
for i in 0..60 {
|
||||
let stem = (i % 10) as usize;
|
||||
let branch = (i % 12) as usize;
|
||||
v.push((stem, branch));
|
||||
}
|
||||
v
|
||||
};
|
||||
|
||||
pub static ref STEMS: Vec<Stem> = {
|
||||
let json = &include_str!("../json/ganzhi_stems.json");
|
||||
let data: Vec<StemData> = get_json::<StemData>(json);
|
||||
data.iter().map(|item| {
|
||||
let item = item.clone();
|
||||
Stem {
|
||||
no: item.no,
|
||||
name: item.language_from_data(),
|
||||
}
|
||||
}).collect()
|
||||
};
|
||||
|
||||
pub static ref BRANCHES: Vec<Branch> = {
|
||||
let json = &include_str!("../json/ganzhi_branches.json");
|
||||
let data: Vec<BranchData> = get_json::<BranchData>(json);
|
||||
data.iter().map(|item| {
|
||||
let item = item.clone();
|
||||
Branch {
|
||||
no: item.no,
|
||||
name: item.language_from_data(),
|
||||
}
|
||||
}).collect()
|
||||
};
|
||||
|
||||
pub static ref WUXING: Vec<WuXing> = {
|
||||
let json = &include_str!("../json/wuxing.json");
|
||||
let data: Vec<WuXingData> = get_json::<WuXingData>(json);
|
||||
data.iter().map(|item| {
|
||||
let item = item.clone();
|
||||
WuXing {
|
||||
no: item.no,
|
||||
name: item.language_from_data(),
|
||||
}
|
||||
}).collect()
|
||||
};
|
||||
|
||||
/// This is a table used when finding Hour Stem.
|
||||
/// Columns represents Day Stem groups, and there are 5 groups.
|
||||
/// For insntace, if you have "甲" for Day Stem,
|
||||
/// you are looking into the first column (group).
|
||||
/// Rows represents Hour Branches, and there are 12.
|
||||
/// For instance, if you have "子" for Hour Branch,
|
||||
/// you are looking into the first row.
|
||||
/// Therefore, when you have "甲" for Day Stem,
|
||||
/// and "子" for Hour Branch, Hour Stem is located
|
||||
/// in the first column in the first row, which is "甲".
|
||||
///
|
||||
/// 甲乙丙丁戊
|
||||
/// 己庚辛壬癸
|
||||
/// -------------
|
||||
/// 子: 甲丙戊庚壬
|
||||
/// 丑: 乙丁己辛癸
|
||||
/// 寅: 丙戊庚壬甲
|
||||
/// 卯: 丁己辛癸乙
|
||||
/// 辰: 戊庚壬甲丙
|
||||
/// 巳: 己辛癸乙丁
|
||||
/// 午: 庚壬甲丙戊
|
||||
/// 未: 辛癸乙丁己
|
||||
/// 申: 壬甲丙戊庚
|
||||
/// 酉: 癸乙丁己辛
|
||||
/// 戌: 甲丙戊庚壬
|
||||
/// 亥: 乙丁己辛癸
|
||||
pub static ref HOUR_STEM_TABLE: [[usize; 5]; 12] = [
|
||||
// 子
|
||||
[0, 2, 4, 6, 8], // 甲丙戊庚壬
|
||||
// 丑
|
||||
[1, 3, 5, 7, 9], // 乙丁己辛癸
|
||||
// 寅
|
||||
[2, 4, 6, 8, 0], // 丙戊庚壬甲
|
||||
// 卯
|
||||
[3, 5, 7, 9, 1], // 丁己辛癸乙
|
||||
// 辰
|
||||
[4, 6, 8, 0, 2], // 戊庚壬甲丙
|
||||
// 巳
|
||||
[5, 7, 9, 1, 3], // 己辛癸乙丁
|
||||
// 午
|
||||
[6, 8, 0, 2, 4], // 庚壬甲丙戊
|
||||
// 未
|
||||
[7, 9, 1, 3, 5], // 辛癸乙丁己
|
||||
// 申
|
||||
[8, 0, 2, 4, 6], // 壬甲丙戊庚
|
||||
// 酉
|
||||
[9, 1, 3, 5, 7], // 癸乙丁己辛
|
||||
// 戌
|
||||
[0, 2, 4, 6, 8], // 甲丙戊庚壬
|
||||
// 亥
|
||||
[1, 3, 5, 7, 9], // 乙丁己辛癸
|
||||
];
|
||||
}
|
||||
275
src/ganzhi.rs
275
src/ganzhi.rs
@@ -1,3 +1,39 @@
|
||||
//! Based on 5 elements in nature with its 陰 (Yin) and 陽 (Yang) for each,
|
||||
//! ancient Chinese described the plant growth using 10 conventional symbols
|
||||
//! known as "10 Gan" (十干). Also, they tracked the motion of Jupiter
|
||||
//! (which has 12 year cycle) and so they did divided the night sky into 12 regions,
|
||||
//! and this is known as "12 Zhi" (十二支). When they record time and space,
|
||||
//! they used the combinations of 10 Gan (干) and 12 Zhi (支)
|
||||
//! which makes 60 patterns, and this is called 干支 (Gan-Zhi).
|
||||
//!
|
||||
//! 10 Gan (干):
|
||||
//!
|
||||
//! [0] 甲 (Jia)
|
||||
//! [1] 乙 (Yi)
|
||||
//! [2] 丙 (Bing)
|
||||
//! [3] 丁 (Ding)
|
||||
//! [4] 戊 (Wu)
|
||||
//! [5] 己 (Ji)
|
||||
//! [6] 庚 (Geng)
|
||||
//! [7] 辛 (Xin)
|
||||
//! [8] 壬 (Ren)
|
||||
//! [9] 癸 (Gui)
|
||||
//!
|
||||
//! 12 Zhi (支):
|
||||
//!
|
||||
//! [0] 子 (Zi)
|
||||
//! [1] 丑 (Chou)
|
||||
//! [2] 寅 (Yin)
|
||||
//! [3] 卯 (Mao)
|
||||
//! [4] 辰 (Chen)
|
||||
//! [5] 巳 (Si)
|
||||
//! [6] 午 (Wu)
|
||||
//! [7] 未 (Wei)
|
||||
//! [8] 申 (Shen)
|
||||
//! [9] 酉 (You)
|
||||
//! [10] 戌 (Xu)
|
||||
//! [11] 亥 (Hai)
|
||||
|
||||
#[cfg(test)]
|
||||
use sowngwala::time::Month;
|
||||
|
||||
@@ -6,54 +42,62 @@ use sowngwala::time::{
|
||||
julian_day, julian_day_from_ut, modified_julian_day_from_ut, Date, DateTime, Time,
|
||||
};
|
||||
|
||||
use crate::constants::{BRANCHES, GANZHI_SEXAGESIMAL, HOUR_STEM_TABLE, STEMS};
|
||||
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
|
||||
use crate::solar_terms::get_lichun;
|
||||
use crate::time::ut_from_local;
|
||||
use crate::utils::longitude_of_the_sun_from_date;
|
||||
use crate::utils::{get_json, longitude_of_the_sun_from_date};
|
||||
|
||||
/// A struct representing 干 (Gan) or "Stem" and stores its attributes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Stem {
|
||||
pub no: u8,
|
||||
pub num: u8,
|
||||
pub name: Language,
|
||||
}
|
||||
|
||||
/// A struct representing 支 (Zhi) or "Branch" and stores its attributes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Branch {
|
||||
pub no: u8,
|
||||
pub num: u8,
|
||||
pub name: Language,
|
||||
}
|
||||
|
||||
/// A temporary struct for loading JSON data when defining a static const `STEMS`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StemData {
|
||||
pub no: u8,
|
||||
pub struct StemRawData {
|
||||
pub num: u8,
|
||||
pub name: LanguageData,
|
||||
}
|
||||
|
||||
/// A temporary struct for loading JSON data when defining a static const `BRANCHES`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BranchData {
|
||||
pub no: u8,
|
||||
pub struct BranchRawData {
|
||||
pub num: u8,
|
||||
pub name: LanguageData,
|
||||
}
|
||||
|
||||
impl NameDataTrait for StemData {
|
||||
impl NameDataTrait for StemRawData {
|
||||
fn name(&self) -> Box<LanguageData> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl NameDataTrait for BranchData {
|
||||
impl NameDataTrait for BranchRawData {
|
||||
fn name(&self) -> Box<LanguageData> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct for holding `Stem` and `Branch`, or denoted as 干支 (Gan-Zhi).
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GanZhi<'a> {
|
||||
pub stem: &'a Stem,
|
||||
pub branch: &'a Branch,
|
||||
}
|
||||
|
||||
/// A struct representing 八字 (Bazi) and stores `GanZhi` as its attributes.
|
||||
/// It is referred as "The Four Pillars of Destiny" in English
|
||||
/// mainly because the structure of 八字 (Bazi) necessary
|
||||
/// for divinations in 四柱命理学 (_"The Four Pillars of Destiny"_).
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Bazi<'a> {
|
||||
pub year: GanZhi<'a>,
|
||||
@@ -104,28 +148,177 @@ impl<'a> Bazi<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Bazi` from localtime (`DateTime`) and zone (`i8`).
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::ganzhi::Bazi;
|
||||
/// use mikaboshi::time::{DateTime, Month};
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn get_bazi(params: &JsValue) -> JsValue {
|
||||
/// let localtime = DateTime {
|
||||
/// year: 1985,
|
||||
/// month: Month::Nov,
|
||||
/// day: 5.0,
|
||||
/// hour: 1,
|
||||
/// min: 35,
|
||||
/// sec: 0.0,
|
||||
/// };
|
||||
/// let zone: i8 = 9;
|
||||
/// JsValue::from_serde(&Bazi::from_local(&localtime, zone)).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from_local(lt: &DateTime, zone: i8) -> Bazi {
|
||||
let ut = ut_from_local(<, zone);
|
||||
let ut = ut_from_local(lt, zone);
|
||||
println!("ut: {:?}", ut);
|
||||
|
||||
let year = _year_ganzhi(Box::new(ut));
|
||||
let month = _month_ganzhi(Box::new(ut), year.stem.no);
|
||||
let day = _day_ganzhi(Box::new(ut));
|
||||
let hour = _hour_ganzhi(Box::new(Time::from(lt)), day.stem.no);
|
||||
let year = get_year_ganzhi(Box::new(ut));
|
||||
let month = get_month_ganzhi(Box::new(ut), year.stem.num);
|
||||
let day = get_day_ganzhi(Box::new(ut));
|
||||
let hour = get_hour_ganzhi(Box::new(Time::from(lt)), day.stem.num);
|
||||
Bazi::new(year, month, day, hour)
|
||||
}
|
||||
|
||||
pub fn from_ut(ut: &DateTime, t: &Time) -> Bazi<'a> {
|
||||
let year = _year_ganzhi(Box::new(*ut));
|
||||
let month = _month_ganzhi(Box::new(*ut), year.stem.no);
|
||||
let day = _day_ganzhi(Box::new(*ut));
|
||||
let hour = _hour_ganzhi(Box::new(*t), day.stem.no);
|
||||
let year = get_year_ganzhi(Box::new(*ut));
|
||||
let month = get_month_ganzhi(Box::new(*ut), year.stem.num);
|
||||
let day = get_day_ganzhi(Box::new(*ut));
|
||||
let hour = get_hour_ganzhi(Box::new(*t), day.stem.num);
|
||||
Bazi::new(year, month, day, hour)
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A static vector with 60 items. `Vec<usize, usize>` where the first
|
||||
/// `usize` being the `STEMS` index, and the second for the `BRANCHES`.
|
||||
/// It is simply the combination of 10 stems and 12 branches
|
||||
/// which eventually adds up to 60 patterns.
|
||||
pub static ref GANZHI_SEXAGESIMAL: Vec<(usize, usize)> = {
|
||||
let mut v = vec![];
|
||||
for i in 0..60 {
|
||||
let stem = (i % 10) as usize;
|
||||
let branch = (i % 12) as usize;
|
||||
v.push((stem, branch));
|
||||
}
|
||||
v
|
||||
};
|
||||
|
||||
/// A static vector with 10 items, each represents 干 (Gan).
|
||||
/// Each stores associated attributes for the 干 (Gan).
|
||||
///
|
||||
/// [0] 甲 (Jia)
|
||||
/// [1] 乙 (Yi)
|
||||
/// [2] 丙 (Bing)
|
||||
/// [3] 丁 (Ding)
|
||||
/// [4] 戊 (Wu)
|
||||
/// [5] 己 (Ji)
|
||||
/// [6] 庚 (Geng)
|
||||
/// [7] 辛 (Xin)
|
||||
/// [8] 壬 (Ren)
|
||||
/// [9] 癸 (Gui)
|
||||
///
|
||||
/// For attributes details stored in the vector is found in JSON file:
|
||||
/// `src/json/ganzhi_stems.json`
|
||||
pub static ref STEMS: Vec<Stem> = {
|
||||
let json = &include_str!("../json/ganzhi_stems.json");
|
||||
let data: Vec<StemRawData> = get_json::<StemRawData>(json);
|
||||
data.iter().map(|item| {
|
||||
let item = item.clone();
|
||||
Stem {
|
||||
num: item.num,
|
||||
name: item.language_from_data(),
|
||||
}
|
||||
}).collect()
|
||||
};
|
||||
|
||||
/// A static vector with 10 items, each represents 支 (Zhi).
|
||||
/// Each stores associated attributes for the 支 (Zhi).
|
||||
///
|
||||
/// [0] 子 (Zi)
|
||||
/// [1] 丑 (Chou)
|
||||
/// [2] 寅 (Yin)
|
||||
/// [3] 卯 (Mao)
|
||||
/// [4] 辰 (Chen)
|
||||
/// [5] 巳 (Si)
|
||||
/// [6] 午 (Wu)
|
||||
/// [7] 未 (Wei)
|
||||
/// [8] 申 (Shen)
|
||||
/// [9] 酉 (You)
|
||||
/// [10] 戌 (Xu)
|
||||
/// [11] 亥 (Hai)
|
||||
///
|
||||
/// For attributes details stored in the vector is found in JSON file:
|
||||
/// `src/json/ganzhi_branches.json`
|
||||
pub static ref BRANCHES: Vec<Branch> = {
|
||||
let json = &include_str!("../json/ganzhi_branches.json");
|
||||
let data: Vec<BranchRawData> = get_json::<BranchRawData>(json);
|
||||
data.iter().map(|item| {
|
||||
let item = item.clone();
|
||||
Branch {
|
||||
num: item.num,
|
||||
name: item.language_from_data(),
|
||||
}
|
||||
}).collect()
|
||||
};
|
||||
|
||||
/// This is a table used when finding "Hour Stem".
|
||||
/// Columns represents "Day Stem" groups, and there are 5 groups.
|
||||
/// For insntace, if you have 甲 for "Day Stem",
|
||||
/// you are looking into the first column (group).
|
||||
/// Rows represents "Hour Branches" for which there are 12.
|
||||
/// For instance, if you have 子 for "Hour Branch",
|
||||
/// you are looking into the first row.
|
||||
/// So, when you have 甲 for "Day Stem",
|
||||
/// and 子 for "Hour Branch", "Hour Stem" is located
|
||||
/// in the first column in the first row, which is 甲.
|
||||
///
|
||||
/// 甲乙丙丁戊
|
||||
/// 己庚辛壬癸
|
||||
/// ‐‐‐‐‐‐‐‐‐‐‐‐‐
|
||||
/// 子: 甲丙戊庚壬
|
||||
/// 丑: 乙丁己辛癸
|
||||
/// 寅: 丙戊庚壬甲
|
||||
/// 卯: 丁己辛癸乙
|
||||
/// 辰: 戊庚壬甲丙
|
||||
/// 巳: 己辛癸乙丁
|
||||
/// 午: 庚壬甲丙戊
|
||||
/// 未: 辛癸乙丁己
|
||||
/// 申: 壬甲丙戊庚
|
||||
/// 酉: 癸乙丁己辛
|
||||
/// 戌: 甲丙戊庚壬
|
||||
/// 亥: 乙丁己辛癸
|
||||
pub static ref HOUR_STEM_TABLE: [[usize; 5]; 12] = [
|
||||
// 子
|
||||
[0, 2, 4, 6, 8], // 甲丙戊庚壬
|
||||
// 丑
|
||||
[1, 3, 5, 7, 9], // 乙丁己辛癸
|
||||
// 寅
|
||||
[2, 4, 6, 8, 0], // 丙戊庚壬甲
|
||||
// 卯
|
||||
[3, 5, 7, 9, 1], // 丁己辛癸乙
|
||||
// 辰
|
||||
[4, 6, 8, 0, 2], // 戊庚壬甲丙
|
||||
// 巳
|
||||
[5, 7, 9, 1, 3], // 己辛癸乙丁
|
||||
// 午
|
||||
[6, 8, 0, 2, 4], // 庚壬甲丙戊
|
||||
// 未
|
||||
[7, 9, 1, 3, 5], // 辛癸乙丁己
|
||||
// 申
|
||||
[8, 0, 2, 4, 6], // 壬甲丙戊庚
|
||||
// 酉
|
||||
[9, 1, 3, 5, 7], // 癸乙丁己辛
|
||||
// 戌
|
||||
[0, 2, 4, 6, 8], // 甲丙戊庚壬
|
||||
// 亥
|
||||
[1, 3, 5, 7, 9], // 乙丁己辛癸
|
||||
];
|
||||
}
|
||||
|
||||
/// Year Ganzhi
|
||||
fn _year_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
|
||||
fn get_year_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
|
||||
// Year Stem and Branch are easily found.
|
||||
// However, we must watch out if it is before
|
||||
// or after Lichun. The year begins from Lichun,
|
||||
@@ -161,7 +354,7 @@ fn _year_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
|
||||
|
||||
/// Month Ganzhi
|
||||
#[allow(clippy::boxed_local)]
|
||||
fn _month_ganzhi(ut: Box<DateTime>, year_stem_no: u8) -> GanZhi<'static> {
|
||||
fn get_month_ganzhi(ut: Box<DateTime>, year_stem_num: u8) -> GanZhi<'static> {
|
||||
let lng: f64 = longitude_of_the_sun_from_date(&Date::from(&*ut));
|
||||
|
||||
// Branch is easily found by looking at the longitude of the sun.
|
||||
@@ -198,13 +391,13 @@ fn _month_ganzhi(ut: Box<DateTime>, year_stem_no: u8) -> GanZhi<'static> {
|
||||
// you simply count up to the current month.
|
||||
// This is done by adding 'branch_id' because 'branch_id'
|
||||
// is nothing but how many month from the beginning (Lichun).
|
||||
let stem_index: usize = if year_stem_no == 1 || year_stem_no == 6 {
|
||||
let stem_index: usize = if year_stem_num == 1 || year_stem_num == 6 {
|
||||
2 // 甲(jia:1) or 己(ji:6) ---> 丙(bing:3)
|
||||
} else if year_stem_no == 2 || year_stem_no == 7 {
|
||||
} else if year_stem_num == 2 || year_stem_num == 7 {
|
||||
4 // 乙(yi:2) or 庚(geng:7) ---> 戊(wu:5)
|
||||
} else if year_stem_no == 3 || year_stem_no == 8 {
|
||||
} else if year_stem_num == 3 || year_stem_num == 8 {
|
||||
6 // 丙(bing:3) or 辛(xin:8) ---> 庚(geng:7)
|
||||
} else if year_stem_no == 4 || year_stem_no == 9 {
|
||||
} else if year_stem_num == 4 || year_stem_num == 9 {
|
||||
8 // 丁(ding:4) or 壬(ren:9) ---> 壬(ren:9)
|
||||
} else {
|
||||
0 // 戊(wu:5) or 癸(gui:10) ---> 甲(jia:1)
|
||||
@@ -218,7 +411,7 @@ fn _month_ganzhi(ut: Box<DateTime>, year_stem_no: u8) -> GanZhi<'static> {
|
||||
|
||||
/// Day Ganzhi
|
||||
#[allow(clippy::boxed_local)]
|
||||
fn _day_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
|
||||
fn get_day_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
|
||||
let mjd: f64 = modified_julian_day_from_ut(&*ut);
|
||||
let index = ((mjd - 10.0) % 60.0).floor() as usize;
|
||||
|
||||
@@ -232,7 +425,7 @@ fn _day_ganzhi(ut: Box<DateTime>) -> GanZhi<'static> {
|
||||
|
||||
/// Hour Ganzhi
|
||||
#[allow(clippy::boxed_local)]
|
||||
fn _hour_ganzhi(t: Box<Time>, day_stem_no: u8) -> GanZhi<'static> {
|
||||
fn get_hour_ganzhi(t: Box<Time>, day_stem_num: u8) -> GanZhi<'static> {
|
||||
// The branch is easily found by looking at the hour range of the day.
|
||||
let branch_id: usize = if t.hour == 23 || t.hour == 0 {
|
||||
0
|
||||
@@ -263,16 +456,16 @@ fn _hour_ganzhi(t: Box<Time>, day_stem_no: u8) -> GanZhi<'static> {
|
||||
// The stem is found by looking at a special table.
|
||||
// Read comments for 'HOUR_STEM_TABLE' for details.
|
||||
|
||||
let group_id: usize = if day_stem_no == 1 || day_stem_no == 6 {
|
||||
let group_id: usize = if day_stem_num == 1 || day_stem_num == 6 {
|
||||
0
|
||||
} else if day_stem_no == 2 || day_stem_no == 7 {
|
||||
} else if day_stem_num == 2 || day_stem_num == 7 {
|
||||
1
|
||||
} else if day_stem_no == 3 || day_stem_no == 8 {
|
||||
} else if day_stem_num == 3 || day_stem_num == 8 {
|
||||
2
|
||||
} else if day_stem_no == 4 || day_stem_no == 9 {
|
||||
} else if day_stem_num == 4 || day_stem_num == 9 {
|
||||
3
|
||||
} else {
|
||||
4 // day_stem_no == 5 || day_stem_no == 10
|
||||
4 // day_stem_num == 5 || day_stem_num == 10
|
||||
};
|
||||
|
||||
let stem_id: usize = HOUR_STEM_TABLE[branch_id][group_id];
|
||||
@@ -287,8 +480,22 @@ fn _hour_ganzhi(t: Box<Time>, day_stem_no: u8) -> GanZhi<'static> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// TODO: GANZHI_SEXAGESIMAL
|
||||
|
||||
#[test]
|
||||
fn bazi_from_local_works() {
|
||||
fn test_constant_stems() {
|
||||
assert_eq!(STEMS[0].num, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constant_branches() {
|
||||
assert_eq!(BRANCHES[0].num, 1);
|
||||
}
|
||||
|
||||
// TODO: HOUR_STEM_TABLE
|
||||
|
||||
#[test]
|
||||
fn test_bazi_from_local() {
|
||||
let zone: i8 = 9;
|
||||
|
||||
let lt = DateTime {
|
||||
@@ -320,7 +527,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bazi_from_ut_works() {
|
||||
fn test_bazi_from_ut() {
|
||||
let ut = DateTime {
|
||||
year: 2021,
|
||||
month: Month::Jul,
|
||||
|
||||
719
src/jiuxing.rs
719
src/jiuxing.rs
@@ -1,12 +1,98 @@
|
||||
//! At the beginning, the law governing the universe was simple.
|
||||
//! Yet, as man acquired the faculty of thought, was it no longer so.
|
||||
//! It was not the universe which changed, but was about how man began
|
||||
//! to see the universe differently. Thought, after all, is nothing
|
||||
//! but reflections of the outer world. In another word, the outer
|
||||
//! world we perceive could only be understood via patterns
|
||||
//! that are innate to man's thought. Just like "Malkuth" in _Kabbalha_
|
||||
//! is about both the earthly kingdom and the man himself,
|
||||
//! as ancient Chinese attempted describing patterns in the universe,
|
||||
//! they introduced another artificial element "metal"
|
||||
//! (or "earth" when it is deployed in actual reality).
|
||||
//! For the ancient Chinese, the former is called
|
||||
//! 先天八卦 ("the Primordial Heaven"), and the latter,
|
||||
//! 後天八卦 ("the Manifested Heaven").
|
||||
//! To study the patterns peculiar to each universe, a conventional
|
||||
//! board with 8 directions and 1 in the center has been in use,
|
||||
//! where "8 Gua" (八卦) are assigned for slots on the board.
|
||||
//! However, for many 風水 (Feng-Shui) systems, we are normally
|
||||
//! dealing with the latter, or 後天八卦 ("the Manifested Heaven").
|
||||
//!
|
||||
//! For 後天八卦 ("the Manifested Heaven") has a specific name
|
||||
//! in 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui), and is called
|
||||
//! 地盤 (Di-Pan). However, there are 3 more boards in
|
||||
//! 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui)
|
||||
//! in addition to 地盤 (Di-Pan), namely:
|
||||
//!
|
||||
//! (1) 運盤 (Un-Pan) (or 天盤 (Tien-Pan))
|
||||
//! (2) 山星 (Shan-Xing)
|
||||
//! (3) 向星 (Xiang-Xing)
|
||||
//!
|
||||
//! In practice, for all the above 3 boards, 九星 (Jiu-Xing)
|
||||
//! or "the Nine Stars" are assigned. While "8 Gua" (八卦)
|
||||
//! has fixed positions, 九星 (Jiu-Xing) changes
|
||||
//! over time for spatial constraints given.
|
||||
//! When their positions change, the movement is called
|
||||
//! 飞泊 (Fei-Po) or "flying" because of how it appears
|
||||
//! to our eyes when they move.
|
||||
//!
|
||||
//! For the first board 運盤 (Un-Pan), positions of 九星 (Jiu-Xing)
|
||||
//! are determined by building's construction year,
|
||||
//! and calculated based on 三元九運 (Sang-Yuan Jiu-Yun)
|
||||
//! or "9 Yearly Cycles". We could say that 運盤 (Un-Pan)
|
||||
//! essentially describes of the temporal aspect of the building
|
||||
//! For 山星 (Shan-Xing) and 向星 (Xiang-Xing) are determined
|
||||
//! by spatial aspects of the building, though, temporal aspects
|
||||
//! are also associated indirectly in calculations.
|
||||
//!
|
||||
//! When 運盤 (Un-Pan), 山星 (Shan-Xing), and 向星 (Xiang-Xing)
|
||||
//! are added to 地盤 (Di-Pan) at the bottom, it is called
|
||||
//! 下卦図 (Xia-Gua-Tu), or simply referred as
|
||||
//! 飞星図 (Fei-Xing-Tu; "the Flying Star Chart").
|
||||
//!
|
||||
//! Jiu-Xing (九星):
|
||||
//!
|
||||
//! [0] 一白水星 (1 White)
|
||||
//! [1] 二黒土星 (2 Black)
|
||||
//! [2] 三碧木星 (3 Jade)
|
||||
//! [3] 四緑木星 (4 Green)
|
||||
//! [4] 五黄土星 (5 Yellow)
|
||||
//! [5] 六白金星 (6 White)
|
||||
//! [6] 七赤金星 (7 Red)
|
||||
//! [7] 八白土星 (8 White)
|
||||
//! [8] 九紫火星 (9 Purple)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sowngwala::time::{julian_day, Date};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use crate::language::{Language, LanguageTrait};
|
||||
use crate::compass::{get_opposite_direction, DIRECTIONS, DIRECTION_POSITIONS_IN_CHART};
|
||||
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
|
||||
use crate::planet::{Planet, PLANETS};
|
||||
use crate::utils::{get_json, make_positive};
|
||||
use crate::wuxing::{WuXing, WU_XING};
|
||||
|
||||
pub const SAN_YUAN_JIU_YUN_START_YEAR: u16 = 1864;
|
||||
|
||||
/// A struct representing 九星 (Jiu-Xing).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JiuXing {
|
||||
pub num: u8,
|
||||
pub direction: String,
|
||||
pub name: Language,
|
||||
pub color: String,
|
||||
pub element: WuXing,
|
||||
pub planet: Planet,
|
||||
}
|
||||
|
||||
/// A temporary struct for loading JSON data when defining a static const `JIU_XING`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JiuXingRawData {
|
||||
pub num: u8,
|
||||
pub direction: String,
|
||||
pub name: LanguageData,
|
||||
pub color: String,
|
||||
pub element: u8,
|
||||
pub planet: u8,
|
||||
}
|
||||
@@ -16,3 +102,634 @@ impl LanguageTrait for JiuXing {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl NameDataTrait for JiuXingRawData {
|
||||
fn name(&self) -> Box<LanguageData> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
|
||||
pub enum XiaGuaTuKind {
|
||||
UnPanXing, // 運盤
|
||||
ShanXing, // 山星
|
||||
XiangXing, // 向星
|
||||
}
|
||||
|
||||
/// A struct representing 下卦図 (Xia-Gua-Tu).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct XiaGuaTu<'a> {
|
||||
pub kind: XiaGuaTuKind,
|
||||
pub center: Option<usize>,
|
||||
pub direction: Option<&'a str>,
|
||||
pub sector: Option<usize>,
|
||||
pub chart: Option<[usize; 9]>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A static vector with 9 items, each represents 九星 (Jiu-Xing).
|
||||
///
|
||||
/// [0] 一白水星 (1 White)
|
||||
/// [1] 二黒土星 (2 Black)
|
||||
/// [2] 三碧木星 (3 Jade)
|
||||
/// [3] 四緑木星 (4 Green)
|
||||
/// [4] 五黄土星 (5 Yellow)
|
||||
/// [5] 六白金星 (6 White)
|
||||
/// [6] 七赤金星 (7 Red)
|
||||
/// [7] 八白土星 (8 White)
|
||||
/// [8] 九紫火星 (9 Purple)
|
||||
///
|
||||
/// For attributes details stored in the vector is found in JSON file:
|
||||
/// `src/json/jiuxing.json`
|
||||
pub static ref JIU_XING: [JiuXing; 9] = {
|
||||
let json = &include_str!("../json/jiuxing.json");
|
||||
get_json::<JiuXingRawData>(json)
|
||||
.iter() // TODO: into_iter()?
|
||||
.map(|item| {
|
||||
JiuXing {
|
||||
num: item.num,
|
||||
direction: item.direction.clone(),
|
||||
name: item.language_from_data(),
|
||||
color: item.color.clone(),
|
||||
element: WU_XING[item.element as usize].clone(),
|
||||
planet: PLANETS[item.planet as usize].clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<JiuXing>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
/// A getter for `JIU_XING`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::jiuxing::{get_jiuxing_from_index, JiuXing};
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(index: usize) -> JsValue {
|
||||
/// let dir: &JiuXing = get_jiuxing_from_index(index);
|
||||
/// JsValue::from_serde(dir).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_jiuxing_from_index(index: usize) -> &'static JiuXing {
|
||||
&JIU_XING[index]
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DIRECTION_TO_JIU_XING: HashMap<&'static str, usize> = JIU_XING
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, jiuxing)| {
|
||||
let dir = jiuxing.direction.as_str();
|
||||
match dir {
|
||||
"" => ("", index),
|
||||
_ => (jiuxing.direction.as_str(), index),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn jiuxing_index_from_direction(dir: &str) -> usize {
|
||||
match dir {
|
||||
"" => 4,
|
||||
_ => DIRECTION_TO_JIU_XING[dir],
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Although 洛書 (Lo-Shu) order is fixed, when 地盤 (Di-Pan)
|
||||
/// is drawn on a device screen, the mapping for
|
||||
/// 九星 (Jiu-Xing) changes as the device rotates.
|
||||
/// For example, 一白水星 (1 White) usually comes to the top
|
||||
/// of the board when a device is pointing north. However,
|
||||
/// when pointing north east, 一白水星 (1 White) moves
|
||||
/// to the top left (which is north west).
|
||||
/// For 8 compass directions, this constant provides
|
||||
/// a mapping for the 洛書 (Lo-Shu) order.
|
||||
/// For "n", 一白水星 (1 White) is the 2nd in the array.
|
||||
/// For "ne", 一白水星 (1 White) is the 1st in the array.
|
||||
///
|
||||
/// It would look like this:
|
||||
///
|
||||
/// [5] 六白 [0] 一白 [7] 八白
|
||||
/// [6] 七赤 [4] 五黄 [2] 三碧
|
||||
/// [1] 二黒 [8] 九紫 [3] 四緑
|
||||
/// n: [5, 0, 7, 6, 4, 2, 1, 8, 3]
|
||||
///
|
||||
/// [0] 一白 [7] 八白 [2] 三碧
|
||||
/// [5] 六白 [4] 五黄 [3] 四緑
|
||||
/// [6] 七赤 [1] 二黒 [8] 九紫
|
||||
/// ne: [0, 7, 2, 5, 4, 3, 6, 1, 8]
|
||||
///
|
||||
/// [7] 八白 [2] 三碧 [3] 四緑
|
||||
/// [0] 一白 [4] 五黄 [8] 九紫
|
||||
/// [5] 六白 [6] 七赤 [1] 二黒
|
||||
/// e: [7, 2, 3, 0, 4, 8, 5, 6, 1]
|
||||
///
|
||||
/// [2] 三碧 [3] 四緑 [8] 九紫
|
||||
/// [7] 八白 [4] 五黄 [1] 二黒
|
||||
/// [0] 一白 [5] 六白 [6] 七赤
|
||||
/// se: [2, 3, 8, 7, 4, 1, 0, 5, 6]
|
||||
///
|
||||
/// [3] 四緑 [8] 九紫 [1] 二黒
|
||||
/// [2] 三碧 [4] 五黄 [6] 七赤
|
||||
/// [7] 八白 [0] 一白 [5] 六白
|
||||
/// s: [3, 8, 1, 2, 4, 6, 7, 0, 5]
|
||||
///
|
||||
/// [8] 九紫 [1] 二黒 [6] 七赤
|
||||
/// [3] 四緑 [4] 五黄 [5] 六白
|
||||
/// [2] 三碧 [7] 八白 [0] 一白
|
||||
/// sw: [8, 1, 6, 3, 4, 5, 2, 7, 0]
|
||||
///
|
||||
/// [1] 二黒 [6] 七赤 [5] 六白
|
||||
/// [8] 九紫 [4] 五黄 [0] 一白
|
||||
/// [3] 四緑 [2] 三碧 [7] 八白
|
||||
/// w: [1, 6, 5, 8, 4, 0, 3, 2, 7]
|
||||
///
|
||||
/// [6] 七赤 [5] 六白 [0] 一白
|
||||
/// [1] 二黒 [4] 五黄 [7] 八白
|
||||
/// [8] 九紫 [3] 四緑 [2] 三碧
|
||||
/// nw: [6, 5, 0, 1, 4, 7, 8, 3, 2]
|
||||
pub static ref JIU_XING_DI_PAN_POSITIONS: HashMap<&'static str, [usize; 9]> = DIRECTIONS
|
||||
.iter()
|
||||
.map(|dir| -> (&str, [usize; 9]) {
|
||||
(
|
||||
dir,
|
||||
DIRECTION_POSITIONS_IN_CHART[dir]
|
||||
.iter()
|
||||
.map(|d| jiuxing_index_from_direction(d))
|
||||
.collect::<Vec<usize>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
}
|
||||
|
||||
/// A getter for `JIU_XING_DI_PAN_POSITIONS`.
|
||||
pub fn get_jiuxing_dipan_positions_from_direction(direction: &str) -> Option<&[usize; 9]> {
|
||||
JIU_XING_DI_PAN_POSITIONS.get(direction)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/*
|
||||
* NOT IN USE
|
||||
*
|
||||
* Order for Jiu-Xing, starting north, CLOCK-WISE.
|
||||
* Since it is starting north, see bellow for
|
||||
* where each index is located:
|
||||
*
|
||||
* [7] xxx [0] xxx [1] xxx
|
||||
* [6] xxx [_] ___ [2] xxx
|
||||
* [5] xxx [4] xxx [3] xxx
|
||||
*
|
||||
* or, to be more specific, here are how Jiu-Xing
|
||||
* will be placed on the board:
|
||||
*
|
||||
* [7] 六白金星 (6 White) [0] 一白水星 (1 White) [1] 八白土星 (8 White)
|
||||
* [6] 七赤金星 (7 Red) [_] 五黄土星 (5 Yellow) [2] 三碧木星 (3 Jade)
|
||||
* [5] 二黒土星 (2 Black) [4] 九紫火星 (9 Purple) [3] 四緑木星 (4 Green)
|
||||
*
|
||||
* For each index means:
|
||||
*
|
||||
* [0] --> N --> 一白水星 (1 White) [0]
|
||||
* [1] --> NE --> 八白土星 (8 White) [7]
|
||||
* [2] --> E --> 三碧木星 (3 Jade) [2]
|
||||
* [3] --> SE --> 四緑木星 (4 Green) [3]
|
||||
* [4] --> S --> 九紫火星 (9 Purple) [8]
|
||||
* [5] --> SW --> 二黒土星 (2 Black) [1]
|
||||
* [6] --> W --> 七赤金星 (7 Red) [6]
|
||||
* [7] --> NW --> 六白金星 (6 White) [5]
|
||||
* pub static ref JIU_XING_DI_PAN_POSITIONS_CLOCKWISE: [usize; 8] = [0, 7, 2, 3, 8, 1, 6, 5];
|
||||
*/
|
||||
}
|
||||
|
||||
/// Given incorrect value for Jiu-Xing index, applies a modulo
|
||||
/// to normalize it to fit within the range of 0 to 8.
|
||||
/// Ex.
|
||||
/// 0 -> 0 ... Stays the same. "0" being 一白水星 (1 White).
|
||||
/// 8 -> 8 ... Stays the same. "8" being 九紫火星 (9 Purple).
|
||||
/// 9 -> 0 ... "9" is too much for the range and becoming "0" which is 一白水星 (1 White).
|
||||
/// 10 -> 1 ... "10" is too much, and becoming "1" which is 二黒土星 (2 Black).
|
||||
/// -1 -> 8 ... Making it positive. "8" being 九紫火星 (9 Purple).
|
||||
/// -2 -> 7 ... Making it positive. "8" being 八白土星 (8 White).
|
||||
pub fn normalize_jiuxing(index: i32) -> usize {
|
||||
let tmp = (make_positive(9)(index) + 1) % 9;
|
||||
match tmp {
|
||||
0 => 8,
|
||||
_ => (tmp - 1) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sang-Yuan 三元九運 (Jiu-Yun), or _"9 YEARLY CYCLES"_, is the core
|
||||
/// concept in 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui),
|
||||
/// and it tells how 九星 (Jiu-Xing) fly throughout 180 years.
|
||||
/// This function will calculate from the given:
|
||||
///
|
||||
/// 1. CURRENT LOCALTIME, and
|
||||
/// 2. LIU-CHUN (for the year)
|
||||
///
|
||||
/// for the corresponding Jiu-Xing, for which the generated board
|
||||
/// is usually referred as 運盤 (Un-Pan).
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::jiuxing::unpan_xing_index;
|
||||
/// use mikaboshi::test_mods::DateParams;
|
||||
/// use mikaboshi::time::Date;
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(current: &JsValue, lichun: &JsValue) -> JsValue {
|
||||
/// let params_1: DateParams = current.into_serde().unwrap();
|
||||
/// let params_2: DateParams = lichun.into_serde().unwrap();
|
||||
/// let current = Date::from(¶ms_1);
|
||||
/// let lichun = Date::from(¶ms_2);
|
||||
/// let index: usize = unpan_xing_index(¤t, &lichun);
|
||||
/// JsValue::from_f64(index as f64)
|
||||
/// }
|
||||
pub fn unpan_xing_index(¤t: &Date, &lichun: &Date) -> usize {
|
||||
let year: i16 = if (julian_day(¤t) - julian_day(&lichun)) < 0_f64 {
|
||||
current.year - 1
|
||||
} else {
|
||||
current.year
|
||||
};
|
||||
let dt: i16 = year - SAN_YUAN_JIU_YUN_START_YEAR as i16;
|
||||
let norm: i16 = dt % 180;
|
||||
(norm / 20) as usize
|
||||
}
|
||||
|
||||
// /// Returns Jiu-xing (for Un-Pan)
|
||||
// pub fn unpan_xing_data(¤t: &Date, &lichun: &Date) -> JiuXing {
|
||||
// JIU_XING[unpan_xing_index(¤t, &lichun)].clone()
|
||||
// }
|
||||
|
||||
/// This is a function for 飞泊 (Fei-Po) or "flying".
|
||||
/// The idea is quite simple. Given the order (which is
|
||||
/// the second argument `order` in array) of
|
||||
/// 九星 (Jiu-Xing) indexes, increments or decrements
|
||||
/// each in the array, and simply return the array.
|
||||
/// Depending on whichever currently resides in the center of
|
||||
/// the board (which is the first argument `center`),
|
||||
/// the value to increment or decrement changes.
|
||||
/// For `order` is fundamentally that of the Lo-Shu order
|
||||
/// (which is defined in `JIU_XING_DI_PAN_POSITIONS`),
|
||||
/// however, the layout is always different since
|
||||
/// the position changes depending on which direction
|
||||
/// the device is pointing as the device rotates.
|
||||
pub fn fly_flying_stars(center: usize, order: &[usize; 9], reverse: bool) -> [usize; 9] {
|
||||
let diff: usize = center - 4;
|
||||
order
|
||||
.iter()
|
||||
.map(|index: &usize| -> usize {
|
||||
let index: usize = match reverse {
|
||||
true => 8 - *index,
|
||||
false => *index,
|
||||
} + diff;
|
||||
normalize_jiuxing(index as i32)
|
||||
})
|
||||
.collect::<Vec<usize>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// fn flying_stars_chart(reverse: bool, center: usize, order: &[usize; 9]) -> [JiuXing; 9] {
|
||||
// fly_flying_stars(center, order, reverse)
|
||||
// .iter()
|
||||
// .map(|index: &usize| -> JiuXing { JIU_XING[*index].clone() })
|
||||
// .collect::<Vec<JiuXing>>()
|
||||
// .try_into()
|
||||
// .unwrap()
|
||||
// }
|
||||
|
||||
/// This is a useful well known formula for finding out
|
||||
/// whether 山星 (Shan-Xing) or 向星 (Xiang-Xing) is flying
|
||||
/// in normal order.
|
||||
///
|
||||
/// IMPORTANT:
|
||||
/// It does not work when 九星 (Jiu-Xing) is "5". If such the case,
|
||||
/// center index for Un-Pan must be fed.
|
||||
fn is_shan_xiang_flying_normal(index: usize, sector: usize) -> bool {
|
||||
let num: usize = index + 1;
|
||||
let rem = num % 2;
|
||||
(rem != 0 && sector == 1) || (rem == 0 && sector > 1)
|
||||
}
|
||||
|
||||
/// Looking at the 地盤 (Di-Pan) order, finds the current direction facing.
|
||||
fn direction_from_dipan_order(order: &[usize; 9]) -> &'static str {
|
||||
JIU_XING_DI_PAN_POSITIONS
|
||||
.iter()
|
||||
.find_map(
|
||||
|(dir, dipan_order)| match order.iter().eq(dipan_order.iter()) {
|
||||
true => Some(*dir),
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
/// Calculates for 下卦図 (Xia-Gua-Tu). 1st and 2nd
|
||||
/// arguments (`unpan_xing_center` and `unpan_xing_order`)
|
||||
/// are required for all. For calculating a chart
|
||||
/// for 運盤星 (Un-Pan Xing), that is all we need.
|
||||
/// However, to calculate charts for 山星 (Shan-Xing)
|
||||
/// and 向星 (Xiang-Xing), requires 3rd and 4th arguments
|
||||
/// (`xiang_xing_direction` and `xiang_xing_sector`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use std::collections::HashMap;
|
||||
/// use std::convert::TryInto;
|
||||
/// use mikaboshi::jiuxing::{get_xiaguatu_from_unpan_index, XiaGuaTu};
|
||||
/// use mikaboshi::test_mods::XiaGuaTuParams;
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(params: &JsValue) -> JsValue {
|
||||
/// let params: XiaGuaTuParams = params.into_serde().unwrap();
|
||||
/// let unpan_xing_order: [usize; 9] =
|
||||
/// params
|
||||
/// .unpan_xing_order
|
||||
/// .try_into()
|
||||
/// .unwrap_or_else(|v: Vec<usize>| {
|
||||
/// panic!("Expected a Vec of length 9 but it was {}", v.len())
|
||||
/// });
|
||||
/// let xia_gua_tu: HashMap<&str, XiaGuaTu> = get_xiaguatu_from_unpan_index(
|
||||
/// params.unpan_xing_center,
|
||||
/// &unpan_xing_order,
|
||||
/// params.xiang_xing_direction.as_str(),
|
||||
/// params.xiang_xing_sector,
|
||||
/// );
|
||||
/// JsValue::from_serde(&xia_gua_tu).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_xiaguatu_from_unpan_index<'a>(
|
||||
unpan_xing_center: usize,
|
||||
unpan_xing_order: &'a [usize; 9],
|
||||
xiang_xing_direction: &'a str,
|
||||
xiang_xing_sector: usize,
|
||||
) -> HashMap<&'a str, XiaGuaTu<'a>> {
|
||||
let mut xgtu = HashMap::new();
|
||||
|
||||
// `chart` for 運盤星 (Un-Pan Xing) is straight forward.
|
||||
// The center 九星 (Jiu-Xing) is already given,
|
||||
// and you simply have to fly the given chart.
|
||||
xgtu.insert(
|
||||
"unpan_xing",
|
||||
XiaGuaTu {
|
||||
kind: XiaGuaTuKind::UnPanXing,
|
||||
center: Some(unpan_xing_center),
|
||||
direction: None,
|
||||
sector: None,
|
||||
chart: Some(
|
||||
fly_flying_stars(
|
||||
unpan_xing_center,
|
||||
unpan_xing_order,
|
||||
false
|
||||
)
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
// `chart` for 山星 (Shan-Xing) to be later calculated.
|
||||
// `direction` for 山星 (Shan-Xing) is just the opposite
|
||||
// of 向星 (Xiang-Xing).
|
||||
xgtu.insert(
|
||||
"shan_xing",
|
||||
XiaGuaTu {
|
||||
kind: XiaGuaTuKind::ShanXing,
|
||||
center: None,
|
||||
direction: Some(
|
||||
get_opposite_direction(
|
||||
xiang_xing_direction
|
||||
)
|
||||
),
|
||||
sector: Some(xiang_xing_sector),
|
||||
chart: None,
|
||||
},
|
||||
);
|
||||
|
||||
// `chart` for 向星 (Xiang-Xing) to be later calculated.
|
||||
// `direction` is already given.
|
||||
xgtu.insert(
|
||||
"xiang_xing",
|
||||
XiaGuaTu {
|
||||
kind: XiaGuaTuKind::XiangXing,
|
||||
center: None,
|
||||
direction: Some(xiang_xing_direction),
|
||||
sector: Some(xiang_xing_sector),
|
||||
chart: None,
|
||||
},
|
||||
);
|
||||
|
||||
// First, we need to find out which direction
|
||||
// the device is currently pointing to.
|
||||
let curr_dir: &str = direction_from_dipan_order(unpan_xing_order);
|
||||
|
||||
// Once the direction is found, then we will obtain the compass direction mapping.
|
||||
let compass_positions: [&str; 9] = DIRECTION_POSITIONS_IN_CHART[curr_dir];
|
||||
|
||||
// Find `center` and `chart` for both 山星 (Shan-Xing) and 向星 (Xiang-Xing).
|
||||
for key in ["shan_xing", "xiang_xing"] {
|
||||
// Since we only know directions for 山星 (Shan-Xing)
|
||||
// and 向星 (Xiang-Xing), we will look into the Un-Pan chart,
|
||||
// and will find out what 九星 (Jiu-Xing) we have for the direction.
|
||||
|
||||
// Initially, we only know the direction.
|
||||
let dir: &str = xgtu.get(key).unwrap().direction.unwrap();
|
||||
|
||||
// For the direction, find out its array index.
|
||||
let pos: usize = compass_positions.iter().position(|&d| d == dir).unwrap();
|
||||
|
||||
// Once the index is found, then find out its 九星 (Jiu-Xing).
|
||||
let center: usize = (xgtu.get("unpan_xing").unwrap().chart.unwrap())[pos];
|
||||
|
||||
let prev: &XiaGuaTu = xgtu.get(key).unwrap();
|
||||
|
||||
// It is important to figure out whether it is flying
|
||||
// in normal or reverse order. To do so, we will
|
||||
// use a useful well known formula.
|
||||
let normal: bool = is_shan_xiang_flying_normal(
|
||||
// Having 五黄土星 (5 Yellow) is a special case,
|
||||
// and the formula does not work. Therefore,
|
||||
// replacing it with 運盤星 (Un-Pan Xing) index.
|
||||
if center == 4 {
|
||||
xgtu.get("unpan_xing").unwrap().center.unwrap()
|
||||
} else {
|
||||
center
|
||||
},
|
||||
xgtu.get(key).unwrap().sector.unwrap(),
|
||||
);
|
||||
|
||||
// Now, calculate for the flying chart.
|
||||
let chart: [usize; 9] = fly_flying_stars(
|
||||
center,
|
||||
unpan_xing_order,
|
||||
normal
|
||||
);
|
||||
|
||||
*xgtu.get_mut(key).unwrap() = XiaGuaTu {
|
||||
center: Some(center),
|
||||
chart: Some(chart),
|
||||
..*prev
|
||||
};
|
||||
}
|
||||
|
||||
xgtu
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_constant_jiuxing() {
|
||||
assert_eq!(JIU_XING[0].num, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_jiuxing_from_index() {
|
||||
assert_eq!(get_jiuxing_from_index(0).num, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constant_direction_to_jiuxing() {
|
||||
assert_eq!(*DIRECTION_TO_JIU_XING.get("n").unwrap(), 0);
|
||||
}
|
||||
|
||||
// TODO: jiuxing_index_from_direction
|
||||
// TODO: JIU_XING_DI_PAN_POSITIONS
|
||||
|
||||
#[test]
|
||||
fn test_get_jiuxing_dipan_positions_from_direction() {
|
||||
let exp = [5, 0, 7, 6, 4, 2, 1, 8, 3];
|
||||
assert_eq!(
|
||||
get_jiuxing_dipan_positions_from_direction("n").unwrap(),
|
||||
&exp
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: JIU_XING_DI_PAN_POSITIONS_CLOCKWISE <-- NOT NEEDED
|
||||
|
||||
#[test]
|
||||
fn normalize_jiuxing_single() {
|
||||
assert_eq!(normalize_jiuxing(9), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_jiuxing_all() {
|
||||
let arr: [usize; 11] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let exp: [usize; 11] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1];
|
||||
|
||||
let res: [usize; 11] = arr
|
||||
.iter()
|
||||
.map(|index: &usize| normalize_jiuxing(*index as i32))
|
||||
.collect::<Vec<usize>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, exp);
|
||||
}
|
||||
|
||||
// TODO: unpan_xing_index
|
||||
// TODO: unpan_xing_data <--- NOT IN USE
|
||||
// TODO: flying_stars_chart <--- NOT IN USE
|
||||
// TODO: is_shan_xiang_flying_normal
|
||||
// TODO: get_xiaguatu_from_unpan_index
|
||||
|
||||
// ===================================================================
|
||||
// LO-SHU ORDER - Normal Order
|
||||
// ===================================================================
|
||||
|
||||
// Fly *NORMAL* for Lo-Shu Order: [4] 五黄土星 (5 Green)
|
||||
#[test]
|
||||
fn fly_stars_normal_lo_shu_5_green() {
|
||||
let center: usize = 4; // [4] 五黄土星 (5 Green)
|
||||
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
|
||||
let exp: [usize; 9] = [5, 0, 7, 6, 4, 2, 1, 8, 3];
|
||||
|
||||
assert_eq!(fly_flying_stars(center, order, false), exp);
|
||||
}
|
||||
|
||||
// Fly *NORMAL* for Lo-Shu Order: [5] 六白金星 (6 White)
|
||||
#[test]
|
||||
fn fly_stars_normal_lo_shu_6_white() {
|
||||
let center: usize = 5; // [5] 六白金星 (6 White)
|
||||
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
|
||||
let exp: [usize; 9] = [6, 1, 8, 7, 5, 3, 2, 0, 4];
|
||||
|
||||
assert_eq!(fly_flying_stars(center, order, false), exp);
|
||||
}
|
||||
|
||||
// Fly *NORMAL* for Lo-Shu Order: [6] 七赤金星 (7 Red)
|
||||
#[test]
|
||||
fn fly_stars_normal_lo_shu_7_red() {
|
||||
let center: usize = 6; // [6] 七赤金星 (7 Red)
|
||||
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
|
||||
let exp: [usize; 9] = [7, 2, 0, 8, 6, 4, 3, 1, 5];
|
||||
|
||||
assert_eq!(fly_flying_stars(center, order, false), exp);
|
||||
}
|
||||
|
||||
// Fly *NORMAL* for Lo-Shu Order: [7] 八白土星 (8 White)
|
||||
#[test]
|
||||
fn fly_stars_normal_lo_shu_8_white() {
|
||||
let center: usize = 7; // [7] 八白土星 (8 White)
|
||||
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
|
||||
let exp: [usize; 9] = [8, 3, 1, 0, 7, 5, 4, 2, 6];
|
||||
|
||||
assert_eq!(fly_flying_stars(center, order, false), exp);
|
||||
}
|
||||
|
||||
// Fly *NORMAL* for Lo-Shu Order: [8] 九紫火星 (9 Purple)
|
||||
#[test]
|
||||
fn fly_stars_normal_lo_shu_9_purple() {
|
||||
let center: usize = 8; // [8] 九紫火星 (9 Purple)
|
||||
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
|
||||
let exp: [usize; 9] = [0, 4, 2, 1, 8, 6, 5, 3, 7];
|
||||
|
||||
assert_eq!(fly_flying_stars(center, order, false), exp);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// LO-SHU ORDER - Reverse Order
|
||||
// ===================================================================
|
||||
|
||||
// Fly *REVERSE* for Lo-Shu Order (North): [4] 五黄土星 (5 Green)
|
||||
#[test]
|
||||
fn fly_stars_reverse_5_green_north() {
|
||||
let center: usize = 4; // [4] 五黄土星 (5 Green)
|
||||
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("n").unwrap();
|
||||
let exp: [usize; 9] = [3, 8, 1, 2, 4, 6, 7, 0, 5];
|
||||
|
||||
assert_eq!(fly_flying_stars(center, order,true), exp);
|
||||
}
|
||||
|
||||
// Fly *REVERSE* for Lo-Shu Order (North-East): [4] 五黄土星 (5 Green)
|
||||
#[test]
|
||||
fn fly_stars_reverse_lo_shu_5_green_north_east() {
|
||||
let center: usize = 4; // [4] 五黄土星 (5 Green)
|
||||
let order: &[usize; 9] = JIU_XING_DI_PAN_POSITIONS.get("ne").unwrap();
|
||||
let exp: [usize; 9] = [8, 1, 6, 3, 4, 5, 2, 7, 0];
|
||||
|
||||
assert_eq!(fly_flying_stars(center, order, true), exp);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// COMPASS CLOCKWISE - Normal Order
|
||||
// ===================================================================
|
||||
|
||||
// Fly *NORMAL* for COMPASS CLOCKWISE: [4] 五黄土星 (5 Yellow)
|
||||
// #[test]
|
||||
// fn fly_stars_normal_lo_shu_5_yellow() {
|
||||
// let center: usize = 4; // [4] 五黄土星 (5 Yellow)
|
||||
// let order: &[usize; 8] = &JIU_XING_DI_PAN_POSITIONS_CLOCKWISE;
|
||||
// let exp: [usize; 8] = [0, 7, 2, 3, 8, 1, 6, 5];
|
||||
|
||||
// assert_eq!(fly_flying_stars(center, order,false), exp);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -55,13 +55,22 @@ pub trait LanguageTrait {
|
||||
pub trait NameDataTrait {
|
||||
fn name(&self) -> Box<LanguageData>;
|
||||
|
||||
fn language_details(details: &[String]) -> LanguageDetails {
|
||||
if details.is_empty() {
|
||||
LanguageDetails::new("", "")
|
||||
} else {
|
||||
LanguageDetails::new(&details[0], &details[1])
|
||||
}
|
||||
}
|
||||
|
||||
fn language_from_data(&self) -> Language {
|
||||
let name = &self.name();
|
||||
Language {
|
||||
en: self.name().en,
|
||||
ja: LanguageDetails::new(&self.name().ja[0], &self.name().ja[1]),
|
||||
vi: LanguageDetails::new(&self.name().vi[0], &self.name().vi[1]),
|
||||
zh_cn: LanguageDetails::new(&self.name().zh_cn[0], &self.name().zh_cn[1]),
|
||||
zh_tw: LanguageDetails::new(&self.name().zh_tw[0], &self.name().zh_tw[1]),
|
||||
ja: Self::language_details(&name.ja),
|
||||
vi: Self::language_details(&name.vi),
|
||||
zh_cn: Self::language_details(&name.zh_cn),
|
||||
zh_tw: Self::language_details(&name.zh_tw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod constants;
|
||||
pub mod bagua;
|
||||
pub mod compass;
|
||||
pub mod ganzhi;
|
||||
pub mod jiuxing;
|
||||
pub mod language;
|
||||
pub mod planet;
|
||||
pub mod shengsi;
|
||||
pub mod solar_terms;
|
||||
pub mod time;
|
||||
pub mod utils;
|
||||
pub mod wuxing;
|
||||
|
||||
pub mod test_mods;
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
//! Information about planets in our solar system.
|
||||
//! Notice the planets in `PLANETS` are stored in a special order
|
||||
//! known as _the Ptolemaic Order_. In many ancient traditions,
|
||||
//! when a man is deceased, he will depart the Earth,
|
||||
//! and head toward the Moon. Leaving the Moon behind,
|
||||
//! the Mercury, the Venus, and the Sun. He will continue
|
||||
//! his journey after the Sun, this time, to _the outer planets_,
|
||||
//! that are the Mars, the Jupiter, and the Saturn.
|
||||
//!
|
||||
//! After all, this library provides methodologies
|
||||
//! _NOT_ for _"astronomy"_, but for _"astrology"_, hence,
|
||||
//! follows the tradition which was common to the ancients.
|
||||
//!
|
||||
//! Also noteworthy that, according to Rudolf Steiner,
|
||||
//! "Mercury" was formerly known as "Venus" in ancient times.
|
||||
//! Yet, it is only so when we are talking about the order
|
||||
//! of the _physical_ planets, not in its _symbolical_ sense.
|
||||
//! For instance, when ancients mentioned of "Mercury",
|
||||
//! it was simply about "Mercury" and not "Venus".
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::language::{Language, LanguageTrait};
|
||||
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
|
||||
use crate::utils::get_json;
|
||||
|
||||
/// A struct representing a planet and stores its attributes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Planet {
|
||||
pub num: u8,
|
||||
pub name: Language,
|
||||
pub color: String,
|
||||
pub element: u8,
|
||||
pub planet: u8,
|
||||
}
|
||||
|
||||
/// A temporary struct for loading JSON data when defining a static const `PLANETS`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlanetRawData {
|
||||
pub name: LanguageData,
|
||||
}
|
||||
|
||||
impl LanguageTrait for Planet {
|
||||
@@ -16,3 +39,38 @@ impl LanguageTrait for Planet {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl NameDataTrait for PlanetRawData {
|
||||
fn name(&self) -> Box<LanguageData> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A static vector with 11 items, each represents a planet
|
||||
/// in our solar system. Planets are in Ptolemaic order.
|
||||
///
|
||||
/// [0] Earth
|
||||
/// [1] Moon
|
||||
/// [2] Mercury
|
||||
/// [3] Venus
|
||||
/// [4] Sun
|
||||
/// [5] Mars
|
||||
/// [6] Jupiter
|
||||
/// [7] Saturn
|
||||
/// [8] Uranus
|
||||
/// [9] Neptune
|
||||
/// [10] Pluto
|
||||
///
|
||||
/// For attributes details stored in the vector is found in JSON file:
|
||||
/// `src/json/planets.json`
|
||||
pub static ref PLANETS: Vec<Planet> = {
|
||||
let json = &include_str!("../json/planet.json");
|
||||
let data: Vec<PlanetRawData> = get_json::<PlanetRawData>(json);
|
||||
data.iter()
|
||||
.map(|item| Planet {
|
||||
name: item.language_from_data(),
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
267
src/shengsi.rs
Normal file
267
src/shengsi.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
//! 生死衰旺 (Sheng-Si Shuai-Wang) is just a combination
|
||||
//! of 4 Chinese characters, each being:
|
||||
//!
|
||||
//! (1) Growing --> 生 (Sheng)
|
||||
//! (2) Deadly --> 死 (Si)
|
||||
//! (3) Perishing --> 衰 (Shuai)
|
||||
//! (4) Prosperous --> 旺 (Wang)
|
||||
//!
|
||||
//! They are often used in 四柱命理学 (The Four Pillars of Destiny),
|
||||
//! but used in Feng-Shui as well. It simply suggests
|
||||
//! that there are 4 states to the energy occupying the space.
|
||||
//! In 玄空飞星風水 (Xuan-Kong Fei-Xing Feng-Shui),
|
||||
//! it describes the state for the target year
|
||||
//! in 三元九運 (Sang-Yuan Jiu-Yun),
|
||||
//! especially, for its 向星 (Xiang-Xing).
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::jiuxing::normalize_jiuxing;
|
||||
|
||||
/// A struct representing 生死衰旺 (Sheng-Si Shuai-Wang).
|
||||
/// `key` would be: "sheng", "si", "shuai", or "wang".
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct ShengSi<'a> {
|
||||
pub key: &'a str,
|
||||
pub kanji: &'a str,
|
||||
pub meaning: &'a str,
|
||||
}
|
||||
|
||||
/// A struct holding allocations of 生死衰旺 (Sheng-Si Shuai-Wang) for the given year.
|
||||
/// For `usize` (in `Vec<usize>`) is 九星 (Jiu-Xing) index.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShengSiYearlyAlloc {
|
||||
pub wang: Vec<usize>,
|
||||
pub sheng: Vec<usize>,
|
||||
pub shuai: Vec<usize>,
|
||||
pub si: Vec<usize>,
|
||||
}
|
||||
|
||||
impl ShengSiYearlyAlloc {
|
||||
pub fn accessor(&self, name: &str) -> Option<&Vec<usize>> {
|
||||
match name {
|
||||
"wang" => Some(&self.wang),
|
||||
"sheng" => Some(&self.sheng),
|
||||
"shuai" => Some(&self.shuai),
|
||||
"si" => Some(&self.si),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A HashMap for 生死衰旺 (Sheng-Si Shuai-Wang) by key
|
||||
/// (for each holds `ShengSi`).
|
||||
pub static ref SHENG_SI: HashMap<&'static str, ShengSi<'static>> = HashMap::from([
|
||||
("sheng", ShengSi { key: "sheng", kanji: "生", meaning: "growth" }),
|
||||
("si", ShengSi { key: "si", kanji: "死", meaning: "death" }),
|
||||
("shuai", ShengSi { key: "shuai", kanji: "衰", meaning: "perishing" }),
|
||||
("wang", ShengSi { key: "wang", kanji: "旺", meaning: "prosperous" }),
|
||||
]);
|
||||
|
||||
/// For every year, some 九星 (Jiu-Xing) maybe in 旺 (Wang = Prospering)
|
||||
/// phase, but some maybe in 死 (Si = Dying). 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
/// for 九星 (Jiu-Xing) is no random, but has certain patterns,
|
||||
/// and is repeated every 9 years. This cycle is called
|
||||
/// 三元九運 (Sang-Yuan Jiu-Yun), and given the 運盤星 (Un-Pan Xing) index
|
||||
/// for the specific year, you can tell of 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
/// for all the other 九星 (Jiu-Xing). Here, it is constructing
|
||||
/// the patterns for 9 years, and making them into a static vector
|
||||
/// for which each index being the 運盤星 (Un-Pan Xing) index.
|
||||
/// If you know the 運盤星 (Un-Pan Xing) index for the year,
|
||||
/// this static vector will tell you 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
/// for all 九星 (Jiu-Xing).
|
||||
pub static ref SHENG_SI_ALLOC: Vec<ShengSiYearlyAlloc> = (0..9)
|
||||
.map(|i: i32| {
|
||||
// 旺 (Wang)
|
||||
let unpan_id = i;
|
||||
|
||||
// 生 (Sheng)
|
||||
let sheng: Vec<usize> = [1, 2]
|
||||
.iter()
|
||||
.map(|num| {
|
||||
normalize_jiuxing((unpan_id + num) as i32)
|
||||
})
|
||||
.collect::<Vec<usize>>();
|
||||
|
||||
// 衰 (Shuai)
|
||||
let shuai: Vec<usize> = [1, 2]
|
||||
.iter()
|
||||
.map(|num| {
|
||||
normalize_jiuxing((unpan_id - num) as i32)
|
||||
})
|
||||
.collect::<Vec<usize>>();
|
||||
|
||||
// 死 (Si)
|
||||
let si: Vec<usize> = [1, 2, 3, 4]
|
||||
.iter()
|
||||
.map(|num| -> usize{
|
||||
normalize_jiuxing(shuai[1] as i32 - num)
|
||||
})
|
||||
.collect::<Vec<usize>>();
|
||||
|
||||
ShengSiYearlyAlloc {
|
||||
// 運盤星 (Un-Pan Xing) is always the 旺 (wang) for the year.
|
||||
wang: vec!(unpan_id as usize),
|
||||
|
||||
// Two 九星 (Jiu-Xing) that *proceed* 運盤星 (Un-Pan Xing)
|
||||
// is always the 生 (Sheng).
|
||||
sheng,
|
||||
|
||||
// Two 九星 (Jiu-Xing) that *preceed* 運盤星 (Un-Pan Xing)
|
||||
// is always the 衰 (Shuai). However, there is
|
||||
// an exceptional case when 一白水星 (1 White) were
|
||||
// given for the 運盤星 (Un-Pan Xing) because
|
||||
// it should be converted to 九紫火星 (9 Purple).
|
||||
shuai: if unpan_id == 0 {
|
||||
vec!(8)
|
||||
} else {
|
||||
shuai
|
||||
},
|
||||
|
||||
// Calculation for 死 (Si) is a bit tricky...
|
||||
si: si
|
||||
.iter()
|
||||
.filter_map(|&index| {
|
||||
if unpan_id < 7 {
|
||||
if index != 0 && index != 7 {
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(index)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<usize>>(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Given 運盤 (Un-Pan) index and a layout for the current
|
||||
/// 運盤 (Un-Pan) positions (`&[usize; 9]`), returns the corresponding
|
||||
/// 生死衰旺 (Sheng-Si Shuai-Wang) situation.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use std::convert::TryInto;
|
||||
/// use mikaboshi::shengsi::{get_shengsi_mapping, ShengSi};
|
||||
/// use mikaboshi::test_mods::ShengSiParams;
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(params: &JsValue) -> JsValue {
|
||||
/// let params: ShengSiParams = params.into_serde().unwrap();
|
||||
/// let unpan_id: usize = params.unpan_id;
|
||||
/// let chart: [usize; 9] = params
|
||||
/// .unpan_xing_chart
|
||||
/// .try_into()
|
||||
/// .unwrap_or_else(|v: Vec<usize>| {
|
||||
/// panic!("Expected a Vec of length 9 but it was {}", v.len())
|
||||
/// });
|
||||
/// let mapping: Vec<Option<&ShengSi>> = get_shengsi_mapping(unpan_id, &chart);
|
||||
/// JsValue::from_serde(&mapping).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_shengsi_mapping(
|
||||
unpan_id: usize,
|
||||
unpan_xing_chart: &[usize; 9],
|
||||
) -> Vec<Option<&ShengSi>> {
|
||||
// At first, we will get 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
// for the given 運盤星 (Un-Pan Xing).
|
||||
let yearly_allocs: &ShengSiYearlyAlloc = &SHENG_SI_ALLOC[unpan_id];
|
||||
|
||||
// Now, 生死衰旺 (Sheng-Si Shuai-Wang) just obtained
|
||||
// is mapped by "sheng", "si", "shuai", and "wang".
|
||||
// However, we rather want to look up by 九星 (Jiu-Xing) index.
|
||||
// So, we are creating a temporary mapping here.
|
||||
// Though, in the next line, we are just initializing
|
||||
// each in the mapping with `None`.
|
||||
let mut lookup: HashMap<usize, Option<&ShengSi>> = (0..9)
|
||||
.map(|index: usize| (index, None))
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
// Once the mapping being initialized, we are
|
||||
// creating the mapping.
|
||||
for key in ["sheng", "si", "shuai", "wang"] {
|
||||
let data: Option<&ShengSi> = SHENG_SI.get(key);
|
||||
|
||||
for index in yearly_allocs.accessor(key).unwrap() {
|
||||
*lookup.get_mut(index).unwrap() = data;
|
||||
}
|
||||
}
|
||||
|
||||
// We have 運盤 (Un-Pan) chart given. All we want is
|
||||
// to find 生死衰旺 (Sheng-Si Shuai-Wang)
|
||||
// for each 九星 (Jiu-Xing) in the 運盤 (Un-Pan) chart
|
||||
// (using the temporary mapping just created).
|
||||
unpan_xing_chart
|
||||
.iter()
|
||||
.map(|index: &usize| *lookup.get(index).unwrap())
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_constant_sheng_si() {
|
||||
assert_eq!(SHENG_SI.get("sheng").unwrap().key, "sheng");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constant_sheng_si_alloc_for_wang() {
|
||||
assert_eq!(SHENG_SI_ALLOC[0].wang[0], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constant_sheng_si_alloc_for_shuai() {
|
||||
assert_eq!(SHENG_SI_ALLOC[6].shuai[0], 5);
|
||||
assert_eq!(SHENG_SI_ALLOC[6].shuai[1], 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_shengsi_mapping() {
|
||||
let res = get_shengsi_mapping(6, &[2, 0, 4, 7, 6, 5, 8, 3, 1]);
|
||||
assert_eq!(res[0].unwrap().key, "si"); // 2
|
||||
assert_eq!(res[1], None); // 0
|
||||
assert_eq!(res[2].unwrap().key, "shuai"); // 4
|
||||
assert_eq!(res[3].unwrap().key, "sheng"); // 7
|
||||
assert_eq!(res[4].unwrap().key, "wang"); // 6
|
||||
assert_eq!(res[5].unwrap().key, "shuai"); // 5
|
||||
assert_eq!(res[6].unwrap().key, "sheng"); // 8
|
||||
assert_eq!(res[7].unwrap().key, "si"); // 3
|
||||
assert_eq!(res[8].unwrap().key, "si"); // 1
|
||||
}
|
||||
}
|
||||
|
||||
// 生入 Sheng-Ru (Shēng Rù)
|
||||
// 剋入 Ke-Ru (Kè Rù)
|
||||
// 生出 Sheng-Chu (Shēng Chū)
|
||||
// 剋出 Ke-Chu (Kè Chū)
|
||||
// 差錯 Cha-Cuo
|
||||
|
||||
// 旺 Prosperous
|
||||
// 相 Supportive
|
||||
// 休 Rest
|
||||
// 囚 Inprisoned
|
||||
// 死 Death
|
||||
|
||||
// Chang Sheng 12 Qi Phase (十二運)
|
||||
//
|
||||
// 长生 chang sheng
|
||||
// 沐浴 mu yu
|
||||
// 冠带 guan dai
|
||||
// 临官 lin guān
|
||||
// 帝旺 di wang
|
||||
// 衰 Shuāi
|
||||
// 病 Bing
|
||||
// 死 Si
|
||||
// 墓 Mu
|
||||
// 绝 Jue
|
||||
// 胎 Tai
|
||||
// 养 Yang
|
||||
@@ -1,8 +1,10 @@
|
||||
//! A module for "二十四节气" (Er-Shi-Si Jie-Qi).
|
||||
//! Or, for calculating "立春" (Li-Chun).
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sowngwala::time::{add_date, Date, Month};
|
||||
|
||||
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
|
||||
use crate::utils::longitude_of_the_sun_from_date;
|
||||
use crate::utils::{get_json, longitude_of_the_sun_from_date};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SolarTerm {
|
||||
@@ -12,7 +14,7 @@ pub struct SolarTerm {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SolarTermData {
|
||||
pub struct SolarTermRawData {
|
||||
pub id: u8,
|
||||
pub name: LanguageData,
|
||||
pub angle: u16,
|
||||
@@ -24,15 +26,32 @@ impl LanguageTrait for SolarTerm {
|
||||
}
|
||||
}
|
||||
|
||||
impl NameDataTrait for SolarTermData {
|
||||
impl NameDataTrait for SolarTermRawData {
|
||||
fn name(&self) -> Box<LanguageData> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SOLAR_TERMS: Vec<SolarTerm> = {
|
||||
let json = &include_str!("../json/solar_terms.json");
|
||||
let data: Vec<SolarTermRawData> = get_json::<SolarTermRawData>(json);
|
||||
data.iter()
|
||||
.map(|item| {
|
||||
let item = item.clone();
|
||||
SolarTerm {
|
||||
id: item.id,
|
||||
name: item.language_from_data(),
|
||||
angle: item.angle,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
pub fn get_last_term(date: &Date) -> (f64, Date) {
|
||||
let lng_0: f64 = longitude_of_the_sun_from_date(&date);
|
||||
let lng_0: f64 = longitude_of_the_sun_from_date(date);
|
||||
// For the unit of 15, we want the last term.
|
||||
// Ex.
|
||||
// 317.435511 --> 315.0
|
||||
@@ -61,8 +80,20 @@ pub fn get_last_term(date: &Date) -> (f64, Date) {
|
||||
(target, term.unwrap())
|
||||
}
|
||||
|
||||
/// Given the year, returns the year's Lichun in date.
|
||||
/// * `date` - &Date
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use mikaboshi::solar_terms::get_lichun;
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn xx(year: i16) -> JsValue {
|
||||
/// let lichun = get_lichun(year);
|
||||
/// JsValue::from_str(&format!(
|
||||
/// "{:04}-{:02}-{:02}",
|
||||
/// lichun.year as u16, lichun.month as u8, lichun.day as u8
|
||||
/// ))
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
pub fn get_lichun(year: i16) -> Date {
|
||||
let d: Date = Date {
|
||||
@@ -79,7 +110,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_get_last_term() {
|
||||
fn test_get_last_term() {
|
||||
let date = Date {
|
||||
year: 2022,
|
||||
month: Month::Feb,
|
||||
|
||||
61
src/test_mods.rs
Normal file
61
src/test_mods.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Modules used ONLY from tests.
|
||||
*/
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sowngwala::time::{Date, DateTime, Month};
|
||||
use std::convert::{From, TryFrom};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||
pub struct DateParams {
|
||||
pub year: u16,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
}
|
||||
|
||||
impl From<&DateParams> for Date {
|
||||
fn from(¶ms: &DateParams) -> Self {
|
||||
Date {
|
||||
year: params.year as i16,
|
||||
month: Month::try_from(params.month as i32).unwrap(),
|
||||
day: params.day as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||
pub struct DateTimeParams {
|
||||
pub year: u16,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
pub hour: u8,
|
||||
pub min: u8,
|
||||
pub sec: u8,
|
||||
pub zone: i8,
|
||||
}
|
||||
|
||||
impl From<&DateTimeParams> for DateTime {
|
||||
fn from(¶ms: &DateTimeParams) -> Self {
|
||||
DateTime {
|
||||
year: params.year as i16,
|
||||
month: Month::try_from(params.month as i32).unwrap(),
|
||||
day: params.day as f64,
|
||||
hour: params.hour as i16,
|
||||
min: params.min as i16,
|
||||
sec: params.sec as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct XiaGuaTuParams {
|
||||
pub unpan_xing_center: usize,
|
||||
pub unpan_xing_order: Vec<usize>,
|
||||
pub xiang_xing_direction: String,
|
||||
pub xiang_xing_sector: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ShengSiParams {
|
||||
pub unpan_id: usize,
|
||||
pub unpan_xing_chart: Vec<usize>,
|
||||
}
|
||||
42
src/utils.rs
42
src/utils.rs
@@ -1,6 +1,44 @@
|
||||
// use core::iter::FromIterator;
|
||||
// use std::iter::FromIterator;
|
||||
use serde::Deserialize;
|
||||
|
||||
use sowngwala::sun::ecliptic_position_of_the_sun_from_date;
|
||||
use sowngwala::time::Date;
|
||||
|
||||
pub fn longitude_of_the_sun_from_date(date: &Date) -> f64 {
|
||||
ecliptic_position_of_the_sun_from_date(&date).lng
|
||||
pub fn get_json<'a, T: Deserialize<'a>>(json: &'a str) -> Vec<T> {
|
||||
match serde_json::from_str(json) {
|
||||
Ok(json) => json,
|
||||
Err(err) => panic!("Error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
/// For the given Vec, sorts by given order.
|
||||
pub fn make_sort<T: Clone>(order: Vec<u8>) -> Box<dyn Fn(Vec<T>) -> Vec<T>> {
|
||||
Box::new(move |source: Vec<T>| -> Vec<T> {
|
||||
order
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|index| source[index as usize].clone())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// Increments by the given step until it becomes more than 0.
|
||||
pub fn make_positive(step: u32) -> Box<dyn Fn(i32) -> u32> {
|
||||
Box::new(move |mut num: i32| -> u32 {
|
||||
let limit = 10000;
|
||||
let mut cnt = 0;
|
||||
while num < 0 {
|
||||
if cnt > limit {
|
||||
panic!("Iteration reached: {}", limit);
|
||||
}
|
||||
num += step as i32;
|
||||
cnt += 1;
|
||||
}
|
||||
num as u32
|
||||
})
|
||||
}
|
||||
|
||||
pub fn longitude_of_the_sun_from_date(date: &Date) -> f64 {
|
||||
ecliptic_position_of_the_sun_from_date(date).lng
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
//! A module for 五行 (Wu-Xing).
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::language::{Language, LanguageData, LanguageTrait, NameDataTrait};
|
||||
use crate::utils::get_json;
|
||||
|
||||
/// A struct representing 五行 (Wu-Xing).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WuXing {
|
||||
pub no: u8,
|
||||
pub name: Language,
|
||||
}
|
||||
|
||||
/// A temporary struct for loading JSON data when defining a static const `WU_XING`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WuXingData {
|
||||
pub no: u8,
|
||||
pub struct WuXingRawData {
|
||||
pub name: LanguageData,
|
||||
}
|
||||
|
||||
@@ -20,8 +22,27 @@ impl LanguageTrait for WuXing {
|
||||
}
|
||||
}
|
||||
|
||||
impl NameDataTrait for WuXingData {
|
||||
impl NameDataTrait for WuXingRawData {
|
||||
fn name(&self) -> Box<LanguageData> {
|
||||
Box::new(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// A static vector with 5 items, each represents 五行 (Wu-Xing).
|
||||
///
|
||||
/// For attributes details stored in the vector is found in JSON file:
|
||||
/// `src/json/wuxing.json`
|
||||
pub static ref WU_XING: Vec<WuXing> = {
|
||||
let json = &include_str!("../json/wuxing.json");
|
||||
let data: Vec<WuXingRawData> = get_json::<WuXingRawData>(json);
|
||||
data.iter()
|
||||
.map(|item| {
|
||||
let item = item.clone();
|
||||
WuXing {
|
||||
name: item.language_from_data(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
BIN
tricky.png
Normal file
BIN
tricky.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Reference in New Issue
Block a user