Commit 06639339 authored by Francis Renaud's avatar Francis Renaud
Browse files

WIP: rebuilding line chart wirh React having control of the display.

parent 8cf9e030
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -14606,6 +14606,11 @@
        }
      }
    },
    "react-d3-axis": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/react-d3-axis/-/react-d3-axis-0.1.2.tgz",
      "integrity": "sha512-Id2C208SGcLcIfgrdl9ffgZKXtp3wELV7dC3HJVjim+J0IQiAaZo9APSnlvJE2kB90C7wJKlXdFI1bYPg7jytA=="
    },
    "react-d3-graph": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/react-d3-graph/-/react-d3-graph-2.1.0.tgz",
+1 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@
    "material-design-icons": "3.0.1",
    "prop-types": "15.6.2",
    "react": "^16.8.6",
    "react-d3-axis": "^0.1.2",
    "react-d3-graph": "^2.0.2",
    "react-dom": "^16.8.6",
    "react-iframe": "^1.5.0",
+128 −76
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import ReactDOM from 'react-dom';
import { Button } from '@rmwc/button';
import { Checkbox } from '@rmwc/checkbox';
import { TextField, TextFieldHelperText } from '@rmwc/textfield';
import moment from 'moment';
import * as d3 from 'd3';
import axios from 'axios';

@@ -24,9 +25,8 @@ import {
} from '../util/scenario-utils';

import {
  dataAccessorForType,
  dataSetterForType,
  isDataPointOfType
  isDataPointOfType,
  valueOfPoint
} from '../util/metrics';

