diff --git a/AGENTS.md b/AGENTS.md index 4b26518..95a0e08 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,3 +33,35 @@ - `website/` is a completely separate project — it must **never** affect the library build or tests - Any new sub-project directory **must** be added to the root `tsconfig.json` `exclude` list AND to the `exclude` regex in `webpack.config.js` before committing - After adding a sub-project, always run `npm run build` and `npm test` from the **root** to verify isolation + +## Website / Astro content rules +- **MDX required for component imports:** Starlight content files that use `import` and JSX component tags **must** have a `.mdx` extension. A `.md` file will print the import statement as plain text and silently ignore all component tags. +- **Multi-instance inline script loading:** When an Astro `is:inline` script dynamically loads an external JS bundle, multiple component instances on the same page will all run simultaneously. Use a shared queue pattern to avoid race conditions: + ```javascript + if (window.astrochart) { + initChart() + } else if (document.querySelector('script[src="/astrochart.js"]')) { + window.__astrochartQueue = window.__astrochartQueue || [] + window.__astrochartQueue.push(initChart) + } else { + window.__astrochartQueue = [initChart] + const s = document.createElement('script') + s.src = '/astrochart.js' + s.onload = () => { (window.__astrochartQueue || []).forEach(fn => fn()); window.__astrochartQueue = [] } + document.head.appendChild(s) + } + ``` + +## AstroChart library — data shape +The real `AstroData` type (from `project/src/radix.ts`) is: +```typescript +interface AstroData { + planets: Record // key = symbol name, value = [degrees, retrograde?] + cusps: number[] // exactly 12 degree values +} +``` +- **Valid planet keys** (anything else renders as a red fallback circle with no warning): + `Sun`, `Moon`, `Mercury`, `Venus`, `Mars`, `Jupiter`, `Saturn`, `Uranus`, `Neptune`, `Pluto`, `Chiron`, `Lilith`, `NNode`, `SNode`, `Fortune` +- **Cusps** must be an array of **exactly 12** numbers (degrees); fewer or more will throw via `validate()` +- **Retrograde:** second element of a planet array — negative value = retrograde (e.g. `[245.5, -1]`) +- Do **not** use the invented `Planet[]`/`Cusp[]` shape that appears in older placeholder docs — it does not match the library diff --git a/library-issues/bug-settings-mutation-across-instances.md b/library-issues/bug-settings-mutation-across-instances.md new file mode 100644 index 0000000..a74a234 --- /dev/null +++ b/library-issues/bug-settings-mutation-across-instances.md @@ -0,0 +1,71 @@ +# Bug: `Chart` constructor mutates the shared `default_settings` singleton + +**Type:** Bug +**Affects:** `project/src/chart.ts` → `Chart` constructor +**Severity:** Medium — causes cross-instance settings bleed when multiple charts are on the same page with different custom settings + +--- + +## Current behaviour + +`default_settings` is imported as a module-level singleton object. In the `Chart` constructor, custom settings are merged into it **in-place** via `Object.assign`: + +```typescript +// chart.ts — current constructor +const chartSettings = default_settings // ← just a reference, not a copy +if (settings != null) { + Object.assign(chartSettings, settings) // ← mutates the shared singleton! + ... +} +``` + +Because `chartSettings` is a reference to the same object as `default_settings`, any settings passed to one `Chart` instance permanently modify the module-level default for all subsequent `Chart` instances in the same page/process. + +## Reproduction + +```javascript +import { Chart } from '@astrodraw/astrochart' + +// First chart — custom red background +const chart1 = new Chart('chart1', 600, 600, { COLOR_BACKGROUND: '#ff0000' }) + +// Second chart — no custom settings passed, expects white background +const chart2 = new Chart('chart2', 600, 600) +// ❌ chart2 ALSO has a red background because default_settings was mutated +``` + +## Expected behaviour + +Each `Chart` instance should have its own isolated copy of the settings. The module-level `default_settings` must remain pristine. + +## Suggested fix + +Replace the reference assignment with a deep copy: + +```typescript +// chart.ts — fixed constructor +constructor (elementId: string, width: number, height: number, settings?: Partial) { + // Create a fresh copy of defaults for this instance + const chartSettings: Settings = { ...default_settings } + + if (settings != null) { + Object.assign(chartSettings, settings) + if (!('COLORS_SIGNS' in settings)) { + chartSettings.COLORS_SIGNS = [ + chartSettings.COLOR_ARIES, chartSettings.COLOR_TAURUS, + // ... rest of sign colours + ] + } + } + // ... +} +``` + +For nested objects (e.g. `ASPECTS`, `DIGNITIES_EXACT_EXALTATION_DEFAULT`) a shallow spread may not be enough — consider `structuredClone(default_settings)` if those nested objects are also mutated downstream. + +## Notes + +- The bug may be masked in most use cases where only one `Chart` instance is created per page, or where the same settings are reused +- It is reproducible whenever two `Chart` instances with *different* custom settings are created in the same JavaScript context +- Should be covered by a new test: create two Chart instances in sequence with conflicting settings and assert each uses its own values +- The `COLORS_SIGNS` re-computation block inside the `if (settings != null)` branch also references `default_settings.COLOR_*` — after the fix it should reference `chartSettings.COLOR_*` instead (already correct behaviour, just noting for the reviewer) diff --git a/library-issues/improve-validate-unknown-planet-keys.md b/library-issues/improve-validate-unknown-planet-keys.md new file mode 100644 index 0000000..90a4641 --- /dev/null +++ b/library-issues/improve-validate-unknown-planet-keys.md @@ -0,0 +1,83 @@ +# Improvement: `validate()` does not check for unknown planet keys + +**Type:** Improvement (not a breaking bug) +**Affects:** `project/src/utils.ts` → `validate()` +**Discovered during:** Issue #94 — writing demo data for the website + +--- + +## Current behaviour + +The `validate()` function checks that: +- `data.planets` exists +- each planet value is an `Array` +- `data.cusps` is an Array of exactly 12 values + +It does **not** check whether planet keys are in the set of recognised symbol names. + +```typescript +// validate() — current loop (utils.ts) +for (const property in data.planets) { + if (!Array.isArray(data.planets[property])) { + status.messages.push(...) + status.hasError = true + } + // ↑ only validates the value shape — the KEY is never checked +} +``` + +As a result, passing an unrecognised key (e.g. `NorthNode`, `Vertex`, `PartOfFortune`) **silently succeeds** validation and the library renders a generic red fallback circle at that position — with no warning to the developer. + +## How it was discovered + +When writing `demoData.ts` for the website, the following keys were used by mistake: + +```typescript +// ❌ These do NOT work — wrong key names +NorthNode: [95.45, 0], +SouthNode: [275.45, 0], +Vertex: [325.67, 0] + +// ✅ Correct names +NNode: [95.45, 0], +SNode: [275.45, 0], +Fortune: [325.67, 0] +``` + +The chart appeared to load but no symbols were drawn for those planets — no console error, no validation message. + +## Expected behaviour + +`validate()` should emit a **warning** (not a hard error, to avoid breaking existing charts) when it encounters a key that is not in the recognised symbol list: + +``` +"Unknown planet key 'NorthNode'. Valid keys are: Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Chiron, Lilith, NNode, SNode, Fortune." +``` + +## Suggested fix + +```typescript +// In utils.ts, extend validate() with an optional warning step: +const KNOWN_PLANET_KEYS = new Set([ + 'Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', + 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Chiron', + 'Lilith', 'NNode', 'SNode', 'Fortune' +]) + +for (const property in data.planets) { + if (!Array.isArray(data.planets[property])) { + status.messages.push(`The planets property '${property}' has to be Array.`) + status.hasError = true + } else if (!KNOWN_PLANET_KEYS.has(property)) { + // Warning only — unknown keys are allowed (custom symbols), + // but we surface the information to help developers catch typos + console.warn(`[AstroChart] Unknown planet key '${property}'. It will render as a fallback symbol.`) + } +} +``` + +## Notes + +- This should be a **`console.warn`**, not an error — unknown keys with a `CUSTOM_SYMBOL_FN` setting are a valid use case +- The `DEBUG` settings flag could gate the warning if preferred +- Should be covered by a new unit test in `utils.test.ts` diff --git a/tsconfig.json b/tsconfig.json index 09747aa..34f7704 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,5 +10,5 @@ "declaration": true, "strictNullChecks": true }, - "exclude": ["project/src/**/*.test.ts", "dist", "website"] + "exclude": ["project/src/**/*.test.ts", "project/__tests__", "dist", "website"] } \ No newline at end of file diff --git a/website/public/astrochart.js b/website/public/astrochart.js new file mode 100644 index 0000000..bf84840 --- /dev/null +++ b/website/public/astrochart.js @@ -0,0 +1 @@ +!function(t,s){"object"==typeof exports&&"object"==typeof module?module.exports=s():"function"==typeof define&&define.amd?define([],s):"object"==typeof exports?exports.astrochart=s():t.astrochart=s()}(self,(()=>(()=>{"use strict";var t={d:(s,e)=>{for(var i in e)t.o(e,i)&&!t.o(s,i)&&Object.defineProperty(s,i,{enumerable:!0,get:e[i]})},o:(t,s)=>Object.prototype.hasOwnProperty.call(t,s),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},s={};t.r(s),t.d(s,{AspectCalculator:()=>_,Chart:()=>m,default:()=>R});const e={SYMBOL_SCALE:1,COLOR_BACKGROUND:"#fff",POINTS_COLOR:"#000",POINTS_TEXT_SIZE:8,POINTS_STROKE:1.8,SIGNS_COLOR:"#000",SIGNS_STROKE:1.5,MARGIN:50,PADDING:18,ID_CHART:"astrology",ID_RADIX:"radix",ID_TRANSIT:"transit",ID_ASPECTS:"aspects",ID_POINTS:"planets",ID_SIGNS:"signs",ID_CIRCLES:"circles",ID_AXIS:"axis",ID_CUSPS:"cusps",ID_RULER:"ruler",ID_BG:"bg",CIRCLE_COLOR:"#333",CIRCLE_STRONG:2,LINE_COLOR:"#333",INDOOR_CIRCLE_RADIUS_RATIO:2,INNER_CIRCLE_RADIUS_RATIO:8,RULER_RADIUS:4,SYMBOL_SUN:"Sun",SYMBOL_MOON:"Moon",SYMBOL_MERCURY:"Mercury",SYMBOL_VENUS:"Venus",SYMBOL_MARS:"Mars",SYMBOL_JUPITER:"Jupiter",SYMBOL_SATURN:"Saturn",SYMBOL_URANUS:"Uranus",SYMBOL_NEPTUNE:"Neptune",SYMBOL_PLUTO:"Pluto",SYMBOL_CHIRON:"Chiron",SYMBOL_LILITH:"Lilith",SYMBOL_NNODE:"NNode",SYMBOL_SNODE:"SNode",SYMBOL_FORTUNE:"Fortune",SYMBOL_AS:"As",SYMBOL_DS:"Ds",SYMBOL_MC:"Mc",SYMBOL_IC:"Ic",SYMBOL_AXIS_FONT_COLOR:"#333",SYMBOL_AXIS_STROKE:1.6,SYMBOL_CUSP_1:"1",SYMBOL_CUSP_2:"2",SYMBOL_CUSP_3:"3",SYMBOL_CUSP_4:"4",SYMBOL_CUSP_5:"5",SYMBOL_CUSP_6:"6",SYMBOL_CUSP_7:"7",SYMBOL_CUSP_8:"8",SYMBOL_CUSP_9:"9",SYMBOL_CUSP_10:"10",SYMBOL_CUSP_11:"11",SYMBOL_CUSP_12:"12",CUSPS_STROKE:1,CUSPS_FONT_COLOR:"#000",SYMBOL_ARIES:"Aries",SYMBOL_TAURUS:"Taurus",SYMBOL_GEMINI:"Gemini",SYMBOL_CANCER:"Cancer",SYMBOL_LEO:"Leo",SYMBOL_VIRGO:"Virgo",SYMBOL_LIBRA:"Libra",SYMBOL_SCORPIO:"Scorpio",SYMBOL_SAGITTARIUS:"Sagittarius",SYMBOL_CAPRICORN:"Capricorn",SYMBOL_AQUARIUS:"Aquarius",SYMBOL_PISCES:"Pisces",SYMBOL_SIGNS:["Aries","Taurus","Gemini","Cancer","Leo","Virgo","Libra","Scorpio","Sagittarius","Capricorn","Aquarius","Pisces"],COLOR_ARIES:"#FF4500",COLOR_TAURUS:"#8B4513",COLOR_GEMINI:"#87CEEB",COLOR_CANCER:"#27AE60",COLOR_LEO:"#FF4500",COLOR_VIRGO:"#8B4513",COLOR_LIBRA:"#87CEEB",COLOR_SCORPIO:"#27AE60",COLOR_SAGITTARIUS:"#FF4500",COLOR_CAPRICORN:"#8B4513",COLOR_AQUARIUS:"#87CEEB",COLOR_PISCES:"#27AE60",COLORS_SIGNS:["#FF4500","#8B4513","#87CEEB","#27AE60","#FF4500","#8B4513","#87CEEB","#27AE60","#FF4500","#8B4513","#87CEEB","#27AE60"],CUSTOM_SYMBOL_FN:null,SHIFT_IN_DEGREES:180,STROKE_ONLY:!1,ADD_CLICK_AREA:!1,COLLISION_RADIUS:10,ASPECTS:{conjunction:{degree:0,orbit:10,color:"transparent"},square:{degree:90,orbit:8,color:"#FF4500"},trine:{degree:120,orbit:8,color:"#27AE60"},opposition:{degree:180,orbit:10,color:"#27AE60"}},SHOW_DIGNITIES_TEXT:!0,DIGNITIES_RULERSHIP:"r",DIGNITIES_DETRIMENT:"d",DIGNITIES_EXALTATION:"e",DIGNITIES_EXACT_EXALTATION:"E",DIGNITIES_FALL:"f",DIGNITIES_EXACT_EXALTATION_DEFAULT:[{name:"Sun",position:19,orbit:2},{name:"Moon",position:33,orbit:2},{name:"Mercury",position:155,orbit:2},{name:"Venus",position:357,orbit:2},{name:"Mars",position:298,orbit:2},{name:"Jupiter",position:105,orbit:2},{name:"Saturn",position:201,orbit:2},{name:"NNode",position:63,orbit:2}],ANIMATION_CUSPS_ROTATION_SPEED:2,DEBUG:!1};var i=function(t,s,e,i,n){var r=(n.SHIFT_IN_DEGREES-i)*Math.PI/180;return{x:t+e*Math.cos(r),y:s+e*Math.sin(r)}},n=function(t){return 180*t/Math.PI},r=function(t,s,e){var i=[],n=t.x+e.COLLISION_RADIUS/1.4*e.SYMBOL_SCALE,r=t.y-e.COLLISION_RADIUS*e.SYMBOL_SCALE;return s.forEach((function(t,s){i.push({text:t,x:n,y:r+e.COLLISION_RADIUS/1.4*e.SYMBOL_SCALE*s})}),this),i},h=function(t){var s={hasError:!1,messages:[]};if(null==t)return s.messages.push("Data is not set."),s.hasError=!0,s;for(var e in null==t.planets&&(s.messages.push("There is not property 'planets'."),s.hasError=!0),t.planets)t.planets.hasOwnProperty(e)&&(Array.isArray(t.planets[e])||(s.messages.push("The planets property '"+e+"' has to be Array."),s.hasError=!0));return null==t.cusps||Array.isArray(t.cusps)||(s.messages.push("Property 'cusps' has to be Array."),s.hasError=!0),null!=t.cusps&&12!==t.cusps.length&&(s.messages.push("Count of 'cusps' values has to be 12."),s.hasError=!0),s},a=function(t,s,e){var i=document.getElementById(s);if(null!=i)return o(i),i;var n=document.getElementById(e);if(null==n)throw new Error("Paper element should exist");var r=document.createElementNS(n.namespaceURI,"g");return r.setAttribute("id",s),t.appendChild(r),r},o=function(t){if(null!=t)for(var s;null!=(s=t.lastChild);)t.removeChild(s)},S=function(t,s,e,n){if(0===t.length)return t.push(s),t;if(2*Math.PI*e.r-n.COLLISION_RADIUS*n.SYMBOL_SCALE*2*(t.length+2)<=0)throw n.DEBUG&&console.log("Universe circumference: "+2*Math.PI*e.r+", Planets circumference: "+n.COLLISION_RADIUS*n.SYMBOL_SCALE*2*(t.length+2)),new Error("Unresolved planet collision. Try change SYMBOL_SCALE or paper size.");var r,h,a,o,p,g=!1;t.sort(c);for(var A=0,L=t.length;A180&&(e=(e+180)%360,i=(i+180)%360),e<=i?(t.angle=t.angle-1,s.angle=s.angle+1):e>=i&&(t.angle=t.angle+1,s.angle=s.angle-1),t.angle=(t.angle+360)%360,s.angle=(s.angle+360)%360},p=function(t,s,e,n,r,h){for(var a=[],o=n,S=e<=n?o-Math.abs(n-e)/2:o+Math.abs(n-e)/2,u=0,p=0;u<72;u++){var c=p+r,g=i(t,s,e,c,h),A=i(t,s,u%2==0?o:S,c,h);a.push({startX:g.x,startY:g.y,endX:A.x,endY:A.y}),p+=5}return a},c=function(t,s){return t.angle-s.angle};const g=function(){function t(t,s){if(null===t)throw new Error("Param 'cusps' must not be empty.");if(!Array.isArray(t)||12!==t.length)throw new Error("Param 'cusps' is not 12 length Array.");this.cusps=t,this.settings=null!=s?s:e}return t.prototype.getSign=function(t){var s=t%n(2*Math.PI);return Math.floor(s/30+1)},t.prototype.isRetrograde=function(t){return t<0},t.prototype.getHouseNumber=function(t){for(var s=t%n(2*Math.PI),e=0,i=this.cusps.length;e=this.cusps[e]&&sthis.cusps[e%(i-1)+1])return e+1;throw new Error("Oops, serious error in the method: 'astrology.Zodiac.getHouseNumber'.")},t.prototype.getDignities=function(t,s){if(!t||!t.name||null==t.position)return[];var e=[],i=this.getSign(t.position);switch(t.position,n(2*Math.PI),t.name){case this.settings.SYMBOL_SUN:5===i?e.push(this.settings.DIGNITIES_RULERSHIP):11===i&&e.push(this.settings.DIGNITIES_DETRIMENT),1===i?e.push(this.settings.DIGNITIES_EXALTATION):6===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_MOON:4===i?e.push(this.settings.DIGNITIES_RULERSHIP):10===i&&e.push(this.settings.DIGNITIES_DETRIMENT),2===i?e.push(this.settings.DIGNITIES_EXALTATION):8===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_MERCURY:3===i?e.push(this.settings.DIGNITIES_RULERSHIP):9===i&&e.push(this.settings.DIGNITIES_DETRIMENT),6===i?e.push(this.settings.DIGNITIES_EXALTATION):12===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_VENUS:2===i||7===i?e.push(this.settings.DIGNITIES_RULERSHIP):1!==i&&8!==i||e.push(this.settings.DIGNITIES_DETRIMENT),12===i?e.push(this.settings.DIGNITIES_EXALTATION):6===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_MARS:1===i||8===i?e.push(this.settings.DIGNITIES_RULERSHIP):2!==i&&7!==i||e.push(this.settings.DIGNITIES_DETRIMENT),10===i?e.push(this.settings.DIGNITIES_EXALTATION):4===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_JUPITER:9===i||12===i?e.push(this.settings.DIGNITIES_RULERSHIP):3!==i&&6!==i||e.push(this.settings.DIGNITIES_DETRIMENT),4===i?e.push(this.settings.DIGNITIES_EXALTATION):10===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_SATURN:10===i||11===i?e.push(this.settings.DIGNITIES_RULERSHIP):4!==i&&5!==i||e.push(this.settings.DIGNITIES_DETRIMENT),7===i?e.push(this.settings.DIGNITIES_EXALTATION):1===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_URANUS:11===i?e.push(this.settings.DIGNITIES_RULERSHIP):5===i&&e.push(this.settings.DIGNITIES_DETRIMENT),8===i?e.push(this.settings.DIGNITIES_EXALTATION):2===i&&e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_NEPTUNE:12===i?e.push(this.settings.DIGNITIES_RULERSHIP):6===i&&e.push(this.settings.DIGNITIES_DETRIMENT),5===i||9===i?e.push(this.settings.DIGNITIES_EXALTATION):11!==i&&3!==i||e.push(this.settings.DIGNITIES_FALL);break;case this.settings.SYMBOL_PLUTO:8===i?e.push(this.settings.DIGNITIES_RULERSHIP):2===i&&e.push(this.settings.DIGNITIES_DETRIMENT),1===i?e.push(this.settings.DIGNITIES_EXALTATION):7===i&&e.push(this.settings.DIGNITIES_FALL)}if(null!=s&&Array.isArray(s))for(var r=0,h=s.length;r=n(2*Math.PI)?s+e/2-n(2*Math.PI):s+e/2;return r>h?r>=t&&t<=r&&(i=!0):r<=t&&t<=h&&(i=!0),i},t}();var A={conjunction:{degree:0,orbit:10,color:"transparent"},square:{degree:90,orbit:8,color:"#FF4500"},trine:{degree:120,orbit:8,color:"#27AE60"},opposition:{degree:180,orbit:10,color:"#27AE60"}},L=function(){function t(t,s){var e;if(null==t)throw new Error("Param 'toPoint' must not be empty.");this.settings=null!=s?s:{},this.settings.ASPECTS=null!==(e=null==s?void 0:s.ASPECTS)&&void 0!==e?e:A,this.toPoints=t,this.context=this}return t.prototype.getToPoints=function(){return this.toPoints},t.prototype.radix=function(t){if(null==t)return[];var s=[];for(var e in t)if(t.hasOwnProperty(e))for(var i in this.toPoints)if(this.toPoints.hasOwnProperty(i)&&e!==i)for(var n in this.settings.ASPECTS)this.hasAspect(t[e][0],this.toPoints[i][0],this.settings.ASPECTS[n])&&s.push({aspect:{name:n,degree:this.settings.ASPECTS[n].degree,orbit:this.settings.ASPECTS[n].orbit,color:this.settings.ASPECTS[n].color},point:{name:e,position:t[e][0]},toPoint:{name:i,position:this.toPoints[i][0]},precision:this.calcPrecision(t[e][0],this.toPoints[i][0],this.settings.ASPECTS[n].degree).toFixed(4)});return s.sort(this.compareAspectsByPrecision)},t.prototype.transit=function(t){if(null==t)return[];var s=[];for(var e in t)if(t.hasOwnProperty(e))for(var i in this.toPoints)if(this.toPoints.hasOwnProperty(i))for(var n in this.settings.ASPECTS)if(this.hasAspect(t[e][0],this.toPoints[i][0],this.settings.ASPECTS[n])){var r=this.calcPrecision(t[e][0],this.toPoints[i][0],this.settings.ASPECTS[n].degree);this.isTransitPointApproachingToAspect(this.settings.ASPECTS[n].degree,this.toPoints[i][0],t[e][0])&&(r*=-1),t[e][1]&&t[e][1]<0&&(r*=-1),s.push({aspect:{name:n,degree:this.settings.ASPECTS[n].degree,orbit:this.settings.ASPECTS[n].orbit,color:this.settings.ASPECTS[n].color},point:{name:e,position:t[e][0]},toPoint:{name:i,position:this.toPoints[i][0]},precision:r.toFixed(4)})}return s.sort(this.compareAspectsByPrecision)},t.prototype.hasAspect=function(t,s,e){var i=!1,r=Math.abs(t-s);r>n(Math.PI)&&(r=n(2*Math.PI)-r);var h=e.degree-e.orbit/2,a=e.degree+e.orbit/2;return h<=r&&r<=a&&(i=!0),i},t.prototype.calcPrecision=function(t,s,e){var i=Math.abs(t-s);return i>n(Math.PI)&&(i=n(2*Math.PI)-i),Math.abs(i-e)},t.prototype.isTransitPointApproachingToAspect=function(t,s,e){e-s>0?e-s>n(Math.PI)?e=(e+t)%n(2*Math.PI):s=(s+t)%n(2*Math.PI):s-e>n(Math.PI)?s=(s+t)%n(2*Math.PI):e=(e+t)%n(2*Math.PI);var i=e,r=s,h=i-r;return Math.abs(h)>n(Math.PI)&&(i=s,r=e),i-r<0},t.prototype.compareAspectsByPrecision=function(t,s){return parseFloat(t.precision)-parseFloat(s.precision)},t}();const _=L,O=function(){function t(t,s){if("function"!=typeof t)throw new Error("param 'callback' has to be a function.");this.debug=s,this.callback=t,this.boundTick_=this.tick.bind(this)}return t.prototype.start=function(){this.requestID_||(this.lastGameLoopFrame=(new Date).getTime(),this.tick(),this.debug&&console.log("[astrology.Timer] start"))},t.prototype.stop=function(){this.requestID_&&(window.cancelAnimationFrame(this.requestID_),this.requestID_=void 0,this.debug&&console.log("[astrology.Timer] stop"))},t.prototype.isRunning=function(){return!!this.requestID_},t.prototype.tick=function(){var t=(new Date).getTime();this.requestID_=window.requestAnimationFrame(this.boundTick_),this.callback(t-this.lastGameLoopFrame),this.lastGameLoopFrame=t},t}();const d=function(){function t(t,s){for(var e in this.transit=t,this.isReverse=!1,this.rotation=0,this.settings=s,this.actualPlanetPos={},this.transit.data.planets)this.transit.data.planets.hasOwnProperty(e)&&(this.actualPlanetPos[e]=this.transit.data.planets[e]);this.timer=new O(this.update.bind(this),this.settings.DEBUG),this.timeSinceLoopStart=0,this.context=this,this.cuspsElement=null}return t.prototype.animate=function(t,s,e,i){this.data=t,this.duration=1e3*s,this.isReverse=e||!1,this.callback=i,this.rotation=0,this.cuspsElement=document.getElementById(this.transit.paper._paperElementId+"-"+this.settings.ID_TRANSIT+"-"+this.settings.ID_CUSPS),this.timer.start()},t.prototype.update=function(t){if(t=null!=t?t:1,this.timeSinceLoopStart+=t,this.timeSinceLoopStart>=this.duration)return this.timer.stop(),void("function"==typeof this.callback&&this.callback());var s=this.duration-this.timeSinceLoopStart0&&(e+=this.isReverse?-1*(this.settings.ANIMATION_CUSPS_ROTATION_SPEED*s+s):this.settings.ANIMATION_CUSPS_ROTATION_SPEED*s);var i=this.isReverse?this.rotation-e:e-this.rotation;i<0&&(i+=s);var r=i/t;this.isReverse&&(r*=-1),this.rotation+=r,this.cuspsElement.setAttribute("transform","rotate("+this.rotation+" "+this.transit.cx+" "+this.transit.cy+")"),1===t&&this.cuspsElement.removeAttribute("transform")},t.prototype.updatePlanets=function(t){for(var s in this.data.planets)if(this.data.planets.hasOwnProperty(s)){var e=this.actualPlanetPos[s][0],i=this.data.planets[s][0],r=null!=this.actualPlanetPos[s][1]&&this.actualPlanetPos[s][1]<0,h=void 0;(h=this.isReverse&&r?i-e:this.isReverse||r?e-i:i-e)<0&&(h+=n(2*Math.PI));var a=h/t;this.isReverse&&(a*=-1),r&&(a*=-1);var o=e+a;o<0&&(o+=n(2*Math.PI)),this.actualPlanetPos[s][0]=o}this.transit.drawPoints(this.actualPlanetPos)},t}();const l=function(){function t(t,s,e){var i=h(s);if(i.hasError)throw new Error(i.messages.join(" | "));this.data=s,this.paper=t.paper,this.cx=t.cx,this.cy=t.cy,this.toPoints=t.toPoints,this.radius=t.radius,this.settings=e,this.rulerRadius=this.radius/this.settings.INNER_CIRCLE_RADIUS_RATIO/this.settings.RULER_RADIUS,this.pointRadius=this.radius+(this.radius/this.settings.INNER_CIRCLE_RADIUS_RATIO+this.settings.PADDING*this.settings.SYMBOL_SCALE),this.shift=t.shift,this.universe=document.createElementNS(this.paper.root.namespaceURI,"g"),this.universe.setAttribute("id",this.paper._paperElementId+"-"+this.settings.ID_TRANSIT),this.paper.root.appendChild(this.universe),this.context=this}return t.prototype.drawBg=function(){var t=this.universe,s=a(t,this.paper._paperElementId+"-"+this.settings.ID_BG,this.paper._paperElementId),e=this.paper.segment(this.cx,this.cy,this.radius+this.radius/this.settings.INNER_CIRCLE_RADIUS_RATIO,0,359.99,this.radius/this.settings.INDOOR_CIRCLE_RADIUS_RATIO,1);e.setAttribute("fill",this.settings.STROKE_ONLY?"none":this.settings.COLOR_BACKGROUND),s.appendChild(e)},t.prototype.drawPoints=function(t){var s=null==t?this.data.planets:t;if(null!=s){var e,n,h=this.universe,o=a(h,this.paper._paperElementId+"-"+this.settings.ID_TRANSIT+"-"+this.settings.ID_POINTS,this.paper._paperElementId),u=(this.radius,this.radius,this.settings.INNER_CIRCLE_RADIUS_RATIO,this.radius,this.settings.INDOOR_CIRCLE_RADIUS_RATIO,this.settings.PADDING,this.settings.SYMBOL_SCALE,Object.keys(s).length,this.radius+this.radius/this.settings.INNER_CIRCLE_RADIUS_RATIO);for(var p in this.locatedPoints=[],s)if(s.hasOwnProperty(p)){var c=i(this.cx,this.cy,this.pointRadius,s[p][0]+this.shift,this.settings),A={name:p,x:c.x,y:c.y,r:this.settings.COLLISION_RADIUS*this.settings.SYMBOL_SCALE,angle:s[p][0]+this.shift,pointer:s[p][0]+this.shift};this.locatedPoints=S(this.locatedPoints,A,{cx:this.cx,cy:this.cy,r:this.pointRadius},this.settings)}this.settings.DEBUG&&console.log("Transit count of points: "+this.locatedPoints.length),this.settings.DEBUG&&console.log("Transit located points:\n"+JSON.stringify(this.locatedPoints)),this.locatedPoints.forEach((function(t){e=i(this.cx,this.cy,u,s[t.name][0]+this.shift,this.settings),n=i(this.cx,this.cy,u+this.rulerRadius/2,s[t.name][0]+this.shift,this.settings);var h=this.paper.line(e.x,e.y,n.x,n.y);if(h.setAttribute("stroke",this.settings.CIRCLE_COLOR),h.setAttribute("stroke-width",this.settings.CUSPS_STROKE*this.settings.SYMBOL_SCALE),o.appendChild(h),!this.settings.STROKE_ONLY&&s[t.name][0]+this.shift!==t.angle){e=n,n=i(this.cx,this.cy,this.pointRadius-this.settings.COLLISION_RADIUS*this.settings.SYMBOL_SCALE,t.angle,this.settings);var a=this.paper.line(e.x,e.y,n.x,n.y);a.setAttribute("stroke",this.settings.LINE_COLOR),a.setAttribute("stroke-width",this.settings.CUSPS_STROKE*this.settings.SYMBOL_SCALE*.5),o.appendChild(a)}var S=this.paper.getSymbol(t.name,t.x,t.y);S.setAttribute("id",this.paper.root.id+"-"+this.settings.ID_TRANSIT+"-"+this.settings.ID_POINTS+"-"+t.name),o.appendChild(S);var p=[(Math.floor(s[t.name][0])%30).toString()],c=new g(this.data.cusps,this.settings);s[t.name][1]&&c.isRetrograde(s[t.name][1])?p.push("R"):p.push(""),p=p.concat(c.getDignities({name:t.name,position:s[t.name][0]},this.settings.DIGNITIES_EXACT_EXALTATION_DEFAULT).join(",")),r(t,p,this.settings).forEach((function(t){o.appendChild(this.paper.text(t.text,t.x,t.y,this.settings.POINTS_TEXT_SIZE,this.settings.SIGNS_COLOR))}),this)}),this)}},t.prototype.drawCircles=function(){var t=this.universe,s=a(t,this.paper._paperElementId+"-"+this.settings.ID_TRANSIT+"-"+this.settings.ID_CIRCLES,this.paper._paperElementId),e=this.radius+this.radius/this.settings.INNER_CIRCLE_RADIUS_RATIO,i=this.paper.circle(this.cx,this.cy,e);i.setAttribute("stroke",this.settings.CIRCLE_COLOR),i.setAttribute("stroke-width",(this.settings.CIRCLE_STRONG*this.settings.SYMBOL_SCALE).toString()),s.appendChild(i)},t.prototype.drawCusps=function(t){var s=null==t?this.data.cusps:t;if(null!=s)for(var e=this.universe,r=a(e,this.paper._paperElementId+"-"+this.settings.ID_TRANSIT+"-"+this.settings.ID_CUSPS,this.paper._paperElementId),h=this.radius+(this.radius/this.settings.INNER_CIRCLE_RADIUS_RATIO-this.rulerRadius)/2,o=0,S=s.length;o0?L-A:L-A+g,O=i(this.cx,this.cy,h,(A+_/2)%g+this.shift,this.settings);r.appendChild(this.paper.getSymbol((o+1).toString(),O.x,O.y))}},t.prototype.drawRuler=function(){var t=this.universe,s=a(t,this.paper.root.id+"-"+this.settings.ID_TRANSIT+"-"+this.settings.ID_RULER,this.paper._paperElementId),e=this.radius+this.radius/this.settings.INNER_CIRCLE_RADIUS_RATIO;p(this.cx,this.cy,e,e-this.rulerRadius,this.shift,this.settings).forEach((function(t){var e=this.paper.line(t.startX,t.startY,t.endX,t.endY);e.setAttribute("stroke",this.settings.CIRCLE_COLOR),e.setAttribute("stroke-width",this.settings.CUSPS_STROKE*this.settings.SYMBOL_SCALE),s.appendChild(e)}),this);var i=this.paper.circle(this.cx,this.cy,e-this.rulerRadius);i.setAttribute("stroke",this.settings.CIRCLE_COLOR),i.setAttribute("stroke-width",(this.settings.CUSPS_STROKE*this.settings.SYMBOL_SCALE).toString()),s.appendChild(i)},t.prototype.aspects=function(t){for(var s=null!=t&&Array.isArray(t)?t:new _(this.toPoints,this.settings).transit(this.data.planets),e=this.universe,n=a(e,this.paper.root.id+"-"+this.settings.ID_ASPECTS,this.paper._paperElementId),r=0,h=s.length;r0?I-C:I-C+l,m=i(o.cx,o.cy,e,(C+E/2)%l+o.shift,o.settings);s.appendChild(o.paper.getSymbol((t+1).toString(),m.x,m.y))},o=this,S=0,u=this.data.cusps.length;S { + if (mode === 'radix') { + return `import { Chart } from 'astrochart' + +const chart = new Chart('chart', 600, 600) +chart.radix(data)` + } else if (mode === 'transit') { + return `import { Chart } from 'astrochart' + +const chart = new Chart('chart', 600, 600) +chart.radix(radixData).transit(transitData)` + } else { + return `import { Chart } from 'astrochart' + +const chart = new Chart('chart', 600, 600) +const transit = chart.radix(radixData).transit(transitData) +transit.animate()` + } +} + +const codeSnippet = getCodeSnippet() ---
-
+
- {showCode && code && ( -
- - Show Code - -
-        {code}
-      
+ {mode === 'animate' && ( + + )} + + {showCode && ( +
+ Show Code +
{codeSnippet}
)}
@@ -57,41 +75,62 @@ const { is:inline define:vars={{ chartId: id, - chartData: data, - chartWidth: width, + chartMode: mode, chartHeight: height, - chartType: type + radixData: defaultRadixData, + transitData: defaultTransitData }} > - // Load the AstroChart library if not already loaded - if (typeof window.astrochart === 'undefined') { - const script = document.createElement('script') - script.src = '/astrochart.js' - script.onload = () => { - initChart() + ;(function () { + function initChart() { + const container = document.getElementById(chartId) + if (!container) return + + try { + const { Chart } = window.astrochart + const chart = new Chart(chartId, chartHeight, chartHeight) + + if (chartMode === 'radix') { + chart.radix(radixData) + } else if (chartMode === 'transit') { + chart.radix(radixData).transit(transitData) + } else if (chartMode === 'animate') { + const transit = chart.radix(radixData).transit(transitData) + + const animateBtn = document.getElementById(chartId + '-animate-btn') + if (animateBtn) { + animateBtn.addEventListener('click', function () { + transit.animate() + }) + } + } + } catch (err) { + console.error('ChartDemo [' + chartId + '] error:', err) + } } - document.head.appendChild(script) - } else { - initChart() - } - function initChart() { - const container = document.getElementById(chartId) - if (!container) return - - try { - const { Chart } = window.astrochart - const chart = new Chart(chartId, chartWidth, chartHeight) - - if (chartType === 'transit' && chartData.transit) { - chart.radix(chartData.radix).transit(chartData.transit) - } else { - chart[chartType](chartData) + if (window.astrochart) { + // Library already loaded — init immediately + initChart() + } else if (document.querySelector('script[src="/astrochart.js"]')) { + // Another instance is already loading the bundle — queue up + window.__astrochartQueue = window.__astrochartQueue || [] + window.__astrochartQueue.push(initChart) + } else { + // First instance — load the bundle and flush the queue on load + window.__astrochartQueue = window.__astrochartQueue || [] + window.__astrochartQueue.push(initChart) + + var script = document.createElement('script') + script.src = '/astrochart.js' + script.onload = function () { + var queue = window.__astrochartQueue || [] + window.__astrochartQueue = [] + queue.forEach(function (fn) { fn() }) } - } catch (error) { - console.error('ChartDemo error:', error) + document.head.appendChild(script) } - } + })() diff --git a/website/src/content/docs/api/radix.md b/website/src/content/docs/api/radix.md index 3d989a0..d7171cd 100644 --- a/website/src/content/docs/api/radix.md +++ b/website/src/content/docs/api/radix.md @@ -1,42 +1,89 @@ --- title: Radix -description: API reference for radix chart methods. +description: API reference for the Radix class returned by chart.radix(). --- # Radix -Methods and properties for working with radix charts. +`chart.radix(data)` renders a birth chart and returns a `Radix` instance. +You use this instance to draw aspects or chain into a transit ring. -## Methods - -### `Chart.radix(data: AstroData): Chart` - -Renders a radix chart. - -**Parameters:** -- `data` — AstroData object with planets, cusps, and aspects +## Creating a Radix -**Returns:** The chart instance for method chaining - -## Example - -```typescript +```javascript import { Chart } from '@astrodraw/astrochart' const data = { - planets: [ - { name: 'Sun', x: 120, y: 45, type: 'personal' } - ], + planets: { + Sun: [12.45, 0], + Moon: [145.67, 0], + Mercury: [8.23, 0], + Venus: [35.12, 0], + Mars: [162.34, 0], + Jupiter: [298.56, 0], + Saturn: [245.78, 0], + Uranus: [178.90, 0], + Neptune: [210.12, 0], + Pluto: [238.34, 0] + }, cusps: [ - { name: 'Asc', x: 0, y: 0 } + 315.45, 35.67, 65.23, 92.45, 125.67, 155.89, + 135.45, 215.67, 245.23, 272.45, 305.67, 335.89 ] } const chart = new Chart('chart', 600, 600) -chart.radix(data) +const radix = chart.radix(data) +``` + +## Methods + +### `radix.aspects(formedAspects?): Radix` + +Draws aspect lines between planets inside the chart. + +```javascript +// Draw aspects computed from the default settings +radix.aspects() + +// Or pass pre-computed aspects +radix.aspects(myAspects) ``` +### `radix.transit(data: AstroData): Transit` + +Adds a transit ring around the radix chart. Returns a `Transit` instance. + +```javascript +const transit = radix.transit(transitData) +transit.aspects() +``` + +See the [Transit API](/api/transit) for full details. + +### `radix.addPointsOfInterest(points: Points): Radix` + +Adds extra points (e.g. Arabic parts, fixed stars) to the aspect calculation +without rendering them as planet symbols. + +```javascript +radix.addPointsOfInterest({ Vertex: [324.5, 0] }) +radix.aspects() +``` + +## Data format + +`AstroData` passed to `radix()` must have: + +| Field | Type | Constraint | +|-------|------|------------| +| `planets` | `Record` | Keys must be [valid planet names](/api/types#valid-planet-keys) | +| `cusps` | `number[]` | Exactly **12** degree values | + +Planet array: `[degrees, retrogradeFlag]` — negative second element = retrograde. + ## Next Steps -- **[Radix Chart Guide](/docs/guides/radix-chart)** — Learn more -- **[Chart API](/docs/api/chart)** — See all methods +- [Types reference](/api/types) — `AstroData`, valid planet keys, full example +- [Transit API](/api/transit) — transit ring methods +- [Radix Chart guide](/guides/radix-chart) — practical walkthrough diff --git a/website/src/content/docs/api/types.md b/website/src/content/docs/api/types.md index 849599b..986698e 100644 --- a/website/src/content/docs/api/types.md +++ b/website/src/content/docs/api/types.md @@ -5,66 +5,108 @@ description: TypeScript type definitions for AstroChart. # Types -Complete TypeScript type definitions for AstroChart. +Complete TypeScript type definitions used by AstroChart. ## AstroData +The main data object passed to `chart.radix()` and `chart.radix().transit()`. + ```typescript interface AstroData { - planets?: Planet[] - cusps?: Cusp[] - aspects?: Aspect[] - pointsOfInterest?: PointOfInterest[] + planets: Points // keyed planet positions + cusps: number[] // exactly 12 house cusp degrees } ``` -## Planet +## Points + +Planet positions are stored as a plain object where each key is a planet name and each value is a two-element array. ```typescript -interface Planet { - name: string - x: number - y: number - type: 'personal' | 'social' | 'generational' | 'angle' - retrograde?: boolean -} +type Points = Record +// number[0] — position in degrees (0–360) +// number[1] — retrograde flag: negative value = retrograde (e.g. -1) ``` -## Cusp +### Valid planet keys -```typescript -interface Cusp { - name: string - x: number - y: number -} -``` +Only the following keys are recognised and rendered with their proper astrological symbol. +Any other key is silently rendered as a generic fallback circle. -## Aspect +| Key | Body | +|-----|------| +| `Sun` | ☉ Sun | +| `Moon` | ☽ Moon | +| `Mercury` | ☿ Mercury | +| `Venus` | ♀ Venus | +| `Mars` | ♂ Mars | +| `Jupiter` | ♃ Jupiter | +| `Saturn` | ♄ Saturn | +| `Uranus` | ♅ Uranus | +| `Neptune` | ♆ Neptune | +| `Pluto` | ♇ Pluto | +| `Chiron` | ⚷ Chiron | +| `Lilith` | Lilith (Black Moon) | +| `NNode` | ☊ North Node | +| `SNode` | ☋ South Node | +| `Fortune` | ⊕ Part of Fortune | -```typescript -interface Aspect { - planet1: string - planet2: string - type: 'conjunction' | 'sextile' | 'square' | 'trine' | 'opposition' - value: number - orb?: number +## Full AstroData example + +```javascript +const data = { + planets: { + Sun: [12.45, 0], // Aries 12° — direct + Moon: [145.67, 0], // Leo 25° + Mercury: [8.23, 0], + Venus: [35.12, 0], + Mars: [162.34, 0], + Jupiter: [298.56, -1], // retrograde (negative second element) + Saturn: [245.78, 0], + Uranus: [178.90, 0], + Neptune: [210.12, 0], + Pluto: [238.34, 0], + Chiron: [125.67, 0], + NNode: [95.45, 0], + SNode: [275.45, 0], + Lilith: [145.23, 0], + Fortune: [325.67, 0] + }, + cusps: [ + 315.45, // 1st house (Ascendant) + 35.67, // 2nd house + 65.23, // 3rd house + 92.45, // 4th house (IC) + 125.67, // 5th house + 155.89, // 6th house + 135.45, // 7th house (Descendant) + 215.67, // 8th house + 245.23, // 9th house + 272.45, // 10th house (MC) + 305.67, // 11th house + 335.89 // 12th house + ] } ``` ## Settings +Partial settings object passed as an optional fourth argument to `new Chart()`. +See the [Settings reference](/api/settings) for all available keys. + ```typescript interface Settings { - BACKGROUND_COLOR?: string - PAPER_BORDER_COLOR?: string + SYMBOL_SCALE?: number + COLOR_BACKGROUND?: string STROKE_ONLY?: boolean ADD_CLICK_AREA?: boolean - [key: string]: any + ASPECTS?: Record + // ... and many more — see Settings reference } ``` ## Next Steps -- **[Settings Reference](/docs/api/settings)** — See all settings -- **[API Reference](/docs/api/chart)** — See all methods +- [Settings Reference](/api/settings) — all configurable settings +- [Chart API](/api/chart) — `Chart` class methods +- [Radix Chart guide](/guides/radix-chart) — practical walkthrough diff --git a/website/src/content/docs/guides/radix-chart.md b/website/src/content/docs/guides/radix-chart.md index 743feb1..38a075a 100644 --- a/website/src/content/docs/guides/radix-chart.md +++ b/website/src/content/docs/guides/radix-chart.md @@ -5,57 +5,73 @@ description: Learn how to render a complete radix (natal) chart with AstroChart. # Radix Chart -A radix chart (also called a natal or birth chart) is a snapshot of the sky at a specific time and location. +A radix chart (also called a natal or birth chart) is a snapshot of the sky at a specific moment in time. -This guide shows how to render a complete radix chart with planets, cusps, and aspects using AstroChart. +AstroChart renders the chart from an `AstroData` object containing **planet positions** and **house cusps** — both expressed as degree values (0–360). ## Basic Radix Chart -The simplest radix chart requires planets and cusps: +```html +
+``` ```javascript import { Chart } from '@astrodraw/astrochart' const data = { - planets: [ - { name: 'Sun', x: 120, y: 45, type: 'personal' }, - { name: 'Moon', x: 220, y: 75, type: 'personal' } - ], - cusps: [ - { name: 'Asc', x: 150, y: 0 }, - { name: 'MC', x: 150, y: 300 } - ] + planets: { + Sun: [12.45, 0], + Moon: [145.67, 0] + }, + cusps: [315.45, 35.67, 65.23, 92.45, 125.67, 155.89, + 135.45, 215.67, 245.23, 272.45, 305.67, 335.89] } const chart = new Chart('chart', 600, 600) chart.radix(data) ``` +## Data format + +Planet positions use a plain object (`Record`): + +```javascript +{ + Sun: [degrees, retrogradeFlag] + // ^^^^^^^ 0–360 ^^^^^^^^ negative = retrograde, 0 = direct +} +``` + +House cusps are an **array of exactly 12 degree values** representing the start of each house in order (1st through 12th). Passing fewer or more than 12 will throw a validation error. + +See the [Types reference](/api/types) for all valid planet keys and a full example. + ## Full Example with All Planets ```javascript +import { Chart } from '@astrodraw/astrochart' + const data = { - planets: [ - { name: 'Sun', x: 120, y: 45, type: 'personal' }, - { name: 'Moon', x: 220, y: 75, type: 'personal' }, - { name: 'Mercury', x: 180, y: 60, type: 'personal' }, - { name: 'Venus', x: 150, y: 90, type: 'personal' }, - { name: 'Mars', x: 90, y: 80, type: 'personal' }, - { name: 'Jupiter', x: 280, y: 120, type: 'social' }, - { name: 'Saturn', x: 310, y: 150, type: 'social' }, - { name: 'Uranus', x: 45, y: 200, type: 'generational' }, - { name: 'Neptune', x: 15, y: 250, type: 'generational' }, - { name: 'Pluto', x: 350, y: 280, type: 'generational' } - ], + planets: { + Sun: [12.45, 0], + Moon: [145.67, 0], + Mercury: [8.23, 0], + Venus: [35.12, 0], + Mars: [162.34, 0], + Jupiter: [298.56, 0], + Saturn: [245.78, 0], + Uranus: [178.90, 0], + Neptune: [210.12, 0], + Pluto: [238.34, 0], + Chiron: [125.67, 0], + NNode: [95.45, 0], + SNode: [275.45, 0], + Lilith: [145.23, 0], + Fortune: [325.67, 0] + }, cusps: [ - { name: 'Asc', x: 0, y: 0 }, - { name: 'MC', x: 0, y: 90 }, - { name: 'Desc', x: 0, y: 180 }, - { name: 'IC', x: 0, y: 270 } - ], - aspects: [ - { planet1: 'Sun', planet2: 'Moon', type: 'conjunction', value: 12 }, - { planet1: 'Sun', planet2: 'Mercury', type: 'trine', value: 8 } + 315.45, 35.67, 65.23, 92.45, 125.67, 155.89, + 135.45, 215.67, 245.23, 272.45, 305.67, 335.89 ] } @@ -63,19 +79,46 @@ const chart = new Chart('chart', 600, 600) chart.radix(data) ``` -## API Methods +## Retrograde planets + +Set the second array element to a negative value to mark a planet as retrograde. +The library will render an **R** next to the symbol. + +```javascript +const data = { + planets: { + Jupiter: [298.56, -1], // retrograde + Saturn: [245.78, 0], // direct + }, + cusps: [ /* 12 values */ ] +} +``` + +## Aspects + +Call `.aspects()` on the returned `Radix` instance to draw aspect lines: + +```javascript +const radix = chart.radix(data) +radix.aspects() +``` + +Aspects are computed automatically based on the default orbs (conjunction 10°, square 8°, trine 8°, opposition 10°). Override them via [Settings](/api/settings). + +## API Reference -### `chart.radix(data: AstroData)` +### `chart.radix(data: AstroData): Radix` -Renders a radix chart with the provided astrological data. +Renders a radix chart and returns a `Radix` instance. **Parameters:** -- `data` — An `AstroData` object containing planets, cusps, and aspects +- `data` — `AstroData` object with `planets` and `cusps` -**Returns:** The chart instance (for method chaining) +**Returns:** `Radix` instance (use it to call `.aspects()` or `.transit()`) ## Next Steps -- **[Transit Charts](/docs/guides/transit-chart)** — Add a transit ring -- **[Aspects](/docs/api/chart)** — Learn more about aspect rendering -- **[Settings](/docs/guides/custom-settings)** — Customize appearance +- [Transit Charts](/guides/transit-chart) — overlay a transit ring +- [Animation](/guides/animation) — animate transit movement +- [Custom Settings](/guides/custom-settings) — colours, scale, orbs +- [Types reference](/api/types) — full type definitions and valid planet keys diff --git a/website/src/content/docs/test-demo.mdx b/website/src/content/docs/test-demo.mdx new file mode 100644 index 0000000..f252864 --- /dev/null +++ b/website/src/content/docs/test-demo.mdx @@ -0,0 +1,30 @@ +--- +title: Chart Demo Test +description: Test page for ChartDemo component in all modes +--- + +import ChartDemo from '../../components/ChartDemo.astro' + +# Chart Demo Component Test + +This page demonstrates the ChartDemo component in all three modes. + +## Radix Mode + +A basic radix (birth) chart showing planetary positions at a specific time. + + + +## Transit Mode + +A chart showing both radix (inner) and transit (outer) rings. + + + +## Animation Mode + +An animated chart with a button to trigger the animation. + + + + diff --git a/website/src/data/demoData.ts b/website/src/data/demoData.ts new file mode 100644 index 0000000..7b80c03 --- /dev/null +++ b/website/src/data/demoData.ts @@ -0,0 +1,96 @@ +/** + * Demo data module for AstroChart examples. + * Contains sample birth chart and transit data for component examples. + */ + +export interface AstroData { + planets: Record + cusps: number[] +} + +/** + * Default radix (birth chart) data. + * Represents a sample birth chart with 15 planets and 12 cusps. + * Planet positions are in degrees. + * Based on: 1990-04-03, 14:30 UTC, New York, NY + */ +export const defaultRadixData: AstroData = { + planets: { + Sun: [12.45, 0], // Aries 12°45' + Moon: [145.67, 0], // Leo 25°40' + Mercury: [8.23, 0], // Aries 8°14' + Venus: [35.12, 0], // Taurus 5°07' + Mars: [162.34, 0], // Virgo 12°20' + Jupiter: [298.56, 0], // Aquarius 28°34' + Saturn: [245.78, 0], // Sagittarius 5°47' + Uranus: [178.90, 0], // Libra 28°54' + Neptune: [210.12, 0], // Scorpio 0°07' + Pluto: [238.34, 0], // Sagittarius 28°20' + Chiron: [125.67, 0], // Leo 5°40' + NNode: [95.45, 0], // Gemini 5°27' + SNode: [275.45, 0], // Sagittarius 5°27' + Lilith: [145.23, 0], // Leo 25°14' + Fortune: [325.67, 0] // Aquarius 25°40' + }, + cusps: [ + 315.45, // Asc (Aquarius 15°27') + 35.67, // 2nd (Taurus 5°40') + 65.23, // 3rd (Gemini 5°14') + 92.45, // IC (Gemini 2°27') + 125.67, // 5th (Leo 5°40') + 155.89, // 6th (Virgo 15°53') + 135.45, // Dsc (Leo 15°27') - opposite Asc + 215.67, // 8th (Scorpio 5°40') + 245.23, // 9th (Sagittarius 5°14') + 272.45, // MC (Sagittarius 2°27') + 305.67, // 11th (Aquarius 5°40') + 335.89 // 12th (Pisces 15°53') + ] +} + +/** + * Default transit data. + * Represents current planetary positions for transit chart overlay. + * Date: Example 2024-01-15, 10:00 UTC + */ +export const defaultTransitData: AstroData = { + planets: { + Sun: [25.34, 0], // Aquarius 25°20' + Moon: [182.45, 0], // Libra 2°27' + Mercury: [42.67, 0], // Taurus 12°40' + Venus: [58.12, 0], // Taurus 28°07' + Mars: [198.34, 0], // Libra 18°20' + Jupiter: [328.56, 0], // Pisces 28°34' + Saturn: [288.78, 0], // Capricorn 8°47' + Uranus: [298.90, 0], // Aquarius 28°54' + Neptune: [325.12, 0], // Pisces 25°07' + Pluto: [300.34, 0], // Aquarius 0°20' + Chiron: [168.67, 0], // Virgo 18°40' + NNode: [62.45, 0], // Taurus 2°27' + SNode: [242.45, 0], // Sagittarius 2°27' + Lilith: [215.23, 0], // Scorpio 5°14' + Fortune: [45.67, 0] // Taurus 15°40' + }, + cusps: [ + 315.45, // Asc (Aquarius 15°27') - same as radix for comparison + 35.67, // 2nd (Taurus 5°40') + 65.23, // 3rd (Gemini 5°14') + 92.45, // IC (Gemini 2°27') + 125.67, // 5th (Leo 5°40') + 155.89, // 6th (Virgo 15°53') + 135.45, // Dsc (Leo 15°27') + 215.67, // 8th (Scorpio 5°40') + 245.23, // 9th (Sagittarius 5°14') + 272.45, // MC (Sagittarius 2°27') + 305.67, // 11th (Aquarius 5°40') + 335.89 // 12th (Pisces 15°53') + ] +} + +/** + * Sample data combining both radix and transit for animation examples. + */ +export const animationData = { + radix: defaultRadixData, + transit: defaultTransitData +}