github.com/emate/nomad@v0.8.2-wo-binpacking/ui/app/components/distribution-bar.js (about)

     1  import Component from '@ember/component';
     2  import { computed, observer } from '@ember/object';
     3  import { run } from '@ember/runloop';
     4  import { assign } from '@ember/polyfills';
     5  import { guidFor, copy } from '@ember/object/internals';
     6  import d3 from 'npm:d3-selection';
     7  import 'npm:d3-transition';
     8  import WindowResizable from '../mixins/window-resizable';
     9  import styleStringProperty from '../utils/properties/style-string';
    10  
    11  const sumAggregate = (total, val) => total + val;
    12  
    13  export default Component.extend(WindowResizable, {
    14    classNames: ['chart', 'distribution-bar'],
    15    classNameBindings: ['isNarrow:is-narrow'],
    16  
    17    chart: null,
    18    data: null,
    19    activeDatum: null,
    20    isNarrow: false,
    21  
    22    tooltipStyle: styleStringProperty('tooltipPosition'),
    23    maskId: null,
    24  
    25    _data: computed('data', function() {
    26      const data = copy(this.get('data'), true);
    27      const sum = data.mapBy('value').reduce(sumAggregate, 0);
    28  
    29      return data.map(({ label, value, className, layers }, index) => ({
    30        label,
    31        value,
    32        className,
    33        layers,
    34        index,
    35        percent: value / sum,
    36        offset:
    37          data
    38            .slice(0, index)
    39            .mapBy('value')
    40            .reduce(sumAggregate, 0) / sum,
    41      }));
    42    }),
    43  
    44    didInsertElement() {
    45      const chart = d3.select(this.$('svg')[0]);
    46      const maskId = `dist-mask-${guidFor(this)}`;
    47      this.setProperties({ chart, maskId });
    48  
    49      this.$('svg clipPath').attr('id', maskId);
    50  
    51      chart.on('mouseleave', () => {
    52        run(() => {
    53          this.set('isActive', false);
    54          this.set('activeDatum', null);
    55          chart
    56            .selectAll('g')
    57            .classed('active', false)
    58            .classed('inactive', false);
    59        });
    60      });
    61  
    62      this.renderChart();
    63    },
    64  
    65    didUpdateAttrs() {
    66      this.renderChart();
    67    },
    68  
    69    updateChart: observer('_data.@each.{value,label,className}', function() {
    70      this.renderChart();
    71    }),
    72  
    73    // prettier-ignore
    74    /* eslint-disable */
    75    renderChart() {
    76      const { chart, _data, isNarrow } = this.getProperties('chart', '_data', 'isNarrow');
    77      const width = this.$('svg').width();
    78      const filteredData = _data.filter(d => d.value > 0);
    79  
    80      let slices = chart.select('.bars').selectAll('g').data(filteredData, d => d.label);
    81      let sliceCount = filteredData.length;
    82  
    83      slices.exit().remove();
    84  
    85      let slicesEnter = slices.enter()
    86        .append('g')
    87        .on('mouseenter', d => {
    88          run(() => {
    89            const slices = this.get('slices');
    90            const slice = slices.filter(datum => datum.label === d.label);
    91            slices.classed('active', false).classed('inactive', true);
    92            slice.classed('active', true).classed('inactive', false);
    93            this.set('activeDatum', d);
    94  
    95            const box = slice.node().getBBox();
    96            const pos = box.x + box.width / 2;
    97  
    98            // Ensure that the position is set before the tooltip is visible
    99            run.schedule('afterRender', this, () => this.set('isActive', true));
   100            this.set('tooltipPosition', {
   101              left: pos,
   102            });
   103          });
   104        });
   105  
   106      slices = slices.merge(slicesEnter);
   107      slices.attr('class', d => {
   108        const className = d.className || `slice-${_data.indexOf(d)}`
   109        const activeDatum = this.get('activeDatum');
   110        const isActive = activeDatum && activeDatum.label === d.label;
   111        const isInactive = activeDatum && activeDatum.label !== d.label;
   112        return [ className, isActive && 'active', isInactive && 'inactive' ].compact().join(' ');
   113      });
   114  
   115      this.set('slices', slices);
   116  
   117      const setWidth = d => `${width * d.percent - (d.index === sliceCount - 1 || d.index === 0 ? 1 : 2)}px`
   118      const setOffset = d => `${width * d.offset + (d.index === 0 ? 0 : 1)}px`
   119  
   120      let hoverTargets = slices.selectAll('.target').data(d => [d]);
   121      hoverTargets.enter()
   122          .append('rect')
   123          .attr('class', 'target')
   124          .attr('width', setWidth)
   125          .attr('height', '100%')
   126          .attr('x', setOffset)
   127        .merge(hoverTargets)
   128        .transition()
   129          .duration(200)
   130          .attr('width', setWidth)
   131          .attr('x', setOffset)
   132  
   133      let layers = slices.selectAll('.bar').data((d, i) => {
   134        return new Array(d.layers || 1).fill(assign({ index: i }, d));
   135      });
   136      layers.enter()
   137          .append('rect')
   138          .attr('width', setWidth)
   139          .attr('x', setOffset)
   140          .attr('y', () => isNarrow ? '50%' : 0)
   141          .attr('clip-path', `url(#${this.get('maskId')})`)
   142          .attr('height', () => isNarrow ? '6px' : '100%')
   143          .attr('transform', () => isNarrow ? 'translate(0, -3)' : '')
   144        .merge(layers)
   145          .attr('class', (d, i) => `bar layer-${i}`)
   146        .transition()
   147          .duration(200)
   148          .attr('width', setWidth)
   149          .attr('x', setOffset)
   150  
   151        if (isNarrow) {
   152          d3.select(this.get('element')).select('.mask')
   153            .attr('height', '6px')
   154            .attr('y', '50%');
   155        }
   156    },
   157    /* eslint-enable */
   158  
   159    windowResizeHandler() {
   160      run.once(this, this.renderChart);
   161    },
   162  });