sampler-visual.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import { customElement, bindable, inject } from 'aurelia-framework';
  2. import { EventAggregator } from 'aurelia-event-aggregator';
  3. import * as d3 from './d3custom';
  4. import { VisualConfig } from './config_visual';
  5. import { UpdateVisualisation } from './messages';
  6. @customElement('sampler-visual')
  7. @inject(VisualConfig, EventAggregator)
  8. export class SamplerVisual {
  9. @bindable colours;
  10. @bindable lang;
  11. @bindable maxSamples;
  12. @bindable sampler;
  13. @bindable soffsets;
  14. @bindable sortMethodId;
  15. constructor(configuration, ea) { // injected VisualConfig, EventAggregator
  16. this.ea = ea;
  17. this.svgWidth = 480;
  18. this.svgHeight = 340;
  19. this.axisWidth = 45;
  20. this.axisOffset = 10;
  21. this.tickLength = 10;
  22. this.labelOffset = 5;
  23. this.axisLabel = {de: 'Gewinne', en: 'Profits'};
  24. for (let key in configuration) {
  25. this[key] = configuration[key];
  26. }
  27. this.ea.subscribe(UpdateVisualisation, msg => {
  28. this.updateVisualisation();
  29. });
  30. }
  31. attached() { // (aurelia life cycle function) set up d3 view when dom ready
  32. let columns = (this.blocks.horizontal.amount * this.blocks.horizontal.size);
  33. let rows = this.blocks.vertical.amount * this.blocks.vertical.size;
  34. let vmrgn = (this.svgHeight - rows * (this.radius * (2 + this.dotspacingRatio))) / 2;
  35. let hmrgn = (this.svgWidth - columns * (this.radius * (2 + this.dotspacingRatio))) / 2 - this.axisWidth / 2;
  36. this.margin = {
  37. right: hmrgn,
  38. left: hmrgn,
  39. top: vmrgn,
  40. bottom: vmrgn
  41. };
  42. this.width = this.svgWidth - this.margin.left - this.margin.right;
  43. this.height = this.svgHeight - this.margin.top - this.margin.bottom;
  44. this.svg = d3.select('#' + this.sampler.svgclass)
  45. .attr('width', this.width + this.margin.left + this.margin.right)
  46. .attr('height', this.height + this.margin.top + this.margin.bottom);
  47. this.lgroup = this.svg.append('g')
  48. .attr('class', 'legend-group')
  49. .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
  50. this.cgroup = this.svg.append('g')
  51. .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
  52. if (this.sampler.samples.length >= 0) {
  53. this.drawAxis();
  54. this.updateVisualisation();
  55. }
  56. }
  57. drawAxis() {
  58. let blockspacing = this.radius * 2 * this.blocks.horizontal.spacingRatio;
  59. let dotspacing = this.radius * this.dotspacingRatio;
  60. let xoffset = this.radius;
  61. let cgroup = this.lgroup.append('g')
  62. .attr('class', 'class-label');
  63. cgroup.append('text')
  64. .text(this.axisLabel[this.lang])
  65. .style('font-size', '12px')
  66. .style('font-weight', 'bold')
  67. .attr('x', xoffset + this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2 + blockspacing / 2)
  68. .attr('y', this.height + this.axisOffset + this.labelOffset);
  69. // horizontal line
  70. cgroup.append('line')
  71. .attr('stroke', '#000')
  72. .attr('x1', -this.axisOffset)
  73. .attr('y1', this.height)
  74. .attr('x2', this.width + this.axisOffset)
  75. .attr('y2', this.height);
  76. for (let cidx in this.sampler.classes) {
  77. cidx = parseInt(cidx);
  78. let bidx = cidx;
  79. // all labels are offset by one block + half block spacing
  80. // for every following block the full block spacing is added
  81. let boffset = xoffset + (cidx + 1) * (this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2) + cidx * blockspacing / 2;
  82. let lines = this.sampler.classes[bidx].name[this.lang].split('\n');
  83. let y = this.height + this.axisOffset + this.labelOffset * 4;
  84. if (cidx === 0) {
  85. y = this.height + this.axisOffset + this.labelOffset;
  86. }
  87. let label = cgroup.append('text')
  88. .style('font-size', '12px')
  89. .style('font-weight', function() { return cidx === 0 ? 'bold' : 'normal'; })
  90. .attr('y', y);
  91. for (let lidx = 0; lidx < lines.length; ++lidx) {
  92. label.append('tspan')
  93. .attr('x', boffset - (this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2))
  94. .attr('dy', function() { return lidx > 0 ? '1.2em' : '0em'; })
  95. .text(lines[lidx]);
  96. }
  97. if (cidx < this.sampler.classes.length - 1) {
  98. let y2 = this.height + this.axisOffset;
  99. let y2long = this.height + this.axisOffset * 4.5;
  100. if (cidx === 0) {
  101. y2 = y2long;
  102. }
  103. cgroup.append('line')
  104. .attr('stroke', '#000')
  105. .attr('stroke-dasharray', '1')
  106. .attr('opacity', '0.2')
  107. .attr('x1', boffset - this.radius - dotspacing / 2)
  108. .attr('y1', -this.axisOffset)
  109. .attr('x2', boffset - this.radius - dotspacing / 2)
  110. .attr('y2', y2long);
  111. cgroup.append('line')
  112. .attr('stroke', '#000')
  113. .attr('x1', boffset - this.radius - dotspacing / 2)
  114. .attr('y1', this.height)
  115. .attr('x2', boffset - this.radius - dotspacing / 2)
  116. .attr('y2', y2);
  117. }
  118. }
  119. }
  120. updateVisualisation() {
  121. let radius = this.radius;
  122. let diameter = 2 * radius;
  123. let dotspacing = radius * this.dotspacingRatio;
  124. let nrblocks = {
  125. h: this.blocks.horizontal.amount,
  126. v: this.blocks.vertical.amount
  127. };
  128. let blocksize = {
  129. h: this.blocks.horizontal.size,
  130. v: this.blocks.vertical.size
  131. };
  132. let blockspacing = {
  133. h: diameter * this.blocks.horizontal.spacingRatio,
  134. v: diameter * this.blocks.vertical.spacingRatio
  135. };
  136. let xoffset = radius;
  137. let yoffset = radius;
  138. let iaheight = this.height;
  139. let colours = this.colours;
  140. // Invert every structure: samples, offsets, colours
  141. // FIXME: Is there still a reason to do this instead of inverting the order in the configuration?
  142. let spc = this.sampler.samplesPerClass();
  143. let samplesPerClass = Array(...spc);
  144. samplesPerClass.reverse();
  145. let synClassOffsets = Array(...this.soffsets);
  146. synClassOffsets.reverse();
  147. let samplesConcat = [];
  148. // for (let i = 0; i < samplesPerClass.length; ++i) {
  149. for (let i = samplesPerClass.length - 1; i >= 0; --i) {
  150. for (let j = 0; j < samplesPerClass[i].length; ++j) { // fill array with all samples of current class
  151. samplesConcat.push(i);
  152. }
  153. for (let k = 0; k < synClassOffsets[i] * this.blocks[this.layout].amount * this.blocks.horizontal.size * this.blocks.vertical.size - samplesPerClass[i].length; ++k) { // fill up block with complementary class
  154. samplesConcat.push(undefined);
  155. }
  156. }
  157. let sclength = samplesConcat.length;
  158. for (let i = 0; i < this.maxSamples - sclength; ++i) { // fill up the array with either base class id or undefined (placeholder)
  159. samplesConcat.push(undefined);
  160. }
  161. let circles = this.cgroup.selectAll('circle')
  162. .data(samplesConcat);
  163. circles.enter()
  164. .append('circle')
  165. .attr('r', radius)
  166. .merge(circles) // merge first!!!
  167. .attr('cx', function(d, i) { // y positioning with block grouping
  168. return xoffset +
  169. (dotspacing + diameter) * Math.floor(i / (nrblocks.v * blocksize.v)) +
  170. blockspacing.h * (Math.floor(i / blocksize.h / blocksize.v / nrblocks.h));
  171. })
  172. .attr('cy', function(d, i) { // x positioning with block grouping
  173. return iaheight - 2 * yoffset - (dotspacing + diameter) * (i % (nrblocks.v * blocksize.v));
  174. })
  175. .attr('class', function(d) {
  176. if (d === undefined) {
  177. return 'placeholder';
  178. }
  179. return 'sample';
  180. })
  181. .attr('fill', function(d) {
  182. let col = 'none';
  183. // let col = '#fff';
  184. if (d !== undefined) {
  185. col = colours[colours.length - 1 - d]; // don't forget to invert the colour indices
  186. }
  187. return col;
  188. })
  189. .attr('stroke', function(d) {
  190. let col = 'none';
  191. return col;
  192. })
  193. .on('mouseover', function(e) {
  194. this.setAttribute('r', radius + dotspacing);
  195. })
  196. .on('mouseout', function(e) {
  197. this.setAttribute('r', radius);
  198. });
  199. circles.exit().remove();
  200. }
  201. }