Reputation: 3608
i am trying to port an application from Flex 3
to Sencha Ext JS 4
. this application has a dashboard having a column chart (pleasee see first image below). the value for its xField
is somewhat a long text.
as much as possible, i don't want my label to be rotated. well, i think it's kinda messy. if possible, i want my label to be positioned alternately if it doesn't fit.
label : {
rotate : {degrees:45}
}
below image is my Sencha
custom chart. most of the Category
label are not shown.
i was thinking of customizing the onPlaceLabel
function but i don't know how to do so.
how am i gonna do this to achieve what i needed?
Upvotes: 1
Views: 4719
Reputation: 61
I had a similar problem with xaxis label text being too long. If shortening the visible label text and using tooltips for axis labels is an option for you then you might find the following useful...
My solution was:
Shorten the visible axis label text so that label will fit (and thus render)
Add a tooltip for the axis label sprite and make it display the full label text
Code Snippets:
Inside axis config...
label : {
renderer: function(v){
//if v is 'Grilled Pizza Special' then you get something like 'Grilled ...'
return Ext.String.ellipsis(v,12);
}
}
Modified drawHorizontalLabels()...
drawHorizontalLabels: function () {
var me = this,
labelConf = me.label,
floor = Math.floor,
max = Math.max,
axes = me.chart.axes,
insetPadding = me.chart.insetPadding,
position = me.position,
inflections = me.inflections,
ln = inflections.length,
labels = me.labels,
maxHeight = 0,
ratio,
bbox, point, prevLabel, prevLabelId,
adjustEnd = me.adjustEnd,
hasLeft = axes.findIndex('position', 'left') != -1,
hasRight = axes.findIndex('position', 'right') != -1,
textLabel, text,tooltipText,
last, x, y, i, firstLabel;
last = ln - 1;
//get a reference to the first text label dimensions
point = inflections[0];
firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
for (i = 0; i < ln; i++) {
point = inflections[i];
text = me.label.renderer(labels[i]);
//not using text variable above for tooltip as tooltip would render the ellipsis text rather than the full label text.
tooltipText = labels[i];
textLabel = me.getOrCreateLabel(i, text);
bbox = textLabel._bbox;
maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
x = floor(point[0] - (ratio ? bbox.height : bbox.width) / 2);
if (adjustEnd && me.chart.maxGutter[0] == 0) {
if (i == 0 && !hasLeft) {
x = point[0];
}
else if (i == last && !hasRight) {
x = Math.min(x, point[0] - bbox.width + insetPadding);
}
}
if (position == 'top') {
y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
}
else {
y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
}
textLabel.setAttributes({
hidden: false,
x: x,
y: y
}, true);
var labelTip = Ext.create('Ext.tip.ToolTip', {
target : textLabel.el,
html : tooltipText
});
// Skip label if there isn't available minimum space
if (i != 0 && (me.intersect(textLabel, prevLabel)
|| me.intersect(textLabel, firstLabel))) {
if (i === last && prevLabelId !== 0) {
prevLabel.hide(true);
} else {
textLabel.hide(true);
continue;
}
}
prevLabel = textLabel;
prevLabelId = i;
}
return maxHeight;
}
Upvotes: 0
Reputation: 25001
That's Ext.chart.axis.Axis#drawHoriztontalLabels
that you need to override. onPlaceLabel
is for the stuff written in the chart itself. Like labels at the top of the bar, you see what I mean?
The clean way to do that is to override the class and implement some jolly options. I'll let you explore the full code I've put at the end of the post if you want to understand the implementation...
Ext.define('Ext.ux.chart.axis.Axis.OverlappingLabelOptions', {
override: 'Ext.chart.axis.Axis'
,labelRows: 1
,hideOverlappingLabels: true
,drawHorizontalLabels: function() {
// ... see at the end of the post for the full implementation
}
});
Once you've included this override class using require
, you can use these two options in your axis definition. For example:
{
type: 'Category',
position: 'bottom',
fields: ['name'],
title: 'Sample Metrics',
labelRows: 5,
hideOverlappingLabels: false
}
My first try was to automatically push overlapping labels one row lower. That's what you obtain by setting labelRows: 'auto'
. It works well under certain conditions, and has the advantage of being automatic.
Unfortunately, it can get really messy:
So I resorted to implement an option that let you fix the number of label rows, and evenly distribute the labels among these rows. I also added the hideOverlappingLabels
that ensure that no label will be lost even if we miss luck and some label ends overlapping.
Here's what you get with this configuration:
{
// ...
hideOverlappingLabels: false,
labelRows: 5
}
I hope that will give you what you need, or at least the knowledge to bend Ext's code to your desires!
/**
* This override adds a {@link #labelRows} option to draw horizontal axis labels on multiple
* rows, and also an {@link #hideOverlappingLabels} option.
*/
Ext.define('Ext.ux.chart.axis.Axis.OverlappingLabelOptions', {
override: 'Ext.chart.axis.Axis'
,alternateClassName: 'Ext.ux.AxisOverlappingLabelOptions'
/**
* @cfg {Integer/String}
*
* Number of label rows. If this option is set to 'auto', then overlapping labels will
* be drawn on the next row where they don't overlap. Which can give a messy result.
*/
,labelRows: 1
/**
* @cfg {Boolean}
*
* Set to false to prevent automatic hiding of overlapping labels.
*/
,hideOverlappingLabels: true
,drawHorizontalLabels: function() {
var me = this,
labelConf = me.label,
floor = Math.floor,
max = Math.max,
axes = me.chart.axes,
insetPadding = me.chart.insetPadding,
gutters = me.chart.maxGutters,
position = me.position,
inflections = me.inflections,
ln = inflections.length,
labels = me.labels,
maxHeight = 0,
ratio,
bbox, point, prevLabel, prevLabelId,
adjustEnd = me.adjustEnd,
hasLeft = axes.findIndex('position', 'left') != -1,
hasRight = axes.findIndex('position', 'right') != -1,
textLabel, text,
last, x, y, i, firstLabel;
var labelRows = Ext.num(this.labelRows, 1),
autoOffsetLabels = this.labelRows === 'auto',
hideLabels = this.hideOverlappingLabels;
var lastLabelOnRow = [],
row, j;
last = ln - 1;
//get a reference to the first text label dimensions
point = inflections[0];
firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
for (i = 0; i < ln; i++) {
row = 0; // rx: start at first row
point = inflections[i];
text = me.label.renderer(labels[i]);
textLabel = me.getOrCreateLabel(i, text);
bbox = textLabel._bbox;
//maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
x = floor(point[0] - (ratio ? bbox.height : bbox.width) / 2);
if (adjustEnd && gutters.left == 0 && gutters.right == 0) {
if (i == 0 && !hasLeft) {
x = point[0];
}
else if (i == last && !hasRight) {
x = Math.min(x, point[0] - bbox.width + insetPadding);
}
}
if (position == 'top') {
y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
}
else {
y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
}
// rx: vertical offset
y += (i % labelRows) * bbox.height;
textLabel.setAttributes({
hidden: false,
x: x,
y: y
}, true);
if (autoOffsetLabels) {
// rx: find the row on which we can draw the label without overlapping
for (j=0; j<lastLabelOnRow.length; j++) {
if (me.intersect(textLabel, lastLabelOnRow[j])) {
row++;
textLabel.setAttributes({
y: y + row * bbox.height
}, true);
}
}
// rx: calc maxHeight knowing the row
maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding + (bbox.height * row));
// rx: keep reference to know where we can place the next label
lastLabelOnRow[row] = textLabel;
} else {
if (hideLabels) {
// Skip label if there isn't available minimum space
if (i != 0 && (me.intersect(textLabel, prevLabel)
|| me.intersect(textLabel, firstLabel))) {
if (i === last && prevLabelId !== 0) {
prevLabel.hide(true);
} else {
textLabel.hide(true);
continue;
}
}
}
maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding + bbox.height * (i % labelRows));
}
prevLabel = textLabel;
prevLabelId = i;
}
return maxHeight;
}
});
Upvotes: 5