import {
@@ -68,41 +68,50 @@ function colorArray(dataLength) {

const metricsBasePath = 'http://10.3.16.73:30008/v1';

const dataPointFromEpochDataPoints = destinations => sourceNodeId => dataAccessor => epochDataPoints => {
  if (!epochDataPoints.length) {
    return null;
  }
  let dp = {
    date: epochDataPoints[0].timestamp
  };
// const dataPointFromEpochDataPoints = destinations => sourceNodeId => dataAccessor => epochDataPoints => {
//   if (!epochDataPoints.length) {
//     return null;
//   }
//   let dp = {
//     date: epochDataPoints[0].timestamp
//   };

  const avgForDest = dataPoints => acc => dest => {
    const hasSource = src => p => p.src === src;
    const hasDestination = dest => p => p.dest === dest;
//   const avgForDest = dataPoints => acc => dest => {
//     const hasSource = src => p => p.src === src;
//     const hasDestination = dest => p => p.dest === dest;
    
    const dataPointsForDestSource = dataPoints
      .filter(hasSource(sourceNodeId))
      .filter(hasDestination(dest));
    const avg = d3.mean(dataPointsForDestSource, acc);
    return avg;
  };
//     const dataPointsForDestSource = dataPoints
//       .filter(hasSource(sourceNodeId))
//       .filter(hasDestination(dest));
//     const avg = d3.mean(dataPointsForDestSource, acc);
//     return avg;
//   };
  
  destinations.forEach(dest => {
    dp[dest] = avgForDest(epochDataPoints)(dataAccessor)(dest) || 0;
  });
//   destinations.forEach(dest => {
//     dp[dest] = avgForDest(epochDataPoints)(dataAccessor)(dest) || 0;
//   });

  return dp;
};
//   return dp;
// };

const notNull = x => x;
const epochsToDataPoints = epochs => nb => destinations => dataAccessor => sourceNodeId => {
  const selectedEpochs = epochs.length ? epochs.slice(-nb) : [];

  if (selectedEpochs.length === 0) {
    console.log('epoch length is 0');
const buildSeriesFromEpoch = (series, epoch) => {
  epoch.data.forEach(p => {
    if (! series[p.dest]) {
      series[p.dest] = [];
    }
  const dataPoints = selectedEpochs.map(dataPointFromEpochDataPoints(destinations)(sourceNodeId)(dataAccessor)).filter(notNull);
  return dataPoints;
    series[p.dest].push(p);
  });

  return series;
};

const epochsToSeries = (epochs) => {
  let series = epochs.reduce((s, current) => {
    return buildSeriesFromEpoch(s, current);
  }, {});
  return series;
};

const ConfigurationView = (props) => {
@@ -181,6 +190,8 @@ const ViewForName = (
    min,
    max,
    data,
    series,
    startTime,
    mobilityEvents,
    dataPoints,
    dataAccessor,
@@ -210,6 +221,8 @@ const ViewForName = (
        width={width}
        height={600}
        data={data}
        series={series}
        startTime={startTime}
        dataAccessor={dataAccessor}
        dataType={dataType}
        selectedSource={selectedSource}
@@ -225,6 +238,8 @@ const ViewForName = (
    return (
      <IDCLineChart
        data={dataPoints}
        series={series}
        startTime={startTime}
        mobilityEvents={mobilityEvents}
        width={width} height={600}
        destinations={appIds}
@@ -242,6 +257,8 @@ const ViewForName = (
    return (
      <IDCLineChart
        data={dataPoints}
        series={series}
        startTime={startTime}
        mobilityEvents={mobilityEvents}
        width={width} height={600}
        destinations={appIds}
@@ -314,6 +331,30 @@ const DashboardConfiguration = (props) => {
  );
};

const filterSeries = keys => filter => series => {
  let newSeries = {};
  keys.forEach(key => {
    if (series[key]) {
      newSeries[key] = removeDuplicatePoints(series[key].filter(filter));
    }
  });

  return newSeries;
};

const removeDuplicatePoints = sequence => {
  let timestampsMap = {};
  let newSequence = [];
  sequence.forEach(p => {
    if (!timestampsMap[p.timestamp]) {
      timestampsMap[p.timestamp] = true;
      newSequence.push(p);
    }
  });

  return newSequence;
};

class DashboardContainer extends Component {
  constructor(props) {
    super(props);
@@ -342,9 +383,18 @@ class DashboardContainer extends Component {
  }

  fetchMetrics() {
    return axios.get(`${metricsBasePath}/metrics?startTime=now-6s&stopTime=now`)
    const startTime = moment().add(-7, 'seconds').format(moment.HTML5_FMT.DATETIME_LOCAL_MS);
    const stopTime = moment().add(-6, 'seconds').format(moment.HTML5_FMT.DATETIME_LOCAL_MS);
    // const now = moment().format(moment.HTML5_FMT.DATETIME_LOCAL_MS);
    return axios.get(`${metricsBasePath}/metrics?startTime=${startTime}&stopTime=${stopTime}`)
      .then(res => {
        this.props.addMetricsEpoch(res.data.dataResponse || []);

        let epoch = {
          data: res.data.logResponse || [], //.sort((a, b) => new Date(a).getTime() - new Date(b).getTime() || []),
          startTime: startTime
        };
  
        this.props.addMetricsEpoch(epoch);
      }).catch((e) => {
        console.log('Error while fetching metrics', e);
      });
@@ -389,16 +439,6 @@ class DashboardContainer extends Component {
      return {...res, [val.data.id]: colorRange[i]};
    }, {});

    let lastEpoch = this.props.epochs.length ? this.props.epochs.slice(-1)[0] : [];
    const hasValue = p => {
      const accessor = dataAccessorForType(p.dataType);
      if (! accessor(p)) {
        console.log(`No value for src ${p.src} and dest ${p.dest}`);
      }
      return accessor(p);
    };
    lastEpoch = lastEpoch.filter(hasValue);

    const isDataOfType = type => dataPoint => dataPoint.dataType === type;
    
    const dataTypeForView = view => {
@@ -412,40 +452,48 @@ class DashboardContainer extends Component {
      }
    };

    // Determine last 25 epochs
    // Determine first and last epochs
    const firstEpoch = this.props.epochs.length ? this.props.epochs[0] : {
      data: [],
      startTime: null
    };
    let lastEpoch = this.props.epochs.length ? this.props.epochs.slice(-1)[0] : {
      data: [],
      startTime: null
    };
 
    // Determine startTime of first epoch and endTime of last epoch
    // Create map of arrays of points, one array per source, indexed by source id
    // Pass that map to the views
    // Have each view consume that map
    const startTime = firstEpoch.data.length ? firstEpoch.startTime : null;
    const endTime = lastEpoch.data.length ? new Date(new Date(lastEpoch.startTime).getTime() + 1000).toString() : null;
    const series = epochsToSeries(this.props.epochs, selectedSource);

    const withTypeAndSource = type => source => point => {
      return point.dataType === type && point.src === source;
    };

    // For view 1
    const view1DataType = dataTypeForView(this.state.view1Name);
    const view1Accessor = dataAccessorForType(view1DataType);
    const view1DataPoints = epochsToDataPoints(this.props.epochs)(nbEpochs)(appIds)(view1Accessor)(selectedSource);
    const data1 = lastEpoch.filter(isDataOfType(view1DataType));
    const max1 = d3.max(data1, view1Accessor);
    const min1 = d3.min(data1, view1Accessor);
    const series1 =  filterSeries(appIds)(withTypeAndSource(view1DataType)(selectedSource))(series);
    const lastEpochData1 = lastEpoch.data.filter(isDataOfType(view1DataType));
    // const max1 = d3.max(data1, p => p.value);
    // const min1 = d3.min(data1, p => p.value);

    // For view2
    const view2DataType = dataTypeForView(this.state.view2Name);
    const view2Accessor = dataAccessorForType(view2DataType);
    const view2DataPoints = epochsToDataPoints(this.props.epochs)(nbEpochs)(appIds)(view2Accessor)(selectedSource);
    const data2 = lastEpoch.filter(isDataOfType(view2DataType));
    const series2 =  filterSeries(appIds)(withTypeAndSource(view2DataType)(selectedSource))(series);
    const lastEpochData2 = lastEpoch.data.filter(isDataOfType(view2DataType));

    const extractPointsOfType = type => epoch => epoch.filter(isDataPointOfType(type));
    // Mobility events
    const extractPointsOfType = type => epoch => epoch.data.filter(isDataPointOfType(type));
    const extractMobilityEvents = extractPointsOfType(MOBILITY_EVENT);
    const mobilityEvents = this.props.epochs.flatMap(extractMobilityEvents);

    if (mobilityEvents.length) {
      console.log('Some mobility events ...');
    }
    data2.forEach((d) => {
      const dd = view1Accessor(d);
      if (!dd) {
        console.log(`Null data: ${dd}. `);
      }
    });
    
    const max2 = d3.max(data2, view2Accessor);
    const min2 = d3.min(data2, view2Accessor);
    // const max2 = d3.max(data2, view2Accessor);
    // const min2 = d3.min(data2, view2Accessor);
    
    const width = 700;
    const height = 600;
@@ -475,12 +523,14 @@ class DashboardContainer extends Component {
        colorRange={colorRange}
        width={width1}
        height={height}
        data={data1}
        data={lastEpochData1}
        series={series1}
        startTime={startTime}
        endTime={endTime}
        mobilityEvents={mobilityEvents}
        min={min1}
        max={max1}
        dataPoints={view1DataPoints}
        dataAccessor={view1Accessor}
        // min={min1}
        // max={max1}
        // dataAccessor={view1Accessor}
        dataType={view1DataType}
        selectedSource={selectedSource}
        colorForApp={colorForApp}
@@ -496,12 +546,14 @@ class DashboardContainer extends Component {
        colorRange={colorRange}
        width={width2}
        height={height}
        data={data2}
        data={lastEpochData2}
        series={series2}
        startTime={startTime}
        endTime={endTime}
        mobilityEvents={mobilityEvents}
        min={min2}
        max={max2}
        dataPoints={view2DataPoints}
        dataAccessor={view2Accessor}
        // min={min2}
        // max={max2}
        // dataAccessor={view2Accessor}
        dataType={view2DataType}
        selectedSource={selectedSource}
        colorForApp={colorForApp}
+72 −9
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ import {
  lineGeneratorNodes
} from './graph-utils';

const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => {
const edgesFromData = (data, colorForApp, selectedSource) => {
  const pings = data;
  let m = {};
  _.each(pings, p => {
@@ -38,7 +38,7 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => {

  const apps = Object.keys(m);
 
  const edgesFromSource = dataAccessor => src => {
  const edgesFromSource = src => {
    const rowObject = m[src];
    if (!rowObject) {
      return [];
@@ -48,13 +48,13 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => {
    const edgesFromDestinations = (dest) => {
      // To debug
      const dataFromPing = p => {
        if (dataAccessor(p)) {
        if (p.value) {
          console.log('Bad value!');  
        }
        return dataAccessor(p);
        return p.value;
      };

      if (!d3.mean(rowObject[dest].pings, dataAccessor)) {
      if (!d3.mean(rowObject[dest].pings, p => p.value)) {
        console.log('Bad value!');
      }
      return  {
@@ -62,7 +62,7 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => {
        dest: dest,
        count: rowObject[dest].pings.length,
        color: colorForApp[dest],
        avgData: d3.mean(rowObject[dest].pings, dataAccessor)
        avgData: d3.mean(rowObject[dest].pings, p => p.value)
      };
    };
    return _.map(destinations, edgesFromDestinations);
@@ -75,11 +75,74 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => {
      return true;
    }
  };
  const edges = _.flatMap(apps.map(edgesFromSource(dataAccessor))).filter(outwardEdgesIfSourceSelected);
  const edges = _.flatMap(apps.map(edgesFromSource)).filter(outwardEdgesIfSourceSelected);

  return edges; 
};

const edgesFromSeries = (series, colorForApp, selectedSource) => {
  const pings = data;
  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 apps = Object.keys(m);
 
  const edgesFromSource = src => {
    const rowObject = m[src];
    if (!rowObject) {
      return [];
    }
    const destinations = Object.keys(m[src]);

    const edgesFromDestinations = (dest) => {
      // To debug
      const dataFromPing = p => {
        if (p.value) {
          console.log('Bad value!');  
        }
        return p.value;
      };

      if (!d3.mean(rowObject[dest].pings, p => p.value)) {
        console.log('Bad value!');
      }
      return  {
        src: src,
        dest: dest,
        count: rowObject[dest].pings.length,
        color: colorForApp[dest],
        avgData: d3.mean(rowObject[dest].pings, p => p.value)
      };
    };
    return _.map(destinations, edgesFromDestinations);
  };

  const outwardEdgesIfSourceSelected = e => {
    if (selectedSource) {
      return e.src === selectedSource;
    } else {
      return true;
    }
  };
  const edges = _.flatMap(apps.map(edgesFromSource)).filter(outwardEdgesIfSourceSelected);

  return edges; 
};


const positionAppsCircle = ({apps, width, height}) => {
  const cx = width/2.0;
  const cy = height/2.0;
@@ -121,7 +184,7 @@ const IDCAppsView = (
    colorRange,
    selectedSource,
    data,
    dataAccessor,
    series,
    dataType,
    width,
    height,
@@ -142,7 +205,7 @@ const IDCAppsView = (
  const appsMap = {};
  _.each(apps, a => appsMap[a.data.id] = a);

  const edges = edgesFromData(data.filter(dataAccessor), dataAccessor, colorForApp, selectedSource);
  const edges = edgesFromData(data.filter(p => p.value),  colorForApp, selectedSource);

  const edgeLabel = edgeLabelForDataType(dataType);
  const edgeUnits = unitsForDataType(dataType);
+275 −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, { useRef, useEffect }  from 'react';
import ReactDOM from 'react-dom';
import moment from 'moment';
import * as d3 from 'd3';
import uuid from 'uuid';
import { uiChangeCurrentDialog } from '../state/ui';
import { execFakeChangeSelectedDestination } from '../state/exec';
import { LATENCY_METRICS, THROUGHPUT_METRICS } from '../meep-constants';

const notNull = x => x;
const IDCLineChartBack = props => {
  const d3Container = useRef(null);

  /* The useEffect Hook is for running side effects outside of React,
       for instance inserting elements into the DOM using D3 */
  useEffect(
    () => {
      
      const margin = {top: 20, right: 40, bottom: 30, left: 60};
      const width = props.width - margin.left - margin.right;
      const height = props.height - margin.top - margin.bottom;

      const min = props.min;
      const max = props.max;

      const maxOfYScale = Math.ceil(max/100.0) * 100.0;

      let mainGroup = d3.select(d3Container.current);
      if (mainGroup.select('g').size() === 0) {
        mainGroup = mainGroup.append('g')
          .attr('width', props.width + margin.left + margin.right)
          .attr('height', props.height + margin.top + margin.bottom)
          .attr('transform', `translate(${margin.left}, ${margin.top})`);
      }

      const flattenSeries = series => {
        return _.flatMap(Object.values(series));
      };
      
      const chart = (series) => {
        const destinations = props.selectedSource ? props.destinations.slice(-props.destinations.length) : [];
        const colorRange = destinations.map(s => props.colorForApp[s]);

        const yRange = [0, 200];
        const timeRange = d3.extent(flattenSeries(series), d => new Date(d.timestamp));
        const x = d3.scaleTime().domain(timeRange).range([0, width]);
        const y = d3.scaleLinear().domain(yRange).range([height - 50, 0]);
        const z = d3.scaleOrdinal().range(colorRange);
      
        // Axes
        const xAxis = d3.axisBottom(x); //.ticks(d3.timeSeconds);
        const yAxis = d3.axisLeft(y).scale(y)
          .tickSize(0.01);
        // const yAxisr = d3.axisLeft(y);

        // const dataLinePointFromDataPoint = key => point => {
        //   if (point[key] === undefined) {
        //     console.log('point[key] is undefined, for key ' + key + ' and point ', point);
        //     return null;
        //   }
        //   return {
        //     date: point.date,
        //     value: point[key]
        //   };
        // };
        const dataLineFromSeries = series => key => {
          if (!series[key].filter) {
            console.log('Check');
          }
          const line = series[key].filter(notNull).filter(p => p.value);
          // .sort((a, b) => {
          //   return x(new Date(a.timestamp)) - x(new Date(b.timestamp));
          // });

          //TODO: add point at props.startTime and props.endTime
          
          line.key = key;
          return line;
        };

        let dataLines = destinations.map(dataLineFromSeries(props.series));

        // TODO: remove
        dataLines = dataLines.length ? [dataLines[0]] : [];

        if (dataLines.length < 5) {
          console.log('Too few dataLines: ', dataLines.length);
        }

        const valueLine = d3.line()
          .x(function(d) {
            return margin.left + x(new Date(d.timestamp));
          })
          .y(function(d) {
            return y(d.value) + margin.top;
          })
          .curve(d3.curveMonotoneX);

        mainGroup.selectAll('.line')
          .data(dataLines)
          .join('path').attr('class', 'line')
          .attr('d', valueLine)
          .style('stroke', (d, i) => z(i))
          .style('fill', 'none')
          .style('stroke-width', 3);

        // if (mainGroup.selectAll('.line').size() > 0) {
        //   const linesMarginLeft = mainGroup.selectAll('.line').attr('margin-left');
        //   console.log('linesMarginLeft: ', linesMarginLeft);
        // }

        // Mobility events
        // const mobilityEventLine = d => `M${x(new Date(d.timestamp)) + margin.left},${y(yRange[0]) + margin.top} L${x(new Date(d.timestamp)) + margin.left},${y(yRange[1]) + margin.top}`;
        const mobilityEventLine = d => `M${x(new Date(d.timestamp)) + margin.left},${y(yRange[1]) + margin.top} L${x(new Date(d.timestamp)) + margin.left},${y(yRange[0]) + margin.top}`;
        mainGroup.selectAll('.mobilityEventLine')
          .data(props.mobilityEvents)
          .join('path')
          .attr('class', 'mobilityEventLine')
          .attr('d', mobilityEventLine)
          .attr('id', d => d.timestamp)
          .style('stroke', 'gray')
          .style('stroke-width', 1)
          .style('fill', 'none');
          
        mainGroup.selectAll('.mobilityEventLineText')
          .data(props.mobilityEvents)
          .join('text')
          .attr('class', 'mobilityEventLineText')
          .style('stroke','gray')
          .style('stroke-width', 1)
          .style('fill','gray');
          // .attr('x', d => x(new Date(d.timestamp)) + margin.left)
          // .attr('dy',50 + margin.top)
            
      
        mainGroup.selectAll('.mobilityEventLineTextPath').remove();
        mainGroup.selectAll('.mobilityEventLineText')
          .data(props.mobilityEvents)
          .append('textPath')
          .attr('class', 'mobilityEventLineTextPath')
          .attr('xlink:href', d => `#${d.timestamp}`)
          .attr('stroke','gray')
          .attr('fill','gray')
          .text(d => `Mobility Event:  ${d.src} to ${d.dest}`)
          .attr('transform', 'rotate(-180)');
          

        
        const xAxisGroup = mainGroup.selectAll('.xaxis');
        if (xAxisGroup.size() === 0) {
          mainGroup.append('g')
            .attr('class', 'xaxis')
            .attr('transform', 'translate(0,' + height + ')').call(xAxis);
        } else {
          xAxisGroup.attr('transform', 'translate(0,' + height + ')').call(xAxis);
        }

        mainGroup.selectAll('.xaxis').call(xAxis);
         
        const yAxisGroup = mainGroup.selectAll('.yaxis');
        if (yAxisGroup.size() === 0) {
          mainGroup.append('g')
            .attr('class', 'yaxis')
            .attr('transform', 'translate(' + width + ', 0)')
            .style('z-index', '18')
            .call(yAxis);
        } else {
          yAxisGroup.attr('transform', 'translate(' + width + ', 0)');
        }

        // text label for the y axis
        const labelForType = type => {
          switch (type) {
          case LATENCY_METRICS:
            return 'Latency (ms)';
          case THROUGHPUT_METRICS:
            return 'Throughput (kbs)';
          default:
            return '';
          }
        };

        const yAxisLabel = labelForType(props.dataType);
        if (!mainGroup.selectAll('.yLabel').size()) {
          mainGroup.append('text')
            .attr('class', 'yLabel')
            .attr('transform', 'rotate(-90)')
            .attr('y', 0 - margin.left + 10)
            .attr('x', 0 - (height / 2))
            .attr('dy', '1em')
            .style('text-anchor', 'middle')
            .text(yAxisLabel);
        } else {
          mainGroup.selectAll('.yLabel')
            .text(yAxisLabel);
        }

        // Chart title
        const chartTitleForType = type => {
          switch (type) {
          case LATENCY_METRICS:
            return 'Latency Chart';
          case THROUGHPUT_METRICS:
            return 'Throughput Chart';
          default:
            return '';
          }
        };

        const chartTitle = chartTitleForType(props.dataType);
        if (!mainGroup.selectAll('.chartTitle').size()) {
          mainGroup.append('text')
            .attr('class', 'chartTitle')
            .attr('y', 0 + margin.top + 10)
            .attr('x', width / 2)
            // .attr('dy', '1em')
            .style('text-anchor', 'middle')
            .text(chartTitle);
        } else {
          mainGroup.selectAll('.chartTitle')
            .text(chartTitle);
        }
       
        const yAxisGroup0 = mainGroup.selectAll('.yaxis0');
        if (yAxisGroup0.size() === 0) {
          mainGroup.append('g')
            .attr('class', 'yaxis0')
            .attr('transform', 'translate(0, 0)')
            .style('z-index', '18')
            .call(yAxis);
        } else {
          yAxisGroup0.attr('transform', 'translate(0, 0)').style('z-index', '18')
        }
      };
        
      chart(props.series);
    },

    /*
            useEffect has a dependency array (below). It's a list of dependency
            variables for this useEffect block. The block will run after mount
            and whenever any of these variables change. We still have to check
            if the variables are valid, but we do not have to compare old props
            to next props to decide whether to rerender.
        */
    [props.data, d3Container.current]);

  return (
    <div className='chart'>
      <svg
        //viewBox='0 -20 200 33'
        ref={d3Container}
        className='d3-component'
        height={props.height}
        width={props.width}
      >
      
      </svg>

    </div>
  );  
};

export default IDCLineChartBack;
 No newline at end of file
Loading