Pre Merge pull request !6 from Ender_1024/master

pull/6/MERGE
Ender_1024 2023-02-16 06:54:25 +00:00 committed by Gitee
commit 6d8dd38796
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
168 changed files with 34120 additions and 601 deletions

View File

@ -2,4 +2,4 @@
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
VUE_APP_BASE_API = '/'

View File

@ -16,13 +16,19 @@
"dependencies": {
"axios": "0.18.1",
"core-js": "3.6.5",
"element-ui": "2.13.2",
"echarts": "^5.3.3",
"echarts-wordcloud": "^2.0.0",
"element-ui": "^2.13.2",
"font-awesome": "^4.7.0",
"js-cookie": "2.2.0",
"jsmind": "^0.4.8",
"neo4j-driver": "^4.2.2",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"vue": "2.6.10",
"vue-router": "3.0.6",
"vue-styled-components": "^1.6.0",
"vuex": "3.1.0"
},
"devDependencies": {
@ -35,12 +41,13 @@
"babel-eslint": "10.1.0",
"babel-jest": "23.6.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"coffee-loader": "^0.7.3",
"coffee-script": "^1.12.7",
"chalk": "2.4.2",
"connect": "3.6.6",
"eslint": "6.7.2",
"eslint-plugin-vue": "6.2.2",
"html-webpack-plugin": "3.2.0",
"mockjs": "1.0.1-beta3",
"runjs": "4.3.2",
"sass": "1.26.8",
"sass-loader": "8.0.2",
@ -48,7 +55,9 @@
"serve-static": "1.13.2",
"svg-sprite-loader": "4.1.3",
"svgo": "1.2.2",
"vue-template-compiler": "2.6.10"
"vue-loader": "14.2.0",
"vue-template-compiler": "2.6.10",
"url-loader": "^0.5.8"
},
"browserslist": [
"> 1%",

3210
public/common.css 100644

File diff suppressed because one or more lines are too long

25
public/d3.min.js vendored 100644

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

BIN
public/favicon.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -4,8 +4,10 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="icon" href="<%= BASE_URL %>favicon.png">
<title><%= webpackConfig.name %></title>
<!-- <link rel="stylesheet" href="<%= BASE_URL %>common.css"> -->
<script type="text/javascript" src="<%= BASE_URL %>d3.min.js"></script>
</head>
<body>
<noscript>

88
src/api/job.js 100644
View File

@ -0,0 +1,88 @@
import request from '@/utils/request'
export function getFramework(params) {
return request({
url: '/job/framework',
method: 'get',
params
})
}
export function modifyFramework(data) {
return request({
url: '/job/framework',
method: 'post',
data
})
}
export function getDepartmentDetail(params) {
return request({
url: '/job/department-detail',
method: 'get',
params
})
}
export function modifyDepartmentDetail(data) {
return request({
url: '/job/department-detail',
method: 'post',
data
})
}
export function getJobDetail(params) {
return request({
url: '/job/job-detail',
method: 'get',
params
})
}
export function modifyJobDetail(data) {
return request({
url: '/job/job-detail',
method: 'post',
data
})
}
export function queryDepartmentJob() {
return request({
url: '/ahp/department-job/query',
method: 'get'
})
}
export function queryAhpFramework(params) {
return request({
url: '/ahp/framework/query',
method: 'get',
params
})
}
export function modifyAhpFramework(params) {
return request({
url: '/ahp/framework/save',
method: 'post',
params
})
}
export function queryScore(params) {
return request({
url: '/ahp/score/query',
method: 'get',
params
})
}
export function saveScore(params) {
return request({
url: '/ahp/score/save',
method: 'post',
params
})
}

185
src/api/person.js 100644
View File

@ -0,0 +1,185 @@
import request from '@/utils/request'
export function getPersonData(params) {
return request({
url: '/person/information2',
method: 'get',
params
})
}
export function getPersonTag(params) {
return request({
url: '/person/tag',
method: 'get',
params
})
}
export function getReviewPopulation(params) {
return request({
url: '/person/review/population',
method: 'get',
params
})
}
export function getReviewEducation(params) {
return request({
url: '/person/review/education',
method: 'get',
params
})
}
export function getReviewParty(params) {
return request({
url: '/person/review/party',
method: 'get',
params
})
}
export function getReviewPerformance(params) {
return request({
url: '/person/review/performance',
method: 'get',
params
})
}
export function getReviewLineup(params) {
return request({
url: '/person/review/lineup',
method: 'get',
params
})
}
export function getReviewInnovation(params) {
return request({
url: '/person/review/innovation',
method: 'get',
params
})
}
export function getReviewActivity(params) {
return request({
url: '/person/review/activity',
method: 'get',
params
})
}
export function getReviewRoutine(params) {
return request({
url: '/person/review/routine',
method: 'get',
params
})
}
export function getAppraise(params) {
return request({
url: '/person/appraise',
method: 'get',
params
})
}
export function getHonor(params) {
return request({
url: '/person/honor',
method: 'get',
params
})
}
export function getAdvance(params) {
return request({
url: '/person/advance',
method: 'get',
params
})
}
export function getInnovation(params) {
return request({
url: '/person/innovation',
method: 'get',
params
})
}
export function getArticle(params) {
return request({
url: '/person/article',
method: 'get',
params
})
}
export function getActivity(params) {
return request({
url: '/person/activity',
method: 'get',
params
})
}
export function getRoutine(params) {
return request({
url: '/person/routine',
method: 'get',
params
})
}
export function getParty(params) {
return request({
url: '/person/party',
method: 'get',
params
})
}
export function selectBasicInfo(params) {
return request({
url: '/person/information',
method: 'get',
params
})
}
export function modifyBasicInfo(data) {
return request({
url: '/person/information/modify',
method: 'post',
data
})
}
export function addBasicInfo(data) {
return request({
url: '/person/information/add',
method: 'post',
data
})
}
export function deleteBasicInfo(params) {
return request({
url: '/person/information/delete',
method: 'get',
params
})
}
export function getBasicTips(params) {
return request({
url: '/person/information/tips',
method: 'get',
params
})
}

View File

@ -0,0 +1,42 @@
import request from '@/utils/request'
export function getPriority(params) {
return request({
url: '/priority/select',
method: 'get',
params
})
}
export function modifyPriority(data) {
return request({
url: '/priority/modify',
method: 'post',
data
})
}
export function addUser(data) {
return request({
url: '/priority/add-user',
method: 'post',
data
})
}
export function deleteUser(data) {
return request({
url: '/priority/delete-user',
method: 'post',
data
})
}
export function verifyJobPriority(params) {
return request({
url: '/priority/verify',
method: 'get',
params
})
}

View File

@ -1,9 +0,0 @@
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/vue-admin-template/table/list',
method: 'get',
params
})
}

View File

@ -2,7 +2,7 @@ import request from '@/utils/request'
export function login(data) {
return request({
url: '/vue-admin-template/user/login',
url: '/user/login2',
method: 'post',
data
})
@ -10,7 +10,7 @@ export function login(data) {
export function getInfo(token) {
return request({
url: '/vue-admin-template/user/info',
url: '/user/info',
method: 'get',
params: { token }
})
@ -18,7 +18,15 @@ export function getInfo(token) {
export function logout() {
return request({
url: '/vue-admin-template/user/logout',
url: '/user/logout',
method: 'post'
})
}
export function changePassword(data) {
return request({
url: '/user/change-password',
method: 'post',
data
})
}

View File

@ -0,0 +1,182 @@
/**
* fullPage 2.4.6
* https://github.com/alvarotrigo/fullPage.js
* MIT licensed
*
* Copyright (C) 2013 alvarotrigo.com - A project by Alvaro Trigo
*/
html, body {
margin: 0;
padding: 0;
overflow:hidden;
/*Avoid flicker on slides transitions for mobile phones #336 */
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
#superContainer {
height: 100%;
position: relative;
/* Touch detection for Windows 8 */
-ms-touch-action: none;
/* IE 11 on Windows Phone 8.1*/
touch-action: none;
}
.fp-section {
position: relative;
-webkit-box-sizing: border-box; /* Safari<=5 Android<=3 */
-moz-box-sizing: border-box; /* <=28 */
box-sizing: border-box;
}
.fp-slide {
float: left;
}
.fp-slide, .fp-slidesContainer {
height: 100%;
display: block;
}
.fp-slides {
z-index:1;
height: 100%;
overflow: hidden;
position: relative;
-webkit-transition: all 0.3s ease-out; /* Safari<=6 Android<=4.3 */
transition: all 0.3s ease-out;
}
.fp-section.fp-table, .fp-slide.fp-table {
display: table;
table-layout:fixed;
width: 100%;
}
.fp-tableCell {
display: table-cell;
vertical-align: middle;
width: 100%;
height: 100%;
}
.fp-slidesContainer {
float: left;
position: relative;
}
.fp-controlArrow {
position: absolute;
z-index: 4;
top: 50%;
cursor: pointer;
width: 0;
height: 0;
border-style: solid;
margin-top: -38px;
}
.fp-controlArrow.fp-prev {
left: 15px;
width: 0;
border-width: 38.5px 34px 38.5px 0;
border-color: transparent #fff transparent transparent;
}
.fp-controlArrow.fp-next {
right: 15px;
border-width: 38.5px 0 38.5px 34px;
border-color: transparent transparent transparent #fff;
}
.fp-scrollable {
overflow: scroll;
}
.fp-notransition {
-webkit-transition: none !important;
transition: none !important;
}
#fp-nav {
position: fixed;
z-index: 100;
margin-top: -32px;
top: 50%;
opacity: 1;
}
#fp-nav.right {
right: 17px;
}
#fp-nav.left {
left: 17px;
}
.fp-slidesNav{
position: absolute;
z-index: 4;
left: 50%;
opacity: 1;
}
.fp-slidesNav.bottom {
bottom: 17px;
}
.fp-slidesNav.top {
top: 17px;
}
#fp-nav ul,
.fp-slidesNav ul {
margin: 0;
padding: 0;
}
#fp-nav ul li,
.fp-slidesNav ul li {
display: block;
width: 14px;
height: 13px;
margin: 7px;
position:relative;
}
.fp-slidesNav ul li {
display: inline-block;
}
#fp-nav ul li a,
.fp-slidesNav ul li a {
display: block;
position: relative;
z-index: 1;
width: 100%;
height: 100%;
cursor: pointer;
text-decoration: none;
}
#fp-nav ul li a.active span,
.fp-slidesNav ul li a.active span {
background: #333;
}
#fp-nav ul li a span,
.fp-slidesNav ul li a span {
top: 2px;
left: 2px;
width: 8px;
height: 8px;
border: 1px solid #000;
background: rgba(0, 0, 0, 0);
border-radius: 50%;
position: absolute;
z-index: 1;
}
#fp-nav ul li .fp-tooltip {
position: absolute;
top: -2px;
color: #fff;
font-size: 14px;
font-family: arial, helvetica, sans-serif;
white-space: nowrap;
max-width: 220px;
overflow: hidden;
display: block;
opacity: 0;
width: 0;
}
#fp-nav ul li:hover .fp-tooltip {
-webkit-transition: opacity 0.2s ease-in;
transition: opacity 0.2s ease-in;
width: auto;
opacity: 1;
}
#fp-nav ul li .fp-tooltip.right {
right: 20px;
}
#fp-nav ul li .fp-tooltip.left {
left: 20px;
}

View File

