Simple metric selection clicking!

Locks onto a metric after you mouseout.
This commit is contained in:
Simon Howe
2016-03-08 20:10:24 +01:00
parent ef1c69eb2a
commit a104962aa2
11 changed files with 105 additions and 67 deletions

View File

@@ -19,6 +19,13 @@ export function selectMetric(metricId) {
});
}
export function lockMetric(metricId) {
AppDispatcher.dispatch({
type: ActionTypes.LOCK_METRIC,
metricId: metricId
});
}
export function changeTopologyOption(option, value, topologyId) {
AppDispatcher.dispatch({
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,

View File

@@ -1,10 +1,10 @@
import React from 'react';
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
import {getMetricValue} from '../utils/data-utils.js';
export default function NodeShapeCircle({highlighted, size, color, metric}) {
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
const clipId = `mask-${Math.random()}`;
const {height, v} = getMetricValue(metric, size);
const {height, formattedValue} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -23,9 +23,7 @@ export default function NodeShapeCircle({highlighted, size, color, metric}) {
<circle r={size * 0.45} className="shadow" />
<circle r={size * 0.45} className="metric-fill" clipPath={`url(#${clipId})`} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(v)}
</text> :
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>
);

View File

@@ -1,6 +1,6 @@
import React from 'react';
import d3 from 'd3';
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
import {getMetricValue} from '../utils/data-utils.js';
const line = d3.svg.line()
.interpolate('cardinal-closed')
@@ -23,7 +23,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
});
const clipId = `mask-${Math.random()}`;
const {height, v} = getMetricValue(metric, size);
const {height, formattedValue} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -42,9 +42,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
<path className="shadow" {...pathProps(0.45)} />
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(0.45)} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(v)}
</text> :
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>
);

View File

