aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2013-10-24 16:19:56 +0200
committerStas Vilchik <vilchiks@gmail.com>2013-10-24 16:32:33 +0200
commite73dfa99edc77ba077cff59d57af1f9fa7fefecd (patch)
tree698c98869d50e075cb7df6aad5aeb653fe41f489
parent0a866aebdf1ad476d5cb5544901220210936f9ca (diff)
downloadsonarqube-e73dfa99edc77ba077cff59d57af1f9fa7fefecd.tar.gz
sonarqube-e73dfa99edc77ba077cff59d57af1f9fa7fefecd.zip
SONAR-55 Provide a Bubble chart widget (add resize-on-fly for the widget)
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/bubbleChart.html.erb4
-rw-r--r--sonar-server/src/main/webapp/javascripts/bubble-chart.js349
2 files changed, 207 insertions, 146 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/bubbleChart.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/bubbleChart.html.erb
index c16fc1de9ec..25774cc8a13 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/bubbleChart.html.erb
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/bubbleChart.html.erb
@@ -66,6 +66,10 @@
.yLog(<%= yLog %>)
.height(<%= chartHeight %>)
.render('#<%= container_id %>');
+
+ autoResize(500, function() {
+ bubbleChart.update('#<%= container_id %>');
+ });
</script>
<!--<![endif]-->
diff --git a/sonar-server/src/main/webapp/javascripts/bubble-chart.js b/sonar-server/src/main/webapp/javascripts/bubble-chart.js
index 9d903a75bac..47fa2d88307 100644
--- a/sonar-server/src/main/webapp/javascripts/bubble-chart.js
+++ b/sonar-server/src/main/webapp/javascripts/bubble-chart.js
@@ -53,113 +53,64 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets;
};
SonarWidgets.BubbleChart.prototype.render = function (container) {
- container = d3.select(container);
+ var widget = this,
+ containerS = container;
- var widget = this;
+ container = d3.select(container);
this.width(container.property('offsetWidth'));
- var svg = container.append('svg'),
- gWrap = svg.append('g'),
-
- gxAxis = gWrap.append('g'),
- gyAxis = gWrap.append('g'),
+ this.svg = container.append('svg');
+ this.gWrap = this.svg.append('g');
- gGrid = gWrap.append('g'),
- gxGrid = gGrid.append('g'),
- gyGrid = gGrid.append('g'),
+ this.gxAxis = this.gWrap.append('g');
+ this.gyAxis = this.gWrap.append('g');
- plotWrap = gWrap.append('g'),
+ this.gGrid = this.gWrap.append('g');
+ this.gxGrid = this.gGrid.append('g');
+ this.gyGrid = this.gGrid.append('g');
- infoWrap = gWrap.append('g'),
- infoName = infoWrap.append('text'),
- infoMetrics = infoWrap.append('text');
+ this.plotWrap = this.gWrap.append('g');
- svg
- .attr('width', this.width())
- .attr('height', this.height());
+ this.infoWrap = this.gWrap.append('g');
+ this.infoName = this.infoWrap.append('text');
+ this.infoMetrics = this.infoWrap.append('text');
- gWrap
+ this.gWrap
.attr('transform', trans(this.margin().left, this.margin().top));
- var availableWidth = this.width() - this.margin().left - this.margin().right,
- availableHeight = this.height() - this.margin().top - this.margin().bottom;
-
-
// Configure scales
- var x = this.xLog() ? d3.scale.log() : d3.scale.linear(),
- y = this.yLog() ? d3.scale.log() : d3.scale.linear(),
- size = d3.scale.linear();
+ this.x = this.xLog() ? d3.scale.log() : d3.scale.linear();
+ this.y = this.yLog() ? d3.scale.log() : d3.scale.linear();
+ this.size = d3.scale.linear();
- x
+ this.x
.domain(d3.extent(this.data(), function (d) {
return d.xMetric
}))
- .range([0, availableWidth]);
+ .range([0, this.availableWidth]);
- y
+ this.y
.domain(d3.extent(this.data(), function (d) {
return d.yMetric
}))
- .range([availableHeight, 0]);
+ .range([this.availableHeight, 0]);
- size
+ this.size
.domain(d3.extent(this.data(), function (d) {
return d.sizeMetric
}))
.range([10, 50]);
- // Avoid zero values when using log scale
- if (this.xLog) {
- var xDomain = x.domain();
- x
- .domain([xDomain[0] > 0 ? xDomain[0] : 0.1, xDomain[1]])
- .clamp(true);
- }
-
- if (this.yLog) {
- var yDomain = y.domain();
- y
- .domain([yDomain[0] > 0 ? yDomain[0] : 0.1, yDomain[1]])
- .clamp(true);
- }
-
-
- // Adjust the scale domain so the circles don't cross the bounds
- // X
- var minX = d3.min(this.data(), function (d) {
- return x(d.xMetric) - size(d.sizeMetric)
- }),
- maxX = d3.max(this.data(), function (d) {
- return x(d.xMetric) + size(d.sizeMetric)
- }),
- dMinX = x.range()[0] - minX,
- dMaxX = maxX - x.range()[1];
- x.range([dMinX, availableWidth - dMaxX]);
-
- // Y
- var minY = d3.min(this.data(), function (d) {
- return y(d.yMetric) - size(d.sizeMetric)
- }),
- maxY = d3.max(this.data(), function (d) {
- return y(d.yMetric) + size(d.sizeMetric)
- }),
- dMinY = y.range()[1] - minY,
- dMaxY = maxY - y.range()[0];
- y.range([availableHeight - dMaxY, dMinY]);
-
- x.nice();
- y.nice();
-
-
- // Render items
- var items = plotWrap.selectAll('.item')
+ // Create bubbles
+ this.items = this.plotWrap.selectAll('.item')
.data(this.data());
- items
- .enter().append('g')
+
+ // Render bubbles
+ this.items.enter().append('g')
.attr('class', 'item')
.attr('name', function (d) {
return d.longName
@@ -167,10 +118,7 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets;
.style('cursor', 'pointer')
.append('circle')
.attr('r', function (d) {
- return size(d.sizeMetric)
- })
- .attr('transform', function (d) {
- return trans(x(d.xMetric), y(d.yMetric));
+ return widget.size(d.sizeMetric)
})
.style('fill', function (d) {
return d.sizeMetricFormatted !== '-' ?
@@ -185,15 +133,15 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets;
})
.style('transition', 'all 0.2s ease');
- items.exit().remove();
+ this.items.exit().remove();
- items.sort(function (a, b) {
+ this.items.sort(function (a, b) {
return b.sizeMetric - a.sizeMetric
});
// Set event listeners
- items
+ this.items
.on('click', function (d) {
var url = baseUrl + '/resource/index/' + d.key + '?display_title=true&metric=ncloc';
window.open(url, '', 'height=800,width=900,scrollbars=1,resizable=1');
@@ -202,8 +150,8 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets;
d3.select(this).select('circle')
.style('fill-opacity', 0.8);
- infoName.text(d.longName);
- infoMetrics.text(
+ widget.infoName.text(d.longName);
+ widget.infoMetrics.text(
widget.metrics().x + ': ' + d.xMetricFormatted + '; ' +
widget.metrics().y + ': ' + d.yMetricFormatted + '; ' +
widget.metrics().size + ': ' + d.sizeMetricFormatted);
@@ -212,104 +160,212 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets;
d3.select(this).select('circle')
.style('fill-opacity', 0.2);
- infoName.text('');
- infoMetrics.text('');
+ widget.infoName.text('');
+ widget.infoMetrics.text('');
});
- // Render axis
+ // Configure axis
// X
- var xAxis = d3.svg.axis()
- .scale(x)
+ this.xAxis = d3.svg.axis()
+ .scale(widget.x)
.orient('bottom');
- gxAxis.attr('transform', trans(0, availableHeight + this.margin().bottom - 40));
+ this.gxAxisLabel = this.gxAxis.append('text')
+ .text(this.metrics().x)
+ .style('font-weight', 'bold')
+ .style('text-anchor', 'middle');
- gxAxis.call(xAxis);
- gxAxis.selectAll('path')
- .style('fill', 'none')
- .style('stroke', '#444');
+ // Y
+ this.yAxis = d3.svg.axis()
+ .scale(widget.y)
+ .orient('left');
- gxAxis.selectAll('text')
- .style('fill', '#444');
+ this.gyAxis.attr('transform', trans(60 - this.margin().left, 0));
- gxAxis.append('text')
- .text(this.metrics().x)
+ this.gyAxisLabel = this.gyAxis.append('text')
+ .text(this.metrics().y)
.style('font-weight', 'bold')
- .style('text-anchor', 'middle')
- .attr('transform', trans(availableWidth / 2, 35));
+ .style('text-anchor', 'middle');
+
+
+ // Configure grid
+ this.gxGridLines = this.gxGrid.selectAll('line').data(widget.x.ticks()).enter()
+ .append('line');
+
+ this.gyGridLines = this.gyGrid.selectAll('line').data(widget.y.ticks()).enter()
+ .append('line');
+
+ this.gGrid.selectAll('line')
+ .style('stroke', '#000')
+ .style('stroke-opacity', 0.25);
+
+
+ // Configure info placeholders
+ this.infoWrap
+ .attr('transform', trans(-this.margin().left, -this.margin().top + 20));
+
+ this.infoName
+ .style('text-anchor', 'start')
+ .style('font-weight', 'bold');
+
+ this.infoMetrics
+ .attr('transform', trans(0, 20));
+
+
+ // Update widget
+ this.update(containerS);
+
+ return this;
+ };
+
+
+
+ SonarWidgets.BubbleChart.prototype.update = function(container) {
+ container = d3.select(container);
+
+ var widget = this,
+ width = container.property('offsetWidth');
+
+ this.width(width > 100 ? width : 100);
+
+
+ // Update svg canvas
+ this.svg
+ .attr('width', this.width())
+ .attr('height', this.height());
+
+
+ // Update available size
+ this.availableWidth = this.width() - this.margin().left - this.margin().right;
+ this.availableHeight = this.height() - this.margin().top - this.margin().bottom;
+
+
+ // Update scales
+ this.x
+ .domain(d3.extent(this.data(), function (d) {
+ return d.xMetric
+ }))
+ .range([0, this.availableWidth]);
+
+ this.y
+ .domain(d3.extent(this.data(), function (d) {
+ return d.yMetric
+ }))
+ .range([this.availableHeight, 0]);
+
+
+ // Avoid zero values when using log scale
+ if (this.xLog) {
+ var xDomain = this.x.domain();
+ this.x
+ .domain([xDomain[0] > 0 ? xDomain[0] : 0.1, xDomain[1]])
+ .clamp(true);
+ }
+
+ if (this.yLog) {
+ var yDomain = this.y.domain();
+ this.y
+ .domain([yDomain[0] > 0 ? yDomain[0] : 0.1, yDomain[1]])
+ .clamp(true);
+ }
+
+
+ // Adjust the scale domain so the circles don't cross the bounds
+ // X
+ var minX = d3.min(this.data(), function (d) {
+ return widget.x(d.xMetric) - widget.size(d.sizeMetric)
+ }),
+ maxX = d3.max(this.data(), function (d) {
+ return widget.x(d.xMetric) + widget.size(d.sizeMetric)
+ }),
+ dMinX = this.x.range()[0] - minX,
+ dMaxX = maxX - this.x.range()[1];
+ this.x.range([dMinX, this.availableWidth - dMaxX]);
// Y
- var yAxis = d3.svg.axis()
- .scale(y)
- .orient('left');
+ var minY = d3.min(this.data(), function (d) {
+ return widget.y(d.yMetric) - widget.size(d.sizeMetric)
+ }),
+ maxY = d3.max(this.data(), function (d) {
+ return widget.y(d.yMetric) + widget.size(d.sizeMetric)
+ }),
+ dMinY = this.y.range()[1] - minY,
+ dMaxY = maxY - this.y.range()[0];
+ this.y.range([this.availableHeight - dMaxY, dMinY]);
+
+ this.x.nice();
+ this.y.nice();
+
+
+ // Update bubbles position
+ this.items
+ .transition()
+ .attr('transform', function (d) {
+ return trans(widget.x(d.xMetric), widget.y(d.yMetric));
+ });
- gyAxis.attr('transform', trans(60 - this.margin().left, 0));
- gyAxis.call(yAxis);
+ // Update axis
+ // X
+ this.gxAxis.attr('transform', trans(0, this.availableHeight + this.margin().bottom - 40));
+
+ this.gxAxis.transition().call(this.xAxis);
- gyAxis.selectAll('path')
+ this.gxAxis.selectAll('path')
.style('fill', 'none')
.style('stroke', '#444');
- gyAxis.selectAll('text')
+ this.gxAxis.selectAll('text')
.style('fill', '#444');
- gyAxis.append('text')
- .text(this.metrics().y)
- .style('font-weight', 'bold')
- .style('text-anchor', 'middle')
- .attr('transform', trans(-45, availableHeight / 2) + ' rotate(-90)');
+ this.gxAxisLabel
+ .attr('transform', trans(this.availableWidth / 2, 35));
+ // Y
+ this.gyAxis.transition().call(this.yAxis);
- // Render grid
- gxGrid.selectAll('.gridline').data(x.ticks()).enter()
- .append('line')
+ this.gyAxis.selectAll('path')
+ .style('fill', 'none')
+ .style('stroke', '#444');
+
+ this.gyAxis.selectAll('text')
+ .style('fill', '#444');
+
+ this.gyAxisLabel
+ .attr('transform', trans(-45, this.availableHeight / 2) + ' rotate(-90)');
+
+
+ // Update grid
+ this.gxGridLines
+ .transition()
.attr({
- 'class': 'gridline',
x1: function (d) {
- return x(d)
+ return widget.x(d)
},
x2: function (d) {
- return x(d)
+ return widget.x(d)
},
- y1: y.range()[0],
- y2: y.range()[1]
+ y1: widget.y.range()[0],
+ y2: widget.y.range()[1]
});
- gyGrid.selectAll('.gridline').data(y.ticks()).enter()
- .append('line')
+ this.gyGridLines
+ .transition()
.attr({
- 'class': 'gridline',
- x1: x.range()[0],
- x2: x.range()[1],
+ x1: widget.x.range()[0],
+ x2: widget.x.range()[1],
y1: function (d) {
- return y(d)
+ return widget.y(d)
},
y2: function (d) {
- return y(d)
+ return widget.y(d)
}
});
+ };
- gGrid.selectAll('.gridline')
- .style('stroke', '#000')
- .style('stroke-opacity', 0.25);
-
-
- // Render info
- infoWrap
- .attr('transform', trans(-this.margin().left, -this.margin().top + 20));
-
- infoName
- .style('text-anchor', 'start')
- .style('font-weight', 'bold');
-
- infoMetrics
- .attr('transform', trans(0, 20));
- return this;
- };
SonarWidgets.BubbleChart.defaults = {
width: 350,
@@ -322,6 +378,7 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets;
};
+
// Some helper functions
// Gets or sets parameter