import Component from '@ember/component';
import d3 from 'd3-selection';
import d3Scale from 'd3-scale';
import d3Axis from 'd3-axis';
import d3TimeFormat from 'd3-time-format';
import d3Tip from 'd3-tip';
import d3Transition from 'd3-transition';
import d3Ease from 'd3-ease';
import { assign } from '@ember/polyfills';
import { computed } from '@ember/object';
import { run } from '@ember/runloop';
import { task, waitForEvent } from 'ember-concurrency';
/**
* @module HttpRequestsBarChart
* HttpRequestsBarChart components are used to render a bar chart with the total number of HTTP Requests to a Vault server per month.
*
* @example
* ```js
*
${formatter(d.start_time)}
${Intl.NumberFormat().format(d.total)} Requests
`; }); barChartSVG.call(tip); // render the chart d3.select('.http-requests-bar-chart') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .attr('viewBox', `0 0 ${width} ${height}`); // scale and render the axes const yAxis = d3Axis .axisRight(yScale) .ticks(3, '~s') .tickSizeOuter(0); const xAxis = d3Axis .axisBottom(xScale) .tickFormat(d3TimeFormat.utcFormat('%b %Y')) .tickSizeOuter(0); barChartSVG .select('g.x-axis') .attr('transform', `translate(0,${height})`) .call(xAxis); barChartSVG .select('g.y-axis') .attr('transform', `translate(${width - margin.left - margin.right}, 0)`) .call(yAxis); // render the gridlines const gridlines = d3Axis .axisRight(yScale) .ticks(3) .tickFormat('') .tickSize(width - margin.left - margin.right); barChartSVG.select('.gridlines').call(gridlines); // render the bars const bars = barsContainer.selectAll('.bar').data(parsedCounters, c => +c.start_time); const barsEnter = bars .enter() .append('rect') .attr('class', 'bar'); const t = d3Transition .transition() .duration(DURATION) .ease(d3Ease.easeQuad); bars .merge(barsEnter) .attr('x', counter => xScale(counter.start_time)) // set the initial y value to 0 so the bars animate upwards .attr('y', () => yScale(0)) .attr('width', xScale.bandwidth()) .transition(t) .delay(function(d, i) { return i * BASE_SPEED; }) .attr('height', counter => height - yScale(counter.total)) .attr('y', counter => yScale(counter.total)); bars.exit().remove(); // render transparent bars and bind the tooltip to them since we cannot // bind the tooltip to the actual bars. this is because the bars are // within a clipPath & you cannot bind DOM events to non-display elements. const shadowBarsContainer = d3.select('.shadow-bars'); const shadowBars = shadowBarsContainer.selectAll('.bar').data(parsedCounters, c => +c.start_time); const shadowBarsEnter = shadowBars .enter() .append('rect') .attr('class', 'bar') .on('mouseenter', tip.show) .on('mouseleave', tip.hide); shadowBars .merge(shadowBarsEnter) .attr('width', xScale.bandwidth()) .attr('height', counter => height - yScale(counter.total) + HOVER_PADDING) .attr('x', counter => xScale(counter.start_time)) .attr('y', counter => yScale(counter.total) - HOVER_PADDING) .attr('fill', 'transparent') .attr('stroke', 'transparent'); shadowBars.exit().remove(); }, updateDimensions() { const newWidth = this.element.clientWidth; const { margin } = this; this.set('width', newWidth - margin.left - margin.right); this.renderBarChart(); }, waitForResize: task(function*() { while (true) { yield waitForEvent(window, 'resize'); run.scheduleOnce('afterRender', this, 'updateDimensions'); } }) .on('didInsertElement') .cancelOn('willDestroyElement') .drop(), });