@ -0,0 +1,160 @@
/*
* Released under BSD License
* Copyright (c) 2014-2021 hizzgdev@163.com
*
* Project Home:
* https://github.com/hizzgdev/jsmind/
*/
/* important section */
.jsmind-inner{position:relative;overflow: hidden auto;width:100%;height:100%;outline:none;}/*box-shadow:0 0 2px #000;*/
.jsmind-inner{
moz-user-select:-moz-none;
-moz-user-select:none;
-o-user-select:none;
-khtml-user-select:none;
-webkit-user-select:none;
-ms-user-select:none;
user-select:none;
}
/* z-index:1 */
svg.jsmind{position:absolute;z-index:1;}
canvas.jsmind{position:absolute;z-index:1;}
/* z-index:2 */
jmnodes{position:absolute;z-index:2;background-color:rgba(0,0,0,0);}/*background color is necessary*/
jmnode{position:absolute;cursor:default;max-width:400px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
jmexpander{position:absolute;width:11px;height:11px;display:block;overflow:hidden;line-height:12px;font-size:12px;text-align:center;border-radius:6px;border-width:1px;border-style:solid;cursor:pointer;}
/* default theme */
jmnode{padding:10px;background-color:#fff;color:#333;border-radius:5px;box-shadow:1px 1px 3px #bfbfbf;font:16px/1.125 Verdana,Arial,Helvetica,sans-serif;}
jmnode:hover{box-shadow:2px 2px 8px #999;background-color:#ebebeb;color:#333;}
jmnode.selected{background-color:#ebebeb;color:#333;box-shadow:2px 2px 8px #999;}
jmnode.root{font-size:24px;}
jmexpander{border-color:gray;}
jmexpander:hover{border-color:#000;}
@media screen and (max-device-width: 1024px) {
jmnode{padding:5px;border-radius:3px;font-size:14px;}
jmnode.root{font-size:21px;}
}
/* primary theme */
jmnodes.theme-primary jmnode{background-color:#428bca;color:#fff;border-color:#357ebd;}
jmnodes.theme-primary jmnode:hover{background-color:#3276b1;border-color:#285e8e;}
jmnodes.theme-primary jmnode.selected{background-color:#f1c40f;color:#fff;}
jmnodes.theme-primary jmnode.root{}
jmnodes.theme-primary jmexpander{}
jmnodes.theme-primary jmexpander:hover{}
/* warning theme */
jmnodes.theme-warning jmnode{background-color:#f0ad4e;border-color:#eea236;color:#fff;}
jmnodes.theme-warning jmnode:hover{background-color:#ed9c28;border-color:#d58512;}
jmnodes.theme-warning jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-warning jmnode.root{}
jmnodes.theme-warning jmexpander{}
jmnodes.theme-warning jmexpander:hover{}
/* danger theme */
jmnodes.theme-danger jmnode{background-color:#d9534f;border-color:#d43f3a;color:#fff;}
jmnodes.theme-danger jmnode:hover{background-color:#d2322d;border-color:#ac2925;}
jmnodes.theme-danger jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-danger jmnode.root{}
jmnodes.theme-danger jmexpander{}
jmnodes.theme-danger jmexpander:hover{}
/* success theme */
jmnodes.theme-success jmnode{background-color:#5cb85c;border-color:#4cae4c;color:#fff;}
jmnodes.theme-success jmnode:hover{background-color:#47a447;border-color:#398439;}
jmnodes.theme-success jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-success jmnode.root{}
jmnodes.theme-success jmexpander{}
jmnodes.theme-success jmexpander:hover{}
/* info theme */
jmnodes.theme-info jmnode{background-color:#5dc0de;border-color:#46b8da;;color:#fff;}
jmnodes.theme-info jmnode:hover{background-color:#39b3d7;border-color:#269abc;}
jmnodes.theme-info jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-info jmnode.root{}
jmnodes.theme-info jmexpander{}
jmnodes.theme-info jmexpander:hover{}
/* greensea theme */
jmnodes.theme-greensea jmnode{background-color:#1abc9c;color:#fff;}
jmnodes.theme-greensea jmnode:hover{background-color:#16a085;}
jmnodes.theme-greensea jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-greensea jmnode.root{}
jmnodes.theme-greensea jmexpander{}
jmnodes.theme-greensea jmexpander:hover{}
/* nephrite theme */
jmnodes.theme-nephrite jmnode{background-color:#2ecc71;color:#fff;}
jmnodes.theme-nephrite jmnode:hover{background-color:#27ae60;}
jmnodes.theme-nephrite jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-nephrite jmnode.root{}
jmnodes.theme-nephrite jmexpander{}
jmnodes.theme-nephrite jmexpander:hover{}
/* belizehole theme */
jmnodes.theme-belizehole jmnode{background-color:#3498db;color:#fff;}
jmnodes.theme-belizehole jmnode:hover{background-color:#2980b9;}
jmnodes.theme-belizehole jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-belizehole jmnode.root{}
jmnodes.theme-belizehole jmexpander{}
jmnodes.theme-belizehole jmexpander:hover{}
/* wisteria theme */
jmnodes.theme-wisteria jmnode{background-color:#9b59b6;color:#fff;}
jmnodes.theme-wisteria jmnode:hover{background-color:#8e44ad;}
jmnodes.theme-wisteria jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-wisteria jmnode.root{}
jmnodes.theme-wisteria jmexpander{}
jmnodes.theme-wisteria jmexpander:hover{}
/* asphalt theme */
jmnodes.theme-asphalt jmnode{background-color:#34495e;color:#fff;}
jmnodes.theme-asphalt jmnode:hover{background-color:#2c3e50;}
jmnodes.theme-asphalt jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-asphalt jmnode.root{}
jmnodes.theme-asphalt jmexpander{}
jmnodes.theme-asphalt jmexpander:hover{}
/* orange theme */
jmnodes.theme-orange jmnode{background-color:#f1c40f;color:#fff;}
jmnodes.theme-orange jmnode:hover{background-color:#f39c12;}
jmnodes.theme-orange jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-orange jmnode.root{}
jmnodes.theme-orange jmexpander{}
jmnodes.theme-orange jmexpander:hover{}
/* pumpkin theme */
jmnodes.theme-pumpkin jmnode{background-color:#e67e22;color:#fff;}
jmnodes.theme-pumpkin jmnode:hover{background-color:#d35400;}
jmnodes.theme-pumpkin jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-pumpkin jmnode.root{}
jmnodes.theme-pumpkin jmexpander{}
jmnodes.theme-pumpkin jmexpander:hover{}
/* pomegranate theme */
jmnodes.theme-pomegranate jmnode{background-color:#e74c3c;color:#fff;}
jmnodes.theme-pomegranate jmnode:hover{background-color:#c0392b;}
jmnodes.theme-pomegranate jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-pomegranate jmnode.root{}
jmnodes.theme-pomegranate jmexpander{}
jmnodes.theme-pomegranate jmexpander:hover{}
/* clouds theme */
jmnodes.theme-clouds jmnode{background-color:#ecf0f1;color:#333;}
jmnodes.theme-clouds jmnode:hover{background-color:#bdc3c7;}
jmnodes.theme-clouds jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-clouds jmnode.root{}
jmnodes.theme-clouds jmexpander{}
jmnodes.theme-clouds jmexpander:hover{}
/* asbestos theme */
jmnodes.theme-asbestos jmnode{background-color:#95a5a6;color:#fff;}
jmnodes.theme-asbestos jmnode:hover{background-color:#7f8c8d;}
jmnodes.theme-asbestos jmnode.selected{background-color:#11f;color:#fff;}
jmnodes.theme-asbestos jmnode.root{}
jmnodes.theme-asbestos jmexpander{}
jmnodes.theme-asbestos jmexpander:hover{}

View File

@ -0,0 +1,108 @@
.right-main {
/* padding-top: 10px; */
padding-left: 30px;
padding-right: 30px;
padding-bottom: 100px;
}
.jd-title {
text-align: center;
/* font-family: "Times New Roman","宋体","微软雅黑"; */
font-size: 2em;
/* line-height: 2em; */
}
.jd-h1 {
font-size: 1.5em;
line-height: 1.8em;
}
.jd-h2 {
font-size: 1.3em;
line-height: 1.5em;
}
.jd-h3 {
font-size: 1.2em;
line-height: 1.5em;
text-indent: 1em;
}
.jd-text {
font-size: 1.1em;
line-height: 1.5em;
text-indent: 2em;
}
.jd-li {
font-size: 1.1em;
line-height: 1.5em;
padding-left: 3em;
}
.jd-li2 {
padding-left: 1em;
}
.jd-block1 {
margin-top: 15px;
}
.jd-block2 {
margin-top: 10px;
}
.jd-block3 {
margin-top: 7px;
}
.jd-table {
border-collapse: collapse;
width: 80%;
margin: 10px auto 0px;
}
.jd-table tr {
height: 2em;
}
.jd-table table,
th,
td {
border: 1px solid #ebebeb;
}
.jd-table td {
text-align: center;
}
.jd-table2 td {
padding-left: 1em;
text-align: left;
/* text-indent: 1em; */
line-height: 1.5em;
}
.vertical-one {
width: 2em;
}
.vertical-two {
width: 3em;
}
.vertical-four {
width: 5em;
}
.confirm-button {
background: #fff;
color:#000;
border: 1px solid #dcdfe6;
border-radius: 5px;
}
.confirm-button:hover {
color: #409eff;
background-color: #ecf5ff;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,50 @@
/**
* fullPage 2.5.4
* https://github.com/alvarotrigo/fullPage.js
* MIT licensed
*
* Copyright (C) 2013 alvarotrigo.com - A project by Alvaro Trigo
*/
(function(b){b.fn.fullpage=function(c){function pa(a){a.find(".fp-slides").after('<div class="fp-controlArrow fp-prev"></div><div class="fp-controlArrow fp-next"></div>');"#fff"!=c.controlArrowColor&&(a.find(".fp-controlArrow.fp-next").css("border-color","transparent transparent transparent "+c.controlArrowColor),a.find(".fp-controlArrow.fp-prev").css("border-color","transparent "+c.controlArrowColor+" transparent transparent"));c.loopHorizontal||a.find(".fp-controlArrow.fp-prev").hide()}function qa(){b("body").append('<div id="fp-nav"><ul></ul></div>');
k=b("#fp-nav");k.css("color",c.navigationColor);k.addClass(c.navigationPosition);for(var a=0;a<b(".fp-section").length;a++){var d="";c.anchors.length&&(d=c.anchors[a]);var d='<li><a href="#'+d+'"><span></span></a>',e=c.navigationTooltips[a];void 0!=e&&""!=e&&(d+='<div class="fp-tooltip '+c.navigationPosition+'">'+e+"</div>");d+="</li>";k.find("ul").append(d)}}function R(){b(".fp-section").each(function(){var a=b(this).find(".fp-slide");a.length?a.each(function(){y(b(this))}):y(b(this))});b.isFunction(c.afterRender)&&
c.afterRender.call(this)}function S(){if(!c.autoScrolling||c.scrollBar){var a=b(window).scrollTop(),d=0,e=Math.abs(a-b(".fp-section").first().offset().top);b(".fp-section").each(function(c){var f=Math.abs(a-b(this).offset().top);f<e&&(d=c,e=f)});var f=b(".fp-section").eq(d)}if(!c.autoScrolling&&!f.hasClass("active")){F=!0;var ra=b(".fp-section.active").index(".fp-section")+1,g=G(f),h=f.data("anchor"),k=f.index(".fp-section")+1,l=f.find(".fp-slide.active");if(l.length)var n=l.data("anchor"),t=l.index();
f.addClass("active").siblings().removeClass("active");m||(b.isFunction(c.onLeave)&&c.onLeave.call(this,ra,k,g),b.isFunction(c.afterLoad)&&c.afterLoad.call(this,h,k));H(h,0);c.anchors.length&&!m&&(p=h,I(t,n,h,k));clearTimeout(T);T=setTimeout(function(){F=!1},100)}c.scrollBar&&(clearTimeout(U),U=setTimeout(function(){m||q(f)},1E3))}function V(a){return scrollable=a.find(".fp-slides").length?a.find(".fp-slide.active").find(".fp-scrollable"):a.find(".fp-scrollable")}function z(a,d){if(l[a]){if("down"==
a)var c="bottom",f=b.fn.fullpage.moveSectionDown;else c="top",f=b.fn.fullpage.moveSectionUp;if(0<d.length)if(c="top"===c?!d.scrollTop():"bottom"===c?d.scrollTop()+1+d.innerHeight()>=d[0].scrollHeight:void 0,c)f();else return!0;else f()}}function sa(a){var d=a.originalEvent;if(!W(a.target)){c.autoScrolling&&!c.scrollBar&&a.preventDefault();a=b(".fp-section.active");var e=V(a);m||t||(d=X(d),u=d.y,A=d.x,a.find(".fp-slides").length&&Math.abs(B-A)>Math.abs(v-u)?Math.abs(B-A)>b(window).width()/100*c.touchSensitivity&&
(B>A?l.right&&b.fn.fullpage.moveSlideRight():l.left&&b.fn.fullpage.moveSlideLeft()):c.autoScrolling&&!c.scrollBar&&Math.abs(v-u)>b(window).height()/100*c.touchSensitivity&&(v>u?z("down",e):u>v&&z("up",e)))}}function W(a,d){d=d||0;var e=b(a).parent();return d<c.normalScrollElementTouchThreshold&&e.is(c.normalScrollElements)?!0:d==c.normalScrollElementTouchThreshold?!1:W(e,++d)}function ta(a){a=X(a.originalEvent);v=a.y;B=a.x}function r(a){if(c.autoScrolling){a=window.event||a;var d=Math.max(-1,Math.min(1,
a.wheelDelta||-a.deltaY||-a.detail));c.scrollBar&&(a.preventDefault?a.preventDefault():a.returnValue=!1);a=b(".fp-section.active");a=V(a);m||(0>d?z("down",a):z("up",a));return!1}}function Y(a){var d=b(".fp-section.active").find(".fp-slides");if(d.length&&!t){var e=d.find(".fp-slide.active"),f=null,f="prev"===a?e.prev(".fp-slide"):e.next(".fp-slide");if(!f.length){if(!c.loopHorizontal)return;f="prev"===a?e.siblings(":last"):e.siblings(":first")}t=!0;w(d,f)}}function Z(){b(".fp-slide.active").each(function(){J(b(this))})}
function q(a,d,e){var f=a.position();if("undefined"!==typeof f&&(d={element:a,callback:d,isMovementUp:e,dest:f,dtop:f.top,yMovement:G(a),anchorLink:a.data("anchor"),sectionIndex:a.index(".fp-section"),activeSlide:a.find(".fp-slide.active"),activeSection:b(".fp-section.active"),leavingSection:b(".fp-section.active").index(".fp-section")+1,localIsResizing:x},!(d.activeSection.is(a)&&!x||c.scrollBar&&b(window).scrollTop()===d.dtop))){if(d.activeSlide.length)var g=d.activeSlide.data("anchor"),h=d.activeSlide.index();
c.autoScrolling&&c.continuousVertical&&"undefined"!==typeof d.isMovementUp&&(!d.isMovementUp&&"up"==d.yMovement||d.isMovementUp&&"down"==d.yMovement)&&(d.isMovementUp?b(".fp-section.active").before(d.activeSection.nextAll(".fp-section")):b(".fp-section.active").after(d.activeSection.prevAll(".fp-section").get().reverse()),n(b(".fp-section.active").position().top),Z(),d.wrapAroundElements=d.activeSection,d.dest=d.element.position(),d.dtop=d.dest.top,d.yMovement=G(d.element));a.addClass("active").siblings().removeClass("active");
m=!0;I(h,g,d.anchorLink,d.sectionIndex);b.isFunction(c.onLeave)&&!d.localIsResizing&&c.onLeave.call(this,d.leavingSection,d.sectionIndex+1,d.yMovement);ua(d);p=d.anchorLink;c.autoScrolling&&H(d.anchorLink,d.sectionIndex)}}function ua(a){if(c.css3&&c.autoScrolling&&!c.scrollBar)aa("translate3d(0px, -"+a.dtop+"px, 0px)",!0),setTimeout(function(){ba(a)},c.scrollingSpeed);else{var d=va(a);b(d.element).animate(d.options,c.scrollingSpeed,c.easing).promise().done(function(){ba(a)})}}function va(a){var b=
{};c.autoScrolling&&!c.scrollBar?(b.options={top:-a.dtop},b.element="."+ca):(b.options={scrollTop:a.dtop},b.element="html, body");return b}function wa(a){a.wrapAroundElements&&a.wrapAroundElements.length&&(a.isMovementUp?b(".fp-section:first").before(a.wrapAroundElements):b(".fp-section:last").after(a.wrapAroundElements),n(b(".fp-section.active").position().top),Z())}function ba(a){wa(a);b.isFunction(c.afterLoad)&&!a.localIsResizing&&c.afterLoad.call(this,a.anchorLink,a.sectionIndex+1);setTimeout(function(){m=
!1;b.isFunction(a.callback)&&a.callback.call(this)},600)}function da(){if(!F){var a=window.location.hash.replace("#","").split("/"),b=a[0],a=a[1];if(b.length){var c="undefined"===typeof p,f="undefined"===typeof p&&"undefined"===typeof a&&!t;(b&&b!==p&&!c||f||!t&&K!=a)&&L(b,a)}}}function w(a,d){var e=d.position(),f=a.find(".fp-slidesContainer").parent(),g=d.index(),h=a.closest(".fp-section"),k=h.index(".fp-section"),l=h.data("anchor"),n=h.find(".fp-slidesNav"),m=d.data("anchor"),q=x;if(c.onSlideLeave){var p=
h.find(".fp-slide.active").index(),r;r=p==g?"none":p>g?"left":"right";q||"none"===r||b.isFunction(c.onSlideLeave)&&c.onSlideLeave.call(this,l,k+1,p,r)}d.addClass("active").siblings().removeClass("active");"undefined"===typeof m&&(m=g);!c.loopHorizontal&&c.controlArrows&&(h.find(".fp-controlArrow.fp-prev").toggle(0!=g),h.find(".fp-controlArrow.fp-next").toggle(!d.is(":last-child")));h.hasClass("active")&&I(g,m,l,k);var u=function(){q||b.isFunction(c.afterSlideLoad)&&c.afterSlideLoad.call(this,l,k+
1,m,g);t=!1};c.css3?(e="translate3d(-"+e.left+"px, 0px, 0px)",ea(a.find(".fp-slidesContainer"),0<c.scrollingSpeed).css(fa(e)),setTimeout(function(){u()},c.scrollingSpeed,c.easing)):f.animate({scrollLeft:e.left},c.scrollingSpeed,c.easing,function(){u()});n.find(".active").removeClass("active");n.find("li").eq(g).find("a").addClass("active")}function ga(){ha();if(C){if("text"!==b(document.activeElement).attr("type")){var a=b(window).height();Math.abs(a-M)>20*Math.max(M,a)/100&&(b.fn.fullpage.reBuild(!0),
M=a)}}else clearTimeout(ia),ia=setTimeout(function(){b.fn.fullpage.reBuild(!0)},500)}function ha(){if(c.responsive){var a=g.hasClass("fp-responsive");b(window).width()<c.responsive?a||(b.fn.fullpage.setAutoScrolling(!1,"internal"),b("#fp-nav").hide(),g.addClass("fp-responsive")):a&&(b.fn.fullpage.setAutoScrolling(N.autoScrolling,"internal"),b("#fp-nav").show(),g.removeClass("fp-responsive"))}}function ea(a){var b="all "+c.scrollingSpeed+"ms "+c.easingcss3;a.removeClass("fp-notransition");return a.css({"-webkit-transition":b,
transition:b})}function O(a){return a.addClass("fp-notransition")}function xa(a,d){if(825>a||900>d){var c=Math.min(100*a/825,100*d/900).toFixed(2);b("body").css("font-size",c+"%")}else b("body").css("font-size","100%")}function H(a,d){c.menu&&(b(c.menu).find(".active").removeClass("active"),b(c.menu).find('[data-menuanchor="'+a+'"]').addClass("active"));c.navigation&&(b("#fp-nav").find(".active").removeClass("active"),a?b("#fp-nav").find('a[href="#'+a+'"]').addClass("active"):b("#fp-nav").find("li").eq(d).find("a").addClass("active"))}
function G(a){var d=b(".fp-section.active").index(".fp-section");a=a.index(".fp-section");return d==a?"none":d>a?"up":"down"}function y(a){a.css("overflow","hidden");var b=a.closest(".fp-section"),e=a.find(".fp-scrollable");if(e.length)var f=e.get(0).scrollHeight;else f=a.get(0).scrollHeight,c.verticalCentered&&(f=a.find(".fp-tableCell").get(0).scrollHeight);b=h-parseInt(b.css("padding-bottom"))-parseInt(b.css("padding-top"));f>b?e.length?e.css("height",b+"px").parent().css("height",b+"px"):(c.verticalCentered?
a.find(".fp-tableCell").wrapInner('<div class="fp-scrollable" />'):a.wrapInner('<div class="fp-scrollable" />'),a.find(".fp-scrollable").slimScroll({allowPageScroll:!0,height:b+"px",size:"10px",alwaysVisible:!0})):ja(a);a.css("overflow","")}function ja(a){a.find(".fp-scrollable").children().first().unwrap().unwrap();a.find(".slimScrollBar").remove();a.find(".slimScrollRail").remove()}function ka(a){a.addClass("fp-table").wrapInner('<div class="fp-tableCell" style="height:'+la(a)+'px;" />')}function la(a){var b=
h;if(c.paddingTop||c.paddingBottom)b=a,b.hasClass("fp-section")||(b=a.closest(".fp-section")),a=parseInt(b.css("padding-top"))+parseInt(b.css("padding-bottom")),b=h-a;return b}function aa(a,b){b?ea(g):O(g);g.css(fa(a));setTimeout(function(){g.removeClass("fp-notransition")},10)}function L(a,d){"undefined"===typeof d&&(d=0);var c=isNaN(a)?b('[data-anchor="'+a+'"]'):b(".fp-section").eq(a-1);a===p||c.hasClass("active")?ma(c,d):q(c,function(){ma(c,d)})}function ma(a,b){if("undefined"!=typeof b){var c=
a.find(".fp-slides"),f=c.find('[data-anchor="'+b+'"]');f.length||(f=c.find(".fp-slide").eq(b));f.length&&w(c,f)}}function ya(a,b){a.append('<div class="fp-slidesNav"><ul></ul></div>');var e=a.find(".fp-slidesNav");e.addClass(c.slidesNavPosition);for(var f=0;f<b;f++)e.find("ul").append('<li><a href="#"><span></span></a></li>');e.css("margin-left","-"+e.width()/2+"px");e.find("li").first().find("a").addClass("active")}function I(a,b,e,f){var g="";c.anchors.length?(a?("undefined"!==typeof e&&(g=e),"undefined"===
typeof b&&(b=a),K=b,na(g+"/"+b)):("undefined"!==typeof a&&(K=b),na(e)),D(location.hash)):"undefined"!==typeof a?D(f+"-"+a):D(String(f))}function na(a){if(c.recordHistory)location.hash=a;else if(C||P)history.replaceState(void 0,void 0,"#"+a);else{var b=window.location.href.split("#")[0];window.location.replace(b+"#"+a)}}function D(a){a=a.replace("/","-").replace("#","");b("body")[0].className=b("body")[0].className.replace(/\b\s?fp-viewing-[^\s]+\b/g,"");b("body").addClass("fp-viewing-"+a)}function za(){var a=
document.createElement("p"),b,c={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.insertBefore(a,null);for(var f in c)void 0!==a.style[f]&&(a.style[f]="translate3d(1px,1px,1px)",b=window.getComputedStyle(a).getPropertyValue(c[f]));document.body.removeChild(a);return void 0!==b&&0<b.length&&"none"!==b}function oa(){return window.PointerEvent?{down:"pointerdown",move:"pointermove"}:{down:"MSPointerDown",
move:"MSPointerMove"}}function X(a){var b=[];b.y="undefined"!==typeof a.pageY&&(a.pageY||a.pageX)?a.pageY:a.touches[0].pageY;b.x="undefined"!==typeof a.pageX&&(a.pageY||a.pageX)?a.pageX:a.touches[0].pageX;return b}function J(a){b.fn.fullpage.setScrollingSpeed(0,"internal");w(a.closest(".fp-slides"),a);b.fn.fullpage.setScrollingSpeed(N.scrollingSpeed,"internal")}function n(a){c.scrollBar?g.scrollTop(a):c.css3?aa("translate3d(0px, -"+a+"px, 0px)",!1):g.css("top",-a)}function fa(a){return{"-webkit-transform":a,
"-moz-transform":a,"-ms-transform":a,transform:a}}function Aa(){n(0);b("#fp-nav, .fp-slidesNav, .fp-controlArrow").remove();b(".fp-section").css({height:"","background-color":"",padding:""});b(".fp-slide").css({width:""});g.css({height:"",position:"","-ms-touch-action":"","touch-action":""});b(".fp-section, .fp-slide").each(function(){ja(b(this));b(this).removeClass("fp-table active")});O(g);O(g.find(".fp-easing"));g.find(".fp-tableCell, .fp-slidesContainer, .fp-slides").each(function(){b(this).replaceWith(this.childNodes)});
b("html, body").scrollTop(0)}function Q(a,b,e){c[a]=b;"internal"!==e&&(N[a]=b)}function E(a,b){console&&console[a]&&console[a]("fullPage: "+b)}c=b.extend({menu:!1,anchors:[],navigation:!1,navigationPosition:"right",navigationColor:"#000",navigationTooltips:[],slidesNavigation:!1,slidesNavPosition:"bottom",scrollBar:!1,css3:!0,scrollingSpeed:700,autoScrolling:!0,easing:"easeInQuart",easingcss3:"ease",loopBottom:!1,loopTop:!1,loopHorizontal:!0,continuousVertical:!1,normalScrollElements:null,scrollOverflow:!1,
touchSensitivity:5,normalScrollElementTouchThreshold:5,keyboardScrolling:!0,animateAnchor:!0,recordHistory:!0,controlArrows:!0,controlArrowColor:"#fff",verticalCentered:!0,resize:!0,sectionsColor:[],paddingTop:0,paddingBottom:0,fixedElements:null,responsive:0,sectionSelector:".section",slideSelector:".slide",afterLoad:null,onLeave:null,afterRender:null,afterResize:null,afterReBuild:null,afterSlideLoad:null,onSlideLeave:null},c);(function(){c.continuousVertical&&(c.loopTop||c.loopBottom)&&(c.continuousVertical=
!1,E("warn","Option `loopTop/loopBottom` is mutually exclusive with `continuousVertical`; `continuousVertical` disabled"));c.continuousVertical&&c.scrollBar&&(c.continuousVertical=!1,E("warn","Option `scrollBar` is mutually exclusive with `continuousVertical`; `continuousVertical` disabled"));b.each(c.anchors,function(a,c){(b("#"+c).length||b('[name="'+c+'"]').length)&&E("error","data-anchor tags can not have the same value as any `id` element on the site (or `name` element for IE).")})})();b.extend(b.easing,
{easeInQuart:function(a,b,c,f,g){return f*(b/=g)*b*b*b+c}});b.fn.fullpage.setAutoScrolling=function(a,d){Q("autoScrolling",a,d);var e=b(".fp-section.active");c.autoScrolling&&!c.scrollBar?(b("html, body").css({overflow:"hidden",height:"100%"}),b.fn.fullpage.setRecordHistory(c.recordHistory,"internal"),g.css({"-ms-touch-action":"none","touch-action":"none"}),e.length&&n(e.position().top)):(b("html, body").css({overflow:"visible",height:"initial"}),b.fn.fullpage.setRecordHistory(!1,"internal"),g.css({"-ms-touch-action":"",
"touch-action":""}),n(0),b("html, body").scrollTop(e.position().top))};b.fn.fullpage.setRecordHistory=function(a,b){Q("recordHistory",a,b)};b.fn.fullpage.setScrollingSpeed=function(a,b){Q("scrollingSpeed",a,b)};b.fn.fullpage.setMouseWheelScrolling=function(a){a?document.addEventListener?(document.addEventListener("mousewheel",r,!1),document.addEventListener("wheel",r,!1)):document.attachEvent("onmousewheel",r):document.addEventListener?(document.removeEventListener("mousewheel",r,!1),document.removeEventListener("wheel",
r,!1)):document.detachEvent("onmousewheel",r)};b.fn.fullpage.setAllowScrolling=function(a,c){if("undefined"!=typeof c)c=c.replace(" ","").split(","),b.each(c,function(c,d){switch(d){case "up":l.up=a;break;case "down":l.down=a;break;case "left":l.left=a;break;case "right":l.right=a;break;case "all":b.fn.fullpage.setAllowScrolling(a)}});else if(a){if(b.fn.fullpage.setMouseWheelScrolling(!0),C||P)MSPointer=oa(),b(document).off("touchstart "+MSPointer.down).on("touchstart "+MSPointer.down,ta),b(document).off("touchmove "+
MSPointer.move).on("touchmove "+MSPointer.move,sa)}else if(b.fn.fullpage.setMouseWheelScrolling(!1),C||P)MSPointer=oa(),b(document).off("touchstart "+MSPointer.down),b(document).off("touchmove "+MSPointer.move)};b.fn.fullpage.setKeyboardScrolling=function(a){c.keyboardScrolling=a};b.fn.fullpage.moveSectionUp=function(){var a=b(".fp-section.active").prev(".fp-section");a.length||!c.loopTop&&!c.continuousVertical||(a=b(".fp-section").last());a.length&&q(a,null,!0)};b.fn.fullpage.moveSectionDown=function(){var a=
b(".fp-section.active").next(".fp-section");a.length||!c.loopBottom&&!c.continuousVertical||(a=b(".fp-section").first());a.length&&q(a,null,!1)};b.fn.fullpage.moveTo=function(a,c){var e="",e=isNaN(a)?b('[data-anchor="'+a+'"]'):b(".fp-section").eq(a-1);"undefined"!==typeof c?L(a,c):0<e.length&&q(e)};b.fn.fullpage.moveSlideRight=function(){Y("next")};b.fn.fullpage.moveSlideLeft=function(){Y("prev")};b.fn.fullpage.reBuild=function(a){x=!0;var d=b(window).width();h=b(window).height();c.resize&&xa(h,d);
b(".fp-section").each(function(){parseInt(b(this).css("padding-bottom"));parseInt(b(this).css("padding-top"));c.verticalCentered&&b(this).find(".fp-tableCell").css("height",la(b(this))+"px");b(this).css("height",h+"px");if(c.scrollOverflow){var a=b(this).find(".fp-slide");a.length?a.each(function(){y(b(this))}):y(b(this))}a=b(this).find(".fp-slides");a.length&&w(a,a.find(".fp-slide.active"))});b(".fp-section.active").position();d=b(".fp-section.active");d.index(".fp-section")&&q(d);x=!1;b.isFunction(c.afterResize)&&
a&&c.afterResize.call(this);b.isFunction(c.afterReBuild)&&!a&&c.afterReBuild.call(this)};var t=!1,C=navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|Windows Phone|Tizen|Bada)/),P="ontouchstart"in window||0<navigator.msMaxTouchPoints||navigator.maxTouchPoints,g=b(this),h=b(window).height(),m=!1,x=!1,p,K,k,ca="fullpage-wrapper",l={up:!0,down:!0,left:!0,right:!0},N=jQuery.extend(!0,{},c);b.fn.fullpage.setAllowScrolling(!0);c.css3&&(c.css3=za());b(this).length?(g.css({height:"100%",
position:"relative"}),g.addClass(ca)):E("error","Error! Fullpage.js needs to be initialized with a selector. For example: $('#myContainer').fullpage();");b(c.sectionSelector).each(function(){b(this).addClass("fp-section")});b(c.slideSelector).each(function(){b(this).addClass("fp-slide")});c.navigation&&qa();b(".fp-section").each(function(a){var d=b(this),e=b(this).find(".fp-slide"),f=e.length;a||0!==b(".fp-section.active").length||b(this).addClass("active");b(this).css("height",h+"px");(c.paddingTop||
c.paddingBottom)&&b(this).css("padding",c.paddingTop+" 0 "+c.paddingBottom+" 0");"undefined"!==typeof c.sectionsColor[a]&&b(this).css("background-color",c.sectionsColor[a]);"undefined"!==typeof c.anchors[a]&&b(this).attr("data-anchor",c.anchors[a]);if(1<f){a=100*f;var g=100/f;e.wrapAll('<div class="fp-slidesContainer" />');e.parent().wrap('<div class="fp-slides" />');b(this).find(".fp-slidesContainer").css("width",a+"%");c.controlArrows&&pa(b(this));c.slidesNavigation&&ya(b(this),f);e.each(function(a){b(this).css("width",
g+"%");c.verticalCentered&&ka(b(this))});d=d.find(".fp-slide.active");0==d.length?e.eq(0).addClass("active"):J(d)}else c.verticalCentered&&ka(b(this))}).promise().done(function(){b.fn.fullpage.setAutoScrolling(c.autoScrolling,"internal");var a=b(".fp-section.active").find(".fp-slide.active");a.length&&(0!=b(".fp-section.active").index(".fp-section")||0==b(".fp-section.active").index(".fp-section")&&0!=a.index())&&J(a);c.fixedElements&&c.css3&&b(c.fixedElements).appendTo("body");c.navigation&&(k.css("margin-top",
"-"+k.height()/2+"px"),k.find("li").eq(b(".fp-section.active").index(".fp-section")).find("a").addClass("active"));c.menu&&c.css3&&b(c.menu).closest(".fullpage-wrapper").length&&b(c.menu).appendTo("body");c.scrollOverflow?("complete"===document.readyState&&R(),b(window).on("load",R)):b.isFunction(c.afterRender)&&c.afterRender.call(this);ha();a=window.location.hash.replace("#","").split("/")[0];if(a.length){var d=b('[data-anchor="'+a+'"]');!c.animateAnchor&&d.length&&(c.autoScrolling?n(d.position().top):
(n(0),D(a),b("html, body").scrollTop(d.position().top)),H(a,null),b.isFunction(c.afterLoad)&&c.afterLoad.call(this,a,d.index(".fp-section")+1),d.addClass("active").siblings().removeClass("active"))}b(window).on("load",function(){var a=window.location.hash.replace("#","").split("/"),b=a[0],a=a[1];b&&L(b,a)})});var T,U,F=!1;b(window).on("scroll",S);var v=0,B=0,u=0,A=0;b(window).on("hashchange",da);b(document).keydown(function(a){if(c.keyboardScrolling&&c.autoScrolling&&(40!=a.which&&38!=a.which||a.preventDefault(),
!m))switch(a.which){case 38:case 33:b.fn.fullpage.moveSectionUp();break;case 40:case 34:b.fn.fullpage.moveSectionDown();break;case 36:b.fn.fullpage.moveTo(1);break;case 35:b.fn.fullpage.moveTo(b(".fp-section").length);break;case 37:b.fn.fullpage.moveSlideLeft();break;case 39:b.fn.fullpage.moveSlideRight()}});b(document).on("click touchstart","#fp-nav a",function(a){a.preventDefault();a=b(this).parent().index();q(b(".fp-section").eq(a))});b(document).on("click touchstart",".fp-slidesNav a",function(a){a.preventDefault();
a=b(this).closest(".fp-section").find(".fp-slides");var c=a.find(".fp-slide").eq(b(this).closest("li").index());w(a,c)});c.normalScrollElements&&(b(document).on("mouseenter",c.normalScrollElements,function(){b.fn.fullpage.setMouseWheelScrolling(!1)}),b(document).on("mouseleave",c.normalScrollElements,function(){b.fn.fullpage.setMouseWheelScrolling(!0)}));b(".fp-section").on("click touchstart",".fp-controlArrow",function(){b(this).hasClass("fp-prev")?b.fn.fullpage.moveSlideLeft():b.fn.fullpage.moveSlideRight()});
b(window).resize(ga);var M=h,ia;b.fn.fullpage.destroy=function(a){b.fn.fullpage.setAutoScrolling(!1,"internal");b.fn.fullpage.setAllowScrolling(!1);b.fn.fullpage.setKeyboardScrolling(!1);b(window).off("scroll",S).off("hashchange",da).off("resize",ga);b(document).off("click","#fp-nav a").off("mouseenter","#fp-nav li").off("mouseleave","#fp-nav li").off("click",".fp-slidesNav a").off("mouseover",c.normalScrollElements).off("mouseout",c.normalScrollElements);b(".fp-section").off("click",".fp-controlArrow");
a&&Aa()}}})(jQuery);

View File

@ -0,0 +1,439 @@
/*
* Released under BSD License
* Copyright (c) 2019-2020 Allen_sun_js@hotmail.com
*
* Project Home:
* https://github.com/allensunjian
*/
(function ($w, temp) {
var obj = null
var Jm = $w[temp], name = 'menu', $d = $w['document'], menuEvent = 'oncontextmenu', clickEvent = 'onclick', $c = function (tag) { return $d.createElement(tag); }, _noop = function () { }, logger = (typeof console === 'undefined') ? { log: _noop, debug: _noop, error: _noop, warn: _noop, info: _noop } : console;
var $t = function (n, t) { if (n.hasChildNodes()) { n.firstChild.nodeValue = t; } else { n.appendChild($d.createTextNode(t)); } };
var $h = function (n, t) {
if (t instanceof HTMLElement) {
t.innerHTML = "";
n.appendChild(t)
} else {
n.innerHTML = t;
}
};
if (!Jm || Jm[name]) return;
Jm.menu = function (_jm) {
obj = this
this._get_menu_options(_jm, function () {
this.init(_jm);
this._mount_events()
})
}
Jm.menu.prototype = {
defaultDataMap: {
funcMap: {
edit: {
isDepNode: true,
// defaultFn不受到中台变量的控制始终会先于fn去执行
defaultFn: function (node) {
var f = this._menu_default_mind_methods._menu_begin_edit.call(this.jm);
f && this._menu_default_mind_methods._menu_edit_node_begin(this.jm.view, node);
},
fn: _noop,
text: 'edit node'
},
addChild: {
isDepNode: true,
fn: function (nodeid,text) {
var selected_node = this.get_selected_node();
if (selected_node) {
var node = this.add_node(selected_node, nodeid, text);
if (node) {
this.select_node(nodeid);
this.begin_edit(nodeid);
obj._mount_events();
}
}
},
text: 'append child'
},
addBrother: {
isDepNode: true,
fn: function (nodeid,text) {
var selected_node = this.get_selected_node();
if (selected_node && !selected_node.isroot) {
var node = this.insert_node_after(selected_node, nodeid, text);
if (node) {
this.select_node(nodeid);
this.begin_edit(nodeid);
obj._mount_events();
}
}
},
text: 'append brother'
},
delete: {
isDepNode: true,
fn: function () {
this.shortcut.handle_delnode.call(this.shortcut, this);
},
text: 'delete node'
},
showAll: {
sDepNode: false,
fn: function () {
this.expand_all(this)
},
text: 'show all'
},
hideAll: {
isDepNode: false,
fn: function () {
this.collapse_all(this)
},
text: 'hide all'
},
screenshot: {
isDepNode: false,
fn: function () {
if (!this.screenshot) {
logger.error('[jsmind] screenshot dependent on jsmind.screenshot.js !');
return;
}
this.screenshot.shootDownload();
},
text: 'load mind picture'
},
showNode: {
isDepNode: true,
fn: function (node) {
this.expand_node(node);
},
text: 'show target node'
},
hideNode: {
isDepNode: true,
fn: function (node) {
this.collapse_node(node);
},
text: 'hide target node'
},
},
menuStl: {
'width': '150px',
'padding': '12px 0',
'position': 'fixed',
'z-index': '10',
'background': '#fff',
'box-shadow': '0 2px 12px 0 rgba(0,0,0,0.1)',
'border-radius': '5px',
'font-size': '12px',
'display': 'none'
},
menuItemStl:{
padding: '5px 15px',
cursor: 'pointer',
display: 'block',
'text-align': 'center',
'transition':'all .2s'
},
injectionList:['edit','addChild','delete']
},
init: function (_jm) {
this._create_menu(_jm);
this._get_injectionList(_jm);
this.menuOpts.switchMidStage && Jm.util.dom.add_event(_jm.view.e_editor , 'blur', function () {
this._menu_default_mind_methods._menu_edit_node_end.call(_jm.view);
if(typeof this.menuOpts.editCaller == 'function') {
this.menuOpts.editCaller($w.menu._update_node_info, this._menu_default_mind_methods._menu_update_edit_node)
return
}
this._menu_default_mind_methods._menu_update_edit_node();
}.bind(this));
},
_event_contextMenu (e) {
e.preventDefault();
this.menu.style.left = e.clientX + 'px';
this.menu.style.top = e.clientY + 'px';
this.menu.style.display = 'block';
this.selected_node = this.jm.get_selected_node();
} ,
_event_hideMenu() {
this.menu.style.display = 'none'
},
_mount_events () {
var items = document.getElementsByTagName('jmnode')
for(let i = 0; i < items.length; i++) {
items[i][menuEvent] = this._event_contextMenu.bind(this);
}
$w[clickEvent] = this._event_hideMenu.bind(this);
},
_create_menu (_jm) {
var d = $c('menu');
this._set_menu_wrap_syl(d);
this.menu = d;
this.e_panel = _jm.view.e_panel;
this.e_panel.appendChild(d);
},
_create_menu_item (j, text, fn, isDepNode,cb, defaultFn) {
var d = $c('menu-item'),_this = this;
this._set_menu_item_syl(d);
d.innerText = text;
d.addEventListener('click', function () {
if (this.selected_node || !isDepNode) {
defaultFn.call(_this, this.selected_node);
if (!_this._get_mid_opts()) {
cb(this.selected_node, _noop)
fn.call(j,Jm.util.uuid.newid(), this.menuOpts.newNodeText || 'New Node');
return;
}
cb(this.selected_node,_this._mid_stage_next(function () {
var retArgs = [this.selected_node],
argus = Array.prototype.slice.call(arguments[0],0);
argus[1] = this.menuOpts.newNodeText || 'New Node';
if (argus[0]) {
retArgs = argus
}
fn.apply(j,retArgs);
}.bind(this)))
return
}
alert(this.menuOpts.tipContent || 'Continue with node selected')
}.bind(this))
d.addEventListener('mouseover', function () {
d.style.background = 'rgb(179, 216, 255)'
}.bind(this))
d.addEventListener('mouseleave', function () {
d.style.background = '#fff'
}.bind(this))
return d
},
_set_menu_wrap_syl (d) {
var os = this._get_option_sty('menu',this._get_mixin_sty);
d.style.cssText = this._format_cssText(os);
},
_set_menu_item_syl (d) {
var os = this._get_option_sty('menuItem',this._get_mixin_sty);
d.style.cssText = this._format_cssText(os)
},
_format_cssText (o) {
var text = '';
Object.keys(o).forEach(function (k) {
text += k +':'+o[k] +';'
})
return text;
},
_empty_object (o) {
return Object.keys(o).length == 0? true :false
},
_get_option_sty (type, fn) {
var sty = this.menuOpts.style,
menu = this.defaultDataMap.menuStl,
menuItem = this.defaultDataMap.menuItemStl,
o = {menu,menuItem}
if (!sty) return o[type];
if (!sty[type]) return o[type];
if (!sty[type] || this._empty_object(sty[type])) return o[type];
return fn( o[type],sty[type])
},
_get_mixin_sty (dSty, oSty) {
var o = {};
Object.keys(oSty).forEach(function (k) {
o[k] = oSty[k];
})
Object.keys(dSty).forEach(function (k) {
if (!o[k]) o[k] = dSty[k];
})
return o
},
_get_menu_options (j, fn) {
var options = j.options;
if (!options.menuOpts) return;
if (!options.menuOpts.showMenu) return;
this.menuOpts = j.options.menuOpts
fn.call(this)
},
_get_injectionDetail () {
var iLs = this.menuOpts.injectionList,
dLs = this.defaultDataMap.injectionList;
if (!iLs) return dLs;
if (!Array.isArray(iLs)) {
logger.error('[jsmind] injectionList must be a Array');
return;
}
if (iLs.length == 0) return dLs;
return iLs
},
_get_injectionList (j) {
var list = this._get_injectionDetail(),
_this = this;
list.forEach(function (k) {
var o = null,
text = "",
callback = _noop,
defaultFn = _noop;
if (typeof k == 'object') {
o = _this.defaultDataMap.funcMap[k.target];
text = k.text;
k.callback && (callback = k.callback);
} else {
o = _this.defaultDataMap.funcMap[k];
text = o.text;
}
if (o.defaultFn) defaultFn = o.defaultFn;
_this.menu.appendChild(_this._create_menu_item(j ,text, o.fn, o.isDepNode,callback, defaultFn));
})
},
_get_mid_opts () {
var b = this.menuOpts.switchMidStage;
if (!b) return false;
if (typeof b !== 'boolean') {
logger.error('[jsmind] switchMidStage must be Boolean');
return false;
}
return b
},
_switch_view_db_event () {
Jm.prototype.dblclick_handle = _noop;
Jm.shortcut_provider.prototype.handler = _noop;
Jm.view_provider.prototype.edit_node_end = _noop;
},
_mid_stage_next (fn) {
return function () {
fn(arguments);
}
},
_reset_mind_event_edit () {},
_menu_default_mind_methods: {
_menu_begin_edit: function () {
var f = this.get_editable();
if (!f) {
logger.error('fail, this mind map is not editable.');
}
return f;
},
_menu_edit_node_begin (scope, node) {
if (!node.topic) {
logger.warn("don't edit image nodes");
return;
}
if (scope.editing_node != null) {
this._menu_default_mind_methods._menu_edit_node_end.call(scope);
}
scope.editing_node = node;
var view_data = node._data.view;
var element = view_data.element;
var topic = node.topic;
var ncs = getComputedStyle(element);
scope.e_editor.value = topic;
scope.e_editor.style.width = (element.clientWidth - parseInt(ncs.getPropertyValue('padding-left')) - parseInt(ncs.getPropertyValue('padding-right'))) + 'px';
element.innerHTML = '';
element.appendChild(scope.e_editor);
element.style.zIndex = 5;
scope.e_editor.focus();
scope.e_editor.select();
},
_menu_edit_node_end: function () {
if (this.editing_node != null) {
var node = this.editing_node;
this.editing_node = null;
var view_data = node._data.view;
var element = view_data.element;
var topic = this.e_editor.value;
element.style.zIndex = 'auto';
element.removeChild(this.e_editor);
$w.menu._update_node_info = {id: node.id, topic: topic};
if (Jm.util.text.is_empty(topic) || node.topic === topic) {
if (this.opts.support_html) {
$h(element, node.topic);
} else {
$t(element, node.topic);
}
}
}
},
_menu_update_edit_node: function () {
var info = $w.menu._update_node_info;
$w.menu.jm.update_node(info.id, info.topic);
}
}
}
var plugin = new Jm.plugin('menu',function (_jm) {
$w.menu = new Jm.menu(_jm);
$w.menu.jm = _jm;
if($w.menu.menuOpts) _jm.menu = $w.menu;
})
Jm.register_plugin(plugin)
function preventMindEventDefault() {
Jm.menu.prototype._switch_view_db_event();
}
Jm.preventMindEventDefault = preventMindEventDefault
// eslint-disable-next-line no-unused-vars
function reBuild () {
if (obj !== null) {
obj._mount_events()
}
}
// export reBuild
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = {
reBuild,
init: function (opt) {
Jm = opt
}
};
}
// exports = {
// reBuild,
// // init: function (opt) {
// // console.log(opt)
// // }
// };
// $w['render'] = function () {
// if (obj !== null) {
// obj._mount_events()
// }
// }
})(window, 'jsMind')

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
[{"label": "汉族", "value": "汉族"}, {"label": "壮族", "value": "壮族"}, {"label": "蒙古族", "value": "蒙古族"}, {"label": "回族", "value": "回族"}, {"label": "藏族", "value": "藏族"}, {"label": "维吾尔族", "value": "维吾尔族"}, {"label": "苗族", "value": "苗族"}, {"label": "彝族", "value": "彝族"}, {"label": "布依族", "value": "布依族"}, {"label": "朝鲜族", "value": "朝鲜族"}, {"label": "满族", "value": "满族"}, {"label": "侗族", "value": "侗族"}, {"label": "瑶族", "value": "瑶族"}, {"label": "白族", "value": "白族"}, {"label": "东乡族", "value": "东乡族"}, {"label": "锡伯族", "value": "锡伯族"}, {"label": "土家族", "value": "土家族"}, {"label": "哈尼族", "value": "哈尼族"}, {"label": "哈萨克族", "value": "哈萨克族"}, {"label": "傣族", "value": "傣族"}, {"label": "黎族", "value": "黎族"}, {"label": "僳僳族", "value": "僳僳族"}, {"label": "佤族", "value": "佤族"}, {"label": "畲族", "value": "畲族"}, {"label": "拉祜族", "value": "拉祜族"}, {"label": "水族", "value": "水族"}, {"label": "纳西族", "value": "纳西族"}, {"label": "景颇族", "value": "景颇族"}, {"label": "柯尔克孜族", "value": "柯尔克孜族"}, {"label": "土族", "value": "土族"}, {"label": "高山族", "value": "高山族"}, {"label": "达斡尔族", "value": "达斡尔族"}, {"label": "仫佬族", "value": "仫佬族"}, {"label": "羌族", "value": "羌族"}, {"label": "撒拉族", "value": "撒拉族"}, {"label": "德昂族", "value": "德昂族"}, {"label": "仡佬族", "value": "仡佬族"}, {"label": "阿昌族", "value": "阿昌族"}, {"label": "普米族", "value": "普米族"}, {"label": "布朗族", "value": "布朗族"}, {"label": "塔吉克族", "value": "塔吉克族"}, {"label": "怒族", "value": "怒族"}, {"label": "乌孜别克族", "value": "乌孜别克族"}, {"label": "俄罗斯族", "value": "俄罗斯族"}, {"label": "鄂温克族", "value": "鄂温克族"}, {"label": "毛南族", "value": "毛南族"}, {"label": "保安族", "value": "保安族"}, {"label": "裕固族", "value": "裕固族"}, {"label": "京族", "value": "京族"}, {"label": "塔塔尔族", "value": "塔塔尔族"}, {"label": "独龙族", "value": "独龙族"}, {"label": "鄂伦春族", "value": "鄂伦春族"}, {"label": "赫哲族", "value": "赫哲族"}, {"label": "门巴族", "value": "门巴族"}, {"label": "珞巴族", "value": "珞巴族"}, {"label": "基诺族", "value": "基诺族"}]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"上海": "上海市", "云南": "云南省", "内蒙古": "内蒙古自治区", "北京": "北京市", "台湾": "台湾省", "吉林": "吉林省", "四川": "四川省", "天津": "天津市", "宁夏": "宁夏回族自治区", "安徽": "安徽省", "山东": "山东省", "山西": "山西省", "广东": "广东省", "广西": "广西壮族自治区", "新疆": "新疆维吾尔自治区", "江苏": "江苏省", "江西": "江西省", "河北": "河北省", "河南": "河南省", "浙江": "浙江省", "海南": "海南省", "湖北": "湖北省", "湖南": "湖南省", "澳门": "澳门特别行政区", "甘肃": "甘肃省", "福建": "福建省", "西藏": "西藏自治区", "贵州": "贵州省", "辽宁": "辽宁省", "重庆": "重庆市", "陕西": "陕西省", "青海": "青海省", "香港": "香港特别行政区", "黑龙江": "黑龙江省"}

View File

@ -33,7 +33,7 @@ export default {
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {mapNodes, mapRelationships, getGraphStats} from './mapper'
import store from '@/store'
export class GraphEventHandler {
constructor (graph, graphView, getNodeNeighbours, onItemMouseOver, onItemSelected, onGraphModelChange) {
this.graph = graph
this.graphView = graphView
this.getNodeNeighbours = getNodeNeighbours
this.selectedItem = null
this.onItemMouseOver = onItemMouseOver
this.onItemSelected = onItemSelected
this.onGraphModelChange = onGraphModelChange
}
graphModelChanged () {
this.onGraphModelChange(getGraphStats(this.graph))
}
selectItem (item) {
if (this.selectedItem) {
this.selectedItem.selected = false
}
this.selectedItem = item
item.selected = true
this.graphView.update()
}
deselectItem () {
if (this.selectedItem) {
this.selectedItem.selected = false
this.selectedItem = null
}
this.onItemSelected({type: 'canvas', item: {nodeCount: this.graph.nodes().length, relationshipCount: this.graph.relationships().length}})
this.graphView.update()
}
nodeClose (d) {
this.graph.removeConnectedRelationships(d)
this.graph.removeNode(d)
this.deselectItem()
this.graphView.update()
this.graphModelChanged()
}
nodeClicked (d) {
d.fixed = true
if (!d.selected) {
this.selectItem(d)
this.onItemSelected({type: 'node', item: {id: d.id, labels: d.labels, properties: d.propertyList}})
// 存储节点数据到vuex中其他模块可使用mutation函数似乎只能传一个参数
store.commit('kg/SET_KG', {type: 'node', item: {id: d.id, labels: d.labels, properties: d.propertyList}})
} else {
this.deselectItem()
}
}
nodeUnlock (d) {
d.fixed = false
this.deselectItem()
}
nodeDblClicked (d) {
const graph = this.graph
const graphView = this.graphView
const graphModelChanged = this.graphModelChanged.bind(this)
this.getNodeNeighbours(d, this.graph.findNodeNeighbourIds(d.id), function ({nodes, relationships}) {
graph.addNodes(mapNodes(nodes))
graph.addRelationships(mapRelationships(relationships, graph))
graphView.update()
graphModelChanged()
})
}
onNodeMouseOver (node) {
if (!node.contextMenu) {
this.onItemMouseOver({type: 'node', item: {id: node.id, labels: node.labels, properties: node.propertyList}})
}
}
onMenuMouseOver (itemWithMenu) {
this.onItemMouseOver({type: 'context-menu-item', item: {label: itemWithMenu.contextMenu.label, content: itemWithMenu.contextMenu.menuContent, selection: itemWithMenu.contextMenu.menuSelection}})
}
onRelationshipMouseOver (relationship) {
this.onItemMouseOver({type: 'relationship', item: {id: relationship.id, type: relationship.type, properties: relationship.propertyList}})
}
onRelationshipClicked (relationship) {
if (!relationship.selected) {
this.selectItem(relationship)
this.onItemSelected({type: 'relationship', item: {id: relationship.id, type: relationship.type, properties: relationship.propertyList}})
// 存储关系数据到vuex中其他模块可使用
store.commit('kg/SET_KG', {type: 'relation', item: {id: relationship.id, labels: [relationship.type], properties: relationship.propertyList}})
} else {
this.deselectItem()
}
}
onCanvasClicked () {
this.deselectItem()
}
onItemMouseOut (item) {
this.onItemMouseOver({type: 'canvas', item: {nodeCount: this.graph.nodes().length, relationshipCount: this.graph.relationships().length}})
}
bindEventHandlers () {
this.graphView
.on('nodeMouseOver', this.onNodeMouseOver.bind(this))
.on('nodeMouseOut', this.onItemMouseOut.bind(this))
.on('menuMouseOver', this.onMenuMouseOver.bind(this))
.on('menuMouseOut', this.onItemMouseOut.bind(this))
.on('relMouseOver', this.onRelationshipMouseOver.bind(this))
.on('relMouseOut', this.onItemMouseOut.bind(this))
.on('relationshipClicked', this.onRelationshipClicked.bind(this))
.on('canvasClicked', this.onCanvasClicked.bind(this))
.on('nodeClose', this.nodeClose.bind(this))
.on('nodeClicked', this.nodeClicked.bind(this))
.on('nodeDblClicked', this.nodeDblClicked.bind(this))
.on('nodeUnlock', this.nodeUnlock.bind(this))
this.onItemMouseOut()
}
}

View File

@ -0,0 +1,228 @@
<template>
<div
:style="[{'width': '100%'},
{'height': '75vh'},
{'padding-top': '10px'},
{'display': extendStyle.display}]">
<ExplorerComponent
ref="ExplorerComponent"
:maxNeighbours="maxNeighbours"
:initialNodeDisplay="initialNodeDisplay"
:graphStyleData="graphStyleData"
:updateStyle="updateStyle"
:getNeighbours="getNeighbours"
:nodes="state.nodesAndRelationships.nodes"
:relationships="state.nodesAndRelationships.relationships"
:fullscreen="fullscreen"
:frameHeight="frameHeight"
:assignVisElement="assignVisElement"
:getAutoCompleteCallback="(callback) => { this.autoCompleteCallback = callback }"
:setGraph="setGraph"
@clickNode="handleClickNode"
></ExplorerComponent>
</div>
</template>
<script type="text/ecmascript-6">
import bolt from './services/bolt/bolt'
import ExplorerComponent from './components/Explorer'
import { dim } from './constants'
import Vue from 'vue'
import { setting } from '@/config'
const neo4j = require('neo4j-driver');
export default{
data(){
return{
dim: dim,
state: {
nodesAndRelationships: {
nodes: [],
relationships: []
},
justInitiated: true
},
graphStyleData: {},
frameHeight: 500,
graph: {}
}
},
props: {
clearAll: {
type: Boolean,
default: false
},
fullscreen: {
type: Boolean,
default: false
},
extendStyle: {
type: Object,
default() {
return {}
}
},
maxNeighbours: {
type: Number,
default: 100
},
initialNodeDisplay: '',
assignVisElement: Function,
records: {
type: Array,
default() {
return [];
}
}
},
components:{
ExplorerComponent
},
created() {
},
mounted() {
let me = this;
this.frameHeight = $(this.$el).height() * 1.2;
this.driver = neo4j.driver(setting.neo4jUrl, neo4j.auth.basic(setting.neo4jUserName, setting.neo4jPassword));
me.componentWillMount();
},
methods: {
handleClickNode(item) {
this.$emit('clickNode', item);
},
updateStyle(graphStyleData) {
if (JSON.stringify(graphStyleData) == JSON.stringify(this.graphStyleData)) return;
let inlineGraphStyleData = {};
for (let nodeKey in this.graphStyleData) {
inlineGraphStyleData[nodeKey] = this.graphStyleData[nodeKey];
}
for (var nodeKey in graphStyleData) {
let node = inlineGraphStyleData[nodeKey];
if (!node) node = {};
for (var propKey in graphStyleData[nodeKey]) {
node[propKey] = graphStyleData[nodeKey][propKey];
}
inlineGraphStyleData[nodeKey] = node;
}
this.graphStyleData = inlineGraphStyleData;
this.$refs.ExplorerComponent.reloadPanel();
},
componentWillMount () {
if (this.records && this.records.length > 0) {
this.populateDataToStateFromProps(this)
}
},
shouldComponentUpdate (nextProps) {
return nextProps.extendStyle !== this.extendStyle ||
nextProps.records !== this.records ||
nextProps.graphStyleData !== this.graphStyleData
},
populateDataToStateFromProps (props) {
Vue.set(this.state, 'nodesAndRelationships', bolt.extractNodesAndRelationshipsFromRecordsForOldVis(props.records));
},
mergeToList (list1, list2) {
return list1.concat(list2.filter(itemInList2 => list1.findIndex(itemInList1 => itemInList1.id === itemInList2.id) < 0))
},
autoCompleteRelationships (existingNodes, newNodes) {
if (this.autoComplete) {
const existingNodeIds = existingNodes.map(node => parseInt(node.id))
const newNodeIds = newNodes.map(node => parseInt(node.id))
this.getInternalRelationships(existingNodeIds, newNodeIds)
.then((graph) => {
this.autoCompleteCallback && this.autoCompleteCallback(graph.relationships)
})
.catch((e) => {})
}
},
getNeighbours (id, currentNeighbourIds = []) {
const query = `MATCH path = (a)--(o)
WHERE id(a) = ${id}
AND NOT (id(o) IN[${currentNeighbourIds.join(',')}])
RETURN path, size((a)--()) as c
ORDER BY id(o)
LIMIT ${this.maxNeighbours - currentNeighbourIds.length}`
return new Promise((resolve, reject) => {
var session = this.driver.session();
session.run(query, {})
.then((result) => {
let count = result.records.length > 0 ? parseInt(result.records[0].get('c').toString()) : 0
const resultGraph = bolt.extractNodesAndRelationshipsFromRecordsForOldVis(result.records, false)
this.autoCompleteRelationships(this.graph._nodes, resultGraph.nodes)
resolve({...resultGraph, count: count})
session.close();
}).catch((error) => {
console.log(error);
});
})
},
getInternalRelationships (existingNodeIds, newNodeIds) {
newNodeIds = newNodeIds.map(bolt.neo4j.int)
existingNodeIds = existingNodeIds.map(bolt.neo4j.int)
existingNodeIds = existingNodeIds.concat(newNodeIds)
const query = 'MATCH (a)-[r]->(b) WHERE id(a) IN $existingNodeIds AND id(b) IN $newNodeIds RETURN r;'
return new Promise((resolve, reject) => {
var session = this.driver.session();
session.run(query, {})
.then((result) => {
resolve({...bolt.extractNodesAndRelationshipsFromRecordsForOldVis(result.records, false)})
session.close();
}).catch((error) => {
reject({nodes: [], rels: []})
});
})
},
setGraph (graph) {
this.graph = graph
this.autoCompleteRelationships([], this.graph._nodes)
}
},
watch: {
records: {
handler: function(val, oldVal) {
this.populateDataToStateFromProps(this);
},
deep: true
},
state: {
handler: function(val, oldVal) {
},
deep: true
},
'extendStyle.display': {
handler: function(val, oldVal) {
Vue.set(this.state, 'justInitiated', false);
},
deep: true
},
clearAll: {
handler: function(val, oldVal) {
if (val) {
this.$refs.ExplorerComponent.reloadPanel(true);
}
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,260 @@
<template>
<div
id='svg-vis'
:style="[{'height': '100%'},
{'padding-top': (legendRowHeight * 2) + 1 + 'px'},
{'padding-bottom': forcePaddingBottom ? forcePaddingBottom + 2 * pMarginTop + 'px' : '39px'}]">
<LegendComponent
v-if="state.freezeLegend"
ref="LegendComponent"
:stats="stats"
:graphStyle="neoGraphStyle()"
:graphStyleString="state.graphStyleString"
:onSelectedLabel="onSelectedLabel"
:onSelectedRelType="onSelectedRelType" />
<LegendComponent
v-else
ref="LegendComponent"
:stats="stats"
:graphStyle="state.graphStyle"
:graphStyleString="state.graphStyleString"
:onSelectedLabel="onSelectedLabel"
:onSelectedRelType="onSelectedRelType" />
<GraphComponent
ref="GraphComponent"
:fullscreen="fullscreen"
:frameHeight="frameHeight"
:relationships="state.relationships"
:nodes="state.nodes"
:getNodeNeighbours="getNodeNeighbours"
:onItemMouseOver="onItemMouseOver"
:onItemSelect="onItemSelect"
:graphStyle="state.graphStyle"
:onGraphModelChange="onGraphModelChange"
:assignVisElement="assignVisElement"
:getAutoCompleteCallback="getAutoCompleteCallback"
:setGraph="setGraph" />
<InspectorComponent
ref="InspectorComponent"
:fullscreen="fullscreen"
:hoveredItem="hoveredItem"
:selectedItem="selectedItem"
:graphStyle="state.graphStyle"
:graphStyleString="state.graphStyleString"
:updateStyle="updateStyle"
:onExpandToggled="onInspectorExpandToggled" />
</div>
</template>
<script type="text/ecmascript-6">
import GraphComponent from './Graph'
import neoGraphStyle from '../graphStyle'
import InspectorComponent from './Inspector'
import LegendComponent from './Legend'
import Vue from 'vue'
const deduplicateNodes = (nodes) => {
return nodes.reduce((all, curr) => {
if (all.taken.indexOf(curr.id) > -1) {
return all
} else {
all.nodes.push(curr)
all.taken.push(curr.id)
return all
}
}, {nodes : [], taken : []}).nodes
}
export default{
components : {
GraphComponent,
InspectorComponent,
LegendComponent
},
data(){
return {
neoGraphStyle: neoGraphStyle,
legendRowHeight : 32,
forcePaddingBottom : 20,
pMarginTop: 6,
stats: {labels : {}, relTypes : {}},
selectedItem: {},
hoveredItem: {},
graphStyle: {}
}
},
props : {
graphStyleData : Object,
nodes : {
type : Array,
default() {
return []
}
},
relationships : {
type : Array,
default() {
return []
}
},
fullscreen : {
type : Boolean,
default : false
},
frameHeight : {
type : Number,
default : 0
},
assignVisElement : Function,
getAutoCompleteCallback : {
type : Function
},
setGraph : Function,
updateStyle: Function,
getNeighbours: Function
},
created() {
let state = {}
state.stats = {labels : {}, relTypes : {}}
state.graphStyle = neoGraphStyle();
if (!$.isEmptyObject(this.graphStyleData)) {
state.graphStyle.loadRules(this.graphStyleData)
} else {
state.graphStyle.resetToDefault()
}
state.graphStyleString = state.graphStyle.toString();
let nodes = deduplicateNodes(this.nodes)
state.relationships = this.relationships
state.nodes = nodes
state.relationships = this.relationships
if (nodes.length > parseInt(this.initialNodeDisplay)) {
state.nodes = nodes.slice(0, this.initialNodeDisplay)
state.relationships = this.relationships.filter((item) =>
state.nodes.filter((node) =>
node.id === item.startNodeId).length > 0 && state.nodes.filter((node) =>
node.id === item.endNodeId).length > 0)
state.selectedItem = {
type : 'status-item',
item : `Not all return nodes are being displayed due to Initial Node Display setting. Only ${this.initialNodeDisplay} of ${nodes.length} nodes are being displayed`
}
}
this.state = state;
},
mounted() {
},
methods : {
reloadPanel(forceReload) {
this.$refs.GraphComponent.reloadPanel(forceReload);
},
getNodeNeighbours (node, currentNeighbours, callback) {
if (currentNeighbours.length > this.maxNeighbours) {
callback({nodes : [], relationships : []})
}
this.getNeighbours(node.id, currentNeighbours).then((result) => {
let nodes = result.nodes
if (result.count > (this.maxNeighbours - currentNeighbours.length)) {
this.selectedItem = {
type : 'status-item',
item : `Rendering was limited to ${this.maxNeighbours} of the node's total ${result.count + currentNeighbours.length} neighbours due to browser config maxNeighbours.`
}
Vue.set(this.state, 'selectedItem', {
type : 'status-item',
item : `Rendering was limited to ${this.maxNeighbours} of the node's total ${result.count + currentNeighbours.length} neighbours due to browser config maxNeighbours.`
})
}
callback({nodes : nodes, relationships : result.relationships})
}, () => {
callback({nodes : [], relationships : []})
})
},
onItemMouseOver (item) {
this.hoveredItem = item;
},
onItemSelect (item) {
this.selectedItem = item;
switch(item.type) {
case 'node':
this.$emit('clickNode', item.item)
break;
case 'relationship':
this.$emit('clickNode', null)
break;
case 'canvas':
this.$emit('clickNode', null)
break;
}
},
onGraphModelChange (stats) {
this.stats = stats;
Vue.set(this.state, 'graphStyleString', this.state.graphStyle.toString());
this.updateStyle(this.state.graphStyle.toSheet())
},
onSelectedLabel (label, propertyKeys) {
this.selectedItem = {
type : 'legend-item',
item : {
selectedLabel : {label : label, propertyKeys : propertyKeys},
selectedRelType : null
}
};
},
onSelectedRelType (relType, propertyKeys) {
this.selectedItem = {
type : 'legend-item',
item : {
selectedLabel : null,
selectedRelType : {relType : relType, propertyKeys : propertyKeys}
}
}
},
onInspectorExpandToggled (contracted, inspectorHeight) {
this.inspectorContracted = contracted;
this.forcePaddingBottom = inspectorHeight;
}
},
watch : {
nodes: {
handler: function(val, oldVal) {
Vue.set(this.state, 'nodes', deduplicateNodes(val));
},
deep: true
},
relationships: {
handler: function(val, oldVal) {
Vue.set(this.state, 'relationships', val);
},
deep: true
},
graphStyleData : {
handler : function(val, oldVal) {
if (val) {
this.state.graphStyle.loadRules(val);
Vue.set(this.state, 'graphStyleString', this.state.graphStyle.toString());
} else {
this.state.graphStyle.resetToDefault()
Vue.set(this.state, 'graphStyleString', this.state.graphStyle.toString());
}
},
deep : true
},
freezeLegend: {
handler: function(val, oldVal) {
if (val) {
this.state.freezeLegend = false;
Vue.set(this.state, 'graphStyleString', this.state.graphStyle.toString());
this.updateStyle(this.state.graphStyle.toSheet())
}
},
deep: true
}
}
}
</script>

View File

@ -0,0 +1,216 @@
<template>
<StyledSvgWrapper>
<svg class='neod3viz' ref="graphInit"/>
<div v-if="true" style="position: absolute; right: 0px; bottom: 12px;">
<div
style="float:right;margin-right:5px;font-size:25px;cursor:pointer;"
:class="[state.zoomInLimitReached ? 'faded zoom-in' : 'zoom-in']"
@click="zoomInClicked">
+
</div>
<div
style="float:left;margin-right:10px;font-size:25px;cursor:pointer;"
:class="[state.zoomOutLimitReached ? 'faded zoom-out' : 'zoom-out']"
@click="zoomOutlicked">
-
</div>
</div>
</StyledSvgWrapper>
</template>
<script type="text/ecmascript-6">
import {StyledSvgWrapper} from './styled'
import {createGraph, mapNodes, mapRelationships, getGraphStats} from '../mapper'
import {GraphEventHandler} from '../GraphEventHandler'
import { dim } from '../constants'
import '../lib/visualization/index'
import Vue from 'vue'
export default{
data(){
return{
state: {
zoomInLimitReached: true,
zoomOutLimitReached: false
}
}
},
components:{
StyledSvgWrapper
},
props: {
fullscreen: {
type: Boolean,
default: false
},
frameHeight: {
type: Number,
default: 0
},
nodes : {
type : Array,
default() {
return []
}
},
relationships : {
type : Array,
default() {
return []
}
},
graphStyle: {
type: Object,
default() {
return {};
}
},
onItemMouseOver: Function,
getNodeNeighbours: Function,
onItemSelect: Function,
onGraphModelChange: Function,
getAutoCompleteCallback: Function,
assignVisElement: Function
},
created() {
},
mounted() {
this.graphInit();
this.componentDidMount();
},
updated() {
this.componentDidUpdate();
},
methods: {
reloadPanel(forceReload) {
if (forceReload && this.graph) {
this.graph.resetGraph()
}
this.componentDidMount();
},
graphInit () {
this.svgElement = this.$refs.graphInit;
},
zoomInClicked (el) {
let limits = this.graphView.zoomIn(el);
Vue.set(this.state, 'zoomInLimitReached', limits.zoomInLimit);
Vue.set(this.state, 'zoomOutLimitReached', limits.zoomOutLimit);
},
zoomOutlicked (el) {
let limits = this.graphView.zoomOut(el);
Vue.set(this.state, 'zoomInLimitReached', limits.zoomInLimit);
Vue.set(this.state, 'zoomOutLimitReached', limits.zoomOutLimit);
},
getVisualAreaHeight () {
let areaHeight = 0;
if (this.frameHeight && this.fullscreen) {
areaHeight = this.frameHeight - (dim.frameStatusbarHeight + dim.frameTitlebarHeight * 2)
} else {
areaHeight = this.frameHeight - (dim.frameStatusbarHeight + dim.frameTitlebarHeight * 2) || this.svgElement.parentNode.offsetHeight
}
if (areaHeight < 0) {
areaHeight = 0 - areaHeight;
}
return areaHeight;
},
componentDidMount () {
if (this.svgElement != null) {
if (!this.graphView) {
let NeoConstructor = neo.graphView
let measureSize = () => {
return {width: this.svgElement.offsetWidth, height: this.getVisualAreaHeight()}
}
this.graph = createGraph(this.nodes, this.relationships)
this.graphView = new NeoConstructor(this.svgElement, measureSize, this.graph, this.graphStyle)
new GraphEventHandler(this.graph,
this.graphView,
this.getNodeNeighbours,
this.onItemMouseOver,
this.onItemSelect,
this.onGraphModelChange
).bindEventHandlers()
this.graphView.resize()
this.graphView.update()
this.state.currentStyleRules = this.graphStyle.toString()
this.onGraphModelChange(getGraphStats(this.graph))
} else {
this.graphView.update()
this.state.currentStyleRules = this.graphStyle.toString()
}
this.graph && this.setGraph && this.setGraph(this.graph)
this.getAutoCompleteCallback && this.getAutoCompleteCallback(this.addInternalRelationships.bind(this))
this.assignVisElement && this.assignVisElement(this.svgElement, this.graphView)
}
},
addInternalRelationships (internalRelationships) {
if (this.graph) {
this.graph.addInternalRelationships(mapRelationships(internalRelationships, this.graph))
this.graphView.update()
}
},
componentDidUpdate () {
if (this.state.shouldResize) {
this.graphView.resize()
}
}
},
watch: {
nodes: {
handler: function(val, oldVal) {
if (oldVal.toString() !== val.toString() && this.graphView) {
this.graph.addNodes(mapNodes(val))
this.graphView.update()
this.onGraphModelChange(getGraphStats(this.graph))
}
},
deep: true
},
// selectedNode: {
// handler: function(val, oldVal) {
// this.$store.state.selectedNode = val
// console.log("change:", val)
// }
// },
relationships: {
handler: function(val, oldVal) {
if (oldVal.toString() !== val.toString() && this.graphView) {
this.graph.addRelationships(mapRelationships(val, this.graph))
this.graphView.update()
this.onGraphModelChange(getGraphStats(this.graph))
}
},
deep: false
},
graphStyle: {
handler: function(val, oldVal) {
if (this.graphView) {
this.graphView.update()
Vue.set(this.state, 'currentStyleRules', val.toString());
}
},
deep: false
},
fullscreen: {
handler: function(val, oldVal) {
Vue.set(this.state, 'shouldResize', true);
}
},
frameHeight: {
handler: function(val, oldVal) {
Vue.set(this.state, 'shouldResize', true);
}
}
}
}
</script>

View File

@ -0,0 +1,194 @@
<script type="text/ecmascript-6">
import neoGraphStyle from '../graphStyle'
import {StyledPickerSelector, StyledTokenRelationshipType, StyledInlineList, StyledInlineListItem, StyledLabelToken, StyledPickerListItem, StyledCircleSelector, StyledCaptionSelector} from './styled'
export default{
data(){
return{
nodeDisplaySizes: [],
widths: []
}
},
props: {
selectedLabel: Object,
selectedRelType: Object,
graphStyleData: {
type: Object,
default() {
return {};
}
},
graphStyle: {},
outerUpdateStyle: Function
},
components:{
},
created() {
for (var index = 0; index < 10; index++) {
this.nodeDisplaySizes.push(`${12 + 2 * index}px`)
this.widths.push(`${5 + 3 * index}px`)
}
},
mounted() {
},
methods: {
sizeLessThan (size1, size2) {
let size1Numerical = size1 ? size1.replace('px', '') + 0 : 0
let size2Numerical = size1 ? size2.replace('px', '') + 0 : 0
return size1Numerical <= size2Numerical
},
updateStyle (selector, styleProp) {
this.graphStyle.changeForSelector(selector, styleProp)
if (this.outerUpdateStyle) {
this.outerUpdateStyle(this.graphStyle.toSheet());
}
},
circleSelector (styleProps, styleProvider, activeProvider, className, selector, textProvider = () => { return '' }) {
return styleProps.map((styleProp, i) => {
const onClick = () => {
this.updateStyle(selector, styleProp)
}
const style = styleProvider(styleProp, i)
const text = textProvider(styleProp)
const active = activeProvider(styleProp)
return (
<StyledPickerListItem class={className} key={i}>
<StyledCircleSelector class={active ? 'active' : ''} style={style} nativeOnClick={onClick}>{text}</StyledCircleSelector>
</StyledPickerListItem>
)
})
},
colorPicker (selector, styleForLabel) {
return (
<StyledInlineListItem key='color-picker'>
<StyledInlineList class='color-picker picker'>
<StyledInlineListItem>Color:</StyledInlineListItem>
{this.circleSelector(this.graphStyle.defaultColors(), (color) => { return {'backgroundColor': color.color} }, (color) => { return color.color === styleForLabel.get('color') }, 'color-picker-item', selector)}
</StyledInlineList>
</StyledInlineListItem>
)
},
sizePicker (selector, styleForLabel) {
return (
<StyledInlineListItem key='size-picker'>
<StyledInlineList class='size-picker picker'>
<StyledInlineListItem>Size:</StyledInlineListItem>
{this.circleSelector(this.graphStyle.defaultSizes(), (size, index) => { return { width: this.nodeDisplaySizes[index], height: this.nodeDisplaySizes[index] } }, (size) => { return this.sizeLessThan(size.diameter, styleForLabel.get('diameter')) }, 'size-picker-item', selector)}
</StyledInlineList>
</StyledInlineListItem>
)
},
widthPicker (selector, styleForItem) {
let widthSelectors = this.graphStyle.defaultArrayWidths().map((widthValue, i) => {
const onClick = () => {
this.updateStyle(selector, widthValue)
}
const style = { width: this.widths[i] }
const active = styleForItem.get('shaft-width') === widthValue['shaft-width']
return (
<StyledPickerListItem class='width-picker-item' key={i}>
<StyledPickerSelector class={active ? 'active' : ''} style={style} nativeOnClick={onClick} />
</StyledPickerListItem>
)
})
return (
<StyledInlineListItem key='width-picker'>
<StyledInlineList class='width-picker picker'>
<StyledInlineListItem>Line width:</StyledInlineListItem>
{widthSelectors}
</StyledInlineList>
</StyledInlineListItem>
)
},
iconPicker (selector) {
return (
<li key='icon-picker'>
Icon:
<ul class='icon-picker picker'>
{this.picker(this.graphStyle.defaultIconCodes(), (iconCode) => { return { 'fontFamily': 'streamline' } }, 'icon-picker-item', selector, (iconCode) => { return iconCode['icon-code'] })}
</ul>
</li>
)
},
captionPicker (selector, styleForItem, propertyKeys, showTypeSelector = false) {
let captionSelector = (displayCaption, captionToSave, key) => {
const onClick = () => {
this.updateStyle(selector, { caption: captionToSave })
}
let active = styleForItem.props.caption === captionToSave
return (
<StyledPickerListItem key={key}>
<StyledCaptionSelector class={active ? 'active' : ''} nativeOnClick={onClick}>{displayCaption}</StyledCaptionSelector>
</StyledPickerListItem>
)
}
let captionSelectors = propertyKeys.map((propKey, i) => {
return captionSelector(propKey, `{${propKey}}`)
})
let typeCaptionSelector = null
if (showTypeSelector) {
typeCaptionSelector = captionSelector('<type>', '<type>', 'typecaption')
}
return (
<StyledInlineListItem key='caption-picker'>
<StyledInlineList class='caption-picker picker'>
<StyledInlineListItem>Caption:</StyledInlineListItem>
{captionSelector('<id>', '<id>', 'idcaption')}
{typeCaptionSelector}
{captionSelectors}
</StyledInlineList>
</StyledInlineListItem>
)
},
stylePicker () {
let pickers;
let title;
if (this.selectedLabel) {
const labelList = this.selectedLabel.label !== '*' ? [this.selectedLabel.label] : [];
const styleForLabel = this.graphStyle.forNode({ labels: labelList })
const inlineStyle = {'backgroundColor': styleForLabel.get('color'), 'color': styleForLabel.get('text-color-internal')}
pickers = [this.colorPicker(styleForLabel.selector, styleForLabel), this.sizePicker(styleForLabel.selector, styleForLabel), this.captionPicker(styleForLabel.selector, styleForLabel, this.selectedLabel.propertyKeys)]
title = (<StyledLabelToken class='token token-label' style={inlineStyle}>{this.selectedLabel.label || '*'}</StyledLabelToken>)
} else if (this.selectedRelType) {
const relTypeSelector = this.selectedRelType.relType !== '*' ? {type: this.selectedRelType.relType} : {}
const styleForRelType = this.graphStyle.forRelationship(relTypeSelector)
const inlineStyle = {'backgroundColor': styleForRelType.get('color'), 'color': styleForRelType.get('text-color-internal')}
pickers = [this.colorPicker(styleForRelType.selector, styleForRelType), this.widthPicker(styleForRelType.selector, styleForRelType), this.captionPicker(styleForRelType.selector, styleForRelType, this.selectedRelType.propertyKeys, true)]
title = (<StyledTokenRelationshipType class='token token-relationship' style={inlineStyle}>{this.selectedRelType.relType || '*'}</StyledTokenRelationshipType>)
} else {
return null
}
return (
<StyledInlineList class='style-picker'>
{title}
{pickers}
</StyledInlineList>
)
},
componentWillReceiveProps (nextProps) {
if (nextProps.graphStyleData && nextProps.graphStyleData !== this.graphStyleData) {
this.graphStyle.loadRules(nextProps.graphStyleData)
}
}
},
watch: {
},
render () {
return this.stylePicker()
}
}
</script>

View File

@ -0,0 +1,168 @@
<script type="text/ecmascript-6">
import {inspectorFooterContractedHeight, StyledInspectorFooterStatusMessage, StyledTokenContextMenuKey, StyledTokenRelationshipType, StyledLabelToken, StyledStatusBar, StyledStatus, StyledInspectorFooter, StyledInspectorFooterRow, StyledInspectorFooterRowListPair, StyledInspectorFooterRowListKey, StyledInspectorFooterRowListValue, StyledInlineList} from './styled'
import GrassEditor from './GrassEditor'
import RowExpandToggleComponent from './RowExpandToggle'
import Vue from 'vue'
export default{
components:{
GrassEditor,
RowExpandToggleComponent
},
data(){
return{
}
},
props: {
graphStyle: {
type: Object,
default() {
return {};
}
},
graphStyleString: '',
onExpandToggled: Function,
selectedItem: {},
hoveredItem: {},
updateStyle: Function,
},
created() {
let state = {}
state.contracted = true
this.state = state;
},
mounted() {
this.setFooterRowELem();
},
watch: {
},
methods: {
reloadPanel() {
},
setFooterRowELem () {
if (this.$refs.setFooterRowELem) {
this.footerRowElem = this.$refs.setFooterRowELem;
}
},
componentWillReceiveProps (nextProps) {
if (this.selectedItem !== nextProps.selectedItem) { // || this.hoveredItem !== nextProps.hoveredItem){
this.setState({ contracted: true })
this.onExpandToggled && this.onExpandToggled(true, 0)
}
}
},
render () {
let item
let type
let inspectorContent
const mapItemProperties = (itemProperties) => {
return itemProperties.map((prop, i) => {
return (
<StyledInspectorFooterRowListPair class='pair' key={'prop' + i}>
<StyledInspectorFooterRowListKey class='key'>{prop.key + ': '}</StyledInspectorFooterRowListKey>
<StyledInspectorFooterRowListValue class='value'>{prop.value.toString()}</StyledInspectorFooterRowListValue>
</StyledInspectorFooterRowListPair>
)
})
}
const mapLabels = (itemLabels) => {
return itemLabels.map((label, i) => {
const graphStyleForLabel = this.graphStyle.forNode({labels: [label]})
const style = {'backgroundColor': graphStyleForLabel.get('color'), 'color': graphStyleForLabel.get('text-color-internal')}
return (
<StyledLabelToken key={'label' + i} style={style} class={'token' + ' ' + 'token-label'}>{label}</StyledLabelToken>
)
})
}
if (this.hoveredItem && this.hoveredItem.type !== 'canvas') {
item = this.hoveredItem.item
type = this.hoveredItem.type
} else if (this.selectedItem) {
item = this.selectedItem.item
type = this.selectedItem.type
} else if (this.hoveredItem) {
item = this.hoveredItem.item
type = this.hoveredItem.type
}
if (item && type) {
if (type === 'legend-item') {
inspectorContent = (
<GrassEditor selectedLabel={item.selectedLabel} selectedRelType={item.selectedRelType} outerUpdateStyle={this.updateStyle} graphStyle={this.graphStyle} graphStyleString={this.graphStyleString}/>
)
}
if (type === 'status-item') {
inspectorContent = (
<StyledInspectorFooterStatusMessage class='value'>{item}</StyledInspectorFooterStatusMessage>
)
}
if (type === 'context-menu-item') {
inspectorContent = (
<StyledInlineList class='list-inline'>
<StyledTokenContextMenuKey key='token' class={'token' + ' ' + 'token-context-menu-key' + ' ' + 'token-label'}>{item.label}</StyledTokenContextMenuKey>
<StyledInspectorFooterRowListPair key='pair' class='pair'>
<StyledInspectorFooterRowListValue class='value'>{item.content}</StyledInspectorFooterRowListValue>
</StyledInspectorFooterRowListPair>
</StyledInlineList>
)
} else if (type === 'canvas') {
const description = `Displaying ${item.nodeCount} nodes, ${item.relationshipCount} relationships.`
inspectorContent = (
<StyledInlineList class='list-inline'>
<StyledInspectorFooterRowListPair class='pair' key='pair'>
<StyledInspectorFooterRowListValue class='value'>{description}</StyledInspectorFooterRowListValue>
</StyledInspectorFooterRowListPair>
</StyledInlineList>
)
} else if (type === 'node') {
inspectorContent = (
<StyledInlineList class='list-inline'>
{mapLabels(item.labels)}
<StyledInspectorFooterRowListPair key='pair' class='pair'>
<StyledInspectorFooterRowListKey class='key'>{'<id>:'}</StyledInspectorFooterRowListKey>
<StyledInspectorFooterRowListValue class='value'>{item.id}</StyledInspectorFooterRowListValue>
</StyledInspectorFooterRowListPair>
{mapItemProperties(item.properties)}
</StyledInlineList>
)
} else if (type === 'relationship') {
const style = {'backgroundColor': this.graphStyle.forRelationship(item).get('color'), 'color': this.graphStyle.forRelationship(item).get('text-color-internal')}
inspectorContent = (
<StyledInlineList class='list-inline'>
<StyledTokenRelationshipType key='token' style={style} class={'token' + ' ' + 'token-relationship-type'}>{item.type}</StyledTokenRelationshipType>
<StyledInspectorFooterRowListPair key='pair' class='pair'>
<StyledInspectorFooterRowListKey class='key'>{'<id>:'}</StyledInspectorFooterRowListKey>
<StyledInspectorFooterRowListValue class='value'>{item.id}</StyledInspectorFooterRowListValue>
</StyledInspectorFooterRowListPair>
{mapItemProperties(item.properties)}
</StyledInlineList>
)
}
}
const toggleExpand = () => {
this.state.contracted = !this.state.contracted;
const inspectorHeight = this.footerRowElem.base.clientHeight
this.onExpandToggled && this.onExpandToggled(this.state.contracted, this.state.contracted ? 0 : inspectorHeight)
}
return (
<StyledStatusBar fullscreen={this.fullscreen} class='status-bar'>
<StyledStatus class='status'>
<StyledInspectorFooter class={this.state.contracted ? 'contracted inspector-footer' : 'inspector-footer'}>
<StyledInspectorFooterRow class='inspector-footer-row' ref='setFooterRowELem'>
{ type === 'canvas' ? null : <RowExpandToggleComponent contracted={this.state.contracted} rowElem={this.footerRowElem} containerHeight={inspectorFooterContractedHeight} nativeOnClick={toggleExpand} /> }
{inspectorContent}
</StyledInspectorFooterRow>
</StyledInspectorFooter>
</StyledStatus>
</StyledStatusBar>
)
}
}
</script>

View File

@ -0,0 +1,160 @@
<script type="text/ecmascript-6">
import {legendRowHeight, StyledLegendRow, StyledTokenRelationshipType, StyledLegendInlineListItem, StyledLegend, StyledLegendContents, StyledLabelToken, StyledTokenCount, StyledLegendInlineList} from './styled'
import RowExpandToggleComponent from './RowExpandToggle'
import Vue from 'vue'
export default{
components:{
RowExpandToggleComponent,
StyledLegendRow,
StyledTokenRelationshipType,
StyledLegendInlineListItem,
StyledLegend,
StyledLegendContents,
StyledLabelToken,
StyledTokenCount,
StyledLegendInlineList
},
data(){
return{
legendRowHeight: legendRowHeight,
state: {
typeRowContracted: true,
labelRowContracted: true
}
}
},
props: {
stats: {
type: Object,
default() {
return {}
}
},
graphStyle: {
type: Object,
default() {
return {};
}
},
graphStyleString: '',
onSelectedLabel: Function,
onSelectedRelType: Function
},
created() {
},
mounted() {
this.setLabelRowELem();
this.setTypeRowELem();
},
methods: {
setTypeRowELem () {
if (this.$refs.setTypeRowELem) {
Vue.set(this.state, 'typeRowElem', this.$refs.setTypeRowELem);
}
},
setLabelRowELem () {
if (this.$refs.setLabelRowELem) {
Vue.set(this.state, 'labelRowELem', this.$refs.setLabelRowELem);
}
},
getLegendPanel() {
const mapLabels = (labels) => {
const labelList = Object.keys(labels).map((legendItemKey, i) => {
const styleForItem = this.graphStyle.forNode({labels: [legendItemKey]})
const onClick = () => { this.onSelectedLabel(legendItemKey, Object.keys(labels[legendItemKey].properties)) }
const style = {'backgroundColor': styleForItem.get('color'), 'color': styleForItem.get('text-color-internal')}
return (
<StyledLegendInlineListItem key={i}>
<StyledLegendContents class='contents' style="">
<StyledLabelToken
nativeOnClick={ onClick }
style={style} class='token token-label'>
{legendItemKey}
<StyledTokenCount class='count'>({labels[legendItemKey].count})</StyledTokenCount>
</StyledLabelToken>
</StyledLegendContents>
</StyledLegendInlineListItem>
)
})
return (
<StyledLegendRow class={this.state.labelRowContracted ? 'contracted' : ''}>
<StyledLegendInlineList class='list-inline' ref='setLabelRowELem'>
<RowExpandToggleComponent
contracted={ this.state.labelRowContracted }
rowElem={ this.state.labelRowELem }
containerHeight={ legendRowHeight }
onClick={() => { this.state.labelRowContracted = !this.state.labelRowContracted; }} />
{labelList}
</StyledLegendInlineList>
</StyledLegendRow>
)
}
const mapRelTypes = (legendItems) => {
const relTypeList = Object.keys(legendItems).map((legendItemKey, i) => {
const styleForItem = this.graphStyle.forRelationship({type: legendItemKey})
const onClick = () => { this.onSelectedRelType(legendItemKey, Object.keys(legendItems[legendItemKey].properties)) }
const style = {'backgroundColor': styleForItem.get('color'), 'color': styleForItem.get('text-color-internal')}
return (
<StyledLegendInlineListItem key={i}>
<StyledLegendContents class='contents'>
<StyledTokenRelationshipType
nativeOnClick={ onClick }
style={style} class='token token-relationship-type'>
{legendItemKey}
<StyledTokenCount class='count'>({legendItems[legendItemKey].count})</StyledTokenCount>
</StyledTokenRelationshipType>
</StyledLegendContents>
</StyledLegendInlineListItem>
)
})
return (
<StyledLegendRow class={this.state.typeRowContracted ? 'contracted' : ''}>
<StyledLegendInlineList class='list-inline' ref='setTypeRowELem'>
<RowExpandToggleComponent
contracted={this.state.typeRowContracted}
rowElem={this.state.typeRowElem}
containerHeight={legendRowHeight}
onClick={() => { this.state.typeRowContracted = !this.state.typeRowContracted; }} />
{relTypeList}
</StyledLegendInlineList>
</StyledLegendRow>
)
}
let relTypes = mapRelTypes(this.stats.relTypes);
return (
<StyledLegend class={relTypes ? '' : 'one-row'}>
{mapLabels(this.stats.labels)}
{relTypes}
</StyledLegend>
)
}
},
watch: {
stats: {
handler: function(val, oldVal) {
},
deep: true
},
graphStyle: {
handler: function(val, oldVal) {
},
deep: true
},
graphStyleString: {
handler: function(val, oldVal) {
this.$forceUpdate();
},
deep: true
}
},
render () {
return this.getLegendPanel();
}
}
</script>

View File

@ -0,0 +1,71 @@
<script type="text/ecmascript-6">
import { StyledRowToggle, StyledCaret } from './styled'
import Vue from 'vue'
const getHeightFromElem = (rowElem) => (rowElem && rowElem.base) ? rowElem.base.clientHeight : 0
export default{
components:{
},
data(){
return{
state: {}
}
},
props: {
rowElem: null,
onClick: Function,
contracted: {
type: Boolean,
default: false
},
containerHeight: {
type: Number,
default: 0
}
},
created() {
},
mounted() {
this.componentDidMount();
},
updated() {
this.componentDidUpdate();
},
beforeDestroy() {
this.componentWillUnmount();
},
methods: {
updateDimensions () {
Vue.set(this.state, 'rowHeight', getHeightFromElem(this.rowElem));
},
componentDidMount () {
this.updateDimensions()
window.addEventListener('resize', this.updateDimensions.bind(this))
},
componentWillUnmount () {
window.removeEventListener('resize', this.updateDimensions.bind(this))
},
componentDidUpdate (prevProps, prevState) {
const rowHeight = getHeightFromElem(this.rowElem)
if (this.state.rowHeight !== rowHeight) {
this.updateDimensions()
}
}
},
render () {
if (this.containerHeight * 1.1 < this.state.rowHeight) {
return (
<StyledRowToggle nativeOnClick={this.onClick}>
<StyledCaret class={this.contracted ? 'fa fa-caret-left' : 'fa fa-caret-down'} />
</StyledRowToggle>
)
} else {
return null
}
}
}
</script>

View File

@ -0,0 +1,368 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import styled from 'vue-styled-components'
export const legendRowHeight = 32
export const inspectorFooterContractedHeight = 22
const pMarginTop = 6
export const StyledSvgWrapper = styled.div`
line-height: 0;
height: 100%;
position: relative;
> svg {
height: 100%;
width: 100%;
background-color: #f9fbfd;
.node {
cursor: pointer;
> .ring {
fill: none;
opacity: 0;
stroke: #6ac6ff;
}
&.selected {
> .ring {
stroke: #fdcc59;
opacity: 0.3;
}
}
&:hover {
> .ring {
stroke: #6ac6ff;
opacity: 0.3;
}
}
}
.relationship {
> .overlay {
opacity: 0;
fill: #6ac6ff;
}
&.selected {
> .overlay {
fill: #fdcc59;
opacity: 0.3;
}
}
&:hover {
> .overlay {
fill: #6ac6ff;
opacity: 0.3;
}
}
}
.remove_node {
.expand_node {
&:hover {
border: 2px #000 solid;
}
}
}
.outline {
cursor: pointer
}
path {
&.context-menu-item {
stroke-width: 2px;
fill: #d2d5da;
}
}
text {
line-height: normal;
&.context-menu-item {
fill: #fff;
text-anchor: middle;
pointer-events: none;
font-size: 14px;
}
}
.context-menu-item {
cursor: pointer;
&:hover {
fill: #b9b9b9;
font-size: 14px;
}
}
}
`
export const StyledStream = styled.div`
padding: 0;
display: flex;
flex-direction: column;
margin-top: 17px;
`
export const p = styled.div`
margin-top: ${pMarginTop}px;
font-size: 12px;
width: 100%;
white-space: normal;
`
export const StyledRowToggle = styled.div`
float: right;
display: block;
width: 21px;
height: 21px;
line-height: 21px;
text-align: center;
cursor: pointer;
`
export const StyledCaret = styled.div`
font-size: 17px;
vertical-align: middle;
`
export const StyledInspectorFooter = styled.div`
margin-top: 6px;
font-size: 12px;
width: 100%;
white-space: normal;
overflow: scroll;
&.contracted {
max-height: ${inspectorFooterContractedHeight}px;
overflow: hidden;
}
`
export const StyledInspectorFooterRow = styled.ul`
list-style: none;
word-break: break-word;
line-height: 21px;
`
export const StyledInspectorFooterRowListKey = styled.div`
float: left;
font-weight: 800;
`
export const StyledInspectorFooterRowListValue = styled.div`
padding-left: 3px;
overflow: hidden;
float: left;
white-space: pre-wrap;
`
export const StyledInlineList = styled.ul`
padding-left: 0;
list-style: none;
word-break: break-word
`
export const StyledInlineListItem = styled.li`
display: inline-block;
padding-right: 5px;
padding-left: 5px;
`
export const StyledStatusBarWrapper = styled.div`
height: 68px;
display: none;
`
export const StyledStatusBar = styled.div`
min-height: 39px;
line-height: 39px;
color: #788185;
font-size: 13px;
position: relative;
background-color: #fff;
white-space: nowrap;
overflow: hidden;
border-top: 1px solid #e6e9ef;
${props => props.fullscreen ? 'margin-top: -39px;' : 'margin-bottom: -39px;'}
`
export const StyledStatus = styled.div`
position: relative;
float: left;
padding-left: 16px;
margin-top: 0;
margin-bottom: 0;
width: 100%;
margin-top: 3px;
`
export const StyledInspectorFooterRowListPair = styled(StyledInlineListItem)`
vertical-align: middle;
font-size: 13px;
`
export const StyledToken = styled(StyledInlineListItem)`
display: inline-block;
font-weight: bold;
line-height: 1em;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
user-select: none;
font-size: 12px;
margin-right: 5px;
cursor: pointer;
`
export const StyledLabelToken = styled(StyledToken)`
padding: 4px 7px 4px 9px;
border-radius: 20px;
`
export const StyledTokenRelationshipType = styled(StyledToken)`
padding: 4px 7px 4px 5px;
border-radius: 3px;
`
export const tokenPropertyKey = styled(StyledToken)`
padding: 3px 5px 3px 5px;
`
export const StyledTokenContextMenuKey = styled(StyledLabelToken)` {
color: #f9fbfd;
background-color: #d2d5da;
font-family: FontAwesome;
padding: 4px 9px;
`
export const StyledTokenCount = styled.span`
font-weight: normal;
`
export const StyledLegendContents = styled.div`
float: left;
line-height: 1em;
position: relative;
top: 5px;
`
export const StyledLegendRow = styled.div`
border-bottom: 1px solid #e6e9ef;
&.contracted {
max-height: ${legendRowHeight}px
overflow: hidden;
}
`
export const StyledLegend = styled.div`
background-color: #eef1f8;
margin-top: -${(legendRowHeight * 2) + 1}px;
&.one-row {
margin-top: -${legendRowHeight}px;
}
`
export const StyledLegendInlineList = styled(StyledInlineList) `
padding: 7px 9px 0px 10px;
`
export const StyledLegendInlineListItem = styled(StyledInlineListItem) `
display: inline-block;
margin-bottom: 3px;
`
export const StyledPickerListItem = styled(StyledInlineListItem)`
padding-right: 5px;
padding-left: 0;
vertical-align: middle;
line-height: 0;
`
export const StyledPickerSelector = styled.a`
background-color: #aaa;
display: inline-block;
height: 12px;
width: 12px;
margin-top: 1px;
line-height: 0;
cursor: pointer;
opacity: 0.4;
&:hover {
opacity: 1;
}
&.active {
opacity: 1;
}
`
export const StyledCircleSelector = styled(StyledPickerSelector)`
border-radius: 50%;
`
export const StyledCaptionSelector = styled.a`
cursor: pointer;
user-select: none;
display: inline-block;
padding: 1px 6px;
font-size: 12px;
line-height: 1em;
color: #9195A0;
border: 1px solid #9195A0;
overflow: hidden;
border-radius: .25em;
margin-right: 0;
font-weight: bold;
&:hover {
border-color: #aaa;
color: #aaa;
text-decoration: none;
}
&.active {
color: white;
background-color: #9195A0;
}
`
export const StyledFullSizeContainer = styled.div`
height: 100%;
padding-top: ${(legendRowHeight * 2) + 1}px;
padding-bottom: ${props => props.forcePaddingBottom ? (props.forcePaddingBottom + 2 * pMarginTop) + 'px' : '39px'};
&.one-legend-row {
padding-top: ${legendRowHeight}px;
}
`
export const StyledInspectorFooterStatusMessage = styled.div`
font-weight: bold;
`
export const StyledZoomHolder = styled.div`
position: absolute;
bottom: 39px;
right: 0;
padding: 6px 6px 0 6px;
border-left: #e6e9ef solid 1px;
border-top: #e6e9ef solid 1px;
background: #fff;
`
export const StyledZoomButton = styled.button`
display: list-item;
list-style-type: none;
font-size: 2em;
margin-bottom: 10px;
border: none;
color: #9b9da2;
background: transparent;
border-color: black;
padding: 2px 6px 3px;
&:hover {
color:black;
}
&:focus {
outline: none
}
&.faded {
opacity: .3;
cursor: auto;
&:hover {
color: #9b9da2;
}
}
`

View File

@ -0,0 +1,17 @@
/**
* Created by chendaye666 on 17/7/31.
*/
export const font = {
}
export const dim = {
// Editor bar
editorbarHeight: 70,
// Frame
frameBodyHeight: 550,
frameTitlebarHeight: 39,
frameStatusbarHeight: 39,
frameBodyPadding: 20
}

View File

@ -0,0 +1,482 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export default function neoGraphStyle () {
const defaultStyle = {
'node': {
'diameter': '50px',
'color': '#A5ABB6',
'border-color': '#9AA1AC',
'border-width': '2px',
'text-color-internal': '#FFFFFF',
'font-size': '10px'
},
'relationship': {
'color': '#A5ABB6',
'shaft-width': '1px',
'font-size': '8px',
'padding': '3px',
'text-color-external': '#000000',
'text-color-internal': '#FFFFFF',
'caption': '<type>'
}
}
const defaultSizes = [
{
diameter: '10px'
}, {
diameter: '20px'
}, {
diameter: '50px'
}, {
diameter: '65px'
}, {
diameter: '80px'
}
]
const defaultIconCodes = [
{
'icon-code': 'a'
}, {
'icon-code': '"'
}, {
'icon-code': 'z'
}, {
'icon-code': '_'
}, {
'icon-code': '/'
}, {
'icon-code': '>'
}, {
'icon-code': 'k'
}
]
const defaultArrayWidths = [
{
'shaft-width': '1px'
}, {
'shaft-width': '2px'
}, {
'shaft-width': '3px'
}, {
'shaft-width': '5px'
}, {
'shaft-width': '8px'
}, {
'shaft-width': '13px'
}, {
'shaft-width': '25px'
}, {
'shaft-width': '38px'
}
]
const defaultColors = [
{
color: '#A5ABB6',
'border-color': '#9AA1AC',
'text-color-internal': '#FFFFFF'
}, {
color: '#68BDF6',
'border-color': '#5CA8DB',
'text-color-internal': '#FFFFFF'
}, {
color: '#6DCE9E',
'border-color': '#60B58B',
'text-color-internal': '#FFFFFF'
}, {
color: '#FF756E',
'border-color': '#E06760',
'text-color-internal': '#FFFFFF'
}, {
color: '#DE9BF9',
'border-color': '#BF85D6',
'text-color-internal': '#FFFFFF'
}, {
color: '#FB95AF',
'border-color': '#E0849B',
'text-color-internal': '#FFFFFF'
}, {
color: '#FFD86E',
'border-color': '#EDBA39',
'text-color-internal': '#604A0E'
}
]
const Selector = (function () {
function Selector (tag1, classes1) {
this.tag = tag1
this.classes = classes1 != null ? classes1 : []
}
Selector.prototype.toString = function () {
let str = this.tag
for (let i = 0; i < this.classes.length; i++) {
const classs = this.classes[i]
if (classs != null) {
str += '.' + classs
}
}
return str
}
return Selector
})()
const StyleRule = (function () {
function StyleRule (selector1, props1) {
this.selector = selector1
this.props = props1
}
StyleRule.prototype.matches = function (selector) {
if (this.selector.tag !== selector.tag) {
return false
}
for (let i = 0; i < this.selector.classes.length; i++) {
const classs = this.selector.classes[i]
if ((classs != null) && selector.classes.indexOf(classs) === -1) {
return false
}
}
return true
}
StyleRule.prototype.matchesExact = function (selector) {
return this.matches(selector) && this.selector.classes.length === selector.classes.length
}
return StyleRule
})()
const StyleElement = (function () {
function StyleElement (selector) {
this.selector = selector
this.props = {}
}
StyleElement.prototype.applyRules = function (rules) {
for (let i = 0; i < rules.length; i++) {
const rule = rules[i]
if (rule.matches(this.selector)) {
this.props = {...this.props, ...rule.props}
this.props.caption = this.props.caption || this.props.defaultCaption
}
}
return this
}
StyleElement.prototype.get = function (attr) {
return this.props[attr] || ''
}
return StyleElement
})()
const GraphStyle = (function () {
function GraphStyle () {
this.rules = []
try {
this.loadRules()
} catch (_error) {
// e = _error
}
}
const parseSelector = function (key) {
let tokens = key.split('.')
return new Selector(tokens[0], tokens.slice(1))
}
const selector = function (item) {
if (item.isNode) {
return nodeSelector(item)
} else if (item.isRelationship) {
return relationshipSelector(item)
}
}
const nodeSelector = function (node) {
node = node || {}
const classes = node.labels != null ? node.labels : []
return new Selector('node', classes)
}
const relationshipSelector = function (rel) {
rel = rel || {}
const classes = rel.type != null ? [rel.type] : []
return new Selector('relationship', classes)
}
const findRule = function (selector, rules) {
for (let i = 0; i < rules.length; i++) {
let rule = rules[i]
if (rule.matchesExact(selector)) {
return rule
}
}
}
const findAvailableDefaultColor = function (rules) {
const usedColors = rules.filter((rule) => { return rule.props.color != null }).map((rule) => {
return rule.props.color
})
let index = usedColors.length - 1 > defaultColors ? 0 : usedColors.length - 1
return defaultColors[index]
}
const getDefaultNodeCaption = function (item) {
if (!item || !(item.propertyList != null ? item.propertyList.length : 0) > 0) {
return {
defaultCaption: '<id>'
}
}
const captionPrioOrder = [/^name$/i, /^title$/i, /^label$/i, /name$/i, /description$/i, /^.+/]
let defaultCaption = captionPrioOrder.reduceRight(function (leading, current) {
let hits = item.propertyList.filter(function (prop) {
return current.test(prop.key)
})
if (hits.length) {
return '{' + hits[0].key + '}'
} else {
return leading
}
}, '')
defaultCaption || (defaultCaption = '<id>')
return {
caption: defaultCaption
}
}
GraphStyle.prototype.calculateStyle = function (selector) {
return new StyleElement(selector).applyRules(this.rules)
}
GraphStyle.prototype.forEntity = function (item) {
return this.calculateStyle(selector(item))
}
GraphStyle.prototype.setDefaultNodeStyling = function (selector, item) {
let defaultColor = true
let defaultCaption = true
for (let i = 0; i < this.rules.length; i++) {
let rule = this.rules[i]
if (rule.selector.classes.length > 0 && rule.matches(selector)) {
if (rule.props.hasOwnProperty('color')) {
defaultColor = false
}
if (rule.props.hasOwnProperty('caption')) {
defaultCaption = false
}
}
}
const minimalSelector = new Selector(selector.tag, selector.classes.sort().slice(0, 1))
if (defaultColor) {
this.changeForSelector(minimalSelector, findAvailableDefaultColor(this.rules))
}
if (defaultCaption) {
return this.changeForSelector(minimalSelector, getDefaultNodeCaption(item))
}
}
GraphStyle.prototype.changeForSelector = function (selector, props) {
let rule = findRule(selector, this.rules)
if (rule == null) {
rule = new StyleRule(selector, props)
this.rules.push(rule)
}
rule.props = {...rule.props, ...props}
return rule
}
GraphStyle.prototype.destroyRule = function (rule) {
const idx = this.rules.indexOf(rule)
if (idx != null) {
this.rules.splice(idx, 1)
}
}
GraphStyle.prototype.importGrass = function (string) {
try {
const rules = this.parse(string)
return this.loadRules(rules)
} catch (_error) {
// e = _error
}
}
GraphStyle.prototype.parse = function (string) {
const chars = string.split('')
let insideString = false
let insideProps = false
let keyword = ''
let props = ''
let rules = {}
for (let i = 0; i < chars.length; i++) {
const c = chars[i]
let skipThis = true
switch (c) {
case '{':
if (!insideString) {
insideProps = true
} else {
skipThis = false
}
break
case '}':
if (!insideString) {
insideProps = false
rules[keyword] = props
keyword = ''
props = ''
} else {
skipThis = false
}
break
case "'":
case '\'':
insideString ^= true
break
default:
skipThis = false
}
if (skipThis) {
continue
}
if (insideProps) {
props += c
} else {
if (!c.match(/[\s\n]/)) {
keyword += c
}
}
}
for (let k in rules) {
const v = rules[k]
rules[k] = {}
v.split(';').forEach((prop) => {
const [key, val] = prop.split(':')
if (key && val) {
rules[k][key.trim()] = val.trim()
}
})
}
return rules
}
GraphStyle.prototype.resetToDefault = function () {
this.loadRules()
return true
}
GraphStyle.prototype.toSheet = function () {
let sheet = {}
this.rules.forEach((rule) => {
sheet[rule.selector.toString()] = rule.props
})
return sheet
}
GraphStyle.prototype.toString = function () {
let str = ''
this.rules.forEach((r) => {
str += r.selector.toString() + ' {\n'
for (let k in r.props) {
let v = r.props[k]
if (k === 'caption') {
v = "'" + v + "'"
}
str += ' ' + k + ': ' + v + ';\n'
}
str += '}\n\n'
})
return str
}
GraphStyle.prototype.loadRules = function (data) {
if (typeof data !== 'object') {
data = defaultStyle
}
this.rules.length = 0
for (let key in data) {
const props = data[key]
this.rules.push(new StyleRule(parseSelector(key), props))
}
return this
}
GraphStyle.prototype.defaultSizes = function () {
return defaultSizes
}
GraphStyle.prototype.defaultIconCodes = function () {
return defaultIconCodes
}
GraphStyle.prototype.defaultArrayWidths = function () {
return defaultArrayWidths
}
GraphStyle.prototype.defaultColors = function () {
return defaultColors
}
GraphStyle.prototype.interpolate = function (str, item) {
let ips = str.replace(/\{([^{}]*)\}/g, function (a, b) {
let r = item.propertyMap[b]
if (typeof r === 'object') {
return r.join(', ')
}
if (typeof r === 'string' || typeof r === 'number') {
return r
}
return ''
})
if (ips.length < 1 && str === '{type}' && item.isRelationship) {
ips = '<type>'
}
if (ips.length < 1 && str === '{id}' && item.isNode) {
ips = '<id>'
}
return ips.replace(/^<(id|type)>$/, function (a, b) {
const r = item[b]
if (typeof r === 'string' || typeof r === 'number') {
return r
}
return ''
})
}
GraphStyle.prototype.forNode = function (node) {
node = node || {}
const selector = nodeSelector(node)
if ((node.labels != null ? node.labels.length : 0) > 0) {
this.setDefaultNodeStyling(selector, node)
}
return this.calculateStyle(selector)
}
GraphStyle.prototype.forRelationship = function (rel) {
const selector = relationshipSelector(rel)
return this.calculateStyle(selector)
}
return GraphStyle
})()
return new GraphStyle()
}

View File

@ -0,0 +1,7 @@
/**
* Created by chendaye666 on 17/7/31.
*/
// export Visualization from './Visualization'
export {default as Visualization} from './Visualization'

View File

@ -0,0 +1,51 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.collision = do ->
collision = {}
collide = (node) ->
r = node.radius + 10
nx1 = node.x - r
nx2 = node.x + r
ny1 = node.y - r
ny2 = node.y + r
return (quad, x1, y1, x2, y2) ->
if (quad.point && (quad.point != node))
x = node.x - quad.point.x
y = node.y - quad.point.y
l = Math.sqrt(x * x + y * y)
r = node.radius + 10 + quad.point.radius
if (l < r)
l = (l - r) / l * .5
node.x -= x *= l
node.y -= y *= l
quad.point.x += x
quad.point.y += y
x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1
collision.avoidOverlap = (nodes) ->
q = d3.geom.quadtree(nodes)
for n in nodes
q.visit collide(n)
collision

View File

@ -0,0 +1,135 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.models.Graph
constructor: () ->
@nodeMap = {}
@_nodes = []
@relationshipMap = {}
@_relationships = []
nodes: ->
@_nodes
relationships: ->
@_relationships
groupedRelationships: ->
class NodePair
constructor: (node1, node2) ->
@relationships = []
if node1.id < node2.id
@nodeA = node1
@nodeB = node2
else
@nodeA = node2
@nodeB = node1
isLoop: ->
@nodeA is @nodeB
toString: ->
"#{@nodeA.id}:#{@nodeB.id}"
groups = {}
for relationship in @_relationships
nodePair = new NodePair(relationship.source, relationship.target)
nodePair = groups[nodePair] ? nodePair
nodePair.relationships.push relationship
groups[nodePair] = nodePair
(pair for ignored, pair of groups)
addNodes: (nodes) =>
for node in nodes
if !@findNode(node.id)?
@nodeMap[node.id] = node
@_nodes.push(node)
@
removeNode: (node) =>
if @findNode(node.id)?
delete @nodeMap[node.id]
@_nodes.splice(@_nodes.indexOf(node), 1)
@
updateNode: (node) =>
if @findNode(node.id)?
@removeNode node
node.expanded = false
node.minified = true
@addNodes [node]
@
removeConnectedRelationships: (node) =>
for r in @findAllRelationshipToNode node
@updateNode r.source
@updateNode r.target
@_relationships.splice(@_relationships.indexOf(r), 1)
delete @relationshipMap[r.id]
@
addRelationships: (relationships) =>
for relationship in relationships
existingRelationship = @findRelationship(relationship.id)
if existingRelationship?
existingRelationship.internal = false
else
relationship.internal = false
@relationshipMap[relationship.id] = relationship
@_relationships.push(relationship)
@
addInternalRelationships: (relationships) =>
for relationship in relationships
relationship.internal = true
if not @findRelationship(relationship.id)?
@relationshipMap[relationship.id] = relationship
@_relationships.push(relationship)
@
pruneInternalRelationships: =>
relationships = @_relationships.filter((relationship) -> not relationship.internal)
@relationshipMap = {}
@_relationships = []
@addRelationships(relationships)
findNode: (id) => @nodeMap[id]
findNodeNeighbourIds: (id) =>
@_relationships
.filter((relationship) -> relationship.source.id is id or relationship.target.id is id)
.map((relationship) ->
if relationship.target.id is id
return relationship.source.id
return relationship.target.id
)
findRelationship: (id) => @relationshipMap[id]
findAllRelationshipToNode: (node) =>
@_relationships
.filter((relationship) -> relationship.source.id is node.id or relationship.target.id is node.id)
resetGraph: ->
@nodeMap = {}
@_nodes = []
@relationshipMap = {}
@_relationships = []

View File

@ -0,0 +1,111 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.NeoD3Geometry
square = (distance) -> distance * distance
constructor: (@style) ->
@relationshipRouting = new neo.utils.pairwiseArcsRelationshipRouting(@style)
addShortenedNextWord = (line, word, measure) ->
until word.length <= 2
word = word.substr(0, word.length - 2) + '\u2026'
if measure(word) < line.remainingWidth
line.text += " " + word
break
noEmptyLines = (lines) ->
for line in lines
if line.text.length is 0 then return false
true
fitCaptionIntoCircle = (node, style) ->
template = style.forNode(node).get("caption")
captionText = style.interpolate(template, node)
fontFamily = 'sans-serif'
fontSize = parseFloat(style.forNode(node).get('font-size'))
lineHeight = fontSize
measure = (text) ->
neo.utils.measureText(text, fontFamily, fontSize)
words = captionText.split(" ")
emptyLine = (lineCount, iLine) ->
baseline = (1 + iLine - lineCount / 2) * lineHeight
if (style.forNode(node).get("icon-code"))
baseline = baseline + node.radius/3
constainingHeight = if iLine < lineCount / 2 then baseline - lineHeight else baseline
lineWidth = Math.sqrt(square(node.radius) - square(constainingHeight)) * 2
{
node: node
text: ''
baseline: baseline
remainingWidth: lineWidth
}
fitOnFixedNumberOfLines = (lineCount) ->
lines = []
iWord = 0;
for iLine in [0..lineCount - 1]
line = emptyLine(lineCount, iLine)
while iWord < words.length and measure(" " + words[iWord]) < line.remainingWidth
line.text += " " + words[iWord]
line.remainingWidth -= measure(" " + words[iWord])
iWord++
lines.push line
if iWord < words.length
addShortenedNextWord(lines[lineCount - 1], words[iWord], measure)
[lines, iWord]
consumedWords = 0
maxLines = node.radius * 2 / fontSize
lines = [emptyLine(1, 0)]
for lineCount in [1..maxLines]
[candidateLines, candidateWords] = fitOnFixedNumberOfLines(lineCount)
if noEmptyLines(candidateLines)
[lines, consumedWords] = [candidateLines, candidateWords]
if consumedWords >= words.length
return lines
lines
formatNodeCaptions: (nodes) ->
for node in nodes
node.caption = fitCaptionIntoCircle(node, @style)
formatRelationshipCaptions: (relationships) ->
for relationship in relationships
template = @style.forRelationship(relationship).get("caption")
relationship.caption = @style.interpolate(template, relationship)
setNodeRadii: (nodes) ->
for node in nodes
node.radius = parseFloat(@style.forNode(node).get("diameter")) / 2
onGraphChange: (graph) ->
@setNodeRadii(graph.nodes())
@formatNodeCaptions(graph.nodes())
@formatRelationshipCaptions(graph.relationships())
@relationshipRouting.measureRelationshipCaptions(graph.relationships())
onTick: (graph) ->
@relationshipRouting.layoutRelationships(graph)

View File

@ -0,0 +1,65 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.graphView
constructor: (element, measureSize, @graph, @style) ->
layout = neo.layout.force()
@viz = neo.viz(element, measureSize, @graph, layout, @style)
@callbacks = {}
callbacks = @callbacks
@viz.trigger = do ->
(event, args...) ->
callback.apply(null, args) for callback in (callbacks[event] or [])
on: (event, callback) ->
(@callbacks[event] ?= []).push(callback)
@
layout: (value) ->
return layout unless arguments.length
layout = value
@
grass: (value) ->
return @style.toSheet() unless arguments.length
@style.importGrass(value)
@
update: ->
@viz.update()
@
resize: ->
@viz.resize()
@
boundingBox: ->
@viz.boundingBox()
collectStats: ->
@viz.collectStats()
zoomIn: (elem) ->
@viz.zoomInClick(elem)
zoomOut: (elem) ->
@viz.zoomOutClick(elem)

View File

@ -0,0 +1,106 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.layout = do ->
_layout = {}
_layout.force = ->
_force = {}
_force.init = (render) ->
forceLayout = {}
linkDistance = 45
d3force = d3.layout.force()
.linkDistance((relationship) -> relationship.source.radius + relationship.target.radius + linkDistance)
.charge(-1000)
newStatsBucket = ->
bucket =
layoutTime: 0
layoutSteps: 0
bucket
currentStats = newStatsBucket()
forceLayout.collectStats = ->
latestStats = currentStats
currentStats = newStatsBucket()
latestStats
accelerateLayout = ->
maxStepsPerTick = 100
maxAnimationFramesPerSecond = 60
maxComputeTime = 1000 / maxAnimationFramesPerSecond
now = if window.performance and window.performance.now
() ->
window.performance.now()
else
() ->
Date.now()
d3Tick = d3force.tick
d3force.tick = ->
startTick = now()
step = maxStepsPerTick
while step-- and now() - startTick < maxComputeTime
startCalcs = now()
currentStats.layoutSteps++
neo.collision.avoidOverlap d3force.nodes()
if d3Tick()
maxStepsPerTick = 2
return true
currentStats.layoutTime += now() - startCalcs
render()
false
accelerateLayout()
oneRelationshipPerPairOfNodes = (graph) ->
(pair.relationships[0] for pair in graph.groupedRelationships())
forceLayout.update = (graph, size) ->
nodes = neo.utils.cloneArray(graph.nodes())
relationships = oneRelationshipPerPairOfNodes(graph)
radius = nodes.length * linkDistance / (Math.PI * 2)
center =
x: size[0] / 2
y: size[1] / 2
neo.utils.circularLayout(nodes, center, radius)
d3force
.nodes(nodes)
.links(relationships)
.size(size)
.start()
forceLayout.drag = d3force.drag
forceLayout
_force
_layout

View File

@ -0,0 +1,39 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.models.Node
constructor: (@id, @labels, properties) ->
@propertyMap = properties
@propertyList = for own key,value of properties
{ key: key, value: value }
toJSON: ->
@propertyMap
isNode: true
isRelationship: false
relationshipCount: (graph) ->
node = @
rels = []
rels.push[relationship] for relationship in graph.relationships() when relationship.source is node or relationship.target is node
rels.length

View File

@ -0,0 +1,620 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.queryPlan = (element)->
maxChildOperators = 2 # Fact we know about the cypher compiler
maxComparableRows = 1000000 # link widths are comparable between plans if all operators are below this row count
maxComparableDbHits = 1000000 # db hits are comparable between plans if all operators are below this db hit count
operatorWidth = 180
operatorCornerRadius = 4
operatorHeaderHeight = 18
operatorHeaderFontSize = 11
operatorDetailHeight = 14
maxCostHeight = 50
detailFontSize = 10
operatorMargin = 50
operatorPadding = 3
rankMargin = 50
margin = 10
standardFont = "'Helvetica Neue',Helvetica,Arial,sans-serif"
fixedWidthFont = "Monaco,'Courier New',Terminal,monospace"
linkColor = '#DFE1E3'
costColor = '#F25A29'
dividerColor = '#DFE1E3'
operatorColors = ["#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"]
operatorCategories =
result: ['result']
seek: ['scan', 'seek', 'argument']
rows: ['limit', 'top', 'skip', 'sort', 'union', 'projection']
other: []
filter: ['select', 'filter', 'apply', 'distinct']
expand: ['expand', 'product', 'join', 'optional', 'path']
eager: ['eager']
augment = (color) ->
{
color: color,
'border-color': d3.rgb(color).darker(),
'text-color-internal': if d3.hsl(color).l < 0.7 then '#FFFFFF' else '#000000'
}
colors =
d3.scale.ordinal()
.domain(d3.keys(operatorCategories))
.range(operatorColors);
color = (d) ->
for name, keywords of operatorCategories
for keyword in keywords
if new RegExp(keyword, 'i').test(d)
return augment(colors(name))
augment(colors('other'))
rows = (operator) ->
operator.Rows ? operator.EstimatedRows ? 0
plural = (noun, count) ->
if count is 1 then noun else noun + 's'
formatNumber = d3.format(",.0f")
operatorDetails = (operator) ->
return [] unless operator.expanded
details = []
wordWrap = (string, className) ->
measure = (text) ->
neo.utils.measureText(text, fixedWidthFont, 10)
words = string.split(/([^a-zA-Z\d])/)
firstWord = 0
lastWord = 1
while firstWord < words.length
while lastWord < words.length and measure(words.slice(firstWord, lastWord + 1).join('')) < operatorWidth - operatorPadding * 2
lastWord++
details.push { className: className, value: words.slice(firstWord, lastWord).join('') }
firstWord = lastWord
lastWord = firstWord + 1
if identifiers = operator.identifiers ? operator.KeyNames?.split(', ')
wordWrap(identifiers.filter((d) -> not (/^ /.test(d))).join(', '), 'identifiers')
details.push { className: 'padding' }
if index = operator.Index
wordWrap(index, 'index')
details.push { className: 'padding' }
if expression = operator.LegacyExpression ? operator.ExpandExpression ? operator.LabelName ? operator.Signature
wordWrap(expression, 'expression')
details.push { className: 'padding' }
if operator.Rows? and operator.EstimatedRows?
details.push { className: 'estimated-rows', key: 'estimated rows', value: formatNumber(operator.EstimatedRows)}
if operator.DbHits? and not operator.alwaysShowCost
details.push { className: 'db-hits', key: plural('db hit', operator.DbHits || 0), value: formatNumber(operator.DbHits || 0)}
if details.length and details[details.length - 1].className == 'padding'
details.pop()
y = operatorDetailHeight
for detail in details
detail.y = y
y += if detail.className == 'padding'
operatorPadding * 2
else
operatorDetailHeight
details
transform = (queryPlan) ->
operators = []
links = []
result =
operatorType: 'Result'
children: [queryPlan.root]
collectLinks = (operator, rank) ->
operators.push operator
operator.rank = rank
for child in operator.children
child.parent = operator
collectLinks child, rank + 1
links.push
source: child
target: operator
collectLinks result, 0
[operators, links]
layout = (operators, links) ->
costHeight = do ->
scale = d3.scale.log()
.domain([1, Math.max(d3.max(operators, (operator) -> operator.DbHits or 0), maxComparableDbHits)])
.range([0, maxCostHeight])
(operator) ->
scale((operator.DbHits ? 0) + 1)
operatorHeight = (operator) ->
height = operatorHeaderHeight
if operator.expanded
height += operatorDetails(operator).slice(-1)[0].y + operatorPadding * 2
height += costHeight(operator)
height
linkWidth = do ->
scale = d3.scale.log()
.domain([1, Math.max(d3.max(operators, (operator) -> rows(operator) + 1), maxComparableRows)])
.range([2, (operatorWidth - operatorCornerRadius * 2) / maxChildOperators])
(operator) ->
scale(rows(operator) + 1)
for operator in operators
operator.height = operatorHeight(operator)
operator.costHeight = costHeight(operator)
if operator.costHeight > operatorDetailHeight + operatorPadding
operator.alwaysShowCost = true
childrenWidth = d3.sum(operator.children, linkWidth)
tx = (operatorWidth - childrenWidth) / 2
for child in operator.children
child.tx = tx
tx += linkWidth(child)
for link in links
link.width = linkWidth(link.source)
ranks = d3.nest()
.key((operator) -> operator.rank)
.entries(operators)
currentY = 0
for rank in ranks
currentY -= (d3.max(rank.values, operatorHeight) + rankMargin)
for operator in rank.values
operator.x = 0
operator.y = currentY
width = d3.max(ranks.map((rank) -> rank.values.length * (operatorWidth + operatorMargin)))
height = -currentY
collide = ->
for rank in ranks
x0 = 0
for operator in rank.values
dx = x0 - operator.x
if dx > 0
operator.x += dx
x0 = operator.x + operatorWidth + operatorMargin
dx = x0 - operatorMargin - width
if dx > 0
lastOperator = rank.values[rank.values.length - 1]
x0 = lastOperator.x -= dx
for i in [rank.values.length - 2..0] by -1
operator = rank.values[i]
dx = operator.x + operatorWidth + operatorMargin - x0
if dx > 0
operator.x -= operatorWidth
x0 = operator.x
center = (operator) ->
operator.x + operatorWidth / 2
relaxUpwards = (alpha) ->
for rank in ranks
for operator in rank.values
if operator.children.length
x = d3.sum(operator.children, (child) -> linkWidth(child) * center(child)) / d3.sum(operator.children, linkWidth)
operator.x += (x - center(operator)) * alpha
relaxDownwards = (alpha) ->
for rank in ranks.slice().reverse()
for operator in rank.values
if operator.parent
operator.x += (center(operator.parent) - center(operator)) * alpha
collide()
iterations = 300
alpha = 1
while iterations--
relaxUpwards(alpha)
collide()
relaxDownwards(alpha)
collide()
alpha *= .98
width = d3.max(operators, (o) -> o.x) - d3.min(operators, (o) -> o.x) + operatorWidth
[width, height]
render = (operators, links, width, height, redisplay) ->
svg = d3.select(element)
svg.transition()
.attr('width', width + margin * 2)
.attr('height', height + margin * 2)
.attr('viewBox', [d3.min(operators, (o) -> o.x) - margin, -margin - height, width + margin * 2, height + margin * 2].join(' '))
join = (parent, children) ->
for child in d3.entries(children)
selection = parent.selectAll(child.key).data(child.value.data)
child.value.selections(selection.enter(), selection, selection.exit())
if child.value.children
join(selection, child.value.children)
join(svg, {
'g.layer.links':
data: [links]
selections: (enter) ->
enter.append('g')
.attr('class', 'layer links')
children:
'.link':
data: ((d) -> d),
selections: (enter) ->
enter.append('g')
.attr('class', 'link')
children:
'path':
data: (d) -> [d]
selections: (enter, update) ->
enter
.append('path')
.attr('fill', linkColor)
update
.transition()
.attr('d', (d) ->
width = Math.max(1, d.width)
sourceX = d.source.x + operatorWidth / 2
targetX = d.target.x + d.source.tx
sourceY = d.source.y + d.source.height
targetY = d.target.y
yi = d3.interpolateNumber(sourceY, targetY)
curvature = .5
control1 = yi(curvature)
control2 = yi(1 - curvature)
controlWidth = Math.min(width / Math.PI, (targetY - sourceY) / Math.PI)
if sourceX > targetX + width / 2
controlWidth *= -1
[
'M', (sourceX + width / 2), sourceY,
'C', (sourceX + width / 2), control1 - controlWidth,
(targetX + width), control2 - controlWidth,
(targetX + width), targetY,
'L', targetX, targetY,
'C', targetX, control2 + controlWidth,
(sourceX - width / 2), control1 + controlWidth,
(sourceX - width / 2), sourceY,
'Z'
].join(' '))
'text':
data: (d) ->
x = d.source.x + operatorWidth / 2
y = d.source.y + d.source.height + operatorDetailHeight
source = d.source
if source.Rows? or source.EstimatedRows?
[key, caption] = if source.Rows?
['Rows', 'row']
else
['EstimatedRows', 'estimated row']
[
{ x: x, y: y, text: formatNumber(source[key]) + '\u00A0', anchor: 'end' }
{ x: x, y: y, text: plural(caption, source[key]), anchor: 'start' }
]
else
[]
selections: (enter, update) ->
enter
.append('text')
.attr('font-size', detailFontSize)
.attr('font-family', standardFont)
update
.transition()
.attr('x', (d) -> d.x)
.attr('y', (d) -> d.y)
.attr('text-anchor', (d) -> d.anchor)
.text((d) -> d.text)
'g.layer.operators':
data: [operators]
selections: (enter) ->
enter.append('g')
.attr('class', 'layer operators')
children:
'.operator':
data: ((d) -> d)
selections: (enter, update) ->
enter
.append('g')
.attr('class', 'operator')
update
.transition()
.attr('transform', (d) -> "translate(#{d.x},#{d.y})")
children:
'rect.background':
data: (d) -> [d]
selections: (enter, update) ->
enter
.append('rect')
.attr('class', 'background')
update
.transition()
.attr('width', operatorWidth)
.attr('height', (d) -> d.height)
.attr('rx', operatorCornerRadius)
.attr('ry', operatorCornerRadius)
.attr('fill', 'white')
.style('stroke', 'none')
'g.header':
data: (d) -> [d]
selections: (enter) ->
enter
.append('g')
.attr('class', 'header')
.attr('pointer-events', 'all')
.on('click', (d) ->
d.expanded = !d.expanded
redisplay()
)
children:
'path.banner':
data: (d) -> [d]
selections: (enter, update) ->
enter
.append('path')
.attr('class', 'banner')
update
.attr('d', (d) ->
shaving =
if d.height <= operatorHeaderHeight
operatorCornerRadius
else if d.height < operatorHeaderHeight + operatorCornerRadius
operatorCornerRadius - Math.sqrt(Math.pow(operatorCornerRadius, 2) -
Math.pow(operatorCornerRadius - d.height + operatorHeaderHeight, 2))
else 0
[
'M', operatorWidth - operatorCornerRadius, 0
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, operatorWidth, operatorCornerRadius
'L', operatorWidth, operatorHeaderHeight - operatorCornerRadius
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, operatorWidth - shaving, operatorHeaderHeight
'L', shaving, operatorHeaderHeight
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, 0, operatorHeaderHeight - operatorCornerRadius
'L', 0, operatorCornerRadius
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, operatorCornerRadius, 0
'Z'
].join(' '))
.style('fill', (d) -> color(d.operatorType).color)
'path.expand':
data: (d) -> if d.operatorType is 'Result' then [] else [d]
selections: (enter, update) ->
rotateForExpand = (d) ->
d3.transform()
"translate(#{operatorHeaderHeight / 2}, #{operatorHeaderHeight / 2}) " +
"rotate(#{if d.expanded then 90 else 0}) " +
"scale(0.5)"
enter
.append('path')
.attr('class', 'expand')
.attr('fill', (d) -> color(d.operatorType)['text-color-internal'])
.attr('d', 'M -5 -10 L 8.66 0 L -5 10 Z')
.attr('transform', rotateForExpand)
update
.transition()
.attrTween('transform', (d, i, a) ->
d3.interpolateString(a, rotateForExpand(d))
)
'text.title':
data: (d) -> [d]
selections: (enter) ->
enter
.append('text')
.attr('class', 'title')
.attr('font-size', operatorHeaderFontSize)
.attr('font-family', standardFont)
.attr('x', operatorHeaderHeight)
.attr('y', 13)
.attr('fill', (d) -> color(d.operatorType)['text-color-internal'])
.text((d) -> d.operatorType)
'g.detail':
data: operatorDetails
selections: (enter, update, exit) ->
enter
.append('g')
update
.attr('class', (d) -> 'detail ' + d.className)
.attr('transform', (d) -> "translate(0, #{operatorHeaderHeight + d.y})")
.attr('font-family', (d) ->
if d.className is 'expression' or d.className is 'identifiers'
fixedWidthFont
else
standardFont)
exit.remove()
children:
'text':
data: (d) ->
if d.key
[
{ text: d.value + '\u00A0', anchor: 'end', x: operatorWidth / 2 }
{ text: d.key, anchor: 'start', x: operatorWidth / 2 }
]
else
[
{ text: d.value, anchor: 'start', x: operatorPadding }
]
selections: (enter, update, exit) ->
enter
.append('text')
.attr('font-size', detailFontSize)
update
.attr('x', (d) -> d.x)
.attr('text-anchor', (d) -> d.anchor)
.attr('fill', 'black')
.transition()
.each('end', ->
update
.text((d) -> d.text)
)
exit.remove()
'path.divider':
data: (d) ->
if (d.className == 'padding')
[d]
else
[]
selections: (enter, update) ->
enter
.append('path')
.attr('class', 'divider')
.attr('visibility', 'hidden')
update
.attr('d', [
'M', 0, -operatorPadding * 2
'L', operatorWidth, -operatorPadding * 2
].join(' '))
.attr('stroke', dividerColor)
.transition()
.each('end', ->
update
.attr('visibility', 'visible')
)
'path.cost':
data: (d) -> [d]
selections: (enter, update) ->
enter
.append('path')
.attr('class', 'cost')
.attr('fill', costColor)
update
.transition()
.attr('d', (d) ->
if d.costHeight < operatorCornerRadius
shaving = operatorCornerRadius -
Math.sqrt(Math.pow(operatorCornerRadius, 2) - Math.pow(operatorCornerRadius - d.costHeight, 2))
[
'M', operatorWidth - shaving, d.height - d.costHeight
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, operatorWidth - operatorCornerRadius, d.height
'L', operatorCornerRadius, d.height
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, shaving, d.height - d.costHeight
'Z'
].join(' ')
else
[
'M', 0, d.height - d.costHeight
'L', operatorWidth, d.height - d.costHeight
'L', operatorWidth, d.height - operatorCornerRadius
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, operatorWidth - operatorCornerRadius, d.height
'L', operatorCornerRadius, d.height
'A', operatorCornerRadius, operatorCornerRadius, 0, 0, 1, 0, d.height - operatorCornerRadius
'Z'
].join(' ')
)
'text.cost':
data: (d) ->
if d.alwaysShowCost
y = d.height - d.costHeight + operatorDetailHeight
[
{ text: formatNumber(d.DbHits) + '\u00A0', anchor: 'end', y: y }
{ text: 'db hits', anchor: 'start', y: y }
]
else
[]
selections: (enter, update) ->
enter
.append('text')
.attr('class', 'cost')
.attr('font-size', detailFontSize)
.attr('font-family', standardFont)
.attr('fill', 'white')
update
.attr('x', operatorWidth / 2)
.attr('text-anchor', (d) -> d.anchor)
.transition()
.attr('y', (d) -> d.y)
.each('end', ->
update
.text((d) -> d.text)
)
'rect.outline':
data: (d) -> [d]
selections: (enter, update) ->
enter
.append('rect')
.attr('class', 'outline')
update
.transition()
.attr('width', operatorWidth)
.attr('height', (d) -> d.height)
.attr('rx', operatorCornerRadius)
.attr('ry', operatorCornerRadius)
.attr('fill', 'none')
.attr('stroke-width', 1)
.style('stroke', (d) -> color(d.operatorType)['border-color'])
})
display = (queryPlan) ->
[operators, links] = transform(queryPlan)
[width, height] = layout(operators, links)
render(operators, links, width, height, -> display(queryPlan))
@display = display
@

View File

@ -0,0 +1,35 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.models.Relationship
constructor: (@id, @source, @target, @type, properties) ->
@propertyMap = properties
@propertyList = for own key,value of @propertyMap
{ key: key, value: value }
toJSON: ->
@propertyMap
isNode: false
isRelationship: true
isLoop: ->
@source is @target

View File

@ -0,0 +1,27 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.Renderer
constructor: (opts = {})->
neo.utils.extend(@, opts)
@onGraphChange ?= ->
@onTick ?= ->

View File

@ -0,0 +1,317 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.style = do ->
_style = (storage) ->
new GraphStyle(storage)
_style.defaults =
autoColor: yes
colors: [
{ color: '#DFE1E3', 'border-color': '#D4D6D7', 'text-color-internal': '#000000' }
{ color: '#F25A29', 'border-color': '#DC4717', 'text-color-internal': '#FFFFFF' }
{ color: '#AD62CE', 'border-color': '#9453B1', 'text-color-internal': '#FFFFFF' }
{ color: '#30B6AF', 'border-color': '#46A39E', 'text-color-internal': '#FFFFFF' }
{ color: '#FF6C7C', 'border-color': '#EB5D6C', 'text-color-internal': '#FFFFFF' }
{ color: '#FCC940', 'border-color': '#F3BA25', 'text-color-internal': '#000000' }
{ color: '#4356C0', 'border-color': '#3445A2', 'text-color-internal': '#FFFFFF' }
]
style:
'node':
'diameter': '40px'
'color': '#DFE1E3'
'border-color': '#5CA8DB'
'border-width': '2px'
'text-color-internal': '#000000'
'caption': '{id}'
'font-size': '10px'
'relationship':
'color': '#D4D6D7'
'shaft-width': '1px'
'font-size': '8px'
'padding': '3px'
'text-color-external': '#5CA8DB'
'text-color-internal': '#68BDF6'
# 'node':
# 'diameter': '40px'
# 'color': '#DFE1E3'
# 'border-color': '#D4D6D7'
# 'border-width': '2px'
# 'text-color-internal': '#000000'
# 'caption': '{id}'
# 'font-size': '10px'
# 'relationship':
# 'color': '#D4D6D7'
# 'shaft-width': '1px'
# 'font-size': '8px'
# 'padding': '3px'
# 'text-color-external': '#000000'
# 'text-color-internal': '#FFFFFF'
sizes: [
{ diameter: '10px' }
{ diameter: '20px' }
{ diameter: '30px' }
{ diameter: '50px' }
{ diameter: '80px' }
]
arrayWidths: [
{ 'shaft-width': '1px' }
{ 'shaft-width': '2px' }
{ 'shaft-width': '3px' }
{ 'shaft-width': '5px' }
{ 'shaft-width': '8px' }
{ 'shaft-width': '13px' }
{ 'shaft-width': '25px' }
{ 'shaft-width': '38px' }
]
class Selector
constructor: (selector) ->
[@tag, @klass] = if selector.indexOf('.') > 0
selector.split('.')
else
[selector, undefined]
toString: ->
str = @tag
str += ".#{@klass}" if @klass?
str
class StyleRule
constructor: (@selector, @props) ->
matches: (selector) ->
if @selector.tag is selector.tag
if @selector.klass is selector.klass or not @selector.klass
return yes
return no
matchesExact: (selector) ->
@selector.tag is selector.tag and @selector.klass is selector.klass
class StyleElement
constructor: (selector, @data) ->
@selector = selector
@props = {}
applyRules: (rules) ->
# Two passes
for rule in rules when rule.matches(@selector)
neo.utils.extend(@props, rule.props)
break
for rule in rules when rule.matchesExact(@selector)
neo.utils.extend(@props, rule.props)
break
@
get: (attr) ->
@props[attr] or ''
class GraphStyle
constructor: (@storage) ->
@rules = []
@loadRules()
# Generate a selector string from an object (node or rel)
selector: (item) ->
if item.isNode
@nodeSelector(item)
else if item.isRelationship
@relationshipSelector(item)
#
# Methods for calculating applied style for elements
#
calculateStyle: (selector, data) ->
new StyleElement(selector, data).applyRules(@rules)
forEntity: (item) ->
@calculateStyle(@selector(item), item)
forNode: (node = {}) ->
selector = @nodeSelector(node)
if node.labels?.length > 0
@setDefaultStyling(selector)
@calculateStyle(selector, node)
forRelationship: (rel) ->
@calculateStyle(@relationshipSelector(rel), rel)
findAvailableDefaultColor: () ->
usedColors = {}
for rule in @rules
if rule.props.color?
usedColors[rule.props.color] = yes
for defaultColor in _style.defaults.colors
if !usedColors[defaultColor.color]?
return neo.utils.copy(defaultColor)
return neo.utils.copy(_style.defaults.colors[0])
setDefaultStyling: (selector) ->
rule = @findRule(selector)
if _style.defaults.autoColor and not rule?
rule = new StyleRule(selector, @findAvailableDefaultColor())
@rules.push(rule)
@persist()
#
# Methods for getting and modifying rules
#
change: (item, props) ->
selector = @selector(item)
rule = @findRule(selector)
if not rule?
rule = new StyleRule(selector, {})
@rules.push(rule)
neo.utils.extend(rule.props, props)
@persist()
rule
destroyRule: (rule) ->
idx = @rules.indexOf(rule)
@rules.splice(idx, 1) if idx?
@persist()
findRule: (selector) ->
rule = r for r in @rules when r.matchesExact(selector)
rule
#
# Selector helpers
#
nodeSelector: (node = {}) ->
selector = 'node'
if node.labels?.length > 0
selector += ".#{node.labels[0]}"
new Selector(selector)
relationshipSelector: (rel = {}) ->
selector = 'relationship'
selector += ".#{rel.type}" if rel.type?
new Selector(selector)
#
# Import/export
#
importGrass: (string) ->
try
rules = @parse(string)
@loadRules(rules)
@persist()
catch e
return
loadRules: (data) ->
data = _style.defaults.style unless neo.utils.isObject(data)
@rules.length = 0
for rule, props of data
@rules.push(new StyleRule(new Selector(rule), neo.utils.copy(props)))
@
parse: (string)->
chars = string.split('')
insideString = no
insideProps = no
keyword = ""
props = ""
rules = {}
for c in chars
skipThis = yes
switch c
when "{"
if not insideString
insideProps = yes
else
skipThis = no
when "}"
if not insideString
insideProps = no
rules[keyword] = props
keyword = ""
props = ""
else
skipThis = no
when "'", "\"" then insideString ^= true
else skipThis = no
continue if skipThis
if insideProps
props += c
else
keyword += c unless c.match(/[\s\n]/)
for k, v of rules
rules[k] = {}
for prop in v.split(';')
[key, val] = prop.split(':')
continue unless key and val
rules[k][key?.trim()] = val?.trim()
rules
persist: ->
@storage?.add('grass', JSON.stringify(@toSheet()))
resetToDefault: ->
@loadRules()
@persist()
toSheet: ->
sheet = {}
sheet[rule.selector.toString()] = rule.props for rule in @rules
sheet
toString: ->
str = ""
for r in @rules
str += r.selector.toString() + " {\n"
for k, v of r.props
v = "'#{v}'" if k == "caption"
str += " #{k}: #{v};\n"
str += "}\n\n"
str
#
# Misc.
#
nextDefaultColor: 0
defaultColors: -> neo.utils.copy(_style.defaults.colors)
interpolate: (str, id, properties) ->
# Supplant
# http://javascript.crockford.com/remedial.html
str.replace(
/\{([^{}]*)\}/g,
(a, b) ->
r = properties[b] or id
return if (typeof r is 'string' or typeof r is 'number') then r else a
)
_style

View File

@ -0,0 +1,285 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.viz = (el, measureSize, graph, layout, style) ->
viz =
style: style
root = d3.select(el)
base_group = root.append('g').attr("transform", "translate(0,0)")
rect = base_group.append("rect")
.style("fill", "none")
.style("pointer-events", "all")
#Make the rect cover the whole surface
.attr('x', '-2500')
.attr('y', '-2500')
.attr('width', '5000')
.attr('height', '5000')
.attr('transform', 'scale(1)')
container = base_group.append('g')
geometry = new neo.NeoD3Geometry(style)
# This flags that a panning is ongoing and won't trigger
# 'canvasClick' event when panning ends.
draw = no
# Arbitrary dimension used to keep force layout aligned with
# the centre of the svg view-port.
layoutDimension = 200
updateViz = yes
# To be overridden
viz.trigger = (event, args...) ->
onNodeClick = (node) =>
updateViz = no
viz.trigger('nodeClicked', node)
onNodeDblClick = (node) => viz.trigger('nodeDblClicked', node)
onNodeDragToggle = (node) -> viz.trigger('nodeDragToggle', node)
onRelationshipClick = (relationship) =>
d3.event.stopPropagation()
updateViz = no
viz.trigger('relationshipClicked', relationship)
onNodeMouseOver = (node) -> viz.trigger('nodeMouseOver', node)
onNodeMouseOut = (node) -> viz.trigger('nodeMouseOut', node)
onRelMouseOver = (rel) -> viz.trigger('relMouseOver', rel)
onRelMouseOut = (rel) -> viz.trigger('relMouseOut', rel)
zoomLevel = null
zoomed = ->
draw = yes
container.attr("transform", "translate(" + zoomBehavior.translate() + ")" + "scale(" + zoomBehavior.scale() + ")")
zoomBehavior = d3.behavior.zoom().scaleExtent([0.2, 1]).on("zoom", zoomed)
interpolateZoom = (translate, scale) ->
d3.transition().duration(500).tween("zoom", ->
t = d3.interpolate(zoomBehavior.translate(), translate)
s = d3.interpolate(zoomBehavior.scale(), scale)
(a)->
zoomBehavior.scale(s(a)).translate(t(a))
zoomed())
isZoomingIn = true
viz.zoomInClick = ->
isZoomingIn = true
zoomClick this
viz.zoomOutClick = ->
isZoomingIn = false
zoomClick this
zoomClick = (element) ->
draw = yes
limitsReached = {zoomInLimit: false, zoomOutLimit: false}
if isZoomingIn
zoomLevel = Number (zoomBehavior.scale() * (1 + 0.2 * 1)).toFixed(2)
if zoomLevel >= zoomBehavior.scaleExtent()[1]
limitsReached.zoomInLimit = true
interpolateZoom(zoomBehavior.translate(), zoomBehavior.scaleExtent()[1])
else
interpolateZoom(zoomBehavior.translate(), zoomLevel)
else
zoomLevel = Number (zoomBehavior.scale() * (1 + 0.2 * -1)).toFixed(2)
if zoomLevel <= zoomBehavior.scaleExtent()[0]
limitsReached.zoomOutLimit = true
interpolateZoom(zoomBehavior.translate(), zoomBehavior.scaleExtent()[0])
else
interpolateZoom(zoomBehavior.translate(), zoomLevel)
limitsReached
# Background click event
# Check if panning is ongoing
rect.on('click', ->
if not draw then viz.trigger('canvasClicked', el)
)
base_group.call(zoomBehavior)
.on("dblclick.zoom", null)
#Single click is not panning
.on("click.zoom", -> draw = no)
.on("DOMMouseScroll.zoom", null)
.on("wheel.zoom", null)
.on("mousewheel.zoom", null)
newStatsBucket = ->
bucket =
frameCount: 0
geometry: 0
relationshipRenderers: do ->
timings = {}
neo.renderers.relationship.forEach((r) ->
timings[r.name] = 0
)
timings
bucket.duration = ->
bucket.lastFrame - bucket.firstFrame
bucket.fps = ->
(1000 * bucket.frameCount / bucket.duration()).toFixed(1)
bucket.lps = ->
(1000 * bucket.layout.layoutSteps / bucket.duration()).toFixed(1)
bucket.top = ->
renderers = []
for name, time of bucket.relationshipRenderers
renderers.push {
name: name
time: time
}
renderers.push
name: 'forceLayout'
time: bucket.layout.layoutTime
renderers.sort (a, b) -> b.time - a.time
totalRenderTime = renderers.reduce ((prev, current) -> prev + current.time), 0
renderers.map((d) -> "#{d.name}: #{(100 * d.time / totalRenderTime).toFixed(1)}%").join(', ')
bucket
currentStats = newStatsBucket()
now = if window.performance and window.performance.now
() ->
window.performance.now()
else
() ->
Date.now()
render = ->
currentStats.firstFrame = now() unless currentStats.firstFrame
currentStats.frameCount++
startRender = now()
geometry.onTick(graph)
currentStats.geometry += (now() - startRender)
nodeGroups = container.selectAll('g.node')
.attr('transform', (d) ->
"translate(#{ d.x },#{ d.y })")
for renderer in neo.renderers.node
nodeGroups.call(renderer.onTick, viz)
relationshipGroups = container.selectAll('g.relationship')
.attr('transform', (d) ->
"translate(#{ d.source.x } #{ d.source.y }) rotate(#{ d.naturalAngle + 180 })")
for renderer in neo.renderers.relationship
startRenderer = now()
relationshipGroups.call(renderer.onTick, viz)
currentStats.relationshipRenderers[renderer.name] += (now() - startRenderer)
currentStats.lastFrame = now()
force = layout.init(render)
#Add custom drag event listeners
force.drag().on('dragstart.node', (d) ->
onNodeDragToggle(d)
).on('dragend.node', () ->
onNodeDragToggle()
)
viz.collectStats = ->
latestStats = currentStats
latestStats.layout = force.collectStats()
currentStats = newStatsBucket()
latestStats
viz.update = ->
return unless graph
layers = container.selectAll("g.layer").data(["relationships", "nodes"])
layers.enter().append("g")
.attr("class", (d) -> "layer " + d )
nodes = graph.nodes()
relationships = graph.relationships()
relationshipGroups = container.select("g.layer.relationships")
.selectAll("g.relationship").data(relationships, (d) -> d.id)
relationshipGroups.enter().append("g")
.attr("class", "relationship")
.on("mousedown", onRelationshipClick)
.on('mouseover', onRelMouseOver)
.on('mouseout', onRelMouseOut)
relationshipGroups
.classed("selected", (relationship) -> relationship.selected)
geometry.onGraphChange(graph)
for renderer in neo.renderers.relationship
relationshipGroups.call(renderer.onGraphChange, viz)
relationshipGroups.exit().remove();
nodeGroups = container.select("g.layer.nodes")
.selectAll("g.node").data(nodes, (d) -> d.id)
nodeGroups.enter().append("g")
.attr("class", "node")
.call(force.drag)
.call(clickHandler)
.on('mouseover', onNodeMouseOver)
.on('mouseout', onNodeMouseOut)
nodeGroups
.classed("selected", (node) -> node.selected)
for renderer in neo.renderers.node
nodeGroups.call(renderer.onGraphChange, viz)
for renderer in neo.renderers.menu
nodeGroups.call(renderer.onGraphChange, viz)
nodeGroups.exit().remove();
if updateViz
force.update(graph, [layoutDimension, layoutDimension])
viz.resize()
viz.trigger('updated')
updateViz = yes
viz.resize = ->
size = measureSize()
root.attr('viewBox', [
0, (layoutDimension - size.height) / 2, layoutDimension, size.height
].join(' '))
viz.boundingBox = ->
container.node().getBBox()
clickHandler = neo.utils.clickHandler()
clickHandler.on 'click', onNodeClick
clickHandler.on 'dblclick', onNodeDblClick
viz

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import './neod3.coffee'
import './components/collision.coffee'
import './components/graph.coffee'
import './components/graphGeometry.coffee'
import './components/graphView.coffee'
import './components/layout.coffee'
import './components/node.coffee'
import './components/relationship.coffee'
import './components/renderer.coffee'
import './components/style.coffee'
import './components/visualization.coffee'
import './renders/init.coffee'
import './renders/menu.coffee'
import './utils/adjacentAngles.coffee'
import './utils/angleList.coffee'
import './utils/arcArrow.coffee'
import './utils/arrays.coffee'
import './utils/circularLayout.coffee'
import './utils/circumferentialDistribution.coffee'
import './utils/circumferentialRelationshipRouting.coffee'
import './utils/clickHandler.coffee'
import './utils/loopArrow.coffee'
import './utils/pairwiseArcsRelationshipRouting.coffee'
import './utils/straightArrow.coffee'
import './utils/textMeasurement.coffee'

View File

@ -0,0 +1,45 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
window.neo = window.neo || {}
neo.models = {}
neo.renderers =
menu: []
node: []
relationship: []
neo.utils =
# Note: quick n' dirty. Only works for serializable objects
copy: (src) ->
JSON.parse(JSON.stringify(src))
extend: (dest, src) ->
return if not neo.utils.isObject(dest) and neo.utils.isObject(src)
dest[k] = v for own k, v of src
return dest
isArray: Array.isArray or (obj) ->
Object::toString.call(obj) == '[object Array]';
isObject: (obj) -> Object(obj) is obj

View File

@ -0,0 +1,181 @@
###!
Copyright (c) 2002-2015 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
do ->
noop = ->
nodeRingStrokeSize = 8
nodeOutline = new neo.Renderer(
onGraphChange: (selection, viz) ->
circles = selection.selectAll('circle.outline').data((node) -> [node])
circles.enter()
.append('circle')
.classed('outline', true)
.attr
cx: 0
cy: 0
circles
.attr
r: (node) -> node.radius
fill: (node) -> viz.style.forNode(node).get('color')
stroke: (node) -> viz.style.forNode(node).get('border-color')
'stroke-width': (node) -> viz.style.forNode(node).get('border-width')
circles.exit().remove()
onTick: noop
)
nodeCaption = new neo.Renderer(
onGraphChange: (selection, viz) ->
text = selection.selectAll('text.caption').data((node) -> node.caption)
text.enter().append('text')
# .classed('caption', true)
.attr('text-anchor': 'middle')
.attr('pointer-events': 'none')
text
.text((line) -> line.text)
.attr('y', (line) -> line.baseline )
.attr('font-size', (line) -> viz.style.forNode(line.node).get('font-size'))
.attr('fill': (line) -> viz.style.forNode(line.node).get('text-color-internal'))
text.exit().remove()
onTick: noop
)
nodeIcon = new neo.Renderer(
onGraphChange: (selection, viz) ->
text = selection.selectAll('text').data((node) -> node.caption)
text.enter().append('text')
.attr('text-anchor': 'middle')
.attr('pointer-events': 'none')
.attr('font-family': 'streamline')
text
.text((line) -> viz.style.forNode(line.node).get('icon-code'))
.attr('dy', (line) -> line.node.radius/16)
.attr('font-size', (line) -> line.node.radius)
.attr('fill': (line) -> viz.style.forNode(line.node).get('text-color-internal'))
text.exit().remove()
onTick: noop
)
nodeRing = new neo.Renderer(
onGraphChange: (selection) ->
circles = selection.selectAll('circle.ring').data((node) -> [node])
circles.enter()
.insert('circle', '.outline')
.classed('ring', true)
.attr
cx: 0
cy: 0
'stroke-width': nodeRingStrokeSize + 'px'
circles
.attr
r: (node) -> node.radius + 4
circles.exit().remove()
onTick: noop
)
arrowPath = new neo.Renderer(
name: 'arrowPath'
onGraphChange: (selection, viz) ->
paths = selection.selectAll('path.outline').data((rel) -> [rel])
paths.enter()
.append('path')
.classed('outline', true)
paths
.attr('fill', (rel) -> viz.style.forRelationship(rel).get('color'))
.attr('stroke', 'none')
paths.exit().remove()
onTick: (selection) ->
selection.selectAll('path')
.attr('d', (d) -> d.arrow.outline(d.shortCaptionLength))
)
relationshipType = new neo.Renderer(
name: 'relationshipType'
onGraphChange: (selection, viz) ->
texts = selection.selectAll("text").data((rel) -> [rel])
texts.enter().append("text")
.attr("text-anchor": "middle")
.attr('pointer-events': 'none')
texts
.attr('font-size', (rel) -> viz.style.forRelationship(rel).get('font-size'))
.attr('fill', (rel) -> viz.style.forRelationship(rel).get('text-color-' + rel.captionLayout))
texts.exit().remove()
onTick: (selection, viz) ->
selection.selectAll('text')
.attr('x', (rel) -> rel.arrow.midShaftPoint.x)
.attr('y', (rel) -> rel.arrow.midShaftPoint.y + parseFloat(viz.style.forRelationship(rel).get('font-size')) / 2 - 1)
.attr('transform', (rel) ->
if rel.naturalAngle < 90 or rel.naturalAngle > 270
"rotate(180 #{ rel.arrow.midShaftPoint.x } #{ rel.arrow.midShaftPoint.y })"
else
null)
.text((rel) -> rel.shortCaption)
)
relationshipOverlay = new neo.Renderer(
name: 'relationshipOverlay'
onGraphChange: (selection) ->
rects = selection.selectAll('path.overlay').data((rel) -> [rel])
rects.enter()
.append('path')
.classed('overlay', true)
rects.exit().remove()
onTick: (selection) ->
band = 16
selection.selectAll('path.overlay')
.attr('d', (d) -> d.arrow.overlay(band))
)
neo.renderers.node.push(nodeOutline)
neo.renderers.node.push(nodeIcon)
neo.renderers.node.push(nodeCaption)
neo.renderers.node.push(nodeRing)
neo.renderers.relationship.push(arrowPath)
neo.renderers.relationship.push(relationshipType)
neo.renderers.relationship.push(relationshipOverlay)

View File

@ -0,0 +1,120 @@
###!
Copyright (c) 2002-2015 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
do ->
noop = ->
numberOfItemsInContextMenu = 3
arc = (radius, itemNumber, width = 30) ->
itemNumber = itemNumber - 1
startAngle = ((2*Math.PI)/numberOfItemsInContextMenu) * itemNumber
endAngle = startAngle + ((2*Math.PI)/numberOfItemsInContextMenu)
innerRadius = Math.max(radius + 8, 20)
d3.svg.arc().innerRadius(innerRadius).outerRadius(innerRadius + width).startAngle(startAngle).endAngle(endAngle).padAngle(.03)
getSelectedNode = (node) -> if node.selected then [node] else []
attachContextEvent = (event, elems, viz, content, label) ->
for elem in elems
elem.on('mousedown.drag', ->
d3.event.stopPropagation()
null)
elem.on('mouseup', (node) ->
viz.trigger(event, node))
elem.on('mouseover', (node) ->
node.contextMenu =
menuSelection: event
menuContent: content
label:label
viz.trigger('menuMouseOver', node))
elem.on('mouseout', (node) ->
delete node.contextMenu
viz.trigger('menuMouseOut', node))
createMenuItem = (selection, viz, eventName, itemNumber, className, position, textValue, helpValue) ->
path = selection.selectAll('path.' + className).data(getSelectedNode)
textpath = selection.selectAll('text.' + className).data(getSelectedNode)
tab = path.enter()
.append('path')
.classed(className, true)
.classed('context-menu-item', true)
.attr
d: (node) -> arc(node.radius, itemNumber, 1)()
text = textpath.enter()
.append('text')
.classed('context-menu-item', true)
.text(textValue)
.attr("transform", "scale(0.1)")
.attr
'font-family': 'FontAwesome'
fill: (node) -> viz.style.forNode(node).get('text-color-internal')
x: (node) -> arc(node.radius, itemNumber).centroid()[0] + position[0]
y: (node) -> arc(node.radius, itemNumber).centroid()[1] + position[1]
attachContextEvent(eventName, [tab, text], viz, helpValue, textValue)
tab
.transition()
.duration(200)
.attr
d: (node) -> arc(node.radius, itemNumber)()
text
.attr("transform", "scale(1)")
path
.exit()
.transition()
.duration(200)
.attr
d: (node) -> arc(node.radius, itemNumber, 1)()
.remove()
textpath
.exit()
.attr("transform", "scale(0)")
.remove()
donutRemoveNode = new neo.Renderer(
onGraphChange: (selection, viz) -> createMenuItem(selection, viz, 'nodeClose', 1, 'remove_node', [-4, 0], '\uf00d', 'Remove node from the visualization')
onTick: noop
)
donutExpandNode = new neo.Renderer(
onGraphChange: (selection, viz) -> createMenuItem(selection, viz, 'nodeDblClicked', 2, 'expand_node', [0, 4], '\uf0b2', 'Expand child relationships')
onTick: noop
)
donutUnlockNode = new neo.Renderer(
onGraphChange: (selection, viz) -> createMenuItem(selection, viz, 'nodeUnlock', 3, 'unlock_node', [4, 0], '\uf09c', 'Unlock the node to re-layout the graph')
onTick: noop
)
neo.renderers.menu.push(donutExpandNode)
neo.renderers.menu.push(donutRemoveNode)
neo.renderers.menu.push(donutUnlockNode)

View File

@ -0,0 +1,91 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.utils.adjacentAngles
findRuns: (angleList, minSeparation) ->
p = 0
start = 0
end = 0
runs = []
minStart = () ->
if runs.length == 0
0
else
runs[0].start
scanForDensePair = ->
start = p
end = angleList.wrapIndex(p + 1)
if end == minStart()
'done'
else
p = end
if tooDense(start, end)
extendEnd
else
scanForDensePair
extendEnd = ->
if p == minStart()
'done'
else if tooDense(start, angleList.wrapIndex(p + 1))
end = angleList.wrapIndex(p + 1)
p = end
extendEnd
else
p = start
extendStart
extendStart = ->
candidateStart = angleList.wrapIndex(p - 1)
if tooDense(candidateStart, end) and candidateStart != end
start = candidateStart
p = start
extendStart
else
runs.push
start: start
end: end
p = end
scanForDensePair
tooDense = (start, end) ->
run =
start: start
end: end
angleList.angle(run) < angleList.length(run) * minSeparation
stepCount = 0
step = scanForDensePair
while step != 'done'
if stepCount++ > angleList.totalLength() * 10
console.log 'Warning: failed to layout arrows', ("#{ key }: #{ value.angle }" for own key, value of angleList.list).join('\n'), minSeparation
break
step = step()
runs

View File

@ -0,0 +1,54 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.utils.angleList
constructor: (@list) ->
getAngle: (index) ->
@list[index].angle
fixed: (index) ->
@list[index].fixed
totalLength: ->
@list.length
length: (run) ->
if run.start < run.end
run.end - run.start
else
run.end + @list.length - run.start
angle: (run) ->
if run.start < run.end
@list[run.end].angle - @list[run.start].angle
else
360 - (@list[run.start].angle - @list[run.end].angle)
wrapIndex: (index) ->
if index == -1
@list.length - 1
else if index >= @list.length
index - @list.length
else
index

View File

@ -0,0 +1,180 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.utils.arcArrow
constructor: (startRadius, endRadius, endCentre, @deflection, arrowWidth, headWidth, headLength, captionLayout) ->
square = (l) ->
l * l
deflectionRadians = @deflection * Math.PI / 180
startAttach =
x: Math.cos( deflectionRadians ) * (startRadius),
y: Math.sin( deflectionRadians ) * (startRadius)
radiusRatio = startRadius / (endRadius + headLength)
homotheticCenter = -endCentre * radiusRatio / (1 - radiusRatio)
intersectWithOtherCircle = (fixedPoint, radius, xCenter, polarity) ->
gradient = fixedPoint.y / (fixedPoint.x - homotheticCenter)
hc = fixedPoint.y - gradient * fixedPoint.x
A = 1 + square(gradient)
B = 2 * (gradient * hc - xCenter)
C = square(hc) + square(xCenter) - square(radius)
intersection = { x: (-B + polarity * Math.sqrt(square(B) - 4 * A * C)) / (2 * A) }
intersection.y = (intersection.x - homotheticCenter) * gradient
intersection
endAttach = intersectWithOtherCircle(startAttach, endRadius + headLength, endCentre, -1)
g1 = -startAttach.x / startAttach.y
c1 = startAttach.y + (square(startAttach.x) / startAttach.y)
g2 = -(endAttach.x - endCentre) / endAttach.y
c2 = endAttach.y + (endAttach.x - endCentre) * endAttach.x / endAttach.y
cx = ( c1 - c2 ) / (g2 - g1)
cy = g1 * cx + c1
arcRadius = Math.sqrt(square(cx - startAttach.x) + square(cy - startAttach.y))
startAngle = Math.atan2(startAttach.x - cx, cy - startAttach.y)
endAngle = Math.atan2(endAttach.x - cx, cy - endAttach.y)
sweepAngle = endAngle - startAngle
if @deflection > 0
sweepAngle = 2 * Math.PI - sweepAngle
@shaftLength = sweepAngle * arcRadius
if startAngle > endAngle
@shaftLength = 0
midShaftAngle = (startAngle + endAngle) / 2
if @deflection > 0
midShaftAngle += Math.PI
@midShaftPoint =
x: cx + arcRadius * Math.sin(midShaftAngle)
y: cy - arcRadius * Math.cos(midShaftAngle)
startTangent = (dr) ->
dx = (if dr < 0 then 1 else -1) * Math.sqrt(square(dr) / (1 + square(g1)))
dy = g1 * dx
{
x: startAttach.x + dx,
y: startAttach.y + dy
}
endTangent = (dr) ->
dx = (if dr < 0 then -1 else 1) * Math.sqrt(square(dr) / (1 + square(g2)))
dy = g2 * dx
{
x: endAttach.x + dx,
y: endAttach.y + dy
}
angleTangent = (angle, dr) ->
{
x: cx + (arcRadius + dr) * Math.sin(angle),
y: cy - (arcRadius + dr) * Math.cos(angle)
}
endNormal = (dc) ->
dx = (if dc < 0 then -1 else 1) * Math.sqrt(square(dc) / (1 + square(1 / g2)))
dy = dx / g2
{
x: endAttach.x + dx
y: endAttach.y - dy
}
endOverlayCorner = (dr, dc) ->
shoulder = endTangent(dr)
arrowTip = endNormal(dc)
{
x: shoulder.x + arrowTip.x - endAttach.x,
y: shoulder.y + arrowTip.y - endAttach.y
}
coord = (point) ->
"#{point.x},#{point.y}"
shaftRadius = arrowWidth / 2
headRadius = headWidth / 2
positiveSweep = if startAttach.y > 0 then 0 else 1
negativeSweep = if startAttach.y < 0 then 0 else 1
@outline = (shortCaptionLength) ->
if startAngle > endAngle
return [
'M', coord(endTangent(-headRadius)),
'L', coord(endNormal(headLength)),
'L', coord(endTangent(headRadius)),
'Z'
].join(' ')
if captionLayout is 'external'
captionSweep = shortCaptionLength / arcRadius
if @deflection > 0
captionSweep *= -1
startBreak = midShaftAngle - captionSweep / 2
endBreak = midShaftAngle + captionSweep / 2
[
'M', coord(startTangent(shaftRadius)),
'L', coord(startTangent(-shaftRadius)),
'A', arcRadius - shaftRadius, arcRadius - shaftRadius, 0, 0, positiveSweep, coord(angleTangent(startBreak, -shaftRadius)),
'L', coord(angleTangent(startBreak, shaftRadius)),
'A', arcRadius + shaftRadius, arcRadius + shaftRadius, 0, 0, negativeSweep, coord(startTangent(shaftRadius))
'Z',
'M', coord(angleTangent(endBreak, shaftRadius)),
'L', coord(angleTangent(endBreak, -shaftRadius)),
'A', arcRadius - shaftRadius, arcRadius - shaftRadius, 0, 0, positiveSweep, coord(endTangent(-shaftRadius)),
'L', coord(endTangent(-headRadius)),
'L', coord(endNormal(headLength)),
'L', coord(endTangent(headRadius)),
'L', coord(endTangent(shaftRadius)),
'A', arcRadius + shaftRadius, arcRadius + shaftRadius, 0, 0, negativeSweep, coord(angleTangent(endBreak, shaftRadius))
].join(' ')
else
[
'M', coord(startTangent(shaftRadius)),
'L', coord(startTangent(-shaftRadius)),
'A', arcRadius - shaftRadius, arcRadius - shaftRadius, 0, 0, positiveSweep, coord(endTangent(-shaftRadius)),
'L', coord(endTangent(-headRadius)),
'L', coord(endNormal(headLength)),
'L', coord(endTangent(headRadius)),
'L', coord(endTangent(shaftRadius)),
'A', arcRadius + shaftRadius, arcRadius + shaftRadius, 0, 0, negativeSweep, coord(startTangent(shaftRadius))
].join(' ')
@overlay = (minWidth) ->
radius = Math.max(minWidth / 2, shaftRadius)
[
'M', coord(startTangent(radius)),
'L', coord(startTangent(-radius)),
'A', arcRadius - radius, arcRadius - radius, 0, 0, positiveSweep, coord(endTangent(-radius)),
'L', coord(endOverlayCorner(-radius, headLength)),
'L', coord(endOverlayCorner(radius, headLength)),
'L', coord(endTangent(radius)),
'A', arcRadius + radius, arcRadius + radius, 0, 0, negativeSweep, coord(startTangent(radius))
].join(' ')

View File

@ -0,0 +1,26 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.utils.cloneArray = (original) ->
clone = new Array(original.length)
clone[idx] = node for node, idx in original
clone

View File

@ -0,0 +1,28 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.utils.circularLayout = (nodes, center, radius) ->
unlocatedNodes = []
unlocatedNodes.push(node) for node in nodes when !(node.x? and node.y?)
for n, i in unlocatedNodes
n.x = center.x + radius * Math.sin(2 * Math.PI * i / unlocatedNodes.length)
n.y = center.y + radius * Math.cos(2 * Math.PI * i / unlocatedNodes.length)

View File

@ -0,0 +1,99 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.utils.distributeCircular = (arrowAngles, minSeparation) ->
list = []
for key, angle of arrowAngles.floating
list.push
key: key
angle: angle
fixed: false
for key, angle of arrowAngles.fixed
list.push
key: key
angle: angle
fixed: true
list.sort((a, b) -> a.angle - b.angle)
angleList = new neo.utils.angleList(list)
runsOfTooDenseArrows = new neo.utils.adjacentAngles().findRuns(angleList, minSeparation)
wrapAngle = (angle) ->
if angle >= 360
angle - 360
else if angle < 0
angle + 360
else
angle
result = {}
splitByFixedArrows = (run) ->
runs = []
currentStart = run.start
for i in [1..angleList.length(run)]
wrapped = angleList.wrapIndex(run.start + i)
if angleList.fixed(wrapped)
runs.push
start: currentStart
end: wrapped
currentStart = wrapped
if not angleList.fixed(run.end)
runs.push
start: currentStart
end: run.end
runs
for tooDenseRun in runsOfTooDenseArrows
moveableRuns = splitByFixedArrows(tooDenseRun)
for run in moveableRuns
runLength = angleList.length(run)
if angleList.fixed(run.start) and angleList.fixed(run.end)
separation = angleList.angle(run) / runLength
for i in [0..runLength]
rawAngle = list[run.start].angle + i * separation
result[list[angleList.wrapIndex(run.start + i)].key] = wrapAngle(rawAngle)
else if angleList.fixed(run.start) and not angleList.fixed(run.end)
for i in [0..runLength]
rawAngle = list[run.start].angle + i * minSeparation
result[list[angleList.wrapIndex(run.start + i)].key] = wrapAngle(rawAngle)
else if not angleList.fixed(run.start) and angleList.fixed(run.end)
for i in [0..runLength]
rawAngle = list[run.end].angle - (runLength - i) * minSeparation
result[list[angleList.wrapIndex(run.start + i)].key] = wrapAngle(rawAngle)
else
center = list[run.start].angle + angleList.angle(run) / 2
for i in [0..runLength]
rawAngle = center + (i - runLength / 2) * minSeparation
result[list[angleList.wrapIndex(run.start + i)].key] = wrapAngle(rawAngle)
for key of arrowAngles.floating
if !result.hasOwnProperty(key)
result[key] = arrowAngles.floating[key]
for key of arrowAngles.fixed
if !result.hasOwnProperty(key)
result[key] = arrowAngles.fixed[key]
result

View File

@ -0,0 +1,136 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.utils.circumferentialRelationshipRouting
constructor: (@style) ->
measureRelationshipCaption: (relationship, caption) ->
fontFamily = 'sans-serif'
fontSize = parseFloat(@style.forRelationship(relationship).get('font-size'))
padding = parseFloat(@style.forRelationship(relationship).get('padding'))
neo.utils.measureText(caption, fontFamily, fontSize) + padding * 2
captionFitsInsideArrowShaftWidth: (relationship) ->
parseFloat(@style.forRelationship(relationship).get('shaft-width')) >
parseFloat(@style.forRelationship(relationship).get('font-size'))
measureRelationshipCaptions: (relationships) ->
for relationship in relationships
relationship.captionLength = @measureRelationshipCaption(relationship, relationship.type)
relationship.captionLayout =
if @captionFitsInsideArrowShaftWidth(relationship)
"internal"
else
"external"
shortenCaption: (relationship, caption, targetWidth) ->
shortCaption = caption
while true
if shortCaption.length <= 2
return ['', 0]
shortCaption = shortCaption.substr(0, shortCaption.length - 2) + '\u2026'
width = @measureRelationshipCaption(relationship, shortCaption)
if width < targetWidth
return [shortCaption, width]
layoutRelationships: (graph) ->
for relationship in graph.relationships()
dx = relationship.target.x - relationship.source.x
dy = relationship.target.y - relationship.source.y
relationship.naturalAngle = ((Math.atan2(dy, dx) / Math.PI * 180) + 180) % 360
delete relationship.arrow
sortedNodes = graph.nodes().sort((a, b) ->
b.relationshipCount(graph) - a.relationshipCount(graph))
for node in sortedNodes
relationships = []
relationships.push(relationship) for relationship in graph.relationships() when relationship.source is node or relationship.target is node
arrowAngles = { floating: {}, fixed: {} }
relationshipMap = {}
for relationship in relationships
relationshipMap[relationship.id] = relationship
if node == relationship.source
if relationship.hasOwnProperty('arrow')
arrowAngles.fixed[relationship.id] = relationship.naturalAngle + relationship.arrow.deflection
else
arrowAngles.floating[relationship.id] = relationship.naturalAngle
if node == relationship.target
if relationship.hasOwnProperty('arrow')
arrowAngles.fixed[relationship.id] = (relationship.naturalAngle - relationship.arrow.deflection + 180) % 360
else
arrowAngles.floating[relationship.id] = (relationship.naturalAngle + 180) % 360
distributedAngles = {}
for id, angle of arrowAngles.floating
distributedAngles[id] = angle
for id, angle of arrowAngles.fixed
distributedAngles[id] = angle
if (relationships.length > 1)
distributedAngles = neo.utils.distributeCircular(arrowAngles, 30)
for id, angle of distributedAngles
relationship = relationshipMap[id]
if not relationship.hasOwnProperty('arrow')
deflection = if node == relationship.source
angle - relationship.naturalAngle
else
(relationship.naturalAngle - angle + 180) % 360
shaftRadius = (parseFloat(@style.forRelationship(relationship).get('shaft-width')) / 2) or 2
headRadius = shaftRadius + 3
headHeight = headRadius * 2
dx = relationship.target.x - relationship.source.x
dy = relationship.target.y - relationship.source.y
square = (distance) -> distance * distance
centreDistance = Math.sqrt(square(dx) + square(dy))
if Math.abs(deflection) < Math.PI / 180
relationship.arrow = new neo.utils.straightArrow(
relationship.source.radius,
relationship.target.radius,
centreDistance,
shaftRadius,
headRadius,
headHeight,
relationship.captionLayout
)
else
relationship.arrow = new neo.utils.arcArrow(
relationship.source.radius,
relationship.target.radius,
centreDistance,
deflection,
shaftRadius * 2,
headRadius * 2,
headHeight,
relationship.captionLayout
)
[relationship.shortCaption, relationship.shortCaptionLength] = if relationship.arrow.shaftLength > relationship.captionLength
[relationship.caption, relationship.captionLength]
else
@shortenCaption(relationship, relationship.caption, relationship.arrow.shaftLength)

View File

@ -0,0 +1,56 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.utils.clickHandler = ->
cc = (selection) ->
# euclidean distance
dist = (a, b) ->
Math.sqrt Math.pow(a[0] - b[0], 2), Math.pow(a[1] - b[1], 2)
down = undefined
tolerance = 5
last = undefined
wait = null
selection.on "mousedown", ->
d3.event.target.__data__.fixed = yes
down = d3.mouse(document.body)
last = +new Date()
d3.event.stopPropagation()
selection.on "mouseup", ->
if dist(down, d3.mouse(document.body)) > tolerance
return
else
if wait
window.clearTimeout wait
wait = null
event.dblclick d3.event.target.__data__
else
event.click d3.event.target.__data__
wait = window.setTimeout(((e) ->
->
wait = null
)(d3.event), 250)
event = d3.dispatch("click", "dblclick")
d3.rebind cc, event, "on"

View File

@ -0,0 +1,85 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.utils.loopArrow
constructor: (nodeRadius, straightLength, spreadDegrees, shaftWidth, headWidth, headLength, captionHeight) ->
spread = spreadDegrees * Math.PI / 180
r1 = nodeRadius
r2 = nodeRadius + headLength
r3 = nodeRadius + straightLength
loopRadius = r3 * Math.tan(spread / 2)
shaftRadius = shaftWidth / 2
@shaftLength = loopRadius * 3 + shaftWidth
class Point
constructor: (@x, @y) ->
toString: ->
"#{@x} #{@y}"
normalPoint = (sweep, radius, displacement) ->
localLoopRadius = radius * Math.tan(spread / 2)
cy = radius / Math.cos(spread / 2)
new Point(
(localLoopRadius + displacement) * Math.sin(sweep),
cy + (localLoopRadius + displacement) * Math.cos(sweep)
)
@midShaftPoint = normalPoint(0, r3, shaftRadius + captionHeight / 2 + 2)
startPoint = (radius, displacement) ->
normalPoint((Math.PI + spread) / 2, radius, displacement)
endPoint = (radius, displacement) ->
normalPoint(-(Math.PI + spread) / 2, radius, displacement)
@outline = ->
inner = loopRadius - shaftRadius
outer = loopRadius + shaftRadius
[
'M', startPoint(r1, shaftRadius)
'L', startPoint(r3, shaftRadius)
'A', outer, outer, 0, 1, 1, endPoint(r3, shaftRadius)
'L', endPoint(r2, shaftRadius)
'L', endPoint(r2, -headWidth / 2)
'L', endPoint(r1, 0)
'L', endPoint(r2, headWidth / 2)
'L', endPoint(r2, -shaftRadius)
'L', endPoint(r3, -shaftRadius)
'A', inner, inner, 0, 1, 0, startPoint(r3, -shaftRadius)
'L', startPoint(r1, -shaftRadius)
'Z'
].join(' ')
@overlay = (minWidth) ->
displacement = Math.max(minWidth / 2, shaftRadius)
inner = loopRadius - displacement
outer = loopRadius + displacement
[
'M', startPoint(r1, displacement)
'L', startPoint(r3, displacement)
'A', outer, outer, 0, 1, 1, endPoint(r3, displacement)
'L', endPoint(r2, displacement)
'L', endPoint(r2, -displacement)
'L', endPoint(r3, -displacement)
'A', inner, inner, 0, 1, 0, startPoint(r3, -displacement)
'L', startPoint(r1, -displacement)
'Z'
].join(' ')

View File

@ -0,0 +1,166 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.utils.pairwiseArcsRelationshipRouting
constructor: (@style) ->
measureRelationshipCaption: (relationship, caption) ->
fontFamily = 'sans-serif'
padding = parseFloat(@style.forRelationship(relationship).get('padding'))
neo.utils.measureText(caption, fontFamily, relationship.captionHeight) + padding * 2
captionFitsInsideArrowShaftWidth: (relationship) ->
parseFloat(@style.forRelationship(relationship).get('shaft-width')) > relationship.captionHeight
measureRelationshipCaptions: (relationships) ->
for relationship in relationships
relationship.captionHeight = parseFloat(@style.forRelationship(relationship).get('font-size'))
relationship.captionLength = @measureRelationshipCaption(relationship, relationship.caption)
relationship.captionLayout =
if @captionFitsInsideArrowShaftWidth(relationship) and not relationship.isLoop()
"internal"
else
"external"
shortenCaption: (relationship, caption, targetWidth) ->
shortCaption = caption || 'caption'
while true
if shortCaption.length <= 2
return ['', 0]
shortCaption = shortCaption.substr(0, shortCaption.length - 2) + '\u2026'
width = @measureRelationshipCaption(relationship, shortCaption)
if width < targetWidth
return [shortCaption, width]
computeGeometryForNonLoopArrows: (nodePairs) ->
square = (distance) -> distance * distance
for nodePair in nodePairs
if not nodePair.isLoop()
dx = nodePair.nodeA.x - nodePair.nodeB.x
dy = nodePair.nodeA.y - nodePair.nodeB.y
angle = ((Math.atan2(dy, dx) / Math.PI * 180) + 360) % 360
centreDistance = Math.sqrt(square(dx) + square(dy))
for relationship in nodePair.relationships
relationship.naturalAngle = if relationship.target is nodePair.nodeA
(angle + 180) % 360
else
angle
relationship.centreDistance = centreDistance
distributeAnglesForLoopArrows: (nodePairs, relationships) ->
for nodePair in nodePairs
if nodePair.isLoop()
angles = []
node = nodePair.nodeA
for relationship in relationships
if not relationship.isLoop()
if relationship.source is node
angles.push relationship.naturalAngle
if relationship.target is node
angles.push relationship.naturalAngle + 180
angles = angles.map((a) -> (a + 360) % 360).sort((a, b) -> a - b)
if angles.length > 0
biggestGap =
start: 0
end: 0
for angle, i in angles
start = angle
end = if i == angles.length - 1
angles[0] + 360
else
angles[i + 1]
if end - start > biggestGap.end - biggestGap.start
biggestGap.start = start
biggestGap.end = end
separation = (biggestGap.end - biggestGap.start) / (nodePair.relationships.length + 1)
for relationship, i in nodePair.relationships
relationship.naturalAngle = (biggestGap.start + (i + 1) * separation - 90) % 360
else
separation = 360 / nodePair.relationships.length
for relationship, i in nodePair.relationships
relationship.naturalAngle = i * separation
layoutRelationships: (graph) ->
nodePairs = graph.groupedRelationships()
@computeGeometryForNonLoopArrows(nodePairs)
@distributeAnglesForLoopArrows(nodePairs, graph.relationships())
for nodePair in nodePairs
for relationship in nodePair.relationships
delete relationship.arrow
middleRelationshipIndex = (nodePair.relationships.length - 1) / 2
defaultDeflectionStep = 30
maximumTotalDeflection = 150
numberOfSteps = nodePair.relationships.length - 1
totalDeflection = defaultDeflectionStep * numberOfSteps
deflectionStep = if totalDeflection > maximumTotalDeflection then maximumTotalDeflection/numberOfSteps else defaultDeflectionStep
for relationship, i in nodePair.relationships
shaftWidth = parseFloat(@style.forRelationship(relationship).get('shaft-width')) or 2
headWidth = shaftWidth + 6
headHeight = headWidth
if nodePair.isLoop()
relationship.arrow = new neo.utils.loopArrow(
relationship.source.radius,
40,
defaultDeflectionStep,
shaftWidth,
headWidth,
headHeight,
relationship.captionHeight
)
else
if i == middleRelationshipIndex
relationship.arrow = new neo.utils.straightArrow(
relationship.source.radius,
relationship.target.radius,
relationship.centreDistance,
shaftWidth,
headWidth,
headHeight,
relationship.captionLayout
)
else
deflection = deflectionStep * (i - middleRelationshipIndex)
if nodePair.nodeA isnt relationship.source
deflection *= -1
relationship.arrow = new neo.utils.arcArrow(
relationship.source.radius,
relationship.target.radius,
relationship.centreDistance,
deflection,
shaftWidth,
headWidth,
headHeight,
relationship.captionLayout
)
[relationship.shortCaption, relationship.shortCaptionLength] = if relationship.arrow.shaftLength > relationship.captionLength
[relationship.caption, relationship.captionLength]
else
@shortenCaption(relationship, relationship.caption, relationship.arrow.shaftLength)

View File

@ -0,0 +1,82 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
class neo.utils.straightArrow
constructor: (startRadius, endRadius, centreDistance, shaftWidth, headWidth, headHeight, captionLayout) ->
@length = centreDistance - (startRadius + endRadius)
@shaftLength = @length - headHeight
startArrow = startRadius
endShaft = startArrow + @shaftLength
endArrow = startArrow + @length
shaftRadius = shaftWidth / 2
headRadius = headWidth / 2
@midShaftPoint =
x: startArrow + @shaftLength / 2
y: 0
@outline = (shortCaptionLength) ->
if captionLayout is "external"
startBreak = startArrow + (@shaftLength - shortCaptionLength) / 2
endBreak = endShaft - (@shaftLength - shortCaptionLength) / 2
[
'M', startArrow, shaftRadius,
'L', startBreak, shaftRadius,
'L', startBreak, -shaftRadius,
'L', startArrow, -shaftRadius,
'Z'
'M', endBreak, shaftRadius,
'L', endShaft, shaftRadius,
'L', endShaft, headRadius,
'L', endArrow, 0,
'L', endShaft, -headRadius,
'L', endShaft, -shaftRadius,
'L', endBreak, -shaftRadius,
'Z'
].join(' ')
else
[
'M', startArrow, shaftRadius,
'L', endShaft, shaftRadius,
'L', endShaft, headRadius,
'L', endArrow, 0,
'L', endShaft, -headRadius,
'L', endShaft, -shaftRadius,
'L', startArrow, -shaftRadius,
'Z'
].join(' ')
@overlay = (minWidth) ->
radius = Math.max(minWidth / 2, shaftRadius)
[
'M', startArrow, radius,
'L', endArrow, radius,
'L', endArrow, -radius,
'L', startArrow, -radius,
'Z'
].join(' ')
deflection: 0

View File

@ -0,0 +1,54 @@
###!
Copyright (c) 2002-2017 "Neo Technology,"
Network Engine for Objects in Lund AB [http://neotechnology.com]
This file is part of Neo4j.
Neo4j is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###
'use strict'
neo.utils.measureText = do ->
measureUsingCanvas = (text, font) ->
canvasSelection = d3.select('canvas#textMeasurementCanvas').data([this])
canvasSelection.enter().append('canvas')
.attr('id', 'textMeasurementCanvas')
.style('display', 'none')
canvas = canvasSelection.node()
context = canvas.getContext('2d')
context.font = font
context.measureText(text).width
cache = do () ->
cacheSize = 10000
map = {}
list = []
(key, calc) ->
cached = map[key]
if cached
cached
else
result = calc()
if (list.length > cacheSize)
delete map[list.splice(0, 1)]
list.push(key)
map[key] = result
return (text, fontFamily, fontSize) ->
font = 'normal normal normal ' + fontSize + 'px/normal ' + fontFamily;
cache(text + font, () ->
measureUsingCanvas(text, font)
)

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export function createGraph (nodes, relationships) {
let graph = new neo.models.Graph()
graph.addNodes(mapNodes(nodes))
graph.addRelationships(mapRelationships(relationships, graph))
graph.display = { initialNodeDisplay: 300, nodeCount: 1 }
return graph
}
export function mapNodes (nodes) {
return nodes.map((node) => new neo.models.Node(node.id, node.labels, node.properties))
}
export function mapRelationships (relationships, graph) {
return relationships.map((rel) => {
const source = graph.findNode(rel.startNodeId)
const target = graph.findNode(rel.endNodeId)
return new neo.models.Relationship(rel.id, source, target, rel.type, rel.properties)
})
}
export function getGraphStats (graph) {
let labelStats = {}
let relTypeStats = {}
graph.nodes().forEach((node) => {
node.labels.forEach((label) => {
if (labelStats['*']) {
labelStats['*'].count = labelStats['*'].count + 1
} else {
labelStats['*'] = {
count: 1,
properties: []
}
}
if (labelStats[label]) {
labelStats[label].count = labelStats[label].count + 1
labelStats[label].properties = Object.assign({}, labelStats[label].properties, node.propertyMap)
} else {
labelStats[label] = {
count: 1,
properties: node.propertyMap
}
}
})
})
graph.relationships().forEach((rel) => {
if (relTypeStats['*']) {
relTypeStats['*'].count = relTypeStats['*'].count + 1
} else {
relTypeStats['*'] = {
count: 1,
properties: []
}
}
if (relTypeStats[rel.type]) {
relTypeStats[rel.type].count = relTypeStats[rel.type].count + 1
relTypeStats[rel.type].properties = Object.assign({}, relTypeStats[rel.type].properties, rel.propertyMap)
} else {
relTypeStats[rel.type] = {
count: 1,
properties: rel.propertyMap
}
}
})
return {labels: labelStats, relTypes: relTypeStats}
}

View File

@ -0,0 +1,232 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { v4 } from 'uuid'
import { stringifyMod } from '../utils'
import * as mappings from './boltMappings'
import { BoltConnectionError, createErrorObject } from '../exceptions'
var neo4j = require('neo4j-driver')
let _drivers = null
let _useRoutingConfig = false
let _routingAvailable = false
const runningQueryRegister = {}
const _useRouting = () => _useRoutingConfig && _routingAvailable
const _getDriver = (host, auth, opts, protocol) => {
const boltHost = protocol + (host || '').split('bolt://').join('')
return neo4j.driver(boltHost, auth, opts)
}
const _validateConnection = (driver, res, rej) => {
if (!driver || !driver.session) return rej('No connection')
const tmp = driver.session()
tmp.run('CALL db.indexes()').then(() => {
tmp.close()
res(driver)
}).catch((e) => {
rej([e, driver])
})
}
const _routingAvailability = () => {
return directTransaction('CALL dbms.procedures()').then((res) => {
const names = res.records.map((r) => r.get('name'))
return names.indexOf('dbms.cluster.overview') > -1
})
}
const _getDriversObj = (props, opts = {}) => {
const driversObj = {}
const auth = opts.withoutCredentials || !props.username
? undefined
: neo4j.auth.basic(props.username, props.password)
const getDirectDriver = () => {
if (driversObj.direct) return driversObj.direct
driversObj.direct = _getDriver(props.host, auth, opts, 'bolt://')
return driversObj.direct
}
const getRoutedDriver = () => {
if (!_useRouting()) return getDirectDriver()
if (driversObj.routed) return driversObj.routed
driversObj.routed = _getDriver(props.host, auth, opts, 'bolt+routing://')
return driversObj.routed
}
return {
getDirectDriver,
getRoutedDriver,
close: () => {
if (driversObj.direct) driversObj.direct.close()
if (driversObj.routed) driversObj.routed.close()
}
}
}
function directConnect (props, opts = {}, onLostConnection = () => {}) {
const p = new Promise((resolve, reject) => {
const creds = opts.withoutCredentials || !props.username
? undefined
: neo4j.auth.basic(props.username, props.password)
const driver = _getDriver(props.host, creds, opts, 'bolt://')
driver.onError = (e) => {
onLostConnection(e)
reject([e, driver])
}
_validateConnection(driver, resolve, reject)
})
return p
}
function openConnection (props, opts = {}, onLostConnection) {
const p = new Promise((resolve, reject) => {
const driversObj = _getDriversObj(props, opts)
const driver = driversObj.getDirectDriver()
driver.onError = (e) => {
onLostConnection(e)
_drivers = null
driversObj.close()
reject([e, driver])
}
const myResolve = (driver) => {
_drivers = driversObj
_routingAvailability()
.then((r) => {
if (r) _routingAvailable = true
if (!r) _routingAvailable = false
})
.catch((e) => (_routingAvailable = false))
resolve(driver)
}
const myReject = (err) => {
_drivers = null
driversObj.close()
reject(err)
}
_validateConnection(driver, myResolve, myReject)
})
return p
}
function _trackedTransaction (input, parameters = {}, session, requestId = null) {
const id = requestId || v4()
if (!session) {
return [id, Promise.reject(createErrorObject(BoltConnectionError))]
}
const closeFn = (cb = () => {}) => {
session.close(cb)
if (runningQueryRegister[id]) delete runningQueryRegister[id]
}
runningQueryRegister[id] = closeFn
const queryPromise = session.run(input, parameters)
.then((r) => {
closeFn()
return r
})
.catch((e) => {
closeFn()
throw e
})
return [id, queryPromise]
}
function cancelTransaction (id, cb) {
if (runningQueryRegister[id]) runningQueryRegister[id](cb)
}
function _transaction (input, parameters, session) {
if (!session) return Promise.reject(createErrorObject(BoltConnectionError))
return session.run(input, parameters)
.then((r) => {
session.close()
return r
})
.catch((e) => {
session.close()
throw e
})
}
function directTransaction (input, parameters, requestId = null, cancelable = false) {
const session = _drivers ? _drivers.getDirectDriver().session() : false
if (!cancelable) return _transaction(input, parameters, session)
return _trackedTransaction(input, parameters, session, requestId)
}
function routedReadTransaction (input, parameters, requestId = null, cancelable = false) {
const session = _drivers ? _drivers.getRoutedDriver().session(neo4j.session.READ) : false
if (!cancelable) return _transaction(input, parameters, session)
return _trackedTransaction(input, parameters, session, requestId)
}
function routedWriteTransaction (input, parameters, requestId = null, cancelable = false) {
const session = _drivers ? _drivers.getRoutedDriver().session(neo4j.session.WRITE) : false
if (!cancelable) return _transaction(input, parameters, session)
return _trackedTransaction(input, parameters, session, requestId)
}
export default {
directConnect,
openConnection,
closeConnection: () => {
if (_drivers) {
_drivers.close()
_drivers = null
}
},
directTransaction,
routedReadTransaction,
routedWriteTransaction,
cancelTransaction,
useRoutingConfig: (shouldWe) => (_useRoutingConfig = shouldWe),
recordsToTableArray: (records, convertInts = true) => {
const intChecker = convertInts ? neo4j.isInt : () => true
const intConverter = convertInts ? (val) => val.toString() : (val) => val
return mappings.recordsToTableArray(records, intChecker, intConverter)
},
stringifyRows: (rows) => {
if (!Array.isArray(rows)) return rows
const flat = mappings.flattenProperties(rows)
if (!Array.isArray(flat)) return rows
return flat.map((col) => {
if (!col) return col
return col.map((fVal) => {
return stringifyMod()(fVal, (val) => {
if (neo4j.isInt(val)) return val.toString()
})
})
})
},
extractNodesAndRelationshipsFromRecords: (records) => {
return mappings.extractNodesAndRelationshipsFromRecords(records, neo4j.types)
},
extractNodesAndRelationshipsFromRecordsForOldVis: (records, filterRels = true) => {
const intChecker = neo4j.isInt
const intConverter = (val) => val.toString()
return mappings.extractNodesAndRelationshipsFromRecordsForOldVis(records, neo4j.types, filterRels, intChecker, intConverter)
},
extractPlan: (result) => {
return mappings.extractPlan(result)
},
retrieveFormattedUpdateStatistics: mappings.retrieveFormattedUpdateStatistics,
itemIntToNumber: (item) => mappings.itemIntToString(item, neo4j.isInt, (val) => val.toNumber()),
neo4j: neo4j
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* global location */
import bolt from './bolt'
import { getUrlInfo } from '../utils'
export const getEncryptionMode = () => {
return location.protocol === 'https:'
}
export const getDiscoveryEndpoint = () => {
const url = location.host ? location.href : 'http://localhost:7474/'
const info = getUrlInfo(url)
return `${info.protocol}//${info.host}/`
}
export const getServerConfig = (includePrefixes = []) => {
return getJmxValues([['Configuration']])
.then((confs) => {
const conf = confs[0]
let filtered
if (conf) {
Object.keys(conf)
.filter((key) => includePrefixes.length < 1 || includePrefixes.some((pfx) => key.startsWith(pfx)))
.forEach((key) => (filtered = {...filtered, [key]: bolt.itemIntToNumber(conf[key].value)}))
}
return filtered || conf
})
}
export const getJmxValues = (nameAttributePairs = []) => {
if (!nameAttributePairs.length) return Promise.reject(null)
return bolt.directTransaction('CALL dbms.queryJmx("org.neo4j:*")')
.then((res) => {
let out = []
nameAttributePairs.forEach((pair) => {
const [name, attribute = null] = pair
if (!name) return out.push(null)
const part = res.records.filter((record) => record.get('name').match(new RegExp(name + '$')))
if (!part.length) return out.push(null)
const attributes = part[0].get('attributes')
if (!attribute) return out.push(attributes)
const key = attribute
if (typeof attributes[key] === 'undefined') return out.push(null)
const val = bolt.itemIntToNumber(attributes[key].value)
out.push({ [key]: val })
})
return out
}).catch((e) => {
return null
})
}
export const isConfigValTruthy = (val) => [true, 'true', 'yes', 1, '1'].indexOf(val) > -1
export const isConfigValFalsy = (val) => [false, 'false', 'no', 0, '0'].indexOf(val) > -1

View File

@ -0,0 +1,161 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import updateStatsFields from './updateStatisticsFields'
export function toObjects (records, intChecker, intConverter) {
const recordValues = records.map((record) => {
let out = []
record.forEach((val, key) => out.push(itemIntToString(val, intChecker, intConverter)))
return out
})
return recordValues
}
export function recordsToTableArray (records, intChecker, intConverter) {
const recordValues = toObjects(records, intChecker, intConverter)
const keys = records[0].keys
return [[...keys], ...recordValues]
}
export function itemIntToString (item, intChecker, intConverter) {
if (intChecker(item)) return intConverter(item)
if (Array.isArray(item)) return arrayIntToString(item, intChecker, intConverter)
if (['number', 'string', 'boolean'].indexOf(typeof item) !== -1) return item
if (item === null) return item
if (typeof item === 'object') return objIntToString(item, intChecker, intConverter)
}
export function arrayIntToString (arr, intChecker, intConverter) {
return arr.map((item) => itemIntToString(item, intChecker, intConverter))
}
export function objIntToString (obj, intChecker, intConverter) {
let newObj = {}
Object.keys(obj).forEach((key) => {
newObj[key] = itemIntToString(obj[key], intChecker, intConverter)
})
return newObj
}
export function extractPlan (result) {
if (result.summary && (result.summary.plan || result.summary.profile)) {
const rawPlan = result.summary.profile || result.summary.plan
const boltPlanToRESTPlanShared = (plan) => {
return {
operatorType: plan.operatorType,
LegacyExpression: plan.arguments.LegacyExpression,
ExpandExpression: plan.arguments.ExpandExpression,
DbHits: plan.dbHits,
Rows: plan.rows,
EstimatedRows: plan.arguments.EstimatedRows,
identifiers: plan.identifiers,
Index: plan.arguments.Index,
children: plan.children.map(boltPlanToRESTPlanShared)
}
}
let obj = boltPlanToRESTPlanShared(rawPlan)
obj['runtime-impl'] = rawPlan.arguments['runtime-impl']
obj['planner-impl'] = rawPlan.arguments['planner-impl']
obj['version'] = rawPlan.arguments['version']
obj['KeyNames'] = rawPlan.arguments['KeyNames']
obj['planner'] = rawPlan.arguments['planner']
obj['runtime'] = rawPlan.arguments['runtime']
return {root: obj}
}
return null
}
export function extractNodesAndRelationshipsFromRecords (records, types) {
if (records.length === 0) {
return { nodes: [], relationships: [] }
}
let keys = records[0].keys
let rawNodes = []
let rawRels = []
records.forEach((record) => {
let graphItems = keys.map((key) => record.get(key))
rawNodes = [...rawNodes, ...graphItems.filter((item) => item instanceof types.Node)]
rawRels = [...rawRels, ...graphItems.filter((item) => item instanceof types.Relationship)]
let paths = graphItems.filter((item) => item instanceof types.Path)
paths.forEach((item) => extractNodesAndRelationshipsFromPath(item, rawNodes, rawRels, types))
})
return { nodes: rawNodes, relationships: rawRels }
}
const resultContainsGraphKeys = (keys) => {
return (keys.includes('nodes') && keys.includes('relationships'))
}
export function extractNodesAndRelationshipsFromRecordsForOldVis (records, types, filterRels, intChecker, intConverter) {
if (records.length === 0) {
return { nodes: [], relationships: [] }
}
let keys = records[0].keys
let rawNodes = []
let rawRels = []
if (resultContainsGraphKeys(keys)) {
rawNodes = [...rawNodes, ...records[0].get(keys[0])]
rawRels = [...rawRels, ...records[0].get(keys[1])]
} else {
records.forEach((record) => {
let graphItems = keys.map((key) => record.get(key))
rawNodes = [...rawNodes, ...graphItems.filter((item) => item instanceof types.Node)]
rawRels = [...rawRels, ...graphItems.filter((item) => item instanceof types.Relationship)]
let paths = graphItems.filter((item) => item instanceof types.Path)
paths.forEach((item) => extractNodesAndRelationshipsFromPath(item, rawNodes, rawRels, types))
})
}
const nodes = rawNodes.map((item) => {
return {id: item.identity.toString(), labels: item.labels, properties: itemIntToString(item.properties, intChecker, intConverter)}
})
let relationships = rawRels
if (filterRels) {
relationships = rawRels.filter((item) => nodes.filter((node) => node.id === item.start.toString()).length > 0 && nodes.filter((node) => node.id === item.end.toString()).length > 0)
}
relationships = relationships.map((item) => {
return {id: item.identity.toString(), startNodeId: item.start.toString(), endNodeId: item.end.toString(), type: item.type, properties: itemIntToString(item.properties, intChecker, intConverter)}
})
return { nodes: nodes, relationships: relationships }
}
const extractNodesAndRelationshipsFromPath = (item, rawNodes, rawRels) => {
let paths = Array.isArray(item) ? item : [item]
paths.forEach((path) => {
path.segments.forEach((segment) => {
rawNodes.push(segment.start)
rawNodes.push(segment.end)
rawRels.push(segment.relationship)
})
})
}
export const retrieveFormattedUpdateStatistics = (result) => {
if (result.summary.counters) {
const stats = result.summary.counters._stats
const statsMessages = updateStatsFields.filter(field => stats[field.field] > 0).map(field => `${field.verb} ${stats[field.field]} ${stats[field.field] === 1 ? field.singular : field.plural}`)
return statsMessages.join(', ')
} else return null
}
export const flattenProperties = (rows) => {
return rows.map((row) => row.map((entry) => (entry && entry.properties) ? entry.properties : entry))
}

View File

@ -0,0 +1,332 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* global test, expect */
import { v1 as neo4j } from 'neo4j-driver-alias'
import {
itemIntToString,
arrayIntToString,
objIntToString,
extractNodesAndRelationshipsFromRecords,
extractPlan,
flattenProperties
} from './boltMappings'
describe('boltMappings', () => {
describe('itemIntToString', () => {
test('should convert matching values with provided function', () => {
// Given
const tests = [
{val: 'hello', checker: (_) => false, converter: (_) => false, expected: 'hello'},
{val: ['hello'], checker: (_) => false, converter: (val) => false, expected: ['hello']},
{val: null, checker: (_) => false, converter: (_) => false, expected: null},
{
val: {str: 'hello'},
checker: (_) => true,
converter: (val) => {
val.str = val.str.toUpperCase()
return val
},
expected: {str: 'HELLO'}
}
]
// When and Then
tests.forEach((test) => {
expect(itemIntToString(test.val, test.checker, test.converter)).toEqual(test.expected)
})
})
})
describe('arrayIntToString', () => {
test('should convert matching values with provided function', () => {
// Given
const tests = [
{val: ['hello', 1], checker: (_) => false, converter: (val) => false, expected: ['hello', 1]},
{val: ['hello', ['ola', 'hi']], checker: (val) => typeof val === 'string', converter: (val) => val.toUpperCase(), expected: ['HELLO', ['OLA', 'HI']]},
{val: ['hello', 1], checker: (val) => typeof val === 'string', converter: (val) => val.toUpperCase(), expected: ['HELLO', 1]}
]
// When and Then
tests.forEach((test) => {
expect(arrayIntToString(test.val, test.checker, test.converter)).toEqual(test.expected)
})
})
})
describe('objIntToString', () => {
test('should convert matching values with provided function', () => {
// Given
const tests = [
{val: {arr: ['hello']}, checker: (_) => false, converter: (val) => false, expected: {arr: ['hello']}},
{
val: {
arr: [
'hello',
[
'ola',
'hi'
]
],
str: 'hello',
num: 2,
obj: {
num: 3,
str: 'inner hello'
}
},
checker: (val) => typeof val === 'string',
converter: (val) => val.toUpperCase(),
expected: {
arr: [
'HELLO',
[
'OLA',
'HI'
]
],
str: 'HELLO',
num: 2,
obj: {
num: 3,
str: 'INNER HELLO'
}
}
}
]
// When and Then
tests.forEach((test) => {
expect(objIntToString(test.val, test.checker, test.converter)).toEqual(test.expected)
})
})
})
describe('extractNodesAndRelationshipsFromRecords', () => {
test.skip('should map bolt records with a path to nodes and relationships', () => {
let startNode = new neo4j.v1.types.Node('1', ['Person'], {prop1: 'prop1'})
let endNode = new neo4j.v1.types.Node('2', ['Movie'], {prop2: 'prop2'})
let relationship = new neo4j.v1.types.Relationship('3', startNode.identity, endNode.identity, 'ACTED_IN', {})
let pathSegment = new neo4j.v1.types.PathSegment(startNode, relationship, endNode)
let path = new neo4j.v1.types.Path(startNode, endNode, [pathSegment])
let boltRecord = {
keys: ['p'],
get: (key) => path
}
let {nodes, relationships} = extractNodesAndRelationshipsFromRecords([boltRecord], neo4j.v1.types)
expect(nodes).to.have.lengthOf(2)
let graphNodeStart = nodes.filter((node) => node.id === '1')[0]
expect(graphNodeStart).to.exist
expect(graphNodeStart.labels).toEqual(['Person'])
expect(graphNodeStart.properties).toEqual({prop1: 'prop1'})
let graphNodeEnd = nodes.filter((node) => node.id === '2')[0]
expect(graphNodeEnd).to.exist
expect(graphNodeEnd.labels).toEqual(['Movie'])
expect(graphNodeEnd.properties).toEqual({prop2: 'prop2'})
expect(relationships).to.have.lengthOf(1)
expect(relationships[0].id).toEqual('3')
expect(relationships[0].startNodeId).toEqual('1')
expect(relationships[0].endNodeId).toEqual('2')
expect(relationships[0].type).toEqual('ACTED_IN')
expect(relationships[0].properties).toEqual({})
})
test.skip('should map bolt nodes and relationships to graph nodes and relationships', () => {
let startNode = new neo4j.v1.types.Node('1', ['Person'], {prop1: 'prop1'})
let endNode = new neo4j.v1.types.Node('2', ['Movie'], {prop2: 'prop2'})
let relationship = new neo4j.v1.types.Relationship('3', startNode.identity, endNode.identity, 'ACTED_IN', {})
let boltRecord = {
keys: ['r', 'n1', 'n2'],
get: (key) => {
if (key === 'r') {
return relationship
}
if (key === 'n1') {
return startNode
}
if (key === 'n2') {
return endNode
}
}
}
let {nodes, relationships} = extractNodesAndRelationshipsFromRecords([boltRecord], neo4j.v1.types)
expect(nodes).to.have.lengthOf(2)
let graphNodeStart = nodes.filter((node) => node.id === '1')[0]
expect(graphNodeStart).to.exist
expect(graphNodeStart.labels).toEqual(['Person'])
expect(graphNodeStart.properties).toEqual({prop1: 'prop1'})
let graphNodeEnd = nodes.filter((node) => node.id === '2')[0]
expect(graphNodeEnd).to.exist
expect(graphNodeEnd.labels).toEqual(['Movie'])
expect(graphNodeEnd.properties).toEqual({prop2: 'prop2'})
expect(relationships).to.have.lengthOf(1)
expect(relationships[0].id).toEqual('3')
expect(relationships[0].startNodeId).toEqual('1')
expect(relationships[0].endNodeId).toEqual('2')
expect(relationships[0].type).toEqual('ACTED_IN')
expect(relationships[0].properties).toEqual({})
})
test.skip('should not include relationships where neither start or end node is not in nodes list', () => {
let relationship = new neo4j.v1.types.Relationship('3', 1, 2, 'ACTED_IN', {})
let boltRecord = {
keys: ['r'],
get: (key) => relationship
}
let relationships = extractNodesAndRelationshipsFromRecords([boltRecord], neo4j.v1.types).relationships
expect(relationships.length).toBe(0)
})
test.skip('should not include relationships where end node is not in nodes list', () => {
let startNode = new neo4j.v1.types.Node('1', ['Person'], {prop1: 'prop1'})
let relationship = new neo4j.v1.types.Relationship('3', startNode.identity, 2, 'ACTED_IN', {})
let boltRecord = {
keys: ['r', 'n1'],
get: (key) => {
if (key === 'r') {
return relationship
}
if (key === 'n1') {
return startNode
}
}
}
let relationships = extractNodesAndRelationshipsFromRecords([boltRecord], neo4j.v1.types).relationships
expect(relationships.length).toBe(0)
})
test.skip('should not include relationships where start node is not in nodes list', () => {
let endNode = new neo4j.v1.types.Node('2', ['Movie'], {prop2: 'prop2'})
let relationship = new neo4j.v1.types.Relationship('3', '1', endNode.identity, 'ACTED_IN', {})
let boltRecord = {
keys: ['r', 'n1'],
get: (key) => {
if (key === 'r') {
return relationship
}
if (key === 'n1') {
return endNode
}
}
}
let relationships = extractNodesAndRelationshipsFromRecords([boltRecord], neo4j.v1.types).relationships
expect(relationships.length).toBe(0)
})
})
describe('extractPlan', () => {
const createPlan = () => {
return {
operatorType: 'operatorType',
arguments: {
LegacyExpression: 'legacy',
ExpandExpression: 'expand',
EstimatedRows: 10,
Index: 1,
version: 'version',
KeyNames: ['keyname'],
planner: 'planner',
runtime: 'runtime',
'planner-impl': 'planner-impl',
'runtime-impl': 'runtime-impl'
},
identifiers: [],
children: []
}
}
const checkExtractedPlan = (extractedPlan) => {
expect(extractedPlan).to.not.be.null
expect(extractedPlan.operatorType).toEqual('operatorType')
expect(extractedPlan.identifiers).toEqual([])
expect(extractedPlan.operatorType).toEqual('operatorType')
expect(extractedPlan.LegacyExpression).toEqual('legacy')
expect(extractedPlan.ExpandExpression).toEqual('expand')
expect(extractedPlan.EstimatedRows).toEqual(10)
expect(extractedPlan.Index).toEqual(1)
expect(extractedPlan.version).toEqual('version')
expect(extractedPlan.KeyNames).toEqual(['keyname'])
expect(extractedPlan.planner).toEqual('planner')
expect(extractedPlan.runtime).toEqual('runtime')
expect(extractedPlan['planner-impl']).toEqual('planner-impl')
expect(extractedPlan['runtime-impl']).toEqual('runtime-impl')
}
test.skip('should extract plan from result summary', () => {
// Given
const result = {
summary: {
plan: createPlan()
}
}
const extractedPlan = extractPlan(result).root
checkExtractedPlan(extractedPlan)
})
test.skip('should extract profile from result summary', () => {
// Given
const profile = createPlan()
profile.dbHits = 20
profile.rows = 14
const result = {
summary: {
profile: profile
}
}
const extractedPlan = extractPlan(result).root
checkExtractedPlan(extractedPlan)
expect(extractedPlan.DbHits).toEqual(20)
expect(extractedPlan.Rows).toEqual(14)
})
test.skip('should return null if no plan or profile is available', () => {
const result = {
summary: {}
}
expect(extractPlan(result)).to.be.null
})
})
describe('flattenProperties', () => {
test('should map properties to object when properties exist', () => {
// Given
const result = [
[{properties: {foo: 'bar'}}]
]
const expectedResult = [
[{foo: 'bar'}]
]
// When
const flattenedProperties = flattenProperties(result)
// Then
expect(flattenedProperties).toEqual(expectedResult)
})
test('should not map properties to object when properties do not exist', () => {
// Given
const result = [
[{x: {foo: 'bar'}}]
]
// When
const flattenedProperties = flattenProperties(result)
// Then
expect(flattenedProperties).toEqual(result)
})
})
})

View File

@ -0,0 +1,14 @@
export default [
{ plural: 'constraints', singular: 'constraint', verb: 'added', field: 'constraintsAdded' },
{ plural: 'constraints', singular: 'constraint', verb: 'removed', field: 'constraintsRemoved' },
{ plural: 'indexes', singular: 'index', verb: 'added', field: 'indexesAdded' },
{ plural: 'indexes', singular: 'index', verb: 'removed', field: 'indexesRemoved' },
{ plural: 'labels', singular: 'label', verb: 'added', field: 'labelsAdded' },
{ plural: 'labels', singular: 'label', verb: 'removed', field: 'labelsRemoved' },
{ plural: 'nodes', singular: 'node', verb: 'created', field: 'nodesCreated' },
{ plural: 'nodes', singular: 'node', verb: 'deleted', field: 'nodesDeleted' },
{ plural: 'properties', singular: 'property', verb: 'set', field: 'propertiesSet' },
{ plural: 'relationships', singular: 'relationship', verb: 'deleted', field: 'relationshipDeleted' },
{ plural: 'relationships', singular: 'relationship', verb: 'deleted', field: 'relationshipsDeleted' },
{ plural: 'relationships', singular: 'relationship', verb: 'created', field: 'relationshipsCreated' }
]

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const hydrate = (initialState, state) => {
if (!state) return state
return (state.hydrated) ? state : { ...initialState, ...state, hydrated: true }
}
export const dehydrate = (state) => {
if (state) {
delete state.hydrated
}
return state
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const AddServerValidationError = 'Wrong format. It should be ":server add name username:password@host:port"'
export const CreateDataSourceValidationError = 'Wrong format. It should be ":datasource create {"name": "myName", "command": "RETURN rand()", "bookmarkId":"uuid-of-existing-bookmark", "refreshInterval": 10, "parameters": {}}"'
export const RemoveDataSourceValidationError = 'Wrong format. It should be ":datasource remove uuid-of-existing-datasource"'
export const BoltConnectionError = 'No connection found, did you connect to Neo4j?'
export const BoltError = '#code# - #message#'
export const Neo4jError = '#message#'
export const UnknownCommandError = 'Unknown command #cmd#'
export const BookmarkNotFoundError = 'No connection with the name #name# found. Add a bookmark before trying to connect.'
export const OpenConnectionNotFoundError = 'No open connection with the name #name# found. You have to connect to a bookmark before you can use it.'
export const CouldNotFetchRemoteGuideError = 'Can not fetch remote guide: #error#'
export const FetchURLError = 'Could not fetch URL: "#error#". This could be due to the remote server policy. See your web browsers error console for more information.'

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import * as messages from './exceptionMessages'
export function getErrorMessage (errorObject) {
let str = messages[errorObject.type]
if (!str) return
const keys = Object.keys(errorObject)
keys.forEach((prop) => {
const re = new RegExp('(#' + prop + '#)', 'g')
str = str.replace(re, errorObject[prop])
})
return str
}
export function createErrorObject (ErrorType, ...rest) {
const obj = new ErrorType(...rest)
if (!obj.code) obj.code = obj.type
obj.message = getErrorMessage(obj)
return obj
}
export function UserException (message) {
return {
type: 'UserException',
message
}
}
export function ConnectionException (message, code = 'Connection Error') {
return {fields: [{
code,
message
}]}
}
export function AddServerValidationError () {
return {
type: 'AddServerValidationError'
}
}
export function CreateDataSourceValidationError () {
return {
type: 'CreateDataSourceValidationError'
}
}
export function RemoveDataSourceValidationError () {
return {
type: 'RemoveDataSourceValidationError'
}
}
export function BoltConnectionError () {
return {
type: 'BoltConnectionError'
}
}
export function BoltError (obj) {
return {
type: 'BoltError',
code: obj.fields[0].code,
message: obj.fields[0].message
}
}
export function Neo4jError (obj) {
return {
type: 'Neo4jError',
message: obj.message
}
}
export function ConnectionNotFoundError (name) {
return {
type: 'ConnectionNotFoundError',
name
}
}
export function OpenConnectionNotFoundError (name) {
return {
type: 'OpenConnectionNotFoundError',
name
}
}
export function UnknownCommandError (cmd) {
return {
type: 'UnknownCommandError',
cmd
}
}
export function CouldNotFetchRemoteGuideError (error) {
return {
type: 'CouldNotFetchRemoteGuideError',
error
}
}
export function FetchURLError (error) {
return {
type: 'FetchURLError',
error
}
}

View File

@ -0,0 +1,202 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const deepEquals = (x, y) => {
if (x && y && typeof x === 'object' && typeof y === 'object') {
if (Object.keys(x).length !== Object.keys(y).length) return false
return Object.keys(x).every((key) => deepEquals(x[key], y[key]))
}
return (x === y)
}
export const flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [])
export const moveInArray = (fromIndex, toIndex, arr) => {
if (!Array.isArray(arr)) return false
if (fromIndex < 0 || fromIndex >= arr.length) return false
if (toIndex < 0 || toIndex >= arr.length) return false
const newArr = [].concat(arr)
const el = arr[fromIndex]
newArr.splice(fromIndex, 1)
newArr.splice(toIndex, 0, el)
return newArr
}
export const debounce = (fn, time, context = null) => {
let pending
return (...args) => {
if (pending) clearTimeout(pending)
pending = setTimeout(() => typeof fn === 'function' && fn.apply(context, args), parseInt(time))
}
}
export const throttle = (fn, time, context = null) => {
let blocking
return (...args) => {
if (blocking) return
blocking = true
typeof fn === 'function' && fn.apply(context, args)
setTimeout(() => (blocking = false), parseInt(time))
}
}
export const isRoutingHost = (host) => {
return /^bolt\+routing:\/\//.test(host)
}
export const toBoltHost = (host) => {
return 'bolt://' + (host || '') // prepend with bolt://
.split('bolt://').join('') // remove bolt://
.split('bolt+routing://').join('') // remove bolt+routing://
}
export const hostIsAllowed = (uri, whitelist = null) => {
if (whitelist === '*') return true
const urlInfo = getUrlInfo(uri)
const hostname = urlInfo.hostname
const hostnamePlusProtocol = urlInfo.protocol + '//' + hostname
let whitelistedHosts = ['guides.neo4j.com', 'localhost']
if (whitelist && whitelist !== '') {
whitelistedHosts = whitelist.split(',')
}
return whitelistedHosts.indexOf(hostname) > -1 ||
whitelistedHosts.indexOf(hostnamePlusProtocol) > -1
}
export const getUrlInfo = (url) => {
const reURLInformation = new RegExp([
'^(?:(https?:)//)?', // protocol
'(([^:/?#]*)(?::([0-9]+))?)', // host (hostname and port)
'(/{0,1}[^?#]*)', // pathname
'(\\?[^#]*|)', // search
'(#.*|)$' // hash
].join(''))
const match = url.match(reURLInformation)
return match && {
protocol: match[1],
host: match[2],
hostname: match[3],
port: match[4],
pathname: match[5],
search: match[6],
hash: match[7]
}
}
export const getUrlParamValue = (name, url) => {
if (!url) return false
let out = []
const re = new RegExp('[\\?&]' + name + '=([^&#]*)', 'g')
let results
while ((results = re.exec(url)) !== null) {
if (results && results[1]) out.push(results[1])
}
if (!out.length) return undefined
return out
}
export const toHumanReadableBytes = (input) => {
let number = +input
if (!isFinite(number)) { return '-' }
if (number < 1024) {
return `${number} B`
}
number /= 1024
let units = ['KiB', 'MiB', 'GiB', 'TiB']
for (let unit of Array.from(units)) {
if (number < 1024) { return `${number.toFixed(2)} ${unit}` }
number /= 1024
}
return `${number.toFixed(2)} PiB`
}
export const getBrowserName = function () {
if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) {
return 'Opera'
}
if (typeof InstallTrigger !== 'undefined') {
return 'Firefox'
}
if (navigator.userAgent.match(/Version\/[\d.]+.*Safari/)) {
return 'Safari'
}
if (window.chrome) {
return 'Chrome'
}
if (document.documentMode) {
return 'Internet Explorer'
}
if (window.StyleMedia) {
return 'Edge'
}
return 'Unknown'
}
export const removeComments = (string) => {
return string.split(/\r?\n/).filter((line) => !line.startsWith('//')).join('\r\n')
}
export const canUseDOM = () => !!(
(typeof window !== 'undefined' &&
window.document && window.document.createElement)
)
export const stringifyMod = () => {
const toString = Object.prototype.toString
const isArray = Array.isArray || function (a) { return toString.call(a) === '[object Array]' }
const escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'}
const escFunc = function (m) { return escMap[m] || '\\u' + (m.charCodeAt(0) + 0x10000).toString(16).substr(1) }
const escRE = /[\\"\u0000-\u001F\u2028\u2029]/g
return function stringify (value, modFn = null) {
if (modFn) {
const modVal = modFn && modFn(value)
if (typeof modVal !== 'undefined') return modVal
}
if (value == null) return 'null'
if (typeof value === 'number') return isFinite(value) ? value.toString() : 'null'
if (typeof value === 'boolean') return value.toString()
if (typeof value === 'object') {
if (typeof value.toJSON === 'function') {
return stringify(value.toJSON(), modFn)
} else if (isArray(value)) {
let res = '['
for (let i = 0; i < value.length; i++) {
res += (i ? ',' : '') + stringify(value[i], modFn)
}
return res + ']'
} else if (toString.call(value) === '[object Object]') {
let tmp = []
for (const k in value) {
if (value.hasOwnProperty(k)) tmp.push(stringify(k, modFn) + ':' + stringify(value[k], modFn))
}
return '{' + tmp.join(',') + '}'
}
}
return '"' + value.toString().replace(escRE, escFunc) + '"'
}
}
// Epic helpers
export const put = (dispatch) => (action) => dispatch(action)

View File

@ -0,0 +1,51 @@
<template>
<div
ref="editor"
:contenteditable="editAble"
:class="{ editing: editAble }"
@blur="inputBlur"
@keyup.27="$event.target.blur"
@dblclick="allowInput"
v-text="inputText"
></div>
</template>
<script>
export default {
name: "editDiv",
props: ["value"],
data() {
return {
editAble: false,
inputText: this.value
};
},
methods: {
//
inputBlur() {
this.$emit("input", this.$refs.editor.innerText);
this.$emit("changeVal")
this.editAble = false;
},
allowInput() {
this.editAble = true;
this.$nextTick(() => {
this.$refs.editor.focus();
});
},
}
};
</script>
<style scoped>
.input-box {
width: 400px;
height: 250px;
border: 1px solid #6e6e6e;
outline: none; /* 隐藏聚焦时外边框 */
padding: 10px;
}
.editing {
border: 1px solid #6e6e6e;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<ul :key="listKey">
<li v-for="(item, index) in dataList" :key="index">
<edit-div
v-model="dataList[index]"
@changeVal="checkList"
:class="{ add: item == '双击添加' }"
></edit-div>
</li>
</ul>
</template>
<script>
import EditDiv from "@/components/EditDiv/index.vue";
export default {
name: "EditList",
props: ["data"],
data() {
return {
dataList: this.data,
listKey: 0
};
},
mounted() {
if (this.dataList[this.dataList.length - 1] != "双击添加") {
this.dataList.push("双击添加");
}
},
methods: {
checkList() {
// ""
for (let i=0; i<this.dataList.length; i++) {
if (!this.dataList[i].trim()) {
this.dataList.splice(i, 1);
break;
}
}
if (this.dataList[this.dataList.length - 1] != "双击添加") {
console.log("add");
this.dataList.push("双击添加");
}
this.reRenderList();
},
reRenderList() {
this.listKey = (this.listKey + 1) % 100;
}
},
components: {
EditDiv,
},
};
</script>
<style scoped>
.add {
color: #c0c4cc;
font-size: 0.7em;
}
ul {
list-style-type:circle;
margin-top: 0px;
margin-bottom: 0px;
}
</style>

View File

@ -0,0 +1,72 @@
<template>
<div id="test" class="test" :class="{ editing: isChecked }">
<div class="view">
<label @dblclick="dbTest()">{{ item }}</label>
</div>
<input
v-myfoucs="isChecked"
class="edit"
type="text"
v-model="inputStr"
@keyup.enter="$event.target.blur"
@blur="inputStred"
/>
</div>
</template>
<script>
export default {
name: "EditText",
props: ["val", "itemName","bigDomain"],
data() {
return {
item: this.val,
isChecked: false,
inputStr: "",
domain: this.itemName,
bigDomain:this.bigDomain
};
},
methods: {
dbTest() {
this.isChecked = true;
this.inputStr = this.item;
},
inputStred() {
this.item = this.inputStr;
this.isChecked = false;
this.$emit("changeDomainVal",this.bigDomain, this.domain, this.item)
}
},
directives: {
myfoucs: {
update(el, binding) {
if (binding.value) {
el.focus();
}
},
},
},
};
</script>
<style lang="scss" scoped>
label {
font-weight: normal;
}
.test.editing .edit {
display: block;
// width: 150px;
}
.test.editing .view {
display: none;
}
.test .edit {
display: none;
}
</style>

View File

@ -0,0 +1,9 @@
/**
* 文本双击可编辑 组件
*/
import EditText from "./EditText.vue";
EditText.install = function (vue) {
vue.component(EditText.name, EditText);
};
export default EditText;

View File

@ -0,0 +1,88 @@
<template>
<div class="year-range-picker">
<el-date-picker
v-model="startYear"
type="year"
placeholder="选择开始年"
size="mini"
class="year-picker"
@change="changeStartYear"
format="yyyy 年"
value-format="yyyy"
>
</el-date-picker>
<span class="range-word"> </span>
<el-date-picker
v-model="endYear"
type="year"
placeholder="选择结束年"
size="mini"
class="year-picker"
@change="changeEndYear"
format="yyyy 年"
value-format="yyyy"
>
</el-date-picker>
</div>
</template>
<script>
export default {
name: "YearRangePicker",
//
props: {
yearRange: {
type: Array,
required: true,
},
},
data() {
return {
startYear: "",
endYear: "",
};
},
mounted() {
//
this.startYear = String(this.yearRange[0]);
this.endYear = String(this.yearRange[1]);
},
methods: {
changeStartYear(val) {
//
if (Number(this.startYear) > Number(this.endYear)) {
this.startYear = this.endYear;
this.endYear = val;
}
//
this.$emit("yearChanged", [Number(this.startYear), Number(this.endYear)]);
},
changeEndYear(val) {
if (Number(this.startYear) > Number(this.endYear)) {
this.endYear = this.startYear;
this.startYear = val;
}
this.$emit("yearChanged", [Number(this.startYear), Number(this.endYear)])
},
},
};
</script>
<style scoped>
.year-range-picker {
color: black;
text-align: center;
font-size: 18px;
}
.range-word {
margin-left: 10px;
margin-right: 10px;
}
.year-range-picker .year-picker {
max-width: 160px;
}
</style>

7
src/config.js 100644
View File

@ -0,0 +1,7 @@
module.exports = {
setting: {
neo4jUrl: 'bolt://222.20.95.239:7687',
neo4jUserName: 'neo4j',
neo4jPassword: 'sunrui00'
}
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1656899019644" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2273" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M704 864c-6.4 0-12.8 0-19.2-6.4l-153.6-102.4L454.4 832c-6.4 6.4-25.6 12.8-32 6.4-12.8-6.4-19.2-19.2-19.2-32l0-140.8c0-6.4 0-12.8 6.4-19.2L652.8 384c12.8-12.8 32-12.8 44.8 0 12.8 12.8 12.8 32 0 44.8l-236.8 256 0 51.2 44.8-38.4c12.8-12.8 25.6-12.8 38.4-6.4l140.8 89.6 108.8-531.2L256 512l121.6 76.8C390.4 595.2 396.8 620.8 384 633.6c-6.4 12.8-32 19.2-44.8 12.8L172.8 537.6C166.4 531.2 160 524.8 160 512c0-12.8 6.4-19.2 19.2-25.6l640-320c12.8-6.4 25.6-6.4 32 0 12.8 6.4 12.8 19.2 12.8 32l-128 640c0 12.8-6.4 19.2-19.2 25.6C710.4 864 710.4 864 704 864z" p-id="2274"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670222642954" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6022" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M320 64h64v160h-64z" p-id="6023"></path><path d="M192 192h640v64H192z" p-id="6024"></path><path d="M640 64h64v160h-64z" p-id="6025"></path><path d="M482.26 832H192.11l-0.11-0.11V192.11l0.11-0.11h639.78l0.11 0.11v290.15a257.16 257.16 0 0 1 64 52.44V192a64.19 64.19 0 0 0-64-64H192a64.19 64.19 0 0 0-64 64v640a64.19 64.19 0 0 0 64 64h342.69a257.16 257.16 0 0 1-52.43-64z" p-id="6026"></path><path d="M896 534.69v89.71a48 48 0 0 1 0 75.7V832a64.19 64.19 0 0 1-64 64H534.69A255 255 0 0 0 704 960c141.38 0 256-114.62 256-256a255 255 0 0 0-64-169.31z" p-id="6027"></path><path d="M808 775.25a48 48 0 1 1-89.22-24.6l-71.1-71.1a47.84 47.84 0 0 1-21.32 0l-43.21 43.21a48 48 0 1 1-24.34-20.91l40-40a48 48 0 1 1 76.38 0l68.33 68.33a48.2 48.2 0 0 1 32.93 0l46.88-46.88a48 48 0 0 1 8.67-54.4V482.26A254.81 254.81 0 0 0 704 448c-141.38 0-256 114.62-256 256a254.82 254.82 0 0 0 34.26 128h349.63l0.11-0.11v-112l-30.79 30.79a47.77 47.77 0 0 1 6.79 24.57zM832 482.26V628.9a48 48 0 0 1 64-4.5v-89.71a257.16 257.16 0 0 0-64-52.43z" p-id="6028"></path><path d="M896 832V700.1a48 48 0 0 1-49.86 5.62L832 719.86v112l-0.11 0.11H482.26a257.16 257.16 0 0 0 52.44 64H832A64.19 64.19 0 0 0 896 832zM256 352h512v64H256zM256 480h224v64H256z" p-id="6029"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672282048164" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4415" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M540.7 133.4l103.5 209.8c4.6 9.4 13.6 15.9 24 17.4l231.5 33.6c26.1 3.8 36.6 35.9 17.7 54.4L749.9 612c-7.5 7.3-10.9 17.9-9.2 28.2l39.5 230.6c4.5 26-22.9 45.9-46.2 33.6L526.9 795.5c-9.3-4.9-20.4-4.9-29.7 0l-207 108.9c-23.4 12.3-50.7-7.6-46.2-33.6l39.5-230.6c1.8-10.3-1.7-20.9-9.2-28.2L106.8 448.7c-18.9-18.4-8.5-50.6 17.7-54.4L356 360.7c10.4-1.5 19.4-8 24-17.4l103.5-209.8c11.7-23.7 45.5-23.7 57.2-0.1z" fill="#409eff" p-id="4416"></path></svg>

After

Width:  |  Height:  |  Size: 775 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672129106572" class="icon" viewBox="0 0 1034 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13034" xmlns:xlink="http://www.w3.org/1999/xlink" width="201.953125" height="200"><path d="M743.17312 681.10336c59.00288-59.07968 91.8784-137.12896 93.056-220.2368 0.256-1.63328 0.43008-3.29728 0.4352-5.00224V55.46496H199.1168v398.2336c-0.04608 0.72192-0.10752 1.43872-0.10752 2.17088 0 164.30592 124.95872 299.94496 284.8256 316.94848v129.7664H252.44672c-18.18624 0-32.97792 14.7968-32.97792 32.9728s14.79168 32.96768 32.97792 32.96768h297.33888v-195.47648c73.02144-7.2448 140.75904-39.24992 193.38752-91.94496zM264.99584 457.35424c0.02048-0.4608 0.07168-0.91136 0.07168-1.37728V121.5232h505.53856v330.72128a33.9456 33.9456 0 0 0-0.20992 3.62496c0 139.35104-113.37216 252.71808-252.71808 252.71808-138.8544 0-251.8784-112.56832-252.68224-251.23328z" p-id="13035" fill="#409eff"></path><path d="M654.83776 334.83264a38.22592 38.22592 0 0 0-30.75072-26.09152l-50.55488-7.36256-22.62528-45.9008a38.13376 38.13376 0 0 0-68.1984-0.10752l-22.62528 45.79328-50.33984 7.36256a38.016 38.016 0 0 0-21.3248 64.95232l36.59264 35.61472-8.66304 50.45248A38.12352 38.12352 0 0 0 471.56224 499.712l45.25056-23.71072 45.35808 23.71072a36.79232 36.79232 0 0 0 17.64352 4.33152 37.97504 37.97504 0 0 0 37.3504-44.4928l-8.66304-50.33984 36.37248-35.4048a37.66784 37.66784 0 0 0 9.96352-38.97344z m-95.0528 58.46016l10.17856 59.1104-53.04832-27.8272L463.872 452.4032l10.17856-59.1104-42.86976-41.68192 59.2128-8.65792 26.52672-53.69344 26.5216 53.69344 59.2128 8.65792-42.86976 41.68192zM613.48352 341.00736l-0.10752 0.10752 0.10752-0.10752zM803.52256 509.30176v-65.9456h10.24c54.94272 0 99.64544-44.6976 99.64544-99.64032s-44.6976-99.64032-99.64544-99.64032h-10.24V178.1248h10.24c91.3664 0 165.69856 74.33216 165.69856 165.69344-0.11264 91.25376-74.44992 165.48352-165.69856 165.48352h-10.24zM221.91616 509.30176c-91.3664 0-165.69856-74.33216-165.69856-165.69856s74.33216-165.69856 165.69856-165.69856h10.24v65.95072h-10.24c-54.94272 0-99.64032 44.6976-99.64032 99.64032s44.6976 99.64032 99.64032 99.64032h10.24v66.16576h-10.24zM817.01888 934.83008a33.69984 33.69984 0 0 1-33.69984 33.69984h-190.90944a33.69984 33.69984 0 1 1 0-67.4048h190.90944a33.69984 33.69984 0 0 1 33.69984 33.70496z" p-id="13036" fill="#409eff"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1656923670749" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2742" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M426.666667 170.666667l85.333333 85.333333h341.333333a85.333333 85.333333 0 0 1 85.333334 85.333333v426.666667a85.333333 85.333333 0 0 1-85.333334 85.333333H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333333V256a85.333333 85.333333 0 0 1 85.333334-85.333333h256z m426.666666 170.666666H170.666667v426.666667h682.666666V341.333333z m-392.533333 247.466667a102.826667 102.826667 0 0 1 102.4 102.4v34.133333H256v-34.133333a102.826667 102.826667 0 0 1 102.4-102.4h102.4z m273.066667-68.266667v68.266667H597.333333v-68.266667h136.533334zM409.6 384a85.333333 85.333333 0 1 1 0 170.666667 85.333333 85.333333 0 0 1 0-170.666667z m392.533333 34.133333v68.266667H597.333333V418.133333h204.8z" p-id="2743"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1656923467424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2293" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M141.730909 369.989818a34.909091 34.909091 0 0 1 34.909091 34.909091v220.020364a34.909091 34.909091 0 0 1-69.818182 0V404.898909a34.909091 34.909091 0 0 1 34.909091-34.909091zM847.825455 46.545455H233.565091a126.929455 126.929455 0 0 0-126.743273 126.743272v66.978909a34.909091 34.909091 0 0 0 69.818182 0V173.288727c0-31.371636 25.553455-56.925091 56.925091-56.925091h614.260364c31.371636 0 56.925091 25.553455 56.92509 56.925091v677.422546c0 31.371636-25.553455 56.925091-56.92509 56.925091H233.565091a57.018182 57.018182 0 0 1-56.925091-56.925091v-72.750546a34.443636 34.443636 0 0 0-30.394182-33.978182h83.502546a34.909091 34.909091 0 0 0 0-69.818181H84.340364a34.909091 34.909091 0 0 0 0 69.818181h52.922181a34.536727 34.536727 0 0 0-30.394181 33.978182v72.750546A126.836364 126.836364 0 0 0 233.565091 977.454545h614.260364a126.929455 126.929455 0 0 0 126.743272-126.743272V173.288727A126.929455 126.929455 0 0 0 847.825455 46.545455zM382.324364 655.685818l87.831272-256.139636-41.611636-112.733091a34.909091 34.909091 0 0 1 32.721455-46.964364h177.943272a34.862545 34.862545 0 0 1 32.721455 46.964364l-41.658182 112.733091 87.831273 256.139636a34.909091 34.909091 0 0 1-13.824 40.494546l-139.636364 91.927272a35.002182 35.002182 0 0 1-39.330909-0.698181L395.170909 695.482182a34.909091 34.909091 0 0 1-12.846545-39.796364z m129.070545-346.065454l20.154182 54.551272h37.422545l20.200728-54.551272h-77.777455z m-54.551273 343.970909l89.320728 63.069091 97.140363-63.953455-74.984727-218.670545h-36.165818l-75.310546 219.554909zM84.340364 363.240727h145.454545a34.909091 34.909091 0 0 0 0-69.818182H84.340364a34.909091 34.909091 0 0 0 0 69.818182z" p-id="2294"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1658111017393" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2746" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M898.34 188.81L538.35 74.51l-359.87 114.3c-13.53 4.27-22.67 16.86-22.55 31.1v454.94c0.72 3.09 17.69 77.98 91.27 150.5 56.61 55.78 192.4 127.48 251.63 158.57 9.25 4.87 13.65 6.65 17.56 8.78l17.57 8.78c4.63 2.49 12.94 2.38 17.57 0l17.57-8.78c34.65-16.97 195-102.9 260.4-167.35 73.71-72.52 90.68-147.41 91.39-150.5V219.91c0.23-14.24-9.02-26.83-22.55-31.1zM542.86 930.98l-0.24-0.12h0.59c-0.23 0.12-0.35 0.12-0.35 0.12z m85.81-264.32h-58.16v81.9c0 18.99-15.43 34.42-34.42 34.42-18.99 0-34.42-15.43-34.42-34.42V537.29c0-0.71 0.12-1.3 0.12-1.9-6.29-1.9-12.58-4.16-18.87-7-21.24-9.73-39.4-27.18-51.51-47-12.58-20.65-19.23-46.53-17.21-70.74 2.37-27.65 12.34-51.75 30.03-73.23 30.62-37.15 86.88-52.22 131.98-35.73 25.52 9.38 46.29 25.05 61.96 47.12 13.77 19.35 20.77 43.44 21.37 67.06 0 0.71 0.12 1.3 0 2.02 0 0.83 0 1.66-0.12 2.49-1.66 52.46-37.74 101.95-89.02 115.61v60.41h58.16c18.99 0 34.42 15.43 34.42 34.42v1.42h0.12c-0.01 18.99-15.44 34.42-34.43 34.42z" p-id="2747"></path><path d="M577.99 449.1c0.35-0.59 0.71-1.07 0.83-1.19a97.38 97.38 0 0 0 5.58-9.74c1.54-4.39 2.73-8.78 3.68-13.41 0.12-2.25 0.24-4.63 0.36-6.89 0-2.26-0.12-4.63-0.36-6.88-0.83-4.51-2.14-9.02-3.68-13.41a108.73 108.73 0 0 0-4.51-8.19c-0.36-0.36-1.19-1.54-2.61-3.8-1.3-1.43-2.61-2.97-4.03-4.27-1.66-1.66-3.44-3.32-5.34-4.87-0.59-0.35-1.06-0.71-1.19-0.83a97.226 97.226 0 0 0-9.73-5.58c-4.39-1.54-8.78-2.73-13.29-3.68-4.63-0.35-9.26-0.35-13.89 0-4.51 0.83-9.02 2.14-13.29 3.68a108.73 108.73 0 0 0-8.19 4.51c-0.36 0.36-1.54 1.19-3.8 2.61-1.43 1.3-2.97 2.61-4.27 4.03-1.66 1.66-3.32 3.44-4.87 5.34-0.35 0.59-0.71 1.07-0.83 1.19a97.226 97.226 0 0 0-5.58 9.73c-1.54 4.39-2.73 8.78-3.68 13.29-0.35 4.63-0.35 9.26 0 13.89 0.83 4.51 2.14 9.02 3.68 13.29 1.42 2.85 2.85 5.46 4.51 8.19 0.36 0.36 1.19 1.54 2.61 3.8 1.3 1.43 2.61 2.97 4.03 4.27 1.66 1.66 3.44 3.32 5.34 4.87 0.59 0.35 1.07 0.71 1.19 0.83 3.08 2.02 6.41 3.92 9.73 5.58 4.39 1.54 8.78 2.73 13.29 3.68 4.63 0.36 9.26 0.36 13.89 0 4.51-0.83 9.02-2.14 13.29-3.68 2.85-1.42 5.46-2.85 8.19-4.51 0.36-0.36 1.54-1.19 3.8-2.61 1.54-1.19 2.97-2.49 4.27-3.92 1.67-1.64 3.33-3.42 4.87-5.32z" p-id="2748"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672111313123" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7058" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M784.8 64.1H242.6c-30.7 0-55.7 24.9-55.7 55.7v180.5c0 18.6 9.3 36 24.8 46.3l177.1 117.9C306.6 509 250.6 596.1 250.6 696c0 145.1 118 263.1 263.1 263.1 145 0 263-118 263-263.1 0-99.9-56-186.9-138.2-231.5l177.1-117.9c15.5-10.3 24.8-27.7 24.8-46.3V119.8c0-30.8-24.9-55.7-55.6-55.7z m-63.8 632c0 114.6-92.8 207.4-207.4 207.4-114.6 0-207.4-92.8-207.4-207.4 0-114.5 92.8-207.4 207.4-207.4 114.6 0 207.4 92.9 207.4 207.4z m63.8-395.8L678.7 371V265c0-10.2-8.3-18.5-18.6-18.5-10.2 0-18.6 8.3-18.6 18.5v130.7l-66.9 44.6c-19.6-4.7-40-7.2-61-7.2s-41.4 2.6-61 7.2L384.5 395V265c0-10.2-8.3-18.5-18.6-18.5s-18.6 8.3-18.6 18.5v105.2l-104.9-69.9V119.8h104.9v49.7c0 10.2 8.3 18.6 18.6 18.6 10.2 0 18.6-8.3 18.6-18.6v-49.7h257v49.7c0 10.2 8.3 18.6 18.6 18.6s18.6-8.3 18.6-18.6v-49.7h106.1v180.5z" fill="#e6a23c" p-id="7059"></path><path d="M480.4 550.4L449.5 613l-69.1 10c-14 2-25.6 11.8-30 25.3-4.4 13.4-0.7 28.2 9.4 38.1l50 48.7-11.8 68.8c-2.4 13.9 3.3 28 14.8 36.3 6.5 4.7 14.1 7.1 21.8 7.1 5.9 0 11.8-1.4 17.3-4.3l61.8-32.5 61.8 32.5c5.4 2.8 11.4 4.3 17.3 4.3 7.7 0 15.3-2.4 21.8-7.1 11.4-8.3 17.2-22.4 14.8-36.3L617.5 735l50-48.8c10.1-9.9 13.8-24.6 9.4-38-4.4-13.4-16-23.2-30-25.3l-69.1-10-30.8-62.5c-6.3-12.7-19.2-20.7-33.3-20.7-14.1 0-27 8-33.3 20.7z m72.8 96.5l88.4 12.8-64 62.3 15.1 88-79-41.5-79 41.5 15.1-88-63.9-62.3 88.4-12.8 39.5-80.1 39.4 80.1z" fill="#e6a23c" p-id="7060"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670223140218" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9793" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M820.736 807.488l96.288 96.288a32 32 0 1 1-45.248 45.248l-105.696-105.696a224 224 0 1 1 54.656-35.84z m-143.04-455.424a288.352 288.352 0 0 0-81.184 9.92c-25.28-19.84-53.888-33.44-84.512-39.04v77.568a289.6 289.6 0 0 0-64 58.464V322.88c-108.544 19.968-192 140-192 285.088 0 145.088 83.456 265.12 192 285.088v-72.064a289.6 289.6 0 0 0 64 58.464v13.6c5.344-0.96 10.656-2.24 15.872-3.68a286.144 286.144 0 0 0 83.168 32.128C571.744 946.112 527.2 960 480 960c-100.416 0-188.864-62.816-240.384-158.08A64 64 0 0 0 160 864a32 32 0 0 1-64 0 128 128 0 0 1 115.84-127.424A415.904 415.904 0 0 1 193.152 640H96a32 32 0 0 1 0-64h97.184c2.496-33.792 8.896-66.24 18.624-96.576A128 128 0 0 1 96 352a32 32 0 0 1 64 0 64 64 0 0 0 79.616 62.08c17.28-31.936 38.688-60.224 63.328-83.712a192 192 0 1 1 354.144 0c7.136 6.848 14.048 14.08 20.64 21.696z m116.32 26.976c3.84-8.192 5.984-17.376 5.984-27.04a32 32 0 0 1 64 0c0 21.92-5.504 42.56-15.232 60.608a288 288 0 0 0-54.72-33.568zM356.544 289.92A244.448 244.448 0 0 1 480 256c44.16 0 86.048 12.16 123.456 33.92A128.128 128.128 0 0 0 480 128a128 128 0 0 0-123.456 161.92zM672 800a160 160 0 1 0 0-320 160 160 0 0 0 0 320z" fill="#000000" p-id="9794"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1658195300643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2408" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M512 563.2c-127.9744 0-230.4-115.2512-230.4-256s102.4256-256 230.4-256 230.4 115.2512 230.4 256-102.4256 256-230.4 256z m0-51.2c98.2528 0 179.2-91.0592 179.2-204.8s-80.9472-204.8-179.2-204.8-179.2 91.0592-179.2 204.8 80.9472 204.8 179.2 204.8zM128 793.6a179.2 179.2 0 0 1 179.3792-179.2h409.2416C815.6416 614.4 896 694.7328 896 793.6a179.2 179.2 0 0 1-179.3792 179.2H307.3792C208.3584 972.8 128 892.4672 128 793.6z m51.2 0c0 70.5792 57.4464 128 128.1792 128h409.2416A128 128 0 0 0 844.8 793.6c0-70.5792-57.4464-128-128.1792-128H307.3792A128 128 0 0 0 179.2 793.6z" p-id="2409"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670222779936" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7905" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M544.033302 516.000776V96.000564c0-17.672051-14.478655-31.998984-32.150706-31.998984s-32.150706 14.326933-32.150707 31.998984v420.061624c-54.909072 14.251072-95.809105 64.333925-95.809105 123.935554 0 59.691941 40.82056 109.836206 96.090876 124.000579a32.298816 32.298816 0 0 0-0.281771 4.006195v159.99492c0 17.672051 14.478655 31.998984 32.150707 31.998984s32.150706-14.326933 32.150706-31.998984V768.004516a32.280754 32.280754 0 0 0-0.317894-3.941171c55.389526-14.088512 96.397933-64.301413 96.397932-124.058378 0.010837-59.691941-40.809723-109.832593-96.080038-124.004191z m13.221527 169.249989a64.00158 64.00158 0 1 1 18.744945-45.256635 63.578925 63.578925 0 0 1-18.744945 45.260247zM223.678606 196.003711a32.287979 32.287979 0 0 0 0.292607-4.002582V96.000564a31.970084 31.970084 0 1 0-63.940169 0V192.001129a32.371065 32.371065 0 0 0 0.180622 4.060381c-55.270316 14.160761-96.173962 64.301413-96.173961 124.000579s40.910871 109.818143 96.181186 123.996966a32.360228 32.360228 0 0 0-0.184234 3.937558V927.999436a31.970084 31.970084 0 1 0 63.940169 0V448.000226a32.287979 32.287979 0 0 0-0.278158-3.883372c55.385914-14.088512 96.350971-64.301413 96.350971-124.054765S279.071744 210.084998 223.678606 196.003711z m13.575545 169.315013A64.00158 64.00158 0 1 1 255.999097 320.062089a63.578925 63.578925 0 0 1-18.744946 45.256635zM959.99842 320.062089c0-59.775027-41.048144-109.977091-96.45212-124.058378a32.360228 32.360228 0 0 0 0.173397-4.002582V96.000564a31.970084 31.970084 0 1 0-63.940169 0V192.001129a32.291592 32.291592 0 0 0 0.299833 4.060381c-55.270316 14.160761-96.116162 64.301413-96.116163 124.000579s40.82056 109.836206 96.090876 124.000578a32.298816 32.298816 0 0 0-0.281771 3.99897V927.999436a31.970084 31.970084 0 1 0 63.940169 0V448.061637a32.381903 32.381903 0 0 0-0.166172-3.941171c55.407588-14.084899 96.452119-64.286963 96.45212-124.058377z m-82.746526 45.253022a63.997968 63.997968 0 1 1 18.748558-45.253022 63.578925 63.578925 0 0 1-18.748558 45.253022z" fill="" p-id="7906"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672111280527" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6806" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M784.8 64.1H242.6c-30.7 0-55.7 24.9-55.7 55.7v180.5c0 18.6 9.3 36 24.8 46.3l177.1 117.9C306.6 509 250.6 596.1 250.6 696c0 145.1 118 263.1 263.1 263.1 145 0 263-118 263-263.1 0-99.9-56-186.9-138.2-231.5l177.1-117.9c15.5-10.3 24.8-27.7 24.8-46.3V119.8c0-30.8-24.9-55.7-55.6-55.7z m-63.8 632c0 114.6-92.8 207.4-207.4 207.4-114.6 0-207.4-92.8-207.4-207.4 0-114.5 92.8-207.4 207.4-207.4 114.6 0 207.4 92.9 207.4 207.4z m63.8-395.8L678.7 371V265c0-10.2-8.3-18.5-18.6-18.5-10.2 0-18.6 8.3-18.6 18.5v130.7l-66.9 44.6c-19.6-4.7-40-7.2-61-7.2s-41.4 2.6-61 7.2L384.5 395V265c0-10.2-8.3-18.5-18.6-18.5s-18.6 8.3-18.6 18.5v105.2l-104.9-69.9V119.8h104.9v49.7c0 10.2 8.3 18.6 18.6 18.6 10.2 0 18.6-8.3 18.6-18.6v-49.7h257v49.7c0 10.2 8.3 18.6 18.6 18.6s18.6-8.3 18.6-18.6v-49.7h106.1v180.5z" fill="#f56c6c" p-id="6807"></path><path d="M480.4 550.4L449.5 613l-69.1 10c-14 2-25.6 11.8-30 25.3-4.4 13.4-0.7 28.2 9.4 38.1l50 48.7-11.8 68.8c-2.4 13.9 3.3 28 14.8 36.3 6.5 4.7 14.1 7.1 21.8 7.1 5.9 0 11.8-1.4 17.3-4.3l61.8-32.5 61.8 32.5c5.4 2.8 11.4 4.3 17.3 4.3 7.7 0 15.3-2.4 21.8-7.1 11.4-8.3 17.2-22.4 14.8-36.3L617.5 735l50-48.8c10.1-9.9 13.8-24.6 9.4-38-4.4-13.4-16-23.2-30-25.3l-69.1-10-30.8-62.5c-6.3-12.7-19.2-20.7-33.3-20.7-14.1 0-27 8-33.3 20.7z m72.8 96.5l88.4 12.8-64 62.3 15.1 88-79-41.5-79 41.5 15.1-88-63.9-62.3 88.4-12.8 39.5-80.1 39.4 80.1z" fill="#f56c6c" p-id="6808"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670222723946" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6974" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1024.25175 0l-209.92 23.04L883.45175 92.16 655.61175 370.688 419.06775 152.064c-15.872-14.848-40.96-14.848-57.344-0.512L14.07575 465.408C-3.33225 481.28-4.86825 508.416 11.00375 525.824c8.192 9.216 19.968 13.824 31.744 13.824 10.24 0 20.48-3.584 28.672-10.752l318.464-287.744 241.152 222.72c8.704 8.192 20.48 11.776 31.744 11.264 11.776-1.024 22.528-6.656 30.208-15.36l250.88-306.688 57.344 57.344L1024.25175 0z m0 0M133.37175 1024H30.97175c-16.896 0-30.72-13.824-30.72-30.72v-348.16c0-16.896 13.824-30.72 30.72-30.72h102.4c16.896 0 30.72 13.824 30.72 30.72v348.16c0 16.896-13.824 30.72-30.72 30.72z" p-id="6975"></path><path d="M420.09175 1024H317.69175c-16.896 0-30.72-13.824-30.72-30.72V440.32c0-16.896 13.824-30.72 30.72-30.72h102.4c16.896 0 30.72 13.824 30.72 30.72v552.96c0 16.896-13.824 30.72-30.72 30.72zM706.81175 1024h-102.4c-16.896 0-30.72-13.824-30.72-30.72v-399.36c0-16.896 13.824-30.72 30.72-30.72h102.4c16.896 0 30.72 13.824 30.72 30.72v399.36c0 16.896-13.824 30.72-30.72 30.72zM993.53175 1024h-102.4c-16.896 0-30.72-13.824-30.72-30.72V337.92c0-16.896 13.824-30.72 30.72-30.72h102.4c16.896 0 30.72 13.824 30.72 30.72v655.36c0 16.896-13.824 30.72-30.72 30.72z" p-id="6976"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672111395736" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9490" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M784.8 64.1H242.6c-30.7 0-55.7 24.9-55.7 55.7v180.5c0 18.6 9.3 36 24.8 46.3l177.1 117.9C306.6 509 250.6 596.1 250.6 696c0 145.1 118 263.1 263.1 263.1 145 0 263-118 263-263.1 0-99.9-56-186.9-138.2-231.5l177.1-117.9c15.5-10.3 24.8-27.7 24.8-46.3V119.8c0-30.8-24.9-55.7-55.6-55.7z m-63.8 632c0 114.6-92.8 207.4-207.4 207.4-114.6 0-207.4-92.8-207.4-207.4 0-114.5 92.8-207.4 207.4-207.4 114.6 0 207.4 92.9 207.4 207.4z m63.8-395.8L678.7 371V265c0-10.2-8.3-18.5-18.6-18.5-10.2 0-18.6 8.3-18.6 18.5v130.7l-66.9 44.6c-19.6-4.7-40-7.2-61-7.2s-41.4 2.6-61 7.2L384.5 395V265c0-10.2-8.3-18.5-18.6-18.5s-18.6 8.3-18.6 18.5v105.2l-104.9-69.9V119.8h104.9v49.7c0 10.2 8.3 18.6 18.6 18.6 10.2 0 18.6-8.3 18.6-18.6v-49.7h257v49.7c0 10.2 8.3 18.6 18.6 18.6s18.6-8.3 18.6-18.6v-49.7h106.1v180.5z" fill="#67c23a" p-id="9491"></path><path d="M480.4 550.4L449.5 613l-69.1 10c-14 2-25.6 11.8-30 25.3-4.4 13.4-0.7 28.2 9.4 38.1l50 48.7-11.8 68.8c-2.4 13.9 3.3 28 14.8 36.3 6.5 4.7 14.1 7.1 21.8 7.1 5.9 0 11.8-1.4 17.3-4.3l61.8-32.5 61.8 32.5c5.4 2.8 11.4 4.3 17.3 4.3 7.7 0 15.3-2.4 21.8-7.1 11.4-8.3 17.2-22.4 14.8-36.3L617.5 735l50-48.8c10.1-9.9 13.8-24.6 9.4-38-4.4-13.4-16-23.2-30-25.3l-69.1-10-30.8-62.5c-6.3-12.7-19.2-20.7-33.3-20.7-14.1 0-27 8-33.3 20.7z m72.8 96.5l88.4 12.8-64 62.3 15.1 88-79-41.5-79 41.5 15.1-88-63.9-62.3 88.4-12.8 39.5-80.1 39.4 80.1z" fill="#67c23a" p-id="9492"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672111436452" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9742" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M784.8 64.1H242.6c-30.7 0-55.7 24.9-55.7 55.7v180.5c0 18.6 9.3 36 24.8 46.3l177.1 117.9C306.6 509 250.6 596.1 250.6 696c0 145.1 118 263.1 263.1 263.1 145 0 263-118 263-263.1 0-99.9-56-186.9-138.2-231.5l177.1-117.9c15.5-10.3 24.8-27.7 24.8-46.3V119.8c0-30.8-24.9-55.7-55.6-55.7z m-63.8 632c0 114.6-92.8 207.4-207.4 207.4-114.6 0-207.4-92.8-207.4-207.4 0-114.5 92.8-207.4 207.4-207.4 114.6 0 207.4 92.9 207.4 207.4z m63.8-395.8L678.7 371V265c0-10.2-8.3-18.5-18.6-18.5-10.2 0-18.6 8.3-18.6 18.5v130.7l-66.9 44.6c-19.6-4.7-40-7.2-61-7.2s-41.4 2.6-61 7.2L384.5 395V265c0-10.2-8.3-18.5-18.6-18.5s-18.6 8.3-18.6 18.5v105.2l-104.9-69.9V119.8h104.9v49.7c0 10.2 8.3 18.6 18.6 18.6 10.2 0 18.6-8.3 18.6-18.6v-49.7h257v49.7c0 10.2 8.3 18.6 18.6 18.6s18.6-8.3 18.6-18.6v-49.7h106.1v180.5z" fill="#409eff" p-id="9743"></path><path d="M480.4 550.4L449.5 613l-69.1 10c-14 2-25.6 11.8-30 25.3-4.4 13.4-0.7 28.2 9.4 38.1l50 48.7-11.8 68.8c-2.4 13.9 3.3 28 14.8 36.3 6.5 4.7 14.1 7.1 21.8 7.1 5.9 0 11.8-1.4 17.3-4.3l61.8-32.5 61.8 32.5c5.4 2.8 11.4 4.3 17.3 4.3 7.7 0 15.3-2.4 21.8-7.1 11.4-8.3 17.2-22.4 14.8-36.3L617.5 735l50-48.8c10.1-9.9 13.8-24.6 9.4-38-4.4-13.4-16-23.2-30-25.3l-69.1-10-30.8-62.5c-6.3-12.7-19.2-20.7-33.3-20.7-14.1 0-27 8-33.3 20.7z m72.8 96.5l88.4 12.8-64 62.3 15.1 88-79-41.5-79 41.5 15.1-88-63.9-62.3 88.4-12.8 39.5-80.1 39.4 80.1z" fill="#409eff" p-id="9744"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670222805024" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8841" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M892.224 117.024c-83.936-83.936-231.648-81.12-387.616-3.84-155.936-77.28-303.68-80.096-387.616 3.84-69.76 69.76-79.488 183.552-36.64 309.344a95.776 95.776 0 0 0 12.992 122.528c-55.936 136.896-53.472 266.176 23.68 343.328 83.936 83.936 231.712 81.152 387.776 3.776 152.8 75.68 302.016 81.632 387.424-3.776 85.44-85.44 79.424-234.72 3.68-387.584 75.744-152.896 81.76-302.176-3.68-387.616z m-45.248 729.952c-96.416 96.416-332.64 29.408-523.36-161.344a31.968 31.968 0 1 0-45.248 45.248 913.056 913.056 0 0 0 157.376 126.304c-117.248 47.904-219.104 44.128-273.472-10.24-55.776-55.776-56.544-158.368-10.656-271.808 2.816 0.256 5.504 0.864 8.384 0.864a96 96 0 0 0 96-96c0-21.024-6.944-40.288-18.4-56.096a870.592 870.592 0 0 1 86.016-100.288 31.968 31.968 0 1 0-45.248-45.248 933.6 933.6 0 0 0-93.472 109.344A92.928 92.928 0 0 0 160 384a95.68 95.68 0 0 0-24.736 3.616c-28.704-96.448-19.776-178.56 27.008-225.344 62.368-62.368 187.264-58.176 326.112 14.24a31.008 31.008 0 0 0 16.544 3.04 31.104 31.104 0 0 0 16.032-3.104c138.816-72.352 263.648-76.544 326.016-14.176 56 56 56.544 159.2 10.048 273.216a879.36 879.36 0 0 0-67.584-92.48c6.528-11.584 10.56-24.768 10.56-39.008a80 80 0 1 0-80 80c6.368 0 12.48-0.928 18.4-2.336a795.968 795.968 0 0 1 85.408 122.944c-31.52 56.672-73.984 114.08-126.176 168.352-3.2-0.384-6.336-0.96-9.632-0.96a80 80 0 1 0 65.792 34.624 901.056 901.056 0 0 0 103.232-132.864c46.464 113.984 45.952 217.216-10.048 273.216z" p-id="8842"></path><path d="M512 384a128 128 0 1 0 0 256 128 128 0 0 0 0-256z m0 192a64 64 0 1 1 0.032-128.032A64 64 0 0 1 512 576z" p-id="8843"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670222569896" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3504" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M969.6 502.4l-118.4-112-323.2-300.8c-9.6-9.6-22.4-9.6-32 0l-313.6 297.6c-3.2 3.2-6.4 6.4-9.6 9.6l-118.4 112c-9.6 9.6-9.6 22.4 0 32s22.4 9.6 32 0l83.2-80 0 393.6c0 48 41.6 89.6 92.8 89.6l83.2 0c38.4 0 70.4-28.8 70.4-67.2l0-217.6 99.2 0 99.2 0 0 217.6c0 35.2 32 67.2 70.4 67.2l83.2 0c51.2 0 92.8-38.4 92.8-89.6l0-396.8 80 73.6c9.6 9.6 22.4 9.6 32 0C979.2 524.8 979.2 512 969.6 502.4zM809.6 857.6c0 25.6-19.2 44.8-44.8 44.8l-83.2 0c-12.8 0-22.4-9.6-22.4-22.4L659.2 640c0-12.8-9.6-22.4-22.4-22.4l-121.6 0-121.6 0c-12.8 0-22.4 9.6-22.4 22.4l0 240c0 12.8-9.6 22.4-22.4 22.4l-83.2 0c-25.6 0-44.8-19.2-44.8-44.8l0-438.4 294.4-281.6 294.4 281.6L809.6 857.6z" p-id="3505"></path></svg>

After

Width:  |  Height:  |  Size: 1008 B

View File

@ -7,23 +7,17 @@
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
<img src="@/assets/images/default.png" class="user-avatar">
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/">
<el-dropdown-item>
Home
首页
</el-dropdown-item>
</router-link>
<a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
<el-dropdown-item>Docs</el-dropdown-item>
</a>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">Log Out</span>
<span style="display:block;">退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@ -43,8 +37,7 @@ export default {
},
computed: {
...mapGetters([
'sidebar',
'avatar'
'sidebar'
])
},
methods: {

View File

@ -1,16 +1,54 @@
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
<div
v-if="
!item.hidden &&
(item.path != '/manage' ||
(item.path === '/manage' && role != 'ordinary')) &&
(item.path != '/test' || (item.path === '/test' && role != 'ordinary'))
"
>
<template
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.alwaysShow
"
>
<app-link
v-if="
onlyOneChild.meta &&
(item.path != '/supermanage' ||
(item.path === '/supermanage' && role === 'super')) &&
(item.path != '/analysis' ||
(item.path === '/analysis' &&
(role === 'super' || role === 'manager')))
"
:to="resolvePath(onlyOneChild.path)"
>
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<item
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
:title="onlyOneChild.meta.title"
/>
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<el-submenu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
popper-append-to-body
>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
<item
v-if="item.meta"
:icon="item.meta && item.meta.icon"
:title="item.meta.title"
/>
</template>
<sidebar-item
v-for="child in item.children"
@ -25,71 +63,73 @@
</template>
<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
import path from "path";
import { isExternal } from "@/utils/validate";
import Item from "./Item";
import AppLink from "./Link";
import FixiOSBug from "./FixiOSBug";
export default {
name: 'SidebarItem',
name: "SidebarItem",
components: { Item, AppLink },
mixins: [FixiOSBug],
props: {
// route object
item: {
type: Object,
required: true
required: true,
},
isNest: {
type: Boolean,
default: false
default: false,
},
basePath: {
type: String,
default: ''
}
default: "",
},
},
data() {
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
// TODO: refactor with render function
this.onlyOneChild = null
return {}
this.onlyOneChild = null;
return {
role: this.$store.state.user.role,
};
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
const showingChildren = children.filter((item) => {
if (item.hidden) {
return false
return false;
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
this.onlyOneChild = item;
return true;
}
})
});
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
return true;
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
return true
this.onlyOneChild = { ...parent, path: "", noShowingChildren: true };
return true;
}
return false
return false;
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath
return routePath;
}
if (isExternal(this.basePath)) {
return this.basePath
return this.basePath;
}
return path.resolve(this.basePath, routePath)
}
}
}
return path.resolve(this.basePath, routePath);
},
},
};
</script>

Some files were not shown because too many files have changed in this diff Show More