Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions core/webapp/vis/src/internal/D3Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1762,7 +1762,7 @@ LABKEY.vis.internal.D3Renderer = function(plot) {
y = plot.grid.height / 2;
} else if (name == 'x') {
x = plot.grid.leftEdge + (plot.grid.rightEdge - plot.grid.leftEdge) / 2;
y = plot.grid.height - (plot.labels[name].position != undefined ? plot.labels[name].position : 10);
y = plot.grid.bottomEdge + (plot.labels[name].position != undefined ? plot.labels[name].position : 50);
} else if (name == 'xTop') {
x = plot.grid.leftEdge + (plot.grid.rightEdge - plot.grid.leftEdge) / 2;
y = plot.grid.topEdge - (plot.labels[name].position != undefined ? plot.labels[name].position : 25);
Expand Down Expand Up @@ -1907,10 +1907,20 @@ LABKEY.vis.internal.D3Renderer = function(plot) {
var fontFamily = plot.fontFamily ? plot.fontFamily : 'Roboto, arial, helvetica, sans-serif';
selection.attr('font-family', fontFamily).attr('font-size', '11px');

xPad = plot.scales.yRight && plot.scales.yRight.scale ? 50 : 0;
glyphX = plot.grid.rightEdge + 30 + xPad;
textX = glyphX + 15;
yAcc = function(d, i) {return plot.grid.topEdge + (i * 15);};
if (plot.legendPos === 'bottom') {
// Render the legendTop relative to the xLabel position, which defaults to 50 below the grid.bottomEdge
const xLabelOffset = plot.labels.x.position ?? 50;
let legendTop = plot.grid.bottomEdge + xLabelOffset + 10;
glyphX = plot.grid.leftEdge + 5;
textX = glyphX + 15;
yAcc = function(d, i) {return legendTop + (i * 15);};
} else {
xPad = plot.scales.yRight && plot.scales.yRight.scale ? 50 : 0;
glyphX = plot.grid.rightEdge + 30 + xPad;
textX = glyphX + 15;
yAcc = function(d, i) {return plot.grid.topEdge + (i * 15);};
}

colorAcc = function(d) {
return d.color ? d.color : (d.separator ? '#FFF' : '#000');
};
Expand Down
29 changes: 19 additions & 10 deletions core/webapp/vis/src/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,39 +314,48 @@ boxPlot.render();
var margins = {}, top = 75, right = 75, bottom = 50, left = 75; // Defaults.
var foundLegendScale = false, foundYRight = false;

for(var i = 0; i < allAes.length; i++){
for (var i = 0; i < allAes.length; i++){
var aes = allAes[i];
if(!foundLegendScale && (aes.shape || (aes.color && (!scales.color || (scales.color && scales.color.scaleType == 'discrete'))) || aes.outlierColor || aes.outlierShape || aes.pathColor) && legendPos != 'none'){

if (!foundLegendScale && (aes.shape || (aes.color && (!scales.color || (scales.color && scales.color.scaleType === 'discrete'))) || aes.outlierColor || aes.outlierShape || aes.pathColor) && legendPos !== 'none'){
foundLegendScale = true;
right = right + 150;
}

if(!foundYRight && aes.yRight){
if (!foundYRight && aes.yRight){
foundYRight = true;
right = right + 25;
}
}

if (foundLegendScale) {
if (!legendPos || legendPos === 'right') {
right = right + 150;
} else if (legendPos === 'bottom') {
// The goal here is to net us space to render one item per color of our discrete color scale (8 items)
bottom = bottom += 170;
}
}

if(!userMargins){
userMargins = {};
}

if(typeof userMargins.top === 'undefined'){
if (typeof userMargins.top === 'undefined'){
margins.top = top + (labels && labels.subtitle ? 20 : 0);
} else {
margins.top = userMargins.top;
}
if(typeof userMargins.right === 'undefined'){
if (typeof userMargins.right === 'undefined'){
margins.right = right;
} else {
margins.right = userMargins.right;
}
if(typeof userMargins.bottom === 'undefined'){
if (typeof userMargins.bottom === 'undefined'){
margins.bottom = bottom;
} else {
margins.bottom = userMargins.bottom;
}
if(typeof userMargins.left === 'undefined'){
if (typeof userMargins.left === 'undefined'){
margins.left = left;
} else {
margins.left = userMargins.left;
Expand Down Expand Up @@ -918,7 +927,7 @@ boxPlot.render();
this.data = config.data ? config.data : null; // An array of rows, required. Each row could have several pieces of data. (e.g. {subjectId: '249534596', hemoglobin: '350', CD4:'1400', day:'120'})
this.layers = config.layers ? config.layers : []; // An array of layers, required. (e.g. a layer for a CD4 line chart over time, and a layer for a Hemoglobin line chart over time).
this.clipRect = config.clipRect ? config.clipRect : false;
this.legendPos = config.legendPos;
this.legendPos = config.legendPos ?? 'right';
this.legendNoWrap = config.legendNoWrap;
this.throwErrors = config.throwErrors || false; // Allows the configuration to specify whether chart errors should be thrown or logged (default).
this.brushing = ('brushing' in config && config.brushing != null && config.brushing != undefined) ? config.brushing : null;
Expand Down Expand Up @@ -1033,7 +1042,7 @@ boxPlot.render();
this.layers[i].render(this.renderer, this.grid, this.scales, this.data, this.aes, i);
}

if(!this.legendPos || (this.legendPos && !(this.legendPos == "none"))){
if (this.legendPos !== "none") {
this.renderer.renderLegend();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,10 @@ Ext4.define('LABKEY.ext4.GenericChartPanel', {
if (this.getCustomChartOptions)
config.customOptions = this.getCustomChartOptions();

// Apps can set the legendPos to "bottom", so we use the legendPos if it's set on the original config
if (this.savedReportInfo?.visualizationConfig?.chartConfig?.legendPos)
config.legendPos = this.savedReportInfo.visualizationConfig.chartConfig.legendPos;

return config;
},

Expand Down
66 changes: 44 additions & 22 deletions visualization/resources/web/vis/genericChart/genericChartHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ LABKEY.vis.GenericChartHelper = new function(){
layers = [], clipRect,
emptyTextFn = function(){return '';},
plotConfig = {
legendPos: chartConfig.legendPos,
renderTo: renderTo,
rendererType: 'd3',
width: chartConfig.width,
Expand Down Expand Up @@ -1184,7 +1185,18 @@ LABKEY.vis.GenericChartHelper = new function(){
scales.x.tickLabelMax = Math.floor((plotConfig.width - 300) / 30);
}

var margins = _getPlotMargins(renderType, scales, aes, data, plotConfig, chartConfig);
var wrapLines = _wrapXAxisTickTextLines(plotConfig, chartConfig, aes, scales, data);
var margins = _getPlotMargins(renderType, data, chartConfig, wrapLines);

if (wrapLines > 1) {
labels = {
...labels,
// x-axis position defaults to 50 (see D3Renderer.renderLabel) but if we're wrapping our tick labels
// then they will probably collide with the label, so we add 20 to hopefully not collide.
x: { value: labels.x.value, position: 70 }
}
}

if (LABKEY.Utils.isObject(margins)) {
plotConfig.margins = margins;
}
Expand Down Expand Up @@ -1440,37 +1452,47 @@ LABKEY.vis.GenericChartHelper = new function(){
});
};

var _wrapXAxisTickTextLines = function(scales, plotConfig, maxTickLength, data) {
if (scales.x && scales.x.scaleType === 'discrete') {
var tickCount = scales.x && scales.x.tickLabelMax ? Math.min(scales.x.tickLabelMax, data.length) : data.length;
var _wrapXAxisTickTextLines = function(plotConfig, chartConfig, aes, scales, data) {
if (LABKEY.Utils.isArray(data) && scales.x && scales.x.scaleType === 'discrete') {
let maxTickLength = 0;
$.each(data, function(idx, d) {
const val = LABKEY.Utils.isFunction(aes.x) ? aes.x(d) : d[aes.x];
const subVal = LABKEY.Utils.isFunction(aes.xSub) ? aes.xSub(d) : d[aes.xSub];
if (LABKEY.Utils.isString(subVal)) {
maxTickLength = Math.max(maxTickLength, subVal.length);
} else if (LABKEY.Utils.isString(val)) {
maxTickLength = Math.max(maxTickLength, val.length);
}
});

let tickCount = scales.x && scales.x.tickLabelMax ? Math.min(scales.x.tickLabelMax, data.length) : data.length;
// after 10 tick labels, we switch to rotating the label, so use that as the max here
tickCount = Math.min(tickCount, 10);
var approxTickLabelWidth = plotConfig.width / tickCount;
return Math.max(1, Math.floor((maxTickLength * 8) / approxTickLabelWidth));
const approxTickLabelWidth = plotConfig.width / tickCount;
return Math.max(1, Math.floor((maxTickLength * 12) / approxTickLabelWidth));
}

return 1;
};

var _getPlotMargins = function(renderType, scales, aes, data, plotConfig, chartConfig) {
var margins = {};
const chartConfigHasLegend = (chartConfig) => {
const { color, series, shape, xSub } = chartConfig.measures;
return !!(color || series || shape || xSub);
}

const _getPlotMargins = function(renderType, data, chartConfig, wrapLines) {
const margins = {};
const hasLegend = chartConfigHasLegend(chartConfig);

// issue 29690: for bar and box plots, set default bottom margin based on the number of labels and the max label length
if (LABKEY.Utils.isArray(data)) {
var maxLen = 0;
$.each(data, function(idx, d) {
var val = LABKEY.Utils.isFunction(aes.x) ? aes.x(d) : d[aes.x];
var subVal = LABKEY.Utils.isFunction(aes.xSub) ? aes.xSub(d) : d[aes.xSub];
if (LABKEY.Utils.isString(subVal)) {
maxLen = Math.max(maxLen, subVal.length);
} else if (LABKEY.Utils.isString(val)) {
maxLen = Math.max(maxLen, val.length);
}
});

var wrapLines = _wrapXAxisTickTextLines(scales, plotConfig, maxLen, data);
// min bottom margin: 50, max bottom margin: 150
margins.bottom = Math.min(150, 60 + ((wrapLines - 1) * 25));
if (chartConfig.legendPos === 'bottom' && hasLegend) {
// min bottom margin: 170, max bottom margin: 360
margins.bottom = Math.min(360, 170 + ((wrapLines - 1) * 25));
} else {
// min bottom margin: 80, max bottom margin: 150
margins.bottom = Math.min(150, 80 + ((wrapLines - 1) * 25));
}
}

// issue 31857: allow custom margins to be set in Chart Layout dialog
Expand Down