diff --git a/visualization/Gruntfile.js b/visualization/Gruntfile.js index 62178b09d3..db36b7fca9 100755 --- a/visualization/Gruntfile.js +++ b/visualization/Gruntfile.js @@ -63,7 +63,10 @@ module.exports = function (grunt) { }, karmaSingle: { command: path.resolve("node_modules", ".bin", "karma") + " start ./conf/karma.config.js", - stdout: true + stdout: true, + options: { + maxBuffer: 40000 * 1024 + } }, karmaAuto: { command: path.resolve("node_modules", ".bin", "karma") + " start ./conf/karma.auto.config.js", diff --git a/visualization/app/codeCharta/core/core.js b/visualization/app/codeCharta/core/core.js index 0775238d11..46e47fc0ca 100755 --- a/visualization/app/codeCharta/core/core.js +++ b/visualization/app/codeCharta/core/core.js @@ -6,6 +6,7 @@ import "./url/url.js"; import "./treemap/treemap.js"; import "./scenario/scenario.js"; import "./tooltip/tooltip.js"; +import "./statistic/statistic.js"; angular.module( "app.codeCharta.core", @@ -15,6 +16,7 @@ angular.module( "app.codeCharta.core.url", "app.codeCharta.core.treemap", "app.codeCharta.core.scenario", - "app.codeCharta.core.tooltip" + "app.codeCharta.core.tooltip", + "app.codeCharta.core.statistic" ] ); diff --git a/visualization/app/codeCharta/core/data/model/codeMap.js b/visualization/app/codeCharta/core/data/model/codeMap.js index 5e9ba3c26a..c0ff19ac20 100755 --- a/visualization/app/codeCharta/core/data/model/codeMap.js +++ b/visualization/app/codeCharta/core/data/model/codeMap.js @@ -1,7 +1,6 @@ export class CodeMap { - constructor(fileName, projectName, root) { - + constructor(fileName = "", projectName = "", root = {}) { this.fileName = fileName; this.projectName = projectName; this.root = root; diff --git a/visualization/app/codeCharta/core/data/schema.json b/visualization/app/codeCharta/core/data/schema.json index 55c47798bb..8516916b0e 100755 --- a/visualization/app/codeCharta/core/data/schema.json +++ b/visualization/app/codeCharta/core/data/schema.json @@ -40,7 +40,7 @@ "attributeList":{ "type":"object", "patternProperties": { - "^.*$":{ + "^[A-Za-z0-9_]+$":{ "type": "number" } } diff --git a/visualization/app/codeCharta/core/scenario/scenarioService.js b/visualization/app/codeCharta/core/scenario/scenarioService.js index ac90fc3713..efc178b077 100755 --- a/visualization/app/codeCharta/core/scenario/scenarioService.js +++ b/visualization/app/codeCharta/core/scenario/scenarioService.js @@ -4,6 +4,7 @@ import {Scenario} from "./model/scenario.js"; import {Settings} from "../settings/model/settings.js"; import {Scale} from "../settings/model/scale.js"; import {Range} from "../settings/model/range.js"; +import {STATISTIC_OPS} from "../statistic/statisticMapService"; /** * Applies and manages scenarios. @@ -53,7 +54,7 @@ class ScenarioService { */ getDefaultScenario() { let defaultRange = new Range(20,40,false); - let defaultSettings = new Settings(this.settingsService.settings.map, defaultRange, "rloc", "mcc", "mcc", false, 1, new Scale(1,1,1), new Scale(0,300,1000), 1); + let defaultSettings = new Settings(this.settingsService.settings.map, defaultRange, "rloc", "mcc", "mcc", false, 1, new Scale(1,1,1), new Scale(0,300,1000), 1, STATISTIC_OPS.NO_OPERATIONS); return new Scenario("rloc/mcc/mcc(20,40)", defaultSettings); } diff --git a/visualization/app/codeCharta/core/settings/model/settings.js b/visualization/app/codeCharta/core/settings/model/settings.js index 8371d1dcba..8dd1b53a7f 100755 --- a/visualization/app/codeCharta/core/settings/model/settings.js +++ b/visualization/app/codeCharta/core/settings/model/settings.js @@ -15,8 +15,10 @@ export class Settings { * @param {Scale} scaling * @param {Scale} camera * @param {Number} margin + * @param {STATISTIC_OPS} operation */ - constructor(map, neutralColorRange, areaMetric, heightMetric, colorMetric, deltas, amountOfTopLabels, scaling, camera, margin) { + constructor(map, neutralColorRange, areaMetric, heightMetric, colorMetric, deltas, amountOfTopLabels, scaling, + camera, margin, operation) { /** * currently selected map @@ -78,6 +80,12 @@ export class Settings { */ this.margin = margin; + /** + * statistical operation to be applied in the set of maps to get map + * @type {STATISTIC_OPS} + */ + this.operation = operation; + } /** @@ -95,6 +103,7 @@ export class Settings { this.scaling = settings.scaling; this.camera = settings.camera; this.margin = settings.margin; + this.operation = settings.operation; } } diff --git a/visualization/app/codeCharta/core/settings/settings.js b/visualization/app/codeCharta/core/settings/settings.js index 3b92c25097..dfe6085ff4 100755 --- a/visualization/app/codeCharta/core/settings/settings.js +++ b/visualization/app/codeCharta/core/settings/settings.js @@ -7,7 +7,7 @@ import {SettingsService} from "./settingsService.js"; angular.module( "app.codeCharta.core.settings", - ["app.codeCharta.core.url", "app.codeCharta.core.data"] + ["app.codeCharta.core.url", "app.codeCharta.core.data", "app.codeCharta.core.statistic"] ); angular.module("app.codeCharta.core.settings").service( diff --git a/visualization/app/codeCharta/core/settings/settingsService.js b/visualization/app/codeCharta/core/settings/settingsService.js index cdb6b4afad..49529ab9cc 100755 --- a/visualization/app/codeCharta/core/settings/settingsService.js +++ b/visualization/app/codeCharta/core/settings/settingsService.js @@ -3,6 +3,7 @@ import {Settings} from "./model/settings"; import {Scale} from "./model/scale"; import {Range} from "./model/range"; +import {STATISTIC_OPS, StatisticMapService} from "../statistic/statisticMapService"; /** * Stores and manipulates the current settings @@ -15,15 +16,28 @@ class SettingsService { * @constructor * @param {UrlService} urlService * @param {DataService} dataService - * @param {Scope} $rootScope + * @param {Scope} $rootScope + * @param {StatisticMapService} statisticMapService; */ - constructor(urlService, dataService, $rootScope) { + constructor(urlService, dataService, $rootScope, statisticMapService) { /** * @type {UrlService} */ this.urlService = urlService; + + + /** + * @type {STATISTIC_OPS} + */ + this.STATISTIC_OPS= STATISTIC_OPS; + + /** + * @type {statisticMapService} + */ + this.statisticMapService = statisticMapService; + /** * @type {DataService} */ @@ -49,7 +63,8 @@ class SettingsService { 1, new Scale(1,1,1), new Scale(0,300,1000), - 1 + 1, + STATISTIC_OPS.NO_OPERATIONS ); $rootScope.$on("data-changed", (event,data) => { diff --git a/visualization/app/codeCharta/core/statistic/statistic.js b/visualization/app/codeCharta/core/statistic/statistic.js new file mode 100644 index 0000000000..716b06d175 --- /dev/null +++ b/visualization/app/codeCharta/core/statistic/statistic.js @@ -0,0 +1,12 @@ +import {StatisticMapService, STATISTIC_OPS} from "./statisticMapService.js"; + +angular.module( + "app.codeCharta.core.statistic", [] +); + +angular.module("app.codeCharta.core.statistic").service( + "statisticMapService", StatisticMapService +); +angular.module("app.codeCharta.core.statistic").service( + "STATISTIC_OPS", STATISTIC_OPS +); \ No newline at end of file diff --git a/visualization/app/codeCharta/core/statistic/statisticMapService.js b/visualization/app/codeCharta/core/statistic/statisticMapService.js new file mode 100755 index 0000000000..7bcba058f1 --- /dev/null +++ b/visualization/app/codeCharta/core/statistic/statisticMapService.js @@ -0,0 +1,338 @@ +import {CodeMap} from "../data/model/codeMap.js"; + + +export const STATISTIC_OPS = { + NO_OPERATIONS: "NO_OPERATIONS", + MEAN: "MEAN", + MEDIAN: "MEDIAN", + MAX: "MAX", + MIN: "MIN", + FASHION: "FASHION" +}; + +export class StatisticMapService { + + constructor() { + + } + + /* + * Function that receives an array of maps and returns a map with a structure that contains every leaf contained in + * any of the maps of the input array. + * The attributes of the output map contain as values the result of an statistical operation applied to the values + * of the input maps. That operation can be selected among the implemented ones by the operation value. + * Every new statistical operation should have a new value in STATISTIC_OPS and a new function, which should be + * added to the statistic function switch. + */ + unifyMaps(maps, operation = STATISTIC_OPS.NO_OPERATIONS) { + if(operation==STATISTIC_OPS.NO_OPERATIONS){ + return maps; + } + else if(maps.length==1){ + return maps[0]; + } + var accumulated = new CodeMap();//Map that contains an array of every value of every map given in maps array + var unified; + for(var i=0; iinput[i]){ + output=input[i]; + } + } + } + return output; + } + + /* + * Function that returns the most common value in the input array + */ + fashion(input){ + var frequency = {};//Object that contains every different value in input linked to its absolute frequency + var fashion_frequency = 0;//Absolute frequency of the fashion value + var fashion_value; + for(var i=0;i 1){ + median = (sorted[(num+1)/2]+sorted[(num-1)/2])/2; + } + else{ + median = sorted[0]; + } + return median; + } + + /* + * Function that returns true when two objects contain the same even when it is not in the same order + * ({a,b}=={b,a} would return true) + * Only used in testing + */ + + unorderedCompare(a,b){ + var same = false; + if(Object.keys(a).length==0&& JSON.stringify(a) != JSON.stringify(b)){ + return false; + } + for(var key in a){ + if(!b[key]||typeof(b[key])!=typeof(a[key])||Object.keys(a[key]).length!=Object.keys(b[key]).length){ + return false; + } + } + for(var key in a){ + if(typeof(a[key])=="object"){ + if(!isNaN(key)){ + for(var keyb in b){ + if(this.unorderedCompare(a[key],b[keyb])){ + same= true; + } + } + if(!same){ + return false; + } + } + else if(!this.unorderedCompare(a[key],b[key])){ + return false; + } + } + else if(a[key]!=b[key]){ + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/visualization/app/codeCharta/core/statistic/statisticMapService.spec.js b/visualization/app/codeCharta/core/statistic/statisticMapService.spec.js new file mode 100755 index 0000000000..e31f6345f5 --- /dev/null +++ b/visualization/app/codeCharta/core/statistic/statisticMapService.spec.js @@ -0,0 +1,299 @@ +import "./statistic.js"; +import {CodeMap} from "../data/model/codeMap.js"; +import {STATISTIC_OPS} from "./statisticMapService"; + +describe("app.codeCharta.core.statistic", function() { + + + + const file1 = new CodeMap( + "file", + "Sample Project", + { + "name": "root", + "attributes": {}, + "children": [ + { + "name": "big leaf", + "attributes": {"rloc": 100, "functions": 10, "mcc": 1}, + "link": "http://www.google.de" + }, + { + "name": "Parent Leaf", + "attributes": {}, + "children": [ + { + "name": "other small leaf", + "attributes": {"rloc": 70, "functions": 1000, "mcc": 10} + } + ] + } + ] + } + ); + + const file2 = new CodeMap( + "file", + "Sample Project", + { + "name": "root", + "attributes": {}, + "children": [ + { + "name": "big leaf", + "attributes": {"rloc": 200, "functions": 20, "mcc": 2}, + "link": "http://www.google.de" + }, + { + "name": "Parent Leaf", + "attributes": {}, + "children": [ + { + "name": "small leaf", + "attributes": {"rloc": 60, "functions": 200, "mcc": 200} + } + ] + } + ] + }); + + const file_mean = new CodeMap( + "file", + "Sample Project", + { + "name": "root", + "attributes": {}, + "children": [ + { + "name": "big leaf", + "attributes": {"rloc": 150, "functions": 15, "mcc": 1.5}, + "link": "http://www.google.de" + }, + { + "name": "Parent Leaf", + "attributes": {}, + "children": [ + { + "name": "other small leaf", + "attributes": {"rloc": 35, "functions": 500, "mcc": 5} + }, + { + "name": "small leaf", + "attributes": {"rloc": 30, "functions": 100, "mcc": 100} + } + ] + } + ] + }); + + const file_median = new CodeMap( + "file", + "Sample Project", + { + "name": "root", + "attributes": {}, + "children": [ + { + "name": "big leaf", + "attributes": {"rloc": 200, "functions": 20, "mcc": 2}, + "link": "http://www.google.de" + }, + { + "name": "Parent Leaf", + "attributes": {}, + "children": [ + { + "name": "other small leaf", + "attributes": {"rloc": 70, "functions": 1000, "mcc": 10} + }, + { + "name": "small leaf", + "attributes": {"rloc": 60, "functions": 200, "mcc": 200} + } + ] + } + ] + }); + + const file_max = new CodeMap( + "file", + "Sample Project", + { + "name": "root", + "attributes": {}, + "children": [ + { + "name": "big leaf", + "attributes": {"rloc": 200, "functions": 20, "mcc": 2}, + "link": "http://www.google.de" + }, + { + "name": "Parent Leaf", + "attributes": {}, + "children": [ + { + "name": "other small leaf", + "attributes": {"rloc": 70, "functions": 1000, "mcc": 10} + }, + { + "name": "small leaf", + "attributes": {"rloc": 60, "functions": 200, "mcc": 200} + } + ] + } + ] + }); + + const file_min = new CodeMap( + "file", + "Sample Project", + { + "name": "root", + "attributes": {}, + "children": [ + { + "name": "big leaf", + "attributes": {"rloc": 100, "functions": 10, "mcc": 1}, + "link": "http://www.google.de" + }, + { + "name": "Parent Leaf", + "attributes": {}, + "children": [ + { + "name": "other small leaf", + "attributes": {"rloc": 70, "functions": 1000, "mcc": 10} + }, + { + "name": "small leaf", + "attributes": {"rloc": 60, "functions": 200, "mcc": 200} + } + ] + } + ] + }); + + const file_fashion = new CodeMap( + "file", + "Sample Project", + { + "name": "root", + "attributes": {}, + "children": [ + { + "name": "big leaf", + "attributes": {"rloc": 100, "functions": 10, "mcc": 1}, + "link": "http://www.google.de" + }, + { + "name": "Parent Leaf", + "attributes": {}, + "children": [ + { + "name": "other small leaf", + "attributes": {"rloc": 70, "functions": 1000, "mcc": 10} + }, + { + "name": "small leaf", + "attributes": {"rloc": 60, "functions": 200, "mcc": 200} + } + ] + } + ] + }); + + beforeEach(angular.mock.module("app.codeCharta.core.statistic")); + + it("instance", angular.mock.inject(function(statisticMapService){ + expect(statisticMapService).to.not.equal(undefined); + })); + + it("test mean", angular.mock.inject(function(statisticMapService){ + const util = require('util'); + const maps = [ file1, file2]; + const resultingMap = statisticMapService.unifyMaps(maps, STATISTIC_OPS.MEAN); + expect(resultingMap).to.deep.equal(file_mean); + })); + + it("test median", angular.mock.inject(function(statisticMapService){ + const maps = [ file1, file2]; + const resultingMap = statisticMapService.unifyMaps(maps, STATISTIC_OPS.MEDIAN); + expect(resultingMap).to.deep.equal(file_median); + })); + + it("test max", angular.mock.inject(function(statisticMapService){ + const maps = [ file1, file2]; + const resultingMap = statisticMapService.unifyMaps(maps, STATISTIC_OPS.MAX); + expect(resultingMap).to.deep.equal(file_max); + })); + + it("test min", angular.mock.inject(function(statisticMapService){ + const maps = [ file1, file2]; + const resultingMap = statisticMapService.unifyMaps(maps, STATISTIC_OPS.MIN); + expect(resultingMap).to.deep.equal(file_min); + })); + + it("test fashion", angular.mock.inject(function(statisticMapService){ + const maps = [ file1, file2]; + const resultingMap = statisticMapService.unifyMaps(maps, STATISTIC_OPS.FASHION); + expect(resultingMap).to.deep.equal(file_fashion); + })); + + it("test one", angular.mock.inject(function(statisticMapService){ + const util = require('util'); + const map = [file1]; + const resultingMapMean = statisticMapService.unifyMaps(map, STATISTIC_OPS.MEAN); + const resultingMapMedian = statisticMapService.unifyMaps(map, STATISTIC_OPS.MEDIAN); + const resultingMapMax = statisticMapService.unifyMaps(map, STATISTIC_OPS.MAX); + const resultingMapMin = statisticMapService.unifyMaps(map, STATISTIC_OPS.MIN); + const resultingMapFashion = statisticMapService.unifyMaps(map, STATISTIC_OPS.FASHION); + + expect(resultingMapMean).to.deep.equal(file1); + expect(resultingMapMedian).to.deep.equal(file1); + expect(resultingMapMax).to.deep.equal(file1); + expect(resultingMapMin).to.deep.equal(file1); + expect(resultingMapFashion).to.deep.equal(file1); + })); + + it("test repeated", angular.mock.inject(function(statisticMapService){ + const map = [file1, file1, file1, file1]; + const resultingMapMean = statisticMapService.unifyMaps(map, STATISTIC_OPS.MEAN); + const resultingMapMedian = statisticMapService.unifyMaps(map, STATISTIC_OPS.MEDIAN); + const resultingMapMax = statisticMapService.unifyMaps(map, STATISTIC_OPS.MAX); + const resultingMapMin = statisticMapService.unifyMaps(map, STATISTIC_OPS.MIN); + const resultingMapFashion = statisticMapService.unifyMaps(map, STATISTIC_OPS.FASHION); + + expect(resultingMapMean).to.deep.equal(file1); + expect(resultingMapMedian).to.deep.equal(file1); + expect(resultingMapMax).to.deep.equal(file1); + expect(resultingMapMin).to.deep.equal(file1); + expect(resultingMapFashion).to.deep.equal(file1); + })); + + /* + * It is checked if when introduced in different order the result is the same, not necesarily in the same order + */ + it("test order", angular.mock.inject(function(statisticMapService){ + const util = require('util'); + const maps1 = [file1, file2]; + const maps2 = [file2, file1]; + + const resultingMapMean1 = statisticMapService.unifyMaps(maps1, STATISTIC_OPS.MEAN); + const resultingMapMedian1 = statisticMapService.unifyMaps(maps1, STATISTIC_OPS.MEDIAN); + const resultingMapMax1 = statisticMapService.unifyMaps(maps1, STATISTIC_OPS.MAX); + const resultingMapMin1 = statisticMapService.unifyMaps(maps1, STATISTIC_OPS.MIN); + const resultingMapFashion1 = statisticMapService.unifyMaps(maps1, STATISTIC_OPS.FASHION); + + const resultingMapMean2 = statisticMapService.unifyMaps(maps2, STATISTIC_OPS.MEAN); + const resultingMapMedian2 = statisticMapService.unifyMaps(maps2, STATISTIC_OPS.MEDIAN); + const resultingMapMax2 = statisticMapService.unifyMaps(maps2, STATISTIC_OPS.MAX); + const resultingMapMin2 = statisticMapService.unifyMaps(maps2, STATISTIC_OPS.MIN); + const resultingMapFashion2 = statisticMapService.unifyMaps(maps2, STATISTIC_OPS.FASHION); + + expect(statisticMapService.unorderedCompare(resultingMapMean1,resultingMapMean2)).to.be.true; + expect(statisticMapService.unorderedCompare(resultingMapMedian1,resultingMapMedian2)).to.be.true; + expect(statisticMapService.unorderedCompare(resultingMapMax1,resultingMapMax2)).to.be.true; + expect(statisticMapService.unorderedCompare(resultingMapMin1,resultingMapMin2)).to.be.true; + expect(statisticMapService.unorderedCompare(resultingMapFashion1,resultingMapFashion2)).to.be.true; + })); +}); \ No newline at end of file diff --git a/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.html b/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.html index f613432c1b..592073e6b6 100755 --- a/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.html +++ b/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.html @@ -37,6 +37,10 @@ + + + + Show Url Parameters diff --git a/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.js b/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.js index 311d6865b9..fc49db2211 100755 --- a/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.js +++ b/visualization/app/codeCharta/ui/settingsPanel/settingsPanel.js @@ -1,4 +1,5 @@ "use strict"; +import {STATISTIC_OPS, StatisticMapService} from "../../core/statistic/statisticMapService"; /** * Controls the settingsPanel @@ -26,6 +27,17 @@ export class SettingsPanelController { const ctx = this; + /** + * @type {StatisticMapService} + */ + this.statisticMapService = settingsService.statisticMapService; + + /** + * + * @type {STATISTIC_OPS} + */ + this.STATISTIC_OPS = STATISTIC_OPS; + /** * Options for the rz color slider * @type {Object} @@ -42,6 +54,11 @@ export class SettingsPanelController { */ this.metrics = this.sortStringArrayAlphabetically(dataService.data.metrics); + /** + * + */ + this.setOfMaps= dataService.data.revisions; + $scope.$on("data-changed", (e,d)=>{ctx.onDataChanged(d);}); $scope.$on("settings-changed", (e,s)=>{ctx.onSettingsChanged(s);}); @@ -83,6 +100,17 @@ export class SettingsPanelController { return arr.sort(); } + /** + * Updates the map before broadcasting the udpate of the settings + */ + onStatisticsChange(){ + const util = require('util'); + console.log("operation "+this.settings.operation); + this.settings.map = this.statisticMapService.unifyMaps(this.setOfMaps, this.settings.operation);//Working here + console.log("this.settings.map ",util.inspect(this.settings.map,{showHidden: true, depth: null})); + this.notify(); + } + } export const settingsPanelComponent = { diff --git a/visualization/conf/karma.config.js b/visualization/conf/karma.config.js index dbdcd701c4..a691d33ebf 100644 --- a/visualization/conf/karma.config.js +++ b/visualization/conf/karma.config.js @@ -3,7 +3,7 @@ module.exports = function (config) { basePath: '../', singleRun: true, autoWatch: false, - logLevel: 'INFO', + logLevel: 'ERROR', junitReporter: { outputDir: 'dist/test-reports' }, @@ -49,7 +49,6 @@ module.exports = function (config) { require('karma-phantomjs-launcher'), require('karma-phantomjs-shim'), require('karma-webpack') - ], devtool: 'source-map' }; diff --git a/visualization/conf/webpack.test.config.js b/visualization/conf/webpack.test.config.js index 609c5823e2..8f99d5571e 100644 --- a/visualization/conf/webpack.test.config.js +++ b/visualization/conf/webpack.test.config.js @@ -61,11 +61,6 @@ module.exports = { use: [ 'babel-loader', 'ts-loader' ], - - }, - { - test: /\.glsl$/, - loaders: ['webpack-glsl-loader'] } ] },