forked from p34709852/monkey
Merge branch '400-zero-trust-mvp-venn-diagram' of https://github.com/guardicore/monkey into 400-zero-trust-mvp-venn-diagram
This commit is contained in:
commit
05eab34d45
201
monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
Normal file → Executable file
201
monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
Normal file → Executable file
|
@ -1,17 +1,64 @@
|
||||||
import React, {Fragment} from 'react';
|
import React from 'react';
|
||||||
import {Col, Grid, Row} from 'react-bootstrap';
|
import {Button, Col} from 'react-bootstrap';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader";
|
import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader";
|
||||||
import PillarsOverview from "../report-components/zerotrust/PillarOverview";
|
import PillarGrades from "../report-components/zerotrust/PillarGrades";
|
||||||
|
import PillarLabel from "../report-components/zerotrust/PillarLabel";
|
||||||
|
import ResponsiveVennDiagram from "../report-components/zerotrust/venn-components/ResponsiveVennDiagram";
|
||||||
import FindingsTable from "../report-components/zerotrust/FindingsTable";
|
import FindingsTable from "../report-components/zerotrust/FindingsTable";
|
||||||
import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus";
|
import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus";
|
||||||
import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning";
|
|
||||||
import ReportLoader from "../report-components/common/ReportLoader";
|
let mockup = [
|
||||||
import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning";
|
{
|
||||||
import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance";
|
"Conclusive": 4,
|
||||||
import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary";
|
"Inconclusive": 0,
|
||||||
import PrintReportButton from "../report-components/common/PrintReportButton";
|
"Positive": 1,
|
||||||
import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus";
|
"Unexecuted": 2,
|
||||||
|
"pillar": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conclusive": 0,
|
||||||
|
"Inconclusive": 5,
|
||||||
|
"Positive": 0,
|
||||||
|
"Unexecuted": 2,
|
||||||
|
"pillar": "People"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conclusive": 0,
|
||||||
|
"Inconclusive": 0,
|
||||||
|
"Positive": 6,
|
||||||
|
"Unexecuted": 3,
|
||||||
|
"pillar": "Networks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conclusive": 2,
|
||||||
|
"Inconclusive": 0,
|
||||||
|
"Positive": 1,
|
||||||
|
"Unexecuted": 1,
|
||||||
|
"pillar": "Devices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conclusive": 0,
|
||||||
|
"Inconclusive": 0,
|
||||||
|
"Positive": 0,
|
||||||
|
"Unexecuted": 0,
|
||||||
|
"pillar": "Workloads"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conclusive": 0,
|
||||||
|
"Inconclusive": 2,
|
||||||
|
"Positive": 0,
|
||||||
|
"Unexecuted": 0,
|
||||||
|
"pillar": "Visibility & Analytics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Conclusive": 0,
|
||||||
|
"Inconclusive": 0,
|
||||||
|
"Positive": 0,
|
||||||
|
"Unexecuted": 0,
|
||||||
|
"pillar": "Automation & Orchestration"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
class ZeroTrustReportPageComponent extends AuthComponent {
|
class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
|
|
||||||
|
@ -20,30 +67,16 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
allMonkeysAreDead: false,
|
allMonkeysAreDead: false,
|
||||||
runStarted: true
|
runStarted: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMonkeysRunning = () => {
|
|
||||||
return this.authFetch('/api')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
this.setState(extractExecutionStatusFromServerResponse(res));
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let content;
|
let res;
|
||||||
if (this.state.runStarted) {
|
// Todo move to componentDidMount
|
||||||
content = this.generateReportContent();
|
this.getZeroTrustReportFromServer(res);
|
||||||
} else {
|
|
||||||
content = <MustRunMonkeyWarning/>;
|
const content = this.generateReportContent();
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col xs={12} lg={10}>
|
<Col xs={12} lg={10}>
|
||||||
|
@ -59,75 +92,63 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
if (this.stillLoadingDataFromServer()) {
|
if (this.stillLoadingDataFromServer()) {
|
||||||
content = <ReportLoader loading={true}/>;
|
content = "Still empty";
|
||||||
} else {
|
} else {
|
||||||
content = <div id="MainContentSection">
|
const pillarsSection = <div>
|
||||||
{this.generateOverviewSection()}
|
<h2>Pillars Overview</h2>
|
||||||
{this.generateDirectivesSection()}
|
<PillarGrades pillars={this.state.pillars}/>
|
||||||
{this.generateFindingsSection()}
|
</div>;
|
||||||
|
|
||||||
|
const recommendationsSection = <div><h2>Recommendations Status</h2>
|
||||||
|
{
|
||||||
|
this.state.recommendations.map((recommendation) =>
|
||||||
|
<SinglePillarRecommendationsStatus
|
||||||
|
key={recommendation.pillar}
|
||||||
|
pillar={recommendation.pillar}
|
||||||
|
recommendationStatus={recommendation.recommendationStatus}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
const findingSection = <div><h2>Findings</h2>
|
||||||
|
<FindingsTable findings={this.state.findings}/></div>;
|
||||||
|
|
||||||
|
content = <div>
|
||||||
|
{pillarsSection}
|
||||||
|
{recommendationsSection}
|
||||||
|
{findingSection}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<div>
|
||||||
<div style={{marginBottom: '20px'}}>
|
<div className="text-center no-print" style={{marginBottom: '20px'}}>
|
||||||
<PrintReportButton onClick={() => {print();}} />
|
<Button bsSize="large" onClick={() => {
|
||||||
|
this.print();
|
||||||
|
}}><i className="glyphicon glyphicon-print"/> Print Report</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="report-page">
|
<div className="report-page">
|
||||||
<ReportHeader report_type={ReportTypes.zeroTrust}/>
|
<ReportHeader report_type={ReportTypes.zeroTrust}/>
|
||||||
<hr/>
|
<hr/>
|
||||||
{content}
|
{content}
|
||||||
|
<hr/>
|
||||||
|
<pre>{JSON.stringify(this.state.pillars, undefined, 2)}</pre>
|
||||||
|
<br/>
|
||||||
|
<ResponsiveVennDiagram pillarsGrades={mockup} />
|
||||||
|
<pre>{JSON.stringify(this.state.recommendations, undefined, 2)}</pre>
|
||||||
|
<br/>
|
||||||
|
<pre>{JSON.stringify(this.state.findings, undefined, 2)}</pre>
|
||||||
</div>
|
</div>
|
||||||
<div style={{marginTop: '20px'}}>
|
|
||||||
<PrintReportButton onClick={() => {print();}} />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
generateFindingsSection() {
|
|
||||||
return (<div id="findings-overview">
|
|
||||||
<h2>Findings</h2>
|
|
||||||
<FindingsTable pillarsToStatuses={this.state.pillars.pillarsToStatuses} findings={this.state.findings}/>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateDirectivesSection() {
|
|
||||||
return (<div id="directives-overview">
|
|
||||||
<h2>Directives</h2>
|
|
||||||
{
|
|
||||||
Object.keys(this.state.directives).map((pillar) =>
|
|
||||||
<SinglePillarDirectivesStatus
|
|
||||||
key={pillar}
|
|
||||||
pillar={pillar}
|
|
||||||
directivesStatus={this.state.directives[pillar]}
|
|
||||||
pillarsToStatuses={this.state.pillars.pillarsToStatuses}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateOverviewSection() {
|
|
||||||
return (<div id="overview-section">
|
|
||||||
<h2>Overview</h2>
|
|
||||||
<Grid fluid={true}>
|
|
||||||
<Row className="show-grid">
|
|
||||||
<Col xs={8} sm={8} md={8} lg={8}>
|
|
||||||
<PillarsOverview pillarsToStatuses={this.state.pillars.pillarsToStatuses}
|
|
||||||
grades={this.state.pillars.grades}/>
|
|
||||||
</Col>
|
|
||||||
<Col xs={4} sm={4} md={4} lg={4}>
|
|
||||||
<MonkeysStillAliveWarning allMonkeysAreDead={this.state.allMonkeysAreDead}/>
|
|
||||||
<SecurityIssuesGlance issuesFound={this.anyIssuesFound()}/>
|
|
||||||
<StatusesToPillarsSummary statusesToPillars={this.state.pillars.statusesToPillars}/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Grid>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
stillLoadingDataFromServer() {
|
stillLoadingDataFromServer() {
|
||||||
return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined";
|
return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
|
print() {
|
||||||
|
alert("unimplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
getZeroTrustReportFromServer() {
|
getZeroTrustReportFromServer() {
|
||||||
|
@ -139,11 +160,11 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
findings: res
|
findings: res
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.authFetch('/api/report/zero_trust/directives')
|
this.authFetch('/api/report/zero_trust/recommendations')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
directives: res
|
recommendations: res
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.authFetch('/api/report/zero_trust/pillars')
|
this.authFetch('/api/report/zero_trust/pillars')
|
||||||
|
@ -154,14 +175,6 @@ class ZeroTrustReportPageComponent extends AuthComponent {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
anyIssuesFound() {
|
|
||||||
const severe = function(finding) {
|
|
||||||
return (finding.status === "Conclusive" || finding.status === "Inconclusive");
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.state.findings.some(severe);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ZeroTrustReportPageComponent;
|
export default ZeroTrustReportPageComponent;
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as d3 from 'd3'
|
|
||||||
|
|
||||||
class ArcNode extends React.Component{
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
let {prefix, ref, index, data} = this.props;;
|
|
||||||
|
|
||||||
let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<g transform={'rotate(180)'} id={data.node.pillar}>
|
|
||||||
<path
|
|
||||||
|
|
||||||
id={this._reactInternalFiber.key}
|
|
||||||
className={'arcNode'}
|
|
||||||
data-tooltip={data.tooltip}
|
|
||||||
d={arc()}
|
|
||||||
fill={data.hex}
|
|
||||||
|
|
||||||
/>
|
|
||||||
<text x={0} dy={data.fontStyle.size * 1.2} fontSize={data.fontStyle.size} textAnchor='middle' pointerEvents={'none'}>
|
|
||||||
<textPath href={'#' + this._reactInternalFiber.key} startOffset={'26.4%'}>
|
|
||||||
{data.label}
|
|
||||||
</textPath>
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ArcNode.propTypes = {
|
|
||||||
|
|
||||||
prefix: PropTypes.string,
|
|
||||||
index: PropTypes.number,
|
|
||||||
data: PropTypes.object
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ArcNode;
|
|
|
@ -1,383 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Dimensions from 'react-dimensions'
|
|
||||||
import Tooltip from './Tooltip'
|
|
||||||
import CircularNode from './CircularNode'
|
|
||||||
import ArcNode from './ArcNode'
|
|
||||||
import { TypographicUtilities } from './utility.js'
|
|
||||||
import './index.css'
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
TODO LIST
|
|
||||||
|
|
||||||
UPDATED [21.08.2019]
|
|
||||||
|
|
||||||
[-] SVG > ViewBox 0 0 512 512, so it would be responsive and scalable
|
|
||||||
[-] Add resize listener to ResponsiveVennDiagram wrapper
|
|
||||||
[-] I have noticed that you have PillarGrades at ZeroTrustReportPage, so how I can fetch the data out of it?
|
|
||||||
|
|
||||||
UPDATED [20.08.2019]
|
|
||||||
|
|
||||||
[x] I've seen that lots of D3 responsive examples are using 'wrapper' around the main component to
|
|
||||||
get parent container dimensions. And lister for resize.
|
|
||||||
|
|
||||||
So, here it's Responsive(VennDiagram)
|
|
||||||
|
|
||||||
If it doesn't work, I have another alternative:
|
|
||||||
|
|
||||||
import React, { useRef, useEffect, useState, useLayoutEffect } from 'react'
|
|
||||||
|
|
||||||
const ResponsiveVennDiagram = props => {
|
|
||||||
|
|
||||||
const minWidth = 256;
|
|
||||||
const targetRef = useRef();
|
|
||||||
const [dimensions, setDimensions] = useState({ width: 0, heigth: 0 });
|
|
||||||
let movement_timer = null;
|
|
||||||
const RESET_TIMEOUT = 100;
|
|
||||||
|
|
||||||
const checkForDimensionsUpdate = () => {
|
|
||||||
|
|
||||||
if (targetRef.current) {
|
|
||||||
|
|
||||||
setDimensions({
|
|
||||||
|
|
||||||
width: Math.min(targetRef.current.offsetWidth, targetRef.current.offsetHeight),
|
|
||||||
height: targetRef.current.offsetHeight
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useLayoutEffect(() => { checkForDimensionsUpdate(); }, []);
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); });
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<div ref={targetRef} }>
|
|
||||||
<VennDiagram pillarsGrades={props.pillarsGrades} width={Math.max(minWidth, dimensions.width)} height={Math.max(minWidth, dimensions.width)} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
ResponsiveVennDiagram.propTypes = {
|
|
||||||
|
|
||||||
pillarsGrades: PropTypes.array
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ResponsiveVennDiagram;
|
|
||||||
|
|
||||||
While your diagram laout is squared, the VennDiaagram gets the min(width, height)
|
|
||||||
|
|
||||||
[x] Colours have been updated.
|
|
||||||
|
|
||||||
[x] String prototypes have been moved to '.utilities.js'
|
|
||||||
|
|
||||||
[x] I have use the prefix 'vennDiagram' for all children elements to prevent naming conflicts with other components
|
|
||||||
DOM objects.
|
|
||||||
|
|
||||||
[x] I have used PropTypes already a year ago on ThreeJS/ReactJS stack project.
|
|
||||||
|
|
||||||
[!] External callback is still on my list, can you make a mockup for external function which have to get pillar variable?
|
|
||||||
|
|
||||||
[!] Z-indices sorting on mouseover
|
|
||||||
Would be n my 21.08.2019 TODO list. With D3.JS it's an easy task, however would try do
|
|
||||||
make these z-soring without D3 framework.
|
|
||||||
|
|
||||||
UPDATED [20.08.2019]
|
|
||||||
|
|
||||||
[!] By now, there are three input props for VennDiagram component:
|
|
||||||
data, width and height.
|
|
||||||
|
|
||||||
[-] Since layout has to be hardcoded, it's driven by this.layout object.
|
|
||||||
There're many ways of setting circles/arc positions, now I'm using the
|
|
||||||
stright-forward one.
|
|
||||||
|
|
||||||
Usually, I am put all hardcoded params to external JSON file, i.e config.json.
|
|
||||||
Let me know if it's a good idea.
|
|
||||||
|
|
||||||
[-] Could rearange z-indecies for nodes on hover, so highlighted node would have highest z-index.
|
|
||||||
[-] If you want callback on click even, please provide additional info what are you expecting to pass
|
|
||||||
through this.
|
|
||||||
|
|
||||||
[!] I don't used to make lots of comments in my code, but treying to name everything in a way,
|
|
||||||
so third person could instantly get its job and concept.
|
|
||||||
|
|
||||||
If it is not enoough just let me know.
|
|
||||||
|
|
||||||
[!] I have tried to avoid using D3 so much, especially its mouse events, cause it creates a bunch
|
|
||||||
of listeners for every children DOM elements. I have tried to use raw SVG objects.
|
|
||||||
The ArcNode is the only component where I've to call d3.arc constrictor.
|
|
||||||
|
|
||||||
[!] There are lots of discussion over different blogs an forums that ReactJS and D3.JS have a DOM
|
|
||||||
issue conflict, [could find lots of articels at medium.org about that, for example,
|
|
||||||
https://medium.com/@tibotiber/react-d3-js-balancing-performance-developer-experience-4da35f912484].
|
|
||||||
|
|
||||||
Since the current component has only a few DOM elements, I don't thing we would have any troubles,
|
|
||||||
but please keep in mind that I could tweak current version with react-faux-dom.
|
|
||||||
|
|
||||||
Actually, by now, I'm using D3 only for math, for arc path calculations.
|
|
||||||
|
|
||||||
[!] Don't mind about code spacings, it's just for me, the final code would be clear out of them.
|
|
||||||
|
|
||||||
[-] On click, an EXTERNAL callback should be called with the pillar name as a parameter. That is to enable us to expand the click functionality in future without editing the internal implementation of the component.
|
|
||||||
|
|
||||||
[-] planned, [x] done, [!] see comments
|
|
||||||
|
|
||||||
@author Vladimir V KUCHINOV
|
|
||||||
@email helloworld@vkuchinov.co.uk
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const VENN_MIN_WIDTH = '300px';
|
|
||||||
|
|
||||||
class ResponsiveVennDiagram extends React.Component {
|
|
||||||
|
|
||||||
constructor(props) { super(props); }
|
|
||||||
render() {
|
|
||||||
|
|
||||||
const {options, pillarsGrades} = this.props;
|
|
||||||
let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight;
|
|
||||||
|
|
||||||
if(childrenHeight === 0 || childrenHeight === NaN){ childrenHeight = childrenWidth; }
|
|
||||||
else{ childrenWidth = Math.min(childrenWidth, childrenHeight) }
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<div ref={this.divElement} style={{textAlign: 'center'}}>
|
|
||||||
<VennDiagram style={{minWidth: VENN_MIN_WIDTH, minHeight: VENN_MIN_WIDTH}} pillarsGrades={pillarsGrades} />
|
|
||||||
</div> )
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponsiveVennDiagram.propTypes = {
|
|
||||||
|
|
||||||
pillarsGrades: PropTypes.array
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Dimensions()(ResponsiveVennDiagram);
|
|
||||||
|
|
||||||
class VennDiagram extends React.Component{
|
|
||||||
|
|
||||||
constructor(props_){
|
|
||||||
|
|
||||||
super(props_);
|
|
||||||
|
|
||||||
this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } }
|
|
||||||
|
|
||||||
this.width = this.height = 512, this.zOrder = [];
|
|
||||||
|
|
||||||
this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C'];
|
|
||||||
this.prefix = 'vennDiagram';
|
|
||||||
this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed'];
|
|
||||||
this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}];
|
|
||||||
this.offset = this.width / 16;
|
|
||||||
|
|
||||||
this.thirdWidth = this.width / 3;
|
|
||||||
this.sixthWidth = this.width / 6;
|
|
||||||
this.width2By7 = 2 * this.width / 7
|
|
||||||
this.width1By11 = this.width / 11;
|
|
||||||
this.width1By28 = this.width / 28;
|
|
||||||
|
|
||||||
this.toggle = false;
|
|
||||||
|
|
||||||
this.layout = {
|
|
||||||
|
|
||||||
Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} },
|
|
||||||
People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} },
|
|
||||||
Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} },
|
|
||||||
Devices: { cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11} },
|
|
||||||
Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} },
|
|
||||||
VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth },
|
|
||||||
AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 }
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this._onScroll = this._onScroll.bind(this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
this.parseData();
|
|
||||||
window.addEventListener('scroll', this._onScroll);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMouseMove(e) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
if(!this.toggle){
|
|
||||||
|
|
||||||
let hidden = 'none';
|
|
||||||
let html = '';
|
|
||||||
let bcolor = '#DEDEDE';
|
|
||||||
|
|
||||||
document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)});
|
|
||||||
|
|
||||||
if(e.target.id.includes('Node')) {
|
|
||||||
|
|
||||||
html = e.target.dataset.tooltip;
|
|
||||||
this.divElement.style.cursor = 'pointer';
|
|
||||||
hidden = 'block'; e.target.setAttribute('opacity', 0.95);
|
|
||||||
bcolor = e.target.getAttribute('fill');
|
|
||||||
|
|
||||||
//set highest z-index
|
|
||||||
e.target.parentNode.parentNode.appendChild(e.target.parentNode);
|
|
||||||
|
|
||||||
}else{
|
|
||||||
|
|
||||||
this.divElement.style.cursor = 'default';
|
|
||||||
|
|
||||||
//return z indices to default
|
|
||||||
Object.keys(this.layout).forEach(function(d_, i_){ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); })
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } });
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_onScroll(e){
|
|
||||||
|
|
||||||
this.divElement.style.cursor = 'default';
|
|
||||||
this.setState({target: e, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_onClick(e) {
|
|
||||||
|
|
||||||
if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; }
|
|
||||||
|
|
||||||
//variable to external callback
|
|
||||||
//e.target.parentNode.id)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
relativeCoords (e) {
|
|
||||||
|
|
||||||
let bounds = e.target.getBoundingClientRect();
|
|
||||||
var x = e.clientX - bounds.left;
|
|
||||||
var y = e.clientY - bounds.top;
|
|
||||||
return {x: x, y: y};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
parseData(){
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
let data = [];
|
|
||||||
const omit = (prop, { [prop]: _, ...rest }) => rest;
|
|
||||||
|
|
||||||
this.props.pillarsGrades.forEach((d_, i_) => {
|
|
||||||
|
|
||||||
let params = omit('Unexpected', omit('pillar', d_));
|
|
||||||
let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0);
|
|
||||||
let key = TypographicUtilities.removeAmpersand(d_.pillar);
|
|
||||||
let html = self.buildTooltipHtmlContent(d_);
|
|
||||||
let rule = 3;
|
|
||||||
|
|
||||||
if(sum === 0){ rule = 0 }
|
|
||||||
else if(d_['Conclusive'] > 0){ rule = 1 }
|
|
||||||
else if(d_['Conclusive'] === 0 && d_['Inconclusive'] > 0) { rule = 2 }
|
|
||||||
|
|
||||||
self.setLayoutElement(rule, key, html, d_);
|
|
||||||
data.push(this.layout[key])
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setState({ data: data });
|
|
||||||
this.render();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); }
|
|
||||||
|
|
||||||
setLayoutElement(rule_, key_, html_, d_){
|
|
||||||
|
|
||||||
if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; }
|
|
||||||
else {this.layout[key_].fontStyle = this.fontStyles[1]; }
|
|
||||||
|
|
||||||
this.layout[key_].hex = this.colors[rule_];
|
|
||||||
this.layout[key_].label = d_.pillar + this.suffices[rule_];
|
|
||||||
this.layout[key_].node = d_;
|
|
||||||
this.layout[key_].tooltip = html_;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
if(this.state.data === undefined) { return null; }
|
|
||||||
else {
|
|
||||||
|
|
||||||
//equivalent to center translate (width/2, height/2)
|
|
||||||
let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height;
|
|
||||||
let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')';
|
|
||||||
|
|
||||||
let nodes = Object.values(this.layout).map((d_, i_) =>{
|
|
||||||
|
|
||||||
if(d_.hasOwnProperty('cx')){
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<CircularNode
|
|
||||||
|
|
||||||
prefix={this.prefix}
|
|
||||||
key={this.prefix + 'Node_' + i_}
|
|
||||||
index={i_}
|
|
||||||
data={d_}
|
|
||||||
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
}else{
|
|
||||||
|
|
||||||
d_.label = TypographicUtilities.removeBrokenBar(d_.label);
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<ArcNode
|
|
||||||
|
|
||||||
prefix={this.prefix}
|
|
||||||
key={this.prefix + 'Node_' + i_}
|
|
||||||
index={i_}
|
|
||||||
data={d_}
|
|
||||||
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<div ref={ (divElement) => this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} >
|
|
||||||
<svg id={this.prefix} viewBox={viewPortParameters} width={'100%'} height={'100%'} xmlns='http://www.w3.org/2000/svg' xmlnsXlink='http://www.w3.org/1999/xlink'>
|
|
||||||
{nodes}
|
|
||||||
</svg>
|
|
||||||
<Tooltip id={this.prefix + 'Tooltip'} prefix={this.prefix} {...this.state.tooltip} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
VennDiagram.propTypes = {
|
|
||||||
|
|
||||||
pillarsGrades: PropTypes.array
|
|
||||||
|
|
||||||
}
|
|
BIN
monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store
vendored
Normal file
BIN
monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store
vendored
Normal file
Binary file not shown.
|
@ -0,0 +1,44 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import * as d3 from 'd3'
|
||||||
|
|
||||||
|
class ArcNode extends React.Component{
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
let {prefix, index, data} = this.props;
|
||||||
|
|
||||||
|
let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<g transform={'rotate(180)'} id={data.node.pillar}>
|
||||||
|
<path
|
||||||
|
|
||||||
|
id={prefix + 'Node_' + index}
|
||||||
|
className={'arcNode'}
|
||||||
|
data-tooltip={data.tooltip}
|
||||||
|
d={arc()}
|
||||||
|
fill={data.hex}
|
||||||
|
|
||||||
|
/>
|
||||||
|
<text x={0} dy={data.fontStyle.size * 1.2} fontSize={data.fontStyle.size} textAnchor='middle' pointerEvents={'none'}>
|
||||||
|
<textPath href={'#' + this._reactInternalFiber.key} startOffset={'26.4%'}>
|
||||||
|
{data.label}
|
||||||
|
</textPath>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ArcNode.propTypes = {
|
||||||
|
|
||||||
|
data: PropTypes.object
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArcNode;
|
|
@ -5,7 +5,7 @@ class CircularNode extends React.Component{
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
let {prefix, ref, index, data} = this.props;
|
let {prefix, index, data} = this.props;
|
||||||
|
|
||||||
let tspans = data.label.split("|").map((d_, i_) =>{
|
let tspans = data.label.split("|").map((d_, i_) =>{
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class CircularNode extends React.Component{
|
||||||
<g transform={translate} id={data.node.pillar}>
|
<g transform={translate} id={data.node.pillar}>
|
||||||
<circle
|
<circle
|
||||||
|
|
||||||
id={this._reactInternalFiber.key}
|
id={prefix + 'Node_' + index}
|
||||||
className={'circularNode'}
|
className={'circularNode'}
|
||||||
data-tooltip={data.tooltip}
|
data-tooltip={data.tooltip}
|
||||||
r={data.r}
|
r={data.r}
|
||||||
|
@ -48,7 +48,6 @@ class CircularNode extends React.Component{
|
||||||
|
|
||||||
CircularNode.propTypes = {
|
CircularNode.propTypes = {
|
||||||
|
|
||||||
prefix: PropTypes.string,
|
|
||||||
index: PropTypes.number,
|
index: PropTypes.number,
|
||||||
data: PropTypes.object
|
data: PropTypes.object
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Dimensions from 'react-dimensions'
|
||||||
|
import VennDiagram from './VennDiagram'
|
||||||
|
|
||||||
|
const VENN_MIN_WIDTH = '300px';
|
||||||
|
|
||||||
|
class ResponsiveVennDiagram extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) { super(props); }
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
const {pillarsGrades} = this.props;
|
||||||
|
let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight;
|
||||||
|
|
||||||
|
if(childrenHeight === 0 || isNaN(childrenHeight)){ childrenHeight = childrenWidth; }
|
||||||
|
else{ childrenWidth = Math.min(childrenWidth, childrenHeight) }
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div ref={this.divElement} style={{textAlign: 'center'}}>
|
||||||
|
<VennDiagram style={{minWidth: VENN_MIN_WIDTH, minHeight: VENN_MIN_WIDTH}} pillarsGrades={pillarsGrades} />
|
||||||
|
</div> )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponsiveVennDiagram.propTypes = {
|
||||||
|
|
||||||
|
pillarsGrades: PropTypes.array
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dimensions()(ResponsiveVennDiagram);
|
|
@ -0,0 +1,246 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Tooltip from './Tooltip'
|
||||||
|
import CircularNode from './CircularNode'
|
||||||
|
import ArcNode from './ArcNode'
|
||||||
|
import { TypographicUtilities } from './Utility.js'
|
||||||
|
import './VennDiagram.css'
|
||||||
|
|
||||||
|
class VennDiagram extends React.Component{
|
||||||
|
|
||||||
|
constructor(props_){
|
||||||
|
|
||||||
|
super(props_);
|
||||||
|
|
||||||
|
this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } }
|
||||||
|
|
||||||
|
this.width = this.height = 512, this.zOrder = [];
|
||||||
|
|
||||||
|
this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C'];
|
||||||
|
this.prefix = 'vennDiagram';
|
||||||
|
this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed'];
|
||||||
|
this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}];
|
||||||
|
this.offset = this.width / 16;
|
||||||
|
|
||||||
|
this.thirdWidth = this.width / 3;
|
||||||
|
this.sixthWidth = this.width / 6;
|
||||||
|
this.width2By7 = 2 * this.width / 7
|
||||||
|
this.width1By11 = this.width / 11;
|
||||||
|
this.width1By28 = this.width / 28;
|
||||||
|
|
||||||
|
this.toggle = false;
|
||||||
|
|
||||||
|
this.layout = {
|
||||||
|
|
||||||
|
Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} },
|
||||||
|
People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} },
|
||||||
|
Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} },
|
||||||
|
Devices: { cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11} },
|
||||||
|
Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} },
|
||||||
|
VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth },
|
||||||
|
AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer
|
||||||
|
sum(C, I, P, U) has to be <=0
|
||||||
|
|
||||||
|
RULE #2: Conclusive [C] has to be > 0,
|
||||||
|
sum(C) > 0
|
||||||
|
|
||||||
|
RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0,
|
||||||
|
sum(C, I) > 0 and C * I = 0, while C has to be 0
|
||||||
|
|
||||||
|
RULE #4: Positive [P] and Unexecuted have to be positive
|
||||||
|
sum(P, U) >= 2 and P * U = positive integer, while
|
||||||
|
if the P is bigger by 2 then negative U, first conditional
|
||||||
|
would be true.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.rules = [
|
||||||
|
|
||||||
|
{ id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] + d_['Unexecuted'] <= 0; } },
|
||||||
|
{ id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } },
|
||||||
|
{ id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } },
|
||||||
|
{ id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } }
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
this._onScroll = this._onScroll.bind(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
|
||||||
|
this.parseData();
|
||||||
|
window.addEventListener('scroll', this._onScroll);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMouseMove(e) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
if(!this.toggle){
|
||||||
|
|
||||||
|
let hidden = 'none';
|
||||||
|
let html = '';
|
||||||
|
let bcolor = '#DEDEDE';
|
||||||
|
|
||||||
|
document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)});
|
||||||
|
|
||||||
|
if(e.target.id.includes('Node')) {
|
||||||
|
|
||||||
|
html = e.target.dataset.tooltip;
|
||||||
|
this.divElement.style.cursor = 'pointer';
|
||||||
|
hidden = 'block'; e.target.setAttribute('opacity', 0.95);
|
||||||
|
bcolor = e.target.getAttribute('fill');
|
||||||
|
|
||||||
|
//set highest z-index
|
||||||
|
e.target.parentNode.parentNode.appendChild(e.target.parentNode);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
this.divElement.style.cursor = 'default';
|
||||||
|
|
||||||
|
//return z indices to default
|
||||||
|
Object.keys(this.layout).forEach(function(d_, i_){ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } });
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onScroll(e){
|
||||||
|
|
||||||
|
this.divElement.style.cursor = 'default';
|
||||||
|
this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_onClick(e) {
|
||||||
|
|
||||||
|
if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; }
|
||||||
|
|
||||||
|
//variable to external callback
|
||||||
|
//e.target.parentNode.id)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
parseData(){
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
let data = [];
|
||||||
|
const omit = (prop, { [prop]: _, ...rest }) => rest;
|
||||||
|
|
||||||
|
this.props.pillarsGrades.forEach((d_, i_) => {
|
||||||
|
|
||||||
|
let params = omit('pillar', d_);
|
||||||
|
let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0);
|
||||||
|
let key = TypographicUtilities.removeAmpersand(d_.pillar);
|
||||||
|
let html = self.buildTooltipHtmlContent(d_);
|
||||||
|
let rule = null;
|
||||||
|
|
||||||
|
for(let j = 0; j < self.rules.length; j++){ if(self.rules[j].f(d_)) { rule = j; break; }}
|
||||||
|
|
||||||
|
self.setLayoutElement(rule, key, html, d_);
|
||||||
|
data.push(this.layout[key]);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({ data: data });
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); }
|
||||||
|
|
||||||
|
setLayoutElement(rule_, key_, html_, d_){
|
||||||
|
|
||||||
|
if(rule_ == null) { throw Error('The node scores are invalid'); }
|
||||||
|
|
||||||
|
if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; }
|
||||||
|
else {this.layout[key_].fontStyle = this.fontStyles[1]; }
|
||||||
|
|
||||||
|
this.layout[key_].hex = this.colors[rule_];
|
||||||
|
this.layout[key_].label = d_.pillar + this.suffices[rule_];
|
||||||
|
this.layout[key_].node = d_;
|
||||||
|
this.layout[key_].tooltip = html_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
if(this.state.data === undefined) { return null; }
|
||||||
|
else {
|
||||||
|
|
||||||
|
//equivalent to center translate (width/2, height/2)
|
||||||
|
let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height;
|
||||||
|
let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')';
|
||||||
|
|
||||||
|
let nodes = Object.values(this.layout).map((d_, i_) =>{
|
||||||
|
|
||||||
|
if(d_.hasOwnProperty('cx')){
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<CircularNode
|
||||||
|
|
||||||
|
prefix={this.prefix}
|
||||||
|
key={this.prefix + 'CircularNode' + i_}
|
||||||
|
index={i_}
|
||||||
|
data={d_}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
d_.label = TypographicUtilities.removeBrokenBar(d_.label);
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<ArcNode
|
||||||
|
|
||||||
|
prefix={this.prefix}
|
||||||
|
key={this.prefix + 'ArcNode' + i_}
|
||||||
|
index={i_}
|
||||||
|
data={d_}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div ref={ (divElement) => this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} >
|
||||||
|
<svg id={this.prefix} viewBox={viewPortParameters} width={'100%'} height={'100%'} xmlns='http://www.w3.org/2000/svg' xmlnsXlink='http://www.w3.org/1999/xlink'>
|
||||||
|
{nodes}
|
||||||
|
</svg>
|
||||||
|
<Tooltip id={this.prefix + 'Tooltip'} prefix={this.prefix} {...this.state.tooltip} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VennDiagram.propTypes = {
|
||||||
|
|
||||||
|
pillarsGrades: PropTypes.array
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VennDiagram;
|
Loading…
Reference in New Issue