[6.x] Graph LESS to SASS (#23348) #23363

This commit is contained in:
Caroline Horn 2018-09-20 14:46:52 -04:00 committed by GitHub
parent 0c3aecc408
commit 2971d7635e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 866 additions and 982 deletions

View file

@ -185,25 +185,3 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
This product uses "radioactive button" styles that were published on
https://zurb.com/playground/radioactive-buttons under an "MIT" License.
Copyright (c) ZURB
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -23,6 +23,7 @@ export function graph(kibana) {
icon: 'plugins/graph/icon.png',
description: 'Graph exploration',
main: 'plugins/graph/app',
styleSheetPath: `${__dirname}/public/index.scss`,
},
hacks: ['plugins/graph/hacks/toggle_app_link_in_nav'],
home: ['plugins/graph/register_feature'],

View file

@ -0,0 +1,104 @@
/**
* Nodes
*/
.gphNode-disabled{
opacity:0.3;
}
.gphNode__circle {
fill: $euiColorMediumShade;
// SASSTODO: Can't definitively change modifier class
// because it's not easy to tell what's a class and what's
// part of the data object since they are named the same
&.selectedNode {
stroke-width: $euiSizeXS;
stroke: transparentize($euiColorPrimary, .25);
}
}
.gphNode__text {
fill: $euiColorDarkestShade;
&--lowOpacity{
fill-opacity: 0.5;
}
}
/**
* Forms
*/
.gphFormGroup--small {
margin-bottom: $euiSizeS;
}
.gphColorPicker__color,
.gphIconPicker__icon {
margin: $euiSizeXS;
cursor: pointer;
&.selectedNode,
&:hover,
&:focus {
transform: scale(1.4);
}
}
.gphIconPicker__icon {
opacity: .7;
&.selectedNode,
&:hover,
&:focus {
opacity: 1;
}
}
.gphIndexSelect{
max-width: $euiSizeL * 10;
margin-right: $euiSizeXS;
&-unselected {
@include euiFocusRing;
}
}
.gphAddButton {
background: $euiColorPrimary;
color: $euiColorEmptyShade;
border-radius: 50%;
font-size: $euiFontSizeXS;
margin: 2px $euiSizeS 0 $euiSizeXS;
@include size(26px); // same as svg
&:hover:not(:disabled) {
background: shadeOrTint($euiColorPrimary, 10%, 10%);
cursor: pointer;
}
&:disabled {
background: $euiColorMediumShade;
cursor: not-allowed;
}
&-focus {
@include euiFocusRing;
}
}
.gphFieldList {
min-width: $euiSizeXL * 10;
}
/**
* Utilities
*/
.gphNoUserSelect{
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}

View file

@ -25,7 +25,6 @@ import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import './less/main.less';
import appTemplate from './templates/index.html';
import './angular-venn-simple.js';

View file

@ -0,0 +1,14 @@
// Import the EUI global scope so we can use EUI constants
@import 'ui/public/styles/_styling_constants';
/* Graph plugin styles */
// Prefix all styles with "gph" to avoid conflicts.
// Examples
// gphChart
// gphChart__legend
// gphChart__legend--small
// gphChart__legend-isLoading
@import './main';
@import './templates/index';

View file

@ -1,347 +0,0 @@
@import (reference) "~ui/styles/variables";
@import '~plugins/xpack_main/style/main.less';
.container {
margin-top: 30px;
}
.selectedFieldLine{
background: #D0D3D8;
}
.nodeCircle {
fill: #aaaaaa;
opacity: 0.95;
}
.nodeCircle.selectedNode {
stroke-width: 4;
stroke: yellowgreen;
}
.disabledField{
opacity:0.3;
}
/* @notice
* This product uses "radioactive button" styles that were published on
* https://zurb.com/playground/radioactive-buttons under an "MIT" License.
*
* Copyright (c) ZURB
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in the
* Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
@-webkit-keyframes greenPulse {
from { border-color: #749a02; -webkit-box-shadow: 0 0 9px #333; }
50% { border-color: #91bd09; -webkit-box-shadow: 0 0 18px #91bd09; }
to { border-color: #749a02; -webkit-box-shadow: 0 0 9px #333; }
}
.pulseControl {
border: solid 1px @globalColorTeal;
}
.edge {
fill: #aaaaaa;
stroke-width: 2;
stroke: #aaaaaa;
stroke-opacity: 0.5;
}
.edge.selectedEdge {
stroke: yellowgreen;
stroke-opacity: 0.95;
}
.edge.inferredEdge {
stroke-dasharray: 5,5;
}
.edge:hover {
stroke-opacity: 0.95;
}
.nodeSvgText {
font-size: 7px;
font-family: "Arial";
fill: #555555;
}
.nodeHtmlLabel {
font-size: 7px;
font-family: "Arial";
color: #555555;
cursor:pointer;
text-overflow: ellipsis;
overflow: hidden;
text-align: center;
max-width: 100px;
max-height: 20px;
}
.dialogFooterText {
position: absolute;
bottom: 0;
}
.nodeMarkerText {
font-size: 3px;
font-family: "Arial";
fill: #555555;
}
.nodeMarkerCircle{
fill: #E1E897;
stroke: white;
}
.help-block {
font-size: smaller;
}
.indexDropDown{
max-width: 200px;
margin-right: 5px;
}
#qDisplayField{
max-width: 280px;
}
#addVertexFieldButton {
border-radius: 50%;
font-size: smaller;
margin-right: 5px;
height: 28px;
width: 28px;
}
.selButton{
margin-right: 5px;
margin-bottom: 2px;
margin-top: 2px;
// width:66px;
}
.selectionActionButton i{
margin-right: 5px;
margin-bottom: 2px;
margin-top: 2px;
}
.selectionActionButton i{
font-size:18px;
}
.vertexSelectionActionBar {
padding-top: 2px;
}
#sidebar{
position: absolute;
right: 5px;
top: 5px;
width: 300px;
z-index: 5;
background-color: #FFFFFF;
opacity: 0.96;
}
.sidebarPanel{
border: 1px solid #cccccc;
padding-left: 5px;
padding-right: 5px;
padding-bottom: 5px;
padding-top: 0px;
border-radius: 5px;
margin-bottom: 2px;
}
#qDiversityField{
max-width: 220px;
}
.small-graph-form {
margin-bottom: 5px;
}
#fieldList {
height: 100%;
min-width: 300px;
}
.legend-value-color-picker .dot {
line-height: 14px;
margin: 2px;
font-size: 14px;
}
.legend-value-color-picker .dot:hover {
margin: 0px;
font-size: 18px;
}
.legend-value-icon-picker .icon {
line-height: 14px;
margin: 2px;
font-size: 14px;
color: grey;
}
.legend-value-icon-picker .icon:hover {
margin: 0px;
font-size: 18px;
color: black;
}
.legend-value-icon-picker .selectedNode{
margin: 0px;
font-size: 18px;
color: black;
}
.venn1 {
fill: #FF0000;
fill-opacity:0.5;
}
.venn2 {
fill: #0000FF;
fill-opacity:0.5;
}
.vennTerm1{
color:#FF0000;
}
.vennTerm2{
color:#0000FF;
}
.vennTerm12{
color:#BC4A84;
}
.detailPanel{
max-height: 300px;
overflow: scroll;
}
.detailHeader{
margin-top: 5px;
color: white;
background-color: #656A76;
padding: 5px;
border-radius: 3px;
margin-bottom: 3px;
}
.selectionList{
height: 200px;
max-height: 200px;
background-color: #ECF0F1;
overflow: scroll;
margin-bottom: 0px;
}
.no-margin-bottom{
margin-bottom:0px;
padding-bottom: 0px;
}
.noUserSelect{
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
#GraphSvgContainer {
position: fixed;
height: 100%;
width: ~"calc(100% - 53px)";
}
.global-nav.is-global-nav-open + .app-wrapper #GraphSvgContainer {
width: ~"calc(100% - 160px)";
}
.requestJsonView{
height:200px;
}
.SvgIconFont{
font-family: FontAwesome;
font-size: 1em;
fill: white;
}
.lowOpacitySvgText{
fill-opacity: 0.5;
}
.SvgZoomedIconButton{
fill: #95a5a6;
stroke:#FFFFFF
}
.SvgZoomedIconButton:hover{
fill: #636E6F;
}
.SvgZoomedIconButtonText{
font-family: FontAwesome;
font-size: 0.15em;
fill: white;
}
.SvgZoomedButtonText{
font-size: 0.1em;
fill: white;
}
navbar .input-group{
top: 2px;
padding-bottom: 2px;
}
#fieldFinderIcon{
background-color: #ffffff;
border: none;
}
#fieldFinderInput{
border: none;
}
#fieldIconPlaceholder{
min-height: 35px;
}
.scrollable-list{
max-height: 180px;
overflow: scroll;
}
.scrollable-list ul.li-striped {
li {
border: none;
}
li:nth-child(even) {
background-color: @kibanaGray6;
}
li:nth-child(odd) {
background-color: @white;
}
}
.graphPlusButton {
background: @globalColorBlue;
border: none;
color: white;
&:hover {
background: darken(@globalColorBlue, 10%);
cursor: pointer;
}
}

View file

@ -0,0 +1,70 @@
@mixin gphSvgText() {
font-family: $euiFontFamily;
font-size: $euiSizeS;
line-height: $euiSizeM;
fill: $euiColorDarkShade;
color: $euiColorDarkShade;
}
/**
* THE SVG Graph
* 1. Calculated px values come from the open/closed state of the global nav sidebar
*/
.gphGraph__container {
position: fixed;
height: 100%;
width: calc(100% - 53px); /* 1 */
}
.global-nav.is-global-nav-open + .app-wrapper .gphGraph__container {
width: calc(100% - 180px); /* 1 */
}
.gphGraph {
// SASSTODO: Can't definitively change child class
// because it's not easy to tell what's a class and what's
// part of the data object since they are named the same
.edge {
fill: $euiColorMediumShade;
stroke: $euiColorMediumShade;
stroke-width: 2;
stroke-opacity: 0.5;
&:hover {
stroke-opacity: 0.95;
cursor: pointer;
}
}
.edge.selectedEdge {
stroke: $euiColorDarkShade;
stroke-opacity: 0.95;
}
.edge.inferredEdge {
stroke-dasharray: 5,5;
}
}
.gphNode__label {
@include gphSvgText;
cursor:pointer;
}
.gphNode__label--html {
@include euiTextTruncate;
text-align: center;
}
.gphNode__markerCircle {
fill: $euiColorDarkShade;
stroke: $euiColorEmptyShade;
}
.gphNode__markerText {
@include gphSvgText;
font-size: $euiSizeS - 2px;
fill: $euiColorEmptyShade;
}

View file

@ -0,0 +1,3 @@
@import './graph';
@import './sidebar';
@import './settings';

View file

@ -0,0 +1,25 @@
.gphSettings__jsonView {
height: $euiSizeL * 10;
}
/**
* Lists
*/
.gphDrilldownList {
max-height: $euiSize * 10;
overflow-y: auto;
border-top: $euiBorderThin;
}
.gphDrilldownList__item {
border: none;
&:nth-child(even) {
background-color: $euiColorLightestShade;
}
&:nth-child(odd) {
background-color: $euiColorEmptyShade;
}
}

View file

@ -0,0 +1,89 @@
.gphSidebar {
position: absolute;
right: $euiSizeS;
top: $euiSizeS;
width: $euiSizeXL * 10;
z-index: $euiZLevel1;
background-color: $euiColorEmptyShade;
border: $euiBorderThin;
padding: $euiSizeXS;
border-radius: $euiBorderRadius;
opacity: .9;
@include euiBottomShadowMedium;
.help-block {
font-size: $euiFontSizeXS;
}
}
.gphSidebar__header{
margin-top: $euiSizeS;
color: $euiColorEmptyShade;
background-color: $euiColorDarkShade;
padding: $euiSizeXS;
border-radius: $euiBorderRadius;
margin-bottom: $euiSizeXS;
}
.gphSidebar__panel{
max-height: $euiSizeL * 10;
overflow-y: auto;
}
/**
* Vertex Select
*/
.gphVertexSelect__button {
margin: $euiSizeXS $euiSizeXS $euiSizeXS 0;
}
/**
* Selection List
*/
.gphSelectionList {
height: $euiSizeL * 10;
background-color: $euiColorLightestShade;
overflow: auto;
margin-bottom: 0;
}
.gphSelectionList__field {
line-height: $euiLineHeight;
margin: $euiSizeXS 0;
cursor: pointer;
> * {
vertical-align: middle;
}
}
.gphSelectionList__field--selected {
background: $euiColorLightShade;
}
/**
* Link summary / Venn Diagram
*/
.gphLinkSummary__venn {
.venn1 {
fill: $euiColorDanger;
fill-opacity:0.5;
}
.venn2 {
fill: $euiColorPrimary;
fill-opacity:0.5;
}
}
.gphLinkSummary__term--1 {
color:$euiColorDanger;
}
.gphLinkSummary__term--2 {
color:$euiColorPrimary;
}
.gphLinkSummary__term--1-2 {
color: mix($euiColorDanger, $euiColorPrimary);
}

View file

@ -10,81 +10,36 @@
<div data-transclude-slot="bottomRow" class="flexGroup">
<!-- Select index pattern. -->
<select
class="form-control indexDropDown"
name="mySelect"
ng-change="uiSelectIndex()"
ng-class="{pulseControl:selectedIndex === null}"
ng-options="option.attributes.title for option in indices"
ng-model="grr.proposedIndex"
>
<select class="form-control gphIndexSelect" name="mySelect" ng-change="uiSelectIndex()" ng-class="{'gphIndexSelect-unselected':selectedIndex === null}"
ng-options="option.attributes.title for option in indices" ng-model="grr.proposedIndex">
<option value="">Select index pattern...</option>
</select>
<!-- Added fields. -->
<span
class="noUserSelect"
ng-repeat="f in selectedFields"
ng-attr-tooltip="Shift + click to toggle if search returns {{f.name}}"
>
<svg
width="30"
height="30"
ng-click="clickVertexFieldIcon(f, $event)"
ng-class="{disabledField:f.hopSize<=0}"
>
<circle
class="nodeCircle"
r="13"
cx="15"
cy="15"
ng-class="{selectedNode:kbnTopNav.currentKey === 'fieldConfig'&&f === selectedFieldConfig}"
ng-attr-style="fill:{{f.color}}"
/>
<text ng-if="f.icon" class="SvgIconFont" text-anchor="middle" x="15" y="20">
<span class="gphNoUserSelect" ng-repeat="f in selectedFields" ng-attr-tooltip="Shift + click to toggle if search returns {{f.name}}">
<svg class="gphNode" width="30" height="30" ng-click="clickVertexFieldIcon(f, $event)" ng-class="{'gphNode-disabled':f.hopSize<=0}">
<circle class="gphNode__circle" r="13" cx="15" cy="15" ng-class="{selectedNode:kbnTopNav.currentKey === 'fieldConfig'&&f === selectedFieldConfig}"
ng-attr-style="fill:{{f.color}}" />
<text ng-if="f.icon" class="gphNode__text fa" text-anchor="middle" x="15" y="20">
{{f.icon.code}}
</text>
</svg>
</span>
<!-- Add field button. -->
<span class="noUserSelect" ng-attr-tooltip="Add a field source for vertices">
<button
class="graphPlusButton"
ng-disabled="selectedIndex === null"
aria-label="Add a field"
id="addVertexFieldButton"
ng-class="{pulseControl:selectedIndex !== null&&selectedFields.length === 0}"
ng-click="toggleShowAdvancedFieldsConfig()"
>
<span class="gphNoUserSelect" ng-attr-tooltip="Add a field source for vertices">
<button class="gphAddButton" ng-disabled="selectedIndex === null" aria-label="Add a field" id="addVertexFieldButton"
ng-class="{'gphAddButton-focus':selectedIndex !== null&&selectedFields.length === 0}" ng-click="toggleShowAdvancedFieldsConfig()">
<span aria-hidden="true" class="kuiIcon fa-plus"></span>
</button>
</span>
<!-- Search. -->
<form
ng-submit="submit()"
name="graphBasicSearch"
class="flexGroup__filler"
style="width: 100%"
>
<form ng-submit="submit()" name="graphBasicSearch" class="flexGroup__filler" style="width: 100%">
<div class="kuiLocalSearch">
<input
name="searchTerm"
type="text"
class="kuiLocalSearchInput"
ng-model="grr.searchTerm"
ng-disabled="liveResponseFields.length === 0"
ng-focus="kbnTopNav.close()"
placeholder="foo AND bar NOT baz"
autocomplete="off"
>
<button
class="kuiLocalSearchButton"
type="submit"
ng-disabled="liveResponseFields.length === 0"
aria-label="Search"
>
<input name="searchTerm" type="text" class="kuiLocalSearchInput" ng-model="grr.searchTerm" ng-disabled="liveResponseFields.length === 0"
ng-focus="kbnTopNav.close()" placeholder="foo AND bar NOT baz" autocomplete="off">
<button class="kuiLocalSearchButton" type="submit" ng-disabled="liveResponseFields.length === 0" aria-label="Search">
<span aria-hidden="true" class="kuiIcon fa-search"></span>
</button>
</div>
@ -95,134 +50,108 @@
<div class="config" ng-show="kbnTopNav.currentKey === 'fields'" data-test-subj="fieldSelectionList">
<div class="config">
<div class="container-fluid no-margin-bottom">
<label >Add a field source for vertices</label>
<form>
<div class="input-group form-group finder-form " style="width:100%">
<span class="input-group-addon "
id="fieldFinderIcon">
<span class="kuiIcon fa-search"></span>
</span>
<input
id="fieldFinderInput"
input-focus
ng-model="fieldNamesFilterString"
placeholder="Filter..."
ng-change="filterFieldsKeyDown()"
class="form-control"
name="filter"
type="text"
autocomplete="off" />
</div>
</form>
<div class="container-fluid">
<label>Add a field source for vertices</label>
<form>
<div class="input-group form-group" style="width:100%">
<span class="input-group-addon">
<span class="kuiIcon fa-search"></span>
</span>
<input input-focus ng-model="fieldNamesFilterString" placeholder="Filter..."
ng-change="filterFieldsKeyDown()" class="form-control" name="filter" type="text" autocomplete="off" />
</div>
</form>
<select
class="kuiVerticalRhythm"
id="fieldList"
size="7"
ng-options="item as item.name for item in filteredFields"
ng-dblclick="addFieldToSelection()"
ng-model="selectedField"
></select>
<select class="kuiVerticalRhythm gphFieldList" id="fieldList" size="7" ng-options="item as item.name for item in filteredFields"
ng-dblclick="addFieldToSelection()" ng-model="selectedField"></select>
<div class="kuiVerticalRhythm">
<!-- TODO look at the field chooser directive in Discover to give type icons etc to
<div class="kuiVerticalRhythm">
<!-- TODO look at the field chooser directive in Discover to give type icons etc to
field types. This may filter out indexed-only fields though? -->
<button
ng-disabled="!selectedField"
ng-click="addFieldToSelection()"
class="kuiButton kuiButton--primary"
>
Add
</button>
</div>
<button ng-disabled="!selectedField" ng-click="addFieldToSelection()" class="kuiButton kuiButton--primary">
Add
</button>
</div>
</div>
<button
class="kuiLocalDropdownCloseButton"
ng-click="kbnTopNav.close()"
aria-label="Close"
>
<button class="kuiLocalDropdownCloseButton" ng-click="kbnTopNav.close()" aria-label="Close">
<span class="kuiIcon fa-chevron-circle-up"></span>
</button>
</div>
</div>
<div class="config" ng-show="kbnTopNav.currentKey === 'fieldConfig'">
<div class="config" ng-show="kbnTopNav.currentKey === 'fieldConfig'">
<div class="config">
<div class="container-fluid no-margin-bottom">
<div class="container-fluid">
<div >
<form class="form-horizontal" >
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<strong>{{selectedFieldConfig.name}}</strong>
</div>
</div>
<div class="form-group form-group-sm small-graph-form">
<label for="colorPicker" class="col-sm-4 control-label"> Color</label>
<div class="col-sm-6">
<span class="legend-value-container" id="colorPicker">
<span class="legend-value-color-picker">
<span ng-repeat="c in colors" ng-click="applyColor(selectedFieldConfig,c)" ng-class="c === selectedFieldConfig.color ? 'fa-circle-o' : 'fa-circle'" ng-style="{color: c}" class="kuiIcon dot">
</span>
</span>
</span>
</div>
</div>
<div class="form-group form-group-sm small-graph-form">
<label for="iconPicker" class="col-sm-4 control-label"> Icon</label>
<div class="col-sm-5">
<span class="legend-value-icon-picker">
<span ng-repeat="i in iconChoices" ng-click="applyIcon(selectedFieldConfig,i)" ng-class="{selectedNode:i==selectedFieldConfig.icon}" class="kuiIcon icon noUserSelect">
{{i.code}}</span>
</span>
</div>
</div>
<div class="form-group form-group-sm small-graph-form">
<label for="qHopSize" class="col-sm-4 control-label">Max terms per hop</label>
<div class="col-sm-6">
<input
ng-change="selectedFieldConfigHopSizeChanged()"
type="number" class="input-sm" min="0" max="100" step="1" id="qHopSize" ng-model="selectedFieldConfig.hopSize">
<div class="help-block">
<div class="euiText">
<p>Controls the number of terms returned each search step.</p>
</div>
<div class="hintbox">
<span class="kuiIcon fa-info text-info"></span> Shift-clicking the field icons in the menu bar provides a quick way to toggle this number to zero and back
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div>
<button
ng-click="removeVertexFieldSelection()"
class="kuiButton kuiButton--danger"
>
Remove
</button>
</div>
</div>
</div>
</form>
<div>
<form class="form-horizontal">
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<strong>{{selectedFieldConfig.name}}</strong>
</div>
</div>
<div class="form-group form-group-sm gphFormGroup--small">
<label for="colorPicker" class="col-sm-4 control-label"> Color</label>
<div class="col-sm-6">
<span id="colorPicker">
<span ng-repeat="c in colors" ng-click="applyColor(selectedFieldConfig,c)" ng-class="c === selectedFieldConfig.color ? 'fa-circle-o' : 'fa-circle'"
ng-style="{color: c}" class="kuiIcon gphColorPicker__color">
</span>
</span>
</div>
</div>
<div class="form-group form-group-sm gphFormGroup--small">
<label for="iconPicker" class="col-sm-4 control-label"> Icon</label>
<div class="col-sm-6">
<span class="gphIconPicker">
<span ng-repeat="i in iconChoices" ng-click="applyIcon(selectedFieldConfig,i)" ng-class="{selectedNode:i==selectedFieldConfig.icon}"
class="kuiIcon gphIconPicker__icon gphNoUserSelect">
{{i.code}}</span>
</span>
</div>
</div>
<div class="form-group form-group-sm gphFormGroup--small">
<label for="qHopSize" class="col-sm-4 control-label">Max terms per hop</label>
<div class="col-sm-6">
<input ng-change="selectedFieldConfigHopSizeChanged()" type="number" class="input-sm" min="0" max="100"
step="1" id="qHopSize" ng-model="selectedFieldConfig.hopSize">
<div class="help-block">
<div class="euiText">
<p>Controls the number of terms returned each search step.</p>
</div>
<div class="hintbox">
<span class="kuiIcon fa-info text-info"></span> Shift-clicking the field icons in the menu bar
provides a quick way to toggle this number to zero and back
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div>
<button ng-click="removeVertexFieldSelection()" class="kuiButton kuiButton--danger">
Remove
</button>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="config-close remove" ng-click="kbnTopNav.close()">
<div class="kuiLocalDropdownCloseButton" aria-label="close" ng-click="kbnTopNav.close()">
<span class="kuiIcon fa-chevron-circle-up"></span>
</div>
@ -230,37 +159,41 @@
</div>
<div id="GraphSvgContainer">
<svg width="100%" height="100%" pointer-events="all" id="graphSvg" ng-click="hideAllConfigPanels()">
<div class="gphGraph__container" id="GraphSvgContainer">
<svg class="gphGraph" width="100%" height="100%" pointer-events="all" id="graphSvg" ng-click="hideAllConfigPanels()">
<g id="svgRootGroup">
<g>
<line ng-repeat="edge in workspace.edges" class="edge" ng-attr-x1="{{edge.topSrc.kx}}" ng-attr-y1="{{edge.topSrc.ky}}" ng-attr-x2="{{edge.topTarget.kx}}" ng-attr-y2="{{edge.topTarget.ky}}" ng-click="clickEdge(edge)"
ng-class="{selectedEdge:edge.isSelected, inferredEdge:edge.inferred}" , ng-attr-style="stroke-width:{{edge.width}}px" stroke-linecap="round" />
<line ng-repeat="edge in workspace.edges" class="edge" ng-attr-x1="{{edge.topSrc.kx}}" ng-attr-y1="{{edge.topSrc.ky}}"
ng-attr-x2="{{edge.topTarget.kx}}" ng-attr-y2="{{edge.topTarget.ky}}" ng-click="clickEdge(edge)" ng-class="{'selectedEdge':edge.isSelected, 'inferredEdge':edge.inferred}"
ng-attr-style="stroke-width:{{edge.width}}px" stroke-linecap="round" />
</g>
<g ng-repeat="n in workspace.nodes" ng-if="!n.parent">
<circle class="nodeCircle" ng-attr-r="{{n.scaledSize}}" ng-attr-cx="{{n.kx}}" ng-attr-cy="{{n.ky}}" ng-click="nodeClick(n, $event)"
ng-class="{selectedNode:n.isSelected}"
ng-attr-style="fill:{{n.color}}"
/>
<text class="SvgIconFont lowOpacitySvgText" transform="translate(0,5)" text-anchor="middle" ng-attr-x="{{n.kx}}" ng-attr-y="{{n.ky}}" ng-if="n.icon" ng-click="nodeClick(n, $event)">{{n.icon.code}}</text>
<circle class="gphNode__circle" ng-attr-r="{{n.scaledSize}}" ng-attr-cx="{{n.kx}}" ng-attr-cy="{{n.ky}}" ng-click="nodeClick(n, $event)"
ng-class="{selectedNode:n.isSelected}" ng-attr-style="fill:{{n.color}}" />
<text class="fa gphNode__text gphNode__text--lowOpacity" transform="translate(0,5)" text-anchor="middle" ng-attr-x="{{n.kx}}"
ng-attr-y="{{n.ky}}" ng-if="n.icon" ng-click="nodeClick(n, $event)">{{n.icon.code}}</text>
<!-- Using 2 strategies for rendering labels - small text uses SVG for performance,
larger text uses HTML for word-wrapping capabilities but can be jittery.
-->
<text ng-if="n.label.length<30" class="nodeSvgText" text-anchor="middle" transform="translate(0,22)" ng-attr-x="{{n.kx}}" ng-attr-y="{{n.ky}}" ng-click="nodeClick(n, $event)">
<text ng-if="n.label.length<30" class="gphNode__label" text-anchor="middle" transform="translate(0,22)"
ng-attr-x="{{n.kx}}" ng-attr-y="{{n.ky}}" ng-click="nodeClick(n, $event)">
{{n.label}}
</text>
<foreignObject ng-if="n.label.length>=30" width="100" height="20" transform="translate(-50,15)" ng-attr-x="{{n.kx}}" ng-attr-y="{{n.ky}}"
ng-click="nodeClick(n, $event)">
<body xmlns="http://www.w3.org/1999/xhtml">
<p class="nodeHtmlLabel noUserSelect">{{n.label}}</p>
</body>
<foreignObject ng-if="n.label.length>=30" width="100" height="20" transform="translate(-50,15)" ng-attr-x="{{n.kx}}"
ng-attr-y="{{n.ky}}" ng-click="nodeClick(n, $event)">
<body xmlns="http://www.w3.org/1999/xhtml">
<p class="gphNode__label gphNode__label--html gphNoUserSelect">{{n.label}}</p>
</body>
</foreignObject>
<g ng-if="n.numChildren>0">
<circle r="5" class="nodeMarkerCircle" transform="translate(10,10)" ng-attr-cx="{{n.kx}}" ng-attr-cy="{{n.ky}}" ng-click="nodeClick(n, $event)" />
<text class="nodeMarkerText" text-anchor="middle" transform="translate(10,11)" ng-attr-x="{{n.kx}}" ng-attr-y="{{n.ky}}" ng-click="nodeClick(n, $event)">{{n.numChildren}}
<circle r="5" class="gphNode__markerCircle" transform="translate(10,10)" ng-attr-cx="{{n.kx}}" ng-attr-cy="{{n.ky}}"
ng-click="nodeClick(n, $event)" />
<text class="gphNode__markerText" text-anchor="middle" transform="translate(10,12)" ng-attr-x="{{n.kx}}"
ng-attr-y="{{n.ky}}" ng-click="nodeClick(n, $event)">{{n.numChildren}}
</text>
</g>
@ -286,355 +219,250 @@
-->
<div id="sidebar" ng-if="workspace !== null">
<div class="sidebarPanel">
<div class="vertexSelectionActionBar">
<button
class="kuiButton kuiButton--basic kuiButton--small"
tooltip="Undo"
type="button"
ng-click="workspace.undo()"
ng-disabled="workspace === null||workspace.undoLog.length <1"
>
<span class="kuiIcon fa-history"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
tooltip="Redo"
type="button"
ng-disabled="workspace === null ||workspace.redoLog.length === 0" ng-click="workspace.redo()"
>
<span class="kuiIcon fa-repeat"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-disabled="workspace === null ||liveResponseFields.length === 0||workspace.nodes.length === 0"
tooltip="Expand selection"
ng-click="setDetail(null);workspace.expandSelecteds({toFields:liveResponseFields});"
>
<span class="kuiIcon fa-plus"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-disabled="workspace === null ||workspace.nodes.length === 0"
tooltip="Add links between existing terms"
ng-click="workspace.fillInGraph();"
>
<span class="kuiIcon fa-link"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-disabled="workspace === null ||workspace.nodes.length === 0"
tooltip="Remove vertices from workspace"
ng-click="setDetail(null);workspace.deleteSelection();"
>
<span class="kuiIcon fa-trash"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-disabled="workspace === null ||workspace.selectedNodes.length === 0"
tooltip="Blacklist selection from return to workspace"
ng-click="workspace.blacklistSelection();"
>
<span class="kuiIcon fa-ban"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-disabled="workspace === null ||workspace.selectedNodes.length === 0"
tooltip="Custom style selected vertices"
ng-click="setDetail({showStyle:true})"
>
<span class="kuiIcon fa-paint-brush"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-disabled="workspace === null||workspace.nodes.length === 0"
tooltip="Drill down"
ng-click="setDetail({showDrillDowns:true})"
>
<span class="kuiIcon fa-info"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-disabled="workspace.nodes.length === 0"
ng-if="workspace.nodes.length === 0||workspace.force === null"
tooltip="Run layout"
ng-click="workspace.runLayout()"
>
<span class="kuiIcon fa-play"></span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--small"
ng-if="workspace.force !== null&&workspace.nodes.length>0"
tooltip="Pause layout"
ng-click="workspace.stopLayout()"
>
<span class="kuiIcon fa-pause"></span>
</button>
</div>
<div id="sidebar" class="gphSidebar" ng-if="workspace !== null">
<div>
<div class="detailHeader">
<span class="kuiIcon fa-shopping-cart"></span> Selections
</div>
<button class="kuiButton kuiButton--basic kuiButton--small" tooltip="Undo" type="button" ng-click="workspace.undo()"
ng-disabled="workspace === null||workspace.undoLog.length <1">
<span class="kuiIcon fa-history"></span>
</button>
<div id="vertexSelectionTypesBar">
<button
tooltip="Select all"
type="button"
class="kuiButton kuiButton--basic kuiButton--small selButton"
ng-disabled="workspace.nodes.length === 0"
ng-click="setDetail(null);workspace.selectAll()"
>
all
</button>
<button class="kuiButton kuiButton--basic kuiButton--small" tooltip="Redo" type="button" ng-disabled="workspace === null ||workspace.redoLog.length === 0"
ng-click="workspace.redo()">
<span class="kuiIcon fa-repeat"></span>
</button>
<button
tooltip="Select none"
type="button"
class="kuiButton kuiButton--basic kuiButton--small selButton"
ng-disabled="workspace.nodes.length === 0"
ng-click="setDetail(null);workspace.selectNone()"
>
none
</button>
<button class="kuiButton kuiButton--basic kuiButton--small" ng-disabled="workspace === null ||liveResponseFields.length === 0||workspace.nodes.length === 0"
tooltip="Expand selection" ng-click="setDetail(null);workspace.expandSelecteds({toFields:liveResponseFields});">
<span class="kuiIcon fa-plus"></span>
</button>
<button
tooltip="Invert selection"
type="button"
class="kuiButton kuiButton--basic kuiButton--small selButton"
ng-disabled="workspace.nodes.length === 0"
ng-click="setDetail(null);workspace.selectInvert()"
>
invert
</button>
<button class="kuiButton kuiButton--basic kuiButton--small" ng-disabled="workspace === null ||workspace.nodes.length === 0"
tooltip="Add links between existing terms" ng-click="workspace.fillInGraph();">
<span class="kuiIcon fa-link"></span>
</button>
<button
tooltip="Select neighbours"
type="button"
class="kuiButton kuiButton--basic kuiButton--small selButton"
ng-disabled="workspace.selectedNodes.length === 0"
ng-click="setDetail(null);workspace.selectNeighbours()"
>
linked
</button>
</div>
<button class="kuiButton kuiButton--basic kuiButton--small" ng-disabled="workspace === null ||workspace.nodes.length === 0"
tooltip="Remove vertices from workspace" ng-click="setDetail(null);workspace.deleteSelection();">
<span class="kuiIcon fa-trash"></span>
</button>
<div class="selectionList">
<p ng-if="workspace.selectedNodes.length === 0" class="help-block">No selections. Click on vertices to add</p>
<button class="kuiButton kuiButton--basic kuiButton--small" ng-disabled="workspace === null ||workspace.selectedNodes.length === 0"
tooltip="Blacklist selection from return to workspace" ng-click="workspace.blacklistSelection();">
<span class="kuiIcon fa-ban"></span>
</button>
<div ng-repeat="n in workspace.selectedNodes" ng-class="{'selectedFieldLine': isSelectedSelected(n)}" ng-click="selectSelected(n)" style="line-height: 24px;">
<svg width="24" height="24">
<circle class="nodeCircle " r="10" cx="12" cy="12" ng-class="{selectedNode:n.isSelected}"
ng-attr-style="fill:{{n.color}}"
ng-click="workspace.deselectNode(n)" />
<button class="kuiButton kuiButton--basic kuiButton--small" ng-disabled="workspace === null ||workspace.selectedNodes.length === 0"
tooltip="Custom style selected vertices" ng-click="setDetail({showStyle:true})">
<span class="kuiIcon fa-paint-brush"></span>
</button>
<text ng-if="n.icon" class="SvgIconFont" text-anchor="middle" x="12" y="16" ng-click="workspace.deselectNode(n)">{{n.icon.code}}</text>
</svg>
<span style="vertical-align: top;">{{n.label}}</span>
<span style="vertical-align: top;" ng-if="n.numChildren>0"> (+{{n.numChildren}})</span>
<button class="kuiButton kuiButton--basic kuiButton--small" ng-disabled="workspace === null||workspace.nodes.length === 0"
tooltip="Drill down" ng-click="setDetail({showDrillDowns:true})">
<span class="kuiIcon fa-info"></span>
</button>
</div>
</div>
</div>
<!-- Any drill-downs with a choice of button icon appear here for quick access -->
<div ng-if="(urlTemplates | filter:{icon: {class:''}}).length > 0" >
<button
ng-repeat="urlTemplate in urlTemplates | filter:{icon: {class:''}}"
class="kuiButton kuiButton--basic kuiButton--small selButton"
tooltip="{{urlTemplate.description}}"
type="button"
ng-disabled="workspace === null ||workspace.nodes.length === 0"
ng-click="openUrlTemplate(urlTemplate)"
>
<span class="kuiIcon" ng-class="urlTemplate.icon.class"></span>
</button>
</div>
<div ng-if="detail.showDrillDowns">
<div class="detailHeader">
<span class="kuiIcon fa-info"></span> Drill-downs
</div>
<div class="detailPanel">
<p ng-if="urlTemplates.length === 0" class="help-block">
Configure drill-downs from the settings menu
</p>
<ul ng-repeat="urlTemplate in urlTemplates" class="li-striped list-group list-group-menu">
<li class="list-group-item">
<span ng-if="urlTemplate.icon" class="kuiIcon icon noUserSelect">
{{urlTemplate.icon.code}}</span>
<a ng-click="openUrlTemplate(urlTemplate)">{{urlTemplate.description}}</a>
</li>
</ul>
</div>
</div>
<div class="detailPanel" ng-if="(detail.showStyle)&&(workspace.selectedNodes.length>0)">
<div class="detailHeader">
<span class="kuiIcon fa-paint-brush"></span> Style selected vertices
</div>
<div class="form-group form-group-sm small-graph-form">
<div class="col-sm-10">
<span class="legend-value-container" >
<span class="legend-value-color-picker">
<span ng-repeat="c in colors" ng-disabled="!selectedField.selected" ng-click="workspace.colorSelected(c)" ng-style="{color: c}" class="kuiIcon dot fa-circle">
</span>
</span>
</span>
</div>
</div>
</div>
<div class="detailPanel" ng-if="detail.latestNodeSelection">
<div class="detailHeader">
<span class="kuiIcon {{detail.latestNodeSelection.icon.class}}" ng-if="detail.latestNodeSelection.icon"></span> {{detail.latestNodeSelection.data.field}} {{detail.latestNodeSelection.data.term}}
</div>
<button
class="kuiButton kuiButton--basic kuiButton--iconText kuiButton--small"
ng-if="workspace.selectedNodes.length>1||(workspace.selectedNodes.length>0&&workspace.selectedNodes[0] !== detail.latestNodeSelection)"
tooltip="group the currently selected items into {{detail.latestNodeSelection.label}}"
ng-click="workspace.groupSelections(detail.latestNodeSelection)"
>
<span class="kuiButton__icon kuiIcon fa-object-group"></span>
<span>group</span>
</button>
<button
class="kuiButton kuiButton--basic kuiButton--iconText kuiButton--small"
ng-if="detail.latestNodeSelection.numChildren>0"
tooltip="ungroup {{detail.latestNodeSelection.label}}"
ng-click="workspace.ungroup(detail.latestNodeSelection)"
>
<span class="kuiIcon fa-object-ungroup"></span>
<span>ungroup</span>
</button>
<form class="form-horizontal">
<div class="form-group form-group-sm small-graph-form">
<label for="labelEdit" class="col-sm-3 control-label">Display label</label>
<div class="col-sm-9">
<input type="text" id="labelEdit" class="form-control input-sm" ng-model="detail.latestNodeSelection.label">
<div class="help-block">Change the label for this vertex</div>
</div>
</div>
</form>
</div>
<div ng-if="detail.mergeCandidates.length>0" class="detailPanel">
<div class="detailHeader">
<span class="kuiIcon fa-link"></span> Link summary
</div>
<div ng-repeat="mc in detail.mergeCandidates">
<span>
<button
tooltip="Merge {{mc.term1}} into {{mc.term2}}"
type="button"
ng-attr-style="opacity:{{0.2+(mc.overlap/mc.v1)}};"
class="kuiButton kuiButton--basic kuiButton--small"
ng-click="performMerge(mc.id2, mc.id1)"
>
<span class="kuiIcon fa-chevron-circle-right"></span>
</button>
<span class="vennTerm1">{{mc.term1}}</span>
<span class="vennTerm2">{{mc.term2}}</span>
<button
tooltip="Merge {{mc.term2}} into {{mc.term1}}"
type="button"
class="kuiButton kuiButton--basic kuiButton--small"
ng-attr-style="opacity:{{0.2+(mc.overlap/mc.v2)}};"
ng-click="performMerge(mc.id1, mc.id2)"
>
<span class="kuiIcon fa-chevron-circle-left"></span>
</button>
</span>
<!-- Venn diagram of term/shared doc intersections -->
<div venn="mc"></div>
<small class="vennTerm1" tooltip="{{mc.v1}} documents have term {{mc.term1}}">{{mc.v1}}</small>
<small class="vennTerm12" tooltip="{{mc.overlap}} documents have both terms">&nbsp;({{mc.overlap}})&nbsp;</small>
<small class="vennTerm2" tooltip="{{mc.v2}} documents have term {{mc.term2}}">{{mc.v2}}</small>
</div>
<div class="detailPanel" ng-if="(detail.inferredEdge)">
<div class="detailHeader">
<span class="kuiIcon fa-cog"></span> Similar labels
</div>
<div class="form-group form-group-sm small-graph-form">
<div style="line-height: 24px;">
<button
tooltip="Merge {{detail.inferredEdge.topTarget.label}} into {{detail.inferredEdge.topSrc.label}}"
type="button"
style="vertical-align: top;"
class="kuiButton kuiButton--basic kuiButton--small"
ng-click="performMerge(detail.inferredEdge.topTarget.id, detail.inferredEdge.topSrc.id)"
>
<span class="kui fa-chevron-circle-down"></span>
</button>
<svg width="24" height="24">
<circle class="nodeCircle " r="10" cx="12" cy="12"
ng-attr-style="fill:{{detail.inferredEdge.topSrc.color}}"/>
<text ng-if="detail.inferredEdge.topSrc.icon" class="SvgIconFont" text-anchor="middle" x="12" y="16" >{{detail.inferredEdge.topSrc.icon.code}}</text>
</svg>
<span style="vertical-align: top;">{{detail.inferredEdge.topSrc.label}}</span>
</div>
<div style="line-height: 24px;">
<button
tooltip="Merge {{detail.inferredEdge.topSrc.label}} into {{detail.inferredEdge.topTarget.label}}"
type="button"
style="vertical-align: top;"
class="kuiButton kuiButton--basic kuiButton--small"
ng-click="performMerge(detail.inferredEdge.topSrc.id, detail.inferredEdge.topTarget.id)"
>
<span class="kuiIcon fa-chevron-circle-up"></span>
</button>
<svg width="24" height="24">
<circle class="nodeCircle " r="10" cx="12" cy="12"
ng-attr-style="fill:{{detail.inferredEdge.topTarget.color}}"/>
<text ng-if="detail.inferredEdge.topTarget.icon" class="SvgIconFont" text-anchor="middle" x="12" y="16" >{{detail.inferredEdge.topTarget.icon.code}}</text>
</svg>
<span style="vertical-align: top;">{{detail.inferredEdge.topTarget.label}}</span>
</div>
<div class="col-sm-10">
Key terms: <small> {{detail.inferredEdge.label}}
</div>
</div>
</div>
</div>
<!-- end edge-merge detail panel -->
<button class="kuiButton kuiButton--basic kuiButton--small" ng-disabled="workspace.nodes.length === 0" ng-if="workspace.nodes.length === 0||workspace.force === null"
tooltip="Run layout" ng-click="workspace.runLayout()">
<span class="kuiIcon fa-play"></span>
</button>
<button class="kuiButton kuiButton--basic kuiButton--small" ng-if="workspace.force !== null&&workspace.nodes.length>0"
tooltip="Pause layout" ng-click="workspace.stopLayout()">
<span class="kuiIcon fa-pause"></span>
</button>
</div>
<div>
<div class="gphSidebar__header">
<span class="kuiIcon fa-shopping-cart"></span> Selections
</div>
<div id="vertexSelectionTypesBar">
<button tooltip="Select all" type="button" class="kuiButton kuiButton--basic kuiButton--small gphVertexSelect__button"
ng-disabled="workspace.nodes.length === 0" ng-click="setDetail(null);workspace.selectAll()">
all
</button>
<button tooltip="Select none" type="button" class="kuiButton kuiButton--basic kuiButton--small gphVertexSelect__button"
ng-disabled="workspace.nodes.length === 0" ng-click="setDetail(null);workspace.selectNone()">
none
</button>
<button tooltip="Invert selection" type="button" class="kuiButton kuiButton--basic kuiButton--small gphVertexSelect__button"
ng-disabled="workspace.nodes.length === 0" ng-click="setDetail(null);workspace.selectInvert()">
invert
</button>
<button tooltip="Select neighbours" type="button" class="kuiButton kuiButton--basic kuiButton--small gphVertexSelect__button"
ng-disabled="workspace.selectedNodes.length === 0" ng-click="setDetail(null);workspace.selectNeighbours()">
linked
</button>
</div>
<div class="gphSelectionList">
<p ng-if="workspace.selectedNodes.length === 0" class="help-block">No selections. Click on vertices to add</p>
<div ng-repeat="n in workspace.selectedNodes" class="gphSelectionList__field" ng-class="{'gphSelectionList__field--selected': isSelectedSelected(n)}"
ng-click="selectSelected(n)">
<svg width="24" height="24">
<circle class="gphNode__circle " r="10" cx="12" cy="12" ng-attr-style="fill:{{n.color}}"
ng-click="workspace.deselectNode(n)" />
<text ng-if="n.icon" class="fa gphNode__text" text-anchor="middle" x="12" y="17" ng-click="workspace.deselectNode(n)">{{n.icon.code}}</text>
</svg>
<span>{{n.label}}</span>
<span ng-if="n.numChildren>0"> (+{{n.numChildren}})</span>
</div>
</div>
</div>
<!-- Any drill-downs with a choice of button icon appear here for quick access -->
<div ng-if="(urlTemplates | filter:{icon: {class:''}}).length > 0">
<button ng-repeat="urlTemplate in urlTemplates | filter:{icon: {class:''}}" class="kuiButton kuiButton--basic kuiButton--small gphVertexSelect__button"
tooltip="{{urlTemplate.description}}" type="button" ng-disabled="workspace === null ||workspace.nodes.length === 0"
ng-click="openUrlTemplate(urlTemplate)">
<span class="kuiIcon" ng-class="urlTemplate.icon.class"></span>
</button>
</div>
<div ng-if="detail.showDrillDowns">
<div class="gphSidebar__header">
<span class="kuiIcon fa-info"></span> Drill-downs
</div>
<div class="gphSidebar__panel">
<p ng-if="urlTemplates.length === 0" class="help-block">
Configure drill-downs from the settings menu
</p>
<ul class="list-group">
<li class="list-group-item" ng-repeat="urlTemplate in urlTemplates">
<span ng-if="urlTemplate.icon" class="kuiIcon gphNoUserSelect">
{{urlTemplate.icon.code}}</span>
<a ng-click="openUrlTemplate(urlTemplate)">{{urlTemplate.description}}</a>
</li>
</ul>
</div>
</div>
<div class="gphSidebar__panel" ng-if="(detail.showStyle)&&(workspace.selectedNodes.length>0)">
<div class="gphSidebar__header">
<span class="kuiIcon fa-paint-brush"></span> Style selected vertices
</div>
<div class="form-group form-group-sm gphFormGroup--small">
<span ng-repeat="c in colors" ng-disabled="!selectedField.selected" ng-click="workspace.colorSelected(c)"
ng-style="{color: c}" class="kuiIcon gphColorPicker__color fa-circle">
</span>
</div>
</div>
<div class="gphSidebar__panel" ng-if="detail.latestNodeSelection">
<div class="gphSidebar__header">
<span class="kuiIcon {{detail.latestNodeSelection.icon.class}}" ng-if="detail.latestNodeSelection.icon"></span>
{{detail.latestNodeSelection.data.field}} {{detail.latestNodeSelection.data.term}}
</div>
<button class="kuiButton kuiButton--basic kuiButton--iconText kuiButton--small" ng-if="workspace.selectedNodes.length>1||(workspace.selectedNodes.length>0&&workspace.selectedNodes[0] !== detail.latestNodeSelection)"
tooltip="group the currently selected items into {{detail.latestNodeSelection.label}}" ng-click="workspace.groupSelections(detail.latestNodeSelection)">
<span class="kuiButton__icon kuiIcon fa-object-group"></span>
<span>group</span>
</button>
<button class="kuiButton kuiButton--basic kuiButton--iconText kuiButton--small" ng-if="detail.latestNodeSelection.numChildren>0"
tooltip="ungroup {{detail.latestNodeSelection.label}}" ng-click="workspace.ungroup(detail.latestNodeSelection)">
<span class="kuiIcon fa-object-ungroup"></span>
<span>ungroup</span>
</button>
<form class="form-horizontal">
<div class="form-group form-group-sm gphFormGroup--small">
<label for="labelEdit" class="col-sm-3 control-label">Display label</label>
<div class="col-sm-9">
<input type="text" id="labelEdit" class="form-control input-sm" ng-model="detail.latestNodeSelection.label">
<div class="help-block">Change the label for this vertex</div>
</div>
</div>
</form>
</div>
<div ng-if="detail.mergeCandidates.length>0" class="gphSidebar__panel">
<div class="gphSidebar__header">
<span class="kuiIcon fa-link"></span> Link summary
</div>
<div ng-repeat="mc in detail.mergeCandidates">
<span>
<button tooltip="Merge {{mc.term1}} into {{mc.term2}}" type="button" ng-attr-style="opacity:{{0.2+(mc.overlap/mc.v1)}};"
class="kuiButton kuiButton--basic kuiButton--small" ng-click="performMerge(mc.id2, mc.id1)">
<span class="kuiIcon fa-chevron-circle-right"></span>
</button>
<span class="gphLinkSummary__term--1">{{mc.term1}}</span>
<span class="gphLinkSummary__term--2">{{mc.term2}}</span>
<button tooltip="Merge {{mc.term2}} into {{mc.term1}}" type="button" class="kuiButton kuiButton--basic kuiButton--small"
ng-attr-style="opacity:{{0.2+(mc.overlap/mc.v2)}};" ng-click="performMerge(mc.id1, mc.id2)">
<span class="kuiIcon fa-chevron-circle-left"></span>
</button>
</span>
<!-- Venn diagram of term/shared doc intersections -->
<div class="gphLinkSummary__venn" venn="mc"></div>
<small class="gphLinkSummary__term--1" tooltip="{{mc.v1}} documents have term {{mc.term1}}">{{mc.v1}}</small>
<small class="gphLinkSummary__term--1-2" tooltip="{{mc.overlap}} documents have both terms">&nbsp;({{mc.overlap}})&nbsp;</small>
<small class="gphLinkSummary__term--2" tooltip="{{mc.v2}} documents have term {{mc.term2}}">{{mc.v2}}</small>
</div>
<div class="gphSidebar__panel" ng-if="(detail.inferredEdge)">
<div class="gphSidebar__header">
<span class="kuiIcon fa-cog"></span> Similar labels
</div>
<div class="form-group form-group-sm gphFormGroup--small">
<div style="line-height: 24px;">
<button tooltip="Merge {{detail.inferredEdge.topTarget.label}} into {{detail.inferredEdge.topSrc.label}}"
type="button" style="vertical-align: top;" class="kuiButton kuiButton--basic kuiButton--small"
ng-click="performMerge(detail.inferredEdge.topTarget.id, detail.inferredEdge.topSrc.id)">
<span class="kui fa-chevron-circle-down"></span>
</button>
<svg width="24" height="24">
<circle class="gphNode__circle " r="10" cx="12" cy="12" ng-attr-style="fill:{{detail.inferredEdge.topSrc.color}}" />
<text ng-if="detail.inferredEdge.topSrc.icon" class="fa gphNode__text" text-anchor="middle" x="12" y="16">{{detail.inferredEdge.topSrc.icon.code}}</text>
</svg>
<span style="vertical-align: top;">{{detail.inferredEdge.topSrc.label}}</span>
</div>
<div style="line-height: 24px;">
<button tooltip="Merge {{detail.inferredEdge.topSrc.label}} into {{detail.inferredEdge.topTarget.label}}"
type="button" style="vertical-align: top;" class="kuiButton kuiButton--basic kuiButton--small"
ng-click="performMerge(detail.inferredEdge.topSrc.id, detail.inferredEdge.topTarget.id)">
<span class="kuiIcon fa-chevron-circle-up"></span>
</button>
<svg width="24" height="24">
<circle class="gphNode__circle " r="10" cx="12" cy="12" ng-attr-style="fill:{{detail.inferredEdge.topTarget.color}}" />
<text ng-if="detail.inferredEdge.topTarget.icon" class="fa gphNode__text" text-anchor="middle" x="12" y="16">{{detail.inferredEdge.topTarget.icon.code}}</text>
</svg>
<span style="vertical-align: top;">{{detail.inferredEdge.topTarget.label}}</span>
</div>
<div class="col-sm-10">
Key terms: <small> {{detail.inferredEdge.label}}
</div>
</div>
</div>
</div>
<!-- end edge-merge detail panel -->
</div>
<!-- end sidebarPanel -->
<!-- end sidebar -->
</div>
<!--end svg container-->

View file

@ -12,7 +12,7 @@
<div class="text-warning " ng-if="workspace.nodes.length>0 && !grr.userHasConfirmedSaveWorkspaceData && graphSavePolicy=='configAndDataWithConsent'">
<i class="fa fa-warning"></i> The data in this workspace will be cleared and only the configuration will be saved
</div>
<div class="noUserSelect checkbox" ng-if="graphSavePolicy=='configAndDataWithConsent'">
<div class="gphNoUserSelect checkbox" ng-if="graphSavePolicy=='configAndDataWithConsent'">
<label>
<input type="checkbox" ng-model="grr.userHasConfirmedSaveWorkspaceData"> Save Graph Content
</label>
@ -27,7 +27,7 @@
Save
</button>
<div class="dialogFooterText" ng-if="workspace.nodes.length>0 && graphSavePolicy=='config'">
<div ng-if="workspace.nodes.length>0 && graphSavePolicy=='config'">
<span class="text-info" >
<i class="fa fa-info"></i> The data in this workspace will be cleared and only the configuration will be saved
</span>

View file

@ -15,6 +15,63 @@
<a class="navbar-link" ng-click="configPanel='drillDowns'">Drill-downs</a>
</li>
</ul>
<!-- ====== stop tabs ====== -->
<!-- ====== start spy tab content ====== -->
<div
class="list-group-item list-group-item--noBorder"
ng-if="(workspace !== null) && (configPanel === 'lastRequest')"
>
<small class="help-block">http://host:port/{{selectedIndex.name}}/_xpack/graph/_explore</small>
<ul class="nav nav-tabs">
<li ng-class="{active: spymode === 'request'}">
<a ng-click="spymode='request'">Request</a>
</li>
<li ng-class="{active: spymode === 'response'}">
<a ng-click="spymode='response'">Response</a>
</li>
</ul>
<div
ng-show="spymode == 'request'"
ui-ace="{ onLoad: aceLoaded, mode: 'json', advanced: { readOnly: true } }"
ng-model="workspace.lastRequest"
class="gphSettings__jsonView"
></div>
<div
ng-show="spymode == 'response'"
ui-ace="{ onLoad: aceLoaded, mode: 'json', advanced: { readOnly: true } }"
ng-model="workspace.lastResponse"
class="gphSettings__jsonView"
></div>
</div>
<!-- ====== end spy tab content ====== -->
<!-- ====== start term blacklist tab content ====== -->
<div
class="list-group-item list-group-item--noBorder"
ng-if="(workspace!==null) && (configPanel==='blacklist')"
>
<p class="help-block">
These terms are currently blacklisted from re-appearing in the workspace
</p>
<div class="gphSelectionList kuiVerticalRhythm">
<!-- ====== start terms ====== -->
<div
ng-repeat="n in workspace.blacklistedNodes"
class="kuiFieldGroup"
>
<div class="kuiFieldGroupSection">
<button
class="kuiButton kuiButton--hollow"
ng-click="workspace.unblacklist(n)"
>
<span class="kuiIcon fa-times"></span>
</button>
</div>
<!--========= Begin config =========== -->
@ -36,9 +93,17 @@
<!-- ====== start blacklist ====== -->
<div class="list-group-item list-group-item--noBorder" ng-if="(workspace!==null)&&(configPanel==='blacklist')">
<p class="help-block">These terms are currently blacklisted from re-appearing in the workspace</p>
<div class="col-sm-9">
<span class="gphIconPicker">
<i
ng-repeat="i in drillDownIconChoices"
ng-click="toggleDrillDownIcon(newUrlTemplate,i)"
ng-class="{selectedNode:i==newUrlTemplate.icon}"
class="fa gphIconPicker__icon gphNoUserSelect"
ng-bind="i.code"
></i>
</span>
</div>
<div class="selectionList kuiVerticalRhythm">
<div
@ -61,12 +126,72 @@
</div>
</div>
</div>
</div>
</div>
<!-- ====== stop icon ====== -->
<button
type="button"
class="kuiButton kuiButton--danger kuiButton--iconText kuiVerticalRhythm"
ng-click="workspace.blacklistedNodes=[]"
ng-disabled="workspace.blacklistedNodes.length === 0"
</form>
<!-- ====== stop add form ====== -->
<!-- ====== start list ====== -->
<div class="gphDrilldownList">
<ul class="list-group">
<li class="gphDrilldownList__item list-group-item" ng-repeat="urlTemplate in urlTemplates">
<i
ng-if="urlTemplate.icon"
class="fa icon gphNoUserSelect"
>
{{urlTemplate.icon.code}}
</i>
{{urlTemplate.description}}
<span class="pull-right">
<button
ng-click="editUrlTemplate(urlTemplate)"
class="kuiButton kuiButton--basic kuiButton--iconText kuiButton--small"
aria-label="Edit"
>
<span aria-hidden="true" class="kuiIcon fa-pencil"></span>
</button>
<button
ng-click="removeUrlTemplate(urlTemplate)"
class="kuiButton kuiButton--danger kuiButton--iconText kuiButton--small"
aria-label="Remove"
>
<span aria-hidden="true" class="kuiIcon fa-trash"></span>
</button>
</span>
</li>
</ul>
</div>
<!-- ====== stop list ====== -->
</div>
<!-- ====== end drilldowns tab content ====== -->
<!-- ====== begin explore settings tab content ====== -->
<div
class="list-group-item list-group-item--noBorder"
ng-if="(selectedIndex !== null) && (configPanel === 'settings')"
>
<form class="form-horizontal">
<div class="form-group form-group-sm gphFormGroup--small">
<label for="qIndexSampleSize" class="col-sm-4 control-label">
Sample size
</label>
<div class="col-sm-6">
<input
type="number"
class="form-control input-sm"
min="1"
max="500000"
step="1000"
id="qIndexSampleSize"
ng-model="exploreControls.sampleSize"
>
<span class="kuiButton__icon kuiIcon fa-trash"></span>
<span>Clear</span>
@ -172,92 +297,87 @@
</ul>
</div>
</div>
<!-- ====== end drilldowns ======== -->
</div>
<!-- ======== begin explore settings ======= -->
<div class="list-group-item list-group-item--noBorder" ng-if="(selectedIndex !== null)&&(configPanel === 'settings')">
<form class="form-horizontal">
<div class="form-group form-group-sm small-graph-form">
<label for="qIndexSampleSize" class="col-sm-4 control-label">Sample size</label>
<div class="col-sm-6">
<input type="number" class="form-control input-sm" min="1" max="500000" step="1000" id="qIndexSampleSize" ng-model="exploreControls.sampleSize">
<div class="help-block">Terms are identified from samples of the most relevant documents. Bigger is not necessarily better - can be slower and less relevant.
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div class="checkbox">
<label>
<input type="checkbox" id="isSignifChk2" ng-model="exploreControls.useSignificance"> Significant links
</label>
</div>
<span class="help-block">Identify terms that are "significant" rather than simply popular</span>
</div>
</div>
<div class="form-group form-group-sm small-graph-form">
<label for="minDocCount" class="col-sm-4 control-label">Certainty</label>
<div class="col-sm-6 ">
<input type="number" class="form-control input-sm" min="1" max="500000" step="1" id="minDocCount" ng-model="exploreControls.minDocCount">
<div class="help-block">The min number of documents that are required as evidence before introducing a related term</div>
</div>
</div>
<div class="form-group form-group-sm small-graph-form">
<label for="qDiversityField" class="col-sm-4 control-label">Diversity field</label>
<div class="col-sm-6 ">
<select name="qDiversityField" id="qDiversityField" ng-options="field.name for field in allFields" ng-model="exploreControls.sampleDiversityField">
<option value="">[No diversification]</option>
</select>
<span class="help-block">To avoid document samples being dominated by a single voice, pick the field that helps identify the source of bias.
<em>This must be a single-term field or searches will be rejected with an error</em>
</span>
</div>
</div>
<div class="form-group form-group-sm small-graph-form" ng-show="exploreControls.sampleDiversityField">
<label for="qMaxValuesPerDoc" class="col-sm-4 control-label">Max docs per field</label>
<div class="col-sm-6 ">
<input type="number" class="form-control input-sm" min="1" max="500000" step="1" id="qMaxValuesPerDoc" ng-model="exploreControls.maxValuesPerDoc">
<span class="help-block">Max number of documents in a sample that can contain the same value for the
<em>{{selectedDiversityField.name}}</em> field</span>
</div>
</div>
<div class="form-group form-group-sm small-graph-form">
<label for="qTimeoutMillis" class="col-sm-4 control-label">Timeout (ms)</label>
<div class="col-sm-6 ">
<input type="number" class="form-control input-sm" min="1" max="500000" step="1" id="qTimeoutMillis" ng-model="exploreControls.timeoutMillis">
<span class="help-block">Max time in milliseconds a request can run</span>
</div>
</div>
<!--
TODO - could add a "guiding query" search term here - would, for example, allow
exploration of weblog entries from selected IP addresses etc BUT always where
status==404. There's usually a desire to allow exploration to expand and move
away from the seed query but sometimes a guiding hand is needed across all
expand steps to stay "on topic". The search term would be added to all graph
explore calls automatically.
-->
</form>
<div class="form-group form-group-sm gphFormGroup--small">
<label for="minDocCount" class="col-sm-4 control-label">Certainty</label>
<div class="col-sm-6">
<input
type="number"
class="form-control input-sm"
min="1"
max="500000"
step="1"
id="minDocCount"
ng-model="exploreControls.minDocCount"
>
</div>
<!-- ========== end explore settings =========== -->
<div class="form-group form-group-sm gphFormGroup--small">
<label for="qDiversityField" class="col-sm-4 control-label">
Diversity field
</label>
<div class="col-sm-6">
<select
name="qDiversityField"
id="qDiversityField"
ng-options="field.name for field in allFields"
ng-model="exploreControls.sampleDiversityField"
>
<option value="">[No diversification]</option>
</select>
<div
class="form-group form-group-sm gphFormGroup--small"
ng-show="exploreControls.sampleDiversityField"
>
<label
for="qMaxValuesPerDoc"
class="col-sm-4 control-label"
>
Max docs per field
</label>
<div class="col-sm-6">
<input
type="number"
class="form-control input-sm"
min="1"
max="500000"
step="1"
id="qMaxValuesPerDoc"
ng-model="exploreControls.maxValuesPerDoc"
>
</div>
<!--========= End config =========== -->
<div class="form-group form-group-sm gphFormGroup--small">
<label
for="qTimeoutMillis"
class="col-sm-4 control-label"
>
Timeout (ms)
</label>
<div class="col-sm-6">
<input
type="number"
class="form-control input-sm"
min="1"
max="500000"
step="1"
id="qTimeoutMillis"
ng-model="exploreControls.timeoutMillis"
>
<span class="help-block">
Max time in milliseconds a request can run
</span>
</div>
</div>
</form>
</div>
<!-- ====== end explore settings tab content ====== -->

View file

@ -17,8 +17,8 @@ export function GraphPageProvider({ getService, getPageObjects }) {
class GraphPage {
async selectIndexPattern(pattern) {
await remote.setFindTimeout(defaultFindTimeout).findDisplayedByCssSelector('.indexDropDown').click();
await remote.setFindTimeout(defaultFindTimeout).findByCssSelector('.indexDropDown > option[label="' + pattern + '"]').click();
await remote.setFindTimeout(defaultFindTimeout).findDisplayedByCssSelector('.gphIndexSelect').click();
await remote.setFindTimeout(defaultFindTimeout).findByCssSelector('.gphIndexSelect > option[label="' + pattern + '"]').click();
}
async clickAddField() {
@ -51,7 +51,7 @@ export function GraphPageProvider({ getService, getPageObjects }) {
async getGraphCircleText() {
const chartTypes = await remote.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('text.nodeSvgText');
.findAllByCssSelector('text.gphNode__label');
async function getCircleText(circle) {
return circle.getVisibleText();