# Thread: Column charts cannot handle negative values

Success! Looks like we've fixed this one. According to our records the fix was applied for CHARTS-201 in a recent build.
1. ## Column charts cannot handle negative values

REQUIRED INFORMATION

Ext version tested:

• Ext 4.0.6
Browser versions tested against:
• Chrome 13.0.782.220
Description:
• Column charts will not render correctly if negative y-values are specified
Steps to reproduce the problem:
• Create a column chart with all negative y-values, or both negative and positive y-values
The result that was expected:
• The base would always be zero.
• Negative values would chart down from zero like positive chart up from zero.
• With both positive and negative y-values, they would chart up and down.
• See screenshots below
Test Case:
Code:
```
window.generateDataNegative = function(n, floor){
var data = [],
p = (Math.random() *  11) + 1,
i;

floor = (!floor && floor !== 0)? 20 : floor;

for (i = 0; i < (n || 12); i++) {
data.push({
name: Ext.Date.monthNames[i % 12],
data1: Math.floor(((Math.random() - 0.5) * 100), floor),
data2: Math.floor(((Math.random() - 0.5) * 100), floor),
data3: Math.floor(((Math.random() - 0.5) * 100), floor),
data4: Math.floor(((Math.random() - 0.5) * 100), floor),
data5: Math.floor(((Math.random() - 0.5) * 100), floor),
data6: Math.floor(((Math.random() - 0.5) * 100), floor),
data7: Math.floor(((Math.random() - 0.5) * 100), floor),
data8: Math.floor(((Math.random() - 0.5) * 100), floor),
data9: Math.floor(((Math.random() - 0.5) * 100), floor)
});
}
return data;
};

window.storeNegatives = Ext.create('Ext.data.JsonStore', {
fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5', 'data6', 'data7', 'data9', 'data9'],
data: generateDataNegative()
});

Ext.create("widget.window", {
title: "Negatives broken",
modal: true,
width: 600,
height: 600,
layout: 'fit',
items: [{
xtype: 'chart',
animate: true,
store: storeNegatives,
axes: [{
type: 'Numeric',
position: 'left',
fields: ['data1'],
title: 'Hits',
//minimum: 0,
maximum: 0,
grid: true
}, {
type: 'Category',
position: 'bottom',
fields: ['name'],
title: 'Months',
label: {
rotate: {
degrees: 270
}
}
}],
series: [{
type: 'column',
axis: 'left',
gutter: 80,
xField: 'name',
yField: ['data1'],
tips: {
trackMouse: true,
width: 74,
height: 38,
renderer: function(storeItem, item) {
this.setTitle(storeItem.get('name') + '<br />' + storeItem.get('data1'));
}
},
style: {
fill: '#38B8BF'
}
}]
}]
}).show()```

Screenshot or Video:

All negative values, no maximum or minimum set, y-axis goes from -400 to -100, columns render "up" from -400 to -100
all-negative.jpg
All negative, 0 maximum set, axis goes from 0 to -400, columns do not render correctly
negative-0-max.jpg
All negative, 0 minimum set, y-axis goes from 0 to -100, columns do not render correctly
negative-0-minimum.jpg
Mixed positive and negative, no minimum or maximum set, y-axis goes from -4000 to 4000, but negative column doed not render correctly
mixed.jpg
Mixed, maximum set to 0, y-axis goes from 0 to -4000, but columns do not render correctly
mixed-0-maximum.jpg
Mixed, minimum set to 0, y-axis goes from 0 to -4000, but columns do not render correctly
File uploader will not allow me to attach image

See this URL for live test case: http://

• none
Possible fix:
• not provided
• only default ext-all.css (setting styles in code dynamically)
Operating System:
• Ubuntu 10.04

2. Attachment 28223
Final image (when I tried again using the "Basic Uploader" instead, it gave me an error letting me know I could only attach five images, so I just added it to a reply)

3. I'm having the same problem and can't figure out why. but..

When you go to : http://docs.sencha.com/ext-js/4-0/#!....series.Column and modify the example with some negative values in the data store, the chart display properly.

