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:
parent
af3136d393
commit
2081bf1357
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
ui: update bar chart when model changes
|
||||
```
|
|
@ -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');
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue