Loading js-apps/meep-frontend/src/js/containers/dashboard-container.js +102 −21 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import * as d3 from 'd3'; import IDCAreaChart from './idc-area-chart'; import IDCGraph from './idc-graph'; import IDCAppsView from './idc-apps-view'; import { getScenarioNodeChildren, Loading @@ -16,7 +17,8 @@ import { } from '../util/scenario-utils'; import { execFakeChangeSelectedDestination execFakeChangeSelectedDestination, execFakeAddPingBucket } from '../state/exec'; const newDataPoint = (date) => { Loading Loading @@ -150,42 +152,119 @@ class DashboardContainer extends Component { // data: updateData(that.state.data) // }); // }, 1000); this.bucketCount = 0; this.dataTimer = setInterval(() => this.nextDataBucket(this.bucketCount), 1000); } componendDidUnmount() { clearInterval(this.dataTimer); } nextDataBucket() { this.apps = this.getRoot().descendants().filter(isApp); const nbNewPings = this.apps.length*10; const srcNodeIndex = () => { return Math.floor((Math.random()*this.apps.length)); }; const destNodeIndex = (srcIdx) => { // const destIdx = (srcIdx + 1 + Math.ceil(Math.random()*2)*2) % apps.length; const destIdx = (srcIdx + 1 + Math.ceil((Math.random()*this.apps.length - 1))) % this.apps.length; // const index = Math.floor(Math.random()*destinations.length); return destIdx; }; const funcs = [x => 0.2*(1 + Math.sin(x-5)), x => 0.2*(1 + Math.cos(x)), x => 0.2 * (1 + Math.random() * Math.cos(x)*Math.sin(x-12))]; const newPing = (date, i, bucketCount) => { const srcIdx = srcNodeIndex(); const destIdx = destNodeIndex(srcIdx); const delay = Math.random() + 0.2; const amplitude = 0.2*(destIdx % 3)*(destIdx%5) + 1; const frequency = 0.3*(destIdx % 3)*(destIdx%5) + 1; const phase = (destIdx % 5)*(destIdx%7); const x = bucketCount % 25; const func = funcs[destIdx % 3]; const ping = { src: this.apps[srcIdx].data.id, dest: this.apps[destIdx].data.id, date: date, delay: amplitude*func((x - phase)*frequency) }; return ping; }; let newPings = []; const now = new Date(); for(let i=0; i < nbNewPings; i++) { newPings.push(newPing(now, i, this.bucketCount)); } const dataBucket = { date: now, pings: newPings }; this.props.addPingBucket(dataBucket); this.bucketCount += 1; } componentWillUnmount() { clearInterval(this.timer); } calculateDestinations() { this.root = this.props.displayedScenario; const data = this.root; // || this.props.displayedScenario; this.root = d3.hierarchy(data, getScenarioNodeChildren); const apps = this.root.descendants().filter(isApp); return apps.map(a => a.data.id); getRoot() { return d3.hierarchy(this.props.displayedScenario, getScenarioNodeChildren); } render() { const destinations = this.calculateDestinations(); const root = this.getRoot(); const nodes = root.descendants(); const nbBuckets = this.props.nbBuckets || 25; const max = maxValue(this.props.pingBuckets); const min = minValue(this.props.pingBuckets); const apps = nodes.filter(isApp); const destinations = apps.map(a => a.data.id); const colorRange = colorArray(destinations.length); const dataPoints = pingBucketsToData(this.props.pingBuckets)(nbBuckets)(destinations); const colorRange = colorArray(destinations.length); return ( <Grid> <GridCell span={6}> <IDCGraph const showApps = this.props.showAppsView; const span = showApps ? 6 : 12; let graph = null; if (showApps) { graph = ( <IDCAppsView apps={apps} pingBuckets={this.props.pingBuckets} colorRange={colorRange} width={700} height={600} renderApps={true} colorRange={colorRange} onNodeClicked={() => {}} /> ); } else { graph = (<IDCGraph width={1000} height={600} />); } return ( <Grid> <GridCell span={span}> {graph} </GridCell> <GridCell span={6}> {showApps ? (<GridCell span={6}> <IDCAreaChart data={dataPoints} width={700} height={600} Loading @@ -195,7 +274,8 @@ class DashboardContainer extends Component { min={min} max={max} /> </GridCell> </GridCell>) : null} </Grid> ); Loading @@ -211,7 +291,8 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { changeSelectedDestination: (dest) => dispatch(execFakeChangeSelectedDestination(dest)) changeSelectedDestination: (dest) => dispatch(execFakeChangeSelectedDestination(dest)), addPingBucket: (b) => dispatch(execFakeAddPingBucket(b)) }; }; Loading js-apps/meep-frontend/src/js/containers/exec/exec-page-container.js +1 −1 Original line number Diff line number Diff line Loading @@ -270,7 +270,7 @@ class ExecPageContainer extends Component { <GridCell span={spanLeft}> <Elevation className="component-style" z={2}> <div style={{padding: 10}}> {this.props.experimental ? (<DashboardContainer />) : (<IDCVis type={TYPE_EXEC} />)} {this.props.experimental ? (<DashboardContainer showAppsView={true}/>) : (<IDCVis type={TYPE_EXEC} />)} </div> </Elevation> Loading js-apps/meep-frontend/src/js/containers/graph-utils.js 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019 * InterDigital Communications, Inc. * All rights reserved. * * The information provided herein is the proprietary and confidential * information of InterDigital Communications, Inc. */ import _ from 'lodash'; export const blue = '#5DBCD2'; export const lineGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} L${n2.X},${n2.Y}`; }; export const plusGenerator = () => { const s = 2; return `M25 -20 h${s} v${2*s} h${2*s} v${s} h-${2*s} v${2*s} h-${s} v-${2*s} h-${2*s} v-${s} h${2*s} z`; }; export const minusGenerator = () => { const s = 4; return `M25 -20 h${3*s} v${s} h-${3*s} z`; }; export const curveGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} C${n1.X},${n2.Y + 150} ${n1.X},${n2.Y + 50} ${n2.X},${n2.Y}`; }; export const visitNodes = f => node => { f(node); if (node.children) { _.each(node.children, (c) => { visitNodes(f)(c); }); } }; export const isNodeSelected = n => n.selected; export const isNodeHighlighted = n => n.highlighted; No newline at end of file js-apps/meep-frontend/src/js/containers/idc-apps-view.js 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019 * InterDigital Communications, Inc. * All rights reserved. * * The information provided herein is the proprietary and confidential * information of InterDigital Communications, Inc. */ import _ from 'lodash'; import { connect } from 'react-redux'; import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import * as d3 from 'd3'; import IDCNode from './idc-node.js'; import { lineGeneratorNodes } from './graph-utils'; import { getScenarioNodeChildren } from '../util/scenario-utils'; const positionAppsCircle = ({apps, width, height}) => { const cx = width/2.0; const cy = height/2.0; const PI = 3.141592653598793846264; const r = 0.5*height*0.8; _.each(apps, (app, i) => { const theta = (i/apps.length)*(2*PI); app.X = cx + r*Math.cos(theta); app.Y = cy + r*Math.sin(theta); }); }; const IDCAppsView = ( { apps, colorRange, selectedSource, pingBuckets, width, height, onNodeClicked } ) => { const [positioningNeeded, setPositioningNeeded] = useState(true); const colorForApp = apps.reduce((res, val, i) => { // res[val.data.id] = colorRange[i]; return {...res, [val.data.id]: colorRange[i]}; }, {}); //if (positioningNeeded) { // copyAttributesRecursive(data)(this.root); positionAppsCircle({apps: apps, height: height, width: width}); //setPositioningNeeded(false); //} const pingBucket = _.last(pingBuckets); if (!pingBucket) { return null; } const appsMap = {}; _.each(apps, a => appsMap[a.data.id] = a); const pings = pingBucket.pings; let m = {}; _.each(pings, p => { if (!m[p.src]) { m[p.src] = {}; } if (!m[p.src][p.dest]) { m[p.src][p.dest] = { pings: [] }; } const o = m[p.src][p.dest]; o.pings.push(p); }); const edges = _.flatMap(apps .map((d) => { const rowObject = m[d.data.id]; if (!rowObject) { return []; } const destinations = Object.keys(m[d.data.id]); return _.map(destinations, (dest) => { return { src: d.data.id, dest: dest, count: rowObject[dest].pings.length, color: colorForApp[dest], avgLatency: d3.mean(rowObject[dest].pings, d => d.delay) }; }); } ) ).filter(e => { // return nbSelected ? appsMap[e.src].selected : true; // console.log(`${appsMap[e.src].data.id}:`, appsMap[e.src]); if (selectedSource) { return e.src === selectedSource; } else { return false; } }); const lineDefs = <defs> { _.map(edges, (e, i) => { return <path key={'path' + i} id={'textPathDef' + i} d={lineGeneratorNodes(appsMap[e.src])(appsMap[e.dest])} style={{fill: 'none', 'strokeWidth': e.count*0.1}} className='line' />; }) } </defs>; const lines = _.map(edges, (e, i) => { return <path key={'path' + i} id={'path' + i} d={lineGeneratorNodes(appsMap[e.src])(appsMap[e.dest])} style={{fill: 'none', 'strokeWidth': 0.5, 'stroke': e.color}} className='line' />; }); const textPaths = _.map(edges, (e,i) => <text key={'textPath' + i} style={{stroke: e.color}}> <textPath xlinkHref={`#textPathDef${i}`} startOffset={'45%'} > {`Avg lat: ${e.avgLatency.toFixed(2)} ms`} </textPath> </text> ); const nodes = apps .map((d, i) => <IDCNode collapsible={false} key={`node${i}`} d={d} stroke={colorRange[i]} updateParent={() => {}} onClick={onNodeClicked} /> ); return ( <svg height={height} width={width} > <> {lines} {lineDefs} {textPaths} {nodes} </> </svg> ); }; export default IDCAppsView; No newline at end of file js-apps/meep-frontend/src/js/containers/idc-graph.js +21 −215 Original line number Diff line number Diff line Loading @@ -9,23 +9,23 @@ import _ from 'lodash'; import { connect } from 'react-redux'; import React, { Component } from 'react'; import { Grid, GridCell, GridInner } from '@rmwc/grid'; import { Graph } from 'react-d3-graph'; import ReactDOM from 'react-dom'; import { Button } from '@rmwc/button'; import * as d3 from 'd3'; import moment from 'moment'; import { Client } from '@elastic/elasticsearch'; import { plusGenerator, minusGenerator, lineGeneratorNodes, visitNodes, blue } from './graph-utils'; import { getScenarioSpecificImage, isApp, getScenarioNodeChildren } from '../util/scenario-utils'; import { updateObject } from '../util/object-util'; import { execChangeTable, execChangeVis, Loading @@ -37,197 +37,9 @@ import { cfgChangeTable, cfgChangeVis, cfgElemEdit } from '../state/cfg'; import { FIELD_NAME, getElemFieldVal } from '../util/elem-utils'; // Set fixed to true for provided group function setFixedGroup(group) { group['fixed'] = { x: true, y: true }; } const translate = d => { return `translate(${d.X}, ${d.Y})`; }; const curveGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} C${n1.X},${n2.Y + 150} ${n1.X},${n2.Y + 50} ${n2.X},${n2.Y}`; }; const lineGenerator = d => { return `M${d.X},${d.Y} L${d.parent.X},${d.parent.Y}`; }; const lineGeneratorReverse = d => { return `M${d.parent.X},${d.parent.Y} L${d.X},${d.Y}`; }; const lineGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} L${n2.X},${n2.Y}`; }; const plusGenerator = () => { const s = 2; return `M25 -20 h${s} v${2*s} h${2*s} v${s} h-${2*s} v${2*s} h-${s} v-${2*s} h-${2*s} v-${s} h${2*s} z`; }; const minusGenerator = () => { const s = 4; return `M25 -20 h${3*s} v${s} h-${3*s} z`; }; const hideNode = node => { node.hidden = true; }; const showNode = node => { node.hidden = false; }; const hideChildren = node => { _.each(node.children, c => { visitNodes(hideNode)(c); }); }; const showChildren = node => { _.each(node.children, c => { visitNodes(showNode)(c); }); }; const blue = '#5DBCD2'; const Plus = props => { const d = props.d; const plusMinus = props.collapsible ? (d.collapsed ? plusGenerator : minusGenerator) : () => ''; return ( <path width={20} height={20} d={plusMinus()} style={{fill: blue, 'strokeWidth': 2}} stroke={blue} className='plus' onClick={() => { d.collapsed = !d.collapsed; if (d.collapsed) { hideChildren(d); } else { showChildren(d); } props.updateParent(); }} /> ); }; class IDCNode extends Component { constructor(props) { super(props); getElemFieldVal, this.state = { mouseDown: false, dragging: false, d: this.props.d }; } render() { const d = this.props.d; const fill = this.highlighted ? 'red' : '#69b3a2'; const radius = this.highlighted ? 14 : 12; const size=30; return (<g transform={translate(d)} > <Plus width={10} height={10} d={d} updateParent={this.props.updateParent}/> <image xlinkHref={`../img/${d.data.iconName}`} height={size} width={size} x={-size/2} y={-size/2} /*filter={d.selected ? 'url(#filter)' : '' }*/ r={radius} style={{fill: fill}} stroke={'black'} strokeWidth={3} onMouseDown={ (e) => { this.dragging = true; this.highlighted = true; this.mouseCoords={ x: e.clientX - e.target.farthestViewportElement.parentNode.offsetLeft, y: e.clientY - e.target.farthestViewportElement.parentNode.offsetTop }; this.props.updateParent(); }} onMouseUp={ () => { this.dragging = false; this.highlighted = false; }} onMouseMove={ (e) => { if (!this.dragging) { return; } e.preventDefault(); const newX = e.clientX - e.target.farthestViewportElement.parentNode.offsetLeft; const newY = e.clientY - e.target.farthestViewportElement.parentNode.offsetTop; const dx = newX - this.mouseCoords.x; const dy = newY - this.mouseCoords.y; this.mouseCoords.x = newX; this.mouseCoords.y = newY; const targetXY = e.currentTarget.parentNode.getAttribute('transform').substr(10).slice(0, -1).split(', '); const targetX = Number(targetXY[0]); const targetY = Number(targetXY[1]); // console.log(`(${d.x}, ${d.y}) -> (${X}, ${Y})`); d.X = targetX + dx; d.Y = targetY + dy; this.props.updateParent(); }} onClick={() => { d.selected = !d.selected; console.log('',d); this.props.updateParent(); }} onMouseOver={() => { this.highlighted = true; d.highlighted = true; d.data.dR = 4; this.props.updateParent(); }} onMouseOut={() => { d.data.dR = 0; this.dragging = false; this.highlighted = false; d.highlighted = false; this.props.updateParent(); }} /> <text x={-size/2} y="35" className="tiny" stroke={this.props.stroke} fontWeight={this.highlighted ? 'bold' : 'normal'}>{d.data.id}</text> </g>); } } const visitNodes = f => node => { f(node); if (node.children) { _.each(node.children, (c) => { visitNodes(f)(c); }); } }; } from '../util/elem-utils'; const copyAttributesRecursive = nodeSrc => nodeDest => { if (nodeSrc.X && nodeSrc.Y) { Loading Loading @@ -284,8 +96,13 @@ const createEdgesToChildren = array => node => { const nodeVisible = n => !n.hidden; const isNodeSelected = n => n.selected; const isNodeHighlighted = n => n.highlighted; const IDCHierarchy = (props) => { }; class IDCGraph extends Component { Loading Loading @@ -345,17 +162,10 @@ class IDCGraph extends Component { }; this.props.addPingBucket(dataBucket); this.refreshCharts(); this.bucketCount += 1; } refreshCharts() { this.setState({root: this.root}); } componentDidMount() { // this.ESClient = new Client({ node: 'http://localhost:9200' }); this.dataTimer = setInterval(() => this.nextDataBucket(this.bucketCount), 1000); } Loading @@ -369,7 +179,6 @@ class IDCGraph extends Component { } update() { // this.props.execChangeDisplayedScenario(this.root); this.setState({ root: this.root, apps: this.apps Loading @@ -385,10 +194,10 @@ class IDCGraph extends Component { } positionNodesTree () { const data = this.root; // || this.props.displayedScenario; const data = this.root; this.root = d3.hierarchy(data, getScenarioNodeChildren); this.edges = []; this.createHierarchyEdges(); // this.createHierarchyEdges(); this.nodes = this.root.descendants(); copyAttributesRecursive(data)(this.root); Loading @@ -398,7 +207,7 @@ class IDCGraph extends Component { } positionNodesCircle() { const data = this.root; // || this.props.displayedScenario; const data = this.root; this.root = d3.hierarchy(data, getScenarioNodeChildren); const compareTypes = (a, b) => { Loading Loading @@ -445,7 +254,7 @@ class IDCGraph extends Component { <path key={'path' + i} id={'textPath' + i} d={lineGenerator(d)} d={lineGeneratorNodes(d)(d.parent)} style={{fill: 'none', 'strokeWidth': 2}} stroke={'#aaa'} className='line' Loading Loading @@ -503,8 +312,6 @@ class IDCGraph extends Component { } renderApps() { console.log('selectedDestination: ', this.props.selectedDestination); const colorRange = this.props.colorRange; const colorForApp = _.reduce(this.apps, (res, val, i) => { res[val.data.id] = colorRange[i]; Loading Loading @@ -557,7 +364,7 @@ class IDCGraph extends Component { } const edges = _.flatMap(this.apps .map((d, appIndex) => { .map((d) => { const rowObject = m[d.data.id]; if (!rowObject) { return []; Loading Loading @@ -654,7 +461,6 @@ class IDCGraph extends Component { render() { // return this.renderTree(); return ( <> {this.props.renderApps ? this.renderApps() : this.renderTree()} </> Loading Loading
js-apps/meep-frontend/src/js/containers/dashboard-container.js +102 −21 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import * as d3 from 'd3'; import IDCAreaChart from './idc-area-chart'; import IDCGraph from './idc-graph'; import IDCAppsView from './idc-apps-view'; import { getScenarioNodeChildren, Loading @@ -16,7 +17,8 @@ import { } from '../util/scenario-utils'; import { execFakeChangeSelectedDestination execFakeChangeSelectedDestination, execFakeAddPingBucket } from '../state/exec'; const newDataPoint = (date) => { Loading Loading @@ -150,42 +152,119 @@ class DashboardContainer extends Component { // data: updateData(that.state.data) // }); // }, 1000); this.bucketCount = 0; this.dataTimer = setInterval(() => this.nextDataBucket(this.bucketCount), 1000); } componendDidUnmount() { clearInterval(this.dataTimer); } nextDataBucket() { this.apps = this.getRoot().descendants().filter(isApp); const nbNewPings = this.apps.length*10; const srcNodeIndex = () => { return Math.floor((Math.random()*this.apps.length)); }; const destNodeIndex = (srcIdx) => { // const destIdx = (srcIdx + 1 + Math.ceil(Math.random()*2)*2) % apps.length; const destIdx = (srcIdx + 1 + Math.ceil((Math.random()*this.apps.length - 1))) % this.apps.length; // const index = Math.floor(Math.random()*destinations.length); return destIdx; }; const funcs = [x => 0.2*(1 + Math.sin(x-5)), x => 0.2*(1 + Math.cos(x)), x => 0.2 * (1 + Math.random() * Math.cos(x)*Math.sin(x-12))]; const newPing = (date, i, bucketCount) => { const srcIdx = srcNodeIndex(); const destIdx = destNodeIndex(srcIdx); const delay = Math.random() + 0.2; const amplitude = 0.2*(destIdx % 3)*(destIdx%5) + 1; const frequency = 0.3*(destIdx % 3)*(destIdx%5) + 1; const phase = (destIdx % 5)*(destIdx%7); const x = bucketCount % 25; const func = funcs[destIdx % 3]; const ping = { src: this.apps[srcIdx].data.id, dest: this.apps[destIdx].data.id, date: date, delay: amplitude*func((x - phase)*frequency) }; return ping; }; let newPings = []; const now = new Date(); for(let i=0; i < nbNewPings; i++) { newPings.push(newPing(now, i, this.bucketCount)); } const dataBucket = { date: now, pings: newPings }; this.props.addPingBucket(dataBucket); this.bucketCount += 1; } componentWillUnmount() { clearInterval(this.timer); } calculateDestinations() { this.root = this.props.displayedScenario; const data = this.root; // || this.props.displayedScenario; this.root = d3.hierarchy(data, getScenarioNodeChildren); const apps = this.root.descendants().filter(isApp); return apps.map(a => a.data.id); getRoot() { return d3.hierarchy(this.props.displayedScenario, getScenarioNodeChildren); } render() { const destinations = this.calculateDestinations(); const root = this.getRoot(); const nodes = root.descendants(); const nbBuckets = this.props.nbBuckets || 25; const max = maxValue(this.props.pingBuckets); const min = minValue(this.props.pingBuckets); const apps = nodes.filter(isApp); const destinations = apps.map(a => a.data.id); const colorRange = colorArray(destinations.length); const dataPoints = pingBucketsToData(this.props.pingBuckets)(nbBuckets)(destinations); const colorRange = colorArray(destinations.length); return ( <Grid> <GridCell span={6}> <IDCGraph const showApps = this.props.showAppsView; const span = showApps ? 6 : 12; let graph = null; if (showApps) { graph = ( <IDCAppsView apps={apps} pingBuckets={this.props.pingBuckets} colorRange={colorRange} width={700} height={600} renderApps={true} colorRange={colorRange} onNodeClicked={() => {}} /> ); } else { graph = (<IDCGraph width={1000} height={600} />); } return ( <Grid> <GridCell span={span}> {graph} </GridCell> <GridCell span={6}> {showApps ? (<GridCell span={6}> <IDCAreaChart data={dataPoints} width={700} height={600} Loading @@ -195,7 +274,8 @@ class DashboardContainer extends Component { min={min} max={max} /> </GridCell> </GridCell>) : null} </Grid> ); Loading @@ -211,7 +291,8 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { changeSelectedDestination: (dest) => dispatch(execFakeChangeSelectedDestination(dest)) changeSelectedDestination: (dest) => dispatch(execFakeChangeSelectedDestination(dest)), addPingBucket: (b) => dispatch(execFakeAddPingBucket(b)) }; }; Loading
js-apps/meep-frontend/src/js/containers/exec/exec-page-container.js +1 −1 Original line number Diff line number Diff line Loading @@ -270,7 +270,7 @@ class ExecPageContainer extends Component { <GridCell span={spanLeft}> <Elevation className="component-style" z={2}> <div style={{padding: 10}}> {this.props.experimental ? (<DashboardContainer />) : (<IDCVis type={TYPE_EXEC} />)} {this.props.experimental ? (<DashboardContainer showAppsView={true}/>) : (<IDCVis type={TYPE_EXEC} />)} </div> </Elevation> Loading
js-apps/meep-frontend/src/js/containers/graph-utils.js 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019 * InterDigital Communications, Inc. * All rights reserved. * * The information provided herein is the proprietary and confidential * information of InterDigital Communications, Inc. */ import _ from 'lodash'; export const blue = '#5DBCD2'; export const lineGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} L${n2.X},${n2.Y}`; }; export const plusGenerator = () => { const s = 2; return `M25 -20 h${s} v${2*s} h${2*s} v${s} h-${2*s} v${2*s} h-${s} v-${2*s} h-${2*s} v-${s} h${2*s} z`; }; export const minusGenerator = () => { const s = 4; return `M25 -20 h${3*s} v${s} h-${3*s} z`; }; export const curveGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} C${n1.X},${n2.Y + 150} ${n1.X},${n2.Y + 50} ${n2.X},${n2.Y}`; }; export const visitNodes = f => node => { f(node); if (node.children) { _.each(node.children, (c) => { visitNodes(f)(c); }); } }; export const isNodeSelected = n => n.selected; export const isNodeHighlighted = n => n.highlighted; No newline at end of file
js-apps/meep-frontend/src/js/containers/idc-apps-view.js 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019 * InterDigital Communications, Inc. * All rights reserved. * * The information provided herein is the proprietary and confidential * information of InterDigital Communications, Inc. */ import _ from 'lodash'; import { connect } from 'react-redux'; import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import * as d3 from 'd3'; import IDCNode from './idc-node.js'; import { lineGeneratorNodes } from './graph-utils'; import { getScenarioNodeChildren } from '../util/scenario-utils'; const positionAppsCircle = ({apps, width, height}) => { const cx = width/2.0; const cy = height/2.0; const PI = 3.141592653598793846264; const r = 0.5*height*0.8; _.each(apps, (app, i) => { const theta = (i/apps.length)*(2*PI); app.X = cx + r*Math.cos(theta); app.Y = cy + r*Math.sin(theta); }); }; const IDCAppsView = ( { apps, colorRange, selectedSource, pingBuckets, width, height, onNodeClicked } ) => { const [positioningNeeded, setPositioningNeeded] = useState(true); const colorForApp = apps.reduce((res, val, i) => { // res[val.data.id] = colorRange[i]; return {...res, [val.data.id]: colorRange[i]}; }, {}); //if (positioningNeeded) { // copyAttributesRecursive(data)(this.root); positionAppsCircle({apps: apps, height: height, width: width}); //setPositioningNeeded(false); //} const pingBucket = _.last(pingBuckets); if (!pingBucket) { return null; } const appsMap = {}; _.each(apps, a => appsMap[a.data.id] = a); const pings = pingBucket.pings; let m = {}; _.each(pings, p => { if (!m[p.src]) { m[p.src] = {}; } if (!m[p.src][p.dest]) { m[p.src][p.dest] = { pings: [] }; } const o = m[p.src][p.dest]; o.pings.push(p); }); const edges = _.flatMap(apps .map((d) => { const rowObject = m[d.data.id]; if (!rowObject) { return []; } const destinations = Object.keys(m[d.data.id]); return _.map(destinations, (dest) => { return { src: d.data.id, dest: dest, count: rowObject[dest].pings.length, color: colorForApp[dest], avgLatency: d3.mean(rowObject[dest].pings, d => d.delay) }; }); } ) ).filter(e => { // return nbSelected ? appsMap[e.src].selected : true; // console.log(`${appsMap[e.src].data.id}:`, appsMap[e.src]); if (selectedSource) { return e.src === selectedSource; } else { return false; } }); const lineDefs = <defs> { _.map(edges, (e, i) => { return <path key={'path' + i} id={'textPathDef' + i} d={lineGeneratorNodes(appsMap[e.src])(appsMap[e.dest])} style={{fill: 'none', 'strokeWidth': e.count*0.1}} className='line' />; }) } </defs>; const lines = _.map(edges, (e, i) => { return <path key={'path' + i} id={'path' + i} d={lineGeneratorNodes(appsMap[e.src])(appsMap[e.dest])} style={{fill: 'none', 'strokeWidth': 0.5, 'stroke': e.color}} className='line' />; }); const textPaths = _.map(edges, (e,i) => <text key={'textPath' + i} style={{stroke: e.color}}> <textPath xlinkHref={`#textPathDef${i}`} startOffset={'45%'} > {`Avg lat: ${e.avgLatency.toFixed(2)} ms`} </textPath> </text> ); const nodes = apps .map((d, i) => <IDCNode collapsible={false} key={`node${i}`} d={d} stroke={colorRange[i]} updateParent={() => {}} onClick={onNodeClicked} /> ); return ( <svg height={height} width={width} > <> {lines} {lineDefs} {textPaths} {nodes} </> </svg> ); }; export default IDCAppsView; No newline at end of file
js-apps/meep-frontend/src/js/containers/idc-graph.js +21 −215 Original line number Diff line number Diff line Loading @@ -9,23 +9,23 @@ import _ from 'lodash'; import { connect } from 'react-redux'; import React, { Component } from 'react'; import { Grid, GridCell, GridInner } from '@rmwc/grid'; import { Graph } from 'react-d3-graph'; import ReactDOM from 'react-dom'; import { Button } from '@rmwc/button'; import * as d3 from 'd3'; import moment from 'moment'; import { Client } from '@elastic/elasticsearch'; import { plusGenerator, minusGenerator, lineGeneratorNodes, visitNodes, blue } from './graph-utils'; import { getScenarioSpecificImage, isApp, getScenarioNodeChildren } from '../util/scenario-utils'; import { updateObject } from '../util/object-util'; import { execChangeTable, execChangeVis, Loading @@ -37,197 +37,9 @@ import { cfgChangeTable, cfgChangeVis, cfgElemEdit } from '../state/cfg'; import { FIELD_NAME, getElemFieldVal } from '../util/elem-utils'; // Set fixed to true for provided group function setFixedGroup(group) { group['fixed'] = { x: true, y: true }; } const translate = d => { return `translate(${d.X}, ${d.Y})`; }; const curveGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} C${n1.X},${n2.Y + 150} ${n1.X},${n2.Y + 50} ${n2.X},${n2.Y}`; }; const lineGenerator = d => { return `M${d.X},${d.Y} L${d.parent.X},${d.parent.Y}`; }; const lineGeneratorReverse = d => { return `M${d.parent.X},${d.parent.Y} L${d.X},${d.Y}`; }; const lineGeneratorNodes = n1 => n2 => { return `M${n1.X},${n1.Y} L${n2.X},${n2.Y}`; }; const plusGenerator = () => { const s = 2; return `M25 -20 h${s} v${2*s} h${2*s} v${s} h-${2*s} v${2*s} h-${s} v-${2*s} h-${2*s} v-${s} h${2*s} z`; }; const minusGenerator = () => { const s = 4; return `M25 -20 h${3*s} v${s} h-${3*s} z`; }; const hideNode = node => { node.hidden = true; }; const showNode = node => { node.hidden = false; }; const hideChildren = node => { _.each(node.children, c => { visitNodes(hideNode)(c); }); }; const showChildren = node => { _.each(node.children, c => { visitNodes(showNode)(c); }); }; const blue = '#5DBCD2'; const Plus = props => { const d = props.d; const plusMinus = props.collapsible ? (d.collapsed ? plusGenerator : minusGenerator) : () => ''; return ( <path width={20} height={20} d={plusMinus()} style={{fill: blue, 'strokeWidth': 2}} stroke={blue} className='plus' onClick={() => { d.collapsed = !d.collapsed; if (d.collapsed) { hideChildren(d); } else { showChildren(d); } props.updateParent(); }} /> ); }; class IDCNode extends Component { constructor(props) { super(props); getElemFieldVal, this.state = { mouseDown: false, dragging: false, d: this.props.d }; } render() { const d = this.props.d; const fill = this.highlighted ? 'red' : '#69b3a2'; const radius = this.highlighted ? 14 : 12; const size=30; return (<g transform={translate(d)} > <Plus width={10} height={10} d={d} updateParent={this.props.updateParent}/> <image xlinkHref={`../img/${d.data.iconName}`} height={size} width={size} x={-size/2} y={-size/2} /*filter={d.selected ? 'url(#filter)' : '' }*/ r={radius} style={{fill: fill}} stroke={'black'} strokeWidth={3} onMouseDown={ (e) => { this.dragging = true; this.highlighted = true; this.mouseCoords={ x: e.clientX - e.target.farthestViewportElement.parentNode.offsetLeft, y: e.clientY - e.target.farthestViewportElement.parentNode.offsetTop }; this.props.updateParent(); }} onMouseUp={ () => { this.dragging = false; this.highlighted = false; }} onMouseMove={ (e) => { if (!this.dragging) { return; } e.preventDefault(); const newX = e.clientX - e.target.farthestViewportElement.parentNode.offsetLeft; const newY = e.clientY - e.target.farthestViewportElement.parentNode.offsetTop; const dx = newX - this.mouseCoords.x; const dy = newY - this.mouseCoords.y; this.mouseCoords.x = newX; this.mouseCoords.y = newY; const targetXY = e.currentTarget.parentNode.getAttribute('transform').substr(10).slice(0, -1).split(', '); const targetX = Number(targetXY[0]); const targetY = Number(targetXY[1]); // console.log(`(${d.x}, ${d.y}) -> (${X}, ${Y})`); d.X = targetX + dx; d.Y = targetY + dy; this.props.updateParent(); }} onClick={() => { d.selected = !d.selected; console.log('',d); this.props.updateParent(); }} onMouseOver={() => { this.highlighted = true; d.highlighted = true; d.data.dR = 4; this.props.updateParent(); }} onMouseOut={() => { d.data.dR = 0; this.dragging = false; this.highlighted = false; d.highlighted = false; this.props.updateParent(); }} /> <text x={-size/2} y="35" className="tiny" stroke={this.props.stroke} fontWeight={this.highlighted ? 'bold' : 'normal'}>{d.data.id}</text> </g>); } } const visitNodes = f => node => { f(node); if (node.children) { _.each(node.children, (c) => { visitNodes(f)(c); }); } }; } from '../util/elem-utils'; const copyAttributesRecursive = nodeSrc => nodeDest => { if (nodeSrc.X && nodeSrc.Y) { Loading Loading @@ -284,8 +96,13 @@ const createEdgesToChildren = array => node => { const nodeVisible = n => !n.hidden; const isNodeSelected = n => n.selected; const isNodeHighlighted = n => n.highlighted; const IDCHierarchy = (props) => { }; class IDCGraph extends Component { Loading Loading @@ -345,17 +162,10 @@ class IDCGraph extends Component { }; this.props.addPingBucket(dataBucket); this.refreshCharts(); this.bucketCount += 1; } refreshCharts() { this.setState({root: this.root}); } componentDidMount() { // this.ESClient = new Client({ node: 'http://localhost:9200' }); this.dataTimer = setInterval(() => this.nextDataBucket(this.bucketCount), 1000); } Loading @@ -369,7 +179,6 @@ class IDCGraph extends Component { } update() { // this.props.execChangeDisplayedScenario(this.root); this.setState({ root: this.root, apps: this.apps Loading @@ -385,10 +194,10 @@ class IDCGraph extends Component { } positionNodesTree () { const data = this.root; // || this.props.displayedScenario; const data = this.root; this.root = d3.hierarchy(data, getScenarioNodeChildren); this.edges = []; this.createHierarchyEdges(); // this.createHierarchyEdges(); this.nodes = this.root.descendants(); copyAttributesRecursive(data)(this.root); Loading @@ -398,7 +207,7 @@ class IDCGraph extends Component { } positionNodesCircle() { const data = this.root; // || this.props.displayedScenario; const data = this.root; this.root = d3.hierarchy(data, getScenarioNodeChildren); const compareTypes = (a, b) => { Loading Loading @@ -445,7 +254,7 @@ class IDCGraph extends Component { <path key={'path' + i} id={'textPath' + i} d={lineGenerator(d)} d={lineGeneratorNodes(d)(d.parent)} style={{fill: 'none', 'strokeWidth': 2}} stroke={'#aaa'} className='line' Loading Loading @@ -503,8 +312,6 @@ class IDCGraph extends Component { } renderApps() { console.log('selectedDestination: ', this.props.selectedDestination); const colorRange = this.props.colorRange; const colorForApp = _.reduce(this.apps, (res, val, i) => { res[val.data.id] = colorRange[i]; Loading Loading @@ -557,7 +364,7 @@ class IDCGraph extends Component { } const edges = _.flatMap(this.apps .map((d, appIndex) => { .map((d) => { const rowObject = m[d.data.id]; if (!rowObject) { return []; Loading Loading @@ -654,7 +461,6 @@ class IDCGraph extends Component { render() { // return this.renderTree(); return ( <> {this.props.renderApps ? this.renderApps() : this.renderTree()} </> Loading