github.com/aminovpavel/nomad@v0.11.8/ui/app/components/distribution-bar.js (about)

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