base/TAttMarkerHandler.mjs

  1. import { isObject } from '../core.mjs';
  2. import { select as d3_select } from '../d3.mjs';
  3. import { getColor } from './colors.mjs';
  4. // list of marker types which can have line widths
  5. const root_50_67 = [2, 3, 5, 4, 25, 26, 27, 28, 30, 32, 35, 36, 37, 38, 40, 42, 44, 46],
  6. // internal recoding of root markers
  7. root_markers = [
  8. 0, 1, 2, 3, 4, // 0..4
  9. 5, 106, 107, 104, 1, // 5..9
  10. 1, 1, 1, 1, 1, // 10..14
  11. 1, 1, 1, 1, 1, // 15..19
  12. 104, 125, 126, 132, 4, // 20..24
  13. 25, 26, 27, 28, 130, // 25..29
  14. 30, 3, 32, 127, 128, // 30..34
  15. 35, 36, 37, 38, 137, // 35..39
  16. 40, 140, 42, 142, 44, // 40..44
  17. 144, 46, 146, 148, 149]; // 45..49
  18. /**
  19. * @summary Handle for marker attributes
  20. * @private
  21. */
  22. class TAttMarkerHandler {
  23. /** @summary constructor
  24. * @param {object} args - attributes, see {@link TAttMarkerHandler#setArgs} for details */
  25. constructor(args) {
  26. this.x0 = this.y0 = 0;
  27. this.color = 'black';
  28. this.style = 1;
  29. this.size = 8;
  30. this.scale = 1;
  31. this.stroke = true;
  32. this.fill = true;
  33. this.marker = '';
  34. this.ndig = 0;
  35. this.used = true;
  36. this.changed = false;
  37. this.func = this.apply.bind(this);
  38. this.setArgs(args);
  39. this.changed = false;
  40. }
  41. /** @summary Set marker attributes.
  42. * @param {object} args - arguments can be
  43. * @param {object} args.attr - instance of TAttrMarker (or derived class) or
  44. * @param {string} args.color - color in HTML form like grb(1,4,5) or 'green'
  45. * @param {number} args.style - marker style
  46. * @param {number} args.size - marker size
  47. * @param {number} [args.refsize] - when specified and marker size < 1, marker size will be calculated relative to that size */
  48. setArgs(args) {
  49. if (isObject(args) && (typeof args.fMarkerStyle === 'number')) args = { attr: args };
  50. if (args.attr) {
  51. args.color ??= args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor);
  52. if (!args.style || (args.style < 0)) args.style = args.attr.fMarkerStyle;
  53. args.size ??= args.attr.fMarkerSize;
  54. }
  55. this.color = args.color;
  56. this.style = args.style;
  57. this.size = args.size;
  58. this.refsize = args.refsize;
  59. this._configure();
  60. }
  61. /** @summary Set usage flag of attribute */
  62. setUsed(flag) {
  63. this.used = flag;
  64. }
  65. /** @summary Reset position, used for optimization of drawing of multiple markers
  66. * @private */
  67. resetPos() { this.lastx = this.lasty = null; }
  68. /** @summary Create marker path for given position.
  69. * @desc When drawing many elementary points, created path may depend from previously produced markers.
  70. * @param {number} x - first coordinate
  71. * @param {number} y - second coordinate
  72. * @return {string} path string */
  73. create(x, y) {
  74. if (!this.optimized)
  75. return `M${(x + this.x0).toFixed(this.ndig)},${(y + this.y0).toFixed(this.ndig)}${this.marker}`;
  76. // use optimized handling with relative position
  77. const xx = Math.round(x), yy = Math.round(y);
  78. let mv = `M${xx},${yy}`;
  79. if (this.lastx !== null) {
  80. if ((xx === this.lastx) && (yy === this.lasty))
  81. mv = ''; // pathological case, but let exclude it
  82. else {
  83. const m2 = `m${xx-this.lastx},${yy - this.lasty}`;
  84. if (m2.length < mv.length) mv = m2;
  85. }
  86. }
  87. this.lastx = xx + 1; this.lasty = yy;
  88. return mv + 'h1';
  89. }
  90. /** @summary Returns full size of marker */
  91. getFullSize() { return this.scale * this.size; }
  92. /** @summary Returns approximate length of produced marker string */
  93. getMarkerLength() { return this.marker ? this.marker.length : 10; }
  94. /** @summary Change marker attributes.
  95. * @param {string} color - marker color
  96. * @param {number} style - marker style
  97. * @param {number} size - marker size */
  98. change(color, style, size) {
  99. this.changed = true;
  100. if (color !== undefined) this.color = color;
  101. if ((style !== undefined) && (style >= 0)) this.style = style;
  102. if (size !== undefined) this.size = size;
  103. this._configure();
  104. }
  105. /** @summary Prepare object to create marker
  106. * @private */
  107. _configure() {
  108. this.x0 = this.y0 = 0;
  109. if ((this.style === 1) || (this.style === 777)) {
  110. this.fill = false;
  111. this.marker = 'h1';
  112. this.size = 1;
  113. this.optimized = true;
  114. this.resetPos();
  115. return true;
  116. }
  117. this.optimized = false;
  118. this.lwidth = 1;
  119. let style = this.style;
  120. if (style >= 50) {
  121. this.lwidth = 2 + Math.floor((style - 50) / root_50_67.length);
  122. style = root_50_67[(style - 50) % root_50_67.length];
  123. }
  124. const marker_kind = root_markers[style] ?? 104,
  125. shape = marker_kind % 100;
  126. this.fill = (marker_kind >= 100);
  127. this.scale = this.refsize || 8; // v7 defines refsize as 1 or pad height
  128. const size = this.getFullSize();
  129. this.ndig = (size > 7) ? 0 : ((size > 2) ? 1 : 2);
  130. if (shape === 30) this.ndig++; // increase precision for star
  131. let s1 = size.toFixed(this.ndig);
  132. const s2 = (size/2).toFixed(this.ndig),
  133. s3 = (size/3).toFixed(this.ndig),
  134. s4 = (size/4).toFixed(this.ndig),
  135. s8 = (size/8).toFixed(this.ndig),
  136. s38 = (size*3/8).toFixed(this.ndig),
  137. s34 = (size*3/4).toFixed(this.ndig);
  138. switch (shape) {
  139. case 1: // dot
  140. this.marker = 'h1';
  141. break;
  142. case 2: // plus
  143. this.y0 = -size / 2;
  144. this.marker = `v${s1}m-${s2},-${s2}h${s1}`;
  145. break;
  146. case 3: // asterisk
  147. this.y0 = -size / 2;
  148. this.marker = `v${s1}m-${s2},-${s2}h${s1}m-${s8},-${s38}l-${s34},${s34}m${s34},0l-${s34},-${s34}`;
  149. break;
  150. case 4: // circle
  151. this.x0 = -parseFloat(s2);
  152. s1 = (parseFloat(s2) * 2).toFixed(this.ndig);
  153. this.marker = `a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,-${s1},0z`;
  154. break;
  155. case 5: // multiply
  156. this.x0 = this.y0 = -3 / 8 * size;
  157. this.marker = `l${s34},${s34}m0,-${s34}l-${s34},${s34}`;
  158. break;
  159. case 6: // small dot
  160. this.x0 = -1;
  161. this.marker = 'a1,1,0,1,0,2,0a1,1,0,1,0,-2,0z';
  162. break;
  163. case 7: // medium dot
  164. this.x0 = -1.5;
  165. this.marker = 'a1.5,1.5,0,1,0,3,0a1.5,1.5,0,1,0,-3,0z';
  166. break;
  167. case 25: // square
  168. this.x0 = this.y0 = -size / 2;
  169. this.marker = `v${s1}h${s1}v-${s1}z`;
  170. break;
  171. case 26: // triangle-up
  172. this.y0 = -size / 2;
  173. this.marker = `l-${s2},${s1}h${s1}z`;
  174. break;
  175. case 27: // diamond
  176. this.y0 = -size / 2;
  177. this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`;
  178. break;
  179. case 28: // cross
  180. this.x0 = this.y0 = size / 6;
  181. this.marker = `h${s3}v-${s3}h-${s3}v-${s3}h-${s3}v${s3}h-${s3}v${s3}h${s3}v${s3}h${s3}z`;
  182. break;
  183. case 30: { // star
  184. this.y0 = -size / 2;
  185. const s56 = (size*5/6).toFixed(this.ndig), s58 = (size*5/8).toFixed(this.ndig);
  186. this.marker = `l${s3},${s1}l-${s56},-${s58}h${s1}l-${s56},${s58}z`;
  187. break;
  188. }
  189. case 32: // triangle-down
  190. this.y0 = size / 2;
  191. this.marker = `l-${s2},-${s1}h${s1}z`;
  192. break;
  193. case 35:
  194. this.x0 = -size / 2;
  195. this.marker = `l${s2},${s2}l${s2},-${s2}l-${s2},-${s2}zh${s1}m-${s2},-${s2}v${s1}`;
  196. break;
  197. case 36:
  198. this.x0 = this.y0 = -size / 2;
  199. this.marker = `h${s1}v${s1}h-${s1}zl${s1},${s1}m0,-${s1}l-${s1},${s1}`;
  200. break;
  201. case 37:
  202. this.x0 = -size/2;
  203. this.marker = `h${s1}l-${s4},-${s2}l-${s2},${s1}h${s2}l-${s2},-${s1}z`;
  204. break;
  205. case 38:
  206. this.x0 = -size/4; this.y0 = -size/2;
  207. this.marker = `h${s2}l${s4},${s4}v${s2}l-${s4},${s4}h-${s2}l-${s4},-${s4}v-${s2}zm${s4},0v${s1}m-${s2},-${s2}h${s1}`;
  208. break;
  209. case 40:
  210. this.x0 = -size/4; this.y0 = -size/2;
  211. this.marker = `l${s2},${s1}l${s4},-${s4}l-${s1},-${s2}zm${s2},0l-${s2},${s1}l-${s4},-${s4}l${s1},-${s2}z`;
  212. break;
  213. case 42:
  214. this.y0 = -size/2;
  215. this.marker = `l${s8},${s38}l${s38},${s8}l-${s38},${s8}l-${s8},${s38}l-${s8},-${s38}l-${s38},-${s8}l${s38},-${s8}z`;
  216. break;
  217. case 44:
  218. this.x0 = -size/4; this.y0 = -size/2;
  219. this.marker = `h${s2}l-${s8},${s38}l${s38},-${s8}v${s2}l-${s38},-${s8}l${s8},${s38}h-${s2}l${s8},-${s38}l-${s38},${s8}v-${s2}l${s38},${s8}z`;
  220. break;
  221. case 46:
  222. this.x0 = -size/4; this.y0 = -size/2;
  223. this.marker = `l${s4},${s4}l${s4},-${s4}l${s4},${s4}l-${s4},${s4}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}l-${s4},${s4}l-${s4},-${s4}l${s4},-${s4}l-${s4},-${s4}z`;
  224. break;
  225. case 48:
  226. this.x0 = -size/4; this.y0 = -size/2;
  227. this.marker = `l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm0,${s2}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm-${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}z`;
  228. break;
  229. case 49:
  230. this.x0 = -size/6; this.y0 = -size/2;
  231. this.marker = `h${s3}v${s3}h-${s3}zm${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},-${s3}h${s3}v${s3}h-${s3}z`;
  232. break;
  233. default: // diamond
  234. this.y0 = -size / 2;
  235. this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`;
  236. break;
  237. }
  238. return true;
  239. }
  240. /** @summary get stroke color */
  241. getStrokeColor() { return this.stroke ? this.color : 'none'; }
  242. /** @summary get fill color */
  243. getFillColor() { return this.fill ? this.color : 'none'; }
  244. /** @summary returns true if marker attributes will produce empty (invisible) output */
  245. empty() { return (this.color === 'none') || (!this.fill && !this.stroke); }
  246. /** @summary Apply marker styles to created element */
  247. apply(selection) {
  248. this.used = true;
  249. selection.style('stroke', this.stroke ? this.color : 'none')
  250. .style('stroke-width', this.stroke && (this.lwidth > 1) ? this.lwidth : null)
  251. .style('fill', this.fill ? this.color : 'none');
  252. }
  253. /** @summary Method used when color or pattern were changed with OpenUi5 widgets.
  254. * @private */
  255. verifyDirectChange(/* painter */) {
  256. this.change(this.color, parseInt(this.style), parseFloat(this.size));
  257. }
  258. /** @summary Create sample with marker in given SVG element
  259. * @param {selection} svg - SVG element
  260. * @param {number} width - width of sample SVG
  261. * @param {number} height - height of sample SVG
  262. * @private */
  263. createSample(svg, width, height, plain) {
  264. if (plain) svg = d3_select(svg);
  265. this.resetPos();
  266. svg.append('path')
  267. .attr('d', this.create(width / 2, height / 2))
  268. .call(this.func);
  269. }
  270. } // class TAttMarkerHandler
  271. export { TAttMarkerHandler };