UI/bar chart horizontal (#12437)

* creates bar chart component

* WIP//starts styling

* fixes width of bars

* WIP//barchart

* uses d3 max method instead of Math.max

* stacks data

* adds y axis

* fixes styling and spacing

* adds spacing between bars

* styling DONE

* adds legend

* adds tooltip

* tweaks styling adds pointer cursor to rects

* fixes tooltip placement

* moves starget from bar to whole area

* finishes hover selection styling

* cleans up

* cleans up a tiny bit

* stopping point

* adjusts tooltip placemnt

* WIP//clean up time

* sort of not broken

* unbroken, ish

* tooltip position fixed

* truncates text and adds tooltip

* changes tooltip width depending on content

* unbroken

* finishes initial refactor/cleanup

* finishes documentation

* passes in map legend to component

* more tidying

* add export option

* adds grid to header for export button option

* updates comments

* fix variable name change

* moves dataset formatting to parent

* removes unused code"

* adds assertions and empty state if no data

* cleans up comments adds assertion to check for map legend

* adds storybook

* adds changelog

* deletes dummy parent:

* restores index.hbs

* uses scss variables instead

* exchanges more variables

* remove unused variable in storybook

* writes basic test

* removes pauseTest()
This commit is contained in:
claire bontempo 2021-09-07 12:54:33 -07:00 committed by GitHub
parent b4b61efc75
commit c9eb55cc16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 852 additions and 10 deletions

3
changelog/12437.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: creates bar chart component for displaying client count data by namespace
```

View File

@ -0,0 +1,68 @@
.bar-chart-wrapper {
border: $light-border;
border-radius: $radius-large;
padding: $spacing-l $spacing-l $spacing-s $spacing-l;
height: 100%;
width: 100%;
> div.is-border {
border: 0.3px solid $ui-gray-200;
width: 94%;
margin-left: 3%;
margin-bottom: $spacing-xxs;
}
}
.chart-header {
margin-left: $spacing-l;
display: grid;
grid-template-columns: 3fr 1fr;
.header-left {
.chart-title {
font-size: $size-5;
font-weight: $font-weight-bold;
line-height: normal;
}
.chart-description {
font-size: $size-8;
font-weight: $font-weight-normal;
color: $ui-gray-700;
margin-bottom: $spacing-xs;
}
}
.header-right {
text-align: center;
> button {
font-size: $size-8;
&:hover {
text-decoration: underline;
}
}
}
}
.bar-chart-container {
padding: $spacing-m $spacing-l $spacing-m $spacing-l;
}
.bar-chart {
.tick > text {
font-weight: $font-weight-semibold;
font-size: $size-8;
}
}
.legend-container {
height: $spacing-l;
margin-top: $spacing-xs;
}
.legend {
width: 100%;
height: 100%;
}

View File

@ -45,6 +45,7 @@
@import './components/auth-buttons';
@import './components/auth-form';
@import './components/b64-toggle';
@import './components/bar-chart';
@import './components/box-label';
@import './components/box-radio';
@import './components/codemirror';

View File

@ -0,0 +1,327 @@
/**
* @module BarChart
* BarChart components are used to display data in the form of a stacked bar chart, with accompanying legend and tooltip. Anything passed into the block will display in the top right of the chart header.
*
* @example
* ```js
* <BarChartComponent @title="Top 10 Namespaces" @description="Each namespace's client count includes clients in child namespaces." @labelKey="namespace_path" @dataset={{this.testData}} @mapLegend={{ array (hash key="non_entity_tokens" label="Active direct tokens") (hash key="distinct_entities" label="Unique Entities") }} @onClick={{this.onClick}} >
* <button type="button" class="link">
* Export all namespace data
* </button>/>
* </BarChartComponent>
*
* mapLegendSample = [{
* key: "api_key_for_label",
* label: "Label Displayed on Legend"
* }]
* ```
*
* @param {string} title - title of the chart
* @param {array} mapLegend - array of objects with key names 'key' and 'label' for the map legend
* @param {object} dataset - dataset for the chart
* @param {string} [description] - description of the chart
* @param {string} [labelKey=label] - labelKey is the key name in the dataset passed in that corresponds to the value labeling the y-axis
* @param {function} [onClick] - takes function from parent and passes it to click event on data bars
*
*/
import Component from '@glimmer/component';
import layout from '../templates/components/bar-chart';
import { setComponentTemplate } from '@ember/component';
import { assert } from '@ember/debug';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';
import { scaleLinear, scaleBand } from 'd3-scale';
import { axisLeft } from 'd3-axis';
import { max } from 'd3-array';
import { stack } from 'd3-shape';
// eslint-disable-next-line no-unused-vars
import { select, event, selectAll } from 'd3-selection';
// eslint-disable-next-line no-unused-vars
import { transition } from 'd3-transition';
// SIZING CONSTANTS
const CHART_MARGIN = { top: 10, left: 137 }; // makes space for y-axis legend
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
// COLOR THEME:
const BAR_COLOR_DEFAULT = ['#BFD4FF', '#8AB1FF'];
const BAR_COLOR_HOVER = ['#1563FF', '#0F4FD1'];
const BACKGROUND_BAR_COLOR = '#EBEEF2';
const TOOLTIP_BACKGROUND = '#525761';
class BarChartComponent extends Component {
get labelKey() {
return this.args.labelKey || 'label';
}
get mapLegend() {
assert(
'map legend is required, must be an array of objects with key names of "key" and "label"',
this.hasLegend()
);
return this.args.mapLegend;
}
get dataset() {
return this.args.dataset || null;
}
hasLegend() {
if (!this.args.mapLegend || !Array.isArray(this.args.mapLegend)) {
return false;
} else {
let legendKeys = this.args.mapLegend.map(obj => Object.keys(obj));
return legendKeys.map(array => array.includes('key', 'label')).every(element => element === true);
}
}
@action
renderBarChart(element) {
let elementId = guidFor(element);
let totalCount = this.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
let stackedData = stackFunction(dataset);
// creates and appends tooltip
let container = select('.bar-chart-container');
container
.append('div')
.attr('class', 'chart-tooltip')
.attr('style', 'position: fixed; opacity: 0;')
.style('color', 'white')
.style('background', `${TOOLTIP_BACKGROUND}`)
.style('font-size', '.929rem')
.style('padding', '10px')
.style('border-radius', '4px');
let xScale = scaleLinear()
.domain([0, max(dataset.map(d => d.total))])
.range([0, 75]); // 25% reserved for margins
let yScale = scaleBand()
.domain(dataset.map(d => d[labelKey]))
.range([0, dataset.length * LINE_HEIGHT])
.paddingInner(0.765); // percent of the total width to reserve for padding between bars
let chartSvg = select(element);
chartSvg.attr('viewBox', `0 0 710 ${(dataset.length + 1) * LINE_HEIGHT}`);
chartSvg.attr('id', elementId);
// creates group for each array of stackedData
let groups = chartSvg
.selectAll('g')
.data(stackedData)
.enter()
.append('g')
// shifts chart to accommodate y-axis legend
.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 truncate = selection =>
selection.text(string =>
string.length < CHAR_LIMIT ? string : string.slice(0, CHAR_LIMIT - 3) + '...'
);
chartSvg.selectAll('.tick text').call(truncate);
let rects = groups
.selectAll('rect')
.data(d => d)
.enter()
.append('rect')
.attr('class', 'data-bar')
.style('cursor', 'pointer')
.attr('width', chartData => `${xScale(chartData[1] - chartData[0] - 5)}%`)
.attr('height', yScale.bandwidth())
.attr('x', chartData => `${xScale(chartData[0])}%`)
.attr('y', ({ data }) => yScale(data[labelKey]))
.attr('rx', 3)
.attr('ry', 3);
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}`);
// handles click and mouseover/out/move event 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]}`);
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 - 90}px`)
.style('top', `${event.pageY - 90}px`)
.text(
`${Math.round((chartData.total * 100) / totalCount)}% of total client counts:
${chartData.distinct_entities} unique entities, ${chartData.non_entity_tokens} active tokens.
`
);
});
// handles mouseover/out/move event for y-axis legend
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 - 100}px`)
.style('top', `${event.pageY - 50}px`)
.text(`${chartData.label}`)
.style('max-width', 'fit-content');
} else {
select('.chart-tooltip').style('opacity', 0);
}
});
// 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
.selectAll('text')
.data(totalCountData)
.enter()
.append('text')
.text(d => d.total)
.attr('fill', '#000')
.attr('class', 'total-value')
.style('font-size', '.8rem')
.attr('text-anchor', 'start')
.attr('y', d => `${d.y}`)
.attr('x', d => `${d.x + 1}%`);
// removes axes lines
groups.selectAll('.domain, .tick line').remove();
// TODO: make more flexible, make legend a div instead of svg?
// 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');
this.mapLegend.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');
});
}
}
export default setComponentTemplate(layout, BarChartComponent);

