diff --git a/CHANGELOG b/CHANGELOG
index 5caf9c529b..d1de3ddf99 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -15,6 +15,8 @@ v1.4
v1.3.1 (2013-XX-XX)
-------------------
+* issue #540: suppress return of temporary internal variables in AQL
+
* issue #530: ReferenceError: ArangoError is not a constructor
* issue #535: Problem with AQL user functions javascript API
diff --git a/Documentation/Makefile.files b/Documentation/Makefile.files
index df571ab137..dc1feb042a 100644
--- a/Documentation/Makefile.files
+++ b/Documentation/Makefile.files
@@ -295,7 +295,8 @@ man: Doxygen/.setup-directories
################################################################################
CLEANUP += \
- Doxygen/*
+ Doxygen/.setup-directories \
+ Doxygen/*
## -----------------------------------------------------------------------------
## --SECTION-- EXAMPLES
diff --git a/Documentation/Manual/NewFeatures11.md b/Documentation/Manual/NewFeatures11.md
index 27bf661f31..89643a3b31 100644
--- a/Documentation/Manual/NewFeatures11.md
+++ b/Documentation/Manual/NewFeatures11.md
@@ -209,8 +209,7 @@ Server Statistics {#NewFeatures11ServerStatistics}
--------------------------------------------------
ArangoDB 1.1 allows querying the server status via the administration
-front-end (see @ref UserManualWebInterfaceStatistics) or via REST API
-methods.
+front-end or via REST API methods.
The following methods are available:
- `GET /_admin/connection-statistics`: provides connection statistics
diff --git a/Documentation/UserManual/WebInterface.md b/Documentation/UserManual/WebInterface.md
index 506836f142..1b36bd1a86 100644
--- a/Documentation/UserManual/WebInterface.md
+++ b/Documentation/UserManual/WebInterface.md
@@ -7,7 +7,7 @@ ArangoDB's Web-Interface {#UserManualWebInterface}
Accessing the Web-Interface {#UserManualWebInterfaceAccess}
===========================================================
-The web interfaced can be access as
+The web interface can be accessed via the URL
http://localhost:8529
@@ -20,64 +20,55 @@ application instead. In this case use
Collections Tab {#UserManualWebInterfaceCollections}
----------------------------------------------------
-The collection tabs shows an overview about the loaded and unloaded
-collections of the database.
+The *Collections* tab shows an overview of the loaded and unloaded
+collections present in ArangoDB. System collections (i.e. collections
+whose names start with an underscore) are not shown by default.
-@htmlonly
@endhtmlonly
-@latexonly\includegraphics[width=12cm]{images/fe-collections.png}@endlatexonly
+The list of collections can be restricted using the search bar, or by
+using the filtering at the top. The filter can also be used to show or
+hide system collections.
-You can load, unloaded, delete, or inspect the collections. Please
-note that you should not delete or change system collections, i. e.,
-collections starting with an underscore.
+Clicking on a collection will show the documents contained in it.
+Clicking the small icon on a collection's badge will bring up a dialog
+that allows loading/unloading, renaming and deleting the collection.
-If you click on the magnifying glass, you will get a list of all documents
-in the collection.
+Please note that you should not change or delete system collections.
-@htmlonly
@endhtmlonly
-@latexonly\includegraphics[width=12cm]{images/fe-documents.png}@endlatexonly
+In the list of documents of a collection, you can click on the *Add document*
+line to add a new document to the collection. The document will be created
+instantly, with a system-defined key. The key and all other attributes of the
+document can be adjusted in the following view.
-Using the pencil you can edit the document.
-Query Tab {#UserManualWebInterfaceQuery}
-----------------------------------------
+AQL Editor Tab {#UserManualWebInterfaceQuery}
+---------------------------------------------
-The query tabs allows you to execute AQL queries.
+The *AQL Editor* tab allow to execute AQL queries.
-@htmlonly
@endhtmlonly
-@latexonly\includegraphics[width=12cm]{images/fe-query.png}@endlatexonly
+Type in a query in the bottom box and execute it by pressing the *Submit* button.
+The query result will be shown in the box at the top.
-Type in a query and execute it.
-Shell Tab {#UserManualWebInterfaceShell}
-----------------------------------------
+JS Shell Tab {#UserManualWebInterfaceShell}
+-------------------------------------------
-The shell tabs give you access to a JavaScript shell connection to the
+The *JS Shell* tab provides access to a JavaScript shell connection to the
database server.
-@htmlonly
@endhtmlonly
-@latexonly\includegraphics[width=12cm]{images/fe-shell.png}@endlatexonly
+Any valid JavaScript code can be executed inside the shell. The code will be
+executed inside your browser. To contact the ArangoDB server, you can use the
+`db` object, for example as follows:
+
+ JSH> db._create("mycollection");
+ JSH> db.mycollection.save({ _key: "test", value: "something" });
-Use the OK button or return to execute a command.
Logs Tab {#UserManualWebInterfaceLogs}
--------------------------------------
-You can browse the log files.
-
-@htmlonly
@endhtmlonly
-@latexonly\includegraphics[width=12cm]{images/fe-logs.png}@endlatexonly
+You can use the *Logs* tab to browse the most recent log entries provided by the
+ArangoDB database server.
Note that the server only keeps a limited number of log entries. For
real log analyses write the logs to disk using syslog or a similar
-mechanism.
-
-Statistics Tab {#UserManualWebInterfaceStatistics}
---------------------------------------------------
-
-Use the statistics tab to display information about the server.
-
-@htmlonly
@endhtmlonly
-@latexonly\includegraphics[width=12cm]{images/fe-statistics.png}@endlatexonly
-
-Initially no statistics will be display. You must use the add button
-to configure what type of information should be displayed.
+mechanism. ArangoDB provides several startup options for this.
diff --git a/Documentation/UserManual/WebInterfaceTOC.md b/Documentation/UserManual/WebInterfaceTOC.md
index 4a29ec7410..a17c13ad43 100644
--- a/Documentation/UserManual/WebInterfaceTOC.md
+++ b/Documentation/UserManual/WebInterfaceTOC.md
@@ -6,4 +6,3 @@ TOC {#UserManualWebInterfaceTOC}
- @ref UserManualWebInterfaceQuery
- @ref UserManualWebInterfaceShell
- @ref UserManualWebInterfaceLogs
- - @ref UserManualWebInterfaceStatistics
diff --git a/arangod/Actions/RestActionHandler.cpp b/arangod/Actions/RestActionHandler.cpp
index 8b843a9d77..d54ef1196a 100644
--- a/arangod/Actions/RestActionHandler.cpp
+++ b/arangod/Actions/RestActionHandler.cpp
@@ -103,7 +103,7 @@ bool RestActionHandler::isDirect () {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
-string const& RestActionHandler::queue () {
+string const& RestActionHandler::queue () const {
return _queue;
}
diff --git a/arangod/Actions/RestActionHandler.h b/arangod/Actions/RestActionHandler.h
index 3c90e2ba5d..6cacfa27bd 100644
--- a/arangod/Actions/RestActionHandler.h
+++ b/arangod/Actions/RestActionHandler.h
@@ -126,7 +126,7 @@ namespace triagens {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
- string const& queue ();
+ string const& queue () const;
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
diff --git a/arangod/Ahuacatl/ahuacatl-ast-node.c b/arangod/Ahuacatl/ahuacatl-ast-node.c
index 41bd476115..7b37ab5d43 100644
--- a/arangod/Ahuacatl/ahuacatl-ast-node.c
+++ b/arangod/Ahuacatl/ahuacatl-ast-node.c
@@ -437,10 +437,13 @@ TRI_aql_node_t* TRI_CreateNodeVariableAql (TRI_aql_context_t* const context,
ABORT_OOM
}
- if (! TRI_AddVariableScopeAql(context, name, definingNode)) {
- // duplicate variable name
- TRI_SetErrorContextAql(context, TRI_ERROR_QUERY_VARIABLE_REDECLARED, name);
- return NULL;
+ // if not a temporary variable
+ if (*name != '_') {
+ if (! TRI_AddVariableScopeAql(context, name, definingNode)) {
+ // duplicate variable name
+ TRI_SetErrorContextAql(context, TRI_ERROR_QUERY_VARIABLE_REDECLARED, name);
+ return NULL;
+ }
}
TRI_AQL_NODE_STRING(node) = (char*) name;
diff --git a/arangod/Ahuacatl/ahuacatl-codegen.c b/arangod/Ahuacatl/ahuacatl-codegen.c
index 9ae2d7a5a6..ad0d63b7db 100644
--- a/arangod/Ahuacatl/ahuacatl-codegen.c
+++ b/arangod/Ahuacatl/ahuacatl-codegen.c
@@ -668,7 +668,7 @@ static void InitArray (TRI_aql_codegen_js_t* const generator,
static void EnterSymbol (TRI_aql_codegen_js_t* const generator,
const char* const name,
const TRI_aql_codegen_register_t registerIndex) {
- TRI_aql_codegen_scope_t* scope = CurrentScope(generator);
+ TRI_aql_codegen_scope_t* scope;
TRI_aql_codegen_variable_t* variable = CreateVariable(name, registerIndex);
if (variable == NULL) {
@@ -676,6 +676,17 @@ static void EnterSymbol (TRI_aql_codegen_js_t* const generator,
return;
}
+ // if not a temporary variable
+ if (*name != '_') {
+ scope = CurrentScope(generator);
+ }
+ else {
+ assert(generator->_scopes._length > 0);
+
+ // get scope at level 0
+ scope = (TRI_aql_codegen_scope_t*) TRI_AtVectorPointer(&generator->_scopes, 0);
+ }
+
if (TRI_InsertKeyAssociativePointer(&scope->_variables, name, (void*) variable, false)) {
// variable already exists in symbol table. this should never happen
LOG_TRACE("variable already registered: %s", name);
diff --git a/arangod/RestHandler/RestBatchHandler.cpp b/arangod/RestHandler/RestBatchHandler.cpp
index 3d4bbf02b0..ccb7923f53 100644
--- a/arangod/RestHandler/RestBatchHandler.cpp
+++ b/arangod/RestHandler/RestBatchHandler.cpp
@@ -87,7 +87,7 @@ bool RestBatchHandler::isDirect () {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
-string const& RestBatchHandler::queue () {
+string const& RestBatchHandler::queue () const {
static string const client = "STANDARD";
return client;
diff --git a/arangod/RestHandler/RestBatchHandler.h b/arangod/RestHandler/RestBatchHandler.h
index 793ff2b315..979af30ce7 100644
--- a/arangod/RestHandler/RestBatchHandler.h
+++ b/arangod/RestHandler/RestBatchHandler.h
@@ -153,7 +153,7 @@ namespace triagens {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
- string const& queue ();
+ string const& queue () const;
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
diff --git a/arangod/RestHandler/RestDocumentHandler.cpp b/arangod/RestHandler/RestDocumentHandler.cpp
index 4651c30464..409b0dad78 100644
--- a/arangod/RestHandler/RestDocumentHandler.cpp
+++ b/arangod/RestHandler/RestDocumentHandler.cpp
@@ -85,7 +85,7 @@ bool RestDocumentHandler::isDirect () {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
-string const& RestDocumentHandler::queue () {
+string const& RestDocumentHandler::queue () const {
static string const client = "STANDARD";
return client;
diff --git a/arangod/RestHandler/RestDocumentHandler.h b/arangod/RestHandler/RestDocumentHandler.h
index 21c579f952..de6d0c831c 100644
--- a/arangod/RestHandler/RestDocumentHandler.h
+++ b/arangod/RestHandler/RestDocumentHandler.h
@@ -94,7 +94,7 @@ namespace triagens {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
- string const& queue ();
+ string const& queue () const;
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
diff --git a/arangod/RestHandler/RestImportHandler.cpp b/arangod/RestHandler/RestImportHandler.cpp
index f7e5a8e8aa..8810cf58d8 100644
--- a/arangod/RestHandler/RestImportHandler.cpp
+++ b/arangod/RestHandler/RestImportHandler.cpp
@@ -83,7 +83,7 @@ bool RestImportHandler::isDirect () {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
-string const& RestImportHandler::queue () {
+string const& RestImportHandler::queue () const {
static string const client = "STANDARD";
return client;
diff --git a/arangod/RestHandler/RestImportHandler.h b/arangod/RestHandler/RestImportHandler.h
index a1cc7631a9..dbece94f34 100644
--- a/arangod/RestHandler/RestImportHandler.h
+++ b/arangod/RestHandler/RestImportHandler.h
@@ -94,7 +94,7 @@ namespace triagens {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
- string const& queue ();
+ string const& queue () const;
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
diff --git a/arangod/RestHandler/RestUploadHandler.cpp b/arangod/RestHandler/RestUploadHandler.cpp
index 9b42c126b2..f478a20fe7 100644
--- a/arangod/RestHandler/RestUploadHandler.cpp
+++ b/arangod/RestHandler/RestUploadHandler.cpp
@@ -87,7 +87,7 @@ bool RestUploadHandler::isDirect () {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
-string const& RestUploadHandler::queue () {
+string const& RestUploadHandler::queue () const {
static string const client = "STANDARD";
return client;
diff --git a/arangod/RestHandler/RestUploadHandler.h b/arangod/RestHandler/RestUploadHandler.h
index 9f84809a3e..cb98faead8 100644
--- a/arangod/RestHandler/RestUploadHandler.h
+++ b/arangod/RestHandler/RestUploadHandler.h
@@ -105,7 +105,7 @@ namespace triagens {
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
- string const& queue ();
+ string const& queue () const;
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
diff --git a/arangod/V8Server/ApplicationV8.cpp b/arangod/V8Server/ApplicationV8.cpp
index 37f6dd4f96..c0137c783c 100644
--- a/arangod/V8Server/ApplicationV8.cpp
+++ b/arangod/V8Server/ApplicationV8.cpp
@@ -527,7 +527,7 @@ void ApplicationV8::setupOptions (map
("javascript.gc-frequency", &_gcFrequency, "JavaScript time-based garbage collection frequency (each x seconds)")
("javascript.action-directory", &_actionPath, "path to the JavaScript action directory")
("javascript.app-path", &_appPath, "one directory for applications")
- ("javascript.dev-app-path", &_devAppPath, "one directory for dev aaplications")
+ ("javascript.dev-app-path", &_devAppPath, "one directory for dev applications")
("javascript.modules-path", &_modulesPath, "one or more directories separated by semi-colons")
("javascript.package-path", &_packagePath, "one or more directories separated by semi-colons")
("javascript.startup-directory", &_startupPath, "path to the directory containing alternate JavaScript startup scripts")
diff --git a/arangod/VocBase/headers.c b/arangod/VocBase/headers.c
index 1309488ea9..2325dd3442 100644
--- a/arangod/VocBase/headers.c
+++ b/arangod/VocBase/headers.c
@@ -235,18 +235,14 @@ static void MoveHeader (TRI_headers_t* h,
headers->_begin = header;
}
else if (headers->_begin == header) {
- if (header->_next != NULL) {
- headers->_begin = header->_next;
- }
+ headers->_begin = header->_next;
}
if (old->_next == NULL) {
headers->_end = header;
}
else if (headers->_end == header) {
- if (header->_prev != NULL) {
- headers->_end = header->_prev;
- }
+ headers->_end = header->_prev;
}
if (header->_prev != NULL) {
diff --git a/arangod/VocBase/vocbase.c b/arangod/VocBase/vocbase.c
index acfb172e12..a6886bbba8 100644
--- a/arangod/VocBase/vocbase.c
+++ b/arangod/VocBase/vocbase.c
@@ -215,96 +215,6 @@ static bool EqualKeyCollectionName (TRI_associative_pointer_t* array, void const
/// @{
////////////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////
-/// @brief create a JSON array with collection meta data
-///
-/// this function is called when a collection is created or dropped
-////////////////////////////////////////////////////////////////////////////////
-
-static TRI_json_t* CreateJsonCollectionInfo (TRI_vocbase_col_t const* collection,
- const char* situation) {
- TRI_json_t* json;
- TRI_json_t* details;
- char* cidString;
-
- details = TRI_CreateArrayJson(TRI_CORE_MEM_ZONE);
- TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, details, "type", TRI_CreateStringCopyJson(TRI_CORE_MEM_ZONE, TRI_TypeNameCollection(collection->_type)));
- TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, details, "name", TRI_CreateStringCopyJson(TRI_CORE_MEM_ZONE, collection->_name));
- TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, details, "action", TRI_CreateStringCopyJson(TRI_CORE_MEM_ZONE, situation));
-
- cidString = TRI_StringUInt64((uint64_t) collection->_cid);
-
- json = TRI_CreateArrayJson(TRI_CORE_MEM_ZONE);
- TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, json, "id", TRI_CreateStringCopyJson(TRI_CORE_MEM_ZONE, cidString));
- TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, json, "type", TRI_CreateStringCopyJson(TRI_CORE_MEM_ZONE, "collection"));
- TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, json, "details", details);
-
- TRI_FreeString(TRI_CORE_MEM_ZONE, cidString);
-
- return json;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief insert the id of a collection into the "_ids" collection
-////////////////////////////////////////////////////////////////////////////////
-
-static int InsertIdCallback (TRI_transaction_collection_t* trxCollection,
- void* data) {
- TRI_shaped_json_t* shaped;
- TRI_primary_collection_t* primary;
- TRI_json_t* json;
- TRI_doc_mptr_t mptr;
- int res;
-
- primary = (TRI_primary_collection_t*) trxCollection->_collection->_collection;
- json = data;
-
- shaped = TRI_ShapedJsonJson(primary->_shaper, json);
-
- if (shaped == NULL) {
- return TRI_ERROR_OUT_OF_MEMORY;
- }
-
- res = primary->insert(trxCollection, NULL, &mptr, TRI_DOC_MARKER_KEY_DOCUMENT, shaped, NULL, false, false);
- TRI_FreeShapedJson(primary->_shaper, shaped);
-
- return res;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief save collection info on create or drop
-///
-/// the info will be stored permanently in the "_ids" collection, so we can
-/// later reconstruct ids of collections that are/were dropped
-////////////////////////////////////////////////////////////////////////////////
-
-static bool WriteCollectionInfo (TRI_vocbase_t* vocbase,
- TRI_vocbase_col_t const* collection,
- const char* situation) {
- TRI_json_t* json;
- int res;
-
- if (collection == NULL) {
- return false;
- }
-
- json = CreateJsonCollectionInfo(collection, situation);
-
- if (json == NULL) {
- return false;
- }
-
- res = TRI_ExecuteSingleOperationTransaction(vocbase,
- "_ids",
- TRI_TRANSACTION_WRITE,
- InsertIdCallback,
- json);
-
- TRI_FreeJson(TRI_CORE_MEM_ZONE, json);
-
- return (res == TRI_ERROR_NO_ERROR);
-}
-
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the current tick value, without using a lock
////////////////////////////////////////////////////////////////////////////////
@@ -456,8 +366,6 @@ static void FreeCollection (TRI_vocbase_t* vocbase, TRI_vocbase_col_t* collectio
////////////////////////////////////////////////////////////////////////////////
static bool UnregisterCollection (TRI_vocbase_t* vocbase, TRI_vocbase_col_t* collection) {
- WriteCollectionInfo(vocbase, collection, "drop");
-
TRI_WRITE_LOCK_COLLECTIONS_VOCBASE(vocbase);
TRI_RemoveKeyAssociativePointer(&vocbase->_collectionsByName, collection->_name);
diff --git a/html/admin/css/dashboardView.css b/html/admin/css/dashboardView.css
index 1efd58413a..5de5ab45ee 100644
--- a/html/admin/css/dashboardView.css
+++ b/html/admin/css/dashboardView.css
@@ -12,21 +12,36 @@
}
*/
+.dbNotVisible {
+ opacity: 0 !important;
+}
+
#dashboardHeader .btn-group {
margin-left: 10px;
margin-top: 6px;
}
-.db-zoom, .db-minimize, .db-hide, .db-info {
+.group-close, .group-open {
+ float:right;
+ margin-top: 17px !important;
+ margin-right: 7px !important;
+}
+
+.db-zoom, .db-minimize, .db-hide, .db-info, #db-collectionMinimize {
float: right;
margin-top: -4px !important;
margin-right: 4px !important;
}
-.db-zoom:hover, .db-minimize:hover, .db-hide, .db-info:hover {
+.db-zoom:hover, .db-minimize:hover, .db-hide, .db-info:hover, .group-close:hover, .group-open:hover,
+#db-collectionMinimize:hover {
cursor:pointer;
}
+.groupHidden li {
+ display:none;
+}
+
.statGroups {
margin-left: 0px;
float:left;
@@ -66,36 +81,49 @@
.statClient {
float: left;
- height: 150px;
- width: 280px;
+ height: 120px;
+ width: 203px;
margin-left: 9px;
- margin-bottom: 15px;
+ margin-bottom: 12px;
border: 1px solid black;
border-radius: 2px 2px 2px 2px;
background-color: #F4F3F3;
box-shadow: 0 0 3px #333333;
}
-.statChart {
+#detailGraphChart {
+ margin-left: -10px !important;
+}
+.statGroups .statChart {
+ margin-left: -45px !important;
+}
+
+.nv-axislabel {
+ margin-left: 20px;
}
.nv-axisMaxMin > text {
font: 10px sans-serif;
}
+.svgCollections {
+ height: 300px;
+ width: 300px;
+}
+
.svgClass {
margin-top: 0 !important;
padding-top: 0 !important;
- height: 150px;
- width: 270px;
+ height: 140px;
+ width: 255px;
}
.svgDetailClass {
margin-top: 0 !important;
padding-top: 0 !important;
height: 300px;
- width: 877px;
+ width: 887px;
}
.boxHeader {
@@ -110,6 +138,7 @@
}
.statsHeader {
+ margin-top: 12px;
margin-left: -10px;
width: 940px;
background-color: #686766;
@@ -143,3 +172,83 @@
.nv-point {
display: none;
}
+
+/*Dashboard Dropdown*/
+
+.dropdown-menu li > a {
+ padding: 0px 20px !important;
+}
+
+.checkboxLabel {
+ margin-top: 4px;
+ padding-left: 0;
+}
+
+.svgClass .nv-axisMaxMin text {
+ visibility: hidden;
+}
+
+.svgClass .major text {
+ visibility: hidden;
+}
+
+.svgClass .nv-axislabel {
+ visibility: hidden;
+}
+
+/*Dashboard Checkbox*/
+input[type=checkbox].css-checkbox {
+ display:none;
+}
+
+input[type=checkbox].css-checkbox + label.css-label {
+ padding-left:20px;
+ margin-top: 0px;
+ margin-bottom: -0px;
+ height:15px;
+ display:inline-block;
+ line-height:15px;
+ background-repeat:no-repeat;
+ background-position: 0 0;
+ font-size:15px;
+ vertical-align:middle;
+ cursor:pointer;
+}
+
+input[type=checkbox].css-checkbox:checked + label.css-label {
+ background-position: 0 -15px;
+}
+
+.css-label {
+ background-image:url(../img/dark-check-green.png);
+}
+
+/*Dashboard Radio */
+
+.dropdown-menu .radio {
+ margin-left: -21px;
+}
+
+input[type="radio"] {
+ display:none;
+}
+
+input[type="radio"] + label {
+ /*color:#f2f2f2;*/
+ /*font-family:Arial, sans-serif;
+ font-size:14px;*/
+}
+
+input[type="radio"] + label span {
+ display:inline-block;
+ width:19px;
+ height:19px;
+ margin:-1px 4px 0 0;
+ vertical-align:middle;
+ background:url(../img/check_radio_sheet.png) -38px top no-repeat;
+ cursor:pointer;
+}
+
+input[type="radio"]:checked + label span {
+ background:url(../img/check_radio_sheet.png) -57px top no-repeat;
+}
diff --git a/html/admin/img/check_radio_sheet.png b/html/admin/img/check_radio_sheet.png
new file mode 100644
index 0000000000..fcb4776faa
Binary files /dev/null and b/html/admin/img/check_radio_sheet.png differ
diff --git a/html/admin/img/dark-check-green.png b/html/admin/img/dark-check-green.png
new file mode 100644
index 0000000000..cb6c4f5958
Binary files /dev/null and b/html/admin/img/dark-check-green.png differ
diff --git a/html/admin/index.html b/html/admin/index.html
index 1ff35e8b67..3f43b8de3f 100644
--- a/html/admin/index.html
+++ b/html/admin/index.html
@@ -110,6 +110,7 @@
+
diff --git a/html/admin/js/collections/arangoCollections.js b/html/admin/js/collections/arangoCollections.js
index b3bd1b0ac1..b13889694b 100644
--- a/html/admin/js/collections/arangoCollections.js
+++ b/html/admin/js/collections/arangoCollections.js
@@ -17,6 +17,9 @@ window.arangoCollections = Backbone.Collection.extend({
},
translateStatus : function (status) {
+ if (status == 0) {
+ return 'corrupted';
+ }
if (status == 1) {
return 'new born collection';
}
diff --git a/html/admin/js/graphViewer/graph/JSONAdapter.js b/html/admin/js/graphViewer/graph/JSONAdapter.js
index d9a9d8e2a9..cfa894b226 100644
--- a/html/admin/js/graphViewer/graph/JSONAdapter.js
+++ b/html/admin/js/graphViewer/graph/JSONAdapter.js
@@ -79,6 +79,9 @@ function JSONAdapter(jsonPath, nodes, edges, width, height) {
return this.start + Math.random() * this.range;
};
+ self.loadNode = function(nodeId, callback) {
+ self.loadNodeFromTreeById(nodeId, callback);
+ };
self.loadNodeFromTreeById = function(nodeId, callback) {
var json = jsonPath + nodeId + ".json";
@@ -159,4 +162,8 @@ function JSONAdapter(jsonPath, nodes, edges, width, height) {
};
+ self.expandCommunity = function (commNode, callback) {
+
+ };
+
}
\ No newline at end of file
diff --git a/html/admin/js/graphViewer/graph/arangoAdapter.js b/html/admin/js/graphViewer/graph/arangoAdapter.js
index 93c5cc3501..ddd2959431 100644
--- a/html/admin/js/graphViewer/graph/arangoAdapter.js
+++ b/html/admin/js/graphViewer/graph/arangoAdapter.js
@@ -53,6 +53,7 @@ function ArangoAdapter(nodes, edges, config) {
api = {},
queries = {},
cachedCommunities = {},
+ joinedInCommunities = {},
nodeCollection,
edgeCollection,
limit,
@@ -60,6 +61,7 @@ function ArangoAdapter(nodes, edges, config) {
arangodb,
width,
height,
+ direction,
setWidth = function(w) {
initialX.range = w / 2;
@@ -93,12 +95,22 @@ function ArangoAdapter(nodes, edges, config) {
if (config.height !== undefined) {
setHeight(config.height);
}
+ if (config.undirected !== undefined) {
+ if (config.undirected === true) {
+ direction = "any";
+ } else {
+ direction = "outbound";
+ }
+ } else {
+ direction = "outbound";
+ }
},
findNode = function(id) {
- var res = $.grep(nodes, function(e){
- return e._id === id;
- });
+ var intId = joinedInCommunities[id] || id,
+ res = $.grep(nodes, function(e){
+ return e._id === intId;
+ });
if (res.length === 0) {
return false;
}
@@ -118,7 +130,7 @@ function ArangoAdapter(nodes, edges, config) {
if (res.length === 1) {
return res[0];
}
- throw "Too many nodes with the same ID, should never happen";
+ throw "Too many edges with the same ID, should never happen";
},
insertNode = function(data) {
@@ -145,7 +157,8 @@ function ArangoAdapter(nodes, edges, config) {
_data: data,
_id: data._id
},
- e = findEdge(edge._id);
+ e = findEdge(edge._id),
+ edgeToPush;
if (e) {
return e;
}
@@ -160,8 +173,32 @@ function ArangoAdapter(nodes, edges, config) {
edge.source = source;
edge.target = target;
edges.push(edge);
- source._outboundCounter++;
- target._inboundCounter++;
+
+
+ if (cachedCommunities[source._id] !== undefined) {
+ edgeToPush = {};
+ edgeToPush.type = "s";
+ edgeToPush.id = edge._id;
+ edgeToPush.source = $.grep(cachedCommunities[source._id].nodes, function(e){
+ return e._id === data._from;
+ })[0];
+ edgeToPush.source._outboundCounter++;
+ cachedCommunities[source._id].edges.push(edgeToPush);
+ } else {
+ source._outboundCounter++;
+ }
+ if (cachedCommunities[target._id] !== undefined) {
+ edgeToPush = {};
+ edgeToPush.type = "t";
+ edgeToPush.id = edge._id;
+ edgeToPush.target = $.grep(cachedCommunities[target._id].nodes, function(e){
+ return e._id === data._to;
+ })[0];
+ edgeToPush.target._inboundCounter++;
+ cachedCommunities[target._id].edges.push(edgeToPush);
+ } else {
+ target._inboundCounter++;
+ }
return edge;
},
@@ -187,7 +224,7 @@ function ArangoAdapter(nodes, edges, config) {
removeEdgesForNode = function (node) {
var i;
- for ( i = 0; i < edges.length; i++ ) {
+ for (i = 0; i < edges.length; i++ ) {
if (edges[i].source === node) {
node._outboundCounter--;
edges[i].target._inboundCounter--;
@@ -202,6 +239,53 @@ function ArangoAdapter(nodes, edges, config) {
}
},
+ combineCommunityEdges = function (nodes, commNode) {
+ var i, j, s, t,
+ cachedCommEdges = cachedCommunities[commNode._id].edges,
+ edgeToPush;
+ for (i = 0; i < edges.length; i++ ) {
+ edgeToPush = {};
+ // s and t keep old values yay!
+ s = edges[i].source;
+ t = edges[i].target;
+ for (j = 0; j < nodes.length; j++) {
+ if (s === nodes[j]) {
+ if (edgeToPush.type !== undefined) {
+ edges[i].target = edgeToPush.target;
+ delete edgeToPush.target;
+ edgeToPush.type = "b";
+ edgeToPush.edge = edges[i];
+ edges.splice( i, 1 );
+ i--;
+ break;
+ }
+ edges[i].source = commNode;
+ edgeToPush.type = "s";
+ edgeToPush.id = edges[i]._id;
+ edgeToPush.source = s;
+ }
+ if (t === nodes[j]) {
+ if (edgeToPush.type !== undefined) {
+ edges[i].source = edgeToPush.source;
+ delete edgeToPush.source;
+ edgeToPush.type = "b";
+ edgeToPush.edge = edges[i];
+ edges.splice( i, 1 );
+ i--;
+ break;
+ }
+ edges[i].target = commNode;
+ edgeToPush.type = "t";
+ edgeToPush.id = edges[i]._id;
+ edgeToPush.target = t;
+ }
+ }
+ if (edgeToPush.type !== undefined) {
+ cachedCommEdges.push(edgeToPush);
+ }
+ }
+ },
+
// Helper function to easily remove all outbound edges for one node
removeOutboundEdgesFromNode = function ( node ) {
if (node._outboundCounter > 0) {
@@ -228,6 +312,9 @@ function ArangoAdapter(nodes, edges, config) {
if (query !== queries.connectedEdges) {
bindVars["@nodes"] = nodeCollection;
}
+ if (query !== queries.childrenCentrality) {
+ bindVars["@dir"] = direction;
+ }
bindVars["@edges"] = edgeCollection;
var data = {
query: query,
@@ -257,24 +344,61 @@ function ArangoAdapter(nodes, edges, config) {
},
collapseCommunity = function (community) {
- var commId = "community_1",
+ var commId = "*community_" + Math.floor(Math.random()* 1000000),
commNode = {
_id: commId,
- x: 1,
- y: 1
+ edges: []
},
nodesToRemove = _.map(community, function(id) {
return findNode(id);
});
- cachedCommunities[commId] = nodesToRemove;
+ commNode.x = nodesToRemove[0].x;
+ commNode.y = nodesToRemove[0].y;
+ cachedCommunities[commId] = {};
+ cachedCommunities[commId].nodes = nodesToRemove;
+ cachedCommunities[commId].edges = [];
+ combineCommunityEdges(nodesToRemove, commNode);
_.each(nodesToRemove, function(n) {
+ joinedInCommunities[n._id] = commId;
removeNode(n);
- removeEdgesForNode(n);
});
nodes.push(commNode);
},
+ expandCommunity = function (commNode) {
+ var commId = commNode._id,
+ nodesToAdd = cachedCommunities[commId].nodes,
+ edgesToChange = cachedCommunities[commId].edges,
+ com;
+ removeNode(commNode);
+ if (limit < nodes.length + nodesToAdd.length) {
+ com = reducer.getCommunity(limit);
+ collapseCommunity(com);
+ }
+ _.each(nodesToAdd, function(n) {
+ delete joinedInCommunities[n._id];
+ nodes.push(n);
+ });
+ _.each(edgesToChange, function(e) {
+ var edge;
+ switch(e.type) {
+ case "t":
+ edge = findEdge(e.id);
+ edge.target = e.target;
+ break;
+ case "s":
+ edge = findEdge(e.id);
+ edge.source = e.source;
+ break;
+ case "b":
+ edges.push(e.edge);
+ break;
+ }
+ });
+ delete cachedCommunities[commId];
+ },
+
parseResultOfTraversal = function (result, callback) {
result = result[0];
_.each(result, function(visited) {
@@ -366,7 +490,7 @@ function ArangoAdapter(nodes, edges, config) {
+ "@@nodes, "
+ "@@edges, "
+ "@id, "
- + "\"outbound\", {"
+ + "@dir, {"
+ "strategy: \"depthfirst\","
+ "maxDepth: 1,"
+ "paths: true"
@@ -379,7 +503,7 @@ function ArangoAdapter(nodes, edges, config) {
+ "@@nodes, "
+ "@@edges, "
+ "n._id, "
- + "\"outbound\", {"
+ + "@dir, {"
+ "strategy: \"depthfirst\","
+ "maxDepth: 1,"
+ "paths: true"
@@ -399,7 +523,6 @@ function ArangoAdapter(nodes, edges, config) {
reducer = new NodeReducer(nodes, edges);
-
self.oldLoadNodeFromTreeById = function(nodeId, callback) {
sendQuery(queries.nodeById, {
id: nodeId
@@ -408,6 +531,10 @@ function ArangoAdapter(nodes, edges, config) {
});
};
+ self.loadNode = function(nodeId, callback) {
+ self.loadNodeFromTreeById(nodeId, callback);
+ };
+
self.loadNodeFromTreeById = function(nodeId, callback) {
sendQuery(queries.traversalById, {
id: nodeId
@@ -554,9 +681,16 @@ function ArangoAdapter(nodes, edges, config) {
});
};
- self.changeTo = function (nodesCol, edgesCol ) {
+ self.changeTo = function (nodesCol, edgesCol, dir) {
nodeCollection = nodesCol;
edgeCollection = edgesCol;
+ if (dir !== undefined) {
+ if (dir === true) {
+ direction = "any";
+ } else {
+ direction = "outbound";
+ }
+ }
api.node = api.base + "document?collection=" + nodeCollection;
api.edge = api.base + "edge?collection=" + edgeCollection;
};
@@ -572,4 +706,11 @@ function ArangoAdapter(nodes, edges, config) {
}
};
+ self.expandCommunity = function (commNode, callback) {
+ expandCommunity(commNode);
+ if (callback !== undefined) {
+ callback();
+ }
+ };
+
}
\ No newline at end of file
diff --git a/html/admin/js/graphViewer/graph/eventLibrary.js b/html/admin/js/graphViewer/graph/eventLibrary.js
index 67198b1cb7..3cefa65244 100644
--- a/html/admin/js/graphViewer/graph/eventLibrary.js
+++ b/html/admin/js/graphViewer/graph/eventLibrary.js
@@ -67,8 +67,8 @@ function EventLibrary() {
if (config.startCallback === undefined) {
throw "A callback to the Start-method has to be defined";
}
- if (config.loadNode === undefined) {
- throw "A callback to load a node has to be defined";
+ if (config.adapter === undefined) {
+ throw "An adapter to load data has to be defined";
}
if (config.reshapeNodes === undefined) {
throw "A callback to reshape nodes has to be defined";
@@ -81,7 +81,9 @@ function EventLibrary() {
var edges = config.edges,
nodes = config.nodes,
startCallback = config.startCallback,
- loadNode = config.loadNode,
+ adapter = config.adapter,
+ loadNode = adapter.loadNode,
+ expandCom = adapter.expandCommunity,
reshapeNodes = config.reshapeNodes,
removeNode = function (node) {
var i;
@@ -126,8 +128,12 @@ function EventLibrary() {
},
expandNode = function(n) {
- n._expanded = true;
- loadNode(n._id, startCallback);
+ if (/^\*community/.test(n._id)) {
+ expandCom(n, startCallback);
+ } else {
+ n._expanded = true;
+ loadNode(n._id, startCallback);
+ }
};
return function(n) {
diff --git a/html/admin/js/graphViewer/graph/nodeReducer.js b/html/admin/js/graphViewer/graph/nodeReducer.js
index 839bb36a03..334d3767cd 100644
--- a/html/admin/js/graphViewer/graph/nodeReducer.js
+++ b/html/admin/js/graphViewer/graph/nodeReducer.js
@@ -148,7 +148,7 @@ function NodeReducer(nodes, edges) {
_.each(nodes, function (n) {
var id = n._id,
c1, c2;
- if (id == sID || id == lID) {
+ if (id === sID || id === lID) {
return null;
}
c1 = getDQValue(dQ, id, sID);
@@ -277,6 +277,7 @@ function NodeReducer(nodes, edges) {
res = [],
dist = {},
dist2 = {},
+ detectSteps = true,
sortByDistance = function (a, b) {
var d1 = dist[_.min(a,minDist(dist))],
d2 = dist[_.min(b,minDist(dist))],
@@ -290,7 +291,9 @@ function NodeReducer(nodes, edges) {
throw "Load some nodes first.";
}
populateValues(dQ, a, heap);
- while (communityDetectionStep(dQ, a, heap, coms)) {}
+ while (detectSteps) {
+ detectSteps = communityDetectionStep(dQ, a, heap, coms);
+ }
res = _.pluck(_.values(coms), "com");
if (focus !== undefined) {
dist = floatDist(focus._id);
diff --git a/html/admin/js/graphViewer/graph/nodeShaper.js b/html/admin/js/graphViewer/graph/nodeShaper.js
index 3d80af419e..d60e7cd1cd 100644
--- a/html/admin/js/graphViewer/graph/nodeShaper.js
+++ b/html/admin/js/graphViewer/graph/nodeShaper.js
@@ -64,6 +64,7 @@ function NodeShaper(parent, flags, idfunc) {
"use strict";
var self = this,
+ communityRegEx = /^\*community/,
nodes = [],
visibleLabels = true,
noop = function (node) {
@@ -91,7 +92,17 @@ function NodeShaper(parent, flags, idfunc) {
addColor = noop,
addShape = noop,
addLabel = noop,
-
+ addCommunityShape = function(g) {
+ g.append("polygon")
+ .attr("points", "0,-25 -16,20 23,-10 -23,-10 16,20");
+ },
+ addCommunityLabel = function(g) {
+ g.append("text") // Append a label for the node
+ .attr("text-anchor", "middle") // Define text-anchor
+ .text(function(d) {
+ return d._size;
+ });
+ },
unbindEvents = function() {
// Hard unbind the dragging
self.parent
@@ -120,9 +131,18 @@ function NodeShaper(parent, flags, idfunc) {
},
addQue = function (g) {
- addShape(g);
+ var community = g.filter(function(n) {
+ return communityRegEx.test(n._id);
+ }),
+ normal = g.filter(function(n) {
+ return !communityRegEx.test(n._id);
+ });
+ addCommunityShape(community);
+ addShape(normal);
+
if (visibleLabels) {
- addLabel(g);
+ addCommunityLabel(community);
+ addLabel(normal);
}
addColor(g);
addEvents(g);
@@ -158,7 +178,12 @@ function NodeShaper(parent, flags, idfunc) {
// Append the group and class to all new
g.enter()
.append("g")
- .attr("class", "node") // node is CSS class that might be edited
+ .attr("class", function(d) {
+ if (communityRegEx.test(d._id)) {
+ return "node communitynode";
+ }
+ return "node";
+ }) // node is CSS class that might be edited
.attr("id", idFunction);
// Remove all old
g.exit().remove();
@@ -176,7 +201,8 @@ function NodeShaper(parent, flags, idfunc) {
case NodeShaper.shapes.CIRCLE:
radius = shape.radius || 25;
addShape = function (node) {
- node.append("circle") // Display nodes as circles
+ node
+ .append("circle") // Display nodes as circles
.attr("r", radius); // Set radius
};
break;
diff --git a/html/admin/js/graphViewer/graphViewer.js b/html/admin/js/graphViewer/graphViewer.js
index fafdf81048..75cc74e204 100644
--- a/html/admin/js/graphViewer/graphViewer.js
+++ b/html/admin/js/graphViewer/graphViewer.js
@@ -85,7 +85,7 @@ function GraphViewer(svg, width, height, adapterConfig, config) {
},
nodeLimitCallBack = function(limit) {
- self.adapter.setNodeLimit(limit);
+ self.adapter.setNodeLimit(limit, self.start);
},
parseZoomConfig = function(config) {
@@ -174,7 +174,7 @@ function GraphViewer(svg, width, height, adapterConfig, config) {
edges: edges,
nodes: nodes,
startCallback: self.start,
- loadNode: self.adapter.loadNodeFromTreeById,
+ adapter: self.adapter,
reshapeNodes: self.nodeShaper.reshapeNodes
},
drag: {
diff --git a/html/admin/js/graphViewer/jasmine_test/helper/mocks.js b/html/admin/js/graphViewer/jasmine_test/helper/mocks.js
index f11f015c7b..da8e5270db 100644
--- a/html/admin/js/graphViewer/jasmine_test/helper/mocks.js
+++ b/html/admin/js/graphViewer/jasmine_test/helper/mocks.js
@@ -43,7 +43,9 @@ var mocks = mocks || {};
patchNode: function(){},
createEdge: function(){},
deleteEdge: function(){},
- patchEdge: function(){}
+ patchEdge: function(){},
+ loadNode: function(){},
+ expandCommunity: function(){}
};
}());
\ No newline at end of file
diff --git a/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js b/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js
index bf3c65f866..c721531ab2 100644
--- a/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js
@@ -58,6 +58,17 @@
checkCallbackFunction = function() {
callbackCheck = true;
},
+
+ getCommunityNodes = function() {
+ return _.filter(nodes, function(n) {
+ return n._id.match(/^\*community/);
+ });
+ },
+
+ getCommunityNodesIds = function() {
+ return _.pluck(getCommunityNodes(), "_id");
+ },
+
nodeWithID = function(id) {
return $.grep(nodes, function(e){
return e._id === id;
@@ -295,26 +306,40 @@
height: 40
}
);
- traversalQuery = function(id, nods, edgs) {
+ traversalQuery = function(id, nods, edgs, undirected) {
+ var dir;
+ if (undirected === true) {
+ dir = "any";
+ } else {
+ dir = "outbound";
+ }
return JSON.stringify({
- query: "RETURN TRAVERSAL(@@nodes, @@edges, @id, \"outbound\","
+ query: "RETURN TRAVERSAL(@@nodes, @@edges, @id, @dir,"
+ " {strategy: \"depthfirst\",maxDepth: 1,paths: true})",
bindVars: {
id: id,
"@nodes": nods,
+ "@dir": dir,
"@edges": edgs
}
});
};
- filterQuery = function(v, nods, edgs) {
+ filterQuery = function(v, nods, edgs, undirected) {
+ var dir;
+ if (undirected === true) {
+ dir = "any";
+ } else {
+ dir = "outbound";
+ }
return JSON.stringify({
query: "FOR n IN @@nodes FILTER n.id == @value"
- + " RETURN TRAVERSAL(@@nodes, @@edges, n._id, \"outbound\","
+ + " RETURN TRAVERSAL(@@nodes, @@edges, n._id, @dir,"
+ " {strategy: \"depthfirst\",maxDepth: 1,paths: true})",
bindVars: {
value: v,
"@nodes": nods,
- "@edges": edgs
+ "@dir": dir,
+ "@edges": edgs
}
});
};
@@ -455,6 +480,12 @@
});
});
+ it('should map loadNode to loadByID', function() {
+ spyOn(adapter, "loadNodeFromTreeById");
+ adapter.loadNode("a", "b");
+ expect(adapter.loadNodeFromTreeById).toHaveBeenCalledWith("a", "b");
+ });
+
it('should be able to load a tree node from ArangoDB'
+ ' by internal attribute and value', function() {
@@ -638,6 +669,40 @@
});
+ it('should be able to switch to different collections and change to directed', function() {
+
+ runs(function() {
+
+ spyOn($, "ajax");
+
+ adapter.changeTo(altNodesCollection, altEdgesCollection, false);
+
+ adapter.loadNode("42");
+
+ expect($.ajax).toHaveBeenCalledWith(
+ requests.cursor(traversalQuery("42", altNodesCollection, altEdgesCollection, false))
+ );
+
+ });
+ });
+
+ it('should be able to switch to different collections'
+ + ' and change to undirected', function() {
+
+ runs(function() {
+
+ spyOn($, "ajax");
+
+ adapter.changeTo(altNodesCollection, altEdgesCollection, true);
+
+ adapter.loadNode("42");
+
+ expect($.ajax).toHaveBeenCalledWith(
+ requests.cursor(traversalQuery("42", altNodesCollection, altEdgesCollection, true))
+ );
+
+ });
+ });
describe('that has already loaded one graph', function() {
var c0, c1, c2, c3, c4, c5, c6, c7,
@@ -877,46 +942,58 @@
runs(function() {
adapter.setNodeLimit(6);
- spyOn(this, "fakeReducerRequest");
+ spyOn(this, "fakeReducerRequest").andCallFake(function() {
+ return [c0];
+ });
adapter.loadNodeFromTreeById(c1, checkCallbackFunction);
expect(this.fakeReducerRequest).toHaveBeenCalledWith(6, nodeWithID(c1));
});
});
- it('should not trigger the reducer if the limit is set large enough', function() {
- spyOn(this, "fakeReducerRequest");
- adapter.setNodeLimit(10);
- expect(this.fakeReducerRequest).not.toHaveBeenCalled();
- });
-
-
- it('should trigger the reducer if the limit is set too small', function() {
- spyOn(this, "fakeReducerRequest");
- adapter.setNodeLimit(2);
- expect(this.fakeReducerRequest).toHaveBeenCalledWith(2);
- });
-
describe('checking community nodes', function() {
- it('should create a community node if limit is set too small', function() {
- var called = false,
- callback = function() {
- called = true;
- };
+ it('should not trigger the reducer if the limit is set large enough', function() {
spyOn(this, "fakeReducerRequest").andCallFake(function() {
- return [c0, c1, c2];
+ return [c0];
});
- adapter.setNodeLimit(2, callback);
+ adapter.setNodeLimit(10);
+ expect(this.fakeReducerRequest).not.toHaveBeenCalled();
+ });
+
+ it('should trigger the reducer if the limit is set too small', function() {
+ spyOn(this, "fakeReducerRequest").andCallFake(function() {
+ return [c0];
+ });
+ adapter.setNodeLimit(2);
+ expect(this.fakeReducerRequest).toHaveBeenCalledWith(2);
+ });
- notExistNodes([c0, c1, c2]);
- existNode("community_1");
- existNodes([c3]);
- expect(nodes.length).toEqual(2);
- existEdge("community_1", c3);
- expect(edges.length).toEqual(1);
-
- expect(called).toBeTruthy();
+ it('should create a community node if limit is set too small', function() {
+ var called;
+
+ runs(function() {
+ callbackCheck = false;
+ spyOn(this, "fakeReducerRequest").andCallFake(function() {
+ return [c0, c1, c2];
+ });
+ adapter.setNodeLimit(2, checkCallbackFunction);
+ });
+
+ waitsFor(function() {
+ return callbackCheck;
+ });
+
+ runs(function() {
+ var commId = getCommunityNodesIds()[0];
+ notExistNodes([c0, c1, c2]);
+ existNode(commId);
+ existNodes([c3, c4]);
+ expect(nodes.length).toEqual(3);
+ existEdge(commId, c3);
+ existEdge(commId, c4);
+ expect(edges.length).toEqual(2);
+ });
});
it('should create a community node if too many nodes are added', function() {
@@ -933,20 +1010,312 @@
});
runs(function() {
+ var commId = getCommunityNodesIds()[0];
notExistNodes([c0, c1, c2, c3]);
- existNode("community_1");
+ existNode(commId);
existNodes([c4, c5, c6, c7]);
expect(nodes.length).toEqual(5);
- existEdge("community_1", c4);
- existEdge("community_1", c5);
- existEdge("community_1", c6);
- existEdge("community_1", c7);
+ existEdge(commId, c4);
+ existEdge(commId, c5);
+ existEdge(commId, c6);
+ existEdge(commId, c7);
expect(edges.length).toEqual(4);
});
});
+ describe('expanding after a while', function() {
+
+ it('should connect edges of internal nodes accordingly', function() {
+
+ var commNode, called, counterCallback,
+ v0, v1, v2, v3, v4,
+ e0_1, e0_2, e1_3, e1_4, e2_3, e2_4;
+
+ runs(function() {
+ var v = "vertices",
+ e = "edges";
+ nodes.length = 0;
+ edges.length = 0;
+ v0 = insertNode(v, 0);
+ v1 = insertNode(v, 1);
+ v2 = insertNode(v, 2);
+ v3 = insertNode(v, 3);
+ v4 = insertNode(v, 4);
+ e0_1 = insertEdge(e, v0, v1);
+ e0_2 = insertEdge(e, v0, v2);
+ e1_3 = insertEdge(e, v1, v3);
+ e1_4 = insertEdge(e, v1, v4);
+ e2_3 = insertEdge(e, v2, v3);
+ e2_4 = insertEdge(e, v2, v4);
+ called = 0;
+ counterCallback = function() {
+ called++;
+ };
+ spyOn(this, "fakeReducerRequest").andCallFake(function() {
+ return [v1, v3, v4];
+ });
+ adapter.setNodeLimit(3);
+
+ adapter.changeTo(v, e);
+ adapter.loadNode(v0, counterCallback);
+ adapter.loadNode(v1, counterCallback);
+
+ });
+
+ waitsFor(function() {
+ return called === 2;
+ });
+
+ runs(function() {
+ adapter.loadNode(v2, counterCallback);
+ commNode = getCommunityNodes()[0];
+ });
+
+ waitsFor(function() {
+ return called === 3;
+ });
+
+ runs(function() {
+ var commId = commNode._id;
+ // Check start condition
+ existNodes([commId, v0, v2]);
+ expect(nodes.length).toEqual(3);
+
+ existEdge(v0, v2);
+ existEdge(v0, commId);
+ existEdge(v2, commId);
+ expect(edges.length).toEqual(4);
+
+ adapter.setNodeLimit(20);
+ adapter.expandCommunity(commNode, counterCallback);
+ });
+
+ waitsFor(function() {
+ return called === 4;
+ });
+
+ runs(function() {
+ existNodes([v0, v1, v2, v3, v4]);
+ expect(nodes.length).toEqual(5);
+
+ existEdge(v0, v1);
+ existEdge(v0, v2);
+ existEdge(v1, v3);
+ existEdge(v1, v4);
+ existEdge(v2, v3);
+ existEdge(v2, v4);
+ expect(edges.length).toEqual(6);
+
+ });
+ });
+
+ it('set inbound and outboundcounter correctly', function() {
+
+ var commNode, called, counterCallback,
+ v0, v1, v2, v3, v4,
+ e0_1, e0_2, e1_3, e1_4, e2_3, e2_4;
+
+ runs(function() {
+ var v = "vertices",
+ e = "edges";
+ nodes.length = 0;
+ edges.length = 0;
+ v0 = insertNode(v, 0);
+ v1 = insertNode(v, 1);
+ v2 = insertNode(v, 2);
+ v3 = insertNode(v, 3);
+ v4 = insertNode(v, 4);
+ e0_1 = insertEdge(e, v0, v1);
+ e0_2 = insertEdge(e, v0, v2);
+ e1_3 = insertEdge(e, v1, v3);
+ e1_4 = insertEdge(e, v1, v4);
+ e2_3 = insertEdge(e, v2, v3);
+ e2_4 = insertEdge(e, v2, v4);
+ called = 0;
+ counterCallback = function() {
+ called++;
+ };
+ spyOn(this, "fakeReducerRequest").andCallFake(function() {
+ return [v1, v3, v4];
+ });
+ adapter.setNodeLimit(3);
+
+ adapter.changeTo(v, e);
+ adapter.loadNode(v0, counterCallback);
+ adapter.loadNode(v1, counterCallback);
+
+ });
+
+ waitsFor(function() {
+ return called === 2;
+ });
+
+ runs(function() {
+ adapter.loadNode(v2, counterCallback);
+ commNode = getCommunityNodes()[0];
+ });
+
+ waitsFor(function() {
+ return called === 3;
+ });
+
+ runs(function() {
+ adapter.setNodeLimit(20);
+ adapter.expandCommunity(commNode, counterCallback);
+ });
+
+ waitsFor(function() {
+ return called === 4;
+ });
+
+ runs(function() {
+ var checkNodeWithInAndOut = function(id, inbound, outbound) {
+ var n = nodeWithID(id);
+ expect(n._outboundCounter).toEqual(outbound);
+ expect(n._inboundCounter).toEqual(inbound);
+ };
+ checkNodeWithInAndOut(v0, 0, 2);
+ checkNodeWithInAndOut(v1, 1, 2);
+ checkNodeWithInAndOut(v2, 1, 2);
+ checkNodeWithInAndOut(v3, 2, 0);
+ checkNodeWithInAndOut(v4, 2, 0);
+ });
+ });
+
+ });
+
+ describe('that displays a community node already', function() {
+
+ var firstCommId,
+ fakeResult;
+
+ beforeEach(function() {
+ runs(function() {
+ callbackCheck = false;
+ adapter.setNodeLimit(7);
+ fakeResult = [c0, c2];
+ spyOn(this, "fakeReducerRequest").andCallFake(function() {
+ return fakeResult;
+ });
+ adapter.loadNodeFromTreeById(c1, checkCallbackFunction);
+ });
+
+ waitsFor(function() {
+ return callbackCheck;
+ });
+
+ runs(function() {
+ firstCommId = getCommunityNodesIds()[0];
+ });
+ });
+
+ it('should expand a community if enough space is available', function() {
+ runs(function() {
+ adapter.setNodeLimit(10);
+ callbackCheck = false;
+ adapter.expandCommunity(nodeWithID(firstCommId), checkCallbackFunction);
+ });
+
+ waitsFor(function() {
+ return callbackCheck;
+ });
+
+ runs(function() {
+ expect(getCommunityNodes().length).toEqual(0);
+ existNodes([c0, c1, c2, c3, c4, c5, c6, c7]);
+ existEdge(c0, c1);
+ existEdge(c0, c2);
+ existEdge(c0, c3);
+ existEdge(c0, c4);
+ });
+
+ });
+
+ it('should expand a community and join another '
+ + 'one if not enough space is available', function() {
+ runs(function() {
+ fakeResult = [c1, c7];
+ callbackCheck = false;
+ adapter.expandCommunity(nodeWithID(firstCommId), checkCallbackFunction);
+ });
+
+ waitsFor(function() {
+ return callbackCheck;
+ });
+
+ runs(function() {
+ var newCommId = getCommunityNodesIds()[0];
+ expect(getCommunityNodes().length).toEqual(1);
+ existNodes([c0, c2, c3, c4, c5, c6, newCommId]);
+ notExistNodes([c1, c7]);
+
+ existEdge(c0, c2);
+ existEdge(c0, c3);
+ existEdge(c0, c4);
+
+ existEdge(c0, newCommId);
+ existEdge(newCommId, c5);
+ existEdge(newCommId, c6);
+ });
+ });
+
+ it('should join another community if space is further reduced', function() {
+ runs(function() {
+ fakeResult = [c1, c7];
+ callbackCheck = false;
+ adapter.setNodeLimit(6, checkCallbackFunction);
+ });
+
+ waitsFor(function() {
+ return callbackCheck;
+ });
+
+ runs(function() {
+ expect(getCommunityNodes().length).toEqual(2);
+ var ids = getCommunityNodesIds(),
+ newCommId;
+
+ if (firstCommId === ids[0]) {
+ newCommId = ids[1];
+ } else {
+ newCommId = ids[0];
+ }
+
+ existNodes([c3, c4, c5, c6, firstCommId, newCommId]);
+ notExistNodes([c0, c1, c2, c7]);
+
+ existEdge(firstCommId, c3);
+ existEdge(firstCommId, c4);
+ existEdge(firstCommId, newCommId);
+ existEdge(newCommId, c5);
+ existEdge(newCommId, c6);
+ });
+ });
+
+ it('should connect edges to internal nodes', function() {
+
+ runs(function() {
+ insertEdge(edgesCollection, c3, c0);
+
+ adapter.setNodeLimit(20);
+ callbackCheck = false;
+ adapter.loadNode(c3, checkCallbackFunction);
+ });
+
+ waitsFor(function() {
+ return callbackCheck;
+ });
+
+ runs(function() {
+ existEdge(c3, firstCommId);
+ });
+
+ });
+
+ });
+
});
describe('that has loaded several queries', function() {
diff --git a/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js b/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js
index eefd4b42e3..485dd09d1b 100644
--- a/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js
@@ -56,6 +56,7 @@ var describeInterface = function (testee) {
});
// Add functions to load here:
+ expect(testee).toHaveFunction("loadNode", 2);
expect(testee).toHaveFunction("loadNodeFromTreeById", 2);
expect(testee).toHaveFunction("requestCentralityChildren", 2);
expect(testee).toHaveFunction("loadNodeFromTreeByAttributeValue", 3);
@@ -66,6 +67,7 @@ var describeInterface = function (testee) {
expect(testee).toHaveFunction("deleteNode", 2);
expect(testee).toHaveFunction("patchNode", 3);
expect(testee).toHaveFunction("setNodeLimit", 2);
+ expect(testee).toHaveFunction("expandCommunity", 2);
});
};
diff --git a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js
index 2aea9bda5f..ac7bd7ee46 100644
--- a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js
@@ -62,6 +62,8 @@
spyOn(adapter, "createEdge");
spyOn(adapter, "patchEdge");
spyOn(adapter, "deleteEdge");
+ spyOn(adapter, "loadNode");
+ spyOn(adapter, "expandCommunity");
};
beforeEach(function() {
@@ -74,9 +76,6 @@
nodes = [];
edges = [];
- this.loadNode = function() {};
- spyOn(this, "loadNode");
-
defaultPosition = {
x: 1,
y: 1,
@@ -87,7 +86,7 @@
edges: edges,
nodes: nodes,
startCallback: function() {},
- loadNode: this.loadNode,
+ adapter: adapter,
reshapeNodes: function() {}
};
@@ -437,7 +436,7 @@
});
waitsFor(function() {
- return this.loadNode.wasCalled;
+ return adapter.loadNode.wasCalled;
}, 1000, "The loadNode function should have been called.");
runs(function() {
diff --git a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js
index f49038b680..e641dccb45 100644
--- a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js
@@ -51,6 +51,8 @@
spyOn(adapter, "createEdge");
spyOn(adapter, "patchEdge");
spyOn(adapter, "deleteEdge");
+ spyOn(adapter, "loadNode");
+ spyOn(adapter, "expandCommunity");
};
@@ -87,15 +89,13 @@
}];
adapter = mocks.adapter;
layouter = mocks.layouter;
- this.loadNode = function() {};
- spyOn(this, "loadNode");
addSpies();
var expandConfig = {
edges: edges,
nodes: nodes,
startCallback: function() {},
- loadNode: this.loadNode,
+ adapter: adapter,
reshapeNodes: function() {}
},
@@ -307,7 +307,7 @@
helper.simulateMouseEvent("click", "1");
- expect(this.loadNode).toHaveBeenCalledWith(nodes[0]._id, jasmine.any(Function));
+ expect(adapter.loadNode).toHaveBeenCalledWith(nodes[0]._id, jasmine.any(Function));
});
});
diff --git a/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js b/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js
index 377a58fc9f..6a39dc7b7e 100644
--- a/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js
@@ -1,6 +1,6 @@
/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */
/*global beforeEach, afterEach */
-/*global describe, it, expect */
+/*global describe, it, expect, jasmine */
/*global runs, spyOn, waitsFor */
/*global window, eb, loadFixtures, document, $ */
/*global EventLibrary*/
@@ -40,13 +40,15 @@
var eventLib,
nodeShaperDummy = {},
- edgeShaperDummy = {};
+ edgeShaperDummy = {},
+ adapterDummy = {};
beforeEach(function() {
eventLib = new EventLibrary();
nodeShaperDummy.reshapeNodes = function() {};
edgeShaperDummy.reshapeEdges = function() {};
-
+ adapterDummy.loadNode = function() {};
+ adapterDummy.expandCommunity = function() {};
spyOn(nodeShaperDummy, "reshapeNodes");
spyOn(edgeShaperDummy, "reshapeEdges");
});
@@ -59,10 +61,6 @@
edges,
loadedNodes,
started,
- loadNodeCallback = function(node) {
- loaded++;
- loadedNodes.push(node);
- },
reshapeNodesCallback = function() {
reshaped++;
},
@@ -84,7 +82,7 @@
edges: edges,
nodes: nodes,
startCallback: startCallback,
- loadNode: loadNodeCallback,
+ adapter: adapterDummy,
reshapeNodes: reshapeNodesCallback
};
});
@@ -96,6 +94,11 @@
_inboundCounter: 0
};
nodes.push(node);
+ spyOn(adapterDummy, "loadNode").andCallFake(function(node) {
+ loaded++;
+ loadedNodes.push(node);
+ });
+ //config.adapter = adapterDummy.loadNode;
testee = eventLib.Expand(config);
testee(node);
@@ -206,6 +209,124 @@
expect(c2._outboundCounter).toEqual(1);
});
+ describe('with community nodes', function() {
+
+ it('should expand a community node properly', function() {
+ var comm = {
+ _id: "*community_1"
+ };
+ nodes.push(comm);
+
+ spyOn(adapterDummy, "expandCommunity");
+
+ testee = eventLib.Expand(config);
+ testee(comm);
+
+ expect(adapterDummy.expandCommunity).toHaveBeenCalledWith(comm, jasmine.any(Function));
+ });
+
+ it('should remove a community if last pointer to it is collapsed', function() {
+
+ runs(function() {
+ var c0 = {
+ _id: 0,
+ _outboundCounter: 1,
+ _inboundCounter: 0
+ },
+ c1 = {
+ _id: 1,
+ _expanded: true,
+ _outboundCounter: 1,
+ _inboundCounter: 1
+ },
+ comm = {
+ _id: "*community_1",
+ _outboundCounter: 1,
+ _inboundCounter: 1
+ },
+ c2 = {
+ _id: 1,
+ _outboundCounter: 0,
+ _inboundCounter: 1
+ },
+ e0 = {
+ source: c0,
+ target: c1
+ },
+ e1 = {
+ source: c1,
+ target: comm
+ },
+ e2 = {
+ source: comm,
+ target: c2
+ };
+ nodes.push(c0);
+ nodes.push(c1);
+ nodes.push(comm);
+ nodes.push(c2);
+ edges.push(e0);
+ edges.push(e1);
+ edges.push(e2);
+
+ testee = eventLib.Expand(config);
+ testee(c1);
+
+ expect(nodes).toEqual([c0, c1]);
+ expect(edges).toEqual([e0]);
+ });
+
+ });
+
+ it('should not remove a community if a pointer to it still exists', function() {
+
+ runs(function() {
+ var c0 = {
+ _id: 0,
+ _outboundCounter: 2,
+ _inboundCounter: 0
+ },
+ c1 = {
+ _id: 1,
+ _expanded: true,
+ _outboundCounter: 1,
+ _inboundCounter: 1
+ },
+ comm = {
+ _id: "*community_1",
+ _outboundCounter: 0,
+ _inboundCounter: 2
+ },
+ e0 = {
+ source: c0,
+ target: c1
+ },
+ e1 = {
+ source: c0,
+ target: comm
+ },
+ e2 = {
+ source: c1,
+ target: comm
+ };
+ nodes.push(c0);
+ nodes.push(c1);
+ nodes.push(comm);
+ edges.push(e0);
+ edges.push(e1);
+ edges.push(e2);
+
+ testee = eventLib.Expand(config);
+ testee(c1);
+
+ expect(nodes).toEqual([c0, c1, comm]);
+ expect(edges).toEqual([e0, e1]);
+ });
+
+ });
+
+ });
+
@@ -248,14 +369,14 @@
function() {
eventLib.Expand(testConfig);
}
- ).toThrow("A callback to load a node has to be defined");
+ ).toThrow("An adapter to load data has to be defined");
});
it('should throw an error if reshape node callback is not given', function() {
testConfig.edges = [];
testConfig.nodes = [];
testConfig.startCallback = function(){};
- testConfig.loadNode = function(){};
+ testConfig.adapter = adapterDummy;
expect(
function() {
eventLib.Expand(testConfig);
diff --git a/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerSpec.js b/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerSpec.js
index e74f3b6d5d..4dc6c49423 100644
--- a/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerSpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerSpec.js
@@ -153,7 +153,7 @@ describe("Graph Viewer", function() {
edges: [],
nodes: [],
startCallback: jasmine.any(Function),
- loadNode: jasmine.any(Function),
+ adapter: jasmine.any(Object),
reshapeNodes: jasmine.any(Function)
},
drag: {
@@ -172,7 +172,7 @@ describe("Graph Viewer", function() {
edges: [],
nodes: [],
startCallback: jasmine.any(Function),
- loadNode: jasmine.any(Function),
+ adapter: jasmine.any(Object),
reshapeNodes: jasmine.any(Function)
});
expect(viewer.dispatcherConfig.drag).toEqual({
@@ -270,6 +270,15 @@ describe("Graph Viewer", function() {
expect(viewer.adapter.setNodeLimit).wasCalled();
});
+ it('should trigger the start function if node limit is reduced to far', function() {
+ spyOn(viewer.adapter, "setNodeLimit").andCallFake(function(l, callback) {
+ callback();
+ });
+ spyOn(viewer, "start");
+ helper.simulateScrollUpMouseEvent("outersvg");
+ expect(viewer.start).wasCalled();
+ });
+
});
diff --git a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js
index 73d69f90d0..384b37d18e 100644
--- a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js
@@ -149,7 +149,7 @@
edges.push(helper.createSimpleEdge(nodes, 5, 7));
var com = reducer.getCommunity(6);
- expect(com).toContainNodes([0, 1, 2]);
+ expect(com).toContainNodes([0, 1, 2, 3]);
});
});
diff --git a/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperSpec.js b/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperSpec.js
index 119e7e134b..b60db1b0f2 100644
--- a/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperSpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperSpec.js
@@ -988,6 +988,76 @@
expect(n.attr("transform")).toEqual("translate(10,10)scale(1)");
});
});
+
+ describe('testing community nodes', function() {
+ var shaper;
+
+ beforeEach(function() {
+ shaper = new NodeShaper(d3.select("svg"));
+ });
+
+ it('should render community nodes', function() {
+ var nodes = helper.createSimpleNodes([0, 1, 2]),
+ commNode = {
+ _id: "*community_42",
+ _inboundCounter: 0,
+ _outboundCounter: 0,
+ position: {
+ x: 1,
+ y: 1,
+ z: 1
+ }
+ };
+ nodes.push(commNode);
+ shaper.drawNodes(nodes);
+ expect($("svg .node").length).toEqual(4);
+ expect($("svg #\\*community_42")[0]).toBeDefined();
+ });
+
+ it('should render communtiy nodes as stars', function() {
+ var nodes = helper.createSimpleNodes([0, 1, 2]),
+ commNode = {
+ _id: "*community_42",
+ _size: 4,
+ _inboundCounter: 0,
+ _outboundCounter: 0,
+ position: {
+ x: 1,
+ y: 1,
+ z: 1
+ }
+ },
+ star;
+ nodes.push(commNode);
+ shaper.drawNodes(nodes);
+ expect($("svg .communitynode").length).toEqual(1);
+ expect($("svg #\\*community_42")[0]).toBeDefined();
+ star = $("svg #\\*community_42 polygon");
+ expect(star.length).toEqual(1);
+ expect(star.attr("points")).toEqual("0,-25 -16,20 23,-10 -23,-10 16,20");
+ });
+
+ it('should print the size of the capsulated community', function() {
+ var nodes = helper.createSimpleNodes([0, 1, 2]),
+ commNode = {
+ _id: "*community_42",
+ _size: 4,
+ _inboundCounter: 0,
+ _outboundCounter: 0,
+ position: {
+ x: 1,
+ y: 1,
+ z: 1
+ }
+ },
+ text;
+ nodes.push(commNode);
+ shaper.drawNodes(nodes);
+ text = $("svg #\\*community_42 text")[0].textContent;
+ expect(text).toEqual("4");
+ });
+
+ });
});
diff --git a/html/admin/js/lib/nv.d3.js b/html/admin/js/lib/nv.d3.js
index 61734e2db1..64065ad344 100644
--- a/html/admin/js/lib/nv.d3.js
+++ b/html/admin/js/lib/nv.d3.js
@@ -479,8 +479,9 @@ nv.models.axis = function() {
.attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
.attr('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
}
- axisLabel.enter().append('text').attr('class', 'nv-axislabel')
+ axisLabel.enter().append('text').attr('class', 'nv-axislabel nv-x-axislabel')
.attr('text-anchor', 'middle')
+ .attr('class', 'heikotestclass')
.attr('y', xLabelMargin);
var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
axisLabel
@@ -565,7 +566,7 @@ nv.models.axis = function() {
.attr('text-anchor', rotateYLabel ? 'middle' : 'end')
.attr('transform', rotateYLabel ? 'rotate(-90)' : '')
//Edited 25 in next line -> origin was 12
- .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 28) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
+ .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 40) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
axisLabel
.attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
if (showMaxMin) {
diff --git a/html/admin/js/templates/collectionsView.ejs b/html/admin/js/templates/collectionsView.ejs
index 62b18893dc..42ce3355de 100644
--- a/html/admin/js/templates/collectionsView.ejs
+++ b/html/admin/js/templates/collectionsView.ejs
@@ -8,17 +8,66 @@
diff --git a/html/admin/js/templates/dashboardView.ejs b/html/admin/js/templates/dashboardView.ejs
index af1e83b8ea..028612dece 100644
--- a/html/admin/js/templates/dashboardView.ejs
+++ b/html/admin/js/templates/dashboardView.ejs
@@ -22,11 +22,11 @@
@@ -35,6 +35,16 @@