Source

geoms.js

  1. /**
  2. * @module
  3. * @description Module with available visualization functions (called `geoms`) to display the data.
  4. * The following table summarizes available geoms for different data types:
  5. * | Data type | Name | Link | Size mapping | Color mapping |
  6. * | --- | --- | --- | --- | --- |
  7. * | `number` | `funkyrect` | {@link module:geoms.funkyrect} | ✅ | ✅ |
  8. * | `number` | `circle` | {@link module:geoms.circle} | ✅ | ✅ |
  9. * | `number` | `bar` | {@link module:geoms.bar} | ✅ | ✅ |
  10. * | `number` | `rect` | {@link module:geoms.rect} | 🚫 | ✅ |
  11. * | `string` | `text` | {@link module:geoms.text} | 🚫 | ✅ |
  12. * | `number[]` | `pie` | {@link module:geoms.pie} | 🚫 | ✅ |
  13. * | `image` | `image` | {@link module:geoms.image} | 🚫 | 🚫 |
  14. *
  15. * Each geom is a function with the signature of {@link module:geoms~geom|geom}.
  16. */
  17. import * as d3 from 'd3';
  18. /**
  19. * @name geom
  20. * @constant
  21. * @function
  22. * @abstract
  23. * @description Abstract virtual function representing a geom.
  24. *
  25. * @param {number|number[]|string} value - data value to be visualized, of the above types
  26. * @param {number|string} colorValue - value to be used for color mapping, if applicable
  27. * @param {module:columns.Column} column - column object
  28. * @param {HeatmapOptions} O - heatmap options
  29. * @param {PositionArgs} P - position arguments
  30. * @returns {SVGElement} - SVG element representing the geom
  31. */
  32. export const GEOMS = {
  33. /**
  34. * @memberof module:geoms
  35. * @see {@link module:geoms~geom|geom} for function signature
  36. * @description Text geom. Renders text string. Configured with `fontSize` and `align` options.
  37. * Default fontSize is inherited from {@link HeatmapOptions}. Default align is `left`.
  38. * Color is mapped from palette by text value, if palette is defined (see
  39. * {@link module:palettes~CustomPalette|CustomPalette}).
  40. */
  41. text: (value, _, column, O, P) => {
  42. let fill = O.theme.textColor;
  43. if (column.palette && column.palette !== 'none') {
  44. fill = column.palette(value);
  45. }
  46. let align = 'start', x = 0;
  47. if (column.options.align === 'center' || column.options.align === 'middle') {
  48. align = 'middle';
  49. x = P.rowHeight / 2;
  50. }
  51. if (column.options.align === 'right' || column.options.align === 'end') {
  52. align = 'end';
  53. x = P.rowHeight - P.padding;
  54. }
  55. const el = d3.create('svg:text')
  56. .classed('fh-geom', true)
  57. .attr('dominant-baseline', 'middle')
  58. .attr('y', P.rowHeight / 2)
  59. .attr('x', x)
  60. .attr('text-anchor', align)
  61. .style('fill', fill)
  62. .text(value);
  63. if (O.fontSize) {
  64. el.attr('font-size', O.fontSize);
  65. }
  66. if (column.options.fontSize) {
  67. el.attr('font-size', column.options.fontSize);
  68. }
  69. return el;
  70. },
  71. /**
  72. * @memberof module:geoms
  73. * @see {@link module:geoms~geom|geom} for function signature
  74. * @description Bar geom. Renders a bar with width proportional to value. Maximum bar width is
  75. * configured with `width` property ({@link module:columns~ColumnInfo|ColumnInfo}). If value
  76. * is 0, minimal bar width is set from {@link PositionArgs} `minGeomSize`.
  77. */
  78. bar: (value, colorValue, column, O, P) => {
  79. const fill = column.palette(colorValue);
  80. value = column.scale(value);
  81. let width = value * column.width * P.geomSize;
  82. if (width === 0) {
  83. width = P.minGeomSize;
  84. }
  85. return d3.create('svg:rect')
  86. .classed('fh-geom', true)
  87. .attr('x', P.geomPaddingX)
  88. .attr('y', P.geomPadding)
  89. .attr('width', width.toFixed(2))
  90. .attr('height', P.geomSize)
  91. .style('stroke', O.theme.strokeColor)
  92. .style('stroke-width', 1)
  93. .style('fill', fill);
  94. },
  95. /**
  96. * @memberof module:geoms
  97. * @see {@link module:geoms~geom|geom} for function signature
  98. * @description Circle geom. Renders a circle with radius proportional to value. If value is 0,
  99. * minimal circle radius is set from {@link PositionArgs} `minGeomSize`.
  100. */
  101. circle: (value, colorValue, column, O, P) => {
  102. const fill = column.palette(colorValue);
  103. value = column.scale(value);
  104. let radius = value * P.geomSize / 2;
  105. if (radius === 0) {
  106. radius = P.minGeomSize;
  107. }
  108. return d3.create('svg:circle')
  109. .classed('fh-geom', true)
  110. .style('stroke', O.theme.strokeColor)
  111. .style('stroke-width', 1)
  112. .style('fill', fill)
  113. .attr('cx', P.rowHeight / 2)
  114. .attr('cy', P.rowHeight / 2)
  115. .attr('r', radius.toFixed(2));
  116. },
  117. /**
  118. * @memberof module:geoms
  119. * @see {@link module:geoms~geom|geom} for function signature
  120. * @description Square geom. Renders a square of standard size, but color is mapped from
  121. * palette.
  122. */
  123. rect: (value, colorValue, column, O, P) => {
  124. const fill = column.palette(colorValue);
  125. value = column.scale(value);
  126. return d3.create('svg:rect')
  127. .classed('fh-geom', true)
  128. .style('stroke', O.theme.strokeColor)
  129. .style('stroke-width', 1)
  130. .style('fill', fill)
  131. .attr('x', P.geomPaddingX)
  132. .attr('y', P.geomPadding)
  133. .attr('width', P.geomSize)
  134. .attr('height', P.geomSize);
  135. },
  136. /**
  137. * @memberof module:geoms
  138. * @see {@link module:geoms~geom|geom} for function signature
  139. * @description Funkyrect geom. Renders a circle that grows into a square with rounded corners.
  140. * Value below {@link PositionArgs} `funkyMidpoint` is rendered as a circle, above as
  141. * a square, with corner radius decreasing as value grows.
  142. */
  143. funkyrect: (value, colorValue, column, O, P) => {
  144. let scaled = column.scale(value);
  145. const fill = column.palette(colorValue);
  146. if (scaled < P.funkyMidpoint) {
  147. // transform value to a 0.0 .. 0.5 range
  148. value = column.scale.copy()
  149. .range([0, 0.5])
  150. .domain([column.min, column.min + column.range * P.funkyMidpoint])(value);
  151. let radius = (value * 0.9 + 0.1) * P.geomSize - P.geomPadding; // 0.5 for stroke
  152. if (radius <= 0) {
  153. radius = P.minGeomSize;
  154. }
  155. return d3.create('svg:circle')
  156. .classed('fh-geom', true)
  157. .style('stroke', O.theme.strokeColor)
  158. .style('stroke-width', 1)
  159. .style('fill', fill)
  160. .attr('cx', P.rowHeight / 2)
  161. .attr('cy', P.rowHeight / 2)
  162. .attr('r', radius.toFixed(2));
  163. }
  164. // transform value to a 0.5 .. 1.0 range
  165. value = column.scale
  166. .copy()
  167. .range([0.5, 1])
  168. .domain([column.min + column.range * P.funkyMidpoint, column.max])(value);
  169. const cornerSize = (0.9 - 0.8 * value) * P.geomSize;
  170. return d3.create('svg:rect')
  171. .classed('fh-geom', true)
  172. .style('stroke', O.theme.strokeColor)
  173. .style('stroke-width', 1)
  174. .style('fill', fill)
  175. .attr('x', P.geomPaddingX)
  176. .attr('y', P.geomPadding)
  177. .attr('width', P.geomSize)
  178. .attr('height', P.geomSize)
  179. .attr('rx', cornerSize.toFixed(2))
  180. .attr('ry', cornerSize.toFixed(2));
  181. },
  182. /**
  183. * @memberof module:geoms
  184. * @see {@link module:geoms~geom|geom} for function signature
  185. * @description Pie chart geom. Renders a pie chart with slices proportional to values.
  186. */
  187. pie: (value, _, column, O, P) => {
  188. let nonZero = 0;
  189. let nonZeroIdx = 0;
  190. value.forEach((x, i) => {
  191. if (x > 0) {
  192. nonZero += 1;
  193. nonZeroIdx = i;
  194. }
  195. });
  196. if (nonZero === 1) {
  197. const fill = column.palette(nonZeroIdx);
  198. return d3.create('svg:circle')
  199. .classed('fh-geom', true)
  200. .style('stroke', O.theme.strokeColor)
  201. .style('stroke-width', 1)
  202. .style('fill', fill)
  203. .attr('cx', P.rowHeight / 2)
  204. .attr('cy', P.rowHeight / 2)
  205. .attr('r', P.geomSize / 2);
  206. }
  207. const arcs = d3.pie().sortValues(null)(value);
  208. const g = d3.create('svg:g');
  209. g.classed('fh-geom', true);
  210. g.selectAll('arcs')
  211. .data(arcs)
  212. .enter()
  213. .append('path')
  214. .attr('d', d3.arc().innerRadius(0).outerRadius(P.geomSize / 2))
  215. .attr('fill', (_, i) => {
  216. return column.palette(i);
  217. })
  218. .style('stroke', O.theme.strokeColor)
  219. .style('stroke-width', 1)
  220. .attr('transform', `translate(${P.rowHeight / 2}, ${P.rowHeight / 2})`);
  221. return g;
  222. },
  223. /**
  224. * @memberof module:geoms
  225. * @see {@link module:geoms~geom|geom} for function signature
  226. * @description Image geom. Renders an image with standard height and width specified in column
  227. * options (see {@link module:columns~ColumnInfo|ColumnInfo} `width`).
  228. */
  229. image: function(value, _, column, O, P) {
  230. return d3.create('svg:image')
  231. .classed('fh-geom', true)
  232. .attr('y', P.geomPadding)
  233. .attr('href', value)
  234. .attr('height', P.geomSize)
  235. .attr('width', column.width)
  236. .attr('preserveAspectRatio', 'xMidYMid');
  237. }
  238. };