/* * SonarQube :: Web * Copyright (C) 2009-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import $ from 'jquery'; import _ from 'underscore'; import { translate } from '../../helpers/l10n'; (function () { function Treemap () { this.addField('width', null); this.addField('height', null); this.addField('maxResultsReached', false); window.SonarWidgets.BaseWidget.apply(this, arguments); } Treemap.prototype = new window.SonarWidgets.BaseWidget(); Treemap.prototype.sizeLow = 11; Treemap.prototype.sizeHigh = 18; Treemap.prototype.filterComponents = function () { var that = this, components = this.components().filter(function (d) { return that.sizeMetric.value(d) != null; }); this.components(components); }; Treemap.prototype.getNodes = function () { return this.treemap .nodes({ children: this.components() }) .filter(function (d) { return !d.children; }); }; Treemap.prototype.renderTreemap = function () { var that = this; this.filterComponents(); if (!this.components().length) { this.maxResultsReachedLabel .text(translate('treemap.all_measures_undefined')) .style('display', 'block'); return; } var nodes = this.getNodes(); this.color = that.getColorScale(); this.cells = this.box.selectAll('.treemap-cell').data(nodes); this.cells.exit().remove(); var cellsEnter = this.cells.enter().append('div'); cellsEnter.classed('treemap-cell', true); cellsEnter.append('div').classed('treemap-inner', true); cellsEnter.append('a').classed('treemap-link', true); this.cells.attr('title', function (d) { return that.tooltip(d); }); this.cells.style('background-color', function (d) { if (that.colorMetric.value(d) != null) { return that.color(that.colorMetric.value(d)); } else { return that.colorUnknown; } }); this.cells.classed('treemap-cell-drilldown', function (d) { return (d.qualifier != null) && d.qualifier !== 'FIL' && d.qualifier !== 'CLA'; }); var prefix = this.mostCommonPrefix(_.pluck(this.components(), 'longName')), prefixLength = prefix.length; this.cellsInner = this.box.selectAll('.treemap-inner').data(nodes); this.cellsInner.html(function (d) { if (prefixLength > 0) { return prefix + '
' + (d.longName.substr(prefixLength)); } else { return d.longName; } }); this.cellsLink = this.box.selectAll('.treemap-link').data(nodes); this.cellsLink.html(''); this.cellsLink.attr('href', function (d) { return that.options().baseUrl + '?id=' + encodeURIComponent(d.key); }); this.attachEvents(cellsEnter); return this.maxResultsReachedLabel.style('display', this.maxResultsReached() ? 'block' : 'none'); }; Treemap.prototype.updateTreemap = function (components, maxResultsReached) { this.components(components); this.maxResultsReached(maxResultsReached); this.renderTreemap(); return this.positionCells(); }; Treemap.prototype.attachEvents = function (cells) { var that = this; return cells.on('click', function (d) { return that.requestChildren(d); }); }; Treemap.prototype.positionCells = function () { var that = this; this.cells.style('left', function (d) { return d.x + 'px'; }); this.cells.style('top', function (d) { return d.y + 'px'; }); this.cells.style('width', function (d) { return d.dx + 'px'; }); this.cellsInner.style('max-width', function (d) { return d.dx + 'px'; }); this.cells.style('height', function (d) { return d.dy + 'px'; }); this.cells.style('line-height', function (d) { return d.dy + 'px'; }); this.cells.style('font-size', function (d) { return (that.size(d.dx / d.longName.length)) + 'px'; }); this.cellsLink.style('font-size', function (d) { return (that.sizeLink(Math.min(d.dx, d.dy))) + 'px'; }); this.cells.classed('treemap-cell-small', function (d) { return (d.dx / d.longName.length) < 1 || d.dy < 40; }); return this.cells.classed('treemap-cell-very-small', function (d) { return d.dx < 24 || d.dy < 24; }); }; Treemap.prototype.renderLegend = function (box) { this.legend = box.insert('div', ':first-child'); this.legend.classed('legend', true); this.legend.classed('legend-html', true); this.legend.append('span') .classed('legend-text', true) .html('Size: ' + this.sizeMetric.name + ''); return this.legend.append('span') .classed('legend-text', true) .html('Color: ' + this.colorMetric.name + ''); }; Treemap.prototype.renderBreadcrumbs = function (box) { this.breadcrumbsBox = box.append('div').classed('treemap-breadcrumbs', true); this.breadcrumbs = []; var d = { name: '', components: this.components(), maxResultsReached: this.maxResultsReached() }; return this.addToBreadcrumbs(d); }; Treemap.prototype.updateBreadcrumbs = function () { var that = this; var breadcrumbs = this.breadcrumbsBox.selectAll('.treemap-breadcrumbs-item').data(this.breadcrumbs); breadcrumbs.exit().remove(); var breadcrumbsEnter = breadcrumbs.enter().append('span').classed('treemap-breadcrumbs-item', true); breadcrumbsEnter.attr('title', function (d) { if (d.longName != null) { return d.longName; } else { return that.options().resource; } }); breadcrumbsEnter.append('i') .classed('icon-chevron-right', true) .style('display', function (d, i) { if (i > 0) { return 'inline'; } else { return 'none'; } }); breadcrumbsEnter.append('i').attr('class', function (d) { if (d.qualifier != null) { return 'icon-qualifier-' + (d.qualifier.toLowerCase()); } else { return ''; } }); var breadcrumbsEnterLinks = breadcrumbsEnter.append('a'); breadcrumbsEnterLinks.html(function (d) { return d.name; }); breadcrumbsEnterLinks.on('click', function (d) { that.updateTreemap(d.components, d.maxResultsReached); return that.cutBreadcrumbs(d); }); this.breadcrumbsBox.style('display', this.breadcrumbs.length < 2 ? 'none' : 'block'); }; Treemap.prototype.addToBreadcrumbs = function (d) { this.breadcrumbs.push(d); this.updateBreadcrumbs(); }; Treemap.prototype.cutBreadcrumbs = function (lastElement) { var index = null; this.breadcrumbs.forEach(function (d, i) { if (d.key === lastElement.key) { index = i; } }); if (index != null) { this.breadcrumbs = _.initial(this.breadcrumbs, this.breadcrumbs.length - index - 1); this.updateBreadcrumbs(); } }; Treemap.prototype.getColorScale = function () { if (this.colorMetric.type === 'LEVEL') { return this.getLevelColorScale(); } if (this.colorMetric.type === 'RATING') { return this.getRatingColorScale(); } return this.getPercentColorScale(); }; Treemap.prototype.getPercentColorScale = function () { var color = d3.scale.linear().domain([0, 25, 50, 75, 100]); color.range(this.colorMetric.direction === 1 ? this.colors5 : this.colors5r); return color; }; Treemap.prototype.getRatingColorScale = function () { var domain = [1, 2, 3, 4, 5]; if (this.components().length > 0) { var colorMetricSample = this.colorMetric.value(_.first(this.components())); if (typeof colorMetricSample === 'string') { domain = ['A', 'B', 'C', 'D', 'E']; } } return d3.scale.ordinal().domain(domain).range(this.colors5r); }; Treemap.prototype.getLevelColorScale = function () { return d3.scale.ordinal().domain(['ERROR', 'WARN', 'OK', 'NONE']).range(this.colorsLevel); }; Treemap.prototype.render = function (container) { var that = this; var box = d3.select(container).append('div'); box.classed('sonar-d3', true); this.box = box.append('div').classed('treemap-container', true); this.addMetric('colorMetric', 0); this.addMetric('sizeMetric', 1); this.color = this.getColorScale(); this.size = d3.scale.linear().domain([3, 15]).range([this.sizeLow, this.sizeHigh]).clamp(true); this.sizeLink = d3.scale.linear().domain([60, 100]).range([12, 14]).clamp(true); this.treemap = d3.layout.treemap(); this.treemap.sort(function (a, b) { return a.value - b.value; }); this.treemap.round(true); this.treemap.value(function (d) { return that.sizeMetric.value(d); }); this.maxResultsReachedLabel = box.append('div').text(this.options().maxItemsReachedMessage); this.maxResultsReachedLabel.classed('max-results-reached-message', true); this.renderLegend(box); this.renderBreadcrumbs(box); return window.SonarWidgets.BaseWidget.prototype.render.apply(this, arguments); }; Treemap.prototype.update = function () { this.width(this.box.property('offsetWidth')); this.height(this.width() / 100.0 * this.options().heightInPercents); if (this.components().length) { this.box.style('height', (this.height()) + 'px'); this.treemap.size([this.width(), this.height()]); this.renderTreemap(); this.positionCells(); } }; Treemap.prototype.formatComponents = function (data) { var that = this; var components = _.filter(data, function (component) { var hasSizeMetric = function () { return _.findWhere(component.msr, { key: that.sizeMetric.key }); }; return _.isArray(component.msr) && component.msr.length > 0 && hasSizeMetric(); }); if (_.isArray(components) && components.length > 0) { return components.map(function (component) { var measures = {}; component.msr.forEach(function (measure) { measures[measure.key] = { val: measure.val, fval: measure.frmt_val, text: measure.text, data: measure.data }; }); return { key: component.copy != null ? component.copy : component.key, name: component.name, longName: component.lname, qualifier: component.qualifier, measures: measures }; }); } }; Treemap.prototype.requestChildren = function (d) { var that = this; var metrics = this.metricsPriority().join(','), RESOURCES_URL = baseUrl + '/api/resources/index'; return $.get(RESOURCES_URL, { resource: d.key, depth: 1, metrics: metrics }).done(function (r) { var components = that.formatComponents(r); if (components != null) { components = _.sortBy(components, function (component) { return -that.sizeMetric.value(component); }); components = _.initial(components, components.length - that.options().maxItems - 1); that.updateTreemap(components, components.length > that.options().maxItems); return that.addToBreadcrumbs(_.extend(d, { components: components, maxResultsReached: that.maxResultsReached() })); } }); }; Treemap.prototype.mostCommonPrefix = function (strings) { var sortedStrings = strings.slice(0).sort(), firstString = sortedStrings[0], firstStringLength = firstString.length, lastString = sortedStrings[sortedStrings.length - 1], i = 0; while (i < firstStringLength && firstString.charAt(i) === lastString.charAt(i)) { i++; } var prefix = firstString.substr(0, i), lastPrefixPart = _.last(prefix.split(/[\s\\\/]/)); return prefix.substr(0, prefix.length - lastPrefixPart.length); }; window.SonarWidgets.Treemap = Treemap; })();