| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import { customElement, bindable, inject } from 'aurelia-framework';
- import { EventAggregator } from 'aurelia-event-aggregator';
- import * as d3 from './d3custom';
- import { VisualConfig } from './config_visual';
- import { UpdateVisualisation } from './messages';
- @customElement('sampler-visual')
- @inject(VisualConfig, EventAggregator)
- export class SamplerVisual {
- @bindable colours;
- @bindable lang;
- @bindable maxSamples;
- @bindable sampler;
- @bindable soffsets;
- @bindable sortMethodId;
- constructor(configuration, ea) { // injected VisualConfig, EventAggregator
- this.ea = ea;
- this.svgWidth = 480;
- this.svgHeight = 340;
- this.axisWidth = 45;
- this.axisOffset = 10;
- this.tickLength = 10;
- this.labelOffset = 5;
- this.axisLabel = {de: 'Gewinne', en: 'Profits'};
- for (let key in configuration) {
- this[key] = configuration[key];
- }
- this.ea.subscribe(UpdateVisualisation, msg => {
- this.updateVisualisation();
- });
- }
- attached() { // (aurelia life cycle function) set up d3 view when dom ready
- let columns = (this.blocks.horizontal.amount * this.blocks.horizontal.size);
- let rows = this.blocks.vertical.amount * this.blocks.vertical.size;
- let vmrgn = (this.svgHeight - rows * (this.radius * (2 + this.dotspacingRatio))) / 2;
- let hmrgn = (this.svgWidth - columns * (this.radius * (2 + this.dotspacingRatio))) / 2 - this.axisWidth / 2;
- this.margin = {
- right: hmrgn,
- left: hmrgn,
- top: vmrgn,
- bottom: vmrgn
- };
- this.width = this.svgWidth - this.margin.left - this.margin.right;
- this.height = this.svgHeight - this.margin.top - this.margin.bottom;
- this.svg = d3.select('#' + this.sampler.svgclass)
- .attr('width', this.width + this.margin.left + this.margin.right)
- .attr('height', this.height + this.margin.top + this.margin.bottom);
- this.lgroup = this.svg.append('g')
- .attr('class', 'legend-group')
- .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
- this.cgroup = this.svg.append('g')
- .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
- if (this.sampler.samples.length >= 0) {
- this.drawAxis();
- this.updateVisualisation();
- }
- }
- drawAxis() {
- let blockspacing = this.radius * 2 * this.blocks.horizontal.spacingRatio;
- let dotspacing = this.radius * this.dotspacingRatio;
- let xoffset = this.radius;
- let cgroup = this.lgroup.append('g')
- .attr('class', 'class-label');
- cgroup.append('text')
- .text(this.axisLabel[this.lang])
- .style('font-size', '12px')
- .style('font-weight', 'bold')
- .attr('x', xoffset + this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2 + blockspacing / 2)
- .attr('y', this.height + this.axisOffset + this.labelOffset);
- // horizontal line
- cgroup.append('line')
- .attr('stroke', '#000')
- .attr('x1', -this.axisOffset)
- .attr('y1', this.height)
- .attr('x2', this.width + this.axisOffset)
- .attr('y2', this.height);
- for (let cidx in this.sampler.classes) {
- cidx = parseInt(cidx);
- let bidx = cidx;
- // all labels are offset by one block + half block spacing
- // for every following block the full block spacing is added
- let boffset = xoffset + (cidx + 1) * (this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2) + cidx * blockspacing / 2;
- let lines = this.sampler.classes[bidx].name[this.lang].split('\n');
- let y = this.height + this.axisOffset + this.labelOffset * 4;
- if (cidx === 0) {
- y = this.height + this.axisOffset + this.labelOffset;
- }
- let label = cgroup.append('text')
- .style('font-size', '12px')
- .style('font-weight', function() { return cidx === 0 ? 'bold' : 'normal'; })
- .attr('y', y);
- for (let lidx = 0; lidx < lines.length; ++lidx) {
- label.append('tspan')
- .attr('x', boffset - (this.blocks.horizontal.size * (2 * this.radius + dotspacing) + blockspacing / 2))
- .attr('dy', function() { return lidx > 0 ? '1.2em' : '0em'; })
- .text(lines[lidx]);
- }
- if (cidx < this.sampler.classes.length - 1) {
- let y2 = this.height + this.axisOffset;
- let y2long = this.height + this.axisOffset * 4.5;
- if (cidx === 0) {
- y2 = y2long;
- }
- cgroup.append('line')
- .attr('stroke', '#000')
- .attr('stroke-dasharray', '1')
- .attr('opacity', '0.2')
- .attr('x1', boffset - this.radius - dotspacing / 2)
- .attr('y1', -this.axisOffset)
- .attr('x2', boffset - this.radius - dotspacing / 2)
- .attr('y2', y2long);
- cgroup.append('line')
- .attr('stroke', '#000')
- .attr('x1', boffset - this.radius - dotspacing / 2)
- .attr('y1', this.height)
- .attr('x2', boffset - this.radius - dotspacing / 2)
- .attr('y2', y2);
- }
- }
- }
- updateVisualisation() {
- let radius = this.radius;
- let diameter = 2 * radius;
- let dotspacing = radius * this.dotspacingRatio;
- let nrblocks = {
- h: this.blocks.horizontal.amount,
- v: this.blocks.vertical.amount
- };
- let blocksize = {
- h: this.blocks.horizontal.size,
- v: this.blocks.vertical.size
- };
- let blockspacing = {
- h: diameter * this.blocks.horizontal.spacingRatio,
- v: diameter * this.blocks.vertical.spacingRatio
- };
- let xoffset = radius;
- let yoffset = radius;
- let iaheight = this.height;
- let colours = this.colours;
- // Invert every structure: samples, offsets, colours
- // FIXME: Is there still a reason to do this instead of inverting the order in the configuration?
- let spc = this.sampler.samplesPerClass();
- let samplesPerClass = Array(...spc);
- samplesPerClass.reverse();
- let synClassOffsets = Array(...this.soffsets);
- synClassOffsets.reverse();
- let samplesConcat = [];
- // for (let i = 0; i < samplesPerClass.length; ++i) {
- for (let i = samplesPerClass.length - 1; i >= 0; --i) {
- for (let j = 0; j < samplesPerClass[i].length; ++j) { // fill array with all samples of current class
- samplesConcat.push(i);
- }
- 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
- samplesConcat.push(undefined);
- }
- }
- let sclength = samplesConcat.length;
- for (let i = 0; i < this.maxSamples - sclength; ++i) { // fill up the array with either base class id or undefined (placeholder)
- samplesConcat.push(undefined);
- }
- let circles = this.cgroup.selectAll('circle')
- .data(samplesConcat);
- circles.enter()
- .append('circle')
- .attr('r', radius)
- .merge(circles) // merge first!!!
- .attr('cx', function(d, i) { // y positioning with block grouping
- return xoffset +
- (dotspacing + diameter) * Math.floor(i / (nrblocks.v * blocksize.v)) +
- blockspacing.h * (Math.floor(i / blocksize.h / blocksize.v / nrblocks.h));
- })
- .attr('cy', function(d, i) { // x positioning with block grouping
- return iaheight - 2 * yoffset - (dotspacing + diameter) * (i % (nrblocks.v * blocksize.v));
- })
- .attr('class', function(d) {
- if (d === undefined) {
- return 'placeholder';
- }
- return 'sample';
- })
- .attr('fill', function(d) {
- let col = 'none';
- // let col = '#fff';
- if (d !== undefined) {
- col = colours[colours.length - 1 - d]; // don't forget to invert the colour indices
- }
- return col;
- })
- .attr('stroke', function(d) {
- let col = 'none';
- return col;
- })
- .on('mouseover', function(e) {
- this.setAttribute('r', radius + dotspacing);
- })
- .on('mouseout', function(e) {
- this.setAttribute('r', radius);
- });
- circles.exit().remove();
- }
- }
|