@@ -1,6 +1,6 @@
import React from 'react';
import d3 from 'd3';
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
import {getMetricValue} from '../utils/data-utils.js';
const line = d3.svg.line()
.interpolate('cardinal-closed')
@@ -35,7 +35,7 @@ export default function NodeShapeHex({highlighted, size, color, metric}) {
const upperHexBitHeight = -0.25 * size * shadowSize;
const clipId = `mask-${Math.random()}`;
const {height, v} = getMetricValue(metric, size);
const {height, formattedValue} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -54,7 +54,7 @@ export default function NodeShapeHex({highlighted, size, color, metric}) {
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(v)}
{formattedValue}
</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
import {getMetricValue} from '../utils/data-utils.js';
export default function NodeShapeSquare({
highlighted, size, color, rx = 0, ry = 0, metric
@@ -14,7 +14,7 @@ export default function NodeShapeSquare({
});
const clipId = `mask-${Math.random()}`;
const {height, v} = getMetricValue(metric, size);
const {height, formattedValue} = getMetricValue(metric, size);
return (
<g className="shape">
@@ -33,9 +33,7 @@ export default function NodeShapeSquare({
<rect className="shadow" {...rectProps(0.45)} />
<rect className="metric-fill" clipPath={`url(#${clipId})`} {...rectProps(0.45)} />
{highlighted ?
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
{formatCanvasMetric(v)}
</text> :
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
<circle className="node" r={Math.max(2, (size * 0.125))} />}
</g>
);

View File

@@ -31,6 +31,7 @@ function getStateFromStores() {
highlightedEdgeIds: AppStore.getHighlightedEdgeIds(),
highlightedNodeIds: AppStore.getHighlightedNodeIds(),
hostname: AppStore.getHostname(),
lockedMetric: AppStore.getLockedMetric(),
nodeDetails: AppStore.getNodeDetails(),
nodes: AppStore.getNodes(),
selectedNodeId: AppStore.getSelectedNodeId(),
@@ -118,7 +119,10 @@ export default class App extends React.Component {
topologyId={this.state.currentTopologyId} />
<Sidebar>
<MetricSelector selectedMetric={this.state.selectedMetric}/>
<MetricSelector
lockedMetric={this.state.lockedMetric}
selectedMetric={this.state.selectedMetric}
/>
<Status errorUrl={this.state.errorUrl} topology={this.state.currentTopology}
topologiesLoaded={this.state.topologiesLoaded}
websocketClosed={this.state.websocketClosed} />

View File

@@ -1,6 +1,6 @@
import React from 'react';
import _ from 'lodash';
import { selectMetric } from '../actions/app-actions';
import { selectMetric, lockMetric } from '../actions/app-actions';
import classNames from 'classnames';
const METRICS = {
@@ -16,17 +16,30 @@ function onMouseOver(k) {
return selectMetric(k);
}
export default function MetricSelector({selectedMetric}) {
function onMouseClick(k) {
return lockMetric(k);
}
function onMouseOut(k) {
console.log('onMouseOut', k);
selectMetric(k);
}
export default function MetricSelector({selectedMetric, lockedMetric}) {
return (
<div className="available-metrics">
<div
className="available-metrics"
onMouseLeave={() => onMouseOut(lockedMetric)}>
{_.map(METRICS, (key, name) => {
return (
<div
key={key}
className={classNames('sidebar-item', {
'locked': (key === lockedMetric),
'selected': (key === selectedMetric)
})}
onMouseOver={() => onMouseOver(key)}>
onMouseOver={() => onMouseOver(key)}
onClick={() => onMouseClick(key)}>
{name}
</div>
);

View File

@@ -23,6 +23,7 @@ const ACTION_TYPES = [
'ENTER_NODE',
'LEAVE_EDGE',
'LEAVE_NODE',
'LOCK_METRIC',
'OPEN_WEBSOCKET',
'RECEIVE_CONTROL_PIPE',
'RECEIVE_CONTROL_PIPE_STATUS',

View File

@@ -58,7 +58,9 @@ let routeSet = false;
let controlPipes = makeOrderedMap(); // pipeId -> controlPipe
let updatePausedAt = null; // Date
let websocketClosed = true;
let selectedMetric = 'process_cpu_usage_percent';
let lockedMetric = selectedMetric;
const topologySorter = topology => topology.get('rank');
@@ -165,6 +167,10 @@ export class AppStore extends Store {
return adjacentNodes;
}
getLockedMetric() {
return lockedMetric;
}
getSelectedMetric() {
return selectedMetric;
}
@@ -411,6 +417,11 @@ export class AppStore extends Store {
this.__emitChange();
break;
}
case ActionTypes.LOCK_METRIC: {
lockedMetric = payload.metricId;
this.__emitChange();
break;
}
case ActionTypes.DESELECT_NODE: {
closeNodeDetails();
this.__emitChange();

View File

@@ -1,5 +1,6 @@
import _ from 'lodash';
import d3 from 'd3';
import { formatMetric } from './string-utils';
// Inspired by Lee Byron's test data generator.
@@ -80,23 +81,19 @@ export function addMetrics(delta, prevNodes) {
export function getMetricValue(metric, size) {
if (!metric) {
return {height: 0, v: null};
return {height: 0, value: null, formattedValue: 'n/a'};
}
const max = metric.getIn(['max']);
const v = metric.getIn(['samples', 0, 'value']);
const vp = v === 0 ? 0 : v / max;
const value = metric.getIn(['samples', 0, 'value']);
const valuePercentage = value === 0 ? 0 : value / max;
const baseline = 0.05;
const displayedValue = vp * (1 - baseline) + baseline;
const displayedValue = valuePercentage * (1 - baseline) + baseline;
const height = size * displayedValue;
return {height, v};
}
const formatLargeValue = d3.format('s');
export function formatCanvasMetric(v) {
if (v === null) {
return 'n/a';
}
return formatLargeValue(Number(v).toFixed(1));
return {
height: height,
value: value,
formattedValue: formatMetric(value, metric.toJS(), true)
};
}

View File

@@ -4,43 +4,54 @@ import d3 from 'd3';
const formatLargeValue = d3.format('s');
const formatters = {
filesize(value) {
const obj = filesize(value, {output: 'object'});
return formatters.metric(obj.value, obj.suffix);
},
function toHtml(text, unit) {
return (
<span className="metric-formatted">
<span className="metric-value">{text}</span>
<span className="metric-unit">{unit}</span>
</span>
);
}
integer(value) {
if (value < 1100 && value >= 0) {
return Number(value).toFixed(0);
function makeFormatters(renderFn) {
const formatters = {
filesize(value) {
const obj = filesize(value, {output: 'object'});
console.log('rendering', value);
return renderFn(obj.value, obj.suffix);
},
integer(value) {
if (value < 1100 && value >= 0) {
return Number(value).toFixed(0);
}
return formatLargeValue(value);
},
number(value) {
if (value < 1100 && value >= 0) {
return Number(value).toFixed(2);
}
return formatLargeValue(value);
},
percent(value) {
return renderFn(formatters.number(value), '%');
}
return formatLargeValue(value);
},
};
number(value) {
if (value < 1100 && value >= 0) {
return Number(value).toFixed(2);
}
return formatLargeValue(value);
},
return formatters;
}
percent(value) {
return formatters.metric(formatters.number(value), '%');
},
metric(text, unit) {
return (
<span className="metric-formatted">
<span className="metric-value">{text}</span>
<span className="metric-unit">{unit}</span>
</span>
);
}
};
const formatters = makeFormatters(toHtml);
const svgFormatters = makeFormatters((text, unit) => `${text}${unit}`);
export function formatMetric(value, opts) {
const formatter = opts && formatters[opts.format] ? opts.format : 'number';
return formatters[formatter](value);
export function formatMetric(value, opts, svg) {
const formatterBase = svg ? svgFormatters : formatters;
const formatter = opts && formatterBase[opts.format] ? opts.format : 'number';
return formatterBase[formatter](value);
}
export const formatDate = d3.time.format.iso;