Commit 2baed6a8 authored by Francis Renaud's avatar Francis Renaud
Browse files

WIP

parent d1fce075
Loading
Loading
Loading
Loading
+102 −21
Original line number Diff line number Diff line
@@ -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,
@@ -16,7 +17,8 @@ import {
} from '../util/scenario-utils';

import {
  execFakeChangeSelectedDestination
  execFakeChangeSelectedDestination,
  execFakeAddPingBucket
} from '../state/exec';

const newDataPoint = (date) => {
@@ -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}
@@ -195,7 +274,8 @@ class DashboardContainer extends Component {
            min={min}
            max={max}
          />
        </GridCell>
        </GridCell>) 
          : null}
      </Grid>
      
    );
@@ -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))
  };
};

+1 −1
Original line number Diff line number Diff line
@@ -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>
+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
+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
+21 −215
Original line number Diff line number Diff line
@@ -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,
@@ -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) {
@@ -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 {

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

@@ -369,7 +179,6 @@ class IDCGraph extends Component {
  }

  update() {
    // this.props.execChangeDisplayedScenario(this.root);
    this.setState({
      root: this.root,
      apps: this.apps
@@ -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);
@@ -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)  => {
@@ -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'
@@ -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];
@@ -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 [];
@@ -654,7 +461,6 @@ class IDCGraph extends Component {
  render() {
    // return this.renderTree();
    return (
     
      <>
      {this.props.renderApps ? this.renderApps() : this.renderTree()}
      </>    
Loading