UI/bar chart updates (#12622)

* testing bar chart changeS

* Added namespace search to client count

- Used existing search select component for namespace search

* Added changelog

* Added download csv component

- generate namespaces data in csv format
- Show root in top 10 namespaces
- Changed active direct tokens to non-entity tokens

* Added test for checking graph render

* Added documentation for the download csv component

* correctly updates chart when data changes

* Cleaned up template and tooltip

* Added changelog

* updates label tooltip and regroups dom elements

Co-authored-by: Arnav Palnitkar <arnav@hashicorp.com>
This commit is contained in:
claire bontempo 2021-09-27 13:48:44 -07:00 committed by GitHub
parent af3136d393
commit 2081bf1357
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 40 additions and 41 deletions

3
changelog/12622.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: update bar chart when model changes
```

View File

@ -64,10 +64,6 @@ class BarChartComponent extends Component {
return this.args.mapLegend;
}
get dataset() {
return this.args.dataset || null;
}
hasLegend() {
if (!this.args.mapLegend || !Array.isArray(this.args.mapLegend)) {
return false;
@ -78,12 +74,12 @@ class BarChartComponent extends Component {
}
@action
renderBarChart(element) {
renderBarChart(element, data) {
let elementId = guidFor(element);
let totalCount = this.dataset.reduce((prevValue, currValue) => prevValue + currValue.total, 0);
let [dataset] = data;
let totalCount = dataset.reduce((prevValue, currValue) => prevValue + currValue.total, 0);
let handleClick = this.args.onClick;
let labelKey = this.labelKey;
let dataset = this.dataset;
let stackFunction = stack().keys(this.mapLegend.map(l => l.key));
// creates an array of data for each map legend key
// each array contains coordinates for each data bar
@ -117,6 +113,8 @@ class BarChartComponent extends Component {
// creates group for each array of stackedData
let groups = chartSvg
.selectAll('g')
.remove()
.exit()
.data(stackedData)
.enter()
.append('g')
@ -124,8 +122,8 @@ class BarChartComponent extends Component {
.attr('transform', `translate(${CHART_MARGIN.left}, ${CHART_MARGIN.top})`)
.style('fill', (d, i) => BAR_COLOR_DEFAULT[i]);
let yAxis = axisLeft(yScale);
yAxis(groups.append('g'));
let yAxis = axisLeft(yScale).tickSize(0);
yAxis(chartSvg.append('g').attr('transform', `translate(${CHART_MARGIN.left}, ${CHART_MARGIN.top})`));
let truncate = selection =>
selection.text(string =>
@ -134,9 +132,10 @@ class BarChartComponent extends Component {
chartSvg.selectAll('.tick text').call(truncate);
let rects = groups
groups
.selectAll('rect')
.data(d => d)
// iterate through the stacked data and chart respectively
.data(stackedData => stackedData)
.enter()
.append('rect')
.attr('class', 'data-bar')
@ -214,8 +213,8 @@ class BarChartComponent extends Component {
select('.chart-tooltip')
.style('opacity', 1)
.style('max-width', '200px')
.style('left', `${event.pageX - 95}px`)
.style('top', `${event.pageY - 155}px`)
.style('left', `${event.pageX - 400}px`)
.style('top', `${event.pageY - 150}px`)
.text(
`${Math.round((chartData.total * 100) / totalCount)}% of total client counts:
${chartData.non_entity_tokens} non-entity tokens, ${chartData.distinct_entities} unique entities.
@ -264,8 +263,8 @@ class BarChartComponent extends Component {
.on('mousemove', function(chartData) {
if (chartData.label.length >= CHAR_LIMIT) {
select('.chart-tooltip')
.style('left', `${event.pageX - 100}px`)
.style('top', `${event.pageY - 50}px`)
.style('left', `${event.pageX - 400}px`)
.style('top', `${event.pageY - 100}px`)
.text(`${chartData.label}`)
.style('max-width', 'fit-content');
} else {
@ -273,21 +272,11 @@ class BarChartComponent extends Component {
}
});
// TODO: these render twice, need to only render and append once per line
// creates total count text and coordinates to display to the right of data bars
let totalCountData = [];
rects.each(function(d) {
let textDatum = {
total: d.data.total,
x: parseFloat(select(this).attr('width')) + parseFloat(select(this).attr('x')),
y: parseFloat(select(this).attr('y')) + parseFloat(select(this).attr('height')),
};
totalCountData.push(textDatum);
});
groups
chartSvg
.append('g')
.attr('transform', `translate(${CHART_MARGIN.left}, ${CHART_MARGIN.top + 2})`)
.selectAll('text')
.data(totalCountData)
.data(dataset)
.enter()
.append('text')
.text(d => d.total)
@ -295,13 +284,13 @@ class BarChartComponent extends Component {
.attr('class', 'total-value')
.style('font-size', '.8rem')
.attr('text-anchor', 'start')
.attr('y', d => `${d.y}`)
.attr('x', d => `${d.x + 1}%`);
.attr('alignment-baseline', 'mathematical')
.attr('x', chartData => `${xScale(chartData.total)}%`)
.attr('y', chartData => yScale(chartData.label));
// removes axes lines
groups.selectAll('.domain, .tick line').remove();
chartSvg.select('.domain').remove();
// TODO: make more flexible, make legend a div instead of svg?
// TODO: if mapLegend has more than 4 keys, y attrs ('cy' and 'y') will need to be set to a variable. Currently map keys are centered in the legend SVG (50%)
// each map key symbol & label takes up 20% of legend SVG width
let startingXCoordinate = 100 - this.mapLegend.length * 20; // subtract from 100% to find starting x-coordinate
let legendSvg = select('.legend');

View File

@ -1,4 +1,4 @@
<div class="bar-chart-wrapper">
<div data-test-bar-chart class="bar-chart-wrapper">
<div class="chart-header">
<div class="header-left">
<h2 class="chart-title">{{@title}}</h2>
@ -7,18 +7,21 @@
{{/if}}
</div>
<div class="header-right">
{{#if this.dataset}}
{{#if @dataset}}
{{yield}}
{{/if}}
</div>
</div>
{{#unless this.dataset}}
{{#unless @dataset}}
<div class="toolbar"></div>
<EmptyState @title="No namespace data" @message="There is no data to display for namespaces."/>
{{else}}
<div class="is-border"></div>
<div class="bar-chart-container">
<svg {{did-insert this.renderBarChart}} class="bar-chart"></svg>
<svg
{{did-insert this.renderBarChart @dataset}}
{{did-update this.renderBarChart @dataset}}
class="bar-chart"></svg>
</div>
<div class="is-border"></div>
<div class="legend-container">

View File

@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { findAll, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | bar-chart', function(hooks) {
@ -75,7 +75,11 @@ module('Integration | Component | bar-chart', function(hooks) {
</button>
</BarChart>
`);
assert.dom('.bar-chart-wrapper').exists('it renders');
assert.dom('[data-test-bar-chart]').exists('bar chart renders');
assert.dom('[data-test-bar-chart] .chart-title').hasText('Top Namespaces', 'displays title');
assert
.dom('[data-test-bar-chart] .chart-description')
.hasText('Each namespaces client count includes clients in child namespaces.', 'displays description');
assert.equal(findAll('.data-bar').length, 8, 'bars render');
});
});