diff options
-rw-r--r-- | app/assets/javascripts/vue_shared/components/bar_chart.vue | 391 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/bar_chart_constants.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/svg_gradient.vue | 37 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/graph.scss | 58 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/bar_chart_spec.js | 85 | ||||
-rw-r--r-- | yarn.lock | 160 |
7 files changed, 717 insertions, 19 deletions
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue new file mode 100644 index 00000000000..3ced4eb691a --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue @@ -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> diff --git a/app/assets/javascripts/vue_shared/components/bar_chart_constants.js b/app/assets/javascripts/vue_shared/components/bar_chart_constants.js new file mode 100644 index 00000000000..6957b112da6 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/bar_chart_constants.js @@ -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']; diff --git a/app/assets/javascripts/vue_shared/components/svg_gradient.vue b/app/assets/javascripts/vue_shared/components/svg_gradient.vue new file mode 100644 index 00000000000..b61a1befcd6 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/svg_gradient.vue @@ -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> diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss index 84da9180f93..49d8a5d959b 100644 --- a/app/assets/stylesheets/pages/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss @@ -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; +} diff --git a/package.json b/package.json index 819a1db37ee..33a6827e7df 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,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", diff --git a/spec/javascripts/vue_shared/components/bar_chart_spec.js b/spec/javascripts/vue_shared/components/bar_chart_spec.js new file mode 100644 index 00000000000..7e91cd6f63f --- /dev/null +++ b/spec/javascripts/vue_shared/components/bar_chart_spec.js @@ -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)'); + }); +}); diff --git a/yarn.lock b/yarn.lock index c94496cb37b..ab84c63e837 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1809,7 +1809,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" @@ -2105,15 +2105,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: @@ -2123,44 +2123,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: @@ -2172,31 +2231,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: @@ -2207,10 +2266,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" @@ -3812,6 +3920,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" @@ -6389,6 +6503,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" @@ -7876,6 +7994,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" |