Switched to D3 version 4.4.0

This commit is contained in:
fbarl
2016-11-24 16:02:01 +01:00
parent 308387157e
commit 7442ff3f41
16 changed files with 105 additions and 99 deletions

View File

@@ -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 <Edge {...other} path={path} />;
}
@@ -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 <Edge {...other} path={path} />;
}}
</Motion>

View File

@@ -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 <Node {...other} transform={transform} scaleFactor={interpolated.f} />;
}}
</Motion>

View File

@@ -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.

View File

@@ -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}) {

View File

@@ -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}`;

View File

@@ -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;

View File

@@ -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

View File

@@ -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)
);
}
}

View File

@@ -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 {
<tr key={r}>
<td
title={`${r}`}
style={{backgroundColor: d3.hsl(text2degree(r), 0.5, 0.5).toString()}} />
style={{backgroundColor: hsl(text2degree(r), 0.5, 0.5).toString()}} />
</tr>
))}
</tbody>

View File

@@ -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
};

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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 */

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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",