Add bar chart component
This commit is contained in:
parent
3f14c56bfe
commit
63c25a452e
7 changed files with 717 additions and 19 deletions
391
app/assets/javascripts/vue_shared/components/bar_chart.vue
Normal file
391
app/assets/javascripts/vue_shared/components/bar_chart.vue
Normal file
|
@ -0,0 +1,391 @@
|
|||
<script>
|
||||
import * as d3 from 'd3';
|
||||
import tooltip from '../directives/tooltip';
|
||||
import Icon from './icon.vue';
|
||||
import SvgGradient from './svg_gradient.vue';
|
||||
import {
|
||||
GRADIENT_COLORS,
|
||||
GRADIENT_OPACITY,
|
||||
INVERSE_GRADIENT_COLORS,
|
||||
INVERSE_GRADIENT_OPACITY,
|
||||
} from './bar_chart_constants';
|
||||
|
||||
/**
|
||||
* Renders a bar chart that can be dragged(scrolled) when the number
|
||||
* of elements to renders surpasses that of the available viewport space
|
||||
* while keeping even padding and a width of 24px (customizable)
|
||||
*
|
||||
* It can render data with the following format:
|
||||
* graphData: [{
|
||||
* name: 'element' // x domain data
|
||||
* value: 1 // y domain data
|
||||
* }]
|
||||
*
|
||||
* Used in:
|
||||
* - Contribution analytics - all of the rows describing pushes, merge requests and issues
|
||||
*/
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
Icon,
|
||||
SvgGradient,
|
||||
},
|
||||
props: {
|
||||
graphData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
barWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 24,
|
||||
},
|
||||
yAxisLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
minX: -40,
|
||||
minY: 0,
|
||||
vbWidth: 0,
|
||||
vbHeight: 0,
|
||||
vpWidth: 0,
|
||||
vpHeight: 350,
|
||||
preserveAspectRatioType: 'xMidYMid meet',
|
||||
containerMargin: {
|
||||
leftRight: 30,
|
||||
},
|
||||
viewBoxMargin: {
|
||||
topBottom: 150,
|
||||
},
|
||||
panX: 0,
|
||||
xScale: {},
|
||||
yScale: {},
|
||||
zoom: {},
|
||||
bars: {},
|
||||
xGraphRange: 0,
|
||||
isLoading: true,
|
||||
paddingThreshold: 50,
|
||||
showScrollIndicator: false,
|
||||
showLeftScrollIndicator: false,
|
||||
isGrabbed: false,
|
||||
isPanAvailable: false,
|
||||
gradientColors: GRADIENT_COLORS,
|
||||
gradientOpacity: GRADIENT_OPACITY,
|
||||
inverseGradientColors: INVERSE_GRADIENT_COLORS,
|
||||
inverseGradientOpacity: INVERSE_GRADIENT_OPACITY,
|
||||
maxTextWidth: 72,
|
||||
rectYAxisLabelDims: {},
|
||||
xAxisTextElements: {},
|
||||
yAxisRectTransformPadding: 20,
|
||||
yAxisTextTransformPadding: 10,
|
||||
yAxisTextRotation: 90,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
svgViewBox() {
|
||||
return `${this.minX} ${this.minY} ${this.vbWidth} ${this.vbHeight}`;
|
||||
},
|
||||
xAxisLocation() {
|
||||
return `translate(${this.panX}, ${this.vbHeight})`;
|
||||
},
|
||||
barTranslationTransform() {
|
||||
return `translate(${this.panX}, 0)`;
|
||||
},
|
||||
scrollIndicatorTransform() {
|
||||
return `translate(${this.vbWidth - 80}, 0)`;
|
||||
},
|
||||
activateGrabCursor() {
|
||||
return {
|
||||
'svg-graph-container-with-grab': this.isPanAvailable,
|
||||
'svg-graph-container-grabbed': this.isPanAvailable && this.isGrabbed,
|
||||
};
|
||||
},
|
||||
yAxisLabelRectTransform() {
|
||||
const rectWidth =
|
||||
this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
|
||||
const yCoord = this.vbHeight / 2 - rectWidth;
|
||||
|
||||
return `translate(${this.minX - this.yAxisRectTransformPadding}, ${yCoord})`;
|
||||
},
|
||||
yAxisLabelTextTransform() {
|
||||
const rectWidth =
|
||||
this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
|
||||
const yCoord = this.vbHeight / 2 + rectWidth - 5;
|
||||
|
||||
return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${this.yAxisTextRotation})`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!this.allValuesEmpty) {
|
||||
this.draw();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
draw() {
|
||||
// update viewport
|
||||
this.vpWidth = this.$refs.svgContainer.clientWidth - this.containerMargin.leftRight;
|
||||
// update viewbox
|
||||
this.vbWidth = this.vpWidth;
|
||||
this.vbHeight = this.vpHeight - this.viewBoxMargin.topBottom;
|
||||
let padding = 0;
|
||||
if (this.graphData.length * this.barWidth > this.vbWidth) {
|
||||
this.xGraphRange = this.graphData.length * this.barWidth;
|
||||
padding = this.calculatePadding(this.barWidth);
|
||||
this.showScrollIndicator = true;
|
||||
this.isPanAvailable = true;
|
||||
} else {
|
||||
this.xGraphRange = this.vbWidth - Math.abs(this.minX);
|
||||
}
|
||||
|
||||
this.xScale = d3
|
||||
.scaleBand()
|
||||
.range([0, this.xGraphRange])
|
||||
.round(true)
|
||||
.paddingInner(padding);
|
||||
this.yScale = d3.scaleLinear().rangeRound([this.vbHeight, 0]);
|
||||
|
||||
this.xScale.domain(this.graphData.map(d => d.name));
|
||||
this.yScale.domain([0, d3.max(this.graphData.map(d => d.value))]);
|
||||
|
||||
// Zoom/Panning Function
|
||||
this.zoom = d3
|
||||
.zoom()
|
||||
.translateExtent([[0, 0], [this.xGraphRange, this.vbHeight]])
|
||||
.on('zoom', this.panGraph)
|
||||
.on('end', this.removeGrabStyling);
|
||||
|
||||
const xAxis = d3.axisBottom().scale(this.xScale);
|
||||
|
||||
const yAxis = d3
|
||||
.axisLeft()
|
||||
.scale(this.yScale)
|
||||
.ticks(4);
|
||||
|
||||
const renderedXAxis = d3
|
||||
.select(this.$refs.baseSvg)
|
||||
.select('.x-axis')
|
||||
.call(xAxis);
|
||||
|
||||
this.xAxisTextElements = this.$refs.xAxis.querySelectorAll('text');
|
||||
|
||||
renderedXAxis.select('.domain').remove();
|
||||
|
||||
renderedXAxis
|
||||
.selectAll('text')
|
||||
.style('text-anchor', 'end')
|
||||
.attr('dx', '-.3em')
|
||||
.attr('dy', '-.95em')
|
||||
.attr('class', 'tick-text')
|
||||
.attr('transform', 'rotate(-90)');
|
||||
|
||||
renderedXAxis.selectAll('line').remove();
|
||||
|
||||
const { maxTextWidth } = this;
|
||||
renderedXAxis.selectAll('text').each(function formatText() {
|
||||
const axisText = d3.select(this);
|
||||
let textLength = axisText.node().getComputedTextLength();
|
||||
let textContent = axisText.text();
|
||||
while (textLength > maxTextWidth && textContent.length > 0) {
|
||||
textContent = textContent.slice(0, -1);
|
||||
axisText.text(`${textContent}...`);
|
||||
textLength = axisText.node().getComputedTextLength();
|
||||
}
|
||||
});
|
||||
|
||||
const width = this.vbWidth;
|
||||
|
||||
const renderedYAxis = d3
|
||||
.select(this.$refs.baseSvg)
|
||||
.select('.y-axis')
|
||||
.call(yAxis);
|
||||
|
||||
renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) {
|
||||
if (i > 0) {
|
||||
d3
|
||||
.select(this)
|
||||
.select('line')
|
||||
.attr('x2', width)
|
||||
.attr('class', 'axis-tick');
|
||||
}
|
||||
});
|
||||
|
||||
// Add the panning capabilities
|
||||
if (this.isPanAvailable) {
|
||||
d3
|
||||
.select(this.$refs.baseSvg)
|
||||
.call(this.zoom)
|
||||
.on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
// Update the yAxisLabel coordinates
|
||||
const labelDims = this.$refs.yAxisLabel.getBBox();
|
||||
this.rectYAxisLabelDims = {
|
||||
height: labelDims.width + 10,
|
||||
};
|
||||
},
|
||||
panGraph() {
|
||||
const allowedRightScroll = this.xGraphRange - this.vbWidth - this.paddingThreshold;
|
||||
const graphMaxPan = Math.abs(d3.event.transform.x) < allowedRightScroll;
|
||||
this.isGrabbed = true;
|
||||
this.panX = d3.event.transform.x;
|
||||
|
||||
if (d3.event.transform.x === 0) {
|
||||
this.showLeftScrollIndicator = false;
|
||||
} else {
|
||||
this.showLeftScrollIndicator = true;
|
||||
this.showScrollIndicator = true;
|
||||
}
|
||||
|
||||
if (!graphMaxPan) {
|
||||
this.panX = -1 * (this.xGraphRange - this.vbWidth + this.paddingThreshold);
|
||||
this.showScrollIndicator = false;
|
||||
}
|
||||
},
|
||||
setTooltipTitle(data) {
|
||||
return data !== null ? `${data.name}: ${data.value}` : '';
|
||||
},
|
||||
calculatePadding(desiredBarWidth) {
|
||||
const widthWithMargin = this.vbWidth - Math.abs(this.minX);
|
||||
const dividend = widthWithMargin - this.graphData.length * desiredBarWidth;
|
||||
const divisor = widthWithMargin - desiredBarWidth;
|
||||
|
||||
return dividend / divisor;
|
||||
},
|
||||
removeGrabStyling() {
|
||||
this.isGrabbed = false;
|
||||
},
|
||||
barHoveredIn(index) {
|
||||
this.xAxisTextElements[index].classList.add('x-axis-text');
|
||||
},
|
||||
barHoveredOut(index) {
|
||||
this.xAxisTextElements[index].classList.remove('x-axis-text');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
ref="svgContainer"
|
||||
:class="activateGrabCursor"
|
||||
class="svg-graph-container"
|
||||
>
|
||||
<svg
|
||||
ref="baseSvg"
|
||||
:width="vpWidth"
|
||||
:height="vpHeight"
|
||||
:viewBox="svgViewBox"
|
||||
:preserveAspectRatio="preserveAspectRatioType">
|
||||
<g
|
||||
ref="xAxis"
|
||||
:transform="xAxisLocation"
|
||||
class="x-axis"
|
||||
/>
|
||||
<g v-if="!isLoading">
|
||||
<template
|
||||
v-for="(data, index) in graphData">
|
||||
<rect
|
||||
v-tooltip
|
||||
:key="index"
|
||||
:width="xScale.bandwidth()"
|
||||
:x="xScale(data.name)"
|
||||
:y="yScale(data.value)"
|
||||
:height="vbHeight - yScale(data.value)"
|
||||
:transform="barTranslationTransform"
|
||||
:title="setTooltipTitle(data)"
|
||||
class="bar-rect"
|
||||
data-placement="top"
|
||||
@mouseover="barHoveredIn(index)"
|
||||
@mouseout="barHoveredOut(index)"
|
||||
/>
|
||||
</template>
|
||||
</g>
|
||||
<rect
|
||||
:height="vbHeight + 100"
|
||||
transform="translate(-100, -5)"
|
||||
width="100"
|
||||
fill="#fff"
|
||||
/>
|
||||
<g class="y-axis-label">
|
||||
<line
|
||||
:x1="0"
|
||||
:x2="0"
|
||||
:y1="0"
|
||||
:y2="vbHeight"
|
||||
transform="translate(-35, 0)"
|
||||
stroke="black"
|
||||
/>
|
||||
<!--Get text length and change the height of this rect accordingly-->
|
||||
<rect
|
||||
:height="rectYAxisLabelDims.height"
|
||||
:transform="yAxisLabelRectTransform"
|
||||
:width="30"
|
||||
fill="#fff"
|
||||
/>
|
||||
<text
|
||||
ref="yAxisLabel"
|
||||
:transform="yAxisLabelTextTransform"
|
||||
>
|
||||
{{ yAxisLabel }}
|
||||
</text>
|
||||
</g>
|
||||
<g
|
||||
class="y-axis"
|
||||
/>
|
||||
<g v-if="showScrollIndicator">
|
||||
<rect
|
||||
:height="vbHeight + 100"
|
||||
:transform="`translate(${vpWidth - 60}, -5)`"
|
||||
width="40"
|
||||
fill="#fff"
|
||||
/>
|
||||
<icon
|
||||
:x="vpWidth - 50"
|
||||
:y="vbHeight / 2"
|
||||
:width="14"
|
||||
:height="14"
|
||||
name="chevron-right"
|
||||
class="animate-flicker"
|
||||
/>
|
||||
</g>
|
||||
<!--The line that shows up when the data elements surpass the available width -->
|
||||
<g
|
||||
v-if="showScrollIndicator"
|
||||
:transform="scrollIndicatorTransform">
|
||||
<rect
|
||||
:height="vbHeight"
|
||||
x="0"
|
||||
y="0"
|
||||
width="20"
|
||||
fill="url(#shadow-gradient)"
|
||||
/>
|
||||
</g>
|
||||
<!--Left scroll indicator-->
|
||||
<g
|
||||
v-if="showLeftScrollIndicator"
|
||||
transform="translate(0, 0)">
|
||||
<rect
|
||||
:height="vbHeight"
|
||||
x="0"
|
||||
y="0"
|
||||
width="20"
|
||||
fill="url(#left-shadow-gradient)"
|
||||
/>
|
||||
</g>
|
||||
<svg-gradient
|
||||
:colors="gradientColors"
|
||||
:opacity="gradientOpacity"
|
||||
identifier-name="shadow-gradient"/>
|
||||
<svg-gradient
|
||||
:colors="inverseGradientColors"
|
||||
:opacity="inverseGradientOpacity"
|
||||
identifier-name="left-shadow-gradient"/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,4 @@
|
|||
export const GRADIENT_COLORS = ['#000', '#a7a7a7'];
|
||||
export const GRADIENT_OPACITY = ['0', '0.4'];
|
||||
export const INVERSE_GRADIENT_COLORS = ['#a7a7a7', '#000'];
|
||||
export const INVERSE_GRADIENT_OPACITY = ['0.4', '0'];
|
|
@ -0,0 +1,37 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
colors: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
opacity: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
identifierName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<svg
|
||||
height="0"
|
||||
width="0">
|
||||
<defs>
|
||||
<linearGradient
|
||||
:id="identifierName">
|
||||
<stop
|
||||
:stop-color="colors[0]"
|
||||
:stop-opacity="opacity[0]"
|
||||
offset="0%" />
|
||||
<stop
|
||||
:stop-color="colors[1]"
|
||||
:stop-opacity="opacity[1]"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
|
@ -31,3 +31,61 @@
|
|||
color: $gl-text-red;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-graph-container {
|
||||
width: 100%;
|
||||
|
||||
.axis-tick {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.tick-text {
|
||||
fill: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
.x-axis-text {
|
||||
fill: $theme-gray-900;
|
||||
}
|
||||
|
||||
.bar-rect {
|
||||
fill: rgba($blue-500, 0.1);
|
||||
stroke: $blue-500;
|
||||
}
|
||||
|
||||
.bar-rect:hover {
|
||||
fill: rgba($blue-700, 0.3);
|
||||
}
|
||||
|
||||
.y-axis-label {
|
||||
line {
|
||||
stroke: $stat-graph-axis-fill;
|
||||
}
|
||||
|
||||
text {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
fill: $theme-gray-800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.svg-graph-container-with-grab {
|
||||
cursor: grab;
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
.svg-graph-container-grabbed {
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
@keyframes flickerAnimation {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.animate-flicker {
|
||||
animation: flickerAnimation 1.5s infinite;
|
||||
fill: $theme-gray-500;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
"core-js": "^2.4.1",
|
||||
"cropper": "^2.3.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"d3": "4.12.2",
|
||||
"d3-array": "^1.2.1",
|
||||
"d3-axis": "^1.0.8",
|
||||
"d3-brush": "^1.0.4",
|
||||
|
|
85
spec/javascripts/vue_shared/components/bar_chart_spec.js
Normal file
85
spec/javascripts/vue_shared/components/bar_chart_spec.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
import Vue from 'vue';
|
||||
import BarChart from '~/vue_shared/components/bar_chart.vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
|
||||
function getRandomArbitrary(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
function generateRandomData(dataNumber) {
|
||||
const randomGraphData = [];
|
||||
|
||||
for (let i = 1; i <= dataNumber; i += 1) {
|
||||
randomGraphData.push({
|
||||
name: `random ${i}`,
|
||||
value: parseInt(getRandomArbitrary(1, 8), 10),
|
||||
});
|
||||
}
|
||||
|
||||
return randomGraphData;
|
||||
}
|
||||
|
||||
describe('Bar chart component', () => {
|
||||
let barChart;
|
||||
const graphData = generateRandomData(10);
|
||||
|
||||
beforeEach(() => {
|
||||
const BarChartComponent = Vue.extend(BarChart);
|
||||
|
||||
barChart = mountComponent(BarChartComponent, {
|
||||
graphData,
|
||||
yAxisLabel: 'data',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
barChart.$destroy();
|
||||
});
|
||||
|
||||
it('calculates the padding for even distribution across bars', () => {
|
||||
barChart.vbWidth = 1000;
|
||||
const result = barChart.calculatePadding(30);
|
||||
|
||||
// since padding can't be higher than 1 and lower than 0
|
||||
// for more info: https://github.com/d3/d3-scale#band-scales
|
||||
expect(result).not.toBeLessThan(0);
|
||||
expect(result).not.toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('formats the tooltip title', () => {
|
||||
const tooltipTitle = barChart.setTooltipTitle(barChart.graphData[0]);
|
||||
|
||||
expect(tooltipTitle).toContain('random 1:');
|
||||
});
|
||||
|
||||
it('has a translates the bar graphs on across the X axis', () => {
|
||||
barChart.panX = 100;
|
||||
|
||||
expect(barChart.barTranslationTransform).toEqual('translate(100, 0)');
|
||||
});
|
||||
|
||||
it('translates the scroll indicator to the far right side', () => {
|
||||
barChart.vbWidth = 500;
|
||||
|
||||
expect(barChart.scrollIndicatorTransform).toEqual('translate(420, 0)');
|
||||
});
|
||||
|
||||
it('translates the x-axis to the bottom of the viewbox and pan coordinates', () => {
|
||||
barChart.panX = 100;
|
||||
barChart.vbHeight = 250;
|
||||
|
||||
expect(barChart.xAxisLocation).toEqual('translate(100, 250)');
|
||||
});
|
||||
|
||||
it('Contains a total of 4 ticks across the y axis', () => {
|
||||
const ticks = barChart.$el.querySelector('.y-axis').querySelectorAll('.tick').length;
|
||||
|
||||
expect(ticks).toEqual(4);
|
||||
});
|
||||
|
||||
it('rotates the x axis labels a total of 90 degress (CCW)', () => {
|
||||
const xAxisLabel = barChart.$el.querySelector('.x-axis').querySelectorAll('text')[0];
|
||||
|
||||
expect(xAxisLabel.getAttribute('transform')).toEqual('rotate(-90)');
|
||||
});
|
||||
});
|
160
yarn.lock
160
yarn.lock
|
@ -1774,7 +1774,7 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.13.0, commander@^2.15.1, commander@^2.9.0:
|
||||
commander@2, commander@^2.13.0, commander@^2.15.1, commander@^2.9.0:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
|
||||
|
||||
|
@ -2070,15 +2070,15 @@ cyclist@~0.2.2:
|
|||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
|
||||
|
||||
d3-array@^1.2.0, d3-array@^1.2.1:
|
||||
d3-array@1, d3-array@1.2.1, d3-array@^1.2.0, d3-array@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc"
|
||||
|
||||
d3-axis@^1.0.8:
|
||||
d3-axis@1.0.8, d3-axis@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa"
|
||||
|
||||
d3-brush@^1.0.4:
|
||||
d3-brush@1.0.4, d3-brush@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4"
|
||||
dependencies:
|
||||
|
@ -2088,44 +2088,103 @@ d3-brush@^1.0.4:
|
|||
d3-selection "1"
|
||||
d3-transition "1"
|
||||
|
||||
d3-collection@1:
|
||||
d3-chord@1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c"
|
||||
dependencies:
|
||||
d3-array "1"
|
||||
d3-path "1"
|
||||
|
||||
d3-collection@1, d3-collection@1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2"
|
||||
|
||||
d3-color@1:
|
||||
d3-color@1, d3-color@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"
|
||||
|
||||
d3-dispatch@1:
|
||||
d3-dispatch@1, d3-dispatch@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8"
|
||||
|
||||
d3-drag@1:
|
||||
d3-drag@1, d3-drag@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.1.tgz#df8dd4c502fb490fc7462046a8ad98a5c479282d"
|
||||
dependencies:
|
||||
d3-dispatch "1"
|
||||
d3-selection "1"
|
||||
|
||||
d3-ease@1:
|
||||
d3-dsv@1, d3-dsv@1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae"
|
||||
dependencies:
|
||||
commander "2"
|
||||
iconv-lite "0.4"
|
||||
rw "1"
|
||||
|
||||
d3-ease@1, d3-ease@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e"
|
||||
|
||||
d3-format@1:
|
||||
d3-force@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3"
|
||||
dependencies:
|
||||
d3-collection "1"
|
||||
d3-dispatch "1"
|
||||
d3-quadtree "1"
|
||||
d3-timer "1"
|
||||
|
||||
d3-format@1, d3-format@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f"
|
||||
|
||||
d3-interpolate@1:
|
||||
d3-geo@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"
|
||||
dependencies:
|
||||
d3-array "1"
|
||||
|
||||
d3-hierarchy@1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26"
|
||||
|
||||
d3-interpolate@1, d3-interpolate@1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6"
|
||||
dependencies:
|
||||
d3-color "1"
|
||||
|
||||
d3-path@1:
|
||||
d3-path@1, d3-path@1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764"
|
||||
|
||||
d3-scale@^1.0.7:
|
||||
d3-polygon@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62"
|
||||
|
||||
d3-quadtree@1, d3-quadtree@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438"
|
||||
|
||||
d3-queue@3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-queue/-/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618"
|
||||
|
||||
d3-random@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3"
|
||||
|
||||
d3-request@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-request/-/d3-request-1.0.6.tgz#a1044a9ef4ec28c824171c9379fae6d79474b19f"
|
||||
dependencies:
|
||||
d3-collection "1"
|
||||
d3-dispatch "1"
|
||||
d3-dsv "1"
|
||||
xmlhttprequest "1"
|
||||
|
||||
d3-scale@1.0.7, d3-scale@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d"
|
||||
dependencies:
|
||||
|
@ -2137,31 +2196,31 @@ d3-scale@^1.0.7:
|
|||
d3-time "1"
|
||||
d3-time-format "2"
|
||||
|
||||
d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.2.0:
|
||||
d3-selection@1, d3-selection@1.2.0, d3-selection@^1.1.0, d3-selection@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.2.0.tgz#1b8ec1c7cedadfb691f2ba20a4a3cfbeb71bbc88"
|
||||
|
||||
d3-shape@^1.2.0:
|
||||
d3-shape@1.2.0, d3-shape@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
|
||||
dependencies:
|
||||
d3-path "1"
|
||||
|
||||
d3-time-format@2, d3-time-format@^2.1.1:
|
||||
d3-time-format@2, d3-time-format@2.1.1, d3-time-format@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31"
|
||||
dependencies:
|
||||
d3-time "1"
|
||||
|
||||
d3-time@1, d3-time@^1.0.8:
|
||||
d3-time@1, d3-time@1.0.8, d3-time@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84"
|
||||
|
||||
d3-timer@1:
|
||||
d3-timer@1, d3-timer@1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531"
|
||||
|
||||
d3-transition@1:
|
||||
d3-transition@1, d3-transition@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039"
|
||||
dependencies:
|
||||
|
@ -2172,10 +2231,59 @@ d3-transition@1:
|
|||
d3-selection "^1.1.0"
|
||||
d3-timer "1"
|
||||
|
||||
d3-voronoi@1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c"
|
||||
|
||||
d3-zoom@1.7.1:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.1.tgz#02f43b3c3e2db54f364582d7e4a236ccc5506b63"
|
||||
dependencies:
|
||||
d3-dispatch "1"
|
||||
d3-drag "1"
|
||||
d3-interpolate "1"
|
||||
d3-selection "1"
|
||||
d3-transition "1"
|
||||
|
||||
d3@3.5.17:
|
||||
version "3.5.17"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
|
||||
|
||||
d3@4.12.2:
|
||||
version "4.12.2"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-4.12.2.tgz#12f775564c6a9de229f63db03446e2cb7bb56c8f"
|
||||
dependencies:
|
||||
d3-array "1.2.1"
|
||||
d3-axis "1.0.8"
|
||||
d3-brush "1.0.4"
|
||||
d3-chord "1.0.4"
|
||||
d3-collection "1.0.4"
|
||||
d3-color "1.0.3"
|
||||
d3-dispatch "1.0.3"
|
||||
d3-drag "1.2.1"
|
||||
d3-dsv "1.0.8"
|
||||
d3-ease "1.0.3"
|
||||
d3-force "1.1.0"
|
||||
d3-format "1.2.1"
|
||||
d3-geo "1.9.1"
|
||||
d3-hierarchy "1.1.5"
|
||||
d3-interpolate "1.1.6"
|
||||
d3-path "1.0.5"
|
||||
d3-polygon "1.0.3"
|
||||
d3-quadtree "1.0.3"
|
||||
d3-queue "3.0.7"
|
||||
d3-random "1.1.0"
|
||||
d3-request "1.0.6"
|
||||
d3-scale "1.0.7"
|
||||
d3-selection "1.2.0"
|
||||
d3-shape "1.2.0"
|
||||
d3-time "1.0.8"
|
||||
d3-time-format "2.1.1"
|
||||
d3-timer "1.0.7"
|
||||
d3-transition "1.1.1"
|
||||
d3-voronoi "1.1.2"
|
||||
d3-zoom "1.7.1"
|
||||
|
||||
dagre-d3-renderer@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.4.24.tgz#b36ce2fe4ea20de43e7698627c6ede2a9f15ec45"
|
||||
|
@ -3771,6 +3879,12 @@ https-proxy-agent@^2.2.1:
|
|||
agent-base "^4.1.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
iconv-lite@0.4:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
iconv-lite@0.4.15:
|
||||
version "0.4.15"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
|
||||
|
@ -6293,6 +6407,10 @@ 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"
|
||||
|
||||
rx-lite-aggregates@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
|
||||
|
@ -7772,6 +7890,10 @@ xmlhttprequest-ssl@~1.5.4:
|
|||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
|
||||
|
||||
xmlhttprequest@1:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
|
||||
|
||||
xregexp@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
|
||||
|
|
Loading…
Reference in a new issue