Skip to content

Commit 4a12f03

Browse files
committed
stratification sunburn diagram handle large number of permutations
1 parent 0f94083 commit 4a12f03

File tree

2 files changed

+127
-31
lines changed

2 files changed

+127
-31
lines changed

web/src/main/webapp/resources/js/jqplot.donut.override.js

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
/**
2-
* Combined jqPlot Donut Overrides
3-
* Fixes:
4-
* 1. Continuous color consumption across rings.
5-
* 2. Comprehensive legend for all rings sorted by length.
6-
* 3. Respects numberColumns (multi-column support).
7-
* 4. Measurement and padding fixes to prevent cutoff.
2+
* jqPlot Donut & Legend Overrides
3+
* Fixes:
4+
* 1. Continuous color consumption across nested rings.
5+
* 2. Legend sorting (shortest first), truncation (max 20), and 2-column force.
6+
* 3. IndexSizeError fix for 6+ rings by clamping radius at 0.
7+
* 4. Dynamic thickness scaling to prevent ring collapse.
88
*/
99
(function($) {
1010
if ($.jqplot.DonutRenderer) {
1111
$.jqplot.DonutRenderer.prototype.draw = function (ctx, gd, options, plot) {
1212
var i, opts = (options != undefined) ? options : {}, offx = 0, offy = 0, trans = 1;
13+
14+
// Fix 1: Use the plot-level color generator for continuous colors across rings
1315
var colorGenerator = plot.colorGenerator;
16+
1417
if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
1518
var li = options.legendInfo;
1619
switch (li.location) {
@@ -24,26 +27,45 @@
2427
case 's': offy = li.height + li.yoffset; trans = -1; break;
2528
}
2629
}
27-
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
28-
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
30+
2931
var cw = ctx.canvas.width, ch = ctx.canvas.height;
3032
var w = cw - offx - 2 * this.padding, h = ch - offy - 2 * this.padding;
3133
var mindim = Math.min(w, h), d = mindim;
3234
var ringmargin = (this.ringMargin == null) ? this.sliceMargin * 2.0 : this.ringMargin;
35+
3336
for (i = 0; i < this._previousSeries.length; i++) {
3437
d -= 2.0 * this._previousSeries[i]._thickness + 2.0 * ringmargin;
3538
}
39+
3640
this._diameter = this.diameter || d;
41+
42+
// Fix 3 & 4: Improved thickness logic for many rings
3743
if (this.innerDiameter != null) {
3844
var od = (this._numberSeries > 1 && this.index > 0) ? this._previousSeries[0]._diameter : this._diameter;
3945
this._thickness = this.thickness || (od - this.innerDiameter - 2.0 * ringmargin * this._numberSeries) / this._numberSeries / 2.0;
4046
} else {
41-
this._thickness = this.thickness || mindim / 2 / (this._numberSeries + 1) * 0.85;
47+
// Scale thickness based on the number of rings to prevent IndexSizeError
48+
var availableRadius = mindim / 2;
49+
var totalRings = this._numberSeries;
50+
this._thickness = this.thickness || (availableRadius / (totalRings + 0.5)) * 0.8;
4251
}
52+
4353
var r = this._radius = this._diameter / 2;
44-
this._innerRadius = this._radius - this._thickness;
54+
55+
// Safety Clamp: Prevent negative radius error
56+
if (r < 0) { r = 0; this._radius = 0; }
57+
58+
this._innerRadius = r - this._thickness;
59+
60+
// Safety Clamp: Prevent inner radius from going negative
61+
if (this._innerRadius < 0) {
62+
this._innerRadius = 0;
63+
this._thickness = r;
64+
}
65+
4566
var sa = this.startAngle / 180 * Math.PI;
4667
this._center = [(cw - trans * offx) / 2 + trans * offx, (ch - trans * offy) / 2 + trans * offy];
68+
4769
if (this.shadow) {
4870
var shadowColor = 'rgba(0,0,0,' + this.shadowAlpha + ')';
4971
for (i = 0; i < gd.length; i++) {
@@ -57,14 +79,18 @@
5779
ang1 += this.sliceMargin / 180 * Math.PI;
5880
var ang2 = gd[i][1] + sa;
5981
this._sliceAngles.push([ang1, ang2]);
82+
83+
// Use the global colorGenerator
6084
var sliceColor = colorGenerator.next();
6185
this.renderer.drawSlice.call(this, ctx, ang1, ang2, sliceColor, false);
86+
6287
if (this.showDataLabels && gd[i][2] * 100 >= this.dataLabelThreshold) {
6388
var fstr, avgang = (ang1 + ang2) / 2, label;
6489
if (this.dataLabels == 'label') { fstr = this.dataLabelFormatString || '%s'; label = $.jqplot.sprintf(fstr, gd[i][0]); }
6590
else if (this.dataLabels == 'value') { fstr = this.dataLabelFormatString || '%d'; label = $.jqplot.sprintf(fstr, this.data[i][1]); }
6691
else if (this.dataLabels == 'percent') { fstr = this.dataLabelFormatString || '%d%%'; label = $.jqplot.sprintf(fstr, gd[i][2] * 100); }
6792
else if (this.dataLabels.constructor == Array) { fstr = this.dataLabelFormatString || '%s'; label = $.jqplot.sprintf(fstr, this.dataLabels[i]); }
93+
6894
var fact = this._innerRadius + this._thickness * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
6995
var lx = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
7096
var ly = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
@@ -76,27 +102,29 @@
76102
}
77103

78104
if ($.jqplot.DonutLegendRenderer) {
79-
var originalLegendInit = $.jqplot.DonutLegendRenderer.prototype.init;
80105
$.jqplot.DonutLegendRenderer.prototype.init = function(options) {
106+
this.numberRows = null;
107+
this.numberColumns = null;
81108
this.preDraw = true;
82-
originalLegendInit.call(this, options);
109+
$.extend(true, this, options);
83110
};
84111

85112
$.jqplot.DonutLegendRenderer.prototype.draw = function() {
86113
var legend = this;
87114
if (this.show) {
88115
var series = this._series;
116+
117+
// Fix 2: Retrieve limits from discovery path series[0]
118+
var maxItems = series[0].maxLegendItems || 0;
119+
var maxCols = series[0].maxLegendColumns || 1;
120+
89121
this._elem = $(document.createElement('table')).addClass('jqplot-table-legend');
90122
var ss = { position:'absolute' };
91-
if (this.background) { ss['background'] = this.background; }
92-
if (this.border) { ss['border'] = this.border; }
93-
if (this.fontSize) { ss['fontSize'] = this.fontSize; }
94-
if (this.fontFamily) { ss['fontFamily'] = this.fontFamily; }
95-
if (this.textColor) { ss['textColor'] = this.textColor; }
96-
if (this.marginTop != null) { ss['marginTop'] = this.marginTop; }
97-
if (this.marginBottom != null) { ss['marginBottom'] = this.marginBottom; }
98-
if (this.marginLeft != null) { ss['marginLeft'] = this.marginLeft; }
99-
if (this.marginRight != null) { ss['marginRight'] = this.marginRight; }
123+
if (this.background) ss.background = this.background;
124+
if (this.border) ss.border = this.border;
125+
if (this.fontSize) ss.fontSize = this.fontSize;
126+
if (this.fontFamily) ss.fontFamily = this.fontFamily;
127+
if (this.textColor) ss.textColor = this.textColor;
100128
if (this.placement === 'outsideGrid') { ss['white-space'] = 'nowrap'; ss['z-index'] = '101'; }
101129
this._elem.css(ss);
102130

@@ -117,34 +145,41 @@
117145
}
118146
}
119147
}
148+
149+
// Sort shortest to longest
120150
legendItems.sort(function(a, b) { return a.label.length - b.label.length; });
121151

122-
// --- START MULTI-COLUMN LOGIC ---
152+
// Truncate logic
153+
var wasTruncated = false;
154+
if (maxItems > 0 && legendItems.length > maxItems) {
155+
legendItems = legendItems.slice(0, maxItems);
156+
wasTruncated = true;
157+
}
158+
123159
var numItems = legendItems.length;
124-
var numCols = this.numberColumns || 1;
125-
var numRows = Math.ceil(numItems / numCols);
160+
var targetCols = wasTruncated ? maxCols : (this.numberColumns || 1);
161+
var numRows = Math.ceil(numItems / targetCols);
126162

127163
for (var rIdx = 0; rIdx < numRows; rIdx++) {
128164
var tr = $(document.createElement('tr')).addClass('jqplot-table-legend').appendTo(this._elem);
129-
for (var cIdx = 0; cIdx < numCols; cIdx++) {
130-
var k = rIdx + (cIdx * numRows); // Populate columns vertically first
165+
for (var cIdx = 0; cIdx < targetCols; cIdx++) {
166+
var k = rIdx + (cIdx * numRows);
131167
if (k < numItems) {
132168
var item = legendItems[k];
133169
var rs = (rIdx > 0) ? this.rowSpacing : '0';
134-
var ps = (cIdx > 0) ? '15px' : '0'; // Add horizontal spacing between columns
170+
var ps = (cIdx > 0) ? '15px' : '0';
135171

136172
var td1 = $(document.createElement('td')).addClass('jqplot-table-legend jqplot-table-legend-swatch').css({textAlign: 'center', paddingTop: rs, paddingLeft: ps});
137-
var div0 = $(document.createElement('div')).addClass('jqplot-table-legend-swatch-outline');
138-
var div1 = $(document.createElement('div')).addClass('jqplot-table-legend-swatch').css({backgroundColor: item.color, borderColor: item.color});
139-
td1.append(div0.append(div1)).appendTo(tr);
173+
var divOutline = $(document.createElement('div')).addClass('jqplot-table-legend-swatch-outline');
174+
var divSwatch = $(document.createElement('div')).addClass('jqplot-table-legend-swatch').css({backgroundColor: item.color, borderColor: item.color});
175+
td1.append(divOutline.append(divSwatch)).appendTo(tr);
140176

141177
var td2 = $(document.createElement('td')).addClass('jqplot-table-legend jqplot-table-legend-label').css({paddingTop: rs, paddingLeft: '5px'});
142178
if (this.escapeHtml) { td2.text(item.label); } else { td2.html(item.label); }
143179
td2.appendTo(tr);
144180
}
145181
}
146182
}
147-
// --- END MULTI-COLUMN LOGIC ---
148183
}
149184
return this._elem;
150185
};

web/src/main/webapp/resources/js/primefacesOverrides.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,7 +1823,68 @@ PrimeFaces.widget.LineChart = PrimeFaces.widget.BaseWidget.extend({
18231823

18241824
});
18251825

1826+
PrimeFaces.widget.DonutChart = PrimeFaces.widget.BaseWidget.extend({
1827+
1828+
init: function(cfg) {
1829+
this._super(cfg);
1830+
1831+
this.jqpId = this.id.replace(/:/g,"\\:");
1832+
var _self = this;
1833+
1834+
//renderer options
1835+
var rendererCfg = {
1836+
diameter : this.cfg.diameter,
1837+
sliceMargin : this.cfg.sliceMargin,
1838+
fill: this.cfg.fill,
1839+
shadow : this.cfg.shadow,
1840+
showDataLabels : this.cfg.showDataLabels,
1841+
dataLabels : this.cfg.dataFormat || "percent",
1842+
maxLegendItems: 45,
1843+
maxLegendColumns: 3
1844+
}
18261845

1846+
//renderer configuration
1847+
this.cfg.seriesDefaults = {
1848+
renderer: $.jqplot.DonutRenderer,
1849+
rendererOptions: rendererCfg
1850+
};
1851+
1852+
this.cfg.highlighter = {show:false}; //default highlighter
1853+
1854+
if(this.jq.is(':visible')) {
1855+
this.draw();
1856+
}
1857+
else {
1858+
var hiddenParent = this.jq.parents('.ui-hidden-container:first'),
1859+
hiddenParentWidget = hiddenParent.data('widget');
1860+
1861+
if(hiddenParentWidget) {
1862+
hiddenParentWidget.addOnshowHandler(function() {
1863+
return _self.draw();
1864+
});
1865+
}
1866+
}
1867+
},
1868+
1869+
draw: function(){
1870+
if(this.jq.is(':visible')) {
1871+
//events
1872+
PrimeFaces.widget.ChartUtils.bindItemSelectListener(this);
1873+
1874+
//highlighter
1875+
PrimeFaces.widget.ChartUtils.bindHighlighter(this);
1876+
1877+
//render chart
1878+
this.plot = $.jqplot(this.jqpId, this.cfg.data, this.cfg);
1879+
1880+
return true;
1881+
}
1882+
else {
1883+
return false;
1884+
}
1885+
}
1886+
1887+
});
18271888

18281889
/**
18291890
* PrimeFaces ProgressBar widget

0 commit comments

Comments
 (0)