Skip to content

Commit

Permalink
Merge be62657 into 11a9b58
Browse files Browse the repository at this point in the history
  • Loading branch information
potatopeelings committed May 2, 2016
2 parents 11a9b58 + be62657 commit 18b22a4
Show file tree
Hide file tree
Showing 2 changed files with 293 additions and 2 deletions.
14 changes: 12 additions & 2 deletions docs/03-Bar-Chart.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ var myBarChart = new Chart(ctx, {
});
```

Or if you want horizontal bars.

```javascript
var myBarChart = new Chart(ctx, {
type: 'horizontalBar',
data: data,
options: options
});
```

### Data structure

```javascript
Expand Down Expand Up @@ -73,7 +83,7 @@ The label key on each dataset is optional, and can be used when generating a sca

These are the customisation options specific to Bar charts. These options are merged with the [global chart configuration options](#getting-started-global-chart-configuration), and form the options of the chart.

The default options for bar chart are defined in `Chart.defaults.Bar`.
The default options for bar chart are defined in `Chart.defaults.Bar`. The default options for horizontal bar charts are defined in `Chart.defaults.horizontalBar`.

Name | Type | Default | Description
--- |:---:| --- | ---
Expand Down Expand Up @@ -128,7 +138,7 @@ new Chart(ctx, {
// for both x and y axes.
```

We can also change these defaults values for each Bar type that is created, this object is available at `Chart.defaults.Bar`.
We can also change these defaults values for each Bar type that is created, this object is available at `Chart.defaults.Bar`. For horizontal bars, this object is available at `Chart.defaults.horizontalBar`.

#### barPercentage vs categoryPercentage

Expand Down
281 changes: 281 additions & 0 deletions src/controllers/controller.bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,285 @@ module.exports = function(Chart) {
}

});


// including horizontalBar in the bar file, instead of a file of its own
// it extends bar (like pie extends doughnut)
Chart.defaults.horizontalBar = {
hover: {
mode: "label"
},

scales: {
yAxes: [{
position: "left",
type: "category",

// Specific to Horizontal Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,

// grid line settings
gridLines: {
offsetGridLines: true
}
}],
xAxes: [{
type: "linear",
position: "bottom"
}],
},
};

Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
updateElement: function updateElement(rectangle, index, reset, numBars) {

var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID);

var xScalePoint;

if (xScale.min < 0 && xScale.max < 0) {
// all less than 0. use the right
xScalePoint = xScale.getPixelForValue(xScale.max);
} else if (xScale.min > 0 && xScale.max > 0) {
xScalePoint = xScale.getPixelForValue(xScale.min);
} else {
xScalePoint = xScale.getPixelForValue(0);
}

helpers.extend(rectangle, {
// Utility
_chart: this.chart.chart,
_xScale: xScale,
_yScale: yScale,
_datasetIndex: this.index,
_index: index,

// Desired view properties
_model: {
x: reset ? xScalePoint : this.calculateBarX(index, this.index),
y: this.calculateBarY(index, this.index),

// Tooltip
label: this.chart.data.labels[index],
datasetLabel: this.getDataset().label,

// Appearance
base: this.calculateBarBase(this.index, index),
height: this.calculateBarHeight(numBars),
backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor),
borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor),
borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth),
},

draw: function () {

var ctx = this._chart.ctx;
var vm = this._view;

var halfHeight = vm.height / 2,
topY = vm.y - halfHeight,
bottomY = vm.y + halfHeight,
right = vm.base - (vm.base - vm.x),
halfStroke = vm.borderWidth / 2;

// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if (vm.borderWidth) {
topY += halfStroke;
bottomY -= halfStroke;
right += halfStroke;
}

ctx.beginPath();

ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = vm.borderWidth;

// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[vm.base, bottomY],
[vm.base, topY],
[right, topY],
[right, bottomY]
];

// Find first (starting) corner with fallback to 'left'
var borders = ['left', 'top', 'right', 'bottom'];
var startCorner = borders.indexOf(vm.borderSkipped, 0);
if (startCorner === -1)
startCorner = 0;

function cornerAt(index) {
return corners[(startCorner + index) % 4];
}

// Draw rectangle from 'startCorner'
ctx.moveTo.apply(ctx, cornerAt(0));
for (var i = 1; i < 4; i++)
ctx.lineTo.apply(ctx, cornerAt(i));

ctx.fill();
if (vm.borderWidth) {
ctx.stroke();
}
},

inRange: function (mouseX, mouseY) {
var vm = this._view;
var inRange = false;

if (vm) {
if (vm.x < vm.base) {
inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base);
} else {
inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x);
}
}

return inRange;
}
});

rectangle.pivot();
},

calculateBarBase: function (datasetIndex, index) {

var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID);

var base = 0;

if (xScale.options.stacked) {

var value = this.chart.data.datasets[datasetIndex].data[index];

if (value < 0) {
for (var i = 0; i < datasetIndex; i++) {
var negDS = this.chart.data.datasets[i];
if (helpers.isDatasetVisible(negDS) && negDS.xAxisID === xScale.id) {
base += negDS.data[index] < 0 ? negDS.data[index] : 0;
}
}
} else {
for (var j = 0; j < datasetIndex; j++) {
var posDS = this.chart.data.datasets[j];
if (helpers.isDatasetVisible(posDS) && posDS.xAxisID === xScale.id) {
base += posDS.data[index] > 0 ? posDS.data[index] : 0;
}
}
}

return xScale.getPixelForValue(base);
}

base = xScale.getPixelForValue(xScale.min);

if (xScale.beginAtZero || ((xScale.min <= 0 && xScale.max >= 0) || (xScale.min >= 0 && xScale.max <= 0))) {
base = xScale.getPixelForValue(0, 0);
} else if (xScale.min < 0 && xScale.max < 0) {
// All values are negative. Use the right as the base
base = xScale.getPixelForValue(xScale.max);
}

return base;
},

getRuler: function () {

var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID);
var datasetCount = this.getBarCount();

var tickHeight = (function () {
var min = yScale.getPixelForTick(1) - yScale.getPixelForTick(0);
for (var i = 2; i < this.getDataset().data.length; i++) {
min = Math.min(yScale.getPixelForTick(i) - yScale.getPixelForTick(i - 1), min);
}
return min;
}).call(this);
var categoryHeight = tickHeight * yScale.options.categoryPercentage;
var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
var fullBarHeight = categoryHeight / datasetCount;
var barHeight = fullBarHeight * yScale.options.barPercentage;
var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);

return {
datasetCount: datasetCount,
tickHeight: tickHeight,
categoryHeight: categoryHeight,
categorySpacing: categorySpacing,
fullBarHeight: fullBarHeight,
barHeight: barHeight,
barSpacing: barSpacing,
};
},

calculateBarHeight: function () {
var yScale = this.getScaleForId(this.getDataset().yAxisID);
var ruler = this.getRuler();
return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
},

calculateBarX: function (index, datasetIndex) {

var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID);

var value = this.getDataset().data[index];

if (xScale.options.stacked) {
var sumPos = 0,
sumNeg = 0;

for (var i = 0; i < datasetIndex; i++) {
var ds = this.chart.data.datasets[i];
if (helpers.isDatasetVisible(ds) && ds.bar && ds.xAxisID === xScale.id) {
if (ds.data[index] < 0) {
sumNeg += ds.data[index] || 0;
} else {
sumPos += ds.data[index] || 0;
}
}
}

if (value < 0) {
return xScale.getPixelForValue(sumNeg + value);
} else {
return xScale.getPixelForValue(sumPos + value);
}

return xScale.getPixelForValue(value);
}

return xScale.getPixelForValue(value);
},

calculateBarY: function (index, datasetIndex) {

var yScale = this.getScaleForId(this.getDataset().yAxisID);
var xScale = this.getScaleForId(this.getDataset().xAxisID);
var barIndex = this.getBarIndex(datasetIndex);

var ruler = this.getRuler();
var topTick = yScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
topTick -= this.chart.isCombo ? (ruler.tickHeight / 2) : 0;

if (yScale.options.stacked) {
return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
}

return topTick +
(ruler.barHeight / 2) +
ruler.categorySpacing +
(ruler.barHeight * barIndex) +
(ruler.barSpacing / 2) +
(ruler.barSpacing * barIndex);
}
});
};

0 comments on commit 18b22a4

Please sign in to comment.