diff --git a/Cargo.toml b/Cargo.toml index 34cff55..ecd624a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 99b25c4..40d5add 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,187 @@ # mikaboshi -## About +![screenshot](./screenshot.png) +![screenshot2](./screenshot2.png) -`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" + +![tricky device rotation](./tricky.png) + +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)) diff --git a/docs/bagua.md b/docs/bagua.md new file mode 100644 index 0000000..b44637d --- /dev/null +++ b/docs/bagua.md @@ -0,0 +1,127 @@ +# 八卦 (Bagua) + +Source: [src/bagua.rs](../src/bagua.rs) + +![sample_bagua](./sample_bagua.png) + +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` + +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` + +Another static vector for 八卦 (Ba-Gua) (and is `Vec`). +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` + +While the order is the same as `BAGUA_START_NORTH_INDEXES`, +for this time, it is `Vec` instead of `Vec`. + +## 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() +} +``` diff --git a/docs/compass.md b/docs/compass.md new file mode 100644 index 0000000..ac9d38a --- /dev/null +++ b/docs/compass.md @@ -0,0 +1,286 @@ +# 二十四山向 (Er-Shi-Si Shan-Xiang) + +Source: [src/compass.rs](../src/compass.rs) + +![sample_twentyfour](./sample_twentyfour.png) + +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` + +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` + +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::(), + ) +} +``` + +## 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` + diff --git a/docs/examples/Cargo.toml b/docs/examples/Cargo.toml new file mode 100644 index 0000000..2b37665 --- /dev/null +++ b/docs/examples/Cargo.toml @@ -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 diff --git a/docs/examples/FengShui.js b/docs/examples/FengShui.js new file mode 100644 index 0000000..efb6787 --- /dev/null +++ b/docs/examples/FengShui.js @@ -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 ( + + ); +}; + +// ---------------------------------------------------------------- +// 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; +} diff --git a/docs/examples/bagua.jsx b/docs/examples/bagua.jsx new file mode 100644 index 0000000..5cd51f9 --- /dev/null +++ b/docs/examples/bagua.jsx @@ -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 ( +
+ +
+ ); +}; + +// 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(' '); diff --git a/docs/examples/bagua_info.jsx b/docs/examples/bagua_info.jsx new file mode 100644 index 0000000..2b8d0fe --- /dev/null +++ b/docs/examples/bagua_info.jsx @@ -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 ( +
+ {boxes.map((box, i) => ( +
+ +
{box.ch}
+
+ ))} +
+ ); +}; diff --git a/docs/examples/chart.jsx b/docs/examples/chart.jsx new file mode 100644 index 0000000..a1a05e1 --- /dev/null +++ b/docs/examples/chart.jsx @@ -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 ? ( +
+ {errors.map(err => ( +
{err.error}
+ ))} +
+ ) : ( +
+ + + + + +
+ ); +}; diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..8d434de --- /dev/null +++ b/docs/examples/index.md @@ -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 ( + +}; + +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( + + + , + 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
{pizza}
; +}; +``` + +## 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`. diff --git a/docs/examples/jiuxing.jsx b/docs/examples/jiuxing.jsx new file mode 100644 index 0000000..3ac38aa --- /dev/null +++ b/docs/examples/jiuxing.jsx @@ -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 ( +
+
+
+ {flyingStarChart.map((box, i) => ( +
+
+ {box.shan_xing.num} +
+ +
+ {box.xiang_xing.num} +
+ +
+
{box.unpan_xing.num}
+
{box.unpan_xing.kanji}
+
+
+ ))} +
+
+
+ ); +}; diff --git a/docs/examples/lib.rs b/docs/examples/lib.rs new file mode 100644 index 0000000..f0bc9bc --- /dev/null +++ b/docs/examples/lib.rs @@ -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| { + 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::(), + ) +} + +// ================================================================ +// 生死衰旺 (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| { + panic!("Expected a Vec of length 9 but it was {}", v.len()) + }); + + let mapping: Vec> = _get_shengsi_mapping(unpan_id, &chart); + // log(&format!("[wasm] mapping: {:?}", mapping)); + + JsValue::from_serde(&mapping).unwrap() +} diff --git a/docs/examples/north.jsx b/docs/examples/north.jsx new file mode 100644 index 0000000..3ee8259 --- /dev/null +++ b/docs/examples/north.jsx @@ -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 ( +
+ +
+ ); +}; diff --git a/docs/examples/twentyfour.jsx b/docs/examples/twentyfour.jsx new file mode 100644 index 0000000..6ba6063 --- /dev/null +++ b/docs/examples/twentyfour.jsx @@ -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 ( +
+ +
+ ); +}; diff --git a/docs/examples/twentyfour_info.jsx b/docs/examples/twentyfour_info.jsx new file mode 100644 index 0000000..dd0f2c6 --- /dev/null +++ b/docs/examples/twentyfour_info.jsx @@ -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 ( +
+ {boxes.map((box, i) => ( +
+
{box.text}
+
{box.text2}
+
+ ))} +
+ ); +}; diff --git a/docs/examples/utils.js b/docs/examples/utils.js new file mode 100644 index 0000000..4d85f17 --- /dev/null +++ b/docs/examples/utils.js @@ -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); + } + }; +}; diff --git a/docs/ganzhi.md b/docs/ganzhi.md new file mode 100644 index 0000000..9321fcd --- /dev/null +++ b/docs/ganzhi.md @@ -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` + +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` + +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` 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() +} +``` diff --git a/docs/jiuxing.md b/docs/jiuxing.md new file mode 100644 index 0000000..8e70326 --- /dev/null +++ b/docs/jiuxing.md @@ -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, + pub direction: Option<&'a str>, + pub sector: Option, + 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| { + 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() +} +``` diff --git a/docs/planet.md b/docs/planet.md new file mode 100644 index 0000000..ed425bc --- /dev/null +++ b/docs/planet.md @@ -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` + +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) + diff --git a/docs/sample_bagua.png b/docs/sample_bagua.png new file mode 100644 index 0000000..5768a4c Binary files /dev/null and b/docs/sample_bagua.png differ diff --git a/docs/sample_shengsi.png b/docs/sample_shengsi.png new file mode 100644 index 0000000..be64340 Binary files /dev/null and b/docs/sample_shengsi.png differ diff --git a/docs/sample_twentyfour.png b/docs/sample_twentyfour.png new file mode 100644 index 0000000..9358ec5 Binary files /dev/null and b/docs/sample_twentyfour.png differ diff --git a/docs/shengsi.md b/docs/shengsi.md new file mode 100644 index 0000000..4b9c130 --- /dev/null +++ b/docs/shengsi.md @@ -0,0 +1,104 @@ +# 生死衰旺 (Sheng-Si Shuai-Wang) + +Source: [src/shengsi.rs](../src/shengsi.rs) + +![sample_shengsi](./sample_shengsi.png) + +生死衰旺 (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`) is 九星 (Jiu-Xing) index. + +```rust +#[derive(Debug, Clone)] +pub struct ShengSiYearlyAlloc { + pub wang: Vec, + pub sheng: Vec, + pub shuai: Vec, + pub si: Vec, +} +``` + +## shengsi::SHENG_SI + +`HashMap<&str, ShengSi>` + +A HashMap for 生死衰旺 (Sheng-Si Shuai-Wang) by key +(for each holds `ShengSi`). + +## shengsi::SHENG_SI_ALLOC + +`Vec` + +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| { + panic!("Expected a Vec of length 9 but it was {}", v.len()) + }); + let mapping: Vec> = get_shengsi_mapping(unpan_id, &chart); + JsValue::from_serde(&mapping).unwrap() +} +``` diff --git a/docs/solar_terms.md b/docs/solar_terms.md new file mode 100644 index 0000000..7709b0b --- /dev/null +++ b/docs/solar_terms.md @@ -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` + +## 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 + )) +} +``` diff --git a/docs/time.md b/docs/time.md new file mode 100644 index 0000000..c7d3224 --- /dev/null +++ b/docs/time.md @@ -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 +// } +``` diff --git a/json/bagua.json b/json/bagua.json index e18b62e..c41dffb 100644 --- a/json/bagua.json +++ b/json/bagua.json @@ -4,7 +4,7 @@ "name": { "en": "kan", "zh_cn": ["坎", "kǎn"], - "zh_cn": ["坎", "kǎn"], + "zh_tw": ["坎", "kǎn"], "ja": ["かん", "kan"], "vi": [] }, diff --git a/json/ganzhi_branches.json b/json/ganzhi_branches.json index 15ecd94..5fc5bcd 100644 --- a/json/ganzhi_branches.json +++ b/json/ganzhi_branches.json @@ -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"], diff --git a/json/ganzhi_stems.json b/json/ganzhi_stems.json index 311c149..9f23052 100644 --- a/json/ganzhi_stems.json +++ b/json/ganzhi_stems.json @@ -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"], diff --git a/json/jiuxing.json b/json/jiuxing.json index 9462947..66173f0 100644 --- a/json/jiuxing.json +++ b/json/jiuxing.json @@ -57,7 +57,7 @@ }, { "num": 5, - "direction": null, + "direction": "", "name": { "en": "5 Green", "zh_cn": ["五黄土星", "wǔ huáng tǔ xīng"], diff --git a/json/planet.json b/json/planet.json index e57d5b2..ace7964 100644 --- a/json/planet.json +++ b/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": [] + } } ] diff --git a/json/wuxing.json b/json/wuxing.json index c29f618..ec98616 100644 --- a/json/wuxing.json +++ b/json/wuxing.json @@ -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": [] + } } ] diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..44e6af1 Binary files /dev/null and b/screenshot.png differ diff --git a/screenshot2.png b/screenshot2.png new file mode 100644 index 0000000..988de75 Binary files /dev/null and b/screenshot2.png differ diff --git a/src/bagua.rs b/src/bagua.rs index 31c31ab..43f1d2e 100644 --- a/src/bagua.rs +++ b/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 { Box::new(self.name.clone()) } } + +impl NameDataTrait for BaguaRawData { + fn name(&self) -> Box { + 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 = { + let json = &include_str!("../json/bagua.json"); + let data: Vec = get_json::(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`). + /// 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`, and not `Vec`. + /// + /// [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 = vec![0, 7, 2, 3, 8, 1, 6, 5]; + + /// The order is the same as `BAGUA_START_NORTH_INDEXES`, + /// however, it is not `Vec` but `Vec` for this time. + pub static ref BAGUA_START_NORTH: Vec = 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 +} diff --git a/src/compass.rs b/src/compass.rs new file mode 100644 index 0000000..afc1aea --- /dev/null +++ b/src/compass.rs @@ -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::(), +/// ) +/// } +/// ``` +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 = { + let mut vec: Vec = DIRECTIONS + .iter() + .fold(Vec::new(), |mut acc: Vec, &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 = 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 +} diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index 1c723d1..0000000 --- a/src/constants.rs +++ /dev/null @@ -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 { - match serde_json::from_str(json) { - Ok(json) => json, - Err(err) => panic!("Error: {}", err), - } -} - -lazy_static! { - pub static ref SOLAR_TERMS: Vec = { - let json = &include_str!("../json/solar_terms.json"); - let data: Vec = get_json::(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 = { - let json = &include_str!("../json/ganzhi_stems.json"); - let data: Vec = get_json::(json); - data.iter().map(|item| { - let item = item.clone(); - Stem { - no: item.no, - name: item.language_from_data(), - } - }).collect() - }; - - pub static ref BRANCHES: Vec = { - let json = &include_str!("../json/ganzhi_branches.json"); - let data: Vec = get_json::(json); - data.iter().map(|item| { - let item = item.clone(); - Branch { - no: item.no, - name: item.language_from_data(), - } - }).collect() - }; - - pub static ref WUXING: Vec = { - let json = &include_str!("../json/wuxing.json"); - let data: Vec = get_json::(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], // 乙丁己辛癸 - ]; -} diff --git a/src/ganzhi.rs b/src/ganzhi.rs index f19871d..7d8991c 100644 --- a/src/ganzhi.rs +++ b/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 { Box::new(self.name.clone()) } } -impl NameDataTrait for BranchData { +impl NameDataTrait for BranchRawData { fn name(&self) -> Box { 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` 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 = { + let json = &include_str!("../json/ganzhi_stems.json"); + let data: Vec = get_json::(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 = { + let json = &include_str!("../json/ganzhi_branches.json"); + let data: Vec = get_json::(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) -> GanZhi<'static> { +fn get_year_ganzhi(ut: Box) -> 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) -> GanZhi<'static> { /// Month Ganzhi #[allow(clippy::boxed_local)] -fn _month_ganzhi(ut: Box, year_stem_no: u8) -> GanZhi<'static> { +fn get_month_ganzhi(ut: Box, 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, 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, year_stem_no: u8) -> GanZhi<'static> { /// Day Ganzhi #[allow(clippy::boxed_local)] -fn _day_ganzhi(ut: Box) -> GanZhi<'static> { +fn get_day_ganzhi(ut: Box) -> 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) -> GanZhi<'static> { /// Hour Ganzhi #[allow(clippy::boxed_local)] -fn _hour_ganzhi(t: Box