+
+
+
+ */
+
+angular.module('patternfly.autofocus', []).directive('pfFocused', ["$timeout", function ($timeout) {
+ 'use strict';
+
+ return {
+ restrict: 'A',
+ link: function (scope, element, attrs) {
+ scope.$watch(attrs.pfFocused, function (newValue) {
+ $timeout(function () {
+ if (newValue) {
+ element[0].focus();
+ if (element[0].select) {
+ element[0].select();
+ }
+ }
+ });
+ });
+ }
+ };
+}]);
+;(function () {
+ 'use strict';
+
+ angular.module('patternfly.canvas').component('pfCanvasEditor', {
+
+ bindings: {
+ chartDataModel: '=',
+ chartViewModel: '=?',
+ toolboxTabs: '=',
+ readOnly: ''
+ },
+ transclude: true,
+ templateUrl: 'canvas-view/canvas-editor/canvas-editor.html',
+ controller: ["$timeout", function ($timeout) {
+ var ctrl = this;
+ var newNodeCount = 0;
+ var prevClickedOnChart, prevInConnectingMode;
+
+ ctrl.$onInit = function () {
+ ctrl.toolboxVisible = false;
+ ctrl.hideConnectors = false;
+ ctrl.draggedItem = null;
+ };
+
+ // need to get these in next digest cycle, after pfCanvas sets chartViewModel
+ $timeout(function () {
+ prevClickedOnChart = ctrl.chartViewModel.clickedOnChart;
+ prevInConnectingMode = ctrl.chartViewModel.inConnectingMode;
+ });
+
+ ctrl.$doCheck = function () {
+ if (angular.isDefined(prevClickedOnChart) && angular.isDefined(prevInConnectingMode)) {
+ if (!angular.equals(ctrl.chartViewModel.clickedOnChart, prevClickedOnChart)) {
+ if (ctrl.chartViewModel.clickedOnChart) {
+ ctrl.chartViewModel.clickedOnChart = false;
+ ctrl.hideToolbox();
+ }
+ prevClickedOnChart = ctrl.chartViewModel.clickedOnChart;
+ }
+ if (!angular.equals(ctrl.chartViewModel.inConnectingMode, prevInConnectingMode)) {
+ if (ctrl.chartViewModel.inConnectingMode) {
+ ctrl.hideConnectors = false;
+ ctrl.hideToolbox();
+ }
+ prevInConnectingMode = ctrl.chartViewModel.inConnectingMode;
+ }
+ }
+ };
+
+ ctrl.addNodeToCanvas = function (newNode) {
+ ctrl.chartViewModel.addNode(newNode);
+ };
+
+ /*** Toolbox Methods ***/
+
+ ctrl.showToolbox = function () {
+ ctrl.toolboxVisible = true;
+ // add class to subtabs to apply PF style and
+ // focus to filter input box
+
+ $timeout(function () {
+ angular.element('.subtabs>u').addClass('nav-tabs-pf');
+ angular.element('#filterFld').focus();
+ });
+ };
+
+ ctrl.hideToolbox = function () {
+ ctrl.toolboxVisible = false;
+ };
+
+ ctrl.toggleToolbox = function () {
+ if (!ctrl.readOnly && !ctrl.chartViewModel.inConnectingMode) {
+ if (ctrl.toolboxVisible === true) {
+ ctrl.hideToolbox();
+ } else {
+ ctrl.showToolbox();
+ }
+ }
+ };
+
+ ctrl.tabClicked = function () {
+ angular.element('#filterFld').focus();
+ };
+
+ /*** Toolbox ***/
+
+ ctrl.startCallback = function (event, ui, item) {
+ ctrl.draggedItem = item;
+ };
+
+ ctrl.dropCallback = function (event, ui) {
+ var newNode = angular.copy(ctrl.draggedItem);
+ newNodeCount++;
+ newNode.x = event.clientX - 600;
+ newNode.y = event.clientY - 200;
+ newNode.backgroundColor = newNode.backgroundColor ? newNode.backgroundColor : '#fff';
+
+ ctrl.chartViewModel.addNode(newNode);
+ };
+
+ ctrl.addNodeByClick = function (item) {
+ var newNode = angular.copy(item);
+ newNodeCount++;
+ newNode.x = 250 + (newNodeCount * 4 + 160);
+ newNode.y = 200 + (newNodeCount * 4 + 160);
+ newNode.backgroundColor = newNode.backgroundColor ? newNode.backgroundColor : '#fff';
+
+ ctrl.chartViewModel.addNode(newNode);
+ };
+
+ ctrl.tabClicked = function () {
+ angular.element('#filterFld').focus();
+ };
+
+ ctrl.activeTab = function () {
+ return ctrl.toolboxTabs.filter(function (tab) {
+ return tab.active;
+ })[0];
+ };
+
+ ctrl.activeSubTab = function () {
+ var activeTab = ctrl.activeTab();
+ if (activeTab && activeTab.subtabs) {
+ return activeTab.subtabs.filter(function (subtab) {
+ return subtab.active;
+ })[0];
+ }
+ };
+
+ ctrl.activeSubSubTab = function () {
+ var activeSubTab = ctrl.activeSubTab();
+ if (activeSubTab && activeSubTab.subtabs) {
+ return activeSubTab.subtabs.filter(function (subsubtab) {
+ return subsubtab.active;
+ })[0];
+ }
+ };
+
+ /*** Zoom ***/
+
+ ctrl.maxZoom = function () {
+ if (ctrl.chartViewModel && ctrl.chartViewModel.zoom) {
+ return ctrl.chartViewModel.zoom.isMax();
+ }
+
+ return false;
+ };
+
+ ctrl.minZoom = function () {
+ if (ctrl.chartViewModel && ctrl.chartViewModel.zoom) {
+ return ctrl.chartViewModel.zoom.isMin();
+ }
+
+ return false;
+ };
+
+ ctrl.zoomIn = function () {
+ ctrl.chartViewModel.zoom.in();
+ };
+
+ ctrl.zoomOut = function () {
+ ctrl.chartViewModel.zoom.out();
+ };
+ }] // controller
+ }); // module
+})();
+;(function () {
+ 'use strict';
+
+ angular.module('patternfly.canvas')
+ .component('toolboxItems', {
+ templateUrl: 'canvas-view/canvas-editor/toolbox-items.html',
+ bindings: {
+ items: '=',
+ startDragCallback: '<',
+ clickCallback: '<',
+ searchText: '='
+ },
+ controller: function toolboxItemsController () {
+ var ctrl = this;
+
+ ctrl.itemClicked = function (item) {
+ if (angular.isFunction(ctrl.clickCallback) && !item.disableInToolbox) {
+ ctrl.clickCallback(item);
+ }
+ };
+
+ ctrl.startItemDrag = function (event, ui, item) {
+ if (angular.isFunction(ctrl.startDragCallback)) {
+ ctrl.startDragCallback(event, ui, item);
+ }
+ };
+ }
+ });
+})();
+;(function () {
+ 'use strict';
+
+ angular.module('patternfly.canvas')
+ .filter('trustAsResourceUrl', ['$sce', function ($sce) {
+ return function (val) {
+ return $sce.trustAsResourceUrl(val);
+ };
+ }])
+
+ //
+ // Component that generates the rendered chart from the data model.
+ //
+ .component('pfCanvas', {
+ templateUrl: 'canvas-view/canvas/canvas.html',
+ bindings: {
+ chartDataModel:'=',
+ chartViewModel: '=?',
+ readOnly: '',
+ hideConnectors: '=?'
+ },
+ controller: ["$scope", "dragging", "$element", "$document", function CanvasController ($scope, dragging, $element, $document) {
+ var ctrl = this;
+
+ ctrl.chart = new pfCanvas.ChartViewModel(ctrl.chartDataModel);
+ ctrl.chartViewModel = ctrl.chart;
+
+ //
+ // Init data-model variables.
+ //
+ ctrl.draggingConnection = false;
+ ctrl.connectorSize = 6;
+ ctrl.dragSelecting = false;
+
+ //
+ // Reference to the connection, connector or node that the mouse is currently over.
+ //
+ ctrl.mouseOverConnector = null;
+ ctrl.mouseOverConnection = null;
+ ctrl.mouseOverNode = null;
+
+
+ //
+ // Translate the coordinates so they are relative to the svg element.
+ //
+ this.translateCoordinates = function (x, y, evt) {
+ var svgElem = $element.get(0).children[0];
+ var matrix = svgElem.getScreenCTM();
+ var point = svgElem.createSVGPoint();
+ point.x = (x - evt.view.pageXOffset) / ctrl.zoomLevel();
+ point.y = (y - evt.view.pageYOffset) / ctrl.zoomLevel();
+
+ return point.matrixTransform(matrix.inverse());
+ };
+
+ ctrl.hideConnectors = ctrl.hideConnectors || false;
+
+ ctrl.isConnectorConnected = function (connector) {
+ return (connector && connector.connected());
+ };
+
+ ctrl.isConnectorUnconnectedAndValid = function (connector) {
+ return (connector && !connector.connected() && !connector.invalid() &&
+ connector.parentNode() !== ctrl.connectingModeSourceNode);
+ };
+
+ // determines if a dest. connector is connected to the source node
+ ctrl.isConnectedTo = function (connector, node) {
+ var i,connection;
+ var connections = ctrl.chart.connections;
+ for (i = 0; i < connections.length; i++) {
+ connection = connections[i];
+ if (connection.dest === connector && connection.source.parentNode() === node) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ ctrl.availableConnections = function () {
+ return ctrl.chart.validConnections;
+ };
+
+ ctrl.foreignObjectSupported = function () {
+ return $document[0].implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Extensibility', '1.1');
+ };
+
+ ctrl.addNodeToCanvas = function (newNode) {
+ ctrl.chart.addNode(newNode);
+ };
+
+ $scope.$on('selectAll', function () {
+ ctrl.selectAll();
+ });
+
+ ctrl.selectAll = function () {
+ ctrl.chart.selectAll();
+ };
+
+ $scope.$on('deselectAll', function () {
+ ctrl.deselectAll();
+ });
+
+ ctrl.deselectAll = function () {
+ ctrl.chart.deselectAll();
+ };
+
+ $scope.$on('deleteSelected', function () {
+ ctrl.deleteSelected();
+ });
+
+ ctrl.deleteSelected = function () {
+ ctrl.chart.deleteSelected();
+ };
+
+ //
+ // Called on mouse down in the chart.
+ //
+ ctrl.mouseDown = function (evt) {
+ if (ctrl.readOnly) {
+ return;
+ }
+
+ if (ctrl.chart.inConnectingMode ) {
+ // canceling out of connection mode, remove unused output connector
+ ctrl.cancelConnectingMode();
+ }
+
+ ctrl.chart.deselectAll();
+
+ ctrl.chart.clickedOnChart = true;
+
+ dragging.startDrag(evt, {
+
+ //
+ // Commence dragging... setup variables to display the drag selection rect.
+ //
+ dragStarted: function (x, y) {
+ var startPoint;
+ ctrl.dragSelecting = true;
+ startPoint = ctrl.translateCoordinates(x, y, evt);
+ ctrl.dragSelectionStartPoint = startPoint;
+ ctrl.dragSelectionRect = {
+ x: startPoint.x,
+ y: startPoint.y,
+ width: 0,
+ height: 0
+ };
+ },
+
+ //
+ // Update the drag selection rect while dragging continues.
+ //
+ dragging: function (x, y) {
+ var startPoint = ctrl.dragSelectionStartPoint;
+ var curPoint = ctrl.translateCoordinates(x, y, evt);
+
+ ctrl.dragSelectionRect = {
+ x: curPoint.x > startPoint.x ? startPoint.x : curPoint.x,
+ y: curPoint.y > startPoint.y ? startPoint.y : curPoint.y,
+ width: curPoint.x > startPoint.x ? curPoint.x - startPoint.x : startPoint.x - curPoint.x,
+ height: curPoint.y > startPoint.y ? curPoint.y - startPoint.y : startPoint.y - curPoint.y,
+ };
+ },
+
+ //
+ // Dragging has ended... select all that are within the drag selection rect.
+ //
+ dragEnded: function () {
+ ctrl.dragSelecting = false;
+ ctrl.chart.applySelectionRect(ctrl.dragSelectionRect);
+ delete ctrl.dragSelectionStartPoint;
+ delete ctrl.dragSelectionRect;
+ }
+ });
+ };
+
+ //
+ // Handle nodeMouseOver on an node.
+ //
+ ctrl.nodeMouseOver = function (evt, node) {
+ if (!ctrl.readOnly) {
+ ctrl.mouseOverNode = node;
+ }
+ };
+
+ //
+ // Handle nodeMouseLeave on an node.
+ //
+ ctrl.nodeMouseLeave = function () {
+ ctrl.mouseOverNode = null;
+ };
+
+ //
+ // Handle mousedown on a node.
+ //
+ ctrl.nodeMouseDown = function (evt, node) {
+ var chart = ctrl.chart;
+ var lastMouseCoords;
+
+ if (ctrl.readOnly) {
+ return;
+ }
+
+ dragging.startDrag(evt, {
+
+ //
+ // Node dragging has commenced.
+ //
+ dragStarted: function (x, y) {
+ lastMouseCoords = ctrl.translateCoordinates(x, y, evt);
+
+ //
+ // If nothing is selected when dragging starts,
+ // at least select the node we are dragging.
+ //
+ if (!node.selected()) {
+ chart.deselectAll();
+ node.select();
+ }
+ },
+
+ //
+ // Dragging selected nodes... update their x,y coordinates.
+ //
+ dragging: function (x, y) {
+ var curCoords = ctrl.translateCoordinates(x, y, evt);
+ var deltaX = curCoords.x - lastMouseCoords.x;
+ var deltaY = curCoords.y - lastMouseCoords.y;
+
+ chart.updateSelectedNodesLocation(deltaX, deltaY);
+
+ lastMouseCoords = curCoords;
+ },
+
+ //
+ // The node wasn't dragged... it was clicked.
+ //
+ clicked: function () {
+ chart.handleNodeClicked(node, evt.ctrlKey);
+ }
+
+ });
+ };
+
+ //
+ // Listen for node action
+ //
+ ctrl.nodeClickHandler = function (action, node) {
+ if (action === 'nodeActionConnect') {
+ ctrl.startConnectingMode(node);
+ }
+ };
+
+ ctrl.nodeCloseHandler = function () {
+ ctrl.mouseOverNode = null;
+ };
+
+ ctrl.connectingModeOutputConnector = null;
+ ctrl.connectingModeSourceNode = null;
+
+ ctrl.startConnectingMode = function (node) {
+ ctrl.chart.inConnectingMode = true;
+ ctrl.hideConnectors = false;
+ ctrl.connectingModeSourceNode = node;
+ ctrl.connectingModeSourceNode.select();
+ ctrl.connectingModeOutputConnector = node.getOutputConnector();
+ ctrl.chart.updateValidNodesAndConnectors(ctrl.connectingModeSourceNode);
+ };
+
+ ctrl.cancelConnectingMode = function () {
+ // if output connector not connected to something, remove it
+ if (!ctrl.connectingModeOutputConnector.connected()) {
+ ctrl.chart.removeOutputConnector(ctrl.connectingModeOutputConnector);
+ }
+ ctrl.stopConnectingMode();
+ };
+
+ ctrl.stopConnectingMode = function () {
+ ctrl.chart.inConnectingMode = false;
+ ctrl.chart.resetValidNodesAndConnectors();
+ };
+
+ //
+ // Handle connectionMouseOver on an connection.
+ //
+ ctrl.connectionMouseOver = function (evt, connection) {
+ if (!ctrl.draggingConnection && !ctrl.readOnly) { // Only allow 'connection mouse over' when not dragging out a connection.
+ ctrl.mouseOverConnection = connection;
+ }
+ };
+
+ //
+ // Handle connectionMouseLeave on an connection.
+ //
+ ctrl.connectionMouseLeave = function () {
+ ctrl.mouseOverConnection = null;
+ };
+
+ //
+ // Handle mousedown on a connection.
+ //
+ ctrl.connectionMouseDown = function (evt, connection) {
+ var chart = ctrl.chart;
+ if (!ctrl.readOnly) {
+ chart.handleConnectionMouseDown(connection, evt.ctrlKey);
+ }
+ // Don't let the chart handle the mouse down.
+ evt.stopPropagation();
+ evt.preventDefault();
+ };
+
+ //
+ // Handle connectorMouseOver on an connector.
+ //
+ ctrl.connectorMouseOver = function (evt, node, connector) {
+ if (!ctrl.readOnly) {
+ ctrl.mouseOverConnector = connector;
+ }
+ };
+
+ //
+ // Handle connectorMouseLeave on an connector.
+ //
+ ctrl.connectorMouseLeave = function () {
+ ctrl.mouseOverConnector = null;
+ };
+
+ //
+ // Handle mousedown on an input connector.
+ //
+ ctrl.connectorMouseDown = function (evt, node) {
+ if (ctrl.chart.inConnectingMode && node !== ctrl.connectingModeSourceNode) {
+ ctrl.chart.createNewConnection(ctrl.connectingModeOutputConnector, ctrl.mouseOverConnector);
+ ctrl.stopConnectingMode();
+ }
+ };
+
+ //
+ // zoom.
+ //
+ $scope.$on('zoomIn', function () {
+ ctrl.chart.zoom.in();
+ });
+
+ $scope.$on('zoomOut', function () {
+ ctrl.chart.zoom.out();
+ });
+
+ $scope.maxZoom = function () {
+ return (ctrl.chart.chartViewModel && ctrl.chart.chartViewModel.zoom) ? ctrl.chart.chartViewModel.zoom.isMax() : false;
+ };
+ $scope.minZoom = function () {
+ return (ctrl.chart.chartViewModel && ctrl.chart.chartViewModel.zoom) ? ctrl.chart.chartViewModel.zoom.isMin() : false;
+ };
+
+ ctrl.zoomLevel = function () {
+ return ctrl.chart.zoom.getLevel();
+ };
+
+ ctrl.$onInit = function () {
+ var backspaceKeyCode = 8;
+ var deleteKeyCode = 46;
+ var aKeyCode = 65;
+ var escKeyCode = 27;
+
+ $document.find('body').keydown(function (evt) {
+
+ if (evt.keyCode === aKeyCode && evt.ctrlKey === true) {
+ //
+ // Ctrl + A
+ //
+ ctrl.selectAll();
+ $scope.$digest();
+ evt.stopPropagation();
+ evt.preventDefault();
+ }
+
+ if (evt.keyCode === deleteKeyCode || evt.keyCode === backspaceKeyCode) {
+ ctrl.deleteSelected();
+ $scope.$digest();
+ }
+
+ if (evt.keyCode === escKeyCode) {
+ ctrl.deselectAll();
+ $scope.$digest();
+ }
+ });
+ };
+ }]
+ });
+})();
+;/* eslint-disable */
+//
+// Global accessor.
+//
+var pfCanvas = {};
+
+// Module.
+(function() {
+ //
+ // Height of flow chart.
+ //
+ pfCanvas.defaultHeight = 756;
+
+ //
+ // Width of flow chart.
+ //
+ pfCanvas.defaultWidth = 1396;
+
+ pfCanvas.defaultBgImageSize = 24;
+
+ //
+ // Width of a node.
+ //
+ pfCanvas.defaultNodeWidth = 150;
+
+ //
+ // Height of a node.
+ //
+ pfCanvas.defaultNodeHeight = 150;
+
+ //
+ // Amount of space reserved for displaying the node's name.
+ //
+ pfCanvas.nodeNameHeight = 40;
+
+ //
+ // Height of a connector in a node.
+ //
+ pfCanvas.connectorHeight = 25;
+
+ //
+ // Compute the Y coordinate of a connector, given its index.
+ //
+ pfCanvas.computeConnectorY = function(connectorIndex) {
+ return pfCanvas.defaultNodeHeight / 2 + connectorIndex * pfCanvas.connectorHeight;
+ };
+
+ //
+ // Compute the position of a connector in the graph.
+ //
+ pfCanvas.computeConnectorPos = function(node, connectorIndex, inputConnector) {
+ return {
+ x: node.x() + (inputConnector ? 0 : node.width ? node.width() : pfCanvas.defaultNodeWidth),
+ y: node.y() + pfCanvas.computeConnectorY(connectorIndex)
+ };
+ };
+
+ //
+ // View model for a connector.
+ //
+ pfCanvas.ConnectorViewModel = function(connectorDataModel, x, y, parentNode) {
+ this.data = connectorDataModel;
+
+ this._parentNode = parentNode;
+ this._x = x;
+ this._y = y;
+
+ //
+ // The name of the connector.
+ //
+ this.name = function() {
+ return this.data.name;
+ };
+
+ //
+ // X coordinate of the connector.
+ //
+ this.x = function() {
+ return this._x;
+ };
+
+ //
+ // Y coordinate of the connector.
+ //
+ this.y = function() {
+ return this._y;
+ };
+
+ //
+ // The parent node that the connector is attached to.
+ //
+ this.parentNode = function() {
+ return this._parentNode;
+ };
+
+ //
+ // Is this connector connected?
+ //
+ this.connected = function() {
+ return this.data.connected;
+ };
+
+ //
+ // set connector connected
+ //
+ this.setConnected = function(value) {
+ this.data.connected = value;
+ };
+
+ //
+ // Is this connector invalid for a connecton?
+ //
+ this.invalid = function() {
+ return this.data.invalid;
+ };
+
+ //
+ // set connector invalid
+ //
+ this.setInvalid = function(value) {
+ this.data.invalid = value;
+ };
+
+ //
+ // Font Family for the the node.
+ //
+ this.fontFamily = function() {
+ return this.data.fontFamily || "";
+ };
+
+ //
+ // Font Content for the the node.
+ //
+ this.fontContent = function() {
+ return this.data.fontContent || "";
+ };
+ };
+
+ //
+ // Create view model for a list of data models.
+ //
+ var createConnectorsViewModel = function(connectorDataModels, x, parentNode) {
+ var viewModels = [];
+
+ if (connectorDataModels) {
+ for (var i = 0; i < connectorDataModels.length; ++i) {
+ var connectorViewModel = new pfCanvas.ConnectorViewModel(connectorDataModels[i], x, pfCanvas.computeConnectorY(i), parentNode);
+ viewModels.push(connectorViewModel);
+ }
+ }
+
+ return viewModels;
+ };
+
+ //
+ // View model for a node.
+ //
+ pfCanvas.NodeViewModel = function(nodeDataModel) {
+ this.data = nodeDataModel;
+
+ // set the default width value of the node
+ if (!this.data.width || this.data.width < 0) {
+ this.data.width = pfCanvas.defaultNodeWidth;
+ }
+ this.inputConnectors = createConnectorsViewModel(this.data.inputConnectors, 0, this);
+ this.outputConnectors = createConnectorsViewModel(this.data.outputConnectors, this.data.width, this);
+
+ // Set to true when the node is selected.
+ this._selected = false;
+
+ //
+ // Name of the node.
+ //
+ this.name = function() {
+ return this.data.name || "";
+ };
+
+ //
+ // id of the node.
+ //
+ this.id = function() {
+ return this.data.id || -1;
+ };
+
+ //
+ // Image for the the node.
+ //
+ this.image = function() {
+ return this.data.image || "";
+ };
+
+ //
+ // Icon for the the node.
+ //
+ this.icon = function() {
+ return this.data.icon || "";
+ };
+
+ //
+ // Is node a bundle
+ //
+ this.bundle = function() {
+ return this.data.bundle || "";
+ };
+
+ //
+ // background color for the node.
+ //
+ this.backgroundColor = function() {
+ return this.data.backgroundColor;
+ };
+
+ //
+ // X coordinate of the node.
+ //
+ this.x = function() {
+ return this.data.x;
+ };
+
+ //
+ // Y coordinate of the node.
+ //
+ this.y = function() {
+ return this.data.y;
+ };
+
+ //
+ // Width of the node.
+ //
+ this.width = function() {
+ return this.data.width;
+ };
+
+ //
+ // Font Family for the the node.
+ //
+ this.fontFamily = function() {
+ return this.data.fontFamily || "";
+ };
+
+ //
+ // Font size for the the icon
+ //
+ this.fontSize = function() {
+ return this.data.fontSize || "";
+ };
+
+ //
+ // Font Content for the the node.
+ //
+ this.fontContent = function() {
+ return this.data.fontContent || "";
+ };
+
+ //
+ // Returns valid connection types for the node.
+ //
+ this.validConnectionTypes = function() {
+ return this.data.validConnectionTypes || [];
+ };
+
+ //
+ // Is this node valid for current connection?
+ //
+ this.invalid = function() {
+ return this.data.invalid;
+ };
+
+ //
+ // set node valid
+ //
+ this.setInvalid = function(value) {
+ this.data.invalid = value;
+ };
+
+ //
+ // Height of the node.
+ //
+ this.height = function() {
+ /*
+ var numConnectors =
+ Math.max(
+ this.inputConnectors.length,
+ this.outputConnectors.length);
+
+ return pfCanvas.computeConnectorY(numConnectors);
+ */
+
+ return pfCanvas.defaultNodeHeight;
+ };
+
+ //
+ // Select the node.
+ //
+ this.select = function() {
+ this._selected = true;
+ };
+
+ //
+ // Deselect the node.
+ //
+ this.deselect = function() {
+ this._selected = false;
+ };
+
+ //
+ // Toggle the selection state of the node.
+ //
+ this.toggleSelected = function() {
+ this._selected = !this._selected;
+ };
+
+ //
+ // Returns true if the node is selected.
+ //
+ this.selected = function() {
+ return this._selected;
+ };
+
+ //
+ // Internal function to add a connector.
+ this._addConnector = function(connectorDataModel, x, connectorsDataModel, connectorsViewModel) {
+ var connectorViewModel = new pfCanvas.ConnectorViewModel(connectorDataModel, x,
+ pfCanvas.computeConnectorY(connectorsViewModel.length), this);
+
+ connectorsDataModel.push(connectorDataModel);
+
+ // Add to node's view model.
+ connectorsViewModel.push(connectorViewModel);
+
+ return connectorViewModel;
+ };
+
+ //
+ // Internal function to remove a connector.
+ this._removeConnector = function(connectorDataModel, connectorsDataModel, connectorsViewModel) {
+ var connectorIndex = connectorsDataModel.indexOf(connectorDataModel);
+ connectorsDataModel.splice(connectorIndex, 1);
+ connectorsViewModel.splice(connectorIndex, 1);
+ };
+
+ //
+ // Add an input connector to the node.
+ //
+ this.addInputConnector = function(connectorDataModel) {
+ if (!this.data.inputConnectors) {
+ this.data.inputConnectors = [];
+ }
+ this._addConnector(connectorDataModel, 0, this.data.inputConnectors, this.inputConnectors);
+ };
+
+ //
+ // Get the single ouput connector for the node.
+ //
+ this.getOutputConnector = function() {
+ if (!this.data.outputConnectors) {
+ this.data.outputConnectors = [];
+ }
+
+ if (this.data.outputConnectors.length === 0) {
+ var connectorDataModel = {name: 'out'};
+
+ return this._addConnector(connectorDataModel, this.data.width, this.data.outputConnectors, this.outputConnectors);
+ } else {
+ return this.outputConnectors[0];
+ }
+ };
+
+ //
+ // Remove an ouput connector from the node.
+ //
+ this.removeOutputConnector = function(connectorDataModel) {
+ if (this.data.outputConnectors) {
+ this._removeConnector(connectorDataModel, this.data.outputConnectors, this.outputConnectors);
+ }
+ };
+
+ this.tags = function() {
+ return this.data.tags;
+ };
+ };
+
+ //
+ // Wrap the nodes data-model in a view-model.
+ //
+ var createNodesViewModel = function(nodesDataModel) {
+ var nodesViewModel = [];
+
+ if (nodesDataModel) {
+ for (var i = 0; i < nodesDataModel.length; ++i) {
+ nodesViewModel.push(new pfCanvas.NodeViewModel(nodesDataModel[i]));
+ }
+ }
+
+ return nodesViewModel;
+ };
+
+ //
+ // View model for a node action.
+ //
+ pfCanvas.NodeActionViewModel = function(nodeActionDataModel) {
+ this.data = nodeActionDataModel;
+
+ //
+ // id of the node action.
+ //
+ this.id = function() {
+ return this.data.id || "";
+ };
+
+ //
+ // Name of the node action.
+ //
+ this.name = function() {
+ return this.data.name || "";
+ };
+
+ //
+ // Font Family for the the node.
+ //
+ this.iconClass = function() {
+ return this.data.iconClass || "";
+ };
+
+ //
+ // Font Content for the the node.
+ //
+ this.action = function() {
+ return this.data.action || "";
+ };
+ };
+
+ //
+ // Wrap the node actions data-model in a view-model.
+ //
+ var createNodeActionsViewModel = function(nodeActionsDataModel) {
+ var nodeActionsViewModel = [];
+
+ if (nodeActionsDataModel) {
+ for (var i = 0; i < nodeActionsDataModel.length; ++i) {
+ nodeActionsViewModel.push(new pfCanvas.NodeActionViewModel(nodeActionsDataModel[i]));
+ }
+ }
+
+ return nodeActionsViewModel;
+ };
+
+ //
+ // View model for a connection.
+ //
+ pfCanvas.ConnectionViewModel = function(connectionDataModel, sourceConnector, destConnector) {
+ this.data = connectionDataModel;
+ this.source = sourceConnector;
+ this.dest = destConnector;
+
+ // Set to true when the connection is selected.
+ this._selected = false;
+
+ this.name = function() {
+ return destConnector.name() || "";
+ };
+
+ this.sourceCoordX = function() {
+ return this.source.parentNode().x() + this.source.x();
+ };
+
+ this.sourceCoordY = function() {
+ return this.source.parentNode().y() + this.source.y();
+ };
+
+ this.sourceCoord = function() {
+ return {
+ x: this.sourceCoordX(),
+ y: this.sourceCoordY()
+ };
+ };
+
+ this.sourceTangentX = function() {
+ return pfCanvas.computeConnectionSourceTangentX(this.sourceCoord(), this.destCoord());
+ };
+
+ this.sourceTangentY = function() {
+ return pfCanvas.computeConnectionSourceTangentY(this.sourceCoord(), this.destCoord());
+ };
+
+ this.destCoordX = function() {
+ return this.dest.parentNode().x() + this.dest.x();
+ };
+
+ this.destCoordY = function() {
+ return this.dest.parentNode().y() + this.dest.y();
+ };
+
+ this.destCoord = function() {
+ return {
+ x: this.destCoordX(),
+ y: this.destCoordY()
+ };
+ };
+
+ this.destTangentX = function() {
+ return pfCanvas.computeConnectionDestTangentX(this.sourceCoord(), this.destCoord());
+ };
+
+ this.destTangentY = function() {
+ return pfCanvas.computeConnectionDestTangentY(this.sourceCoord(), this.destCoord());
+ };
+
+ this.middleX = function(scale) {
+ if (angular.isUndefined(scale)) {
+ scale = 0.5;
+ }
+
+ return this.sourceCoordX() * (1 - scale) + this.destCoordX() * scale;
+ };
+
+ this.middleY = function(scale) {
+ if (angular.isUndefined(scale)) {
+ scale = 0.5;
+ }
+
+ return this.sourceCoordY() * (1 - scale) + this.destCoordY() * scale;
+ };
+
+ //
+ // Select the connection.
+ //
+ this.select = function() {
+ this._selected = true;
+ };
+
+ //
+ // Deselect the connection.
+ //
+ this.deselect = function() {
+ this._selected = false;
+ };
+
+ //
+ // Toggle the selection state of the connection.
+ //
+ this.toggleSelected = function() {
+ this._selected = !this._selected;
+ };
+
+ //
+ // Returns true if the connection is selected.
+ //
+ this.selected = function() {
+ return this._selected;
+ };
+ };
+
+ //
+ // Helper function.
+ //
+ var computeConnectionTangentOffset = function(pt1, pt2) {
+ return (pt2.x - pt1.x) / 2;
+ };
+
+ //
+ // Compute the tangent for the bezier curve.
+ //
+ pfCanvas.computeConnectionSourceTangentX = function(pt1, pt2) {
+ return pt1.x + computeConnectionTangentOffset(pt1, pt2);
+ };
+
+ //
+ // Compute the tangent for the bezier curve.
+ //
+ pfCanvas.computeConnectionSourceTangentY = function(pt1, pt2) {
+ return pt1.y;
+ };
+
+ //
+ // Compute the tangent for the bezier curve.
+ //
+ pfCanvas.computeConnectionSourceTangent = function(pt1, pt2) {
+ return {
+ x: pfCanvas.computeConnectionSourceTangentX(pt1, pt2),
+ y: pfCanvas.computeConnectionSourceTangentY(pt1, pt2)
+ };
+ };
+
+ //
+ // Compute the tangent for the bezier curve.
+ //
+ pfCanvas.computeConnectionDestTangentX = function(pt1, pt2) {
+ return pt2.x - computeConnectionTangentOffset(pt1, pt2);
+ };
+
+ //
+ // Compute the tangent for the bezier curve.
+ //
+ pfCanvas.computeConnectionDestTangentY = function(pt1, pt2) {
+ return pt2.y;
+ };
+
+ //
+ // Compute the tangent for the bezier curve.
+ //
+ pfCanvas.computeConnectionDestTangent = function(pt1, pt2) {
+ return {
+ x: pfCanvas.computeConnectionDestTangentX(pt1, pt2),
+ y: pfCanvas.computeConnectionDestTangentY(pt1, pt2)
+ };
+ };
+
+ //
+ // View model for the chart.
+ //
+ pfCanvas.ChartViewModel = function(chartDataModel) {
+ //
+ // Find a specific node within the chart.
+ //
+ this.findNode = function(nodeID) {
+ for (var i = 0; i < this.nodes.length; ++i) {
+ var node = this.nodes[i];
+ if (node.data.id === nodeID) {
+ return node;
+ }
+ }
+
+ throw new Error("Failed to find node " + nodeID);
+ };
+
+ //
+ // Find a specific input connector within the chart.
+ //
+ this.findInputConnector = function(nodeID, connectorIndex) {
+ var node = this.findNode(nodeID);
+
+ if (!node.inputConnectors || node.inputConnectors.length <= connectorIndex) {
+ throw new Error("Node " + nodeID + " has invalid input connectors.");
+ }
+
+ return node.inputConnectors[connectorIndex];
+ };
+
+ //
+ // Find a specific output connector within the chart.
+ //
+ this.findOutputConnector = function(nodeID, connectorIndex) {
+ var node = this.findNode(nodeID);
+
+ /*if (!node.outputConnectors || node.outputConnectors.length < connectorIndex) {
+ throw new Error("Node " + nodeID + " has invalid output connectors.");
+ }
+
+ return node.outputConnectors[connectorIndex];*/
+ return node.getOutputConnector();
+ };
+
+ //
+ // Create a view model for connection from the data model.
+ //
+ this._createConnectionViewModel = function(connectionDataModel) {
+ var sourceConnector = this.findOutputConnector(connectionDataModel.source.nodeID, connectionDataModel.source.connectorIndex);
+ var destConnector = this.findInputConnector(connectionDataModel.dest.nodeID, connectionDataModel.dest.connectorIndex);
+
+ sourceConnector.setConnected(true);
+ destConnector.setConnected(true);
+ return new pfCanvas.ConnectionViewModel(connectionDataModel, sourceConnector, destConnector);
+ };
+
+ //
+ // Wrap the connections data-model in a view-model.
+ //
+ this._createConnectionsViewModel = function(connectionsDataModel) {
+ var connectionsViewModel = [];
+
+ if (connectionsDataModel) {
+ for (var i = 0; i < connectionsDataModel.length; ++i) {
+ connectionsViewModel.push(this._createConnectionViewModel(connectionsDataModel[i]));
+ }
+ }
+
+ return connectionsViewModel;
+ };
+
+ // Reference to the underlying data.
+ this.data = chartDataModel;
+
+ // Create a view-model for nodes.
+ this.nodes = createNodesViewModel(this.data.nodes);
+
+ // Create a view-model for nodes.
+ this.nodeActions = createNodeActionsViewModel(this.data.nodeActions);
+
+ // Create a view-model for connections.
+ this.connections = this._createConnectionsViewModel(this.data.connections);
+
+ // Are there any valid connections (used in connection mode) ?
+ this.validConnections = true;
+
+ // Create a view-model for zoom.
+ this.zoom = new pfCanvas.ZoomViewModel();
+
+ // Flag to indicate in connecting mode
+ this.inConnectingMode = false;
+
+ // Flag to indicate whether the chart was just clicked on.
+ this.clickedOnChart = false;
+
+ //
+ // Create a view model for a new connection.
+ //
+ this.createNewConnection = function(startConnector, endConnector) {
+ var connectionsDataModel = this.data.connections;
+ if (!connectionsDataModel) {
+ connectionsDataModel = this.data.connections = [];
+ }
+
+ var connectionsViewModel = this.connections;
+ if (!connectionsViewModel) {
+ connectionsViewModel = this.connections = [];
+ }
+
+ var startNode = startConnector.parentNode();
+ var startConnectorIndex = startNode.outputConnectors.indexOf(startConnector);
+ startConnector = startNode.outputConnectors[startConnectorIndex];
+ var startConnectorType = 'output';
+ if (startConnectorIndex === -1) {
+ startConnectorIndex = startNode.inputConnectors.indexOf(startConnector);
+ startConnectorType = 'input';
+ if (startConnectorIndex === -1) {
+ throw new Error("Failed to find source connector within either inputConnectors or outputConnectors of source node.");
+ }
+ }
+
+ var endNode = endConnector.parentNode();
+ var endConnectorIndex = endNode.inputConnectors.indexOf(endConnector);
+ endConnector = endNode.inputConnectors[endConnectorIndex];
+ var endConnectorType = 'input';
+ if (endConnectorIndex === -1) {
+ endConnectorIndex = endNode.outputConnectors.indexOf(endConnector);
+ endConnectorType = 'output';
+ if (endConnectorIndex === -1) {
+ throw new Error("Failed to find dest connector within inputConnectors or outputConnectors of dest node.");
+ }
+ }
+
+ if (startConnectorType === endConnectorType) {
+ throw new Error("Failed to create connection. Only output to input connections are allowed.");
+ }
+
+ if (startNode === endNode) {
+ throw new Error("Failed to create connection. Cannot link a node with itself.");
+ }
+
+ startNode = {
+ nodeID: startNode.data.id,
+ connectorIndex: startConnectorIndex
+ };
+
+ endNode = {
+ nodeID: endNode.data.id,
+ connectorIndex: endConnectorIndex
+ };
+
+ var connectionDataModel = {
+ source: startConnectorType === 'output' ? startNode : endNode,
+ dest: startConnectorType === 'output' ? endNode : startNode
+ };
+ connectionsDataModel.push(connectionDataModel);
+
+ var outputConnector = startConnectorType === 'output' ? startConnector : endConnector;
+ var inputConnector = startConnectorType === 'output' ? endConnector : startConnector;
+
+ var connectionViewModel = new pfCanvas.ConnectionViewModel(connectionDataModel, outputConnector, inputConnector);
+ connectionsViewModel.push(connectionViewModel);
+
+ startConnector.setConnected(true);
+ endConnector.setConnected(true);
+ };
+
+ //
+ // Add a node to the view model.
+ //
+ this.addNode = function(nodeDataModel) {
+ if (!this.data.nodes) {
+ this.data.nodes = [];
+ }
+
+ //
+ // Update the data model.
+ //
+ this.data.nodes.push(nodeDataModel);
+
+ //
+ // Update the view model.
+ //
+ this.nodes.push(new pfCanvas.NodeViewModel(nodeDataModel));
+ };
+
+ //
+ // Select all nodes and connections in the chart.
+ //
+ this.selectAll = function() {
+ var nodes = this.nodes;
+ for (var i = 0; i < nodes.length; ++i) {
+ var node = nodes[i];
+ node.select();
+ }
+
+ var connections = this.connections;
+ for (i = 0; i < connections.length; ++i) {
+ var connection = connections[i];
+ connection.select();
+ }
+ };
+
+ //
+ // Deselect all nodes and connections in the chart.
+ //
+ this.deselectAll = function() {
+ var nodes = this.nodes;
+ for (var i = 0; i < nodes.length; ++i) {
+ var node = nodes[i];
+ node.deselect();
+ // close any/all open toolbar dialogs
+ node.toolbarDlgOpen = false;
+ }
+
+ var connections = this.connections;
+ for (i = 0; i < connections.length; ++i) {
+ var connection = connections[i];
+ connection.deselect();
+ }
+ };
+
+ //
+ // Mark nodes & connectors as valid/invalid based on source node's
+ // valid connection types
+ //
+ this.updateValidNodesAndConnectors = function(sourceNode) {
+ this.validConnections = false;
+ var validConnectionTypes = sourceNode.validConnectionTypes();
+ for (var i = 0; i < this.nodes.length; ++i) {
+ var node = this.nodes[i];
+ node.setInvalid(true);
+ for (var c = 0; c < node.inputConnectors.length; c++) {
+ var inputConnector = node.inputConnectors[c];
+ inputConnector.setInvalid(validConnectionTypes.indexOf(inputConnector.data.type) === -1);
+ if (!inputConnector.invalid() && node !== sourceNode && !inputConnector.connected()) {
+ node.setInvalid(false);
+ this.validConnections = true;
+ }
+ }
+ }
+ };
+
+ //
+ // Mark nodes & connectors as valid
+ //
+ this.resetValidNodesAndConnectors = function() {
+ for (var i = 0; i < this.nodes.length; ++i) {
+ var node = this.nodes[i];
+ node.setInvalid(false);
+ for (var c = 0; c < node.inputConnectors.length; c++) {
+ var inputConnector = node.inputConnectors[c];
+ inputConnector.setInvalid(false);
+ }
+ }
+ };
+
+ this.removeOutputConnector = function(connectorViewModel) {
+ var parentNode = connectorViewModel.parentNode();
+ parentNode.removeOutputConnector(connectorViewModel.data);
+ };
+
+ //
+ // Update the location of the node and its connectors.
+ //
+ this.updateSelectedNodesLocation = function(deltaX, deltaY) {
+ var selectedNodes = this.getSelectedNodes();
+
+ for (var i = 0; i < selectedNodes.length; ++i) {
+ var node = selectedNodes[i];
+ node.data.x += deltaX;
+ node.data.y += deltaY;
+ }
+ };
+
+ //
+ // Handle mouse click on a particular node.
+ //
+ this.handleNodeClicked = function(node, ctrlKey) {
+ if (ctrlKey) {
+ node.toggleSelected();
+ } else {
+ this.deselectAll();
+ node.select();
+ }
+
+ // Move node to the end of the list so it is rendered after all the other.
+ // This is the way Z-order is done in SVG.
+
+ var nodeIndex = this.nodes.indexOf(node);
+ if (nodeIndex === -1) {
+ throw new Error("Failed to find node in view model!");
+ }
+ this.nodes.splice(nodeIndex, 1);
+ this.nodes.push(node);
+ };
+
+ //
+ // Handle mouse down on a connection.
+ //
+ this.handleConnectionMouseDown = function(connection, ctrlKey) {
+ if (ctrlKey) {
+ connection.toggleSelected();
+ } else {
+ this.deselectAll();
+ connection.select();
+ }
+ };
+
+ //
+ // Delete all nodes and connections that are selected.
+ //
+ this.duplicateSelectedNode = function() {
+ var duplicatedNode = angular.copy(this.getSelectedNodes()[0]);
+ delete duplicatedNode.data.outputConnectors;
+ return duplicatedNode.data;
+ };
+
+ //
+ // Delete all nodes and connections that are selected.
+ //
+ this.deleteSelected = function() {
+ var newNodeViewModels = [];
+ var newNodeDataModels = [];
+
+ var deletedNodeIds = [];
+
+ //
+ /* Sort nodes into:
+ * nodes to keep and
+ * nodes to delete.
+ */
+
+ for (var nodeIndex = 0; nodeIndex < this.nodes.length; ++nodeIndex) {
+ var node = this.nodes[nodeIndex];
+ if (!node.selected()) {
+ // Only retain non-selected nodes.
+ newNodeViewModels.push(node);
+ newNodeDataModels.push(node.data);
+ } else {
+ // Keep track of nodes that were deleted, so their connections can also
+ // be deleted.
+ deletedNodeIds.push(node.data.id);
+ }
+ }
+
+ var newConnectionViewModels = [];
+ var newConnectionDataModels = [];
+
+ //
+ // Remove connections that are selected.
+ // Also remove connections for nodes that have been deleted.
+ //
+ for (var connectionIndex = 0; connectionIndex < this.connections.length; ++connectionIndex) {
+ var connection = this.connections[connectionIndex];
+ if (!connection.selected()) {
+ if (deletedNodeIds.indexOf(connection.data.source.nodeID) === -1
+ && deletedNodeIds.indexOf(connection.data.dest.nodeID) === -1) {
+ //
+ // The nodes this connection is attached to, where not deleted,
+ // so keep the connection.
+ //
+ newConnectionViewModels.push(connection);
+ newConnectionDataModels.push(connection.data);
+ }
+ } else {
+ // connection selected, so it will be deleted (ie. not included in the 'newConnection models)
+ // also delete the connection's source node's output connector (if source node hasn't been deleteed
+ if (deletedNodeIds.indexOf(connection.data.source.nodeID) === -1) {
+ var sourceConnectorViewModel = connection.source;
+ if (sourceConnectorViewModel) {
+ sourceConnectorViewModel._parentNode.removeOutputConnector(sourceConnectorViewModel.data);
+ // also set connected to false on the dest node
+ var destConnectorViewModel = connection.dest;
+ if (destConnectorViewModel) {
+ destConnectorViewModel.setConnected(false);
+ } else {
+ throw new Error("Failed to find dest node of deleted connection!");
+ }
+ } else {
+ throw new Error("Failed to find source node of deleted connection!");
+ }
+ }
+ }
+ }
+
+ //
+ // Update nodes and connections.
+ //
+ this.nodes = newNodeViewModels;
+ this.data.nodes = newNodeDataModels;
+ this.connections = newConnectionViewModels;
+ this.data.connections = newConnectionDataModels;
+ };
+
+ //
+ // Select nodes and connections that fall within the selection rect.
+ //
+ this.applySelectionRect = function(selectionRect) {
+ this.deselectAll();
+
+ for (var i = 0; i < this.nodes.length; ++i) {
+ var node = this.nodes[i];
+ if (node.x() >= selectionRect.x
+ && node.y() >= selectionRect.y
+ && node.x() + node.width() <= selectionRect.x + selectionRect.width
+ && node.y() + node.height() <= selectionRect.y + selectionRect.height) {
+ // Select nodes that are within the selection rect.
+ node.select();
+ }
+ }
+
+ for (i = 0; i < this.connections.length; ++i) {
+ var connection = this.connections[i];
+ if (connection.source.parentNode().selected()
+ && connection.dest.parentNode().selected()) {
+ // Select the connection if both its parent nodes are selected.
+ connection.select();
+ }
+ }
+ };
+
+ //
+ // Get the array of nodes that are currently selected.
+ //
+ this.getSelectedNodes = function() {
+ var selectedNodes = [];
+
+ for (var i = 0; i < this.nodes.length; ++i) {
+ var node = this.nodes[i];
+ if (node.selected()) {
+ selectedNodes.push(node);
+ }
+ }
+
+ return selectedNodes;
+ };
+
+ //
+ // Is only one node selected
+ //
+ this.isOnlyOneNodeSelected = function() {
+ return this.getSelectedNodes().length === 1;
+ };
+
+ //
+ // Are any nodes selected
+ //
+ this.areAnyNodesSelected = function() {
+ return this.getSelectedNodes().length > 0;
+ };
+
+ //
+ // Get the array of connections that are currently selected.
+ //
+ this.getSelectedConnections = function() {
+ var selectedConnections = [];
+
+ for (var i = 0; i < this.connections.length; ++i) {
+ var connection = this.connections[i];
+ if (connection.selected()) {
+ selectedConnections.push(connection);
+ }
+ }
+
+ return selectedConnections;
+ };
+ };
+
+ //
+ // Zoom view model
+ //
+ pfCanvas.ZoomViewModel = function() {
+ this.max = 1; // Max zoom level
+ this.min = parseFloat(".5"); // Min zoom level
+ this.inc = parseFloat(".25"); // Zoom level increment
+ this.level = this.max; // Zoom level
+
+ //
+ // Is max zoom
+ //
+ this.isMax = function() {
+ return (this.level === this.max);
+ };
+
+ //
+ // Is min zoom
+ //
+ this.isMin = function() {
+ return (this.level === this.min);
+ };
+
+ //
+ // Get background image size
+ //
+ this.getBackgroundSize = function() {
+ var size = pfCanvas.defaultBgImageSize * this.getLevel();
+
+ return size;
+ };
+
+ //
+ // Get height to accomodate flow chart
+ //
+ this.getChartHeight = function() {
+ var height = (pfCanvas.defaultHeight / this.min) * this.getLevel();
+
+ return height;
+ };
+
+ //
+ // Get width to accomodate flow chart
+ //
+ this.getChartWidth = function() {
+ var width = (pfCanvas.defaultWidth / this.min) * this.getLevel();
+
+ return width;
+ };
+
+ //
+ // Zoom level
+ //
+ this.getLevel = function() {
+ return this.level;
+ };
+
+ //
+ // Zoom in
+ //
+ this.in = function() {
+ if (!this.isMax()) {
+ this.level = (this.level * 10 + this.inc * 10) / 10;
+ }
+ };
+
+ //
+ // Zoom out
+ //
+ this.out = function() {
+ if (!this.isMin()) {
+ this.level = (this.level * 10 - this.inc * 10) / 10;
+ }
+ };
+ };
+})();
+;(function () {
+ 'use strict';
+
+ // Service used to help with dragging and clicking on elements.
+ angular.module('dragging', ['mouseCapture'])
+ .factory('dragging', ['mouseCapture', Factory]);
+
+ function Factory (mouseCapture) {
+ //
+ // Threshold for dragging.
+ // When the mouse moves by at least this amount dragging starts.
+ //
+ var threshold = 5;
+
+ return {
+
+ //
+ // Called by users of the service to register a mousedown event and start dragging.
+ // Acquires the 'mouse capture' until the mouseup event.
+ //
+ startDrag: function (evt, config) {
+ var dragging = false;
+ var x = evt.pageX;
+ var y = evt.pageY;
+
+ //
+ // Handler for mousemove events while the mouse is 'captured'.
+ //
+ var mouseMove = function (evt) {
+ if (!dragging) {
+ if (Math.abs(evt.pageX - x) > threshold
+ || Math.abs(evt.pageY - y) > threshold) {
+ dragging = true;
+
+ if (config.dragStarted) {
+ config.dragStarted(x, y, evt);
+ }
+
+ if (config.dragging) {
+ // First 'dragging' call to take into account that we have
+ // already moved the mouse by a 'threshold' amount.
+ config.dragging(evt.pageX, evt.pageY, evt);
+ }
+ }
+ } else {
+ if (config.dragging) {
+ config.dragging(evt.pageX, evt.pageY, evt);
+ }
+
+ x = evt.pageX;
+ y = evt.pageY;
+ }
+ };
+
+ //
+ // Handler for when mouse capture is released.
+ //
+ var released = function () {
+ if (dragging) {
+ if (config.dragEnded) {
+ config.dragEnded();
+ }
+ } else {
+ if (config.clicked) {
+ config.clicked();
+ }
+ }
+ };
+
+ //
+ // Handler for mouseup event while the mouse is 'captured'.
+ // Mouseup releases the mouse capture.
+ //
+ var mouseUp = function (evt) {
+ mouseCapture.release();
+
+ evt.stopPropagation();
+ evt.preventDefault();
+ };
+
+ //
+ // Acquire the mouse capture and start handling mouse events.
+ //
+ mouseCapture.acquire(evt, {
+ mouseMove: mouseMove,
+ mouseUp: mouseUp,
+ released: released
+ });
+
+ evt.stopPropagation();
+ evt.preventDefault();
+ }
+ };
+ }
+})();
+
+;(function () {
+ "use strict";
+
+ // Service used to acquire 'mouse capture' then receive dragging events while the mouse is captured.
+ angular.module('mouseCapture', [])
+ .factory('mouseCapture', ['$rootScope', Factory])
+ .directive('mouseCapture', [ComponentDirective]);
+
+ function Factory ($rootScope) {
+ //
+ // Element that the mouse capture applies to, defaults to 'document'
+ // unless the 'mouse-capture' directive is used.
+ //
+ var $element = document;
+
+ //
+ // Set when mouse capture is acquired to an object that contains
+ // handlers for 'mousemove' and 'mouseup' events.
+ //
+ var mouseCaptureConfig = null;
+
+ //
+ // Handler for mousemove events while the mouse is 'captured'.
+ //
+ var mouseMove = function (evt) {
+ if (mouseCaptureConfig && mouseCaptureConfig.mouseMove) {
+ mouseCaptureConfig.mouseMove(evt);
+
+ $rootScope.$digest();
+ }
+ };
+
+ //
+ // Handler for mouseup event while the mouse is 'captured'.
+ //
+ var mouseUp = function (evt) {
+ if (mouseCaptureConfig && mouseCaptureConfig.mouseUp) {
+ mouseCaptureConfig.mouseUp(evt);
+
+ $rootScope.$digest();
+ }
+ };
+
+ return {
+
+ //
+ // Register an element to use as the mouse capture element instead of
+ // the default which is the document.
+ //
+ registerElement: function (element) {
+ $element = element;
+ },
+
+ //
+ // Acquire the 'mouse capture'.
+ // After acquiring the mouse capture mousemove and mouseup events will be
+ // forwarded to callbacks in 'config'.
+ //
+ acquire: function (evt, config) {
+ //
+ // Release any prior mouse capture.
+ //
+ this.release();
+
+ mouseCaptureConfig = config;
+
+ //
+ // In response to the mousedown event register handlers for mousemove and mouseup
+ // during 'mouse capture'.
+ //
+ $element.mousemove(mouseMove);
+ $element.mouseup(mouseUp);
+ },
+
+ //
+ // Release the 'mouse capture'.
+ //
+ release: function () {
+ if (mouseCaptureConfig) {
+ if (mouseCaptureConfig.released) {
+ //
+ // Let the client know that their 'mouse capture' has been released.
+ //
+ mouseCaptureConfig.released();
+ }
+
+ mouseCaptureConfig = null;
+ }
+
+ $element.unbind("mousemove", mouseMove);
+ $element.unbind("mouseup", mouseUp);
+ }
+ };
+ }
+
+ function ComponentDirective () {
+ return {
+ restrict: 'A',
+ controller: ['$scope', '$element', '$attrs', 'mouseCapture',
+ function ($scope, $element, $attrs, mouseCapture) {
+ //
+ // Register the directives element as the mouse capture element.
+ //
+ mouseCapture.registerElement($element);
+ }]
+
+ };
+ }
+})();
+
+;(function () {
+ 'use strict';
+
+ angular.module('patternfly.canvas')
+ .component('nodeToolbar', {
+ templateUrl: 'canvas-view/canvas/node-toolbar.html',
+ bindings: {
+ node: '=',
+ nodeActions: '=',
+ nodeClickHandler: '<',
+ nodeCloseHandler: '<'
+ },
+ controller: ["$scope", function NodeToolbarController ($scope) {
+ var ctrl = this;
+ ctrl.selectedAction = 'none';
+
+ ctrl.actionIconClicked = function (action) {
+ ctrl.selectedAction = action;
+ $scope.$emit('nodeActionClicked', {'action': action, 'node': ctrl.node});
+ if (angular.isFunction(ctrl.nodeClickHandler)) {
+ ctrl.nodeClickHandler(action, ctrl.node);
+ }
+ };
+
+ ctrl.close = function () {
+ ctrl.selectedAction = 'none';
+ $scope.$emit('nodeActionClosed');
+ if (angular.isFunction(ctrl.nodeCloseHandler)) {
+ ctrl.nodeCloseHandler();
+ }
+ };
+ }]
+ });
+})();
+;/**
+ * @ngdoc directive
+ * @name patternfly.canvas.component:pfCanvas
+ * @restrict E
+ *
+ * @description
+ * Component for core operations and rendering of a canvas. Does not work in IE 11 or lower because they do not support
+ * latest svg specification's 'foreignObject' api. Tested in FireFox, Chrome, and MS-Edge.
+ * @param {object} chartDataModel Chart data object which defines the nodes and connections on the canvas
+ *
+ *
.nodes - An array of node objects. For each node's main icon/image you can define either an image url, an icon class, or
+ * fontContent unicode characters. For more information please see the details below:
+ *
+ *
.name - (string) The name of the node
+ *
.x - (number) The canvas x-coordinate to place the node
+ *
.y - (number) The canvas y-coordinate to place the node
+ *
.id - (number) The node id. Used to define connections between nodes.
+ *
.width - (number) The width of the node rectangle
+ *
.image - (string) (Optional) The url of the main node image. Ex: "/img/kubernetes.svg"
+ *
.icon - (string) (Optional) The icon class of the node icon. Ex: "pf pficon-service" Note: Does not work in IE browsers
+ *
.fontSize - (string) (Optional) The size of the main node icon. Used with icon
+ *
.fontFamily - (string) (Optional) The font family of the node icon. Ex: "fontawesome"
+ *
.fontContent - (string) (Optional) The unicode characters of the node icon. Used with fontFamily. Ex: "\uf0c2"
+ *
.backgroundColor - (string) The background color of the node rectangle
+ *
.inputConnectors - An array of input connectors. Connectors appear on the left side of a node's rectangle when in 'connection mode' and are endpoints of connections between nodes.
+ *
+ *
.name - (string) The name of the connector
+ *
.type - (string) A user defined 'type' of input connector. Nodes can only connect to certain 'types' of connectors. Used with validConnectionTypes. Ex: "network".
+ *
.fontFamily - (string) (Optional) The font family of the connector icon. Ex: "PatternFlyIcons-webfont"
+ *
.fontContent - (string) (Optional) The unicode characters of the connector icon. Used with fontFamily. Ex: "\ue621"
+ *
+ *
.validConnectionTypes - An array of valid connector types which the node can connect to. Used with node.type's. Ex: "["network","container"]
+ *
+ *
.nodeActions - An array of actions which appear in a toolbar under a node.
+ *
+ *
.id - (number) The id of the node action
+ *
.name - (string) The name of the node action
+ *
.iconClass - (string) The icon class of the action. Ex: "pf pficon-edit"
+ *
.action - (string) The action identifier, which is passed along with the action event.
+ *
+ *
.actionIconClicked - function that listens for node actions/events when clicking the items within the node toolbar.
+ *
+ *
nodeClickHandler - (function) A function that starts the connection mode when clicking the items within the node toolbar. Passes the following arguments: string (action) and object (node) as parameters.
+ *
$scope.$emit - (function) A function that listens to the action click event via $scope.$on to log the eventText when clicking the items within the node toolbar. Passes the following arguments: string ('nodeActionClicked') and object ({action, node}) as parameters.
+ * Also is used to listen for when the mouse is currently over a node (or not) when an action is selected - in which it then passes the following argument: string ('nodeActionClosed') as parameters.
+ *
+ *
.connections - An array of connections between nodes
+ *
+ *
.source - (object) The source of a connection
+ *
+ *
.nodeID - (number) The id of the source node
+ *
.connectorIndex - (number) The index of the output connector on the source node. Since all nodes have a single output connector, this value is always 0
+ *
+ *
.dest - (object) The destination/target of a connection
+ *
+ *
.nodeID - (number) The id of the destination node
+ *
.connectorIndex - (number) The index of the input connector on the dest/target node to connect. Zero equals the top input connector, increment for subsequent input connectors.
+ *
+ *
+ *
+ * @param {object} chartViewModel (Optional) The chartViewModel is initialized from the chartDataModel and contains additional helper methods such as chartViewModel.isOnlyOneNodeSelected() and
+ * chartViewModel.getSelectedNodes(). You only need to specify a chartViewModel object if you plan on using advanced canvas operations.
+ * @param {boolean} readOnly A flag indicating whether the canvas is in 'read-only' mode. When in 'read-only' mode nodes cannot be moved, selected, or deleted, and the node action toolbar is hidden.
+ * @param {boolean} hideConnectors A flag indicating whether connections should be hidden or shown on the canvas
+ * @example
+
+
+
+
.preTitle - (string) (Optional) A small title above the main tab title
+ *
.title - (string) The main title of the tab
+ *
.subtabs - (Array) An array of sub Tab objects. Supports up to three levels of nested sub tabs
+ *
.items - (Array) An array of items which can be dragged and dropped onto the canvas
+ *
+ *
.name - (string) The item name/title
+ *
.id - (number) The item id
+ *
.image - (string) (Optional) The url of the item's image. Ex: "/img/kubernetes.svg"
+ *
.icon - (string) (Optional) The icon class of the item's icon. Ex: "pf pficon-service"
+ *
+ *
+ * @param {boolean} readOnly (Optional) A flag indicating whether the canvas is in 'read-only' mode. When in 'read-only' mode nodes cannot be moved, selected, or deleted, and the node action toolbar is hidden.
+ * @example
+
+
+
+
.title - the main title of the aggregate status card
+ *
.count - the number count of the main statuses
+ *
.href - the href to navigate to if one clicks on the title or count
+ *
.iconClass - an icon to display to the left of the count
+ *
.iconImage - an image to display to the left of the count
+ *
.notifications - an array of status icons & counts
+ *
+ *
.iconClass - an icon to display to the right of the notification count
+ *
.iconImage - an image to display to the left of the notification count
+ *
.count - the number count of the notification status
+ *
.href - href to navigate to if one clicks on the notification status icon or count
+ *
+ *
+ * When layout='mini', only one notification can be specified:
+ *
+ *
...
+ *
.notification - an object of containing a single notification icon & count
+ *
+ *
.iconClass - an icon to display to the right of the notification count
+ *
.iconImage - an image to display to the left of the notification count
+ *
.count - the number count of the notification status
+ *
.href - href to navigate to if one clicks on the notification status icon or count
+ *
+ *
+ * @param {boolean=} show-top-border Show/hide the top border, true shows top border, false (default) hides top border
+ * @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
+ * @param {string=} spinnerText Text for the card spinner
+ * @param {string=} spinnerCardHeight Height to set for the card when data is loading and spinner is shown
+ * @param {string=} layout Various alternative layouts the aggregate status card may have:
+ *
+ *
'mini' displays a mini aggregate status card. Note: when using 'mini' layout, only one notification can be specified in the status object
+ *
'tall' displays a tall aggregate status card. This equals the depreciated 'alt-layout' param.
+ *
+ * @deprecated {boolean=} alt-layout Display the aggregate status card in a 'alternate tall' layout. false (default) displays normal layout, true displays tall layout
+ *
+ * @description
+ * Component for easily displaying status information
+ *
+ * @example
+
+
+
+
.iconClass - (optional) the icon to show on the bottom left of the footer panel
+ *
.text - (optional) the text to show on the bottom left of the footer panel, to the right of the icon
+ *
.href - (optional) the href link to navigate to when the footer href is clicked
+ *
.callBackFn - (optional) user defined function to call when the footer href is clicked
+ *
+ * *Note: If a href link and a callBackFn are specified, the href link will be called
+ * @param {object=} filter filter configuration properties:
+ *
.iconClass - (optional) the icon to show on the bottom left of the footer panel
+ *
.text - (optional) the text to show on the bottom left of the footer panel, to the right of the icon
+ *
.href - (optional) the href link to navigate to when the footer href is clicked
+ *
.callBackFn - (optional) user defined function to call when the footer href is clicked
+ *
+ * *Note: If a href link and a callBackFn are specified, the href link will be called
+ * @param {object=} filter filter configuration properties:
+ *
.defaultFilter - integer, 0 based index into the filters array
+ *
.callBackFn - user defined function to call when a filter is selected
+ *
.position - (optional) If not specified, or set to 'footer'; the position of the filter dropdown will appear in the
+ * card footer. If set to 'header', the filter dropdown will appear in the card header.
+ *
+ * @description
+ * Component for easily displaying a card with html content
+ *
+ * @example
+
+
+
+
+
+
+ angular.module( 'demo', ['patternfly.charts', 'patternfly.card'] ).controller( 'ChartCtrl', function( $scope, $timeout ) {
+
+ $scope.dataLoading = true;
+
+ $timeout(function () {
+ $scope.dataLoading = false;
+ }, 3000 );
+
+ $scope.footerConfig = {
+ 'iconClass' : 'fa fa-flag',
+ 'text' : 'View All Events',
+ 'callBackFn': function () {
+ alert("Footer Callback Fn Called");
+ }
+ };
+
+ $scope.filterConfigHeader = {
+ 'filters' : [{label:'Last 30 Days', value:'30'},
+ {label:'Last 15 Days', value:'15'},
+ {label:'Today', value:'today'}],
+ 'callBackFn': function (f) {
+ alert("Header Filter Callback Fn Called for '" + f.label + "' value = " + f.value);
+ },
+ 'position' : 'header'
+ };
+
+ $scope.filterConfig = {
+ 'filters' : [{label:'Last 30 Days', value:'30'},
+ {label:'Last 15 Days', value:'15'},
+ {label:'Today', value:'today'}],
+ 'callBackFn': function (f) {
+ alert("Filter Callback Fn Called for '" + f.label + "' value = " + f.value);
+ },
+ 'defaultFilter' : '1'
+ };
+ });
+
+
+ */
+;/**
+ * @ngdoc directive
+ * @name patternfly.card.component:pfCard - Trends
+ * @restrict E
+ *
+ * @param {string} headTitle Title for the card
+ * @param {string=} subTitle Sub-Title for the card
+ * @param {string=} spinnerText Text for the card spinner
+ * @param {string=} spinnerCardHeight Height to set for the card when data is loading and spinner is shown
+ * @param {boolean=} showTopBorder Show/Hide the blue top border. True shows top border, false (default) hides top border
+ * @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
+ * @param {boolean=} showTitlesSeparator Show/Hide the grey line between the title and sub-title.
+ * True (default) shows the line, false hides the line
+ * @param {object=} footer footer configuration properties:
+ *
+ *
.iconClass - (optional) the icon to show on the bottom left of the footer panel
+ *
.text - (optional) the text to show on the bottom left of the footer panel, to the right of the icon
+ *
.href - (optional) the href link to navigate to when the footer href is clicked
+ *
.callBackFn - (optional) user defined function to call when the footer href is clicked
+ *
+ * *Note: If a href link and a callBackFn are specified, the href link will be called
+ * @param {object=} filter filter configuration properties:
+ *
.title - the main title of the info status card
+ *
.href - the href to navigate to if one clicks on the title or count
+ *
.iconClass - an icon to display to the left of the count
+ *
.iconImage - an image to display to the left of Infrastructure
+ *
.info - an array of strings to display, each element in the array is on a new line, accepts HTML content
+ *
+ * @param {boolean=} show-top-border Show/hide the top border, true shows top border, false (default) hides top border
+ * @param {boolean} htmlContent Flag to allow HTML content within the info options
+ * @param {boolean=} showSpinner Show/Hide the spinner for loading state. True shows the spinner, false (default) hides the spinner
+ * @param {string=} spinnerText Text for the card spinner
+ * @param {string=} spinnerCardHeight Height to set for the card when data is loading and spinner is shown
+ *
+ * @description
+ * Component for easily displaying textual information
+ *
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ angular.module( 'patternfly.card' ).controller( 'CardDemoCtrl', function( $scope, $window, $timeout ) {
+ var imagePath = $window.IMAGE_PATH || "img";
+
+ $scope.dataLoading = true;
+
+ $scope.infoStatus = {
+ "title":"TinyCore-local",
+ "href":"#",
+ "iconClass": "fa fa-shield",
+ "info":[
+ "VM Name: aapdemo002",
+ "Host Name: localhost.localdomian",
+ "IP Address: 10.9.62.100",
+ "Power status: on"
+ ]
+ };
+
+ $scope.infoStatus2 = {
+ "title":"TinyCore-local",
+ "iconClass": "fa fa-shield"
+ };
+
+ $scope.infoStatusTitless = {
+ "iconImage": imagePath + "/OpenShift-logo.svg",
+ "info":[
+ "Infastructure: VMware",
+ "Vmware: 1 CPU (1 socket x 1 core), 1024 MB",
+ "12 Snapshots",
+ "Drift History: 1"
+ ]
+ };
+
+ $scope.infoStatusAlt = {};
+
+ $timeout(function () {
+ $scope.dataLoading = false;
+
+ $scope.infoStatus2 = {
+ "title":"TinyCore-local",
+ "href":"#",
+ "iconClass": "fa fa-shield",
+ "info":[
+ "VM Name: aapdemo002",
+ "Host Name: localhost.localdomian",
+ "IP Address: 10.9.62.100",
+ "Power status: on"
+ ]
+ };
+
+ $scope.infoStatusAlt = {
+ "title":"Favorite Things",
+ "iconClass":"fa fa-heart",
+ "info":[
+ "",
+ "",
+ "Tacos"
+ ]
+ };
+ }, 6000 );
+ });
+
+
+
+ */
+
+angular.module( 'patternfly.card' ).component('pfInfoStatusCard', {
+ bindings: {
+ status: '=',
+ showTopBorder: '@?',
+ showSpinner: '',
+ spinnerText: '@?',
+ spinnerCardHeight: '@?',
+ htmlContent: '@?'
+ },
+ templateUrl: 'card/info-status/info-status-card.html',
+ controller: ["$sce", function ($sce) {
+ 'use strict';
+ var ctrl = this;
+ ctrl.$onInit = function () {
+ ctrl.shouldShowTopBorder = (ctrl.showTopBorder === 'true');
+ ctrl.shouldShowHtmlContent = (ctrl.htmlContent === 'true');
+ ctrl.showSpinner = ctrl.showSpinner === true;
+ ctrl.trustAsHtml = function (html) {
+ return $sce.trustAsHtml(html);
+ };
+
+ if (ctrl.spinnerCardHeight) {
+ ctrl.spinnerHeight = {'height': ctrl.spinnerCardHeight};
+ }
+ };
+ }]
+});
+;(function () {
+ 'use strict';
+
+ var patternflyDefaults = patternfly.c3ChartDefaults();
+
+ angular.module('patternfly.charts').constant('c3ChartDefaults', {
+ getDefaultColors: patternflyDefaults.getDefaultColors,
+ getDefaultDonut: patternflyDefaults.getDefaultDonut,
+ getDefaultDonutSize: patternflyDefaults.getDefaultDonutSize,
+ getDefaultDonutColor: patternflyDefaults.getDefaultDonutColors,
+ getDefaultDonutLegend: patternflyDefaults.getDefaultDonutLegend,
+ getDefaultDonutConfig: patternflyDefaults.getDefaultDonutConfig,
+ getDefaultSparklineArea: patternflyDefaults.getDefaultSparklineArea,
+ getDefaultSparklineSize: patternflyDefaults.getDefaultSparklineSize,
+ getDefaultSparklineAxis: patternflyDefaults.getDefaultSparklineAxis,
+ getDefaultSparklineColor: patternflyDefaults.getDefaultColors,
+ getDefaultSparklineLegend: patternflyDefaults.getDefaultSparklineLegend,
+ getDefaultSparklinePoint: patternflyDefaults.getDefaultSparklinePoint,
+ getDefaultSparklineTooltip: patternflyDefaults.getDefaultSparklineTooltip,
+ getDefaultSparklineConfig: patternflyDefaults.getDefaultSparklineConfig,
+ getDefaultLineConfig: patternflyDefaults.getDefaultLineConfig
+ });
+})();
+;/**
+ * @ngdoc directive
+ * @name patternfly.charts.component:pfC3Chart
+ * @restrict E
+ *
+ * @description
+ * Component for wrapping c3 library
+ *
+ * Note: The 'patternfly.charts' module is not a dependency in the default angular 'patternfly' module.
+ * In order to use patternfly charts you must add 'patternfly.charts' as a dependency in your application.
+ *
+ *
+ * @param {string} id the ID of the container that the chart should bind to
+ * @param {expression} config the c3 configuration options for the chart
+ * @param {function (chart))=} getChartCallback the callback user function to be called once the chart is generated, containing the c3 chart object
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+ angular.module( 'patternfly.charts' ).controller( 'ChartCtrl', function( $scope ) {
+ $scope.used = 950;
+ $scope.total = 1000;
+ $scope.available = $scope.total - $scope.used;
+
+ $scope.chartConfig = patternfly.c3ChartDefaults().getDefaultDonutConfig('MHz Used');
+ $scope.chartConfig.data = {
+ type: "donut",
+ columns: [
+ ["Used", $scope.used],
+ ["Available", $scope.total - $scope.used]
+ ],
+ groups: [
+ ["used", "available"]
+ ],
+ order: null
+ };
+
+ $scope.getChart = function (chart) {
+ $scope.chart = chart;
+ };
+
+ $scope.focusUsed = function () {
+ $scope.chart.focus("Used");
+ };
+
+ $scope.updateAvailable = function (val) {
+ $scope.available = $scope.total - $scope.used;
+ };
+
+ $scope.submitform = function (val) {
+ console.log("submitform");
+ $scope.used = val;
+ $scope.updateAvailable();
+ $scope.chartConfig.data.columns = [["Used",$scope.used],["Available",$scope.available]];
+ };
+ });
+
+
+ */
+(function () {
+ 'use strict';
+
+ angular.module('patternfly.charts').component('pfC3Chart', {
+ bindings: {
+ config: '<',
+ getChartCallback: '<'
+ },
+ template: '',
+ controller: ["$timeout", "$attrs", function ($timeout, $attrs) {
+ var ctrl = this, prevConfig;
+
+ // store the chart object
+ var chart;
+ ctrl.generateChart = function () {
+ var chartData;
+
+ // Need to deep watch changes in chart config
+ prevConfig = angular.copy(ctrl.config);
+
+ $timeout(function () {
+ chartData = ctrl.config;
+ if (chartData) {
+ chartData.bindto = '#' + $attrs.id;
+ // only re-generate donut pct chart if it has a threshold object
+ // because it's colors will change based on data and thresholds
+ if (!chart || ($attrs.id.indexOf('donutPctChart') !== -1 && chartData.thresholds)) {
+ chart = c3.generate(chartData);
+ } else {
+ //if chart is already created, then we only need to re-load data
+ chart.load(ctrl.config.data);
+ }
+ if (ctrl.getChartCallback) {
+ ctrl.getChartCallback(chart);
+ }
+ prevConfig = angular.copy(ctrl.config);
+ }
+ });
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on config
+ if (!angular.equals(ctrl.config, prevConfig)) {
+ ctrl.generateChart();
+ }
+ };
+ }]
+ });
+}());
+;angular.module('patternfly.charts').component('pfDonutChart', {
+ bindings: {
+ config: '<',
+ data: '<',
+ chartHeight: ''
+ },
+ templateUrl: 'charts/donut/donut-chart.html',
+ controller: ["pfUtils", "$element", "$timeout", "$log", function (pfUtils, $element, $timeout, $log) {
+ 'use strict';
+ var ctrl = this, prevData;
+
+ ctrl.$onInit = function () {
+ ctrl.donutChartId = 'donutChart';
+ if (ctrl.config.chartId) {
+ ctrl.donutChartId = ctrl.config.chartId + ctrl.donutChartId;
+ }
+
+ ctrl.updateAll();
+ };
+
+ ctrl.getDonutData = function () {
+ return {
+ type: 'donut',
+ columns: ctrl.data,
+ order: null,
+ colors: ctrl.config.colors
+ };
+ };
+
+ ctrl.updateAll = function () {
+ // Need to deep watch changes in chart data
+ prevData = angular.copy(ctrl.data);
+
+ ctrl.config = pfUtils.merge(patternfly.c3ChartDefaults().getDefaultDonutConfig(), ctrl.config);
+ ctrl.config.tooltip = { contents: patternfly.pfDonutTooltipContents };
+ ctrl.config.data = ctrl.getDonutData();
+ ctrl.config.data.onclick = ctrl.config.onClickFn;
+
+ };
+
+ ctrl.getTotal = function () {
+ var total = 0;
+ angular.forEach(ctrl.data, function (value) {
+ angular.forEach(value, function (value) {
+ if (!isNaN(value)) {
+ total += Number(value);
+ }
+ });
+ });
+ return total;
+ };
+
+ ctrl.getCenterLabelText = function () {
+ var centerLabelText;
+
+ // default
+ centerLabelText = { bigText: ctrl.getTotal(),
+ smText: ctrl.config.donut.title};
+
+ if (ctrl.config.centerLabelFn) {
+ centerLabelText.bigText = ctrl.config.centerLabelFn();
+ centerLabelText.smText = '';
+ }
+
+ return centerLabelText;
+ };
+
+ ctrl.setupDonutChartTitle = function () {
+ var donutChartTitle, centerLabelText;
+
+ if (angular.isUndefined(ctrl.chart)) {
+ return;
+ }
+
+ donutChartTitle = d3.select(ctrl.chart.element).select('text.c3-chart-arcs-title');
+ if (!donutChartTitle) {
+ return;
+ }
+
+ centerLabelText = ctrl.getCenterLabelText();
+
+ // Remove any existing title.
+ donutChartTitle.text('');
+ if (centerLabelText.bigText && !centerLabelText.smText) {
+ donutChartTitle.text(centerLabelText.bigText);
+ } else {
+ donutChartTitle.insert('tspan').text(centerLabelText.bigText).classed('donut-title-big-pf', true).attr('dy', 0).attr('x', 0);
+ donutChartTitle.insert('tspan').text(centerLabelText.smText).classed('donut-title-small-pf', true).attr('dy', 20).attr('x', 0);
+ }
+ };
+
+ ctrl.setChart = function (chart) {
+ ctrl.chart = chart;
+ ctrl.setupDonutChartTitle();
+ };
+
+ ctrl.$onChanges = function (changesObj) {
+ if (changesObj.config || changesObj.data) {
+ ctrl.updateAll();
+ }
+ if (changesObj.chartHeight) {
+ ctrl.config.size.height = changesObj.chartHeight.currentValue;
+ }
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on data
+ if (!angular.equals(ctrl.data, prevData)) {
+ ctrl.updateAll();
+ }
+ };
+ }]
+});
+;angular.module('patternfly.charts').component('pfDonutPctChart', {
+ bindings: {
+ config: '<',
+ data: '<',
+ tooltip: '<',
+ chartHeight: '',
+ centerLabel: '',
+ onThresholdChange: '&'
+ },
+ templateUrl: 'charts/donut/donut-pct-chart.html',
+ controller: ["pfUtils", "$scope", function (pfUtils, $scope) {
+ 'use strict';
+ var ctrl = this, prevData;
+ ctrl.$id = $scope.$id;
+
+ ctrl.$onInit = function () {
+ ctrl.donutChartId = 'donutPctChart' + ctrl.$id;
+
+ if (ctrl.config.chartId) {
+ ctrl.donutChartId = ctrl.config.chartId + ctrl.donutChartId;
+ }
+
+ ctrl.updateAll();
+ };
+
+ ctrl.updateAvailable = function () {
+ ctrl.data.available = ctrl.data.total - ctrl.data.used;
+ };
+
+ ctrl.updatePercentage = function () {
+ ctrl.data.percent = Math.round(ctrl.data.used / ctrl.data.total * 100.0);
+ };
+
+ ctrl.getStatusColor = function (used, thresholds) {
+ var threshold = "none";
+ var color = pfUtils.colorPalette.blue;
+
+ if (thresholds) {
+ threshold = "ok";
+ color = pfUtils.colorPalette.green;
+ if (used >= thresholds.error) {
+ threshold = "error";
+ color = pfUtils.colorPalette.red;
+ } else if (used >= thresholds.warning) {
+ threshold = "warning";
+ color = pfUtils.colorPalette.orange;
+ }
+ }
+
+ if (!ctrl.threshold || ctrl.threshold !== threshold) {
+ ctrl.threshold = threshold;
+ ctrl.onThresholdChange({ threshold: ctrl.threshold });
+ }
+
+ return color;
+ };
+
+ ctrl.statusDonutColor = function () {
+ var color, percentUsed;
+
+ color = { pattern: [] };
+ percentUsed = ctrl.data.used / ctrl.data.total * 100.0;
+ color.pattern[0] = ctrl.getStatusColor(percentUsed, ctrl.config.thresholds);
+ color.pattern[1] = pfUtils.colorPalette.black300;
+ return color;
+ };
+
+ ctrl.donutTooltip = function () {
+ return {
+ contents: function (d) {
+ // Default to percent format
+ var tooltipContent =
+ '' +
+ Math.round(d[0].ratio * 100) + '% ' + d[0].name +
+ '';
+ if (ctrl.config.tooltipFn) {
+ tooltipContent =
+ '' +
+ ctrl.config.tooltipFn(d) +
+ '';
+ } else if (ctrl.tooltip === "amount") {
+ tooltipContent =
+ '' +
+ d[0].value + ' ' + ctrl.config.units + ' ' + d[0].name +
+ '';
+ } else if (ctrl.tooltip === "both") {
+ tooltipContent =
+ '
';
+ }
+ return tooltipContent;
+ }
+ };
+ };
+
+ ctrl.getDonutData = function () {
+ return {
+ columns: [
+ ['Used', ctrl.data.used],
+ ['Available', ctrl.data.available]
+ ],
+ type: 'donut',
+ donut: {
+ label: {
+ show: false
+ }
+ },
+ groups: [
+ ['used', 'available']
+ ],
+ order: null
+ };
+ };
+
+ ctrl.getCenterLabelText = function () {
+ var centerLabelText;
+
+ // default to 'used' info.
+ centerLabelText = { bigText: ctrl.data.used,
+ smText: ctrl.config.units + ' Used' };
+
+ if (ctrl.config.centerLabelFn) {
+ centerLabelText.bigText = ctrl.config.centerLabelFn();
+ centerLabelText.smText = '';
+ } else if (ctrl.centerLabel === 'none') {
+ centerLabelText.bigText = '';
+ centerLabelText.smText = '';
+ } else if (ctrl.centerLabel === 'available') {
+ centerLabelText.bigText = ctrl.data.available;
+ centerLabelText.smText = ctrl.config.units + ' Available';
+ } else if (ctrl.centerLabel === 'percent') {
+ centerLabelText.bigText = Math.round(ctrl.data.used / ctrl.data.total * 100.0) + '%';
+ centerLabelText.smText = 'of ' + ctrl.data.total + ' ' + ctrl.config.units;
+ }
+
+ return centerLabelText;
+ };
+
+ ctrl.updateAll = function () {
+ // Need to deep watch changes in chart data
+ prevData = angular.copy(ctrl.data);
+
+ ctrl.config = pfUtils.merge(patternfly.c3ChartDefaults().getDefaultDonutConfig(), ctrl.config);
+ ctrl.updateAvailable();
+ ctrl.updatePercentage();
+ ctrl.config.data = pfUtils.merge(ctrl.config.data, ctrl.getDonutData());
+ ctrl.config.color = ctrl.statusDonutColor(ctrl);
+ ctrl.config.tooltip = ctrl.donutTooltip();
+ ctrl.config.data.onclick = ctrl.config.onClickFn;
+ };
+
+ ctrl.setupDonutChartTitle = function () {
+ var donutChartTitle, centerLabelText;
+
+ if (angular.isUndefined(ctrl.chart)) {
+ return;
+ }
+
+ donutChartTitle = d3.select(ctrl.chart.element).select('text.c3-chart-arcs-title');
+ if (!donutChartTitle) {
+ return;
+ }
+
+ centerLabelText = ctrl.getCenterLabelText();
+
+ // Remove any existing title.
+ donutChartTitle.selectAll('*').remove();
+ if (centerLabelText.bigText && !centerLabelText.smText) {
+ donutChartTitle.text(centerLabelText.bigText);
+ } else {
+ donutChartTitle.insert('tspan').text(centerLabelText.bigText).classed('donut-title-big-pf', true).attr('dy', 0).attr('x', 0);
+ donutChartTitle.insert('tspan').text(centerLabelText.smText).classed('donut-title-small-pf', true).attr('dy', 20).attr('x', 0);
+ }
+ };
+
+ ctrl.setChart = function (chart) {
+ ctrl.chart = chart;
+ ctrl.setupDonutChartTitle();
+ };
+
+ ctrl.$onChanges = function (changesObj) {
+ if (changesObj.config || changesObj.data) {
+ ctrl.updateAll();
+ }
+ if (changesObj.chartHeight) {
+ ctrl.config.size.height = changesObj.chartHeight.currentValue;
+ }
+ if (changesObj.centerLabel) {
+ ctrl.setupDonutChartTitle();
+ }
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on data
+ if (!angular.equals(ctrl.data, prevData)) {
+ ctrl.updateAll();
+ }
+ };
+ }]
+});
+;/**
+ * @ngdoc directive
+ * @name patternfly.charts.component:pfDonutChart
+ * @restrict E
+ *
+ * @description
+ * Component for rendering a donut chart which shows the relationships of a set of values to a whole. When using a
+ * Donut Chart to show the relationship of a set of values to a whole, there should be no more than six
+ * categories.
+ *
+ *
+ * See http://c3js.org/reference.html for a full list of C3 chart options.
+ *
+ * @param {object} config configuration properties for the donut chart:
+ *
+ *
.chartId - the unique id of the donut chart
+ *
.centerLabelFn - user defined function to customize the text of the center label (optional)
+ *
.onClickFn(d,i) - user defined function to handle when donut arc is clicked upon.
+ *
+ *
+ * @param {object} data an array of values for the donut chart.
+ *
+ *
.key - string representing an arc within the donut chart
+ *
.value - number representing the value of the arc
+ *
+ *
+ * @param {number} chartHeight height of the donut chart
+
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ angular.module( 'patternfly.charts' ).controller( 'ChartCtrl', function( $scope, $interval ) {
+ $scope.config = {
+ 'chartId': 'chartOne',
+ 'legend': {"show":true},
+ 'colors' : {
+ 'Cats': '#0088ce', // blue
+ 'Hamsters': '#3f9c35', // green
+ 'Fish': '#ec7a08', // orange
+ 'Dogs': '#cc0000' // red
+ },
+ donut: {
+ title: "Animals"
+ },
+ 'onClickFn': function (d, i) {
+ alert("You clicked on donut arc: " + d.id);
+ }
+ };
+
+ $scope.custConfig = angular.copy($scope.config);
+ $scope.custConfig.chartId = 'chartTwo';
+ $scope.custConfig.legend.position = 'right';
+ $scope.custConfig.centerLabelFn = function () {
+ return "Pets";
+ };
+ $scope.chartHeight = 120;
+
+ $scope.data = [
+ ['Cats', 2],
+ ['Hamsters', 1],
+ ['Fish', 3],
+ ['Dogs', 2]
+ ];
+
+
+ });
+
+
+ */
+;/**
+ * @ngdoc directive
+ * @name patternfly.charts.component:pfDonutPctChart
+ * @restrict E
+ *
+ * @description
+ * Component for rendering a percentage used donut/radial chart. The Used Percentage fill starts at 12 o’clock and
+ * moves clockwise. Whatever portion of the donut not Used, will be represented as Available, and rendered as a
+ * gray fill.
+ * There are three possible fill colors for Used Percentage, dependent on whether or not there are thresholds:
+ *
+ *
When no thresholds exist, or if the used percentage has not surpassed any thresholds, the indicator is blue.
+ *
When the used percentage has surpassed the warning threshold, but not the error threshold, the indicator is orange.
+ *
When the used percentage has surpassed the error threshold, the indicator is is red.
+ *
+ * The directive will calculate the Available Percentage (Total - Used), and display it as a grey radial fill.
+ *
+ *
+ * See http://c3js.org/reference.html for a full list of C3 chart options.
+ *
+ * @param {object} config configuration properties for the donut chart:
+ *
+ *
.chartId - the unique id of the donut chart
+ *
.units - unit label for values, ex: 'MHz','GB', etc..
+ *
.thresholds - warning and error percentage thresholds used to determine the Usage Percentage fill color (optional)
+ *
.tooltipFn(d) - user defined function to customize the tool tip (optional)
+ *
.centerLabelFn - user defined function to customize the text of the center label (optional)
+ *
.onClickFn(d,i) - user defined function to handle when donut arc is clicked upon.
+ *
.orientation - string with possible values: 'left', 'right' (optional) - default: 'center'
+ *
.title - string representing a prefix or title (optional) - default: empty string
+ *
.label - the wording format to display, possible values: 'used', 'available', 'percent', 'none' (optional) - default: 'used'
+ *
.units - unit label for values, ex: 'MHz','GB', etc.. (optional) - default: empty string
+ *
.labelFn - function to customize the text of the external label. This callback returns no data. Updated display data can be accessed through the passed and updated parameter 'data'. (optional) - default: undefined
+ *
+ *
+ *
+ *
+ * @param {object} data the Total and Used values for the donut chart. Available is calculated as Total - Used.
+ *
+ *
.used - number representing the amount used
+ *
.percent - number representing the percentage used
+ *
.total - number representing the total amount
+ *
.dataAvailable - Flag if there is data available - default: true
+ *
+ *
+ * @param {string=} center-label specifies the contents of the donut's center label.
+ * Values:
+ *
+ *
'used' - displays the Used amount in the center label (default)
+ *
'available' - displays the Available amount in the center label
+ *
'percent' - displays the Usage Percent of the Total amount in the center label
+ *
'none' - does not display the center label
+ *
+ *
+ * @param {string=} tooltip specifies the value to show in the tooltip when hovering Used or Available chart segments
+ * Values:
+ *
+ *
'percent' - displays the Used or Available percentage of the Total in the tooltop (default)
+ *
'amount' - displays the Used or Available amount and units in the tooltip
+ *
'both' - displays both the percentage and amount in the tooltip
+ *
+ *
+ * @param {int=} chartHeight height of the donut chart
+ * @param {function (threshold)} on-threshold-change user defined function to handle when thresolds change
+ * 'threshold' Values:
+ *
+ *
+ * @param {boolean=} chartDataAvailable flag if the chart data is available - default: true
+ * @param {number=} height height of the chart (no units) - default: 200
+ * @param {string=} chartTitle title of the chart
+ * @param {boolean=} showLegend flag to show the legend, defaults to true
+ * @param {array=} legendLabels the labels for the legend - defaults: ['< 70%', '70-80%' ,'80-90%', '> 90%']
+ * @param {number=} maxBlockSize the maximum size for blocks in the heatmap. Default: 50, Range: 5 - 50
+ * @param {number=} minBlockSize the minimum size for blocks in the heatmap. Default: 2
+ * @param {number=} blockPadding the padding in pixels between blocks (default: 2)
+ * @param {array=} thresholds the threshold values for the heapmap - defaults: [0.7, 0.8, 0.9]
+ * @param {array=} heatmapColorPattern the colors that correspond to the various threshold values (lowest to hightest value ex: <70& to >90%) - defaults: ['#d4f0fa', '#F9D67A', '#EC7A08', '#CE0000']
+ * @param {function=} clickAction function(block) function to call when a block is clicked on
+ * @param {number=} rangeHoverSize the maximum size for highlighting blocks in the same range. Default: 15
+ * @param {boolean=} rangeOnHover flag to highlight blocks in the same range on hover, defaults to true
+ * @param {array=} rangeTooltips the tooltips for blocks in the same range - defaults: ['< 70%', '70-80%' ,'80-90%', '> 90%']
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ angular.module( 'patternfly.charts' ).controller( 'ChartCtrl', function( $scope) {
+ $scope.data = [
+ {'id': 9,'value': 0.96,'tooltip': 'Node 8 : My OpenShift Provider 96% : 96 Used of 100 Total 4 Available'},
+ {'id': 44, 'value': 0.94, 'tooltip': 'Node 19 : My Kubernetes Provider 94% : 94 Used of 100 Total 6 Available'},
+ {'id': 0, 'value': 0.91, 'tooltip': 'Node 9 : My OpenShift Provider 91% : 91 Used of 100 Total 9 Available'},
+ {'id': 43, 'value': 0.9, 'tooltip': 'Node 18 : My Kubernetes Provider 90% : 90 Used of 100 Total 10 Available'},
+ {'id': 7, 'value': 0.89, 'tooltip': 'Node 12 : My OpenShift Provider 89% : 89 Used of 100 Total 11 Available'},
+ {'id': 41, 'value': 0.82, 'tooltip': 'Node 16 : My Kubernetes Provider 82% : 82 Used of 100 Total 18 Available'},
+ {'id': 21, 'value': 0.81, 'tooltip': 'Node 21 : My OpenShift Provider 81% : 81 Used of 100 Total 19 Available'},
+ {'id': 26, 'value': 0.8, 'tooltip': 'Node 1 : My Kubernetes Provider 80% : 80 Used of 100 Total 20 Available'},
+ {'id': 48, 'value': 0.74, 'tooltip': 'Node 23 : My Kubernetes Provider 74% : 74 Used of 100 Total 26 Available'},
+ {'id': 27, 'value': 0.72, 'tooltip': 'Node 2 : My Kubernetes Provider 72% : 72 Used of 100 Total 28 Available'},
+ {'id': 42, 'value': 0.71, 'tooltip': 'Node 17 : My Kubernetes Provider 71% : 71 Used of 100 Total 29 Available'},
+ {'id': 23, 'value': 0.71, 'tooltip': 'Node 23 : My OpenShift Provider 71% : 71 Used of 100 Total 29 Available'},
+ {'id': 22, 'value': 0.69, 'tooltip': 'Node 22 : My OpenShift Provider 69% : 69 Used of 100 Total 31 Available'},
+ {'id': 2, 'value': 0.66, 'tooltip': 'Node 2 : M8y OpenShift Provider 66% : 66 Used of 100 Total 34 Available'},
+ {'id': 39, 'value': 0.66, 'tooltip': 'Node 14 : My Kubernetes Provider 66% : 66 Used of 100 Total 34 Available'},
+ {'id': 3, 'value': 0.65, 'tooltip': 'Node 39 : My OpenShift Provider 65% : 65 Used of 100 Total 35 Available'},
+ {'id': 29, 'value': 0.65, 'tooltip': 'Node 4 : My Kubernetes Provider 65% : 65 Used of 100 Total 35 Available'},
+ {'id': 32, 'value': 0.56, 'tooltip': 'Node 7 : My Kubernetes Provider 56% : 56 Used of 100 Total 44 Available'},
+ {'id': 13, 'value': 0.56, 'tooltip': 'Node 13 : My OpenShift Provider 56% : 56 Used of 100 Total 44 Available'},
+ {'id': 49, 'value': 0.52, 'tooltip': 'Node 24 : My Kubernetes Provider 52% : 52 Used of 100 Total 48 Available'},
+ {'id': 36, 'value': 0.5, 'tooltip': 'Node 11 : My Kubernetes Provider 50% : 50 Used of 100 Total 50 Available'},
+ {'id': 6, 'value': 0.5, 'tooltip': 'Node 5 : My OpenShift Provider 50% : 50 Used of 100 Total 50 Available'},
+ {'id': 38, 'value': 0.49, 'tooltip': 'Node 13 : My Kubernetes Provider 49% : 49 Used of 100 Total 51 Available'},
+ {'id': 15, 'value': 0.48, 'tooltip': 'Node 15 : My OpenShift Provider 48% : 48 Used of 100 Total 52 Available'},
+ {'id': 30, 'value': 0.48, 'tooltip': 'Node 5 : My Kubernetes Provider 48% : 48 Used of 100 Total 52 Available'},
+ {'id': 11, 'value': 0.47, 'tooltip': 'Node 11 : My OpenShift Provider 47% : 47 Used of 100 Total 53 Available'},
+ {'id': 17, 'value': 0.46, 'tooltip': 'Node 17 : My OpenShift Provider 46% : 46 Used of 100 Total 54 Available'},
+ {'id': 25, 'value': 0.45, 'tooltip': 'Node 0 : My Kubernetes Provider 45% : 45 Used of 100 Total 55 Available'},
+ {'id': 50, 'value': 0.45, 'tooltip': 'Node 25 : My Kubernetes Provider 45% : 45 Used of 100 Total 55 Available'},
+ {'id': 46, 'value': 0.45, 'tooltip': 'Node 21 : My Kubernetes Provider 45% : 45 Used of 100 Total 55 Available'},
+ {'id': 47, 'value': 0.45, 'tooltip': 'Node 22 : My Kubernetes Provider 45% : 45 Used of 100 Total 55 Available'},
+ {'id': 1, 'value': 0.44, 'tooltip': 'Node 1 : My OpenShift Provider 44% : 44 Used of 100 Total 56 Available'},
+ {'id': 31, 'value': 0.44, 'tooltip': 'Node 6 : My Kubernetes Provider 44% : 44 Used of 100 Total 56 Available'},
+ {'id': 37, 'value': 0.44, 'tooltip': 'Node 12 : My Kubernetes Provider 44% : 44 Used of 100 Total 56 Available'},
+ {'id': 24, 'value': 0.44, 'tooltip': 'Node 24 : My OpenShift Provider 44% : 44 Used of 100 Total 56 Available'},
+ {'id': 40, 'value': 0.43, 'tooltip': 'Node 40 : My Kubernetes Provider 43% : 43 Used of 100 Total 57 Available'},
+ {'id': 20, 'value': 0.39, 'tooltip': 'Node 20 : My OpenShift Provider 39% : 39 Used of 100 Total 61 Available'},
+ {'id': 8, 'value': 0.39, 'tooltip': 'Node 8 : My OpenShift Provider 39% : 39 Used of 100 Total 61 Available'},
+ {'id': 5, 'value': 0.38, 'tooltip': 'Node 5 : My OpenShift Provider 38% : 38 Used of 100 Total 62 Available'},
+ {'id': 45, 'value': 0.37, 'tooltip': 'Node 20 : My Kubernetes Provider 37% : 37 Used of 100 Total 63 Available'},
+ {'id': 12, 'value': 0.37, 'tooltip': 'Node 12 : My OpenShift Provider 37% : 37 Used of 100 Total 63 Available'},
+ {'id': 34, 'value': 0.37, 'tooltip': 'Node 9 : My Kubernetes Provider 37% : 37 Used of 100 Total 63 Available'},
+ {'id': 33, 'value': 0.33, 'tooltip': 'Node 8 : My Kubernetes Provider 33% : 33 Used of 100 Total 67 Available'},
+ {'id': 16, 'value': 0.32, 'tooltip': 'Node 16 : My OpenShift Provider 32% : 32 Used of 100 Total 68 Available'},
+ {'id': 10, 'value': 0.29, 'tooltip': 'Node 10 : My OpenShift Provider 28% : 29 Used of 100 Total 71 Available'},
+ {'id': 35, 'value': 0.28, 'tooltip': 'Node 35 : My Kubernetes Provider 28% : 28 Used of 100 Total 72 Available'},
+ {'id': 18, 'value': 0.27, 'tooltip': 'Node 18 : My OpenShift Provider 27% : 27 Used of 100 Total 73 Available'},
+ {'id': 4, 'value': 0.26, 'tooltip': 'Node 4 : My OpenShift Provider 26% : 26 Used of 100 Total 74 Available'},
+ {'id': 19, 'value': 0.25, 'tooltip': 'Node 19 : My OpenShift Provider 25% : 25 Used of 100 Total 75 Available'},
+ {'id': 28, 'value': 0.25, 'tooltip': 'Node 3 : My Kubernetes Provider 25% : 25 Used of 100 Total 75 Available'},
+ {'id': 51, 'value': 0.22, 'tooltip': 'Node 26 : My Kubernetes Provider 22% : 22 Used of 100 Total 78 Available'},
+ {'id': 14, 'value': 0.2, 'tooltip': 'Node 14 : My OpenShift Provider 20% : 20 Used of 100 Total 80 Available'}];
+
+ $scope.dataAvailable = true;
+ $scope.title = 'Utilization - Using Defaults';
+ $scope.titleAlt = 'Utilization - Overriding Defaults';
+ $scope.titleSmall = 'Utilization - Small Blocks';
+ $scope.legendLabels = ['< 60%','70%', '70-80%' ,'80-90%', '> 90%'];
+ $scope.rangeTooltips = ['Memory Utilization < 70% 40 Nodes', 'Memory Utilization 70-80% 4 Nodes', 'Memory Utilization 80-90% 4 Nodes', 'Memory Utilization > 90% 4 Nodes'];
+ $scope.thresholds = [0.6, 0.7, 0.8, 0.9];
+ $scope.heatmapColorPattern = ['#d4f0fa', '#F9D67A', '#EC7A08', '#CE0000', '#f00'];
+
+ $scope.showLegends = true;
+ var clickAction = function (block) {
+ console.log(block);
+ };
+ $scope.clickAction = clickAction;
+ });
+
+
+ */
+angular.module('patternfly.charts').component('pfHeatmap', {
+ bindings: {
+ data: '<',
+ chartDataAvailable: '',
+ height: '',
+ chartTitle: '',
+ showLegend: '',
+ legendLabels: '',
+ maxBlockSize: '@',
+ minBlockSize: '@',
+ blockPadding: '@',
+ thresholds: '',
+ heatmapColorPattern: '',
+ clickAction: '',
+ rangeOnHover: '',
+ rangeHoverSize: '@',
+ rangeTooltips: ''
+ },
+ templateUrl: 'charts/heatmap/heatmap.html',
+ controller: ["$element", "$window", "$compile", "$scope", "$timeout", function ($element, $window, $compile, $scope, $timeout) {
+ 'use strict';
+ var ctrl = this, prevData;
+
+ var containerWidth, containerHeight, blockSize, numberOfRows;
+
+ var thresholdDefaults = [0.7, 0.8, 0.9];
+ var heatmapColorPatternDefaults = ['#d4f0fa', '#F9D67A', '#EC7A08', '#CE0000'];
+ var legendLabelDefaults = ['< 70%', '70-80%' ,'80-90%', '> 90%'];
+ var rangeTooltipDefaults = ['< 70%', '70-80%' ,'80-90%', '> 90%'];
+ var heightDefault = 200;
+
+ var setStyles = function () {
+ ctrl.containerStyles = {
+ height: ctrl.height + 'px',
+ display: ctrl.chartDataAvailable === false ? 'none' : 'block'
+ };
+ };
+
+ var setSizes = function () {
+ var parentContainer = $element[0].querySelector('.heatmap-container');
+ containerWidth = parentContainer.clientWidth;
+ containerHeight = parentContainer.clientHeight;
+ blockSize = determineBlockSize();
+
+ if ((blockSize - ctrl.padding) > ctrl.maxSize) {
+ blockSize = ctrl.padding + ctrl.maxSize;
+
+ // Attempt to square off the area, check if square fits
+ numberOfRows = Math.ceil(Math.sqrt(ctrl.data.length));
+ if (blockSize * numberOfRows > containerWidth ||
+ blockSize * numberOfRows > containerHeight) {
+ numberOfRows = (blockSize === 0) ? 0 : Math.floor(containerHeight / blockSize);
+ }
+ } else if ((blockSize - ctrl.padding) < ctrl.minSize) {
+ blockSize = ctrl.padding + ctrl.minSize;
+
+ // Attempt to square off the area, check if square fits
+ numberOfRows = Math.ceil(Math.sqrt(ctrl.data.length));
+ if (blockSize * numberOfRows > containerWidth ||
+ blockSize * numberOfRows > containerHeight) {
+ numberOfRows = (blockSize === 0) ? 0 : Math.floor(containerHeight / blockSize);
+ }
+ } else {
+ numberOfRows = (blockSize === 0) ? 0 : Math.floor(containerHeight / blockSize);
+ }
+ };
+
+ var determineBlockSize = function () {
+ var x = containerWidth;
+ var y = containerHeight;
+ var n = ctrl.data ? ctrl.data.length : 0;
+ var px = Math.ceil(Math.sqrt(n * x / y));
+ var py = Math.ceil(Math.sqrt(n * y / x));
+ var sx, sy;
+
+ if (Math.floor(px * y / x) * px < n) {
+ sx = y / Math.ceil(px * y / x);
+ } else {
+ sx = x / px;
+ }
+
+ if (Math.floor(py * x / y) * py < n) {
+ sy = x / Math.ceil(x * py / y);
+ } else {
+ sy = y / py;
+ }
+ return Math.max(sx, sy);
+ };
+
+ var redraw = function () {
+ var data = ctrl.data;
+ var color = d3.scale.threshold().domain(ctrl.thresholds).range(ctrl.heatmapColorPattern);
+ var rangeTooltip = d3.scale.threshold().domain(ctrl.thresholds).range(ctrl.rangeTooltips);
+ var blocks;
+ var fillSize = blockSize - ctrl.padding;
+ var highlightBlock = function (block, active) {
+ block.style('fill-opacity', active ? 1 : 0.4);
+ };
+ var highlightBlockColor = function (block, fillColor) {
+ // Get fill color from given block
+ var blockColor = color(block.map(function (d) {
+ return d[0].__data__.value;
+ }));
+ // If given color matches, apply highlight
+ if (blockColor === fillColor) {
+ block.style('fill-opacity', 1);
+ }
+ };
+
+ var svg = window.d3.select(ctrl.thisComponent);
+ svg.selectAll('*').remove();
+ blocks = svg.selectAll('rect').data(data).enter().append('rect');
+ blocks.attr('x', function (d, i) {
+ return Math.floor(i / numberOfRows) * blockSize;
+ }).attr('y', function (d, i) {
+ return i % numberOfRows * blockSize;
+ }).attr('width', fillSize).attr('height', fillSize).style('fill', function (d) {
+ return color(d.value);
+ }).attr('uib-tooltip-html', function (d, i) { //tooltip-html is throwing an exception
+ if (ctrl.rangeOnHover && fillSize <= ctrl.rangeHoverSize) {
+ return '"' + rangeTooltip(d.value) + '"';
+ }
+ return "'" + d.tooltip + "'";
+ }).attr('tooltip-append-to-body', function (d, i) {
+ return true;
+ }).attr('tooltip-animation', function (d, i) {
+ return false;
+ });
+
+ //Adding events
+ blocks.on('mouseover', function () {
+ var fillColor;
+ blocks.call(highlightBlock, false);
+ if (ctrl.rangeOnHover && fillSize <= ctrl.rangeHoverSize) {
+ // Get fill color for current block
+ fillColor = color(d3.select(this).map(function (d) {
+ return d[0].__data__.value;
+ }));
+ // Highlight all blocks matching fill color
+ blocks[0].forEach(function (block) {
+ highlightBlockColor(d3.select(block), fillColor);
+ });
+ } else {
+ d3.select(this).call(highlightBlock, true);
+ }
+ });
+ blocks.on('click', function (d) {
+ if (ctrl.clickAction) {
+ ctrl.clickAction(d);
+ }
+ });
+
+ //Compiles the tooltips
+ angular.forEach(angular.element(blocks), function (block) {
+ var el = angular.element(block);
+ // TODO: get heatmap tooltips to work without using $compile or $scope
+ $compile(el)($scope);
+ });
+
+ svg.on('mouseleave', function () {
+ blocks.call(highlightBlock, true);
+ });
+ };
+
+ var updateDisplay = function () {
+ setStyles();
+
+ if (ctrl.chartDataAvailable !== false && ctrl.data) {
+ ctrl.loadingDone = true;
+
+ // Allow the style change to take effect to update the container size
+ $timeout(function () {
+ setSizes();
+ redraw();
+ });
+ }
+ };
+
+ var handleDataUpdate = function () {
+ prevData = angular.copy(ctrl.data);
+ updateDisplay();
+ };
+
+ var debounceResize = _.debounce(function () {
+ updateDisplay();
+ }, 250, 500);
+
+ var updateConfig = function () {
+ //Allow overriding of defaults
+ if (ctrl.maxBlockSize === undefined || isNaN(ctrl.maxBlockSize)) {
+ ctrl.maxSize = 64;
+ } else {
+ ctrl.maxSize = parseInt(ctrl.maxBlockSize);
+ if (ctrl.maxSize < 5) {
+ ctrl.maxSize = 5;
+ } else if (ctrl.maxSize > 50) {
+ ctrl.maxSize = 50;
+ }
+ }
+
+ if (ctrl.minBlockSize === undefined || isNaN(ctrl.minBlockSize)) {
+ ctrl.minSize = 2;
+ } else {
+ ctrl.minSize = parseInt(ctrl.minBlockSize);
+ }
+
+ if (ctrl.blockPadding === undefined || isNaN(ctrl.blockPadding)) {
+ ctrl.padding = 2;
+ } else {
+ ctrl.padding = parseInt(ctrl.blockPadding);
+ }
+
+ if (ctrl.rangeHoverSize === undefined || isNaN(ctrl.rangeHoverSize)) {
+ ctrl.rangeHoverSize = 15;
+ } else {
+ ctrl.rangeHoverSize = parseInt(ctrl.rangeHoverSize);
+ }
+
+ ctrl.rangeOnHover = (ctrl.rangeOnHover === undefined || ctrl.rangeOnHover) ? true : false;
+
+ if (!ctrl.rangeTooltips) {
+ ctrl.rangeTooltips = rangeTooltipDefaults;
+ }
+
+ if (!ctrl.thresholds) {
+ ctrl.thresholds = thresholdDefaults;
+ }
+
+ if (!ctrl.heatmapColorPattern) {
+ ctrl.heatmapColorPattern = heatmapColorPatternDefaults;
+ }
+
+ if (!ctrl.legendLabels) {
+ ctrl.legendLabels = legendLabelDefaults;
+ }
+ ctrl.height = ctrl.height || heightDefault;
+ ctrl.showLegend = ctrl.showLegend || (ctrl.showLegend === undefined);
+ };
+
+ ctrl.loadingDone = false;
+
+ ctrl.$onChanges = function (changesObj) {
+ if (changesObj.chartDataAvailable && !changesObj.chartDataAvailable.isFirstChange()) {
+ setStyles();
+ } else if (!changesObj.data) {
+ updateConfig();
+ updateDisplay();
+ }
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on chartData and config
+ if (!angular.equals(ctrl.data, prevData)) {
+ handleDataUpdate();
+ }
+ };
+
+ ctrl.$postLink = function () {
+ ctrl.thisComponent = $element[0].querySelector('.heatmap-pf-svg');
+ updateConfig();
+ handleDataUpdate();
+
+ angular.element($window).on('resize', debounceResize);
+ };
+
+ ctrl.$onDestroy = function () {
+ angular.element($window).off('resize', debounceResize);
+ };
+ }]
+});
+;/**
+ * @ngdoc directive
+ * @name patternfly.charts.component:pfLineChart
+ * @restrict E
+ *
+ * @description
+ * Component for rendering a line chart.
+ *
+ * See http://c3js.org/reference.html for a full list of C3 chart options.
+ *
+ * @param {object} config configuration settings for the line chart:
+ *
+ *
.chartId - the ID of the container that the chart should bind to
+ *
.units - unit label for values, ex: 'MHz','GB', etc..
+ *
.tooltipFn - (optional) override the tooltip contents generation functions. Should take a data point and
+ * return HTML markup for the tooltip contents. Setting this overrides the tooltipType value.
+ *
.area - (optional) overrides the default Area properties of the C3 chart
+ *
.size - (optional) overrides the default Size properties of the C3 chart
+ *
.axis - (optional) overrides the default Axis properties of the C3 chart
+ *
.color - (optional) overrides the default Color properties of the C3 chart
+ *
.legend - (optional) overrides the default Legend properties of the C3 chart
+ *
.point - (optional) overrides the default Point properties of the C3 chart
+ *
+ *
+ * @param {object} chartData the data to be shown as an area chart
+ * First and second Array elements, xData and yData, must exist, next data arrays are optional.
+ *
+ *
.xData - Array, X values for the data points, first element must be the name of the data
+ *
.yData - Array, Y Values for the data points, first element must be the name of the data
+ *
.yData1 - Array, Y Values for the data points, first element must be the name of the data
+ *
.[...] - Array, Y Values for the data points, first element must be the name of the data
+ *
+ *
+ * @param {boolean=} showXAxis override config settings for showing the X Axis
+ * @param {boolean=} showYAxis override config settings for showing the Y Axis
+ * @param {boolean=} setAreaChart override config settings for showing area type chart
+
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ angular.module( 'patternfly.charts' ).controller( 'ChartCtrl', function( $scope, pfUtils ) {
+
+ $scope.config = {
+ chartId: 'exampleLine',
+ grid: {y: {show: false}},
+ point: {r: 1},
+ color: {pattern: [pfUtils.colorPalette.blue, pfUtils.colorPalette.green]}
+ };
+
+ var today = new Date();
+ var dates = ['dates'];
+ for (var d = 20 - 1; d >= 0; d--) {
+ dates.push(new Date(today.getTime() - (d * 24 * 60 * 60 * 1000)));
+ }
+
+ $scope.data = {
+ dataAvailable: true,
+ xData: dates,
+ yData0: ['Created', 12, 10, 10, 62, 17, 10, 15, 13, 17, 10, 12, 10, 10, 12, 17, 16, 15, 13, 17, 10],
+ yData1: ['Deleted', 10, 17, 76, 14, 10, 10, 10, 10, 10, 10, 10, 17, 17, 14, 10, 10, 10, 10, 10, 10]
+ };
+
+ $scope.custShowXAxis = false;
+ $scope.custShowYAxis = false;
+ $scope.custAreaChart = false;
+
+ $scope.addDataPoint = function () {
+ $scope.data.xData.push(new Date($scope.data.xData[$scope.data.xData.length - 1].getTime() + (24 * 60 * 60 * 1000)));
+ $scope.data.yData0.push(Math.round(Math.random() * 100));
+ $scope.data.yData1.push(Math.round(Math.random() * 100));
+ };
+
+ $scope.resetData = function () {
+ $scope.data = {
+ xData: dates,
+ yData0: ['Created', 12, 10, 10, 62],
+ yData1: ['Deleted', 10, 17, 76, 14]
+ };
+ };
+ });
+
+
+ */
+angular.module('patternfly.charts').component('pfLineChart', {
+ bindings: {
+ config: '<',
+ chartData: '<',
+ showXAxis: '',
+ showYAxis: '',
+ setAreaChart: ''
+ },
+ templateUrl: 'charts/line/line-chart.html',
+ controller: ["pfUtils", function (pfUtils) {
+ 'use strict';
+ var ctrl = this, prevChartData;
+
+ ctrl.updateAll = function () {
+ // Need to deep watch changes in chart data
+ prevChartData = angular.copy(ctrl.chartData);
+
+ // Create an ID for the chart based on the chartId in the config if given
+ if (ctrl.lineChartId === undefined) {
+ ctrl.lineChartId = 'lineChart';
+ if (ctrl.config.chartId) {
+ ctrl.lineChartId = ctrl.config.chartId + ctrl.lineChartId;
+ }
+ }
+
+ /*
+ * Setup Axis options. Default is to not show either axis. This can be overridden in two ways:
+ * 1) in the config, setting showAxis to true will show both axes
+ * 2) in the attributes showXAxis and showYAxis will override the config if set
+ *
+ * By default only line and the tick marks are shown, no labels. This is a line and should be used
+ * only to show a brief idea of trending. This can be overridden by setting the config.axis options per C3
+ */
+
+ if (ctrl.showXAxis === undefined) {
+ ctrl.showXAxis = (ctrl.config.showAxis !== undefined) && ctrl.config.showAxis;
+ }
+
+ if (ctrl.showYAxis === undefined) {
+ ctrl.showYAxis = (ctrl.config.showAxis !== undefined) && ctrl.config.showAxis;
+ }
+
+ ctrl.defaultConfig = patternfly.c3ChartDefaults().getDefaultLineConfig();
+ ctrl.defaultConfig.axis = {
+ x: {
+ show: ctrl.showXAxis === true,
+ type: 'timeseries',
+ tick: {
+ format: function () {
+ return '';
+ }
+ }
+ },
+ y: {
+ show: ctrl.showYAxis === true,
+ tick: {
+ format: function () {
+ return '';
+ }
+ }
+ }
+ };
+
+ /*
+ * Setup Chart type option. Default is Line Chart.
+ */
+ if (ctrl.setAreaChart === undefined) {
+ ctrl.setAreaChart = (ctrl.config.setAreaChart !== undefined) && ctrl.config.setAreaChart;
+ }
+
+ // Convert the given data to C3 chart format
+ ctrl.config.data = ctrl.getLineData(ctrl.chartData);
+
+ // Override defaults with callers specifications
+ ctrl.defaultConfig = pfUtils.merge(ctrl.defaultConfig, ctrl.config);
+
+ // Will trigger c3 chart generation
+ ctrl.chartConfig = pfUtils.merge(ctrl.defaultConfig, ctrl.config);
+ };
+
+ /*
+ * Convert the config data to C3 Data
+ */
+ ctrl.getLineData = function (chartData) {
+ var lineData = {
+ type: ctrl.setAreaChart ? "area" : "line"
+ };
+
+ if (chartData && chartData.dataAvailable !== false && chartData.xData) {
+ lineData.x = chartData.xData[0];
+ // Convert the chartData dictionary into a C3 columns data arrays
+ lineData.columns = Object.keys(chartData).map(function (key) {
+ return chartData[key];
+ });
+ }
+
+ return lineData;
+ };
+
+ ctrl.$onChanges = function (changesObj) {
+ ctrl.updateAll();
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on chartData
+ if (!angular.equals(ctrl.chartData, prevChartData)) {
+ ctrl.updateAll();
+ }
+ };
+ }]
+});
+;/**
+ * @ngdoc directive
+ * @name patternfly.charts.component:pfSparklineChart
+ * @restrict E
+ *
+ * @description
+ * Component for rendering a sparkline chart.
+ *
+ * See http://c3js.org/reference.html for a full list of C3 chart options.
+ *
+ * @param {object} config configuration settings for the sparkline chart:
+ *
+ *
.chartId - the ID of the container that the chart should bind to
+ *
.units - unit label for values, ex: 'MHz','GB', etc..
+ *
.tooltipType - (optional) set the type of tooltip, valid values:
+ *
+ *
'default' - show the data point value and the data point name.
+ *
'usagePerDay' - show the date, percent used, and used value for the data point.
+ *
'valuePerDay' - show the date and value for the data point.
+ *
'percentage' - show the current data point as a percentage.
+ *
+ *
.tooltipFn - (optional) override the tooltip contents generation functions. Should take a data point and
+ * return HTML markup for the tooltip contents. Setting this overrides the tooltipType value.
+ *
.area - (optional) overrides the default Area properties of the C3 chart
+ *
.size - (optional) overrides the default Size properties of the C3 chart
+ *
.axis - (optional) overrides the default Axis properties of the C3 chart
+ *
.color - (optional) overrides the default Color properties of the C3 chart
+ *
.legend - (optional) overrides the default Legend properties of the C3 chart
+ *
.point - (optional) overrides the default Point properties of the C3 chart
+ *
+ *
+ * @param {object} chartData the data to be shown as an area chart
+ *
+ *
.xData - Array, X values for the data points, first element must be the name of the data
+ *
.yData - Array, Y Values for the data points, first element must be the name of the data
+ *
.total - (optional) The Total amount, used when determining percentages
+ *
.dataAvailable - Flag if there is data available - default: true
+ *
+ *
+ * @param {int=} chartHeight height of the sparkline chart
+ * @param {boolean=} showXAxis override config settings for showing the X Axis
+ * @param {boolean=} showYAxis override config settings for showing the Y Axis
+
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ angular.module( 'patternfly.charts' ).controller( 'ChartCtrl', function( $scope ) {
+
+ $scope.config = {
+ chartId: 'exampleSparkline',
+ tooltipType: 'default'
+ };
+
+ var today = new Date();
+ var dates = ['dates'];
+ for (var d = 20 - 1; d >= 0; d--) {
+ dates.push(new Date(today.getTime() - (d * 24 * 60 * 60 * 1000)));
+ }
+
+ $scope.data = {
+ dataAvailable: true,
+ total: 100,
+ xData: dates,
+ yData: ['used', 10, 20, 30, 20, 30, 10, 14, 20, 25, 68, 54, 56, 78, 56, 67, 88, 76, 65, 87, 76]
+ };
+
+ $scope.custShowXAxis = false;
+ $scope.custShowYAxis = false;
+ $scope.custChartHeight = 60;
+
+ $scope.addDataPoint = function () {
+ $scope.data.xData.push(new Date($scope.data.xData[$scope.data.xData.length - 1].getTime() + (24 * 60 * 60 * 1000)));
+ $scope.data.yData.push(Math.round(Math.random() * 100));
+ };
+ });
+
+
+ */
+angular.module('patternfly.charts').component('pfSparklineChart', {
+ bindings: {
+ config: '<',
+ chartData: '<',
+ chartHeight: '',
+ showXAxis: '',
+ showYAxis: ''
+ },
+ templateUrl: 'charts/sparkline/sparkline-chart.html',
+ controller: ["pfUtils", function (pfUtils) {
+ 'use strict';
+ var ctrl = this, prevChartData;
+
+ ctrl.updateAll = function () {
+ // Need to deep watch changes in chart data
+ prevChartData = angular.copy(ctrl.chartData);
+
+ // Create an ID for the chart based on the chartId in the config if given
+ if (ctrl.sparklineChartId === undefined) {
+ ctrl.sparklineChartId = 'sparklineChart';
+ if (ctrl.config.chartId) {
+ ctrl.sparklineChartId = ctrl.config.chartId + ctrl.sparklineChartId;
+ }
+ }
+
+ /*
+ * Setup Axis options. Default is to not show either axis. This can be overridden in two ways:
+ * 1) in the config, setting showAxis to true will show both axes
+ * 2) in the attributes showXAxis and showYAxis will override the config if set
+ *
+ * By default only line and the tick marks are shown, no labels. This is a sparkline and should be used
+ * only to show a brief idea of trending. This can be overridden by setting the config.axis options per C3
+ */
+
+ if (ctrl.showXAxis === undefined) {
+ ctrl.showXAxis = (ctrl.config.showAxis !== undefined) && ctrl.config.showAxis;
+ }
+
+ if (ctrl.showYAxis === undefined) {
+ ctrl.showYAxis = (ctrl.config.showAxis !== undefined) && ctrl.config.showAxis;
+ }
+
+ ctrl.defaultConfig = patternfly.c3ChartDefaults().getDefaultSparklineConfig();
+ ctrl.defaultConfig.axis = {
+ x: {
+ show: ctrl.showXAxis === true,
+ type: 'timeseries',
+ tick: {
+ format: function () {
+ return '';
+ }
+ }
+ },
+ y: {
+ show: ctrl.showYAxis === true,
+ tick: {
+ format: function () {
+ return '';
+ }
+ }
+ }
+ };
+
+ // Setup the default configuration
+ ctrl.defaultConfig.tooltip = ctrl.sparklineTooltip();
+ if (ctrl.chartHeight) {
+ ctrl.defaultConfig.size.height = ctrl.chartHeight;
+ }
+ ctrl.defaultConfig.units = '';
+
+ // Convert the given data to C3 chart format
+ ctrl.config.data = pfUtils.merge(ctrl.config.data, ctrl.getSparklineData(ctrl.chartData));
+
+ // Override defaults with callers specifications
+ ctrl.chartConfig = pfUtils.merge(ctrl.defaultConfig, ctrl.config);
+ };
+
+ /*
+ * Convert the config data to C3 Data
+ */
+ ctrl.getSparklineData = function (chartData) {
+ var sparklineData = {
+ type: 'area'
+ };
+
+ if (chartData && chartData.dataAvailable !== false && chartData.xData && chartData.yData) {
+ sparklineData.x = chartData.xData[0];
+ sparklineData.columns = [
+ chartData.xData,
+ chartData.yData
+ ];
+ }
+
+ return sparklineData;
+ };
+
+ ctrl.getTooltipTableHTML = function (tipRows) {
+ return '
';
+ break;
+ default:
+ tipRows = patternfly.c3ChartDefaults().getDefaultSparklineTooltip().contents(d);
+ }
+ }
+ return ctrl.getTooltipTableHTML(tipRows);
+ },
+ position: function (data, width, height, element) {
+ var center;
+ var top;
+ var chartBox;
+ var graphOffsetX;
+ var x;
+
+ try {
+ center = parseInt(element.getAttribute('x'));
+ top = parseInt(element.getAttribute('y'));
+ chartBox = document.querySelector('#' + ctrl.sparklineChartId).getBoundingClientRect();
+ graphOffsetX = document.querySelector('#' + ctrl.sparklineChartId + ' g.c3-axis-y').getBoundingClientRect().right;
+ x = Math.max(0, center + graphOffsetX - chartBox.left - Math.floor(width / 2));
+
+ return {
+ top: top - height,
+ left: Math.min(x, chartBox.width - width)
+ };
+ } catch (e) {
+ }
+ }
+ };
+ };
+
+ ctrl.$onChanges = function (changesObj) {
+ ctrl.updateAll();
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on chartData
+ if (!angular.equals(ctrl.chartData, prevChartData)) {
+ ctrl.updateAll();
+ }
+ };
+ }]
+});
+;/**
+ * @ngdoc directive
+ * @name patternfly.charts.component:pfTopologyMap
+ * @restrict E
+ *
+ * @param {array} nodes array containing objects representing graph nodes. Each node has these attributes. Only node id is mandatory parameter.
+ *
+ *
id: unique node identifier
+ *
title: node title
+ *
size: node radius; default value is 17
+ *
fileicon: this attribute specifies path to image file. eg: '/some/path/to/image/image.png'. File icon has higher priority than fonticon.
+ *
fonticon: css class of node icon eg: 'fa fa-info' File icon has higher priority than fonticon.
+ *
fill: string containing color code (basic, RGB, HEX) of node background.
+ *
borderColor: string containing color code (basic, RGB, HEX) of node border.
+ *
iconColor: string containing color code (basic, RGB, HEX) of node icon, if iconType is fonticon.
+ *
opacity: number from 〈0,1〉range, representing node opacity
+ *
utilization: number from〈0,100〉range, representing node utilization percentage
+ *
+ * @param {array} edges array of objects. Each object represents one edge between two nodes. Source and target are mandatory attributes.
+ *
+ *
source: id of source node
+ *
target: id of target node
+ *
lineStyle: stroke style of edge; currently only 'dashed' is avaliable
+ *
title: label of edge
+ *
+ *
+ * @param {boolean=} show-node-labels show/hide all node tooltips
+ *
+ * @param {boolean=} show-edge-labels show/hode all edge tooltips
+ *
+ * @param {object=} tooltip-style object used for tooltip styling. This is an optional parameter.
+ *
+ *
size: text size in px
+ *
font: font name. eg: 'Arial'
+ *
textColor: string containing color code (basic, RGB, HEX) of title text.
+ *
background: string containing color code (basic, RGB, HEX) of title background
+ *
+ * @param {function (node) =} select-node function that return selected(clicked) node from graph
+ * @param {function (array) =} multi-select-nodes function that returns array of selected nodes. Multiple nodes are selected while holding the ctrl/shift key and clicking
+ * @param {function (edge) =} select-edge function that return selected(clicked) edge from graph
+ * @param {function (array) =} multi-select-edges function that returns array of selected edges. Multiple edges are selected while holding the ctrl/shift key and clicking
+ *
+ * @description
+ * Component for rendering topology chart on Canvas element. This is just a simple component. It has no searching/filtering or other methods. Will only render given data and return data about selected objects.
+ * Component also supports semantic zooming. Only distance between nodes is growing/shrinking, but node size remains the same. Canvas will zoom around the mouse cursor.
+ * @example
+
+
+
.status - optional status of the node (can be used to differentiate the circle color)
+ *
.kind - the kind of node - this is a general key that needs to be unique for grouping the nodes Filtering and styles use this value as well to correctly select the nodes.
+ *
+ *
+ * @param {object} relations the object containing all of the node relationships:
+ *
+ *
.source - the key of the source node
+ *
.target - the key of the target node
+ *
+ *
+ * @param {object} icons The different icons to be used in the node representations
+ * @param {object} selection The item to be selected
+ * @param {object} force Optional. A D3 force layout to use instead of creating one by default. The force layout size will be updated, and layout will be started as appropriate.
+ * @param {object} nodes The node configuration for the various types of nodes
+ * @param {string} searchText Search text which is watched for changes and highlights the nodes matching the search text
+ * @param {object} kinds The different kinds of nodes represented in the topology chart
+ * @param {function (vertices, added) } chartRendered The argument will be D3 selection of elements that correspond to items. Each item has its data set to one of the items. The default implementation of this event sets the title from Kubernetes metadata and tweaks the look of for certain statuses. Use event.preventDefault() to prevent this default behavior.
+ * @param {boolean} itemSelected A function that is dispatched when an item is selected (along with the node data associated with the function
+ * @param {boolean} showLabels A watched boolean that determines whether or not lables should be displayed beneath the nodes
+ * @param {function (node) } tooltipFunction A passed in tooltip function which can be used to overwrite the default tooltip behavior
+ *
+ * @example
+
+
+
+ * See http://c3js.org/reference.html for a full list of C3 chart options.
+ * See also: {@link patternfly.charts.component:pfSparklineChart}
+ *
+ * @param {object} config configuration settings for the trends chart:
+ *
+ *
.chartId - the unique id of this trends chart
+ *
.title - (optional) title of the Trends chart
+ *
.layout - (optional) the layout and sizes of titles and chart. Values are 'large' (default), 'small', 'compact', and 'inline'
+ *
.compactLabelPosition - (optional) the trend label positioning when the layout value is 'compact'. Values are 'left' (default) or 'right'
+ *
.trendLabel - (optional) the trend label used in the 'inline' layout
+ *
.timeFrame - (optional) the time frame for the data in the pfSparklineChart, ex: 'Last 30 Days'
+ *
.units - unit label for values, ex: 'MHz','GB', etc..
+ *
.valueType - (optional) the format of the latest data point which is shown in the title. Values are 'actual'(default) or 'percentage'
+ *
+ *
+ * @param {object} chartData the data to be shown in the sparkline charts
+ *
+ *
.total - number representing the total amount
+ *
.xData - Array, X values for the data points, first element must be the name of the data
+ *
.yData - Array, Y Values for the data points, first element must be the name of the data
+ *
.dataAvailable - Flag if there is data available - default: true
+ *
+ *
+ * @param {int=} chartHeight height of the sparkline chart
+ * @param {boolean=} showXAxis override sparkline config settings for showing the X Axis
+ * @param {boolean=} showYAxis override sparkline config settings for showing the Y Axis
+ * @example
+
+
+
When no thresholds exist, or if the used percentage has not surpassed any thresholds, the indicator is blue.
+ *
When the used percentage has surpassed the warning threshold, but not the error threshold, the indicator is orange.
+ *
When the used percentage has surpassed the error threshold, the indicator is is red.
+ *
+ *
+ * @param {object} chartData the data to be shown in the utilization bar chart
+ *
+ *
.used - number representing the amount used
+ *
.total - number representing the total amount
+ *
.dataAvailable - Flag if there is data available - default: true
+ *
+ *
+ * @param {object=} chart-title The title displayed on the left-hand side of the chart
+ * @param {object=} chart-footer The label displayed on the right-hand side of the chart. If chart-footer is not
+ * specified, the automatic footer-label-format will be used.
+ * @param {object=} layout Various alternative layouts the utilization bar chart may have:
+ *
+ *
.type - The type of layout to use. Valid values are 'regular' (default) displays the standard chart layout,
+ * and 'inline' displays a smaller, inline layout.
+ *
.titleLabelWidth - Width of the left-hand title label when using 'inline' layout. Example values are "120px", "20%", "10em", etc..
+ *
.footerLabelWidth - Width of the right-hand used label when using 'inline' layout. Example values are "120px", "20%", "10em", etc..
+ *
+ * @param {string=} footer-label-format The auto-format of the label on the right side of the bar chart when chart-footer
+ * has not been specified. Values may be:
+ *
+ *
'actual' - (default) displays the standard label of '(n) of (m) (units) Used'.
+ *
'percent' - displays a percentage label of '(n)% Used'.
+ *
+ * @param {object=} units to be displayed on the chart. Examples: "GB", "MHz", "I/Ops", etc...
+ * @param {string=} threshold-error The percentage used, when reached, denotes an error. Valid values are 1-100. When the error threshold
+ * has been reached, the used donut arc will be red.
+ * @param {string=} threshold-warning The percentage usage, when reached, denotes a warning. Valid values are 1-100. When the warning threshold
+ * has been reached, the used donut arc will be orange.
+ * @param {function(items)} avaliableTooltipFunction A passed in tooltip function which can be used to overwrite the default available tooltip behavior
+ * @param {function(items)} usedTooltipFunction A passed in tooltip function which can be used to overwrite the default usedtooltip behavior
+ *
+ * @example
+
+
+
';
+ };
+ });
+
+
+*/
+
+angular.module('patternfly.charts').component('pfUtilizationBarChart', {
+ bindings: {
+ chartData: '=',
+ chartTitle: '=',
+ chartFooter: '=',
+ units: '=',
+ thresholdError: '=?',
+ thresholdWarning: '=?',
+ footerLabelFormat: '@?',
+ layout: '=?',
+ usedTooltipFunction: '&?',
+ availableTooltipFunction: '&?'
+ },
+
+ templateUrl: 'charts/utilization-bar/utilization-bar-chart.html',
+ controller: ["$timeout", function ($timeout) {
+ 'use strict';
+ var ctrl = this, prevChartData, prevLayout;
+
+ ctrl.updateAll = function () {
+ // Need to deep watch changes
+ prevChartData = angular.copy(ctrl.chartData);
+ prevLayout = angular.copy(ctrl.layout);
+
+ if (!ctrl.chartData) {
+ return;
+ }
+
+ //Calculate the percentage used
+ if (!isNaN(ctrl.chartData.used) && !isNaN(ctrl.chartData.total) && (ctrl.chartData.total > 0)) {
+ ctrl.chartData.percentageUsed = Math.round(100 * (ctrl.chartData.used / ctrl.chartData.total));
+ } else {
+ ctrl.chartData.percentageUsed = 0;
+ }
+
+ if (ctrl.thresholdError || ctrl.thresholdWarning) {
+ ctrl.isError = (ctrl.chartData.percentageUsed >= ctrl.thresholdError);
+ ctrl.isWarn = (ctrl.chartData.percentageUsed >= ctrl.thresholdWarning &&
+ ctrl.chartData.percentageUsed < ctrl.thresholdError);
+ ctrl.isOk = (ctrl.chartData.percentageUsed < ctrl.thresholdWarning);
+ }
+
+ //Animate in the chart load.
+ ctrl.animate = true;
+ $timeout(function () {
+ ctrl.animate = false;
+ }, 0);
+ };
+
+ ctrl.$onChanges = function (changesObj) {
+ ctrl.updateAll();
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on chartData and layout
+ if (!angular.equals(ctrl.chartData, prevChartData) || !angular.equals(ctrl.layout, prevLayout)) {
+ ctrl.updateAll();
+ }
+ };
+
+ ctrl.usedTooltipMessage = function () {
+ return ctrl.usedTooltipFunction ? ctrl.usedTooltipFunction() : _.get(ctrl.chartData, 'percentageUsed', 'N/A') + '% Used';
+ };
+
+ ctrl.availableTooltipMessage = function () {
+ return ctrl.availableTooltipFunction ? ctrl.availableTooltipFunction() : (100 - _.get(ctrl.chartData, 'percentageUsed', 0)) + '% Available';
+ };
+ }]
+});
+;/**
+ * @ngdoc directive
+ * @name patternfly.charts.directive:pfUtilizationTrendChart
+ * @restrict E
+ *
+ * @description
+ * Component for rendering a utilization trend chart. The utilization trend chart combines overall
+ * data with a pfDonutPctChart and a pfSparklineChart. Add the options for the pfDonutChart via
+ * the donutConfig parameter. Add the options for the pfSparklineChart via the sparklineConfig
+ * parameter.
+ *
+ * See http://c3js.org/reference.html for a full list of C3 chart options.
+ *
+ * @param {object} config configuration settings for the utilization trend chart:
+ *
+ *
.title - title of the Utilization chart
+ *
.units - unit label for values, ex: 'MHz','GB', etc..
+ *
+ *
+ * @param {object} donutConfig configuration settings for the donut pct chart, see pfDonutPctChart for specifics
+ * @param {object} sparklineConfig configuration settings for the sparkline chart, see pfSparklineChart for specifics
+ *
+ * @param {object} chartData the data to be shown in the donut and sparkline charts
+ *
+ *
.used - number representing the amount used
+ *
.total - number representing the total amount
+ *
.xData - Array, X values for the data points, first element must be the name of the data
+ *
.yData - Array, Y Values for the data points, first element must be the name of the data
+ *
.dataAvailable - Flag if there is data available - default: true
+ *
+ *
+ * @param {string=} donutCenterLabel specifies the contents of the donut's center label.
+ * Values:
+ *
+ *
'used' - displays the Used amount in the center label (default)
+ *
'available' - displays the Available amount in the center label
+ *
'percent' - displays the Usage Percent of the Total amount in the center label
+ *
'none' - does not display the center label
+ *
+ * @param {int=} sparklineChartHeight height of the sparkline chart
+ * @param {boolean=} showSparklineXAxis override sparkline config settings for showing the X Axis
+ * @param {boolean=} showSparklineYAxis override sparkline config settings for showing the Y Axis
+
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ angular.module( 'patternfly.charts' ).controller( 'ChartCtrl', function( $scope ) {
+ $scope.config = {
+ title: 'Memory',
+ units: 'GB'
+ };
+ $scope.donutConfig = {
+ chartId: 'chartA',
+ thresholds: {'warning':'60','error':'90'}
+ };
+ $scope.sparklineConfig = {
+ 'chartId': 'exampleSparkline',
+ 'tooltipType': 'default',
+ 'units': 'GB'
+ };
+
+ var today = new Date();
+ var dates = ['dates'];
+ for (var d = 20 - 1; d >= 0; d--) {
+ dates.push(new Date(today.getTime() - (d * 24 * 60 * 60 * 1000)));
+ }
+
+ $scope.data = {
+ dataAvailable: true,
+ used: 76,
+ total: 100,
+ xData: dates,
+ yData: ['used', '10', '20', '30', '20', '30', '10', '14', '20', '25', '68', '54', '56', '78', '56', '67', '88', '76', '65', '87', '76']
+ };
+
+ $scope.centerLabel = 'used';
+
+ $scope.custShowXAxis = false;
+ $scope.custShowYAxis = false;
+ $scope.custChartHeight = 60;
+
+ $scope.addDataPoint = function () {
+ var newData = Math.round(Math.random() * 100);
+ var newDate = new Date($scope.data.xData[$scope.data.xData.length - 1].getTime() + (24 * 60 * 60 * 1000));
+
+ $scope.data.used = newData;
+ $scope.data.xData.push(newDate);
+ $scope.data.yData.push(newData);
+ };
+ });
+
+
+ */
+angular.module('patternfly.charts').component('pfUtilizationTrendChart', {
+ bindings: {
+ chartData: '<',
+ config: '<',
+ centerLabel: '',
+ donutConfig: '<',
+ sparklineConfig: '<',
+ sparklineChartHeight: '',
+ showSparklineXAxis: '',
+ showSparklineYAxis: ''
+ },
+ templateUrl: 'charts/utilization-trend/utilization-trend-chart.html',
+ controller: ["pfUtils", function (pfUtils) {
+ 'use strict';
+ var ctrl = this, prevChartData, prevConfig;
+
+ ctrl.updateAll = function () {
+ // Need to deep watch changes
+ prevChartData = angular.copy(ctrl.chartData);
+ prevConfig = angular.copy(ctrl.config);
+
+ if (ctrl.centerLabel === undefined) {
+ ctrl.centerLabel = 'used';
+
+ }
+
+ if (ctrl.donutConfig.units === undefined) {
+ ctrl.donutConfig.units = ctrl.config.units;
+ }
+
+ if (ctrl.chartData.available === undefined) {
+ ctrl.chartData.available = ctrl.chartData.total - ctrl.chartData.used;
+ }
+
+ ctrl.config.units = ctrl.config.units || ctrl.units;
+
+ if (ctrl.centerLabel === 'available') {
+ ctrl.currentValue = ctrl.chartData.used;
+ ctrl.currentText = 'Used';
+ } else {
+ ctrl.currentValue = ctrl.chartData.total - ctrl.chartData.used;
+ ctrl.currentText = 'Available';
+ }
+ };
+
+ ctrl.$onChanges = function (changesObj) {
+ ctrl.updateAll();
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on chartData and config
+ if (!angular.equals(ctrl.chartData, prevChartData) || !angular.equals(ctrl.config, prevConfig)) {
+ ctrl.updateAll();
+ }
+ };
+ }]
+});
+;angular.module('patternfly.datepicker').component('pfBootstrapDatepicker', {
+ bindings: {
+ date: '<',
+ format: '@?',
+ dateOptions: '',
+ isOpen: '',
+ popupPlacement: '@?',
+ onDateChange: '&'
+ },
+ templateUrl: 'datepicker/datepicker.html',
+ controller: function () {
+ 'use strict';
+
+ var ctrl = this, prevDate;
+
+ ctrl.defaultDateOptions = {
+ showWeeks : false,
+ formatDay : "d"
+ };
+ ctrl.defaultIsOpen = false;
+
+ ctrl.$onInit = function () {
+ ctrl.format = "MM/dd/yyyy";
+ ctrl.showButtonBar = true;
+ ctrl.popupPlacement = "auto bottom-left";
+
+ if (angular.isUndefined(ctrl.dateOptions)) {
+ ctrl.dateOptions = {};
+ }
+ _.defaults(ctrl.dateOptions, ctrl.defaultDateOptions);
+ _.defaults(ctrl.isOpen, ctrl.defaultIsOpen);
+ };
+
+ ctrl.$onChanges = function (changes) {
+ prevDate = angular.copy(ctrl.date);
+ _.defaults(ctrl.isOpen, ctrl.defaultIsOpen);
+ };
+
+ ctrl.$doCheck = function () {
+ // do a deep compare on data
+ if (!angular.equals(ctrl.date, prevDate)) {
+ prevDate = angular.copy(ctrl.date);
+ if (ctrl.onDateChange) {
+ ctrl.onDateChange({newDate: ctrl.date});
+ }
+ }
+ };
+ }
+});
+;/**
+ * @ngdoc directive
+ * @name patternfly.datepicker.componenet:pfBootstrapDatepicker
+ * @element pf-bootstrap-datepicker
+ *
+ * @param {date} date Must be a Javascript Date - to be displayed in the input. Can be left empty.
+ * @param {string} format Optional date format for displayed dates ('MM/dd/yyyy' by default).
+ * @param {function} on-date-change Optional user defined function which is called when the date is changed by the picker.
+ * @param {boolean} isOpen Optional boolean for determining whether or not to have the datepicker default to open (false by default).
+ * @param {string} popupPlacement Optional configuration string used to position the popup datepicker relative to the input element. See {@link https://angular-ui.github.io/bootstrap/#datepickerPopup Angular UI Datepicker Popup}.
+ * @param {object} dateOptions Optional uib-datepicker configuration object. See {@link https://angular-ui.github.io/bootstrap/#datepicker Angular UI Datepicker}.
+ *
+ * @description
+ * A wrapper for the Angular UI {@link http://angular-ui.github.io/bootstrap/#!#datepickerPopup datepicker}.
+ *
+ * @example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ angular.module('patternfly.datepicker').controller('DemoBootstrapDatepicker', function( $scope ) {
+ $scope.eventText = '';
+
+ // first datepicker
+ $scope.dateOne = new Date("Jan 1, 2000");
+ $scope.firstDateChanged = function(date) {
+ $scope.eventText = 'Date One Updated to: ' + date + '\r\n' + $scope.eventText;
+ };
+
+ // second datepicker
+ $scope.dateTwo = new Date("Feb 1, 2000");
+ $scope.format1 = "MM/dd/yy";
+ $scope.dateOptions = {
+ showWeeks : true
+ };
+ $scope.isOpen = true;
+ $scope.popupPlacement = "bottom-left";
+
+ $scope.secondDateChanged = function(date) {
+ $scope.eventText = 'Date Two Updated to: ' + date + '\r\n' + $scope.eventText;
+ };
+ });
+
+
+
+ */
+;/**
+ * @ngdoc directive
+ * @name patternfly.filters.component:pfFilterPanel
+ * @restrict E
+ *
+ * @description
+ * The Filter Panel is opened and closed by clicking on the Filter button. It can contain any HTML desired by
+ * the application developer. As such, the application developer is repsonsible for constructing the appliedFilters array which is passed to
+ * the filter results tags.
+ * The application developer is responsible for filtering items based on the appliedFilters array; both when a
+ * filter is changed on the panel, or when a filter tag is 'cleared' (by user clicking on 'x' in the filter results tag).
+ *
+ *
+ * @param {object} config configuration settings for the filters:
+ *
+ *
.appliedFilters - (Array) List of the currently applied filters. Used to render the filter results tags and returned
+ * in the onFilterChange function where it can be used to filter a set of items.
+ *
+ *
.id - (String) Id for the filter, useful for comparisons
+ *
.title - (String) The title to display for the filter results tag
+ *
.values - (Array) The value(s) to display for the filter results tag. Ie. [title: [value1 x] [value2 x]]
+ *
+ *
.resultsCount - (int) The number of results returned after the current applied filters have been applied
+ *
.totalCount - (int) The total number of items before any filters have been applied. The 'm' in the label: 'n' of 'm'
+ *
.resultsLabel - (String) Optional label for the result units. Default is "Results". Ex: "'n' of 'm' Results"
+ *
.onFilterChange - ( function(appliedFilters, changedFilterId, changedFilterValue) ) Function to call when the applied
+ * filters list changes. Triggered by user clicking on 'x' or 'Clear All Filters' in the filter result tags.
+ * changedFilterId and changedFilterValue are returned after user clicks on an 'x' in a tag to
+ * denote which filter and filter value was cleared. changedFilterId and changedFilterValue are
+ * not returned when 'Clear All Filters' link is clicked.
+ *
+ *
+ * @param {object} config configuration settings for the filters:
+ *
+ *
.fields - (Array) List of filterable fields containing:
+ *
+ *
.id - (String) Optional unique Id for the filter field, useful for comparisons
+ *
.title - (String) The title to display for the filter field
+ *
.placeholder - (String) Text to display when no filter value has been entered
+ *
.filterMultiselect - (Boolean) In `complex-select`, allow selection of multiple categories and values. Optional, default is `false`
+ *
.filterType - (String) The filter input field type (any html input type, or 'select' for a single select box or 'complex-select' for a category select box)
+ *
.filterValues - (Array) List of valid select values used when filterType is 'select' or 'complex-select' (in where these values serve as case insensitve keys for .filterCategories objects)
+ *
.filterCategories - (Array of (Objects)) For 'complex-select' only, array of objects whoes keys (case insensitive) match the .filterValues, these objects include each of the filter fields above (sans .placeholder)
+ *
.filterCategoriesPlaceholder - (String) Text to display in `complex-select` category value select when no filter value has been entered, Optional
+ *
.filterDelimiter - (String) Delimiter separating 'complex-select' category and value. Optional, default is a space, ' '
+ *
+ *
.inlineResults - (Boolean) Flag to show results inline with the filter selection (default: false)
+ *
.appliedFilters - (Array) List of the currently applied filters
+ *
.resultsCount - (int) The number of results returned after the current applied filters have been applied
+ *
.totalCount - (int) The total number of items before any filters have been applied. The 'm' in the label: 'n' of 'm' selected
+ *
.showTotalCountResults - (Boolean) Optional, flag to show the total count in the filter results as well (ie. 'n' of 'm' Results)
+ *
.itemsLabel - (String) Optional label to use for the items in the results count (default: Result)
+ *
.itemsLabelPlural - (String) Optional label to use for the items in the resuults count when plural (default: Results)
+ *
.onFilterChange - ( function(array of filters) ) Function to call when the applied filters list changes
+ *
+ *
+ * @param {object} config configuration settings for the filter panel results:
+ *
+ *
.appliedFilters - (Array) List of the currently applied filters. Used to render the filter results tags and returned
+ * in the onFilterChange function where it can be used to filter a set of items.
+ *
+ *
.id - (String) Id for the filter, useful for comparisons
+ *
.title - (String) The title to display for the filter results tag
+ *
.values - (Array) The value(s) to display for the filter results tag. Ie. [title: [value1 x] [value2 x]]
+ *
+ *
.resultsCount - (int) The number of results returned after the current applied filters have been applied
+ *
.totalCount - (int) The total number of items before any filters have been applied. The 'm' in the label: 'n' of 'm'
+ *
.resultsLabel - (String) Optional label for the result units. Default is "Results". Ex: "'n' of 'm' Results"
+ *
.onFilterChange - ( function(appliedFilters, changedFilterId, changedFilterValue) ) Function to call when the applied
+ * filters list changes. Triggered by user clicking on 'x' or 'Clear All Filters' in the filter result tags.
+ * changedFilterId and changedFilterValue are returned after user clicks on an 'x' in a tag to
+ * denote which filter and filter value was cleared. changedFilterId and changedFilterValue are
+ * not returned when 'Clear All Filters' link is clicked.
+ *
+ *
+ * @param {object} config configuration settings for the filters:
+ *
+ *
.fields - (Array) List of filterable fields containing:
+ *
+ *
.id - (String) Optional unique Id for the filter field, useful for comparisons
+ *
.title - (String) The title to display for the filter field
+ *
.placeholder - (String) Text to display when no filter value has been entered
+ *
.filterMultiselect - (Boolean) In `complex-select`, allow selection of multiple categories and values. Optional, default is `false`
+ *
.filterType - (String) The filter input field type (any html input type, or 'select' for a single select box or 'complex-select' for a category select box)
+ *
.filterValues - (Array) List of valid select values used when filterType is 'select' or 'complex-select' (in where these values serve as case insensitve keys for .filterCategories objects)
+ *
.filterCategories - (Array of (Objects)) For 'complex-select' only, array of objects whoes keys (case insensitive) match the .filterValues, these objects include each of the filter fields above (sans .placeholder)
+ *
.filterCategoriesPlaceholder - (String) Text to display in `complex-select` category value select when no filter value has been entered, Optional
+ *
.filterDelimiter - (String) Delimiter separating 'complex-select' category and value. Optional, default is a space, ' '
+ *
+ *
.appliedFilters - (Array) List of the currently applied filters
+ *