4. I can confirm that behaviour for Ext 4.0.6, the bug is not present in 4.0.2a that's why the example at the sencha docs page is still working (they are using 4.0.2a there).

I digged into the series-bar-chart-code and found a little change in the way the column height and maxY, minY gets calculated.

in 4.0.2a it is
Code:
```# line 240 Bar.js
if (me.axis) {
axis = chart.axes.get(me.axis);
if (axis) {
out = axis.calcEnds();
minY = out.from || axis.prevMin;
maxY = mmax(out.to || axis.prevMax, 0);
}
}

if (me.yField && !Ext.isNumber(minY)) {
axis = Ext.create('Ext.chart.axis.Axis', {
chart: chart,
fields: [].concat(me.yField)
});
out = axis.calcEnds();
minY = out.from || axis.prevMin;
maxY = mmax(out.to || axis.prevMax, 0);
}

# line 351 Bar.js
height = Math.round((yValue - ((bounds.minY < 0) ? 0 : bounds.minY)) * bounds.scale);```
In 4.0.6. it is
Code:
```# line 231 Bar.js

if (me.axis) {
axis = chart.axes.get(me.axis);
if (axis) {
out = axis.calcEnds();
minY = out.from;
maxY = out.to;
}
}

if (me.yField && !Ext.isNumber(minY)) {
axis = Ext.create('Ext.chart.axis.Axis', {
chart: chart,
fields: [].concat(me.yField)
});
out = axis.calcEnds();
minY = out.from;
maxY = out.to;
}

# line 342 Bar.js
height = Math.round((yValue - bounds.minY) * bounds.scale);```
I'm using a store which sends varying data, on some days the store contains negative values mixed with positive ones, on other days there might be only positive values. and the values also range from -300 up to 5000 and then will be filtered locally so I can't (and won't) use minimum and maximum.

The chart should be able to figure the scale and size of the chart by itself.

PS: when I use 4.0.2a the charts show up perfectly displaying negative and positive values as they should be. So I quite don't get the changes made in 4.0.6....

5. god, I had a test case in a sandbox which was working perfectly and the same chart that had trouble running in my app. I tried to reproduced the same environment in my sandbox and the chart always rendered perfectly. My sandbox was running 4.05 and my app is under 4.06.. broken under 4.06. Thanks Fschaeffer, I will stop wasting time trying to figuring this out!

Code:
```Ext.application({
name: 'ExtjJS Chart Test',
launch: function() {

Ext.define('testModel', {
extend: 'Ext.data.Model',
fields: [
{name: 'VOS', type: 'string'},
{name: 'DESC',type: 'string'},
{name: 'COST', type: 'float'}
]
});

var store = Ext.create('Ext.data.Store', {
model: 'testModel',
data: [
{"VOS": "CAS", "DESC": "CASE", "COST": 20896.17, "LIST": 76096.17},
{"VOS": "CHR", "DESC": "CHRYSLER", "COST": 138203.93, "LIST": 29096.17},
{"VOS": "DIV", "DESC": "DIVERS", "COST": 19064.31, "LIST": 1089.17},
{"VOS": "ENV", "DESC": "ENVIRONNEMENT", "COST": -33720.80, "LIST": 2096.17},
{"VOS": "FOR", "DESC": "FORD",  "COST": 157577.64, "LIST": 138744.17},
{"VOS": "FTL", "DESC": "FREIGHT",  "COST": 4.62, "LIST": -25336.17},
{"VOS": "GMC", "DESC": "GMC", "COST": 38363.97, "LIST": 16.17},
{"VOS": "HON", "DESC": "HONDA", "COST": 48891.65, "LIST": 35778.17},
{"VOS": "MIT", "DESC": "MITSUBISHI", "COST": 61735.00, "LIST": 56998.17},
{"VOS": "NAV", "DESC": "NAVISTAR",  "COST": 211176.33, "LIST": 1356.17},
{"VOS": "POR", "DESC": "PORSCHE",  "COST": 647.93, "LIST": 11526.17},
{"VOS": "UAP", "DESC": "UAP", "COST": 16.00, "LIST": 6585.17},
{"VOS": "VOL", "DESC": "VOLVO", "COST": 467762.71, "LIST": 896.17},
{"VOS": "VWC", "DESC": "VOLKSWAGEN",  "COST": 86843.94, "LIST": 20896.17},
{"VOS": "YAM", "DESC": "GH", "COST": -123.00, "LIST": -6555.45}
]
});

var win = Ext.create('Ext.window.Window', {
layout: 'fit',
title: 'test',
items: [
{
xtype: 'chart',
animate: true,
height: 500,
width: 500,
legend: {
position: 'right'
},
store: store,
axes: [
{
type: 'Numeric',
position: 'left',
fields: ['COST', 'LIST'],
label: {
renderer: Ext.util.Format.numberRenderer('0')
},
title: 'Price',
grid: true
},{
type: 'Category',
position: 'bottom',
fields: ['VOS'],
title: 'Value'
}
],
series: [
{
type: 'column',
axis: 'left',
highlight: true,
xField: 'VOS',
yField: ['COST', 'LIST'],
tips: {
trackMouse: true,
width: 250,
height: 40,
renderer: function(rec, item){
this.setTitle(rec.get('VOS') + ' = ' + rec.get('COST'));
}
}
}

]
}
]
});
win.show();
}
});```

6. I just made an override and replaced the 4.0.6. methods by the ones from 4.0.2a and the negative values are showing up as expected.

The problem is that there might be situations where one would not provide minimum and maximum. E.g. if I use a store providing data with varying values (negative and positive ones) I would expect that the chart configures itself to match all of my values and then draws the series.

If I provide minimum and maximum I sort of cut off the range of values the store could be holding for me.

In other situations it might be appropriate to just define a minimum value (lets say -100 to always display the negative part of an chart) without defininig a maximum (as I simply don't know how much revenue we will make on that day ....).

7. big thanks ! work perfectly with your override. Now, I hope sencha will notice this thread and fix for next release!

btw, if anyone wants the full override code
Code:
```Ext.override(Ext.chart.series.Bar, {
getBounds: function() {
var me = this,
chart = me.chart,
store = chart.substore || chart.store,
bars = [].concat(me.yField),
barsLen = bars.length,
groupBarsLen = barsLen,
groupGutter = me.groupGutter / 100,
column = me.column,
stacked = me.stacked,
barWidth = me.getBarGirth(),
math = Math,
mmax = math.max,
mabs = math.abs,
groupBarWidth, bbox, minY, maxY, axis, out,
scale, zero, total, rec, j, plus, minus;

me.setBBox(true);
bbox = me.bbox;

//Skip excluded series
if (me.__excludes) {
for (j = 0, total = me.__excludes.length; j < total; j++) {
if (me.__excludes[j]) {
groupBarsLen--;
}
}
}

if (me.axis) {
axis = chart.axes.get(me.axis);
if (axis) {
out = axis.calcEnds();
//             from 4.06
//             minY = out.from;
//             maxY = out.to;

//             new override
minY = out.from || axis.prevMin;
maxY = mmax(out.to || axis.prevMax, 0);
}
}

if (me.yField && !Ext.isNumber(minY)) {
axis = Ext.create('Ext.chart.axis.Axis', {
chart: chart,
fields: [].concat(me.yField)
});
out = axis.calcEnds();
//         from 4.06
//         minY = out.from;
//         maxY = out.to;

//             new override
minY = out.from || axis.prevMin;
maxY = mmax(out.to || axis.prevMax, 0);
}

if (!Ext.isNumber(minY)) {
minY = 0;
}
if (!Ext.isNumber(maxY)) {
maxY = 0;
}
scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;

if (stacked) {
total = [[], []];
store.each(function(record, i) {
total[0][i] = total[0][i] || 0;
total[1][i] = total[1][i] || 0;
for (j = 0; j < barsLen; j++) {
if (me.__excludes && me.__excludes[j]) {
continue;
}
rec = record.get(bars[j]);
total[+(rec > 0)][i] += mabs(rec);
}
});
total[+(maxY > 0)].push(mabs(maxY));
total[+(minY > 0)].push(mabs(minY));
minus = mmax.apply(math, total[0]);
plus = mmax.apply(math, total[1]);
scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
zero = zero + minus * scale * (column ? -1 : 1);
}
else if (minY / maxY < 0) {
zero = zero - minY * scale * (column ? -1 : 1);
}
return {
bars: bars,
bbox: bbox,
barsLen: barsLen,
groupBarsLen: groupBarsLen,
barWidth: barWidth,
groupBarWidth: groupBarWidth,
scale: scale,
zero: zero,
signed: minY / maxY < 0,
minY: minY,
maxY: maxY
};
},

getPaths: function() {
var me = this,
chart = me.chart,
store = chart.substore || chart.store,
bounds = me.bounds = me.getBounds(),
items = me.items = [],
gutter = me.gutter / 100,
groupGutter = me.groupGutter / 100,
animate = chart.animate,
column = me.column,
group = me.group,
bbox = bounds.bbox,
stacked = me.stacked,
barsLen = bounds.barsLen,
colors = me.colorArrayStyle,
colorLength = colors && colors.length || 0,
math = Math,
mmax = math.max,
mmin = math.min,
mabs = math.abs,
j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,

store.each(function(record, i, total) {
bottom = bounds.zero;
top = bounds.zero;
totalDim = 0;
totalNegDim = 0;
for (j = 0, counter = 0; j < barsLen; j++) {
// Excluded series
if (me.__excludes && me.__excludes[j]) {
continue;
}
yValue = record.get(bounds.bars[j]);
//                from 4.06
//                height = Math.round((yValue - bounds.minY) * bounds.scale);

//             new override
height = Math.round((yValue - ((bounds.minY < 0) ? 0 : bounds.minY)) * bounds.scale);
barAttr = {
fill: colors[(barsLen > 1 ? j : 0) % colorLength]
};
if (column) {
Ext.apply(barAttr, {
height: height,
width: mmax(bounds.groupBarWidth, 0),
x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
y: bottom - height
});
}
else {
// draw in reverse order
offset = (total - 1) - i;
Ext.apply(barAttr, {
height: mmax(bounds.groupBarWidth, 0),
width: height + (bottom == bounds.zero),
x: bottom + (bottom != bounds.zero),
y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
});
}
if (height < 0) {
if (column) {
barAttr.y = top;
barAttr.height = mabs(height);
} else {
barAttr.x = top + height;
barAttr.width = mabs(height);
}
}
if (stacked) {
if (height < 0) {
top += height * (column ? -1 : 1);
} else {
bottom += height * (column ? -1 : 1);
}
totalDim += mabs(height);
if (height < 0) {
totalNegDim += mabs(height);
}
}
barAttr.x = Math.floor(barAttr.x) + 1;
floorY = Math.floor(barAttr.y);
if (!Ext.isIE9 && barAttr.y > floorY) {
floorY--;
}
barAttr.y = floorY;
barAttr.width = Math.floor(barAttr.width);
barAttr.height = Math.floor(barAttr.height);
items.push({
series: me,
storeItem: record,
value: [record.get(me.xField), yValue],
attr: barAttr,
point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
[yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
});
// When resizing, reset before animating
if (animate && chart.resizing) {
attrs = column ? {
x: barAttr.x,
y: bounds.zero,
width: barAttr.width,
height: 0
} : {
x: bounds.zero,
y: barAttr.y,
width: 0,
height: barAttr.height
};
}
}
}
//update sprite position and width/height
sprite = group.getAt(i * barsLen + j);
if (sprite) {
sprite.setAttributes(attrs, true);
}
}
counter++;
}
if (stacked && items.length) {
items[i * counter].totalDim = totalDim;
items[i * counter].totalNegDim = totalNegDim;
}
}, me);
}
});```

8. bump, still a problem under 4.07

Proposed overwrite by fschaeffer still works.

9. thread marked as fixed, uner touch 2.0 ?!