View File

@ -0,0 +1,28 @@
<div class="bar-chart-wrapper">
<div class="chart-header">
<div class="header-left">
<h2 class="chart-title">{{@title}}</h2>
{{#if @description}}
<p class="chart-description">{{@description}}</p>
{{/if}}
</div>
<div class="header-right">
{{#if this.dataset}}
{{yield}}
{{/if}}
</div>
</div>
{{#unless this.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>
</div>
<div class="is-border"></div>
<div class="legend-container">
<svg class="legend"></svg>
</div>
{{/unless}}
</div>

View File

@ -0,0 +1 @@
export { default } from 'core/components/bar-chart';

View File

@ -0,0 +1,37 @@
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in lib/core/addon/components/bar-chart.js. To make changes, first edit that file and run "yarn gen-story-md bar-chart" to re-generate the content.-->
## BarChart
BarChart components are used to display data in the form of a stacked bar chart, with accompanying legend and tooltip. Anything passed into the block will display in the top right of the chart header.
**Params**
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| title | <code>string</code> | | title of the chart |
| mapLegend | <code>array</code> | | array of objects with key names 'key' and 'label' for the map legend |
| dataset | <code>object</code> | | dataset for the chart |
| [description] | <code>string</code> | | description of the chart |
| [labelKey] | <code>string</code> | <code>&quot;label&quot;</code> | labelKey is the key name in the dataset passed in that corresponds to the value labeling the y-axis |
| [onClick] | <code>function</code> | | takes function from parent and passes it to click event on data bars |
**Example**
```js
<BarChartComponent @title="Top 10 Namespaces" @description="Each namespace's client count includes clients in child namespaces." @labelKey="namespace_path" @dataset={{this.testData}} @mapLegend={{ array (hash key="non_entity_tokens" label="Active direct tokens") (hash key="distinct_entities" label="Unique Entities") }} @onClick={{this.onClick}} >
<button type="button" class="link">
Export all namespace data
</button>/>
</BarChartComponent>
mapLegendSample = [{
key: "api_key_for_label",
label: "Label Displayed on Legend"
}]
```
**See**
- [Uses of BarChart](https://github.com/hashicorp/vault/search?l=Handlebars&q=BarChart+OR+bar-chart)
- [BarChart Source Code](https://github.com/hashicorp/vault/blob/master/ui/lib/core/addon/components/bar-chart.js)
---

View File

@ -0,0 +1,99 @@
import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember';
import { object, text, withKnobs } from '@storybook/addon-knobs';
import notes from './bar-chart.md';
const dataset = [
{
namespace_id: 'root',
namespace_path: 'root',
counts: {
distinct_entities: 268,
non_entity_tokens: 985,
clients: 1253,
},
},
{
namespace_id: 'O0i4m',
namespace_path: 'top-namespace',
counts: {
distinct_entities: 648,
non_entity_tokens: 220,
clients: 868,
},
},
{
namespace_id: '1oihz',
namespace_path: 'anotherNamespace',
counts: {
distinct_entities: 547,
non_entity_tokens: 337,
clients: 884,
},
},
{
namespace_id: '1oihz',
namespace_path: 'someOtherNamespaceawgagawegawgawgawgaweg',
counts: {
distinct_entities: 807,
non_entity_tokens: 234,
clients: 1041,
},
},
];
const flattenData = () => {
return dataset.map(d => {
return {
label: d['namespace_path'],
non_entity_tokens: d['counts']['non_entity_tokens'],
distinct_entities: d['counts']['distinct_entities'],
total: d['counts']['clients'],
};
});
};
storiesOf('BarChart', module)
.addParameters({ options: { showPanel: true } })
.addDecorator(withKnobs())
.add(
`BarChart`,
() => ({
template: hbs`
<h5 class="title is-5">Bar Chart</h5>
<p> <code>dataset</code> is passed to a function in the parent to format it appropriately for the chart. Any data passed should be flattened (not nested).</p>
<p> The legend typically displays within the bar chart border, below the second grey divider. There is also a tooltip that pops up when hovering over the data bars and overflowing labels. Gotta love storybook :) </p>
<div class="chart-container" style="margin-top:24px; max-width:750px; max-height:500px;" >
<BarChart
@title={{title}}
@description={{description}}
@dataset={{dataset}}
@mapLegend={{array
(hash key="non_entity_tokens" label="Active direct tokens")
(hash key="distinct_entities" label="Unique Entities")}}
>
<button type="button" class="link">
Export all namespace data
</button>
</BarChart>
<br>
<h6 class="title is-6">Legend:</h6>
<svg class="legend">
<circle cx="60%" cy="10%" r="6" style="fill: rgb(191, 212, 255);"></circle>
<text x="62%" y="10%" alignment-baseline="middle" style="font-size: 0.8rem;">Active direct tokens</text>
<circle cx="80%" cy="10%" r="6" style="fill: rgb(138, 177, 255);"></circle>
<text x="82%" y="10%" alignment-baseline="middle" style="font-size: 0.8rem;">Unique Entities</text></svg>
</div>
`,
context: {
title: text('title', 'Top Namespaces'),
description: text(
'description',
'Each namespaces client count includes clients in child namespaces.'
),
dataset: object('dataset', flattenData()),
},
}),
{ notes }
);

View File

@ -97,6 +97,7 @@
"ember-concurrency-test-waiter": "^0.3.2",
"ember-copy": "^1.0.0",
"ember-cp-validations": "^4.0.0-beta.12",
"ember-d3": "^0.5.1",
"ember-data": "~3.22.0",
"ember-data-model-fragments": "5.0.0-beta.0",
"ember-engines": "^0.8.3",

View File

@ -0,0 +1,81 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | bar-chart', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
let dataset = [
{
namespace_id: 'root',
namespace_path: 'root',
counts: {
distinct_entities: 268,
non_entity_tokens: 985,
clients: 1253,
},
},
{
namespace_id: 'O0i4m',
namespace_path: 'top-namespace',
counts: {
distinct_entities: 648,
non_entity_tokens: 220,
clients: 868,
},
},
{
namespace_id: '1oihz',
namespace_path: 'anotherNamespace',
counts: {
distinct_entities: 547,
non_entity_tokens: 337,
clients: 884,
},
},
{
namespace_id: '1oihz',
namespace_path: 'someOtherNamespaceawgagawegawgawgawgaweg',
counts: {
distinct_entities: 807,
non_entity_tokens: 234,
clients: 1041,
},
},
];
let flattenData = () => {
return dataset.map(d => {
return {
label: d['namespace_path'],
non_entity_tokens: d['counts']['non_entity_tokens'],
distinct_entities: d['counts']['distinct_entities'],
total: d['counts']['clients'],
};
});
};
this.set('title', 'Top Namespaces');
this.set('description', 'Each namespaces client count includes clients in child namespaces.');
this.set('dataset', flattenData());
await render(hbs`
<BarChart
@title={{title}}
@description={{description}}
@dataset={{dataset}}
@mapLegend={{array
(hash key="non_entity_tokens" label="Active direct tokens")
(hash key="distinct_entities" label="Unique Entities")}}
>
<button type="button" class="link">
Export all namespace data
</button>
</BarChart>
`);
assert.dom('.bar-chart-wrapper').exists('it renders');
});
});

View File

@ -6288,6 +6288,11 @@ command-line-usage@^4.1.0:
table-layout "^0.4.2"
typical "^2.6.1"
commander@2, commander@^2.15.1, commander@^2.20.0, commander@^2.6.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@2.8.x:
version "2.8.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
@ -6300,11 +6305,6 @@ commander@7.1.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
commander@^2.15.1, commander@^2.20.0, commander@^2.6.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
@ -6853,16 +6853,35 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
d3-array@^1.2.0:
d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
d3-axis@^1.0.8:
d3-axis@1, d3-axis@^1.0.8:
version "1.0.12"
resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.12.tgz#cdf20ba210cfbb43795af33756886fb3638daac9"
integrity sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==
d3-brush@1:
version "1.1.6"
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.1.6.tgz#b0a22c7372cabec128bdddf9bddc058592f89e9b"
integrity sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==
dependencies:
d3-dispatch "1"
d3-drag "1"
d3-interpolate "1"
d3-selection "1"
d3-transition "1"
d3-chord@1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.6.tgz#309157e3f2db2c752f0280fedd35f2067ccbb15f"
integrity sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==
dependencies:
d3-array "1"
d3-path "1"
d3-collection@1, d3-collection@^1.0.4:
version "1.0.7"
resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
@ -6873,21 +6892,74 @@ d3-color@1:
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a"
integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==
d3-contour@1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3"
integrity sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==
dependencies:
d3-array "^1.1.1"
d3-dispatch@1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
d3-drag@1:
version "1.2.5"
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70"
integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==
dependencies:
d3-dispatch "1"
d3-selection "1"
d3-dsv@1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c"
integrity sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==
dependencies:
commander "2"
iconv-lite "0.4"
rw "1"
d3-ease@1, d3-ease@^1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2"
integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==
d3-fetch@1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-1.2.0.tgz#15ce2ecfc41b092b1db50abd2c552c2316cf7fc7"
integrity sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==
dependencies:
d3-dsv "1"
d3-force@1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b"
integrity sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==
dependencies:
d3-collection "1"
d3-dispatch "1"
d3-quadtree "1"
d3-timer "1"
d3-format@1:
version "1.4.5"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
d3-geo@1:
version "1.12.1"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f"
integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==
dependencies:
d3-array "1"
d3-hierarchy@1:
version "1.1.9"
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83"
integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==
d3-interpolate@1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987"
@ -6895,6 +6967,46 @@ d3-interpolate@1:
dependencies:
d3-color "1"
d3-path@1:
version "1.0.9"
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
d3-polygon@1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.6.tgz#0bf8cb8180a6dc107f518ddf7975e12abbfbd38e"
integrity sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==
d3-quadtree@1:
version "1.0.7"
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135"
integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==
d3-random@1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.2.tgz#2833be7c124360bf9e2d3fd4f33847cfe6cab291"
integrity sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==
d3-scale-chromatic@1:
version "1.5.0"
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz#54e333fc78212f439b14641fb55801dd81135a98"
integrity sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==
dependencies:
d3-color "1"
d3-interpolate "1"
d3-scale@2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f"
integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==
dependencies:
d3-array "^1.2.0"
d3-collection "1"
d3-format "1"
d3-interpolate "1"
d3-time "1"
d3-time-format "2"
d3-scale@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d"
@ -6908,11 +7020,26 @@ d3-scale@^1.0.7:
d3-time "1"
d3-time-format "2"
d3-selection@^1.1.0, d3-selection@^1.3.0:
d3-selection-multi@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d3-selection-multi/-/d3-selection-multi-1.0.1.tgz#cd6c25413d04a2cb97470e786f2cd877f3e34f58"
integrity sha1-zWwlQT0EosuXRw54byzYd/PjT1g=
dependencies:
d3-selection "1"
d3-transition "1"
d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.3.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c"
integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==
d3-shape@1:
version "1.3.7"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
dependencies:
d3-path "1"
d3-time-format@2, d3-time-format@^2.1.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.3.0.tgz#107bdc028667788a8924ba040faf1fbccd5a7850"
@ -6938,7 +7065,7 @@ d3-tip@^0.9.1:
d3-collection "^1.0.4"
d3-selection "^1.3.0"
d3-transition@^1.2.0:
d3-transition@1, d3-transition@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398"
integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==
@ -6950,6 +7077,59 @@ d3-transition@^1.2.0:
d3-selection "^1.1.0"
d3-timer "1"
d3-voronoi@1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297"
integrity sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==
d3-zoom@1:
version "1.8.3"
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a"
integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==
dependencies:
d3-dispatch "1"
d3-drag "1"
d3-interpolate "1"
d3-selection "1"
d3-transition "1"
d3@^5.0.0:
version "5.16.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877"
integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==
dependencies:
d3-array "1"
d3-axis "1"
d3-brush "1"
d3-chord "1"
d3-collection "1"
d3-color "1"
d3-contour "1"
d3-dispatch "1"
d3-drag "1"
d3-dsv "1"
d3-ease "1"
d3-fetch "1"
d3-force "1"
d3-format "1"
d3-geo "1"
d3-hierarchy "1"
d3-interpolate "1"
d3-path "1"
d3-polygon "1"
d3-quadtree "1"
d3-random "1"
d3-scale "2"
d3-scale-chromatic "1"
d3-selection "1"
d3-shape "1"
d3-time "1"
d3-time-format "2"
d3-timer "1"
d3-transition "1"
d3-voronoi "1"
d3-zoom "1"
dag-map@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/dag-map/-/dag-map-2.0.2.tgz#9714b472de82a1843de2fba9b6876938cab44c68"
@ -8252,6 +8432,17 @@ ember-cp-validations@^4.0.0-beta.12:
ember-require-module "^0.3.0"
ember-validators "^3.0.1"
ember-d3@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/ember-d3/-/ember-d3-0.5.1.tgz#b23ce145863f082b5e73d25d9a43a0f1d9e9f412"
integrity sha512-NyjTUuIOxGxZdyrxLasNwwjqyFgay1pVHGRAWFj7mriwTI44muKsM9ZMl6YeepqixceuFig2fDxHmLLrkQV+QQ==
dependencies:
broccoli-funnel "^2.0.0"
broccoli-merge-trees "^3.0.0"
d3 "^5.0.0"
d3-selection-multi "^1.0.1"
ember-cli-babel "^7.1.2"
ember-data-model-fragments@5.0.0-beta.0:
version "5.0.0-beta.0"
resolved "https://registry.yarnpkg.com/ember-data-model-fragments/-/ember-data-model-fragments-5.0.0-beta.0.tgz#da90799970317ca852f96b2ea1548ca70094a5bb"
@ -11075,7 +11266,7 @@ human-signals@^1.1.1:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
iconv-lite@0.4.24, iconv-lite@^0.4.24:
iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@ -15938,6 +16129,11 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rw@1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
rxjs@^6.4.0, rxjs@^6.5.2, rxjs@^6.6.0, rxjs@^6.6.7:
version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"