1. /**
  2. * @module meteoJS/thermodynamicDiagram/axis
  3. */
  4. import {
  5. getNormalizedTextOptions
  6. } from './Functions.js';
  7. import PlotArea from './PlotArea.js';
  8. /**
  9. * Definitions for the labels of an axis.
  10. *
  11. * @typedef {module:meteoJS/thermodynamicDiagram~textOptions}
  12. * module:meteoJS/thermodynamicDiagram/axis~labelsOptions
  13. * @property {number} [interval] - Interval between the labels.
  14. * @property {string} [unit] - Unit of the label values.
  15. * @property {string} [prefix=''] - Prefix of the label text.
  16. * @property {integer} [decimalPlaces=0]
  17. * Number of digits to appear after the decimal point of the label values.
  18. */
  19. /**
  20. * Title definition for an axis.
  21. *
  22. * @typedef {module:meteoJS/thermodynamicDiagram~textOptions}
  23. * module:meteoJS/thermodynamicDiagram/axis~titleOptions
  24. * @property {string} [text=''] - Title text.
  25. */
  26. /**
  27. * Options for the constructor.
  28. *
  29. * @typedef {module:meteoJS/thermodynamicDiagram/plotArea~options}
  30. * module:meteoJS/thermodynamicDiagram/axis~options
  31. * @property {module:meteoJS/thermodynamicDiagram/axis~labelsOptions} labels
  32. * Options for the yAxis Labels.
  33. * @property {module:meteoJS/thermodynamicDiagram/axis~titleOptions} title
  34. * Options for the title of the Axis.
  35. * @property {boolean} [isHorizontal=true]
  36. * Internal. Is the axis horizontal or vertical.
  37. */
  38. /**
  39. * Abstract class to draw an axis with labelling.
  40. *
  41. * @extends module:meteoJS/thermodynamicDiagram/plotArea.PlotArea
  42. * @abstract
  43. */
  44. export class Axis extends PlotArea {
  45. /**
  46. * @param {module:meteoJS/thermodynamicDiagram/axis~options} options
  47. * Options.
  48. */
  49. constructor({
  50. svgNode = undefined,
  51. coordinateSystem,
  52. x,
  53. y,
  54. width,
  55. height,
  56. style = {},
  57. visible = true,
  58. events = {},
  59. labels = {},
  60. title = {},
  61. isHorizontal = true
  62. }) {
  63. if (style.overflow === undefined)
  64. style.overflow = 'visible';
  65. super({
  66. svgNode,
  67. coordinateSystem,
  68. x,
  69. y,
  70. width,
  71. height,
  72. style,
  73. visible,
  74. events
  75. });
  76. /**
  77. * @type Object
  78. * @private
  79. */
  80. this._labelsOptions = this.getNormalizedLabelsOptions(labels);
  81. /**
  82. * @type Object
  83. * @private
  84. */
  85. this._titleOptions = getNormalizedTitleOptions(title);
  86. /**
  87. * @type boolean
  88. * @private
  89. */
  90. this._isHorizontal = isHorizontal;
  91. this.init();
  92. }
  93. /**
  94. * Normalize the options for the labels.
  95. *
  96. * @param {module:meteoJS/thermodynamicDiagram/axis~labelsOptions}
  97. * options - Options.
  98. * @returns {module:meteoJS/thermodynamicDiagram/axis~labelsOptions}
  99. * Normalized options.
  100. */
  101. getNormalizedLabelsOptions({
  102. interval = undefined,
  103. unit = '',
  104. prefix = '',
  105. decimalPlaces = 0,
  106. ...rest
  107. }) {
  108. const options = getNormalizedTextOptions({ ...rest }, {
  109. font: {
  110. size: 11,
  111. anchor: 'middle'
  112. }
  113. });
  114. options.interval = interval;
  115. options.unit = unit;
  116. options.prefix = prefix;
  117. options.decimalPlaces = decimalPlaces;
  118. return options;
  119. }
  120. /**
  121. * Draws the labels of the axis.
  122. *
  123. * @param {external:SVG} svgNode - Node to draw into.
  124. * @param {number} min - Minimum value for the labels.
  125. * @param {number} max - Maximum value for the labels.
  126. * @param {Function} getTextByInterval
  127. * Returns the text representation of the label value (its argument).
  128. * @param {Function} getPositionByInterval
  129. * Returns the position in pixels of the label value (its argument).
  130. * @internal
  131. */
  132. drawLabels({
  133. svgNode,
  134. min,
  135. max,
  136. getTextByInterval =
  137. i => Number.parseFloat(i).toFixed(this._labelsOptions.decimalPlaces),
  138. getPositionByInterval
  139. }) {
  140. for (let i=min; i<=max; i+=this._labelsOptions.interval) {
  141. let text = getTextByInterval(i);
  142. text += this._labelsOptions.prefix;
  143. let fontColor = undefined;
  144. const font = {...this._labelsOptions.font};
  145. if ('color' in font) {
  146. fontColor = font.color;
  147. delete font.color;
  148. }
  149. if (!this._isHorizontal)
  150. font['anchor'] = 'end';
  151. const textNode = svgNode
  152. .plain(text)
  153. .font(font);
  154. if (this._isHorizontal) {
  155. textNode.center(
  156. getPositionByInterval(i),
  157. font.size
  158. );
  159. if (font['anchor'] == 'end')
  160. textNode.dx(-textNode.bbox().width/2);
  161. else if (font['anchor'] == 'start')
  162. textNode.dx(+textNode.bbox().width/2);
  163. }
  164. else
  165. textNode
  166. .x(this.width)
  167. .cy(getPositionByInterval(i))
  168. .dx(-textNode.bbox().width);
  169. if (fontColor !== undefined)
  170. textNode.fill(fontColor);
  171. }
  172. }
  173. /**
  174. * Draws a title for the axis.
  175. *
  176. * @param {Object} options - Options.
  177. * @param {external:SVG} svgNode - Node to insert into.
  178. * @param {external:SVG} svgLabelsNode - Node of the axis labels.
  179. * @private
  180. */
  181. _drawTitle({
  182. svgNode,
  183. svgLabelsNode
  184. }) {
  185. let rotation = 0;
  186. if (!this._isHorizontal)
  187. rotation = -90;
  188. let margin = 0;
  189. if (svgLabelsNode !== undefined)
  190. margin = (rotation == -90)
  191. ? svgLabelsNode.bbox().width
  192. : svgLabelsNode.bbox().height;
  193. let fontColor = undefined;
  194. const font = {...this._titleOptions.font};
  195. if ('color' in font) {
  196. fontColor = font.color;
  197. delete font.color;
  198. }
  199. let cxText = this.width/2;
  200. let cyText = font.size + margin;
  201. if (rotation == -90) {
  202. cxText = this.width - font.size - margin;
  203. cyText = this.height/2;
  204. }
  205. const textNode = svgNode
  206. .plain(this._titleOptions.text)
  207. .font(font)
  208. .center(cxText, cyText)
  209. .rotate(rotation);
  210. if (fontColor !== undefined)
  211. textNode.fill(fontColor);
  212. if (rotation == -90) {
  213. if (font['anchor'] == 'end')
  214. textNode.dy(-textNode.bbox().height/2);
  215. else if (font['anchor'] == 'start')
  216. textNode.dy(+textNode.bbox().height/2);
  217. }
  218. else {
  219. if (font['anchor'] == 'end')
  220. textNode.dx(-textNode.bbox().width/2);
  221. else if (font['anchor'] == 'start')
  222. textNode.dx(+textNode.bbox().width/2);
  223. }
  224. }
  225. /**
  226. * Draw background into SVG group.
  227. *
  228. * @override
  229. */
  230. _drawBackground(svgNode) {
  231. super._drawBackground(svgNode);
  232. let svgLabelsGroup = undefined;
  233. if (this._labelsOptions.visible) {
  234. svgLabelsGroup = svgNode.group();
  235. this.drawLabels({
  236. svgNode: svgLabelsGroup
  237. });
  238. }
  239. if (this._titleOptions.visible)
  240. this._drawTitle({
  241. svgNode: svgNode.group(),
  242. svgLabelsNode: svgLabelsGroup
  243. });
  244. }
  245. }
  246. export default Axis;
  247. /**
  248. * Normalize the options for the title.
  249. *
  250. * @param {module:meteoJS/thermodynamicDiagram/axis~titleOptions}
  251. * options - Options.
  252. * @returns {module:meteoJS/thermodynamicDiagram/axis~titleOptions}
  253. * Normalized options.
  254. */
  255. function getNormalizedTitleOptions({
  256. text = '',
  257. ...rest
  258. }) {
  259. const options = getNormalizedTextOptions({...rest}, {
  260. font: {
  261. anchor: 'middle'
  262. }
  263. });
  264. options.text = text;
  265. return options;
  266. }