Skip to content

Commit

Permalink
UI/ Double horizontal bar charts (#13398)
Browse files Browse the repository at this point in the history
* add descriptions and styling to side by side charts

* add border below horizontal charts

* starts legend styling

* center legend

* add to do

* add hover actions/event listeners
  • Loading branch information
hellobontempo authored Dec 14, 2021
1 parent e8e23e1 commit 2aa1344
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 94 deletions.
183 changes: 147 additions & 36 deletions ui/app/components/clients/horizontal-bar-charts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { assert } from '@ember/debug';
import { stack } from 'd3-shape';
// eslint-disable-next-line no-unused-vars
import { select, event, selectAll } from 'd3-selection';
Expand All @@ -17,7 +16,7 @@ import { max, maxIndex } from 'd3-array';
* <HorizontalBarChart @requiredParam={requiredParam} @optionalParam={optionalParam} @param1={{param1}}/>
* ```
* @param {object} dataset - dataset for the chart
* @param {array} chartLegend - array of objects with key names 'key' and 'label' for the map legend
* @param {array} chartLegend - array of objects with key names 'key' and 'label' for the chart legend
* @param {string} [labelKey=label] - labelKey is the key name in the dataset passed in that corresponds to the value labeling the y-axis (i.e. 'namespace_path')
* @param {string} [param1=defaultValue] - param1 is...
*/
Expand All @@ -27,6 +26,7 @@ import { max, maxIndex } from 'd3-array';

// SIZING CONSTANTS
const CHART_MARGIN = { top: 10, left: 137 }; // makes space for y-axis legend
const TRANSLATE = { down: 16 };
const CHAR_LIMIT = 18; // character count limit for y-axis labels to trigger truncating
const LINE_HEIGHT = 24; // each bar w/ padding is 24 pixels thick

Expand Down Expand Up @@ -103,28 +103,13 @@ export default class HorizontalBarChart extends Component {
}

get chartLegend() {
assert(
'chart legend is required, must be an array of objects with key names of "key" and "label"',
this.hasLegend()
);
return this.args.chartLegend;
}

// TODO: use maxIndex function when packages are updated
get topNamespace() {
console.log(this.args.dataset[maxIndex(this.args.dataset, d => d.total)]);
return this.args.dataset[maxIndex(this.args.dataset, d => d.total)];
}

hasLegend() {
if (!this.args.chartLegend || !Array.isArray(this.args.chartLegend)) {
return false;
} else {
let legendKeys = this.args.chartLegend.map(obj => Object.keys(obj));
return legendKeys.map(array => array.includes('key', 'label')).every(element => element === true);
}
}

@action
renderChart(element, args) {
// chart legend tells stackFunction how to stack/organize data
Expand All @@ -134,6 +119,7 @@ export default class HorizontalBarChart extends Component {
let dataset = args[0];
let stackedData = stackFunction(dataset);
let labelKey = this.labelKey;
let handleClick = this.args.onClick;

let xScale = scaleLinear()
.domain([0, max(dataset.map(d => d.total))])
Expand Down Expand Up @@ -161,13 +147,14 @@ export default class HorizontalBarChart extends Component {
let yAxis = axisLeft(yScale).tickSize(0);
yAxis(chartSvg.append('g').attr('transform', `translate(${CHART_MARGIN.left}, ${CHART_MARGIN.top})`));

chartSvg.select('.domain').remove();

let truncate = selection =>
selection.text(string =>
string.length < CHAR_LIMIT ? string : string.slice(0, CHAR_LIMIT - 3) + '...'
);

chartSvg.selectAll('.tick text').call(truncate);
chartSvg.select('.domain').remove();

groups
.selectAll('rect')
Expand All @@ -184,23 +171,147 @@ export default class HorizontalBarChart extends Component {
.attr('rx', 3)
.attr('ry', 3);

let startingXCoordinate = 100 - this.chartLegend.length * 20;
let legendSvg = select('.legend');
this.chartLegend.map((legend, i) => {
let xCoordinate = startingXCoordinate + i * 20;
legendSvg
.append('circle')
.attr('cx', `${xCoordinate}%`)
.attr('cy', '50%')
.attr('r', 6)
.style('fill', `${BAR_COLOR_DEFAULT[i]}`);
legendSvg
.append('text')
.attr('x', `${xCoordinate + 2}%`)
.attr('y', '50%')
.text(`${legend.label}`)
.style('font-size', '.8rem')
.attr('alignment-baseline', 'middle');
});
let actionBars = chartSvg
.selectAll('.action-bar')
.data(dataset)
.enter()
.append('rect')
.style('cursor', 'pointer')
.attr('class', 'action-bar')
.attr('width', '100%')
.attr('height', `${LINE_HEIGHT}px`)
.attr('x', '0')
.attr('y', chartData => yScale(chartData[labelKey]))
.style('fill', `${BACKGROUND_BAR_COLOR}`)
.style('opacity', '0')
.style('mix-blend-mode', 'multiply');

let yLegendBars = chartSvg
.selectAll('.label-bar')
.data(dataset)
.enter()
.append('rect')
.style('cursor', 'pointer')
.attr('class', 'label-action-bar')
.attr('width', CHART_MARGIN.left)
.attr('height', `${LINE_HEIGHT}px`)
.attr('x', '0')
.attr('y', chartData => yScale(chartData[labelKey]))
.style('opacity', '0')
.style('mix-blend-mode', 'multiply');

let dataBars = chartSvg.selectAll('rect.data-bar');
let actionBarSelection = chartSvg.selectAll('rect.action-bar');
let compareAttributes = (elementA, elementB, attr) =>
select(elementA).attr(`${attr}`) === elementB.getAttribute(`${attr}`);

// MOUSE AND CLICK EVENTS FOR DATA BARS
actionBars
.on('click', function(chartData) {
if (handleClick) {
handleClick(chartData);
}
})
.on('mouseover', function() {
select(this).style('opacity', 1);
dataBars
.filter(function() {
return compareAttributes(this, event.target, 'y');
})
.style('fill', (b, i) => `${BAR_COLOR_HOVER[i]}`);
// TODO: change to use modal instead of tooltip div
select('.chart-tooltip')
.transition()
.duration(200)
.style('opacity', 1);
})
.on('mouseout', function() {
select(this).style('opacity', 0);
select('.chart-tooltip').style('opacity', 0);
dataBars
.filter(function() {
return compareAttributes(this, event.target, 'y');
})
.style('fill', (b, i) => `${BAR_COLOR_DEFAULT[i]}`);
})
.on('mousemove', function(chartData) {
select('.chart-tooltip')
.style('opacity', 1)
.style('max-width', '200px')
.style('left', `${event.pageX - 325}px`)
.style('top', `${event.pageY - 140}px`)
.text(
`${Math.round((chartData.total * 100) / totalCount)}% of total client counts:
${chartData.non_entity_tokens} non-entity tokens, ${chartData.distinct_entities} unique entities.
`
);
});

// MOUSE EVENTS FOR Y-AXIS LABELS
yLegendBars
.on('click', function(chartData) {
if (handleClick) {
handleClick(chartData);
}
})
.on('mouseover', function(chartData) {
dataBars
.filter(function() {
return compareAttributes(this, event.target, 'y');
})
.style('fill', (b, i) => `${BAR_COLOR_HOVER[i]}`);
actionBarSelection
.filter(function() {
return compareAttributes(this, event.target, 'y');
})
.style('opacity', '1');
if (chartData.label.length >= CHAR_LIMIT) {
select('.chart-tooltip')
.transition()
.duration(200)
.style('opacity', 1);
}
})
.on('mouseout', function() {
select('.chart-tooltip').style('opacity', 0);
dataBars
.filter(function() {
return compareAttributes(this, event.target, 'y');
})
.style('fill', (b, i) => `${BAR_COLOR_DEFAULT[i]}`);
actionBarSelection
.filter(function() {
return compareAttributes(this, event.target, 'y');
})
.style('opacity', '0');
})
.on('mousemove', function(chartData) {
if (chartData.label.length >= CHAR_LIMIT) {
select('.chart-tooltip')
.style('left', `${event.pageX - 300}px`)
.style('top', `${event.pageY - 100}px`)
.text(`${chartData.label}`)
.style('max-width', 'fit-content');
} else {
select('.chart-tooltip').style('opacity', 0);
}
});

// add client count total values to the right
chartSvg
.append('g')
.attr('transform', `translate(${CHART_MARGIN.left}, ${TRANSLATE.down})`)
.selectAll('text')
.data(dataset)
.enter()
.append('text')
.text(d => d.total)
.attr('fill', '#000')
.attr('class', 'total-value')
.style('font-size', '.8rem')
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'middle')
.attr('x', chartData => `${xScale(chartData.total)}%`)
.attr('y', chartData => yScale(chartData.label));
}
}
8 changes: 0 additions & 8 deletions ui/app/styles/components/horizontal-bar-charts.scss

This file was deleted.

1 change: 0 additions & 1 deletion ui/app/styles/core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
@import './components/features-selection';
@import './components/form-section';
@import './components/global-flash';
@import './components/horizontal-bar-charts';
@import './components/hover-copy-button';
@import './components/init-illustration';
@import './components/info-table';
Expand Down
77 changes: 55 additions & 22 deletions ui/app/styles/core/charts.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@
line-height: normal;
margin-bottom: $spacing-xs;
}
.chart-description {
font-size: $size-8;
font-weight: $font-weight-normal;
color: $ui-gray-700;
margin-bottom: $spacing-xs;
}
}

p.chart-description {
font-size: $size-8;
font-weight: $font-weight-normal;
color: $ui-gray-700;
margin-bottom: $spacing-xs;
}

.has-export {
Expand Down Expand Up @@ -74,27 +75,41 @@
}
}

.chart-column-left {
.chart-container-left {
grid-column-start: 1;
grid-column-end: 4;
grid-row-start: 2;
grid-row-end: 5;
h2.title {
box-shadow: inset 0 -1px 0 $vault-gray-200;

h2.chart-title {
font-size: $size-5;
font-weight: $font-weight-bold;
margin-bottom: $spacing-l;
}
}

.chart-column-right {
.chart-container-right {
grid-column-start: 4;
grid-column-end: 8;
grid-row-start: 2;
grid-row-end: 5;
h2.title {
box-shadow: inset 0 -1px 0 $vault-gray-200;

h2.chart-title {
font-size: $size-5;
font-weight: $font-weight-bold;
margin-bottom: $spacing-l;
}
}

.horizontal-bar-chart {
.tick > text {
font-weight: $font-weight-semibold;
font-size: $size-8;
}
> div.legend {
height: $spacing-l;
margin-top: $spacing-xs;
float: right;
}
}

Expand Down Expand Up @@ -141,22 +156,40 @@
}
}

.legend-container-center {
.timestamp {
grid-column-start: 1;
grid-column-end: 2;
grid-row-start: 5;
color: $ui-gray-500;
font-size: $size-9;
align-self: end;
}

.light-dot {
background-color: #bfd4ff;
height: 10px;
width: 10px;
border-radius: 50%;
display: inline-block;
padding-right: 10px;
}

.dark-dot {
background-color: #1563ff;
height: 10px;
width: 10px;
border-radius: 50%;
display: inline-block;
}

.legend-container {
grid-row-start: 5;
grid-column-start: 3;
grid-column-end: 4;
height: $spacing-l;
align-self: center;
justify-self: center;
}

.legend-container-right {
grid-row-start: 5;
grid-column-start: 5;
grid-column-end: 6;
height: $spacing-l;
align-self: start;
justify-self: start;
font-size: $size-9;
}

.chart-tooltip {
Expand Down
Loading

0 comments on commit 2aa1344

Please sign in to comment.