diff --git a/client/app/scripts/charts/edge-container.js b/client/app/scripts/charts/edge-container.js
index b0545329d..b8c4209b5 100644
--- a/client/app/scripts/charts/edge-container.js
+++ b/client/app/scripts/charts/edge-container.js
@@ -1,17 +1,17 @@
import _ from 'lodash';
-import d3 from 'd3';
import React from 'react';
import { connect } from 'react-redux';
import { Motion, spring } from 'react-motion';
import { Map as makeMap } from 'immutable';
+import { line, curveBasis } from 'd3-shape';
import Edge from './edge';
const animConfig = [80, 20]; // stiffness, damping
const pointCount = 30;
-const line = d3.svg.line()
- .interpolate('basis')
+const spline = line()
+ .curve(curveBasis)
.x(d => d.x)
.y(d => d.y);
@@ -23,7 +23,7 @@ const buildPath = (points, layoutPrecision) => {
if (!extracted[index]) {
extracted[index] = {};
}
- extracted[index][axis] = d3.round(value, layoutPrecision);
+ extracted[index][axis] = Math.round(value, layoutPrecision);
});
return extracted;
};
@@ -53,7 +53,7 @@ class EdgeContainer extends React.Component {
const other = _.omit(this.props, 'points');
if (layoutPrecision === 0) {
- const path = line(points.toJS());
+ const path = spline(points.toJS());
return ;
}
@@ -62,7 +62,7 @@ class EdgeContainer extends React.Component {
{(interpolated) => {
// convert points to path string, because that lends itself to
// JS-equality checks in the child component
- const path = line(buildPath(interpolated, layoutPrecision));
+ const path = spline(buildPath(interpolated, layoutPrecision));
return ;
}}
diff --git a/client/app/scripts/charts/node-container.js b/client/app/scripts/charts/node-container.js
index 74820303d..8d513e45c 100644
--- a/client/app/scripts/charts/node-container.js
+++ b/client/app/scripts/charts/node-container.js
@@ -1,7 +1,6 @@
import _ from 'lodash';
import React from 'react';
import { connect } from 'react-redux';
-import d3 from 'd3';
import { Motion, spring } from 'react-motion';
import Node from './node';
@@ -20,8 +19,8 @@ class NodeContainer extends React.Component {
f: spring(scaleFactor, animConfig)
}}>
{interpolated => {
- const transform = `translate(${d3.round(interpolated.x, layoutPrecision)},`
- + `${d3.round(interpolated.y, layoutPrecision)})`;
+ const transform = `translate(${Math.round(interpolated.x, -layoutPrecision)},`
+ + `${Math.round(interpolated.y, -layoutPrecision)})`;
return ;
}}
diff --git a/client/app/scripts/charts/node-networks-overlay.js b/client/app/scripts/charts/node-networks-overlay.js
index b09dcac2e..7d2b02d27 100644
--- a/client/app/scripts/charts/node-networks-overlay.js
+++ b/client/app/scripts/charts/node-networks-overlay.js
@@ -1,5 +1,5 @@
import React from 'react';
-import d3 from 'd3';
+import { scaleOrdinal } from 'd3-scale';
import { List as makeList } from 'immutable';
import { getNetworkColor } from '../utils/color-utils';
import { isContrastMode } from '../utils/contrast-utils';
@@ -10,7 +10,7 @@ const minBarHeight = 3;
const padding = 0.05;
const rx = 1;
const ry = rx;
-const x = d3.scale.ordinal();
+const x = scaleOrdinal();
function NodeNetworksOverlay({offset, size, stack, networks = makeList()}) {
// Min size is about a quarter of the width, feels about right.
diff --git a/client/app/scripts/charts/node-shape-cloud.js b/client/app/scripts/charts/node-shape-cloud.js
index f77af9346..cf71e326e 100644
--- a/client/app/scripts/charts/node-shape-cloud.js
+++ b/client/app/scripts/charts/node-shape-cloud.js
@@ -1,5 +1,5 @@
import React from 'react';
-import d3 from 'd3';
+import { extent } from 'd3-array';
import { isContrastMode } from '../utils/contrast-utils';
@@ -15,7 +15,7 @@ function toPoint(stringPair) {
function getExtents(svgPath) {
const points = svgPath.split(' ').filter(s => s.length > 1).map(toPoint);
- return [d3.extent(points, p => p[0]), d3.extent(points, p => p[1])];
+ return [extent(points, p => p[0]), extent(points, p => p[1])];
}
export default function NodeShapeCloud({highlighted, size, color}) {
diff --git a/client/app/scripts/charts/node-shape-heptagon.js b/client/app/scripts/charts/node-shape-heptagon.js
index f4f4b715a..1abfe4b5b 100644
--- a/client/app/scripts/charts/node-shape-heptagon.js
+++ b/client/app/scripts/charts/node-shape-heptagon.js
@@ -1,20 +1,19 @@
import React from 'react';
-import d3 from 'd3';
import classNames from 'classnames';
-import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
-import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
+import { line, curveCardinalClosed } from 'd3-shape';
+import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils.js';
+import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles.js';
-const line = d3.svg.line()
- .interpolate('cardinal-closed')
- .tension(0.25);
+const spline = line()
+ .curve(curveCardinalClosed.tension(0.65));
function polygon(r, sides) {
const a = (Math.PI * 2) / sides;
- const points = [[r, 0]];
- for (let i = 1; i < sides; i++) {
- points.push([r * Math.cos(a * i), r * Math.sin(a * i)]);
+ const points = [];
+ for (let i = 0; i < sides; i++) {
+ points.push([r * Math.sin(a * i), -r * Math.cos(a * i)]);
}
return points;
}
@@ -23,8 +22,7 @@ function polygon(r, sides) {
export default function NodeShapeHeptagon({id, highlighted, size, color, metric}) {
const scaledSize = size * 1.0;
const pathProps = v => ({
- d: line(polygon(scaledSize * v, 7)),
- transform: 'rotate(90)'
+ d: spline(polygon(scaledSize * v, 7))
});
const clipId = `mask-${id}`;
diff --git a/client/app/scripts/charts/node-shape-hex.js b/client/app/scripts/charts/node-shape-hexagon.js
similarity index 76%
rename from client/app/scripts/charts/node-shape-hex.js
rename to client/app/scripts/charts/node-shape-hexagon.js
index d7045c9ab..4e4c18d72 100644
--- a/client/app/scripts/charts/node-shape-hex.js
+++ b/client/app/scripts/charts/node-shape-hexagon.js
@@ -1,13 +1,12 @@
import React from 'react';
-import d3 from 'd3';
import classNames from 'classnames';
-import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
-import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
+import { line, curveCardinalClosed } from 'd3-shape';
+import { getMetricValue, getMetricColor, getClipPathDefinition } from '../utils/metric-utils.js';
+import { CANVAS_METRIC_FONT_SIZE } from '../constants/styles.js';
-const line = d3.svg.line()
- .interpolate('cardinal-closed')
- .tension(0.25);
+const spline = line()
+ .curve(curveCardinalClosed.tension(0.65));
function getWidth(h) {
@@ -26,14 +25,14 @@ function getPoints(h) {
[0, 0.25 * h]
];
- return line(points);
+ return spline(points);
}
-export default function NodeShapeHex({id, highlighted, size, color, metric}) {
+export default function NodeShapeHexagon({id, highlighted, size, color, metric}) {
const pathProps = v => ({
d: getPoints(size * v * 2),
- transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
+ transform: `translate(-${size * getWidth(v)}, -${size * v})`
});
const shadowSize = 0.45;
diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js
index 63511fdfa..118f5194f 100644
--- a/client/app/scripts/charts/node.js
+++ b/client/app/scripts/charts/node.js
@@ -12,7 +12,7 @@ import MatchedResults from '../components/matched-results';
import NodeShapeCircle from './node-shape-circle';
import NodeShapeStack from './node-shape-stack';
import NodeShapeRoundedSquare from './node-shape-rounded-square';
-import NodeShapeHex from './node-shape-hex';
+import NodeShapeHexagon from './node-shape-hexagon';
import NodeShapeHeptagon from './node-shape-heptagon';
import NodeShapeCloud from './node-shape-cloud';
import NodeNetworksOverlay from './node-networks-overlay';
@@ -30,7 +30,7 @@ function stackedShape(Shape) {
const nodeShapes = {
circle: NodeShapeCircle,
- hexagon: NodeShapeHex,
+ hexagon: NodeShapeHexagon,
heptagon: NodeShapeHeptagon,
square: NodeShapeRoundedSquare,
cloud: NodeShapeCloud
diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js
index ed20156a0..5f3cdb201 100644
--- a/client/app/scripts/charts/nodes-chart.js
+++ b/client/app/scripts/charts/nodes-chart.js
@@ -1,11 +1,14 @@
import _ from 'lodash';
-import d3 from 'd3';
import debug from 'debug';
import React from 'react';
import { connect } from 'react-redux';
import { Map as makeMap, fromJS } from 'immutable';
import timely from 'timely';
+import { scaleThreshold, scaleLinear } from 'd3-scale';
+import { event as d3Event, select } from 'd3-selection';
+import { zoom, zoomIdentity } from 'd3-zoom';
+
import { nodeAdjacenciesSelector, adjacentNodesSelector } from '../selectors/chartSelectors';
import { clickBackground } from '../actions/app-actions';
import { EDGE_ID_SEPARATOR } from '../constants/naming';
@@ -20,7 +23,7 @@ const log = debug('scope:nodes-chart');
const ZOOM_CACHE_FIELDS = ['scale', 'panTranslateX', 'panTranslateY'];
// make sure circular layouts a bit denser with 3-6 nodes
-const radiusDensity = d3.scale.threshold()
+const radiusDensity = scaleThreshold()
.domain([3, 6])
.range([2.5, 3.5, 3]);
@@ -80,7 +83,7 @@ function getNodeScale(nodesCount, width, height) {
const normalizedNodeSize = Math.max(MIN_NODE_SIZE,
Math.min(nodeSize / Math.sqrt(nodesCount), maxNodeSize));
- return d3.scale.linear().range([0, normalizedNodeSize]);
+ return scaleLinear().range([0, normalizedNodeSize]);
}
@@ -123,11 +126,11 @@ class NodesChart extends React.Component {
this.state = {
edges: makeMap(),
nodes: makeMap(),
- nodeScale: d3.scale.linear(),
+ nodeScale: scaleLinear(),
panTranslateX: 0,
panTranslateY: 0,
scale: 1,
- selectedNodeScale: d3.scale.linear(),
+ selectedNodeScale: scaleLinear(),
hasZoomed: false,
height: props.height || 0,
width: props.width || 0,
@@ -146,12 +149,11 @@ class NodesChart extends React.Component {
// wipe node states when showing different topology
if (nextProps.topologyId !== this.props.topologyId) {
- // re-apply cached canvas zoom/pan to d3 behavior (or set defaul values)
+ // re-apply cached canvas zoom/pan to d3 behavior (or set the default values)
const defaultZoom = { scale: 1, panTranslateX: 0, panTranslateY: 0, hasZoomed: false };
const nextZoom = this.state.zoomCache[nextProps.topologyId] || defaultZoom;
if (nextZoom) {
- this.zoom.scale(nextZoom.scale);
- this.zoom.translate([nextZoom.panTranslateX, nextZoom.panTranslateY]);
+ this.setZoom(nextZoom);
}
// saving previous zoom state
@@ -188,17 +190,17 @@ class NodesChart extends React.Component {
// distinguish pan/zoom from click
this.isZooming = false;
- this.zoom = d3.behavior.zoom()
+ this.zoom = zoom()
.scaleExtent([0.1, 2])
.on('zoom', this.zoomed);
- d3.select('.nodes-chart svg')
- .call(this.zoom);
+ this.svg = select('.nodes-chart svg');
+ this.svg.call(this.zoom);
}
componentWillUnmount() {
// undoing .call(zoom)
- d3.select('.nodes-chart svg')
+ this.svg
.on('mousedown.zoom', null)
.on('onwheel', null)
.on('onmousewheel', null)
@@ -317,8 +319,7 @@ class NodesChart extends React.Component {
restoreLayout(state) {
// undo any pan/zooming that might have happened
- this.zoom.scale(state.scale);
- this.zoom.translate([state.panTranslateX, state.panTranslateY]);
+ this.setZoom(state);
const nodes = state.nodes.map(node => node.merge({
x: node.get('px'),
@@ -361,10 +362,8 @@ class NodesChart extends React.Component {
const zoomFactor = Math.min(xFactor, yFactor);
let zoomScale = state.scale;
- if (this.zoom && !state.hasZoomed && zoomFactor > 0 && zoomFactor < 1) {
+ if (this.svg && !state.hasZoomed && zoomFactor > 0 && zoomFactor < 1) {
zoomScale = zoomFactor;
- // saving in d3's behavior cache
- this.zoom.scale(zoomFactor);
}
return {
@@ -376,18 +375,27 @@ class NodesChart extends React.Component {
}
zoomed() {
- // debug('zoomed', d3.event.scale, d3.event.translate);
+ // debug('zoomed', d3Event.transform);
this.isZooming = true;
// dont pan while node is selected
if (!this.props.selectedNodeId) {
this.setState({
hasZoomed: true,
- panTranslateX: d3.event.translate[0],
- panTranslateY: d3.event.translate[1],
- scale: d3.event.scale
+ panTranslateX: d3Event.transform.x,
+ panTranslateY: d3Event.transform.y,
+ scale: d3Event.transform.k
});
}
}
+
+ setZoom(zoom) {
+ this.svg.call(
+ this.zoom.transform,
+ zoomIdentity
+ .scale(zoom.scale)
+ .translate(zoom.panTranslateX, zoom.panTranslateY)
+ );
+ }
}
diff --git a/client/app/scripts/components/debug-toolbar.js b/client/app/scripts/components/debug-toolbar.js
index 14d5eaa74..7a0ac7b65 100644
--- a/client/app/scripts/components/debug-toolbar.js
+++ b/client/app/scripts/components/debug-toolbar.js
@@ -1,10 +1,10 @@
/* eslint react/jsx-no-bind: "off" */
import React from 'react';
-import d3 from 'd3';
import _ from 'lodash';
import Perf from 'react-addons-perf';
import { connect } from 'react-redux';
import { fromJS, Set as makeSet } from 'immutable';
+import { hsl } from 'd3-color';
import debug from 'debug';
const log = debug('scope:debug-panel');
@@ -328,7 +328,7 @@ class DebugToolbar extends React.Component {
|
+ style={{backgroundColor: hsl(text2degree(r), 0.5, 0.5).toString()}} />
))}
diff --git a/client/app/scripts/components/sparkline.js b/client/app/scripts/components/sparkline.js
index 29f39c0fc..55254ffc1 100644
--- a/client/app/scripts/components/sparkline.js
+++ b/client/app/scripts/components/sparkline.js
@@ -1,18 +1,20 @@
// Forked from: https://github.com/KyleAMathews/react-sparkline at commit a9d7c5203d8f240938b9f2288287aaf0478df013
import React from 'react';
-import d3 from 'd3';
+import { min as d3Min, max as d3Max, mean as d3Mean } from 'd3-array';
+import { isoParse as parseDate } from 'd3-time-format';
+import { line, curveLinear } from 'd3-shape';
+import { scaleLinear } from 'd3-scale';
import { formatMetricSvg } from '../utils/string-utils';
-const parseDate = d3.time.format.iso.parse;
export default class Sparkline extends React.Component {
constructor(props, context) {
super(props, context);
- this.x = d3.scale.linear();
- this.y = d3.scale.linear();
- this.line = d3.svg.line()
+ this.x = scaleLinear();
+ this.y = scaleLinear();
+ this.line = line()
.x(d => this.x(d.date))
.y(d => this.y(d.value));
}
@@ -29,7 +31,7 @@ export default class Sparkline extends React.Component {
// adjust scales
this.x.range([2, this.props.width - 2]);
this.y.range([this.props.height - 2, 2]);
- this.line.interpolate(this.props.interpolate);
+ this.line.curve(this.props.curve);
// Convert dates into D3 dates
data = data.map(d => ({
@@ -50,18 +52,18 @@ export default class Sparkline extends React.Component {
this.x.domain([firstDate, lastDate]);
// determine value range
- const minValue = this.props.min !== undefined ? this.props.min : d3.min(data, d => d.value);
+ const minValue = this.props.min !== undefined ? this.props.min : d3Min(data, d => d.value);
const maxValue = this.props.max !== undefined
- ? Math.max(this.props.max, d3.max(data, d => d.value)) : d3.max(data, d => d.value);
+ ? Math.max(this.props.max, d3Max(data, d => d.value)) : d3Max(data, d => d.value);
this.y.domain([minValue, maxValue]);
const lastValue = data[data.length - 1].value;
const lastX = this.x(lastDate);
const lastY = this.y(lastValue);
- const min = formatMetricSvg(d3.min(data, d => d.value), this.props);
- const max = formatMetricSvg(d3.max(data, d => d.value), this.props);
- const mean = formatMetricSvg(d3.mean(data, d => d.value), this.props);
- const title = `Last ${d3.round((lastDate - firstDate) / 1000)} seconds, ` +
+ const min = formatMetricSvg(d3Min(data, d => d.value), this.props);
+ const max = formatMetricSvg(d3Max(data, d => d.value), this.props);
+ const mean = formatMetricSvg(d3Mean(data, d => d.value), this.props);
+ const title = `Last ${Math.round((lastDate - firstDate) / 1000)} seconds, ` +
`${data.length} samples, min: ${min}, max: ${max}, mean: ${mean}`;
return {title, lastX, lastY, data};
@@ -98,6 +100,6 @@ Sparkline.defaultProps = {
height: 24,
strokeColor: '#7d7da8',
strokeWidth: '0.5px',
- interpolate: 'none',
+ curve: curveLinear,
circleDiameter: 1.75
};
diff --git a/client/app/scripts/hoc/metric-feeder.js b/client/app/scripts/hoc/metric-feeder.js
index 5fd233687..9a538f4d2 100644
--- a/client/app/scripts/hoc/metric-feeder.js
+++ b/client/app/scripts/hoc/metric-feeder.js
@@ -1,9 +1,8 @@
import React from 'react';
-import d3 from 'd3';
+import { isoParse as parseDate } from 'd3-time-format';
import { OrderedMap } from 'immutable';
const makeOrderedMap = OrderedMap;
-const parseDate = d3.time.format.iso.parse;
const sortDate = (v, d) => d;
const DEFAULT_TICK_INTERVAL = 1000; // DEFAULT_TICK_INTERVAL + renderTime < 1000ms
const WINDOW_LENGTH = 60;
diff --git a/client/app/scripts/utils/color-utils.js b/client/app/scripts/utils/color-utils.js
index c67821364..c728c1088 100644
--- a/client/app/scripts/utils/color-utils.js
+++ b/client/app/scripts/utils/color-utils.js
@@ -1,11 +1,12 @@
-import d3 from 'd3';
+import { hsl } from 'd3-color';
+import { scaleLinear, scaleOrdinal, schemeCategory10 } from 'd3-scale';
const PSEUDO_COLOR = '#b1b1cb';
const hueRange = [20, 330]; // exclude red
-const hueScale = d3.scale.linear().range(hueRange);
-const networkColorScale = d3.scale.category10();
+const hueScale = scaleLinear().range(hueRange);
+const networkColorScale = scaleOrdinal(schemeCategory10);
// map hues to lightness
-const lightnessScale = d3.scale.linear().domain(hueRange).range([0.5, 0.7]);
+const lightnessScale = scaleLinear().domain(hueRange).range([0.5, 0.7]);
const startLetterRange = 'A'.charCodeAt();
const endLetterRange = 'Z'.charCodeAt();
const letterRange = endLetterRange - startLetterRange;
@@ -36,8 +37,7 @@ export function colors(text, secondText) {
// reuse text2degree and feed degree to lightness scale
lightness = lightnessScale(text2degree(secondText));
}
- const color = d3.hsl(hue, saturation, lightness);
- return color;
+ return hsl(hue, saturation, lightness);
}
export function getNeutralColor() {
@@ -55,31 +55,30 @@ export function getNodeColorDark(text = '', secondText = '', isPseudo = false) {
if (isPseudo) {
return PSEUDO_COLOR;
}
- const color = d3.rgb(colors(text, secondText));
- let hsl = color.hsl();
+ let color = hsl(colors(text, secondText));
// ensure darkness
- if (hsl.h > 20 && hsl.h < 120) {
- hsl = hsl.darker(2);
+ if (color.h > 20 && color.h < 120) {
+ color = color.darker(2);
} else if (hsl.l > 0.7) {
- hsl = hsl.darker(1.5);
+ color = color.darker(1.5);
} else {
- hsl = hsl.darker(1);
+ color = color.darker(1);
}
- return hsl.toString();
+ return color.toString();
}
export function getNetworkColor(text) {
return networkColorScale(text);
}
-export function brightenColor(color) {
- let hsl = d3.rgb(color).hsl();
+export function brightenColor(c) {
+ let color = hsl(c);
if (hsl.l > 0.5) {
- hsl = hsl.brighter(0.5);
+ color = color.brighter(0.5);
} else {
- hsl = hsl.brighter(0.8);
+ color = color.brighter(0.8);
}
- return hsl.toString();
+ return color.toString();
}
diff --git a/client/app/scripts/utils/data-generator-utils.js b/client/app/scripts/utils/data-generator-utils.js
index e79717eeb..e75a6d83a 100644
--- a/client/app/scripts/utils/data-generator-utils.js
+++ b/client/app/scripts/utils/data-generator-utils.js
@@ -1,5 +1,6 @@
import _ from 'lodash';
-import d3 from 'd3';
+import { scaleLinear } from 'd3-scale';
+import { extent } from 'd3-array';
// Inspired by Lee Byron's test data generator.
@@ -20,7 +21,7 @@ function bumpLayer(n, maxValue) {
for (i = 0; i < n; ++i) a[i] = 0;
for (i = 0; i < 5; ++i) bump(a);
const values = a.map(function(d) { return Math.max(0, d * maxValue); });
- const s = d3.scale.linear().domain(d3.extent(values)).range([0, maxValue]);
+ const s = scaleLinear().domain(extent(values)).range([0, maxValue]);
return values.map(s);
}
/*eslint-enable */
diff --git a/client/app/scripts/utils/metric-utils.js b/client/app/scripts/utils/metric-utils.js
index 9eed53ad3..9ae8e0465 100644
--- a/client/app/scripts/utils/metric-utils.js
+++ b/client/app/scripts/utils/metric-utils.js
@@ -1,5 +1,5 @@
import _ from 'lodash';
-import d3 from 'd3';
+import { scaleLog } from 'd3-scale';
import { formatMetricSvg } from './string-utils';
import { colors } from './color-utils';
import React from 'react';
@@ -24,7 +24,7 @@ export function getClipPathDefinition(clipId, size, height,
//
// loadScale(1) == 0.5; E.g. a nicely balanced system :).
-const loadScale = d3.scale.log().domain([0.01, 100]).range([0, 1]);
+const loadScale = scaleLog().domain([0.01, 100]).range([0, 1]);
export function getMetricValue(metric, size) {
diff --git a/client/app/scripts/utils/string-utils.js b/client/app/scripts/utils/string-utils.js
index ee09f6cae..25727259f 100644
--- a/client/app/scripts/utils/string-utils.js
+++ b/client/app/scripts/utils/string-utils.js
@@ -1,10 +1,11 @@
import React from 'react';
import filesize from 'filesize';
-import d3 from 'd3';
+import { format as d3Format } from 'd3-format';
+import { isoFormat } from 'd3-time-format';
import LCP from 'lcp';
import moment from 'moment';
-const formatLargeValue = d3.format('s');
+const formatLargeValue = d3Format('s');
function renderHtml(text, unit) {
@@ -69,7 +70,7 @@ function makeFormatMetric(renderFn) {
export const formatMetric = makeFormatMetric(renderHtml);
export const formatMetricSvg = makeFormatMetric(renderSvg);
-export const formatDate = d3.time.format.iso;
+export const formatDate = isoFormat; // d3.time.format.iso;
const CLEAN_LABEL_REGEX = /[^A-Za-z0-9]/g;
export function slugify(label) {
diff --git a/client/package.json b/client/package.json
index b335502a2..3d4ac528b 100644
--- a/client/package.json
+++ b/client/package.json
@@ -8,7 +8,7 @@
"dependencies": {
"babel-polyfill": "~6.16.0",
"classnames": "~2.2.5",
- "d3": "~3.5.5",
+ "d3": "~4.4.0",
"dagre": "0.7.4",
"debug": "~2.3.3",
"filesize": "~3.3.0",