UNPKG

@astrodraw/astrochart

Version:

A free and open-source JavaScript library for generating SVG charts to display planets in astrology.

450 lines (381 loc) 18.8 kB
import Zodiac from './zodiac' import AspectCalculator from './aspect' import type { FormedAspect } from './aspect' import Transit from './transit' import { validate , radiansToDegree , getEmptyWrapper , getPointPosition , getRulerPositions , getDescriptionPosition , getDashedLinesPositions , assemble } from './utils' import type SVG from './svg' import type { Settings } from './settings' export type Points = Record<string, number[]> export interface LocatedPoint { name: string; x: number; y: number; r: number; angle: number; pointer?: number; index?: number } export interface AstroData { planets: Points cusps: number[] } /** * Radix charts. * * @class * @public * @constructor * @param {this.settings.SVG} paper * @param {int} cx * @param {int} cy * @param {int} radius * @param {Object} data */ class Radix { settings: Settings data: AstroData paper: SVG cx: number cy: number radius: number locatedPoints: LocatedPoint[] rulerRadius: number pointRadius: number toPoints: Points shift: number universe: Element context: this constructor(paper: SVG, cx: number, cy: number, radius: number, data: AstroData, settings: Settings) { this.settings = settings // Validate data const status = validate(data) if (status.hasError) { throw new Error(status.messages.join(' | ')) } this.data = data this.paper = paper this.cx = cx this.cy = cy this.radius = radius // after calling this.drawPoints() it contains current position of point this.locatedPoints = [] 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 + 2 * this.rulerRadius + (this.settings.PADDING * this.settings.SYMBOL_SCALE)) // @see aspects() // @see setPointsOfInterest() this.toPoints = JSON.parse(JSON.stringify(this.data.planets)) // Clone object this.shift = 0 if (this.data.cusps && this.data.cusps[0]) { const deg360 = radiansToDegree(2 * Math.PI) this.shift = deg360 - this.data.cusps[0] } // preparing wrapper for aspects. It is the lowest layer const divisionForAspects = document.createElementNS(this.paper.root.namespaceURI, 'g') divisionForAspects.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_ASPECTS) this.paper.root.appendChild(divisionForAspects) this.universe = document.createElementNS(this.paper.root.namespaceURI, 'g') this.universe.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_RADIX) this.paper.root.appendChild(this.universe) this.context = this } /** * Draw background */ drawBg(): void { const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_BG, this.paper.root.id) const LARGE_ARC_FLAG = 1 const start = 0 // degree const end = 359.99 // degree const hemisphere = this.paper.segment(this.cx, this.cy, this.radius - this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO, start, end, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, LARGE_ARC_FLAG) hemisphere.setAttribute('fill', this.settings.STROKE_ONLY ? 'none' : this.settings.COLOR_BACKGROUND) wrapper.appendChild(hemisphere) } /** * Draw universe. */ drawUniverse(): void { const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_SIGNS, this.paper.root.id) // colors for (let i = 0, step = 30, start = this.shift, len = this.settings.COLORS_SIGNS.length; i < len; i++) { const segment = this.paper.segment(this.cx, this.cy, this.radius, start, start + step, this.radius - this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) segment.setAttribute('fill', this.settings.STROKE_ONLY ? 'none' : this.settings.COLORS_SIGNS[i]) segment.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_SIGNS + '-' + i) segment.setAttribute('stroke', this.settings.STROKE_ONLY ? this.settings.CIRCLE_COLOR : 'none') segment.setAttribute('stroke-width', this.settings.STROKE_ONLY ? '1' : '0') wrapper.appendChild(segment) start += step } // signs for (let i = 0, step = 30, start = 15 + this.shift, len = this.settings.SYMBOL_SIGNS.length; i < len; i++) { const position = getPointPosition(this.cx, this.cy, this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) / 2, start, this.settings) wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_SIGNS[i], position.x, position.y)) start += step } } /** * Draw points */ drawPoints(): void { if (this.data.planets == null) { return } const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_POINTS, this.paper.root.id) const gap = this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO) const step = (gap - 2 * (this.settings.PADDING * this.settings.SYMBOL_SCALE)) / Object.keys(this.data.planets).length const pointerRadius = this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.rulerRadius) let startPosition let endPosition for (const planet in this.data.planets) { if (this.data.planets.hasOwnProperty(planet)) { const position = getPointPosition(this.cx, this.cy, this.pointRadius, this.data.planets[planet][0] + this.shift, this.settings) const point = { name: planet, x: position.x, y: position.y, r: (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE), angle: this.data.planets[planet][0] + this.shift, pointer: this.data.planets[planet][0] + this.shift } this.locatedPoints = assemble(this.locatedPoints, point, { cx: this.cx, cy: this.cy, r: this.pointRadius }, this.settings) } } if (this.settings.DEBUG) console.log('Radix count of points: ' + this.locatedPoints.length) if (this.settings.DEBUG) console.log('Radix located points:\n' + JSON.stringify(this.locatedPoints)) this.locatedPoints.forEach(function (point: LocatedPoint) { // draw pointer startPosition = getPointPosition(this.cx, this.cy, pointerRadius, this.data.planets[point.name][0] + this.shift, this.settings) endPosition = getPointPosition(this.cx, this.cy, pointerRadius - this.rulerRadius / 2, this.data.planets[point.name][0] + this.shift, this.settings) const pointer = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y) pointer.setAttribute('stroke', this.settings.CIRCLE_COLOR) pointer.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE)) wrapper.appendChild(pointer) // draw pointer line if (!this.settings.STROKE_ONLY && (this.data.planets[point.name][0] + this.shift) !== point.angle) { startPosition = endPosition endPosition = getPointPosition(this.cx, this.cy, this.pointRadius + (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE), point.angle, this.settings) const line = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y) line.setAttribute('stroke', this.settings.LINE_COLOR) line.setAttribute('stroke-width', 0.5 * (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE)) wrapper.appendChild(line) } // draw symbol const symbol = this.paper.getSymbol(point.name, point.x, point.y) symbol.setAttribute('id', this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_POINTS + '-' + point.name) wrapper.appendChild(symbol) // draw point descriptions let textsToShow = [(Math.floor(this.data.planets[point.name][0]) % 30).toString()] const zodiac = new Zodiac(this.data.cusps, this.settings) if (this.data.planets[point.name][1] && zodiac.isRetrograde(this.data.planets[point.name][1])) { textsToShow.push('R') } else { textsToShow.push('') } if (this.settings.SHOW_DIGNITIES_TEXT) textsToShow = textsToShow.concat(zodiac.getDignities({ name: point.name, position: this.data.planets[point.name][0] }, this.settings.DIGNITIES_EXACT_EXALTATION_DEFAULT).join(',')) const pointDescriptions = getDescriptionPosition(point, textsToShow, this.settings) pointDescriptions.forEach(function (dsc) { wrapper.appendChild(this.paper.text(dsc.text, dsc.x, dsc.y, this.settings.POINTS_TEXT_SIZE, this.settings.SIGNS_COLOR)) }, this) }, this) } drawAxis(): void { if (this.data.cusps == null) { return } const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_AXIS, this.paper.root.id) const axisRadius = this.radius + ((this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) / 4) const AS = 0 const IC = 3 const DC = 6 const MC = 9 let overlapLine let startPosition let endPosition [AS, IC, DC, MC].forEach(function (i) { let textPosition // overlap startPosition = getPointPosition(this.cx, this.cy, this.radius, this.data.cusps[i] + this.shift, this.settings) endPosition = getPointPosition(this.cx, this.cy, axisRadius, this.data.cusps[i] + this.shift, this.settings) overlapLine = this.paper.line(startPosition.x, startPosition.y, endPosition.x, endPosition.y) overlapLine.setAttribute('stroke', this.settings.LINE_COLOR) overlapLine.setAttribute('stroke-width', (this.settings.SYMBOL_AXIS_STROKE * this.settings.SYMBOL_SCALE)) wrapper.appendChild(overlapLine) // As if (i === AS) { // Text textPosition = getPointPosition(this.cx, this.cy, axisRadius + (20 * this.settings.SYMBOL_SCALE), this.data.cusps[i] + this.shift, this.settings) wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_AS, textPosition.x, textPosition.y)) } // Ds if (i === DC) { // Text textPosition = getPointPosition(this.cx, this.cy, axisRadius + (2 * this.settings.SYMBOL_SCALE), this.data.cusps[i] + this.shift, this.settings) wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_DS, textPosition.x, textPosition.y)) } // Ic if (i === IC) { // Text textPosition = getPointPosition(this.cx, this.cy, axisRadius + (10 * this.settings.SYMBOL_SCALE), this.data.cusps[i] - 2 + this.shift, this.settings) wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_IC, textPosition.x, textPosition.y)) } // Mc if (i === MC) { // Text textPosition = getPointPosition(this.cx, this.cy, axisRadius + (10 * this.settings.SYMBOL_SCALE), this.data.cusps[i] + 2 + this.shift, this.settings) wrapper.appendChild(this.paper.getSymbol(this.settings.SYMBOL_MC, textPosition.x, textPosition.y)) } }, this) } /** * Draw cusps */ drawCusps(): void { if (this.data.cusps == null) { return } let lines const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_CUSPS, this.paper.root.id) const numbersRadius = this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO + (this.settings.COLLISION_RADIUS * this.settings.SYMBOL_SCALE) const AS = 0 const IC = 3 const DC = 6 const MC = 9 const mainAxis = [AS, IC, DC, MC] // Cusps for (let i = 0, ln = this.data.cusps.length; i < ln; i++) { // Draws a dashed line when an point is in the way lines = getDashedLinesPositions( this.cx, this.cy, this.data.cusps[i] + this.shift, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.rulerRadius), this.pointRadius, this.locatedPoints, this.settings ) lines.forEach(function (line) { const newLine = this.paper.line(line.startX, line.startY, line.endX, line.endY) newLine.setAttribute('stroke', this.settings.LINE_COLOR) if (mainAxis.includes(i)) { newLine.setAttribute('stroke-width', (this.settings.SYMBOL_AXIS_STROKE * this.settings.SYMBOL_SCALE)) } else { newLine.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE)) } wrapper.appendChild(newLine) }, this) // Cup number const deg360 = radiansToDegree(2 * Math.PI) const startOfCusp = this.data.cusps[i] const endOfCusp = this.data.cusps[(i + 1) % 12] const gap = endOfCusp - startOfCusp > 0 ? endOfCusp - startOfCusp : endOfCusp - startOfCusp + deg360 const textPosition = getPointPosition(this.cx, this.cy, numbersRadius, ((startOfCusp + gap / 2) % deg360) + this.shift, this.settings) wrapper.appendChild(this.paper.getSymbol((i + 1).toString(), textPosition.x, textPosition.y)) } } /** * Draw aspects * @param{Array<Object> | null} customAspects - posible custom aspects to draw; */ aspects(customAspects?: FormedAspect[] | null): Radix { const aspectsList = customAspects != null && Array.isArray(customAspects) ? customAspects : new AspectCalculator(this.toPoints).radix(this.data.planets) const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_ASPECTS, this.paper.root.id) const duplicateCheck: string[] = [] for (let i = 0, ln = aspectsList.length; i < ln; i++) { const key = aspectsList[i].aspect.name + '-' + aspectsList[i].point.name + '-' + aspectsList[i].toPoint.name const opositeKey = aspectsList[i].aspect.name + '-' + aspectsList[i].toPoint.name + '-' + aspectsList[i].point.name if (!duplicateCheck.includes(opositeKey)) { duplicateCheck.push(key) const startPoint = getPointPosition(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, aspectsList[i].toPoint.position + this.shift, this.settings) const endPoint = getPointPosition(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO, aspectsList[i].point.position + this.shift, this.settings) const line = this.paper.line(startPoint.x, startPoint.y, endPoint.x, endPoint.y) line.setAttribute('stroke', this.settings.STROKE_ONLY ? this.settings.LINE_COLOR : aspectsList[i].aspect.color) line.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE).toString()) line.setAttribute('data-name', aspectsList[i].aspect.name) line.setAttribute('data-degree', aspectsList[i].aspect.degree.toString()) line.setAttribute('data-point', aspectsList[i].point.name) line.setAttribute('data-toPoint', aspectsList[i].toPoint.name) line.setAttribute('data-precision', aspectsList[i].precision.toString()) wrapper.appendChild(line) } } return this.context } /** * Add points of interest for aspects calculation * @param {Obect} points, {"As":[0],"Ic":[90],"Ds":[180],"Mc":[270]} * @see (this.settings.AspectCalculator( toPoints) ) */ addPointsOfInterest(points: Points): Radix { for (const point in points) { if (points.hasOwnProperty(point)) { this.toPoints[point] = points[point] } } return this.context } drawRuler(): void { const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_RULER, this.paper.root.id) const startRadius = (this.radius - (this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO + this.rulerRadius)) const rays = getRulerPositions(this.cx, this.cy, startRadius, startRadius + this.rulerRadius, this.shift, this.settings) rays.forEach(function (ray) { const line = this.paper.line(ray.startX, ray.startY, ray.endX, ray.endY) line.setAttribute('stroke', this.settings.CIRCLE_COLOR) line.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE)) wrapper.appendChild(line) }, this) const circle = this.paper.circle(this.cx, this.cy, startRadius) circle.setAttribute('stroke', this.settings.CIRCLE_COLOR) circle.setAttribute('stroke-width', (this.settings.CUSPS_STROKE * this.settings.SYMBOL_SCALE).toString()) wrapper.appendChild(circle) } /** * Draw circles */ drawCircles(): void { const universe = this.universe const wrapper = getEmptyWrapper(universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_CIRCLES, this.paper.root.id) // indoor circle let circle = this.paper.circle(this.cx, this.cy, this.radius / this.settings.INDOOR_CIRCLE_RADIUS_RATIO) circle.setAttribute('stroke', this.settings.CIRCLE_COLOR) circle.setAttribute('stroke-width', (this.settings.CIRCLE_STRONG * this.settings.SYMBOL_SCALE).toString()) wrapper.appendChild(circle) // outdoor circle circle = this.paper.circle(this.cx, this.cy, this.radius) circle.setAttribute('stroke', this.settings.CIRCLE_COLOR) circle.setAttribute('stroke-width', (this.settings.CIRCLE_STRONG * this.settings.SYMBOL_SCALE).toString()) wrapper.appendChild(circle) // inner circle circle = this.paper.circle(this.cx, this.cy, this.radius - this.radius / this.settings.INNER_CIRCLE_RADIUS_RATIO) circle.setAttribute('stroke', this.settings.CIRCLE_COLOR) circle.setAttribute('stroke-width', (this.settings.CIRCLE_STRONG * this.settings.SYMBOL_SCALE).toString()) wrapper.appendChild(circle) } /** * Display transit horoscope * * @param {Object} data * @example * { * "planets":{"Moon":[0], "Sun":[30], ... }, * "cusps":[300, 340, 30, 60, 75, 90, 116, 172, 210, 236, 250, 274], * * } * * @return {Transit} transit */ transit(data: AstroData): Transit { // remove axis (As, Ds, Mc, Ic) from radix getEmptyWrapper(this.universe, this.paper.root.id + '-' + this.settings.ID_RADIX + '-' + this.settings.ID_AXIS, this.paper.root.id) const transit = new Transit(this.context, data, this.settings) transit.drawBg() transit.drawPoints() transit.drawCusps() transit.drawRuler() transit.drawCircles() return transit } } export default Radix